音声ファイルフォーマット¶
Family mruby が対応する音声ファイル形式と、その作成・変換方法を説明します。
| 形式 | 用途 | API |
|---|---|---|
| FMSQ | 短い BGM / SE のシーケンスデータ。スロットにロードして再生 | FmrbAudio#load_fmsq, play_slot |
| NSF | ファミコン音楽(NES Sound Format) | FmrbAudio#play(path, track:) |
| MID | 標準 MIDI ファイル。MIDI 層を通して内蔵音源でも外部音源でも鳴らせます | FmrbMidi::SmfPlayer |
FMSQ¶
FMSQ (Family mruby Sequence) は NES APU 互換 の音楽データを記述する独自フォーマットです。短い効果音や BGM ループに向いています。
レジスタの意味は以下参照。
https://www.nesdev.org/wiki/APU_registers
ファイル構造¶
12 バイトのヘッダ + 可変長コマンド列。すべて little-endian。
+------+----------------+
| 0..3 | "FMSQ" | magic
| 4 | version (=1) |
| 5 | flags (=0) | reserved
| 6..7 | frame_count | total frames
| 8..9 | data_size | command bytes
| 10..11 | loop_offset | 0 = no loop
+------+----------------+
| 12.. | command stream |
+------+----------------+
命令コード一覧¶
cc = チャンネル番号 (00=Pulse1, 01=Pulse2, 10=Triangle, 11=Noise)、
aaaaa = $4000 からのレジスタオフセット (0x00-0x17)。
| ビットパターン | 命令 | 後続バイト | 合計サイズ |
|---|---|---|---|
0xxxxxxx |
WAIT (1-128フレーム) | 0 | 1 |
10cc0000 |
NOTE_ON | チャンネル別 2-4 | 3-5 |
10cc0001 |
NOTE_OFF | 0 | 1 |
10cc0010 |
PARAM | MASK + 可変 | 2 以上 |
110aaaaa |
REG_WRITE ($4000+aaaaa) |
1 (DATA) | 2 |
11100000 |
DPCM_PLAY | 3 (RATE_FLAGS, ADDR, LENGTH) | 4 |
11100001 |
DPCM_STOP | 0 | 1 |
11100010 |
DPCM_RAW | 1 (VALUE) | 2 |
11111110 |
END | 0 | 1 |
11111111 |
LOOP | 2 (OFFSET) | 3 |
APU エミュレータ側の制限: 現状の APU エミュレータ側ではDPCMの機能がサポートされてない
WAIT命令¶
0xxxxxxx
- 上位1ビット = 0 -> WAIT
- 下位7ビット = 待ちフレーム数 - 1
| 値 | 意味 |
|---|---|
| 0x00 | 1フレーム待ち |
| 0x01 | 2フレーム待ち |
| ... | ... |
| 0x7F | 128フレーム待ち |
チャンネル命令 (NOTE_ON / NOTE_OFF / PARAM)¶
10ccxxxx 形式の高レベルコマンド。cc がチャンネル(Pulse1=0, Pulse2=1, Triangle=2, Noise=3)、下位4ビットがサブコマンド。
NOTE_ON / NOTE_OFF は内部で $4015(Status)の対応ビットも更新する。
NOTE_ON¶
10cc0000 後続バイトはチャンネル別:
| チャンネル | 後続バイト | 書き込み順 |
|---|---|---|
| Pulse1 / Pulse2 | TIMER_LO, TIMER_HI, VOL_ENV, SWEEP (4 バイト) |
VOL → SWEEP → LO → HI |
| Triangle | TIMER_LO, TIMER_HI, LINEAR (3 バイト) |
LINEAR → LO → HI |
| Noise | PERIOD_MODE, VOL_ENV (2 バイト) |
VOL → LO |
NOTE_OFF¶
10cc0001 後続バイトなし。$4015 の該当チャンネルビットをクリアするのみ(APUレジスタ自体は変更しない)。
PARAM¶
10cc0010 [MASK] [DATA...]
マスクで指定したサブパラメータだけを差分更新する。マスクビットと後続データは以下:
Pulse PARAM (Pulse1 / Pulse2)¶
| mask bit | 後続データ | 書き込み先 |
|---|---|---|
0x01 (TIMER) |
LO, HI (2 バイト) |
$4002/3 または $4006/7 |
0x02 (VOL) |
VOL_ENV (1 バイト) |
$4000 または $4004 |
0x04 (SWEEP) |
SWEEP (1 バイト) |
$4001 または $4005 |
Triangle PARAM¶
| mask bit | 後続データ | 書き込み先 |
|---|---|---|
0x01 (TIMER) |
LO, HI (2 バイト) |
$400A/B |
0x02 (LINEAR) |
LINEAR (1 バイト) |
$4008 |
Noise PARAM¶
| mask bit | 後続データ | 書き込み先 |
|---|---|---|
0x01 (PERIOD) |
PERIOD_MODE (1 バイト) |
$400E |
0x02 (VOL) |
VOL_ENV (1 バイト) |
$400C |
マスクの bit 順に後続バイトを読み出して書き込む。指定されていないビットの後続バイトは存在しない。
REG_WRITE命令¶
110aaaaa [DATA]
APUレジスタへの直接書き込み。NSFのAPUレジスタ操作を順序・値ともに完全に保持する。
aaaaa: $4000からのオフセット (0-23)DATA: 書き込む値 (1バイト)- 合計: 2バイト/書き込み
| offset (aaaaa) | APUレジスタ | 内容 |
|---|---|---|
| 0x00 | $4000 | Pulse1 Volume/Envelope/Duty |
| 0x01 | $4001 | Pulse1 Sweep |
| 0x02 | $4002 | Pulse1 Timer Low |
| 0x03 | $4003 | Pulse1 Timer High + Length |
| 0x04 | $4004 | Pulse2 Volume/Envelope/Duty |
| 0x05 | $4005 | Pulse2 Sweep |
| 0x06 | $4006 | Pulse2 Timer Low |
| 0x07 | $4007 | Pulse2 Timer High + Length |
| 0x08 | $4008 | Triangle Linear Counter |
| 0x0A | $400A | Triangle Timer Low |
| 0x0B | $400B | Triangle Timer High + Length |
| 0x0C | $400C | Noise Volume/Envelope |
| 0x0E | $400E | Noise Period + Mode |
| 0x0F | $400F | Noise Length |
| 0x10 | $4010 | DMC Frequency/Flags |
| 0x11 | $4011 | DMC DAC (7bit) |
| 0x12 | $4012 | DMC Sample Address |
| 0x13 | $4013 | DMC Sample Length |
| 0x15 | $4015 | Status (Channel Enable) |
参考¶
fmruby-graphics-audio/tools/ 以下に Ruby のジェネレータがあります(PC で実行)。
fmruby-graphics-audio/tools/gen_test_fmsq.rb # スケールパターン
fmruby-graphics-audio/tools/gen_intro_fmsq.rb # ジングル
これらを参考に音階・コードを記述すると .fmsq バイナリが生成できます。
再生¶
スロット(番号 ID)にロードしてから再生します。
data = File.open("/sfx.fmsq", "r") { |f| f.read }
@audio.load_fmsq(0, data) # スロット 0 に登録
@audio.play_slot(0) # 再生
詳細は FmrbAudio を参照。
NSF (NES Sound Format)¶
NES Sound Format は実機ファミコン音楽を再生する標準フォーマットです。Family mruby は NSF ファイルの再生に対応しています。
入手方法¶
- 自分で作る場合は FamiStudio などの DAW から NSF エクスポート
再生¶
@audio.play("/usr/share/sounds/nsf/song.nsf", track: 1)
track: は曲番号(1 始まり)。NSF には複数曲が含まれるため、ファイル名で曲を選んだ後、track 指定で個別曲を選べます。
制限¶
- 再生できないNSFファイルもあります
サンプル¶
/app/tool/nsf_player.app.rb に NSF 再生 GUI のサンプルがあります(曲送り・トラック選択・一時停止 / 再開 を実装)。
MID (標準 MIDI ファイル)¶
2.0 で追加。.mid は音声スロットではなく MIDI 層が再生します。つまり同じ
ファイルで、内蔵音源も GROVE 端子につないだ外部音源も鳴らせます。
player = FmrbMidi::SmfPlayer.new(@device)
player.load("/usr/share/sounds/midi/song.mid")
player.play
/usr/share/sounds/midi に著作権の切れた曲が 7 つ入っています。SMF Player
(/app/tool/smf_player.app.rb) が一覧から選んで鳴らします。
内蔵音源で鳴らす場合¶
APU は矩形波 2・三角波 1・雑音 1 の 4 声で、MIDI の 16 チャンネルには足りません。一般的な MIDI ファイルを内蔵音源で鳴らすと、原理的に音が落ちます。音数の多い曲ほど顕著です。外部の GM 音源にはこの制約がありません。MIDI を参照してください。
FMSQ に変換する¶
その場で演奏するのではなく決まったシーケンスとして持ちたい場合は、リポジトリの
tool/midi/smf2fmsq.rb で .mid を FMSQ に変換できます。
ruby tool/midi/smf2fmsq.rb input.mid -o out.fmsq
APU の状態を丸ごと運ぶレジスタ書き込みだけを出力するので、どちらの FMSQ 再生系でも同じに 鳴ります。4 声への圧縮は同じように起こります。
WAV / MP3¶
未対応です。FMSQ に変換するか、MID か NSF を使ってください。
直接合成 (note_on / note_off)¶
ファイルを使わずに、FmrbAudio#note_on で APU を直接駆動できます。リズムやセリフの効果音、ボタンクリック音などに向きます。
@audio.note_on(0, 440, 10, 2, 0) # 矩形波 1ch で A4
sleep_ms(200)
@audio.note_off(0)
詳細は FmrbAudio ▸ note_on / note_off を参照。