memlnaut-nisps/firmware/MEMLNaut-NISPS/glue/peripherals.hpp

240 lines
12 KiB
C++
Raw Normal View History

Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
// firmware/glue/peripherals.hpp — Joystick, buttons, toggles → mode input
// channels.
//
// The MEMLNaut hardware exposes:
// - 3 analog joystick axes (X, Y, Z) via `setJoyXCallback` / `Y` / `Z`,
// each delivering a float in [0, 1].
// - Up to 5 rotary pots (RVGain1, RVZ1, RVY1, RVX1, ADC3) — same callback
// shape.
// - 4 momentary buttons (MomA1/2, MomB1/2) and 5 toggles (TogA1/2, TogB1/2,
// JoySW). Buttons fire `void()` on press; toggles fire `void(bool)` on
// edge.
//
// The `Mode` concept's input channels are abstract floats in [0, 1]. Each
// concrete mode's schema names its channels (e.g. paf_synth: joy_x, joy_y,
// joy_z, joy_w). The glue maps the *first N* analog inputs onto channels
// `[0, N)` — that's the trivial mapping that matches every concrete mode's
// schema today (joystick first, optional 4D extra pot for joy_w).
//
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:44:57 +02:00
// Buttons/toggles drive the SHARED ExploreAndPlace lifecycle. The authoritative
// per-control mapping is documented at the binding site below; in summary:
// MomA1 (TA up) : enter / exit explore (toggle)
// MomA2 (TA down) : like — freeze current scratchpad output (begin place)
// MomB1 (MA up) : Exploring → reroll scratchpad; Idle → grab (reposition)
// MomB2 (MA down) : Exploring → nudge scratchpad; Repositioning → drop+train
// TogB1 : Jolt — held continuous weight morph (up=morph, down=freeze)
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:44:57 +02:00
// TogB2 : commit place (rising-edge) — store +1 example + train
// RVX1 : exploration amount (Ornstein-Uhlenbeck output walk)
Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
//
// The button block below wires the SHARED ExploreAndPlace lifecycle
// (nisps/ml/feedback.hpp, the same core the browser drives via WASM) to the
// hardware buttons. See the per-button mapping at the binding site.
//
// FOLLOW-UP (needs a firmware build to verify — no arduino-cli here): while the
// controller is PLACING, the audition should hold feedback.static_output()
// (the frozen vector) instead of running fresh inference. That requires a hook
// in the control path (tick_control / audio_driver) to consult
// feedback.static_output() before ml().process() — not yet wired here because
// the controller lives in this translation unit. Wiring the buttons is the
// deliverable; the audition-hold is a small follow-up.
Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
#pragma once
#include <Arduino.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include "../src/nisps/core/perf.hpp"
#include "../src/nisps/ml/feedback.hpp"
Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
#include "../src/memllib/hardware/memlnaut/MEMLNaut.hpp"
namespace nisps_firmware {
// Salt matching nisps/wasm/bindings.cpp so the firmware FeedbackController's
// per-instance Rng stream is independent of (and reproducible against) the
// MLP's inference/move RNG — same discipline as the browser.
inline constexpr std::uint64_t kFeedbackSalt = 0xFEEDBACC0DEull;
// Firmware undo-ring depth for ExploreAndPlace (rl-feedback-design §2.2: WASM
// D=4, firmware D=2 — SRAM budget).
inline constexpr std::size_t kFirmwareUndoDepth = 2u;
Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
// Number of analog input channels we forward to the mode. Modes with fewer
// inputs ignore the surplus (set_input(idx, ...) silently rejects out-of-
// range idx in ModeBase).
inline constexpr std::size_t kAnalogInputCount = 4u; // X, Y, Z, plus one pot
// A small POD that the .ino owns. Captured by lambda below.
template <typename Mode>
struct PeripheralBindings {
Mode* mode = nullptr;
};
// Wire all hardware → mode bindings. Must be called *after*
// MEMLNaut::Initialize() (so MEMLNaut::Instance() is valid). The Mode
// reference must outlive the program (it's a static in the .ino).
template <typename Mode>
inline void bind_peripherals(Mode& mode) {
auto* meml = MEMLNaut::Instance();
if (!meml) return;
// ---- Analog inputs → mode input channels ----
meml->setJoyXCallback([&mode](float v) { mode.set_input(0u, v); });
meml->setJoyYCallback([&mode](float v) { mode.set_input(1u, v); });
meml->setJoyZCallback([&mode](float v) { mode.set_input(2u, v); });
// Fourth analog input: use ADC3 (the spare). Modes that don't expose a
// joy_w channel just don't see it (set_input drops out-of-range idx).
meml->setADC3Callback([&mode](float v) { mode.set_input(3u, v); });
// ---- Rotary pots ----
// Reuse RVGain1 as a "tempo" knob for sequencer modes. The mode is free
// to ignore via the same channel-bounds drop. RVGain1 is also used by
// InterfaceRL legacy as an audio output volume knob — for now we hand
// it to the ML interface for non-sequencer modes by setting the output
// master volume directly via AudioDriver.
meml->setRVGain1Callback([](float v) {
AudioDriver::SetMasterVolume(v);
});
// RVX1 → exploration amount. Drives the Ornstein-Uhlenbeck random walk
// added to the mode's output vector (nisps/ml/ou_noise.hpp, ported from
// upstream InterfaceRL). 0 = off (inert); turning it up makes the sound
// slowly roam so likes/dislikes can steer the net. Available on every
// mode via ModeBase::set_explore_intensity.
meml->setRVX1Callback([&mode](float v) {
mode.set_explore_intensity(v);
});
// ---- Buttons / toggles → ExploreAndPlace lifecycle (SHARED C++ core) ----
//
// The same nisps::ml::FeedbackController that runs in the browser (via
// WASM) drives the Idle → Exploring → Placing → Idle state machine here.
// The HARDWARE button mapping differs from the browser's on_down/on_up
// default policy — firmware maps its buttons to the GRANULAR core methods:
//
// MomA1 (TA up) : down — enter explore (Idle→Exploring) /
// exit explore (Exploring→Idle) TOGGLE
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:44:57 +02:00
// MomB1 (MA up) : STATE-GATED — Exploring: reroll the scratchpad;
// Idle: GRAB (begin reposition — freeze the
// current trained-net output to carry it).
// MomB2 (MA down) : STATE-GATED — Exploring: nudge (small perturbation);
// Repositioning: DROP (commit the carried output
// at the current joystick input + train).
// MomA2 (TA down) : like — begin place (Exploring→Placing, freeze output)
// TogB2 (rising) : up — commit place (Placing→Idle) at the CURRENT
// joystick input, then store the +1 example and
// train (caller owns training). Outside Placing,
// a plain train().
//
// The controller is a function-local static so it lives for the whole
// program (like the mode). It is seeded off the same salt as the browser.
using FB = nisps::ml::FeedbackController<typename Mode::ML, kFirmwareUndoDepth>;
static FB feedback(static_cast<std::uint64_t>(0xC0FFEEu) ^ kFeedbackSalt);
feedback.set_mode(nisps::ml::FeedbackMode::ExploreAndPlace, mode.ml());
const float spread = mode.param_schema().default_spread;
// MomA1: enter/exit explore toggle.
Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
meml->setMomA1Callback([&mode]() {
if (feedback.explore_state() == nisps::ml::ExploreState::Idle) {
feedback.enter_explore(mode.ml(), mode.param_schema().default_spread);
} else {
feedback.exit_explore(mode.ml());
}
Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
});
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:44:57 +02:00
// MomB1: STATE-GATED.
// Exploring → reroll the scratchpad (a fresh random sound).
// Idle → GRAB: begin a reposition. Freezes the output currently heard
// from the trained net so the user can carry it to a new input
// location (the 4D variant has no joystick button, so the
// grab/drop gesture lives on this momentary toggle). The real
// net is NOT set aside — only the heard output is frozen.
Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
meml->setMomB1Callback([&mode]() {
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:44:57 +02:00
switch (feedback.explore_state()) {
case nisps::ml::ExploreState::Exploring:
feedback.reroll(mode.ml(), mode.param_schema().default_spread);
break;
case nisps::ml::ExploreState::Idle:
feedback.begin_reposition(mode.ml()); // process + capture + hold
break;
case nisps::ml::ExploreState::Placing:
break; // already holding — ignore
}
Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
});
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:44:57 +02:00
// MomB2: STATE-GATED.
// Exploring → nudge the scratchpad (small bounded perturbation).
// Repositioning → DROP: commit the carried output at the CURRENT joystick
// input, store the +1 example (new input → carried output)
// and warm-start train. This is the "move an existing
// positive example to a new position" gesture.
Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
meml->setMomB2Callback([&mode]() {
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:44:57 +02:00
if (feedback.explore_state() == nisps::ml::ExploreState::Exploring) {
feedback.nudge(mode.ml(), 0.05f);
return;
}
if (feedback.repositioning()) {
// Capture the current joystick input — the DESTINATION position.
const auto in = mode.input_channels();
std::array<float, Mode::ML::kInput> features{};
for (std::size_t i = 0; i < Mode::ML::kInput; ++i) {
features[i] = (i < in.size()) ? in[i] : 0.f;
}
feedback.commit_reposition(); // no weight restore (net never set aside)
const auto label = feedback.committed_output(); // the carried output
if (!label.empty()) {
mode.ml().add_example(
std::span<const float>(features.data(), Mode::ML::kInput), label);
(void)mode.ml().train();
}
}
});
// MomA2: like → begin place. Freezes the current scratchpad output so the
// user can move the joystick to choose WHERE to place it.
meml->setMomA2Callback([&mode]() {
feedback.begin_place(mode.ml());
Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
});
// TogB1: Jolt — held continuous weight morph (nisps/ml/jolt.hpp, ported
// from upstream InterfaceRL). Flip up to start morphing a scatter of
// weights live; flip down to freeze them (the change is permanent) and
// let the learning rate ramp gently back in. A toggle (not a momentary)
// because Jolt is a HELD gesture and momentary buttons only fire on
// press. Available on every mode via ModeBase::jolt_press/jolt_release.
meml->setTogB1Callback([&mode](bool state) {
if (state) mode.jolt_press();
else mode.jolt_release();
});
// TogB2 (rising): up → commit place at the current joystick input, then
// store the +1 example (input → placed output) and train. Outside Placing,
// just train (legacy behaviour).
Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
meml->setTogB2Callback([&mode](bool state) {
if (!state) return;
if (feedback.placing()) {
// Capture the current joystick input BEFORE the restore.
const auto in = mode.input_channels();
std::array<float, Mode::ML::kInput> features{};
for (std::size_t i = 0; i < Mode::ML::kInput; ++i) {
features[i] = (i < in.size()) ? in[i] : 0.f;
}
feedback.commit_place(mode.ml()); // restores the real net
// CALLER owns training: add the +1 example then warm-start train.
const auto label = feedback.committed_output(); // valid post-commit
if (!label.empty()) {
mode.ml().add_example(
std::span<const float>(features.data(), Mode::ML::kInput), label);
(void)mode.ml().train();
}
} else {
Stream 6: extract firmware glue under firmware/ Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the hardware (memllib) to the platform-agnostic nisps/ library through a slim glue layer. Delete the legacy root-level *AudioApp.hpp, modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis, and the src/memlp submodule. Glue layout (firmware/MEMLNaut-NISPS/glue/): audio_driver.hpp - bridge memllib block callback to Mode::process via per-Mode templated trampoline (no virtual dispatch) peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing, drains mode ControlEvent ring -> MIDI UART mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to nisps::modes::*Mode (build script rewrites the #define MEMLNAUT_MODE_TYPE line) input_router.hpp / output_router.hpp - top-level wire/drain entry points The sketch tree uses src/{memllib,daisysp,nisps} symlinks because Arduino-CLI rejects ".." in include paths from sketch-tree headers. mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before including nisps headers (some nisps engines use those identifiers as method names). The audio bridge struct is extern in the header and defined in the .ino because inline + __not_in_flash section attribute collide at link time. Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip, and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3, -std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries, 110+ tests). Build script (scripts/build-firmware.sh) updated to point at the new sketch path; mode-rewrite logic unchanged. Closes meml-gkm.
2026-04-29 16:05:38 +02:00
(void)mode.ml().train();
}
});
}
} // namespace nisps_firmware