Q-W2v: split VST god-modules — editor 8 face-axis TUs (+pure layout hoist), processor 3 TUs, component_state_io codec split (extension drops the voice engine), zone_params.h, core/wire putLE; formats frozen, 61/61 green

This commit is contained in:
2026-07-29 10:56:09 -04:00
parent 9d5783453c
commit ea86f540b8
37 changed files with 6202 additions and 5352 deletions
+23
View File
@@ -3,6 +3,8 @@
#include "core/instrument/ui/browser_scroll.h"
#include "core/instrument/ui/editor_geometry.h" // kPad / kTitleHeight / kNavButtonWidth (Q-W2v hoist)
#include <algorithm>
#include <cctype>
@@ -155,4 +157,25 @@ std::vector<int> filterNameIndices(const std::vector<std::string>& names,
return out;
}
// The Browse-modal regions (hoisted from the editor shell, Q-W2v/T2-06 — body verbatim;
// the band metrics come from editor_geometry, the search height from searchBoxRect).
BrowseModal computeBrowseModal(int w, int h) {
constexpr int kBrowseFooterH = 30;
BrowseModal m;
const int titleH = (std::min)(kTitleHeight, h);
m.title = Rect::ltrb(0, 0, w, titleH);
m.back = Rect::ltrb(w - kPad - kNavButtonWidth, 2, w - kPad, (std::max)(2, titleH - 2));
// Search box below the title, spanning the width (searchBoxRect lays it out from 0).
const Rect sb = searchBoxRect(w);
m.search = Rect::ltrb(kPad, titleH, w - kPad, titleH + sb.height);
const int footerTop = (std::max)(m.search.bottom(), h - kBrowseFooterH);
m.content = Rect::ltrb(0, m.search.bottom(), w, footerTop);
// Footer: Cancel (left) + Load (right).
const int fTop = footerTop + 3;
const int fBot = (std::max)(fTop, h - 3);
m.cancel = Rect::ltrb(kPad, fTop, kPad + 90, fBot);
m.confirm = Rect::ltrb(w - kPad - 90, fTop, w - kPad, fBot);
return m;
}
} // namespace reasampler::instrument::ui
+17
View File
@@ -104,4 +104,21 @@ bool nameMatchesQuery(const std::string& name, const std::string& query);
std::vector<int> filterNameIndices(const std::vector<std::string>& names,
const std::string& query);
// --- The Browse-modal (S-VIEW-5) top-level regions (Q-W2v hoist, T2-06) -------
//
// A title band with a Back button, the search box, the browser sub-area (tabs + card
// grid — layoutBrowser's origin), and a footer with Cancel / Load-confirm. The picker
// covers the full window (F3: full-window overlay). Draw + hit-test both derive from
// this single layout so they never drift. Homed here (not editor_geometry) because the
// search-box height feeds it — browser_scroll already owns the search/scroll geometry.
struct BrowseModal {
Rect title;
Rect back; // the "Back" title-band button
Rect search; // the type-to-filter box (absolute)
Rect content; // the browser sub-area (tabs + grid) — layoutBrowser's origin
Rect cancel; // footer Cancel
Rect confirm; // footer Load (confirm)
};
BrowseModal computeBrowseModal(int w, int h);
} // namespace reasampler::instrument::ui
+146
View File
@@ -159,4 +159,150 @@ bool addZoneHitTest(const KeymapEditorLayout& layout, int x, int y) {
return contains(layout.addZoneButton, x, y);
}
// --- r11 Sample / Zone face layout (Q-W2v hoist, T2-06) ----------------------
// Bodies moved verbatim from the reasampler_editor shell (behavior-identical); the
// only signature change is clusterRects' `knobSize` parameter (formerly knob_deck's
// kDeckKnobSize read directly — passed in so this module stays knob_deck-free).
namespace {
// Fixed band metrics (formerly the editor shell's anon-ns constants).
constexpr int kHeroMinHeight = 150; // the elastic hero's floor (r11)
constexpr int kClusterHeight = 52; // root strip + preview + vel knob + curve btn + channel toggle
constexpr int kStripBandHeight = 40; // the keyboard-strip band height (root strip + zone strip)
// The r11 cluster's fixed right-anchored run (left -> right: Preview button, the radial
// preview-velocity knob cell, the mini curve-preview button, Mono|Stereo).
constexpr int kPreviewBtnW = 64;
constexpr int kVelCellW = 48; // the Vel knob cell (deck cell grammar)
constexpr int kCurveBtnSize = 28; // the square curve-preview button
// The S7 mono/stereo toggle segments.
constexpr int kChanSegW = 52;
constexpr int kChanSegH = 18;
} // namespace
// r11 band order: title (fixed) -> hero (ELASTIC: absorbs all height left after the fixed
// bands, floor kHeroMinHeight) -> cluster (fixed) -> deck (fixed height `deckH`, bottom-
// anchored). When the window is too short for the floor (below the checkSizeConstraint
// minimum — a defensive case), the hero keeps its floor and the lower bands clip past the
// window bottom gracefully.
SampleBands computeSampleBands(int w, int h, int deckH) {
SampleBands b;
const int titleH = (std::min)(kTitleHeight, h);
b.title = Rect::ltrb(0, 0, w, titleH);
// Two nav buttons right-anchored in the title band (Browse then Zone).
const int navTop = 2;
const int navBot = (std::max)(navTop, titleH - 2);
const Rect zone = Rect::ltrb(w - kPad - kNavButtonWidth, navTop, w - kPad, navBot);
const Rect browse = Rect::ltrb(zone.x - 4 - kNavButtonWidth, navTop, zone.x - 4, navBot);
b.navBrowse = browse;
b.navZone = zone;
int deckTop = h - kPad - deckH;
int clusterTop = deckTop - kClusterHeight - 4;
int heroBottom = clusterTop - 4;
if (heroBottom - titleH < kHeroMinHeight) {
heroBottom = titleH + kHeroMinHeight; // hero floor wins; lower bands clip below
clusterTop = heroBottom + 4;
deckTop = clusterTop + kClusterHeight + 4;
}
b.hero = Rect::ltrb(kPad, titleH, w - kPad, heroBottom);
b.cluster = Rect::ltrb(0, clusterTop, w, clusterTop + kClusterHeight);
b.deck = Rect::ltrb(kPad, deckTop, w - kPad, deckTop + deckH);
return b;
}
// Draw + hit-test both derive from this ONE formula.
ClusterRects clusterRects(const Rect& cluster, const Rect& chanMono, int knobSize) {
ClusterRects r;
const int stripTop = cluster.y + (cluster.height - kStripBandHeight) / 2;
const int stripBot = stripTop + kStripBandHeight;
const int curveTop = cluster.y + (cluster.height - kCurveBtnSize) / 2;
r.curveBtn = Rect::ltrb(chanMono.x - kPad - kCurveBtnSize, curveTop,
chanMono.x - kPad, curveTop + kCurveBtnSize);
r.velCell = Rect::ltrb(r.curveBtn.x - kPad - kVelCellW, stripTop,
r.curveBtn.x - kPad, stripBot);
const int knobLeft = r.velCell.x + (kVelCellW - knobSize) / 2;
r.velKnob = Rect::ltrb(knobLeft, r.velCell.y, knobLeft + knobSize,
r.velCell.y + knobSize);
r.velLabel = Rect::ltrb(r.velCell.x, r.velKnob.bottom(), r.velCell.right(), r.velCell.bottom());
r.preview = Rect::ltrb(r.velCell.x - kPad - kPreviewBtnW, stripTop,
r.velCell.x - kPad, stripBot);
r.rootStrip = Rect::ltrb(cluster.x + kPad, stripTop, r.preview.x - kPad, stripBot);
return r;
}
ChannelToggleRects channelToggleRects(const Rect& area) {
const int top = area.y + (area.height - kChanSegH) / 2;
const int right = area.right() - kPad;
const Rect stereo = Rect::ltrb(right - kChanSegW, top, right, top + kChanSegH);
const Rect mono = Rect::ltrb(stereo.x - kChanSegW, top, stereo.x, top + kChanSegH);
return {mono, stereo};
}
Rect zoneContentArea(int w, int h) {
const int titleH = (std::min)(kTitleHeight, h);
return Rect::ltrb(0, titleH, w, h);
}
Rect zoneBackRect(int w, int h) {
return Rect::ltrb(w - kPad - kNavButtonWidth, 2, w - kPad,
(std::max)(2, (std::min)(kTitleHeight, h) - 2));
}
Rect zoneAddRect(const Rect& content) {
return Rect::ltrb(content.x + kPad, content.y + 4, content.x + kPad + 96,
content.y + 4 + 20);
}
Rect zoneDeleteRect(const Rect& addR) {
return Rect::ltrb(addR.right() + 8, addR.y, addR.right() + 8 + 64, addR.bottom());
}
// Zone content sits below the "+ Add Zone" affordance (top+4, height 20) with a 12px
// gap, padded kPad horizontally. All call sites use this formula.
Rect zonesStripArea(const Rect& content) {
const int stripTop = content.y + 4 + 20 + 12; // addR.bottom() + 12
return Rect::ltrb(content.x + kPad, stripTop, content.right() - kPad,
stripTop + kStripBandHeight);
}
// Anchored off zonesStripArea.bottom() so the legend top tracks the strip bottom
// without re-inlining the strip arithmetic here.
Rect noteEntryFieldsArea(const Rect& content) {
const int stripBottom = zonesStripArea(content).bottom();
const int top = stripBottom + 8; // legendTop (== zonesStripArea.bottom() + 8)
return Rect::ltrb(content.x + 8 + 128, top, content.right() - 8, top + 18);
}
Rect noteEntryFieldRect(const Rect& fields, int f) {
if (f < 0 || f > 2 || fields.width <= 0) return Rect{};
const int segW = fields.width / 3;
const int left = fields.x + f * segW + (f > 0 ? 4 : 0); // small inter-field gap
const int right = (f == 2) ? fields.right() : fields.x + (f + 1) * segW;
return Rect::ltrb(left, fields.y, right, fields.bottom());
}
Rect zonesControlPanel(const Rect& content) {
const Rect strip = zonesStripArea(content);
const int panelTop = strip.bottom() + 8 + 18 + 8; // strip + the 18px legend row + gap
return Rect::ltrb(content.x + kPad, panelTop, content.right() - kPad,
content.bottom() - 4);
}
// FB2 (R11-F2 parity): the deck lays out from the panel top (top-anchored), with a
// column at the panel's right reserved for the mini curve-preview button so no deck row
// starts inside it.
Rect zonesDeckArea(const Rect& content) {
const Rect panel = zonesControlPanel(content);
return Rect::ltrb(panel.x, panel.y, panel.right() - kCurveBtnSize - kPad, panel.bottom());
}
Rect zonesCurveButton(const Rect& content) {
const Rect panel = zonesControlPanel(content);
return Rect::ltrb(panel.right() - kCurveBtnSize, panel.y, panel.right(), panel.y + kCurveBtnSize);
}
} // namespace reasampler::instrument::ui
+82
View File
@@ -139,4 +139,86 @@ ZoneHit zoneHitTest(const KeymapEditorLayout& layout, int zoneCount, int x, int
// True if (x, y) lands on the "Add Zone" button. Pure.
bool addZoneHitTest(const KeymapEditorLayout& layout, int x, int y);
// --- r11 Sample / Zone face layout (Q-W2v hoist, T2-06) ----------------------
//
// The capture-first editor's band/cluster/zone-surface layout math, hoisted out of the
// reasampler_editor shell where it had accreted untestable (the §2 scope gap). Draw and
// hit-test both derive every rect from these ONE formulas so they can never drift; the
// shell only draws + routes. The Browse-modal layout lives in browser_scroll (its search
// box height feeds it — dependency-clean placement beside its scroll/search siblings).
// Shared band metrics (the shell's remaining direct uses: horizontal padding + the
// title-band height; everything else is internal to the layout functions below).
inline constexpr int kPad = 8;
inline constexpr int kTitleHeight = 26;
inline constexpr int kNavButtonWidth = 62; // Browse / Zone / Back title-band buttons
// The r11 Sample-face bands (top->bottom): a TITLE band (name + Browse/Zone nav), the
// FULL-WIDTH ELASTIC HERO (absorbs all height left after the fixed bands, floor
// kHeroMinHeight), the ROOT + PREVIEW CLUSTER, and the bottom-anchored KNOB DECK
// (height `deckH` from the pure knob_deck wrap). When the window is too short for the
// hero floor (below the checkSizeConstraint minimum — defensive), the hero keeps its
// floor and the lower bands clip past the window bottom gracefully.
struct SampleBands {
Rect title; // top: name + Browse/Zone nav buttons
Rect navBrowse; // the "Browse" title-band button
Rect navZone; // the "Zone" title-band button
Rect hero; // the FULL-WIDTH ELASTIC hero waveform + S-VIEW-3 envelope overlay
Rect cluster; // root strip + preview + vel knob + curve button + channel toggle
Rect deck; // the bottom-anchored knob deck (height from the pure knob_deck wrap)
};
SampleBands computeSampleBands(int w, int h, int deckH);
// The r11 cluster sub-rects: the root strip keeps the left side at REMAINDER width; the
// right side is the fixed-width right-anchored run (Preview 64 · Vel knob cell 48 · curve
// preview button 28 · Mono|Stereo). `knobSize` is the deck knob square (knob_deck's
// kDeckKnobSize — passed in so this module does not depend on knob_deck).
struct ClusterRects {
Rect rootStrip; // remainder-width fenced root strip
Rect preview; // the preview-trigger button
Rect velCell; // the radial preview-velocity knob cell (knob + label band)
Rect velKnob; // the knob square at the cell's top
Rect velLabel; // the label band beneath it
Rect curveBtn; // the mini curve-preview button (opens the popup)
};
ClusterRects clusterRects(const Rect& cluster, const Rect& chanMono, int knobSize);
// The S7 mono/stereo toggle: a two-segment control right-anchored in `area`, vertically
// centered. Returns {mono-segment, stereo-segment}, side by side.
struct ChannelToggleRects {
Rect mono;
Rect stereo;
};
ChannelToggleRects channelToggleRects(const Rect& area);
// The Zone-view (S-VIEW-8) content area: the whole window below the title band.
Rect zoneContentArea(int w, int h);
// The Zone/Browse "Back" title-band button (right-anchored — the same slot the Sample
// face's Zone nav button occupies).
Rect zoneBackRect(int w, int h);
// The "+ Add Zone" affordance at the top of the Zone content, and the "Delete" button
// beside it (Delete only draws/hits when a zone is selected).
Rect zoneAddRect(const Rect& content);
Rect zoneDeleteRect(const Rect& addR);
// The Zone-view keyboard strip rect: below the "+ Add Zone" affordance with a 12px gap,
// padded kPad horizontally.
Rect zonesStripArea(const Rect& content);
// The S12 numeric-entry field ROW area inside the Zones legend (a band to the right of
// the sample label), and the rect of field `f` (0=low, 1=high, 2=root) within it —
// three equal segments left-to-right. An out-of-range index yields an empty rect.
Rect noteEntryFieldsArea(const Rect& content);
Rect noteEntryFieldRect(const Rect& fields, int f);
// The per-zone parameter panel below the strip + the one-line legend, running to the
// content bottom; the FB2 knob-deck area within it (a column at the right reserved for
// the mini curve-preview button); and that button's rect (the cluster's 28px square,
// right-anchored at the panel top).
Rect zonesControlPanel(const Rect& content);
Rect zonesDeckArea(const Rect& content);
Rect zonesCurveButton(const Rect& content);
} // namespace reasampler::instrument::ui