Bound the automation hold to the window the model has not caught up on, and make that authority model stated, enforced and tested
This commit is contained in:
+51
-12
@@ -1,6 +1,7 @@
|
||||
// Standalone tests for the audio thread's parameter patch. The load-bearing one is the
|
||||
// EQUIVALENCE assertion: patching a control into the live block must produce, bit for bit, the
|
||||
// block the model path would have folded — which is what makes a second routing table safe.
|
||||
// Standalone tests for a host parameter write, both sides of the model/audio split. The
|
||||
// load-bearing one is the EQUIVALENCE assertion: patching a control into the live block must
|
||||
// produce exactly the block the model path would have folded after the same write — which is what
|
||||
// makes a second routing table safe.
|
||||
|
||||
#include "../src/core/instrument/param/param_live.h"
|
||||
|
||||
@@ -8,9 +9,10 @@
|
||||
#include "../src/core/instrument/param/param_units.h"
|
||||
#include "../src/core/instrument/map/sample_map.h"
|
||||
#include "../src/core/instrument/ui/deck_values.h"
|
||||
#include "../src/core/util/curve_law.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
using namespace reasampler;
|
||||
using namespace reasampler::instrument::param;
|
||||
@@ -71,9 +73,13 @@ InstrumentParams dialledParams() {
|
||||
} // namespace
|
||||
|
||||
// THE assertion this module exists for. For every exposed control and several normalized
|
||||
// positions: writing it through the model and folding must equal patching it into the folded
|
||||
// block. Bytes, not fields — a member the patch forgot to route is caught as surely as one it
|
||||
// routed to the wrong place.
|
||||
// positions: writing it through the MODEL side of a host write and folding must equal patching it
|
||||
// into the folded block. Whole-block, not per-field — a member the patch forgot to route is
|
||||
// caught as surely as one it routed to the wrong place. Compared through live_params' own
|
||||
// field-wise operator==, NOT a memcmp: the block carries padding no copy is required to preserve,
|
||||
// so a byte compare here was non-deterministic. Both sides are the HOST's paths, which is what
|
||||
// the shell actually calls; that the host's value map agrees with the editor's everywhere it
|
||||
// should is the separate assertion below.
|
||||
static void testPatchingAControlEqualsFoldingTheModelAfterTheSameWrite() {
|
||||
const double kPositions[] = {0.0, 0.137, 0.5, 0.813, 1.0};
|
||||
for (const ParamRow& row : exposedParams()) {
|
||||
@@ -83,7 +89,7 @@ static void testPatchingAControlEqualsFoldingTheModelAfterTheSameWrite() {
|
||||
LiveValues block = modelBlock(dialledParams());
|
||||
const LiveValues before = block;
|
||||
CHECK_ID(!applyLiveParam(block, row.deck, 0.25, kRate), row.id);
|
||||
CHECK_ID(std::memcmp(&before, &block, sizeof(LiveValues)) == 0, row.id);
|
||||
CHECK_ID(before == block, row.id);
|
||||
continue;
|
||||
}
|
||||
for (double norm : kPositions) {
|
||||
@@ -91,14 +97,13 @@ static void testPatchingAControlEqualsFoldingTheModelAfterTheSameWrite() {
|
||||
if (row.deck == DeckParam::kKeyTrack) {
|
||||
written.keyTrack = reasampler::instrument::ui::keyTrackFromNorm(norm);
|
||||
} else {
|
||||
reasampler::instrument::ui::setDeckParam(row.deck, written.play, norm,
|
||||
/*segment=*/0);
|
||||
CHECK_ID(writeHostParam(row.deck, written.play, norm), row.id);
|
||||
}
|
||||
const LiveValues expected = modelBlock(written);
|
||||
|
||||
LiveValues patched = modelBlock(dialledParams());
|
||||
CHECK_ID(applyLiveParam(patched, row.deck, norm, kRate), row.id);
|
||||
CHECK_ID(std::memcmp(&expected, &patched, sizeof(LiveValues)) == 0, row.id);
|
||||
CHECK_ID(expected == patched, row.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,7 +127,7 @@ static void testAnUnexposedControlIsRefused() {
|
||||
const LiveValues before = block;
|
||||
CHECK(!applyLiveParam(block, DeckParam::kPlayMode, 1.0, kRate));
|
||||
CHECK(!applyLiveParam(block, DeckParam::kVoiceCount, 1.0, kRate));
|
||||
CHECK(std::memcmp(&before, &block, sizeof(LiveValues)) == 0);
|
||||
CHECK(before == block);
|
||||
}
|
||||
|
||||
// Every exposed control resolves to a home the host's read and write paths actually reach. The
|
||||
@@ -142,11 +147,45 @@ static void testEveryExposedControlHasAValueHome() {
|
||||
CHECK(instanceScalars == 2);
|
||||
}
|
||||
|
||||
// The host's value map is the editor's EXCEPT on the twelve curve exponents, where it skips the
|
||||
// knob detent — a drag affordance a lane has no use for and which would otherwise flatten a
|
||||
// knot-drawn near-neutral exponent to exactly 1.0 on any lane pass. Both halves are asserted: the
|
||||
// agreement everywhere else, and the difference exactly inside the detent band.
|
||||
static void testTheHostSkipsTheCurveDetentAndNothingElse() {
|
||||
using reasampler::instrument::ui::deckParamUnit;
|
||||
using reasampler::instrument::ui::storedFromNorm;
|
||||
using reasampler::instrument::ui::UnitCategory;
|
||||
const double kPositions[] = {0.0, 0.137, 0.4, 0.495, 0.5, 0.505, 0.6, 0.813, 1.0};
|
||||
for (const ParamRow& row : exposedParams()) {
|
||||
const bool exponent = deckParamUnit(row.deck) == UnitCategory::Exponent;
|
||||
for (double norm : kPositions) {
|
||||
const double editor = storedFromNorm(row.deck, norm);
|
||||
const double host = hostStoredFromNorm(row.deck, norm);
|
||||
// Inside the band but off centre is the ONE place they may differ, and must.
|
||||
const bool inBand = exponent && norm != 0.5 &&
|
||||
norm > 0.5 - reasampler::util::kCurveKnobDetent &&
|
||||
norm < 0.5 + reasampler::util::kCurveKnobDetent;
|
||||
if (inBand) {
|
||||
CHECK_ID(editor == reasampler::util::kCurveNeutral, row.id);
|
||||
CHECK_ID(host != editor, row.id);
|
||||
} else {
|
||||
CHECK_ID(host == editor, row.id);
|
||||
}
|
||||
}
|
||||
// The identity stays reachable from the host side too — that is what makes skipping the
|
||||
// detent a value-preserving change rather than a lost reset.
|
||||
if (exponent) {
|
||||
CHECK_ID(hostStoredFromNorm(row.deck, 0.5) == reasampler::util::kCurveNeutral, row.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
testPatchingAControlEqualsFoldingTheModelAfterTheSameWrite();
|
||||
testTriggerLengthIsInertUnderADrawnEnvelope();
|
||||
testAnUnexposedControlIsRefused();
|
||||
testEveryExposedControlHasAValueHome();
|
||||
testTheHostSkipsTheCurveDetentAndNothingElse();
|
||||
if (g_fail == 0) std::printf("param_live: all tests passed\n");
|
||||
return g_fail == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user