RocRay plays short sound effects, streams music, and can generate tones in code. This chapter assumes [app-model]; loading sounds from disk also uses the asset stores described in [files-and-assets].
There are two kinds of audio resource, and they are distinct types so they cannot be mixed up:
-
A
Soundis decoded into memory. Use it for short effects: a jump, a coin, a hit. -
A
Musictrack streams from its encoded file a piece at a time. Use it for long tracks; the host keeps it playing automatically every cycle.
Like every host resource, both are released automatically when the last
reference goes away. Load or generate them in init! and keep them in the
model.
Audio.gen_tone! and Audio.gen_sound! build a Sound with no file behind
it, so they need no asset store and are legal in init!, update!, and
tasks. The quickest way to hear something:
init! : App.Init(Model, [SoundGenerationFailed, ResourceLimit])
init! = App.init(
App.default,
|_io| {
blip = Audio.gen_tone!({ freq: 440, ms: 120 })?
Ok({ blip: blip })
},
)gen_tone! can fail with SoundGenerationFailed or ResourceLimit (the
sound table is full), and ? passes either error on. That is why both appear
in init!'s error list, the second type argument of App.Init. When you add
a call with ? that can fail in another way, add its errors to that list;
the compiler tells you which are missing.
gen_sound! takes a waveform (Sine, Square, Triangle, Saw, or
Noise), a start and end frequency, a duration, an envelope, and a volume. A
generated sound lasts from 1 millisecond to Audio.max_generated_ms (5
seconds). Asking for any other length is a mistake in the program, so the app
stops with a message naming the fix; load a file for anything longer.
Pong and
Pixel Workshop make all their sounds
this way.
jump = Audio.load_sound!(store, "jump.wav")?
theme = Audio.load_music!(store, "theme.ogg")?Here store is an asset store opened in init!; [drawing-first-sprite]
shows how to set one up for your own project.
The format is taken from the file’s extension. Sounds and music read WAV, OGG Vorbis, MP3, and QOA files; music also reads XM and MOD tracker modules.
Both loaders read the disk, so they wait: they are legal in init! and in
tasks. To start a new track after startup, load it in a task and keep the
Music that the task’s message contains.
Playing, stopping, and adjusting audio change what the mixer is doing. They are
legal in init!, update!, and tasks, and refused in render!:
if input.devices.key_pressed(KeySpace) {
model.blip.playback().with_volume(0.4).play!()
}sound.play!() plays a sound at full volume, normal pitch, and centred. For
anything else, sound.playback() creates a description of one play, and
with_volume, with_pitch, and with_pan adjust it before .play!()
starts it. The settings apply to that play only; the next play starts from
the defaults again. Sound also has stop!, pause!, and resume!.
A Sound is one voice. Playing it again while it is still playing restarts
it from the beginning; the two plays do not overlap. For effects that should
overlap, such as rapid gunfire, load or generate the same sound two or three
times and play them in turn.
Music is different: its settings stay in effect until you change them. Music
has play!, stop!, pause!, resume!, seek!, set_volume!,
set_pitch!, set_pan!, and set_looping!.
Audio.set_master_volume! scales everything.
Four queries only read values the audio device already has, so they are legal
in any callback, render! included: Sound.is_playing!, Music.is_playing!,
Music.length!, and Music.time_played!.
-
The
Audiopage of the API reference. -
Spark Run loads music and sound effects and switches tracks with the game state.