memlnaut-nisps/nisps/modes/voice_space.hpp
w1n5t0n b6b4cf7362 feat(nisps/modes): scaffolding for platform-agnostic mode layer (meml-beb)
Adds two foundational pieces shared by every concrete mode:

  - `base.hpp`: defines `nisps::ParamSchema` (the aggregate type the
    `Mode` concept's `param_schema()` returns by const-reference),
    `nisps::ControlEvent`, and `ModeBase<Derived, EngineT, MLPType,
    NInputs>` — a CRTP scaffold absorbing input forwarding, ML
    inference + voice space mapping, engine.set_params() routing, and
    a per-mode ControlEvent ring buffer for MIDI/I2C events.

    `ModeRoutesOutputsToEngine<Derived>` lets a derived mode (e.g.
    SoundAnalysisMIDI) opt out of routing ML outputs to engine
    params when its outputs become MIDI CCs instead.

  - `voice_space.hpp`: `VoiceSpaceEntry` + `make_voice_space_entries()`
    + `find_voice_space()` helpers. Voice-space *mapping code* lives
    inside engines (mirroring firmware); mode layer just selects
    which voice space the engine uses by index/name.

No heap, no virtuals, no Pico/Arduino dependencies; satisfies stream
4 of architecture.md.
2026-04-29 16:26:33 +03:00

54 lines
1.9 KiB
C++

// 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