fix: close round-3 review findings — smallest-target-first, residue test fix, extraction
Waveform overlay now resolves node/tab/marker click collisions by target area instead of check order; residue test now uses a distinguishing fixture; Gate-unavailable-while-drawn logic extracted to one pure helper shared by resolvePlay and applyControl.
This commit is contained in:
@@ -195,6 +195,17 @@ bool splineActive(const Play& p) {
|
||||
(p.filter.enabled && p.filterSpline.mode == EnvMode::Spline);
|
||||
}
|
||||
|
||||
// The one enforcement of the rule above: forces `p` to Trigger the instant it goes
|
||||
// splineActive. Every path that can leave `p` in a state splineActive() would newly return true
|
||||
// for — resolving stored params toward the engine, or an editor control edit that can flip a
|
||||
// mode/enable toggle — routes through this, so none of them can reopen the Gate+spline hole
|
||||
// splineActive's own doc comment describes. Header-inline and allocation-free: play_params.h
|
||||
// sits on the per-voice-per-sample include path.
|
||||
template <class Play>
|
||||
void enforceGateUnavailableWhileDrawn(Play& p) {
|
||||
if (splineActive(p)) p.playMode = PlayMode::Trigger;
|
||||
}
|
||||
|
||||
// [start, end) frames, half-open. A zero-length loop (start == end) is the "no sustain loop"
|
||||
// marker — a held note past the sample end goes silent rather than looping a zero span.
|
||||
struct SampleLoop {
|
||||
|
||||
@@ -232,13 +232,14 @@ public:
|
||||
// True once the cursor has settled on the contour's LAST segment. On its own this does NOT
|
||||
// make a 0 read here a terminus: the final segment's LEFT endpoint can also be 0 (a 2-point
|
||||
// contour is nothing but a single "final" segment starting at frame 0), which would read 0
|
||||
// while about to rise. Voice::tickAmplitude pairs this with terminalValue() == 0 — the
|
||||
// while about to rise. Voice::tickAmplitude pairs this with segmentEndValue() == 0 — the
|
||||
// segment's RIGHT endpoint, i.e. the whole contour's true end — before calling a 0 read the
|
||||
// note's genuine permanent terminus.
|
||||
bool onFinalSegment() const { return seg_ + 2 == n_; }
|
||||
// The current segment's right endpoint — the whole contour's terminal Y only when paired
|
||||
// with onFinalSegment() (see there).
|
||||
double terminalValue() const { return y1_; }
|
||||
// The CURRENT SEGMENT's right endpoint — not a contour-level concept despite the name's
|
||||
// shape; it is the whole contour's terminal Y only when paired with onFinalSegment() (see
|
||||
// there). Named for what it returns, not for its one call site's use of it.
|
||||
double segmentEndValue() const { return y1_; }
|
||||
|
||||
// `phase` is normalized position over the contour's whole span, [0,1]; out-of-range clamps
|
||||
// to the terminal values (a note past its span holds the contour's last level).
|
||||
|
||||
@@ -184,18 +184,11 @@ private:
|
||||
// entirely — release() then has no envelope to end, and an active sustain loop rings
|
||||
// forever.
|
||||
if (ampSplineCur_.active() && playMode_ == PlayMode::Trigger) {
|
||||
// A contour covers the sample end to end, so the head leaving the span IS the end of
|
||||
// the note — the exhaustion path in advanceFrame is what frees the voice. A contour
|
||||
// whose TERMINAL value (the final segment's right endpoint) is 0 reaches a genuine
|
||||
// permanent terminus early, the spline analogue of a staged AHD's finished(). Gating
|
||||
// on the terminal value, not just the segment index, matters because a 2-point
|
||||
// contour IS a single "final" segment from frame 0 — checking onFinalSegment() alone
|
||||
// would call a contour that STARTS at 0 (e.g. a fade-in) over before it ever rises.
|
||||
// Mid-contour dips through 0 still don't free early, since the spline is deliberately
|
||||
// not globally monotone.
|
||||
// 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 = ampSplineCur_.eval(splinePhase());
|
||||
if (amp == 0.0 && ampSplineCur_.onFinalSegment() &&
|
||||
ampSplineCur_.terminalValue() == 0.0) {
|
||||
ampSplineCur_.segmentEndValue() == 0.0) {
|
||||
amplitudeDone_ = true;
|
||||
}
|
||||
} else if (playMode_ == PlayMode::Gate) {
|
||||
|
||||
@@ -262,10 +262,10 @@ PlayParams resolvePlay(const PlaySeconds& stored, int sampleRate) {
|
||||
out.ampSpline = stored.ampSpline;
|
||||
out.pitchSpline = stored.pitchSpline;
|
||||
out.filterSpline = stored.filterSpline;
|
||||
// Gate is unavailable while any EG is drawn — see splineActive (play_params.h) for why.
|
||||
// The editor refuses the Gate segment for the same reason; enforcing it HERE as well is
|
||||
// what keeps a hand-edited or downgraded blob from reaching the engine as Gate + spline.
|
||||
if (splineActive(stored)) out.playMode = PlayMode::Trigger;
|
||||
// Every field splineActive reads on `out` is already copied from `stored` above, so this
|
||||
// enforces the same rule enforceGateUnavailableWhileDrawn's doc comment (play_params.h)
|
||||
// describes — the editor's applyControl is the other caller, so the two cannot drift.
|
||||
enforceGateUnavailableWhileDrawn(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,10 @@ reasampler_pure_library(waveform_view
|
||||
SOURCES waveform_view.cpp
|
||||
LINK PUBLIC editor_geometry peaks PRIVATE sample_bands)
|
||||
# sample_bands is linked directly here because the test exercises the lane metrics that
|
||||
# waveform_view does not re-export.
|
||||
reasampler_test(waveform_view LINK waveform_view sample_bands)
|
||||
# waveform_view does not re-export. velocity_curve is linked for the smallest-target-first
|
||||
# arbitration tests, which pin the geometric facts editor_input_waveform.cpp's node/tab/marker
|
||||
# resolution depends on (the shell itself has no test target).
|
||||
reasampler_test(waveform_view LINK waveform_view sample_bands velocity_curve)
|
||||
|
||||
reasampler_pure_library(browser_scroll
|
||||
SOURCES browser_scroll.cpp
|
||||
|
||||
Reference in New Issue
Block a user