Commit graph

12 commits

Author SHA1 Message Date
monkey-w1n5t0n
c986377b4c feat(firmware): reposition gesture — relocate an existing example to a new input
Add a "grab → move → drop" gesture that moves an existing positive
example's output to a new input position, preserving the output. This is
the new core's home for upstream InterfaceRL's drag-store/reposition-commit,
distinct from Explore→Place (which places newly-auditioned scratchpad sounds).

- nisps/ml/feedback.hpp: begin_reposition()/commit_reposition()/repositioning().
  Reuses the Placing state (static_output holds the carried vector) but a
  reposition_ flag makes commit AND the mode-switch teardown SKIP the weight
  restore — the real net is never set aside here, so restoring snapshot_ would
  clobber the live trained net. Guards cancel_place + abort_explore_place.
- firmware glue: state-gate Toggle B. Exploring → reroll/nudge (unchanged);
  Idle → MomB1 grab, MomB2 drop (commit + add_example + train). The 4D variant
  has no joystick button, so the gesture lives on the momentary toggle. Also
  fix a stale top-of-file control-map comment that contradicted the bindings.
- tests: 4 reposition cases (hold without snapshot; commit stores carried
  output with no restore; mode-switch aborts without clobber; begin-only-Idle).
- MAP.md: document the full ExploreAndPlace lifecycle + reposition wiring.

Audio-hold (carrying the sound audibly during the move) remains the existing
unwired static_output() TODO and affects Explore→Place identically.
Firmware compile unverified (no arduino-cli); host tests + lint pass.
2026-06-28 23:45:27 +02:00
monkey-w1n5t0n
c45aff4411 Merge remote-tracking branch 'origin/main' into workshop/synth-fw-audit
# Conflicts:
#	nisps/modes/base.hpp
2026-06-28 22:29:41 +02:00
monkey-w1n5t0n
4e60d0192a feat(slp-workshop): new MEMLCelium-based mode + port Jolt & OU-noise RL learning
New SLP-Workshop firmware variant (Synth Library Portland), built on the
MEMLCelium engine + MLP shape. Ports the two post-fork learning-algorithm
changes from upstream memllib InterfaceRL into the shared nisps/ml core,
runtime-configurable (no compile-time switch), inert by default:

- nisps/ml/jolt.hpp: Jolt — held continuous weight morph over the flat
  weight buffer + post-release LR ramp (kJolt* constants verbatim).
- nisps/ml/ou_noise.hpp: OUNoise<N> — Ornstein-Uhlenbeck exploration walk
  on the output vector (theta=0.02, dt=0.001, kMaxAmplitude=0.65).

Both wired into ModeBase so every mode gains jolt_press/jolt_release/
jolt_lr_scale + set_explore_intensity; gated so existing modes stay
bit-identical (parity + golden tests green). Firmware surfaces them on
TogB1 (Jolt) and RVX1 (explore). New SLPWorkshopMode mode + schema +
codegen; firmware alias + .ino variant; playground mode registration.

Tests: jolt + OU unit tests, ModeBase learning integration incl. an
inert-parity test proving SLP-Workshop == MEMLCelium with features off.
Verified: cpp tests, wasm build, native↔wasm parity, lint, codegen
golden, playground typecheck. Firmware compile/e2e/hardware are
environment-bound (no arduino-cli/submodules/browser here).

Refs ergo 019f0fca.
2026-06-28 22:15:36 +02:00
monkey-w1n5t0n
2f9cfd1de2 feat(modes): ModeBase input-pin API for single/dual joystick
Platform-agnostic neutral-pin mechanism (set_input_pinned/pin_value) on
ModeBase, lifted from ebb953a. Consumed by the firmware Joystick Dual/Single
settings menu; the browser fixed-MLP<4> half of ebb953a is superseded by main's
MLP<32> N-D input work and intentionally dropped.
2026-06-28 21:17:51 +02:00
monkey-w1n5t0n
9e59eb04ce feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.

Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
  (enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
  (every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
  add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.

Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
  X randomise / Y nudge / B undo / A-hold reposition); mirror composed
  position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
  device picker + batch-learn flow, learned-control meters).

Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
  active axis gets a dedicated slot, unused slots held at 0 (inert).
  Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
  arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
  whole vector via spine.reprocess().

Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
  matches FMA-free WASM (training amplified the gap past 1e-5).

Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:48 +02:00
monkey-w1n5t0n
22efb1c411 feat(nisps/ml): crystallise Explore-and-place into shared FeedbackController
Add FeedbackMode::ExploreAndPlace + Idle/Exploring/Placing state machine
(no-heap, deterministic nisps::Rng): enter/exit_explore, reroll, nudge, undo,
begin_place, commit_place, cancel_place + on_down/on_up browser policy. Wire
the nisps_ml_feedback_* C API + EXPORTED_FUNCTIONS, extend parity Stage 5d.
Fix set_mode(3) falling back to Avoid. Native ctest 4/4; parity native==WASM
within 1e-5 (max delta 2.4e-7). Rebuilt nisps.{js,wasm}.
2026-06-28 04:14:12 +02:00
w1n5t0n
3138701100 Stream 11: verification infrastructure (golden vectors + parity + lint + CI + Playwright migration)
Agent ran out of API credits before committing. Files staged + committed by orchestrator. Coverage:
- tests/cpp/ml_golden_vectors.cpp: fixed-seed regression tests for MLP determinism
- tests/cpp/engine_impulse.cpp: white-noise impulse responses with binary baseline
- tests/cpp/parity_check.cpp + parity_wasm.mjs + parity_diff.mjs: native vs WASM bit-equivalence
- scripts/build-cpp-tests.sh, lint-cpp.sh, parity-check.sh, run-all-tests.sh
- playground/tests/e2e/{ml-engine,modes,persistence,ui-interactions}.spec.ts (+helpers)
- .github/workflows/ci.yml

