Move the playback device to miniaudio; link no audio library
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.
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(-)CHANGELOG.mdmodified
The format is based on [Keep a Changelog](https://keepachangelog.com/),and this project adheres to [Semantic Versioning](https://semver.org/).## [0.12.2] - unreleased### Changed- **The playback device is miniaudio 0.11.25** (vendored, unpatched, device layer only), replacing sokol_audio. Backends are loaded at run time: on Linux PulseAudio (`libpulse.so.0`; pipewire-pulse on a PipeWire desktop), then ALSA, then JACK; WASAPI on Windows; CoreAudio on macOS. **Nothing audio-related is linked any more**: `-lasound`, `AudioToolbox` and `ole32`/`winmm`/`ksuser` are gone from `link-flags`. A game's NEEDED list loses `libasound.so.2`.- The stream is named after the program (`program_invocation_short_name`), so `pactl list sink-inputs` shows the game, not "miniaudio".- With no backend library on the machine the mixer runs silent on miniaudio's Null backend and one stderr line says so; `audio-setup` still succeeds and `(audio-initialized?)` is `#t`, as under sokol_audio.- `src/c/dl-search.c`, a byte-identical copy of sigil-desktop's resolver (`src/c/DL-SEARCH-ORIGIN.md` records the hash and `test/dl-search-test.sh` enforces it), finds backend libraries in Guix and Nix profiles when the loader alone cannot; miniaudio's `dlopen` is redirected to it. A program that links sigil-desktop too has both copies; only sigil-desktop's defines `dlopen` itself.- `SIGIL_AUDIO_VERBOSE=1` prints the backend, the device, the format conversion miniaudio applies and every resolver hit.- `manifest.scm` no longer needs `alsa-lib` to build; it lists `pulseaudio` for the tests that open a device.### Removed- `vendor/sokol` (sokol_audio.h, sokol_log.h) and `src/c/sokol-audio.c`.### Requires- Sigil 0.20+, unchanged. The Sigil API is unchanged.## [0.12.0] - 2026-07-10### AddedREADME.mdmodified
Audio playback and streaming for [Sigil](https://codeberg.org/sigil/sigil).Provides audio playback capabilities via sokol_audio. Sound effects are loaded into memory, music is streamed via stb_vorbis.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.## Modules## System Prerequisites- ALSA development headers on Linux (`libasound2-dev` / `alsa-lib-devel`)- AudioToolbox framework on macOS (included with Xcode)- Windows: nothing extra — sokol_audio drives WASAPI through `ole32` / `winmm` / `ksuser`, all bundled with the OS.None to build. The playback device is [miniaudio](https://miniaud.io)0.11.25, vendored under `vendor/miniaudio` (device layer only; see`vendor/miniaudio/VENDOR.md`), and it loads its backend at run time:- Linux: `libpulse.so.0` first (PulseAudio, which on a PipeWire desktop is pipewire-pulse; `PULSE_SINK` picks the sink), then `libasound.so.2` (ALSA), then `libjack.so.0`. Nothing is linked: `-lasound` is gone.- Windows: WASAPI through `ole32` / `mmdevapi` / `avrt`, loaded at run time; no link flags.- macOS: the CoreAudio frameworks, loaded by absolute path; no link flags.On Guix and Nix a profile's `lib/` is on no default loader path, so theloads go through the resolver in `src/c/dl-search.c` (a copy ofsigil-desktop's, see `src/c/DL-SEARCH-ORIGIN.md`): `SIGIL_LIBRARY_PATH`,the active `guix shell` / `nix-shell`, then the user and system profilesof both. With no backend library anywhere miniaudio's Null backend runsthe mixer in silence and one line on stderr says so.`SIGIL_AUDIO_VERBOSE=1` prints the backend, the device and everyresolver hit. A run manifest for a game on Guix adds `pulseaudio` (theclient library, 187 MiB of closure) or `alsa-lib` (3 MiB, but the ALSAroute needs the system's ALSA configuration to reach PipeWire) tosigil-desktop's `guix/run-wayland.scm`.OGG Vorbis encoding/decoding is **vendored** (libogg 1.3.6 +libvorbis 1.3.7, see `vendor/ogg/README.md` and```Notes:- Sample rate is fixed at 44100 to match sokol_audio's- Sample rate is fixed at 44100 to match the device's configured rate; callers MUST match (no resampling).- `push-audio-samples` is non-blocking and returns the frame count actually accepted — the caller decides whether tomanifest.scmmodified
;; sigil-audio Development Environment;; Use with: guix shell -m manifest.scm;; libogg / libvorbis used to live here but are now vendored under;; vendor/ogg + vendor/vorbis (see package.sgl), so the system;; packages are no longer required to build sigil-audio.;;;; Nothing audio-related is needed to BUILD: miniaudio (vendored) loads its;; backends at run time, libogg/libvorbis are vendored. At RUN time on;; Guix a program needs one backend library in a profile the resolver;; (src/c/dl-search.c) searches: `pulseaudio` for libpulse.so.0 (the first;; backend miniaudio tries; on a PipeWire desktop that is pipewire-pulse,;; and PULSE_SINK routes it), or `alsa-lib` for libasound.so.2 (the ALSA;; backend; going through PipeWire's ALSA plugin needs the system ALSA;; configuration, so prefer pulseaudio in a pure shell).(specifications->manifest '("gcc-toolchain" "pkg-config" "binutils" ;; Audio playback (Sokol backend) — still a system dep on Linux. "alsa-lib")) No newline at end of file ;; run time only, for the tests that open a device "pulseaudio"))package.sglmodified
;;; sigil-audio - Audio playback for Sigil;;;;;; Provides audio playback capabilities via sokol_audio.;;; Provides audio playback through miniaudio (vendored; the device layer;;; only, every backend loaded at run time).;;; Sound effects are loaded into memory, music is streamed via stb_vorbis.;;; OGG Vorbis encoding ships in-tree via vendored libogg + libvorbis.;; skip-on-wasm wraps a build step so it is a no-op on wasm/wasi targets.;; The native audio C (sokol_audio, vendored libogg/libvorbis) has no;; The native audio C (miniaudio, vendored libogg/libvorbis) has no;; wasm32-wasi backend; on wasm the (sigil audio sink) facade delegates to;; the WebAudio bridge, so none of this C is needed. Only compile-sigil-modules;; runs on wasm, producing the facade + module bytecode. Native builds are (step ctx)))))) (package name: "sigil-audio" version: "0.12.1" version: "0.12.2" sigil: "0.20" description: "Audio playback and streaming for Sigil" url: "https://codeberg.org/sigil/sigil-audio" libraries: (list (library name: 'sigil-audio c-sources: '("src/c/sokol-audio.c" "src/c/audio.c" "src/c/audio-stream.c" "src/c/ogg-encode.c") 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") ;; vendor/ogg/include + vendor/vorbis/include expose the public ;; ogg/vorbis headers consumed by ogg-encode.c. vendor/vorbis/lib ;; is needed because libvorbis internal .c files include their ;; siblings by bare filename (e.g. #include "codebook.h"). c-include-dirs: '("vendor/sokol" "vendor/stb" c-include-dirs: '("vendor/stb" "vendor/ogg/include" "vendor/vorbis/include" "vendor/vorbis/lib") native-init: "sigil__init_sigil_audio_module" ;; Platform-specific linker flags for sokol_audio's audio backend. ;; OGG Vorbis is now vendored — no more -lvorbis/-lvorbisenc/-logg. ;; Platform linker flags: none. miniaudio loads every backend at ;; run time (libpulse.so.0, libasound.so.2, libjack.so.0 on Linux; ;; ole32/mmdevapi/avrt/winmm/dsound DLLs on Windows; the CoreAudio ;; frameworks by absolute path on macOS), so -lasound, AudioToolbox ;; and ole32/winmm/ksuser are gone. pthread and dl are libc's on ;; glibc 2.34+ (sigil-build adds -lpthread -ldl on Linux anyway). ;; ;; Procedural (lambda ctx) form so cross-compile configs evaluate ;; the right branch at link time. (case (host-os) ...) was evaluated ;; once at package-load on the host, leaking Linux flags into ;; cross builds. ;; Procedural (lambda ctx) form kept so a target can add a flag ;; later without going back to the host-evaluated (case (host-os)). link-flags: (lambda (ctx) (case (target-os ctx) ((linux) '("-lasound")) ((darwin) '("-framework" "AudioToolbox")) ;; sokol_audio's WASAPI backend uses CoCreateInstance ;; / CoInitializeEx (ole32). winmm + ksuser are ;; carried for completeness against future sokol ;; revisions that touch waveOut / kernel-streaming. ((windows) '("-lole32" "-lwinmm" "-lksuser")) ((linux) '()) ((darwin) '()) ((windows) '()) (else '()))))) dev-dependencies: (list "-Wno-strict-aliasing" "-fno-sanitize=undefined")))) ;; miniaudio: the playback device, compiled once here with the ;; device layer only (the MA_NO_* cuts are in the file). Its ;; runtime dlopen of libpulse / libasound / libjack goes through ;; the resolver in dl-search.c (shared with sigil-desktop, which ;; is the copy that may define dlopen itself; this one exposes the ;; named sigil_audio_dl_open that miniaudio-impl.c redirects to). ;; dl-search.c is Linux-only (Guix and Nix profiles); the other ;; targets compile miniaudio alone. Warnings relaxed for the ;; vendored header only. (skip-on-wasm (lambda (ctx) ((compile-c-sources sources: (if (eq? (target-os ctx) 'linux) '("src/c/miniaudio-impl.c" "src/c/dl-search.c") '("src/c/miniaudio-impl.c")) include-dirs: '("vendor/miniaudio" "src/c") flags: '("-std=c11" "-D_GNU_SOURCE" "-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" "-Wno-unused-parameter" "-Wno-unused-function" "-Wno-unused-variable" "-Wno-unused-but-set-variable" "-Wno-sign-compare")) ctx))) ;; sigil-audio's own C sources. (skip-on-wasm (compile-c-sources sources: '("src/c/sokol-audio.c" "src/c/audio.c" "src/c/audio-stream.c" "src/c/ogg-encode.c") include-dirs: '("vendor/sokol" "vendor/stb" sources: '("src/c/audio.c" "src/c/audio-stream.c" "src/c/ogg-encode.c") include-dirs: '("vendor/stb" "vendor/ogg/include" "vendor/vorbis/include") flags: (with-sigil-c-flags '("-std=c11" "-D_GNU_SOURCE"src/c/DL-SEARCH-ORIGIN.mdadded
# dl-search.c is a copy`dl-search.c` (and `test/dl-search-probe.c`, `test/dl-search-test.sh`) issigil-desktop's `src/c/dl-search.c`, byte for byte, configured here with`-DSIGIL_DL_SEARCH_INTERPOSE=0` and the `sigil_audio_*` names (see`package.sgl`). The one file serves both packages because a program maylink either without the other (a game links both; motif's CLI links onlysigil-audio), and a shared package for 200 lines would cost a repositoryand a release cycle in every consumer. Two copies with recordedprovenance is the estate's vendoring rule applied to its own code.Origin: sigil-desktop `src/c/dl-search.c`, sha256 c3495d28b963ff5defe58375153c96e4d9e86ced6b9d3e4fa1fbb234bac3881aChange it in sigil-desktop first, copy it here, update this hash;`test/dl-search-test.sh` refuses a copy whose hash differs from the onerecorded here.src/c/audio-device.hadded
/* * audio-device.h - the playback device (miniaudio-impl.c) as audio.c sees it */#ifndef SIGIL_AUDIO_DEVICE_H#define SIGIL_AUDIO_DEVICE_H/* Fill `frames` frames of interleaved f32 samples for `channels` channels; * runs on the device's own thread. */typedef void (*sigil_audio_stream_cb)(float *buffer, int frames, int channels);/* Open and start the playback device: 0 on success, -1 when no backend * could be initialised or no device opened (already reported on stderr). */int sigil_audio_device_open(int sample_rate, int channels, int buffer_frames, sigil_audio_stream_cb stream_cb);void sigil_audio_device_close(void);/* miniaudio's name for the backend in use ("PulseAudio", "ALSA", "JACK", * "WASAPI", "Core Audio", ...), or NULL when no device is open. */const char *sigil_audio_device_backend(void);#endif /* SIGIL_AUDIO_DEVICE_H */src/c/audio.cmodified
/* * audio.c - Sigil Audio Module * * Wraps sokol_audio.h to provide audio playback capabilities. * Audio playback: a mixer of loaded sounds and one streamed music track over * the playback device in miniaudio-impl.c (miniaudio). * Uses stb_vorbis for OGG decoding. * * Sound effects are loaded entirely into memory.#include <string.h>#include <math.h>/* Sokol headers (implementation is in sokol.c) */#include "sokol_audio.h"/* The playback device (miniaudio, in miniaudio-impl.c) */#include "audio-device.h"/* stb_vorbis for OGG decoding (implementation in stb_impl.c) */#include "stb_vorbis.c" return SIGIL_NIL; } saudio_desc desc = { .stream_cb = audio_callback, .num_channels = 2, .sample_rate = 44100, .buffer_frames = 2048 }; saudio_setup(&desc); /* No device (a headless runner, no sound server, no ALSA card) is not * an error, as it was not under sokol_audio: the mixer runs with no * output, every play is a no-op, and (audio-initialized?) is still * #t. miniaudio-impl.c has already said why on stderr. */ if (sigil_audio_device_open(44100, 2, 2048, audio_callback) != 0) { fprintf(stderr, "sigil-audio: continuing without an audio device\n"); } /* Clear playing sounds */ memset(g_playing_sounds, 0, sizeof(g_playing_sounds)); free(g_music.filepath); g_music.filepath = NULL; saudio_shutdown(); sigil_audio_device_close(); g_audio_initialized = false; }src/c/dl-search.cadded
/* * dl-search.c - a dlopen that also looks in the profile library directories * * GLFW loads every platform library at run time by bare soname: * dlopen("libwayland-client.so.0"), dlopen("libX11.so.6"), dlopen("libEGL.so.1"). * The dynamic loader finds such a name through LD_LIBRARY_PATH, the * caller's RUNPATH and the system default directories. On Guix and Nix none * of those name a profile's lib directory: the profile sets no * LD_LIBRARY_PATH, and the distribution's own packages are patched to * dlopen absolute store paths, which a user's binary cannot be. * * In sigil-desktop this file defines dlopen in the executable. The executable's symbols come * first in the lookup order, so every dlopen in the process (GLFW's in * posix_module.c, and any other loader compiled into the program) resolves * here; in sigil-audio it exposes a named resolver that miniaudio calls * instead of dlopen. It tries the name as given first, so on a system where the loader * already finds the library nothing changes. When that fails and the name * is a bare soname (no slash), it retries the name under each entry of the * table below, in order. The real dlopen is reached with * dlsym(RTLD_NEXT, "dlopen"). A library found this way loads its own * dependencies through its RUNPATH, which on Guix and Nix names their * store paths, so one absolute path is enough. * * A NEEDED entry (a library linked with -l) is resolved by the loader * before main runs and cannot be helped here; only dlopen is covered. * * The verbose env var (SIGIL_DESKTOP_VERBOSE=1 in sigil-desktop, * SIGIL_AUDIO_VERBOSE=1 in sigil-audio) prints, for every fallback that succeeds, the * soname, the table entry and the path, and whether /gnu/store and * /nix/store exist on this machine. * * Linux only: on macOS the two-level namespace binds libraries to * libSystem's dlopen, and on Windows LoadLibrary has its own search order. *//* ---- per-package configuration --------------------------------------- * This file is shared by sigil-desktop and sigil-audio, byte for byte * (each repo's test checks its copy against the other's recorded hash). * The package that compiles it says, with -D flags: * SIGIL_DL_SEARCH_OPEN the resolver's name, e.g. sigil_desktop_dl_open * SIGIL_DL_SEARCH_SUMMARY the summary function's name * SIGIL_DL_SEARCH_TAG the log prefix, e.g. "sigil-desktop" * SIGIL_DL_SEARCH_VERBOSE_ENV the env var that turns the trace on * SIGIL_DL_SEARCH_INTERPOSE 1 to also define dlopen itself (one package * per executable may; sigil-desktop does), * 0 to expose only the named resolver (a * loader compiled in the same package calls * it, e.g. miniaudio through a define) */#if !defined(SIGIL_DL_SEARCH_OPEN) || !defined(SIGIL_DL_SEARCH_SUMMARY) || \ !defined(SIGIL_DL_SEARCH_TAG) || !defined(SIGIL_DL_SEARCH_VERBOSE_ENV) || \ !defined(SIGIL_DL_SEARCH_INTERPOSE)#error "dl-search.c: define SIGIL_DL_SEARCH_OPEN, _SUMMARY, _TAG, _VERBOSE_ENV and _INTERPOSE (see the file header)"#endif#ifndef _GNU_SOURCE#define _GNU_SOURCE#endif#include <dlfcn.h>#include <limits.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/stat.h>/* ---- the search table ------------------------------------------------ */typedef enum { ENTRY_ENV_COLON_DIRS, /* env var: colon-separated directories */ ENTRY_ENV_SPACE_ROOTS, /* env var: space-separated roots, suffix appended */ ENTRY_ENV_ROOT, /* env var: one root, suffix appended */ ENTRY_HOME, /* $HOME + suffix */ ENTRY_USER, /* prefix + $USER + suffix */ ENTRY_DIR /* a fixed directory */} entry_kind;typedef struct { entry_kind kind; const char *name; /* env var, prefix, or the directory itself */ const char *suffix; /* appended to each root (may be "") */ const char *only_if; /* env var that must be set for the row to apply, or NULL */} search_entry;/* One row per source of library directories; adding a distribution is a * row. Every row is walked on every machine (Guix and Nix coexist: David * runs Nix inside Guix), so there is no "which distro" branch and a * library may come from a Nix profile while its own dependency, resolved * by RUNPATH, comes from a Guix one. Order: the user's explicit override, * the active environment (guix shell, nix-shell, nix-ld), then the user * profiles, then the system profiles, Guix and Nix interleaved. When two * rows offer the same soname the first hit wins and the trace names it. */static const search_entry SEARCH_TABLE[] = { /* explicit override */ { ENTRY_ENV_COLON_DIRS, "SIGIL_LIBRARY_PATH", "", NULL }, /* the active environment */ { ENTRY_ENV_ROOT, "GUIX_ENVIRONMENT", "/lib", NULL }, /* guix shell */ { ENTRY_ENV_SPACE_ROOTS, "buildInputs", "/lib", "IN_NIX_SHELL" }, /* nix-shell */ { ENTRY_ENV_SPACE_ROOTS, "nativeBuildInputs", "/lib", "IN_NIX_SHELL" }, { ENTRY_ENV_COLON_DIRS, "NIX_LD_LIBRARY_PATH", "", NULL }, /* nix-ld */ /* user profiles */ { ENTRY_HOME, "", "/.guix-home/profile/lib", NULL }, /* Guix Home */ { ENTRY_HOME, "", "/.guix-profile/lib", NULL }, /* guix package */ { ENTRY_HOME, "", "/.nix-profile/lib", NULL }, /* nix-env, nix profile */ { ENTRY_USER, "/etc/profiles/per-user/", "/lib", NULL }, /* NixOS per-user */ /* system profiles */ { ENTRY_DIR, "/run/current-system/profile/lib", "", NULL }, /* Guix System */ { ENTRY_DIR, "/run/current-system/sw/lib", "", NULL }, /* NixOS */};#define SEARCH_TABLE_LEN (sizeof SEARCH_TABLE / sizeof SEARCH_TABLE[0])/* ---- the real dlopen ------------------------------------------------- */typedef void *(*sigil_dlopen_fn)(const char *, int);static sigil_dlopen_fn real_dlopen(void){ static sigil_dlopen_fn fn = NULL; if (!fn) fn = (sigil_dlopen_fn)dlsym(RTLD_NEXT, "dlopen"); return fn;}static int dir_exists(const char *path){ struct stat st; return stat(path, &st) == 0 && S_ISDIR(st.st_mode);}static void trace_found(const search_entry *e, const char *name, const char *path){ if (!getenv(SIGIL_DL_SEARCH_VERBOSE_ENV)) return; const char *label = (e->kind == ENTRY_DIR) ? e->name : (e->kind == ENTRY_HOME) ? "HOME" : (e->kind == ENTRY_USER) ? "USER" : e->name; fprintf(stderr, SIGIL_DL_SEARCH_TAG ": dlopen %s: found via %s%s as %s (/gnu/store %s, /nix/store %s)\n", name, label, e->suffix, path, dir_exists("/gnu/store") ? "present" : "absent", dir_exists("/nix/store") ? "present" : "absent");}/* dir[0..dirlen) + suffix + "/" + name, through the real dlopen */static void *try_dir(sigil_dlopen_fn real, const search_entry *e, const char *dir, size_t dirlen, const char *suffix, const char *name, int flags){ char path[PATH_MAX]; size_t sufflen = strlen(suffix), namelen = strlen(name); if (dirlen == 0 || dirlen + sufflen + 1 + namelen + 1 > sizeof path) return NULL; memcpy(path, dir, dirlen); memcpy(path + dirlen, suffix, sufflen); path[dirlen + sufflen] = '/'; memcpy(path + dirlen + sufflen + 1, name, namelen + 1); void *h = real(path, flags); if (h) trace_found(e, name, path); return h;}/* every item of a separated list */static void *try_list(sigil_dlopen_fn real, const search_entry *e, const char *list, char sep, const char *name, int flags){ if (!list) return NULL; while (*list) { const char *end = strchr(list, sep); size_t len = end ? (size_t)(end - list) : strlen(list); void *h = try_dir(real, e, list, len, e->suffix, name, flags); if (h) return h; if (!end) break; list = end + 1; } return NULL;}static void *try_entry(sigil_dlopen_fn real, const search_entry *e, const char *name, int flags){ if (e->only_if && !getenv(e->only_if)) return NULL; const char *v; switch (e->kind) { case ENTRY_ENV_COLON_DIRS: return try_list(real, e, getenv(e->name), ':', name, flags); case ENTRY_ENV_SPACE_ROOTS: return try_list(real, e, getenv(e->name), ' ', name, flags); case ENTRY_ENV_ROOT: v = getenv(e->name); return v ? try_dir(real, e, v, strlen(v), e->suffix, name, flags) : NULL; case ENTRY_HOME: v = getenv("HOME"); return v ? try_dir(real, e, v, strlen(v), e->suffix, name, flags) : NULL; case ENTRY_USER: { v = getenv("USER"); if (!v || !*v) return NULL; char root[PATH_MAX]; int n = snprintf(root, sizeof root, "%s%s", e->name, v); if (n <= 0 || (size_t)n >= sizeof root) return NULL; return try_dir(real, e, root, (size_t)n, e->suffix, name, flags); } case ENTRY_DIR: return try_dir(real, e, e->name, strlen(e->name), e->suffix, name, flags); } return NULL;}/* ---- the resolver ---------------------------------------------------- */void *SIGIL_DL_SEARCH_OPEN(const char *name, int flags){ sigil_dlopen_fn real = real_dlopen(); if (!real) { /* No next dlopen: only a fully static link gets here; say so, since * the caller then reports the library as missing. */ fprintf(stderr, SIGIL_DL_SEARCH_TAG ": dlopen(%s): no underlying dlopen (static link?)\n", name ? name : "(null)"); return NULL; } void *h = real(name, flags); if (h || !name || strchr(name, '/')) return h; /* A bare soname the loader could not find: the table, in order. */ for (size_t i = 0; i < SEARCH_TABLE_LEN; i++) { h = try_entry(real, &SEARCH_TABLE[i], name, flags); if (h) return h; } /* Nothing found: repeat the plain call so dlerror() names the soname * the caller asked for, not the last directory tried. */ return real(name, flags);}#if SIGIL_DL_SEARCH_INTERPOSE/* The executable's dlopen: every dlopen in the process resolves here. */void *dlopen(const char *name, int flags){ return SIGIL_DL_SEARCH_OPEN(name, flags);}#endif/* Referenced from the package's own code so this object is always linked * ahead of libc, whatever order the linker meets the archive members in. */const char *SIGIL_DL_SEARCH_SUMMARY(void){ return "SIGIL_LIBRARY_PATH; $GUIX_ENVIRONMENT/lib; each $buildInputs and " "$nativeBuildInputs root + /lib under IN_NIX_SHELL; $NIX_LD_LIBRARY_PATH; ~/.guix-home/profile/lib; " "~/.guix-profile/lib; ~/.nix-profile/lib; /etc/profiles/per-user/$USER/lib; " "/run/current-system/profile/lib; /run/current-system/sw/lib";}src/c/miniaudio-impl.cadded
/* * miniaudio-impl.c - the audio device, over miniaudio * * The one translation unit that compiles miniaudio (vendor/miniaudio, * unpatched; VENDOR.md has the pin). It exposes three functions to audio.c * (audio-device.h) so the 95k-line header is included exactly once: * open a playback device with a stream callback, close it, name the * backend miniaudio picked. * * Every audio backend library is loaded at run time by miniaudio * (libpulse.so.0 first on Linux, then libasound.so.2, then libjack.so.0; * ole32/mmdevapi on Windows; the CoreAudio frameworks on macOS), so this * package links no audio library. Those loads go through * sigil_audio_dl_open (dl-search.c, the resolver shared with * sigil-desktop) instead of dlopen: the define below renames the call in * miniaudio's ma_dlopen, so a Guix or Nix profile's lib/ is searched when * the loader alone finds nothing. A program that also links sigil-desktop * has two copies of the resolver (one of them interposing dlopen for the * whole process); they agree on the table and never conflict, because * only sigil-desktop's copy defines the dlopen symbol. */#include "audio-device.h"#include <stdio.h>#include <stdlib.h>#include <string.h>#if defined(__linux__)/* The resolver (dl-search.c, SIGIL_DL_SEARCH_OPEN=sigil_audio_dl_open): * Linux only, like the file itself; Windows and macOS load by name or by * absolute framework path and need no help. */#include <dlfcn.h>void *sigil_audio_dl_open(const char *name, int flags);#define dlopen sigil_audio_dl_open#endif/* Device layer only: this package decodes with stb_vorbis and encodes with * the vendored libvorbis; miniaudio's engine, node graph, resource manager, * generators, decoders and encoders are compiled out. */#define MA_NO_DECODING#define MA_NO_ENCODING#define MA_NO_GENERATION#define MA_NO_RESOURCE_MANAGER#define MA_NO_NODE_GRAPH#define MA_NO_ENGINE#define MA_API static#define MINIAUDIO_IMPLEMENTATION#include "miniaudio.h"static ma_context g_context;static ma_device g_device;static int g_context_ready = 0;static int g_device_ready = 0;static sigil_audio_stream_cb g_stream_cb = NULL;static char g_app_name[128];static void data_callback(ma_device *device, void *output, const void *input, ma_uint32 frame_count){ (void)input; if (g_stream_cb) { g_stream_cb((float *)output, (int)frame_count, (int)device->playback.channels); } else { memset(output, 0, (size_t)frame_count * device->playback.channels * sizeof(float)); }}static void log_callback(void *user, ma_uint32 level, const char *message){ (void)user; if (level <= MA_LOG_LEVEL_WARNING || getenv("SIGIL_AUDIO_VERBOSE")) { fprintf(stderr, "sigil-audio: miniaudio %s: %s", ma_log_level_to_string(level), message); }}/* The name a sound server shows for this stream: the program's own, as * the process sees it, so `pactl list sink-inputs` names the game. */static const char *app_name(void){ if (g_app_name[0]) return g_app_name; const char *name = NULL;#if defined(__GLIBC__) extern char *program_invocation_short_name; name = program_invocation_short_name;#endif if (!name || !*name) name = "sigil-audio"; snprintf(g_app_name, sizeof g_app_name, "%s", name); return g_app_name;}int sigil_audio_device_open(int sample_rate, int channels, int buffer_frames, sigil_audio_stream_cb stream_cb){ if (g_device_ready) return 0; ma_context_config cc = ma_context_config_init(); cc.pulse.pApplicationName = app_name(); cc.jack.pClientName = app_name(); ma_log *log = NULL; static ma_log g_log; if (ma_log_init(NULL, &g_log) == MA_SUCCESS) { ma_log_register_callback(&g_log, ma_log_callback_init(log_callback, NULL)); log = &g_log; } cc.pLog = log; if (ma_context_init(NULL, 0, &cc, &g_context) != MA_SUCCESS) { fprintf(stderr, "sigil-audio: no audio backend could be initialised\n"); return -1; } g_context_ready = 1; if (g_context.backend == ma_backend_null) { /* No backend library was found (or none opened): miniaudio's Null * backend consumes frames in silence. Say so once, out loud, so a * silent game is not a mystery; the trace under SIGIL_AUDIO_VERBOSE * lists every directory the resolver tried. */ fprintf(stderr, "sigil-audio: no audio backend library found " "(libpulse.so.0, libasound.so.2, libjack.so.0 on Linux); running silent. " "Put one in a profile the resolver searches or in SIGIL_LIBRARY_PATH.\n"); } g_stream_cb = stream_cb; ma_device_config dc = ma_device_config_init(ma_device_type_playback); dc.playback.format = ma_format_f32; dc.playback.channels = (ma_uint32)channels; dc.sampleRate = (ma_uint32)sample_rate; dc.periodSizeInFrames = (ma_uint32)buffer_frames; dc.dataCallback = data_callback; /* the stream callback writes every frame (it clears first) */ dc.noPreSilencedOutputBuffer = MA_TRUE; dc.pulse.pStreamNamePlayback = app_name(); if (ma_device_init(&g_context, &dc, &g_device) != MA_SUCCESS) { fprintf(stderr, "sigil-audio: %s: could not open a playback device\n", ma_get_backend_name(g_context.backend)); ma_context_uninit(&g_context); g_context_ready = 0; return -1; } if (ma_device_start(&g_device) != MA_SUCCESS) { fprintf(stderr, "sigil-audio: %s: could not start the playback device\n", ma_get_backend_name(g_context.backend)); ma_device_uninit(&g_device); ma_context_uninit(&g_context); g_context_ready = 0; return -1; } g_device_ready = 1; if (getenv("SIGIL_AUDIO_VERBOSE")) { fprintf(stderr, "sigil-audio: backend=%s device=\"%s\" format=f32 channels=%u rate=%u period=%u frames\n", ma_get_backend_name(g_context.backend), g_device.playback.name, g_device.playback.channels, g_device.sampleRate, g_device.playback.internalPeriodSizeInFrames); } return 0;}void sigil_audio_device_close(void){ if (g_device_ready) { ma_device_uninit(&g_device); g_device_ready = 0; } if (g_context_ready) { ma_context_uninit(&g_context); g_context_ready = 0; } g_stream_cb = NULL;}const char *sigil_audio_device_backend(void){ if (!g_device_ready) return NULL; return ma_get_backend_name(g_context.backend);}src/c/sokol-audio.cdeleted
/* * sokol-audio.c - Sokol implementation for sigil-audio * * Implements sokol_audio. Audio is independent of windowing. * * Note: sokol_log.h is included for declarations only (implementation in sigil-app). *//* Include sokol_log declarations WITHOUT implementation (implemented in sigil-app) */#include "sokol_log.h"/* Now define SOKOL_IMPL for sokol_audio */#define SOKOL_IMPL#include "sokol_audio.h"test/dl-search-probe.cadded
/* dl-search-probe.c - links dl-search.c and reports which file a soname * resolved to. Driven by test/dl-search-test.sh, compiled with the same * -D configuration as the package: with SIGIL_DL_SEARCH_INTERPOSE=1 it * calls plain dlopen (proving the interposition), with 0 it calls the * named resolver the package's loader calls. * usage: dl-search-probe <soname> exit 0 and print the path on success */#ifndef _GNU_SOURCE#define _GNU_SOURCE#endif#include <dlfcn.h>#include <link.h>#include <stdio.h>void *SIGIL_DL_SEARCH_OPEN(const char *name, int flags);const char *SIGIL_DL_SEARCH_SUMMARY(void);int main(int argc, char **argv){ if (argc != 2) { fprintf(stderr, "usage: %s <soname>\n", argv[0]); return 2; }#if SIGIL_DL_SEARCH_INTERPOSE void *h = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL);#else void *h = SIGIL_DL_SEARCH_OPEN(argv[1], RTLD_NOW | RTLD_LOCAL);#endif if (!h) { printf("UNRESOLVED %s: %s\n", argv[1], dlerror()); return 1; } struct link_map *lm = NULL; if (dlinfo(h, RTLD_DI_LINKMAP, &lm) == 0 && lm) printf("RESOLVED %s -> %s\n", argv[1], lm->l_name); else printf("RESOLVED %s\n", argv[1]); (void)SIGIL_DL_SEARCH_SUMMARY; return 0;}test/dl-search-test.shadded
#!/bin/sh# dl-search-test.sh - the interposed dlopen against fake Guix and Nix profiles.## Builds a probe from src/c/dl-search.c, a throwaway shared object, and a# temp HOME holding an empty ~/.guix-home/profile/lib and a# ~/.nix-profile/lib that has the object; then asserts, in order:# 1. the plain loader cannot find the soname (the control: the fallback# is what resolves it, not LD_LIBRARY_PATH or the cache)# 2. it resolves, from the Nix profile, with the Guix row walked first# and empty (both distributions' rows are live at once)# 3. with a copy in the Guix profile too, the Guix row wins (first hit)# and the trace names it# 4. IN_NIX_SHELL + buildInputs resolves ahead of every profile, and the# same buildInputs without IN_NIX_SHELL resolves nothing# 5. SIGIL_LIBRARY_PATH resolves ahead of everything# Any assertion failing exits 1. Needs a C compiler: $CC, or the pinned# zig under ~/.sigil/toolchain/zig, or cc.set -ucd "$(dirname "$0")/.." || exit 2if [ -n "${CC:-}" ]; then :elif [ -x "$HOME/.sigil/toolchain/zig/zig" ]; then CC="$HOME/.sigil/toolchain/zig/zig cc"elif command -v cc >/dev/null 2>&1; then CC=ccelse echo "SETUP-FAILED: no C compiler (set CC)"; exit 2; fiT=$(mktemp -d) || exit 2trap 'rm -rf "$T"' EXITFAKE=libsigildlsearchfake.so.7printf 'int sigil_fake_answer(void) { return 42; }\n' > "$T/fake.c"$CC -shared -fPIC -o "$T/$FAKE" "$T/fake.c" || { echo "SETUP-FAILED: cannot build the fake library"; exit 2; }$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; }H="$T/home"; mkdir -p "$H/.guix-home/profile/lib" "$H/.nix-profile/lib" "$T/nixstore/foo-1.0/lib"cp "$T/$FAKE" "$H/.nix-profile/lib/"fails=0pass() { echo "PASS $1: $2"; }fail() { echo "FAIL $1: $2"; fails=$((fails + 1)); }run() { # name, expected-substring-of-stdout, env assignments... name=$1; want=$2; shift 2 out=$(env -i HOME="$H" USER=sigiltest SIGIL_AUDIO_VERBOSE=1 "$@" "$T/probe" "$FAKE" 2>&1) case "$out" in *"$want"*) pass "$name" "$want";; *) fail "$name" "wanted [$want], got: $out";; esac}# 1. control: HOME with no profile dirs at allmkdir -p "$T/emptyhome"run control "UNRESOLVED $FAKE" HOME="$T/emptyhome"# 2. Nix profile only (the Guix row exists and is empty)run nix-profile "found via HOME/.nix-profile/lib as $H/.nix-profile/lib/$FAKE"# 3. both: Guix Home firstcp "$T/$FAKE" "$H/.guix-home/profile/lib/"run first-hit-wins "found via HOME/.guix-home/profile/lib as $H/.guix-home/profile/lib/$FAKE"# 4. nix-shell inputs beat the profilescp "$T/$FAKE" "$T/nixstore/foo-1.0/lib/"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"# 4b. the nix-shell rows apply only under IN_NIX_SHELL: same buildInputs, no# IN_NIX_SHELL, no copies in the profiles -> unresolvedrm -f "$H/.guix-home/profile/lib/$FAKE" "$H/.nix-profile/lib/$FAKE"run nix-shell-gated "UNRESOLVED $FAKE" buildInputs="$T/nixstore/foo-1.0"cp "$T/$FAKE" "$H/.nix-profile/lib/"# 5. the override beats everythingmkdir -p "$T/override"; cp "$T/$FAKE" "$T/override/"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"# provenance: this copy must be the one src/c/DL-SEARCH-ORIGIN.md recordsif [ -f src/c/DL-SEARCH-ORIGIN.md ]; then want=$(grep -E '^ [0-9a-f]{64}$' src/c/DL-SEARCH-ORIGIN.md | tr -d ' ') have=$(sha256sum src/c/dl-search.c | cut -d' ' -f1) if [ "$want" = "$have" ]; then pass provenance "dl-search.c matches the hash in DL-SEARCH-ORIGIN.md" else fail provenance "dl-search.c sha256 $have, DL-SEARCH-ORIGIN.md records $want: recopy from sigil-desktop or update the record"; fifi# the trace names the stores present on this machineout=$(env -i HOME="$H" USER=sigiltest SIGIL_AUDIO_VERBOSE=1 "$T/probe" "$FAKE" 2>&1)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";; esacif [ $fails -eq 0 ]; then echo "GREEN: dl-search"; exit 0; else echo "RED: $fails failed"; exit 1; fivendor/miniaudio/LICENSEadded
This software is available as a choice of the following licenses. Choosewhichever you prefer.===============================================================================ALTERNATIVE 1 - Public Domain (www.unlicense.org)===============================================================================This is free and unencumbered software released into the public domain.Anyone is free to copy, modify, publish, use, compile, sell, or distribute thissoftware, either in source code form or as a compiled binary, for any purpose,commercial or non-commercial, and by any means.In jurisdictions that recognize copyright laws, the author or authors of thissoftware dedicate any and all copyright interest in the software to the publicdomain. We make this dedication for the benefit of the public at large and tothe detriment of our heirs and successors. We intend this dedication to be anovert act of relinquishment in perpetuity of all present and future rights tothis software under copyright law.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN ANACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTIONWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.For more information, please refer to <http://unlicense.org/>===============================================================================ALTERNATIVE 2 - MIT No Attribution===============================================================================Copyright 2025 David ReidPermission is hereby granted, free of charge, to any person obtaining a copy ofthis software and associated documentation files (the "Software"), to deal inthe Software without restriction, including without limitation the rights touse, copy, modify, merge, publish, distribute, sublicense, and/or sell copiesof the Software, and to permit persons to whom the Software is furnished to doso.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE. No newline at end of filevendor/miniaudio/VENDOR.mdadded
# Vendored miniaudio- Upstream: https://github.com/mackron/miniaudio- Tag: `0.11.25` (released 2026-03-03)- Source archive: `https://github.com/mackron/miniaudio/archive/refs/tags/0.11.25.tar.gz`, sha256 `b900edcffe979816e2560a0580b9b1216d674b4f17fbadeca8f777a7f8ab0274`- License: public domain (Unlicense) or MIT-0, at the user's choice; `LICENSE` is the archive's## RuleNo local patches: `miniaudio.h` is byte-identical to the archive's. Allconfiguration is `#define`s in `src/c/miniaudio-impl.c` ahead of theimplementation (the `MA_NO_*` feature cuts, and `dlopen` redirected to thispackage's resolver). Upgrade by replacing the two files from a newer tagand updating this file.Re-derive: curl -sSL -o /tmp/ma.tar.gz https://github.com/mackron/miniaudio/archive/refs/tags/0.11.25.tar.gz sha256sum /tmp/ma.tar.gz # b900edcf... tar xzf /tmp/ma.tar.gz -C /tmp cmp vendor/miniaudio/miniaudio.h /tmp/miniaudio-0.11.25/miniaudio.h cmp vendor/miniaudio/LICENSE /tmp/miniaudio-0.11.25/LICENSE## What this package usesThe device layer only: `ma_context` + `ma_device` in playback mode, f32,the data callback feeding from this package's own mixer and streamingring. Decoding (stb_vorbis here), encoding (vendored libvorbis), theengine, node graph, resource manager and generators are compiled out.Backends, in miniaudio's own priority order, every one loaded at runtime by soname through `dlopen` (redirected to `sigil_audio_dl_open`, theresolver shared with sigil-desktop, so Guix and Nix profiles are searched):Linux `libpulse.so.0` (PulseAudio, which on a PipeWire desktop ispipewire-pulse), then `libasound.so.2` (ALSA), then `libjack.so.0`;Windows `ole32.dll`, `mmdevapi.dll`, `avrt.dll`, `user32.dll`,`advapi32.dll`, `winmm.dll`, `dsound.dll` (WASAPI first); macOS theCoreFoundation, CoreAudio and AudioToolbox frameworks by absolute path.Nothing audio-related is linked on any platform.vendor/miniaudio/miniaudio.hadded
/*Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file.miniaudio - v0.11.25 - 2026-03-04David Reid - mackron@gmail.comWebsite: https://miniaud.ioDocumentation: https://miniaud.io/docsGitHub: https://github.com/mackron/miniaudio*//*1. Introduction===============To use miniaudio, just include "miniaudio.h" like any other header and add "miniaudio.c" to yoursource tree. If you don't want to add it to your source tree you can compile and link to it likeany other library. Note that ABI compatibility is not guaranteed between versions, even with bugfix releases, so take care if compiling as a shared object.miniaudio includes both low level and high level APIs. The low level API is good for those who wantto do all of their mixing themselves and only require a light weight interface to the underlyingaudio device. The high level API is good for those who have complex mixing and effect requirements.In miniaudio, objects are transparent structures. Unlike many other libraries, there are no handlesto opaque objects which means you need to allocate memory for objects yourself. In the examplespresented in this documentation you will often see objects declared on the stack. You need to becareful when translating these examples to your own code so that you don't accidentally declareyour objects on the stack and then cause them to become invalid once the function returns. Inaddition, you must ensure the memory address of your objects remain the same throughout theirlifetime. You therefore cannot be making copies of your objects.A config/init pattern is used throughout the entire library. The idea is that you set up a configobject and pass that into the initialization routine. The advantage to this system is that theconfig object can be initialized with logical defaults and new properties added to it withoutbreaking the API. The config object can be allocated on the stack and does not need to bemaintained after initialization of the corresponding object.1.1. Low Level API------------------The low level API gives you access to the raw audio data of an audio device. It supports playback,capture, full-duplex and loopback (WASAPI only). You can enumerate over devices to determine whichphysical device(s) you want to connect to.The low level API uses the concept of a "device" as the abstraction for physical devices. The ideais that you choose a physical device to emit or capture audio from, and then move data to/from thedevice when miniaudio tells you to. Data is delivered to and from devices asynchronously via acallback which you specify when initializing the device.When initializing the device you first need to configure it. The device configuration allows you tospecify things like the format of the data delivered via the callback, the size of the internalbuffer and the ID of the device you want to emit or capture audio from.Once you have the device configuration set up you can initialize the device. When initializing adevice you need to allocate memory for the device object beforehand. This gives the applicationcomplete control over how the memory is allocated. In the example below we initialize a playbackdevice on the stack, but you could allocate it on the heap if that suits your situation better. ```c void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) { // In playback mode copy data to pOutput. In capture mode read data from pInput. In full-duplex mode, both // pOutput and pInput will be valid and you can move data from pInput into pOutput. Never process more than // frameCount frames. } int main() { ma_device_config config = ma_device_config_init(ma_device_type_playback); config.playback.format = ma_format_f32; // Set to ma_format_unknown to use the device's native format. config.playback.channels = 2; // Set to 0 to use the device's native channel count. config.sampleRate = 48000; // Set to 0 to use the device's native sample rate. config.dataCallback = data_callback; // This function will be called when miniaudio needs more data. config.pUserData = pMyCustomData; // Can be accessed from the device object (device.pUserData). ma_device device; if (ma_device_init(NULL, &config, &device) != MA_SUCCESS) { return -1; // Failed to initialize the device. } ma_device_start(&device); // The device is sleeping by default so you'll need to start it manually. // Do something here. Probably your program's main loop. ma_device_uninit(&device); return 0; } ```In the example above, `data_callback()` is where audio data is written and read from the device.The idea is in playback mode you cause sound to be emitted from the speakers by writing audio datato the output buffer (`pOutput` in the example). In capture mode you read data from the inputbuffer (`pInput`) to extract sound captured by the microphone. The `frameCount` parameter tells youhow many frames can be written to the output buffer and read from the input buffer. A "frame" isone sample for each channel. For example, in a stereo stream (2 channels), one frame is 2samples: one for the left, one for the right. The channel count is defined by the device config.The size in bytes of an individual sample is defined by the sample format which is also specifiedin the device config. Multi-channel audio data is always interleaved, which means the samples foreach frame are stored next to each other in memory. For example, in a stereo stream the first pairof samples will be the left and right samples for the first frame, the second pair of samples willbe the left and right samples for the second frame, etc.The configuration of the device is defined by the `ma_device_config` structure. The config objectis always initialized with `ma_device_config_init()`. It's important to always initialize theconfig with this function as it initializes it with logical defaults and ensures your programdoesn't break when new members are added to the `ma_device_config` structure. The example aboveuses a fairly simple and standard device configuration. The call to `ma_device_config_init()` takesa single parameter, which is whether or not the device is a playback, capture, duplex or loopbackdevice (loopback devices are not supported on all backends). The `config.playback.format` membersets the sample format which can be one of the following (all formats are native-endian): +---------------+----------------------------------------+---------------------------+ | Symbol | Description | Range | +---------------+----------------------------------------+---------------------------+ | ma_format_f32 | 32-bit floating point | [-1, 1] | | ma_format_s16 | 16-bit signed integer | [-32768, 32767] | | ma_format_s24 | 24-bit signed integer (tightly packed) | [-8388608, 8388607] | | ma_format_s32 | 32-bit signed integer | [-2147483648, 2147483647] | | ma_format_u8 | 8-bit unsigned integer | [0, 255] | +---------------+----------------------------------------+---------------------------+The `config.playback.channels` member sets the number of channels to use with the device. Thechannel count cannot exceed MA_MAX_CHANNELS. The `config.sampleRate` member sets the sample rate(which must be the same for both playback and capture in full-duplex configurations). This isusually set to 44100 or 48000, but can be set to anything. It's recommended to keep this between8000 and 384000, however.Note that leaving the format, channel count and/or sample rate at their default values will resultin the internal device's native configuration being used which is useful if you want to avoid theoverhead of miniaudio's automatic data conversion.In addition to the sample format, channel count and sample rate, the data callback and user datapointer are also set via the config. The user data pointer is not passed into the callback as aparameter, but is instead set to the `pUserData` member of `ma_device` which you can accessdirectly since all miniaudio structures are transparent.Initializing the device is done with `ma_device_init()`. This will return a result code telling youwhat went wrong, if anything. On success it will return `MA_SUCCESS`. After initialization iscomplete the device will be in a stopped state. To start it, use `ma_device_start()`.Uninitializing the device will stop it, which is what the example above does, but you can also stopthe device with `ma_device_stop()`. To resume the device simply call `ma_device_start()` again.Note that it's important to never stop or start the device from inside the callback. This willresult in a deadlock. Instead you set a variable or signal an event indicating that the deviceneeds to stop and handle it in a different thread. The following APIs must never be called insidethe callback: ```c ma_device_init() ma_device_init_ex() ma_device_uninit() ma_device_start() ma_device_stop() ```You must never try uninitializing and reinitializing a device inside the callback. You must alsonever try to stop and start it from inside the callback. There are a few other things you shouldn'tdo in the callback depending on your requirements, however this isn't so much a thread-safetything, but rather a real-time processing thing which is beyond the scope of this introduction.The example above demonstrates the initialization of a playback device, but it works exactly thesame for capture. All you need to do is change the device type from `ma_device_type_playback` to`ma_device_type_capture` when setting up the config, like so: ```c ma_device_config config = ma_device_config_init(ma_device_type_capture); config.capture.format = MY_FORMAT; config.capture.channels = MY_CHANNEL_COUNT; ```In the data callback you just read from the input buffer (`pInput` in the example above) and leavethe output buffer alone (it will be set to NULL when the device type is set to`ma_device_type_capture`).These are the available device types and how you should handle the buffers in the callback: +-------------------------+--------------------------------------------------------+ | Device Type | Callback Behavior | +-------------------------+--------------------------------------------------------+ | ma_device_type_playback | Write to output buffer, leave input buffer untouched. | | ma_device_type_capture | Read from input buffer, leave output buffer untouched. | | ma_device_type_duplex | Read from input buffer, write to output buffer. | | ma_device_type_loopback | Read from input buffer, leave output buffer untouched. | +-------------------------+--------------------------------------------------------+You will notice in the example above that the sample format and channel count is specifiedseparately for playback and capture. This is to support different data formats between the playbackand capture devices in a full-duplex system. An example may be that you want to capture audio dataas a monaural stream (one channel), but output sound to a stereo speaker system. Note that if youuse different formats between playback and capture in a full-duplex configuration you will need toconvert the data yourself. There are functions available to help you do this which will beexplained later.The example above did not specify a physical device to connect to which means it will use theoperating system's default device. If you have multiple physical devices connected and you want touse a specific one you will need to specify the device ID in the configuration, like so: ```c config.playback.pDeviceID = pMyPlaybackDeviceID; // Only if requesting a playback or duplex device. config.capture.pDeviceID = pMyCaptureDeviceID; // Only if requesting a capture, duplex or loopback device. ```To retrieve the device ID you will need to perform device enumeration, however this requires theuse of a new concept called the "context". Conceptually speaking the context sits above the device.There is one context to many devices. The purpose of the context is to represent the backend at amore global level and to perform operations outside the scope of an individual device. Mainly it isused for performing run-time linking against backend libraries, initializing backends andenumerating devices. The example below shows how to enumerate devices. ```c ma_context context; if (ma_context_init(NULL, 0, NULL, &context) != MA_SUCCESS) { // Error. } ma_device_info* pPlaybackInfos; ma_uint32 playbackCount; ma_device_info* pCaptureInfos; ma_uint32 captureCount; if (ma_context_get_devices(&context, &pPlaybackInfos, &playbackCount, &pCaptureInfos, &captureCount) != MA_SUCCESS) { // Error. } // Loop over each device info and do something with it. Here we just print the name with their index. You may want // to give the user the opportunity to choose which device they'd prefer. for (ma_uint32 iDevice = 0; iDevice < playbackCount; iDevice += 1) { printf("%d - %s\n", iDevice, pPlaybackInfos[iDevice].name); } ma_device_config config = ma_device_config_init(ma_device_type_playback); config.playback.pDeviceID = &pPlaybackInfos[chosenPlaybackDeviceIndex].id; config.playback.format = MY_FORMAT; config.playback.channels = MY_CHANNEL_COUNT; config.sampleRate = MY_SAMPLE_RATE; config.dataCallback = data_callback; config.pUserData = pMyCustomData; ma_device device; if (ma_device_init(&context, &config, &device) != MA_SUCCESS) { // Error } ... ma_device_uninit(&device); ma_context_uninit(&context); ```The first thing we do in this example is initialize a `ma_context` object with `ma_context_init()`.The first parameter is a pointer to a list of `ma_backend` values which are used to override thedefault backend priorities. When this is NULL, as in this example, miniaudio's default prioritiesare used. The second parameter is the number of backends listed in the array pointed to by thefirst parameter. The third parameter is a pointer to a `ma_context_config` object which can beNULL, in which case defaults are used. The context configuration is used for setting the loggingcallback, custom memory allocation callbacks, user-defined data and some backend-specificconfigurations.Once the context has been initialized you can enumerate devices. In the example above we use thesimpler `ma_context_get_devices()`, however you can also use a callback for handling devices byusing `ma_context_enumerate_devices()`. When using `ma_context_get_devices()` you provide a pointerto a pointer that will, upon output, be set to a pointer to a buffer containing a list of`ma_device_info` structures. You also provide a pointer to an unsigned integer that will receivethe number of items in the returned buffer. Do not free the returned buffers as their memory ismanaged internally by miniaudio.The `ma_device_info` structure contains an `id` member which is the ID you pass to the deviceconfig. It also contains the name of the device which is useful for presenting a list of devicesto the user via the UI.When creating your own context you will want to pass it to `ma_device_init()` when initializing thedevice. Passing in NULL, like we do in the first example, will result in miniaudio creating thecontext for you, which you don't want to do since you've already created a context. Note thatinternally the context is only tracked by it's pointer which means you must not change the locationof the `ma_context` object. If this is an issue, consider using `malloc()` to allocate memory forthe context.1.2. High Level API-------------------The high level API consists of three main parts: * Resource management for loading and streaming sounds. * A node graph for advanced mixing and effect processing. * A high level "engine" that wraps around the resource manager and node graph.The resource manager (`ma_resource_manager`) is used for loading sounds. It supports loading soundsfully into memory and also streaming. It will also deal with reference counting for you whichavoids the same sound being loaded multiple times.The node graph is used for mixing and effect processing. The idea is that you connect a number ofnodes into the graph by connecting each node's outputs to another node's inputs. Each node canimplement its own effect. By chaining nodes together, advanced mixing and effect processing canbe achieved.The engine encapsulates both the resource manager and the node graph to create a simple, easy touse high level API. The resource manager and node graph APIs are covered in more later sections ofthis manual.The code below shows how you can initialize an engine using its default configuration. ```c ma_result result; ma_engine engine; result = ma_engine_init(NULL, &engine); if (result != MA_SUCCESS) { return result; // Failed to initialize the engine. } ```This creates an engine instance which will initialize a device internally which you can access with`ma_engine_get_device()`. It will also initialize a resource manager for you which can be accessedwith `ma_engine_get_resource_manager()`. The engine itself is a node graph (`ma_node_graph`) whichmeans you can pass a pointer to the engine object into any of the `ma_node_graph` APIs (with acast). Alternatively, you can use `ma_engine_get_node_graph()` instead of a cast.Note that all objects in miniaudio, including the `ma_engine` object in the example above, aretransparent structures. There are no handles to opaque structures in miniaudio which means you needto be mindful of how you declare them. In the example above we are declaring it on the stack, butthis will result in the struct being invalidated once the function encapsulating it returns. Ifallocating the engine on the heap is more appropriate, you can easily do so with a standard callto `malloc()` or whatever heap allocation routine you like: ```c ma_engine* pEngine = malloc(sizeof(*pEngine)); ```The `ma_engine` API uses the same config/init pattern used all throughout miniaudio. To configurean engine, you can fill out a `ma_engine_config` object and pass it into the first parameter of`ma_engine_init()`: ```c ma_result result; ma_engine engine; ma_engine_config engineConfig; engineConfig = ma_engine_config_init(); engineConfig.pResourceManager = &myCustomResourceManager; // <-- Initialized as some earlier stage. result = ma_engine_init(&engineConfig, &engine); if (result != MA_SUCCESS) { return result; } ```This creates an engine instance using a custom config. In this particular example it's showing howyou can specify a custom resource manager rather than having the engine initialize one internally.This is particularly useful if you want to have multiple engine's share the same resource manager.The engine must be uninitialized with `ma_engine_uninit()` when it's no longer needed.By default the engine will be started, but nothing will be playing because no sounds have beeninitialized. The easiest but least flexible way of playing a sound is like so: ```c ma_engine_play_sound(&engine, "my_sound.wav", NULL); ```This plays what miniaudio calls an "inline" sound. It plays the sound once, and then puts theinternal sound up for recycling. The last parameter is used to specify which sound group the soundshould be associated with which will be explained later. This particular way of playing a sound issimple, but lacks flexibility and features. A more flexible way of playing a sound is to firstinitialize a sound: ```c ma_result result; ma_sound sound; result = ma_sound_init_from_file(&engine, "my_sound.wav", 0, NULL, NULL, &sound); if (result != MA_SUCCESS) { return result; } ma_sound_start(&sound); ```This returns a `ma_sound` object which represents a single instance of the specified sound file. Ifyou want to play the same file multiple times simultaneously, you need to create one sound for eachinstance.Sounds should be uninitialized with `ma_sound_uninit()`.Sounds are not started by default. Start a sound with `ma_sound_start()` and stop it with`ma_sound_stop()`. When a sound is stopped, it is not rewound to the start. Use`ma_sound_seek_to_pcm_frame(&sound, 0)` to seek back to the start of a sound. By default, startingand stopping sounds happens immediately, but sometimes it might be convenient to schedule the soundto be started and/or stopped at a specific time. This can be done with the following functions: ```c ma_sound_set_start_time_in_pcm_frames() ma_sound_set_start_time_in_milliseconds() ma_sound_set_stop_time_in_pcm_frames() ma_sound_set_stop_time_in_milliseconds() ```The start/stop time needs to be specified based on the absolute timer which is controlled by theengine. The current global time in PCM frames can be retrieved with`ma_engine_get_time_in_pcm_frames()`. The engine's global time can be changed with`ma_engine_set_time_in_pcm_frames()` for synchronization purposes if required. Note that schedulinga start time still requires an explicit call to `ma_sound_start()` before anything will play: ```c ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time_in_pcm_frames(&engine) + (ma_engine_get_sample_rate(&engine) * 2); ma_sound_start(&sound); ```The third parameter of `ma_sound_init_from_file()` is a set of flags that control how the sound beloaded and a few options on which features should be enabled for that sound. By default, the soundis synchronously loaded fully into memory straight from the file system without any kind ofdecoding. If you want to decode the sound before storing it in memory, you need to specify the`MA_SOUND_FLAG_DECODE` flag. This is useful if you want to incur the cost of decoding at an earlierstage, such as a loading stage. Without this option, decoding will happen dynamically at mixingtime which might be too expensive on the audio thread.If you want to load the sound asynchronously, you can specify the `MA_SOUND_FLAG_ASYNC` flag. Thiswill result in `ma_sound_init_from_file()` returning quickly, but the sound will not start playinguntil the sound has had some audio decoded.The fourth parameter is a pointer to sound group. A sound group is used as a mechanism to organisesounds into groups which have their own effect processing and volume control. An example is a gamewhich might have separate groups for sfx, voice and music. Each of these groups have their ownindependent volume control. Use `ma_sound_group_init()` or `ma_sound_group_init_ex()` to initializea sound group.Sounds and sound groups are nodes in the engine's node graph and can be plugged into any `ma_node`API. This makes it possible to connect sounds and sound groups to effect nodes to produce complexeffect chains.A sound can have its volume changed with `ma_sound_set_volume()`. If you prefer decibel volumecontrol you can use `ma_volume_db_to_linear()` to convert from decibel representation to linear.Panning and pitching is supported with `ma_sound_set_pan()` and `ma_sound_set_pitch()`. If you knowa sound will never have its pitch changed with `ma_sound_set_pitch()` or via the doppler effect,you can specify the `MA_SOUND_FLAG_NO_PITCH` flag when initializing the sound for an optimization.By default, sounds and sound groups have spatialization enabled. If you don't ever want tospatialize your sounds, initialize the sound with the `MA_SOUND_FLAG_NO_SPATIALIZATION` flag. Thespatialization model is fairly simple and is roughly on feature parity with OpenAL. HRTF andenvironmental occlusion are not currently supported, but planned for the future. The supportedfeatures include: * Sound and listener positioning and orientation with cones * Attenuation models: none, inverse, linear and exponential * Doppler effectSounds can be faded in and out with `ma_sound_set_fade_in_pcm_frames()`.To check if a sound is currently playing, you can use `ma_sound_is_playing()`. To check if a soundis at the end, use `ma_sound_at_end()`. Looping of a sound can be controlled with`ma_sound_set_looping()`. Use `ma_sound_is_looping()` to check whether or not the sound is looping.2. Building===========miniaudio should work cleanly out of the box without the need to download or install anydependencies. See below for platform-specific details.This library has been designed to be added directly to your source tree which is the preferred wayof using it, but you can compile it as a normal library if that's your preference. Be careful ifcompiling as a shared object because miniaudio is not ABI compatible between any release, includingbug fix releases. It's recommended you link statically.Note that GCC and Clang require `-msse2`, `-mavx2`, etc. for SIMD optimizations.If you get errors about undefined references to `__sync_val_compare_and_swap_8`, `__atomic_load_8`,etc. you need to link with `-latomic`.2.1. Windows------------The Windows build should compile cleanly on all popular compilers without the need to configure anyinclude paths nor link to any libraries.The UWP build may require linking to mmdevapi.lib if you get errors about an unresolved externalsymbol for `ActivateAudioInterfaceAsync()`.2.2. macOS and iOS------------------The macOS build should compile cleanly without the need to download any dependencies nor link toany libraries or frameworks. The iOS build needs to be compiled as Objective-C and will need tolink the relevant frameworks but should compile cleanly out of the box with Xcode. Compilingthrough the command line requires linking to `-lpthread` and `-lm`.Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple'snotarization process. To fix this there are two options. The first is to compile with`-DMA_NO_RUNTIME_LINKING` which in turn will require linking with`-framework CoreFoundation -framework CoreAudio -framework AudioToolbox`. If you get errors aboutAudioToolbox, try with `-framework AudioUnit` instead. You may get this when using older versionsof iOS. Alternatively, if you would rather keep using runtime linking you can add the following toyour entitlements.xcent file: ``` <key>com.apple.security.cs.allow-dyld-environment-variables</key> <true/> <key>com.apple.security.cs.allow-unsigned-executable-memory</key> <true/> ```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
#if defined(SOKOL_IMPL) && !defined(SOKOL_AUDIO_IMPL)#define SOKOL_AUDIO_IMPL#endif#ifndef SOKOL_AUDIO_INCLUDED/* sokol_audio.h -- cross-platform audio-streaming API Project URL: https://github.com/floooh/sokol Do this: #define SOKOL_IMPL or #define SOKOL_AUDIO_IMPL before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following defines with your own implementations: SOKOL_DUMMY_BACKEND - use a dummy backend SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) SOKOL_AUDIO_API_DECL- public function declaration prefix (default: extern) SOKOL_API_DECL - same as SOKOL_AUDIO_API_DECL SOKOL_API_IMPL - public function implementation prefix (default: -) SAUDIO_RING_MAX_SLOTS - max number of slots in the push-audio ring buffer (default 1024) SAUDIO_OSX_USE_SYSTEM_HEADERS - define this to force inclusion of system headers on macOS instead of using embedded CoreAudio declarations If sokol_audio.h is compiled as a DLL, define the following before including the declaration or implementation: SOKOL_DLL On Windows, SOKOL_DLL will define SOKOL_AUDIO_API_DECL as __declspec(dllexport) or __declspec(dllimport) as needed. Link with the following libraries: - on macOS: AudioToolbox - on iOS: AudioToolbox, AVFoundation - on FreeBSD: asound - on Linux: asound - on Android: aaudio - on Windows with MSVC or Clang toolchain: no action needed, libs are defined in-source via pragma-comment-lib - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' and link with -lole32 - on Vita: SceAudio - on 3DS: NDSP (libctru) FEATURE OVERVIEW ================ You provide a mono- or stereo-stream of 32-bit float samples, which Sokol Audio feeds into platform-specific audio backends: - Windows: WASAPI - Linux: ALSA - FreeBSD: ALSA - macOS: CoreAudio - iOS: CoreAudio+AVAudioSession - emscripten: WebAudio with ScriptProcessorNode - Android: AAudio - Vita: SceAudio - 3DS: NDSP (libctru) Sokol Audio will not do any buffer mixing or volume control, if you have multiple independent input streams of sample data you need to perform the mixing yourself before forwarding the data to Sokol Audio. There are two mutually exclusive ways to provide the sample data: 1. Callback model: You provide a callback function, which will be called when Sokol Audio needs new samples. On all platforms except emscripten, this function is called from a separate thread. 2. Push model: Your code pushes small blocks of sample data from your main loop or a thread you created. The pushed data is stored in a ring buffer where it is pulled by the backend code when needed. The callback model is preferred because it is the most direct way to feed sample data into the audio backends and also has less moving parts (there is no ring buffer between your code and the audio backend). Sometimes it is not possible to generate the audio stream directly in a callback function running in a separate thread, for such cases Sokol Audio provides the push-model as a convenience. SOKOL AUDIO, SOLOUD AND MINIAUDIO ================================= The WASAPI, ALSA and CoreAudio backend code has been taken from the SoLoud library (with some modifications, so any bugs in there are most likely my fault). If you need a more fully-featured audio solution, check out SoLoud, it's excellent: https://github.com/jarikomppa/soloud Another alternative which feature-wise is somewhere inbetween SoLoud and sokol-audio might be MiniAudio: https://github.com/mackron/miniaudio GLOSSARY ======== - stream buffer: The internal audio data buffer, usually provided by the backend API. The size of the stream buffer defines the base latency, smaller buffers have lower latency but may cause audio glitches. Bigger buffers reduce or eliminate glitches, but have a higher base latency. - stream callback: Optional callback function which is called by Sokol Audio when it needs new samples. On Windows, macOS/iOS and Linux, this is called in a separate thread, on WebAudio, this is called per-frame in the browser thread. - channel: A discrete track of audio data, currently 1-channel (mono) and 2-channel (stereo) is supported and tested. - sample: The magnitude of an audio signal on one channel at a given time. In Sokol Audio, samples are 32-bit float numbers in the range -1.0 to +1.0. - frame: The tightly packed set of samples for all channels at a given time. For mono 1 frame is 1 sample. For stereo, 1 frame is 2 samples. - packet: In Sokol Audio, a small chunk of audio data that is moved from the main thread to the audio streaming thread in order to decouple the rate at which the main thread provides new audio data, and the streaming thread consuming audio data. WORKING WITH SOKOL AUDIO ======================== First call saudio_setup() with your preferred audio playback options. In most cases you can stick with the default values, these provide a good balance between low-latency and glitch-free playback on all audio backends. You should always provide a logging callback to be aware of any warnings and errors. The easiest way is to use sokol_log.h for this: #include "sokol_log.h" // ... saudio_setup(&(saudio_desc){ .logger = { .func = slog_func, } }); If you want to use the callback-model, you need to provide a stream callback function either in saudio_desc.stream_cb or saudio_desc.stream_userdata_cb, otherwise keep both function pointers zero-initialized. Use push model and default playback parameters: saudio_setup(&(saudio_desc){ .logger.func = slog_func }); Use stream callback model and default playback parameters: saudio_setup(&(saudio_desc){ .stream_cb = my_stream_callback .logger.func = slog_func, }); The standard stream callback doesn't have a user data argument, if you want that, use the alternative stream_userdata_cb and also set the user_data pointer: saudio_setup(&(saudio_desc){ .stream_userdata_cb = my_stream_callback, .user_data = &my_data .logger.func = slog_func, }); The following playback parameters can be provided through the saudio_desc struct: General parameters (both for stream-callback and push-model): int sample_rate -- the sample rate in Hz, default: 44100 int num_channels -- number of channels, default: 1 (mono) int buffer_frames -- number of frames in streaming buffer, default: 2048 The stream callback prototype (either with or without userdata): void (*stream_cb)(float* buffer, int num_frames, int num_channels) void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data) Function pointer to the user-provide stream callback. Push-model parameters: int packet_frames -- number of frames in a packet, default: 128 int num_packets -- number of packets in ring buffer, default: 64 The sample_rate and num_channels parameters are only hints for the audio backend, it isn't guaranteed that those are the values used for actual playback. To get the actual parameters, call the following functions after saudio_setup(): int saudio_sample_rate(void) int saudio_channels(void); It's unlikely that the number of channels will be different than requested, but a different sample rate isn't uncommon. (NOTE: there's an yet unsolved issue when an audio backend might switch to a different sample rate when switching output devices, for instance plugging in a bluetooth headset, this case is currently not handled in Sokol Audio). You can check if audio initialization was successful with saudio_isvalid(). If backend initialization failed for some reason (for instance when there's no audio device in the machine), this will return false. Not checking for success won't do any harm, all Sokol Audio function will silently fail when called after initialization has failed, so apart from missing audio output, nothing bad will happen. Before your application exits, you should call saudio_shutdown(); This stops the audio thread (on Linux, Windows and macOS/iOS) and properly shuts down the audio backend. THE STREAM CALLBACK MODEL ========================= To use Sokol Audio in stream-callback-mode, provide a callback function like this in the saudio_desc struct when calling saudio_setup(): void stream_cb(float* buffer, int num_frames, int num_channels) { ... } Or the alternative version with a user-data argument: void stream_userdata_cb(float* buffer, int num_frames, int num_channels, void* user_data) { my_data_t* my_data = (my_data_t*) user_data; ... } The job of the callback function is to fill the *buffer* with 32-bit float sample values. To output silence, fill the buffer with zeros: void stream_cb(float* buffer, int num_frames, int num_channels) { const int num_samples = num_frames * num_channels; for (int i = 0; i < num_samples; i++) { buffer[i] = 0.0f; } } For stereo output (num_channels == 2), the samples for the left and right channel are interleaved: void stream_cb(float* buffer, int num_frames, int num_channels) { assert(2 == num_channels); for (int i = 0; i < num_frames; i++) { buffer[2*i + 0] = ...; // left channel buffer[2*i + 1] = ...; // right channel } } Please keep in mind that the stream callback function is running in a separate thread, if you need to share data with the main thread you need to take care yourself to make the access to the shared data thread-safe! THE PUSH MODEL ============== To use the push-model for providing audio data, simply don't set (keep zero-initialized) the stream_cb field in the saudio_desc struct when calling saudio_setup(). To provide sample data with the push model, call the saudio_push() function at regular intervals (for instance once per frame). You can call the saudio_expect() function to ask Sokol Audio how much room is in the ring buffer, but if you provide a continuous stream of data at the right sample rate, saudio_expect() isn't required (it's a simple way to sync/throttle your sample generation code with the playback rate though). With saudio_push() you may need to maintain your own intermediate sample buffer, since pushing individual sample values isn't very efficient. The following example is from the MOD player sample in sokol-samples (https://github.com/floooh/sokol-samples): const int num_frames = saudio_expect(); if (num_frames > 0) { const int num_samples = num_frames * saudio_channels(); read_samples(flt_buf, num_samples); saudio_push(flt_buf, num_frames); } Another option is to ignore saudio_expect(), and just push samples as they are generated in small batches. In this case you *need* to generate the samples at the right sample rate: The following example is taken from the Tiny Emulators project (https://github.com/floooh/chips-test), this is for mono playback, so (num_samples == num_frames): // tick the sound generator if (ay38910_tick(&sys->psg)) { // new sample is ready sys->sample_buffer[sys->sample_pos++] = sys->psg.sample; if (sys->sample_pos == sys->num_samples) { // new sample packet is ready saudio_push(sys->sample_buffer, sys->num_samples); sys->sample_pos = 0; } } THE WEBAUDIO BACKEND ==================== The WebAudio backend is currently using a ScriptProcessorNode callback to feed the sample data into WebAudio. ScriptProcessorNode has been deprecated for a while because it is running from the main thread, with the default initialization parameters it works 'pretty well' though. Ultimately Sokol Audio will use Audio Worklets, but this requires a few more things to fall into place (Audio Worklets implemented everywhere, SharedArrayBuffers enabled again, and I need to figure out a 'low-cost' solution in terms of implementation effort, since Audio Worklets are a lot more complex than ScriptProcessorNode if the audio data needs to come from the main thread). The WebAudio backend is automatically selected when compiling for emscripten (__EMSCRIPTEN__ define exists). https://developers.google.com/web/updates/2017/12/audio-worklet https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern "Blob URLs": https://www.html5rocks.com/en/tutorials/workers/basics/ Also see: https://blog.paul.cx/post/a-wait-free-spsc-ringbuffer-for-the-web/ THE COREAUDIO BACKEND ===================== The CoreAudio backend is selected on macOS and iOS (__APPLE__ is defined). Since the CoreAudio API is implemented in C (not Objective-C) on macOS the implementation part of Sokol Audio can be included into a C source file. However on iOS, Sokol Audio must be compiled as Objective-C due to it's reliance on the AVAudioSession object. The iOS code path support both being compiled with or without ARC (Automatic Reference Counting). For thread synchronisation, the CoreAudio backend will use the pthread_mutex_* functions. The incoming floating point samples will be directly forwarded to CoreAudio without further conversion. macOS and iOS applications that use Sokol Audio need to link with the AudioToolbox framework. THE WASAPI BACKEND ================== The WASAPI backend is automatically selected when compiling on Windows (_WIN32 is defined). For thread synchronisation a Win32 critical section is used. WASAPI may use a different size for its own streaming buffer then requested, so the base latency may be slightly bigger. The current backend implementation converts the incoming floating point sample values to signed 16-bit integers. The required Windows system DLLs are linked with #pragma comment(lib, ...), so you shouldn't need to add additional linker libs in the build process (otherwise this is a bug which should be fixed in sokol_audio.h). THE ALSA BACKEND ================ The ALSA backend is automatically selected when compiling on Linux ('linux' is defined). For thread synchronisation, the pthread_mutex_* functions are used. Samples are directly forwarded to ALSA in 32-bit float format, no further conversion is taking place. You need to link with the 'asound' library, and the <alsa/asoundlib.h> header must be present (usually both are installed with some sort of ALSA development package). THE VITA BACKEND ================ The VITA backend is automatically selected when compiling with vitasdk ('PSP2_SDK_VERSION' is defined). For thread synchronisation, the pthread_mutex_* functions are used. Samples are converted from float to short (uint16_t) to maintain all the same interface/api as other platforms. You may use any supported sample rate you wish, but all audio MUST match the same sample rate you choose. This uses the "BGM" port to allow selecting the sample rate ("Main" port is restricted to 48000 only). You need to link with the 'SceAudio' library, and the <psp2/audioout.h> header must be present (usually both are installed with the vitasdk). THE 3DS BACKEND ================ The 3DS backend is automatically selected when compiling with libctru ('__3DS__' is defined). Running a separate thread on the older 3ds is not a good idea and I was not able to get it working without slowing down the main thread too much (it has a single core available with cooperative threads). The NDSP seems to work better by using its ndspSetCallback method. You may use any supported sample rate you wish, but all audio MUST match the same sample rate you choose or it will sound slowed down or sped up. The queue size and other NDSP specific parameters can be chosen by the provided 'saudio_n3ds_desc' type. Defaults will be used if nothing is provided. There is a known issue of a noticeable delay when starting a new sound on emulators. I was not able to improve this to my liking and ~300ms can be expected. This can be improved by using a lower buffer size than the 2048 default but I would not suggest under 1536. It may crash under 1408, and they must be in multiples of 128. Note: I was NOT able to reproduce this issue on a real device and the audio worked perfectly. MEMORY ALLOCATION OVERRIDE ========================== You can override the memory allocation functions at initialization time like this: void* my_alloc(size_t size, void* user_data) { return malloc(size); } void my_free(void* ptr, void* user_data) { free(ptr); } ... saudio_setup(&(saudio_desc){ // ... .allocator = { .alloc_fn = my_alloc, .free_fn = my_free, .user_data = ..., } }); ... If no overrides are provided, malloc and free will be used. This only affects memory allocation calls done by sokol_audio.h itself though, not any allocations in OS libraries. Memory allocation will only happen on the same thread where saudio_setup() was called, so you don't need to worry about thread-safety. ERROR REPORTING AND LOGGING =========================== To get any logging information at all you need to provide a logging callback in the setup call the easiest way is to use sokol_log.h: #include "sokol_log.h" saudio_setup(&(saudio_desc){ .logger.func = slog_func }); To override logging with your own callback, first write a logging function like this: void my_log(const char* tag, // e.g. 'saudio' uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info uint32_t log_item_id, // SAUDIO_LOGITEM_* const char* message_or_null, // a message string, may be nullptr in release mode uint32_t line_nr, // line number in sokol_audio.h const char* filename_or_null, // source filename, may be nullptr in release mode void* user_data) { ... } ...and then setup sokol-audio like this: saudio_setup(&(saudio_desc){ .logger = { .func = my_log, .user_data = my_user_data, } }); The provided logging function must be reentrant (e.g. be callable from different threads).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
#if defined(SOKOL_IMPL) && !defined(SOKOL_LOG_IMPL)#define SOKOL_LOG_IMPL#endif#ifndef SOKOL_LOG_INCLUDED/* sokol_log.h -- common logging callback for sokol headers Project URL: https://github.com/floooh/sokol Example code: https://github.com/floooh/sokol-samples Do this: #define SOKOL_IMPL or #define SOKOL_LOG_IMPL before you include this file in *one* C or C++ file to create the implementation. Optionally provide the following defines when building the implementation: SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) SOKOL_LOG_API_DECL - public function declaration prefix (default: extern) SOKOL_API_DECL - same as SOKOL_GFX_API_DECL SOKOL_API_IMPL - public function implementation prefix (default: -) Optionally define the following for verbose output: SOKOL_DEBUG - by default this is defined if NDEBUG is not defined OVERVIEW ======== sokol_log.h provides a default logging callback for other sokol headers. To use the default log callback, just include sokol_log.h and provide a function pointer to the 'slog_func' function when setting up the sokol library: For instance with sokol_audio.h: #include "sokol_log.h" ... saudio_setup(&(saudio_desc){ .logger.func = slog_func }); Logging output goes to stderr and/or a platform specific logging subsystem (which means that in some scenarios you might see logging messages duplicated): - Windows: stderr + OutputDebugStringA() - macOS/iOS/Linux: stderr + syslog() - Emscripten: console.info()/warn()/error() - Android: __android_log_write() On Windows with sokol_app.h also note the runtime config items to make stdout/stderr output visible on the console for WinMain() applications via sapp_desc.win32.console_attach or sapp_desc.win32.console_create, however when running in a debugger on Windows, the logging output should show up on the debug output UI panel. In debug mode, a log message might look like this: [sspine][error][id:12] /Users/floh/projects/sokol/util/sokol_spine.h:3472:0: SKELETON_DESC_NO_ATLAS: no atlas object provided in sspine_skeleton_desc.atlas The source path and line number is formatted like compiler errors, in some IDEs (like VSCode) such error messages are clickable. In release mode, logging is less verbose as to not bloat the executable with string data, but you still get enough information to identify the type and location of an error: [sspine][error][id:12][line:3472] RULES FOR WRITING YOUR OWN LOGGING FUNCTION =========================================== - must be re-entrant because it might be called from different threads - must treat **all** provided string pointers as optional (can be null) - don't store the string pointers, copy the string data instead - must not return for log level panic LICENSE ======= zlib/libpng license Copyright (c) 2023 Andre Weissflog This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution.*/#define SOKOL_LOG_INCLUDED (1)#include <stdint.h>#if defined(SOKOL_API_DECL) && !defined(SOKOL_LOG_API_DECL)#define SOKOL_LOG_API_DECL SOKOL_API_DECL#endif#ifndef SOKOL_LOG_API_DECL#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_LOG_IMPL)#define SOKOL_LOG_API_DECL __declspec(dllexport)#elif defined(_WIN32) && defined(SOKOL_DLL)#define SOKOL_LOG_API_DECL __declspec(dllimport)#else#define SOKOL_LOG_API_DECL extern#endif#endif#ifdef __cplusplusextern "C" {#endif/* Plug this function into the 'logger.func' struct item when initializing any of the sokol headers. For instance for sokol_audio.h it would look like this: saudio_setup(&(saudio_desc){ .logger = { .func = slog_func } });*/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);#ifdef __cplusplus} // extern "C"#endif#endif // SOKOL_LOG_INCLUDED// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████//// >>implementation#ifdef SOKOL_LOG_IMPL#define SOKOL_LOG_IMPL_INCLUDED (1)#ifndef SOKOL_API_IMPL #define SOKOL_API_IMPL#endif#ifndef SOKOL_DEBUG #ifndef NDEBUG #define SOKOL_DEBUG #endif#endif#ifndef SOKOL_ASSERT #include <assert.h> #define SOKOL_ASSERT(c) assert(c)#endif#ifndef _SOKOL_PRIVATE #if defined(__GNUC__) || defined(__clang__) #define _SOKOL_PRIVATE __attribute__((unused)) static #else #define _SOKOL_PRIVATE static #endif#endif#ifndef _SOKOL_UNUSED #define _SOKOL_UNUSED(x) (void)(x)#endif// platform detection#if defined(__APPLE__) #define _SLOG_APPLE (1)#elif defined(__EMSCRIPTEN__) #define _SLOG_EMSCRIPTEN (1)#elif defined(_WIN32) #define _SLOG_WINDOWS (1)#elif defined(__ANDROID__) #define _SLOG_ANDROID (1)#elif defined(__linux__) || defined(__unix__) #define _SLOG_LINUX (1)#else#error "sokol_log.h: unknown platform"#endif#include <stdlib.h> // abort#include <stdio.h> // fputs#include <stddef.h> // size_t#if defined(_SLOG_EMSCRIPTEN)#include <emscripten/emscripten.h>#elif defined(_SLOG_WINDOWS)#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN#endif#ifndef NOMINMAX #define NOMINMAX#endif#include <windows.h>#elif defined(_SLOG_ANDROID)#include <android/log.h>#elif defined(_SLOG_LINUX) || defined(_SLOG_APPLE)#include <syslog.h>#endif// size of line buffer (on stack!) in bytes including terminating zero#define _SLOG_LINE_LENGTH (512)_SOKOL_PRIVATE char* _slog_append(const char* str, char* dst, char* end) { if (str) { char c; while (((c = *str++) != 0) && (dst < (end - 1))) { *dst++ = c; } } *dst = 0; return dst;}_SOKOL_PRIVATE char* _slog_itoa(uint32_t x, char* buf, size_t buf_size) { const size_t max_digits_and_null = 11; if (buf_size < max_digits_and_null) { return 0; } char* p = buf + max_digits_and_null; *--p = 0; do { *--p = '0' + (x % 10); x /= 10; } while (x != 0); return p;}#if defined(_SLOG_EMSCRIPTEN)EM_JS(void, slog_js_log, (uint32_t level, const char* c_str), { const str = UTF8ToString(c_str); switch (level) { case 0: console.error(str); break; case 1: console.error(str); break; case 2: console.warn(str); break; default: console.info(str); break; }})#endifSOKOL_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) { _SOKOL_UNUSED(user_data); const char* log_level_str; switch (log_level) { case 0: log_level_str = "panic"; break; case 1: log_level_str = "error"; break; case 2: log_level_str = "warning"; break; default: log_level_str = "info"; break; } // build log output line char line_buf[_SLOG_LINE_LENGTH]; char* str = line_buf; char* end = line_buf + sizeof(line_buf); char num_buf[32]; if (tag) { str = _slog_append("[", str, end); str = _slog_append(tag, str, end); str = _slog_append("]", str, end); } str = _slog_append("[", str, end); str = _slog_append(log_level_str, str, end); str = _slog_append("]", str, end); str = _slog_append("[id:", str, end); str = _slog_append(_slog_itoa(log_item, num_buf, sizeof(num_buf)), str, end); str = _slog_append("]", str, end); // if a filename is provided, build a clickable log message that's compatible with compiler error messages if (filename) { str = _slog_append(" ", str, end); #if defined(_MSC_VER) // MSVC compiler error format str = _slog_append(filename, str, end); str = _slog_append("(", str, end); str = _slog_append(_slog_itoa(line_nr, num_buf, sizeof(num_buf)), str, end); str = _slog_append("): ", str, end); #else // gcc/clang compiler error format str = _slog_append(filename, str, end); str = _slog_append(":", str, end); str = _slog_append(_slog_itoa(line_nr, num_buf, sizeof(num_buf)), str, end); str = _slog_append(":0: ", str, end); #endif } else { str = _slog_append("[line:", str, end); str = _slog_append(_slog_itoa(line_nr, num_buf, sizeof(num_buf)), str, end); str = _slog_append("] ", str, end); } if (message) { str = _slog_append("\n\t", str, end); str = _slog_append(message, str, end); } str = _slog_append("\n\n", str, end); if (0 == log_level) { str = _slog_append("ABORTING because of [panic]\n", str, end); (void)str; } // print to stderr? #if defined(_SLOG_LINUX) || defined(_SLOG_WINDOWS) || defined(_SLOG_APPLE) fputs(line_buf, stderr); #endif // platform specific logging calls #if defined(_SLOG_WINDOWS) OutputDebugStringA(line_buf); #elif defined(_SLOG_ANDROID) int prio; switch (log_level) { case 0: prio = ANDROID_LOG_FATAL; break; case 1: prio = ANDROID_LOG_ERROR; break; case 2: prio = ANDROID_LOG_WARN; break; default: prio = ANDROID_LOG_INFO; break; } __android_log_write(prio, "SOKOL", line_buf); #elif defined(_SLOG_EMSCRIPTEN) slog_js_log(log_level, line_buf); #endif if (0 == log_level) { abort(); }}#endif // SOKOL_LOG_IMPL