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:
@@ -274,8 +274,8 @@ void ReaSamplerEditor::applyControl(int id, PlaySeconds& play, double value,
|
||||
// (above) or an enable toggle (kPitchEnvEnable/kFilterEnable), whose enabling can make an
|
||||
// already-Spline pitch/filter envelope newly active. Applying it once here, rather than at
|
||||
// each site that could cause the flip, is what keeps a future such control from reopening
|
||||
// the same hole.
|
||||
if (splineActive(play)) play.playMode = PlayMode::Trigger;
|
||||
// the same hole. `resolvePlay` (sample_map.cpp) is the other caller of the shared helper.
|
||||
enforceGateUnavailableWhileDrawn(play);
|
||||
}
|
||||
|
||||
double ReaSamplerEditor::liveSampleRate() const {
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "core/instrument/engine/loop/loop_span.h" // maxCrossfade (the shared drag-clamp bound)
|
||||
#include "core/instrument/engine/loop/loop_span.h" // maxCrossfade (the shared drag-clamp bound)
|
||||
#include "core/instrument/engine/velocity_curve.h" // kCurveNodeGrabRadius (target-size arbitration)
|
||||
#include "core/instrument/ui/envelope_edit.h" // nodeAtPoint / resolveNodeDrag
|
||||
#include "core/instrument/ui/spline_edit.h" // the shared point-editing grammar
|
||||
#include "core/instrument/ui/waveform_view.h" // waveformOverlayArea / markerAtPoint / snap
|
||||
@@ -24,6 +25,7 @@ namespace reasampler::vst {
|
||||
using namespace reasampler::ui;
|
||||
using namespace reasampler::instrument::ui;
|
||||
using instrument::engine::loop::maxCrossfade;
|
||||
using instrument::engine::kCurveNodeGrabRadius;
|
||||
|
||||
bool ReaSamplerEditor::mouseDownWaveform(const FaceLayout& fl, int x, int y) {
|
||||
const std::vector<AudioSample>& pcm = monoPcmFor(selectedId_);
|
||||
@@ -59,33 +61,65 @@ bool ReaSamplerEditor::mouseDownWaveform(const FaceLayout& fl, int x, int y) {
|
||||
return true; // node moves once the cursor drags
|
||||
}
|
||||
}
|
||||
// An existing contour node's grab/toggle/delete runs BEFORE the markers — mirroring
|
||||
// markerHandleRect's tab-vs-column split (below): a coincident pixel (the default contour
|
||||
// endpoint sits at the same x as the default start marker) is resolved by asking the
|
||||
// NARROWER target first. pointAtPixel's pick radius is a small box around the node's own
|
||||
// (x, y), not a full-height column, so this claims only genuine node hits — the marker's
|
||||
// column stays grabbable at every other y along the same x. Never add here (kAdd is only
|
||||
// tried once the markers have also passed on the click, below).
|
||||
if (splineLive && splineOverlayClick(overlay, x, y, gesture, /*addOnEmptySpace=*/false)) {
|
||||
return true;
|
||||
const SetupMarkers m = pickedMarkers(frames);
|
||||
const std::int64_t markerFrames[3] = {m.start, m.loopStart, m.loopEnd};
|
||||
|
||||
// Three affordances can claim the same pixel: a contour node (a small fixed pick box), the
|
||||
// crossfade tab (a small clipped top-strip tab), and a marker's full-height grab column
|
||||
// (waveform_view.h's tab-vs-column split already keeps the tab apart from ITS OWN column;
|
||||
// this is the cross-affordance case on top of that). Resolving by any fixed check order
|
||||
// shadows whichever one loses the tie — this seam regressed twice from exactly that fix.
|
||||
// Instead measure each claimant's own target area and let the SMALLEST hit win: the marker
|
||||
// column is the odd one out (its target is the whole band height), so it only wins where
|
||||
// nothing narrower also claims the pixel. Never add here (kAdd is only tried once nothing
|
||||
// else has claimed the click, below).
|
||||
struct Candidate {
|
||||
bool hit = false;
|
||||
std::int64_t area = 0;
|
||||
};
|
||||
|
||||
Candidate node;
|
||||
if (splineLive) {
|
||||
const VelocityCurve::Box box = splineOverlayBox(overlay);
|
||||
// Also require strict in-box, matching splineOverlayClick's own narrowing (spline_edit.h's
|
||||
// grammar note) — otherwise this candidate could "win" the arbitration below for a click
|
||||
// splineOverlayClick would then refuse, silently swallowing it instead of falling through
|
||||
// to the tab/marker checks.
|
||||
if (contains(overlay.rect, x, y) && splineFor(overlayEnv_).pointAtPixel(box, x, y) >= 0) {
|
||||
node.hit = true;
|
||||
constexpr std::int64_t side = 2 * kCurveNodeGrabRadius + 1;
|
||||
node.area = side * side;
|
||||
}
|
||||
}
|
||||
|
||||
const SetupMarkers m = pickedMarkers(frames);
|
||||
// The crossfade handle first, and only when there IS a loop to fade: at a zero fade it
|
||||
// sits exactly on the loop start, so it can only stay reachable by owning the top strip
|
||||
// (waveform_view.h's handle-vs-column split) and being asked first. The same ambiguity
|
||||
// recurs whenever ANY marker's frame lands on loopStart - crossfade (most plausibly the
|
||||
// start marker dragged up against the fade edge), so this check has to run before the
|
||||
// marker array below regardless of which marker the collision is with.
|
||||
if (m.hasLoop &&
|
||||
contains(markerHandleRect(overlay, frames, m.loopStart - m.crossfade), x, y)) {
|
||||
Candidate tab;
|
||||
const Rect tabRect =
|
||||
m.hasLoop ? markerHandleRect(overlay, frames, m.loopStart - m.crossfade) : Rect{};
|
||||
if (m.hasLoop && contains(tabRect, x, y)) {
|
||||
tab.hit = true;
|
||||
tab.area = static_cast<std::int64_t>(tabRect.width) * tabRect.height;
|
||||
}
|
||||
|
||||
const int markerHit = markerAtPoint(overlay, frames, markerFrames, 3, x, y);
|
||||
Candidate marker;
|
||||
if (markerHit >= 0) {
|
||||
marker.hit = true;
|
||||
marker.area = static_cast<std::int64_t>(2 * kMarkerGrabWidth + 1) * overlay.rect.height;
|
||||
}
|
||||
|
||||
// Node checked before marker on an area tie so a degenerate (zero-height) overlay still
|
||||
// prefers the node — unreachable in practice (sample_bands floors the band well above it),
|
||||
// kept only so this arbitration has one well-defined answer for every input, not just the
|
||||
// ones the current geometry constants happen to produce.
|
||||
if (node.hit && (!tab.hit || node.area <= tab.area) && (!marker.hit || node.area <= marker.area)) {
|
||||
return splineOverlayClick(overlay, x, y, gesture, /*addOnEmptySpace=*/false);
|
||||
}
|
||||
if (tab.hit && (!marker.hit || tab.area <= marker.area)) {
|
||||
beginMarkerDrag(WaveMarker::kLoopXfade, m, frames, x);
|
||||
return true;
|
||||
}
|
||||
const std::int64_t markerFrames[3] = {m.start, m.loopStart, m.loopEnd};
|
||||
const int hit = markerAtPoint(overlay, frames, markerFrames, 3, x, y);
|
||||
if (hit >= 0) {
|
||||
beginMarkerDrag(static_cast<WaveMarker>(hit), m, frames, x);
|
||||
if (marker.hit) {
|
||||
beginMarkerDrag(static_cast<WaveMarker>(markerHit), m, frames, x);
|
||||
return true;
|
||||
}
|
||||
// Nothing else wanted the click: now the drawn contour may take the empty space.
|
||||
@@ -98,10 +132,7 @@ bool ReaSamplerEditor::splineOverlayClick(const OverlayArea& waveArea, int x, in
|
||||
const VelocityCurve::Box box = splineOverlayBox(waveArea);
|
||||
VelocityCurve& contour = splineFor(overlayEnv_);
|
||||
SplineEdit edit = resolveSplineEdit(contour, box, gesture, x, y);
|
||||
// resolveSplineEdit's outside-box grab/delete/toggle allowance (pointAtPixel's radius has
|
||||
// no box check of its own) was designed for the popup's inset ring; the overlay box has NO
|
||||
// inset (splineOverlayBox), so honoring it here would extend the grab halo 6px into the
|
||||
// inter-band pad. kAdd already requires in-box (resolveSplineEdit's own check).
|
||||
// The overlay's outside-box narrowing — see spline_edit.h's grammar note for why.
|
||||
if (edit.kind != SplineEditKind::kNone && edit.kind != SplineEditKind::kAdd &&
|
||||
!(x >= box.left && x < box.left + box.width && y >= box.top && y < box.top + box.height)) {
|
||||
edit = SplineEdit{};
|
||||
|
||||
Reference in New Issue
Block a user