New "Modular" engine in a-immersive with three hot-swappable Faust sub-engines (subtractive/additive/fm) sharing a common modulation pool: 16 ADSR slots + 32 LFO slots (single-knob sine->tri->square->saw wavemorph) routed through a 48-source x 10-destination matrix per engine. Per-connection scalar amounts in [-1, 1], summed at each destination. Default MLP output count is 512 (32 mod-source params + 480 matrix cells); model reinits on sub-engine swap, count change, or engine-param exposure toggle. Faust layer: - mod-pool.lib: shared ADSR/LFO/source-bus library - gen-modular-dsp.py: byte-reproducible generator (source of truth) - modular-subtractive: faithful Minimoog (3 osc, ladder filter, no envs) - modular-additive: 64-partial, spectral shape + formants, no envs/LFOs - modular-fm: 4-op matrix + self-feedback, no envs - All three share d08=amp, d09=pan conventions - MODULAR_DESTINATIONS.md: authoritative destination table JS layer: - ModularEngine: self-contained SynthEngine with getState/setState, setSubEngine, setModSourceCount, setExposeEngineParam - modular-ui: drawer with sub-engine toggle, ADSR/LFO count steppers, per-slot enable switches, matrix grid editor (tap-cycle, long-press precise, right-click menu, negative amounts), preset overlay - modular-presets: 6 named presets (Slow pad, Plucky bass, Crystal, DX bell, Morphing drone + default) - a-app.js: Modular mode registered, paramMeta:change -> resizeMLP, modular DSP state persisted under modularDspState, window.__nisps debug hooks for programmatic control Tests: tests/e2e/modular-mode.spec.js (11 Playwright tests, all passing including DSP state survives reload, sub-engine swap keeps paramCount, preset apply verification). Also fixes a pre-existing build.sh bug where the -e flag caused faust to overwrite .wasm outputs with expanded DSP source text, leaving additive/fm-matrix/eoc-* committed as invalid WebAssembly. Rebuilt all affected engines with the corrected script. Added an early-message buffer to faust-worklet-processor.js so setParam calls arriving before wasm instantiation are queued rather than dropped (needed when the user configures modular state before clicking Start Audio).
111 lines
3.8 KiB
Bash
Executable file
111 lines
3.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# build.sh — compile all Faust DSP files in this directory to WASM + JSON
|
|
#
|
|
# Required tools:
|
|
# faust >= 2.60.0 (https://faust.grame.fr / nix: faust)
|
|
# emcc >= 3.1.x (Emscripten, already present at /usr/lib/emscripten/emcc)
|
|
#
|
|
# Usage:
|
|
# cd playground/faust && ./build.sh
|
|
#
|
|
# Outputs (alongside each .dsp):
|
|
# <name>.wasm — compiled audio DSP binary
|
|
# <name>.json — Faust UI descriptor (consumed by faustJsonToParamMeta)
|
|
# <name>.js — JS glue / AudioWorklet wrapper generated by faust2wasm
|
|
#
|
|
# The JSON descriptor is the key artifact: it contains the full UI tree
|
|
# (hslider / vslider / nentry / groups) that faustJsonToParamMeta.js parses
|
|
# into the playground's standard paramMeta format.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dependency checks
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if ! command -v faust &>/dev/null; then
|
|
echo ""
|
|
echo "ERROR: 'faust' not found in PATH."
|
|
echo ""
|
|
echo "Install options:"
|
|
echo " nix-shell -p faust # one-off"
|
|
echo " nix profile install nixpkgs#faust # permanent"
|
|
echo " Or download from: https://faust.grame.fr/downloads/"
|
|
echo ""
|
|
echo "Required version: >= 2.60.0"
|
|
echo "Check: faust --version"
|
|
exit 1
|
|
fi
|
|
|
|
FAUST_VER="$(faust --version 2>&1 | head -1)"
|
|
echo "faust: $FAUST_VER"
|
|
|
|
# emcc is optional — faust -lang wasm doesn't require it.
|
|
# It is needed if you want to link extra C++ into the WASM module.
|
|
if command -v emcc &>/dev/null; then
|
|
EMCC_VER="$(emcc --version 2>&1 | head -1)"
|
|
echo "emcc : $EMCC_VER"
|
|
else
|
|
echo "emcc : not found (not required for basic faust -lang wasm builds)"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Compile each .dsp
|
|
# ---------------------------------------------------------------------------
|
|
|
|
DSP_FILES=("$SCRIPT_DIR"/*.dsp)
|
|
|
|
if [ ${#DSP_FILES[@]} -eq 0 ]; then
|
|
echo "No .dsp files found in $SCRIPT_DIR"
|
|
exit 0
|
|
fi
|
|
|
|
for DSP in "${DSP_FILES[@]}"; do
|
|
NAME="$(basename "$DSP" .dsp)"
|
|
echo "Compiling $NAME.dsp ..."
|
|
|
|
# Faust -o is interpreted relative to -O (so we must cd into SCRIPT_DIR
|
|
# and pass just the filename). Class names (-cn) must be valid C identifiers,
|
|
# so dash characters are replaced with underscores.
|
|
CLASSNAME="$(echo "$NAME" | tr '-' '_')"
|
|
|
|
# Step 1: emit WASM binary + JS glue
|
|
# -lang wasm — target WebAssembly
|
|
# -cn <Name> — class name prefix in generated JS
|
|
# -O <dir> — output directory
|
|
#
|
|
# NB: do NOT pass -e here. In Faust 2.79, -e means "export expanded DSP
|
|
# (textual)" — it causes the .wasm output to be Faust source text, not a
|
|
# real WebAssembly binary. This script historically used -e by mistake.
|
|
( cd "$SCRIPT_DIR" && \
|
|
faust -lang wasm \
|
|
-cn "$CLASSNAME" \
|
|
-O "$SCRIPT_DIR" \
|
|
"${NAME}.dsp" \
|
|
-o "${NAME}.wasm" )
|
|
|
|
# Note: `faust -lang wasm -O . foo.dsp -o foo.wasm` already writes
|
|
# foo.json alongside foo.wasm with the wasm-native descriptor (including
|
|
# numeric `index` fields that workers use as setParamValue zone addresses).
|
|
# The old standalone `-json` pass produced a smaller JSON WITHOUT the
|
|
# `index` field, which is the wrong shape for workers — don't run it.
|
|
if [ ! -f "$SCRIPT_DIR/${NAME}.json" ]; then
|
|
echo " WARNING: ${NAME}.json not produced — check faust version"
|
|
fi
|
|
|
|
if [ -f "$SCRIPT_DIR/${NAME}.wasm" ]; then
|
|
WASM_SIZE="$(du -h "$SCRIPT_DIR/${NAME}.wasm" | cut -f1)"
|
|
echo " -> ${NAME}.wasm (${WASM_SIZE})"
|
|
fi
|
|
if [ -f "$SCRIPT_DIR/${NAME}.json" ]; then
|
|
echo " -> ${NAME}.json"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
echo "Done. Load engines in the playground via FaustEngineBase:"
|
|
echo " import { AdditiveEngine } from './js/synth/additive-engine.js';"
|