instrument: one staged-envelope system — per-segment curves, the sustain-less AHD, and a shared overlay for all three envelopes
Trigger's fade pair folds into the AHD (and goes live); the release anchors right; Preserve rings its synthetic tail out instead of cutting it. Payload v10.
This commit is contained in:
+18
-12
@@ -1023,17 +1023,20 @@ static void testAhdsrHoldZeroEqualsAdsr() {
|
||||
}
|
||||
|
||||
// A trigger-mode DC sample (all 1.0) so a rendered voice's output tracks the trigger envelope
|
||||
// * velocity directly. `play` sets Trigger mode + params; Varispeed so no shift colours the amp.
|
||||
// * velocity directly. `attack`/`decay` are the AHD's ramp lengths in frames, with Hold taking
|
||||
// the whole remainder — the shape that replaced the retired fade pair. Varispeed so no shift
|
||||
// colours the amp.
|
||||
static SampleData triggerSample(std::size_t frames, double lengthFraction,
|
||||
std::int64_t fadeIn, std::int64_t fadeOut,
|
||||
std::int64_t attack, std::int64_t decay,
|
||||
std::int64_t startFrame = 0) {
|
||||
SampleData s = dcSample(frames, 60);
|
||||
s.startFrame = startFrame;
|
||||
s.play.playMode = PlayMode::Trigger;
|
||||
s.play.pitchEngine = PitchEngine::Varispeed; // isolate amp shape from pitch
|
||||
s.play.trigger.lengthFraction = lengthFraction;
|
||||
s.play.trigger.fadeInFrames = fadeIn;
|
||||
s.play.trigger.fadeOutFrames = fadeOut;
|
||||
s.play.trigAhd.attackFrames = attack;
|
||||
s.play.trigAhd.decayFrames = decay;
|
||||
s.play.trigAhd.holdFraction = 1.0;
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1181,10 +1184,13 @@ static void testPreserveDurationInvariance() {
|
||||
const std::size_t atUp = lengthAt(72); // +12
|
||||
const std::size_t atDown = lengthAt(48); // -12
|
||||
// All three within a small tolerance of the source length (Preserve holds duration). The
|
||||
// tolerance covers the shifter's fill/latency edge, not a duration scaling (which would be 2x).
|
||||
CHECK(atRoot >= frames - 20 && atRoot <= frames + 20);
|
||||
CHECK(atUp >= frames - 20 && atUp <= frames + 20);
|
||||
CHECK(atDown >= frames - 20 && atDown <= frames + 20);
|
||||
// tolerance covers the shifter's fill/latency edge and the terminal ring-out Preserve ends
|
||||
// on (voice.h's seedTerminalDeclick — bounded by the declick floor at ~185 frames), not a
|
||||
// duration scaling, which would be 2x.
|
||||
const std::size_t kTail = 200;
|
||||
CHECK(atRoot >= frames - 20 && atRoot <= frames + kTail);
|
||||
CHECK(atUp >= frames - 20 && atUp <= frames + kTail);
|
||||
CHECK(atDown >= frames - 20 && atDown <= frames + kTail);
|
||||
// The decisive assertion: the up/down lengths track the root length (NOT halved/doubled).
|
||||
CHECK(atUp > frames / 2 + 200); // an octave up did NOT halve the duration (Varispeed would)
|
||||
CHECK(atDown < frames * 2 - 200); // an octave down did NOT double it
|
||||
@@ -1220,8 +1226,8 @@ static void testPitchEnvOffBitIdentical() {
|
||||
if (withDisabledEnv) {
|
||||
s.play.pitchEnv.enabled = false; // explicitly disabled (offset always 0)
|
||||
s.play.pitchEnv.peakSemitones = 12.0; // a depth that WOULD matter if enabled
|
||||
s.play.pitchEnv.attackFrames = 0;
|
||||
s.play.pitchEnv.decayFrames = 500;
|
||||
s.play.pitchEnv.shape.attackFrames = 0;
|
||||
s.play.pitchEnv.shape.decayFrames = 500;
|
||||
}
|
||||
SampleData km = (std::move(s));
|
||||
VoiceEngine eng(1, km);
|
||||
@@ -1248,8 +1254,8 @@ static void testPitchEnvOnBendsVarispeed() {
|
||||
SampleData s = sineSample(n, 40.0, 60);
|
||||
s.play.pitchEngine = PitchEngine::Varispeed;
|
||||
s.play.pitchEnv.enabled = true;
|
||||
s.play.pitchEnv.attackFrames = 0; // start at the peak
|
||||
s.play.pitchEnv.decayFrames = 3000; // glide to base over 3000 frames
|
||||
s.play.pitchEnv.shape.attackFrames = 0; // start at the peak
|
||||
s.play.pitchEnv.shape.decayFrames = 3000; // glide to base over 3000 frames
|
||||
s.play.pitchEnv.peakSemitones = 12.0; // +1 octave at t=0
|
||||
SampleData km = (std::move(s));
|
||||
VoiceEngine eng(1, km);
|
||||
|
||||
Reference in New Issue
Block a user