Commit graph

144 commits

Author SHA1 Message Date
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
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
f225c7c2a6 feat(wasm): add batch inference, extended training, pin mask, eval loss, and layer stats bindings
Five new C functions for the WASM module:
- nisps_mlp_infer_batch: N-point batch inference in a single call
- nisps_mlp_train_ex: training with per-iteration loss history output
- nisps_mlp_move_weights_ex: moveWeights with output pin mask to skip pinned nodes
- nisps_mlp_eval_loss: compute MSE loss without updating weights
- nisps_mlp_get_layer_stats: per-layer weight magnitude, dead, and saturation stats
2026-04-03 17:11:49 +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
c8d7779699 feat(nisps-core,wasm): add per-sample weights to MLP training
Add optional sample_weights parameter to MLP::Train() and the WASM
nisps_mlp_train binding. When provided, weights replace the uniform
1/N scaling per sample — enabling recency, spatial, or any custom
importance weighting without changing the training interface.
2026-04-02 20:35:24 +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
w1n5t0n
73eeaac0cc feat(playground): implement Phase 1 control surface — compound axes, input pipeline, enhanced joy-map
Three new standalone ES modules + integration into a-app.js:

- input-pipeline.js: 5-stage processing (deadzone → zoom → curve → smoothing →
  momentum-as-zoom), 3 anchor modes, zoom-at-zero freeze, per-axis overrides
- control-surface.js: compound axes (Boldness/Memory/Precision) with interpolation
  tables, offset-based override resolution (trim-pot model), 6 built-in presets
- control-surface-ui.js: floating bar axis sliders, gear icon settings drawer with
  Input/Training/Exploration/Output sections, log-scale sliders, override dots
- joy-map-enhanced.js: zoom minimap with adaptive grid (4×4→32×32), vanishing trail
  with Catmull-Rom spline + tap-to-return, dual concentric noise rings, frozen overlay

Integration fixes from fresh-eyes review:
- getCurrentInputs()/setCurrentInputs() use cached pipeline coords (not raw)
- CSS noise ring hidden when canvas version active (no doubling)
- Input mode switch re-runs through pipeline
- Control surface state persisted to localStorage

Implements full Phase 1 of SPEC-controls.md plus bonus items from later phases
(zoom-aware feedback, control presets with override resolution, input curve/deadzone/
smoothing/momentum all wired).
2026-03-26 10:24:24 +02:00
w1n5t0n
e8b4067c91 fix(playground): improve OSC UI discoverability and mobile layout
- OSC pill: add status dot indicator (gray=off, green=connected,
  pulsing yellow=connecting), use flexbox for reliable alignment
- OSC pill: add hover state for better affordance
- Synth panel: add separator line above OSC row so it doesn't
  blend into the arpeggiator controls
- Bottom sheet: increase expanded height from 55vh to 60vh so
  OSC row isn't cut off on mobile
- Help modal: update step 3 to mention the OSC pill in the
  bottom bar as the primary connection method
2026-03-25 12:01:12 +02: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
43e066484b docs(playground): add comprehensive control surface spec
Design spec for input zoom, compound control axes, pinning system,
exploration noise controls, and visualization enhancements. Covers
the full parameter inventory with implementation phases and open
design questions for experimentation.
2026-03-24 21:38:33 +02:00
w1n5t0n
0246ca58ab fix(playground): restore play drawer and preset button fixes
Re-apply fixes from ddb6b77 that were lost in the arpeggiator restore:
- play-btn: align-self: flex-start (pin to top)
- Play drawer: trigger on .play-btn:hover instead of parent :hover
  (prevents preset select from opening the drawer)
- preset-select: align-self: flex-start (pin to top)
2026-03-24 12:01:40 +02:00
w1n5t0n
d0bc34d8c9 refactor(shapeseq): restore arpeggiator, gate ShapeSeq behind ?shapeseq=1
Restore the original arpeggiator as the default sequencer. ShapeSeq
code is preserved but gated behind a URL feature flag (?shapeseq=1):

- Arpeggiator import, state, and all wiring fully restored
- ShapeSeq modules loaded via dynamic import() only when flag is on
- ensureShapeSeqInit() creates engine/viz/UI lazily on first audio start
- animate loop routes inputs to ShapeSeq only when flag + playing
- setOutputMode shows/hides ShapeSeq container only when flag is on
- HTML: ShapeSeq container added but hidden by default
- CSS: ShapeSeq styles added (inert when container is hidden)

