SNES "Dragon Quest VI / III" Heart Beat Format Spec

Abstruct: this article explains about the structure of SNES DQ6/DQ3 Seq. The format is actually a sort of Nintendo SPC variants (there are much differences between this engine and basic N-SPC format, though).

ドラゴンクエスト6ドラゴンクエスト3の音楽フォーマットのコマンドについて書き記しておきます。hbdqspcを作ってから半年経った今ようやく気がついたことには、これはSNES Nintendo Music Format Specに記した任天堂標準フォーマットの変種です。とはいえ、その構造やコマンドは結構大幅に異なっているので、同一のフォーマットとして取り扱うのはあまり賢明ではありません。

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

Structure / 構造

; song/sfx list (16 entries?)
f000: db $00,$00 ; lo-byte
f00c: db $80,$90 ; hi-byte
; seq header
8000: dw $000a ; instrument table offset
8002: dw $0016,$0200,$0300 ; start pos of voice stream for each track
8008: dw $0000 ; no more tracks
; instrument table (1 entry = 6 bytes)
800a: db $xx,$xx,$xx,$xx,$xx,$xx
8010: db $xx,$xx,$xx,$xx,$xx,$xx
; voice stream
8016: db ...

めんどくさがって疑似アセンブリの内容が幾分適当ですが……非常にシンプルで、かつまとまった構造をしています。ARAM上の絶対アドレスで位置を表現するフォーマットは多いですが、このエンジンでは、最初の楽曲位置はさておき、それ以降のシーケンスデータに関してはすべて「オフセット」で場所を示します。私が見た限り、曲へのポインタは上位バイトと下位バイトが少し離れた位置にありました。

細かいことは、拙作のhbdqspcを通して生のデータと向き合えば、それなりにわかるのではないかと思います。

Voice Bytes / 演奏データ

Mapping
  • $00 - end of track
  • $01-7f - note params
  • $80-cf - note
  • $d0 - tie
  • $d1 - rest
  • $d2 - slur on
  • $d3 - slur off
  • $d4 - set instrument
  • $d5 - unknown.7
  • $d6 - pan
  • $d7 - pan fade
  • $d8 - vibrato on
  • $d9 - vibrato fade
  • $da - vibrato off
  • $db - master volume
  • $dc - master volume fade
  • $dd - tempo
  • $de - unknown.1
  • $df - global transpose
  • $e0 - per-voice transpose
  • $e1 - tremolo on
  • $e2 - tremolo off
  • $e3 - volume
  • $e4 - volume fade
  • $e5 - unknown.3
  • $e6 - pitch envelope (to)
  • $e7 - pitch envelope (from)
  • $e8 - pitch envelope off
  • $e9 - tuning
  • $ea - echo volume
  • $eb - echo params
  • $ec - unknown.3
  • $ed - echo off
  • $ee - echo on
  • $ef - echo FIR
  • $f0 - set ADSR
  • $f1 - duration/velocity
  • $f2 - jump
  • $f3 - call subroutine
  • $f4 - return from subroutine
  • $f5 - noise on
  • $f6 - noise off
  • $f7 - noise clock
  • $f8 - unknown.0
  • $f9 - execute sub command
  • $fa-ff - undefined (out of table)
  • subcmd $00 - set repeat count
  • subcmd $01 - conditional loop
  • subcmd $02 - unknown.1
  • subcmd $03 - set attack rate
  • subcmd $04 - set decay rate
  • subcmd $05 - set sustain level
  • subcmd $06 - set release rate
  • subcmd $07 - set sustain rate
  • subcmd $08 - undefined (null)
  • subcmd $09 - phase reverse surround on/off
  • subcmd $0a - undefined (null)
  • subcmd $0b-ff - undefined (out of table)

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

($00) End of Track

チャンネルの演奏データの終端を意味します。

($01-7f) Note Parameters [(xy)]
  • vbyte itself means the length of the following note (24 = quarter note, usually).
  • take 1 argument $xy, when $xy < $80. $x = duration rate (0-7), $y = velocity rate (0-15).

コマンドバイト自身で音の長さを表します。通常24が四分音符に相当します。

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

つまり、このコマンドの効果は(細かいパラメータの違いは除いて)任天堂エンジンと同じです。デュレーションとベロシティのテーブルを以下に示します。

0 1 2 3 4 5 6 7
35 70 105 140 175 210 245 255
$23 $46 $69 $8c $af $d2 $f5 $ff
0 1 2 3 4 5 6 7 8 9 a b c d e f
25 40 55 70 85 100 115 130 145 160 176 190 205 220 235 255
$19 $28 $37 $46 $55 $64 $73 $82 $91 $a0 $b0 $be $cd $dc $eb $ff
($80-cf) Note, ($d0) Tie, ($d1) Rest
  • vbyte itself means tone of the note.
  • Rest stops uttering sound, Tie keeps.

tone map (can be tuned differently though)

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
o7 bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7
o8 c8 c9 ca cb cc cd ce cf

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

($d2) Slur On, ($d3) Slur Off

スラーの有効・無効を設定します。再発音することなく、音程のみ変更して音を鳴らす場合に使用します。

($d4) Set Instrument [xx]
  • xx = voice number (SRCN and ADSR is stored in the instrument table, xx is index for it.)

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

($d5) Unknown