(meml-x06)
2026-04-29 19:50:55 +03:00
w1n5t0n
4964037da0 test(nisps/modes): host C++ tests for stream 4 mode layer (meml-beb)
Adds `nisps_modes_tests` executable to nisps/CMakeLists.txt with four TUs:

  - `test_mode_concepts.cpp`: 8 `static_assert(Mode<...>)` (concept
    satisfaction), plus runtime metadata sanity for each mode (mode_id,
    input_channel_count, schema sizes match engine param_count).

  - `test_mode_paf_synth.cpp`: end-to-end exercise — setup, set_input,
    tick_control, process audio. Verifies idle process is finite, output
    bounds [0,1] hold, note_on triggers nonzero audio, input clamping,
    and engine/ml accessors round-trip.

  - `test_mode_voice_space.cpp`: voice-space round trip for PAFSynth,
    ChannelStrip and VerbFX (all dispatched modes). Confirms
    out-of-range index is silently ignored.

  - `test_mode_breakor_events.cpp`: sequencer event pumping. BreakOr
    emits Clock + NoteOn/Off, Elysiamorf emits CC, SoundAnalysisMIDI
    converts 8 ML outputs → 8 ControlEvents (CC 0..7) per tick, ring
    buffer overflow drops cleanly.

Total: 22 new tests (110 across nisps/), all passing under -Werror
-Wpedantic. Build remains clean.
2026-04-29 16:27:03 +03:00
w1n5t0n
8d0d47b992 feat(nisps/engines): port firmware audio engines to AudioEngine concept (meml-1v6)
Concept-based, no virtual dispatch, per-engine voice spaces as inline
methods. Each engine satisfies nisps::AudioEngine via static_assert.

- NoOpEngine: silent passthrough; used for sequencer-only modes and
  for the SoundAnalysisMIDI mode's audio path.
- PAFSynthEngine (33 params, 7 voice spaces): 4-voice PAF synth with
  detune cascade, ring-mod, sine-shaper, ADSR, feedback delay. note_on/
  note_off interface for MIDI keyboard.
- ChannelStripEngine (24 params, 6 voice spaces): stereo console strip
  (pre-gain/HPF/LPF/2x peak/low-shelf/high-shelf/comp/post-gain). Voice
  spaces: WannabeNeve66, SSL4K, SSL9K, MaleVox, FemaleVox, Neve80
  (stepped-frequency).
- XIASRIEngine (24 params, "Direct" voice space): pitch-shift + 6 allpass
  + 2 comb + 4 delays. Direct NN→param mapping per firmware semantics.
