instrument: one staged-envelope system — per-segment curves, the sustain-less AHD, and a shared overlay for all three envelopes

Trigger's fade pair folds into the AHD (and goes live); the release anchors right;
Preserve rings its synthetic tail out instead of cutting it. Payload v10.
This commit is contained in:
2026-07-31 08:37:57 -04:00
parent 87d7ceb066
commit 13e8c5c4d9
51 changed files with 3406 additions and 1812 deletions
+32 -20
View File
@@ -17,7 +17,7 @@
#include "core/instrument/ui/deck_groups.h" // DeckParam / DeckGroupId / sampleDeckGroups
#include "core/instrument/ui/editor_geometry.h" // Rect (shared sub-rect type)
#include "core/instrument/ui/envelope_edit.h" // EnvClampBounds / NodeHit (envelope node hit-test/edit)
#include "core/instrument/ui/envelope_overlay.h" // AmpEnvelope / EnvNode (envelope overlay draw seam)
#include "core/instrument/ui/envelope_overlay.h" // StageEnvelope / EnvNode (envelope overlay draw seam)
#include "core/instrument/ui/knob_deck.h" // DeckGroupDesc / DeckLayout (the deck band)
#include "core/instrument/ui/sample_bands.h" // SampleBands (the band-stack allocator)
#include "core/instrument/ui/sample_chrome.h" // ChromeRects (chrome-band interior)
@@ -41,7 +41,6 @@ using instrument::map::PlaySeconds;
using instrument::map::SampleChoice;
using instrument::map::SampleRefEntry;
using instrument::map::SampleRefs;
using instrument::ui::AmpEnvelope;
using instrument::ui::ChromeRects;
using instrument::ui::DeckGroupDesc;
using instrument::ui::EnvClampBounds;
@@ -49,6 +48,7 @@ using instrument::ui::EnvNode;
using instrument::ui::OverlayArea;
using instrument::ui::Rect;
using instrument::ui::SampleBands;
using instrument::ui::StageEnvelope;
class ReaSamplerProcessor;
@@ -81,6 +81,11 @@ private:
enum class DragKind { kNone, kRootMarker, kWaveMarker, kScrollThumb, kEnvNode,
kCurveNode, kDeckKnob };
// Which envelope the waveform overlay is drawing and editing. Exclusive, and kNone is a
// valid resting state — the editor opens there. Transient view state: never persisted,
// never a parameter.
enum class OverlayEnv { kNone, kAmp, kPitch, kFilter };
// Controls on the setup surface. The int value is the opaque control id the pure
// knob_deck hit-test returns; the shell maps it to the one parameter set or a
// processor-side per-instance setter. The id space and the deck's group composition are
@@ -107,6 +112,8 @@ private:
kChanStereo, // the stereo channel-mode segment
kPreview, // the preview-trigger button
kControl, // a knob-deck element (index = control id)
kInnerDial, // a knob cell's inner curve dial (index = the OUTER control id)
kEnvRadio, // an envelope deck's overlay-select radio (index = radio control id)
kCurveNode, // a velocity-curve control point (index = point index)
kVelKnob, // the chrome preview-velocity radial knob
kStripKey, // a piano-strip key (index = MIDI note); carries the name tooltip
@@ -302,8 +309,8 @@ private:
// pitch envelope) — wall-clock seconds, rate-free; the build resolves to frames.
// The normalized [0,1] display value for control `id` given `play` (seconds -> 0..1 over
// a fixed ceiling, sustain 0..1 as-is, %-length/fade frames -> 0..1, semitone depth
// centered at 0.5).
// a fixed ceiling, levels and fractions as-is, semitone depth centered at 0.5, curve
// exponents over their logarithmic travel).
double controlValue(int id, const PlaySeconds& play) const;
// Applies a committed control interaction to `play`: a knob's normalized `value` or a
@@ -315,22 +322,22 @@ private:
// over the knob's 0..1).
void applyParamControl(int id, double value, int segment);
// The Trigger fade-in/out knob full-scale, in source frames: kFadeMaxSeconds resolved
// against the live rate — never a baked-in rate. Returns 0 when the rate is unknown.
double fadeMaxFrames() const;
// The overlay speaks one StageEnvelope whichever envelope is active; pack/unpack are the
// only place that knows which stored struct each `which` maps onto, so the drawn shape and
// a committed node drag can never disagree about it. AHDSR seconds are rate-free and copy
// 1-to-1; an AHD additionally needs the wall-clock span its Hold fraction is taken against,
// which is where `frames`/`startFrame` and the live rate come in.
// envelope_overlay's AmpEnvelope stores Trigger fades as fractions of the played span,
// while the parameter set stores source frames — pack/unpack own that conversion (see
// envelope_overlay.h's trigger-seam note). `frames` is total source frames; AHDSR
// seconds are rate-free and copy 1-to-1.
// PACK (draw): play params -> StageEnvelope. `startFrame` is the effective start point.
StageEnvelope packEnvelope(OverlayEnv which, const PlaySeconds& play, std::int64_t frames,
std::int64_t startFrame) const;
// PACK (draw): play params -> AmpEnvelope. `startFrame` is the effective start point.
AmpEnvelope packEnvelope(const PlaySeconds& play, std::int64_t frames,
std::int64_t startFrame) const;
// UNPACK (commit): an edited StageEnvelope -> the play params, in place.
void unpackEnvelope(OverlayEnv which, const StageEnvelope& env, PlaySeconds& play) const;
// UNPACK (commit): an edited AmpEnvelope -> the play params, in place.
void unpackEnvelope(const AmpEnvelope& env, std::int64_t frames, std::int64_t startFrame,
PlaySeconds& play) const;
// The radio control id that selects `which`, and its inverse. One table, so the deck's
// radio and the overlay can never drift apart.
static OverlayEnv overlayEnvForRadio(int radioId);
// Clamp bounds envelope_edit uses, matching the sliders' own domains so a node drag can
// never produce a param a slider couldn't.
@@ -417,16 +424,21 @@ private:
WaveMarker waveMarker_ = WaveMarker::kStart;
SetupMarkers dragStartMarkers_;
std::int64_t dragSampleFrames_ = 0; // decoded length of the sample under the drag
std::int64_t dragStartFrame_ = 0; // effective start point at grab time; for env-node drag
// Scrollbar-thumb drag: the offset at grab time. kDeckKnob drag: which control id.
int dragStartScrollOffset_ = 0;
int dragParamId_ = -1; // control id under a kDeckKnob drag; -2 = preview-vel knob
// The cell whose INNER dial is under a kDeckKnob drag (dragParamId_ then holds the curve
// control), so the paint side can light the right ring. -1 when the grab was the outer knob.
int dragInnerCellId_ = -1;
// Envelope-node drag: which node + the AmpEnvelope snapshotted at grab (absolute-delta
// Which envelope the overlay draws and edits (kNone = none, the opening state).
OverlayEnv overlayEnv_ = OverlayEnv::kNone;
// Envelope-node drag: which node + the StageEnvelope snapshotted at grab (absolute-delta
// contract, per envelope_edit's grabEnv).
EnvNode envNode_ = EnvNode::Origin;
AmpEnvelope dragStartEnv_{};
StageEnvelope dragStartEnv_{};
// Velocity-curve node drag: which point, the curve snapshotted at grab
// (resolvePointDrag's absolute-delta contract), and the grab-time box rect.