fix(cold-eval): sample external inputs
This commit is contained in:
parent
67d04bf229
commit
d470f1d2c5
3 changed files with 46 additions and 4 deletions
|
|
@ -30,10 +30,23 @@ void SignalEngine::init_defaults(Sample bpm, int beats_per_bar,
|
||||||
GraphBuilder::init_symbols();
|
GraphBuilder::init_symbols();
|
||||||
state = EngineState{};
|
state = EngineState{};
|
||||||
session_generation = 0;
|
session_generation = 0;
|
||||||
|
memset(cold_hw_inputs, 0, sizeof(cold_hw_inputs));
|
||||||
reset_session_storage(bpm, beats_per_bar, bars_per_phrase,
|
reset_session_storage(bpm, beats_per_bar, bars_per_phrase,
|
||||||
phrases_per_section, false);
|
phrases_per_section, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SignalEngine::snapshot_cold_hw_inputs(const Sample* values,
|
||||||
|
uint16_t count) {
|
||||||
|
if (!values) count = 0;
|
||||||
|
if (count > MAX_HW_INPUT_CHANNELS) count = MAX_HW_INPUT_CHANNELS;
|
||||||
|
if (count > 0)
|
||||||
|
memcpy(cold_hw_inputs, values, count * sizeof(Sample));
|
||||||
|
if (count < MAX_HW_INPUT_CHANNELS) {
|
||||||
|
memset(cold_hw_inputs + count, 0,
|
||||||
|
(MAX_HW_INPUT_CHANNELS - count) * sizeof(Sample));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SignalEngine::reset_session_storage(Sample bpm, int beats_per_bar,
|
void SignalEngine::reset_session_storage(Sample bpm, int beats_per_bar,
|
||||||
int bars_per_phrase,
|
int bars_per_phrase,
|
||||||
int phrases_per_section,
|
int phrases_per_section,
|
||||||
|
|
@ -807,7 +820,6 @@ static EvalResult do_set(TokenStream& ts, SignalEngine& engine,
|
||||||
|
|
||||||
Sample cell_vals[MAX_CELLS];
|
Sample cell_vals[MAX_CELLS];
|
||||||
engine.cells.snapshot_values(cell_vals, MAX_CELLS);
|
engine.cells.snapshot_values(cell_vals, MAX_CELLS);
|
||||||
Sample hw_inputs[32] = {};
|
|
||||||
Sample workspace[MAX_TOTAL_NODES] = {};
|
Sample workspace[MAX_TOTAL_NODES] = {};
|
||||||
Sample outputs[MAX_OUTPUTS] = {};
|
Sample outputs[MAX_OUTPUTS] = {};
|
||||||
|
|
||||||
|
|
@ -815,7 +827,7 @@ static EvalResult do_set(TokenStream& ts, SignalEngine& engine,
|
||||||
ctx.t = engine.state.current_time;
|
ctx.t = engine.state.current_time;
|
||||||
ctx.dt = engine.state.current_dt;
|
ctx.dt = engine.state.current_dt;
|
||||||
ctx.cell_values = cell_vals;
|
ctx.cell_values = cell_vals;
|
||||||
ctx.hw_inputs = hw_inputs;
|
ctx.hw_inputs = engine.cold_hw_inputs;
|
||||||
ctx.data_pool = engine.cells.data_pool;
|
ctx.data_pool = engine.cells.data_pool;
|
||||||
ctx.data_offsets = engine.cells.data_offsets;
|
ctx.data_offsets = engine.cells.data_offsets;
|
||||||
ctx.data_lengths = engine.cells.data_lengths;
|
ctx.data_lengths = engine.cells.data_lengths;
|
||||||
|
|
@ -2864,7 +2876,6 @@ EvalResult eval_expression(const char* source, uint32_t length,
|
||||||
engine.cells.snapshot_values(cell_values, MAX_CELLS);
|
engine.cells.snapshot_values(cell_values, MAX_CELLS);
|
||||||
|
|
||||||
// Execute one sample
|
// Execute one sample
|
||||||
Sample hw_inputs[32] = {};
|
|
||||||
Sample outputs[MAX_OUTPUTS] = {};
|
Sample outputs[MAX_OUTPUTS] = {};
|
||||||
Sample workspace[MAX_TOTAL_NODES] = {};
|
Sample workspace[MAX_TOTAL_NODES] = {};
|
||||||
|
|
||||||
|
|
@ -2872,7 +2883,7 @@ EvalResult eval_expression(const char* source, uint32_t length,
|
||||||
ctx.t = engine.state.current_time;
|
ctx.t = engine.state.current_time;
|
||||||
ctx.dt = engine.state.current_dt;
|
ctx.dt = engine.state.current_dt;
|
||||||
ctx.cell_values = cell_values;
|
ctx.cell_values = cell_values;
|
||||||
ctx.hw_inputs = hw_inputs;
|
ctx.hw_inputs = engine.cold_hw_inputs;
|
||||||
ctx.data_pool = engine.cells.data_pool;
|
ctx.data_pool = engine.cells.data_pool;
|
||||||
ctx.data_offsets = engine.cells.data_offsets;
|
ctx.data_offsets = engine.cells.data_offsets;
|
||||||
ctx.data_lengths = engine.cells.data_lengths;
|
ctx.data_lengths = engine.cells.data_lengths;
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,12 @@ struct SignalEngine {
|
||||||
NodePool scratch_pool;
|
NodePool scratch_pool;
|
||||||
char eval_text_buf[512] = {};
|
char eval_text_buf[512] = {};
|
||||||
|
|
||||||
|
// Latest host-owned external-input snapshot used only by top-level cold
|
||||||
|
// expression evaluation. Hot graphs receive their input pointer through
|
||||||
|
// ExecutionContext on every tick. Keeping a copy here gives queries the
|
||||||
|
// same last-known values without retaining a host-lifetime pointer.
|
||||||
|
Sample cold_hw_inputs[MAX_HW_INPUT_CHANNELS] = {};
|
||||||
|
|
||||||
#if USEQ_HAS_SYNTH_ENGINE
|
#if USEQ_HAS_SYNTH_ENGINE
|
||||||
// ── Host synth compiler domain (synth-nodes.md) ─────────────────────
|
// ── Host synth compiler domain (synth-nodes.md) ─────────────────────
|
||||||
// Published synth artefacts: identity-keyed declarations + control
|
// Published synth artefacts: identity-keyed declarations + control
|
||||||
|
|
@ -197,6 +203,8 @@ struct SignalEngine {
|
||||||
int bars_per_phrase = 4,
|
int bars_per_phrase = 4,
|
||||||
int phrases_per_section = 4,
|
int phrases_per_section = 4,
|
||||||
bool publish_session_clear = true);
|
bool publish_session_clear = true);
|
||||||
|
|
||||||
|
void snapshot_cold_hw_inputs(const Sample* values, uint16_t count);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Cold-Path Evaluation ────────────────────────────────────────────────────
|
// ── Cold-Path Evaluation ────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,29 @@ TEST_CASE("external inputs resolve as graph leaves", "[ext_registry]")
|
||||||
REQUIRE(h.tick("a1", 1.5) == Approx(0.4));
|
REQUIRE(h.tick("a1", 1.5) == Approx(0.4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("cold expressions read the latest external-input snapshot")
|
||||||
|
{
|
||||||
|
REQUIRE(register_external_input(
|
||||||
|
{"nn/out", 4, 4, 0.5f, "[0,1]"}));
|
||||||
|
h.hw_inputs[4] = 0.1;
|
||||||
|
h.hw_inputs[5] = 0.2;
|
||||||
|
h.hw_inputs[6] = 0.3;
|
||||||
|
h.hw_inputs[7] = 0.4;
|
||||||
|
h.engine.snapshot_cold_hw_inputs(h.hw_inputs, 32);
|
||||||
|
|
||||||
|
EvalResult selected = h.eval("(nn/out 4)");
|
||||||
|
REQUIRE(selected.kind == EvalResult::Number);
|
||||||
|
REQUIRE(selected.number == Approx(0.4));
|
||||||
|
|
||||||
|
EvalResult composed = h.eval("(* 2 (nn/out 2))");
|
||||||
|
REQUIRE(composed.kind == EvalResult::Number);
|
||||||
|
REQUIRE(composed.number == Approx(0.4));
|
||||||
|
|
||||||
|
EvalResult invalid = h.eval("(nn/out 9)");
|
||||||
|
REQUIRE(invalid.kind == EvalResult::Number);
|
||||||
|
REQUIRE(invalid.number == Approx(0.5));
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("invalid multi-channel selectors return the declared neutral")
|
SECTION("invalid multi-channel selectors return the declared neutral")
|
||||||
{
|
{
|
||||||
REQUIRE(register_external_input(
|
REQUIRE(register_external_input(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue