From ea588e79cd70c64e0b44ee05eabb7e8adc2d5184 Mon Sep 17 00:00:00 2001 From: monkey-w1n5t0n Date: Tue, 21 Jul 2026 12:48:50 +0200 Subject: [PATCH] refactor(nisps): delete dead engine/mode mass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 group 3 (L3, L9, L4, L5, L6, ST1). - L3: nisps/modes/voice_space.hpp — entirely dead, no includes anywhere. - L4: deleted SawOsc and SquareOsc. KEPT SineOsc — a verifier caught that the original reviewer's grep missed its live consumer (the firmware selftest); re-confirmed here before touching the file. - L9: removed the no-op VoiceSpace enum/table/setter boilerplate from the five engines with no real voice spaces; kept it on PAFSynth, VerbFX and ChannelStrip, which have real ones. Every engine member was checked against nisps/wasm/bindings.cpp, firmware/ and tests/ for callers first. - L5: ModeBase::input_dirty_ was write-only state — deleted the flag rather than making it gate inference, which would have been a behaviour change. - L6: VerbFXEngine's delay_to_verb_ (computed 12x per block, never read), enable_reverb_, enable_delay_to_reverb_ and the unused set_enable_* setters. - ST1: rewrote the four engine header comment blocks that described implementations which do not exist. L7 (MEMLCeliumEngine's inert feedback path) is deliberately NOT done — tracing git history showed feedbackGain went 0.1f (live) -> "0; //0.1f" (explicitly muted, value preserved) -> dropped entirely in the port. That is a muted feature, not dead weight, and deleting it would silently lose it. Left intact pending an operator decision; see the phase report. Gates: run-all-tests.sh ALL GREEN. --- nisps/dsp/osc.hpp | 39 ++------------------------ nisps/engines/analysis.hpp | 6 ---- nisps/engines/base.hpp | 14 ++++++---- nisps/engines/breakor.hpp | 13 +++------ nisps/engines/elysiamorf.hpp | 6 ---- nisps/engines/memlcelium.hpp | 13 +++------ nisps/engines/verb_fx.hpp | 25 ++++++----------- nisps/engines/xiasri.hpp | 14 +++------- nisps/modes/base.hpp | 5 ---- nisps/modes/voice_space.hpp | 54 ------------------------------------ 10 files changed, 31 insertions(+), 158 deletions(-) delete mode 100644 nisps/modes/voice_space.hpp diff --git a/nisps/dsp/osc.hpp b/nisps/dsp/osc.hpp index 31e43b3..174bb90 100644 --- a/nisps/dsp/osc.hpp +++ b/nisps/dsp/osc.hpp @@ -1,7 +1,7 @@ // nisps/dsp/osc.hpp — oscillators. // -// SineOsc, SawOsc, SquareOsc — basic waveforms. Cheap, naive sine via -// sinf(phase * TWO_PI). For high-fidelity needs, replace with a wavetable. +// SineOsc — naive sine via sinf(phase * TWO_PI), used by the firmware +// selftest's audio sweep. For high-fidelity needs, replace with a wavetable. // // PAFOperator — Phase-Aligned Formant operator. Direct port of `maxiPAFOperator` // in memllib. Maintains a static gauss/cauchy table populated on first @@ -47,41 +47,6 @@ class SineOsc { float phase_ = 0.f; }; -class SawOsc { - public: - SawOsc() noexcept = default; - void setup(float sample_rate) noexcept { inv_sr_ = 1.f / sample_rate; } - - // Naive saw — aliases above ~Nyquist/2; fine for sub-bass / LFO use. - NISPS_HOT NISPS_FORCE_INLINE float saw(float frequency) noexcept { - const float y = (phase_ * 2.f) - 1.f; - phase_ += inv_sr_ * frequency; - if (phase_ >= 1.f) phase_ -= 1.f; - return y; - } - - private: - float inv_sr_ = 1.f / 48000.f; - float phase_ = 0.f; -}; - -class SquareOsc { - public: - SquareOsc() noexcept = default; - void setup(float sample_rate) noexcept { inv_sr_ = 1.f / sample_rate; } - - NISPS_HOT NISPS_FORCE_INLINE float square(float frequency) noexcept { - const float y = phase_ < 0.5f ? -1.f : 1.f; - phase_ += inv_sr_ * frequency; - if (phase_ >= 1.f) phase_ -= 1.f; - return y; - } - - private: - float inv_sr_ = 1.f / 48000.f; - float phase_ = 0.f; -}; - // Phase-Aligned Formant operator — port of maxiPAFOperator. The Gaussian and // Cauchy lookup tables are generated once at first call to `init()` (file- // local static; safe in single-threaded firmware audio path). diff --git a/nisps/engines/analysis.hpp b/nisps/engines/analysis.hpp index fbfc2dd..533a671 100644 --- a/nisps/engines/analysis.hpp +++ b/nisps/engines/analysis.hpp @@ -94,12 +94,6 @@ class AnalysisEngine { static constexpr std::size_t param_count() noexcept { return 0u; } static constexpr std::string_view engine_id() noexcept { return "analysis"; } - enum class VoiceSpace : std::size_t { None = 0 }; - static constexpr std::size_t kVoiceSpaceCount = 0u; - static constexpr std::array kVoiceSpaceNames = {}; - void set_voice_space(VoiceSpace) noexcept {} - VoiceSpace voice_space() const noexcept { return VoiceSpace::None; } - struct Features { float pitch = 0.f; float aperiodicity = 0.f; diff --git a/nisps/engines/base.hpp b/nisps/engines/base.hpp index 3ae1295..b202005 100644 --- a/nisps/engines/base.hpp +++ b/nisps/engines/base.hpp @@ -2,12 +2,14 @@ // implementations. // // Includes: -// - NoOpEngine: silent passthrough. Two uses: -// (1) the standalone "thru" engine for the SoundAnalysisMIDI mode (audio -// is analysed but not synthesised), exposed via engine_id()=="thru"; -// (2) composed inside sequencer-only engines (BreakOr, Elysiamorf) whose -// MIDI/I2C event emission lives outside the audio path. Internal -// composition uses the type directly, not the engine_id lookup. +// - NoOpEngine: silent passthrough (engine_id() == "thru"), used directly +// as the mode-level EngineT wherever a mode produces no audio itself — +// SoundAnalysisMIDIMode (audio is analysed via a separately-composed +// AnalysisEngine member, not synthesised) and ExternalSynthMIDIMode +// (joystick -> MLP -> MIDI CC only, no audio path). BreakOrEngine and +// ElysiamorfEngine are sequencer-only engines that also return silence +// from process(), but each implements that directly — neither composes +// NoOpEngine. // - Helper macros / static_asserts to verify each concrete engine satisfies // `nisps::AudioEngine` at compile time. diff --git a/nisps/engines/breakor.hpp b/nisps/engines/breakor.hpp index 64ed068..1f1134e 100644 --- a/nisps/engines/breakor.hpp +++ b/nisps/engines/breakor.hpp @@ -1,8 +1,9 @@ // nisps/engines/breakor.hpp — 8-track ratio-sequencer. // -// `process()` returns silence; the engine's job is to emit MIDI/I2C events on -// each tick. Wraps a NoOpEngine for the audio path. Stream 4/6 will hook -// `pop_events()` into the firmware's MIDI/I2C output. +// `process()` returns silence directly; the engine's job is to emit +// NoteOn/NoteOff/Clock events on each tick via `pop_events()`. BreakOrMode +// (mode layer) drains these into its ControlEvent ring for platform glue +// (firmware MIDI/I2C, browser WebMIDI) to forward. // // Param layout: 8 tracks × 7 ratio-seq params each = 56 params. // per track: [ratio0, ratio1, ratio2, phasorMul, phaseOff, ampRatio0, ampRatio1] @@ -34,12 +35,6 @@ class BreakOrEngine { static constexpr std::size_t param_count() noexcept { return kNParams; } static constexpr std::string_view engine_id() noexcept { return "breakor"; } - enum class VoiceSpace : std::size_t { None = 0, Count = 0 }; - static constexpr std::size_t kVoiceSpaceCount = 0u; - static constexpr std::array kVoiceSpaceNames = {}; - void set_voice_space(VoiceSpace) noexcept {} - VoiceSpace voice_space() const noexcept { return VoiceSpace::None; } - enum class EventKind : std::uint8_t { NoteOn, NoteOff, Clock }; struct Event { EventKind kind; diff --git a/nisps/engines/elysiamorf.hpp b/nisps/engines/elysiamorf.hpp index 03f1518..3f2fe83 100644 --- a/nisps/engines/elysiamorf.hpp +++ b/nisps/engines/elysiamorf.hpp @@ -34,12 +34,6 @@ class ElysiamorfEngine { static constexpr std::size_t param_count() noexcept { return kNParams; } static constexpr std::string_view engine_id() noexcept { return "elysiamorf"; } - enum class VoiceSpace : std::size_t { None = 0, Count = 0 }; - static constexpr std::size_t kVoiceSpaceCount = 0u; - static constexpr std::array kVoiceSpaceNames = {}; - void set_voice_space(VoiceSpace) noexcept {} - VoiceSpace voice_space() const noexcept { return VoiceSpace::None; } - enum class EventKind : std::uint8_t { CC, Clock }; struct Event { EventKind kind; diff --git a/nisps/engines/memlcelium.hpp b/nisps/engines/memlcelium.hpp index a49f172..814fef3 100644 --- a/nisps/engines/memlcelium.hpp +++ b/nisps/engines/memlcelium.hpp @@ -12,9 +12,10 @@ // Voice 1 (3 PAF operators): base freq, detune1/2, 3× cf, 3× bw, 3× shift, // amp ADSR, pitch envelope, pitch emphasis. (~20 params) // -// The sequencer fires note events that trigger V0/V1 envelopes. The actual -// internal "RatioSeq" tick uses the sequencer-side params; we expose -// `pop_events()` for stream 4/6 to consume MIDI/I2C if desired. +// The sequencer fires note events internally to trigger V0/V1 envelopes. +// `pop_events()` exposes that same NoteOn/NoteOff/Clock stream (as BreakOr +// and Elysiamorf do), but no mode currently drains it — MEMLCeliumMode is a +// pure synth with no MIDI/I2C output wired for this engine. #pragma once @@ -41,12 +42,6 @@ class MEMLCeliumEngine { static constexpr std::size_t param_count() noexcept { return kNParams; } static constexpr std::string_view engine_id() noexcept { return "memlcelium"; } - enum class VoiceSpace : std::size_t { Direct = 0, Count = 1 }; - static constexpr std::size_t kVoiceSpaceCount = 1u; - static constexpr std::array kVoiceSpaceNames = {"Direct"}; - void set_voice_space(VoiceSpace) noexcept {} - VoiceSpace voice_space() const noexcept { return VoiceSpace::Direct; } - void setup(float sample_rate) noexcept { sample_rate_ = sample_rate; for (auto* op : {&v0_paf0_, &v0_paf1_, &v0_paf2_, diff --git a/nisps/engines/verb_fx.hpp b/nisps/engines/verb_fx.hpp index 45260bb..4dec685 100644 --- a/nisps/engines/verb_fx.hpp +++ b/nisps/engines/verb_fx.hpp @@ -157,11 +157,9 @@ class VerbFXEngine { } void set_enable_filterbank(bool v) noexcept { enable_filterbank_ = v; } - void set_enable_reverb(bool v) noexcept { enable_reverb_ = v; } void set_enable_short_delay(bool v) noexcept { enable_short_delay_ = v; } void set_enable_medium_delay(bool v) noexcept { enable_medium_delay_ = v; } void set_enable_long_delay(bool v) noexcept { enable_long_delay_ = v; } - void set_enable_delay_to_reverb(bool v) noexcept { enable_delay_to_reverb_ = v; } void set_wet_dry_override(float v) noexcept { wet_dry_ = v; } private: @@ -239,7 +237,6 @@ class VerbFXEngine { filterbank_res_linear(p, 19.f); common_delays_linear(p); verb_vs_delay_ = p[43]; - delay_to_verb_ = p[44] * 0.99f; delay_morph_ = p[45]; delay_blend_ = p[46]; } @@ -259,7 +256,7 @@ class VerbFXEngine { ddelay_feedback1_ = p[40] * 0.99f; ddelay_time2_ = 10.f + p[41] * 501.f; ddelay_feedback2_ = p[42] * 0.99f; - verb_vs_delay_ = p[43]; delay_to_verb_ = p[44] * 0.99f; + verb_vs_delay_ = p[43]; delay_morph_ = p[45]; delay_blend_ = p[46]; } @@ -282,7 +279,7 @@ class VerbFXEngine { ddelay_feedback1_ = p[40] * p[40] * 0.98f; ddelay_time2_ = 10.f + p[41] * 501.f; ddelay_feedback2_ = p[42] * p[42] * 0.98f; - verb_vs_delay_ = p[43]; delay_to_verb_ = p[44] * 0.99f; + verb_vs_delay_ = p[43]; delay_morph_ = p[45]; delay_blend_ = p[46]; } @@ -302,7 +299,6 @@ class VerbFXEngine { ddelay_time2_ = 10.f + std::sqrt(p[41]) * 501.f; ddelay_feedback2_ = std::sqrt(p[42]) * 0.98f; verb_vs_delay_ = p[43] * p[43]; - delay_to_verb_ = std::sqrt(p[44]) * 0.99f; delay_morph_ = p[45]; delay_blend_ = p[46]; } @@ -322,7 +318,6 @@ class VerbFXEngine { ddelay_time2_ = 10.f + p[41] * 501.f; ddelay_feedback2_ = std::sqrt(p[42]) * 0.95f; verb_vs_delay_ = p[43] * p[43]; - delay_to_verb_ = std::sqrt(p[44]) * 0.99f; delay_morph_ = p[45]; delay_blend_ = p[46]; } @@ -345,7 +340,7 @@ class VerbFXEngine { ddelay_feedback1_ = p[40] * p[40] * 0.98f; ddelay_time2_ = 10.f + p[41] * p[41] * 501.f; ddelay_feedback2_ = p[42] * p[42] * 0.98f; - verb_vs_delay_ = p[43]; delay_to_verb_ = p[44] * 0.99f; + verb_vs_delay_ = p[43]; delay_morph_ = p[45]; delay_blend_ = p[46]; } @@ -364,7 +359,7 @@ class VerbFXEngine { : (1.f + v * v * 19.f); } common_delays_linear(p); - verb_vs_delay_ = p[43]; delay_to_verb_ = p[44] * 0.99f; + verb_vs_delay_ = p[43]; delay_morph_ = p[45]; delay_blend_ = p[46]; } @@ -392,7 +387,7 @@ class VerbFXEngine { ddelay_feedback1_ = std::sqrt(p[40]) * 0.98f; ddelay_time2_ = 10.f + p[41] * 501.f; ddelay_feedback2_ = std::sqrt(p[42]) * 0.98f; - verb_vs_delay_ = p[43]; delay_to_verb_ = p[44] * 0.99f; + verb_vs_delay_ = p[43]; delay_morph_ = p[45]; delay_blend_ = p[46]; } @@ -416,7 +411,7 @@ class VerbFXEngine { : (1.f + v * v * 19.f); } common_delays_linear(p); - verb_vs_delay_ = p[43]; delay_to_verb_ = p[44] * 0.99f; + verb_vs_delay_ = p[43]; delay_morph_ = p[45]; delay_blend_ = p[46]; } @@ -437,7 +432,7 @@ class VerbFXEngine { : (1.f + std::sqrt(v) * 25.f); } common_delays_linear(p); - verb_vs_delay_ = p[43]; delay_to_verb_ = p[44] * 0.99f; + verb_vs_delay_ = p[43]; delay_morph_ = p[45]; delay_blend_ = p[46]; } @@ -453,7 +448,7 @@ class VerbFXEngine { for (std::size_t i = 0u; i < 8u; ++i) fb_freqs_[i] = harm_bases[i] + p[21 + i] * 40.f; filterbank_res_sqrt(p, 19.f); common_delays_linear(p); - verb_vs_delay_ = p[43]; delay_to_verb_ = p[44] * 0.99f; + verb_vs_delay_ = p[43]; delay_morph_ = p[45]; delay_blend_ = p[46]; } @@ -492,17 +487,15 @@ class VerbFXEngine { float ddelay_time_ = 0.f, ddelay_feedback_ = 0.f; float ddelay_time1_ = 0.f, ddelay_feedback1_ = 0.f; float ddelay_time2_ = 0.f, ddelay_feedback2_ = 0.f; - float verb_vs_delay_ = 0.f, delay_to_verb_ = 0.f; + float verb_vs_delay_ = 0.f; float delay_morph_ = 0.5f, delay_blend_ = 0.f; float filter_bank_delay_xfade_ = 0.f; float wet_dry_ = 0.5f; bool enable_filterbank_ = true; - bool enable_reverb_ = true; bool enable_short_delay_ = true; bool enable_medium_delay_ = true; bool enable_long_delay_ = true; - bool enable_delay_to_reverb_ = true; VoiceSpace voice_space_ = VoiceSpace::Default; }; diff --git a/nisps/engines/xiasri.hpp b/nisps/engines/xiasri.hpp index 788b5e0..f93e78a 100644 --- a/nisps/engines/xiasri.hpp +++ b/nisps/engines/xiasri.hpp @@ -1,8 +1,9 @@ // nisps/engines/xiasri.hpp — Reverb + delays + pitch-shift FX engine. // -// Mirrors firmware XIASRIAudioApp. Direct (no voice space) NN→param mapping — -// the firmware ignored its voice-space slot and read smoothParams[] inline. -// We expose a single "Direct" voice space for schema parity. +// Mirrors firmware XIASRIAudioApp. Direct NN→param mapping — the firmware +// ignored its voice-space slot and read smoothParams[] inline, so this +// engine has no VoiceSpace machinery at all: the 24 NN outputs feed the +// smoother directly. // // Pipeline (per sample): // pitch_shift → DC-blocker → 6-allpass + 2-comb reverb tail → 4 parallel @@ -36,13 +37,6 @@ class XIASRIEngine { static constexpr std::size_t param_count() noexcept { return kNParams; } static constexpr std::string_view engine_id() noexcept { return "xiasri"; } - enum class VoiceSpace : std::size_t { Direct = 0, Count = 1 }; - static constexpr std::size_t kVoiceSpaceCount = 1u; - static constexpr std::array kVoiceSpaceNames = {"Direct"}; - - void set_voice_space(VoiceSpace) noexcept {} // no-op for parity - VoiceSpace voice_space() const noexcept { return VoiceSpace::Direct; } - void setup(float sample_rate) noexcept { sample_rate_ = sample_rate; smoother_.setup(150.f, sample_rate); diff --git a/nisps/modes/base.hpp b/nisps/modes/base.hpp index 81778c9..ca0d86e 100644 --- a/nisps/modes/base.hpp +++ b/nisps/modes/base.hpp @@ -162,7 +162,6 @@ class ModeBase { if (value < 0.f) value = 0.f; else if (value > 1.f) value = 1.f; input_channels_[idx] = value; - input_dirty_ = true; } // ---- Input neutralization (single/double controller toggle) ---- @@ -175,7 +174,6 @@ class ModeBase { NISPS_FORCE_INLINE void set_input_pinned(std::size_t idx, bool pinned) noexcept { if (idx >= NInputs) return; input_pinned_[idx] = pinned; - input_dirty_ = true; } NISPS_FORCE_INLINE bool is_input_pinned(std::size_t idx) const noexcept { return idx < NInputs && input_pinned_[idx]; @@ -184,7 +182,6 @@ class ModeBase { if (v < 0.f) v = 0.f; else if (v > 1.f) v = 1.f; pin_value_ = v; - input_dirty_ = true; } float pin_value() const noexcept { return pin_value_; } @@ -226,7 +223,6 @@ class ModeBase { engine_.set_params(ml_.outputs()); } } - input_dirty_ = false; if constexpr (requires(Derived& d) { d.on_post_inference(); }) { static_cast(*this).on_post_inference(); } @@ -325,7 +321,6 @@ class ModeBase { std::array input_channels_{}; std::array input_pinned_{}; // false => live float pin_value_ = 0.5f; // neutral - bool input_dirty_ = false; std::size_t voice_space_idx_ = 0u; RingBuffer events_{}; // Adaptive-learning state (Jolt + OU). Declared AFTER events_ so the diff --git a/nisps/modes/voice_space.hpp b/nisps/modes/voice_space.hpp deleted file mode 100644 index 63bc815..0000000 --- a/nisps/modes/voice_space.hpp +++ /dev/null @@ -1,54 +0,0 @@ -// nisps/modes/voice_space.hpp — Voice-space binding helpers. -// -// In this codebase the *voice-space code* (the lambda that maps NN outputs -// to engine state fields) lives INSIDE each engine — see e.g. -// `PAFSynthEngine::apply_quad_detune` etc. Engines expose a -// `set_voice_space(EngineT::VoiceSpace)` method to switch which mapping is -// active; the next `set_params(span)` call routes through the -// selected voice space. -// -// The mode layer therefore only needs to: -// 1. Tell the engine which voice space is active. -// 2. Surface the voice-space NAMES (for UI dropdowns / schema parity). -// -// `VoiceSpaceEntry` is a thin value used by `ParamSchema::voice_spaces` and -// for static introspection (e.g. matching schema names against -// `EngineT::kVoiceSpaceNames`). We keep it constexpr-friendly. - -#pragma once - -#include -#include -#include -#include - -namespace nisps { - -struct VoiceSpaceEntry { - std::string_view name; - std::size_t index; -}; - -// Build a constexpr list of {name, index} from the engine's static name table. -template -constexpr auto make_voice_space_entries() noexcept { - constexpr std::size_t N = EngineT::kVoiceSpaceCount; - std::array entries{}; - for (std::size_t i = 0u; i < N; ++i) { - entries[i] = VoiceSpaceEntry{EngineT::kVoiceSpaceNames[i], i}; - } - return entries; -} - -// Look up a voice-space index by name on an engine. Returns SIZE_MAX if not -// found. Modes use this to translate schema voice-space strings to the -// engine's enum index when loading a default. -template -constexpr std::size_t find_voice_space(std::string_view name) noexcept { - for (std::size_t i = 0u; i < EngineT::kVoiceSpaceCount; ++i) { - if (EngineT::kVoiceSpaceNames[i] == name) return i; - } - return static_cast(-1); -} - -} // namespace nisps