memlnaut-nisps/playground/faust/MODULAR_DESTINATIONS.md
monkey-w1n5t0n afff406d92 feat(playground): add Modular audio mode with shared mod pool
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).
2026-04-11 07:34:41 +02:00

2.9 KiB
Raw Blame History

Modular engines — modulation destination table

Each of the three modular-* Faust engines exposes a 48-source × 10-destination modulation matrix. All engines import mod-pool.lib (16 ADSRs + 32 LFOs = 48 sources) and bind those sources to engine-specific destinations via the MM_Matrix/sNN_dNN_<destname> slider labels.

The destination index (dNN) is consistent per engine but NOT consistent across engines — d08 happens to be amp for all three because the generator places the master amplitude destination in the same slot for every engine, which makes it easy for Phase B to wire up a default ADSR1→amp patch regardless of the active engine.

Engine d00 d01 d02 d03 d04 d05 d06 d07 d08 d09
subtractive pitch osc2_detune osc3_detune osc_mix_bal noise_level cutoff resonance filter_env_amt amp pan
additive pitch bright tilt inharmonicity odd_even formant_ctr formant_depth noise_mix amp pan
fm pitch op1_level op2_level op3_level op4_level cross_mod_global feedback_global global_ratio amp pan

Conventions

  • Source index (sNN): 00..15 = ADSR slots 1..16, 16..47 = LFO slots 1..32.
  • Amount range: each matrix slider is [-1.0, +1.0], default 0 (no route).
  • amp destination (d08): always the master amplitude destination for the engine. Without any source routed here, the voice is either silent (subtractive — the mod_amp signal multiplies into the VCA) or produces a sustained tone at the engine's natural level (additive and fm — still gated through mod_amp; without a route the amp signal is 0). Route ADSR1 → d08 for a standard VCA envelope in all three engines.
  • pitch destination (d00): always semitones, ±12 per mod unit.
  • pan destination (d09): added to the master pan knob, clamped to [-1,1].

Notes

  • additive.bright is a progressive high-partial boost/cut (replaces the old brightness envelope in the non-modular additive.dsp). At mod=0, it is unity; at mod=+1 it boosts top partials, at -1 it cuts them.
  • additive.formant_ctr shifts BOTH formant centre frequencies by the same amount (up to ±8 harmonics) so you can "move" the vocal shape with an LFO or envelope.
  • fm.cross_mod_global and fm.feedback_global are (1 + mod) multipliers applied to the respective matrix cells BEFORE the feedback loop is closed, so a mod of +1 doubles FM depth and -1 zeroes it out.
  • fm.global_ratio is an ADDITIVE offset on all four operator ratios (±8), clamped to ≥0.01. This can behave like a pitch-bend when animated slowly.