memlnaut-nisps/playground/faust/README.md

155 lines
4.9 KiB
Markdown
Raw Normal View History

# 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:
- `<name>.wasm` — the compiled DSP binary (loaded as an AudioWorkletNode)
- `<name>.json` — the Faust UI descriptor (consumed by `faustJsonToParamMeta`)
- `<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:
```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()`.