If faust isn't in PATH but nix-shell is, transparently re-exec the script via `nix-shell -p faust --run` instead of erroring out with a hint. NISPS_BUILD_IN_NIX_SHELL guards against infinite recursion if the shell somehow still lacks faust. |
||
|---|---|---|
| .. | ||
| additive-processor.js | ||
| additive.dsp | ||
| additive.json | ||
| additive.wasm | ||
| build.sh | ||
| eoc-compressor-processor.js | ||
| eoc-compressor.dsp | ||
| eoc-compressor.json | ||
| eoc-compressor.wasm | ||
| eoc-delay-processor.js | ||
| eoc-delay.dsp | ||
| eoc-delay.json | ||
| eoc-delay.wasm | ||
| eoc-eq-processor.js | ||
| eoc-eq.dsp | ||
| eoc-eq.json | ||
| eoc-eq.wasm | ||
| eoc-master-processor.js | ||
| eoc-master.dsp | ||
| eoc-master.json | ||
| eoc-master.wasm | ||
| eoc-reverb-processor.js | ||
| eoc-reverb.dsp | ||
| eoc-reverb.json | ||
| eoc-reverb.wasm | ||
| eoc-saturation-processor.js | ||
| eoc-saturation.dsp | ||
| eoc-saturation.json | ||
| eoc-saturation.wasm | ||
| faust-worklet-processor.js | ||
| fm-matrix-processor.js | ||
| fm-matrix.dsp | ||
| fm-matrix.json | ||
| fm-matrix.wasm | ||
| gen-modular-dsp.py | ||
| mod-pool.lib | ||
| modular-additive-processor.js | ||
| modular-additive.dsp | ||
| modular-additive.json | ||
| modular-additive.wasm | ||
| modular-fm-processor.js | ||
| modular-fm.dsp | ||
| modular-fm.json | ||
| modular-fm.wasm | ||
| modular-subtractive-processor.js | ||
| modular-subtractive-smoke.html | ||
| modular-subtractive.dsp | ||
| modular-subtractive.json | ||
| modular-subtractive.wasm | ||
| MODULAR_DESTINATIONS.md | ||
| README.md | ||
playground/faust — Faust DSP Build Pipeline
This directory contains Faust DSP source files and the toolchain for compiling them to WebAssembly for use in the MEMLNaut playground.
Required Tools
| Tool | Version | Purpose |
|---|---|---|
faust |
>= 2.60 | Compiles .dsp → .wasm + .json |
emcc |
>= 3.1.x | Optional — needed only if linking extra C++ code |
emcc is already available at /usr/lib/emscripten/emcc on this system.
faust is not currently installed — use nix-shell -p faust for a one-off
build, or nix profile install nixpkgs#faust to install permanently.
How to Compile
cd playground/faust
./build.sh
This compiles every .dsp file in the directory, producing alongside it:
<name>.wasm— the compiled DSP binary (loaded as an AudioWorkletNode)<name>.json— the Faust UI descriptor (consumed byfaustJsonToParamMeta)<name>.js— JS glue / AudioWorklet wrapper generated by faust
DSP Files
| File | Status | Description |
|---|---|---|
additive.dsp |
Placeholder | 4-harmonic sine bank (2 params). Full engine: meml-pj4 |
fm-matrix.dsp |
Placeholder | 2-op FM synth (4 params). Full engine: meml-wgg |
Output Format — .json Descriptor
Faust's -json flag emits a UI descriptor tree. Example:
{
"name": "additive",
"version": "2.75.7",
"options": "-vec",
"size": "0",
"inputs": "0",
"outputs": "2",
"meta": [...],
"ui": [
{
"type": "vgroup",
"label": "additive",
"items": [
{
"type": "hslider",
"label": "freq",
"address": "/additive/freq",
"meta": [{"unit": "Hz"}],
"init": 220,
"min": 20,
"max": 4000,
"step": 0.1
},
{
"type": "hslider",
"label": "amp",
"address": "/additive/amp",
"init": 0.5,
"min": 0,
"max": 1,
"step": 0.001
}
]
}
]
}
How faustJsonToParamMeta Consumes the JSON
playground/js/synth/faust-param-meta.js exports:
import { faustJsonToParamMeta, loadFaustParamMeta } from './faust-param-meta.js';
// From a pre-parsed object:
const paramMeta = faustJsonToParamMeta(faustJson);
// Or fetch + parse in one step:
const paramMeta = await loadFaustParamMeta('faust/additive.json');
faustJsonToParamMeta recursively walks the ui tree, collects all
hslider / vslider / nentry items, and returns:
[
{ id: 'freq', name: 'freq', min: 20, max: 4000, init: 220, curve: 0.5, group: 'additive' },
{ id: 'amp', name: 'amp', min: 0, max: 1, init: 0.5, curve: 0.5, group: 'additive' },
]
This is the standard paramMeta format used throughout the playground
(SynthEngine.paramMeta, preset system, group drawer, etc.).
FaustEngineBase Loading Pattern
playground/js/synth/faust-engine-base.js provides a base class for any
engine compiled with this pipeline:
import { FaustEngineBase } from './js/synth/faust-engine-base.js';
class AdditiveEngine extends FaustEngineBase {
constructor() {
super({
id: 'additive',
displayName: 'Additive',
wasmUrl: 'faust/additive.wasm',
jsonUrl: 'faust/additive.json',
workletUrl: 'faust/additive-processor.js', // AudioWorklet file
processorName: 'additive-processor', // registerProcessor() name
});
}
}
const engine = new AdditiveEngine();
await engine.init(audioCtx); // fetches JSON + WASM, loads worklet
engine.noteOn(69, 0.8); // A4, velocity 0.8
engine.setParam(0, 0.6); // normalized [0,1] → maps to param range
engine.noteOff(69);
The init() call:
- Fetches
jsonUrl→ callsloadFaustParamMeta()→ populatesengine.paramMeta - Fetches
wasmUrl→ transfers bytes to AudioWorklet - Registers
workletUrlwithaudioCtx.audioWorklet.addModule() - Creates an
AudioWorkletNodeand connects it toaudioCtx.destination - Sends
{ type: 'init', wasmBytes, sampleRate }to the worklet - Waits for the worklet to reply
{ type: 'ready' }(10 s timeout)
AudioWorklet Processor Base
faust-worklet-processor.js (this directory) defines FaustWorkletProcessor,
a base class for concrete engine worklets. It handles:
{ type: 'init', wasmBytes, sampleRate }— calls_initWasm(){ type: 'setParam', index, value }— calls_onSetParam(){ type: 'noteOn', freq, vel }— calls_onNoteOn(){ type: 'noteOff', freq }— calls_onNoteOff()- Replies
{ type: 'ready' }or{ type: 'error', message }to main thread
Each concrete engine processor overrides _initWasm() and _renderBlock().