SNES Nintendo Music Format Spec

Abstruct: this article explains the outline of the structure of SNES Nintendo Seq. The format, and its variants, are commonly used in many games (not only Nintendo games, actually).

悟茶辞苑ッにあるSMW音楽フォーマットの内容を、同系統のエンジン全般に拡張してまとめたいと思います。これらのエンジン(とその変種)は、Super MarioやZelda、Metroidといった任天堂の有名タイトルはもちろん、Gradius 3やLemmings、ルパン三世奇々怪界など、他メーカーのタイトルにも広い使用が確認されています。

Structure / 構造


; song list
feff: dw $ffff,$a000,$a200,$a400,...
; block list of song $01
a000: dw $b000
a002: dw $b010
a004: dw $0003,$b020 ; $0001-$007f - play for 4 (3+1) times
a006: dw $00ff,$a002 ; $0080-$00ff - goto $a002 (loop)
a008: dw $0000       ; end of song (shouldn't come here, though)
; block - starting point for each track ($00xx for null)
b000: dw $c000,$c100,$c200,$c300,$0000,$0000,$0000,$0000
; sequence
c000: ; voice cmds to playback...

どんな形で演奏データが表現されるのか、アセンブリで擬似的に示してみました。多くのゲームでは標準MIDIファイルのように、最初にデータの先頭位置を指定したらあとはひたすら演奏するのみかと思いますが、このゲームでは上位に小区間演奏データを束ねる形で曲データを表現します。繰り返しも簡単な制御構造によってそこで記述されています。ブロックの終端は演奏データ側で示されます。詳細は後述。

なお、通常は繰り返し回数がマイナスであれば goto なのですが、ゲームによっては $80,$81 のあたりを特別扱いしている感じも見られました。このあたりに関してはまだよく調べていません。

拙作のnintspcは、html出力の先頭にこれらの演奏制御情報を出力します。

Version/Variation of the Format / フォーマットの種類

  • Old Version (has 24 vcmds ($da-f2), used by Super Mario World & Pilotwings)
  • Basic Version (has 27 vcmds (usually $e0-fa, used by a lot of games, Yoshi's Island for example)
  • Extended Version (based on Basic Version)
    • may have additional vcmds (Super Metroid, Marvelous, Intelligent Systems games, etc.)
    • vcmds may start from not $e0 (Intelligent Systems games, Fire Emblem for example)
    • some of its vbytes may work differently from Basic Version (Intelligent Systems games, Lemmings)

$e0-faの27コマンドを持っているものが基本的です。Super Mario World と Pilotwings に使われている初期のエンジンは少しコマンドが少ないですが、内容は対応します。その他、いくらか標準のものをベースにコマンド追加、あるいはそれ以上の拡張をしているものがあります。ちなみに、拡張コマンドの中には可変長のものもありました。

どれを扱うにしても、ひとまず基本的なタイプの構成を知らなければなりません。本稿では以降、基本タイプのコマンドについて解説したあと、少しだけ拡張の例について触れたいと思います。

vcmd map (basic version)
  • $00 - end/return
  • $01-7f - note params
  • $80-c7 - note
  • $c8 - tie
  • $c9 - rest
  • $ca-df - percussion note
  • $e0 - set instrument
  • $e1 - pan
  • $e2 - pan fade
  • $e3 - vibrato on
  • $e4 - vibrato off
  • $e5 - master volume
  • $e6 - master volume fade
  • $e7 - tempo
  • $e8 - tempo fade
  • $e9 - global transpose
  • $ea - per-voice transpose
  • $eb - tremolo on
  • $ec - tremolo off
  • $ed - volume
  • $ee - volume fade
  • $ef - call subroutine
  • $f0 - vibrato fade
  • $f1 - pitch envelope (to)
  • $f2 - pitch envelope (from)
  • $f3 - pitch envelope off
  • $f4 - tuning
  • $f5 - echo vbits/volume
  • $f6 - echo off
  • $f7 - echo params
  • $f8 - echo volume fade
  • $f9 - pitch slide
  • $fa - percussion patch base
  • $fb-ff - undefined (out of table)
vcmd map (old version)
  • $00 - end/return
  • $01-7f - note params
  • $80-c5 - note
  • $c6 - tie
  • $c7 - rest
  • $c8-cf - unused (may work as a rest?)
  • $d0-d9 - percussion note
  • $da - set instrument
  • $db - pan
  • $dc - pan fade
  • $dd - pitch slide
  • $de - vibrato on
  • $df - vibrato off
  • $e0 - master volume
  • $e1 - master volume fade
  • $e2 - tempo
  • $e3 - tempo fade
  • $e4 - global transpose
  • $e5 - tremolo on (buggy)
  • $e6 - tremolo off
  • $e7 - volume
  • $e8 - volume fade
  • $e9 - call subroutine
  • $ea - vibrato fade
  • $eb - pitch envelope (to)
  • $ec - pitch envelope (from)
  • $ed - (undefined) pitch envelope off (code exists, but never reached)
  • $ee - tuning
  • $ef - echo vbits/volume
  • $f0 - echo off
  • $f1 - echo params
  • $f2 - echo volume fade
  • $f3-ff - undefined (out of table)

Voice Bytes / 演奏データ

"($vbyte) Name [arguments] Description..."

($00) End/Return
  • "end" of block, or "return" from subroutine (see also: $ef).
  • when a voice reaches "end", all voices end up then go to next block. "return" works independently per voice.

演奏データの終端を意味します。サブルーチンコマンドで呼び出されてきたのであればサブルーチンの終端を、そうでないならブロックの終端を意味します。

ブロックの終端に達した場合、他のブロックも同じタイミングで演奏を中断し、次のブロックに移ります(繰り返し指定がなければ)。これに対して、サブルーチンからの復帰はチャンネル別に独立しておこなわれます。

($01-7f) Note Parameters [(xy)]
  • vbyte itself means the length of the following note (48 = quarter note, usually).
  • take 1 argument $xy, when $xy < $80. $x = duration rate (0-7), $y = velocity rate (0-15).
  • some games (Fire Emblem, Lemmings) takes dur/vel info in different manner, be careful.

コマンドバイト自身で音の長さを表します。48で四分音符だと言われていますが、あまり気にしなくていいです。

続くバイト($xyとする)が$80未満であればそれも処理します。xで音の長さ(クオンタイズ)、yで音の大きさを段階的に指定します。数値に対応してどのような比率が割り当てられているのかは、中のテーブルを見てください(拙作nintspcはテーブルの位置を自動検索します)。

($80-c7) Note, ($c8) Tie, ($c9) Rest
  • vbyte itself means tone of the note.
  • Rest stops uttering sound, Tie keeps.

tone map (some instruments might be tuned differently)

c c+ d d+ e f f+ g g+ a a+ b
o1 80 81 82 83 84 85 86 87 88 89 8a 8b
o2 8c 8d 8e 8f 90 91 92 93 94 95 96 97
o3 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3
o4 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af
o5 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb
o6 bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7

音程を伴って音符を意味します。長さや強さは$01-7fで設定します。

($ca-df) Percussion Note
  • vbyte itself means percussion #. Relations between percussion # and SRCN depends on $fa.

音程を持たないパーカッション類を鳴らします。コマンドと音色番号(SRCN)の関係は$faで設定することができます。

($e0) Set Instrument [xx]
  • xx = voice number (SRCN and ADSR is stored in a table, xx is index for it.)
    • when SRCN >= $80, the music engine sets "noise sound" vbit on, then uses lower-5bit for its clock. (old version doesn't have such function)

音色を設定します。実際に音源に設定されるSRCNやADSRの情報は内部のテーブルに収められていて、このコマンドで指定するのはそのテーブルのインデックスです。

テーブル内部のSRCN情報が$80以上のときは、音源にノイズフラグを立てて、下位5ビットをそのクロックに設定します(古いバージョンにはそのような機能はない)。

($e1) Pan [xx]
  • lower-5bit for pan value (0-20), higher-2bit is used for phase reverse switch.
  • some versions (for example, Super Mario World, F-Zero and Yoshi Islands) treat larger value as left, some versions not (for example, Super Mario All-Stars and Kirby Super Star).

音の左右位置を設定します。値の範囲は0〜20と狭いです(実際の設定比率は内部テーブル定義)。バージョンによって、大きい値が左なのか右なのか異なります。上位2ビットは位相反転に用いられます。

($e2) Pan Fade [xx yy]

音の位置をxx時間かけて現在の値からyyへとフェード。

($e3) Vibrato On [xx yy zz]
  • xx = delay, yy = rate, zz = depth.

ビブラート(音程の揺れ)を有効にします。発音からxx時間後に、yyの早さでzzの大きさのビブラートがかかるよう設定します。

($e4) Vibrato Off

ビブラートを無効にします。具体的にはVibrato Depthを0にしています。

($e5) Master Volume [xx]

楽曲全体の音量をxxに設定します。初期値は最大値より幾分低い。

($e6) Master Volume Fade [xx yy]

楽曲全体の音量をxx時間かけて現在の値からyyへとフェード。

($e7) Tempo [xx]
  • x = speed (about 24/60 of beat per minutes).

楽曲の演奏速度を指定します。bpmとの正確な対応はよくわかりませんが、およそ24/60の値が書かれています。

($e8) Tempo Fade [xx yy]

楽曲の演奏速度をxx時間かけて現在の値からyyへとフェード。

($e9) Global Transpose [xx]
  • xx = semitones (signed)

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

($ea) Per-Voice Transpose [xx]
  • xx = semitones (signed)

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

($eb) Tremolo On [xx yy zz]
  • xx = delay, yy = rate, zz = depth.
  • As far as I know, Super Mario World cannot handle tremolo correctly.

トレモロ(音量の揺れ)を有効にします。発音からxx時間後に、yyの早さでzzの大きさのトレモロがかかるよう設定します。

($ec) Tremolo Off

トレモロを無効にします。

($ed) Volume [xx]

チャンネルの音量を設定。初期値は$ff(最大値)。

($ee) Volume Fade [xx yy]

チャンネルの音量をxx時間かけて現在の値からyyへとフェード。

($ef) Call Subroutine [xx yy zz]
  • play block $yyxx for zz+1 times.
  • subroutine call cannot be nested.
  • see also: $00

ブロック$yyxxの演奏をzz+1回繰り返します。サブルーチンはネスト不可です。

($f0) Vibrato Fade [xx]
  • xx = length, fade vibrato depth from 0 to current value.

ビブラートの深さを一時的に0に設定したのち、xx時間かけて滑らかに元の値へと変化させます。

($f1) Pitch Envelope (To) [xx yy zz]
  • xx = delay, yy = length, zz = key (signed).
  • sort of "do pitch slide for all of the following notes" vcmd.
  • utter a note normally, wait for xx ticks, then fade the key to zz semitones higher (lower, when it's a minus number) for yy ticks.

以降の音が「xx時間後にyy時間かけてzzだけ高くなる」ように指定します。zzは半音単位で、負数も指定できます。

($f2) Pitch Envelope (From) [xx yy zz]
  • xx = delay, yy = length, zz = key (signed).
  • sort of "do pitch slide for all of the following notes" vcmd.
  • utter a note from higher (lower, when it's a minus number) than zz semitones, wait for xx ticks, then fade the key to normal pitch for yy ticks.

以降の音が「通常よりzz高い音程で発音、xx時間後にyy時間かけて通常の音程に戻る」ように指定します。zzは半音単位で、負数も指定できます。

($f3) Pitch Envelope Off

Pitch Envelopeの効果を無効にします。

($f4) Tuning [xx]
  • xx = unsigned, make the pitch xx/256 semitones higher.

音程をわずかに変化させます。指定できる値は正の数のみなので、音程を下げることはできません。

($f5) Echo vbits/volume [xx yy zz]
  • xx = echo switch (EON), yy = echo left volume (EVOL(L)), zz = echo right volume (EVOL(R)).

エコーを有効にするチャンネルと音量を指定します。レジスタに設定される値そのままです。

($f6) Echo Off

エコーを無効にします。

($f7) Echo Parameters [xx yy zz]
  • xx = echo delay (EDL), yy = echo feedback (EFB), zz = echo filter (index for table, 0-3 in most cases).

エコーの詳細を設定します。xxとyyはレジスタの値そのまま、zzはFIRフィルタの種類で、だいたい0〜3の範囲で指定します。

($f8) Echo Volume Fade [xx yy zz]

エコーの音量をxx時間かけて現在の値から指定した値へとフェード。yyとzzで左右別々に値を指定。

($f9) Pitch Slide [xx yy zz]
  • xx = delay, yy = length, zz = note vbyte.
  • this vcmd works only for current note, not for all of the following notes.
  • old version doesn't have this vcmd in address table, but it's certainly handled specially.

This vcmd is handled at different timing than other vcmds.

; utter note $90, (wait), set instrument to $01, utter note $92
$90, $e0 $01, $92
; utter note $90 then change the key immidiately to note $91, (wait),
; utter note $92.
$90, $f9 $00 $01 $91, $92
; utter note $90 then change the key immidiately to note $91, (wait), 
; set instrument to $01, utter note $92.
$90, $f9 $00 $01 $91, $e0 01, $92
; <weird example> utter note $90, (wait), set instrument to $01,
; (pitch slide vcmd appears but note $90 has been end), utter note $92.
$90, $e0 01, $f9 $00 $01 $91, $92

発音中の音の音程を滑らかに変化させます。発音からxx時間後に、yy時間かけてzzの音程(絶対的指定)に変化させます。ひとつの音の途中で上げたり下げたりしたい場合、連続してこのコマンドを記述します。発音時間が長い場合はタイを交えて記述すればよいです。

普通はNoteの出現後は、音符の長さだけ待ってから次のバイトを処理しますが、このコマンドだけは即時に読み込まれて処理されます。扱いの特殊さゆえか、スーパーマリオワールドではこのコマンドは完全に処理テーブル外で処理されています。

($fa) Percussion Patch Base [xx]
  • xx = instrument number.
  • Kirby Super Star has $fa, but it does nothing actually.

パーカッションの値と鳴らす音の対応を決めます。

About Extended Versions

拡張に関しては、あまり調べていないこともあって多くは書けません。

  • Super Metroid, Earthbound等は同種の拡張コマンド4つを持っている(Zeldaにもコードはあるようだが、テーブルに書かれていないため使えない)。
  • Intelligent Systems開発のゲームは可変長のコマンドを持っていたりする。ちょっとコンバータ泣かせ。
  • Fire Emblemなどは、通常とは異なる方法でのクオンタイズ・ベロシティ指定がある。Lemmingsも同様(規則は異なる)。
  • MarvelousにはADSRを設定するコマンドがあった。
  • Gradius 3のようなコナミの初期のタイトルは、若干改変されたこのサウンドエンジンが用いられている。
(Marvelous $fb) Set Instrument with ADSR [xx yy zz]
  • xx = voice number, yy = ADSR(1), zz = ADSR(2).
  • overwrites ADSR parameter in instrument table, therefore default ADSR will be changed, perhaps.

テーブルに書かれている楽器xxのADSRを書き換えつつ、音色を設定します。テーブルの内容を書き換えてしまうため、以降の音色指定時のデフォルトADSRも伴って変化すると思われます。

(Lemmings $01-7f) Note Parameters [(xx) (yy)]
  • (when xx < $80) xx = duration rate. actual rate seems to be "(byte) ( (xx << 1) + (xx & 1) + (xx >> 1) )" (approx percent?)
  • (when yy < $80) yy = velocity rate. actual rate = yy << 1.

概念は似ているのですが、クオンタイズとベロシティの情報を別々、直接に設定します。引数の数に違いが生じ得ます。

(Fire Emblem $01-7f) Note Parameters [(xx) (yy)]
  • (xx < $40 && xx < $80) : lower 6-bits for duration rate (index)
  • (yy >= $40 && yy < $80) : lower 6-bits for velocity rate (index)
  • to know how to handle this vcmd more precisely, see nintspc source code.

値が続くとき、$40未満であればクオンタイズを、$40以上であればベロシティを、下位6ビットから設定します。両者を一度に設定することもあります。

では、たとえば $30 $40 $50 と3つ値が続いてしまった場合はどうなるのでしょうか。この答えはちょっと示すのが面倒なので、割愛します。