deck: filter mod moves to FILTER ENV, cell runs centre in their reserves, two-segment toggles become single buttons, deck focuses its overlay
This commit is contained in:
@@ -120,47 +120,58 @@ static void testOnlyALiveControlsDragTakesTheLiveTier() {
|
||||
LiveCommit::Reload);
|
||||
}
|
||||
|
||||
// --- The overlay selection state machine ---------------------------------------
|
||||
// --- The overlay focus state machine -------------------------------------------
|
||||
|
||||
static int radio(DeckParam p) { return static_cast<int>(p); }
|
||||
|
||||
// EXCLUSIVITY: picking another deck's radio switches to it outright — two envelopes can never
|
||||
// be overlay-active at once, whatever the previous selection was.
|
||||
static void testOverlaySelectionIsExclusiveAcrossTheThreeEnvelopeDecks() {
|
||||
const OverlayEnv states[] = {OverlayEnv::kNone, OverlayEnv::kAmp, OverlayEnv::kPitch,
|
||||
OverlayEnv::kFilter};
|
||||
for (OverlayEnv from : states) {
|
||||
if (from != OverlayEnv::kAmp) {
|
||||
CHECK(nextOverlaySelection(from, radio(DeckParam::kAmpEnvSelect)) == OverlayEnv::kAmp);
|
||||
}
|
||||
if (from != OverlayEnv::kPitch) {
|
||||
CHECK(nextOverlaySelection(from, radio(DeckParam::kPitchEnvSelect)) ==
|
||||
OverlayEnv::kPitch);
|
||||
}
|
||||
if (from != OverlayEnv::kFilter) {
|
||||
CHECK(nextOverlaySelection(from, radio(DeckParam::kFilterEnvSelect)) ==
|
||||
OverlayEnv::kFilter);
|
||||
// EXCLUSIVITY, and the whole of it: the focus is a function of the clicked GROUP alone, so
|
||||
// wherever it was before, clicking an envelope deck lands on that deck's envelope. Two
|
||||
// envelopes can never be overlay-active at once, and no previous state can change the answer.
|
||||
static void testOverlayFocusIsExclusiveAndIndependentOfThePreviousSelection() {
|
||||
const struct { int group; OverlayEnv env; } decks[] = {
|
||||
{kGroupAmpEnv, OverlayEnv::kAmp},
|
||||
{kGroupPitchEnv, OverlayEnv::kPitch},
|
||||
{kGroupFilterEnv, OverlayEnv::kFilter},
|
||||
};
|
||||
for (const auto& d : decks) CHECK(overlayEnvForGroup(d.group) == d.env);
|
||||
// Distinct answers, so no two decks can select the same overlay.
|
||||
CHECK(overlayEnvForGroup(kGroupAmpEnv) != overlayEnvForGroup(kGroupPitchEnv));
|
||||
CHECK(overlayEnvForGroup(kGroupPitchEnv) != overlayEnvForGroup(kGroupFilterEnv));
|
||||
CHECK(overlayEnvForGroup(kGroupAmpEnv) != overlayEnvForGroup(kGroupFilterEnv));
|
||||
}
|
||||
|
||||
// Focus SETS; it does not toggle. Driven as the SHELL drives it — `focus = f(group)` over a
|
||||
// click sequence starting from every prior focus — because that composition is the thing the
|
||||
// retired re-click-clears branch broke: a second click on the focused deck (which is every
|
||||
// knob tweak on it) landed back on kNone. The map taking no current focus is what makes that
|
||||
// unreachable; this pins the sequence a reader would otherwise have to reconstruct.
|
||||
static void testAClickSequenceOnOneDeckNeverLeavesIt() {
|
||||
for (OverlayEnv prior : {OverlayEnv::kNone, OverlayEnv::kAmp, OverlayEnv::kPitch,
|
||||
OverlayEnv::kFilter}) {
|
||||
OverlayEnv focus = prior;
|
||||
// Panel, then knob, then button — all three land in the same group, so all three are
|
||||
// the same assignment, whatever the click before them was.
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
focus = overlayEnvForGroup(kGroupFilterEnv);
|
||||
CHECK(focus == OverlayEnv::kFilter);
|
||||
}
|
||||
// And leaving is a click ELSEWHERE, never a repeat of the one that got here.
|
||||
focus = overlayEnvForGroup(kGroupVoice);
|
||||
CHECK(focus == OverlayEnv::kNone);
|
||||
}
|
||||
}
|
||||
|
||||
// kNone is a RESTING STATE the user can get back to: clicking the active radio clears it.
|
||||
static void testClickingTheActiveOverlayRadioClearsToNone() {
|
||||
CHECK(nextOverlaySelection(OverlayEnv::kAmp, radio(DeckParam::kAmpEnvSelect)) ==
|
||||
OverlayEnv::kNone);
|
||||
CHECK(nextOverlaySelection(OverlayEnv::kPitch, radio(DeckParam::kPitchEnvSelect)) ==
|
||||
OverlayEnv::kNone);
|
||||
CHECK(nextOverlaySelection(OverlayEnv::kFilter, radio(DeckParam::kFilterEnvSelect)) ==
|
||||
OverlayEnv::kNone);
|
||||
}
|
||||
|
||||
// A control that is not one of the three radios selects nothing and clears nothing.
|
||||
static void testANonRadioIdLeavesTheOverlaySelectionAlone() {
|
||||
CHECK(overlayEnvForRadio(radio(DeckParam::kFilterCutoff)) == OverlayEnv::kNone);
|
||||
CHECK(overlayEnvForRadio(-1) == OverlayEnv::kNone);
|
||||
CHECK(nextOverlaySelection(OverlayEnv::kFilter, radio(DeckParam::kFilterCutoff)) ==
|
||||
OverlayEnv::kFilter);
|
||||
CHECK(nextOverlaySelection(OverlayEnv::kAmp, 9999) == OverlayEnv::kAmp);
|
||||
// kNone is still a reachable resting state — reached by clicking a control surface OUTSIDE the
|
||||
// envelope decks rather than by clicking the active one again.
|
||||
static void testClickingAnyNonEnvelopeDeckClearsTheFocus() {
|
||||
for (int id : {kGroupPitch, kGroupFilter, kGroupVelocity, kGroupVoice, kGroupMaster}) {
|
||||
CHECK(overlayEnvForGroup(id) == OverlayEnv::kNone);
|
||||
}
|
||||
CHECK(overlayEnvForGroup(-1) == OverlayEnv::kNone); // off the deck entirely
|
||||
// A CONTROL id is not a group id: the map keys on groups now, and a stray control id must
|
||||
// never light an overlay by numeric coincidence.
|
||||
CHECK(overlayEnvForGroup(radio(DeckParam::kAmpEnvSelect)) == OverlayEnv::kNone);
|
||||
CHECK(overlayEnvForGroup(radio(DeckParam::kFilterCutoff)) == OverlayEnv::kNone);
|
||||
}
|
||||
|
||||
// The two group gates, spelled the way the predicates read them. Spline flags default off, so
|
||||
@@ -215,25 +226,22 @@ static void testDeckKnobIsInertExactlyWithItsGroupsEnableToggle() {
|
||||
// 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() {
|
||||
static void testAModeToggleIsNotALiveControl() {
|
||||
CHECK(deckParamCommit(DeckParam::kAmpEnvMode) == LiveCommit::Reload);
|
||||
CHECK(deckParamCommit(DeckParam::kPitchEnvMode) == LiveCommit::Reload);
|
||||
CHECK(deckParamCommit(DeckParam::kFilterEnvMode) == LiveCommit::Reload);
|
||||
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);
|
||||
// It needs no overlay map of its own: the toggle sits INSIDE its envelope's deck, so the
|
||||
// click that flips it already focuses that envelope through the group map.
|
||||
CHECK(overlayEnvForGroup(kGroupAmpEnv) == OverlayEnv::kAmp);
|
||||
}
|
||||
|
||||
int main() {
|
||||
testOverlaySelectionIsExclusiveAcrossTheThreeEnvelopeDecks();
|
||||
testClickingTheActiveOverlayRadioClearsToNone();
|
||||
testANonRadioIdLeavesTheOverlaySelectionAlone();
|
||||
testOverlayFocusIsExclusiveAndIndependentOfThePreviousSelection();
|
||||
testAClickSequenceOnOneDeckNeverLeavesIt();
|
||||
testClickingAnyNonEnvelopeDeckClearsTheFocus();
|
||||
testOverlayIsInertExactlyWhenItsGroupToggleIsOff();
|
||||
testDeckKnobIsInertExactlyWithItsGroupsEnableToggle();
|
||||
testAModeToggleIsNeitherLiveNorAnOverlayRadio();
|
||||
testAModeToggleIsNotALiveControl();
|
||||
testEveryDeckControlIsClassifiedIntoOneOfTheThreeCommitTiers();
|
||||
testOnlyALiveControlsDragTakesTheLiveTier();
|
||||
if (g_fail == 0) std::printf("deck_groups_state: all tests passed\n");
|
||||
|
||||
Reference in New Issue
Block a user