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:
parent
e37f16739e
commit
ea588e79cd
10 changed files with 31 additions and 158 deletions
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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_,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in a new issue