Q-W1 pt2: core/shell/app relocation + sub-namespaces; one concrete ui::Rect (LTRB fork retired); slot_map split from bank_book; BankIndex→BankModel; 59/59 green
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
// keyboard_strip.h — PURE layout + hit-test + drag math for the S10 capture-first
|
||||
// editor's keyboard strip. NO VST3, NO REAPER, NO SWELL/LICE types at the boundary.
|
||||
// The mirror of editor_geometry / embed_strip / mode_switch: the fiddly rectangle +
|
||||
// note-mapping arithmetic lives here so it is unit-tested outside the DAW, while the
|
||||
// editor shell (reasampler_editor.cpp) draws the strip and marshals mouse events into
|
||||
// these functions.
|
||||
//
|
||||
// The strip maps the full 128-key MIDI span across a horizontal band (the same key-span
|
||||
// idiom embed_strip uses). It serves TWO faces of the S10 editor:
|
||||
// * the SINGLE-CAPTURE fast path (default): one loaded capture with a ROOT MARKER on
|
||||
// the strip, click-a-key (or drag the marker) sets the capture's root note; and
|
||||
// * the opt-in ZONES panel (S10-Z, demoted): each performance zone drawn as a bar over
|
||||
// the keys it covers, with edge-grab resize handles + a body move-handle so a drag
|
||||
// sets low/high (edges) or moves the span (body), and a key-click sets the zone root.
|
||||
//
|
||||
// All interaction resolves through the pure DRAG-DELTA resolver here: the shell captures
|
||||
// a grab on WM_LBUTTONDOWN, feeds each WM_MOUSEMOVE's pixel delta back through
|
||||
// resolveDragNote, and commits the resolved note(s) on WM_LBUTTONUP. Live feedback is the
|
||||
// shell re-drawing the in-flight note; one coherent edit lands on release.
|
||||
//
|
||||
// It reuses the same Rect + contains() as editor_geometry (one shared geometry idiom),
|
||||
// so this header depends on editor_geometry.h rather than redefining a rectangle type.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect, contains — one shared geometry idiom
|
||||
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
// The full MIDI key span the strip maps across its width: 128 keys (0..127). Named
|
||||
// distinctly from embed_strip's kEmbedKeyCount (same value) so the two strips stay
|
||||
// independent — the editor strip may grow octave labels/metrics the embed strip never does.
|
||||
inline constexpr int kStripKeyCount = 128;
|
||||
|
||||
// The width (px) of an edge-grab hit region at each end of a zone bar: a drag started
|
||||
// within this many pixels of the bar's left/right edge resizes that edge; a drag started
|
||||
// anywhere else on the bar moves the whole span. A zone narrower than 2*this has no body
|
||||
// move-handle (both edges win their halves) — deliberate: a 1-key zone is all edges.
|
||||
inline constexpr int kStripEdgeGrabWidth = 6;
|
||||
|
||||
// The strip's regions, derived from the (w x h) band the shell allots it. The keys band
|
||||
// takes the whole area today (a future octave-label lane can carve a sub-band here without
|
||||
// changing callers). Clamped so a degenerate (tiny/zero) size never yields an inverted rect.
|
||||
struct StripLayout {
|
||||
Rect keys; // the key band: the 128-key span maps linearly across keys.width
|
||||
};
|
||||
|
||||
// Divide a (w x h) strip area into its regions. Pure: same inputs -> same layout. A zero or
|
||||
// negative size yields empty rects (no inversion).
|
||||
StripLayout layoutStrip(int w, int h);
|
||||
|
||||
// The x pixel (inside the keys band) of the LEFT edge of key `note` (0..127). The 128-key
|
||||
// span maps linearly across keys.width; key N occupies the half-open pixel range
|
||||
// [keyLeftX(N), keyLeftX(N+1)). Notes are clamped to [0,127]; note==128 maps to the band's
|
||||
// right edge (so a key's right edge is keyLeftX(note+1)). Pure.
|
||||
int keyLeftX(const StripLayout& layout, int note);
|
||||
|
||||
// The half-open pixel rect of a single key `note` (0..127): [keyLeftX(note),
|
||||
// keyLeftX(note+1)) horizontally, the full keys-band height. A malformed (out-of-range)
|
||||
// note clamps to [0,127]. Pure.
|
||||
Rect keyRect(const StripLayout& layout, int note);
|
||||
|
||||
// The rect of the ROOT MARKER for the single-capture fast path: the key cell of `rootNote`,
|
||||
// drawn as a highlighted key. Equivalent to keyRect(layout, rootNote) — a named entry point
|
||||
// so the shell's intent (this is the root marker, not just any key) reads at the call site,
|
||||
// and so a future marker shape (a triangle over the key) has one place to change. Pure.
|
||||
Rect rootMarkerRect(const StripLayout& layout, int rootNote);
|
||||
|
||||
// The MIDI note a point (x, y) lands on, or -1 for a point outside the keys band. Backs
|
||||
// click-to-set-root (single capture) and click-a-key-sets-zone-root (zones). Pure.
|
||||
int keyAtPoint(const StripLayout& layout, int x, int y);
|
||||
|
||||
// The horizontal sub-rect of the keys band for a zone spanning [lowNote, highNote]
|
||||
// (inclusive): [keyLeftX(low), keyLeftX(high+1)) horizontally, the full band height. Notes
|
||||
// clamp to [0,127] and low clamps to <= high, so a malformed zone yields an in-band
|
||||
// (possibly zero-width) rect, never an inverted one. Mirrors embed_strip::zoneSegmentRect.
|
||||
// Pure.
|
||||
Rect zoneBarRect(const StripLayout& layout, int lowNote, int highNote);
|
||||
|
||||
// Which part of a zone bar a grab landed on. The shell uses this to decide what a drag
|
||||
// edits: an edge resizes that boundary; the body moves the whole span; none means the grab
|
||||
// missed the bar entirely (the shell may treat that as a key-click to set the root, or as a
|
||||
// deselect).
|
||||
enum class ZoneGrab {
|
||||
kNone, // the point is not on this zone's bar
|
||||
kLowEdge, // within kStripEdgeGrabWidth of the bar's LEFT edge -> resize low
|
||||
kHighEdge, // within kStripEdgeGrabWidth of the bar's RIGHT edge -> resize high
|
||||
kBody, // on the bar but not an edge -> move the whole span
|
||||
};
|
||||
|
||||
// Classify a grab at (x, y) against ONE zone's bar (low..high). Returns kNone when the
|
||||
// point is off the bar (or off the keys band). On the bar: kLowEdge/kHighEdge when within
|
||||
// kStripEdgeGrabWidth of that edge, else kBody. A narrow bar (< 2*kStripEdgeGrabWidth)
|
||||
// resolves the near half to each edge (no body). The LOW edge wins a tie at the exact
|
||||
// midpoint of a narrow bar (deterministic). Pure.
|
||||
ZoneGrab zoneGrabAt(const StripLayout& layout, int lowNote, int highNote, int x, int y);
|
||||
|
||||
// The zone (index into `lows`/`highs`, draw order) whose bar a grab at (x, y) lands on,
|
||||
// plus which part of it, or {-1, kNone} for a point off every bar. First covering zone in
|
||||
// draw order wins (first-match, mirroring the core's Keymap::resolve + embed_strip). The
|
||||
// arrays are parallel (lows[i]/highs[i] is zone i's inclusive range); `count` is their
|
||||
// length. Pure — no host containers at the boundary (a raw pointer pair, like
|
||||
// embed_strip::zoneAtPoint).
|
||||
struct ZoneBarHit {
|
||||
int zoneIndex = -1;
|
||||
ZoneGrab grab = ZoneGrab::kNone;
|
||||
};
|
||||
ZoneBarHit zoneBarAtPoint(const StripLayout& layout, const int* lows, const int* highs,
|
||||
int count, int x, int y);
|
||||
|
||||
// Resolve a drag to a new MIDI note. Given the note the grabbed field held at grab time
|
||||
// (`startNote`) and the horizontal pixel delta since grab (`dxPixels`), returns the note
|
||||
// the field should now hold: startNote shifted by round(dxPixels / keyWidth), clamped to
|
||||
// [0,127]. keyWidth is derived from the layout (band width / 128); a zero-width band pins
|
||||
// the result to startNote (no motion). This is the single arithmetic behind edge-resize,
|
||||
// body-move (apply to both edges with the SAME delta so the span is preserved), and
|
||||
// root-marker drag. Pure — rounding is to the nearest key so a half-key drag flips at the
|
||||
// key centre. Returns startNote unchanged for dxPixels==0.
|
||||
int resolveDragNote(const StripLayout& layout, int startNote, int dxPixels);
|
||||
|
||||
// Returns true when `note` (0..127) is a NATURAL (white) key in standard 12-tone equal
|
||||
// temperament; false when it is an ACCIDENTAL (black) key. Notes out of the [0,127]
|
||||
// range are clamped to [0,127] before classification (i.e. this never throws/UBs on a
|
||||
// bad input). The 12 semitone positions within an octave:
|
||||
// Natural (white): 0(C) 2(D) 4(E) 5(F) 7(G) 9(A) 11(B)
|
||||
// Accidental (black): 1(C#) 3(D#) 6(F#) 8(G#) 10(A#)
|
||||
// Used by the shell to overlay the two-tone bright/dark piano-key pattern over the
|
||||
// pastel spectral fill (S-VIEW-7). Pure — no layout required, no host types.
|
||||
bool isNaturalKey(int note);
|
||||
|
||||
} // namespace reasampler::instrument::ui
|
||||
Reference in New Issue
Block a user