All 13 ShapeSeq modules in playground/js/shapeseq/ are untouched.
To test ShapeSeq: add ?shapeseq=1 to the URL.
2026-03-24 11:54:13 +02:00
w1n5t0n
d40711eb77 fix(shapeseq): fix 6 bugs found in fresh-eyes review
1. Pitch pipeline: IntervalLock stores midiNote/127, sequencer now
   converts back via pitch*127. Removed pitch RangeMap from projection
   presets (was double-mapping already-quantized values).

2. Clock step duration: changed from quarter-note grid (60/bpm) to
   16th-note grid (60/bpm/4). 8 steps at 120 BPM now = 1 second.

3. Method name: drawWeights → randomiseWeights (matching WasmIML API).

4. Dirty-check: setSequenceInputs now skips re-evaluation when inputs
   haven't changed (epsilon 1e-5). Avoids 60fps chain evaluation.

5. SwingGroove: swingGrid param now functional — selects 8th note
   (period 2), 16th note (period 4), or triplet (period 3) swing grid.

6. IntervalLock base octave: starts from C3 (MIDI 48) instead of C0,
   so default output is in a playable range.
2026-03-24 11:42:25 +02:00
w1n5t0n
505ed26f21 feat(shapeseq): integrate ShapeSeq into synth mode, replace arpeggiator
- Remove Arpeggiator import and all arp references from a-app.js
- Add ShapeSeq imports (ShapeSeqEngine, StepVisualizer, ChainBuilderUI,
  getDefaultBus) and lazy initialization on first audio start
- In synth mode: show ShapeSeq container (circular viz + chain builder),
  hide flow-field particles. Visual mode unchanged.
- Route joystick or hand tracking inputs to shapeSeq.setSequenceInputs()
  each frame; render step viz at 60fps
- Add HTML: #shapeseq-container with #shapeseq-viz canvas and
  #shapeseq-chain wrapper, sequencer transport controls (play/stop,
  tempo, step count)
- Add CSS: full-screen flex layout, viz takes upper 45%, chain builder
  scrollable below, hidden by default
- Quick play button wires to ShapeSeq start/stop instead of arp

Closes meml-6ry. This completes ShapeSeq Phase 1.
2026-03-24 02:17:32 +02:00
w1n5t0n
027510a7e4 feat(shapeseq): implement Layer 3 — chain builder UI and sequencer core
chain-ui.js (1002 lines): Vertical stack chain builder (pedalboard-
style) with expandable primitive cards, category-colored borders,
drag-to-reorder via pointer events, add primitive palette modal,
NISPS live-param pulsing indicator, generator combine mode toggle.
All styles inline matching dark glass theme.

sequencer.js (304 lines): ShapeSeqEngine orchestrator wiring the
full pipeline: input routing → sequence MLP (16 outputs) → param
mapping → chain evaluation → projection → clock scheduling → C15
bridge. Default chain: Euclidean→ProbGate→PitchWalker→IntervalLock→
VelocityShaper. Tracks active notes to prevent orphaned noteOns.
2026-03-24 02:10:04 +02:00
w1n5t0n
efcf6d2c06 feat(shapeseq): implement Layer 2 — all 8 primitives and chain runner
primitives.js (505 lines): All 8 sequencing primitives extending
Primitive base class:
- EuclideanRhythm: Bjorklund-distributed trigger patterns
- ProbabilityGate: PRNG-based trigger filtering + accent assignment
- PitchWalker: stateful constrained random walk with gravity
- Ratchet: probabilistic step subdivision (1-4x)
- SwingGroove: alternating-step timing offsets (max triplet feel)
- DensityMorph: trigger placement with clustering control
- IntervalLock: 11-scale pitch quantizer (chromatic→diminished)
- VelocityShaper: 5 curve types with depth/phase control
Includes PRIMITIVE_REGISTRY for chain builder UI.

chain.js (319 lines): Sequential pipeline chain evaluator.
Buckets primitives by category (generators→processors→converters→
timing), forks deterministic PRNG per primitive, merges multiple
generators via additive (OR) or multiplicative (AND) mode
(configurable in real time). Flat param distribution by chain
position. Full state serialization for freeze support.
2026-03-24 02:06:17 +02:00
w1n5t0n
c8015da35a feat(shapeseq): implement Layer 1 modules
Four modules that build on the Layer 0 foundation:

