fix: close review findings on the velocity-curve deck and bipolar curves

Cancels the curve-node drag whenever the popup closes so Esc mid-drag can't alias the amp curve; generalizes CurveTarget routing to one switch; fixes stale/overstated comments; clamps a pre-v12 depth fold; adds deck-inertness and filter-fold test coverage.
This commit is contained in:
2026-07-31 19:46:37 -04:00
parent 9d38f87a2d
commit cfb53aade3
22 changed files with 223 additions and 99 deletions
+35 -6
View File
@@ -859,21 +859,36 @@ static void testPriorPayloadVersionsLiftToAHardSeam() {
in.params.play.pitchVelocityCurve = reasampler::instrument::engine::VelocityCurve::fromPoints(
{VelocityPoint{0.0, 0.5}, VelocityPoint{127.0, 1.0}},
reasampler::instrument::engine::CurveDomain::Bipolar);
// The filter's pre-v12 depth fold (see testPreV12FilterVelocityDepthFoldsIntoTheCurve for
// the single-version proof); planted here too so EVERY version that carries a filter tail
// (v9..v11) is walked, not just v11 — the fold's branch condition is pv < kParamsVelocityVersion.
in.params.play.filter.enabled = true;
in.params.play.filter.modAmount = -0.6251953125; // distinct, exactly representable anchor
const reasampler::instrument::engine::VelocityCurve filterShape =
reasampler::instrument::engine::VelocityCurve::fromPoints(
{VelocityPoint{0.0, 0.0}, VelocityPoint{64.0, 0.25}, VelocityPoint{127.0, 1.0}},
reasampler::instrument::engine::CurveDomain::Bipolar);
in.params.play.filter.velocityCurve = filterShape;
constexpr double kPlantedDepth = -0.75;
struct Case {
std::uint32_t pv;
std::size_t cut;
bool keepsCurveTail;
bool keepsFilterTail;
};
const Case cases[] = {
{11, kVelocityTailBytes, true},
{10, kVelocityTailBytes + kLoopTailBytes, true},
{9, kVelocityTailBytes + kLoopTailBytes + kCurveTailBytes, false},
{8, kVelocityTailBytes + kLoopTailBytes + kCurveTailBytes + kFilterTailBytes, false},
{11, kVelocityTailBytes, true, true},
{10, kVelocityTailBytes + kLoopTailBytes, true, true},
{9, kVelocityTailBytes + kLoopTailBytes + kCurveTailBytes, false, true},
{8, kVelocityTailBytes + kLoopTailBytes + kCurveTailBytes + kFilterTailBytes, false, false},
};
for (const Case& c : cases) {
const ComponentState out =
deserializeComponentState(payloadDowngradedTo(in, c.pv, c.cut), 48000.0);
std::vector<std::uint8_t> bytes = payloadDowngradedTo(in, c.pv, c.cut);
if (c.keepsFilterTail) {
plantPreV12FilterDepth(bytes, in.params.play.filter.modAmount, kPlantedDepth);
}
const ComponentState out = deserializeComponentState(bytes, 48000.0);
// The span itself has been in the format since v2 and must survive untouched.
CHECK(out.params.loopOverride && out.params.loopOverride->hasLoop);
CHECK(out.params.loopOverride && out.params.loopOverride->start == 2000);
@@ -889,6 +904,20 @@ static void testPriorPayloadVersionsLiftToAHardSeam() {
// And the cut landed on the tail boundary the ladder claims, not somewhere inside it.
CHECK(out.params.play.adsr.attackCurve ==
(c.keepsCurveTail ? 4.0 : reasampler::util::kCurveNeutral));
// The filter's own velocity curve: folded by the planted depth where the tail survives
// (v9..v11), the off/neutral default where it was cut away entirely (v8).
if (c.keepsFilterTail) {
CHECK(out.params.play.filter.enabled);
for (int v = 0; v <= 127; ++v) {
CHECK(std::fabs(out.params.play.filter.velocityCurve.eval(v) -
kPlantedDepth * filterShape.eval(v)) < 1e-12);
}
} else {
CHECK(!out.params.play.filter.enabled);
for (int v = 0; v <= 127; ++v) {
CHECK(out.params.play.filter.velocityCurve.eval(v) == 0.0);
}
}
}
}