AGB Music Player 2000 (M4A/MP2000/Sappy) Specification

Abstruct: this article explains about the structure of AGB Music Player 2000 (aka. M4A, MP2000, Sappy), which is the official GBA sound engine by Nintendo.

There are some differences among the different versions and customized engine of M4A. The most well-known format seems to be called Music Player 2000 v1.05.

本書はGBAの標準ドライバである AGB Music Player 2000 (別名 M4A, MP2000, Sappy) の音楽フォーマットの詳細を示します*1

バージョン違いや派生ドライバによってフォーマットに細かな違いはありますが、最もよく知られたフォーマットは Music Player 2000 v1.05 と呼ばれているようです。

ドキュメント内容の転載等は自由です。

TODO: This document is still work in progress. Add more content and improve accuracy of description.

Common Rules / 共通事項

Byte Order (Endianness)

All integers are little-endian.

Addressing (Absolute Offset) / アドレス表現

Addressing in M4A is very hardware-specific. M4A uses 32bit address here and there, to specify data location. They are raw GBA memory address. (See GBATEK - GBA Memory Map)

In short, you need to do "AND 0x1FFFFFFF" bitwise operation to the address, when you want to know the absolute data offset in ROM file. Addresses usually takes the form of 0x08XXXXXX, so that you need to remove unwanted higher 7 bits.

In this document, "address" means raw GBA address described above, and "offset" means relative offset from a specific location.

M4Aでは非常にハードウェア依存のアドレス表現がされています。データの位置を示すために各所で32bitのアドレスが用いられますが、それらは生のGBAのメモリアドレスです(参考:GBATEK - GBA Memory Map)。

端的に言うと、ROMファイルにおける絶対オフセットを得たい場合、アドレスに対して AND 0x1FFFFFFF のビット演算を施す必要があります。アドレスは通常 0x08XXXXXX の形を取るので、不要な上位7ビットを取り除く必要があります。

本書では、「アドレス」が上記のGBAアドレス表現を指し、「オフセット」が特定位置からの相対オフセットを指すものとします。

Structure / 構造

TODO

Voice Bytes / 演奏データ

M4A song sequence is somewhat similar to SMF (Standard MIDI File). It has events such as note on/off and control changes like volume, pan or whatever. It even has a rule which is very similar to the "running status" of SMF.

  • Ticks per quarter note is 24

M4Aの楽曲シーケンスはいくらかSMF(Standard MIDI File)に似ています。M4AにはSMFと同様にノートオン・ノートオフ、及びコントロールチェンジ相当のイベント(音量、パンなど)がありますし、ランニングステータスに相当するものもあります。

  • 分解能(4分音符あたりのtick数)は24
Mapping (AGB Music Player v1.05)
  • $00-7f - Parameter Byte
  • $80-bc - Fixed-Length Events
    • $80-b0 - Wait
    • $b1 - End of Track
    • $b2 - Jump
    • $b3 - Subroutine Jump
    • $b4 - Return From Subroutine
    • $b5 - Repeat
    • $b9 - Memory Access
    • $ba - Priority
    • $bb - Tempo
    • $bc - Per-Voice Transpose
    • $bd - Set Instrument
  • $bd-ff - Variable-Length Events (Status Byte)
    • $be - Volume
    • $bf - Pan
    • $c0 - Pitch Bend
    • $c1 - Pitch Bend Range
    • $c2 - LFO Speed
    • $c3 - LFO Delay
    • $c4 - Modulation Depth
    • $c5 - Modulation Type
    • $c8 - Micro Tuning
    • $cd - Extend Command
    • $ce - Note Off
    • $cf - Note On
    • $d0-ff - Single Note

Variants:

    • $b6 - End of Track (some new version uses $b6 instead of $b1? For instance, Metroid Fusion)
($00-7f) Parameter Byte

These bytes are parameter part of an event. Some events can omit their head byte (running status rule). Also, some events can omit their optional parameters.

The MSB of voice byte will tell whether it is an event byte, or a parameter byte. (i.e. how to know which parameter is omitted)

これらのバイトはイベントのパラメータの一部です。イベントによっては先頭のイベントバイトを省略できますし(ランニングステータスルール)、また、イベントによっては一部のパラメータを省略できます。

データの最上位ビットによってイベントかパラメータかの判断、すなわちどのパラメータが省略されたのかを知ることができます。

Running Status Rule / ランニングステータスルール

As stated above, some events can omit their head byte and optional parameters. The following table tells what one can do that.

