Proposal: Audio standard library (Audio.*) — sfx/music playback, volume, deterministic triggers #22
Labels
No labels
area:ci
area:docs
area:input
area:net
area:rendering
area:repo
area:stdlib
area:tooling
area:types
cleanup
dx
priority:high
priority:low
priority:medium
proposal
status:in-progress
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: workshopsoft/ludic#22
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up to #2 (namespaced standard library). The rest of the stdlib shipped on branch
stdlib-namespaces, butAudio.*was left unimplemented because the runtime has no audio subsystem yet — it is a new platform feature, not a stdlib addition.Proposed surface
Audio.play_sound(id),Audio.play_music(id),Audio.stop_music(),Audio.stop_all(),Audio.volume(v),Audio.pitch(v),Audio.is_playing(id) -> bool(PICO-8sfx/music, LÖVElove.audio).Work required
A platform audio backend in
runtime/native/(CoreAudio native, WebAudio for the wasm target) plus asset loading. Playback device output is non-deterministic, but trigger points are deterministic (driven by the frame clock), so recording/replay stays exact.Blocked on: a runtime audio backend.
Shipped in
4eef5eb—Audio.*plus the platform audio backend this was blocked on.Backend —
runtime/native/audio.ll, an AVAudioPlayer client driven through the Objective-C runtime's C ABI, in exactly the same hand-written-IR style ascocoa.ll(no ObjC and no C source in the build). It exposessnd_load / snd_play / snd_stop / snd_playing / snd_set_volume / snd_set_rate. It is spliced in — and AVFoundation linked (-needed_framework, sinceAVAudioPlayeris reached by name) — only when a windowed program actually usesAudio.*; a game that doesn't touch audio links neither.Surface —
runtime/native/audio.ludickeeps a handle table, a master volume + pitch, and a single music channel:Audio.load(path) -> int— load once, get a handle.Audio.play(id)/Audio.play_sound(id)— one-shot sfx.Audio.play_music(id)— loop on the music channel (replaces current music).Audio.stop(id)/Audio.stop_music()/Audio.stop_all().Audio.volume(v)— master volume 0..1, applied across all sounds.Audio.pitch(v)— playback rate / pitch (AVAudioPlayer'senableRate), 1.0 normal.Audio.is_playing(id) -> bool.Determinism — playback output is real-time and out-of-band, and nothing here feeds the lockstep/replay sim, exactly as the proposal required. But every trigger is an ordinary call a frame handler makes, so a recorded run fires the same sounds at the same frames.
Headless — every native
snd_*call isis_windowed()-guarded, so a headless build dead-strips the whole backend (and AVFoundation):Audio.loadreturns 0,Audio.is_playingreturns false, and the rest no-op. The same game code runs under the test harness with no audio device —examples/library/audio.ludicself-asserts that path in the suite.Docs: a full
docs/language/audiosection (10 method pages);check-impl/check-docsgreen. Full + self-host suites green (80 + 29).