instrument: fix AHD node-tracking/tie-break/live-latch defects and close staged-envelope-curve test gaps

This commit is contained in:
2026-07-31 09:52:17 -04:00
parent d60ab1524a
commit 2fa1405b06
27 changed files with 360 additions and 86 deletions
+14 -4
View File
@@ -90,8 +90,7 @@ EnvVertex gateVtx(EnvNode node, const Rect& area, double px, double level, bool
}
// The knot for a segment running from `startLevel` to `endLevel`, placed at the segment's
// pixel midpoint. Its level is the curve's own value at the segment midpoint, which is what
// makes the knot's height and the inner dial two readings of one exponent.
// pixel midpoint, its level read through curve_law.h's own law (the knot/dial pairing's home).
EnvVertex knotVtx(EnvNode node, const Rect& area, int x0, int x1, double startLevel,
double endLevel, double exponent) {
const double u = curveMidLevel(exponent);
@@ -178,8 +177,19 @@ std::vector<EnvVertex> ahdPolyline(const StageEnvelope& env, const Rect& area,
pts.reserve(6);
pts.push_back(vtx(EnvNode::Origin, area, totalSeconds, t0, 0.0));
pts.push_back(vtx(EnvNode::AttackEnd, area, totalSeconds, t0 + s.attack, 1.0));
pts.push_back(vtx(EnvNode::HoldEnd, area, totalSeconds, t0 + s.attack + s.hold, 1.0));
pts.push_back(vtx(EnvNode::DecayEnd, area, totalSeconds, t0 + s.total, 0.0));
EnvVertex holdVtx = vtx(EnvNode::HoldEnd, area, totalSeconds, t0 + s.attack + s.hold, 1.0);
EnvVertex decayVtx = vtx(EnvNode::DecayEnd, area, totalSeconds, t0 + s.total, 0.0);
// A hold that consumes the WHOLE post-attack/decay remainder (holdFraction == 1.0, the
// Trigger AHD default) puts HoldEnd and DecayEnd on the same wall-clock instant, and
// nodeAtPoint's earlier-draw-order tie-break then hides DecayEnd behind HoldEnd forever.
// Nudge apart, clamped to the canvas — the same minimum-separation rationale
// kGateNodeSepPx exists for on the AHDSR schematic, applied to this coincidence instead.
if (decayVtx.x - holdVtx.x < kGateNodeSepPx) {
const int right = area.x + std::max(1, area.width) - 1;
decayVtx.x = std::min(holdVtx.x + kGateNodeSepPx, right);
}
pts.push_back(holdVtx);
pts.push_back(decayVtx);
if (s.attack > 0.0) {
pts.push_back(knotVtx(EnvNode::AttackCurve, area, pts[0].x, pts[1].x, 0.0, 1.0,
env.attackCurve));