refactor(nisps): delete dead engine/mode mass

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.
This commit is contained in:
monkey-w1n5t0n 2026-07-21 12:48:50 +02:00
parent e37f16739e
commit ea588e79cd
10 changed files with 31 additions and 158 deletions

View file

@ -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).

View file

@ -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<std::string_view, 0u> 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;

View file

@ -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.

View file

@ -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<std::string_view, 0u> 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;

View file

@ -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<std::string_view, 0u> 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;

View file

@ -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<std::string_view, kVoiceSpaceCount> 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_,

View file

@ -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;
};

View file

@ -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<std::string_view, kVoiceSpaceCount> 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);

View file

@ -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<Derived&>(*this).on_post_inference();
}
@ -325,7 +321,6 @@ class ModeBase {
std::array<float, NInputs> input_channels_{};
std::array<bool, NInputs> input_pinned_{}; // false => live
float pin_value_ = 0.5f; // neutral
bool input_dirty_ = false;
std::size_t voice_space_idx_ = 0u;
RingBuffer<ControlEvent, kModeEventBufferSize> events_{};
// Adaptive-learning state (Jolt + OU). Declared AFTER events_ so the

View file

@ -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<float>)` 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 <array>
#include <cstddef>
#include <span>
#include <string_view>
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 <typename EngineT>
constexpr auto make_voice_space_entries() noexcept {
constexpr std::size_t N = EngineT::kVoiceSpaceCount;
std::array<VoiceSpaceEntry, N> 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 <typename EngineT>
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<std::size_t>(-1);
}
} // namespace nisps