Commit graph

21 commits

Author SHA1 Message Date
monkey-w1n5t0n
4507db00c8 fix(modular): matrix cells opt-in + per-group drawer for non-C15 engines
Two fixes driven by user reports:

1. MLP wasn't affecting the sound. Matrix cells are now opt-in to the
   MLP output vector rather than always-driven. Default modular paramMeta
   is 32 mod-source params (4 ADSR * A/D/S/R + 8 LFO * rate/morph), down
   from 512. The default patch's MM_Matrix/s00_d08_amp=1.0 now survives
   the first inference tick because it's not in paramMeta — ADSR1 stays
   routed to amp and the MLP drives envelope shape per joystick position.

   Matrix cells still clickable as direct-DSP knobs in modular-ui: setCell
   now routes through engine._setRawByLabel(). A later UI pass can add a
   "expose to MLP" menu entry that calls engine.setExposeMatrixCell(s,d).

   Also removes the earlier exampleCount-based routing gate; no longer
   needed now that the default patch is stable.

2. Per-group curve drawer (hover over section labels on the synth
   visualizer) is now available for all synth engines, not just C15.
   Refactored showGroupDrawer behind a getSectionView(sectionIndex)
   helper that returns a uniform view for either C15 (via SYNTH_SECTIONS
   + groupOverrides) or non-C15 (via nonC15Sections + nonC15GroupCurves
   + engineParamOverrides).

   Group-level curve persists across sub-engine swaps by group name, so
   e.g. tuning the "ADSR 1" curve survives a switch between subtractive
   and fm without being reset.

ModularEngine: adds setExposeMatrixCell(s,d,exposed) +
getExposedMatrixCells() + clears exposed cells on sub-engine swap.
2026-04-11 08:31:19 +02:00
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
w1n5t0n
960676acf3 feat(tests): add 24 Playwright e2e tests for engine switching + fix hidden param filtering
Tests cover:
- Default state (C15, 126 params, heatmap cells)
- Engine switcher UI (3 cards, active state)
- Switch to Additive (48 params, outputs bounded, training works)
- Switch to FM Matrix (55 params, outputs bounded, training works)
- Round-trip switching (C15→Add→C15, C15→FM→Add→C15)
- Warm-start weight preservation across resize
- EOC chain accessibility across engines
- SynthVisualizer visibility across engines
- No console errors during any switch

Also fixes: faustJsonToParamMeta now filters [hidden:1] params,
giving correct counts (48 additive, 55 FM) instead of including
freq/gate/_vel control inputs.
2026-04-03 19:28:23 +01:00
w1n5t0n
cb3bda034b fix(playground): FaustEngineBase creates AudioContext if none passed; add stop()/start() methods 2026-04-03 19:08:52 +01:00
w1n5t0n
f19d5e5baa fix(playground): engine switch — eager paramMeta + engine-agnostic SynthVisualizer (meml-gdh8)
When switching from C15 to Additive or FM engines, the MLP was resized
to 0 outputs because paramMeta wasn't loaded until init() (which needs
AudioContext). The SynthVisualizer also disappeared because its section
map was hardcoded to C15's 18 param groups.

- Add FaustEngineBase.loadParamMeta() for eager JSON-only loading
- Call loadParamMeta() before setActiveEngine() in engine switch handler
- Add SynthVisualizer.rebuild(paramMeta) to derive sections from groups
- Add rebuildParamToSection() / restoreC15ParamToSection() for engine-agnostic
  param-to-section mapping
- Fix tooltip to use engineParamOverrides for non-C15 engines
2026-04-03 18:41:05 +01:00
w1n5t0n
bc68ee64f8 Merge branch 'worktree-agent-a3abdb63' 2026-04-03 18:26:31 +01:00
w1n5t0n
329b326ede feat(playground): per-engine param presets for additive + FM (meml-ax4)
Additive engine: 5 presets across 3 tiers (Spectral Basics, Formant Play,
Harmonic Sculptor, Phase Explorer, Full Spectrum). FM engine: 5 presets
across 3 tiers (Simple FM, Bell Tones, Matrix Explorer, Feedback Machine,
Full Matrix).

