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

45 lines
2.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.