- primitive.js: base class with category system (generator/processor/
  timing/converter), param schema validation, boundary enforcement
  (clamp/wrap/scaled), symbolic process() interface, state management
  for freeze, and applyBoundary() utility for delta control
- clock.js: AudioContext lookahead scheduling (25ms interval, 100ms
  window) replacing setTimeout arpeggiator. Reads pattern descriptions
  for per-step swing (timeOffset) and ratchet (subdivisions). Emits
  seq.noteOn/noteOff/step/loopStart via event bus.
- projection.js: composable transform chain with 5 transforms
  (VelocityCurve, GateThreshold, RangeMap, OctaveFolder, StutterMap)
  and 3 presets (expressive, percussive, fullRange). Pitch quantization
  deliberately excluded (handled by Interval Lock primitive).
- step-viz.js: Canvas2D circular step visualizer with even angular
  spacing for any step count. Pitch→radius, velocity→node size,
  accent→color. 60fps-friendly with pre-allocated coordinate buffers.
  Tap interaction for step toggling.
2026-03-24 02:02:01 +02:00
w1n5t0n
e3b94d644f feat(shapeseq): implement Layer 0 foundation modules
Five independent modules with no interdependencies:

- event-bus.js: namespaced pub/sub (seq.*/ml.*/ui.*) with wildcard
  subscriptions and automatic AudioContext/performance.now timestamps
- prng.js: seedable mulberry32 PRNG with pure-functional API, fork()
  for independent per-primitive streams, serializable state for freeze
- pattern.js: symbolic pattern description data structure with
  create/clone/merge/validate, additive and multiplicative merge modes
- param-map.js: maps fixed 16-output MLP to variable-count primitive
  params via linear interpolation, with optional per-param min/max scaling
- seq-iml.js: factory for second WasmIML instance (2 inputs, 16 outputs,
  [16,16,16] hidden layers) following existing imlJoy/imlHand pattern

All modules are ES modules with no build step. PRNG, pattern, and
param-map are port-ready (explicit state, typed arrays, no closures).
2026-03-24 01:57:59 +02:00
w1n5t0n
ddb6b77abb fix(playground): prevent play drawer from triggering on preset hover, pin controls to top
- Change drawer hover trigger from .synth-quick-controls:hover to
  .play-btn:hover ~ so hovering preset selector no longer opens it
- Add .play-drawer:hover to keep drawer open when mouse moves to it
- Set align-self: flex-start on play-btn and preset-select to prevent
  vertical layout shift when drawer appears
2026-03-24 01:54:09 +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
4fd0b39d26 docs(playground): revise ShapeSeq spec after fresh-eyes review
Major revisions addressing architectural gaps:
- Fix data flow: clock drives everything (top of diagram)
- Add fixed MLP + param mapping layer to handle dynamic chain sizing
- Introduce symbolic chain evaluation (pattern descriptions, not concrete values)
- Categorize primitives: generator, processor, timing modifier, converter
- Generator combination modes (additive/multiplicative, user-configurable)
- Swing and Ratchet are now timing modifiers on the pattern description
- Remove Scale Quantizer from projection layer (Interval Lock is the sole quantizer)
- Clarify mode integration: ShapeSeq replaces particle viz in synth mode
- Build on existing imlJoy/imlHand dual-instance pattern
- Add Open Design Questions section for deferred decisions
- Port-ready applies to primitive layer only, not orchestration
2026-03-24 00:30:43 +02:00
w1n5t0n
1a0f2c4d1c docs(playground): add ShapeSeq generative sequencing system spec
Comprehensive spec for ShapeSeq — a NISPS-driven generative sequencing
system that replaces the placeholder arpeggiator. Covers 8 composable
algorithmic primitives, configurable chain modes, delta control with
freeze/re-expose workflow, namespaced event bus, AudioContext-based
precise clock, circular step visualization, and phased implementation
plan.
2026-03-23 23:42:34 +02:00
w1n5t0n
5a4924728f feat(playground): add MediaPipe hand tracking input with dev panel
Hand tracking via webcam as alternative to joystick input:
- Right hand tracks 14 derived features (palm XY, finger curls, spread,
  roll, pitch, pinch) through a separate 14-input MLP
