138 lines
5.9 KiB
C
138 lines
5.9 KiB
C
|
|
#ifndef SIGNAL_ENGINE_EXT_REGISTRY_H
|
||
|
|
#define SIGNAL_ENGINE_EXT_REGISTRY_H
|
||
|
|
|
||
|
|
#include "types.h"
|
||
|
|
|
||
|
|
namespace sig {
|
||
|
|
|
||
|
|
// ── Generic external registers (NISPS-USEQ spec §3) ─────────────────────────
|
||
|
|
//
|
||
|
|
// A firmware profile owns the names and metadata of everything that surrounds
|
||
|
|
// the engine: named external inputs (producers stage latest-value state that
|
||
|
|
// signal graphs read as leaves), named external sinks (live code binds
|
||
|
|
// evaluated signals to transports), and discrete cold commands. The engine
|
||
|
|
// provides only the generic registration and dispatch seam — no nn/* or
|
||
|
|
// midi/* name is compiled in; those arrive via registration at firmware boot.
|
||
|
|
//
|
||
|
|
// Registration is profile state, not livecoding session state: it survives
|
||
|
|
// (useq-clear) and is deliberately NOT touched by SignalEngine session resets.
|
||
|
|
// reset_registry() exists for tests and explicit profile re-initialisation.
|
||
|
|
|
||
|
|
constexpr uint8_t MAX_EXTERNAL_INPUTS = 24;
|
||
|
|
constexpr uint8_t MAX_EXTERNAL_SINKS = 16;
|
||
|
|
constexpr uint8_t MAX_EXTERNAL_COMMANDS = 16;
|
||
|
|
|
||
|
|
// Width of the executor-side hw_inputs[] snapshot array (see
|
||
|
|
// ExecutionContext::hw_inputs). Registered descriptors must address channels
|
||
|
|
// inside this span.
|
||
|
|
constexpr uint16_t MAX_HW_INPUT_CHANNELS = 32;
|
||
|
|
|
||
|
|
// ── External input registers (spec §3.1/§3.2) ───────────────────────────────
|
||
|
|
|
||
|
|
struct ExternalInputDesc {
|
||
|
|
const char* name; // static-lifetime spelling, e.g. "nn/out1"
|
||
|
|
uint16_t hw_index; // first hw_inputs[] channel behind the name
|
||
|
|
uint8_t channels; // consecutive channels the register spans
|
||
|
|
float neutral; // profile-neutral value before first update
|
||
|
|
const char* units_or_range; // static metadata for UI/diagnostics
|
||
|
|
};
|
||
|
|
|
||
|
|
// ── External sinks (spec §3.3/§3.5/§7.4) ────────────────────────────────────
|
||
|
|
|
||
|
|
struct ExternalSinkDesc {
|
||
|
|
const char* name; // static-lifetime spelling, e.g. "midi/cc74"
|
||
|
|
uint8_t arity; // exact argument count of the sink form
|
||
|
|
float min; // accepted range (deadband scaling + metadata)
|
||
|
|
float max;
|
||
|
|
uint32_t max_rate_hz; // profile transport ceiling; enforced firmware-side
|
||
|
|
uint16_t quant_bits; // 0 = unquantised; else step = (max-min)/2^bits
|
||
|
|
};
|
||
|
|
|
||
|
|
struct ExternalCommandDesc {
|
||
|
|
SymbolID cmd;
|
||
|
|
uint8_t arity_min;
|
||
|
|
uint8_t arity_max;
|
||
|
|
};
|
||
|
|
|
||
|
|
// ── Registration / lookup ───────────────────────────────────────────────────
|
||
|
|
// Re-registering a name replaces its descriptor in place (idempotent profile
|
||
|
|
// init). Return false on a full registry or an invalid descriptor. Lookup is
|
||
|
|
// by SymbolID-interned name: registration interns desc->name, and the
|
||
|
|
// tokenizer interns the same spelling to the same ID.
|
||
|
|
|
||
|
|
bool register_external_input(const ExternalInputDesc& desc);
|
||
|
|
bool register_external_sink(const ExternalSinkDesc& desc);
|
||
|
|
bool register_external_command(SymbolID cmd, uint8_t arity_min,
|
||
|
|
uint8_t arity_max);
|
||
|
|
|
||
|
|
// Clears inputs, sinks, commands, and the cold-command handler.
|
||
|
|
void reset_registry();
|
||
|
|
|
||
|
|
const ExternalInputDesc* find_external_input(SymbolID name);
|
||
|
|
// Hardware-input channel for a registered name, or NODE_NONE. This is the
|
||
|
|
// compiler-facing lookup used by GraphBuilder::resolve_hardware_input.
|
||
|
|
uint16_t external_input_index_for(SymbolID name);
|
||
|
|
|
||
|
|
const ExternalSinkDesc* find_external_sink(SymbolID name);
|
||
|
|
bool external_sink_registered(SymbolID name);
|
||
|
|
const ExternalCommandDesc* find_external_command(SymbolID cmd);
|
||
|
|
|
||
|
|
// ── Sink bindings ───────────────────────────────────────────────────────────
|
||
|
|
// One entry per assigned external sink, owned by SignalEngine. Each channel's
|
||
|
|
// compiled graph is published as a real pool output slot (indices
|
||
|
|
// SINK_SLOT_BASE..MAX_OUTPUTS-1) so execution order, LKG fallback, and GC
|
||
|
|
// reachability are the ordinary output machinery — no second mechanism.
|
||
|
|
|
||
|
|
constexpr uint8_t MAX_SINK_ARITY = 8;
|
||
|
|
constexpr uint8_t MAX_SINK_BINDINGS = 16;
|
||
|
|
|
||
|
|
// Pool output slots 0..23 are the named a/d/s outputs; sink channel graphs
|
||
|
|
// occupy the remainder.
|
||
|
|
constexpr uint16_t SINK_SLOT_BASE = 24;
|
||
|
|
static_assert(SINK_SLOT_BASE < MAX_OUTPUTS,
|
||
|
|
"sink channel slots must fit inside the pool output table");
|
||
|
|
|
||
|
|
struct SinkBinding {
|
||
|
|
SymbolID sink = SymbolIntern::INVALID_ID;
|
||
|
|
uint16_t value_index[MAX_SINK_ARITY] = {}; // pool output slot per channel
|
||
|
|
uint8_t arity = 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
// ── Cold command handler (spec §2.3/§6.1 — generic hook) ────────────────────
|
||
|
|
// A top-level form whose head is a registered command symbol parses its
|
||
|
|
// constant arguments into ColdArg values and calls the installed handler
|
||
|
|
// after every earlier form in the same eval compiled successfully. The
|
||
|
|
// handler must not re-enter the evaluator.
|
||
|
|
|
||
|
|
constexpr uint8_t MAX_COLD_ARGS = 8;
|
||
|
|
|
||
|
|
struct ColdArg {
|
||
|
|
enum class Kind : uint8_t { Int, Number, Symbol, Vector };
|
||
|
|
static constexpr uint8_t MAX_VEC = MAX_SINK_ARITY;
|
||
|
|
|
||
|
|
Kind kind = Kind::Int;
|
||
|
|
|
||
|
|
// Number tokens without a fractional part that fit in int32 parse as
|
||
|
|
// Int; everything numeric else is Number. Names parse as their interned
|
||
|
|
// SymbolID; bracketed numeric literals parse as Vector.
|
||
|
|
union {
|
||
|
|
float number;
|
||
|
|
int32_t integer;
|
||
|
|
SymbolID symbol;
|
||
|
|
float vec[MAX_VEC];
|
||
|
|
};
|
||
|
|
|
||
|
|
ColdArg() : integer(0) {}
|
||
|
|
};
|
||
|
|
|
||
|
|
using ColdCommandHandler = bool (*)(SymbolID cmd, const ColdArg* args,
|
||
|
|
uint8_t nargs, void* user);
|
||
|
|
|
||
|
|
void set_cold_command_handler(ColdCommandHandler handler, void* user);
|
||
|
|
ColdCommandHandler cold_command_handler();
|
||
|
|
void* cold_command_user();
|
||
|
|
|
||
|
|
} // namespace sig
|
||
|
|
|
||
|
|
#endif // SIGNAL_ENGINE_EXT_REGISTRY_H
|