Commit graph

378 commits

Author SHA1 Message Date
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
chriskiefer
c633b9c2ab PAF 2026-03-29 19:53:09 +01:00
chriskiefer
bae80fbd89 voice spaces 2026-03-29 19:09:10 +01:00
chriskiefer
041b79c202 delay xfades 2026-03-29 17:31:43 +01:00
chriskiefer
27a1e4e5b0 wet dry override 2026-03-29 17:07:07 +01:00
chriskiefer
d5bb77d03e font and refactor 2026-03-29 16:48:47 +01:00
chriskiefer
8be4f85cb6 refactor and block select font size option 2026-03-29 16:45:53 +01:00
w1n5t0n
2060f10b40 fix(vcv): resolve 6 critical thread safety issues from code review
Fixes from Opus 4.6 review (C1-C6, I1, I3, I4, I8):

C1: static lastOutputs → per-instance lastOutputsForDelta member
C2: add_example() now on audio thread only (owns iml); worker reads
    from mutex-protected staging area (stagedFeatures/stagedLabels)
C3: Worker reads stagedWeightsForWorker (not iml.get_weights()),
    eliminating concurrent read/write on iml's MLP
C4: Worker spins on weightsPending before writing pendingWeights,
    preventing double-write race
C5: RAND and CLEAR now enqueue Randomize/Clear jobs through worker
    instead of directly mutating iml on the audio thread
C6: OSC callbacks stage JSON into oscStagedJson + atomic flag;
    audio thread applies in process() (no recv-thread mutation)

Also fixed:
- I4: Separate pendingJob field (enqueueJob no longer overwrites currentJob)
- I8: Removed redundant swapReady atomic
- noiseLevel, cachedNovelty, cachedConfidence now std::atomic<float>
- Worker syncs examples back to iml after training via load_examples()
2026-03-28 02:02:11 +02:00
w1n5t0n
c7964fd454 fix(vcv): implement proper double-buffered threading
Replace direct-mutation threading with shadow IML:
- Background thread clones weights from main → shadow IML
- Training and perturbation operate only on shadow instance
- New weights staged in pendingWeights, swapped atomically by audio thread
- Thumbs-down now enqueues Perturb job instead of calling move_weights directly
- Audio thread applies new weights via iml.set_weights() at safe point
- Examples copied to shadow for training, results copied back as weights only

Threading invariant now fully enforced: background thread never writes to
the inference IML. Audio thread applies staged weights between inference calls.
2026-03-28 01:55:30 +02: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
74c52fadc7 feat(nisps-core,vcv): complete Phases 6 + 7 — persistence, derived outputs
Phase 6 — State persistence:
- Full state serialization: version, weights (3D), examples (features+labels),
  mlpConfig, noiseLevel, slewMs, output/input ranges
- Validation on load: version check, graceful missing field handling
- .nisps preset save/load via right-click menu (osdialog file dialogs)
- Param values included in preset files

Phase 7 — Derived outputs:
- Mean, STD, delta computed on audio thread (trivial cost)
- Novelty/confidence: nearest_example_distance() computed on background
  thread after each training/perturbation job, cached for audio thread
- Defaults with 0 examples: novelty=10V, confidence=0V

nisps-core IML additions:
- get_weights() / set_weights() for MLP weight serialization
- get_example_features/labels() / load_examples() for dataset serialization
- nearest_example_distance() for novelty/confidence metric
- get_example_count() / get_max_examples() for UI display
2026-03-28 01:27:19 +02:00
w1n5t0n
6a76f15736 feat(vcv): complete Phases 3, 4, 5 — RL feedback, display, configurability
Phase 3 — RL feedback system:
- Background worker thread with job queue, condition variable, atomic flags
- Thumbs up/down buttons + CV trigger inputs (Schmitt triggers)
- Learn enable toggle + gate input (OR logic)
- Noise level tracking (decay on +, increase on −, spread-dependent cap)
- Post-change output crossfade (configurable slew, default 10ms)
- Rapid feedback queueing with coalescing (max depth 1)
- Graceful thread shutdown (shouldStop flag, joins in destructor)

Phase 4 — Visual feedback:
- NanoVG bar graph display (12 hue-coded bars, noise level, TRAIN indicator)
- 12 output level LEDs, LEARN LED (green), training LED (yellow)

Phase 5 — Configurability:
- RATE knob: exponential decimation from block-rate to audio-rate
- Per-output range: unipolar (0-10V) / bipolar (±5V) via context menu
- Per-input range: unipolar / bipolar via context menu
- 12 attenuverter trimpots (-1 to +1)
- SPREAD CV input for knob modulation
- CLEAR button with 1-second long-press guard
- Output slew configurable via context menu (0-100ms)
- State serialization (ranges, noise, slew) via dataToJson/dataFromJson

