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ビットを取り除く必要があります。


Structure / 構造


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


    • $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.



uint8_t M4AWaitTable[0x31] = {
     1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,
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


複数個配置されている場合、前回の指定値による影響は受けないため、半音上げた後にさらに半音上げたい場合には 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


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


($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] = {
     1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,
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