memlnaut-nisps/nisps/core/concepts.hpp
w1n5t0n 4f60fc8405 feat: nisps/core foundation — perf, types, concepts, buffers, rng, math
Greenfield C++20 core for the unified firmware+WASM rewrite (architecture.md
streams, meml-dn7). Header-only, platform-agnostic, no heap, no virtual
dispatch.

Components:
- perf.hpp        memory section + inlining macros, RP2040/RP2350-aware,
                  inert on host/Emscripten
- types.hpp       stereosample_t (mirrors firmware AudioDriver API),
                  sample_t/param_t aliases, DriverConfig negotiation struct
- concepts.hpp    MLEngine, AudioEngine, Mode (architecture §4.1-4.3)
- fixed_buffer.hpp  std::array-backed cursor; replaces std::vector in hot paths
- ring_buffer.hpp   SPSC lock-free FIFO, power-of-two capacity, atomic
                    head/tail; replaces pico/util/queue in core
- rng.hpp         xoshiro256+ with splitmix64 seeding, uniform/signed/
                  gaussian-via-3-uniforms (matches legacy MoveWeights shape)
- math.hpp        clamp01, fast_sigmoid (tanh-Padé, ~1.2% max err on [-6,6]),
                  exact_sigmoid, fast_exp, named Curve catalog (linear/exp/
                  log/square/sqrt/sigmoid/cubic) — TypeScript twin lives in
                  playground/src/output/curves.ts (stream 5)

Performance discipline (Chris's rules):
- No heap, no std::vector, no malloc/new in core
- All float literals carry .f suffix
- Memory section attrs syntactically present, inert on non-firmware builds
2026-04-29 15:21:52 +03:00

85 lines
4.1 KiB
C++

// 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 <concepts>
#include <cstddef>
#include <cstdint>
#include <span>
#include <string_view>
#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<NIn, NHidden..., NOut> in stream 2.
// ---------------------------------------------------------------------------
template <typename T>
concept MLEngine = requires(T e,
std::span<const float> in,
std::span<float> out,
std::size_t idx,
float fv,
std::uint64_t seed) {
{ e.set_input(idx, fv) } -> std::same_as<void>;
{ e.process() } -> std::same_as<void>;
{ e.outputs() } -> std::same_as<std::span<const float>>;
{ e.add_example(in, in) } -> std::same_as<void>;
{ e.train() } -> std::same_as<float>; // returns final loss
{ e.move_weights(fv, fv) } -> std::same_as<void>; // (speed, spread)
{ e.draw_weights(fv) } -> std::same_as<void>; // (spread)
{ e.reset() } -> std::same_as<void>;
{ e.seed(seed) } -> std::same_as<void>;
};
// ---------------------------------------------------------------------------
// AudioEngine — per-sample stereo processor. Sample-rate is negotiated at
// setup; engines should NOT assume 48 kHz.
// ---------------------------------------------------------------------------
template <typename T>
concept AudioEngine = requires(T e,
stereosample_t s,
std::span<const float> params,
float sr) {
{ T::param_count() } -> std::convertible_to<std::size_t>; // constexpr
{ T::engine_id() } -> std::same_as<std::string_view>; // constexpr
{ e.setup(sr) } -> std::same_as<void>;
{ e.set_params(params) } -> std::same_as<void>; // non-RT
{ e.process(s) } -> std::same_as<stereosample_t>; // RT, per-sample
{ e.driver_config() } -> std::convertible_to<DriverConfig>;
};
// ---------------------------------------------------------------------------
// 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 <typename T>
concept Mode = requires(T m, std::size_t idx, float v, stereosample_t s, float sr) {
{ T::mode_id() } -> std::same_as<std::string_view>; // constexpr
{ T::input_channel_count() } -> std::convertible_to<std::size_t>; // constexpr
{ T::param_schema() } -> std::same_as<const ParamSchema&>; // constexpr ref
{ m.setup(sr) } -> std::same_as<void>;
{ m.set_input(idx, v) } -> std::same_as<void>;
{ m.tick_control() } -> std::same_as<void>; // non-RT
{ m.process(s) } -> std::same_as<stereosample_t>; // 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