- Left hand gesture recognition (1 finger = thumbs up, 2 = thumbs down)
  with 400ms debounce hold
- Split-zone PIP display (no camera feed, skeleton only) with dashed
  divider and cross-zone dimming
- Dual IML architecture: independent imlJoy (2 inputs) and imlHand
  (14 inputs) with pointer swap, preserving training data per mode
- Dev panel (?devmode=true): draggable/collapsible floating panel with
  sliders for MediaPipe confidence thresholds, smoothing, gesture hold
  time, world landmarks toggle, and live feature bar monitor
- Visual presets always route to joystick IML (prevents dimension mismatch)
- Race condition guard on input mode switching
2026-03-23 23:28:58 +02:00
w1n5t0n
f8983c4806 feat(playground): replace JS ML engine with WASM nisps-core
Compile nisps-core C++ MLP to WASM (36KB) and use it as the ML engine
in the playground, replacing the JavaScript port for inference, training,
and weight manipulation.

- Add extern "C" WASM bindings with spread-aware drawWeights/moveWeights
- WasmIML class is a drop-in replacement for the JS IML
- Inference runs on main thread via WASM (fast, synchronous)
- Training runs in a Web Worker with its own WASM instance (non-blocking)
- Interactive training (thumbs-up, train button) no longer freezes UI/audio
- Preset loading and state restore still use sync training
2026-03-23 23:23:07 +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
4e32919494 fix(playground): correct SYNTH_SECTIONS alignment and bidirectional slider sync
SVF count was 7 (actual 9), Gap Filter section was missing (6 params),
FB Mix count was 10 (actual 9) — causing cascading mislabeling of all
synth sections from SVF onwards. Also sync volume/tempo changes from
bottom sheet back to quick-play controls.
2026-03-22 23:59:08 +02:00
w1n5t0n
baa5d5016d docs(playground): add synth controls and MIDI/arpeggiator info to help modal 2026-03-22 22:47:55 +02:00
w1n5t0n
33580f0ae0 feat(playground): add help/intro modal shown on first visit
- Glass-style modal explains NISPS, how learning works, and what to expect
- Documents all controls: touch, keyboard, and gamepad bindings
- Shows automatically on first visit (localStorage flag)
- Dismissible via close button, "Got it", overlay click, or Escape
- Reopenable via ? button at bottom right
2026-03-22 22:41:49 +02:00
w1n5t0n
de75a3f0e4 feat(playground): expand gamepad support with face buttons and Steam Deck fixes
- Map A=Train, B=Clear Examples, X=Randomize, LB=Thumbs Down, RB=Thumbs Up
- Add periodic gamepad polling fallback for environments where
  gamepadconnected event doesn't fire reliably (Steam Deck, some Linux browsers)
- Show "Press any gamepad button to connect" hint on page load
2026-03-22 22:40:15 +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
21832e0099 fix(playground): prevent 1/2 keys being swallowed by focused sliders
When synth controls (volume, tempo, etc.) have focus after dragging,
keyboard shortcuts 1/2 for thumbs down/up were intercepted by the
range input. Now all four UIs skip shortcut handling when an input,
select, or textarea element has focus.
2026-03-22 11:52:30 +02:00
w1n5t0n
a0adb8b14b fix(playground): move Visual/Synth toggle into bottom toolbar in immersive UI
Relocated the output mode toggle from a floating top-right position into
the bottom floating bar where it's more discoverable. Removed the
duplicate toggle from the expanded sheet. Compact pill-toggle-sm style
fits the toolbar layout.
2026-03-22 11:07:38 +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
d3797945d2 tweak(playground): simplify descriptions in card body text 2026-03-22 02:22:47 +02:00
w1n5t0n
6b6881da7d fix(playground): fix RL button position on desktop and add floating output toggle
- Fix RL buttons stuck high on desktop: media query was overriding
  bottom to 196px, now matches mobile 92px
- Add floating Visual/Synth toggle at top-right of immersive view,
  synced with the existing sheet toggle
2026-03-22 02:11:31 +02:00
w1n5t0n
3db90b035a feat(playground): add spread param for sigmoid saturation control and fix randomise sync
Add ?spread=0-1 URL param that controls weight initialization scaling,
RL noise scaling per layer, noise cap, and weight decay to prevent
sigmoid output saturation. At spread=0 (original behavior) weights are
uniform [-1,1] and outputs polarise near 0/1. At spread=1 weights use
Xavier scaling (1/sqrt(fan_in)), noise is proportionally reduced, and
10% weight decay per thumbs-down prevents unbounded magnitude drift.

