instrument: spline EGs — hard points on the one shared spline, a drawn contour per envelope beside its staged state, payload v13
This commit is contained in:
+67
-15
@@ -366,6 +366,7 @@ static void testEveryDeckControlIsClassifiedLiveOrReloading() {
|
||||
DeckParam::kAmpVelCurve, DeckParam::kPitchVelCurve, DeckParam::kFilterVelCurve,
|
||||
DeckParam::kKeyTrack, DeckParam::kTrigLength,
|
||||
DeckParam::kAmpEnvSelect, DeckParam::kPitchEnvSelect, DeckParam::kFilterEnvSelect,
|
||||
DeckParam::kAmpEnvMode, DeckParam::kPitchEnvMode, DeckParam::kFilterEnvMode,
|
||||
DeckParam::kVoiceCount, DeckParam::kVoiceMode,
|
||||
DeckParam::kMonoTrigger, DeckParam::kMasterGain,
|
||||
};
|
||||
@@ -450,16 +451,36 @@ static void testANonRadioIdLeavesTheOverlaySelectionAlone() {
|
||||
CHECK(nextOverlaySelection(OverlayEnv::kAmp, 9999) == OverlayEnv::kAmp);
|
||||
}
|
||||
|
||||
// The two group gates, spelled the way the predicates read them. Spline flags default off, so
|
||||
// a case that says nothing about them is asserting the staged behaviour.
|
||||
static DeckEnableState gates(bool pitchEnv, bool filter) {
|
||||
DeckEnableState s;
|
||||
s.pitchEnvEnabled = pitchEnv;
|
||||
s.filterEnabled = filter;
|
||||
return s;
|
||||
}
|
||||
|
||||
// An overlay whose deck group is switched OFF is inert, matching the drawn-but-dead knobs on
|
||||
// the same params: a node drag must not reach a value the knob refuses.
|
||||
static void testOverlayIsInertExactlyWhenItsGroupToggleIsOff() {
|
||||
CHECK(overlayEnvInert(OverlayEnv::kPitch, /*pitchEnv=*/false, /*filter=*/true));
|
||||
CHECK(!overlayEnvInert(OverlayEnv::kPitch, true, true));
|
||||
CHECK(overlayEnvInert(OverlayEnv::kFilter, true, /*filter=*/false));
|
||||
CHECK(!overlayEnvInert(OverlayEnv::kFilter, true, true));
|
||||
CHECK(overlayEnvInert(OverlayEnv::kPitch, gates(/*pitchEnv=*/false, /*filter=*/true)));
|
||||
CHECK(!overlayEnvInert(OverlayEnv::kPitch, gates(true, true)));
|
||||
CHECK(overlayEnvInert(OverlayEnv::kFilter, gates(true, /*filter=*/false)));
|
||||
CHECK(!overlayEnvInert(OverlayEnv::kFilter, gates(true, true)));
|
||||
// Amp has no enable toggle, so it is never inert; kNone draws nothing to grab.
|
||||
CHECK(!overlayEnvInert(OverlayEnv::kAmp, false, false));
|
||||
CHECK(!overlayEnvInert(OverlayEnv::kNone, false, false));
|
||||
CHECK(!overlayEnvInert(OverlayEnv::kAmp, gates(false, false)));
|
||||
CHECK(!overlayEnvInert(OverlayEnv::kNone, gates(false, false)));
|
||||
|
||||
// The enable gate alone, which the SPLINE overlay reads: it survives a mode switch, so a
|
||||
// disabled group's contour is as dead as its knobs.
|
||||
CHECK(!overlayEnvEnabled(OverlayEnv::kPitch, gates(false, true)));
|
||||
CHECK(overlayEnvEnabled(OverlayEnv::kAmp, gates(false, false)));
|
||||
// ...while the staged overlay additionally goes inert once the envelope is drawn: its
|
||||
// nodes are no longer what the overlay is editing.
|
||||
DeckEnableState drawn = gates(true, true);
|
||||
drawn.ampSpline = true;
|
||||
CHECK(overlayEnvInert(OverlayEnv::kAmp, drawn));
|
||||
CHECK(overlayEnvEnabled(OverlayEnv::kAmp, drawn));
|
||||
}
|
||||
|
||||
// A deck knob goes inert exactly with its group's own enable toggle — including the filter's
|
||||
@@ -467,16 +488,45 @@ static void testOverlayIsInertExactlyWhenItsGroupToggleIsOff() {
|
||||
// go inert with the rest of the filter (the reachable-through-the-deck route mouseDownDeck
|
||||
// checks before ever routing a curve-cell click to the popup).
|
||||
static void testDeckKnobIsInertExactlyWithItsGroupsEnableToggle() {
|
||||
CHECK(deckKnobInert(DeckParam::kFilterVelCurve, /*pitchEnv=*/true, /*filter=*/false));
|
||||
CHECK(!deckKnobInert(DeckParam::kFilterVelCurve, true, true));
|
||||
CHECK(deckKnobInert(DeckParam::kFilterCutoff, true, false));
|
||||
CHECK(!deckKnobInert(DeckParam::kFilterCutoff, true, true));
|
||||
CHECK(deckKnobInert(DeckParam::kPitchEnvDepth, /*pitchEnv=*/false, true));
|
||||
CHECK(!deckKnobInert(DeckParam::kPitchEnvDepth, true, true));
|
||||
CHECK(deckKnobInert(DeckParam::kFilterVelCurve, gates(/*pitchEnv=*/true, /*filter=*/false)));
|
||||
CHECK(!deckKnobInert(DeckParam::kFilterVelCurve, gates(true, true)));
|
||||
CHECK(deckKnobInert(DeckParam::kFilterCutoff, gates(true, false)));
|
||||
CHECK(!deckKnobInert(DeckParam::kFilterCutoff, gates(true, true)));
|
||||
CHECK(deckKnobInert(DeckParam::kPitchEnvDepth, gates(/*pitchEnv=*/false, true)));
|
||||
CHECK(!deckKnobInert(DeckParam::kPitchEnvDepth, gates(true, true)));
|
||||
// The amp's own velocity cell and every ordinary control are never inert here — inertness
|
||||
// is a filter/pitch-env-group-only concept.
|
||||
CHECK(!deckKnobInert(DeckParam::kAmpVelCurve, false, false));
|
||||
CHECK(!deckKnobInert(DeckParam::kAttack, false, false));
|
||||
// is a filter/pitch-env-group-only concept until an envelope is drawn.
|
||||
CHECK(!deckKnobInert(DeckParam::kAmpVelCurve, gates(false, false)));
|
||||
CHECK(!deckKnobInert(DeckParam::kAttack, gates(false, false)));
|
||||
}
|
||||
|
||||
// A drawn envelope's STAGED segment knobs go inert; the mode toggle itself and the depth knobs
|
||||
// that scale either shape stay live. (Which segment knobs, per envelope, is pinned in
|
||||
// spline_egs_tests alongside the rest of the spline rules.)
|
||||
static void testAModeToggleIsNeitherLiveNorAnOverlayRadio() {
|
||||
CHECK(!isLiveDeckParam(DeckParam::kAmpEnvMode));
|
||||
CHECK(!isLiveDeckParam(DeckParam::kPitchEnvMode));
|
||||
CHECK(!isLiveDeckParam(DeckParam::kFilterEnvMode));
|
||||
CHECK(overlayEnvForModeToggle(radio(DeckParam::kAmpEnvMode)) == OverlayEnv::kAmp);
|
||||
CHECK(overlayEnvForModeToggle(radio(DeckParam::kPitchEnvMode)) == OverlayEnv::kPitch);
|
||||
CHECK(overlayEnvForModeToggle(radio(DeckParam::kFilterEnvMode)) == OverlayEnv::kFilter);
|
||||
// A mode toggle must not be mistaken for the overlay-select radio beside it.
|
||||
CHECK(overlayEnvForRadio(radio(DeckParam::kAmpEnvMode)) == OverlayEnv::kNone);
|
||||
CHECK(overlayEnvForModeToggle(radio(DeckParam::kAmpEnvSelect)) == OverlayEnv::kNone);
|
||||
}
|
||||
|
||||
// The three mode toggles ride each env group's caption slack, so the deck's wrapped geometry
|
||||
// is unchanged by them: raising their segment width past the caption headroom would reflow the
|
||||
// first row and push the deck to a fourth one (see testDeckFitsInsideTheEnforcedMinimumWindow).
|
||||
static void testTheModeTogglesCostNoGroupWidth() {
|
||||
for (PlayMode mode : {PlayMode::Gate, PlayMode::Trigger}) {
|
||||
for (const DeckGroupDesc& g : sampleDeckGroups(mode)) {
|
||||
if (g.captionToggle2.id < 0) continue;
|
||||
DeckGroupDesc without = g;
|
||||
without.captionToggle2 = DeckToggleDesc{};
|
||||
CHECK(deckGroupWidth(g) == deckGroupWidth(without));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
@@ -485,6 +535,8 @@ int main() {
|
||||
testANonRadioIdLeavesTheOverlaySelectionAlone();
|
||||
testOverlayIsInertExactlyWhenItsGroupToggleIsOff();
|
||||
testDeckKnobIsInertExactlyWithItsGroupsEnableToggle();
|
||||
testAModeToggleIsNeitherLiveNorAnOverlayRadio();
|
||||
testTheModeTogglesCostNoGroupWidth();
|
||||
testEveryDeckControlIsClassifiedLiveOrReloading();
|
||||
testOnlyALiveControlsDragTakesTheLiveTier();
|
||||
testDeckReadsPitchThenFilterThenAmpLeftToRight();
|
||||
|
||||
Reference in New Issue
Block a user