Retire the zone system: one capture = one parameter set, and re-seam the engine and Sample face into bands

This commit is contained in:
2026-07-30 07:15:54 -04:00
parent a689fb75eb
commit 8d4ccbf841
61 changed files with 5416 additions and 8008 deletions
+133 -143
View File
@@ -1,8 +1,9 @@
// reasampler_editor.h — VST3 IPlugView LICE editor for the ReaSampler 9000 UI. Thin shell:
// hosts a LICE child window, routing host paint/mouse into the pure geometry modules
// (capture_browser, keyboard_strip, sample_map) — default face is the capture browser, then
// single-capture setup, with an opt-in zones panel. All layout/hit-test/drag math lives in
// the pure modules; every edit commits off the audio thread via reloadInstrument.
// (sample_bands, sample_chrome, capture_browser, keyboard_strip, sample_map). The Sample
// face is a three-band stack — chrome, waveform, decks — and the shell TUs split on that
// same axis; Browse is a modal picker over it. All layout/hit-test/drag math lives in the
// pure modules; every edit commits off the audio thread via reloadInstrument.
#pragma once
@@ -16,9 +17,11 @@
#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/knob_deck.h" // DeckGroupDesc / DeckLayout (Sample + Zone knob deck)
#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)
#include "core/audio/peaks.h" // Envelope (the cached peak thumbnail)
#include "core/instrument/map/sample_map.h" // SampleChoice, BankChoice, PerformanceMap (the shell's snapshot)
#include "core/instrument/map/sample_map.h" // SampleChoice, BankChoice, InstrumentParams
#include "core/instrument/engine/velocity_curve.h" // VelocityCurve (transfer-curve editor state)
#ifdef _WIN32
@@ -32,25 +35,26 @@ namespace reasampler::vst {
using audio::AudioSample;
using audio::Envelope;
using instrument::map::BankChoice;
using instrument::map::PerformanceMap;
using instrument::map::PerformanceZone;
using instrument::map::InstrumentParams;
using instrument::map::PlaySeconds;
using instrument::map::SampleChoice;
using instrument::map::SampleRefEntry;
using instrument::map::SampleRefs;
using instrument::map::ZonePlaySeconds;
using instrument::ui::AmpEnvelope;
using instrument::ui::ChromeRects;
using instrument::ui::DeckGroupDesc;
using instrument::ui::EnvClampBounds;
using instrument::ui::EnvNode;
using instrument::ui::Rect;
using instrument::ui::SampleBands;
class ReaSamplerProcessor;
class ReaSamplerEditor : public Steinberg::CPluginView {
public:
// `processor` outlives this editor; the editor reads the live bank through it and drives
// selection/zone edits + reload on user input. May be null (defensive; a real host always
// supplies one).
// selection/parameter edits + reload on user input. May be null (defensive; a real host
// always supplies one).
explicit ReaSamplerEditor(ReaSamplerProcessor* processor);
~ReaSamplerEditor() override;
@@ -65,18 +69,18 @@ protected:
Steinberg::tresult PLUGIN_API onSize(Steinberg::ViewRect* newSize) override;
private:
// Sample is the home/default face. Browse is a full-window modal picker overlaid on
// Sample. Zone is the dedicated multi-zone keymap surface, button-summoned.
enum class View { kSample, kBrowse, kZone };
// Sample is the home/default face (the three-band stack). Browse is a full-window modal
// picker overlaid on it.
enum class View { kSample, kBrowse };
// What a mouse drag is currently editing. kWaveMarker/kEnvNode/kCurveNode track their
// grabbed item in waveMarker_/envNode_/curvePointIndex_; kDeckKnob is a grab-anchored
// knob drag (control in dragParamId_, grab value in dragKnobStartValue_).
enum class DragKind { kNone, kRootMarker, kZoneLow, kZoneHigh, kZoneBody, kWaveMarker,
kScrollThumb, kEnvNode, kCurveNode, kDeckKnob };
enum class DragKind { kNone, kRootMarker, kWaveMarker, kScrollThumb, kEnvNode,
kCurveNode, kDeckKnob };
// 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 zone's play params or a
// knob_deck hit-test returns; the shell maps it to the one parameter set or a
// processor-side per-instance setter.
enum class ParamControl {
kPlayMode = 0, // Gate | Trigger toggle
@@ -93,9 +97,9 @@ private:
kPitchEnvAttack, // AD pitch attack
kPitchEnvDecay, // AD pitch decay
kPitchEnvDepth, // AD pitch depth in +/- semitones
kKeyTrack, // key-tracking 0..200% (lives on PerformanceZone, not ZonePlaySeconds)
// Deck-only controls: processor-side per-instance params, NOT zone params — routed to
// the processor setters, never through applyZoneControl / the map.
kKeyTrack, // key-tracking 0..200% (lives on InstrumentParams, not PlaySeconds)
// Deck-only controls: processor-side per-instance params — routed to the processor
// setters, never through applyParamControl.
kVoiceCount, // polyphony bound (1..32) — a stepped knob in the VOICE group
kVoiceMode, // Poly | Mono caption toggle (VOICE group)
kMonoTrigger, // Retrig | Legato row toggle (VOICE group; live only in Mono)
@@ -103,8 +107,8 @@ private:
kCount
};
// The waveform markers on the single-capture setup surface: start-point + the sustain
// loop's two ends, in draw + hit order.
// The waveform markers on the waveform band: start-point + the sustain loop's two ends,
// in draw + hit order.
enum class WaveMarker { kStart = 0, kLoopStart = 1, kLoopEnd = 2, kCount = 3 };
// The interactive element under the pointer, resolved live in WM_MOUSEMOVE. `index`
@@ -112,9 +116,8 @@ private:
// not applicable.
enum class HoverKind {
kNone,
kNavBrowse, // the Sample-view "Browse" title-band button (opens the Browse modal)
kNavZone, // the Sample-view "Zone" title-band button (opens the Zone surface)
kBack, // the Browse/Zone "back" affordance (returns to Sample)
kNavBrowse, // the chrome "Browse" toolbar button (opens the Browse modal)
kBack, // the Browse "back" affordance (returns to Sample)
kSearchBox, // the browser search box
kFilterTab, // a bank-filter tab (index = tab ordinal, 0 = All)
kCard, // a capture card (index = visible_ index)
@@ -122,13 +125,11 @@ private:
kBrowseCancel, // the Browse modal "Cancel" button
kChanMono, // the mono channel-mode segment
kChanStereo, // the stereo channel-mode segment
kPreview, // the Sample-view preview-trigger button
kAddZone, // the "+ Add Zone" button
kDeleteZone, // the "Delete" zone button
kPreview, // the preview-trigger button
kControl, // a knob-deck element (index = control id)
kCurveNode, // a velocity-curve control point (index = point index)
kVelKnob, // the cluster preview-velocity radial knob
kCurveButton, // the cluster mini curve-preview button (opens the popup)
kVelKnob, // the chrome preview-velocity radial knob
kCurveButton, // the chrome mini curve-preview button (opens the popup)
kPopupClose, // the curve popup's Close (x) button
};
struct HoverTarget {
@@ -138,60 +139,82 @@ private:
bool operator!=(const HoverTarget& o) const { return !(*this == o); }
};
// The three-band stack for the current client size, plus the chrome interior. Every
// paint/hit-test path derives both through this one call so draw and hit-test can never
// disagree about where a band is.
struct FaceLayout {
SampleBands bands;
ChromeRects chrome;
std::vector<DeckGroupDesc> deckDescs;
};
FaceLayout faceLayout(int w, int h) const;
#ifdef _WIN32
void paint(HDC hdc);
void paintSample(LICE_IBitmap* bmp, int w, int h); // home face
void paintSample(LICE_IBitmap* bmp, int w, int h); // home face (band composition)
void paintBrowse(LICE_IBitmap* bmp, int w, int h); // modal picker overlay
void paintZone(LICE_IBitmap* bmp, int w, int h); // zone surface
void paintEmptyState(LICE_IBitmap* bmp, const Rect& area);
// The knob deck: group fence + caption + compact caption toggles + radial knobs with
// label<->value swap on hover/drag. `descs` picks the group set (Sample's deckGroupDescs
// or the Zone panel's zoneDeckGroupDescs); caller anchors (Sample bottom, Zone top).
void paintKnobDeck(LICE_IBitmap* bmp, const Rect& deckArea, const PerformanceZone& zone,
const std::vector<DeckGroupDesc>& descs);
// The mini curve-preview button shared by the Sample cluster + the Zone panel.
void paintCurveButton(LICE_IBitmap* bmp, const Rect& r, const PerformanceZone& zone);
// The centered curve-popup sheet. Edits popupZone() — the Sample face's one-zone site
// or the Zone surface's selected zone.
// --- Band painters (one TU each, mirroring the input side) ---
// Chrome: title band + Browse nav + the control row (root strip, preview, velocity knob,
// curve button, channel toggle).
void paintChrome(LICE_IBitmap* bmp, const FaceLayout& fl, bool empty);
// Waveform: the channel lane(s), the loop/start markers, and the envelope overlay.
void paintWaveform(LICE_IBitmap* bmp, const Rect& band);
// Decks: the group fence + caption + compact caption toggles + radial knobs with
// label<->value swap on hover/drag.
void paintDeck(LICE_IBitmap* bmp, const FaceLayout& fl);
// The mini curve-preview button (chrome) and the modal curve editor it summons.
void paintCurveButton(LICE_IBitmap* bmp, const Rect& r);
void paintCurvePopup(LICE_IBitmap* bmp, int w, int h);
// Traces the amp-envelope overlay + its draggable node handles over `waveArea`.
void paintEnvelopeOverlay(LICE_IBitmap* bmp, const Rect& waveArea, const PerformanceZone& zone,
std::int64_t frames);
// The velocity->amp transfer-curve editor (X = velocity 0-127, Y = amp 0-1); its only
// host is the popup sheet. `r` empty -> draws nothing.
void paintVelocityCurve(LICE_IBitmap* bmp, const Rect& r, const PerformanceZone& zone);
// Mouse-down inside curve-editor box `r` editing map_.zones[zoneIndex]: a node grab
// starts a kCurveNode drag; Alt-click on an interior node deletes it at once; an
// empty-space click adds a point and grabs it. `zoneIndex` must be valid (callers
// materialize first).
void handleCurveMouseDown(const Rect& r, int zoneIndex, int x, int y);
// Left-click while the curve popup is open (modal over both faces): Close /
// outside-wash dismiss, in-box clicks route to the curve machinery, else swallowed.
// Returns true whenever the popup is open (it consumed the click).
bool handlePopupMouseDown(int w, int h, int x, int y);
void paintVelocityCurve(LICE_IBitmap* bmp, const Rect& r);
// Traces the amp-envelope overlay + its draggable node handles over `waveArea`, ONCE at
// full band height (never per lane).
void paintEnvelopeOverlay(LICE_IBitmap* bmp, const Rect& waveArea, std::int64_t frames);
// --- Input: the mouse-down dispatch and its per-band branches ---
void onMouseDown(int x, int y);
// The Browse-modal and Zone-surface halves of the mouse-down dispatch (bodies in
// editor_input_browse_zone.cpp).
// Each returns true when it consumed the click. Called in band order by onMouseDown.
bool mouseDownChrome(const FaceLayout& fl, int x, int y);
bool mouseDownWaveform(const FaceLayout& fl, int x, int y);
bool mouseDownDeck(const FaceLayout& fl, int x, int y);
void mouseDownBrowse(int w, int h, int x, int y);
void mouseDownZone(int w, int h, int x, int y);
// Live drag resolution, split on the same axis; each handles only its own DragKind
// values and is called from onMouseMove's router.
void dragChrome(const FaceLayout& fl, int x, int y); // kRootMarker
void dragWaveform(const FaceLayout& fl, int x, int y); // kEnvNode / kWaveMarker
void dragDeck(int x, int y); // kDeckKnob
void dragBrowse(int x, int y); // kScrollThumb
void dragCurve(int x, int y); // kCurveNode
void onMouseMove(int x, int y);
void onMouseUp(int x, int y);
// Right-click is the curve popup's primary node-delete affordance; only acts while the
// popup is open (deletePoint's endpoint guard makes an endpoint right-click a no-op).
void onMouseRDown(int x, int y);
// Applies a knob/toggle interaction to map_.zones[zoneIndex] for control `id`: ordinary
// controls route through applyControl; kKeyTrack writes the zone's keyTrack scalar
// (0..200% over the knob's 0..1).
void applyZoneControl(int zoneIndex, int id, double value, int segment);
// Mouse-down inside curve-editor box `r`: a node grab starts a kCurveNode drag;
// Alt-click on an interior node deletes it at once; an empty-space click adds a point
// and grabs it.
void handleCurveMouseDown(const Rect& r, int x, int y);
// Left-click while the curve popup is open (modal over the Sample face): Close /
// outside-wash dismiss, in-box clicks route to the curve machinery, else swallowed.
// Returns true whenever the popup is open (it consumed the click).
bool handlePopupMouseDown(int w, int h, int x, int y);
// Resolves the interactive element under (x, y) into hover_, called from WM_MOUSEMOVE.
// Repaints only on change, so an idle move is free. Windows-only.
// Repaints only on change, so an idle move is free. The per-band resolvers mirror the
// mouse-down branches but are read-only. Windows-only.
void resolveHover(int x, int y);
HoverTarget hoverChrome(const FaceLayout& fl, int x, int y) const;
HoverTarget hoverDeck(const FaceLayout& fl, int x, int y) const;
HoverTarget hoverBrowse(int w, int h, int x, int y) const;
HoverTarget hoverCurvePopup(int w, int h, int x, int y) const;
bool isHovered(HoverKind kind, int index) const {
return hover_.kind == kind && hover_.index == index;
}
@@ -215,16 +238,15 @@ private:
#endif
// Re-read the bank (samples + banks) from the live bridge and snapshot the instrument's
// selection + performance map. Main/UI thread only. Called on attach and after any edit.
// selection + parameter set. Main/UI thread only. Called on attach and after any edit.
void refreshFromBank();
// Publishes the edited zones/selection to the processor, then rebuilds the instrument
// off the audio thread. UI thread only.
// Publishes the edited selection + parameters to the processor, then rebuilds the
// instrument off the audio thread. UI thread only.
void commitAndReload();
// Commits `id` as the loaded single-capture selection. Runs reconcileSingleCaptureZones
// first so the previous sample's materialized full-range zone cannot linger and shadow
// the new pick under first-match resolve, then publishes + reloads.
// Commits `id` as the loaded capture. The one parameter set carries over — it governs
// whatever is loaded, so a load swaps the sound, not the settings.
void loadSelection(const std::string& id);
// Recomputes the visible capture cards (samples_ narrowed by activeFilterBankId_ then
@@ -240,8 +262,8 @@ private:
// thread only (file I/O); cleared with the thumbnail cache on refresh.
const std::vector<AudioSample>& monoPcmFor(const std::string& sampleId);
// The effective loop + start markers for the picked capture: the per-zone override when
// one exists in map_, else the bank's loop intrinsic / frame 0. Absent loop ->
// The effective loop + start markers for the loaded capture: the parameter set's
// override when one is set, else the bank's loop intrinsic / frame 0. Absent loop ->
// loopStart==loopEnd==0. `frames` defaults loopEnd when the bank left the loop empty.
struct SetupMarkers {
std::int64_t start = 0;
@@ -251,94 +273,74 @@ private:
};
SetupMarkers pickedMarkers(std::int64_t frames) const;
// Commits an edited marker set for the picked capture as a per-zone loop/start override
// (upsert on the picked id), then reloads off-thread.
void commitPickedMarkers(const SetupMarkers& m);
// Writes `m` into params_ as the loop/start override. Does NOT call commitAndReload —
// callers decide live-drag vs final commit.
void applyMarkers(const SetupMarkers& m);
// Writes `m` as a loop/start override upsert into map_ for selectedId_ (find-or-append).
// Does NOT call commitAndReload — callers decide live-drag vs final commit. selectedId_
// must be non-empty. Returns the updated/appended zone index.
int upsertPickedOverride(const SetupMarkers& m);
// Deck knobs edit a zone's ZonePlaySeconds (play mode + AHDSR; pitch engine + AD pitch
// envelope) — wall-clock seconds, rate-free; the keymap build resolves to frames.
// Deck knobs edit the parameter set's PlaySeconds (play mode + AHDSR; pitch engine + AD
// 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).
double controlValue(int id, const ZonePlaySeconds& play) const;
double controlValue(int id, const PlaySeconds& play) const;
// Applies a committed control interaction to `play`: a knob's normalized `value` or a
// toggle's `segment` (0/1). Mutates `play` in place.
void applyControl(int id, ZonePlaySeconds& play, double value, int segment) const;
void applyControl(int id, PlaySeconds& play, double value, int segment) const;
// Applies a knob/toggle interaction to the ONE parameter set for control `id`: ordinary
// controls route through applyControl; kKeyTrack writes the keyTrack scalar (0..200%
// 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. 44.1 kHz fallback pre-setupProcessing.
// against the live rate — never a baked-in rate. Returns 0 when the rate is unknown.
double fadeMaxFrames() const;
// envelope_overlay's AmpEnvelope stores Trigger fades as fractions of the played span,
// while the zone stores source frames — pack/unpack own that conversion (see
// 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): zone play params -> AmpEnvelope. `startFrame` is the zone's effective
// start point (zone.startPoint.value_or(0)).
AmpEnvelope packEnvelope(const ZonePlaySeconds& play, std::int64_t frames,
// 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 AmpEnvelope -> the zone's play params, in place.
// UNPACK (commit): an edited AmpEnvelope -> the play params, in place.
void unpackEnvelope(const AmpEnvelope& env, std::int64_t frames, std::int64_t startFrame,
ZonePlaySeconds& play) const;
PlaySeconds& play) const;
// Clamp bounds envelope_edit uses, matching the sliders' own domains so a node drag can
// never produce a param a slider couldn't.
EnvClampBounds envClampBounds() const;
// The Sample face and the Zone surface read/write the same one-zone map site.
// effectiveSampleZone returns the picked id's override if present in map_, else a
// product-default zone (not yet materialized — a control edit does that).
PerformanceZone effectiveSampleZone() const;
// The effective root: rootOverride, else the bank intrinsic, else middle C.
// The effective root: params_.rootOverride, else the bank intrinsic, else middle C.
int effectiveRoot() const;
// The live sample rate from the bridge, or 0 when unavailable (caller guards).
double liveSampleRate() const;
// Persisted preview velocity as a 0..1 slider value (MIDI 1..127 -> [0,1]).
double previewVelocity01() const;
// Find-or-materializes the one-zone override for the picked id, appending a
// product-default zone if none exists. Mirror of upsertPickedOverride for a control
// edit. Returns -1 if selectedId_ is empty.
int ensureSampleZone();
// The deck groups: AMP ENVELOPE (Gate A/H/D/S/R; Trigger Fade In/Length %/Fade Out + two
// reserved blanks so a mode flip never reflows neighbours) / PITCH (Key Track) / PITCH
// ENV (P.Attack/P.Decay/P.Depth) / VOICE (Voices knob + Poly|Mono + Retrig|Legato) /
// MASTER (Gain knob).
std::vector<DeckGroupDesc> deckGroupDescs(const PlaySeconds& play) const;
// The popup edits ONE zone per open: the Zone surface's selected zone or the Sample
// face's picked site. popupZone is the read-only resolve; popupZoneIndex is the edit
// target — materializes on the Sample face via ensureSampleZone, never on the Zone
// surface (button only shows for an explicit selection). -1 = no valid target.
PerformanceZone popupZone() const;
int popupZoneIndex();
// The per-zone deck groups both surfaces share: AMP ENVELOPE (Gate A/H/D/S/R; Trigger
// Fade In/Length %/Fade Out + two reserved blanks so a mode flip never reflows
// neighbours) / PITCH (Key Track) / PITCH ENV (P.Attack/P.Decay/P.Depth).
std::vector<DeckGroupDesc> zoneDeckGroupDescs(const ZonePlaySeconds& play) const;
// The full Sample-face deck: the shared groups + the per-instance VOICE (Voices knob +
// Poly|Mono + Retrig|Legato) and MASTER (Gain knob) groups.
std::vector<DeckGroupDesc> deckGroupDescs(const ZonePlaySeconds& play) const;
// The normalized [0,1] value a deck knob shows for `zone` — zone params route through
// The normalized [0,1] value a deck knob shows — parameter-set ids route through
// controlValue/keyTrack; processor-side ids (voice count, master gain, preview velocity
// via the -2 sentinel) read the processor's live value.
double deckControlNorm(int id, const PerformanceZone& zone) const;
double deckControlNorm(int id) const;
// Applies a deck-knob value: zone params write map_.zones[zoneIndex] (commit on
// release); processor params write through the processor setters immediately
// (transient — no map edit, no reload). zoneIndex ignored for processor-side ids.
void applyDeckKnob(int zoneIndex, int id, double norm);
// Applies a deck-knob value: parameter-set ids write params_ (commit on release);
// processor params write through the processor setters immediately (transient — no
// params edit, no reload).
void applyDeckKnob(int id, double norm);
// The knob's live value label shown during hover/drag: seconds, percents, source
// frames, signed semitones, a voice count, or the master-gain dB.
std::string deckValueLabel(int id, const PerformanceZone& zone) const;
std::string deckValueLabel(int id) const;
ReaSamplerProcessor* processor_ = nullptr;
@@ -346,8 +348,8 @@ private:
std::vector<SampleChoice> samples_; // every bank sample, bank order
std::vector<BankChoice> banks_; // the named banks, for the filter tab strip
std::vector<SampleChoice> visible_; // samples_ narrowed by the active bank filter
std::string selectedId_; // the single-capture pick ("" = empty state)
PerformanceMap map_; // the opt-in zones (empty = no zones)
std::string selectedId_; // the loaded capture ("" = empty state)
InstrumentParams params_; // the ONE parameter set governing it
ChannelMode channelMode_ = ChannelMode::Mono; // mono/stereo toggle snapshot
// Mirrors of the processor's persisted voice-system params, refreshed with the rest of the
@@ -357,10 +359,9 @@ private:
VoiceMode voiceMode_ = VoiceMode::Poly;
MonoTrigger monoTrigger_ = MonoTrigger::Retrigger;
// Transient UI state (not persisted; component state carries selection + zones).
// Transient UI state (not persisted; component state carries selection + parameters).
View view_ = View::kSample; // default face is the loaded-sample home
std::string activeFilterBankId_; // "" = All; else a bank id from banks_
int selectedZone_ = -1; // highlighted zone in the Zone surface; -1 = none
// The Browse overlay is a select-then-confirm picker: a click marks a pending pick;
// Confirm/double-click commits it + reloads; Cancel discards it. "" = nothing picked.
@@ -381,11 +382,6 @@ private:
std::string searchQuery_; // type-to-filter narrow; "" = no search
bool searchFocused_ = false; // whether the search box has keyboard focus
// When >= 0, a low/high/root field is being typed (0=low,1=high,2=root); entryText_
// accumulates keystrokes and commits via parseNoteEntry on Enter. -1 = no field editing.
int entryField_ = -1;
std::string entryText_;
// Hover state (transient, never persisted).
HoverTarget hover_; // the interactive element under the pointer
#ifdef _WIN32
@@ -398,10 +394,8 @@ private:
int dragStartY_ = 0; // grab y (px), for the vertical scrollbar-thumb drag
int dragCurX_ = 0; // live cursor x (px) during a drag — updated in onMouseMove
int dragCurY_ = 0; // live cursor y (px) during a drag — updated in onMouseMove
int dragStartLow_ = 0; // the grabbed field's note at grab time
int dragStartHigh_ = 0;
int dragStartRoot_ = 60;
PerformanceMap dragStartMap_; // map_ snapshotted at grab; restored on capture-loss
int dragStartRoot_ = 60; // the root note at grab time
InstrumentParams dragStartParams_; // params_ snapshotted at grab; restored on capture-loss
// Waveform-marker drag: which marker + the marker set snapshotted at grab time, so the
// pixel-delta resolver shifts from the grab-time value and inter-marker clamps use the
@@ -409,12 +403,11 @@ 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; // zone startPoint at grab time (0 if absent); for env-node 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 + zone.
// 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
int dragParamZone_ = -1; // the zone index a kDeckKnob drag edits; -1 = processor-side
// Envelope-node drag: which node + the AmpEnvelope snapshotted at grab (absolute-delta
// contract, per envelope_edit's grabEnv).
@@ -422,19 +415,16 @@ private:
AmpEnvelope dragStartEnv_{};
// Velocity-curve node drag: which point, the curve snapshotted at grab
// (resolvePointDrag's absolute-delta contract), the grab-time box rect (Sample and Zone
// place the editor differently), and which zone the edit lands on.
// (resolvePointDrag's absolute-delta contract), and the grab-time box rect.
int curvePointIndex_ = -1;
VelocityCurve dragStartCurve_ = VelocityCurve::flat();
Rect dragCurveRect_{};
int dragCurveZone_ = -1;
// Deck-knob drag: the normalized value at grab — knobDragValue maps the vertical pixel
// delta from this anchor, so a grab never jumps the value.
double dragKnobStartValue_ = 0.0;
// Curve popup open flag, never persisted. Edits popupZone(), re-resolved each paint so a
// sync-tick refresh mid-open stays coherent (a refresh that drops the target closes it).
// Curve popup open flag, never persisted.
bool curvePopupOpen_ = false;
// Peak-thumbnail cache (mirror of bank_panel), keyed by "id|binCount" so a resize