DQ6DQ3でコードが違ったかもしれません。nopのようにも見えますが、なぜかサイズテーブルには長さが7だと書かれています。本家BGM中には出現しません。

($d6) Pan [xx]
  • lower-5bit for pan value (0-20).

音の左右位置を設定します。値の範囲は0〜20と狭いです(実際の設定比率は内部テーブル定義)。10を中心として、値が小さくなるにつれ左、大きくなるにつれ右に音が移動します。

任天堂エンジンでは上位ビットで左右の位相反転を決定しますが、このエンジンではsubcmd $09でそれを決定します。

($d7) Pan Fade [xx yy]

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

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

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

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

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

($da) Vibrato Off

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

($db) Master Volume [xx]

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

($dc) Master Volume Fade [xx yy]

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

($dd) Tempo [xx]
  • x = speed (about 12240000/60000000 of beat per minutes?).
    • 12240000 = (timer0) 2ms * 24 * 256

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

($de) Unknown [xx]

不明なイベントです。本家BGM中には出現しません。

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

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

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

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

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

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

($e2) Tremolo Off

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

($e3) Volume [xx]

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

($e4) Volume Fade [xx yy]

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

($e5) Unknown [xx yy zz]

不明なイベントです。本家BGM中には出現しません。

($e6) 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は半音単位で、負数も指定できます。

($e7) 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は半音単位で、負数も指定できます。

($e8) Pitch Envelope Off

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

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

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

($ea) Echo volume [xx yy]
  • xx = echo left volume (EVOL(L)), yy = echo right volume (EVOL(R)).

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

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

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

# FIR
0 ff 08 17 24 24 17 08 ff
1 58 bf db f0 fe 07 0c 0c
2 0c 21 2b 2b 13 fe f3 f9
3 34 33 00 d9 e5 01 fc eb
($ec) Unknown [xx yy zz]

不明なイベントです。本家BGM中には出現しません。

($ed) Echo Off, ($ee) Echo On

チャンネルのエコーを有効・無効にします。

($ef) Echo FIR [C0 C1 C2 C3 C4 C5 C6 C7]

FIRフィルタの係数を直接指定します。

($f0) Set ADSR [ADSR(1) ADSR(2)]

ADSRレジスタの内容を直接指定します。

($f1) Duration / Velocity [xx]
  • xx ($00..7f) = same as the second parameter of $01-7f

デュレーション・ベロシティの設定をします。$01-7fの2番目の引数の処理をコマンドとして独立させたものです。なので通常$80以上は指定しません。

($f2) Jump [xx yy]
  • jump to offset $yyxx from the head of sequence

指定位置に無条件ジャンプします。位置はシーケンスのヘッダからの相対的なオフセットで指定します。ループに用いられます。

($f3) Call Subroutine [xx yy]
  • subroutine jump to offset $yyxx from the head of sequence (nesting is not allowed)
  • see also: vcmd $f4

指定位置にサブルーチンジャンプします。$f2と違い、$f4で呼び出し位置に戻ります。特定フレーズを使い回すのに用いられます。サブルーチンのネストは許されません。

($f4) Return From Subroutine

$f3によるサブルーチンジャンプから復帰します。

($f5) Noise On, ($f6) Noise Off

SPC700のノイズを有効・無効にします。クロックは$f7で設定します。

($f7) Noise Clock [NCK]

ノイズのクロックを設定します。引数はNCKの値なので、$00-1fで指定します。

($f8) Unknown

不明なイベントです。本家BGM中には出現しません。

($fa-ff) Undefined

処理テーブルの範囲外です。使わないでください。

($f9) Execute Sub Command [nn ...]
  • nn = subcmd #

サブコマンドを実行します。nnの値に応じてさまざまなコマンドを実行します。

以下、サブコマンドに関して解説を続けます。nn以前の引数は省略しています。

(subcmd $00) Set Repeat Count [xx]
  • set repeat count for subcmd $01

サブコマンド$01の条件ジャンプのループ回数を設定します。ループ部突入の直前に記せばよいでしょう。

(subcmd $01) Conditional Loop [xx yy]
  1. if repeat count is non-zero, decrement it.
  2. if it's still non-zero, jump to offset $yyxx.
  • initial value of the repeat count is set by subcmd $00.

条件ジャンプです。カウンタが0になるまでは、デクリメントとともにジャンプを続けます。カウンタの初期値はサブコマンド$00で指定します。

(subcmd $02) Unknown

不明なイベントです。本家BGM中には出現しません。nopっぽく見えます。

(subcmd $03) Set Attack Rate [xx]

ADSRのARを指定します。値の範囲は$00-0fです。

(subcmd $04) Set Decay Rate [xx]

ADSRのDRを指定します。値の範囲は$00-07です。

(subcmd $05) Set Sustain Level [xx]

ADSRのSLを指定します。値の範囲は$00-07です。

(subcmd $06) Set Release Rate [xx]

デュレーションで設定された長さの発音終了後に設定される、ADSRのSRを指定します。値の範囲は$00-1fです。

(subcmd $07) Set Sustain Rate [xx]

デュレーションで設定された長さの発音時に設定される、ADSRのSRを指定します。値の範囲は$00-1fです。

(subcmd $09) Phase Reverse Surround On/Off [xx yy]
  • xx is for left, yy is for right. zero = off, non-zero = on.

波形の位相反転の有無を、左右それぞれ設定します。