AtlatestRepositorysigil-audio
1# sigil-audio
2
3Audio playback and streaming for [Sigil](https://codeberg.org/sigil/sigil).
4
5Provides audio playback through miniaudio (vendored, device layer only, every backend loaded at run time). Sound effects are loaded into memory, music is streamed via stb_vorbis.
6
7## Modules
8
9| Module | Purpose |
10|--------|---------|
11| `(sigil audio)` | Audio playback, sound effects, and music streaming |
13## System Prerequisites
15None to build. The playback device is [miniaudio](https://miniaud.io)
160.11.25, vendored under `vendor/miniaudio` (device layer only; see
17`vendor/miniaudio/VENDOR.md`), and it loads its backend at run time:
19- Linux: `libpulse.so` / `libpulse.so.0` first (PulseAudio, which on a PipeWire desktop is
20 pipewire-pulse; `PULSE_SINK` picks the sink), then `libasound.so.2`
21 (ALSA), then `libjack.so.0`. Nothing is linked: `-lasound` is gone.
22- Windows: WASAPI through `ole32` / `mmdevapi` / `avrt`, loaded at run
23 time; no link flags.
24- macOS: the CoreAudio frameworks, loaded by absolute path; no link flags.
26On Guix and Nix a profile's `lib/` is on no default loader path, so the
27loads go through the resolver in `src/c/dl-search.c` (a copy of
28sigil-desktop's, see `src/c/DL-SEARCH-ORIGIN.md`): `SIGIL_LIBRARY_PATH`,
29the active `guix shell` / `nix-shell`, then the user and system profiles
30of both. With no backend library anywhere miniaudio's Null backend runs
31the mixer in silence and one line on stderr says so.
32`(audio-backend)` answers the backend as a symbol and `(audio-device?)`
33whether it is a real one; `SIGIL_AUDIO_BACKEND=null` (or a backend name)
34pins the choice. `SIGIL_AUDIO_VERBOSE=1` prints the backend, the device and every
35resolver hit. A run manifest for a game on Guix adds `pulseaudio` (the
36client library, 187 MiB of closure) or `alsa-lib` (3 MiB; untested here, and the ALSA
37route would go through the machine's ALSA configuration to reach PipeWire) to
38sigil-desktop's `guix/run-wayland.scm`.
40OGG Vorbis encoding/decoding is **vendored** (libogg 1.3.6 +
41libvorbis 1.3.7, see `vendor/ogg/README.md` and
42`vendor/vorbis/README.md`). No system `libogg` / `libvorbis` /
43`libvorbisenc` packages need to be installed — sigil-audio links
44them statically into `libsigil-audio.a`. This adds ~1 MB to the
45final binary and unblocks Windows cross-compile.
47## Dependencies
49- sigil-stdlib
51## Build
53```sh
54sigil deps install
55sigil build # native (host platform)
56sigil build --config windows-amd64 # cross-compile for Windows (zig)
57```
59## Streaming audio sink
61Three playback paths coexist in `(sigil audio)`:
631. **`load-sound` + `play-sound`** — short SFX, decoded once into
64 memory, fired from Sigil, mixed on the audio thread.
652. **`play-music`** — long OGG, streamed from disk on the audio
66 thread via stb_vorbis.
673. **`open-audio-stream` + `push-audio-samples`** — caller-driven
68 streaming sink. The caller produces interleaved float32 PCM
69 (any thread) and pushes it into a lockless SPSC ring; the audio
70 thread drains the ring into its output buffer. Use this for
71 live / generative audio (motif streaming render, live-coded
72 synths, etc.).
74Example — play a 440 Hz sine wave for 1 second:
76```scheme
77(import (sigil audio) (sigil math))
79(audio-setup)
81(define stream (open-audio-stream channels: 2
82 buffer-frames: 8192))
84(define sr 44100)
85(define pi 3.14159265358979)
86(define frames (* sr 1))
88;; Build a stereo float32 bytevector with a 440 Hz sine.
89(define samples
90 (let ((v (make-vector (* frames 2) 0.0)))
91 (let loop ((i 0))
92 (when (< i frames)
93 (let ((s (sin (* 2.0 pi 440.0 (/ i sr)))))
94 (vector-set! v (* i 2) s)
95 (vector-set! v (+ (* i 2) 1) s))
96 (loop (+ i 1))))
97 v))
99(define pcm (make-float-buffer samples))
101;; Push in chunks of whatever the ring has room for.
102(let loop ((remaining frames) (offset-frames 0))
103 (when (> remaining 0)
104 (let ((room (audio-stream-room stream)))
105 (if (= room 0)
106 (begin (sleep 0.005) (loop remaining offset-frames))
107 (let ((n (min remaining room)))
108 (push-audio-samples stream pcm n)
109 (loop (- remaining n) (+ offset-frames n)))))))
111(close-audio-stream! stream)
112```
114Notes:
115- Sample rate is fixed at 44100 to match the device's
116 configured rate; callers MUST match (no resampling).
117- `push-audio-samples` is non-blocking and returns the frame
118 count actually accepted — the caller decides whether to
119 retry or drop.
120- Streaming sinks coexist with `play-sound` / `play-music` —
121 they're an additional mix source, not a replacement.
122- Up to 4 streams may be open simultaneously.
123- Default `buffer-frames: 16384` (~370 ms at 44.1 kHz) is
124 safety margin against under-run, not added latency. Live
125 producers who want tighter reactivity can open with
126 `buffer-frames: 2048` or smaller.
128See `folio topics/sigil-audio-streaming-sink-architecture`
129for the SPSC ring design, threading model, and under-run /
130over-run semantics.
132## License
134BSD-3-Clause.
136Vendored libogg (1.3.6) and libvorbis (1.3.7) are also distributed
137under BSD-3-Clause (see `vendor/ogg/COPYING` and
138`vendor/vorbis/COPYING`). Both licenses are compatible with this
139package's BSD-3-Clause terms.