Audio File Formats¶
This page describes the audio file formats supported by Family mruby, along with how to create and convert them.
| Format | Purpose | API |
|---|---|---|
| FMSQ | Sequence data for short BGM / sound effects. Loaded into a slot for playback | FmrbAudio#load_fmsq, play_slot |
| NSF | NES music (NES Sound Format) | FmrbAudio#play(path, track:) |
FMSQ¶
FMSQ (Family mruby Sequence) is a proprietary format for writing NES APU-compatible music data. It is suited for short sound effects and BGM loops.
See https://www.nesdev.org/wiki/APU_registers
File Structure¶
12-byte header + variable-length command stream. All values are 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 |
+------+----------------+
Opcode List¶
cc = channel number (00=Pulse1, 01=Pulse2, 10=Triangle, 11=Noise),
aaaaa = register offset from $4000 (0x00-0x17).
| Bit Pattern | Instruction | Following Bytes | Total Size |
|---|---|---|---|
0xxxxxxx |
WAIT (1-128 frames) | 0 | 1 |
10cc0000 |
NOTE_ON | 2-4 per channel | 3-5 |
10cc0001 |
NOTE_OFF | 0 | 1 |
10cc0010 |
PARAM | MASK + variable | 2 or more |
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 emulator limitation: DPCM functionality is not currently supported by the APU emulator side.
WAIT instruction¶
0xxxxxxx
- Upper 1 bit = 0 -> WAIT
- Lower 7 bits = number of frames to wait minus 1
| Value | Meaning |
|---|---|
| 0x00 | Wait 1 frame |
| 0x01 | Wait 2 frames |
| ... | ... |
| 0x7F | Wait 128 frames |
Channel instructions (NOTE_ON / NOTE_OFF / PARAM)¶
High-level commands in the form 10ccxxxx. cc selects the channel (Pulse1=0, Pulse2=1, Triangle=2, Noise=3), and the lower 4 bits select the subcommand.
NOTE_ON / NOTE_OFF also update the corresponding bit of $4015 (Status) internally.
NOTE_ON¶
10cc0000. Following bytes depend on the channel:
| Channel | Following Bytes | Write order |
|---|---|---|
| Pulse1 / Pulse2 | TIMER_LO, TIMER_HI, VOL_ENV, SWEEP (4 bytes) |
VOL -> SWEEP -> LO -> HI |
| Triangle | TIMER_LO, TIMER_HI, LINEAR (3 bytes) |
LINEAR -> LO -> HI |
| Noise | PERIOD_MODE, VOL_ENV (2 bytes) |
VOL -> LO |
NOTE_OFF¶
10cc0001. No following bytes. Only clears the corresponding channel bit in $4015 (the APU registers themselves are not modified).
PARAM¶
10cc0010 [MASK] [DATA...]
Updates only the sub-parameters specified by the mask. The mask bits and their following data are as follows:
Pulse PARAM (Pulse1 / Pulse2)¶
| mask bit | Following data | Write target |
|---|---|---|
0x01 (TIMER) |
LO, HI (2 bytes) |
$4002/3 or $4006/7 |
0x02 (VOL) |
VOL_ENV (1 byte) |
$4000 or $4004 |
0x04 (SWEEP) |
SWEEP (1 byte) |
$4001 or $4005 |
Triangle PARAM¶
| mask bit | Following data | Write target |
|---|---|---|
0x01 (TIMER) |
LO, HI (2 bytes) |
$400A/B |
0x02 (LINEAR) |
LINEAR (1 byte) |
$4008 |
Noise PARAM¶
| mask bit | Following data | Write target |
|---|---|---|
0x01 (PERIOD) |
PERIOD_MODE (1 byte) |
$400E |
0x02 (VOL) |
VOL_ENV (1 byte) |
$400C |
Following bytes are read and written in mask bit order. No following byte is present for bits that are not set.
REG_WRITE instruction¶
110aaaaa [DATA]
Direct write to an APU register. Preserves both the order and the values of APU register operations from NSF.
aaaaa: offset from $4000 (0-23)DATA: value to write (1 byte)- Total: 2 bytes per write
| offset (aaaaa) | APU register | Description |
|---|---|---|
| 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) |
Reference¶
Ruby generators are available under fmruby-graphics-audio/tools/ (run on PC).
fmruby-graphics-audio/tools/gen_test_fmsq.rb # Scale pattern
fmruby-graphics-audio/tools/gen_intro_fmsq.rb # Jingle
Use these as a reference to write scales and chords, and generate .fmsq binary files.
Playback¶
Load the data into a slot (by numeric ID), then play it.
data = File.open("/sfx.fmsq", "r") { |f| f.read }
@audio.load_fmsq(0, data) # Register in slot 0
@audio.play_slot(0) # Play
See FmrbAudio for details.
NSF (NES Sound Format)¶
NES Sound Format is a standard format for playing actual NES music. Family mruby supports playback of NSF files.
Obtaining NSF Files¶
- To create your own, export as NSF from a DAW like FamiStudio
Playback¶
@audio.play("/usr/share/music/music.nsf", track: 1)
track: is the song number (1-based). Since an NSF file can contain multiple songs, you select the file first and then choose a specific song with the track parameter.
Limitations¶
- Some NSF files cannot be played
Sample¶
A sample NSF player GUI is available at /app/tool/nsf_player.app.rb (implements track forward/backward, track selection, and pause/resume).
WAV / MP3¶
Not supported. Please convert to FMSQ or NSF.
Direct Synthesis (note_on / note_off)¶
You can drive the APU directly using FmrbAudio#note_on without any files. This is useful for rhythm, dialogue sound effects, button click sounds, and similar.
@audio.note_on(0, 440, 10, 2, 0) # A4 on Pulse 1 channel
sleep_ms(200)
@audio.note_off(0)
See FmrbAudio - note_on / note_off for details.
Related¶
- FmrbAudio
- Piano app:
/app/game/piano.app.rb - Sound effects + BGM example:
/app/game/flappy.rb - NSF player:
/app/tool/nsf_player.app.rb