Note: double-buffering uses direct IML access (not shadow copy) pending
IML weight get/set API (filed as meml-ft7).
2026-03-28 01:04:07 +02:00
w1n5t0n
d2f6ffc65e test(vcv): add smoke test harness — 7/7 pass
Standalone test exercising IML inference pipeline without VCV runtime:
- Basic inference: all 12 outputs valid in [0,1]
- Input responsiveness: 12/12 outputs change between corners
- Randomize: weights change produces different output mapping
- Spread parameter: measurably different distributions at 0 vs 1
- Expressiveness: 6/6 corner pairs distinct, full range utilized
- Smoothness: 100% of sweep steps are smooth (no binary jumps)

Finding: spread sigmoid saturation effect is architecture-dependent
with small [16,24,16] network (filed as meml-l5a for investigation).
2026-03-28 00:50:36 +02:00
w1n5t0n
e52b800a92 feat(vcv): complete Phase 2 — core engine wired up
- IML<float> with [16,24,16] hidden layers as module member
- CV inputs read, clamped 0-10V, normalized to [0,1]
- MLP inference in process(), 12 outputs scaled to 0-10V
- SPREAD knob (0-1, default 0.6) controls weight initialization
- RAND button randomizes weights using current spread value
- Panel: knob + button + 2 inputs + 12 outputs in 2x6 grid
2026-03-28 00:38:58 +02:00
w1n5t0n
d0ba1faaea feat(nisps-core,vcv): complete Phase 0 + Phase 1
Phase 0 — spread-aware API ported to nisps-core C++:
- MLP::DrawWeightsSpread(T spread) — interpolate uniform↔Xavier per layer
- MLP::MoveWeightsSpread(T speed, T spread) — per-layer noise + weight decay
- IML::randomise_weights(Float spread) and IML::move_weights(speed, spread)
- 5 unit tests (10/10 total pass)

Phase 1 — VCV Rack 2 plugin skeleton:
- Makefile with C++20, nisps-core include path
- plugin.json manifest
- Empty MEMLNaut module: 2 inputs, 12 outputs, placeholder SVG panel
- static_assert verifies nisps-core headers resolve
- C++20 confirmed working in VCV SDK (8 existing plugins use it)
2026-03-28 00:36:46 +02:00
w1n5t0n
9040886e16 docs(vcv): third review — input ranges, CV modulation, lifecycle, smoke test
- Add per-input range configuration (unipolar/bipolar) for LFO vs envelope compat
- Add SPREAD CV input for automated exploration/precision control
- Add background thread graceful shutdown (shouldStop flag + join timeout)
- Document multi-instance behavior (per-instance threads, ~80KB each)
- Add integration smoke test milestone after Phase 2 (go/no-go gate)
- Note OSC library dependency for Phase 8
- Fix 30HP layout: acknowledge density, defer validation to Phase 9
2026-03-28 00:22:43 +02:00
w1n5t0n
b4340c7f34 docs(vcv): fix 7 issues from second fresh-eyes review
1. Rename derived output SPREAD → STD to avoid collision with SPREAD knob
2. Route thumbs-down through background thread (was mutating inference
   MLP directly — data race). Establish single-writer threading invariant.
3. Add rapid feedback queueing with coalescing (max queue depth 1)
4. Document slew-on-slew interaction (decimation + crossfade compose cleanly)
5. Cut compact (20HP) panel — physically impossible to fit 22 jacks
6. Define derived output defaults with 0 examples (NOVELTY=10V, CONFIDENCE=0V)
7. Initial random output is intentional (shows liveness, gives RL starting point)
2026-03-27 23:39:56 +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
9dfa1429d0 docs(vcv): address spec gaps from fresh-eyes review
- Fix licensing: nisps-core is MPL-2.0, VCV SDK is GPLv3
- Fix VCV Rack 2 description: Community Edition is free, Pro is paid
- Add Phase 0: port spread-aware drawWeights/moveWeights to nisps-core C++
- Detail threading model: two MLP instances, atomic swap flag, memory cost
- Add configurable output slew (default 10ms) for post-training crossfade
- Add input signal handling: mono channel 0, hard-clamp out-of-range CV
- Add .nisps file format version field for forward compatibility
- Document dataset capacity (100 max, FIFO forgetting)
- Specify novelty/confidence computation strategy (training thread, cached grid)
- Add open questions: novelty grid scaling, C++20 toolchain, expander protocol
2026-03-25 12:25:21 +02:00
w1n5t0n
412782ed0e docs(vcv): add VCV Rack module specification
Full spec for MEMLNaut VCV Rack module based on interview:
- 2-8 configurable CV inputs, 12 raw + 5 derived CV outputs
- RL feedback via panel buttons + CV triggers with learn gate guard
- nisps-core C++ engine with background thread training
- User-configurable inference rate (block to audio rate)
- Bidirectional state transfer with companion webapp (file + OSC)
- 10-phase development plan from skeleton to distribution
2026-03-25 12:17: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