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:
@@ -36,6 +36,7 @@ BakeAudio renderBake(SampleData sample, const BakePlan& plan, double masterGainL
|
||||
// depend on what the user happened to be dragging. The dialed values are already in
|
||||
// this SampleData's own play params, which is what the bake is meant to print.
|
||||
sample.live = nullptr;
|
||||
sample.liveContours = nullptr;
|
||||
|
||||
const int channels = sample.channelCount();
|
||||
// The limiter delays its output by its lookahead, so the buffers carry that many extra
|
||||
|
||||
@@ -225,8 +225,9 @@ private:
|
||||
// 128-point ceiling.
|
||||
//
|
||||
// Holds a RAW POINTER into the bound point array: the caller guarantees the array outlives the
|
||||
// cursor, or rebinds it first. The voice binds against its SampleData, which has exactly that
|
||||
// lifetime, or against its engine's live contour copy, which the engine rebinds before reusing.
|
||||
// cursor, or rebinds it first. The voice binds against its SampleData (that lifetime), or
|
||||
// against its engine's live contour copy — the double-buffer contract VoiceEngine::contours_
|
||||
// owns (voice_engine.h) is what makes that second binding safe.
|
||||
class SplineCursor {
|
||||
public:
|
||||
// Binds `c` if it has an evaluable segment; a shorter curve leaves the cursor inactive so
|
||||
|
||||
@@ -335,8 +335,8 @@ void Voice::applyLive(const instrument::engine::LiveValues& live, bool snap) {
|
||||
|
||||
namespace {
|
||||
|
||||
// One EG's half of Voice::applyContours. `before` is read through the OLD binding, which is
|
||||
// still intact: the engine only overwrites a buffer no active voice is bound to.
|
||||
// One EG's half of Voice::applyContours. `before` is read through the OLD binding — still
|
||||
// intact, by the double-buffer contract VoiceEngine::contours_ owns (voice_engine.h).
|
||||
void rebindContour(SplineCursor& cursor, StepSmoother& smooth,
|
||||
const instrument::engine::ContourPoints& pts, double phase, bool snap) {
|
||||
if (!cursor.active()) return;
|
||||
|
||||
@@ -302,10 +302,16 @@ private:
|
||||
// Early-free at a genuine permanent terminus (the spline analogue of a staged AHD's
|
||||
// finished()) — onFinalSegment()/segmentEndValue()'s own doc comments own the why.
|
||||
amp = readSpline(ampSplineCur_, ampSplineSmooth_);
|
||||
// A swap-step absorbed by ampSplineSmooth_ can briefly push the sum outside [0,1]
|
||||
// (a contour's own points are drawn in-range, but the smoother's addend isn't); the
|
||||
// filter clamps its own read for the same reason (below). The finished-check reads
|
||||
// the PRE-clamp value so a genuine zero-terminus isn't masked by the clamp itself.
|
||||
if (amp == 0.0 && ampSplineCur_.onFinalSegment() &&
|
||||
ampSplineCur_.segmentEndValue() == 0.0) {
|
||||
amplitudeDone_ = true;
|
||||
}
|
||||
if (amp < 0.0) amp = 0.0;
|
||||
else if (amp > 1.0) amp = 1.0;
|
||||
} else if (playMode_ == PlayMode::Gate) {
|
||||
amp = env_.tick();
|
||||
if (env_.finished()) amplitudeDone_ = true;
|
||||
|
||||
@@ -69,9 +69,13 @@ void VoiceEngine::applyContoursToActive() {
|
||||
}
|
||||
|
||||
void VoiceEngine::startVoice(Voice& voice, int note, int velocity) {
|
||||
// FIRST, so no sounding voice is left bound to a buffer the next read overwrites.
|
||||
// FIRST, so no sounding voice is left bound to a buffer the next read overwrites. A held
|
||||
// note sharing this generation with the note-on must see it too — refreshLive() alone
|
||||
// would consume the generation without applying it, and since applyLiveToActive() only
|
||||
// acts on a NEW generation, the held voice would then never see this publish at all
|
||||
// (not merely late a block), only catching up whenever the next one arrives.
|
||||
applyContoursToActive();
|
||||
refreshLive();
|
||||
applyLiveToActive();
|
||||
// THE read of the note-on-latched commit class, and the only one: a published block outranks
|
||||
// the snapshot's own copy (a live edit deliberately leaves that stale), and applyLive below
|
||||
// touches none of these three — so a move reaches the next note and no sounding one.
|
||||
|
||||
Reference in New Issue
Block a user