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:
2026-07-26 19:49:03 -04:00
parent 5de2183ce3
commit 991c190bb8
3 changed files with 312 additions and 102 deletions
+96 -36
View File
@@ -1426,6 +1426,35 @@ Setting a zone from C1 to C4 by clicking "+" thirty-six times is the catastrophe
a fixed envelope) compound it. The overhaul is scoped as **S10S13**, sequenced so the a fixed envelope) compound it. The overhaul is scoped as **S10S13**, sequenced so the
friction Daniel feels every test pass is removed first. friction Daniel feels every test pass is removed first.
### Workflow hierarchy (REVISED 2026-07-26 — Daniel; supersedes the keymap-first S10)
The overhaul is reframed around the **actual workflow**, not a keymap. Daniel's directive,
distilled: *a giant list of "item" blocks is visually useless; optimize for working with
individual captures, not a huge list of everything.* The settled hierarchy:
1. **Primary flow = one capture, fast.** Most instances play a **single capture**. The
metric is **time-to-first-note**: open → pick a capture → see it (waveform/peaks) → play
it. The default editor face serves this, not a zone table.
2. **Fresh instance is SILENT — nothing auto-selected (policy reversal of S4).** On open
with no stored selection, the instrument plays **nothing** and shows a clear **empty
state** ("pick a capture") — it does **not** auto-play sample #1. This deliberately
reverses the S4 "first sample plays" convenience: the `selectSample` first-sample
fallback and the processor's Tier-0 fallback that resolved it are removed; an empty
stored id resolves to silence. (Recorded as a reversal, not a regression.)
3. **Capture browser, not an item list.** Scannable **cards/rows** with **peak thumbnails**
(the `Sample` peaks bank_model already carries — the same data the dock panel thumbnails
draw), name, and a **root/key badge** where present; **filterable by bank** (bank_book
named banks). A "giant list of item blocks" is the anti-pattern — the browser is designed
for scanning by eye.
4. **Graphic, descriptive controls with a guided fast path.** Once a capture is picked, a
prominent, self-explanatory single-capture setup surface (root note, play-mode basics,
level). The keyboard strip serves the **single-capture** case first (shows where the
capture sits / its root); drag matters most when zoning.
5. **Zones demoted to secondary (nice-to-have).** Multi-zone keymap editing becomes an
**opt-in "Zones" panel** (S10-Z), not the default face — "most of the time the zones
won't be used." The keyboard-strip drag machinery is still built, but in service of the
capture-first layout.
**What "better than RS5K" means, specifically (not vibes).** RS5K's genuine strengths — **What "better than RS5K" means, specifically (not vibes).** RS5K's genuine strengths —
match or beat each: (1) **drag a file straight onto it** loads the sample (our S13 relay); match or beat each: (1) **drag a file straight onto it** loads the sample (our S13 relay);
(2) **note-start / note-end** range with a visual sense of the keyboard (our S10 keyboard (2) **note-start / note-end** range with a visual sense of the keyboard (our S10 keyboard
@@ -1434,10 +1463,12 @@ strip — RS5K's own range UI is two number fields, so a *draggable* strip beats
S12); (5) velocity layers / round-robin (Tier 2 — held, not in this overhaul). RS5K's real S12); (5) velocity layers / round-robin (Tier 2 — held, not in this overhaul). RS5K's real
**weaknesses are our opening:** its **one-sample-per-instance** model forces track sprawl **weaknesses are our opening:** its **one-sample-per-instance** model forces track sprawl
(one RS5K per drum) and it has **no multi-zone view in a single instance** — ReaSampler (one RS5K per drum) and it has **no multi-zone view in a single instance** — ReaSampler
9000 is multi-zone in one instrument by design (S5), so the keyboard-strip editor showing 9000 is multi-zone in one instrument by design (S5), so the **opt-in Zones panel** showing
*all* zones at once is a capability RS5K structurally lacks. "Better than RS5K" = *all* zones at once is a capability RS5K structurally lacks. But per the reframe, the *default*
direct-manipulation where RS5K uses number fields, multi-zone where RS5K is one-shot, and face is the single-capture fast path (browser + setup), and multi-zone is the demoted
bank-integrated ingest where RS5K is file-at-a-time. nice-to-have. "Better than RS5K" = a fast single-capture browser where RS5K makes you drag a
file blind, direct-manipulation where RS5K uses number fields, multi-zone-when-you-want-it
where RS5K is one-shot, and bank-integrated ingest where RS5K is file-at-a-time.
**Constraints (unchanged — settled, do not re-open):** LICE/SWELL drawing only (no toolkit **Constraints (unchanged — settled, do not re-open):** LICE/SWELL drawing only (no toolkit
change — D-A settled); **all layout/hit-test math in pure geometry modules** (mirror of change — D-A settled); **all layout/hit-test math in pure geometry modules** (mirror of
@@ -1448,17 +1479,28 @@ stays a **read-only bank consumer** (loop/root/ADSR edits are the instrument's *
map*, D-B — never written back to the bank); component-state persistence and map*, D-B — never written back to the bank); component-state persistence and
read-only-over-bank stay settled. read-only-over-bank stay settled.
- **S10 — direct-manipulation keymap editor.** A **piano-keyboard strip** along the top; - **S10 — capture-first editor: browser + guided single-capture setup (REVISED 2026-07-26).**
each zone a bar over the keys it covers. **Drag an edge** → change low/high note; **drag The default face is the **capture browser** (scannable cards with **peak thumbnails** from
the bar body** → move the zone (span preserved); **click a key** → set/relocate the the `Sample` peaks bank_model carries, name, root/key badge; **bank filter** over bank_book
zone's root. Retires the seven ±1 nudge buttons per row. New pure `keyboard_strip` banks) feeding a **guided single-capture setup** (root note, play-mode basics, level).
geometry module (key-span↔pixel reusing the `embed_strip` idiom; edge-grab vs. body-move Fresh instance is **silent, nothing auto-selected** — the S4 first-sample fallback is
hit regions; `pixel→note`; drag-delta resolver). Shell extends the click-only `wndProc` **removed** (empty stored id → silence + a "pick a capture" empty state). New pure modules:
to a `WM_MOUSEMOVE`/`WM_LBUTTONUP` drag-state machine with live feedback, one coherent `capture_browser` (card/grid layout + hit-test) and `keyboard_strip` (key-span↔pixel via
edit on release. **Built with the current LICE drawing; adopts the Phase L kit (L1) when the `embed_strip` idiom; a **root marker** for the single loaded capture; `pixel→note`;
it lands** (drawn through the shared component kit rather than flat `LICE_FillRect`/GDI once drag-delta resolver; per-zone bar rect + edge-grab hit regions for the opt-in Zones panel).
available) — **not gated on Phase L**; the drag machine's `WM_MOUSEMOVE` tracking also lights Shell extends the click-only `wndProc` to a `WM_MOUSEMOVE`/`WM_LBUTTONUP` drag-state
the kit's hover states at near-zero marginal cost once the kit is present. machine with live feedback, one coherent edit on release. **Multi-zone keymap editing is an
opt-in "Zones" panel (S10-Z), not the default** — the demoted nice-to-have; it reuses the
same strip geometry + drag machine (edge = resize, body = move, key = root) and retires the
seven ±1 nudge buttons per row. **Built with the current LICE drawing; adopts the Phase L
kit (L1) when it lands** (drawn through the shared component kit rather than flat
`LICE_FillRect`/GDI once available) — **not gated on Phase L**; the drag machine's
`WM_MOUSEMOVE` tracking also lights the kit's hover states at near-zero marginal cost once
the kit is present. **Boundary shifts (from the reframe):** the "sample list" S12 was to
scroll/search **is now this browser** — the card layout, peak thumbnails, and bank filter
are S10's; S12 keeps **scroll** + **type-to-filter search** *layered over* S10's browser
(bank filter picks the bank, search narrows within it). The waveform S11 makes loop-editable
is the same waveform S10 shows read-only for the picked single capture ("see it").
- **S11 — waveform view + draggable loop points.** Selecting a zone shows its sample's - **S11 — waveform view + draggable loop points.** Selecting a zone shows its sample's
**waveform** (peaks via the existing `peaks` module over the shell's already-decoded PCM **waveform** (peaks via the existing `peaks` module over the shell's already-decoded PCM
— no new decode/WAV path) with draggable **start/end/loop-start/loop-end** markers that — no new decode/WAV path) with draggable **start/end/loop-start/loop-end** markers that
@@ -1466,13 +1508,17 @@ read-only-over-bank stay settled.
**per-zone loop override** (additive on `PerformanceZone`, same shape as `rootOverride`; **per-zone loop override** (additive on `PerformanceZone`, same shape as `rootOverride`;
seeded from the S2 bank intrinsic, never written back). Marker/waveform geometry pure seeded from the S2 bank intrinsic, never written back). Marker/waveform geometry pure
(`frame↔pixel`, marker grab regions, clamp start≤end, zero-crossing snap helper). (`frame↔pixel`, marker grab regions, clamp start≤end, zero-crossing snap helper).
- **S12 — scale + ergonomics.** The sample list **scrolls** (wheel + scrollbar) so a long - **S12 — scale + ergonomics.** **Scroll** (wheel + scrollbar) over **S10's capture
bank is fully reachable, and has a **type-to-filter search**. **Direct numeric entry** browser** so a bank longer than the panel is fully reachable, and a **type-to-filter
for zone low/high/root (a click-to-type field over the strip, for precision the drag search** that narrows the cards by name, **composing with S10's bank filter** (bank filter
can't hit). An **ADSR editor** — four draggable controls over the S3 `AdsrParams` (the selects the bank; search narrows within it). *(Boundary shift from the 2026-07-26 reframe:
math already exists and is wired into the voice engine; today the envelope is a fixed the browser card layout, peak thumbnails, and bank filter are now **S10's**; S12 = scroll
default). Scroll/search/slider/entry layout pure; ADSR + (implicitly) any exposed + search layered over that browser.)* **Direct numeric entry** for zone low/high/root (a
parameters become per-instance component state (additive, version-bumped, back-compat). click-to-type field over the strip, for precision the drag can't hit — Zones-panel-scoped).
An **ADSR editor** — four draggable controls over the S3 `AdsrParams` (the math already
exists and is wired into the voice engine; today the envelope is a fixed default).
Scroll/search/slider/entry layout pure; ADSR + (implicitly) any exposed parameters become
per-instance component state (additive, version-bumped, back-compat).
- **S13 — drop-to-load (the S8 relay, in the editor).** Dropping an OS file / media item - **S13 — drop-to-load (the S8 relay, in the editor).** Dropping an OS file / media item
**onto the editor window** ingests into the bank + assigns to this instance — the RS5K **onto the editor window** ingests into the bank + assigns to this instance — the RS5K
"drop a file straight on it" affordance. **The instrument does not ingest:** the editor's "drop a file straight on it" affordance. **The instrument does not ingest:** the editor's
@@ -1482,12 +1528,15 @@ read-only-over-bank stay settled.
degrade to the docked-`bank_panel` drop path with a clear affordance. Never inserts a degrade to the docked-`bank_panel` drop path with a clear affordance. Never inserts a
timeline item (capture/placement separation intact). timeline item (capture/placement separation intact).
**Sequencing (recommendation, argued below in this section's tail).** S10 first — the **Sequencing (recommendation, argued below in this section's tail).** S10 first — under the
nudge-button zone editor is the friction Daniel hits on *every* test pass, so removing it reframe it now carries the **whole felt win**: the empty-state / no-auto-select fix, the
buys the most felt improvement per unit work and de-risks the drag-state machine S11/S12 capture browser (peak thumbnails, bank filter) that replaces the useless item list, and the
reuse. S11 and S12 follow (both lean on S10's drag machine). S13 depends on S8's ingest guided single-capture setup that retires the nudge buttons. This is the entire "the UX is
seam, so it sequences after S8. Against the queued engine work: **S10 should land before or awful" wound, and time-to-first-note is the metric it moves. S11 (waveform + loop) and S12
interleaved with S7 (stereo).** S7 is a real engine capability (stereo capture in true (scroll/search over the browser, numeric entry, ADSR) follow — both lean on S10's browser +
drag machine, and S11's waveform is the same surface S10 shows for the picked capture. S13
depends on S8's ingest seam, so it sequences after S8. Against the queued engine work: **S10
should land before or interleaved with S7 (stereo).** S7 is a real engine capability (stereo capture in true
stereo) and touches the DSP Daniel smoke-tests — but the *reason* he'll keep smoke-testing stereo) and touches the DSP Daniel smoke-tests — but the *reason* he'll keep smoke-testing
is the editor, and today every test pass is taxed by the nudge-button UX. Fixing what he is the editor, and today every test pass is taxed by the nudge-button UX. Fixing what he
feels first (S10) makes every subsequent S7 test less painful; there is no hard dependency feels first (S10) makes every subsequent S7 test less painful; there is no hard dependency
@@ -1502,17 +1551,28 @@ The MIDI-playback instrument's product name is **ReaSampler 9000**. The extensio
Set by Daniel on DAW-testing the S1S6 instrument, alongside the UX-overhaul directive. Set by Daniel on DAW-testing the S1S6 instrument, alongside the UX-overhaul directive.
- **Propagate the display name** across user-visible surfaces: the VST3 class **display - **Propagate the display name** across user-visible surfaces: the VST3 class **display
name** string in the factory registration, the `IPlugView` editor **title band** name** string in the factory registration, the **factory vendor/name strings**, the
(currently "ReaSampler Instrument"), the **S6 embed-strip label**, and the Phase S docs. `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 the - **Do NOT change the VST3 class UID.** Instances in already-saved projects key off the
class UID; changing it orphans every existing instance in every saved project. The UID is class UID; changing it orphans every existing instance in every saved project. The UID is
a forever-stable contract (mirror of the command-id / ext-state-namespace forever-stable a forever-stable contract (mirror of the command-id / ext-state-namespace forever-stable
strings). The name change is **display-string-only** on the code side. strings).
- **Fork S-NAME-1 (Daniel's call): binary filename.** Renaming the built module (e.g. - **S-NAME-1 SETTLED (Daniel, 2026-07-26): rename the binary filename too.** The on-disk
`reasampler_9000.vst3`) carries compat weight — REAPER partly keys a saved project's module name is renamed to match the product (e.g. `reasampler_9000.vst3`), not just the
plugin reference by filename, so a rename can force users to re-select the plugin on display strings. Full rename surface: **CMake `OUTPUT_NAME`** on the second VST3 target,
existing instances. **Lean: keep the filename, change only display strings** (zero compat the **factory vendor/name strings**, the **editor title**, and the **embed label**. The
weight; the name shows everywhere the user reads it). Flagged for Daniel, not decided. **class UID stays locked** as 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.
**This is a to-verify assumption, not a confirmed fact:** a web check surfaced a
JUCE/VST3-replace-VST2 case suggesting REAPER's binding can be more nuanced than "UID only"
(an FXID match is involved), so it is not safe to assert UID-only rebinding from source.
**DAW-verify:** save a project with an instance under the old filename, rename the module,
reopen, and confirm the instance rebinds and restores its state. If REAPER keys partly on
filename, fall back to keeping the current filename (display-strings-only) and record that
as the shipped choice.
## REAPER / Steinberg API surface (verify all signatures) ## REAPER / Steinberg API surface (verify all signatures)
+143 -55
View File
@@ -399,48 +399,107 @@ alongside S8.
> `docs/product/visual-design-language.md` (all on `dev`). S10S13 below build with the > `docs/product/visual-design-language.md` (all on `dev`). S10S13 below build with the
> current drawing and **adopt the L1 kit when it lands — not gated on Phase L.** > 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) ## S10 — capture-first editor: browser + guided single-capture setup ("ReaSampler 9000" UX overhaul, part 1)
**Goal:** Replace the ±1 nudge-button zone editor with a **piano-keyboard-strip zone **Goal (REVISED 2026-07-26 — workflow-first reframe, Daniel):** Rebuild the editor's
editor** where the keymap is edited by direct manipulation: a horizontal keyboard span default face around the **primary flow = one capture, fast**, not a keymap. A giant list of
along the top, each zone drawn as a bar over the keys it covers; **drag a zone's edge to "item" blocks is visually useless; most instances play a *single capture*, and zones are a
change its low/high note, drag the bar's body to move the whole zone, click a key (or the nice-to-have. So the default view is a **capture browser** (scannable cards with peak
zone's root marker) to set the root note.** No number is ever set by clicking a button 40 thumbnails, name, root/key badge; **bank filter**) feeding a **guided single-capture setup**
times. This is the friction Daniel feels every test pass — the current row of seven (root note, play-mode basics, level) — and the keyboard strip serves the *single-capture*
mini-buttons per zone is the catastrophe. All layout/hit-test math is a **new pure case first (shows where the capture sits / its root). **Time-to-first-note is the metric.**
geometry module** (`keyboard_strip` — mirror of `mode_switch`/`editor_geometry`); the LICE Multi-zone keymap editing is **demoted to an opt-in "Zones" panel** (S10-Z below), not the
draw + drag-state machine is the editor shell. RT discipline untouched (edits still commit default. The keyboard-strip drag machinery is still built here, but in service of the
off-thread via `commitMapAndReload`). CONTEXT.md §Phase S (ReaSampler 9000 UX — keymap capture-first layout. All layout/hit-test math is **pure geometry** (new `keyboard_strip` +
editor). a `capture_browser` layout module — mirrors of `mode_switch`/`editor_geometry`); the LICE
**Verify (in DAW):** a zone's range is set by **dragging its edges on the keyboard strip** draw + drag-state machine is the editor shell. RT discipline untouched (edits commit
(not by nudge clicks); dragging a zone's body moves the whole range preserving its span; off-thread via `commitMapAndReload`); the instrument stays a **read-only bank consumer**.
clicking a key sets/relocates the zone's root (with the repitched result audible on the CONTEXT.md §Phase S (ReaSampler 9000 UX — capture-first editor).
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.
- [ ] Pure `keyboard_strip` geometry module: map a MIDI key span across a strip width **Policy reversal — fresh instance is SILENT, nothing auto-selected (was S4).** The S4
(128 keys → pixels, reusing the S6 `embed_strip` key-span idiom); per-zone bar rect from "first sample plays" fallback is **removed**: on open with no stored selection, the
`[lowNote,highNote]`; **edge-grab hit regions** (a few px inside each end = resize handles, instrument plays **nothing** and the editor shows a clear **empty state** ("pick a capture")
the middle = move handle); `pixel→note` and a `keyAtPoint` for click-to-set-root; a — it does not auto-play sample #1. Retires the `selectSample` first-sample fallback
drag-delta resolver `(grabbedField, startNote, dxPixels) → newNote`. No VST3/REAPER/LICE (`sample_map.cpp` "No stored id → fall back to the FIRST sample") and the processor's
types at the boundary; unit-tested outside the DAW (edge grabs, body-move delta, key Tier-0 fallback that resolved it; an empty stored id now resolves to silence. A capture is
mapping, clamps low≤high, boundary rounding). Mirror of `mode_switch`/`editor_geometry`. loaded when the user picks one (or via S13 drop-to-load / S8 ingest). This is a deliberate
- [ ] Editor shell: draw the keyboard strip + zone bars in LICE (house palette), and drive reversal of the S4 convenience default, not a regression.
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 **Verify (in DAW):** a fresh instance plays **nothing** and shows the "pick a capture"
existing `commitMapAndReload` (off-thread reload; RT path untouched). Live visual feedback empty state (no auto-play of sample #1); the capture browser draws **peak thumbnails**
while dragging; a single undo-coherent edit on release. (the `Sample` peaks bank_model already carries — same data the dock panel thumbnails use),
- [ ] Click-to-set-root + zone move/resize replace the seven per-row nudge/delete name, and a root/key badge where present, and is **filterable by bank** (bank_book named
mini-buttons; delete stays as one affordance (a small × on the zone bar or a keystroke). banks); picking a capture loads it, shows it (waveform/peaks + its root on the keyboard
The `zoneHitTest`/±1 nudge path in `editor_geometry` is retired for the new strip (kept strip), and it plays repitched from its root; time-to-first-note is a pick-then-play, not a
only if a numeric fallback is wanted for accessibility — build-time residual, not a fork). 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) ## S11 — waveform view with draggable loop points (UX overhaul, part 2)
**Goal:** Give each sample/zone a **waveform display** with **draggable start/end/loop **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 (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. 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 - [ ] 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), 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 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 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 **adjustable** (four draggable controls) and the change is audible + persists across project
save/reopen (component state); the scroll/search/slider geometry is CTest-green. 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 **Depends on:** S10 (the editor shell + drag-state machine + the **capture browser** the
sliders drive — already wired into the voice engine; today they are fixed defaults), S5 scroll/search now apply to), S3 (the `AdsrParams` the ADSR sliders drive — already wired
(the map the numeric fields edit). 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 > **Boundary note (S10 reframe, 2026-07-26):** the "sample list" S12 originally scrolled and
banks are fully reachable; a type-to-filter search that narrows the drawn rows to matching > searched **is now S10's capture browser** (cards with peak thumbnails, bank filter). What
display names. Scroll/filter layout + hit-test is pure geometry (visible-row window, > pulled INTO S10: the browser layout itself, the peak thumbnails, and the **bank filter**
scrollbar thumb rect, search-box rect); filter/scroll state is transient UI state. > (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 - [ ] 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 (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. 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 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. 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 **Do NOT change the VST3 class UID** — instances in already-saved projects key off it; a
UID change orphans every existing instance (fork below). UID change orphans every existing instance.
- [ ] **Fork S-NAME-1 (Daniel's call): the binary filename.** Renaming the built VST3 - [ ] **Rename the binary filename too (S-NAME-1 SETTLED, Daniel 2026-07-26):** rename the
module (e.g. `reasampler_9000.vst3`) is a compat break — REAPER keys a saved project's built VST3 module (CMake `OUTPUT_NAME` / target artifact — e.g. `reasampler_9000.vst3`)
plugin reference partly by filename, so a rename can require users to re-select the plugin alongside the display strings, so the on-disk name matches the product name. Record the
on existing instances. Lean: **keep the current filename, change only display strings** full rename surface: **CMake output name** (the second VST3 target's artifact name), the
(zero compat weight, the name shows everywhere the user reads), unless Daniel wants the **factory vendor/name strings**, the **`IPlugView` editor title**, and the **S6 embed
on-disk name to match. Flag, do not decide. 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 ## 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 **Goal:** Turn a bank capture into a playable instrument in one gesture. Today a drag
+73 -11
View File
@@ -7,10 +7,15 @@ the **product framing behind a scoped phase**. Its build roadmap lives in **PLAN
*why* (the plugin-format reasoning, the bare-VST3-vs-JUCE assessment, the settled *why* (the plugin-format reasoning, the bare-VST3-vs-JUCE assessment, the settled
decision record). decision record).
Status: framed by product-designer (2026-07-26), **revised 2026-07-26 (r5)**. r5 records Status: framed by product-designer (2026-07-26), **revised 2026-07-26 (r6)**. r6 records the
the post-DAW-test directives on the S1S6 instrument: the product name **ReaSampler 9000** **workflow-first reframe of S10** (Daniel, 2026-07-26): the editor's default face becomes a
and the **"better than RS5K" UX overhaul** (Phase S points S10S13) — see the Addendum in **capture browser + guided single-capture setup**, a fresh instance is **silent with a "pick
§4. r4 (below) settled the four residual forks D-A..D-D. The a capture" empty state** (reversing the S4 first-sample auto-play), and multi-zone editing is
demoted to an opt-in Zones panel — see the r6 Addendum in §4. r6 also settles **S-NAME-1**
(rename the binary filename too, UID locked). r5 records the post-DAW-test directives on the
S1S6 instrument: the product name **ReaSampler 9000** and the **"better than RS5K" UX
overhaul** (Phase S points S10S13) — see the r5 Addendum in §4. r4 (below) settled the four
residual forks D-A..D-D. The
"no PLAN.md footprint" era is **over** — with D-A through D-D settled (below), the "no PLAN.md footprint" era is **over** — with D-A through D-D settled (below), the
instrument was scoped into **Phase S** (codename Daniel's: "S" for Sampler, because "D" instrument was scoped into **Phase S** (codename Daniel's: "S" for Sampler, because "D"
collides with the existing Design View phase). **PLAN.md §Phase S is now the collides with the existing Design View phase). **PLAN.md §Phase S is now the
@@ -676,8 +681,9 @@ Instrument"), the S6 embed-strip label, and the docs. **Compat guard (load-beari
orphans every existing instance. The name change is **display-string-only** on the code orphans every existing instance. The name change is **display-string-only** on the code
side. *Fork S-NAME-1 (Daniel's call):* the on-disk **binary filename** — renaming it side. *Fork S-NAME-1 (Daniel's call):* the on-disk **binary filename** — renaming it
(`reasampler_9000.vst3`) carries compat weight (REAPER keys a saved project's plugin (`reasampler_9000.vst3`) carries compat weight (REAPER keys a saved project's plugin
reference partly by filename), so the lean is **keep the filename, change only display reference partly by filename), so the r5 lean was **keep the filename, change only display
strings**; flagged, not decided. strings**; flagged, not decided. *(Now SETTLED in r6, below: rename the filename too, UID
locked, compat is a DAW-verify — the r5 lean is superseded.)*
**The UX bar is "better than ReaSamplOMatic5000."** Verdict verbatim: "okay it works, but **The UX bar is "better than ReaSamplOMatic5000."** Verdict verbatim: "okay it works, but
the UX is awful." The S1S6 editor was spike-grade — a clickable list, zone rows with the UX is awful." The S1S6 editor was spike-grade — a clickable list, zone rows with
@@ -711,6 +717,59 @@ is no hard ordering, but the live wound is the editor. Honest counter: if the st
*sound* is what blocks real use, S7-first is defensible — but "it works, the UX is awful" *sound* is what blocks real use, S7-first is defensible — but "it works, the UX is awful"
names the editor as the wound. names the editor as the wound.
### Addendum — S10 workflow-first reframe + S-NAME-1 settled (Daniel, 2026-07-26, r6)
After the r5 UX-overhaul directive was specced (keymap-first S10), Daniel reframed the
workflow before S10 was implemented. This **revises S10** and settles S-NAME-1. Settled
directions, not open forks — recorded here per the doc's settled-decisions convention; PLAN.md
§S10 and CONTEXT.md §Phase S (workflow hierarchy) carry the spec.
**The reframe, verbatim (Daniel, 2026-07-26):** *"We need to think hard about the workflow
with this plugin. Have a giant list of 'item' blocks is visually useless. When the plugin is
loaded, we should not have any samples selected. We also need to show the peaks for each
capture. Filters for a specific bank would be useful. We need to be graphic and descriptive
with the controls, and guide the user QUICKLY towards setting up a sampler. Most of the time
the zones won't be used, but it's a nice-to-have. So we should optimize the UX for working
with individual captures, not a huge list of everything."*
**What changed in S10 (the hierarchy is Daniel's; details are product judgment):**
1. **Primary flow = one capture, fast.** The metric is **time-to-first-note**: open → pick a
capture → see it → play it. The default face serves the single capture, not a keymap.
2. **Fresh instance is SILENT — nothing auto-selected (policy reversal of S4).** The S4
"first sample plays" convenience is **removed**: open with no stored selection → the
instrument plays nothing and shows a **"pick a capture" empty state**, not auto-play of
sample #1. Concretely retires the `selectSample` first-sample fallback (`sample_map.cpp`)
and the processor's Tier-0 fallback that resolved it (an empty stored id → silence). This
is a deliberate reversal of the S4 default, recorded as such — not a regression.
3. **Capture browser, not an item list.** Scannable cards with **peak thumbnails** (the
`Sample` peaks bank_model already carries — the same data the dock panel thumbnails draw),
name, root/key badge, and a **bank filter** (bank_book named banks). "A giant list of item
blocks" is the named anti-pattern; the browser is designed for scanning by eye. `SampleChoice`
grows to carry the peaks + badge + bank (today it is only `{id, displayName}`).
4. **Graphic, descriptive controls with a guided fast path.** Once a capture is picked, a
prominent single-capture setup surface (root note, play-mode basics, level); the keyboard
strip serves the single-capture case first (shows the capture's root).
5. **Zones demoted to an opt-in "Zones" panel (S10-Z), not the default face.** "Most of the
time the zones won't be used." The keyboard-strip drag machinery is still built (it serves
both the single-capture root-set and the opt-in zoning), but multi-zone editing is behind a
toggle. Some S12 list ergonomics **pulled into S10**: the browser card layout, peak
thumbnails, and bank filter are S10's; S12 keeps **scroll** + **type-to-filter search**
layered over the S10 browser. S11's waveform is the same surface S10 shows for the picked
capture ("see it"). No renumber — S11/S12/S13 keep their numbers and their boundaries were
annotated, not moved wholesale.
**S-NAME-1 → SETTLED: rename the binary filename too.** The r5 lean (keep the filename,
display-strings-only) is superseded. The on-disk module is renamed to match the product (e.g.
`reasampler_9000.vst3`) — full surface: **CMake `OUTPUT_NAME`**, factory vendor/name strings,
editor title, embed label. The **VST3 class UID stays locked** as the compat anchor.
**Compat is a DAW-verify, not an asserted fact:** the working assumption is REAPER rebinds a
saved instance by class UID (not filename), so a rename with an unchanged UID keeps saved
projects working — but a web check surfaced a JUCE/VST3-replace-VST2 case suggesting REAPER's
binding can be more nuanced than "UID only" (an FXID match is involved), so UID-only rebinding
is **not** safe to assert from source. Verify by save-rename-reopen in the DAW; if REAPER keys
partly on filename, fall back to keeping the filename and record that as shipped.
--- ---
## Where this landed ## Where this landed
@@ -738,11 +797,14 @@ Two further directions set post-scoping (2026-07-26; see the Addendum in §4):
Post-DAW-test directives (2026-07-26; see the "product name + UX overhaul" Addendum in §4): Post-DAW-test directives (2026-07-26; see the "product name + UX overhaul" Addendum in §4):
7. **Product name → `ReaSampler 9000`** (display-string-only; VST3 class UID unchanged; 7. **Product name → `ReaSampler 9000`** (VST3 class UID unchanged; **binary filename renamed
binary filename = fork S-NAME-1, Daniel's call). too — S-NAME-1 SETTLED r6**, compat is a DAW-verify).
8. **UX overhaul → "better than RS5K"** (**S10S13**): direct-manipulation keyboard-strip 8. **UX overhaul → workflow-first, "better than RS5K"** (**S10S13**; S10 **reframed r6**):
keymap editor (S10), waveform + draggable loop points (S11), scale/ergonomics — scroll, S10 = **capture browser (peak thumbnails + bank filter) + guided single-capture setup**,
search, direct numeric entry, ADSR (S12), and drop-to-load folding in the S8 relay (S13). **silent-on-open / no auto-select** (reverses S4), multi-zone editing demoted to an opt-in
Zones panel (S10-Z); S11 waveform + draggable loop points; S12 scroll/search over the S10
browser + numeric entry + ADSR; S13 drop-to-load folding in the S8 relay. Metric:
time-to-first-note.
9. **Visual design language → modern/sleek, system-wide — moved to its own Phase L 9. **Visual design language → modern/sleek, system-wide — moved to its own Phase L
(2026-07-26).** The look-and-feel work (a shared LICE drawing kit + the surfaces that (2026-07-26).** The look-and-feel work (a shared LICE drawing kit + the surfaces that
adopt it) was originally drafted here as Phase S points S0-DS + S14; it has been **lifted adopt it) was originally drafted here as Phase S points S0-DS + S14; it has been **lifted