Commit graph

86 commits

Author SHA1 Message Date
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
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
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
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
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
w1n5t0n
7f90b4323d feat(playground/wasm): wire 5 new WASM functions into JS WasmIML + worker
Expose inferBatch, trainEx, moveWeightsEx, evalLoss, and getLayerStats
from the WASM binary into the JavaScript layer:

- inferBatch: batch inference for heatmap sampling
- trainEx: replaces train() with per-iteration loss history capture
- moveWeightsEx: native output pin mask support (removes save/restore hack)
- evalLoss: compute loss without updating weights
- getLayerStats: per-layer weight statistics (meanAbs, maxAbs, dead/sat fracs)

Worker also upgraded to trainEx, returning full lossHistory in payload.
2026-04-03 17:15:38 +01:00
w1n5t0n
44fc974425 feat(tests): add Playwright e2e suite + debug probe for a-immersive
- Add window.__nisps debug probe (gated on ?debug=1) exposing iml state,
  getOutputs/getLoss/getWeights/getExampleCount, and action triggers
  (thumbsUp/thumbsDown/train/randomise/clearExamples/saveState)
- Fix WasmIML bug: this.dataset was a plain object; import Dataset and
  use new Dataset(100) so computeWeights() is available for training
- Fix WasmIML.addExample/clearDataset to use Dataset API methods
- 44 Playwright e2e tests across 4 spec files:
  - ml-engine.spec.js: WASM inference bounds, training loss, thumbs
    up/down behavior, async training, example capture semantics
  - ui-interactions.spec.js: drawer open/close, mode switching,
    heatmap bar counts, preset chips, keyboard shortcuts (1/2/Z)
  - input-pipeline.spec.js: input→output variation, clamping, joystick
    drag, post-training output bounds across the full input space
  - persistence.spec.js: URL params (?preset, ?spread), localStorage
    round-trip, saveState probe
2026-04-03 16:38:04 +01:00
w1n5t0n
b5b90a623d feat(playground): audio-canvas mode — NISPS-driven generative sampler
New output mode in a-immersive with 4 submodes:
- Remix: per-clip volume/loop-start/filter, joystick shapes mix
- Granular: grain density/pitch/position/size per clip
- Drone: ultra-slow playback (0.005–0.1×), spectral texture
- Slicer: beat-synced 8-step slice sequencer, NISPS picks clip/position/speed

Canvas features:
- Infinite pan/zoom canvas (pointer + pinch + wheel)
- Drag-and-drop audio files (MP3/WAV/FLAC/OGG/M4A)
- Grid + overflow layout (3×3 cells, expands automatically)
- Waveform cells with overview + detail view (tap to toggle)
- Loop region highlight, live level metering, per-cell glow feedback
- Beat sync toggle (free vs. BPM-locked) with configurable BPM
- 36 NISPS outputs: vol×9, pitch×9, loop×9, filter×9
2026-04-02 22:12:18 +01:00
w1n5t0n
0a112f2d84 feat(playground): MIDI CC device presets + bug fixes
Add MIDI CC preset system for bundled device configurations:
- midi-cc-presets.js: listPresets/loadPreset/loadPresetFromFile API
- presets/polybrute.json: Arturia PolyBrute CC map
- Preset selector in quick controls bar and MIDI CC drawer
- File import button for loading JSON presets from disk

Bug fixes in a-app.js:
- Destroy IML instances before resizeMLP() to free WASM memory
- Fix randomiseWeights() call (was drawWeights())
- Fix thumbs-up to store rawParamValues instead of post-pipeline outputs
2026-04-02 20:35:38 +01:00
w1n5t0n
aa0ffcfd32 feat(playground/nisps): recency & spatial weighted training in JS ML engine
Add Dataset.computeWeights() with three modes:
- global: exponential recency decay (newest examples weighted higher)
- local: spatial suppression of older examples near the current input
- combined: both applied together

IML and WasmIML now compute weights on every train() call using the
active mode. Exposes recencyBias, weightingMode, localRadius properties.
WASM worker path passes sampleWeights through to C++ via the new binding.
2026-04-02 20:35:31 +01:00
w1n5t0n
456c426cb1 hello come back to me 2026-03-30 17:05:45 +03:00
w1n5t0n
ba0fcab2a2 feat(vcv,playground): complete Phases 8, 9, 10 — all phases done
Phase 8 — Companion webapp bridge:
- NISPS-FORMAT.md: full .nisps JSON schema with validation rules
- Webapp iml.js: exportState() / importState() with bias handling
- osc_server.hpp: minimal UDP OSC server (cross-platform, no deps)
- VCV module: OSC toggle + port selection in right-click menu
- osc-client.js: WebSocket client with auto-reconnect
- Bridge scripts updated for bidirectional VCV↔webapp relay

Phase 9 — Panel layout variants:
- MEMLNaut.svg: 30HP standard panel (matches widget positions)
- MEMLNaut-wide.svg: 44HP with expanded display and 8 input slots
- MEMLNaut-expander.svg: 8HP with 6 extra inputs and LINK LED

Phase 10 — Polish & distribution:
- README.md: 267-line user guide (install, quick start, RL workflow,
  presets, OSC, technical details)
- BUILDING.md: build prerequisites, SDK setup, local install
- Makefile.dist: platform-stamped zip packaging
- SPEC.md: performance characteristics (1060 MADs/pass, ~46KB/instance)
- SPEC.md: v1 compatibility assessment (v2-only recommended)
2026-03-28 01:48:44 +02:00
w1n5t0n
1f21494dee feat(playground): implement Phases 2-4 of control surface spec
Phase 2 — Pinning + History:
- snapshot-stack.js: ring buffer (20 max) with auto-snapshot on
  train/randomize/thumbs-down, multi-level undo, tagged entries
- ab-compare.js: A/B weight state comparison with capture/toggle/accept/revert
- region-pin.js: pin rectangular input-space regions (Approach A: example
  pinning), pinned examples always included in training
- param-pin.js: per-output pin flags, pin mask skips pinned nodes in moveWeights
- phase2-ui.js: undo button with history popup, A/B toggle, long-press region
  pin, double-tap param pin
- Modified mlp.js/iml.js/nisps-wasm.js to accept outputPinMask in moveWeights

Phase 3 — Input Refinement + Exploration:
- pressure-feedback.js: touch force + hold duration → intensity multiplier
- auto-explore.js: automated thumbs-down at configurable interval, zoom-scaled
- input-heatmap.js: 16×16 MLP sampling, 3 color modes (luminance/variance/
  divergence), zoom-aware resampling, offscreen canvas rendering
- phase3-ui.js: auto-explore toggle with progress ring, heatmap eye icon,
  pressure indicators, settings drawer section
- joy-map-enhanced.js: added setHeatmap() for background layer rendering

Phase 4 — Output Pipeline + Visualization + Polish:
- output-pipeline.js: global curve → smoothing → slew rate → freeze gate
- weight-health.js: weight magnitude histogram, dead/saturating/healthy status
- gradient-flow.js: per-layer weight-delta analysis, vanishing/exploding detection
- session-presets.js: save/load full state, URL sharing via compact params
- phase4-ui.js: freeze button, network health panel, session preset UI

All phases merged into a-app.js with proper integration: auto-snapshots,
pressure-modulated RL, heatmap triggers, output pipeline in routeOutputs,
gradient capture around training, persistence for all new state.
2026-03-26 10:48:12 +02:00