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/MEMLNaut-NISPS.ino — Thin entry point.
|
|
|
|
|
//
|
|
|
|
|
// The heavy lifting now lives under:
|
|
|
|
|
// - nisps/... — platform-agnostic ML, DSP, engines, modes
|
|
|
|
|
// - firmware/glue/... — hardware bindings (audio driver, MIDI,
|
refactor(firmware): delete vendored daisysp and the input_router layer
Phase 1 group 8 (S8, ST2, L14).
- S8: the vendored src/daisysp tree (96 files, 41 .cpp compiled into every
firmware build) and its sketch-tree symlink. Zero consumers — nisps replaced
daisysp's PitchShifter with a custom granular implementation. Corrected
firmware/README.md and setup-firmware-toolchain.sh, which called it a live
submodule. The attribution comment in nisps/dsp/pitch_shift.hpp stays: it is
an honest provenance note about a port, not a dependency.
Noted while verifying: src/memllib/examples/KassiaAudioApp includes
../../daisysp/..., but examples/ is not symlinked into the sketch tree and is
never compiled by the firmware build, so the deletion stands.
- ST2: input_router.hpp was a zero-logic speculative layer with one consumer;
the .ino now calls bind_peripherals directly.
- L14: peripherals.hpp — deleted kAnalogInputCount, PeripheralBindings and the
unused spread local, and extracted the duplicated commit-and-train block into
one helper. The audit's suggested commit_and_train(mode, feedback) signature
could NOT be behaviour-identical: repositioning() implies placing() (both
live in ExploreState::Placing), so dispatching on feedback state inside the
helper would silently reroute a TogB2 press mid-reposition from commit_place
to commit_reposition, which differs (no snapshot restore, clears
reposition_). Implemented as commit_and_train(mode, feedback, bool
reposition) with the branch decided at the call site, preserving behaviour.
Firmware is not compiled by any gate yet (that arrives with the PlatformIO
migration, plan §5/S9), so this group is verified by reading and grep only.
Gates: run-all-tests.sh ALL GREEN.
2026-07-21 12:49:25 +02:00
|
|
|
// peripherals, output router)
|
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
|
|
|
//
|
|
|
|
|
// This file does only:
|
|
|
|
|
// 1. Pick a mode at compile time (`MEMLNAUT_MODE_TYPE`).
|
|
|
|
|
// 2. Instantiate it (in `AUDIO_MEM` so it lives in SRAM).
|
|
|
|
|
// 3. setup() / loop() on core 0:
|
|
|
|
|
// - boot board
|
|
|
|
|
// - bind peripherals → mode.set_input
|
|
|
|
|
// - bind MIDI in → mode.note_on/update_bpm/...
|
|
|
|
|
// - run mode.tick_control() at ML cadence (5ms)
|
|
|
|
|
// 4. setup1() / loop1() on core 1:
|
|
|
|
|
// - register the audio bridge so AudioDriver streams into mode.process
|
|
|
|
|
// - pump engine events / drain MIDI out at sub-ms cadence
|
|
|
|
|
|
|
|
|
|
// ---- Hardware ----
|
|
|
|
|
#include "src/memllib/PicoDefs.hpp"
|
|
|
|
|
#include "src/memllib/audio/AudioDriver.hpp"
|
|
|
|
|
#include "src/memllib/hardware/memlnaut/MEMLNaut.hpp"
|
|
|
|
|
#include "src/memllib/interface/MIDIInOut.hpp"
|
|
|
|
|
#include "src/memllib/utils/perf.hpp"
|
|
|
|
|
#include "hardware/structs/bus_ctrl.h"
|
|
|
|
|
|
|
|
|
|
// ---- Glue ----
|
|
|
|
|
#include "glue/audio_driver.hpp"
|
|
|
|
|
#include "glue/midi_io.hpp"
|
|
|
|
|
#include "glue/mode_select.hpp"
|
|
|
|
|
#include "glue/output_router.hpp"
|
refactor(firmware): delete vendored daisysp and the input_router layer
Phase 1 group 8 (S8, ST2, L14).
- S8: the vendored src/daisysp tree (96 files, 41 .cpp compiled into every
firmware build) and its sketch-tree symlink. Zero consumers — nisps replaced
daisysp's PitchShifter with a custom granular implementation. Corrected
firmware/README.md and setup-firmware-toolchain.sh, which called it a live
submodule. The attribution comment in nisps/dsp/pitch_shift.hpp stays: it is
an honest provenance note about a port, not a dependency.
Noted while verifying: src/memllib/examples/KassiaAudioApp includes
../../daisysp/..., but examples/ is not symlinked into the sketch tree and is
never compiled by the firmware build, so the deletion stands.
- ST2: input_router.hpp was a zero-logic speculative layer with one consumer;
the .ino now calls bind_peripherals directly.
- L14: peripherals.hpp — deleted kAnalogInputCount, PeripheralBindings and the
unused spread local, and extracted the duplicated commit-and-train block into
one helper. The audit's suggested commit_and_train(mode, feedback) signature
could NOT be behaviour-identical: repositioning() implies placing() (both
live in ExploreState::Placing), so dispatching on feedback state inside the
helper would silently reroute a TogB2 press mid-reposition from commit_place
to commit_reposition, which differs (no snapshot restore, clears
reposition_). Implemented as commit_and_train(mode, feedback, bool
reposition) with the branch decided at the call site, preserving behaviour.
Firmware is not compiled by any gate yet (that arrives with the PlatformIO
migration, plan §5/S9), so this group is verified by reading and grep only.
Gates: run-all-tests.sh ALL GREEN.
2026-07-21 12:49:25 +02:00
|
|
|
#include "glue/peripherals.hpp"
|
2026-06-28 21:02:23 +02:00
|
|
|
#include "glue/settings_view.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
|
|
|
|
|
|
|
|
// ---- Mode selection ----
|
|
|
|
|
// Build script rewrites which line is uncommented.
|
|
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeSoundAnalysisMIDI
|
|
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeXIASRI
|
|
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeVerbFX
|
|
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeBreakOr
|
|
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeElysiamorfs
|
|
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeChannelStrip
|
2026-07-13 22:17:08 +02:00
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModePAFSynth
|
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
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeMEMLCelium
|
2026-07-13 22:17:08 +02:00
|
|
|
#define MEMLNAUT_MODE_TYPE MEMLNautModeSLPWorkshop
|
feat(firmware): ExternalSynthMIDIMode + 6 device variants
Joystick -> MLP -> MIDI CC for an external hardware synth, using the
compile-time device templates from nisps/midi/generated. ExternalSynthMIDIMode
<const MidiDevice&, NOut> mirrors SoundAnalysisMIDIMode (NoOpEngine,
kRouteOutputsToEngine=false, pushes ControlChange events). A consteval
pick_cc_slots curates which NOut params fill the output slots (prefers musical
params over Bank Select/global). Adds six flashable variants
(MEMLNautModeExtSynth{Sub37,SubPhatty,Pro12,AnalogKeys,Hydrasynth,JD800}) wired
into mode_select.hpp + the .ino variant list + NISPS_ST guards, and a src/nisps/
midi symlink so the sketch tree can reach nisps/midi.
Host-compile-verified under C++20 (incl. via the sketch include path) and
lint-clean; full arduino-cli build + flash is on the hardware (no toolchain here.
2026-06-28 20:13:08 +02:00
|
|
|
// ---- External-synth MIDI-CC variants (control an external hardware synth) ----
|
|
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeExtSynthSub37
|
|
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeExtSynthSubPhatty
|
|
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeExtSynthPro12
|
|
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeExtSynthAnalogKeys
|
|
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeExtSynthHydrasynth
|
|
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeExtSynthJD800
|
2026-06-28 04:14:30 +02:00
|
|
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeSelfTest
|
|
|
|
|
|
|
|
|
|
// NISPS_SELFTEST == 1 iff the selected variant is the guided hardware self-test
|
|
|
|
|
// (see glue/mode_select.hpp). Must be computed AFTER the mode #define above so
|
|
|
|
|
// MEMLNAUT_MODE_TYPE is in scope for the token paste.
|
|
|
|
|
#define NISPS_SELFTEST NISPS_ST_CAT(MEMLNAUT_MODE_TYPE)
|
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 <memory>
|
|
|
|
|
|
2026-06-28 04:14:30 +02:00
|
|
|
// Inter-core handshake flags + stack flag — shared by BOTH the normal-mode and
|
|
|
|
|
// self-test build paths, so they live outside the fork below.
|
|
|
|
|
volatile bool APP_SRAM g_core0_ready = false;
|
|
|
|
|
volatile bool APP_SRAM g_core1_ready = false;
|
|
|
|
|
volatile bool APP_SRAM g_serial_ready = false;
|
|
|
|
|
volatile bool APP_SRAM g_iface_ready = false;
|
|
|
|
|
|
|
|
|
|
bool core1_separate_stack = true;
|
|
|
|
|
|
|
|
|
|
#if !NISPS_SELFTEST
|
|
|
|
|
// =====================================================================
|
|
|
|
|
// Normal-mode build path (an engine + ML mode runs the device).
|
|
|
|
|
// =====================================================================
|
|
|
|
|
|
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
|
|
|
using ActiveMode = MEMLNAUT_MODE_TYPE;
|
|
|
|
|
|
|
|
|
|
ActiveMode AUDIO_MEM g_mode;
|
|
|
|
|
|
|
|
|
|
// Definition of the audio bridge declared in glue/audio_driver.hpp.
|
|
|
|
|
// Lives in the audio SRAM section so the per-block callback dereferences it
|
|
|
|
|
// without paying flash latency.
|
|
|
|
|
volatile nisps_firmware::ActiveModeBridge AUDIO_MEM nisps_firmware::g_active_mode_bridge{};
|
|
|
|
|
|
|
|
|
|
// Global MIDI handle (shared across cores like the legacy entry point did).
|
|
|
|
|
std::shared_ptr<MIDIInOut> APP_SRAM g_midi;
|
|
|
|
|
|
|
|
|
|
// Audio block callback — placed in SRAM via __not_in_flash_func so the audio
|
|
|
|
|
// ISR avoids XIP latency. Forwards into the (header-inline) dispatch helper.
|
|
|
|
|
void AUDIO_FUNC(audio_block_callback)(
|
|
|
|
|
float in[][kBufferSize],
|
|
|
|
|
float out[][kBufferSize],
|
|
|
|
|
size_t n_channels,
|
|
|
|
|
size_t n_frames) {
|
|
|
|
|
nisps_firmware::dispatch_audio_block(in, out, n_channels, n_frames);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static uint32_t get_rosc_entropy_seed(int bits) {
|
|
|
|
|
uint32_t seed = 0;
|
|
|
|
|
for (int i = 0; i < bits; ++i) {
|
|
|
|
|
busy_wait_us_32(5);
|
|
|
|
|
seed <<= 1;
|
|
|
|
|
seed |= (rosc_hw->randombit & 1);
|
|
|
|
|
}
|
|
|
|
|
return seed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =====================================================================
|
|
|
|
|
// Core 0 — UI / hardware polling / ML inference
|
|
|
|
|
// =====================================================================
|
|
|
|
|
|
|
|
|
|
void setup() {
|
|
|
|
|
set_sys_clock_khz(AudioDriver::GetSysClockSpeed(), true);
|
|
|
|
|
bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS
|
|
|
|
|
| BUSCTRL_BUS_PRIORITY_DMA_R_BITS
|
|
|
|
|
| BUSCTRL_BUS_PRIORITY_PROC1_BITS;
|
|
|
|
|
|
|
|
|
|
const uint32_t seed = get_rosc_entropy_seed(32);
|
|
|
|
|
srand(seed);
|
|
|
|
|
g_mode.ml().seed(static_cast<uint64_t>(seed));
|
|
|
|
|
g_mode.ml().draw_weights(g_mode.param_schema().default_spread);
|
|
|
|
|
|
|
|
|
|
g_midi = std::make_shared<MIDIInOut>();
|
|
|
|
|
|
|
|
|
|
Serial.begin(115200);
|
|
|
|
|
Serial.println("Serial initialised.");
|
|
|
|
|
WRITE_VOLATILE(g_serial_ready, true);
|
|
|
|
|
|
|
|
|
|
MEMLNaut::Initialize();
|
|
|
|
|
pinMode(33, OUTPUT);
|
|
|
|
|
|
|
|
|
|
// Wire hardware → mode I/O channels.
|
refactor(firmware): delete vendored daisysp and the input_router layer
Phase 1 group 8 (S8, ST2, L14).
- S8: the vendored src/daisysp tree (96 files, 41 .cpp compiled into every
firmware build) and its sketch-tree symlink. Zero consumers — nisps replaced
daisysp's PitchShifter with a custom granular implementation. Corrected
firmware/README.md and setup-firmware-toolchain.sh, which called it a live
submodule. The attribution comment in nisps/dsp/pitch_shift.hpp stays: it is
an honest provenance note about a port, not a dependency.
Noted while verifying: src/memllib/examples/KassiaAudioApp includes
../../daisysp/..., but examples/ is not symlinked into the sketch tree and is
never compiled by the firmware build, so the deletion stands.
- ST2: input_router.hpp was a zero-logic speculative layer with one consumer;
the .ino now calls bind_peripherals directly.
- L14: peripherals.hpp — deleted kAnalogInputCount, PeripheralBindings and the
unused spread local, and extracted the duplicated commit-and-train block into
one helper. The audit's suggested commit_and_train(mode, feedback) signature
could NOT be behaviour-identical: repositioning() implies placing() (both
live in ExploreState::Placing), so dispatching on feedback state inside the
helper would silently reroute a TogB2 press mid-reposition from commit_place
to commit_reposition, which differs (no snapshot restore, clears
reposition_). Implemented as commit_and_train(mode, feedback, bool
reposition) with the branch decided at the call site, preserving behaviour.
Firmware is not compiled by any gate yet (that arrives with the PlatformIO
migration, plan §5/S9), so this group is verified by reading and grep only.
Gates: run-all-tests.sh ALL GREEN.
2026-07-21 12:49:25 +02:00
|
|
|
nisps_firmware::bind_peripherals(g_mode);
|
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
|
|
|
|
|
|
|
|
WRITE_VOLATILE(g_iface_ready, true);
|
|
|
|
|
Serial.println("Bound peripherals to mode.");
|
|
|
|
|
|
|
|
|
|
WRITE_VOLATILE(g_core0_ready, true);
|
|
|
|
|
while (!READ_VOLATILE(g_core1_ready)) {
|
|
|
|
|
MEMORY_BARRIER();
|
|
|
|
|
delay(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MEMLNaut::Instance()->addSystemInfoView();
|
2026-06-28 21:02:23 +02:00
|
|
|
// Settings menu (e.g. Joystick: Dual/Single for the 4-input modes).
|
|
|
|
|
nisps_firmware::wire_settings(g_mode);
|
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
|
|
|
Serial.println("Finished initialising core 0.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PERF_DECLARE(MLSTATS);
|
|
|
|
|
|
|
|
|
|
#define ML_INFERENCE_PERIOD_US 5000
|
|
|
|
|
|
|
|
|
|
void loop() {
|
|
|
|
|
PERIODIC_RUN_US({
|
|
|
|
|
PERF_BEGIN(MLSTATS);
|
|
|
|
|
g_mode.tick_control();
|
|
|
|
|
MEMLNaut::Instance()->loop();
|
|
|
|
|
PERF_END(MLSTATS);
|
|
|
|
|
}, ML_INFERENCE_PERIOD_US)
|
|
|
|
|
|
|
|
|
|
PERIODIC_RUN_US({
|
|
|
|
|
static size_t blip_counter = 0;
|
|
|
|
|
if (blip_counter++ > 10) {
|
|
|
|
|
blip_counter = 0;
|
|
|
|
|
Serial.println(".");
|
|
|
|
|
digitalWrite(33, HIGH);
|
feat(firmware): bump memllib to current upstream and dissolve the fork
The §7.5 decision was "rebase our commits onto upstream, then vendor". On
inspection there was no rebase to do, and the inventory that recommended it
(7a30da9) was wrong on two counts — both corrected in this commit.
All three fork commits touch only `examples/`. That directory is not in the
sketch symlink forest (firmware/MEMLNaut-NISPS/src/ links exactly audio,
hardware, interface, synth, utils, PicoDefs.hpp), so it is never compiled.
They existed to let the RL code build against nisps/core *while it was being
ported*, and that port is done — nisps/ml/{jolt,ou_noise,feedback,geo_push}.hpp
cite the upstream sources directly. Two of the 31 commits I flagged as
"work we are missing", `9fcd459 jolts` and `d0d8a72 noise`, are precisely the
ones already absorbed.
So the question was never rebase-or-not but which snapshot to vendor. Current
upstream, because the staleness was already costing us:
- upstream has DisplayDriver::NavigateToView; the pin does not. The SelfTest
variant called it and had been failing to compile (worked around in
b953681). It was written against a newer memllib, not against nothing.
- e291192 "l r input swap" is a hardware bug fix: the physical L/R input
sockets are wired to opposite codec ADC channels. Every mode on the old
pin sees its stereo input backwards.
Verified by building all three variants with arduino-cli:
SLPWorkshop 145348 flash (+320) 87388 RAM (+4)
PAFSynth 145300 flash (+312) 107060 RAM (+4)
SelfTest 141840 flash (+320) 12028 RAM (+4)
Exactly one compile error: the .ino used kSampleRate in a constexpr, and
upstream 1997699 made it a runtime `extern size_t` so a mode can pick its own
rate. constexpr -> const; it is a once-per-second diagnostic print. The uniform
+316 is the AudioDriver/DisplayDriver deltas — the bulky new upstream code
(GrainDelayI16, ReverbI16, ModFXI16, CCSelectView, RLView, VUMeterView,
PSRAMManager) is header-only and unreferenced, so the linker drops all of it.
.gitmodules points at upstream again: with the pin on an upstream commit, the
fork holds nothing the firmware compiles. Phase 0 pointed it at the fork only
because b37fc53 existed on no remote. The fork's feat/nisps-core-swap branch
stays pushed; nothing is destroyed.
Refs: plan §5, §7.5; ALIGNMENT Q4.
2026-07-21 17:04:10 +02:00
|
|
|
// `const`, not `constexpr`: memllib made kSampleRate a runtime
|
|
|
|
|
// `extern size_t` so a mode can pick its own rate. This is a
|
|
|
|
|
// once-per-second diagnostic print, so the divide is free.
|
|
|
|
|
const float audioHeadroomMul = 1.0f / (1000000.f * 48.0f / kSampleRate);
|
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
|
|
|
Serial.printf("ml: %d, aud: %d, q: %f\n",
|
|
|
|
|
PERF_GET_MEAN(MLSTATS),
|
|
|
|
|
AUDIOLOOP_MEAN,
|
|
|
|
|
AUDIOLOOP_MEAN * audioHeadroomMul);
|
|
|
|
|
} else {
|
|
|
|
|
digitalWrite(33, LOW);
|
|
|
|
|
}
|
|
|
|
|
}, 100000)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =====================================================================
|
|
|
|
|
// Core 1 — real-time audio + MIDI I/O drain
|
|
|
|
|
// =====================================================================
|
|
|
|
|
|
|
|
|
|
void setup1() {
|
|
|
|
|
while (!READ_VOLATILE(g_serial_ready)) { MEMORY_BARRIER(); delay(1); }
|
|
|
|
|
while (!READ_VOLATILE(g_iface_ready)) { MEMORY_BARRIER(); delay(1); }
|
|
|
|
|
|
|
|
|
|
if (g_midi) {
|
|
|
|
|
g_midi->Setup(/*n_outputs=*/16);
|
|
|
|
|
g_midi->SetMIDISendChannel(1);
|
|
|
|
|
nisps_firmware::bind_midi_input(g_midi, g_mode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_mode.setup(static_cast<float>(AudioDriver::GetSampleRate()));
|
|
|
|
|
|
|
|
|
|
nisps_firmware::register_audio_engine(g_mode, &audio_block_callback);
|
|
|
|
|
AudioDriver::Setup();
|
|
|
|
|
|
|
|
|
|
WRITE_VOLATILE(g_core1_ready, true);
|
|
|
|
|
while (!READ_VOLATILE(g_core0_ready)) { MEMORY_BARRIER(); delay(1); }
|
|
|
|
|
Serial.println("Finished initialising core 1.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void loop1() {
|
|
|
|
|
PERIODIC_RUN_US({
|
|
|
|
|
nisps_firmware::drain_outputs(g_midi, g_mode);
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
|
|
|
|
PERIODIC_RUN_US({
|
|
|
|
|
if (g_midi) g_midi->Poll();
|
|
|
|
|
}, 1000)
|
|
|
|
|
}
|
2026-06-28 04:14:30 +02:00
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
// =====================================================================
|
|
|
|
|
// SelfTest build path — guided hardware self-test rig (no engine / no ML).
|
|
|
|
|
// All four entry points delegate into glue/selftest.hpp.
|
|
|
|
|
// =====================================================================
|
|
|
|
|
|
|
|
|
|
#include "glue/selftest.hpp"
|
|
|
|
|
|
|
|
|
|
void setup() { nisps_firmware::selftest::setup(); }
|
|
|
|
|
void loop() { nisps_firmware::selftest::loop(); }
|
|
|
|
|
void setup1() { nisps_firmware::selftest::setup1(); }
|
|
|
|
|
void loop1() { nisps_firmware::selftest::loop1(); }
|
|
|
|
|
|
|
|
|
|
#endif // NISPS_SELFTEST
|