Skip to content

Sound (FmrbAudio)

FmrbAudio is the audio playback API. Create an instance by calling FmrbAudio.new(self).

class MyApp < FmrbApp
  def on_create
    @audio = FmrbAudio.new(self)
  end
end

Internally, it sends MSG_TYPE_APP_AUDIO messages to the kernel, which are processed by the audio engine on the graphics side.

Supported file formats

For details on supported audio file formats, see Audio File Formats.

Methods

File Playback

Method Purpose
play(path, track: 0) Start playing a file
stop Stop playback
pause Pause playback
resume Resume playback

path is a file path (e.g. /usr/share/sounds/nsf/song.nsf). track: specifies the track number (used for files like NSF that contain multiple tracks).

Playing a recording (play_wav)

@audio.play_wav("/usr/share/sounds/sine440_16k.wav")
Method Purpose
play_wav(path) Play a WAV mixed on top of the sound chip. true if it started
stop_wav Stop it

PCM 16-bit, mono, 8-48 kHz, up to 2 MB. It rides over whatever the sound chip is playing, so a spoken line or a recorded chime does not interrupt the music. One clip at a time for the whole machine: starting another replaces it.

Modern only

play_wav returns false on Retro without sending anything, so an app can call it unconditionally and fall back on the answer — which is what the hourly chime does: a recording if it can, its note if it cannot. Retro's audio lives on the WROVER at the far end of a serial link, and shipping a clip across it before a note could sound is too slow to be worth it.

The microphone (Modern only)

Method Purpose
mic_available? Whether this machine has one
mic_rate Samples per second. Fixed by the hardware, so a spectrum's bin width follows from it
mic_enable(on = true) Start or stop sampling
mic_read(count, timeout_ms = 200) count 16-bit samples as a byte String, or nil if they did not arrive

Nothing is recorded and nothing leaves the device; the samples are handed to the app and that is all. The bundled Mic Spectrum app is the worked example.

FMSQ Sequences

A mechanism for pre-loading sequences into slots before playback. Suitable for short sound effects and looping BGM.

Method Purpose
load_fmsq(slot_id, binary_data) Register binary data into a slot. Bounded by the message size, so a real score does not fit
load_fmsq_file(slot_id, path) Load a slot from a file already on the audio side (put it there with @gfx.sync_file first). This is the route for anything bigger than a jingle
play_slot(slot_id) Play a registered slot
data = File.open("/sfx.fmsq", "r") { |f| f.read }
@audio.load_fmsq(0, data)
@audio.play_slot(0)

For detailed specifications, see Audio File Formats.

Tone Synthesis (note_on / note_off)

Directly drives NES APU-compatible channels. Allows playing short sound effects and game BGM from scripts.

@audio.note_on(channel, freq, volume = 10, duty = 2, sweep = 0)
@audio.note_off(channel)
Argument Range and Meaning
channel Channel number. NES APU configuration: 0 / 1 = pulse wave, 2 = triangle wave, 3 = noise
freq Frequency (Hz). Integer. May have a different meaning on the noise channel
volume Volume. Approximately 0 to 15. Default 10
duty Pulse wave duty cycle. 0 to 3
sweep Frequency sweep value (packed APU register value)

Example: Piano-style

class MiniPiano < FmrbApp
  KEYS = {
    "a" => 261, "s" => 293, "d" => 329, "f" => 349,
    "g" => 392, "h" => 440, "j" => 493, "k" => 523
  }

  def on_create
    @audio = FmrbAudio.new(self)
    @ch = 0
  end

  def on_event(ev)
    super
    return unless ev[:character]
    if ev[:type] == :key_down && (freq = KEYS[ev[:character]])
      @audio.note_on(@ch, freq, 10, 2, 0)
    elsif ev[:type] == :key_up
      @audio.note_off(@ch)
    end
  end
end

MiniPiano.new.start

Track Numbers and Playback Control

Use Case Recommended Track
BGM 0
SE (Sound Effects) 1 and above

Calling play(path, track:) with different track numbers simultaneously enables parallel playback on separate tracks (implementation dependent).

NSF File Playback

NES Sound Format is supported.

@audio.play("/usr/share/sounds/nsf/song.nsf", track: 1)

A playback GUI sample is available in tool/nsf_player.app.rb. For details, see Audio File Formats.

Cleanup

To stop any playing audio when the app exits, it is safe to call stop in on_destroy.

def on_destroy
  @audio.stop if @audio
end
  • Audio File Formats -- FMSQ / NSF specifications and how to create them
  • MIDI -- driving this same sound chip through a MIDI device, or sending MIDI to an external instrument
  • Examples -- piano, flappy, nsf_player, and more