- VerbFXEngine (47 params, 12 voice spaces): 8-band SVF filterbank +
  3-lane dynamic delay + 8-lpcomb/4-allpass Freeverb-style tail with
  cross-fades. All 12 voice spaces ported from voicespaces/VerbFX/*.hpp.
- MEMLCeliumEngine (56 params): 2-track ratio sequencer + dual-voice
  PAF synth (7+7+22+20 layout). Sequencer triggers V0/V1 ADSR.
- BreakOrEngine (56 params): 8-track ratio sequencer; emits NoteOn/
  NoteOff/Clock events via pop_events(span). process() returns silence.
- ElysiamorfEngine (40 params): 8-track FM-pair sequencer; emits CC
  events on CCs {1,2,3,4,5,9,11,12}. Silent audio path.
- AnalysisEngine (0 params, 6 features): port of XiasriAnalysis (pitch
  via zero-crossing, aperiodicity via MAD, log-domain energy + attack
  derivative + brightness ratio). Inputs to ML on SoundAnalysisMIDI mode.

All param_count() values match schemas/modes/*.json output_size.
4074 LOC total. CMake adds nisps_dsp_engine_tests target with 38
passing tests under -Wall -Wextra -Werror -Wpedantic.
2026-04-29 16:09:12 +03:00
w1n5t0n
973455b158 feat(nisps/dsp): lean DSP primitives ported from maximilian (meml-1v6)
Header-only, heap-free, sample-rate-aware DSP primitives for stream 3:
- Biquad (LPF/HPF/BPF/Notch/Peak/LowShelf/HighShelf, denormal flush)
- Delay<N> + DynamicDelay<N> (fixed-length feedback + power-of-two
  ring with fractional read & smoothed offset)
- AllPass / Comb / LpComb (Schroeder-Moorer reverb sections, split per
  role rather than maximilian's one-class-many-roles maxiReverbFilters)
- DCBlocker (one-pole HPF)
- ChamberlinSVF + OnePoleSmoother<NCh> + EnvelopeFollower
- ADSR envelope generator
- SineOsc/SawOsc/SquareOsc, PAFOperator (port of maxiPAFOperator with
  static gauss/cauchy tables), FMOp single-operator FM building block
- PitchShifter<N> granular two-head crossfade (replaces daisysp PitchShifter)

Tests: biquad freq-domain attenuation, delay tap timing, reverb
boundedness, pitch-shifter ratio + finite output. All pass under
-Wall -Wextra -Werror -Wpedantic, C++20.
2026-04-29 16:08:49 +03:00
w1n5t0n
825ed6ad33 feat(nisps/ml): MLP library with fixed-architecture template + spread-aware RL (meml-wmh)
Stream 2 of the clean-slate rewrite: nisps/ml/ replaces src/memlp/ with a
header-only, heap-free MLP that satisfies nisps::core::MLEngine.

Files (nisps/ml/):
- activations.hpp — ReLU (leaky 0.01 for parity), sigmoid, tanh
- loss.hpp — MSE per-sample (fixes meml-ues double-scaling: returns the
  sample's MSE without an extra 1/N multiplication; the training loop
  averages explicitly)
- init.hpp — uniform/Xavier/spread-aware weight init
- training.hpp — gradient clip helper (±10.0 matches legacy)
- rl.hpp — move_weights with per-layer Xavier scaling, weight decay
  (10% * spread), gaussian noise via the deterministic Rng (matches the
  legacy JS sum-of-three-uniforms shape); draw_weights also spread-aware
- stats.hpp — per-layer mean/max/dead/saturating diagnostics
- mlp.hpp — 4-layer (3 hidden + sigmoid output) MLP class with
  std::array-backed weights, biases, gradient accumulators, dataset
  ring buffer (default 128 examples), loss history (default 4096 iters).
  Bias is a separate per-layer parameter — no input-vector mutation.
  Flat get_weights/set_weights layout: weights all layers (row-major,
  layer order), then biases all layers.

Tests (tests/cpp/, all 50 passing under -Wall -Wextra -Werror -Wpedantic):
- test_mlp_init.cpp — deterministic seeding, spread regimes,
  static_assert MLEngine concept satisfied
- test_mlp_inference.cpp — golden hand-computed forward pass match,
  sigmoid output range, set_input bounds
- test_mlp_training.cpp — XOR convergence (loss < 0.01 in <2k iters),
  ring-buffer eviction
- test_mlp_loss.cpp — meml-ues regression test: reported loss equals
  hand-computed average MSE without extra 1/N scaling; sample weights
  honoured
- test_mlp_rl.cpp — move_weights respects output_pin_mask (final-layer
  rows + biases preserved); spread regimes; grad clear after draw_weights
- test_mlp_serialize.cpp — get_weights/set_weights round-trip preserves
  inference exactly; eval_loss is non-mutating; infer_batch matches
  individual inference

Verification:
- Clean build, no warnings
- 50 tests pass (22 prior + 28 new)
- No std::vector / new / malloc in nisps/ml/
- All float literals .f-suffixed in code (comments excepted)
2026-04-29 15:55:43 +03:00
w1n5t0n
e5bf2aa055 feat: nisps build + host test harness
CMakeLists.txt:
- Native host build by default; Emscripten-target detection plumbed but
  WASM emit deferred to stream 7 (playground build script).
- Header-only INTERFACE library `nisps_core`.
- Host test executable `nisps_core_tests` compiled with -Wall -Wextra
  -Werror -Wpedantic (Chris's rules: clean build is non-negotiable).

tests/cpp/test_helpers.hpp:
- Minimal NISPS_TEST / NISPS_EXPECT / NISPS_EXPECT_NEAR macros, no external
  deps. Rationale documented in-file: Catch2/doctest would add ~10MB and 30s
  for what is currently <100 LOC of test runtime.

22 unit tests covering FixedBuffer (5), RingBuffer (5), Rng (7), math (5).
All green; verified via `cmake --build nisps/build && ./nisps/build/nisps_core_tests`.
2026-04-29 15:22:01 +03:00