The longest sound gen_sound! and gen_tone! make: five seconds, in
milliseconds. For anything longer, load a file with load_music!.
Audio
Short sound effects, streamed music, and procedural tones.
Load or generate resources in init! and keep the opaque values in the
model. Sound and music are distinct types, so they cannot be mixed by
accident. Their final Roc reference automatically unloads the host resource.
init! = App.init(
App.default,
|_io| Ok({ blip: Audio.gen_tone!({ freq: 440, ms: 120 })? }),
)
if input.devices.key_pressed(KeySpace) {
model.blip.playback().with_volume(0.4).play!()
}
load_sound! and load_music! read from an Assets.Store, exactly as
Assets.load_texture! does.
Sound is decoded into memory for short effects. Music streams encoded
bytes for long tracks and is advanced automatically each frame.
The two loaders read the disk, so they wait: load_sound! and load_music!
are legal in init!, where they block startup, and in tasks, where they
park the task, and are refused in update! and render!. To start a track
or a new effect after startup, call the loader inside Task.spawn! and keep
the resource the task's message contains. gen_sound! and gen_tone! build
a Sound with no file behind it, so they stay legal in init!, update!,
and tasks. A generated sound lasts from 1 ms to max_generated_ms; asking
for any other length stops the app, naming the fix, rather than making a
different sound.
Every other effect here changes what the mixer is doing and is legal in
init!, update!, and tasks, and refused in render!. The four queries
that only read a scalar the device already has -- Sound.is_playing!,
Music.is_playing!, Music.length!, and Music.time_played! -- are legal
in any callback, render! included.
gen_sound! : GenSound => Try(Sound, [SoundGenerationFailed, ResourceLimit])
Generate a reusable procedural sound.
ms must be from 1 to max_generated_ms. Any other length is a
programming mistake, not a runtime condition, so it stops the app with a
message naming the fix rather than making a shorter sound; for anything
longer, load a file. ResourceLimit is the host's
fixed sound table being full, and SoundGenerationFailed is the audio
device refusing the samples, so initialization should propagate the
returned error.
Legal in init!, update!, and tasks; refused in render!.
Generate a reusable sine tone. freq is Hz and ms is milliseconds,
from 1 to max_generated_ms, as for gen_sound!.
Legal in init!, update!, and tasks; refused in render!.
set_master_volume! : F32 => {}
Set global output volume for all sounds and music, clamped to 0 through 1.
Legal in init!, update!, and tasks; refused in render!.
load_sound! : Store, Str => Try(Sound, [PathInvalid, NotFound, ReadFailed, SoundLoadFailed, ResourceLimit])
Load a short sound effect from an asset store.
Legal in init!, where it blocks startup, and in tasks, where it parks
the task; refused in update! and render!. The file is read off the
frame thread and decoded onto the audio device when the bytes are back.
PathInvalid, NotFound and ReadFailed mean what they mean for
Assets.load_texture!; SoundLoadFailed is bytes raylib would not
decode. The format is taken from the extension, and .wav, .ogg,
.mp3 and .qoa are the ones it reads; any other extension is
SoundLoadFailed.
load_music! : Store, Str => Try(Music, [PathInvalid, NotFound, ReadFailed, MusicLoadFailed, ResourceLimit])
Load a streamed music file from an asset store. Keep the returned value in the app model.
Legal in init!, where it blocks startup, and in tasks, where it parks
the task; refused in update! and render!. The file is read off the
frame thread and the host keeps those bytes for as long as the stream
exists, releasing them with the final reference to the Music.
MusicLoadFailed is bytes raylib would not decode; the format is taken
from the extension, and .wav, .ogg, .mp3, .qoa, .xm and
.mod are the ones it reads; any other extension is MusicLoadFailed.
Sound
Audio.Sound :: # (opaque)
Host-owned short sound effect. Use methods such as sound.play!().
A sound has no volume, pitch, or pan of its own that outlives a play.
raylib's are sticky per resource, and every play sets all three, so there
is nothing to set once and inherit -- see Playback.
play! : Sound => {}
Play this sound at its default volume, pitch, and pan.
Equivalent to sound.playback().play!(), and stated the same way: the
three playback parameters are always set before the sound starts.
Legal in init!, update!, and tasks; refused in render!.
stop! : Sound => {}
Stop playback and rewind to the beginning.
Legal in init!, update!, and tasks; refused in render!.
pause! : Sound => {}
Pause playback at the current position.
Legal in init!, update!, and tasks; refused in render!.
resume! : Sound => {}
Resume a paused sound.
Legal in init!, update!, and tasks; refused in render!.
is_playing! : Sound => Bool
Whether this sound is currently playing.
Legal in any callback, render! included.
playback : Sound -> Playback
The settings this sound plays at by default, so a play can adjust one
of them without spelling out the rest:
sound.playback().with_pitch(0.8).play!().
stub : Sound
Resource-free sound value for pure tests.
The handle never resolves to a host resource, so every host path it
is passed to treats it as an invalid one: playing, stopping, pausing,
and resuming it are all no-ops, and is_playing! returns Bool.False.
Put it in a model to test the app's pure update logic from an
expect. Do not use it to test playback or resource lifetime.
Playback
:= { sound : Sound, volume : F32, pitch : F32, pan : F32 }
A sound together with the settings it should be played at.
Volume, pitch, and pan are applied in that order before playback starts.
Every play states all three, so a play cannot inherit what some earlier
play left behind on the same host resource. That is why Sound has no
set_volume!: raylib's setters are sticky per sound, and a PlaySound
overwriting them silently was a trap rather than a feature.
with_volume : Playback, F32 -> Playback
Volume for this play, clamped by the host to 0 through 1.
with_pitch : Playback, F32 -> Playback
Pitch multiplier for this play. Non-positive values are clamped.
Stereo pan for this play, clamped by the host to -1 through 1.
play! : Playback => {}
Apply the settings and start playback.
Legal in init!, update!, and tasks; refused in render!.
Volume, pitch, and pan are all set before the sound starts, so this play cannot inherit what an earlier play left on the same sound.
Music
Audio.Music :: # (opaque)
Host-owned streamed music. The platform updates active streams each frame.
play! : Music => {}
Start or restart playback.
Legal in init!, update!, and tasks; refused in render!.
stop! : Music => {}
Stop playback and rewind.
Legal in init!, update!, and tasks; refused in render!.
pause! : Music => {}
Pause at the current position.
Legal in init!, update!, and tasks; refused in render!.
resume! : Music => {}
Resume paused playback.
Legal in init!, update!, and tasks; refused in render!.
set_volume! : Music, F32 => {}
Set stream volume, clamped to 0 through 1.
Legal in init!, update!, and tasks; refused in render!.
set_pitch! : Music, F32 => {}
Set stream pitch multiplier.
Legal in init!, update!, and tasks; refused in render!.
Set stereo pan, clamped to -1 through 1.
Legal in init!, update!, and tasks; refused in render!.
set_looping! : Music, Bool => {}
Enable or disable automatic looping.
Legal in init!, update!, and tasks; refused in render!.
is_playing! : Music => Bool
Whether this stream is currently playing.
Legal in any callback, render! included.
Seek to seconds from the start. Negative values are clamped to zero.
Legal in init!, update!, and tasks; refused in render!.
Total stream length in seconds, or zero for an invalid resource.
Legal in any callback, render! included.
time_played! : Music => F32
Current playback position in seconds.
Legal in any callback, render! included.
stub : Music
Resource-free music value for pure tests.
The handle never resolves to a host resource, so every host path it
is passed to treats it as an invalid one: transport and mutation calls
are no-ops, is_playing! returns Bool.False, and length! and
time_played! return zero. Put it in a model to test the app's pure
update logic from an expect. Do not use it to test playback or
resource lifetime.
Waveform
:= [Sine, Square, Triangle, Saw, Noise]
Procedural waveform used by gen_sound!.
GenSound : {
waveform : Waveform,
freq_start : F32,
freq_end : F32,
ms : I32,
attack_ms : I32,
decay_ms : I32,
sustain : F32,
release_ms : I32,
volume : F32,
}
Envelope and pitch configuration for a generated sound.