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
+56
View File
@@ -474,6 +474,62 @@ static void testEveryEnvelopeStageTimeAndLevelMovesTheSoundingNote() {
s.play.pitchEnv.peakSemitones = 12.0;
},
[](LiveValues& v) { v.pitchEnv.peakSemitones = 0.0; }, -1},
// A small hold that finishes the envelope well inside the render window (Hold ends at
// frame ~5090, comfortably short of the window) vs. a live move that opens the hold out
// near the whole span: with the fraction alone unmoved, the boundary the two renders
// cross (or don't) inside the observed tail is what makes this audible, not a level
// change — Hold's own output is flat regardless of exactly where inside it pos_ sits.
{"pitch env hold fraction",
[](SampleData& s) {
s.play.pitchEnv.enabled = true;
s.play.pitchEnv.peakSemitones = 12.0;
s.play.pitchEnv.shape.attackFrames = 100;
s.play.pitchEnv.shape.decayFrames = 100;
s.play.pitchEnv.shape.holdFraction = 0.05;
},
[](LiveValues& v) { v.pitchEnv.shape.holdFraction = 1.0; }, -1},
// The Trigger AHDs — Voice::applyLive's ampAhd_/filterAhd_ branches, otherwise
// unexercised by this table (every case above is Gate/AdsrEnvelope).
{"trigger amp attack (AHD)",
[](SampleData& s) {
s.play.playMode = PlayMode::Trigger;
s.play.trigger.lengthFraction = 1.0;
s.play.trigAhd.attackFrames = 48000;
},
[](LiveValues& v) { v.ampAhd.attackFrames = 4000; }, -1},
{"trigger filter attack (AHD)",
[](SampleData& s) {
s.play.playMode = PlayMode::Trigger;
s.play.trigger.lengthFraction = 1.0;
filterSweep(s);
s.play.filter.trigEnv.attackFrames = 48000;
},
[](LiveValues& v) { v.filterAhd.attackFrames = 4000; }, -1},
// One curve exponent per envelope (amp/pitch/filter), reusing each envelope's own
// attack/decay rig above so only the mutated field differs.
{"amp attack curve",
[](SampleData& s) { s.play.adsr.attackFrames = 48000; },
[](LiveValues& v) { v.adsr.attackCurve = 5.0; }, -1},
{"amp release curve",
[](SampleData& s) { s.play.adsr.releaseFrames = 48000; },
[](LiveValues& v) { v.adsr.releaseCurve = 5.0; }, 2},
{"pitch env attack curve",
[](SampleData& s) {
s.play.pitchEnv.enabled = true;
s.play.pitchEnv.shape.attackFrames = 48000;
s.play.pitchEnv.shape.decayFrames = 48000;
s.play.pitchEnv.peakSemitones = 12.0;
},
[](LiveValues& v) { v.pitchEnv.shape.attackCurve = 5.0; }, -1},
{"filter env decay curve",
[](SampleData& s) {
filterSweep(s);
s.play.filter.env.decayFrames = 48000;
s.play.filter.env.sustainLevel = 0.0;
},
[](LiveValues& v) { v.filterEnv.decayCurve = 5.0; }, -1},
};
for (const Case& c : cases) {
assertLiveFieldMovesTheSoundingNote(c.name, c.rig, c.mutate, c.noteOffBlock);