// nisps/core/concepts.hpp — C++20 concepts that pin down the shape of the // three central types in the rewrite: ML engines, audio engines and modes. // // These are CONCEPTS, not interfaces — there is no virtual dispatch in the // hot paths. Concrete types are templated/satisfied at compile time on // firmware; the WASM bindings layer flattens to a C API for the browser. // // See architecture.md §4.1, §4.2, §4.3. #pragma once #include #include #include #include #include #include "types.hpp" namespace nisps { // Forward declaration. Concrete `ParamSchema` lives with the codegen output // (stream 5). Modes only need to expose a constexpr reference to it. struct ParamSchema; // --------------------------------------------------------------------------- // MLEngine — interactive ML inference + training + RL state perturbation. // Implementations: nisps::MLP in stream 2. // --------------------------------------------------------------------------- template concept MLEngine = requires(T e, std::span in, std::span out, std::size_t idx, float fv, std::uint64_t seed) { { e.set_input(idx, fv) } -> std::same_as; { e.process() } -> std::same_as; { e.outputs() } -> std::same_as>; { e.add_example(in, in) } -> std::same_as; { e.train() } -> std::same_as; // returns final loss { e.move_weights(fv, fv) } -> std::same_as; // (speed, spread) { e.draw_weights(fv) } -> std::same_as; // (spread) { e.reset() } -> std::same_as; { e.seed(seed) } -> std::same_as; }; // --------------------------------------------------------------------------- // AudioEngine — per-sample stereo processor. Sample-rate is negotiated at // setup; engines should NOT assume 48 kHz. // --------------------------------------------------------------------------- template concept AudioEngine = requires(T e, stereosample_t s, std::span params, float sr) { { T::param_count() } -> std::convertible_to; // constexpr { T::engine_id() } -> std::same_as; // constexpr { e.setup(sr) } -> std::same_as; { e.set_params(params) } -> std::same_as; // non-RT { e.process(s) } -> std::same_as; // RT, per-sample { e.driver_config() } -> std::convertible_to; }; // --------------------------------------------------------------------------- // Mode — binds an ML config + audio engine + voice space + I/O channel // mapping. Hardware bindings live OUTSIDE this concept (firmware/glue, // playground/src/modes); modes are platform-agnostic. // --------------------------------------------------------------------------- template concept Mode = requires(T m, std::size_t idx, float v, stereosample_t s, float sr) { { T::mode_id() } -> std::same_as; // constexpr { T::input_channel_count() } -> std::convertible_to; // constexpr { T::param_schema() } -> std::same_as; // constexpr ref { m.setup(sr) } -> std::same_as; // Audio-driver setup the mode wants (codec input source / gain staging / // sample rate). Defaults to the mode's audio engine's config; a mode whose // audio INPUT is consumed by something other than that engine overrides it // (see ModeBase::driver_config). Platform glue reads this at mode start. { m.driver_config() } -> std::convertible_to; { m.set_input(idx, v) } -> std::same_as; { m.tick_control() } -> std::same_as; // non-RT { m.process(s) } -> std::same_as; // RT // engine() / ml() return references; we only assert they are callable. // (`auto&` in concept signatures is awkward; require non-void.) { m.engine() }; { m.ml() }; }; } // namespace nisps