instrument: one VELOCITY deck for all three velocity curves, bipolar and off by default for pitch and filter
Payload v12 appends the new velocity->pitch curve and folds the retired filter velAmount into its now-bipolar curve, so pre-v12 projects reopen sounding identical. Preview button takes a drawn play triangle.
This commit is contained in:
+78
-13
@@ -1,7 +1,9 @@
|
||||
// Standalone tests for reasampler::instrument::ui::deck_groups — no VST3, no REAPER, no
|
||||
// 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
|
||||
// the Filter group's contents, the VELOCITY group's exclusive ownership of the three curve
|
||||
// cells and its placement immediately left of VOICE, 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, the bipolar knob
|
||||
// law's inverse pair, the commit-tier routing — which controls are live, and which drags take
|
||||
// the live tier — and the overlay-selection state machine (exclusivity, the none resting state,
|
||||
@@ -48,20 +50,77 @@ static void testDeckReadsPitchThenFilterThenAmpLeftToRight() {
|
||||
CHECK(penv < filt);
|
||||
CHECK(filt < fenv);
|
||||
CHECK(fenv < amp);
|
||||
// The two instance-wide groups stay at the end.
|
||||
CHECK(amp < indexOfGroup(g, kGroupVoice));
|
||||
// VELOCITY then the two instance-wide groups at the end. Velocity sits IMMEDIATELY
|
||||
// left of VOICE — MASTER is reserved for post-voice-mixer concerns, so the curves
|
||||
// must not drift into it.
|
||||
const int vel = indexOfGroup(g, kGroupVelocity);
|
||||
CHECK(amp < vel);
|
||||
CHECK(vel + 1 == indexOfGroup(g, kGroupVoice));
|
||||
CHECK(indexOfGroup(g, kGroupVoice) < indexOfGroup(g, kGroupMaster));
|
||||
}
|
||||
}
|
||||
|
||||
static void testFilterGroupCarriesItsFiveToneControlsPlusModulation() {
|
||||
// The three velocity curves live together in VELOCITY and nowhere else: no other group may
|
||||
// carry a curve cell, or the "one home" the group exists for is not one.
|
||||
static void testVelocityGroupOwnsTheThreeCurvesExclusively() {
|
||||
for (PlayMode mode : {PlayMode::Gate, PlayMode::Trigger}) {
|
||||
const std::vector<DeckGroupDesc> g = sampleDeckGroups(mode);
|
||||
const DeckGroupDesc& v =
|
||||
g[static_cast<std::size_t>(indexOfGroup(g, kGroupVelocity))];
|
||||
const std::vector<int> expected = {cell(DeckParam::kAmpVelCurve),
|
||||
cell(DeckParam::kPitchVelCurve),
|
||||
cell(DeckParam::kFilterVelCurve)};
|
||||
CHECK(v.cellIds == expected);
|
||||
CHECK(v.captionToggle.id == -1 && v.rowToggle.id == -1 && v.captionRadio.id == -1);
|
||||
for (const DeckGroupDesc& d : g) {
|
||||
if (d.id == kGroupVelocity) continue;
|
||||
for (int id : d.cellIds) CHECK(curveTargetFor(id) == CurveTarget::kNone);
|
||||
CHECK(curveTargetFor(d.captionToggle.id) == CurveTarget::kNone);
|
||||
CHECK(curveTargetFor(d.rowToggle.id) == CurveTarget::kNone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Each curve cell names its OWN destination, and an ordinary knob names none — the predicate
|
||||
// the shell uses to tell a popup opener from a dial.
|
||||
static void testCurveTargetNamesEachCellsOwnDestination() {
|
||||
CHECK(curveTargetFor(cell(DeckParam::kAmpVelCurve)) == CurveTarget::kAmp);
|
||||
CHECK(curveTargetFor(cell(DeckParam::kPitchVelCurve)) == CurveTarget::kPitch);
|
||||
CHECK(curveTargetFor(cell(DeckParam::kFilterVelCurve)) == CurveTarget::kFilter);
|
||||
CHECK(curveTargetFor(cell(DeckParam::kFilterCutoff)) == CurveTarget::kNone);
|
||||
CHECK(curveTargetFor(cell(DeckParam::kMasterGain)) == CurveTarget::kNone);
|
||||
CHECK(curveTargetFor(-1) == CurveTarget::kNone); // a blank reserved cell
|
||||
CHECK(curveTargetFor(9999) == CurveTarget::kNone); // out of the id space
|
||||
}
|
||||
|
||||
// The cells hit-test inside their own group, from the centre of each cell — the deck grammar
|
||||
// treats them as knob cells, so the popup routing rides an ordinary Knob hit.
|
||||
static void testVelocityCellsHitTestWithinTheirGroup() {
|
||||
const std::vector<DeckGroupDesc> g = sampleDeckGroups(PlayMode::Gate);
|
||||
const DeckLayout dl = layoutDeck(g, kPad, 40, kAvailAtMinWidth);
|
||||
const DeckGroupLayout& v =
|
||||
dl.groups[static_cast<std::size_t>(indexOfGroup(g, kGroupVelocity))];
|
||||
CHECK(v.cells.size() == 3);
|
||||
const CurveTarget want[] = {CurveTarget::kAmp, CurveTarget::kPitch, CurveTarget::kFilter};
|
||||
for (std::size_t i = 0; i < v.cells.size(); ++i) {
|
||||
const DeckCellLayout& c = v.cells[i];
|
||||
const DeckHit hit = hitTestDeck(dl, c.cell.x + c.cell.width / 2,
|
||||
c.cell.y + c.cell.height / 2);
|
||||
CHECK(hit.kind == DeckHitKind::Knob);
|
||||
CHECK(hit.id == c.id);
|
||||
CHECK(curveTargetFor(hit.id) == want[i]);
|
||||
// Inside its own group box, and the cell the hit resolved is this one.
|
||||
CHECK(c.cell.x >= v.box.x && c.cell.right() <= v.box.right());
|
||||
}
|
||||
}
|
||||
|
||||
static void testFilterGroupCarriesItsToneControlsPlusModulation() {
|
||||
const std::vector<DeckGroupDesc>& g = sampleDeckGroups(PlayMode::Gate);
|
||||
const DeckGroupDesc& f = g[static_cast<std::size_t>(indexOfGroup(g, kGroupFilter))];
|
||||
const std::vector<int> expected = {
|
||||
cell(DeckParam::kFilterMorph), cell(DeckParam::kFilterCutoff),
|
||||
cell(DeckParam::kFilterQ), cell(DeckParam::kFilterDrive),
|
||||
cell(DeckParam::kFilterModAmt), cell(DeckParam::kFilterVel),
|
||||
cell(DeckParam::kFilterKeyTrack)};
|
||||
cell(DeckParam::kFilterModAmt), cell(DeckParam::kFilterKeyTrack)};
|
||||
CHECK(f.cellIds == expected);
|
||||
// Off by default is a state question, but reachability is a layout one: the enable
|
||||
// toggle is in the caption row and the morph law in the knob row.
|
||||
@@ -177,10 +236,12 @@ static void testAmpGroupWidthSurvivesAGateTriggerFlip() {
|
||||
|
||||
static void testWrappedDeckHeightAtTheEditorFloorWidth() {
|
||||
const std::vector<DeckGroupDesc> g = sampleDeckGroups(PlayMode::Gate);
|
||||
// At the floor (== default) 840 the deck takes two rows: PITCH + PITCH ENV + FILTER fill
|
||||
// the first, the remaining four fit the second.
|
||||
CHECK(deckRowCount(g, kAvailAtMinWidth) == 2);
|
||||
CHECK(deckHeight(g, kAvailAtMinWidth) == 2 * kDeckGroupH + kDeckRowGap);
|
||||
// At the floor (== default) 840 the deck takes three rows: PITCH + PITCH ENV + FILTER fill
|
||||
// the first, FILTER ENV + AMP + VELOCITY the second, VOICE + MASTER the third. The eight
|
||||
// groups total more than two rows can hold at this width — the VELOCITY group's three
|
||||
// cells are ~150 px more than the FILTER group gave back when its velocity depth retired.
|
||||
CHECK(deckRowCount(g, kAvailAtMinWidth) == 3);
|
||||
CHECK(deckHeight(g, kAvailAtMinWidth) == 3 * kDeckGroupH + 2 * kDeckRowGap);
|
||||
|
||||
// Whole groups only, never split: every group's box lies inside the available width or is
|
||||
// the first of its row.
|
||||
@@ -224,7 +285,7 @@ static void testHitTestResolvesTheNewFilterControls() {
|
||||
CHECK(hit.kind == DeckHitKind::Knob);
|
||||
CHECK(hit.id == c.id);
|
||||
}
|
||||
CHECK(f.cells.size() == 7);
|
||||
CHECK(f.cells.size() == 6);
|
||||
CHECK(f.cells[1].id == cell(DeckParam::kFilterCutoff));
|
||||
|
||||
// The enable toggle's two segments and the morph-law row toggle's two.
|
||||
@@ -298,7 +359,8 @@ static void testEveryDeckControlIsClassifiedLiveOrReloading() {
|
||||
// is excluded.
|
||||
const DeckParam reloads[] = {
|
||||
DeckParam::kPlayMode, DeckParam::kPitchEngine, DeckParam::kPitchEnvEnable,
|
||||
DeckParam::kFilterEnable, DeckParam::kFilterLaw, DeckParam::kFilterVel,
|
||||
DeckParam::kFilterEnable, DeckParam::kFilterLaw,
|
||||
DeckParam::kAmpVelCurve, DeckParam::kPitchVelCurve, DeckParam::kFilterVelCurve,
|
||||
DeckParam::kKeyTrack, DeckParam::kTrigLength,
|
||||
DeckParam::kAmpEnvSelect, DeckParam::kPitchEnvSelect, DeckParam::kFilterEnvSelect,
|
||||
DeckParam::kVoiceCount, DeckParam::kVoiceMode,
|
||||
@@ -405,7 +467,10 @@ int main() {
|
||||
testEveryDeckControlIsClassifiedLiveOrReloading();
|
||||
testOnlyALiveControlsDragTakesTheLiveTier();
|
||||
testDeckReadsPitchThenFilterThenAmpLeftToRight();
|
||||
testFilterGroupCarriesItsFiveToneControlsPlusModulation();
|
||||
testVelocityGroupOwnsTheThreeCurvesExclusively();
|
||||
testCurveTargetNamesEachCellsOwnDestination();
|
||||
testVelocityCellsHitTestWithinTheirGroup();
|
||||
testFilterGroupCarriesItsToneControlsPlusModulation();
|
||||
testOnlyTheThreeEnvelopeDecksCarryARadio();
|
||||
testGateAndTriggerFacesCarryTheirOwnShapes();
|
||||
testOnlySlopedStageKnobsCarryAnInnerCurveDial();
|
||||
|
||||
Reference in New Issue
Block a user