From 991c190bb8bcf9f386eab0e00b90818fb8aff543 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Sun, 26 Jul 2026 19:49:03 -0400 Subject: [PATCH] docs(product): reframe S10 capture-first (browser + single-capture setup); reverse S4 auto-select; settle S-NAME-1 filename rename --- CONTEXT.md | 132 ++++++++++++++++------- PLAN.md | 198 ++++++++++++++++++++++++---------- docs/product/midi-playback.md | 84 +++++++++++++-- 3 files changed, 312 insertions(+), 102 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 57fff3d..ebab860 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -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) diff --git a/PLAN.md b/PLAN.md index f16d062..86560b1 100644 --- a/PLAN.md +++ b/PLAN.md @@ -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 diff --git a/docs/product/midi-playback.md b/docs/product/midi-playback.md index 31d03d7..e488dee 100644 --- a/docs/product/midi-playback.md +++ b/docs/product/midi-playback.md @@ -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