Skip to content

feat(audio): live push/queue playback stream for on-the-fly synthesis - #612

Merged
InauguralPhysicist merged 1 commit into
mainfrom
feat/audio-stream-live-synthesis
Jul 14, 2026
Merged

feat(audio): live push/queue playback stream for on-the-fly synthesis#612
InauguralPhysicist merged 1 commit into
mainfrom
feat/audio-stream-live-synthesis

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

What

Adds five builtins — audio_stream_open / audio_stream_push / audio_stream_queued / audio_stream_clear / audio_stream_close — a dedicated SDL playback device opened in queue mode (callback-NULL) and fed block-by-block via SDL_QueueAudio. It coexists with the callback mixer (audio_open) and the SDL_mixer music device; the OS mixes all three.

This is the capability the fixed-buffer mixer channels can't provide: audio generated on the fly as notes start and stop, with no pre-rendered clip.

Why (forcing function)

DeslanStudio musical typing (F-DS-17). Sustain-while-held notes need a stream you can feed each tick from the synth's stateful voice pool — the whole-buffer audio_play can't sustain. Rather than hack it in the app, the streaming primitive is forged upstream where every future audio consumer can use it.

Design notes

  • push is a pure output sink — float→int16 through the existing audio_convert_samples (same 64 MiB cap / clamp / NaN guards). SDL_QueueAudio copies the block, so the temp buffer is freed immediately. Not trace-recorded.
  • queued reads a live, timing-dependent value that steers the caller's refill control flow, so it carries the TRACE_NONDET_TAKE/RECORD tape pair exactly like audio_capture_read: under EIGS_REPLAY the recorded queue depth replays and the refill pump makes the same push decisions, keeping sessions byte-deterministic.
  • Registration via the ext_names.h X-macro (zero lint/registrar drift).

Validation

  • -Wall -Wextra -Werror clean (gfx + default builds)
  • test_audio 53/53 under SDL_AUDIODRIVER=dummy, plus the no-device skip path
  • ASan + UBSan clean (detect_leaks=1)
  • Full suite 2931/2931; discoverability gate [99b] green (documented in docs/BUILTINS.md)
  • 8/8 stable audio runs

🤖 Generated with Claude Code

Adds audio_stream_open/push/queued/clear/close — a dedicated SDL playback
device in queue mode (callback-NULL), fed block-by-block via SDL_QueueAudio.
It coexists with the callback mixer (audio_open) and SDL_mixer music device;
the OS mixes all three. This is what a synth needs that the fixed-buffer
mixer channels cannot give: audio generated on the fly as notes start and
stop, with no pre-rendered clip.

Forcing function: DeslanStudio musical typing (F-DS-17). Sustain-while-held
notes need a stream you can feed each tick, not the whole-buffer audio_play.

- push is a pure output sink (float->int16 via audio_convert_samples, same
  64 MiB cap / clamp / NaN guards; SDL copies the block so the temp is freed
  immediately). Not trace-recorded.
- queued reads a live, timing-dependent value that steers the caller's
  refill control flow, so it carries the TAKE/RECORD tape pair exactly like
  audio_capture_read: under EIGS_REPLAY the recorded depth replays and the
  refill pump makes the same push decisions, keeping sessions deterministic.
- Registration via the ext_names.h X-macro (zero lint/registrar drift).
- Documented in docs/BUILTINS.md; discoverability gate [99b] green.

Validated: -Wall -Wextra -Werror clean; test_audio 53/53 (dummy driver) and
the no-device skip path; ASan+UBSan clean (detect_leaks=1); full suite
2931/2931; 8/8 stable audio runs.
@InauguralPhysicist
InauguralPhysicist merged commit f6de1d6 into main Jul 14, 2026
17 checks passed
@InauguralPhysicist
InauguralPhysicist deleted the feat/audio-stream-live-synthesis branch July 14, 2026 21:13
InauguralPhysicist added a commit that referenced this pull request Jul 14, 2026
Version bump + CHANGELOG [Unreleased]->[0.31.0] + CLAUDE.md latest-release
line. Batch: bulk audio-I/O kernels (#602/#603), live audio-streaming
primitive (#612, the sustain path behind DeslanStudio musical typing),
waveform_view selection edge markers (#610/#611).

NOTE: doc-drift rule 2 (Latest-release line vs latest tag) only passes once
the release workflow creates the v0.31.0 tag — dispatch after merge.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant