# 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 ```bash cd playground/faust ./build.sh ``` This compiles every `.dsp` file in the directory, producing alongside it: - `.wasm` — the compiled DSP binary (loaded as an AudioWorkletNode) - `.json` — the Faust UI descriptor (consumed by `faustJsonToParamMeta`) - `.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: ```json { "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: ```js 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: ```js [ { 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: ```js 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: 1. Fetches `jsonUrl` → calls `loadFaustParamMeta()` → populates `engine.paramMeta` 2. Fetches `wasmUrl` → transfers bytes to AudioWorklet 3. Registers `workletUrl` with `audioCtx.audioWorklet.addModule()` 4. Creates an `AudioWorkletNode` and connects it to `audioCtx.destination` 5. Sends `{ type: 'init', wasmBytes, sampleRate }` to the worklet 6. 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()`.