instrument: snap live params onto a fresh voice, roll a live drag back on capture loss, serialize the seqlock's two writers
This commit is contained in:
+46
-11
@@ -2,8 +2,9 @@
|
||||
// framework. knob_deck's own tests pin how a descriptor list LAYS OUT; these pin WHICH
|
||||
// descriptors the Sample face carries: the signal-flow group order (pitch -> filter -> amp),
|
||||
// the Filter group's contents, the wrapped deck height at the editor's floor width and its fit
|
||||
// inside the floor window, the hit-test reaching the new filter controls, and the bipolar knob
|
||||
// law's inverse pair.
|
||||
// inside the floor window, the hit-test reaching the new filter controls, the bipolar knob
|
||||
// law's inverse pair, and the commit-tier routing — which controls are live, and which drags
|
||||
// take the live tier.
|
||||
|
||||
#include "../src/core/instrument/ui/deck_groups.h"
|
||||
#include "../src/core/instrument/ui/sample_bands.h"
|
||||
@@ -186,7 +187,7 @@ static void testBipolarKnobLawRoundTripsAndIsExactAtCentre() {
|
||||
CHECK(deckNormFromBipolar(3.0) == 1.0);
|
||||
}
|
||||
|
||||
static void testLiveRoutingCoversEveryContinuousPlaybackControl() {
|
||||
static void testEveryDeckControlIsClassifiedLiveOrReloading() {
|
||||
// The live set: the six filter tone/modulation knobs, plus every stage time and stage
|
||||
// level on all three envelopes.
|
||||
const DeckParam live[] = {
|
||||
@@ -200,9 +201,8 @@ static void testLiveRoutingCoversEveryContinuousPlaybackControl() {
|
||||
};
|
||||
for (DeckParam p : live) CHECK(isLiveDeckParam(p));
|
||||
|
||||
// Everything else reloads or rebuilds. kFilterVel and kKeyTrack are the two that look
|
||||
// continuous but feed values a voice latches at note-on (the velocity-curve result and
|
||||
// the pitch ratio) — making them live would re-gain or retune a note already struck.
|
||||
// Everything else reloads or rebuilds; deck_groups.h is the home for why each exclusion
|
||||
// is excluded.
|
||||
const DeckParam reloads[] = {
|
||||
DeckParam::kPlayMode, DeckParam::kPitchEngine, DeckParam::kPitchEnvEnable,
|
||||
DeckParam::kFilterEnable, DeckParam::kFilterLaw, DeckParam::kFilterVel,
|
||||
@@ -212,14 +212,49 @@ static void testLiveRoutingCoversEveryContinuousPlaybackControl() {
|
||||
};
|
||||
for (DeckParam p : reloads) CHECK(!isLiveDeckParam(p));
|
||||
|
||||
// Every id in the space is classified by one of the two lists above, so a control added
|
||||
// later cannot slip through unclassified.
|
||||
const std::size_t classified = (sizeof(live) + sizeof(reloads)) / sizeof(DeckParam);
|
||||
CHECK(classified == static_cast<std::size_t>(DeckParam::kCount));
|
||||
// COVERAGE, not cardinality: every id appears in EXACTLY ONE of the two lists. A sum check
|
||||
// would stay green if an edit duplicated one id and dropped another, leaving that one
|
||||
// unclassified.
|
||||
for (int i = 0; i < static_cast<int>(DeckParam::kCount); ++i) {
|
||||
const DeckParam p = static_cast<DeckParam>(i);
|
||||
int seen = 0;
|
||||
for (DeckParam q : live) if (q == p) ++seen;
|
||||
for (DeckParam q : reloads) if (q == p) ++seen;
|
||||
if (seen != 1) std::printf(" (deck id %d classified %d times)\n", i, seen);
|
||||
CHECK(seen == 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void testOnlyALiveControlsDragTakesTheLiveTier() {
|
||||
// isLiveDeckParam alone is not what a user experiences — liveCommitFor is, at the editor's
|
||||
// commit site. Inverting it has to FAIL a test rather than merely read wrong.
|
||||
CHECK(liveCommitFor(LiveDragKind::kDeckKnob, static_cast<int>(DeckParam::kFilterCutoff),
|
||||
PlayMode::Gate));
|
||||
// A knob's routing is the knob's, not the play mode's.
|
||||
CHECK(liveCommitFor(LiveDragKind::kDeckKnob, static_cast<int>(DeckParam::kAttack),
|
||||
PlayMode::Trigger));
|
||||
CHECK(!liveCommitFor(LiveDragKind::kDeckKnob, static_cast<int>(DeckParam::kTrigFadeIn),
|
||||
PlayMode::Trigger));
|
||||
CHECK(!liveCommitFor(LiveDragKind::kDeckKnob, static_cast<int>(DeckParam::kMasterGain),
|
||||
PlayMode::Gate));
|
||||
// The shell's processor-side sentinels (preview velocity is -2) and any out-of-range id
|
||||
// are not parameter-set controls, so they must never reach the enum.
|
||||
CHECK(!liveCommitFor(LiveDragKind::kDeckKnob, -2, PlayMode::Gate));
|
||||
CHECK(!liveCommitFor(LiveDragKind::kDeckKnob, -1, PlayMode::Gate));
|
||||
CHECK(!liveCommitFor(LiveDragKind::kDeckKnob, static_cast<int>(DeckParam::kCount),
|
||||
PlayMode::Gate));
|
||||
// An envelope-node drag edits the AHDSR in Gate; the same drag in Trigger rewrites the
|
||||
// play span, which is not a live control.
|
||||
CHECK(liveCommitFor(LiveDragKind::kEnvNode, -1, PlayMode::Gate));
|
||||
CHECK(!liveCommitFor(LiveDragKind::kEnvNode, -1, PlayMode::Trigger));
|
||||
// Every other drag (markers, scrollbar, curve nodes) commits through a reload.
|
||||
CHECK(!liveCommitFor(LiveDragKind::kOther, static_cast<int>(DeckParam::kFilterCutoff),
|
||||
PlayMode::Gate));
|
||||
}
|
||||
|
||||
int main() {
|
||||
testLiveRoutingCoversEveryContinuousPlaybackControl();
|
||||
testEveryDeckControlIsClassifiedLiveOrReloading();
|
||||
testOnlyALiveControlsDragTakesTheLiveTier();
|
||||
testDeckReadsPitchThenFilterThenAmpLeftToRight();
|
||||
testFilterGroupCarriesItsFiveToneControlsPlusModulation();
|
||||
testAmpGroupWidthSurvivesAGateTriggerFlip();
|
||||
|
||||
Reference in New Issue
Block a user