fix: pin the waveform arbitration in a testable predicate, close round-4 review minors
Extracts resolveWaveformClaim (core/instrument/ui/spline_edit) so the shell's node/tab/marker click resolution is unit-tested directly, not just its input geometry; folds the staged-envelope node into it; fixes comment accuracy, a cost regression, and test fidelity issues.
This commit is contained in:
@@ -195,12 +195,10 @@ 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.
|
||||
// The one enforcement of splineActive's rule (see its doc above). Header-inline and
|
||||
// allocation-free: play_params.h sits on the per-voice-per-sample include path. Both callers —
|
||||
// resolvePlay (sample_map.cpp) and the editor's applyControl — route through here, so the two
|
||||
// cannot drift apart.
|
||||
template <class Play>
|
||||
void enforceGateUnavailableWhileDrawn(Play& p) {
|
||||
if (splineActive(p)) p.playMode = PlayMode::Trigger;
|
||||
|
||||
@@ -24,10 +24,8 @@ 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. 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)
|
||||
# waveform_view does not re-export.
|
||||
reasampler_test(waveform_view LINK waveform_view sample_bands)
|
||||
|
||||
reasampler_pure_library(browser_scroll
|
||||
SOURCES browser_scroll.cpp
|
||||
@@ -63,7 +61,12 @@ reasampler_test(deck_groups LINK deck_groups sample_bands)
|
||||
reasampler_pure_library(spline_edit
|
||||
SOURCES spline_edit.cpp
|
||||
LINK PUBLIC editor_geometry velocity_curve)
|
||||
reasampler_test(spline_edit LINK spline_edit)
|
||||
# waveform_view and sample_bands are linked for the test only: resolveWaveformClaim's
|
||||
# smallest-target-first tests build the real node/tab/marker geometry
|
||||
# editor_input_waveform.cpp's mouseDownWaveform composes (the shell that calls it has no test
|
||||
# target of its own), which needs waveform_view's marker/tab primitives and sample_bands'
|
||||
# kWaveformMinHeight floor.
|
||||
reasampler_test(spline_edit LINK spline_edit waveform_view sample_bands)
|
||||
|
||||
reasampler_pure_library(curve_popup SOURCES curve_popup.cpp LINK PUBLIC editor_geometry)
|
||||
# velocity_curve is linked for the test only: the sheet's geometry is domain-agnostic, and
|
||||
|
||||
@@ -26,4 +26,14 @@ VelocityCurve::Box splineOverlayBox(const OverlayArea& area) {
|
||||
return VelocityCurve::Box{area.rect.x, area.rect.y, area.rect.width, area.rect.height};
|
||||
}
|
||||
|
||||
WaveformClaimant resolveWaveformClaim(const WaveformClaim& node, const WaveformClaim& tab,
|
||||
const WaveformClaim& marker, SplineGesture gesture) {
|
||||
if (gesture == SplineGesture::kControlLeft && node.hit) return WaveformClaimant::kNode;
|
||||
if (node.hit && (!tab.hit || node.area <= tab.area) && (!marker.hit || node.area <= marker.area))
|
||||
return WaveformClaimant::kNode;
|
||||
if (tab.hit && (!marker.hit || tab.area <= marker.area)) return WaveformClaimant::kTab;
|
||||
if (marker.hit) return WaveformClaimant::kMarker;
|
||||
return WaveformClaimant::kNone;
|
||||
}
|
||||
|
||||
} // namespace reasampler::instrument::ui
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// spline_edit.h — the point-editing grammar's CLICK resolution: add/grab/delete/toggle from a
|
||||
// single (x, y). Both spline consumers — the velocity-curve popup and the spline EG overlay —
|
||||
// route their mouse-down through it, so the two cannot drift into two click grammars. Two more
|
||||
// gesture rules complete the grammar but live in the shell (see the note near the bottom of this
|
||||
// file). Mirror of envelope_edit otherwise: decision logic only, no host types, no drawing.
|
||||
// single (x, y), plus resolveWaveformClaim, the waveform overlay's cross-affordance arbitration
|
||||
// (node vs. crossfade tab vs. marker). Both spline consumers — the velocity-curve popup and the
|
||||
// spline EG overlay — route their mouse-down through the click grammar, so the two cannot drift
|
||||
// apart. Decision logic only, no host types, no drawing; mirror of envelope_edit otherwise.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "core/instrument/engine/velocity_curve.h"
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect / OverlayArea
|
||||
|
||||
@@ -51,4 +53,28 @@ VelocityCurve::Box splineOverlayBox(const OverlayArea& area);
|
||||
// strictly in-box, since splineOverlayBox has no inset — editor_input_waveform.cpp's
|
||||
// splineOverlayClick.
|
||||
|
||||
// One arbitration candidate: whether the affordance was hit under the cursor, and its own
|
||||
// pick-target area (nominal, per its own module's constants — not the actual clipped pixel
|
||||
// count; see resolveWaveformClaim).
|
||||
struct WaveformClaim {
|
||||
bool hit = false;
|
||||
std::int64_t area = 0;
|
||||
};
|
||||
|
||||
// Which affordance a waveform-overlay click claims.
|
||||
enum class WaveformClaimant { kNone, kNode, kTab, kMarker };
|
||||
|
||||
// The overlay's cross-affordance arbitration: a contour node (or, mutually exclusively, a
|
||||
// staged envelope's drag node — both feed the same `node` slot), the loop crossfade tab, and a
|
||||
// marker's full-height column can all claim the same pixel. Hit gates a candidate out
|
||||
// entirely; among the ones that hit, the SMALLEST nominal area wins — the marker column is the
|
||||
// odd one out (its target is the whole overlay height), so it only wins where nothing narrower
|
||||
// also claims the click. Ties go to whichever is checked first: node, then tab, then marker —
|
||||
// no live geometry produces a tie except tab-vs-marker, which the tab correctly wins (see
|
||||
// editor_input_waveform.cpp's mouseDownWaveform for the live constants). A control-click has no
|
||||
// tab/marker meaning (they answer plain grabs only), so it resolves to the node whenever the
|
||||
// node is in the running, regardless of area.
|
||||
WaveformClaimant resolveWaveformClaim(const WaveformClaim& node, const WaveformClaim& tab,
|
||||
const WaveformClaim& marker, SplineGesture gesture);
|
||||
|
||||
} // namespace reasampler::instrument::ui
|
||||
|
||||
Reference in New Issue
Block a user