Commit graph

379 commits

Author SHA1 Message Date
w1n5t0n
9b08918b1e feat(shapeseq): freeze-as-algorithm/pattern + delta controller
Two freeze modes for capturing and preserving musical state:

- Algorithm freeze: snapshot all primitive params + seeds. Individual
  params can be selectively unfrozen for ML-driven exploration via
  DeltaController (applies MLP deltas to frozen base values with
  per-param boundary enforcement).

- Pattern freeze: snapshot the realized note pattern. The sequencer
  bypasses the primitive chain entirely and loops the frozen pattern.

Extracts _runPipeline() from setSequenceInputs() for reuse.
2026-04-06 23:55:23 +01:00
w1n5t0n
122bdb9843 feat(sequencer): generation counter forces re-eval on config changes
The dirty-check that skips re-evaluation when inputs haven't changed now
also checks a generation counter. Config changes (tempo, step count,
projection, chain edits) bump the counter, ensuring the pipeline re-runs
even when joystick position is static.
2026-04-06 23:51:57 +01:00
w1n5t0n
5796d8fd35 feat(seq-iml): configurable output count with scaled architecture
The sequence MLP output count is now configurable (default 16). Hidden
layer widths scale with the output count to keep inference cheap while
providing sufficient capacity. The sequencer exposes setOutputCount()
to resize the MLP at runtime.
2026-04-06 23:45:55 +01:00
w1n5t0n
4cea170795 refactor(projection): simplify to 3 global knobs
Replace the composable transform chain (VelocityCurve, GateThreshold,
RangeMap, OctaveFolder, StutterMap, presets) with 3 straightforward
post-chain knobs: velocityCurve, gateThreshold, and pitchRange. The
old system was over-engineered for a research prototype — the new API
is easier to wire to UI controls and reason about.
2026-04-06 23:41:30 +01:00
w1n5t0n
af4f19bc72 refactor(PitchWalker): redesign as processor, add reEvalOnLoop
PitchWalker was classified as a generator but always consumed the
incoming pattern's triggers. Reclassify as a processor so it modifies
the pattern produced by an upstream generator (clonePattern instead of
createPattern). Add reEvalOnLoop flag on the Primitive base class; when
set, the sequencer re-evaluates the chain at each loop start so
stateful primitives produce evolving patterns across loops.
2026-04-06 23:37:32 +01:00
w1n5t0n
b4b4025f59 feat(shapeseq): polyrhythm — per-generator step counts with LCM tiling
Generators can now have independent step counts. When merging patterns
of different lengths, both are tiled to their LCM. Chain exposes
setGeneratorStepCount/getGeneratorStepCount for polyrhythmic control.
2026-04-06 23:31:45 +01:00
w1n5t0n
aec58537db feat(pattern): add midiNote field + trigger-aware merge
Replace the pitch*127 encoding hack with a dedicated midiNote field on
pattern steps. IntervalLock now writes integer MIDI notes to step.midiNote
while preserving the original [0,1] pitch for other consumers. The clock
and sequencer bridge pass midiNote through the event pipeline.

Also reworks mergePatterns to be trigger-aware: when only one generator
fires at a step, use its values directly instead of averaging with the
other generator's defaults.
2026-04-06 23:28:01 +01:00
w1n5t0n
3f4ffcd4f9 refactor(event-bus): extract to shared module
The EventBus was duplicated inside shapeseq/ but is used by other
playground code too. Move the canonical implementation to js/event-bus.js
and turn the shapeseq copy into a re-export shim.
2026-04-06 23:18:47 +01:00
w1n5t0n
452754f448 docs(playground): add ARCHITECTURE.md 2026-04-06 23:17:42 +01:00
w1n5t0n
41106f22c0 feat(audio-canvas): per-file loop count, beat-synced lengths, param range groups, pitch lock
- Per-file loop count (0-4) with stepper UI on each waveform cell;
  dynamic MLP output sizing (8 range + N_loops × 4 position outputs)
- Beat-synced loop length: global bars/beats selector (2/4/8/16/32/64)
  with per-loop overrides via long-press dropdown, persistent badges
