54 explicit operator bool()
const {
return ok; }
66 for (std::size_t i = 0; i < samples.size(); ++i) {
67 if (!std::isfinite(samples[i])) {
69 "non-finite sample at index " + std::to_string(i) +
" (" +
70 (std::isnan(samples[i]) ?
"NaN" :
"Inf") +
")");
79template <
typename Sample>
81 for (std::size_t ch = 0; ch < buffer.num_channels(); ++ch) {
82 const auto channel = buffer.channel(ch);
83 for (std::size_t i = 0; i < channel.size(); ++i) {
84 if (!std::isfinite(channel[i])) {
86 "non-finite sample at ch " + std::to_string(ch) +
87 " index " + std::to_string(i) +
" (" +
88 (std::isnan(channel[i]) ?
"NaN" :
"Inf") +
")");
99 float eps = 1.0e-7f) {
100 for (
float s : samples) {
101 if (std::fabs(s) > eps) {
106 " samples are within +/-" + std::to_string(eps) +
107 " of zero (output is silent)");
114 float eps = 1.0e-7f) {
115 for (std::size_t i = 0; i < samples.size(); ++i) {
116 if (!std::isfinite(samples[i])) {
118 "non-finite sample at index " + std::to_string(i));
120 if (std::fabs(samples[i]) > eps) {
122 "expected silence but sample " + std::to_string(i) +
123 " = " + std::to_string(samples[i]) +
" exceeds +/-" +
124 std::to_string(eps));
136 for (std::size_t i = 0; i < samples.size(); ++i) {
137 if (!std::isfinite(samples[i])) {
139 "non-finite sample at index " + std::to_string(i));
141 if (std::fabs(samples[i]) > peak_limit) {
143 "sample " + std::to_string(i) +
" = " +
144 std::to_string(samples[i]) +
" exceeds peak limit " +
145 std::to_string(peak_limit));
173 float raw,
float tol = 1.0e-4f) {
174 if (!std::isfinite(raw)) {
177 const float span = range.
max - range.
min;
178 const float scale = std::max(1.0f, std::max(std::fabs(range.
min),
179 std::fabs(range.
max)));
180 const float eff_tol = std::max(tol, 1.0e-4f * scale);
182 const float normalized = range.
normalize(raw);
183 if (!std::isfinite(normalized) || normalized < -eff_tol ||
184 normalized > 1.0f + eff_tol) {
186 ") = " + std::to_string(normalized) +
187 " is not finite/in [0,1]");
189 const float projected = range.
denormalize(normalized);
190 if (!std::isfinite(projected) || projected < range.
min - eff_tol ||
191 projected > range.
max + eff_tol) {
193 "denormalized value " + std::to_string(projected) +
194 " is not finite/in range [" + std::to_string(range.
min) +
", " +
195 std::to_string(range.
max) +
"]");
202 float expected = std::clamp(raw, range.
min, range.
max);
203 if (range.
step > 0.0f) {
204 expected = range.
min +
205 std::round((expected - range.
min) / range.
step) *
207 expected = std::clamp(expected, range.
min, range.
max);
209 if (std::fabs(projected - expected) > eff_tol) {
211 "linear map wrong: expected " + std::to_string(expected) +
212 ", got " + std::to_string(projected));
221 if (std::fabs(at_zero - range.
min) > eff_tol ||
222 std::fabs(at_one - range.
max) > eff_tol) {
224 "skewed map endpoints off: denorm(0)=" +
225 std::to_string(at_zero) +
" denorm(1)=" +
226 std::to_string(at_one));
232 if (std::fabs(reprojected - projected) > eff_tol) {
234 "skewed round trip not stable: " + std::to_string(projected) +
235 " -> " + std::to_string(reprojected));
258 const std::vector<uint8_t> first =
host.save_state();
266 auto& store =
host.state();
267 bool perturbed_any =
false;
268 for (
const auto& info : store.all_params()) {
269 const auto& r = info.range;
270 if (!(r.max > r.min)) {
273 const float current = store.get_value(info.id);
274 const float alt = (current - r.min) >= (r.max - current) ? r.min : r.max;
275 if (alt != current) {
276 store.set_value(info.id, alt);
277 perturbed_any =
true;
281 if (!
host.load_state(first)) {
284 const std::vector<uint8_t> second =
host.save_state();
285 if (first != second) {
287 "state not restored across save/perturb/load/save (" +
288 std::to_string(first.size()) +
" vs " +
289 std::to_string(second.size()) +
290 " bytes; load_state may be ignoring the blob)");
292 if (!perturbed_any) {
297 "save/load determinism only: no perturbable "
298 "parameters to prove load actually restores"};
307 std::span<const uint8_t> actual) {
308 if (expected.size() != actual.size()) {
310 "sysex length mismatch: expected " +
311 std::to_string(expected.size()) +
" bytes, got " +
312 std::to_string(actual.size()));
314 for (std::size_t i = 0; i < expected.size(); ++i) {
315 if (expected[i] != actual[i]) {
317 "sysex byte " + std::to_string(i) +
" mismatch: expected " +
318 std::to_string(expected[i]) +
", got " +
319 std::to_string(actual[i]));
338 if (expected.
size() != actual.
size()) {
340 "short-message count mismatch: expected " +
341 std::to_string(expected.
size()) +
", got " +
342 std::to_string(actual.
size()));
344 for (std::size_t i = 0; i < expected.
size(); ++i) {
345 const auto& e = expected[i];
346 const auto& a = actual[i];
347 if (e.sample_offset != a.sample_offset) {
349 "event " + std::to_string(i) +
" sample_offset mismatch: " +
350 std::to_string(e.sample_offset) +
" vs " +
351 std::to_string(a.sample_offset));
353 if (e.size() != a.size()) {
355 "event " + std::to_string(i) +
" byte-length mismatch: " +
356 std::to_string(e.size()) +
" vs " + std::to_string(a.size()));
358 for (uint32_t b = 0; b < e.size(); ++b) {
359 if (e.data()[b] != a.data()[b]) {
361 "event " + std::to_string(i) +
" byte " +
362 std::to_string(b) +
" mismatch");
367 const auto& expected_sysex = expected.
sysex();
368 const auto& actual_sysex = actual.
sysex();
369 if (expected_sysex.size() != actual_sysex.size()) {
371 "sysex count mismatch: expected " +
372 std::to_string(expected_sysex.size()) +
", got " +
373 std::to_string(actual_sysex.size()));
375 for (std::size_t i = 0; i < expected_sysex.size(); ++i) {
376 if (expected_sysex[i].sample_offset != actual_sysex[i].sample_offset) {
378 "sysex " + std::to_string(i) +
" sample_offset mismatch");
380 const auto expected_bytes = expected_sysex[i].data.to_vector();
381 const auto actual_bytes = actual_sysex[i].data.to_vector();
385 byte_result.message);
393 const std::size_t expected_ump_count = expected_ump ? expected_ump->
size() : 0;
394 const std::size_t actual_ump_count = actual_ump ? actual_ump->
size() : 0;
395 if (expected_ump_count != actual_ump_count) {
397 "UMP packet count mismatch: expected " +
398 std::to_string(expected_ump_count) +
", got " +
399 std::to_string(actual_ump_count));
401 for (std::size_t i = 0; i < expected_ump_count; ++i) {
402 const auto& e = (*expected_ump)[i];
403 const auto& a = (*actual_ump)[i];
404 if (e.sample_offset != a.sample_offset) {
406 "UMP " + std::to_string(i) +
" sample_offset mismatch");
408 if (e.packet.word_count != a.packet.word_count) {
410 "UMP " + std::to_string(i) +
" word_count mismatch");
412 for (
int w = 0; w < e.packet.word_count && w < 4; ++w) {
413 if (e.packet.words[w] != a.packet.words[w]) {
415 " word " + std::to_string(w) +
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
std::size_t size() const
Definition buffer.hpp:123
const std::vector< SysexEvent > & sysex() const
Definition buffer.hpp:416
const class UmpBuffer * ump() const
Attached UmpBuffer or nullptr.
Definition buffer.hpp:217
A time-ordered list of UMP events for one audio block.
Definition ump_buffer.hpp:41
std::size_t size() const
Definition ump_buffer.hpp:96
Headless processing adapter for programmatic plugin use without UI or audio device.
Bounded event contracts shared by host ingress and graph-authored nodes.
Definition anticipation_eligibility.hpp:9
Defines the numeric range of a parameter, including normalization.
Definition parameter.hpp:131
bool is_linear() const
True when this range is a plain affine (linear) map — the common case.
Definition parameter.hpp:163
float min
Definition parameter.hpp:132
float denormalize(float normalized) const
Map a normalized [0, 1] value back to the real range.
Definition parameter.hpp:188
float max
Definition parameter.hpp:133
float normalize(float value) const
Map a real value to the normalized [0, 1] range.
Definition parameter.hpp:168
float step
Quantization step. 0 = continuous.
Definition parameter.hpp:135
Sample-accurate MIDI 2.0 UMP packet stream.