Loop: an explicit enable, four named marks with grabbable caps, and the crossfade painted where it is actually heard

hasLoop becomes user-owned with the gestures as shortcuts onto it; no format change. START uses overlay/trace, not accent/primary, which is the waveform's own fill.
This commit is contained in:
2026-08-02 05:18:53 -04:00
parent ef59265e7a
commit a7c3c7a828
26 changed files with 1190 additions and 191 deletions
+22
View File
@@ -1089,6 +1089,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() {
@@ -2039,6 +2060,7 @@ int main() {
testDefaultStateRoundTripsToDefaults();
testEnvelopePrefixBytesFrozen();
testLoopSpanAndCrossfadeRoundTrip();
testAnOffLoopRoundTripsWithItsSpanAndCrossfadeRetained();
testNegativeCrossfadeOnTheWireLiftsToZero();
testPriorPayloadVersionsLiftToAHardSeam();
testLimiterEnableRoundTripsAndV14LiftsToBypassedWithItsHoldIntact();