Cut core/instrument/ui comment bloat ~49% (comments only, zero code change)
This commit is contained in:
@@ -9,34 +9,28 @@ namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
// Seconds represented by one horizontal pixel under the overlay's linear time base. Zero when the
|
||||
// area is degenerate (the caller then produces no motion). Matches envelope_overlay::timeToX.
|
||||
// Matches envelope_overlay::timeToX. Zero when the area is degenerate (no motion).
|
||||
double secondsPerPixel(const Rect& area, double totalSeconds) {
|
||||
const int w = std::max(0, area.width);
|
||||
if (w <= 0 || totalSeconds <= 0.0) return 0.0;
|
||||
return totalSeconds / static_cast<double>(w);
|
||||
}
|
||||
|
||||
// Seconds per pixel for a GATE time-node drag (FA2): the reciprocal of the overlay's
|
||||
// param-domain gatePxPerSecond(area) scale — sample-length-free, matching
|
||||
// envelope_overlay::gatePolyline exactly so the dragged handle tracks the cursor 1:1 (each
|
||||
// node's x is affine in its own segment duration with slope gatePxPerSecond). Zero when the
|
||||
// area is degenerate.
|
||||
// Reciprocal of the overlay's gatePxPerSecond, matching gatePolyline's scale exactly so a
|
||||
// dragged handle tracks the cursor 1:1.
|
||||
double gateSecondsPerPixel(const Rect& area) {
|
||||
const double pps = gatePxPerSecond(area);
|
||||
return pps > 0.0 ? 1.0 / pps : 0.0;
|
||||
}
|
||||
|
||||
// Level (0..1) represented by one vertical pixel. levelToY spans (height-1) rows for [0,1], so one
|
||||
// pixel is 1/(height-1). Zero when degenerate. Matches envelope_overlay::levelToY.
|
||||
// Matches envelope_overlay::levelToY (spans height-1 rows for [0,1]).
|
||||
double levelPerPixel(const Rect& area) {
|
||||
const int h = std::max(0, area.height);
|
||||
if (h <= 1) return 0.0;
|
||||
return 1.0 / static_cast<double>(h - 1);
|
||||
}
|
||||
|
||||
// True for the nodes the user can grab-and-drag (Origin + ReleaseStart are draw-only anchors).
|
||||
// Origin + ReleaseStart are draw-only anchors, not grabbable.
|
||||
bool isDraggable(EnvNode n) {
|
||||
switch (n) {
|
||||
case EnvNode::Origin:
|
||||
@@ -47,10 +41,8 @@ bool isDraggable(EnvNode n) {
|
||||
}
|
||||
}
|
||||
|
||||
// True when the node belongs to the envelope's active mode. Guards the degenerate cross-mode
|
||||
// write: the degenerate baseline polyline carries a ReleaseEnd vertex regardless of mode, so a
|
||||
// zero-height Trigger-mode grab of it must not write releaseSeconds (and vice versa for Gate
|
||||
// nodes vs Trigger fields). Applied by BOTH the hit-test and the drag resolver so they agree.
|
||||
// Guards the degenerate baseline's cross-mode ReleaseEnd vertex from writing releaseSeconds in
|
||||
// Trigger mode (and vice versa). Applied by both the hit-test and the drag resolver.
|
||||
bool nodeInMode(EnvNode n, EnvMode m) {
|
||||
switch (n) {
|
||||
case EnvNode::AttackEnd:
|
||||
@@ -64,7 +56,7 @@ bool nodeInMode(EnvNode n, EnvMode m) {
|
||||
return m == EnvMode::Trigger;
|
||||
case EnvNode::Origin:
|
||||
case EnvNode::ReleaseStart:
|
||||
return false; // never draggable in any mode (isDraggable filters these anyway)
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -73,18 +65,15 @@ bool nodeInMode(EnvNode n, EnvMode m) {
|
||||
|
||||
NodeHit nodeAtPoint(const AmpEnvelope& env, const Rect& area, double totalSeconds, int x, int y) {
|
||||
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, area, totalSeconds);
|
||||
// NEAREST draggable, mode-matching node within the pick radius wins (Chebyshev distance —
|
||||
// the square grab box); ties break to the earlier draw-order node (FA2). Gate nodes never
|
||||
// coincide (the forward map enforces kGateNodeSepPx separation), so the tie-break only
|
||||
// matters for Trigger's zero-fade-out coincidence: FadeOutStart overlays LengthEnd, WINS the
|
||||
// tie, and can be dragged inward from the right edge. The mode filter keeps the degenerate
|
||||
// baseline's ReleaseEnd vertex from registering as a grabbable node in Trigger mode.
|
||||
// Nearest draggable, mode-matching node within the pick radius wins (Chebyshev distance);
|
||||
// ties go to the earlier draw-order node. Only matters for Trigger's zero-fade-out
|
||||
// coincidence (FadeOutStart overlaps LengthEnd and wins).
|
||||
NodeHit best;
|
||||
int bestDist = kNodeGrabRadius + 1;
|
||||
for (const EnvVertex& v : poly) {
|
||||
if (!isDraggable(v.node) || !nodeInMode(v.node, env.mode)) continue;
|
||||
const int dist = std::max(std::abs(x - v.x), std::abs(y - v.y));
|
||||
if (dist < bestDist) { // strictly closer only: earlier draw order keeps ties
|
||||
if (dist < bestDist) { // strict-less-than keeps ties at the earlier draw order
|
||||
bestDist = dist;
|
||||
best = NodeHit{true, v.node};
|
||||
}
|
||||
@@ -101,16 +90,12 @@ AmpEnvelope resolveNodeDrag(const AmpEnvelope& grabEnv, EnvNode node, const Rect
|
||||
const double secPerPx = secondsPerPixel(area, totalSeconds);
|
||||
if (secPerPx <= 0.0) return out; // degenerate area / duration — no motion
|
||||
const double dSec = static_cast<double>(dxPixels) * secPerPx;
|
||||
// Gate time nodes use the schematic's PARAM-DOMAIN px->seconds scale (FA2) — the reciprocal
|
||||
// of the overlay's gatePxPerSecond, sample-length-free — so the dragged handle tracks the
|
||||
// cursor 1:1. gateTimedWidth >= 1 whenever the area is non-empty, so gateDSec is
|
||||
// well-defined past the degenerate guard above.
|
||||
const double gateDSec = static_cast<double>(dxPixels) * gateSecondsPerPixel(area);
|
||||
|
||||
switch (node) {
|
||||
// --- Gate: each cumulative-time node edits its OWN segment duration. Non-negative
|
||||
// durations ARE the monotonic-in-time guarantee (a node can never cross a neighbour
|
||||
// because every segment stays >= 0), so the [0, max] clamp is the whole constraint.
|
||||
// Gate: each cumulative-time node edits its own segment duration. Non-negative durations
|
||||
// ARE the monotonic-in-time guarantee (a segment can never go negative, so a node can
|
||||
// never cross a neighbour) — the [0, max] clamp is the whole constraint.
|
||||
case EnvNode::AttackEnd:
|
||||
out.attackSeconds =
|
||||
std::clamp(grabEnv.attackSeconds + gateDSec, 0.0, bounds.maxAttackSeconds);
|
||||
@@ -119,8 +104,7 @@ AmpEnvelope resolveNodeDrag(const AmpEnvelope& grabEnv, EnvNode node, const Rect
|
||||
out.holdSeconds = std::clamp(grabEnv.holdSeconds + gateDSec, 0.0, bounds.maxHoldSeconds);
|
||||
break;
|
||||
case EnvNode::DecayEnd: {
|
||||
// Sustain node: X sets decay time, Y sets sustain level (drag DOWN = higher y = lower
|
||||
// level, so subtract the level delta).
|
||||
// X sets decay time, Y sets sustain level (drag down = higher y = lower level).
|
||||
out.decaySeconds = std::clamp(grabEnv.decaySeconds + gateDSec, 0.0, bounds.maxDecaySeconds);
|
||||
const double lvlPerPx = levelPerPixel(area);
|
||||
const double dLevel = -static_cast<double>(dyPixels) * lvlPerPx;
|
||||
@@ -132,17 +116,9 @@ AmpEnvelope resolveNodeDrag(const AmpEnvelope& grabEnv, EnvNode node, const Rect
|
||||
std::clamp(grabEnv.releaseSeconds + gateDSec, 0.0, bounds.maxReleaseSeconds);
|
||||
break;
|
||||
|
||||
// --- Trigger: fades + length are FRACTIONS. X pixels convert to a fraction of the PLAYED
|
||||
// span (fades) or the whole sample (length). Monotonic: fadeIn + fadeOut <= 1 so the
|
||||
// two fade nodes never cross (each clamps against the other), and length in [0, max].
|
||||
//
|
||||
// TRIGGER SEAM — CONVERSION REQUIRED ON BOTH PATHS (Wave 2 shell author, read this):
|
||||
// fadeInFraction/fadeOutFraction in AmpEnvelope are fractions of the played span.
|
||||
// TriggerParams (sampler_core.h) stores the corresponding values as SOURCE FRAMES
|
||||
// (fadeInFrames/fadeOutFrames, int64_t). The shell owes a converter on BOTH directions:
|
||||
// pack (draw): fadeInFrames/fadeOutFrames -> fraction (needs frameCount + rate)
|
||||
// unpack (commit): fraction -> fadeInFrames/fadeOutFrames (same inputs)
|
||||
// See the TRIGGER SEAM note on AmpEnvelope in envelope_overlay.h for the formula.
|
||||
// Trigger: fades + length are fractions. X pixels convert to a fraction of the played
|
||||
// span (fades) or the whole sample (length). fadeIn + fadeOut <= 1 keeps the two fade
|
||||
// nodes from crossing (each clamps against the other).
|
||||
case EnvNode::FadeInEnd: {
|
||||
if (dxPixels == 0) break; // zero-motion grab: no param change, no division
|
||||
const double playSeconds = std::max(0.0, grabEnv.lengthFraction) * totalSeconds;
|
||||
|
||||
Reference in New Issue
Block a user