Skip to content

Latest commit

 

History

History
118 lines (93 loc) · 4.34 KB

audio.adoc

File metadata and controls

118 lines (93 loc) · 4.34 KB

Audio

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].

Sounds and music

There are two kinds of audio resource, and they are distinct types so they cannot be mixed up:

  • A Sound is decoded into memory. Use it for short effects: a jump, a coin, a hit.

  • A Music track 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.

Sounds made in code

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.

Loading from disk

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

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!.

Further reading

  • The Audio page of the API reference.

  • Spark Run loads music and sound effects and switches tracks with the game state.