Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
scsynth-sys
Raw FFI bindings to a statically-linked SuperCollider scsynth engine.
This crate builds libscsynth from a pinned SuperCollider revision, compiles a small C/C++ shim, and
generates Rust bindings over the engine's extern "C" surface with bindgen. It is the only place
unsafe FFI lives; safe abstractions belong in the scsynth crate.
Two completely different builds sit behind one binding surface, selected by target in
build.rs:
- native - CMake builds a static
libscsynthagainst the system C/C++ toolchain,libsndfileandfftw, with our host-pumped external audio driver. - wasm (
wasm32-unknown-unknown) - no system toolchain or sndfile/fftw exist, so the engine's DSP core + static UGen plugins are compiled from source against a from-source musl + libc++ (seewasm-toolchain/), driverless (mRealTime=false).
Where the source comes from
SuperCollider is not vendored in-tree. source.toml pins its { url, rev }, and
build.rs resolves the source as: SCSYNTH_SYS_SUPERCOLLIDER_DIR if set (the Nix build
and dev shell set it - they never fetch), otherwise a git fetch of that exact revision into a
rev-keyed cache (~/.cache/scsynth-rs/, overridable with SCSYNTH_SRC_CACHE_DIR). The Nix flake
reads the same source.toml, so the two cannot drift.
We fetch at build time rather than vendoring because SuperCollider's source (~135 MB) is far larger
than crates.io's package-size limit - vendoring it would make this crate unpublishable. The cost: a
first build needs git + network (or the env-var override; offline and docs.rs builds must use it).
How SuperCollider is modified
The fetched source is never touched: the build copies it into OUT_DIR and applies every change
to the copy. Updating SuperCollider is therefore just bumping the rev in source.toml - the
patches re-apply automatically (or fail loudly; see below). The changes are of two kinds: files we
add, and small text patches to SC's own sources.
Files we add (csrc/)
| File | Purpose |
|---|---|
SC_ExternalDriver.cpp |
The native custom backend: an SC_AUDIO_API_EXTERNAL audio driver that owns no device and no audio thread. The host (a cpal callback, a test, ...) pumps it one buffer at a time via scsynth_pump. Modelled on SuperCollider's externally-driven SC_WebAudio.cpp. |
SC_WasmPump.cpp |
The wasm driverless shim (scsynth_wasm_new/perform/pump), plus the "fake implementations" that fill the link holes left by the SC files excluded on wasm - mirroring SC's own SC_WebAudio.cpp. |
shim.cpp |
scsynth_default_world_options: constructs a default WorldOptions C++-side (bindgen can't reproduce its C++ default member initializers). |
libc_gap.c |
The few libc symbols the minimal wasm libc doesn't provide. |
Text patches to SC sources
Applied to the OUT_DIR copy via assert-guarded find-and-replace:
-
Native (
patch_audioapi_external): patchesserver/scsynth/CMakeLists.txtto (1) acceptexternalin theAUDIOAPIvalidation regex and (2) add oneelseif(AUDIOAPI STREQUAL external)branch that compilesSC_ExternalDriver.cppand definesSC_AUDIO_API_EXTERNAL. That is the entire native tweak - one CMake branch. -
Wasm (
patch_sc_sources): a handful of#ifndef SC_WASMguards so the engine links without the filesystem / networking / shared-memory code that doesn't exist on bare wasm:SC_Endian.h- add a wasm branch (little-endian, no<netinet/in.h>);SC_ReplyImpl.hpp- drop theboost::asioaddress field;SC_World.cpp- guard<filesystem>and the never-calledWorld_LoadGraphDefs;SC_GraphDef.cpp- guard the<filesystem>/d_loadfile-loaders.
Each guard extends a conditional SuperCollider already has for
__EMSCRIPTEN__; we just fire it on ourSC_WASMdefine as well.
Build steps (per target, in build.rs)
copy_tree- copy the fetched source intoOUT_DIR(skipping.git).sync_file- dropSC_ExternalDriver.cppinto the copy (native).patch_audioapi_external/patch_sc_sources- the text patches.generate_version_header- produceSC_Version.hpp(wasm only; CMake does this natively).- Build from the copy - CMake (native) or the
cccrate (wasm).
Why this survives SuperCollider updates
Every replacement first assert!s that its anchor text is present. If an upstream change moves or
renames an anchor, the build fails immediately with a precise message (e.g. "...SuperCollider
CMake layout changed", "...guard anchor not found in ") instead of silently mis-building. A
routine SC bump just works; a breaking one points you at the exact patch to update.
Plugins (UGens)
Both targets build with STATIC_PLUGINS, so the available UGens are exactly those registered by
SuperCollider's static-plugin load list (the #ifdef STATIC_PLUGINS block in SC_Lib_Cintf.cpp):
the core DSP set - IOUGens, OscUGens, DelayUGens, BinaryOp/UnaryOp/MulAdd, FilterUGens,
GendynUGens, LFUGens, NoiseUGens/DynNoiseUGens, GrainUGens, PanUGens, ReverbUGens,
TriggerUGens, PhysicalModelingUGens, TestUGens, DemandUGens, and the FFT/PV chain
(FFT_UGens + PV_UGens + PartitionedConvolution).
The wasm build compiles and registers this same set; the only intentional difference is DiskIO
(DiskIn/DiskOut/VDiskIn), which native registers through libsndfile but wasm omits (no
filesystem/libsndfile on bare wasm). So web and native expose an identical UGen set apart from disk
I/O.
This is SuperCollider's static set, which is narrower than a full desktop install's dynamically
loaded plugins: UGens that SuperCollider ships only as separately-loaded plugins - ChaosUGens, the
MIR/analysis set (MFCC, BeatTrack, KeyTrack, Loudness, Onsets, ...), the PV third-party
onset detectors, UnpackFFTUGens and DemoUGens - are compiled by the native CMake build but
not registered by the static load list, so they are unavailable on both targets (a SynthDef
that references one fails to load with UGen '<name>' not installed). Exposing them would mean
extending that load list (a patch applied to both targets) and is intentionally out of scope.
Pinning
The SuperCollider revision lives in source.toml - the single source of truth,
shared with the Nix build (which reads the same file). Bump rev there to update; the build-time
patches re-apply automatically.