Commit8e069cf9Recorded9 Jul 2026Repositorysigil-audio
Add (sigil audio sink) cond-expand facade
Message
Extract a portable streaming audio-sink facade that selects its backend with cond-expand: the native (else) arm re-exports the sokol SPSC ring from (sigil audio); the wasm arm imports (sigil browser audio-sink) from the sigil-wasm-audio bridge package.
The facade adds WebAudio-shaped context lifecycle ops (audio-context-open /-resume/-sample-rate/-state) plus a portable make-float-buffer re-export so the same source compiles and runs on native and wasm. On native the context ops are thin shims over sokol's single always-running device.
(sigil audio) and the native sink C are untouched; test-streaming-sink.sgl stays green.
Changed
src/sigil/audio/sink.sgl | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 93 insertions(+)Diff
src/sigil/audio/sink.sgladded
@@ -0,0 +1,93 @@
+1
;;; (sigil audio sink) - Portable streaming audio-sink facade+2
;;;+3
;;; This is the target-independent audio sink API. It presents ONE surface+4
;;; for pushing interleaved float32 PCM into a device, and selects its+5
;;; backend with `cond-expand`:+6
;;;+7
;;; - native (else): the sokol_audio SPSC ring living in (sigil audio)+8
;;; - wasm: the WebAudio bridge in (sigil browser audio-sink),+9
;;; provided by the sigil-wasm-audio package+10
;;;+11
;;; This mirrors how (sigil websocket) selects a native socket stack vs the+12
;;; (sigil browser websocket) bridge. Application code imports THIS module+13
;;; and the same source compiles for both targets.+14
;;;+15
;;; ## Context lifecycle+16
;;;+17
;;; WebAudio requires an AudioContext that starts suspended and must be+18
;;; resumed from a user gesture (autoplay policy). The context ops model+19
;;; that. On native, sokol owns a single always-running device, so the+20
;;; context ops are thin shims (open initializes sokol; resume is a no-op;+21
;;; the sample rate is sokol's fixed 44100). They exist so the SAME source+22
;;; compiles and runs on both targets behind this facade.+23
;;;+24
;;; ```scheme+25
;;; (import (sigil audio sink))+26
;;; (define ctx (audio-context-open))+27
;;; (audio-context-resume ctx) ; from a user gesture on wasm+28
;;; (let ((rate (audio-context-sample-rate ctx))+29
;;; (s (open-audio-stream channels: 2 buffer-frames: 8192)))+30
;;; (push-audio-samples s pcm n-frames) ; interleaved f32+31
;;; (audio-stream-depth s)) ; backpressure accounting+32
;;; ```+33
+34
(define-library (sigil audio sink)+35
(import (sigil core))+36
+37
(cond-expand+38
(wasm+39
(import (sigil browser audio-sink)))+40
(else+41
(import (sigil audio))))+42
+43
(export+44
;; Context lifecycle (WebAudio-shaped; native shims)+45
audio-context-open+46
audio-context-resume+47
audio-context-sample-rate+48
audio-context-state+49
;; Streaming sink+50
open-audio-stream+51
push-audio-samples+52
audio-stream-depth+53
audio-stream-room+54
audio-stream-capacity+55
audio-stream-channels+56
audio-stream-underruns+57
audio-stream?+58
audio-stream-closed?+59
close-audio-stream!+60
set-audio-stream-volume!+61
;; Portable f32 PCM buffer builder (available on both backends)+62
make-float-buffer)+63
+64
;; The wasm arm gets every export from (sigil browser audio-sink). The+65
;; native arm re-exports the sink ops from (sigil audio) and supplies the+66
;; context shims below.+67
(cond-expand+68
(wasm)+69
(else+70
(begin+71
+72
;;; Open the audio context. On native this initializes sokol_audio+73
;;; and returns an opaque non-#f handle (sokol owns a single device,+74
;;; so the handle is a sentinel). On wasm this creates a suspended+75
;;; AudioContext and returns its id.+76
(define (audio-context-open)+77
(audio-setup)+78
'native-audio-context)+79
+80
;;; Resume a suspended context from a user gesture. Native contexts+81
;;; are never suspended, so this is a no-op that reports success.+82
(define (audio-context-resume ctx)+83
#t)+84
+85
;;; The context sample rate in Hz. Sokol runs at a fixed 44100; the+86
;;; sink ring assumes the same rate (no resampling).+87
(define (audio-context-sample-rate ctx)+88
44100)+89
+90
;;; The context run state: one of 'suspended, 'running, 'closed.+91
;;; Native contexts are always 'running once opened.+92
(define (audio-context-state ctx)+93
'running)))))