66 lines
3.6 KiB
C++
66 lines
3.6 KiB
C++
// envelope_edit.h — node hit-test + pixel-delta -> clamped-param inverse map for the draggable
|
|
// envelope nodes and their mid-segment curve knots. The inverse of envelope_overlay's forward
|
|
// (draw) map; both read/write the same StageEnvelope fields, so node drag / knot drag / knob
|
|
// edit are one model — see core/instrument/CLAUDE.md's "envelope overlay" section for why.
|
|
// Mirror of card_drag/waveform_view: drag arithmetic lives here, unit-tested outside the DAW.
|
|
//
|
|
// Time-only nodes drag on X; DecayEnd in an AHDSR drags on both axes (X = decay time, Y =
|
|
// sustain level); a curve knot drags on Y alone (curve_law.h owns the exponent domain). Origin
|
|
// is never draggable, and neither is an AHDSR's ReleaseEnd — anchored to the right edge, with
|
|
// release dragged from ReleaseStart instead. A node is only editable in its own kind.
|
|
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
#include "core/instrument/ui/editor_geometry.h" // Rect
|
|
#include "core/instrument/ui/envelope_overlay.h" // EnvNode, EnvKind, StageEnvelope, EnvVertex
|
|
|
|
namespace reasampler::instrument::ui {
|
|
|
|
// Pick radius (px) around a node's drawn point, in both x and y. Mirrors waveform_view's
|
|
// kMarkerGrabWidth.
|
|
inline constexpr int kNodeGrabRadius = 6;
|
|
|
|
// Per-param clamp bounds the shell supplies — the same maxima its knobs map [0,1] onto.
|
|
// Lower bound is always 0. Defaults are placeholders; the shell overrides with its live domain.
|
|
struct EnvClampBounds {
|
|
double maxAttackSeconds = 4.0;
|
|
double maxHoldSeconds = 4.0;
|
|
double maxDecaySeconds = 4.0;
|
|
double maxReleaseSeconds = 4.0;
|
|
// sustainLevel is always [0,1] and the hold FRACTION is always [0,1] — no shell knob needed.
|
|
};
|
|
|
|
// Which node a grab at (x, y) lands on, given the current envelope/rect/duration (the same
|
|
// inputs buildEnvelopePolyline drew from). `hit` is false for a point off every draggable node.
|
|
// Nearest node within the radius wins (Chebyshev distance); an exact tie goes to the earlier
|
|
// draw-order node, and since knots are appended last, a coincident endpoint handle wins over a
|
|
// knot rather than the drag silently becoming a curve edit. An AHD's DecayEnd is excluded
|
|
// entirely when it coincides with HoldEnd AND holdFraction == 1.0 — there it cannot move
|
|
// (resolveNodeDrag's derivative is 0), so it is dropped rather than won by draw order.
|
|
struct NodeHit {
|
|
bool hit = false;
|
|
EnvNode node = EnvNode::Origin; // meaningful only when hit == true
|
|
};
|
|
// Takes the waveform overlay (not a lane) — see waveform_view.h's overlay contract.
|
|
NodeHit nodeAtPoint(const StageEnvelope& env, const OverlayArea& area, double totalSeconds,
|
|
int x, int y);
|
|
|
|
// Resolves a drag of `node` to a new StageEnvelope. `grabEnv` is the envelope as of grab time
|
|
// (the shell snapshots it on button-down so the delta is absolute, not accumulated);
|
|
// `dxPixels`/`dyPixels` is the pixel delta since grab.
|
|
// * X delta -> the node's time param, at the same scale the forward map drew it, clamped to
|
|
// [0, per-param max].
|
|
// * Y delta -> the level param (AHDSR DecayEnd's sustain) or, on a knot, the segment's curve
|
|
// exponent. Ignored for time-only nodes.
|
|
// * A non-draggable node, an other-kind node, a zero-size area, or totalSeconds <= 0 returns
|
|
// `grabEnv` unchanged.
|
|
// Only the dragged node's param(s) change. Pure.
|
|
StageEnvelope resolveNodeDrag(const StageEnvelope& grabEnv, EnvNode node, const OverlayArea& area,
|
|
double totalSeconds, const EnvClampBounds& bounds,
|
|
int dxPixels, int dyPixels);
|
|
|
|
} // namespace reasampler::instrument::ui
|