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:
2026-08-01 21:10:38 -04:00
parent 19aeb92775
commit 65f6070348
35 changed files with 772 additions and 226 deletions
+48
View File
@@ -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");