Audio¶
Playing sounds: loading or synthesizing a Sound, playing it through an Audio as voices, and
controlling each voice. A complete program is examples/audio.
Sounds¶
A Sound is decoded samples, an asset like an Image (see
Assets and playheads): immutable, and cheap to copy, since
copies share their samples.
# A fragment, inside `create`.
var chime = Sound.load("assets/chime.wav")
- Loading:
Sound.load(path)reads WAV, OGG, FLAC or MP3, chosen by the file's contents, not its extension. The whole file is decoded at load, to 16-bit samples whatever the file held. It raises, naming the path, for a file it can't read or one with no samples. - Synthesizing:
Sound.from_samples(samples, channels=1, sample_rate=44100)takesFloat64samples from-1.0to1.0, clipping any outside. With several channels they interleave, a sample of each to a frame: left, right, left, right. It raises unlesschannelsandsample_rateare positive and the samples are whole frames. - Reading:
channels(),sample_rate(),frame_count()andduration()in seconds.sample(frame, channel=0)reads one sample back, from-1.0to1.0, and is0.0outside the sound, so drawing a waveform needs no bounds checks.
A one-second 440 Hz tone:
# A fragment, inside `create`.
var samples = List[Float64](capacity=44100)
for i in range(44100):
samples.append(0.5 * sin(TAU * 440.0 * Float64(i) / 44100.0))
var tone = Sound.from_samples(samples)
Playing¶
An Audio is the program's playback device: construct one in create and keep it as a field.
Where no audio device can be opened, as on a server or in CI, it plays silently instead of
raising, so the program still runs.
audio.play(sound) plays a sound once and audio.loop(sound) over and over until stopped. Each
returns a Voice, the handle of that one playback, a playhead in the sound like an Animator in
an animation. A sound can play as many voices at once:
# A fragment of `update`: the program has `audio: Audio`, `music: Sound`, `jump: Sound` and
# `music_voice: Voice` fields.
if context.keyboard.pressed(Key.SPACE):
_ = self.audio.play(self.jump, volume=0.6)
if context.keyboard.pressed(Key.M):
if self.audio.is_playing(self.music_voice):
self.audio.stop(self.music_voice)
else:
self.music_voice = self.audio.loop(self.music)
An Audio needs nothing each frame. A looping voice is fed on SDL's audio thread whenever the
device wants more, so it plays on through a slow frame or a long load; a one-shot voice is stale
once it has played to its end.
Voices¶
Each call taking a voice does nothing once the voice is stale, that is once it has ended (a
one-shot that played to its end) or been stopped, even after its slot plays another sound.
Voice.NONE is no voice, for a field before anything plays.
| Call | Does |
|---|---|
stop(voice) |
Stops it for good: it is stale from now on |
pause(voice), resume(voice) |
Holds it where it is, and continues it from there |
is_playing(voice), is_paused(voice) |
Whether it sounds, or is held. Neither means stale |
volume(voice), volume(voice, v) |
Its own volume, 1.0 unless set; 0.0 once stale |
speed(voice), speed(voice, s) |
Its playback speed, 1.0 unless set; 0.0 once stale |
stop_all(), pause_all(), resume_all() |
The same for every voice at once |
play and loop take a starting volume= and speed=, applied before the first sample
plays; setting them afterwards can let the start through as it was.
- Volume:
0.0is silent and1.0as recorded; above1.0amplifies, and below0.0is silent too.audio.volume(v)is the overall volume, which scales every voice's own: a voice at0.5under an overall0.5plays at a quarter. Either applies at once, to voices already playing. - Speed plays a voice faster and higher, or slower and lower, as a record sped up or slowed
down:
2.0is twice as fast and an octave up. It clamps to0.01through100.0. - A pause screen calls
pause_all()on the way in andresume_all()on the way out. Voices played in between play as usual, so the screen can have sounds of its own;resume_allalso resumes voices paused one by one before.