- Global param range groups: dual-thumb range sliders (volume, pitch,
  position, filter) with ML-driven min/max and per-loop position mapping
- Pitch lock toggle (1×) to freeze playback rate across all submodes
- Fix: _resizeOutputs now syncs _loopOutputs to prevent stale reads
- Fix: granular visuals consolidated per-clip to prevent multi-loop flicker
2026-04-03 20:36:24 +01: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
3fabc342a0 fix(playground): getParamOverride uses engineParamOverrides for non-C15; guard undefined sectionMap entries 2026-04-03 19:01:14 +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
78e9e64dee fix(playground): move engine switcher to synth drawer, fix output mode labels
- Engine switcher now lives in the Synth drawer (was incorrectly in NISPS params drawer)
- Params drawer renamed to "NISPS" (it's for ML engine tuning, not synth engine selection)
- Output mode button says "Synth" (generic) not "C15 Shaper-Feedback" (engine-specific)
- Dock icon for params drawer now labelled "NISPS" with correct tooltip
2026-04-03 18:30:20 +01:00
w1n5t0n
d5a315904a feat(playground): expand audio canvas import formats + add file picker button
Support opus, webm, mp4, wma, aif, aiff in addition to existing formats.
Add "+" button to control bar for importing files without drag-and-drop (mobile).
2026-04-03 18:29:59 +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
09991c030b Merge branch 'worktree-agent-a6e16a29' 2026-04-03 18:22:42 +01:00
w1n5t0n
8cf29fc7eb Merge branch 'worktree-agent-ab96c9fd' 2026-04-03 18:22:19 +01:00
w1n5t0n
97376c8d3f Merge branch 'worktree-agent-a188c7ec' 2026-04-03 18:22:19 +01:00
w1n5t0n
5ac67df699 Merge EOC Delay + Saturation + Master Bus modules (meml-2sl, meml-2zm)
Resolved conflicts: merged all 6 module imports in eoc-chain-ui.js and index.js.
All moduleFactory cases now return real Faust WASM implementations — no stubs remain.
2026-04-03 18:22:00 +01:00
w1n5t0n
2cda1a6178 feat(playground): EOC NISPS Independent mode — separate FX joystick (meml-vqo) 2026-04-03 18:21:59 +01:00
w1n5t0n
e01660c1d4 feat(playground): Linked mode training target UI — separate FX RL buttons (meml-0r0) 2026-04-03 18:20:52 +01:00
w1n5t0n
3c44b75416 fix(playground): decouple MIDIInput from C15Bridge — use SynthEngine interface (meml-se4) 2026-04-03 18:20:29 +01:00
w1n5t0n
a9e2b5a8f9 feat(playground/eoc): Delay + Saturation + Master Bus modules (meml-2sl, meml-2zm)
Faust DSP sources, compiled WASM + JSON descriptors, AudioWorklet processors,
and EOCModule subclasses for all three effects.
2026-04-03 18:19:32 +01:00
w1n5t0n
5a004c6a3e Merge Shared + Linked NISPS modes (meml-dqt, meml-vqg)
Resolved eoc:change listener conflict: both modes coexist in same handler.
Shared mode resizes combined MLP; Linked mode manages second IML.
2026-04-03 18:16:54 +01:00
w1n5t0n
e8cb8e4195 feat(playground): EOC NISPS Linked mode — dual MLP from same controller (meml-vqg) 2026-04-03 18:03:12 +01:00
w1n5t0n
4f44cd016d feat(playground): EOC NISPS Shared mode — combined MLP for synth + effects (meml-dqt)
- Add totalOutputCount() helper: engine params + EOC params when nispsMode === 'shared'
- setActiveEngine() calls resizeMLP(totalOutputCount()) and rebuilds heatmap with combined paramMeta
- eoc:change listener handles nispsMode-changed and module-* events: resizes MLP, rebuilds heatmap, toggles .shared-mode CSS class
- routeOutputs() slices outputs: indices < engineParamCount go to engine, remainder to eocChain.setParam() in Shared mode
- CSS: .heatmap-strip.shared-mode adds green bottom border and '+ FX' label to signal combined output space
2026-04-03 18:01:10 +01:00
w1n5t0n
0a5f2544ca feat(playground/eoc): EQ + Compressor + Reverb modules (meml-4b4, meml-cpe, meml-wwc)
Faust DSP sources, compiled WASM + JSON descriptors, AudioWorklet processors,
and EOCModule subclasses for all three effects.

- meml-4b4: 4-band parametric EQ (fi.low_shelf, fi.peak_eq × 2, fi.high_shelf)
  10 params: freq/gain per band, Q for the two mid bell bands
- meml-cpe: stereo feed-forward compressor (co.compressor_mono × 2)
  7 params: threshold, ratio, attack, release, knee, makeup, mix (parallel)
- meml-wwc: zita reverb (re.zita_rev1_stereo) with pre-delay and M/S width
  8 active params + mod_rate placeholder (zita internal mod not yet exposed)

All three DSPs compile cleanly under Faust 2.83.1. moduleFactory in
eoc-chain-ui.js now returns real instances for eq/compressor/reverb.
2026-04-03 17:57:36 +01:00
w1n5t0n
6007ab3853 Merge branch 'worktree-agent-ae813a27' 2026-04-03 17:57:05 +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
6f1107bae2 feat(playground): engine-aware heatmap + MIDI CC storage on engine switch (meml-qaa)
- Add rebuildHeatmap(paramMeta): resets rawParamValues/_lastSentParams and rebuilds
  #heatmap-cells from engine paramMeta; colors from SYNTH_PARAM_COLORS for C15,
  _colorFromGroup() hash for other engines
- Replace static SYNTH_PARAM_NAMES/COLORS lookups in buildHeatmap, setHeatmapValue,
  and showParamPopup with activeEngine.paramMeta[index] lookups
- Scope MIDI CC localStorage key per engine via midiCCStorageKey()
- Add reloadMidiCCMap() helper; called in setActiveEngine() after engine switch
- loadCCMap/saveCCMap in midi-cc-map.js accept optional key parameter
2026-04-03 17:55:39 +01:00
w1n5t0n
80f95b8db7 feat(playground/faust): full 48-param additive synth DSP (meml-pj4)
Replace placeholder stub with a complete additive synthesiser:
- 64 harmonics, 5 parameter groups (Spectral Shape, Temporal, Phase,
  Modulation, Master), 48 NISPS-mapped params in exact spec order
- Spectral descriptors: per-harmonic amplitudes, tilt, inharmonicity,
  odd/even balance, formant Gaussian shaping
- Temporal: dual ADSR (global + brightness), spectral flux LFO
- Phase: randomisation, walk, inter-partial beating, stereo spread,
  noise floor with colour, sub-harmonic
- Modulation: vibrato (delayed onset), tremolo, harmonic drift,
  formant resonance peaks
- Master: level, vel coupling, portamento, saturation waveshaper,
  fine-tune cents
- additive-processor.js: AudioWorklet processor extending
  FaustWorkletProcessor with dynamic zone-table construction at init,
  _onNoteOn/Off, _renderBlock, _onSetParam
- additive.json: Faust descriptor with 48 params in spec-ordered groups
  (numeric prefixes force alphabetical sort = spec order)
- additive.wasm: compiled with faust 2.83.1 -lang wasm -cn additive
2026-04-03 17:52:07 +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
f1b6e1aba0 feat(playground): engine switcher UI component (meml-phg)
Adds EngineSwitcher module with clickable engine cards (active highlight,
coming-soon greyed state, loading bar animation, confirm-on-switch when
training data exists). Wired into the Engine drawer; C15 active,
Additive/FM marked coming-soon with toast. Dock title reflects active
engine; engineId persisted to/restored from localStorage.
2026-04-03 17:49:07 +01:00
w1n5t0n
46e1e59aaf feat(playground): EOC chain drawer UI with drag-and-drop + bypass toggles (meml-u6x)
Adds eoc-chain-ui.js with NISPS mode selector, draggable module rows, bypass
pill toggles, remove buttons, param count badges, add-module bar, and stub
moduleFactory. Wires into a-immersive.html dock + drawer-stack and a-app.js init.
2026-04-03 17:42:59 +01:00
w1n5t0n
67d2230ba8 feat(playground/faust): full 56-param FM matrix synth DSP (meml-wgg)
- Full 4-operator FM with continuous 4×4 routing matrix (12 cross-mod pairs)
- Compiles to 46KB WASM via faust -lang wasm
- 55 visible NISPS params (pitch_glide missing from DSP, tracked)
- Includes fm-matrix-processor.js AudioWorklet + JSON param descriptor
2026-04-03 17:41:54 +01:00
w1n5t0n
e28c76535b docs: update CLAUDE.md with WASM engine architecture, testing, and debug probe
- Add WASM ML Engine section documenting the dual-instance architecture
  (main thread for inference, worker for training), all C bindings, and
  the Dataset/computeWeights JS layer
- Add Debug Probe section documenting window.__nisps API
- Add Testing section with Playwright e2e test infrastructure
- Update output modes (now 4: visual, synth, MIDI CC, audio canvas)
- Document known issue: double loss scaling (meml-ues)
- Note batch inference support in input-heatmap.js
2026-04-03 17:40:12 +01:00
w1n5t0n
70d807cdce feat(playground): EOC module base class + chain container (meml-vx2)
Adds playground/js/eoc/ with EOCModule base class, EOCChain container,
and barrel index. Implements bypass GainNode graph, connect/rewire
algorithm, ordered module management, flat NISPS param routing, and
eoc:change CustomEvent dispatch on structural changes.
2026-04-03 17:38:22 +01:00
w1n5t0n
7cbda2304b feat(playground): dynamic engine label in output mode toggle (meml-i40) 2026-04-03 17:34: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
2d20a8c256 fix(playground): 3 bugs found during bug hunt
- input-pipeline.js: guard circular clamp against div-by-zero when
  input is exactly at center (0.5, 0.5) — dist=0 produced NaN
- snapshot-stack.js: jumpTo() used slice(0, index) which excluded the
  target snapshot; fixed to slice(0, index + 1)
- a-app.js: _lastSentParams not resized when MLP output count changes
  during mode switch, causing stale throttle state and potential param
  flood on first frame after switch
2026-04-03 17:30:26 +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
c69b46f8cc feat(playground/nisps): warm-start weight transfer on MLP resize (meml-8jc)
Add WasmIML.extractWeights() and WasmIML.createWithWarmStart() to preserve
learned joystick mappings across output-count changes; resizeMLP() now
transfers hidden-layer weights and shared output nodes instead of cold-starting.
2026-04-03 17:27:54 +01:00
w1n5t0n
2d2cabe159 Merge branch 'worktree-agent-a1105760' into worktree-agent-acf82828 2026-04-03 17:23:04 +01:00
w1n5t0n
f81f0cc51d test(e2e): add 11 tests for new WASM API methods
Cover inferBatch, evalLoss, getLayerStats, lossHistory, and
moveWeights pin mask — verifying correctness, bounds, and
weight preservation semantics.
2026-04-03 17:22:13 +01:00
w1n5t0n
1a3672c3f5 Merge branch 'worktree-agent-ac53e53a' into worktree-agent-a1105760 2026-04-03 17:18:48 +01:00
w1n5t0n
0fc2d94049 feat(playground): batch inference support in input heatmap
InputHeatmap.update() now accepts options.inferBatchFn to evaluate
all grid points (plus the divergence center point) in a single WASM
call instead of 256 separate round-trips. The per-point inferFn path
is preserved as a fallback when inferBatchFn is not provided.
2026-04-03 17:18:01 +01:00
w1n5t0n
c646872e42 feat(playground): wire WasmIML methods into debug probe
Add evalLoss, inferBatch, and getLayerStats to the window.__nisps
debug probe so Playwright tests and dev console can access the new
WasmIML capabilities. InputHeatmap (Phase 3) is not yet wired into
a-app.js, so batch inference heatmap integration is deferred.
2026-04-03 17:17:59 +01:00