上記のとおり、イベントによっては先頭バイトや一部のパラメータを省略することができます。下記の表にイベントごとの省略の可否を示します。

$00-7f Parameter byte. (パラメータバイト)
$80-bc Events which cannot omit the head byte. They are usually fixed-length event, and also can take $80-ff as parameter. (固定長、データ省略不可)
$bd-ff Events which can omit the head byte. They are variable-length event, and also can omit some optional parameters. (可変長、データ省略可)

As for variable-length events, its head byte can be omitted when the previous various-length event is the same event to the current one. Also, when an optional parameter (e.g. note number) is omitted, its previous value is used for the event.

可変長イベントに関しては、前回の可変長イベントが今回のイベントと同一である場合に先頭バイトを省略することができます。また、省略可能なパラメータが省略された際は、前回のイベントで指定された値が用いられます。

Example: both two data have the same effect.
(Data 1) BE 00, 81, BE 10, 81, BE 20, 81, BE 30, E5 3C 64 01, E5 3E 64 01, E5 40 7F 01
(Data 2) BE 00, 81,    10, 81,    20, 81,    30, E5 3C 64 01,    3E      ,    40 7F
($80-b0) Wait

A event to wait for a moment. In other words, this event specifies the delta-time until the next event.

Music player needs to decode the wait amount (in tick) from the event byte, by using the following delta-time length table.

待ち時間を発生させます。言い換えると、このイベントは次のイベントまでの待ち時間を示しています。

イベントバイトから待ち時間(tick単位)を取得するには、下記のデルタタイム長テーブルを使用します。

uint8_t M4AWaitTable[0x31] = {
     0,
     1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,
    17,18,19,20,21,22,23,24,28,30,32,36,40,42,44,48,
    52,54,56,60,64,66,68,72,76,78,80,84,88,90,92,96
};
uint8_t WaitTick = M4AWaitTable[vbyte - 0x80]; // vbyte = 80-b0
($b1) End of Track

This event stops the playing of the track.

イベントが出現したトラックの演奏を停止します。

($b2) Jump [address]
  • address (4bytes) = destination address
  • It is commonly used to make a infinite loop.

指定アドレスにジャンプします。よく楽曲のループに用いられます。

($b3) Subroutine Jump [address]
  • address (4bytes) = destination address
  • It can be nested up to three times.
  • See also: $b4

指定アドレスにサブルーチンジャンプします。$b4 でジャンプ元アドレスの次のイベントに戻ります。

($b4) Return From Subroutine
  • See also: $b3
  • It will be ignored if it is not being called from anywhere.

$b3 のサブルーチンジャンプから復帰します。サブルーチンで呼ばれていない場合は無視されます。

($b5) Repeat [xx address]
  • xx (unsigned 8bit) = loop count
    • if xx = 0, unconditional jump
  • address (4bytes) = destination address
  • It cannot be nested unlike subroutine jump.
  • For instance, it appears in Winning Post.

Repeats from the specified address, xx number of times.

($b9) Memory Access [op xx yy] (v1.03+)

This event can access to the memory area specially prepared for.

  • Memory Access Area size is depending on games. It can be between 0~255 bytes.
  • op (unsigned 8bit) = operator type
  • xx (unsigned 8bit) = operand #1
  • yy (unsigned 8bit) = operand #2
op Summary Description
0 set(mem,imm)
1 add(mem,imm)
2 sub(mem,imm)
3 set(mem,mem)
4 add(mem,mem)
5 sub(mem,mem)
6 beq(mem,imm)
7 bne(mem,imm)
8 bhi(mem,imm)
9 bhs(mem,imm)
10 bls(mem,imm)
11 blo(mem,imm)
12 beq(mem,mem)
13 bne(mem,mem)
14 bhi(mem,mem)
15 bhs(mem,mem)
16 bls(mem,mem)
17 blo(mem,mem)

TODO: learn more details

($ba) Priority [xx]
  • xx (unsigned 8bit) = priority level (added to the priority of a song)
($bb) Tempo [xx]
  • xx (unsigned 8bit) = half of bpm

楽曲の演奏速度を指定します。bpm/2 の値で指定します。

($bc) Per-Voice Transpose [xx]
  • xx (signed 8bit) = semitones

単一チャンネルの演奏音程をxx上げます(負数も指定可)。

複数個配置されている場合、前回の指定値による影響は受けないため、半音上げた後にさらに半音上げたい場合には 2 を指定します。

($bd) Set Instrument [xx]
  • xx (unsigned 7bit) = voice number

音色を設定します。

($be) Volume [xx]
  • xx (unsigned 7bit) = volume

