Phase S FB1 (r11): Sample-view recomposition — knob deck + elastic full-width hero + curve popup w/ right-click delete + post-mixer master gain (ComponentState v8)

This commit is contained in:
2026-07-27 23:08:04 -04:00
parent 27b2661ef2
commit 43155cf320
17 changed files with 1942 additions and 324 deletions
+71 -6
View File
@@ -33,7 +33,8 @@
#include "editor_geometry.h" // Rect (the shell's sub-rect type, shared with the pure modules)
#include "envelope_edit.h" // EnvClampBounds / NodeHit (S-VIEW-3 envelope node hit-test/edit)
#include "envelope_overlay.h" // AmpEnvelope / EnvNode (S-VIEW-3 envelope overlay draw seam)
#include "param_slider.h" // ControlRow (the S12/S15/S16 control-surface geometry)
#include "knob_deck.h" // DeckGroupDesc / DeckLayout (r11 Sample-face knob deck, FB1)
#include "param_slider.h" // ControlRow + the FA4 radial-knob primitive (S12/S15/S16 + r11)
#include "peaks.h" // Envelope (the cached peak thumbnail)
#include "sample_map.h" // SampleChoice, BankChoice, PerformanceMap (the shell's snapshot)
#include "velocity_curve.h" // VelocityCurve (S-VIEW-10 transfer-curve editor state)
@@ -80,9 +81,11 @@ private:
// marker on the S11 waveform surface (which marker is in waveMarker_); kEnvNode is a
// draggable envelope breakpoint on the Sample-view hero overlay (S-VIEW-3, which node in
// envNode_); kCurveNode is a draggable velocity-curve control point in the S-VIEW-10
// transfer-curve editor (which point in curvePointIndex_).
// transfer-curve editor (which point in curvePointIndex_); kDeckKnob is a GRAB-ANCHORED
// vertical radial-knob drag on the r11 Sample-face deck/cluster (which control in
// dragParamId_; the value at grab in dragKnobStartValue_ — no jump on grab, FA4).
enum class DragKind { kNone, kRootMarker, kZoneLow, kZoneHigh, kZoneBody, kWaveMarker,
kScrollThumb, kParamSlider, kEnvNode, kCurveNode };
kScrollThumb, kParamSlider, kEnvNode, kCurveNode, kDeckKnob };
// The parameter controls on the setup surface (S12 AHDSR + the S15/S16 control surfaces).
// The int value is the ControlDesc id the pure param_slider hit-test returns; the shell
@@ -103,6 +106,12 @@ private:
kPitchEnvDecay, // AD pitch decay (S16)
kPitchEnvDepth, // AD pitch depth in +/- semitones (S16)
kKeyTrack, // S-VIEW-6 key-tracking 0..200% (lives on PerformanceZone, not ZonePlaySeconds)
// r11 deck-only controls (FB1): processor-side per-instance params, NOT zone params —
// routed to the processor setters, never through applyZoneControl / the map.
kVoiceCount, // Phase S 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)
kMasterGain, // FB1 post-mixer master gain knob (-inf..+24 dB taper, MASTER group)
kCount
};
@@ -134,8 +143,11 @@ private:
kPreview, // the Sample-view preview-trigger button
kAddZone, // the "+ Add Zone" button
kDeleteZone, // the "Delete" zone button
kControl, // a param-panel control row (index = ControlDesc id)
kControl, // a param-panel control row / deck element (index = control id)
kCurveNode, // a velocity-curve control point (index = point index, S-VIEW-10)
kVelKnob, // the cluster preview-velocity radial knob (r11)
kCurveButton, // the cluster mini curve-preview button (r11 — opens the popup)
kPopupClose, // the curve popup's Close (x) button (r11)
};
struct HoverTarget {
HoverKind kind = HoverKind::kNone;
@@ -146,11 +158,19 @@ private:
#ifdef _WIN32
void paint(HDC hdc);
void paintSample(LICE_IBitmap* bmp, int w, int h); // S-VIEW-2 home face
void paintSample(LICE_IBitmap* bmp, int w, int h); // S-VIEW-2/r11 home face
void paintBrowse(LICE_IBitmap* bmp, int w, int h); // S-VIEW-5 modal picker overlay
void paintZone(LICE_IBitmap* bmp, int w, int h); // S-VIEW-8 zone surface
void paintEmptyState(LICE_IBitmap* bmp, const Rect& area);
void paintControls(LICE_IBitmap* bmp, const Rect& panel, const PerformanceZone& zone); // S12/S15/S16 + keyTrack
void paintControls(LICE_IBitmap* bmp, const Rect& panel, const PerformanceZone& zone); // S12/S15/S16 + keyTrack (Zone surface; the Sample face uses the r11 knob deck)
// --- r11 Sample-face recomposition (FB1) ---------------------------------------
// The knob deck: the fenced task groups (AMP ENVELOPE / PITCH / PITCH ENV / VOICE /
// MASTER) drawn through the L1 kit — group fence + caption + compact caption toggles +
// radial knobs (param_slider's FA4 primitive) with label<->value swap on hover/drag.
void paintKnobDeck(LICE_IBitmap* bmp, const Rect& deckArea, const PerformanceZone& zone);
// The centered curve-popup sheet (wash + title + close + full-size curve editor).
void paintCurvePopup(LICE_IBitmap* bmp, int w, int h);
// Trace the S-VIEW-3 amp-envelope overlay + its draggable node handles over `waveArea` for
// `zone`'s play params, at the sample's wall-clock duration. Shared by the Sample hero band.
void paintEnvelopeOverlay(LICE_IBitmap* bmp, const Rect& waveArea, const PerformanceZone& zone,
@@ -170,6 +190,11 @@ private:
void onMouseDown(int x, int y);
void onMouseMove(int x, int y);
void onMouseUp(int x, int y);
// r11: right-click — the curve popup's PRIMARY node-delete affordance (issue 3c). Only
// acts while the popup is open; a right-click on a popup curve node deletes it through
// the same commit path as Alt-click (deletePoint's endpoint guard makes endpoint
// right-clicks a safe no-op). Everything else ignores right-clicks.
void onMouseRDown(int x, int y);
// Route a click at (x,y) into the param control panel `panel` editing map_.zones[zoneIndex]:
// a toggle segment commits immediately, a slider grab starts a live param-drag (kParamSlider),
@@ -337,6 +362,37 @@ private:
// needs a concrete zone to write. Returns -1 if selectedId_ is empty.
int ensureSampleZone();
// --- r11 knob-deck plumbing (FB1) ---------------------------------------------
//
// The deck is the r11 replacement for the Sample face's slider control strip: the pure
// knob_deck module lays out the fenced groups, param_slider's FA4 primitive owns the
// value<->needle map, and these members own the control-id <-> value binding (the same
// division of labor paintControls/applyControl use for the Zone surface's sliders).
// The deck group descriptors for the current mode: AMP ENVELOPE (Gate: A/H/D/S/R;
// Trigger: Fade In / Length % / Fade Out + two RESERVED blanks so a mode flip never
// reflows the neighbours) / PITCH (Key Track) / PITCH ENV (P.Attack/P.Decay/P.Depth) /
// VOICE (Voices knob + Poly|Mono caption toggle + Retrig|Legato row toggle) / MASTER
// (the FB1 post-mixer Gain knob).
std::vector<DeckGroupDesc> deckGroupDescs(const ZonePlaySeconds& play) const;
// The normalized [0,1] value a deck knob shows for `zone` — zone params route through
// controlValue/keyTrack; the processor-side ids (voice count, master gain, and the
// cluster's preview velocity via the -2 sentinel) read the processor's live value, so
// the knob and its storage are two views on one model (re-read each paint).
double deckControlNorm(int id, const PerformanceZone& zone) const;
// Apply a deck-knob value: zone params write map_.zones[zoneIndex] (live-drag semantics,
// commit on release); processor params (voice count / master gain / preview velocity)
// write through the processor setters immediately (transient — no map edit, no reload).
// zoneIndex is ignored for processor-side ids.
void applyDeckKnob(int zoneIndex, int id, double norm);
// The knob's live value label (shown in place of the name label during hover/drag):
// seconds ("0.123s"), percents ("85%"), source frames ("8820f"), signed semitones
// ("+3.5st"), a voice count ("16"), or the master-gain dB ("-inf"/"+2.4dB").
std::string deckValueLabel(int id, const PerformanceZone& zone) const;
ReaSamplerProcessor* processor_ = nullptr;
// --- Snapshot of the live bank (drawn each paint; refreshed off the audio thread) ---
@@ -444,6 +500,15 @@ private:
Rect dragCurveRect_{};
int dragCurveZone_ = -1;
// r11 deck-knob drag (FB1): the control's normalized value AT GRAB — knobDragValue maps
// the vertical pixel delta from this anchor, so a grab never jumps the value (FA4).
double dragKnobStartValue_ = 0.0;
// r11 curve popup (FB1): open flag — editor-local, never persisted. The popup edits the
// picked capture's one-zone site (effectiveSampleZone / ensureSampleZone), re-resolved
// each paint so a sync-tick refresh mid-open stays coherent.
bool curvePopupOpen_ = false;
// --- Peak-thumbnail cache (mirror of bank_panel; id -> envelope at a bin width) ------
// Keyed by "id|binCount" so a resize recomputes at the new width. Cleared on refresh so
// a bank edit (a re-captured or deleted sample) does not show a stale thumbnail.