Commit6a39a5fdRecorded20 Sep 2026Repositorysigil-audio

Move the playback device to miniaudio; link no audio library

Message

sokol_audio (ALSA, -lasound) is replaced by miniaudio 0.11.25, vendored unpatched under vendor/miniaudio (VENDOR.md has the pin) and compiled once in src/c/miniaudio-impl.c with the device layer only (MA_NO_* for decoding, encoding, generation, resource manager, node graph, engine; MA_API static). audio.c's audio-setup opens an ma_device (playback, f32, 2 channels, 44100) through src/c/audio-device.h; the mixer and the streaming ring are untouched. The stream is named after the program.

Every backend is loaded at run time: libpulse.so(.0) first on Linux (pipewire-pulse on a PipeWire desktop, PULSE_SINK routes), then libasound.so.2, then libjack.so.0; ole32/mmdevapi/avrt on Windows; the CoreAudio frameworks by absolute path on macOS. link-flags are empty on every target. Those loads go through sigil_audio_dl_open in src/c/dl-search.c, a byte-identical copy of sigil-desktop's resolver (src/c/DL-SEARCH-ORIGIN.md records the hash, the test enforces it), reached with a define ahead of the implementation; only sigil-desktop's copy defines dlopen itself, so a game linking both has one dlopen and an audio-only binary still resolves Guix and Nix profiles. With no backend library anywhere miniaudio's Null backend runs the mixer in silence and one stderr line says so; audio-setup still succeeds, as under sokol_audio.

manifest.scm needs nothing audio-related to build (pulseaudio for the device test). README and the 0.12.2 CHANGELOG entry.

Changed
 CHANGELOG.md                 |    35 +
 README.md                    |    30 +-
 manifest.scm                 |    17 +-
 package.sgl                  |    66 +-
 src/c/DL-SEARCH-ORIGIN.md    |    18 +
 src/c/audio-device.h         |    22 +
 src/c/audio.c                |    23 +-
 src/c/dl-search.c            |   245 +
 src/c/miniaudio-impl.c       |   173 +
 src/c/sokol-audio.c          |    15 -
 test/dl-search-probe.c       |    31 +
 test/dl-search-test.sh       |    69 +
 vendor/miniaudio/LICENSE     |    47 +
 vendor/miniaudio/VENDOR.md   |    41 +
 vendor/miniaudio/miniaudio.h | 95864 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/sokol/sokol_audio.h   |  2663 -----
 vendor/sokol/sokol_log.h     |   334 -
 17 files changed, 96638 insertions(+), 3055 deletions(-)
