From 3952c65d755f683a0e97d778dd57c81be11e86ab Mon Sep 17 00:00:00 2001 From: monkey-w1n5t0n Date: Sun, 28 Jun 2026 21:02:23 +0200 Subject: [PATCH] feat(firmware): on-device Joystick Dual/Single settings menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add glue/settings_view.hpp: wire_settings(mode) registers a "Joystick" SingleSelectView on the MEMLNaut display carousel (TFT + rotary encoder, same DisplayDriver as SystemView/SelfTest). For the 4-input two-joystick modes, "Single" pins ML input channels 2,3 (the second joystick) to neutral via ModeBase::set_input_pinned — the network is never rebuilt and trained state survives toggling. Default is Dual. Only registered when input arity == 4. Called from MEMLNaut-NISPS.ino setup() after addSystemInfoView(). MAP.md + CLAUDE.md glue listings updated. NOTE: compile-unverified — no arduino-cli/RP2350 toolchain on this host and no hardware; needs scripts/build-firmware.sh + a flash test (chokepoint A). --- CLAUDE.md | 3 +- MAP.md | 1 + firmware/MEMLNaut-NISPS/MEMLNaut-NISPS.ino | 3 + .../MEMLNaut-NISPS/glue/settings_view.hpp | 64 +++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 firmware/MEMLNaut-NISPS/glue/settings_view.hpp diff --git a/CLAUDE.md b/CLAUDE.md index 7fb1284..edb7553 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,7 +58,8 @@ firmware/MEMLNaut-NISPS/ │ ├── midi_io.hpp # MIDI in → mode handlers; drain ControlEvent ring → MIDI UART │ ├── mode_select.hpp # type aliases firmware mode name → nisps::modes::*Mode │ ├── input_router.hpp # wire_inputs() entry point -│ └── output_router.hpp # drain_outputs() entry point +│ ├── output_router.hpp # drain_outputs() entry point +│ └── settings_view.hpp # wire_settings(): TFT/rotary menu (Joystick Dual/Single for 4-in modes) └── src/{memllib,daisysp,nisps} # symlinks (Arduino-CLI sketch tree convention) ``` diff --git a/MAP.md b/MAP.md index fb0b999..6a3b5ea 100644 --- a/MAP.md +++ b/MAP.md @@ -23,6 +23,7 @@ MEMLNaut-NISPS — Neural Interactive Shaping of Parameter Spaces. One C++20 cod - `mode_select.hpp` — type aliases mapping firmware mode identifiers to `nisps::modes::*Mode` C++ types. Build script rewrites the active line. Includes the six `MEMLNautModeExtSynth*` external-synth variants (one per device template in `nisps/midi`, e.g. `MEMLNautModeExtSynthSub37`). Also defines the `MEMLNautModeSelfTest` pseudo-variant (tag type) + the `NISPS_ST_*`/`NISPS_ST_CAT` token-paste macros the `.ino` uses to compute `NISPS_SELFTEST`. Note: `src/nisps/` exposes each referenced top-level nisps subdir as a symlink — `midi` was added alongside `core/dsp/engines/ml/modes`. - `selftest.hpp` — standalone guided hardware self-test rig (`SelfTest` variant; no engine/ML). Step-driven state machine on a `SelfTestView`: TFT prompts the operator through every control, auto-advances on detection, encoder-press skips. Ends with optional L/R/BOTH sine-sweep headphone check (core 1 block callback) + MIDI loopback-cable test. Lives firmware-side (touches TFT + raw pins) so it stays out of platform-agnostic `nisps/`. - `input_router.hpp`, `output_router.hpp` — top-level `wire_inputs()` / `drain_outputs()` entry points. + - `settings_view.hpp` — `wire_settings(mode)`: adds on-device settings views to the MEMLNaut display carousel (TFT + rotary encoder). Joystick Dual/Single toggle for the 4-input ("two 2-D joystick") modes — "Single" pins ML input channels 2,3 to neutral via `ModeBase::set_input_pinned` (no net rebuild). Registered in the `.ino` after `addSystemInfoView()`. - `firmware/MEMLNaut-NISPS/src/{memllib,daisysp,nisps}` — symlinks (Arduino-CLI requires sketch-tree includes; preprocessor refuses `..` in headers). - `firmware/README.md` — structure + build instructions. diff --git a/firmware/MEMLNaut-NISPS/MEMLNaut-NISPS.ino b/firmware/MEMLNaut-NISPS/MEMLNaut-NISPS.ino index 5115790..c5ebbcf 100644 --- a/firmware/MEMLNaut-NISPS/MEMLNaut-NISPS.ino +++ b/firmware/MEMLNaut-NISPS/MEMLNaut-NISPS.ino @@ -31,6 +31,7 @@ #include "glue/midi_io.hpp" #include "glue/mode_select.hpp" #include "glue/output_router.hpp" +#include "glue/settings_view.hpp" // ---- Mode selection ---- // Build script rewrites which line is uncommented. @@ -141,6 +142,8 @@ void setup() { } MEMLNaut::Instance()->addSystemInfoView(); + // Settings menu (e.g. Joystick: Dual/Single for the 4-input modes). + nisps_firmware::wire_settings(g_mode); Serial.println("Finished initialising core 0."); } diff --git a/firmware/MEMLNaut-NISPS/glue/settings_view.hpp b/firmware/MEMLNaut-NISPS/glue/settings_view.hpp new file mode 100644 index 0000000..9c5ab29 --- /dev/null +++ b/firmware/MEMLNaut-NISPS/glue/settings_view.hpp @@ -0,0 +1,64 @@ +// glue/settings_view.hpp — on-device settings menu views (TFT + rotary encoder). +// +// Adds entries to the MEMLNaut display carousel (the same DisplayDriver the +// SystemView and SelfTest use). Navigation: rotate to move between views; +// press to focus a view; rotate-while-focused to change its value; press +// again to unfocus. See src/memllib/.../display/DisplayDriver.hpp. +// +// Currently provides: +// * Joystick: Dual / Single — for the 4-input ("two 2-D joystick") modes, +// toggles whether the SECOND joystick (input channels 2,3) feeds the ML +// engine. "Single" pins those channels to the neutral value (0.5) via the +// platform-agnostic ModeBase pin mechanism, so the network is never +// rebuilt and trained state survives toggling. Default is Dual (the 4-D +// variant naturally uses both joysticks). +// +// Only registered for modes whose input arity is exactly 4 (the two-joystick +// modes); other modes (e.g. SoundAnalysisMIDI, whose joystick channels are not +// 2,3) get no joystick toggle. + +#pragma once + +#include +#include +#include +#include + +#include "../src/memllib/hardware/memlnaut/MEMLNaut.hpp" +#include "../src/memllib/hardware/memlnaut/display/SingleSelectView.hpp" + +namespace nisps_firmware { + +// The second 2-D joystick maps to ML input channels 2 and 3 (joy_z / joy_w). +inline constexpr std::size_t kSecondJoystickCh0 = 2u; +inline constexpr std::size_t kSecondJoystickCh1 = 3u; + +// Add the settings views for `mode` to the display carousel. Must run AFTER +// MEMLNaut::Initialize() (so the display + TFT exist) and after +// addSystemInfoView(), on core 0. +template +inline void wire_settings(ModeT& mode) { + if constexpr (ModeT::input_channel_count() == 4u) { + MEMLNaut* meml = MEMLNaut::Instance(); + if (!meml || !meml->disp) return; + + // Kept alive for the program lifetime (the carousel holds a shared_ptr + // too, but this guarantees ownership regardless of view churn). + static std::shared_ptr joystick_view; + joystick_view = std::make_shared("Joystick"); + meml->disp->AddView(joystick_view); // calls Setup() → creates the selector + + // Index 0 = Dual (default, no pinning); index 1 = Single. + std::array options = { String("Dual"), String("Single") }; + joystick_view->setOptions(std::span(options.data(), options.size())); + joystick_view->setNewVoiceCallback([&mode](std::size_t idx) { + const bool single = (idx == 1u); + mode.set_input_pinned(kSecondJoystickCh0, single); + mode.set_input_pinned(kSecondJoystickCh1, single); + }); + } else { + (void)mode; + } +} + +} // namespace nisps_firmware