Bake window: derived note lengths carry exact durations, not ladder rungs — a long take is no longer cut at 384 beats
Hold keeps its picker. Also: one home for the %-fold, duration-ordered Hold travel, and a corrupt tail degrades to absent rather than fabricating one.
This commit is contained in:
@@ -491,6 +491,52 @@ static void testEnforceGateUnavailableWhileDrawnForcesTriggerOnBothRepresentatio
|
||||
CHECK(frames.playMode == PlayMode::Trigger);
|
||||
}
|
||||
|
||||
// effectiveLengthFraction is the third member of the same rule family, and the one every
|
||||
// consumer of the Trigger span (the voice, the overlay, the bake's window) must fold through:
|
||||
// a drawn contour covers the FULL sample length, so the stored %-knob goes inert. It is NOT
|
||||
// cleared — a pre-spline value survives in the record, and a reader that takes it raw plays,
|
||||
// draws or bakes a fraction of the take. Gated on the same per-envelope `enabled` flags for the
|
||||
// same reason the predicate above is.
|
||||
static void testEffectiveLengthFractionFoldsToOneOnlyWhileAnEgIsActuallyDrawn() {
|
||||
PlayParams p;
|
||||
p.trigger.lengthFraction = 0.25;
|
||||
CHECK(effectiveLengthFraction(p) == 0.25); // staged: the knob is what it says
|
||||
|
||||
p.ampSpline.mode = EnvMode::Spline;
|
||||
CHECK(effectiveLengthFraction(p) == 1.0); // drawn: the whole take
|
||||
CHECK(p.trigger.lengthFraction == 0.25); // …and the stored value is untouched
|
||||
|
||||
// Flipping back restores the stored fraction, which is why the fold cannot be a write.
|
||||
p.ampSpline.mode = EnvMode::Staged;
|
||||
CHECK(effectiveLengthFraction(p) == 0.25);
|
||||
|
||||
p.pitchSpline.mode = EnvMode::Spline;
|
||||
CHECK(effectiveLengthFraction(p) == 0.25); // pitchEnv.enabled is still false
|
||||
p.pitchEnv.enabled = true;
|
||||
CHECK(effectiveLengthFraction(p) == 1.0);
|
||||
|
||||
p.pitchEnv.enabled = false;
|
||||
p.pitchSpline.mode = EnvMode::Staged;
|
||||
p.filterSpline.mode = EnvMode::Spline;
|
||||
CHECK(effectiveLengthFraction(p) == 0.25); // filter.enabled is still false
|
||||
p.filter.enabled = true;
|
||||
CHECK(effectiveLengthFraction(p) == 1.0);
|
||||
}
|
||||
|
||||
// effectivePlayMode is what a READ-ONLY consumer asks instead of re-testing the fields — the
|
||||
// editor's bake-Hold predicate is one. It must answer exactly what the enforcement writes.
|
||||
static void testEffectivePlayModeAgreesWithTheEnforcementItShares() {
|
||||
PlaySeconds stored;
|
||||
stored.playMode = PlayMode::Gate;
|
||||
CHECK(effectivePlayMode(stored) == PlayMode::Gate);
|
||||
stored.ampSpline.mode = EnvMode::Spline;
|
||||
CHECK(effectivePlayMode(stored) == PlayMode::Trigger);
|
||||
CHECK(stored.playMode == PlayMode::Gate); // a read, never a write
|
||||
PlaySeconds enforced = stored;
|
||||
enforceGateUnavailableWhileDrawn(enforced);
|
||||
CHECK(enforced.playMode == effectivePlayMode(stored));
|
||||
}
|
||||
|
||||
// --- 11. The velocity->amp curve is the same grammar -------------------------
|
||||
|
||||
static void testTheVelocityAmpCurveGainsTheToggleAndKeepsItsDelete() {
|
||||
@@ -563,6 +609,8 @@ int main() {
|
||||
testAFreshSplineEgDefaultsToTheSmoothDownwardSlope();
|
||||
testGateIsUnavailableWhileASplineEgIsActiveAndReturnsAfterwards();
|
||||
testEnforceGateUnavailableWhileDrawnForcesTriggerOnBothRepresentations();
|
||||
testEffectiveLengthFractionFoldsToOneOnlyWhileAnEgIsActuallyDrawn();
|
||||
testEffectivePlayModeAgreesWithTheEnforcementItShares();
|
||||
testTheVelocityAmpCurveGainsTheToggleAndKeepsItsDelete();
|
||||
testSplineCursorBinarySearchAgreesWithTheColdReaderOnAJumpingRead();
|
||||
if (g_fail == 0) std::printf("spline_egs: all tests passed\n");
|
||||
|
||||
Reference in New Issue
Block a user