チャンネルの音量を設定します。

($bf) Pan [xx]
  • xx (unsigned 7bit) = pan (0 = left, 64 = center, 127 = right)

チャンネルのパンを設定します。

($c0) Pitch Bend [xx]
  • xx (signed 7bit) = pitch (-64 = lowest, 63 = highest)
  • See also: $c1

音程の高低を変化させます。滑らかに音程を変化させたいときに使用します。MIDIイベントは -8192〜8191 の範囲を取りますが、M4A では -64〜63 の範囲を取ります。

音程の変化量は $c1 で指定します。

($c1) Pitch Bend Range [xx]
  • xx (unsigned 7bit) = maximum pitch change amount (semitones)
  • See also: $c0

$c0 による音程変化の最大幅を半音単位で指定します。

($c2) LFO Speed [xx]
  • xx (unsigned 7bit) = LFO speed (higher = faster)
  • See also: $c3, $c4, $c5

LFOの速さを指定します。値が大きいほど変化が速くなります。

($c3) LFO Delay [xx]
  • xx (unsigned 7bit) = delay until LFO start (tick)
  • See also: $c2, $c4, $c5

LFOがかかるまでの時間をtick単位で指定します。

($c4) Modulation Depth [xx]
  • xx (unsigned 7bit) = modulation depth
  • See also: $c2, $c3, $c5

モジュレーションの深さを指定します。

($c5) Modulation Type [xx]
  • xx (unsigned 7bit) = modulation type
  • See also: $c2, $c3, $c4
xx Description
0 Vibrato (Pitch Change, default)
1 Tremolo (Volume Change)
2 Pan (Position Change)

モジュレーションのタイプを設定します。

($c8) Micro Tuning [xx]
  • xx (signed 7bit) = pitch (-64 = -1 semitone, 63 = +1 semitone)

音程をわずかに変化させます。Detune などに利用します。

($cd) Extend Command [op xx]
  • op (unsigned 7bit) = operation type
  • xx (7bit) = parameter
op Description
$08 Echo Volume (xx = 0 ~ 127)
$09 Echo Length (xx = 0 ~ 127)
($ce) Note Off [xx]
  • xx (unsigned 7bit) = note number (C-2 ~ G8)
  • See also: $cf, $d0-ff

$cf で発音されたノートの発音を停止します。

($cf) Note On [xx yy]
  • xx (unsigned 7bit) = note number (C-2 ~ G8)
  • yy (unsigned 7bit) = velocity (per-note volume)
  • Velocity can be omitted. The previous value is used instead of the omitted parameter.
    • Previous velocity is shared by both $cf and $d0-ff.
  • See also: $ce, $d0-ff

ノートの発音を開始します。このイベントは主に長いノートに使用されます。

パラメータが省略された場合、前回のイベントの値を使用します。

($d0-ff) Single Note [xx yy zz]
  • xx (unsigned 7bit) = note number (C-2 ~ G8)
  • yy (unsigned 7bit) = velocity (per-note volume)
  • zz (unsigned 7bit) = gate+ (tick, 1 ~ 3)
  • Velocity can be omitted. The previous value is used instead of the omitted parameter.
    • Previous velocity is shared by both $cf and $d0-ff.
  • See also: $80, $ce, $d0-ff

This event utters a single note, (and wait for its end?).

The length of note is determined by the head byte value and length table (see $80-b0), and gate+ parameter.

単一のノートを発音し、その長さ分の時間待ちを行います(?)。

ノートの長さは先頭バイトの値と長さテーブル($80-b0 参照)、及び gate+ パラメータによって決定されます。下記に擬似コードを示します。

uint8_t M4AWaitTable[0x31] = {
     0,
     1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,
    17,18,19,20,21,22,23,24,28,30,32,36,40,42,44,48,
    52,54,56,60,64,66,68,72,76,78,80,84,88,90,92,96
};
uint8_t NoteWaitTick = M4AWaitTable[vbyte - 0xcf] + GatePlus; // vbyte = d0-ff

Special Thanks

  • DJ Bouche - for making initial version of Sappy
  • Kyoufu Kawa - for making newer version of Sappy
  • Author of VGMTrans - for providing me a lot of resources and motivations, I want to give you a ton of delicious SPAM :)
  • Several Anonymous People - for providing some interesting informations

*1:過去にそのようなドキュメントを記したことがありますが、記述が稚拙であったり、解析の踏み込みが甘かったり、派生ドライバの情報が区別なく混在していたりとわかりにくいため、改めて取りまとめようと考えました。