docs(product): reframe S10 capture-first (browser + single-capture setup); reverse S4 auto-select; settle S-NAME-1 filename rename
This commit is contained in:
@@ -399,48 +399,107 @@ alongside S8.
|
||||
> `docs/product/visual-design-language.md` (all on `dev`). S10–S13 below build with the
|
||||
> current drawing and **adopt the L1 kit when it lands — not gated on Phase L.**
|
||||
|
||||
## S10 — direct-manipulation keymap editor ("ReaSampler 9000" UX overhaul, part 1)
|
||||
**Goal:** Replace the ±1 nudge-button zone editor with a **piano-keyboard-strip zone
|
||||
editor** where the keymap is edited by direct manipulation: a horizontal keyboard span
|
||||
along the top, each zone drawn as a bar over the keys it covers; **drag a zone's edge to
|
||||
change its low/high note, drag the bar's body to move the whole zone, click a key (or the
|
||||
zone's root marker) to set the root note.** No number is ever set by clicking a button 40
|
||||
times. This is the friction Daniel feels every test pass — the current row of seven
|
||||
mini-buttons per zone is the catastrophe. All layout/hit-test math is a **new pure
|
||||
geometry module** (`keyboard_strip` — mirror of `mode_switch`/`editor_geometry`); the LICE
|
||||
draw + drag-state machine is the editor shell. RT discipline untouched (edits still commit
|
||||
off-thread via `commitMapAndReload`). CONTEXT.md §Phase S (ReaSampler 9000 UX — keymap
|
||||
editor).
|
||||
**Verify (in DAW):** a zone's range is set by **dragging its edges on the keyboard strip**
|
||||
(not by nudge clicks); dragging a zone's body moves the whole range preserving its span;
|
||||
clicking a key sets/relocates the zone's root (with the repitched result audible on the
|
||||
next held note); overlapping zones render legibly and resolve first-match as before;
|
||||
zone add/select/delete still work; the pure geometry module is CTest-green (edge-grab
|
||||
regions, body-drag delta, key→note mapping, root hit-test) with no host types at its
|
||||
boundary; the ±1 nudge-button row is gone.
|
||||
**Depends on:** S5 (the `PerformanceMap`/zone model it edits), S1 (the LICE `IPlugView`
|
||||
drag/event routing — extends the click-only `wndProc` to `WM_MOUSEMOVE`/`WM_LBUTTONUP`).
|
||||
**Adopts the Phase L kit when available — not gated on Phase L.** S10 builds its keyboard
|
||||
strip + zone bars with the current LICE drawing; when Phase L's L1 kit lands on `dev`, this
|
||||
surface adopts it (the one source of drawing). The drag machine's `WM_MOUSEMOVE` tracking
|
||||
also lights the kit's **hover** states at near-zero marginal cost once the kit is present.
|
||||
## S10 — capture-first editor: browser + guided single-capture setup ("ReaSampler 9000" UX overhaul, part 1)
|
||||
**Goal (REVISED 2026-07-26 — workflow-first reframe, Daniel):** Rebuild the editor's
|
||||
default face around the **primary flow = one capture, fast**, not a keymap. A giant list of
|
||||
"item" blocks is visually useless; most instances play a *single capture*, and zones are a
|
||||
nice-to-have. So the default view is a **capture browser** (scannable cards with peak
|
||||
thumbnails, name, root/key badge; **bank filter**) feeding a **guided single-capture setup**
|
||||
(root note, play-mode basics, level) — and the keyboard strip serves the *single-capture*
|
||||
case first (shows where the capture sits / its root). **Time-to-first-note is the metric.**
|
||||
Multi-zone keymap editing is **demoted to an opt-in "Zones" panel** (S10-Z below), not the
|
||||
default. The keyboard-strip drag machinery is still built here, but in service of the
|
||||
capture-first layout. All layout/hit-test math is **pure geometry** (new `keyboard_strip` +
|
||||
a `capture_browser` layout module — mirrors of `mode_switch`/`editor_geometry`); the LICE
|
||||
draw + drag-state machine is the editor shell. RT discipline untouched (edits commit
|
||||
off-thread via `commitMapAndReload`); the instrument stays a **read-only bank consumer**.
|
||||
CONTEXT.md §Phase S (ReaSampler 9000 UX — capture-first editor).
|
||||
|
||||
- [ ] Pure `keyboard_strip` geometry module: map a MIDI key span across a strip width
|
||||
(128 keys → pixels, reusing the S6 `embed_strip` key-span idiom); per-zone bar rect from
|
||||
`[lowNote,highNote]`; **edge-grab hit regions** (a few px inside each end = resize handles,
|
||||
the middle = move handle); `pixel→note` and a `keyAtPoint` for click-to-set-root; a
|
||||
drag-delta resolver `(grabbedField, startNote, dxPixels) → newNote`. No VST3/REAPER/LICE
|
||||
types at the boundary; unit-tested outside the DAW (edge grabs, body-move delta, key
|
||||
mapping, clamps low≤high, boundary rounding). Mirror of `mode_switch`/`editor_geometry`.
|
||||
- [ ] Editor shell: draw the keyboard strip + zone bars in LICE (house palette), and drive
|
||||
a **drag-state machine** — `WM_LBUTTONDOWN` grabs an edge/body/key, `WM_MOUSEMOVE`
|
||||
updates the in-flight zone against the pure resolver, `WM_LBUTTONUP` commits via the
|
||||
existing `commitMapAndReload` (off-thread reload; RT path untouched). Live visual feedback
|
||||
while dragging; a single undo-coherent edit on release.
|
||||
- [ ] Click-to-set-root + zone move/resize replace the seven per-row nudge/delete
|
||||
mini-buttons; delete stays as one affordance (a small × on the zone bar or a keystroke).
|
||||
The `zoneHitTest`/±1 nudge path in `editor_geometry` is retired for the new strip (kept
|
||||
only if a numeric fallback is wanted for accessibility — build-time residual, not a fork).
|
||||
**Policy reversal — fresh instance is SILENT, nothing auto-selected (was S4).** The S4
|
||||
"first sample plays" fallback is **removed**: on open with no stored selection, the
|
||||
instrument plays **nothing** and the editor shows a clear **empty state** ("pick a capture")
|
||||
— it does not auto-play sample #1. Retires the `selectSample` first-sample fallback
|
||||
(`sample_map.cpp` "No stored id → fall back to the FIRST sample") and the processor's
|
||||
Tier-0 fallback that resolved it; an empty stored id now resolves to silence. A capture is
|
||||
loaded when the user picks one (or via S13 drop-to-load / S8 ingest). This is a deliberate
|
||||
reversal of the S4 convenience default, not a regression.
|
||||
|
||||
**Verify (in DAW):** a fresh instance plays **nothing** and shows the "pick a capture"
|
||||
empty state (no auto-play of sample #1); the capture browser draws **peak thumbnails**
|
||||
(the `Sample` peaks bank_model already carries — same data the dock panel thumbnails use),
|
||||
name, and a root/key badge where present, and is **filterable by bank** (bank_book named
|
||||
banks); picking a capture loads it, shows it (waveform/peaks + its root on the keyboard
|
||||
strip), and it plays repitched from its root; time-to-first-note is a pick-then-play, not a
|
||||
list-scroll; the keyboard strip shows the single capture's root and is draggable to set it;
|
||||
the pure geometry modules are CTest-green (browser card/grid layout + hit-test; strip
|
||||
edge-grab/body-move/key→note) with no host types at their boundary; the ±1 nudge-button row
|
||||
is gone.
|
||||
**Depends on:** S4 (the selection state + reload path this reverses the fallback on), S1
|
||||
(the LICE `IPlugView` drag/event routing — extends the click-only `wndProc` to
|
||||
`WM_MOUSEMOVE`/`WM_LBUTTONUP`), S5 (the `PerformanceMap`/zone model the opt-in Zones panel
|
||||
edits — but the default face does not require a keymap). **Adopts the Phase L kit when
|
||||
available — not gated on Phase L.** S10 builds its browser cards + keyboard strip with the
|
||||
current LICE drawing; when Phase L's L1 kit lands on `dev`, this surface adopts it (the one
|
||||
source of drawing). The drag machine's `WM_MOUSEMOVE` tracking also lights the kit's
|
||||
**hover** states at near-zero marginal cost once the kit is present.
|
||||
|
||||
- [ ] No-auto-select + empty state (the policy reversal): remove the `selectSample`
|
||||
first-sample fallback (`sample_map.cpp`) and the processor's Tier-0 fallback that
|
||||
consumed it — an empty stored selection resolves to **silence**, not sample #1. The
|
||||
editor draws a clear **empty state** ("pick a capture" affordance) when nothing is
|
||||
selected. Pure change is testable (empty id → `nullopt`); the empty-state draw is shell.
|
||||
- [ ] Capture browser (pure layout + shell draw): grow `SampleChoice` to carry the
|
||||
**peak thumbnail data** (from the `Sample` peaks bank_model already stores — the same
|
||||
peaks the dock panel draws), the **root/key badge** (S2 `rootNote` intrinsic / the
|
||||
optional musical key), and its bank. A new pure `capture_browser` module lays out
|
||||
scannable **cards/rows** (card rect grid, thumbnail rect, hit-test a point → card) — no
|
||||
host types at the boundary, unit-tested. The shell draws each card's peak thumbnail +
|
||||
name + badge in LICE (house palette) and routes a click to select.
|
||||
- [ ] Bank filter (pure + shell): a filter/tab strip over the browser that narrows the
|
||||
drawn cards to a chosen bank_book bank (or "all"). Filter-tab layout + hit-test pure
|
||||
(mirror of `mode_switch`); the active-filter state is transient UI state; the shell draws
|
||||
the tabs and applies the filter to the card list. (Type-to-filter search folds in from
|
||||
S12 — see S12's boundary note; a name-substring filter over the same card list.)
|
||||
- [ ] Guided single-capture setup (the fast path): once a capture is picked, a prominent,
|
||||
self-explanatory setup surface — **root note** (settable on the keyboard strip / typed),
|
||||
**play-mode basics**, **level** — sized for the single-capture case, not a zone table.
|
||||
Graphic and descriptive; the point is to get from pick → set → play with no hunting.
|
||||
- [ ] Pure `keyboard_strip` geometry module (serves the single-capture case first): map a
|
||||
MIDI key span across a strip width (128 keys → pixels, reusing the S6 `embed_strip`
|
||||
key-span idiom); a **root marker** for the loaded capture; `pixel→note` and a `keyAtPoint`
|
||||
for click-to-set-root; a drag-delta resolver `(grabbedField, startNote, dxPixels) →
|
||||
newNote`; **per-zone bar rect** + **edge-grab hit regions** (resize handles vs. body
|
||||
move-handle) for the opt-in Zones panel. No VST3/REAPER/LICE types at the boundary;
|
||||
unit-tested (root marker, edge grabs, body-move delta, key mapping, clamps low≤high,
|
||||
boundary rounding). Mirror of `mode_switch`/`editor_geometry`.
|
||||
- [ ] Editor shell drag-state machine: `WM_LBUTTONDOWN` grabs a card / a key / a zone
|
||||
edge-or-body, `WM_MOUSEMOVE` updates the in-flight edit against the pure resolver,
|
||||
`WM_LBUTTONUP` commits via the existing `commitMapAndReload` (off-thread reload; RT path
|
||||
untouched). Live visual feedback while dragging; a single undo-coherent edit on release.
|
||||
|
||||
### S10-Z — Zones panel (opt-in multi-zone keymap editing; demoted from the default face)
|
||||
The multi-zone keymap editor is now an **opt-in view/panel** ("Zones" toggle), not the
|
||||
default. It reuses the same `keyboard_strip` geometry and drag-state machine: each zone a
|
||||
bar over the keys it covers; **drag an edge** → low/high note; **drag the bar body** → move
|
||||
the zone (span preserved); **click a key** → set/relocate the zone's root. This is the
|
||||
capability RS5K structurally lacks (multi-zone in one instrument), kept as a *nice-to-have*
|
||||
per Daniel's hierarchy — "most of the time the zones won't be used." Add/select/delete a
|
||||
zone; overlapping zones render legibly and resolve first-match. The seven ±1 nudge/delete
|
||||
mini-buttons are retired everywhere; delete is one affordance (a small × on the bar or a
|
||||
keystroke). The `zoneHitTest`/±1 nudge path in `editor_geometry` is retired (a numeric
|
||||
fallback for accessibility is a build-time residual, not a fork).
|
||||
**Verify (in DAW):** the Zones panel is reachable via an explicit toggle (default view is
|
||||
the capture browser + single-capture setup, not this); a zone's range is set by **dragging
|
||||
edges** (not nudge clicks); body-drag moves the span; click-a-key sets the root (audible on
|
||||
the next held note); zone add/select/delete work; the ±1 nudge row is gone.
|
||||
|
||||
- [ ] "Zones" panel toggle (opt-in): the default editor face is the capture browser +
|
||||
single-capture setup; a toggle reveals the multi-zone keymap editor. Toggle state is
|
||||
transient UI state (or per-instance component state if it should persist — build-time
|
||||
residual).
|
||||
- [ ] Zone edit via the shared strip: draw the keyboard strip + zone bars in LICE, drive
|
||||
the shared drag-state machine (edge = resize, body = move, key = root), commit via
|
||||
`commitMapAndReload`. Zone add/select/delete as single affordances; ±1 nudge row gone.
|
||||
|
||||
## S11 — waveform view with draggable loop points (UX overhaul, part 2)
|
||||
**Goal:** Give each sample/zone a **waveform display** with **draggable start/end/loop
|
||||
@@ -464,6 +523,13 @@ CTest-green (px↔frame mapping, marker grab regions, clamp start≤end).
|
||||
(the zone the loop attaches to), S10 (shares the editor's drag-state machine + shell). The
|
||||
zero-crossing snap is a small pure helper over the decoded PCM.
|
||||
|
||||
> **Boundary note (S10 reframe, 2026-07-26):** the waveform view is now **central to the
|
||||
> single-capture fast path**, not just per-zone. Selecting a capture in S10's browser shows
|
||||
> its waveform (this is "see it" in pick → see it → play it); the loop-marker drag here
|
||||
> extends that same waveform surface. S11's waveform draw is the same one S10's picked-
|
||||
> capture view uses — build it once, S10 shows it read-only for the single capture, S11 adds
|
||||
> the draggable loop markers. No renumber; S11 stays the loop-editing point.
|
||||
|
||||
- [ ] Pure waveform/marker geometry: `frame↔pixel` mapping across the waveform rect, marker
|
||||
x-position from a frame index, marker grab regions (start/end/loop-start/loop-end),
|
||||
drag-delta `(grabbedMarker, dxPixels) → newFrame` with clamps (start≤end, in-bounds). A
|
||||
@@ -495,14 +561,26 @@ sample is reachable; typing filters the list to matching names; a zone's low/hig
|
||||
be **typed** (not only dragged) via a click-to-edit field; the amp envelope's ADSR is
|
||||
**adjustable** (four draggable controls) and the change is audible + persists across project
|
||||
save/reopen (component state); the scroll/search/slider geometry is CTest-green.
|
||||
**Depends on:** S10 (the editor shell + drag-state machine), S3 (the `AdsrParams` the ADSR
|
||||
sliders drive — already wired into the voice engine; today they are fixed defaults), S5
|
||||
(the map the numeric fields edit).
|
||||
**Depends on:** S10 (the editor shell + drag-state machine + the **capture browser** the
|
||||
scroll/search now apply to), S3 (the `AdsrParams` the ADSR sliders drive — already wired
|
||||
into the voice engine; today they are fixed defaults), S5 (the map the numeric fields edit).
|
||||
|
||||
- [ ] Scrollable, searchable sample list: a scroll offset (wheel + scrollbar drag) so long
|
||||
banks are fully reachable; a type-to-filter search that narrows the drawn rows to matching
|
||||
display names. Scroll/filter layout + hit-test is pure geometry (visible-row window,
|
||||
scrollbar thumb rect, search-box rect); filter/scroll state is transient UI state.
|
||||
> **Boundary note (S10 reframe, 2026-07-26):** the "sample list" S12 originally scrolled and
|
||||
> searched **is now S10's capture browser** (cards with peak thumbnails, bank filter). What
|
||||
> pulled INTO S10: the browser layout itself, the peak thumbnails, and the **bank filter**
|
||||
> (a bank_book tab, distinct from name search). What stays in S12 and applies **to S10's
|
||||
> browser**: (a) **scroll** for a bank longer than the panel, and (b) **type-to-filter
|
||||
> search** (a name-substring narrow over the same cards, composing with S10's bank filter —
|
||||
> bank filter picks the bank, search narrows within it). The scroll/search geometry is pure,
|
||||
> layered over the `capture_browser` module S10 builds. Net: S12 = scroll + search over the
|
||||
> S10 browser + numeric entry + ADSR; the browser *card* work is S10's.
|
||||
|
||||
- [ ] Scrollable, searchable capture browser: a scroll offset (wheel + scrollbar drag) so a
|
||||
bank longer than the panel is fully reachable; a **type-to-filter search** that narrows
|
||||
the drawn cards to matching display names, **composing with S10's bank filter** (bank
|
||||
filter selects the bank; search narrows within it). Scroll/search layout + hit-test is
|
||||
pure geometry (visible-card window, scrollbar thumb rect, search-box rect), layered over
|
||||
S10's `capture_browser` module; filter/scroll state is transient UI state.
|
||||
- [ ] Direct numeric entry for zone low/high/root: a click-to-edit field over the strip
|
||||
(LICE text-entry idiom or a SWELL edit control on the child HWND) so a precise note can be
|
||||
typed, not only dragged. Commits via `commitMapAndReload` like every other edit.
|
||||
@@ -564,13 +642,23 @@ is **ReaSampler 9000**. Framing + propagation surfaces:
|
||||
class **display name** string (in the factory registration), the `IPlugView` editor title
|
||||
band (currently "ReaSampler Instrument"), the S6 embed-strip label, and the Phase S docs.
|
||||
**Do NOT change the VST3 class UID** — instances in already-saved projects key off it; a
|
||||
UID change orphans every existing instance (fork below).
|
||||
- [ ] **Fork S-NAME-1 (Daniel's call): the binary filename.** Renaming the built VST3
|
||||
module (e.g. `reasampler_9000.vst3`) is a compat break — REAPER keys a saved project's
|
||||
plugin reference partly by filename, so a rename can require users to re-select the plugin
|
||||
on existing instances. Lean: **keep the current filename, change only display strings**
|
||||
(zero compat weight, the name shows everywhere the user reads), unless Daniel wants the
|
||||
on-disk name to match. Flag, do not decide.
|
||||
UID change orphans every existing instance.
|
||||
- [ ] **Rename the binary filename too (S-NAME-1 SETTLED, Daniel 2026-07-26):** rename the
|
||||
built VST3 module (CMake `OUTPUT_NAME` / target artifact — e.g. `reasampler_9000.vst3`)
|
||||
alongside the display strings, so the on-disk name matches the product name. Record the
|
||||
full rename surface: **CMake output name** (the second VST3 target's artifact name), the
|
||||
**factory vendor/name strings**, the **`IPlugView` editor title**, and the **S6 embed
|
||||
label**. Do NOT touch the **VST3 class UID** (unchanged — the compat anchor).
|
||||
- [ ] **Compat verification (must-DAW-verify before shipping the rename):** the working
|
||||
assumption is that REAPER **rebinds a saved instance by its VST3 class UID, not by the
|
||||
module filename**, so a filename rename with an unchanged UID keeps saved projects working
|
||||
(existing instances still resolve). **This is not yet confirmed from source** — a web
|
||||
check surfaced a JUCE/VST3-replace-VST2 case suggesting REAPER's binding is more nuanced
|
||||
than "UID only" (it can involve an FXID match), so treat UID-rebind as **to-verify, not
|
||||
asserted fact**. **DAW-verify:** save a project with a ReaSampler 9000 instance under the
|
||||
old filename, rename the module, reopen — confirm the instance rebinds and restores its
|
||||
state. If REAPER does key partly on filename, fall back to keeping the current filename
|
||||
(display-strings-only) and record that as the shipped choice.
|
||||
|
||||
## S17 — drop-and-load: drag a capture onto a track's FX button → instantiate ReaSampler 9000 with the capture loaded
|
||||
**Goal:** Turn a bank capture into a playable instrument in one gesture. Today a drag
|
||||
|
||||
Reference in New Issue
Block a user