Merge Γ-W2-T2: an explicit loop enable, four named marks with grabbable caps, and the crossfade painted where it is heard

This commit is contained in:
2026-08-02 06:32:29 -04:00
28 changed files with 1289 additions and 192 deletions
+22
View File
@@ -1105,6 +1105,27 @@ static void testLoopSpanAndCrossfadeRoundTrip() {
CHECK(out.params.rootOverride && *out.params.rootOverride == 55);
}
// The loop ENABLE is `hasLoop`, and the block writes start/end unconditionally — so the wire
// already carries "off, with a span remembered." Nothing about the format changes to make the
// enable user-owned; this pins that the off state and its retained span both survive a reload,
// because an off that came back as a re-parked default would be a delete button, not a toggle.
static void testAnOffLoopRoundTripsWithItsSpanAndCrossfadeRetained() {
ComponentState in;
in.selectionId = "pad";
SampleLoop lp;
lp.hasLoop = false;
lp.start = 4096;
lp.end = 65536;
in.params.loopOverride = lp;
in.params.loopCrossfadeFrames = 1024;
const ComponentState out = deserializeComponentState(serializeComponentState(in), 48000.0);
CHECK(out.params.loopOverride && !out.params.loopOverride->hasLoop);
CHECK(out.params.loopOverride && out.params.loopOverride->start == 4096);
CHECK(out.params.loopOverride && out.params.loopOverride->end == 65536);
CHECK(out.params.loopCrossfadeFrames == 1024);
}
// A negative fade cannot mean anything and would only reach resolveLoop's clamp; refusing it
// at the wire keeps the parameter set the editor reads back sane.
static void testNegativeCrossfadeOnTheWireLiftsToZero() {
@@ -2155,6 +2176,7 @@ int main() {
testDefaultStateRoundTripsToDefaults();
testEnvelopePrefixBytesFrozen();
testLoopSpanAndCrossfadeRoundTrip();
testAnOffLoopRoundTripsWithItsSpanAndCrossfadeRetained();
testNegativeCrossfadeOnTheWireLiftsToZero();
testPriorPayloadVersionsLiftToAHardSeam();
testLimiterEnableRoundTripsAndV14LiftsToBypassedWithItsHoldIntact();