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:
+96
-36
@@ -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 **S10–S13**, sequenced so the
|
||||
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 —
|
||||
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
|
||||
@@ -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
|
||||
**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
|
||||
9000 is multi-zone in one instrument by design (S5), so the keyboard-strip editor showing
|
||||
*all* zones at once is a capability RS5K structurally lacks. "Better than RS5K" =
|
||||
direct-manipulation where RS5K uses number fields, multi-zone where RS5K is one-shot, and
|
||||
bank-integrated ingest where RS5K is file-at-a-time.
|
||||
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. But per the reframe, the *default*
|
||||
face is the single-capture fast path (browser + setup), and multi-zone is the demoted
|
||||
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
|
||||
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
|
||||
read-only-over-bank stay settled.
|
||||
|
||||
- **S10 — direct-manipulation keymap editor.** A **piano-keyboard strip** along the top;
|
||||
each zone a bar over the keys it covers. **Drag an edge** → change low/high note; **drag
|
||||
the bar body** → move the zone (span preserved); **click a key** → set/relocate the
|
||||
zone's root. Retires the seven ±1 nudge buttons per row. New pure `keyboard_strip`
|
||||
geometry module (key-span↔pixel reusing the `embed_strip` idiom; edge-grab vs. body-move
|
||||
hit regions; `pixel→note`; drag-delta resolver). Shell extends the click-only `wndProc`
|
||||
to a `WM_MOUSEMOVE`/`WM_LBUTTONUP` drag-state machine with live feedback, one coherent
|
||||
edit on release. **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.
|
||||
- **S10 — capture-first editor: browser + guided single-capture setup (REVISED 2026-07-26).**
|
||||
The default face is the **capture browser** (scannable cards with **peak thumbnails** from
|
||||
the `Sample` peaks bank_model carries, name, root/key badge; **bank filter** over bank_book
|
||||
banks) feeding a **guided single-capture setup** (root note, play-mode basics, level).
|
||||
Fresh instance is **silent, nothing auto-selected** — the S4 first-sample fallback is
|
||||
**removed** (empty stored id → silence + a "pick a capture" empty state). New pure modules:
|
||||
`capture_browser` (card/grid layout + hit-test) and `keyboard_strip` (key-span↔pixel via
|
||||
the `embed_strip` idiom; a **root marker** for the single loaded capture; `pixel→note`;
|
||||
drag-delta resolver; per-zone bar rect + edge-grab hit regions for the opt-in Zones panel).
|
||||
Shell extends the click-only `wndProc` to a `WM_MOUSEMOVE`/`WM_LBUTTONUP` drag-state
|
||||
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
|
||||
**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
|
||||
@@ -1466,13 +1508,17 @@ read-only-over-bank stay settled.
|
||||
**per-zone loop override** (additive on `PerformanceZone`, same shape as `rootOverride`;
|
||||
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).
|
||||
- **S12 — scale + ergonomics.** The sample list **scrolls** (wheel + scrollbar) so a long
|
||||
bank is fully reachable, and has a **type-to-filter search**. **Direct numeric entry**
|
||||
for zone low/high/root (a click-to-type field over the strip, for precision the drag
|
||||
can't hit). 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).
|
||||
- **S12 — scale + ergonomics.** **Scroll** (wheel + scrollbar) over **S10's capture
|
||||
browser** so a bank longer than the panel is fully reachable, and a **type-to-filter
|
||||
search** that narrows the cards by name, **composing with S10's bank filter** (bank filter
|
||||
selects the bank; search narrows within it). *(Boundary shift from the 2026-07-26 reframe:
|
||||
the browser card layout, peak thumbnails, and bank filter are now **S10's**; S12 = scroll
|
||||
+ search layered over that browser.)* **Direct numeric entry** for zone low/high/root (a
|
||||
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
|
||||
**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
|
||||
@@ -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
|
||||
timeline item (capture/placement separation intact).
|
||||
|
||||
**Sequencing (recommendation, argued below in this section's tail).** S10 first — the
|
||||
nudge-button zone editor is the friction Daniel hits on *every* test pass, so removing it
|
||||
buys the most felt improvement per unit work and de-risks the drag-state machine S11/S12
|
||||
reuse. S11 and S12 follow (both lean on S10's drag machine). 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
|
||||
**Sequencing (recommendation, argued below in this section's tail).** S10 first — under the
|
||||
reframe it now carries the **whole felt win**: the empty-state / no-auto-select fix, the
|
||||
capture browser (peak thumbnails, bank filter) that replaces the useless item list, and the
|
||||
guided single-capture setup that retires the nudge buttons. This is the entire "the UX is
|
||||
awful" wound, and time-to-first-note is the metric it moves. S11 (waveform + loop) and S12
|
||||
(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
|
||||
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
|
||||
@@ -1502,17 +1551,28 @@ The MIDI-playback instrument's product name is **ReaSampler 9000**. The extensio
|
||||
Set by Daniel on DAW-testing the S1–S6 instrument, alongside the UX-overhaul directive.
|
||||
|
||||
- **Propagate the display name** across user-visible surfaces: the VST3 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.
|
||||
name** string in the factory registration, the **factory vendor/name strings**, 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 the
|
||||
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
|
||||
strings). The name change is **display-string-only** on the code side.
|
||||
- **Fork S-NAME-1 (Daniel's call): binary filename.** Renaming the built module (e.g.
|
||||
`reasampler_9000.vst3`) carries compat weight — REAPER partly keys a saved project's
|
||||
plugin reference by filename, so a rename can force users to re-select the plugin on
|
||||
existing instances. **Lean: keep the filename, change only display strings** (zero compat
|
||||
weight; the name shows everywhere the user reads it). Flagged for Daniel, not decided.
|
||||
strings).
|
||||
- **S-NAME-1 SETTLED (Daniel, 2026-07-26): rename the binary filename too.** The on-disk
|
||||
module name is renamed to match the product (e.g. `reasampler_9000.vst3`), not just the
|
||||
display strings. Full rename surface: **CMake `OUTPUT_NAME`** on the second VST3 target,
|
||||
the **factory vendor/name strings**, the **editor title**, and the **embed label**. The
|
||||
**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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
decision record).
|
||||
|
||||
Status: framed by product-designer (2026-07-26), **revised 2026-07-26 (r5)**. r5 records
|
||||
the post-DAW-test directives on the S1–S6 instrument: the product name **ReaSampler 9000**
|
||||
and the **"better than RS5K" UX overhaul** (Phase S points S10–S13) — see the Addendum in
|
||||
§4. r4 (below) settled the four residual forks D-A..D-D. The
|
||||
Status: framed by product-designer (2026-07-26), **revised 2026-07-26 (r6)**. r6 records the
|
||||
**workflow-first reframe of S10** (Daniel, 2026-07-26): the editor's default face becomes a
|
||||
**capture browser + guided single-capture setup**, a fresh instance is **silent with a "pick
|
||||
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
|
||||
S1–S6 instrument: the product name **ReaSampler 9000** and the **"better than RS5K" UX
|
||||
overhaul** (Phase S points S10–S13) — 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
|
||||
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
|
||||
@@ -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
|
||||
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
|
||||
reference partly by filename), so the lean is **keep the filename, change only display
|
||||
strings**; flagged, not decided.
|
||||
reference partly by filename), so the r5 lean was **keep the filename, change only display
|
||||
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 is awful." The S1–S6 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"
|
||||
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
|
||||
@@ -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):
|
||||
|
||||
7. **Product name → `ReaSampler 9000`** (display-string-only; VST3 class UID unchanged;
|
||||
binary filename = fork S-NAME-1, Daniel's call).
|
||||
8. **UX overhaul → "better than RS5K"** (**S10–S13**): direct-manipulation keyboard-strip
|
||||
keymap editor (S10), waveform + draggable loop points (S11), scale/ergonomics — scroll,
|
||||
search, direct numeric entry, ADSR (S12), and drop-to-load folding in the S8 relay (S13).
|
||||
7. **Product name → `ReaSampler 9000`** (VST3 class UID unchanged; **binary filename renamed
|
||||
too — S-NAME-1 SETTLED r6**, compat is a DAW-verify).
|
||||
8. **UX overhaul → workflow-first, "better than RS5K"** (**S10–S13**; S10 **reframed r6**):
|
||||
S10 = **capture browser (peak thumbnails + bank filter) + guided single-capture setup**,
|
||||
**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
|
||||
(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
|
||||
|
||||
Reference in New Issue
Block a user