Also fix randomise to re-inject current joystick position and re-run
inference before routing outputs, eliminating the jump on first
joystick move after randomise.

Defaults: tame=1, spread=0.6 across all app variants.
2026-03-22 02:10:26 +02:00
w1n5t0n
d9be808aaa style(playground): unify dark theme with orange accents and improve RL button layout
- Replace fragmented green/blue/orange accent scheme with unified hacker
  orange (#ff6a00) across all CSS, JS canvas drawing, and param palettes
- Redesign designs.html: monospace font, clickable cards, grid background,
  remove A/B/C labels; copy to index.html as landing page
- Add JetBrains Mono font across all views for consistent techy aesthetic
- Sharpen border-radius (pills 20px→6px, back buttons→6px) for angular feel
- Restructure RL buttons: key numbers inside buttons (larger, white),
  teal-green for thumbs-up, dark red-orange for thumbs-down
- Fix RL button z-index (25→45) so they render above the bottom sheet
- Add Xavier-scaled weight randomization (spread parameter)
2026-03-22 01:36:00 +02:00
w1n5t0n
b80a9e4e86 fix(playground): resize canvas when switching output modes in Design B (meml-2c3)
When switching from synth back to visual mode, the FlowFieldVisualizer
canvas had stale dimensions from being hidden. Add requestAnimationFrame
resize call after toggling canvas visibility so the visualizer picks up
correct dimensions and renders properly.
2026-03-22 01:07:52 +02:00
w1n5t0n
a669a54543 feat(playground): add three alternative UI design explorations
Three redesigns of the playground UI, each exploring a different
interaction philosophy for the NISPS interactive ML engine:

A) Immersive — fullscreen canvas with floating glassmorphism overlays
B) Workbench — dashboard with 2D mapping heatmap as centerpiece
C) Journey — phased UI that dissolves (Explore → Teach → Perform)

All share the same 126-output MLP ([32,48,64] hidden layers), C15
synth bridge, and flow field visualizer. Each adds:
- SynthVisualizer (126 grouped vertical bars) replacing particles in synth mode
- RL as default learning mode with keyboard shortcuts (1/2)
- Follow mode for trackpad exploration
- ?tame= URL param for safe-range constraining
- localStorage persistence (random on first boot)
- Live iframe previews on designs.html homepage

See devlog/2026-03-21-ui-redesign-explorations.md and meml-2c3 for
remaining refinements identified during browser testing.
2026-03-22 00:57:58 +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
c3577be42c docs: update CLAUDE.md and playground README for 126-param C15 map
Document the expanded parameter map, MLP architecture, exclusion
rationale table, and synth mode in both project-level and playground docs.
2026-03-21 23:11:45 +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
w1n5t0n
843f80c54a chore: add playground serve script 2026-03-04 20:47:06 +00:00
w1n5t0n
2c2c9f769d playground: add expandable visual mode and docs 2026-02-14 08:47:47 +00:00
w1n5t0n
3039221165 playground: add loss history tracking and diagnostics panel 2026-02-11 15:56:24 +00:00
w1n5t0n
96a88f4b0f playground: add follow-mode joystick and RL hotkeys 2026-02-11 15:56:19 +00:00
w1n5t0n
6187a4b4ae playground: add orbiting repulsor field controls 2026-02-11 15:50:06 +00:00
w1n5t0n
0498bbe371 playground: add inertia and drag velocity memory 2026-02-11 15:48:34 +00:00
w1n5t0n
06833cdd19 playground: add blended advection modes 2026-02-11 15:47:13 +00:00
w1n5t0n
97acfd2824 playground: add particle lifetime and respawn modes 2026-02-11 15:45:50 +00:00
w1n5t0n
0550088516 playground: add attractor and dispersion controls 2026-02-11 15:44:33 +00:00
monkey-w1n5t0n
57aae34870 feat: add web-based interactive playground for NISPS
Faithful JS port of nisps-core MLP + IML engine with a touch-friendly
UI for exploring neural parameter mapping visually. Two learning modes:
example-based (set slider targets) and RL feedback (thumbs up/down with
exploration noise). Flow field particle system controlled by 8 MLP outputs.
2026-02-11 13:17:17 +01:00