Diff
CHANGELOG.mdmodified
@@ -5,6 +5,41 @@ All notable changes to this project will be documented in this file.
5
The format is based on [Keep a Changelog](https://keepachangelog.com/),
6
and this project adheres to [Semantic Versioning](https://semver.org/).
7
+8
## [0.12.2] - unreleased
+9
+10
### Changed
+11
+12
- **The playback device is miniaudio 0.11.25** (vendored, unpatched,
+13
device layer only), replacing sokol_audio. Backends are loaded at run
+14
time: on Linux PulseAudio (`libpulse.so.0`; pipewire-pulse on a
+15
PipeWire desktop), then ALSA, then JACK; WASAPI on Windows; CoreAudio
+16
on macOS. **Nothing audio-related is linked any more**: `-lasound`,
+17
`AudioToolbox` and `ole32`/`winmm`/`ksuser` are gone from
+18
`link-flags`. A game's NEEDED list loses `libasound.so.2`.
+19
- The stream is named after the program (`program_invocation_short_name`),
+20
so `pactl list sink-inputs` shows the game, not "miniaudio".
+21
- With no backend library on the machine the mixer runs silent on
+22
miniaudio's Null backend and one stderr line says so; `audio-setup`
+23
still succeeds and `(audio-initialized?)` is `#t`, as under sokol_audio.
+24
- `src/c/dl-search.c`, a byte-identical copy of sigil-desktop's resolver
+25
(`src/c/DL-SEARCH-ORIGIN.md` records the hash and `test/dl-search-test.sh`
+26
enforces it), finds backend libraries in Guix and Nix profiles when the
+27
loader alone cannot; miniaudio's `dlopen` is redirected to it. A program
+28
that links sigil-desktop too has both copies; only sigil-desktop's
+29
defines `dlopen` itself.
+30
- `SIGIL_AUDIO_VERBOSE=1` prints the backend, the device, the format
+31
conversion miniaudio applies and every resolver hit.
+32
- `manifest.scm` no longer needs `alsa-lib` to build; it lists `pulseaudio`
+33
for the tests that open a device.
+34
+35
### Removed
+36
+37
- `vendor/sokol` (sokol_audio.h, sokol_log.h) and `src/c/sokol-audio.c`.
+38
+39
### Requires
+40
+41
- Sigil 0.20+, unchanged. The Sigil API is unchanged.
+42
43
## [0.12.0] - 2026-07-10
44
45
### Added
README.mdmodified
@@ -2,7 +2,7 @@
2
3
Audio playback and streaming for [Sigil](https://codeberg.org/sigil/sigil).
4
−5
Provides audio playback capabilities via sokol_audio. Sound effects are loaded into memory, music is streamed via stb_vorbis.
+5
Provides 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
@@ -12,10 +12,28 @@ Provides audio playback capabilities via sokol_audio. Sound effects are loaded i
12
13
## System Prerequisites
14
−15
- ALSA development headers on Linux (`libasound2-dev` / `alsa-lib-devel`)
−16
- AudioToolbox framework on macOS (included with Xcode)
−17
- Windows: nothing extra — sokol_audio drives WASAPI through
−18
`ole32` / `winmm` / `ksuser`, all bundled with the OS.
+15
None to build. The playback device is [miniaudio](https://miniaud.io)
+16
0.11.25, vendored under `vendor/miniaudio` (device layer only; see
+17
`vendor/miniaudio/VENDOR.md`), and it loads its backend at run time:
+18
+19
- Linux: `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.
+25
+26
On Guix and Nix a profile's `lib/` is on no default loader path, so the
+27
loads go through the resolver in `src/c/dl-search.c` (a copy of
+28
sigil-desktop's, see `src/c/DL-SEARCH-ORIGIN.md`): `SIGIL_LIBRARY_PATH`,
+29
the active `guix shell` / `nix-shell`, then the user and system profiles
+30
of both. With no backend library anywhere miniaudio's Null backend runs
+31
the mixer in silence and one line on stderr says so.
+32
`SIGIL_AUDIO_VERBOSE=1` prints the backend, the device and every
+33
resolver hit. A run manifest for a game on Guix adds `pulseaudio` (the
+34
client library, 187 MiB of closure) or `alsa-lib` (3 MiB, but the ALSA
+35
route needs the system's ALSA configuration to reach PipeWire) to
+36
sigil-desktop's `guix/run-wayland.scm`.
37
38
OGG Vorbis encoding/decoding is **vendored** (libogg 1.3.6 +
39
libvorbis 1.3.7, see `vendor/ogg/README.md` and
@@ -92,7 +110,7 @@ Example — play a 440 Hz sine wave for 1 second:
110
```
111
112
Notes:
−95
- Sample rate is fixed at 44100 to match sokol_audio's
+113
- Sample rate is fixed at 44100 to match the device's
114
configured rate; callers MUST match (no resampling).
115
- `push-audio-samples` is non-blocking and returns the frame
116
count actually accepted — the caller decides whether to
manifest.scmmodified
@@ -1,14 +1,19 @@
1
;; sigil-audio Development Environment
2
;; Use with: guix shell -m manifest.scm
−3
−4
;; libogg / libvorbis used to live here but are now vendored under
−5
;; vendor/ogg + vendor/vorbis (see package.sgl), so the system
−6
;; packages are no longer required to build sigil-audio.
+3
;;
+4
;; Nothing audio-related is needed to BUILD: miniaudio (vendored) loads its
+5
;; backends at run time, libogg/libvorbis are vendored. At RUN time on
+6
;; Guix a program needs one backend library in a profile the resolver
+7
;; (src/c/dl-search.c) searches: `pulseaudio` for libpulse.so.0 (the first
+8
;; backend miniaudio tries; on a PipeWire desktop that is pipewire-pulse,
+9
;; and PULSE_SINK routes it), or `alsa-lib` for libasound.so.2 (the ALSA
+10
;; backend; going through PipeWire's ALSA plugin needs the system ALSA
+11
;; configuration, so prefer pulseaudio in a pure shell).
12
13
(specifications->manifest
14
'("gcc-toolchain"
15
"pkg-config"
16
"binutils"
17
−13
;; Audio playback (Sokol backend) — still a system dep on Linux.
−14
"alsa-lib"))
18
No newline at end of file
+19
;; run time only, for the tests that open a device
+20
"pulseaudio"))
package.sglmodified
@@ -1,11 +1,12 @@
1
;;; sigil-audio - Audio playback for Sigil
2
;;;
−3
;;; Provides audio playback capabilities via sokol_audio.
+3
;;; Provides audio playback through miniaudio (vendored; the device layer
+4
;;; only, every backend loaded at run time).
5
;;; Sound effects are loaded into memory, music is streamed via stb_vorbis.
6
;;; OGG Vorbis encoding ships in-tree via vendored libogg + libvorbis.
7
8
;; skip-on-wasm wraps a build step so it is a no-op on wasm/wasi targets.
−8
;; The native audio C (sokol_audio, vendored libogg/libvorbis) has no
+9
;; The native audio C (miniaudio, vendored libogg/libvorbis) has no
10
;; wasm32-wasi backend; on wasm the (sigil audio sink) facade delegates to
11
;; the WebAudio bridge, so none of this C is needed. Only compile-sigil-modules
12
;; runs on wasm, producing the facade + module bytecode. Native builds are
@@ -20,7 +21,7 @@
21
(step ctx))))))
22
(package
23
name: "sigil-audio"
−23
version: "0.12.1"
+24
version: "0.12.2"
25
sigil: "0.20"
26
description: "Audio playback and streaming for Sigil"
27
url: "https://codeberg.org/sigil/sigil-audio"
@@ -59,32 +60,30 @@
60
libraries: (list
61
(library
62
name: 'sigil-audio
−62
c-sources: '("src/c/sokol-audio.c" "src/c/audio.c" "src/c/audio-stream.c" "src/c/ogg-encode.c")
+63
c-sources: '("src/c/miniaudio-impl.c" "src/c/dl-search.c" "src/c/audio.c" "src/c/audio-stream.c" "src/c/ogg-encode.c")
64
;; vendor/ogg/include + vendor/vorbis/include expose the public
65
;; ogg/vorbis headers consumed by ogg-encode.c. vendor/vorbis/lib
66
;; is needed because libvorbis internal .c files include their
67
;; siblings by bare filename (e.g. #include "codebook.h").
−67
c-include-dirs: '("vendor/sokol" "vendor/stb"
+68
c-include-dirs: '("vendor/stb"
69
"vendor/ogg/include"
70
"vendor/vorbis/include"
71
"vendor/vorbis/lib")
72
native-init: "sigil__init_sigil_audio_module"
−72
;; Platform-specific linker flags for sokol_audio's audio backend.
−73
;; OGG Vorbis is now vendored — no more -lvorbis/-lvorbisenc/-logg.
+73
;; Platform linker flags: none. miniaudio loads every backend at
+74
;; run time (libpulse.so.0, libasound.so.2, libjack.so.0 on Linux;
+75
;; ole32/mmdevapi/avrt/winmm/dsound DLLs on Windows; the CoreAudio
+76
;; frameworks by absolute path on macOS), so -lasound, AudioToolbox
+77
;; and ole32/winmm/ksuser are gone. pthread and dl are libc's on
+78
;; glibc 2.34+ (sigil-build adds -lpthread -ldl on Linux anyway).
79
;;
−75
;; Procedural (lambda ctx) form so cross-compile configs evaluate
−76
;; the right branch at link time. (case (host-os) ...) was evaluated
−77
;; once at package-load on the host, leaking Linux flags into
−78
;; cross builds.
+80
;; Procedural (lambda ctx) form kept so a target can add a flag
+81
;; later without going back to the host-evaluated (case (host-os)).
82
link-flags: (lambda (ctx)
83
(case (target-os ctx)
−81
((linux) '("-lasound"))
−82
((darwin) '("-framework" "AudioToolbox"))
−83
;; sokol_audio's WASAPI backend uses CoCreateInstance
−84
;; / CoInitializeEx (ole32). winmm + ksuser are
−85
;; carried for completeness against future sokol
−86
;; revisions that touch waveOut / kernel-streaming.
−87
((windows) '("-lole32" "-lwinmm" "-lksuser"))
+84
((linux) '())
+85
((darwin) '())
+86
((windows) '())
87
(else '())))))
88
89
dev-dependencies: (list
@@ -176,10 +175,37 @@
175
"-Wno-strict-aliasing"
176
"-fno-sanitize=undefined"))))
177
+178
;; miniaudio: the playback device, compiled once here with the
+179
;; device layer only (the MA_NO_* cuts are in the file). Its
+180
;; runtime dlopen of libpulse / libasound / libjack goes through
+181
;; the resolver in dl-search.c (shared with sigil-desktop, which
+182
;; is the copy that may define dlopen itself; this one exposes the
+183
;; named sigil_audio_dl_open that miniaudio-impl.c redirects to).
+184
;; dl-search.c is Linux-only (Guix and Nix profiles); the other
+185
;; targets compile miniaudio alone. Warnings relaxed for the
+186
;; vendored header only.
+187
(skip-on-wasm
+188
(lambda (ctx)
+189
((compile-c-sources
+190
sources: (if (eq? (target-os ctx) 'linux)
+191
'("src/c/miniaudio-impl.c" "src/c/dl-search.c")
+192
'("src/c/miniaudio-impl.c"))
+193
include-dirs: '("vendor/miniaudio" "src/c")
+194
flags: '("-std=c11" "-D_GNU_SOURCE"
+195
"-DSIGIL_DL_SEARCH_OPEN=sigil_audio_dl_open"
+196
"-DSIGIL_DL_SEARCH_SUMMARY=sigil_audio_dlopen_search_summary"
+197
"-DSIGIL_DL_SEARCH_TAG=\"sigil-audio\""
+198
"-DSIGIL_DL_SEARCH_VERBOSE_ENV=\"SIGIL_AUDIO_VERBOSE\""
+199
"-DSIGIL_DL_SEARCH_INTERPOSE=0"
+200
"-Wno-unused-parameter" "-Wno-unused-function"
+201
"-Wno-unused-variable" "-Wno-unused-but-set-variable"
+202
"-Wno-sign-compare"))
+203
ctx)))
+204
205
;; sigil-audio's own C sources.
206
(skip-on-wasm (compile-c-sources
−181
sources: '("src/c/sokol-audio.c" "src/c/audio.c" "src/c/audio-stream.c" "src/c/ogg-encode.c")
−182
include-dirs: '("vendor/sokol" "vendor/stb"
+207
sources: '("src/c/audio.c" "src/c/audio-stream.c" "src/c/ogg-encode.c")
+208
include-dirs: '("vendor/stb"
209
"vendor/ogg/include"
210
"vendor/vorbis/include")
211
flags: (with-sigil-c-flags '("-std=c11" "-D_GNU_SOURCE"
src/c/DL-SEARCH-ORIGIN.mdadded
@@ -0,0 +1,18 @@
+1
# dl-search.c is a copy
+2
+3
`dl-search.c` (and `test/dl-search-probe.c`, `test/dl-search-test.sh`) is
+4
sigil-desktop's `src/c/dl-search.c`, byte for byte, configured here with
+5
`-DSIGIL_DL_SEARCH_INTERPOSE=0` and the `sigil_audio_*` names (see
+6
`package.sgl`). The one file serves both packages because a program may
+7
link either without the other (a game links both; motif's CLI links only
+8
sigil-audio), and a shared package for 200 lines would cost a repository
+9
and a release cycle in every consumer. Two copies with recorded
+10
provenance is the estate's vendoring rule applied to its own code.
+11
+12
Origin: sigil-desktop `src/c/dl-search.c`, sha256
+13
+14
c3495d28b963ff5defe58375153c96e4d9e86ced6b9d3e4fa1fbb234bac3881a
+15
+16
Change it in sigil-desktop first, copy it here, update this hash;
+17
`test/dl-search-test.sh` refuses a copy whose hash differs from the one
+18
recorded here.
src/c/audio-device.hadded
@@ -0,0 +1,22 @@
+1
/*
+2
* audio-device.h - the playback device (miniaudio-impl.c) as audio.c sees it
+3
*/
+4
+5
#ifndef SIGIL_AUDIO_DEVICE_H
+6
#define SIGIL_AUDIO_DEVICE_H
+7
+8
/* Fill `frames` frames of interleaved f32 samples for `channels` channels;
+9
* runs on the device's own thread. */
+10
typedef void (*sigil_audio_stream_cb)(float *buffer, int frames, int channels);
+11
+12
/* Open and start the playback device: 0 on success, -1 when no backend
+13
* could be initialised or no device opened (already reported on stderr). */
+14
int sigil_audio_device_open(int sample_rate, int channels, int buffer_frames,
+15
sigil_audio_stream_cb stream_cb);
+16
void sigil_audio_device_close(void);
+17
+18
/* miniaudio's name for the backend in use ("PulseAudio", "ALSA", "JACK",
+19
* "WASAPI", "Core Audio", ...), or NULL when no device is open. */
+20
const char *sigil_audio_device_backend(void);
+21
+22
#endif /* SIGIL_AUDIO_DEVICE_H */
src/c/audio.cmodified
@@ -1,7 +1,8 @@
1
/*
2
* audio.c - Sigil Audio Module
3
*
−4
* Wraps sokol_audio.h to provide audio playback capabilities.
+4
* Audio playback: a mixer of loaded sounds and one streamed music track over
+5
* the playback device in miniaudio-impl.c (miniaudio).
6
* Uses stb_vorbis for OGG decoding.
7
*
8
* Sound effects are loaded entirely into memory.
@@ -15,8 +16,8 @@
16
#include <string.h>
17
#include <math.h>
18
−18
/* Sokol headers (implementation is in sokol.c) */
−19
#include "sokol_audio.h"
+19
/* The playback device (miniaudio, in miniaudio-impl.c) */
+20
#include "audio-device.h"
21
22
/* stb_vorbis for OGG decoding (implementation in stb_impl.c) */
23
#include "stb_vorbis.c"
@@ -246,13 +247,13 @@ static Value native_audio_setup(SigilVM *vm, int argc, Value *args)
247
return SIGIL_NIL;
248
}
249
−249
saudio_desc desc = {
−250
.stream_cb = audio_callback,
−251
.num_channels = 2,
−252
.sample_rate = 44100,
−253
.buffer_frames = 2048
−254
};
−255
saudio_setup(&desc);
+250
/* No device (a headless runner, no sound server, no ALSA card) is not
+251
* an error, as it was not under sokol_audio: the mixer runs with no
+252
* output, every play is a no-op, and (audio-initialized?) is still
+253
* #t. miniaudio-impl.c has already said why on stderr. */
+254
if (sigil_audio_device_open(44100, 2, 2048, audio_callback) != 0) {
+255
fprintf(stderr, "sigil-audio: continuing without an audio device\n");
+256
}
257
258
/* Clear playing sounds */
259
memset(g_playing_sounds, 0, sizeof(g_playing_sounds));
@@ -287,7 +288,7 @@ static Value native_audio_shutdown(SigilVM *vm, int argc, Value *args)
288
free(g_music.filepath);
289
g_music.filepath = NULL;
290
−290
saudio_shutdown();
+291
sigil_audio_device_close();
292
g_audio_initialized = false;
293
}
294
src/c/dl-search.cadded
@@ -0,0 +1,245 @@
+1
/*
+2
* dl-search.c - a dlopen that also looks in the profile library directories
+3
*
+4
* GLFW loads every platform library at run time by bare soname:
+5
* dlopen("libwayland-client.so.0"), dlopen("libX11.so.6"), dlopen("libEGL.so.1").
+6
* The dynamic loader finds such a name through LD_LIBRARY_PATH, the
+7
* caller's RUNPATH and the system default directories. On Guix and Nix none
+8
* of those name a profile's lib directory: the profile sets no
+9
* LD_LIBRARY_PATH, and the distribution's own packages are patched to
+10
* dlopen absolute store paths, which a user's binary cannot be.
+11
*
+12
* In sigil-desktop this file defines dlopen in the executable. The executable's symbols come
+13
* first in the lookup order, so every dlopen in the process (GLFW's in
+14
* posix_module.c, and any other loader compiled into the program) resolves
+15
* here; in sigil-audio it exposes a named resolver that miniaudio calls
+16
* instead of dlopen. It tries the name as given first, so on a system where the loader
+17
* already finds the library nothing changes. When that fails and the name
+18
* is a bare soname (no slash), it retries the name under each entry of the
+19
* table below, in order. The real dlopen is reached with
+20
* dlsym(RTLD_NEXT, "dlopen"). A library found this way loads its own
+21
* dependencies through its RUNPATH, which on Guix and Nix names their
+22
* store paths, so one absolute path is enough.
+23
*
+24
* A NEEDED entry (a library linked with -l) is resolved by the loader
+25
* before main runs and cannot be helped here; only dlopen is covered.
+26
*
+27
* The verbose env var (SIGIL_DESKTOP_VERBOSE=1 in sigil-desktop,
+28
* SIGIL_AUDIO_VERBOSE=1 in sigil-audio) prints, for every fallback that succeeds, the
+29
* soname, the table entry and the path, and whether /gnu/store and
+30
* /nix/store exist on this machine.
+31
*
+32
* Linux only: on macOS the two-level namespace binds libraries to
+33
* libSystem's dlopen, and on Windows LoadLibrary has its own search order.
+34
*/
+35
+36
+37
/* ---- per-package configuration ---------------------------------------
+38
* This file is shared by sigil-desktop and sigil-audio, byte for byte
+39
* (each repo's test checks its copy against the other's recorded hash).
+40
* The package that compiles it says, with -D flags:
+41
* SIGIL_DL_SEARCH_OPEN the resolver's name, e.g. sigil_desktop_dl_open
+42
* SIGIL_DL_SEARCH_SUMMARY the summary function's name
+43
* SIGIL_DL_SEARCH_TAG the log prefix, e.g. "sigil-desktop"
+44
* SIGIL_DL_SEARCH_VERBOSE_ENV the env var that turns the trace on
+45
* SIGIL_DL_SEARCH_INTERPOSE 1 to also define dlopen itself (one package
+46
* per executable may; sigil-desktop does),
+47
* 0 to expose only the named resolver (a
+48
* loader compiled in the same package calls
+49
* it, e.g. miniaudio through a define) */
+50
#if !defined(SIGIL_DL_SEARCH_OPEN) || !defined(SIGIL_DL_SEARCH_SUMMARY) || \
+51
!defined(SIGIL_DL_SEARCH_TAG) || !defined(SIGIL_DL_SEARCH_VERBOSE_ENV) || \
+52
!defined(SIGIL_DL_SEARCH_INTERPOSE)
+53
#error "dl-search.c: define SIGIL_DL_SEARCH_OPEN, _SUMMARY, _TAG, _VERBOSE_ENV and _INTERPOSE (see the file header)"
+54
#endif
+55
#ifndef _GNU_SOURCE
+56
#define _GNU_SOURCE
+57
#endif
+58
#include <dlfcn.h>
+59
#include <limits.h>
+60
#include <stdio.h>
+61
#include <stdlib.h>
+62
#include <string.h>
+63
#include <sys/stat.h>
+64
+65
/* ---- the search table ------------------------------------------------ */
+66
+67
typedef enum {
+68
ENTRY_ENV_COLON_DIRS, /* env var: colon-separated directories */
+69
ENTRY_ENV_SPACE_ROOTS, /* env var: space-separated roots, suffix appended */
+70
ENTRY_ENV_ROOT, /* env var: one root, suffix appended */
+71
ENTRY_HOME, /* $HOME + suffix */
+72
ENTRY_USER, /* prefix + $USER + suffix */
+73
ENTRY_DIR /* a fixed directory */
+74
} entry_kind;
+75
+76
typedef struct {
+77
entry_kind kind;
+78
const char *name; /* env var, prefix, or the directory itself */
+79
const char *suffix; /* appended to each root (may be "") */
+80
const char *only_if; /* env var that must be set for the row to apply, or NULL */
+81
} search_entry;
+82
+83
/* One row per source of library directories; adding a distribution is a
+84
* row. Every row is walked on every machine (Guix and Nix coexist: David
+85
* runs Nix inside Guix), so there is no "which distro" branch and a
+86
* library may come from a Nix profile while its own dependency, resolved
+87
* by RUNPATH, comes from a Guix one. Order: the user's explicit override,
+88
* the active environment (guix shell, nix-shell, nix-ld), then the user
+89
* profiles, then the system profiles, Guix and Nix interleaved. When two
+90
* rows offer the same soname the first hit wins and the trace names it. */
+91
static const search_entry SEARCH_TABLE[] = {
+92
/* explicit override */
+93
{ ENTRY_ENV_COLON_DIRS, "SIGIL_LIBRARY_PATH", "", NULL },
+94
/* the active environment */
+95
{ ENTRY_ENV_ROOT, "GUIX_ENVIRONMENT", "/lib", NULL }, /* guix shell */
+96
{ ENTRY_ENV_SPACE_ROOTS, "buildInputs", "/lib", "IN_NIX_SHELL" }, /* nix-shell */
+97
{ ENTRY_ENV_SPACE_ROOTS, "nativeBuildInputs", "/lib", "IN_NIX_SHELL" },
+98
{ ENTRY_ENV_COLON_DIRS, "NIX_LD_LIBRARY_PATH", "", NULL }, /* nix-ld */
+99
/* user profiles */
+100
{ ENTRY_HOME, "", "/.guix-home/profile/lib", NULL }, /* Guix Home */
+101
{ ENTRY_HOME, "", "/.guix-profile/lib", NULL }, /* guix package */
+102
{ ENTRY_HOME, "", "/.nix-profile/lib", NULL }, /* nix-env, nix profile */
+103
{ ENTRY_USER, "/etc/profiles/per-user/", "/lib", NULL }, /* NixOS per-user */
+104
/* system profiles */
+105
{ ENTRY_DIR, "/run/current-system/profile/lib", "", NULL }, /* Guix System */
+106
{ ENTRY_DIR, "/run/current-system/sw/lib", "", NULL }, /* NixOS */
+107
};
+108
#define SEARCH_TABLE_LEN (sizeof SEARCH_TABLE / sizeof SEARCH_TABLE[0])
+109
+110
/* ---- the real dlopen ------------------------------------------------- */
+111
+112
typedef void *(*sigil_dlopen_fn)(const char *, int);
+113
+114
static sigil_dlopen_fn real_dlopen(void)
+115
{
+116
static sigil_dlopen_fn fn = NULL;
+117
if (!fn) fn = (sigil_dlopen_fn)dlsym(RTLD_NEXT, "dlopen");
+118
return fn;
+119
}
+120
+121
static int dir_exists(const char *path)
+122
{
+123
struct stat st;
+124
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
+125
}
+126
+127
static void trace_found(const search_entry *e, const char *name, const char *path)
+128
{
+129
if (!getenv(SIGIL_DL_SEARCH_VERBOSE_ENV)) return;
+130
const char *label = (e->kind == ENTRY_DIR) ? e->name
+131
: (e->kind == ENTRY_HOME) ? "HOME"
+132
: (e->kind == ENTRY_USER) ? "USER"
+133
: e->name;
+134
fprintf(stderr, SIGIL_DL_SEARCH_TAG ": dlopen %s: found via %s%s as %s (/gnu/store %s, /nix/store %s)\n",
+135
name, label, e->suffix, path,
+136
dir_exists("/gnu/store") ? "present" : "absent",
+137
dir_exists("/nix/store") ? "present" : "absent");
+138
}
+139
+140
/* dir[0..dirlen) + suffix + "/" + name, through the real dlopen */
+141
static void *try_dir(sigil_dlopen_fn real, const search_entry *e,
+142
const char *dir, size_t dirlen, const char *suffix,
+143
const char *name, int flags)
+144
{
+145
char path[PATH_MAX];
+146
size_t sufflen = strlen(suffix), namelen = strlen(name);
+147
if (dirlen == 0 || dirlen + sufflen + 1 + namelen + 1 > sizeof path) return NULL;
+148
memcpy(path, dir, dirlen);
+149
memcpy(path + dirlen, suffix, sufflen);
+150
path[dirlen + sufflen] = '/';
+151
memcpy(path + dirlen + sufflen + 1, name, namelen + 1);
+152
void *h = real(path, flags);
+153
if (h) trace_found(e, name, path);
+154
return h;
+155
}
+156
+157
/* every item of a separated list */
+158
static void *try_list(sigil_dlopen_fn real, const search_entry *e, const char *list,
+159
char sep, const char *name, int flags)
+160
{
+161
if (!list) return NULL;
+162
while (*list) {
+163
const char *end = strchr(list, sep);
+164
size_t len = end ? (size_t)(end - list) : strlen(list);
+165
void *h = try_dir(real, e, list, len, e->suffix, name, flags);
+166
if (h) return h;
+167
if (!end) break;
+168
list = end + 1;
+169
}
+170
return NULL;
+171
}
+172
+173
static void *try_entry(sigil_dlopen_fn real, const search_entry *e, const char *name, int flags)
+174
{
+175
if (e->only_if && !getenv(e->only_if)) return NULL;
+176
const char *v;
+177
switch (e->kind) {
+178
case ENTRY_ENV_COLON_DIRS:
+179
return try_list(real, e, getenv(e->name), ':', name, flags);
+180
case ENTRY_ENV_SPACE_ROOTS:
+181
return try_list(real, e, getenv(e->name), ' ', name, flags);
+182
case ENTRY_ENV_ROOT:
+183
v = getenv(e->name);
+184
return v ? try_dir(real, e, v, strlen(v), e->suffix, name, flags) : NULL;
+185
case ENTRY_HOME:
+186
v = getenv("HOME");
+187
return v ? try_dir(real, e, v, strlen(v), e->suffix, name, flags) : NULL;
+188
case ENTRY_USER: {
+189
v = getenv("USER");
+190
if (!v || !*v) return NULL;
+191
char root[PATH_MAX];
+192
int n = snprintf(root, sizeof root, "%s%s", e->name, v);
+193
if (n <= 0 || (size_t)n >= sizeof root) return NULL;
+194
return try_dir(real, e, root, (size_t)n, e->suffix, name, flags);
+195
}
+196
case ENTRY_DIR:
+197
return try_dir(real, e, e->name, strlen(e->name), e->suffix, name, flags);
+198
}
+199
return NULL;
+200
}
+201
+202
/* ---- the resolver ---------------------------------------------------- */
+203
+204
void *SIGIL_DL_SEARCH_OPEN(const char *name, int flags)
+205
{
+206
sigil_dlopen_fn real = real_dlopen();
+207
if (!real) {
+208
/* No next dlopen: only a fully static link gets here; say so, since
+209
* the caller then reports the library as missing. */
+210
fprintf(stderr, SIGIL_DL_SEARCH_TAG ": dlopen(%s): no underlying dlopen (static link?)\n",
+211
name ? name : "(null)");
+212
return NULL;
+213
}
+214
+215
void *h = real(name, flags);
+216
if (h || !name || strchr(name, '/')) return h;
+217
+218
/* A bare soname the loader could not find: the table, in order. */
+219
for (size_t i = 0; i < SEARCH_TABLE_LEN; i++) {
+220
h = try_entry(real, &SEARCH_TABLE[i], name, flags);
+221
if (h) return h;
+222
}
+223
+224
/* Nothing found: repeat the plain call so dlerror() names the soname
+225
* the caller asked for, not the last directory tried. */
+226
return real(name, flags);
+227
}
+228
+229
#if SIGIL_DL_SEARCH_INTERPOSE
+230
/* The executable's dlopen: every dlopen in the process resolves here. */
+231
void *dlopen(const char *name, int flags)
+232
{
+233
return SIGIL_DL_SEARCH_OPEN(name, flags);
+234
}
+235
#endif
+236
+237
/* Referenced from the package's own code so this object is always linked
+238
* ahead of libc, whatever order the linker meets the archive members in. */
+239
const char *SIGIL_DL_SEARCH_SUMMARY(void)
+240
{
+241
return "SIGIL_LIBRARY_PATH; $GUIX_ENVIRONMENT/lib; each $buildInputs and "
+242
"$nativeBuildInputs root + /lib under IN_NIX_SHELL; $NIX_LD_LIBRARY_PATH; ~/.guix-home/profile/lib; "
+243
"~/.guix-profile/lib; ~/.nix-profile/lib; /etc/profiles/per-user/$USER/lib; "
+244
"/run/current-system/profile/lib; /run/current-system/sw/lib";
+245
}
src/c/miniaudio-impl.cadded
@@ -0,0 +1,173 @@
+1
/*
+2
* miniaudio-impl.c - the audio device, over miniaudio
+3
*
+4
* The one translation unit that compiles miniaudio (vendor/miniaudio,
+5
* unpatched; VENDOR.md has the pin). It exposes three functions to audio.c
+6
* (audio-device.h) so the 95k-line header is included exactly once:
+7
* open a playback device with a stream callback, close it, name the
+8
* backend miniaudio picked.
+9
*
+10
* Every audio backend library is loaded at run time by miniaudio
+11
* (libpulse.so.0 first on Linux, then libasound.so.2, then libjack.so.0;
+12
* ole32/mmdevapi on Windows; the CoreAudio frameworks on macOS), so this
+13
* package links no audio library. Those loads go through
+14
* sigil_audio_dl_open (dl-search.c, the resolver shared with
+15
* sigil-desktop) instead of dlopen: the define below renames the call in
+16
* miniaudio's ma_dlopen, so a Guix or Nix profile's lib/ is searched when
+17
* the loader alone finds nothing. A program that also links sigil-desktop
+18
* has two copies of the resolver (one of them interposing dlopen for the
+19
* whole process); they agree on the table and never conflict, because
+20
* only sigil-desktop's copy defines the dlopen symbol.
+21
*/
+22
+23
#include "audio-device.h"
+24
+25
#include <stdio.h>
+26
#include <stdlib.h>
+27
#include <string.h>
+28
+29
#if defined(__linux__)
+30
/* The resolver (dl-search.c, SIGIL_DL_SEARCH_OPEN=sigil_audio_dl_open):
+31
* Linux only, like the file itself; Windows and macOS load by name or by
+32
* absolute framework path and need no help. */
+33
#include <dlfcn.h>
+34
void *sigil_audio_dl_open(const char *name, int flags);
+35
#define dlopen sigil_audio_dl_open
+36
#endif
+37
+38
/* Device layer only: this package decodes with stb_vorbis and encodes with
+39
* the vendored libvorbis; miniaudio's engine, node graph, resource manager,
+40
* generators, decoders and encoders are compiled out. */
+41
#define MA_NO_DECODING
+42
#define MA_NO_ENCODING
+43
#define MA_NO_GENERATION
+44
#define MA_NO_RESOURCE_MANAGER
+45
#define MA_NO_NODE_GRAPH
+46
#define MA_NO_ENGINE
+47
#define MA_API static
+48
#define MINIAUDIO_IMPLEMENTATION
+49
#include "miniaudio.h"
+50
+51
static ma_context g_context;
+52
static ma_device g_device;
+53
static int g_context_ready = 0;
+54
static int g_device_ready = 0;
+55
static sigil_audio_stream_cb g_stream_cb = NULL;
+56
static char g_app_name[128];
+57
+58
static void data_callback(ma_device *device, void *output, const void *input, ma_uint32 frame_count)
+59
{
+60
(void)input;
+61
if (g_stream_cb) {
+62
g_stream_cb((float *)output, (int)frame_count, (int)device->playback.channels);
+63
} else {
+64
memset(output, 0, (size_t)frame_count * device->playback.channels * sizeof(float));
+65
}
+66
}
+67
+68
static void log_callback(void *user, ma_uint32 level, const char *message)
+69
{
+70
(void)user;
+71
if (level <= MA_LOG_LEVEL_WARNING || getenv("SIGIL_AUDIO_VERBOSE")) {
+72
fprintf(stderr, "sigil-audio: miniaudio %s: %s", ma_log_level_to_string(level), message);
+73
}
+74
}
+75
+76
/* The name a sound server shows for this stream: the program's own, as
+77
* the process sees it, so `pactl list sink-inputs` names the game. */
+78
static const char *app_name(void)
+79
{
+80
if (g_app_name[0]) return g_app_name;
+81
const char *name = NULL;
+82
#if defined(__GLIBC__)
+83
extern char *program_invocation_short_name;
+84
name = program_invocation_short_name;
+85
#endif
+86
if (!name || !*name) name = "sigil-audio";
+87
snprintf(g_app_name, sizeof g_app_name, "%s", name);
+88
return g_app_name;
+89
}
+90
+91
int sigil_audio_device_open(int sample_rate, int channels, int buffer_frames,
+92
sigil_audio_stream_cb stream_cb)
+93
{
+94
if (g_device_ready) return 0;
+95
+96
ma_context_config cc = ma_context_config_init();
+97
cc.pulse.pApplicationName = app_name();
+98
cc.jack.pClientName = app_name();
+99
ma_log *log = NULL;
+100
static ma_log g_log;
+101
if (ma_log_init(NULL, &g_log) == MA_SUCCESS) {
+102
ma_log_register_callback(&g_log, ma_log_callback_init(log_callback, NULL));
+103
log = &g_log;
+104
}
+105
cc.pLog = log;
+106
if (ma_context_init(NULL, 0, &cc, &g_context) != MA_SUCCESS) {
+107
fprintf(stderr, "sigil-audio: no audio backend could be initialised\n");
+108
return -1;
+109
}
+110
g_context_ready = 1;
+111
if (g_context.backend == ma_backend_null) {
+112
/* No backend library was found (or none opened): miniaudio's Null
+113
* backend consumes frames in silence. Say so once, out loud, so a
+114
* silent game is not a mystery; the trace under SIGIL_AUDIO_VERBOSE
+115
* lists every directory the resolver tried. */
+116
fprintf(stderr, "sigil-audio: no audio backend library found "
+117
"(libpulse.so.0, libasound.so.2, libjack.so.0 on Linux); running silent. "
+118
"Put one in a profile the resolver searches or in SIGIL_LIBRARY_PATH.\n");
+119
}
+120
+121
g_stream_cb = stream_cb;
+122
ma_device_config dc = ma_device_config_init(ma_device_type_playback);
+123
dc.playback.format = ma_format_f32;
+124
dc.playback.channels = (ma_uint32)channels;
+125
dc.sampleRate = (ma_uint32)sample_rate;
+126
dc.periodSizeInFrames = (ma_uint32)buffer_frames;
+127
dc.dataCallback = data_callback;
+128
/* the stream callback writes every frame (it clears first) */
+129
dc.noPreSilencedOutputBuffer = MA_TRUE;
+130
dc.pulse.pStreamNamePlayback = app_name();
+131
if (ma_device_init(&g_context, &dc, &g_device) != MA_SUCCESS) {
+132
fprintf(stderr, "sigil-audio: %s: could not open a playback device\n",
+133
ma_get_backend_name(g_context.backend));
+134
ma_context_uninit(&g_context);
+135
g_context_ready = 0;
+136
return -1;
+137
}
+138
if (ma_device_start(&g_device) != MA_SUCCESS) {
+139
fprintf(stderr, "sigil-audio: %s: could not start the playback device\n",
+140
ma_get_backend_name(g_context.backend));
+141
ma_device_uninit(&g_device);
+142
ma_context_uninit(&g_context);
+143
g_context_ready = 0;
+144
return -1;
+145
}
+146
g_device_ready = 1;
+147
if (getenv("SIGIL_AUDIO_VERBOSE")) {
+148
fprintf(stderr, "sigil-audio: backend=%s device=\"%s\" format=f32 channels=%u rate=%u period=%u frames\n",
+149
ma_get_backend_name(g_context.backend), g_device.playback.name,
+150
g_device.playback.channels, g_device.sampleRate,
+151
g_device.playback.internalPeriodSizeInFrames);
+152
}
+153
return 0;
+154
}
+155
+156
void sigil_audio_device_close(void)
+157
{
+158
if (g_device_ready) {
+159
ma_device_uninit(&g_device);
+160
g_device_ready = 0;
+161
}
+162
if (g_context_ready) {
+163
ma_context_uninit(&g_context);
+164
g_context_ready = 0;
+165
}
+166
g_stream_cb = NULL;
+167
}
+168
+169
const char *sigil_audio_device_backend(void)
+170
{
+171
if (!g_device_ready) return NULL;
+172
return ma_get_backend_name(g_context.backend);
+173
}
src/c/sokol-audio.cdeleted
@@ -1,15 +0,0 @@
−1
/*
−2
* sokol-audio.c - Sokol implementation for sigil-audio
−3
*
−4
* Implements sokol_audio. Audio is independent of windowing.
−5
*
−6
* Note: sokol_log.h is included for declarations only (implementation in sigil-app).
−7
*/
−8
−9
/* Include sokol_log declarations WITHOUT implementation (implemented in sigil-app) */
−10
#include "sokol_log.h"
−11
−12
/* Now define SOKOL_IMPL for sokol_audio */
−13
#define SOKOL_IMPL
−14
−15
#include "sokol_audio.h"
test/dl-search-probe.cadded
@@ -0,0 +1,31 @@
+1
/* dl-search-probe.c - links dl-search.c and reports which file a soname
+2
* resolved to. Driven by test/dl-search-test.sh, compiled with the same
+3
* -D configuration as the package: with SIGIL_DL_SEARCH_INTERPOSE=1 it
+4
* calls plain dlopen (proving the interposition), with 0 it calls the
+5
* named resolver the package's loader calls.
+6
* usage: dl-search-probe <soname> exit 0 and print the path on success */
+7
#ifndef _GNU_SOURCE
+8
#define _GNU_SOURCE
+9
#endif
+10
#include <dlfcn.h>
+11
#include <link.h>
+12
#include <stdio.h>
+13
+14
void *SIGIL_DL_SEARCH_OPEN(const char *name, int flags);
+15
const char *SIGIL_DL_SEARCH_SUMMARY(void);
+16
+17
int main(int argc, char **argv)
+18
{
+19
if (argc != 2) { fprintf(stderr, "usage: %s <soname>\n", argv[0]); return 2; }
+20
#if SIGIL_DL_SEARCH_INTERPOSE
+21
void *h = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL);
+22
#else
+23
void *h = SIGIL_DL_SEARCH_OPEN(argv[1], RTLD_NOW | RTLD_LOCAL);
+24
#endif
+25
if (!h) { printf("UNRESOLVED %s: %s\n", argv[1], dlerror()); return 1; }
+26
struct link_map *lm = NULL;
+27
if (dlinfo(h, RTLD_DI_LINKMAP, &lm) == 0 && lm) printf("RESOLVED %s -> %s\n", argv[1], lm->l_name);
+28
else printf("RESOLVED %s\n", argv[1]);
+29
(void)SIGIL_DL_SEARCH_SUMMARY;
+30
return 0;
+31
}
test/dl-search-test.shadded
@@ -0,0 +1,69 @@
+1
#!/bin/sh
+2
# dl-search-test.sh - the interposed dlopen against fake Guix and Nix profiles.
+3
#
+4
# Builds a probe from src/c/dl-search.c, a throwaway shared object, and a
+5
# temp HOME holding an empty ~/.guix-home/profile/lib and a
+6
# ~/.nix-profile/lib that has the object; then asserts, in order:
+7
# 1. the plain loader cannot find the soname (the control: the fallback
+8
# is what resolves it, not LD_LIBRARY_PATH or the cache)
+9
# 2. it resolves, from the Nix profile, with the Guix row walked first
+10
# and empty (both distributions' rows are live at once)
+11
# 3. with a copy in the Guix profile too, the Guix row wins (first hit)
+12
# and the trace names it
+13
# 4. IN_NIX_SHELL + buildInputs resolves ahead of every profile, and the
+14
# same buildInputs without IN_NIX_SHELL resolves nothing
+15
# 5. SIGIL_LIBRARY_PATH resolves ahead of everything
+16
# Any assertion failing exits 1. Needs a C compiler: $CC, or the pinned
+17
# zig under ~/.sigil/toolchain/zig, or cc.
+18
set -u
+19
cd "$(dirname "$0")/.." || exit 2
+20
if [ -n "${CC:-}" ]; then :
+21
elif [ -x "$HOME/.sigil/toolchain/zig/zig" ]; then CC="$HOME/.sigil/toolchain/zig/zig cc"
+22
elif command -v cc >/dev/null 2>&1; then CC=cc
+23
else echo "SETUP-FAILED: no C compiler (set CC)"; exit 2; fi
+24
T=$(mktemp -d) || exit 2
+25
trap 'rm -rf "$T"' EXIT
+26
FAKE=libsigildlsearchfake.so.7
+27
printf 'int sigil_fake_answer(void) { return 42; }\n' > "$T/fake.c"
+28
$CC -shared -fPIC -o "$T/$FAKE" "$T/fake.c" || { echo "SETUP-FAILED: cannot build the fake library"; exit 2; }
+29
$CC -o "$T/probe" -DSIGIL_DL_SEARCH_OPEN=sigil_audio_dl_open -DSIGIL_DL_SEARCH_SUMMARY=sigil_audio_dlopen_search_summary -DSIGIL_DL_SEARCH_TAG=\"sigil-audio\" -DSIGIL_DL_SEARCH_VERBOSE_ENV=\"SIGIL_AUDIO_VERBOSE\" -DSIGIL_DL_SEARCH_INTERPOSE=0 test/dl-search-probe.c src/c/dl-search.c -ldl || { echo "SETUP-FAILED: cannot build the probe"; exit 2; }
+30
H="$T/home"; mkdir -p "$H/.guix-home/profile/lib" "$H/.nix-profile/lib" "$T/nixstore/foo-1.0/lib"
+31
cp "$T/$FAKE" "$H/.nix-profile/lib/"
+32
fails=0
+33
pass() { echo "PASS $1: $2"; }
+34
fail() { echo "FAIL $1: $2"; fails=$((fails + 1)); }
+35
run() { # name, expected-substring-of-stdout, env assignments...
+36
name=$1; want=$2; shift 2
+37
out=$(env -i HOME="$H" USER=sigiltest SIGIL_AUDIO_VERBOSE=1 "$@" "$T/probe" "$FAKE" 2>&1)
+38
case "$out" in *"$want"*) pass "$name" "$want";; *) fail "$name" "wanted [$want], got: $out";; esac
+39
}
+40
# 1. control: HOME with no profile dirs at all
+41
mkdir -p "$T/emptyhome"
+42
run control "UNRESOLVED $FAKE" HOME="$T/emptyhome"
+43
# 2. Nix profile only (the Guix row exists and is empty)
+44
run nix-profile "found via HOME/.nix-profile/lib as $H/.nix-profile/lib/$FAKE"
+45
# 3. both: Guix Home first
+46
cp "$T/$FAKE" "$H/.guix-home/profile/lib/"
+47
run first-hit-wins "found via HOME/.guix-home/profile/lib as $H/.guix-home/profile/lib/$FAKE"
+48
# 4. nix-shell inputs beat the profiles
+49
cp "$T/$FAKE" "$T/nixstore/foo-1.0/lib/"
+50
run nix-shell "found via buildInputs/lib as $T/nixstore/foo-1.0/lib/$FAKE" IN_NIX_SHELL=impure buildInputs="$T/nixstore/bar-2.0 $T/nixstore/foo-1.0"
+51
# 4b. the nix-shell rows apply only under IN_NIX_SHELL: same buildInputs, no
+52
# IN_NIX_SHELL, no copies in the profiles -> unresolved
+53
rm -f "$H/.guix-home/profile/lib/$FAKE" "$H/.nix-profile/lib/$FAKE"
+54
run nix-shell-gated "UNRESOLVED $FAKE" buildInputs="$T/nixstore/foo-1.0"
+55
cp "$T/$FAKE" "$H/.nix-profile/lib/"
+56
# 5. the override beats everything
+57
mkdir -p "$T/override"; cp "$T/$FAKE" "$T/override/"
+58
run override "found via SIGIL_LIBRARY_PATH as $T/override/$FAKE" SIGIL_LIBRARY_PATH="/nonexistent:$T/override" IN_NIX_SHELL=impure buildInputs="$T/nixstore/foo-1.0"
+59
# provenance: this copy must be the one src/c/DL-SEARCH-ORIGIN.md records
+60
if [ -f src/c/DL-SEARCH-ORIGIN.md ]; then
+61
want=$(grep -E '^ [0-9a-f]{64}$' src/c/DL-SEARCH-ORIGIN.md | tr -d ' ')
+62
have=$(sha256sum src/c/dl-search.c | cut -d' ' -f1)
+63
if [ "$want" = "$have" ]; then pass provenance "dl-search.c matches the hash in DL-SEARCH-ORIGIN.md"
+64
else fail provenance "dl-search.c sha256 $have, DL-SEARCH-ORIGIN.md records $want: recopy from sigil-desktop or update the record"; fi
+65
fi
+66
# the trace names the stores present on this machine
+67
out=$(env -i HOME="$H" USER=sigiltest SIGIL_AUDIO_VERBOSE=1 "$T/probe" "$FAKE" 2>&1)
+68
case "$out" in *"(/gnu/store "*", /nix/store "*")"*) pass trace-stores "$(echo "$out" | grep -o '(/gnu/store [a-z]*, /nix/store [a-z]*)' | head -1)";; *) fail trace-stores "no store note in: $out";; esac
+69
if [ $fails -eq 0 ]; then echo "GREEN: dl-search"; exit 0; else echo "RED: $fails failed"; exit 1; fi
vendor/miniaudio/LICENSEadded
@@ -0,0 +1,47 @@
+1
This software is available as a choice of the following licenses. Choose
+2
whichever you prefer.
+3
+4
===============================================================================
+5
ALTERNATIVE 1 - Public Domain (www.unlicense.org)
+6
===============================================================================
+7
This is free and unencumbered software released into the public domain.
+8
+9
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+10
software, either in source code form or as a compiled binary, for any purpose,
+11
commercial or non-commercial, and by any means.
+12
+13
In jurisdictions that recognize copyright laws, the author or authors of this
+14
software dedicate any and all copyright interest in the software to the public
+15
domain. We make this dedication for the benefit of the public at large and to
+16
the detriment of our heirs and successors. We intend this dedication to be an
+17
overt act of relinquishment in perpetuity of all present and future rights to
+18
this software under copyright law.
+19
+20
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+21
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+22
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+23
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+24
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+25
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+26
+27
For more information, please refer to <http://unlicense.org/>
+28
+29
===============================================================================
+30
ALTERNATIVE 2 - MIT No Attribution
+31
===============================================================================
+32
Copyright 2025 David Reid
+33
+34
Permission is hereby granted, free of charge, to any person obtaining a copy of
+35
this software and associated documentation files (the "Software"), to deal in
+36
the Software without restriction, including without limitation the rights to
+37
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+38
of the Software, and to permit persons to whom the Software is furnished to do
+39
so.
+40
+41
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+42
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+43
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+44
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+45
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+46
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+47
SOFTWARE.
48
No newline at end of file
vendor/miniaudio/VENDOR.mdadded
@@ -0,0 +1,41 @@
+1
# Vendored miniaudio
+2
+3
- Upstream: https://github.com/mackron/miniaudio
+4
- Tag: `0.11.25` (released 2026-03-03)
+5
- Source archive: `https://github.com/mackron/miniaudio/archive/refs/tags/0.11.25.tar.gz`,
+6
sha256 `b900edcffe979816e2560a0580b9b1216d674b4f17fbadeca8f777a7f8ab0274`
+7
- License: public domain (Unlicense) or MIT-0, at the user's choice; `LICENSE`
+8
is the archive's
+9
+10
## Rule
+11
+12
No local patches: `miniaudio.h` is byte-identical to the archive's. All
+13
configuration is `#define`s in `src/c/miniaudio-impl.c` ahead of the
+14
implementation (the `MA_NO_*` feature cuts, and `dlopen` redirected to this
+15
package's resolver). Upgrade by replacing the two files from a newer tag
+16
and updating this file.
+17
+18
Re-derive:
+19
+20
curl -sSL -o /tmp/ma.tar.gz https://github.com/mackron/miniaudio/archive/refs/tags/0.11.25.tar.gz
+21
sha256sum /tmp/ma.tar.gz # b900edcf...
+22
tar xzf /tmp/ma.tar.gz -C /tmp
+23
cmp vendor/miniaudio/miniaudio.h /tmp/miniaudio-0.11.25/miniaudio.h
+24
cmp vendor/miniaudio/LICENSE /tmp/miniaudio-0.11.25/LICENSE
+25
+26
## What this package uses
+27
+28
The device layer only: `ma_context` + `ma_device` in playback mode, f32,
+29
the data callback feeding from this package's own mixer and streaming
+30
ring. Decoding (stb_vorbis here), encoding (vendored libvorbis), the
+31
engine, node graph, resource manager and generators are compiled out.
+32
+33
Backends, in miniaudio's own priority order, every one loaded at run
+34
time by soname through `dlopen` (redirected to `sigil_audio_dl_open`, the
+35
resolver shared with sigil-desktop, so Guix and Nix profiles are searched):
+36
Linux `libpulse.so.0` (PulseAudio, which on a PipeWire desktop is
+37
pipewire-pulse), then `libasound.so.2` (ALSA), then `libjack.so.0`;
+38
Windows `ole32.dll`, `mmdevapi.dll`, `avrt.dll`, `user32.dll`,
+39
`advapi32.dll`, `winmm.dll`, `dsound.dll` (WASAPI first); macOS the
+40
CoreFoundation, CoreAudio and AudioToolbox frameworks by absolute path.
+41
Nothing audio-related is linked on any platform.
vendor/miniaudio/miniaudio.hadded
@@ -0,0 +1,95864 @@
+1
/*
+2
Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file.
+3
miniaudio - v0.11.25 - 2026-03-04
+4
+5
David Reid - mackron@gmail.com
+6
+7
Website: https://miniaud.io
+8
Documentation: https://miniaud.io/docs
+9
GitHub: https://github.com/mackron/miniaudio
+10
*/
+11
+12
/*
+13
1. Introduction
+14
===============
+15
To use miniaudio, just include "miniaudio.h" like any other header and add "miniaudio.c" to your
+16
source tree. If you don't want to add it to your source tree you can compile and link to it like
+17
any other library. Note that ABI compatibility is not guaranteed between versions, even with bug
+18
fix releases, so take care if compiling as a shared object.
+19
+20
miniaudio includes both low level and high level APIs. The low level API is good for those who want
+21
to do all of their mixing themselves and only require a light weight interface to the underlying
+22
audio device. The high level API is good for those who have complex mixing and effect requirements.
+23
+24
In miniaudio, objects are transparent structures. Unlike many other libraries, there are no handles
+25
to opaque objects which means you need to allocate memory for objects yourself. In the examples
+26
presented in this documentation you will often see objects declared on the stack. You need to be
+27
careful when translating these examples to your own code so that you don't accidentally declare
+28
your objects on the stack and then cause them to become invalid once the function returns. In
+29
addition, you must ensure the memory address of your objects remain the same throughout their
+30
lifetime. You therefore cannot be making copies of your objects.
+31
+32
A config/init pattern is used throughout the entire library. The idea is that you set up a config
+33
object and pass that into the initialization routine. The advantage to this system is that the
+34
config object can be initialized with logical defaults and new properties added to it without
+35
breaking the API. The config object can be allocated on the stack and does not need to be
+36
maintained after initialization of the corresponding object.
+37
+38
+39
1.1. Low Level API
+40
------------------
+41
The low level API gives you access to the raw audio data of an audio device. It supports playback,
+42
capture, full-duplex and loopback (WASAPI only). You can enumerate over devices to determine which
+43
physical device(s) you want to connect to.
+44
+45
The low level API uses the concept of a "device" as the abstraction for physical devices. The idea
+46
is that you choose a physical device to emit or capture audio from, and then move data to/from the
+47
device when miniaudio tells you to. Data is delivered to and from devices asynchronously via a
+48
callback which you specify when initializing the device.
+49
+50
When initializing the device you first need to configure it. The device configuration allows you to
+51
specify things like the format of the data delivered via the callback, the size of the internal
+52
buffer and the ID of the device you want to emit or capture audio from.
+53
+54
Once you have the device configuration set up you can initialize the device. When initializing a
+55
device you need to allocate memory for the device object beforehand. This gives the application
+56
complete control over how the memory is allocated. In the example below we initialize a playback
+57
device on the stack, but you could allocate it on the heap if that suits your situation better.
+58
+59
```c
+60
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
+61
{
+62
// In playback mode copy data to pOutput. In capture mode read data from pInput. In full-duplex mode, both
+63
// pOutput and pInput will be valid and you can move data from pInput into pOutput. Never process more than
+64
// frameCount frames.
+65
}
+66
+67
int main()
+68
{
+69
ma_device_config config = ma_device_config_init(ma_device_type_playback);
+70
config.playback.format = ma_format_f32; // Set to ma_format_unknown to use the device's native format.
+71
config.playback.channels = 2; // Set to 0 to use the device's native channel count.
+72
config.sampleRate = 48000; // Set to 0 to use the device's native sample rate.
+73
config.dataCallback = data_callback; // This function will be called when miniaudio needs more data.
+74
config.pUserData = pMyCustomData; // Can be accessed from the device object (device.pUserData).
+75
+76
ma_device device;
+77
if (ma_device_init(NULL, &config, &device) != MA_SUCCESS) {
+78
return -1; // Failed to initialize the device.
+79
}
+80
+81
ma_device_start(&device); // The device is sleeping by default so you'll need to start it manually.
+82
+83
// Do something here. Probably your program's main loop.
+84
+85
ma_device_uninit(&device);
+86
return 0;
+87
}
+88
```
+89
+90
In the example above, `data_callback()` is where audio data is written and read from the device.
+91
The idea is in playback mode you cause sound to be emitted from the speakers by writing audio data
+92
to the output buffer (`pOutput` in the example). In capture mode you read data from the input
+93
buffer (`pInput`) to extract sound captured by the microphone. The `frameCount` parameter tells you
+94
how many frames can be written to the output buffer and read from the input buffer. A "frame" is
+95
one sample for each channel. For example, in a stereo stream (2 channels), one frame is 2
+96
samples: one for the left, one for the right. The channel count is defined by the device config.
+97
The size in bytes of an individual sample is defined by the sample format which is also specified
+98
in the device config. Multi-channel audio data is always interleaved, which means the samples for
+99
each frame are stored next to each other in memory. For example, in a stereo stream the first pair
+100
of samples will be the left and right samples for the first frame, the second pair of samples will
+101
be the left and right samples for the second frame, etc.
+102
+103
The configuration of the device is defined by the `ma_device_config` structure. The config object
+104
is always initialized with `ma_device_config_init()`. It's important to always initialize the
+105
config with this function as it initializes it with logical defaults and ensures your program
+106
doesn't break when new members are added to the `ma_device_config` structure. The example above
+107
uses a fairly simple and standard device configuration. The call to `ma_device_config_init()` takes
+108
a single parameter, which is whether or not the device is a playback, capture, duplex or loopback
+109
device (loopback devices are not supported on all backends). The `config.playback.format` member
+110
sets the sample format which can be one of the following (all formats are native-endian):
+111
+112
+---------------+----------------------------------------+---------------------------+
+113
| Symbol | Description | Range |
+114
+---------------+----------------------------------------+---------------------------+
+115
| ma_format_f32 | 32-bit floating point | [-1, 1] |
+116
| ma_format_s16 | 16-bit signed integer | [-32768, 32767] |
+117
| ma_format_s24 | 24-bit signed integer (tightly packed) | [-8388608, 8388607] |
+118
| ma_format_s32 | 32-bit signed integer | [-2147483648, 2147483647] |
+119
| ma_format_u8 | 8-bit unsigned integer | [0, 255] |
+120
+---------------+----------------------------------------+---------------------------+
+121
+122
The `config.playback.channels` member sets the number of channels to use with the device. The
+123
channel count cannot exceed MA_MAX_CHANNELS. The `config.sampleRate` member sets the sample rate
+124
(which must be the same for both playback and capture in full-duplex configurations). This is
+125
usually set to 44100 or 48000, but can be set to anything. It's recommended to keep this between
+126
8000 and 384000, however.
+127
+128
Note that leaving the format, channel count and/or sample rate at their default values will result
+129
in the internal device's native configuration being used which is useful if you want to avoid the
+130
overhead of miniaudio's automatic data conversion.
+131
+132
In addition to the sample format, channel count and sample rate, the data callback and user data
+133
pointer are also set via the config. The user data pointer is not passed into the callback as a
+134
parameter, but is instead set to the `pUserData` member of `ma_device` which you can access
+135
directly since all miniaudio structures are transparent.
+136
+137
Initializing the device is done with `ma_device_init()`. This will return a result code telling you
+138
what went wrong, if anything. On success it will return `MA_SUCCESS`. After initialization is
+139
complete the device will be in a stopped state. To start it, use `ma_device_start()`.
+140
Uninitializing the device will stop it, which is what the example above does, but you can also stop
+141
the device with `ma_device_stop()`. To resume the device simply call `ma_device_start()` again.
+142
Note that it's important to never stop or start the device from inside the callback. This will
+143
result in a deadlock. Instead you set a variable or signal an event indicating that the device
+144
needs to stop and handle it in a different thread. The following APIs must never be called inside
+145
the callback:
+146
+147
```c
+148
ma_device_init()
+149
ma_device_init_ex()
+150
ma_device_uninit()
+151
ma_device_start()
+152
ma_device_stop()
+153
```
+154
+155
You must never try uninitializing and reinitializing a device inside the callback. You must also
+156
never try to stop and start it from inside the callback. There are a few other things you shouldn't
+157
do in the callback depending on your requirements, however this isn't so much a thread-safety
+158
thing, but rather a real-time processing thing which is beyond the scope of this introduction.
+159
+160
The example above demonstrates the initialization of a playback device, but it works exactly the
+161
same for capture. All you need to do is change the device type from `ma_device_type_playback` to
+162
`ma_device_type_capture` when setting up the config, like so:
+163
+164
```c
+165
ma_device_config config = ma_device_config_init(ma_device_type_capture);
+166
config.capture.format = MY_FORMAT;
+167
config.capture.channels = MY_CHANNEL_COUNT;
+168
```
+169
+170
In the data callback you just read from the input buffer (`pInput` in the example above) and leave
+171
the output buffer alone (it will be set to NULL when the device type is set to
+172
`ma_device_type_capture`).
+173
+174
These are the available device types and how you should handle the buffers in the callback:
+175
+176
+-------------------------+--------------------------------------------------------+
+177
| Device Type | Callback Behavior |
+178
+-------------------------+--------------------------------------------------------+
+179
| ma_device_type_playback | Write to output buffer, leave input buffer untouched. |
+180
| ma_device_type_capture | Read from input buffer, leave output buffer untouched. |
+181
| ma_device_type_duplex | Read from input buffer, write to output buffer. |
+182
| ma_device_type_loopback | Read from input buffer, leave output buffer untouched. |
+183
+-------------------------+--------------------------------------------------------+
+184
+185
You will notice in the example above that the sample format and channel count is specified
+186
separately for playback and capture. This is to support different data formats between the playback
+187
and capture devices in a full-duplex system. An example may be that you want to capture audio data
+188
as a monaural stream (one channel), but output sound to a stereo speaker system. Note that if you
+189
use different formats between playback and capture in a full-duplex configuration you will need to
+190
convert the data yourself. There are functions available to help you do this which will be
+191
explained later.
+192
+193
The example above did not specify a physical device to connect to which means it will use the
+194
operating system's default device. If you have multiple physical devices connected and you want to
+195
use a specific one you will need to specify the device ID in the configuration, like so:
+196
+197
```c
+198
config.playback.pDeviceID = pMyPlaybackDeviceID; // Only if requesting a playback or duplex device.
+199
config.capture.pDeviceID = pMyCaptureDeviceID; // Only if requesting a capture, duplex or loopback device.
+200
```
+201
+202
To retrieve the device ID you will need to perform device enumeration, however this requires the
+203
use of a new concept called the "context". Conceptually speaking the context sits above the device.
+204
There is one context to many devices. The purpose of the context is to represent the backend at a
+205
more global level and to perform operations outside the scope of an individual device. Mainly it is
+206
used for performing run-time linking against backend libraries, initializing backends and
+207
enumerating devices. The example below shows how to enumerate devices.
+208
+209
```c
+210
ma_context context;
+211
if (ma_context_init(NULL, 0, NULL, &context) != MA_SUCCESS) {
+212
// Error.
+213
}
+214
+215
ma_device_info* pPlaybackInfos;
+216
ma_uint32 playbackCount;
+217
ma_device_info* pCaptureInfos;
+218
ma_uint32 captureCount;
+219
if (ma_context_get_devices(&context, &pPlaybackInfos, &playbackCount, &pCaptureInfos, &captureCount) != MA_SUCCESS) {
+220
// Error.
+221
}
+222
+223
// Loop over each device info and do something with it. Here we just print the name with their index. You may want
+224
// to give the user the opportunity to choose which device they'd prefer.
+225
for (ma_uint32 iDevice = 0; iDevice < playbackCount; iDevice += 1) {
+226
printf("%d - %s\n", iDevice, pPlaybackInfos[iDevice].name);
+227
}
+228
+229
ma_device_config config = ma_device_config_init(ma_device_type_playback);
+230
config.playback.pDeviceID = &pPlaybackInfos[chosenPlaybackDeviceIndex].id;
+231
config.playback.format = MY_FORMAT;
+232
config.playback.channels = MY_CHANNEL_COUNT;
+233
config.sampleRate = MY_SAMPLE_RATE;
+234
config.dataCallback = data_callback;
+235
config.pUserData = pMyCustomData;
+236
+237
ma_device device;
+238
if (ma_device_init(&context, &config, &device) != MA_SUCCESS) {
+239
// Error
+240
}
+241
+242
...
+243
+244
ma_device_uninit(&device);
+245
ma_context_uninit(&context);
+246
```
+247
+248
The first thing we do in this example is initialize a `ma_context` object with `ma_context_init()`.
+249
The first parameter is a pointer to a list of `ma_backend` values which are used to override the
+250
default backend priorities. When this is NULL, as in this example, miniaudio's default priorities
+251
are used. The second parameter is the number of backends listed in the array pointed to by the
+252
first parameter. The third parameter is a pointer to a `ma_context_config` object which can be
+253
NULL, in which case defaults are used. The context configuration is used for setting the logging
+254
callback, custom memory allocation callbacks, user-defined data and some backend-specific
+255
configurations.
+256
+257
Once the context has been initialized you can enumerate devices. In the example above we use the
+258
simpler `ma_context_get_devices()`, however you can also use a callback for handling devices by
+259
using `ma_context_enumerate_devices()`. When using `ma_context_get_devices()` you provide a pointer
+260
to a pointer that will, upon output, be set to a pointer to a buffer containing a list of
+261
`ma_device_info` structures. You also provide a pointer to an unsigned integer that will receive
+262
the number of items in the returned buffer. Do not free the returned buffers as their memory is
+263
managed internally by miniaudio.
+264
+265
The `ma_device_info` structure contains an `id` member which is the ID you pass to the device
+266
config. It also contains the name of the device which is useful for presenting a list of devices
+267
to the user via the UI.
+268
+269
When creating your own context you will want to pass it to `ma_device_init()` when initializing the
+270
device. Passing in NULL, like we do in the first example, will result in miniaudio creating the
+271
context for you, which you don't want to do since you've already created a context. Note that
+272
internally the context is only tracked by it's pointer which means you must not change the location
+273
of the `ma_context` object. If this is an issue, consider using `malloc()` to allocate memory for
+274
the context.
+275
+276
+277
1.2. High Level API
+278
-------------------
+279
The high level API consists of three main parts:
+280
+281
* Resource management for loading and streaming sounds.
+282
* A node graph for advanced mixing and effect processing.
+283
* A high level "engine" that wraps around the resource manager and node graph.
+284
+285
The resource manager (`ma_resource_manager`) is used for loading sounds. It supports loading sounds
+286
fully into memory and also streaming. It will also deal with reference counting for you which
+287
avoids the same sound being loaded multiple times.
+288
+289
The node graph is used for mixing and effect processing. The idea is that you connect a number of
+290
nodes into the graph by connecting each node's outputs to another node's inputs. Each node can
+291
implement its own effect. By chaining nodes together, advanced mixing and effect processing can
+292
be achieved.
+293
+294
The engine encapsulates both the resource manager and the node graph to create a simple, easy to
+295
use high level API. The resource manager and node graph APIs are covered in more later sections of
+296
this manual.
+297
+298
The code below shows how you can initialize an engine using its default configuration.
+299
+300
```c
+301
ma_result result;
+302
ma_engine engine;
+303
+304
result = ma_engine_init(NULL, &engine);
+305
if (result != MA_SUCCESS) {
+306
return result; // Failed to initialize the engine.
+307
}
+308
```
+309
+310
This creates an engine instance which will initialize a device internally which you can access with
+311
`ma_engine_get_device()`. It will also initialize a resource manager for you which can be accessed
+312
with `ma_engine_get_resource_manager()`. The engine itself is a node graph (`ma_node_graph`) which
+313
means you can pass a pointer to the engine object into any of the `ma_node_graph` APIs (with a
+314
cast). Alternatively, you can use `ma_engine_get_node_graph()` instead of a cast.
+315
+316
Note that all objects in miniaudio, including the `ma_engine` object in the example above, are
+317
transparent structures. There are no handles to opaque structures in miniaudio which means you need
+318
to be mindful of how you declare them. In the example above we are declaring it on the stack, but
+319
this will result in the struct being invalidated once the function encapsulating it returns. If
+320
allocating the engine on the heap is more appropriate, you can easily do so with a standard call
+321
to `malloc()` or whatever heap allocation routine you like:
+322
+323
```c
+324
ma_engine* pEngine = malloc(sizeof(*pEngine));
+325
```
+326
+327
The `ma_engine` API uses the same config/init pattern used all throughout miniaudio. To configure
+328
an engine, you can fill out a `ma_engine_config` object and pass it into the first parameter of
+329
`ma_engine_init()`:
+330
+331
```c
+332
ma_result result;
+333
ma_engine engine;
+334
ma_engine_config engineConfig;
+335
+336
engineConfig = ma_engine_config_init();
+337
engineConfig.pResourceManager = &myCustomResourceManager; // <-- Initialized as some earlier stage.
+338
+339
result = ma_engine_init(&engineConfig, &engine);
+340
if (result != MA_SUCCESS) {
+341
return result;
+342
}
+343
```
+344
+345
This creates an engine instance using a custom config. In this particular example it's showing how
+346
you can specify a custom resource manager rather than having the engine initialize one internally.
+347
This is particularly useful if you want to have multiple engine's share the same resource manager.
+348
+349
The engine must be uninitialized with `ma_engine_uninit()` when it's no longer needed.
+350
+351
By default the engine will be started, but nothing will be playing because no sounds have been
+352
initialized. The easiest but least flexible way of playing a sound is like so:
+353
+354
```c
+355
ma_engine_play_sound(&engine, "my_sound.wav", NULL);
+356
```
+357
+358
This plays what miniaudio calls an "inline" sound. It plays the sound once, and then puts the
+359
internal sound up for recycling. The last parameter is used to specify which sound group the sound
+360
should be associated with which will be explained later. This particular way of playing a sound is
+361
simple, but lacks flexibility and features. A more flexible way of playing a sound is to first
+362
initialize a sound:
+363
+364
```c
+365
ma_result result;
+366
ma_sound sound;
+367
+368
result = ma_sound_init_from_file(&engine, "my_sound.wav", 0, NULL, NULL, &sound);
+369
if (result != MA_SUCCESS) {
+370
return result;
+371
}
+372
+373
ma_sound_start(&sound);
+374
```
+375
+376
This returns a `ma_sound` object which represents a single instance of the specified sound file. If
+377
you want to play the same file multiple times simultaneously, you need to create one sound for each
+378
instance.
+379
+380
Sounds should be uninitialized with `ma_sound_uninit()`.
+381
+382
Sounds are not started by default. Start a sound with `ma_sound_start()` and stop it with
+383
`ma_sound_stop()`. When a sound is stopped, it is not rewound to the start. Use
+384
`ma_sound_seek_to_pcm_frame(&sound, 0)` to seek back to the start of a sound. By default, starting
+385
and stopping sounds happens immediately, but sometimes it might be convenient to schedule the sound
+386
to be started and/or stopped at a specific time. This can be done with the following functions:
+387
+388
```c
+389
ma_sound_set_start_time_in_pcm_frames()
+390
ma_sound_set_start_time_in_milliseconds()
+391
ma_sound_set_stop_time_in_pcm_frames()
+392
ma_sound_set_stop_time_in_milliseconds()
+393
```
+394
+395
The start/stop time needs to be specified based on the absolute timer which is controlled by the
+396
engine. The current global time in PCM frames can be retrieved with
+397
`ma_engine_get_time_in_pcm_frames()`. The engine's global time can be changed with
+398
`ma_engine_set_time_in_pcm_frames()` for synchronization purposes if required. Note that scheduling
+399
a start time still requires an explicit call to `ma_sound_start()` before anything will play:
+400
+401
```c
+402
ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time_in_pcm_frames(&engine) + (ma_engine_get_sample_rate(&engine) * 2);
+403
ma_sound_start(&sound);
+404
```
+405
+406
The third parameter of `ma_sound_init_from_file()` is a set of flags that control how the sound be
+407
loaded and a few options on which features should be enabled for that sound. By default, the sound
+408
is synchronously loaded fully into memory straight from the file system without any kind of
+409
decoding. If you want to decode the sound before storing it in memory, you need to specify the
+410
`MA_SOUND_FLAG_DECODE` flag. This is useful if you want to incur the cost of decoding at an earlier
+411
stage, such as a loading stage. Without this option, decoding will happen dynamically at mixing
+412
time which might be too expensive on the audio thread.
+413
+414
If you want to load the sound asynchronously, you can specify the `MA_SOUND_FLAG_ASYNC` flag. This
+415
will result in `ma_sound_init_from_file()` returning quickly, but the sound will not start playing
+416
until the sound has had some audio decoded.
+417
+418
The fourth parameter is a pointer to sound group. A sound group is used as a mechanism to organise
+419
sounds into groups which have their own effect processing and volume control. An example is a game
+420
which might have separate groups for sfx, voice and music. Each of these groups have their own
+421
independent volume control. Use `ma_sound_group_init()` or `ma_sound_group_init_ex()` to initialize
+422
a sound group.
+423
+424
Sounds and sound groups are nodes in the engine's node graph and can be plugged into any `ma_node`
+425
API. This makes it possible to connect sounds and sound groups to effect nodes to produce complex
+426
effect chains.
+427
+428
A sound can have its volume changed with `ma_sound_set_volume()`. If you prefer decibel volume
+429
control you can use `ma_volume_db_to_linear()` to convert from decibel representation to linear.
+430
+431
Panning and pitching is supported with `ma_sound_set_pan()` and `ma_sound_set_pitch()`. If you know
+432
a sound will never have its pitch changed with `ma_sound_set_pitch()` or via the doppler effect,
+433
you can specify the `MA_SOUND_FLAG_NO_PITCH` flag when initializing the sound for an optimization.
+434
+435
By default, sounds and sound groups have spatialization enabled. If you don't ever want to
+436
spatialize your sounds, initialize the sound with the `MA_SOUND_FLAG_NO_SPATIALIZATION` flag. The
+437
spatialization model is fairly simple and is roughly on feature parity with OpenAL. HRTF and
+438
environmental occlusion are not currently supported, but planned for the future. The supported
+439
features include:
+440
+441
* Sound and listener positioning and orientation with cones
+442
* Attenuation models: none, inverse, linear and exponential
+443
* Doppler effect
+444
+445
Sounds can be faded in and out with `ma_sound_set_fade_in_pcm_frames()`.
+446
+447
To check if a sound is currently playing, you can use `ma_sound_is_playing()`. To check if a sound
+448
is at the end, use `ma_sound_at_end()`. Looping of a sound can be controlled with
+449
`ma_sound_set_looping()`. Use `ma_sound_is_looping()` to check whether or not the sound is looping.
+450
+451
+452
+453
2. Building
+454
===========
+455
miniaudio should work cleanly out of the box without the need to download or install any
+456
dependencies. See below for platform-specific details.
+457
+458
This library has been designed to be added directly to your source tree which is the preferred way
+459
of using it, but you can compile it as a normal library if that's your preference. Be careful if
+460
compiling as a shared object because miniaudio is not ABI compatible between any release, including
+461
bug fix releases. It's recommended you link statically.
+462
+463
Note that GCC and Clang require `-msse2`, `-mavx2`, etc. for SIMD optimizations.
+464
+465
If you get errors about undefined references to `__sync_val_compare_and_swap_8`, `__atomic_load_8`,
+466
etc. you need to link with `-latomic`.
+467
+468
+469
2.1. Windows
+470
------------
+471
The Windows build should compile cleanly on all popular compilers without the need to configure any
+472
include paths nor link to any libraries.
+473
+474
The UWP build may require linking to mmdevapi.lib if you get errors about an unresolved external
+475
symbol for `ActivateAudioInterfaceAsync()`.
+476
+477
+478
2.2. macOS and iOS
+479
------------------
+480
The macOS build should compile cleanly without the need to download any dependencies nor link to
+481
any libraries or frameworks. The iOS build needs to be compiled as Objective-C and will need to
+482
link the relevant frameworks but should compile cleanly out of the box with Xcode. Compiling
+483
through the command line requires linking to `-lpthread` and `-lm`.
+484
+485
Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's
+486
notarization process. To fix this there are two options. The first is to compile with
+487
`-DMA_NO_RUNTIME_LINKING` which in turn will require linking with
+488
`-framework CoreFoundation -framework CoreAudio -framework AudioToolbox`. If you get errors about
+489
AudioToolbox, try with `-framework AudioUnit` instead. You may get this when using older versions
+490
of iOS. Alternatively, if you would rather keep using runtime linking you can add the following to
+491
your entitlements.xcent file:
+492
+493
```
+494
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
+495
<true/>
+496
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
+497
<true/>
+498
```
+499

Showing the first 500 of 95865 diff lines for this file. This diff is INCOMPLETE; read the file or clone the repository for the rest.

vendor/sokol/sokol_audio.hdeleted
@@ -1,2663 +0,0 @@
−1
#if defined(SOKOL_IMPL) && !defined(SOKOL_AUDIO_IMPL)
−2
#define SOKOL_AUDIO_IMPL
−3
#endif
−4
#ifndef SOKOL_AUDIO_INCLUDED
−5
/*
−6
sokol_audio.h -- cross-platform audio-streaming API
−7
−8
Project URL: https://github.com/floooh/sokol
−9
−10
Do this:
−11
#define SOKOL_IMPL or
−12
#define SOKOL_AUDIO_IMPL
−13
before you include this file in *one* C or C++ file to create the
−14
implementation.
−15
−16
Optionally provide the following defines with your own implementations:
−17
−18
SOKOL_DUMMY_BACKEND - use a dummy backend
−19
SOKOL_ASSERT(c) - your own assert macro (default: assert(c))
−20
SOKOL_AUDIO_API_DECL- public function declaration prefix (default: extern)
−21
SOKOL_API_DECL - same as SOKOL_AUDIO_API_DECL
−22
SOKOL_API_IMPL - public function implementation prefix (default: -)
−23
−24
SAUDIO_RING_MAX_SLOTS - max number of slots in the push-audio ring buffer (default 1024)
−25
SAUDIO_OSX_USE_SYSTEM_HEADERS - define this to force inclusion of system headers on
−26
macOS instead of using embedded CoreAudio declarations
−27
−28
If sokol_audio.h is compiled as a DLL, define the following before
−29
including the declaration or implementation:
−30
−31
SOKOL_DLL
−32
−33
On Windows, SOKOL_DLL will define SOKOL_AUDIO_API_DECL as __declspec(dllexport)
−34
or __declspec(dllimport) as needed.
−35
−36
Link with the following libraries:
−37
−38
- on macOS: AudioToolbox
−39
- on iOS: AudioToolbox, AVFoundation
−40
- on FreeBSD: asound
−41
- on Linux: asound
−42
- on Android: aaudio
−43
- on Windows with MSVC or Clang toolchain: no action needed, libs are defined in-source via pragma-comment-lib
−44
- on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' and link with -lole32
−45
- on Vita: SceAudio
−46
- on 3DS: NDSP (libctru)
−47
−48
FEATURE OVERVIEW
−49
================
−50
You provide a mono- or stereo-stream of 32-bit float samples, which
−51
Sokol Audio feeds into platform-specific audio backends:
−52
−53
- Windows: WASAPI
−54
- Linux: ALSA
−55
- FreeBSD: ALSA
−56
- macOS: CoreAudio
−57
- iOS: CoreAudio+AVAudioSession
−58
- emscripten: WebAudio with ScriptProcessorNode
−59
- Android: AAudio
−60
- Vita: SceAudio
−61
- 3DS: NDSP (libctru)
−62
−63
Sokol Audio will not do any buffer mixing or volume control, if you have
−64
multiple independent input streams of sample data you need to perform the
−65
mixing yourself before forwarding the data to Sokol Audio.
−66
−67
There are two mutually exclusive ways to provide the sample data:
−68
−69
1. Callback model: You provide a callback function, which will be called
−70
when Sokol Audio needs new samples. On all platforms except emscripten,
−71
this function is called from a separate thread.
−72
2. Push model: Your code pushes small blocks of sample data from your
−73
main loop or a thread you created. The pushed data is stored in
−74
a ring buffer where it is pulled by the backend code when
−75
needed.
−76
−77
The callback model is preferred because it is the most direct way to
−78
feed sample data into the audio backends and also has less moving parts
−79
(there is no ring buffer between your code and the audio backend).
−80
−81
Sometimes it is not possible to generate the audio stream directly in a
−82
callback function running in a separate thread, for such cases Sokol Audio
−83
provides the push-model as a convenience.
−84
−85
SOKOL AUDIO, SOLOUD AND MINIAUDIO
−86
=================================
−87
The WASAPI, ALSA and CoreAudio backend code has been taken from the
−88
SoLoud library (with some modifications, so any bugs in there are most
−89
likely my fault). If you need a more fully-featured audio solution, check
−90
out SoLoud, it's excellent:
−91
−92
https://github.com/jarikomppa/soloud
−93
−94
Another alternative which feature-wise is somewhere inbetween SoLoud and
−95
sokol-audio might be MiniAudio:
−96
−97
https://github.com/mackron/miniaudio
−98
−99
GLOSSARY
−100
========
−101
- stream buffer:
−102
The internal audio data buffer, usually provided by the backend API. The
−103
size of the stream buffer defines the base latency, smaller buffers have
−104
lower latency but may cause audio glitches. Bigger buffers reduce or
−105
eliminate glitches, but have a higher base latency.
−106
−107
- stream callback:
−108
Optional callback function which is called by Sokol Audio when it
−109
needs new samples. On Windows, macOS/iOS and Linux, this is called in
−110
a separate thread, on WebAudio, this is called per-frame in the
−111
browser thread.
−112
−113
- channel:
−114
A discrete track of audio data, currently 1-channel (mono) and
−115
2-channel (stereo) is supported and tested.
−116
−117
- sample:
−118
The magnitude of an audio signal on one channel at a given time. In
−119
Sokol Audio, samples are 32-bit float numbers in the range -1.0 to
−120
+1.0.
−121
−122
- frame:
−123
The tightly packed set of samples for all channels at a given time.
−124
For mono 1 frame is 1 sample. For stereo, 1 frame is 2 samples.
−125
−126
- packet:
−127
In Sokol Audio, a small chunk of audio data that is moved from the
−128
main thread to the audio streaming thread in order to decouple the
−129
rate at which the main thread provides new audio data, and the
−130
streaming thread consuming audio data.
−131
−132
WORKING WITH SOKOL AUDIO
−133
========================
−134
First call saudio_setup() with your preferred audio playback options.
−135
In most cases you can stick with the default values, these provide
−136
a good balance between low-latency and glitch-free playback
−137
on all audio backends.
−138
−139
You should always provide a logging callback to be aware of any
−140
warnings and errors. The easiest way is to use sokol_log.h for this:
−141
−142
#include "sokol_log.h"
−143
// ...
−144
saudio_setup(&(saudio_desc){
−145
.logger = {
−146
.func = slog_func,
−147
}
−148
});
−149
−150
If you want to use the callback-model, you need to provide a stream
−151
callback function either in saudio_desc.stream_cb or saudio_desc.stream_userdata_cb,
−152
otherwise keep both function pointers zero-initialized.
−153
−154
Use push model and default playback parameters:
−155
−156
saudio_setup(&(saudio_desc){ .logger.func = slog_func });
−157
−158
Use stream callback model and default playback parameters:
−159
−160
saudio_setup(&(saudio_desc){
−161
.stream_cb = my_stream_callback
−162
.logger.func = slog_func,
−163
});
−164
−165
The standard stream callback doesn't have a user data argument, if you want
−166
that, use the alternative stream_userdata_cb and also set the user_data pointer:
−167
−168
saudio_setup(&(saudio_desc){
−169
.stream_userdata_cb = my_stream_callback,
−170
.user_data = &my_data
−171
.logger.func = slog_func,
−172
});
−173
−174
The following playback parameters can be provided through the
−175
saudio_desc struct:
−176
−177
General parameters (both for stream-callback and push-model):
−178
−179
int sample_rate -- the sample rate in Hz, default: 44100
−180
int num_channels -- number of channels, default: 1 (mono)
−181
int buffer_frames -- number of frames in streaming buffer, default: 2048
−182
−183
The stream callback prototype (either with or without userdata):
−184
−185
void (*stream_cb)(float* buffer, int num_frames, int num_channels)
−186
void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data)
−187
Function pointer to the user-provide stream callback.
−188
−189
Push-model parameters:
−190
−191
int packet_frames -- number of frames in a packet, default: 128
−192
int num_packets -- number of packets in ring buffer, default: 64
−193
−194
The sample_rate and num_channels parameters are only hints for the audio
−195
backend, it isn't guaranteed that those are the values used for actual
−196
playback.
−197
−198
To get the actual parameters, call the following functions after
−199
saudio_setup():
−200
−201
int saudio_sample_rate(void)
−202
int saudio_channels(void);
−203
−204
It's unlikely that the number of channels will be different than requested,
−205
but a different sample rate isn't uncommon.
−206
−207
(NOTE: there's an yet unsolved issue when an audio backend might switch
−208
to a different sample rate when switching output devices, for instance
−209
plugging in a bluetooth headset, this case is currently not handled in
−210
Sokol Audio).
−211
−212
You can check if audio initialization was successful with
−213
saudio_isvalid(). If backend initialization failed for some reason
−214
(for instance when there's no audio device in the machine), this
−215
will return false. Not checking for success won't do any harm, all
−216
Sokol Audio function will silently fail when called after initialization
−217
has failed, so apart from missing audio output, nothing bad will happen.
−218
−219
Before your application exits, you should call
−220
−221
saudio_shutdown();
−222
−223
This stops the audio thread (on Linux, Windows and macOS/iOS) and
−224
properly shuts down the audio backend.
−225
−226
THE STREAM CALLBACK MODEL
−227
=========================
−228
To use Sokol Audio in stream-callback-mode, provide a callback function
−229
like this in the saudio_desc struct when calling saudio_setup():
−230
−231
void stream_cb(float* buffer, int num_frames, int num_channels) {
−232
...
−233
}
−234
−235
Or the alternative version with a user-data argument:
−236
−237
void stream_userdata_cb(float* buffer, int num_frames, int num_channels, void* user_data) {
−238
my_data_t* my_data = (my_data_t*) user_data;
−239
...
−240
}
−241
−242
The job of the callback function is to fill the *buffer* with 32-bit
−243
float sample values.
−244
−245
To output silence, fill the buffer with zeros:
−246
−247
void stream_cb(float* buffer, int num_frames, int num_channels) {
−248
const int num_samples = num_frames * num_channels;
−249
for (int i = 0; i < num_samples; i++) {
−250
buffer[i] = 0.0f;
−251
}
−252
}
−253
−254
For stereo output (num_channels == 2), the samples for the left
−255
and right channel are interleaved:
−256
−257
void stream_cb(float* buffer, int num_frames, int num_channels) {
−258
assert(2 == num_channels);
−259
for (int i = 0; i < num_frames; i++) {
−260
buffer[2*i + 0] = ...; // left channel
−261
buffer[2*i + 1] = ...; // right channel
−262
}
−263
}
−264
−265
Please keep in mind that the stream callback function is running in a
−266
separate thread, if you need to share data with the main thread you need
−267
to take care yourself to make the access to the shared data thread-safe!
−268
−269
THE PUSH MODEL
−270
==============
−271
To use the push-model for providing audio data, simply don't set (keep
−272
zero-initialized) the stream_cb field in the saudio_desc struct when
−273
calling saudio_setup().
−274
−275
To provide sample data with the push model, call the saudio_push()
−276
function at regular intervals (for instance once per frame). You can
−277
call the saudio_expect() function to ask Sokol Audio how much room is
−278
in the ring buffer, but if you provide a continuous stream of data
−279
at the right sample rate, saudio_expect() isn't required (it's a simple
−280
way to sync/throttle your sample generation code with the playback
−281
rate though).
−282
−283
With saudio_push() you may need to maintain your own intermediate sample
−284
buffer, since pushing individual sample values isn't very efficient.
−285
The following example is from the MOD player sample in
−286
sokol-samples (https://github.com/floooh/sokol-samples):
−287
−288
const int num_frames = saudio_expect();
−289
if (num_frames > 0) {
−290
const int num_samples = num_frames * saudio_channels();
−291
read_samples(flt_buf, num_samples);
−292
saudio_push(flt_buf, num_frames);
−293
}
−294
−295
Another option is to ignore saudio_expect(), and just push samples as they
−296
are generated in small batches. In this case you *need* to generate the
−297
samples at the right sample rate:
−298
−299
The following example is taken from the Tiny Emulators project
−300
(https://github.com/floooh/chips-test), this is for mono playback,
−301
so (num_samples == num_frames):
−302
−303
// tick the sound generator
−304
if (ay38910_tick(&sys->psg)) {
−305
// new sample is ready
−306
sys->sample_buffer[sys->sample_pos++] = sys->psg.sample;
−307
if (sys->sample_pos == sys->num_samples) {
−308
// new sample packet is ready
−309
saudio_push(sys->sample_buffer, sys->num_samples);
−310
sys->sample_pos = 0;
−311
}
−312
}
−313
−314
THE WEBAUDIO BACKEND
−315
====================
−316
The WebAudio backend is currently using a ScriptProcessorNode callback to
−317
feed the sample data into WebAudio. ScriptProcessorNode has been
−318
deprecated for a while because it is running from the main thread, with
−319
the default initialization parameters it works 'pretty well' though.
−320
Ultimately Sokol Audio will use Audio Worklets, but this requires a few
−321
more things to fall into place (Audio Worklets implemented everywhere,
−322
SharedArrayBuffers enabled again, and I need to figure out a 'low-cost'
−323
solution in terms of implementation effort, since Audio Worklets are
−324
a lot more complex than ScriptProcessorNode if the audio data needs to come
−325
from the main thread).
−326
−327
The WebAudio backend is automatically selected when compiling for
−328
emscripten (__EMSCRIPTEN__ define exists).
−329
−330
https://developers.google.com/web/updates/2017/12/audio-worklet
−331
https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern
−332
−333
"Blob URLs": https://www.html5rocks.com/en/tutorials/workers/basics/
−334
−335
Also see: https://blog.paul.cx/post/a-wait-free-spsc-ringbuffer-for-the-web/
−336
−337
THE COREAUDIO BACKEND
−338
=====================
−339
The CoreAudio backend is selected on macOS and iOS (__APPLE__ is defined).
−340
Since the CoreAudio API is implemented in C (not Objective-C) on macOS the
−341
implementation part of Sokol Audio can be included into a C source file.
−342
−343
However on iOS, Sokol Audio must be compiled as Objective-C due to it's
−344
reliance on the AVAudioSession object. The iOS code path support both
−345
being compiled with or without ARC (Automatic Reference Counting).
−346
−347
For thread synchronisation, the CoreAudio backend will use the
−348
pthread_mutex_* functions.
−349
−350
The incoming floating point samples will be directly forwarded to
−351
CoreAudio without further conversion.
−352
−353
macOS and iOS applications that use Sokol Audio need to link with
−354
the AudioToolbox framework.
−355
−356
THE WASAPI BACKEND
−357
==================
−358
The WASAPI backend is automatically selected when compiling on Windows
−359
(_WIN32 is defined).
−360
−361
For thread synchronisation a Win32 critical section is used.
−362
−363
WASAPI may use a different size for its own streaming buffer then requested,
−364
so the base latency may be slightly bigger. The current backend implementation
−365
converts the incoming floating point sample values to signed 16-bit
−366
integers.
−367
−368
The required Windows system DLLs are linked with #pragma comment(lib, ...),
−369
so you shouldn't need to add additional linker libs in the build process
−370
(otherwise this is a bug which should be fixed in sokol_audio.h).
−371
−372
THE ALSA BACKEND
−373
================
−374
The ALSA backend is automatically selected when compiling on Linux
−375
('linux' is defined).
−376
−377
For thread synchronisation, the pthread_mutex_* functions are used.
−378
−379
Samples are directly forwarded to ALSA in 32-bit float format, no
−380
further conversion is taking place.
−381
−382
You need to link with the 'asound' library, and the <alsa/asoundlib.h>
−383
header must be present (usually both are installed with some sort
−384
of ALSA development package).
−385
−386
THE VITA BACKEND
−387
================
−388
The VITA backend is automatically selected when compiling with vitasdk
−389
('PSP2_SDK_VERSION' is defined).
−390
−391
For thread synchronisation, the pthread_mutex_* functions are used.
−392
−393
Samples are converted from float to short (uint16_t) to maintain
−394
all the same interface/api as other platforms.
−395
−396
You may use any supported sample rate you wish, but all audio MUST
−397
match the same sample rate you choose.
−398
−399
This uses the "BGM" port to allow selecting the sample rate ("Main"
−400
port is restricted to 48000 only).
−401
−402
You need to link with the 'SceAudio' library, and the <psp2/audioout.h>
−403
header must be present (usually both are installed with the vitasdk).
−404
−405
THE 3DS BACKEND
−406
================
−407
The 3DS backend is automatically selected when compiling with libctru
−408
('__3DS__' is defined).
−409
−410
Running a separate thread on the older 3ds is not a good idea and I
−411
was not able to get it working without slowing down the main thread
−412
too much (it has a single core available with cooperative threads).
−413
−414
The NDSP seems to work better by using its ndspSetCallback method.
−415
−416
You may use any supported sample rate you wish, but all audio MUST
−417
match the same sample rate you choose or it will sound slowed down
−418
or sped up.
−419
−420
The queue size and other NDSP specific parameters can be chosen by
−421
the provided 'saudio_n3ds_desc' type. Defaults will be used if
−422
nothing is provided.
−423
−424
There is a known issue of a noticeable delay when starting a new
−425
sound on emulators. I was not able to improve this to my liking
−426
and ~300ms can be expected. This can be improved by using a lower
−427
buffer size than the 2048 default but I would not suggest under
−428
1536. It may crash under 1408, and they must be in multiples of 128.
−429
Note: I was NOT able to reproduce this issue on a real device and
−430
the audio worked perfectly.
−431
−432
−433
MEMORY ALLOCATION OVERRIDE
−434
==========================
−435
You can override the memory allocation functions at initialization time
−436
like this:
−437
−438
void* my_alloc(size_t size, void* user_data) {
−439
return malloc(size);
−440
}
−441
−442
void my_free(void* ptr, void* user_data) {
−443
free(ptr);
−444
}
−445
−446
...
−447
saudio_setup(&(saudio_desc){
−448
// ...
−449
.allocator = {
−450
.alloc_fn = my_alloc,
−451
.free_fn = my_free,
−452
.user_data = ...,
−453
}
−454
});
−455
...
−456
−457
If no overrides are provided, malloc and free will be used.
−458
−459
This only affects memory allocation calls done by sokol_audio.h
−460
itself though, not any allocations in OS libraries.
−461
−462
Memory allocation will only happen on the same thread where saudio_setup()
−463
was called, so you don't need to worry about thread-safety.
−464
−465
−466
ERROR REPORTING AND LOGGING
−467
===========================
−468
To get any logging information at all you need to provide a logging callback in the setup call
−469
the easiest way is to use sokol_log.h:
−470
−471
#include "sokol_log.h"
−472
−473
saudio_setup(&(saudio_desc){ .logger.func = slog_func });
−474
−475
To override logging with your own callback, first write a logging function like this:
−476
−477
void my_log(const char* tag, // e.g. 'saudio'
−478
uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info
−479
uint32_t log_item_id, // SAUDIO_LOGITEM_*
−480
const char* message_or_null, // a message string, may be nullptr in release mode
−481
uint32_t line_nr, // line number in sokol_audio.h
−482
const char* filename_or_null, // source filename, may be nullptr in release mode
−483
void* user_data)
−484
{
−485
...
−486
}
−487
−488
...and then setup sokol-audio like this:
−489
−490
saudio_setup(&(saudio_desc){
−491
.logger = {
−492
.func = my_log,
−493
.user_data = my_user_data,
−494
}
−495
});
−496
−497
The provided logging function must be reentrant (e.g. be callable from
−498
different threads).
−499

Showing the first 500 of 2664 diff lines for this file. This diff is INCOMPLETE; read the file or clone the repository for the rest.

vendor/sokol/sokol_log.hdeleted
@@ -1,334 +0,0 @@
−1
#if defined(SOKOL_IMPL) && !defined(SOKOL_LOG_IMPL)
−2
#define SOKOL_LOG_IMPL
−3
#endif
−4
#ifndef SOKOL_LOG_INCLUDED
−5
/*
−6
sokol_log.h -- common logging callback for sokol headers
−7
−8
Project URL: https://github.com/floooh/sokol
−9
−10
Example code: https://github.com/floooh/sokol-samples
−11
−12
Do this:
−13
#define SOKOL_IMPL or
−14
#define SOKOL_LOG_IMPL
−15
before you include this file in *one* C or C++ file to create the
−16
implementation.
−17
−18
Optionally provide the following defines when building the implementation:
−19
−20
SOKOL_ASSERT(c) - your own assert macro (default: assert(c))
−21
SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false))
−22
SOKOL_LOG_API_DECL - public function declaration prefix (default: extern)
−23
SOKOL_API_DECL - same as SOKOL_GFX_API_DECL
−24
SOKOL_API_IMPL - public function implementation prefix (default: -)
−25
−26
Optionally define the following for verbose output:
−27
−28
SOKOL_DEBUG - by default this is defined if NDEBUG is not defined
−29
−30
−31
OVERVIEW
−32
========
−33
sokol_log.h provides a default logging callback for other sokol headers.
−34
−35
To use the default log callback, just include sokol_log.h and provide
−36
a function pointer to the 'slog_func' function when setting up the
−37
sokol library:
−38
−39
For instance with sokol_audio.h:
−40
−41
#include "sokol_log.h"
−42
...
−43
saudio_setup(&(saudio_desc){ .logger.func = slog_func });
−44
−45
Logging output goes to stderr and/or a platform specific logging subsystem
−46
(which means that in some scenarios you might see logging messages duplicated):
−47
−48
- Windows: stderr + OutputDebugStringA()
−49
- macOS/iOS/Linux: stderr + syslog()
−50
- Emscripten: console.info()/warn()/error()
−51
- Android: __android_log_write()
−52
−53
On Windows with sokol_app.h also note the runtime config items to make
−54
stdout/stderr output visible on the console for WinMain() applications
−55
via sapp_desc.win32.console_attach or sapp_desc.win32.console_create,
−56
however when running in a debugger on Windows, the logging output should
−57
show up on the debug output UI panel.
−58
−59
In debug mode, a log message might look like this:
−60
−61
[sspine][error][id:12] /Users/floh/projects/sokol/util/sokol_spine.h:3472:0:
−62
SKELETON_DESC_NO_ATLAS: no atlas object provided in sspine_skeleton_desc.atlas
−63
−64
The source path and line number is formatted like compiler errors, in some IDEs (like VSCode)
−65
such error messages are clickable.
−66
−67
In release mode, logging is less verbose as to not bloat the executable with string data, but you still get
−68
enough information to identify the type and location of an error:
−69
−70
[sspine][error][id:12][line:3472]
−71
−72
RULES FOR WRITING YOUR OWN LOGGING FUNCTION
−73
===========================================
−74
- must be re-entrant because it might be called from different threads
−75
- must treat **all** provided string pointers as optional (can be null)
−76
- don't store the string pointers, copy the string data instead
−77
- must not return for log level panic
−78
−79
LICENSE
−80
=======
−81
zlib/libpng license
−82
−83
Copyright (c) 2023 Andre Weissflog
−84
−85
This software is provided 'as-is', without any express or implied warranty.
−86
In no event will the authors be held liable for any damages arising from the
−87
use of this software.
−88
−89
Permission is granted to anyone to use this software for any purpose,
−90
including commercial applications, and to alter it and redistribute it
−91
freely, subject to the following restrictions:
−92
−93
1. The origin of this software must not be misrepresented; you must not
−94
claim that you wrote the original software. If you use this software in a
−95
product, an acknowledgment in the product documentation would be
−96
appreciated but is not required.
−97
−98
2. Altered source versions must be plainly marked as such, and must not
−99
be misrepresented as being the original software.
−100
−101
3. This notice may not be removed or altered from any source
−102
distribution.
−103
*/
−104
#define SOKOL_LOG_INCLUDED (1)
−105
#include <stdint.h>
−106
−107
#if defined(SOKOL_API_DECL) && !defined(SOKOL_LOG_API_DECL)
−108
#define SOKOL_LOG_API_DECL SOKOL_API_DECL
−109
#endif
−110
#ifndef SOKOL_LOG_API_DECL
−111
#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_LOG_IMPL)
−112
#define SOKOL_LOG_API_DECL __declspec(dllexport)
−113
#elif defined(_WIN32) && defined(SOKOL_DLL)
−114
#define SOKOL_LOG_API_DECL __declspec(dllimport)
−115
#else
−116
#define SOKOL_LOG_API_DECL extern
−117
#endif
−118
#endif
−119
−120
#ifdef __cplusplus
−121
extern "C" {
−122
#endif
−123
−124
/*
−125
Plug this function into the 'logger.func' struct item when initializing any of the sokol
−126
headers. For instance for sokol_audio.h it would look like this:
−127
−128
saudio_setup(&(saudio_desc){
−129
.logger = {
−130
.func = slog_func
−131
}
−132
});
−133
*/
−134
SOKOL_LOG_API_DECL void slog_func(const char* tag, uint32_t log_level, uint32_t log_item, const char* message, uint32_t line_nr, const char* filename, void* user_data);
−135
−136
#ifdef __cplusplus
−137
} // extern "C"
−138
#endif
−139
#endif // SOKOL_LOG_INCLUDED
−140
−141
// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██
−142
// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██
−143
// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██
−144
// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
−145
// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████
−146
//
−147
// >>implementation
−148
#ifdef SOKOL_LOG_IMPL
−149
#define SOKOL_LOG_IMPL_INCLUDED (1)
−150
−151
#ifndef SOKOL_API_IMPL
−152
#define SOKOL_API_IMPL
−153
#endif
−154
#ifndef SOKOL_DEBUG
−155
#ifndef NDEBUG
−156
#define SOKOL_DEBUG
−157
#endif
−158
#endif
−159
#ifndef SOKOL_ASSERT
−160
#include <assert.h>
−161
#define SOKOL_ASSERT(c) assert(c)
−162
#endif
−163
−164
#ifndef _SOKOL_PRIVATE
−165
#if defined(__GNUC__) || defined(__clang__)
−166
#define _SOKOL_PRIVATE __attribute__((unused)) static
−167
#else
−168
#define _SOKOL_PRIVATE static
−169
#endif
−170
#endif
−171
−172
#ifndef _SOKOL_UNUSED
−173
#define _SOKOL_UNUSED(x) (void)(x)
−174
#endif
−175
−176
// platform detection
−177
#if defined(__APPLE__)
−178
#define _SLOG_APPLE (1)
−179
#elif defined(__EMSCRIPTEN__)
−180
#define _SLOG_EMSCRIPTEN (1)
−181
#elif defined(_WIN32)
−182
#define _SLOG_WINDOWS (1)
−183
#elif defined(__ANDROID__)
−184
#define _SLOG_ANDROID (1)
−185
#elif defined(__linux__) || defined(__unix__)
−186
#define _SLOG_LINUX (1)
−187
#else
−188
#error "sokol_log.h: unknown platform"
−189
#endif
−190
−191
#include <stdlib.h> // abort
−192
#include <stdio.h> // fputs
−193
#include <stddef.h> // size_t
−194
−195
#if defined(_SLOG_EMSCRIPTEN)
−196
#include <emscripten/emscripten.h>
−197
#elif defined(_SLOG_WINDOWS)
−198
#ifndef WIN32_LEAN_AND_MEAN
−199
#define WIN32_LEAN_AND_MEAN
−200
#endif
−201
#ifndef NOMINMAX
−202
#define NOMINMAX
−203
#endif
−204
#include <windows.h>
−205
#elif defined(_SLOG_ANDROID)
−206
#include <android/log.h>
−207
#elif defined(_SLOG_LINUX) || defined(_SLOG_APPLE)
−208
#include <syslog.h>
−209
#endif
−210
−211
// size of line buffer (on stack!) in bytes including terminating zero
−212
#define _SLOG_LINE_LENGTH (512)
−213
−214
_SOKOL_PRIVATE char* _slog_append(const char* str, char* dst, char* end) {
−215
if (str) {
−216
char c;
−217
while (((c = *str++) != 0) && (dst < (end - 1))) {
−218
*dst++ = c;
−219
}
−220
}
−221
*dst = 0;
−222
return dst;
−223
}
−224
−225
_SOKOL_PRIVATE char* _slog_itoa(uint32_t x, char* buf, size_t buf_size) {
−226
const size_t max_digits_and_null = 11;
−227
if (buf_size < max_digits_and_null) {
−228
return 0;
−229
}
−230
char* p = buf + max_digits_and_null;
−231
*--p = 0;
−232
do {
−233
*--p = '0' + (x % 10);
−234
x /= 10;
−235
} while (x != 0);
−236
return p;
−237
}
−238
−239
#if defined(_SLOG_EMSCRIPTEN)
−240
EM_JS(void, slog_js_log, (uint32_t level, const char* c_str), {
−241
const str = UTF8ToString(c_str);
−242
switch (level) {
−243
case 0: console.error(str); break;
−244
case 1: console.error(str); break;
−245
case 2: console.warn(str); break;
−246
default: console.info(str); break;
−247
}
−248
})
−249
#endif
−250
−251
SOKOL_API_IMPL void slog_func(const char* tag, uint32_t log_level, uint32_t log_item, const char* message, uint32_t line_nr, const char* filename, void* user_data) {
−252
_SOKOL_UNUSED(user_data);
−253
−254
const char* log_level_str;
−255
switch (log_level) {
−256
case 0: log_level_str = "panic"; break;
−257
case 1: log_level_str = "error"; break;
−258
case 2: log_level_str = "warning"; break;
−259
default: log_level_str = "info"; break;
−260
}
−261
−262
// build log output line
−263
char line_buf[_SLOG_LINE_LENGTH];
−264
char* str = line_buf;
−265
char* end = line_buf + sizeof(line_buf);
−266
char num_buf[32];
−267
if (tag) {
−268
str = _slog_append("[", str, end);
−269
str = _slog_append(tag, str, end);
−270
str = _slog_append("]", str, end);
−271
}
−272
str = _slog_append("[", str, end);
−273
str = _slog_append(log_level_str, str, end);
−274
str = _slog_append("]", str, end);
−275
str = _slog_append("[id:", str, end);
−276
str = _slog_append(_slog_itoa(log_item, num_buf, sizeof(num_buf)), str, end);
−277
str = _slog_append("]", str, end);
−278
// if a filename is provided, build a clickable log message that's compatible with compiler error messages
−279
if (filename) {
−280
str = _slog_append(" ", str, end);
−281
#if defined(_MSC_VER)
−282
// MSVC compiler error format
−283
str = _slog_append(filename, str, end);
−284
str = _slog_append("(", str, end);
−285
str = _slog_append(_slog_itoa(line_nr, num_buf, sizeof(num_buf)), str, end);
−286
str = _slog_append("): ", str, end);
−287
#else
−288
// gcc/clang compiler error format
−289
str = _slog_append(filename, str, end);
−290
str = _slog_append(":", str, end);
−291
str = _slog_append(_slog_itoa(line_nr, num_buf, sizeof(num_buf)), str, end);
−292
str = _slog_append(":0: ", str, end);
−293
#endif
−294
}
−295
else {
−296
str = _slog_append("[line:", str, end);
−297
str = _slog_append(_slog_itoa(line_nr, num_buf, sizeof(num_buf)), str, end);
−298
str = _slog_append("] ", str, end);
−299
}
−300
if (message) {
−301
str = _slog_append("\n\t", str, end);
−302
str = _slog_append(message, str, end);
−303
}
−304
str = _slog_append("\n\n", str, end);
−305
if (0 == log_level) {
−306
str = _slog_append("ABORTING because of [panic]\n", str, end);
−307
(void)str;
−308
}
−309
−310
// print to stderr?
−311
#if defined(_SLOG_LINUX) || defined(_SLOG_WINDOWS) || defined(_SLOG_APPLE)
−312
fputs(line_buf, stderr);
−313
#endif
−314
−315
// platform specific logging calls
−316
#if defined(_SLOG_WINDOWS)
−317
OutputDebugStringA(line_buf);
−318
#elif defined(_SLOG_ANDROID)
−319
int prio;
−320
switch (log_level) {
−321
case 0: prio = ANDROID_LOG_FATAL; break;
−322
case 1: prio = ANDROID_LOG_ERROR; break;
−323
case 2: prio = ANDROID_LOG_WARN; break;
−324
default: prio = ANDROID_LOG_INFO; break;
−325
}
−326
__android_log_write(prio, "SOKOL", line_buf);
−327
#elif defined(_SLOG_EMSCRIPTEN)
−328
slog_js_log(log_level, line_buf);
−329
#endif
−330
if (0 == log_level) {
−331
abort();
−332
}
−333
}
−334
#endif // SOKOL_LOG_IMPL