Sounds are currently generated fully before playback starts: sampleWave (src/bundles/sound/src/functions.ts) samples the whole duration into a Float32Array first, and only then does play() fire off io().playSamples(...) to the host for actual AudioContext output. For a duration that generates faster than real-time (the common case), this means waiting out the entire sound's worth of generation before hearing anything, even though playback itself could have started almost immediately.
Idea: stream samples to playback as they're generated instead of batching the whole buffer first - start playing very soon after play_wave/play() is called, and only pause/throttle generation if it ever gets ahead of what's actually been consumed by playback. If generation is faster than real-time (typical), the result is a much more responsive "instant start" instead of the current wait-for-the-whole-buffer delay; if generation is ever slower than real-time (e.g. an expensive wave function evaluated through a tree-walking interpreter), playback would just naturally throttle to match.
This is the standard producer/consumer streaming-audio pattern: a ring buffer feeding a Web Audio AudioWorklet, with the producer pausing once the buffer's full and resuming once it drains.
Two things worth noting for anyone picking this up:
sampleWave's existing per-sample yield* wave(i / FS) delegation already gives a natural checkpoint between samples to hook an "await until there's room in the buffer" backpressure check into - the throttling point is closer to already-there than it looks.
- The real cost is that sample generation happens in Conductor's runner Worker, while playback only works on the browser main thread (see the comment on
SoundTabPlugin/SoundTabRpc). Today's single big-array handoff at the end would need to become a genuine streaming transport - either chunked postMessage calls or a SharedArrayBuffer ring buffer (which needs cross-origin-isolation headers). That's a real feature, not a small tweak to the current fire-and-forget design.
Side benefit: this would also naturally pace print() output (from a student's wave function) at real playback speed instead of the current all-at-once flood - loosely related to source-academy/py-slang#355 / source-academy/frontend#4183, though not required by either.
Sounds are currently generated fully before playback starts:
sampleWave(src/bundles/sound/src/functions.ts) samples the whole duration into aFloat32Arrayfirst, and only then doesplay()fire offio().playSamples(...)to the host for actualAudioContextoutput. For a duration that generates faster than real-time (the common case), this means waiting out the entire sound's worth of generation before hearing anything, even though playback itself could have started almost immediately.Idea: stream samples to playback as they're generated instead of batching the whole buffer first - start playing very soon after
play_wave/play()is called, and only pause/throttle generation if it ever gets ahead of what's actually been consumed by playback. If generation is faster than real-time (typical), the result is a much more responsive "instant start" instead of the current wait-for-the-whole-buffer delay; if generation is ever slower than real-time (e.g. an expensive wave function evaluated through a tree-walking interpreter), playback would just naturally throttle to match.This is the standard producer/consumer streaming-audio pattern: a ring buffer feeding a Web Audio
AudioWorklet, with the producer pausing once the buffer's full and resuming once it drains.Two things worth noting for anyone picking this up:
sampleWave's existing per-sampleyield* wave(i / FS)delegation already gives a natural checkpoint between samples to hook an "await until there's room in the buffer" backpressure check into - the throttling point is closer to already-there than it looks.SoundTabPlugin/SoundTabRpc). Today's single big-array handoff at the end would need to become a genuine streaming transport - either chunkedpostMessagecalls or aSharedArrayBufferring buffer (which needs cross-origin-isolation headers). That's a real feature, not a small tweak to the current fire-and-forget design.Side benefit: this would also naturally pace
print()output (from a student's wave function) at real playback speed instead of the current all-at-once flood - loosely related to source-academy/py-slang#355 / source-academy/frontend#4183, though not required by either.