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).
2.9 KiB
2.9 KiB
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). ampdestination (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.pitchdestination (d00): always semitones, ±12 per mod unit.pandestination (d09): added to the master pan knob, clamped to [-1,1].
Notes
additive.brightis a progressive high-partial boost/cut (replaces the old brightness envelope in the non-modularadditive.dsp). At mod=0, it is unity; at mod=+1 it boosts top partials, at -1 it cuts them.additive.formant_ctrshifts 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_globalandfm.feedback_globalare(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_ratiois an ADDITIVE offset on all four operator ratios (±8), clamped to ≥0.01. This can behave like a pitch-bend when animated slowly.