Fix spline-drag rollback, bake contour leak, held-note live miss, amp clamp
Editor close mid-drag now rolls back published contours like capture-loss does; bake detaches liveContours so a printed capture can't observe a live drag; a held note now receives a live publish that lands in the same block as a note-on; amp spline read clamped to [0,1].
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
#include "../src/core/instrument/bake/bake_render.h"
|
||||
|
||||
#include "../src/core/instrument/engine/limiter.h"
|
||||
#include "../src/core/instrument/engine/live_contours.h"
|
||||
#include "../src/core/instrument/engine/live_params.h"
|
||||
|
||||
#include <cmath>
|
||||
@@ -338,6 +339,36 @@ int main() {
|
||||
CHECK(sameSamples(audio, reference));
|
||||
}
|
||||
|
||||
// --- The bake detaches liveContours too, not just live -------------------------------
|
||||
// A contour drag in flight publishes into liveContours (see reasampler_processor.h); a
|
||||
// render that observed it would print whatever the user happens to be dragging instead
|
||||
// of the dialed shape already sitting in play.ampSpline.contour.
|
||||
{
|
||||
instrument::engine::LiveContourBlock block;
|
||||
SampleData s = makeSample(/*stereo=*/false, /*frames=*/1000);
|
||||
s.play.playMode = PlayMode::Trigger;
|
||||
s.play.ampSpline.mode = EnvMode::Spline;
|
||||
s.play.ampSpline.contour = instrument::engine::VelocityCurve::flat(); // dialed: unity
|
||||
|
||||
// Publish a very different contour — ramping down to silence — into the block.
|
||||
s.liveContours = █
|
||||
{
|
||||
PlayParams dragged = s.play;
|
||||
dragged.ampSpline.contour = instrument::engine::VelocityCurve::rampDown();
|
||||
block.publish(instrument::engine::foldContours(dragged));
|
||||
}
|
||||
|
||||
const BakePlan plan = planOf(/*total=*/1000, /*noteOn=*/0, /*noteOff=*/1000);
|
||||
const BakeAudio audio = renderBake(s, plan, kUnity, kNoLimiter);
|
||||
|
||||
CHECK(s.liveContours == &block); // the caller's own snapshot was not detached
|
||||
|
||||
SampleData detached = s;
|
||||
detached.liveContours = nullptr;
|
||||
const BakeAudio reference = renderBake(detached, plan, kUnity, kNoLimiter);
|
||||
CHECK(sameSamples(audio, reference));
|
||||
}
|
||||
|
||||
// --- Regression baseline: the neutral render is the source, sample for sample --------
|
||||
// A Trigger voice at its own root under Varispeed reads at ratio exactly 1 and hits no
|
||||
// filter, so every printed frame equals its source frame PROVIDED the amp curve's gain at
|
||||
|
||||
@@ -1167,6 +1167,38 @@ static void testVelocityGainSurvivesAHostilePublishThatReallyLands() {
|
||||
CHECK(proportional);
|
||||
}
|
||||
|
||||
// A note-on that lands in the same block boundary as a publish must not let the already-
|
||||
// sounding voice miss it: startVoice used to call refreshLive() bare, which consumes the
|
||||
// generation stamp without applying it to the voices already active — a held note that shared
|
||||
// a block with a note-on then never saw the publish at all (not merely delayed a block).
|
||||
static void testHeldVoiceReceivesALivePublishThatCoincidesWithANoteOn() {
|
||||
SampleData s;
|
||||
s.frames.assign(200000, 1.0f); // DC: the envelope alone shapes the output
|
||||
s.sampleRate = kRate;
|
||||
s.rootNote = 60;
|
||||
s.play.adsr.attackFrames = 0;
|
||||
s.play.adsr.sustainLevel = 1.0;
|
||||
s.play.adsr.releaseFrames = 480000; // long enough that release never intrudes
|
||||
|
||||
LiveParams block;
|
||||
s.live = █
|
||||
VoiceEngine engine(2, s);
|
||||
engine.noteOn(60, 100); // voice A, sustaining at the dialed 1.0
|
||||
|
||||
LiveValues quieter = foldLive(s.play, s.keyTrack);
|
||||
quieter.adsr.sustainLevel = 0.2;
|
||||
block.publish(quieter);
|
||||
|
||||
engine.noteOn(61, 100); // voice B starts in the SAME generation as the publish
|
||||
|
||||
std::vector<AudioSample> out;
|
||||
for (int i = 0; i < 20; ++i) engine.render(out, 512); // let voice A's glide settle
|
||||
|
||||
// Both voices settle at 0.2 — a held voice that missed the publish would still sit at 1.0,
|
||||
// for a combined 1.2 rather than 0.4.
|
||||
CHECK(std::fabs(static_cast<double>(out.back()) - 0.4) < 0.01);
|
||||
}
|
||||
|
||||
int main() {
|
||||
testStageDurationChangeHoldsPhase();
|
||||
testShortenedStageStillLandsContinuously();
|
||||
@@ -1189,6 +1221,7 @@ int main() {
|
||||
testAPublishedPitchOffsetRefitsThePitchEnvelopeSpan();
|
||||
testPitchRatioAndVelocityGainStayLatched();
|
||||
testVelocityGainSurvivesAHostilePublishThatReallyLands();
|
||||
testHeldVoiceReceivesALivePublishThatCoincidesWithANoteOn();
|
||||
if (g_fail == 0) std::printf("live_delivery tests passed\n");
|
||||
return g_fail == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user