Engine-aware preset system: applyPreset() dispatches to C15 groupOverrides
or new flat engineParamOverrides depending on active engine. Preset selector
dropdown rebuilds on engine switch. URL ?preset param searches all engines.
2026-04-03 18:26:12 +01:00
w1n5t0n
3c44b75416 fix(playground): decouple MIDIInput from C15Bridge — use SynthEngine interface (meml-se4) 2026-04-03 18:20:29 +01:00
w1n5t0n
5e8102c8c3 feat(playground): Additive + FM engine integration, live engine switching (meml-mdy, meml-355)
- Add AdditiveEngine (48 params) and FMEngine (55 params) wrapping FaustEngineBase
- Remove comingSoon flags from ENGINES descriptors
- Replace stub onSwitch handler with real engine construction + init + setActiveEngine
- Fix setActiveEngine EOC rewire to work with both C15 (_bridge.audioContext) and Faust (_audioCtx)
- Fix _startEocChain audioCtx resolution to support Faust engines
- Fix synth param send loop: use N_OUTPUTS instead of hardcoded SYNTH_PARAM_MAP.length
2026-04-03 17:56:35 +01:00
w1n5t0n
25caf726d8 feat(playground): EOC chain wired into audio graph, bypass mode manual sliders (meml-xp3)
- Wire EOCChain into audio graph after c15.start() (both start-btn and
  quick-play paths); guarded by _eocInited flag so init happens once per
  AudioContext lifetime
- getOutputNode() on C15Adapter now returns limiter (last node before
  destination) so EOC inserts correctly between limiter and destination
- Expose limiterNode getter on C15Bridge
- Add getCurrentParamValue(i) to EOCModule base class, backed by _paramValues
  array that setParam() writes to
- Add _buildParamSliders() to EOCChainUI: collapsible ▶ params toggle per
  module row, one range slider per paramMeta entry, calls module.setParam()
  on input; labelled "manual" when nispsMode=bypass
- eoc:change listener in a-app.js logs paramCount (hook point for future modes)
- saveState()/loadState() persist eocModules (id, enabled, params) and
  eocNispsMode; restore re-adds modules via moduleFactory and re-applies values
- CSS: eoc-params-section, eoc-params-toggle, eoc-param-row/label/slider;
  eoc-module-row gains flex-wrap to accommodate params section below controls
2026-04-03 17:49:14 +01:00
w1n5t0n
40ac7256f3 feat(playground): engine abstraction layer + C15 adapter (meml-byk)
Introduces SynthEngine interface so additive/FM engines can be hot-swapped
later. C15Bridge is wrapped by C15Adapter; arpeggiator decoupled from SAB.
2026-04-03 17:30:28 +01:00
w1n5t0n
55bf1b5d48 feat(playground): Faust WASM build pipeline + param auto-discovery (meml-5s3)
Adds the Faust DSP toolchain infrastructure: placeholder additive and FM DSP
files, build.sh (faust -lang wasm per .dsp), faustJsonToParamMeta() to convert
Faust JSON UI trees into the standard paramMeta format, FaustEngineBase
(SynthEngine subclass wiring init/setParam/noteOn/noteOff through AudioWorklet
messages), and FaustWorkletProcessor base class for concrete engine processors.
2026-04-03 17:30:23 +01:00
w1n5t0n
456c426cb1 hello come back to me 2026-03-30 17:05:45 +03:00
w1n5t0n
b4d3311b32 feat(playground): add OSC output bridge for external synth control
Browser-side OSCOutput module sends parameter values over WebSocket to
a companion Deno bridge script that converts them to OSC/UDP messages.
Enables controlling SuperCollider, Max/MSP, Pure Data, TouchDesigner,
or any OSC-capable software from the NISPS playground.

- Browser module (js/synth/osc-output.js): WebSocket client with
  auto-reconnect, throttle (~20fps), and dead-zone filtering
- Deno bridge (osc-bridge/bridge.ts): zero-dependency, compiles to
  standalone binaries via deno compile for Linux/macOS/Windows
- Test receiver (osc-bridge/test-receive.ts): terminal dashboard
  showing live OSC parameter values with bar charts
- OSC pill button in floating bar for quick connect/disconnect
- Help modal section with platform-aware download, setup guide,
  and examples for SuperCollider/PD/Max
- GitHub Actions workflow for cross-platform binary builds
- OSC sends in both visual and synth modes
2026-03-25 11:55:58 +02:00
w1n5t0n
2fb00f20a9 fix(playground): move arpeggiator to Worker thread, throttle synth params
Hand tracking at 30fps was writing 126 params per frame to the C15 ring
buffer (capacity 512), flooding it and starving arpeggiator noteOn/noteOff
messages — causing stuck/dropped notes.

