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();
|
||||
state = EngineState{};
|
||||
session_generation = 0;
|
||||
memset(cold_hw_inputs, 0, sizeof(cold_hw_inputs));
|
||||
reset_session_storage(bpm, beats_per_bar, bars_per_phrase,
|
||||
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,
|
||||
int bars_per_phrase,
|
||||
int phrases_per_section,
|
||||
|
|
@ -807,7 +820,6 @@ static EvalResult do_set(TokenStream& ts, SignalEngine& engine,
|
|||
|
||||
Sample cell_vals[MAX_CELLS];
|
||||
engine.cells.snapshot_values(cell_vals, MAX_CELLS);
|
||||
Sample hw_inputs[32] = {};
|
||||
Sample workspace[MAX_TOTAL_NODES] = {};
|
||||
Sample outputs[MAX_OUTPUTS] = {};
|
||||
|
||||
|
|
@ -815,7 +827,7 @@ static EvalResult do_set(TokenStream& ts, SignalEngine& engine,
|
|||
ctx.t = engine.state.current_time;
|
||||
ctx.dt = engine.state.current_dt;
|
||||
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_offsets = engine.cells.data_offsets;
|
||||
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);
|
||||
|
||||
// Execute one sample
|
||||
Sample hw_inputs[32] = {};
|
||||
Sample outputs[MAX_OUTPUTS] = {};
|
||||
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.dt = engine.state.current_dt;
|
||||
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_offsets = engine.cells.data_offsets;
|
||||
ctx.data_lengths = engine.cells.data_lengths;
|
||||
|
|
|
|||
|
|
@ -161,6 +161,12 @@ struct SignalEngine {
|
|||
NodePool scratch_pool;
|
||||
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
|
||||
// ── Host synth compiler domain (synth-nodes.md) ─────────────────────
|
||||
// Published synth artefacts: identity-keyed declarations + control
|
||||
|
|
@ -197,6 +203,8 @@ struct SignalEngine {
|
|||
int bars_per_phrase = 4,
|
||||
int phrases_per_section = 4,
|
||||
bool publish_session_clear = true);
|
||||
|
||||
void snapshot_cold_hw_inputs(const Sample* values, uint16_t count);
|
||||
};
|
||||
|
||||
// ── 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));
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
REQUIRE(register_external_input(
|
||||
|
|
|
|||
Loading…
Reference in a new issue