Ω-W1-T5 review fixes: unify the drag frame resolve onto frameToX/xToFrame; honest comments on grabbableMarks and the grey-loop-mark contrast trade
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdlib> // std::abs (int overload)
|
||||
|
||||
#include "core/instrument/ui/sample_bands.h" // waveformLanes (the band's lane inventory)
|
||||
#include "core/ui/component_geometry.h" // waveformColumnCount (THE draw chain's columns)
|
||||
@@ -170,13 +169,13 @@ std::int64_t resolveDragFrame(const OverlayArea& area, std::int64_t frameCount,
|
||||
if (dxPixels == 0) return start;
|
||||
const int w = std::max(0, area.rect.width);
|
||||
if (frameCount <= 0 || w <= 0) return start; // no room to move
|
||||
// Proportional shift, rounded to the nearest frame (same linear map as frameToX/xToFrame).
|
||||
const std::int64_t magnitude =
|
||||
(static_cast<std::int64_t>(std::abs(dxPixels)) * frameCount +
|
||||
static_cast<std::int64_t>(w) / 2) /
|
||||
static_cast<std::int64_t>(w);
|
||||
const std::int64_t shift = dxPixels > 0 ? magnitude : -magnitude;
|
||||
return clampFrame(start + shift, frameCount);
|
||||
// THE unified mapping, not a second one: find the start frame's own column (frameToX),
|
||||
// walk it by dxPixels, and read the frame back off the resulting pixel (xToFrame) — so a
|
||||
// drag always resolves to the column under the cursor, never a proportional approximation
|
||||
// of it. A prior independent linear map here could disagree with frameToX/xToFrame's
|
||||
// truncating column partition, most visibly when frames < columns.
|
||||
const int grabX = frameToX(area, frameCount, start);
|
||||
return xToFrame(area, frameCount, grabX + dxPixels);
|
||||
}
|
||||
|
||||
std::int64_t nearestZeroCrossing(const AudioSample* pcm, std::int64_t frames,
|
||||
|
||||
@@ -83,6 +83,14 @@ inline constexpr int kMarkerGrabWidth = 5;
|
||||
// for every f whenever frames <= columns, i.e. exactly where the choice of end is observable).
|
||||
// Above that, several frames share a column and the round trip snaps to the column's first
|
||||
// frame, which is the quantization the shared column already is.
|
||||
//
|
||||
// KNOWN CONVENTION MISMATCH, not a bug: in the frames < columns regime a mark lands on the
|
||||
// LAST column of its frame's span (above), while the spline contour and the staged AHD
|
||||
// polyline map normalized t in [0,1] straight across the same box and so land on a span's
|
||||
// FIRST column instead. Both land inside the frame's own span, so the mapping contract holds,
|
||||
// but the two conventions can disagree by up to a span's width for captures under ~1000
|
||||
// frames. Unifying them is a real option, left alone here since it touches the spline/AHD draw
|
||||
// path rather than this pair.
|
||||
|
||||
// x pixel of `frame`: the column that draws it. `frameCount` itself is a span's EXCLUSIVE end,
|
||||
// not a frame, and maps to area.right(). Frame is clamped to [0, frameCount] first;
|
||||
@@ -169,9 +177,11 @@ int crossfadeWedgeHeight(int x0, int x1, int x);
|
||||
int markerAtPoint(const OverlayArea& area, std::int64_t frameCount, const std::int64_t* frames,
|
||||
int count, int x, int y);
|
||||
|
||||
// Resolves a drag to a new frame: `startFrame` shifted by round(dxPixels * frameCount /
|
||||
// areaWidth), clamped to [0, frameCount]. The shell applies between-marker clamps (e.g.
|
||||
// start <= loopEnd) after this per-marker resolve.
|
||||
// Resolves a drag to a new frame: locates `startFrame`'s own column via frameToX, walks it by
|
||||
// dxPixels, and reads the frame back via xToFrame — the SAME partition, so the result is always
|
||||
// the frame the cursor's column actually draws, never a proportional approximation of it.
|
||||
// Clamped to [0, frameCount]. The shell applies between-marker clamps (e.g. start <= loopEnd)
|
||||
// after this per-marker resolve.
|
||||
std::int64_t resolveDragFrame(const OverlayArea& area, std::int64_t frameCount,
|
||||
std::int64_t startFrame, int dxPixels);
|
||||
|
||||
|
||||
@@ -164,9 +164,11 @@ instrument::ui::WaveMarks ReaSamplerEditor::waveMarksFor(const SetupMarkers& m)
|
||||
}
|
||||
|
||||
instrument::ui::WaveMarks ReaSamplerEditor::grabbableMarks(const SetupMarkers& m) const {
|
||||
// Drawn IFF grabbable. Kept as its own fold because paint and hit-test stay separate
|
||||
// questions, but do NOT re-add a suppression here: the Gate-with-loop-off marks are drawn
|
||||
// grey precisely so they can still be dragged, and dragging one is what turns the enable on.
|
||||
// Drawn IFF grabbable is the product rule, so this is an exact alias of waveMarksFor and
|
||||
// cannot currently diverge from it. Kept as its own seam anyway because paint and hit-test
|
||||
// are separate questions in principle — but do NOT re-add a suppression here: the
|
||||
// Gate-with-loop-off marks are drawn grey precisely so they can still be dragged, and
|
||||
// dragging one is what turns the enable on.
|
||||
return waveMarksFor(m);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,9 +46,11 @@ constexpr Role kRoleStartMarker = Role::OverlayTrace;
|
||||
constexpr Role kRoleLoopMarker = Role::AccentSecondary;
|
||||
// A loop mark whose enable is off keeps its position and its cap — and its full weight, since
|
||||
// it is still draggable. It changes HUE, not opacity: the dim teal it replaces read as broken
|
||||
// rather than as off. Grey against the lime is deliberately under the 3:1 state-indicator floor
|
||||
// the two-neighbour rule (core/ui/CLAUDE.md) sets for a LIVE mark — an inactive control is
|
||||
// exempt, and that lower contrast is the off cue.
|
||||
// rather than as off. Grey against the lime deliberately sits under the 3:1 state-indicator
|
||||
// floor the two-neighbour rule (core/ui/CLAUDE.md) sets — on a mark that stays LIVE and still
|
||||
// drives the loop enable, so this is NOT the WCAG 1.4.11 inactive-component carve-out. It is a
|
||||
// deliberate trade (the off cue reads as lower contrast on a control that can still be
|
||||
// grabbed), pending Daniel's eye in the DAW. Do not change the colour to chase it.
|
||||
constexpr Role kRoleLoopMarkerOff = Role::TextDim;
|
||||
|
||||
// Mark weights. The crossfade is a SOFT boundary and rides below the loop pair's weight at rest.
|
||||
|
||||
Reference in New Issue
Block a user