Three-part fix:
- Arpeggiator now runs in a dedicated Web Worker with direct
  SharedArrayBuffer access, bypassing the main thread entirely for
  note timing. Worker setInterval isn't subject to main thread jank.
- RingBufferWriter uses CAS (Atomics.compareExchange) for multi-producer
  safety — both main thread (params) and worker (notes) write safely.
- routeOutputs throttles synth parameter sends: dead-zone filter
  (0.2% change threshold) + rate cap (~20fps), reducing ring buffer
  pressure from ~3800 msg/s to ~50-100 msg/s of actual changes.
2026-03-24 01:42:37 +02:00
w1n5t0n
8a550361ea feat(playground): add tiered synth parameter preset system
4 tiers of progressive complexity (Beginner 15 params → Expert 126),
13 presets total with per-param min/max/curve overrides that bias
distributions without clamping extremes. Preset dropdown in UI,
persisted to localStorage, supports ?preset= URL param.
2026-03-23 00:50:56 +02:00
w1n5t0n
affec8f604 feat(playground): add synth parameter tooltips, group drawer with per-param curve/mute controls
- Canvas tooltip follows mouse over synth bars showing param name, value, range, curve
- Group drawer: per-param draggable curve canvas, dual-thumb min/max range slider, mute toggle
- Muted params hide from visualizer (bars redistribute), use fixed value slider instead
- Group curve drag applies relative delta preserving individual param offsets
- Pulsing orange play button when audio engine not initialized (all UI modes)
2026-03-22 21:57:21 +02:00
w1n5t0n
295221d1b2 feat(playground): unify learning modes, add MIDI input and gamepad support
Merge Examples/RL tabs into single unified toolbar across all four UIs.
Users can now freely mix supervised learning (Add Example + Train) with
RL feedback (thumbs up/down) without switching modes. Param bar dragging
is always enabled.

Add Web MIDI input module (js/synth/midi-input.js) for external MIDI
controllers — routes note on/off to C15 synth, CC 1/2 to joystick.
Add gamepad module (js/ui/gamepad.js) with auto-detection, deadzone,
and axis normalization across all UIs.

Fix synth parameter sync: routeOutputs() now called after trainModel()
in onThumbsUp and loadState to prevent stale params on first joystick
move. Add separate Clear Examples button (dataset only, keeps weights).
2026-03-22 11:01:50 +02:00
w1n5t0n
925ddc5be6 feat(playground): add volume safety limiter, devmode tame parameter, and voice buildup mitigation
- Insert DynamicsCompressorNode as always-on brick-wall limiter after
  master gain (threshold -6dB, ratio 20:1, 2ms attack)
- Add devmode tame parameter (0-1) controlling range constraining:
  - ?tame=0.7 URL param (default) or window.setTameLevel(n)
  - 0 = no mitigation, 1 = strongest constraining
- Constrain 20 volume/buildup-sensitive params with safeMin/safeMax:
  - Envelope release/decay2 times capped to prevent infinite ringing
  - Envelope gains kept in moderate range
  - All 5 drive stages (ShpA/B, FB, Out, Cabinet) capped
  - Output mixer levels given floor and ceiling
  - Echo feedback and reverb size limited to prevent wash buildup
- Fix Comb Filter group count (8, not 9) in comments and color generator
2026-03-22 00:15:11 +02:00
w1n5t0n
24e3e9665e feat(playground): expand C15 param map from 20 to 126 synth parameters
Control all sonically meaningful C15 parameters through NISPS ML outputs.
Excludes hardware routing, macros, scale/tuning, key tracking, velocity,
envelope mod depths, discrete switches, and dangerous volume/pitch params.

- Expand param-map.js with 126 curated params grouped by synthesis section
- Widen MLP architecture from [3,10,10,14,20] to [3,32,48,64,126]
- Add ParamDisplay.rebuild() for mode-dependent bar count (20 visual, 126 synth)
- Add scrollable compact layout for synth mode param bars
- Pad visual presets and old saves to 126 outputs for backward compatibility
2026-03-21 22:58:48 +02:00
w1n5t0n
6f6a334529 feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.

Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.

New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 22:26:37 +02:00