From c92d82df3a9a3d085829876af34a38a6730eb398 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Mon, 27 Jul 2026 15:07:57 -0400 Subject: [PATCH] docs(phase-s): reconcile PLAN/COMPLETED/CLAUDE for S-VIEW Wave 2 Move S-VIEW-1..9 to COMPLETED (three-view editor + velocity spline curve v7); F3 settled (full-window Browse modal); S-VIEW-10 (curve editor UI) remains the lone open Phase S item. Add trigger_seam + velocity_curve to the module inventory. --- CLAUDE.md | 6 ++- COMPLETED.md | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++ PLAN.md | 103 +++++------------------------------- 3 files changed, 163 insertions(+), 91 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9a71588..5214a3a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Repo identity and current state -The CMake project and binary are now named `reaper_reasampler`. This is **ReaSampler** — a per-project audio sample-bank capture tool that now builds TWO artifacts: the REAPER extension (`reaper_reasampler`) AND **ReaSampler 9000**, a Windows-only VST3 sampler instrument (`reasampler_9000.vst3`, `src/vst/`, second CMake target `reasampler_vst`, gated on the vendored `vendor/vst3sdk` submodule slice). The MPE modules (`mpe_model`, `mpe_view`) have been removed. M0–M8 are complete (bank_model, peaks, capture offline+realtime, persist, bank_panel, insert, capture action family, RealtimeRecordBackend, tail T1+T2+T1-followons). Phase B multi-bank (B1–B5, B-cap), Phase D1/D2 Design View (D1–D5, D2-W1–W3-B), Phase V versioning/beta-channel (V1/V3, V4), M10 provenance + re-capture from source, Phase R Reclaim (R1–R3: prune-reconcile core, dry-run shell, guarded deletion + action + panel button), M11 in full (batch capture, action-button strip + keybinding labels, conform-on-insert verified-extant, native OS drag-out), Phase L L1 (shared LICE drawing kit: `theme`/palette module, `component_geometry` geometry helpers, `draw_kit` shell, GDI DrawText retired in `bank_panel`), Phase L L2 (dock-panel layout redesign: `action_bar` pure task-grouped layout module, full M11-aware button inventory placed by task cluster, `bank_panel` redesigned through the L1 kit), Phase L L4 (dock-panel button layout enhancement: three-zone layout — top capture/placement/maintenance toolbar, bottom Design-View tagging/switching toolbar, footer narrow-toggle + Tail-button + Prune; `footer_bar` pure module + `footer_bar_tests`; `ActionCluster::Tagging`/`Switching` in `action_bar`), Phase L L5 (dock-panel button refinements: top-bar overflow menu for Batch Items/Batch Razor/Capture RT via `overflow_menu` pure module; custom LICE-kit hover-delay tooltips via `tooltip` pure module; four opposite-mode Item/Track tag buttons + Show Both on the bottom toolbar; Toggle + Activate-Arrange/Design buttons removed; grouping spacing widened — `mode_enable` pure opposite-mode enablement predicate), Phase L L6 (toolbar polish: single-row short-label button faces — keybinding micro sub-row removed from `ActionBarSlot`, keybinding now shown in hover tooltip as "`phrase — binding`"; Cancel RT moved from visible bar into the overflow menu — More (⋯) now lists Batch Items / Batch Razor / Capture RT / Cancel RT; visible top-bar cluster order Capture → Maintenance → Placement; toolbar height 40→28 px), Phase L L7 (capture ordering, card metadata, and selection styling: per-bank `SlotMap` (id→slot) in `bank_book` — gap-preserving persisted display order, `reorderSample`/`replaceSample`/`orderedSampleIds`/`reconcileSlots` mutators; `captureTimeSigNum`/`captureTimeSigDenom` stamped on `Sample` at capture via `TimeMap_GetTimeSigAtTime`; `card_drag` pure module — gesture precedence + SWELL stock cursor cues; `card_meta` pure module — bars.beats.subdivisions + s.ms formatters; `bank_panel` sparse-grid rendering with gap cells, drop dispatch + cursor cues, metadata overlay, tertiary-border selection), Phase L L3 (VST editor + embed-strip restyle: `reasampler_editor.cpp` + `reasampler_embed.cpp` restyled through the L1 kit — REAPER-grey neutrals + three pastel accents, pastel spectral keyboard strip + zone bars as the signature surface with active zone lifting to `accent/primary` + static glow, hover/pressed/drag states throughout; local `kCol*` forest-green palette retired; GDI `DrawTextA` path retired in both VST shells; VST3 class UID unchanged; Phase L now complete), and Phase S (S1–S18 complete; S13 cross-artifact relay deferred): VST3 sampler instrument `reasampler_9000.vst3` — voice engine (`sampler_core`), zone payload (`sample_map`), pitch-preserve OLA (`pitch_shift`), bank change-detection (`bank_sync`), IPC bridge (`bridge_marshal`), editor UI modules (`editor_geometry`, `keyboard_strip`, `waveform_view`, `capture_browser`, `browser_scroll`, `note_entry`, `param_slider`, `embed_strip`), VST3 shells (`reasampler_processor`, `reasampler_editor`, `reasampler_embed`, `reaper_bridge` READ-ONLY bank consumer, `vst_entry`); extension additions: `instrument_drop` pure FX-button blob builder (S17), `assignment_request` pure ingest-assign wire (S8), `ingest` shell (S8: docked-panel drop → capture/import + assign), `instrument_drop_win` shell (S17: `TrackFX_AddByName` + vst_chunk inject, rollback on fail); `drag_out` extended with `InstrumentDrop` third gesture; S18 channel isolation forks VST3 binary to `reasampler_9000_beta.vst3` alongside the beta extension have all landed. M9 slots abandoned (Daniel, 2026-07-27). Phase S Wave 1 S-VIEW pure cores landed (2026-07-27): `envelope_overlay` (params→polyline) + `envelope_edit` (node hit-test + clamped/monotonic inverse map), `previewVelocity` on `ComponentState` v6 (envelope bump v5→v6, processor round-trip), per-`PerformanceZone` `keyTrack` field (zones payload v6, pure ratio math applied in both engines), `keyboard_strip::isNaturalKey` predicate; S-VIEW-BUG-1 (drop-to-FX predicate fix in `instrument_drop`: `fx_*`/`tcp.fx`/`mcp.fx`) and S-VIEW-SIZE-1 (default `ViewRect` 840×560 + `checkSizeConstraint` floor) also landed; Wave 2 shell integration pending. The *discipline* — pure REAPER-free testable core split from REAPER-facing shells — is **preserved** throughout. +The CMake project and binary are now named `reaper_reasampler`. This is **ReaSampler** — a per-project audio sample-bank capture tool that now builds TWO artifacts: the REAPER extension (`reaper_reasampler`) AND **ReaSampler 9000**, a Windows-only VST3 sampler instrument (`reasampler_9000.vst3`, `src/vst/`, second CMake target `reasampler_vst`, gated on the vendored `vendor/vst3sdk` submodule slice). The MPE modules (`mpe_model`, `mpe_view`) have been removed. M0–M8 are complete (bank_model, peaks, capture offline+realtime, persist, bank_panel, insert, capture action family, RealtimeRecordBackend, tail T1+T2+T1-followons). Phase B multi-bank (B1–B5, B-cap), Phase D1/D2 Design View (D1–D5, D2-W1–W3-B), Phase V versioning/beta-channel (V1/V3, V4), M10 provenance + re-capture from source, Phase R Reclaim (R1–R3: prune-reconcile core, dry-run shell, guarded deletion + action + panel button), M11 in full (batch capture, action-button strip + keybinding labels, conform-on-insert verified-extant, native OS drag-out), Phase L L1 (shared LICE drawing kit: `theme`/palette module, `component_geometry` geometry helpers, `draw_kit` shell, GDI DrawText retired in `bank_panel`), Phase L L2 (dock-panel layout redesign: `action_bar` pure task-grouped layout module, full M11-aware button inventory placed by task cluster, `bank_panel` redesigned through the L1 kit), Phase L L4 (dock-panel button layout enhancement: three-zone layout — top capture/placement/maintenance toolbar, bottom Design-View tagging/switching toolbar, footer narrow-toggle + Tail-button + Prune; `footer_bar` pure module + `footer_bar_tests`; `ActionCluster::Tagging`/`Switching` in `action_bar`), Phase L L5 (dock-panel button refinements: top-bar overflow menu for Batch Items/Batch Razor/Capture RT via `overflow_menu` pure module; custom LICE-kit hover-delay tooltips via `tooltip` pure module; four opposite-mode Item/Track tag buttons + Show Both on the bottom toolbar; Toggle + Activate-Arrange/Design buttons removed; grouping spacing widened — `mode_enable` pure opposite-mode enablement predicate), Phase L L6 (toolbar polish: single-row short-label button faces — keybinding micro sub-row removed from `ActionBarSlot`, keybinding now shown in hover tooltip as "`phrase — binding`"; Cancel RT moved from visible bar into the overflow menu — More (⋯) now lists Batch Items / Batch Razor / Capture RT / Cancel RT; visible top-bar cluster order Capture → Maintenance → Placement; toolbar height 40→28 px), Phase L L7 (capture ordering, card metadata, and selection styling: per-bank `SlotMap` (id→slot) in `bank_book` — gap-preserving persisted display order, `reorderSample`/`replaceSample`/`orderedSampleIds`/`reconcileSlots` mutators; `captureTimeSigNum`/`captureTimeSigDenom` stamped on `Sample` at capture via `TimeMap_GetTimeSigAtTime`; `card_drag` pure module — gesture precedence + SWELL stock cursor cues; `card_meta` pure module — bars.beats.subdivisions + s.ms formatters; `bank_panel` sparse-grid rendering with gap cells, drop dispatch + cursor cues, metadata overlay, tertiary-border selection), Phase L L3 (VST editor + embed-strip restyle: `reasampler_editor.cpp` + `reasampler_embed.cpp` restyled through the L1 kit — REAPER-grey neutrals + three pastel accents, pastel spectral keyboard strip + zone bars as the signature surface with active zone lifting to `accent/primary` + static glow, hover/pressed/drag states throughout; local `kCol*` forest-green palette retired; GDI `DrawTextA` path retired in both VST shells; VST3 class UID unchanged; Phase L now complete), and Phase S (S1–S18 complete; S13 cross-artifact relay deferred): VST3 sampler instrument `reasampler_9000.vst3` — voice engine (`sampler_core`), zone payload (`sample_map`), pitch-preserve OLA (`pitch_shift`), bank change-detection (`bank_sync`), IPC bridge (`bridge_marshal`), editor UI modules (`editor_geometry`, `keyboard_strip`, `waveform_view`, `capture_browser`, `browser_scroll`, `note_entry`, `param_slider`, `embed_strip`), VST3 shells (`reasampler_processor`, `reasampler_editor`, `reasampler_embed`, `reaper_bridge` READ-ONLY bank consumer, `vst_entry`); extension additions: `instrument_drop` pure FX-button blob builder (S17), `assignment_request` pure ingest-assign wire (S8), `ingest` shell (S8: docked-panel drop → capture/import + assign), `instrument_drop_win` shell (S17: `TrackFX_AddByName` + vst_chunk inject, rollback on fail); `drag_out` extended with `InstrumentDrop` third gesture; S18 channel isolation forks VST3 binary to `reasampler_9000_beta.vst3` alongside the beta extension have all landed. M9 slots abandoned (Daniel, 2026-07-27). Phase S S-VIEW Wave 1 + Wave 2 fully landed (2026-07-27, `Merge pS-w2-t1-shell` + `Merge pS-w2-t2-velcurve`, 52/52 green): three-view editor (Sample home face — hero waveform + envelope overlay + draggable nodes + fenced root + preview-trigger + velocity knob + Modes-and-down control strip; Browse full-window modal picker with confirm/cancel; Zone view retained + wired with piano-key pattern + key-track control); `envelope_overlay` (params→polyline) + `envelope_edit` (node hit-test + clamped/monotonic inverse map) wired in `reasampler_editor.cpp`; `trigger_seam` pure module (Trigger frames↔fraction converter, startFrame-aware, unit-tested); preview button + velocity knob UI wired (RT-safe off-thread preview-note mailbox); key-track UI control on Zone + Sample; two-tone piano-key overlay drawn; `velocity_curve` pure module (Fritsch–Carlson monotone cubic Hermite spline eval + control-point editing + hit-test/inverse-map; `velocity_curve_tests`); zones payload bumped to v7 (`velocityCurve` on `PerformanceZone`, pre-v7 blobs lift to flat y=1); `Voice::start()` now applies `curve.eval(velocity)` — default is flat y=1 (replaces prior linear `velocity/127`, deliberate non-back-compat change, Daniel-approved). S-VIEW-10 (velocity-curve editor UI, Wave 3) remains open. The *discipline* — pure REAPER-free testable core split from REAPER-facing shells — is **preserved** throughout. CONTEXT.md is the authoritative spec and build roadmap. Read it first for any non-trivial task. Every REAPER API name cited there is correct-by-intent; verify argument order, types, and flag values against `vendor/reaper-sdk/sdk/reaper_plugin_functions.h` before use. @@ -74,6 +74,8 @@ Key targets (see CMakeLists.txt for the full list): | `browser_scroll_tests` | executable | Pure unit tests for `browser_scroll` (VST3 browser scroll, S12) — no REAPER, no DAW. | | `note_entry_tests` | executable | Pure unit tests for `note_entry` (VST3 note entry, S12) — no REAPER, no DAW. | | `param_slider_tests` | executable | Pure unit tests for `param_slider` (VST3 param slider, S12) — no REAPER, no DAW. | +| `trigger_seam_tests` | executable | Pure unit tests for `trigger_seam` (VST3 Trigger frames↔fraction converter, S-VIEW-3) — no REAPER, no DAW. | +| `velocity_curve_tests` | executable | Pure unit tests for `velocity_curve` (VST3 velocity→amp monotone-spline transfer curve, S-VIEW-9) — no REAPER, no DAW. | | `reaper_reasampler` | loadable module | The REAPER extension binary (`.dll` / `.dylib` / `.so`). | | `reasampler_vst` | loadable module | The VST3 sampler instrument (`reasampler_9000.vst3`); Windows-only, omitted if `vendor/vst3sdk` slice is absent. | @@ -159,6 +161,8 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde - `browser_scroll` — Phase S scroll + type-to-filter pure module (S12) layered over `capture_browser`: vertical scroll offset, max-offset clamp, visible-row window, scrollbar thumb rect, thumb-drag↔offset mapping; AND name-substring search (case-insensitive, composing with the bank filter). No REAPER or VST3 types at the boundary. - `note_entry` — Phase S note-entry pure module (S12): parses a raw string into a clamped MIDI note [0,127] for zone low/high/root note editing; accepts plain decimal integers or note names (C4==60, DAW convention); out-of-range clamps, unparseable returns nullopt. No REAPER or VST3 types at the boundary. - `param_slider` — Phase S parameter control-panel pure module (S12): lays out a vertical stack of fixed-height control rows in two shapes — TOGGLE (two-segment selector) and SLIDER (horizontal track with draggable 0..1 handle); maps normalized value to/from handle pixel; the shell owns domain-value conversion and control-id→engine-param binding. No REAPER or VST3 types at the boundary. +- `trigger_seam` — Phase S S-VIEW-3 pure Trigger frames↔fraction converter: owns the shared formula for converting between the engine's source-frame fade counts and the overlay's fractional representation (fraction of the played span, itself derived from `lengthFraction × (frameCount − startFrame)`); `startFrame` is threaded correctly through both the pack (frames→fraction, draw path) and unpack (fraction→frames, commit path) directions. Unit-tested independently of the shell. No REAPER or VST3 types at the boundary. +- `velocity_curve` — Phase S S-VIEW-9 pure velocity→amp transfer curve: `VelocityCurve` — an x-ordered list of `VelocityPoint` knots evaluated by a Fritsch–Carlson monotone cubic Hermite spline (no overshoot outside [0,1]; collinear knots reduce to the exact linear ramp); `eval(velocity)` called once per note-on in `Voice::start()`; control-point editing (`addPoint`/`movePoint`/`deletePoint`) with box-clamp and x-order invariant; hit-test (`pointAtPixel`) + drag inverse-map (`resolvePointDrag`); `flat()` default (y=1, every velocity→unity — zones-payload v7 default) and `linear()` (y=velocity/127, retained for tests). Replaces the prior fixed `velocity/127` engine path; default flat y=1 is a deliberate non-back-compat behavior change (Daniel-approved). No REAPER or VST3 types at the boundary. - `embed_strip` — Phase S embed-mode strip layout pure module (S6): compact single-row control layout for when the editor is embedded in the track FX chain. No REAPER or VST3 types at the boundary. **VST3 instrument (`src/vst/`) — shells:** diff --git a/COMPLETED.md b/COMPLETED.md index bf5e0c5..1675bb6 100644 --- a/COMPLETED.md +++ b/COMPLETED.md @@ -2271,3 +2271,148 @@ shrink below the floor. Final tuning deferred to T-SHELL. - [x] Default `ViewRect` bumped to 840×560. - [x] `checkSizeConstraint` minimum floor enforced. + +--- + +# Phase S — editor view-model redesign (three views: Sample / Browse / Zone) + +> **Additive Phase S sub-phase (S-VIEW; Daniel, 2026-07-27, r9).** Merged to dev (Wave 1 +> 2026-07-27, Wave 2 2026-07-27 — `Merge pS-w2-t1-shell` + `Merge pS-w2-t2-velcurve`). +> Integrated suite 52/52 green. S-VIEW-10 (velocity-curve editor UI) remains open in `PLAN.md`. + +## S-VIEW-1 — three-view navigation model +**Goal:** Retire the flat Browser|Zones toggle; introduce Sample (home/default), Browse (modal +overlay over Sample, select+confirm), Zone (dedicated surface, own button). Empty state surfaces +Browse as the dominant call-to-action. Fresh instance stays silent (S10 reversal). Fork +S-VIEW-F3 SETTLED — full-window overlay: Browse renders as a full-window modal over Sample +(not a centered sheet). See CONTEXT.md §S-VIEW navigation contract. + +- [x] Flat Browser|Zones toggle retired; three-view model: Sample home, Browse full-window modal + over Sample, Zone dedicated surface with its own button. +- [x] Empty state surfaces Browse as the dominant call-to-action; fresh instance is silent + (S10 reversal preserved). +- [x] S-VIEW-F3 settled and implemented: Browse is a full-window modal overlay over Sample. + +--- + +## S-VIEW-2 — Sample view (the new main face) +**Goal:** Compose the home face: enlarged **hero waveform** with the S11 markers (moved from +Browse), a **fenced root affordance**, the **Mono/Stereo toggle** (moved from Browse), and the +**"Modes-and-down" control strip** (Mode / Pitch engine / AHDSR|Trigger / AD pitch env — moved +from Zone's param panel, single-capture one-zone storage per S15-F2). Reference grammar: +Simpler / Phase Plant (labelled value-strip under a hero waveform). + +- [x] Hero waveform with S11 markers (start/loop start/loop end) moved to Sample face. +- [x] Fenced root affordance on the Sample face. +- [x] Mono/Stereo toggle moved from Browse to Sample. +- [x] "Modes-and-down" control strip (Mode / Pitch engine / AHDSR|Trigger / AD pitch env) moved + from Zone's param panel to the Sample face (single-capture one-zone storage per S15-F2). + +--- + +## S-VIEW-3 — envelope overlay + draggable nodes +**Goal:** Draw the amp envelope (AHDSR for Gate, fade/%-length for Trigger) as a curve over the +Sample waveform at accurate wall-clock time. Pure `envelope_overlay` module (params + frame-length +→ polyline; unit-tested); shell traces it in an accent hue. Breakpoints are draggable handles +(S-VIEW-F2 SETTLED): X → segment time, Y → level on level-breakpoint nodes (sustain drags both +axes), monotonic-in-time + range-clamped. Pure `envelope_edit` module (node hit-test + +pixel-delta→clamped-param inverse map; mirror of `card_drag`; unit-tested). Both surfaces +read/write the same `PerformanceZone` envelope fields. Wave 1 landed the pure modules; +Wave 2 wired them in `reasampler_editor.cpp`. The Trigger frames↔fraction conversion was +extracted to a new pure unit-tested `trigger_seam` module (startFrame threaded correctly). + +- [x] **Wave 1:** `envelope_overlay` + `envelope_edit` pure modules landed and unit-tested. +- [x] **Wave 2:** `reasampler_editor.cpp` traces the overlay and wires draggable node handles. +- [x] **Wave 2:** `trigger_seam` pure module — Trigger frames↔fraction converter (pack and unpack + directions, unit-tested); `startFrame` threaded correctly through the seam. + +--- + +## S-VIEW-4 — preview-trigger + velocity knob +**Goal:** A button firing the sampler at the loaded capture's root note through the live voice +engine (off the audio-thread commit path — no MIDI controller needed) + an adjacent velocity +knob. Preview velocity PERSISTS (S-VIEW-F1 SETTLED): `previewVelocity` on `ComponentState` via +an envelope bump to v6 (`src/vst/sample_map.h`), round-tripped through `getState`/`setState` +over `IBStream`. Zones payload untouched; older blobs lift to a mid default. Wave 1 landed the +field; Wave 2 landed the preview button + velocity knob UI. + +- [x] **Wave 1:** `previewVelocity` field on `ComponentState`, envelope v5→v6 bump, processor + `getState`/`setState` round-trip, clamped 1..127. +- [x] **Wave 2:** preview button + velocity knob UI in `reasampler_editor.cpp`; RT-safe + off-thread preview-note mailbox on the processor. + +--- + +## S-VIEW-5 — Browse reduced to choosing (modal over Sample) +**Goal:** Keep search + bank tabs + captures grid + scroll + selection; add confirm/cancel +(double-click loads). Remove the large waveform preview, Mono/Stereo toggle, root keyboard-strip +(all moved to Sample), and the loop-point labels + track-root message (cut). Render as a +full-window modal overlay (S-VIEW-F3 SETTLED — implemented here and in S-VIEW-1). + +- [x] Search + bank tabs + captures grid + scroll + selection retained. +- [x] Confirm/cancel affordance added; double-click loads. +- [x] Large waveform preview, Mono/Stereo toggle, root keyboard-strip, loop-point labels, and + track-root message removed from Browse. +- [x] Browse renders as a full-window modal overlay over Sample (S-VIEW-F3 settled and shipped). + +--- + +## S-VIEW-6 — key-tracking parameter +**Goal:** Per-`PerformanceZone` scalar on keyboard pitch tracking around the root (100% = 12-tone-ET, +0% = no tracking, 200% = double). Additive/version-bumped, defaults 100% (bit-identical). Key-track +math in the pure sampler core (unit-tested note/root/keyTrack → ratio), applied in both Varispeed +and Preserve repitch. Surfaces as a control on the Zone param panel + Sample control strip. +Wave 1 landed the field/math/apply; Wave 2 landed the UI control. + +- [x] **Wave 1:** per-`PerformanceZone` `keyTrack` field (zones payload v6, default 100% + bit-identical), pure ratio math, applied in both Varispeed and Preserve engines. +- [x] **Wave 2:** UI control on the Zone param panel + Sample control strip. + +--- + +## S-VIEW-7 — piano-key pattern on the keyboard strip +**Goal:** Overlay the actual alternating white/black (bright/dark per palette) key pattern over +the pastel spectral fill so the strip reads as a keyboard. Pure `keyboard_strip` gains a +natural/accidental predicate (12-tone, unit-tested); shell draws the two-tone overlay. Shared +by the Zone strip + Sample root affordance. Wave 1 landed `isNaturalKey`; Wave 2 drew the overlay. + +- [x] **Wave 1:** pure `isNaturalKey` predicate on `keyboard_strip`, unit-tested. +- [x] **Wave 2:** `reasampler_editor.cpp` draws the two-tone overlay over the spectral fill. + +--- + +## S-VIEW-8 — Zone view retained + wired +**Goal:** Keep +Add Zone / Delete, the per-zone keyboard strip (now with the piano pattern), the +Low/High/Root numeric-entry legend, and the per-zone param panel; add the key-tracking control. +Nothing from today's Zones view dropped. + +- [x] +Add Zone / Delete retained. +- [x] Per-zone keyboard strip with piano-key pattern. +- [x] Low/High/Root numeric-entry legend retained. +- [x] Per-zone param panel retained. +- [x] Key-tracking control added to Zone view. + +--- + +## S-VIEW-9 — velocity→amp transfer curve (pure core + engine application) +**Goal:** New pure `velocity_curve` module — eval (monotone cubic Hermite spline, +Fritsch–Carlson slope limiting) + control-point editing (add/move/delete x-ordered + +box-clamped) + hit-test + pixel-delta→clamped-point inverse map; unit-tested at eval + +clamp/order boundaries. Additive `velocityCurve` field on `PerformanceZone` (instrument-owned, +D-B), zones-payload version axis bumped to **v7**; default = **flat y=1** (R10-F1 SETTLED — +Option A, Daniel 2026-07-27: "any velocity plays at full level"); older ≤v6 blobs lift to flat +y=1. Applied at `Voice::start()` — replaces `velocityGain_ = velocity / 127.0` with +`velocityGain_ = curve.eval(velocity)`, off the per-frame path (no new RT work). +**Default is a deliberate non-back-compat behavior change:** the prior engine used linear +`velocity/127`; existing zones' soft hits will play at full level after upgrade. Flagged and +accepted by Daniel. + +- [x] Pure `velocity_curve` module: `eval(velocity 0–127)→amp 0–1` via Fritsch–Carlson monotone + cubic Hermite spline (no overshoot outside [0,1]; collinear points reduce to exact linear ramp). +- [x] Control-point editing: `addPoint`, `movePoint` (x-clamped between neighbours, endpoints + x-pinned), `deletePoint` (endpoints not deletable); `fromPoints` repair-on-deserialize. +- [x] Hit-test + inverse map: `pointAtPixel`, `resolvePointDrag` (pure; mirror of `envelope_edit`). +- [x] Additive `velocityCurve` on `PerformanceZone`, zones-payload v7; pre-v7 blobs lift to + flat y=1; `velocity_curve_tests` CTest target. +- [x] Applied at `Voice::start()` — `velocityGain_` now set from `curve.eval(velocity)`. +- [x] Default flat y=1 — back-compat caveat documented and accepted. diff --git a/PLAN.md b/PLAN.md index 3fb5395..3043223 100644 --- a/PLAN.md +++ b/PLAN.md @@ -207,92 +207,16 @@ fenced root + preview-trigger + control strip) with no scroll; Browse opens as a Sample, select+confirm loads a new capture; Zone opens on its own button, key-tracking + the piano-key pattern work; drop-a-capture-onto-FX instantiates a playing instance. -- [ ] **S-VIEW-1 — three-view navigation model.** Retire the flat Browser|Zones toggle; introduce - Sample (home/default), Browse (modal overlay over Sample, select+confirm), Zone (dedicated - surface, own button). Empty state surfaces Browse as the dominant call-to-action. Fresh instance - stays silent (S10 reversal). See CONTEXT.md §S-VIEW navigation contract. -- [ ] **S-VIEW-2 — Sample view (the new main face).** Compose the home face: enlarged **hero - waveform** with the S11 markers (moved from Browse), a **fenced root affordance**, the - **Mono/Stereo toggle** (moved from Browse), and the **"Modes-and-down" control strip** (Mode / - Pitch engine / AHDSR|Trigger / AD pitch env — moved from Zone's param panel, single-capture - one-zone storage per S15-F2). Reference grammar: Simpler / Phase Plant (labelled value-strip - under a hero waveform). -- [ ] **S-VIEW-3 — envelope overlay + DRAGGABLE NODES on the hero waveform (new visual + edit - surface).** Draw the amp envelope (AHDSR for Gate, fade/%-length for Trigger) as a curve over the - Sample waveform at accurate wall-clock time. New pure `envelope_overlay` module (params + frame- - length → polyline; unit-tested); shell traces it in an accent hue. **The breakpoints are draggable - handles** (S-VIEW-F2 SETTLED — in scope, not deferred): X → segment time, Y → level on level- - breakpoint nodes (sustain drags both axes), monotonic-in-time + range-clamped so a drag can never - produce a param the slider couldn't. New pure `envelope_edit` module (node hit-test + pixel-delta→ - clamped-param inverse map; mirror of `card_drag`; unit-tested at the clamp/monotonic boundaries). - Sliders stay the precise numeric surface; **both surfaces read/write the same `PerformanceZone` - envelope fields** — one source of truth, kept in sync by re-reading params each paint (no listener - chain). Node-drag commits via the same off-audio-thread path a slider edit uses (no new RT - surface). - > **Wave 1 (S-VIEW cores) ✓:** `envelope_overlay` + `envelope_edit` pure modules landed - > and unit-tested. **Wave 2 shell PENDING:** `reasampler_editor.cpp` must trace the overlay - > and wire draggable node handles; a Trigger frames↔fraction converter is owed at the module - > seam (documented there). -- [ ] **S-VIEW-4 — preview-trigger + velocity knob (new, Sample view).** A button firing the - sampler at the loaded capture's root note through the live voice engine (off the audio-thread - commit path — no MIDI controller needed) + an adjacent velocity knob. **Preview velocity PERSISTS** - (S-VIEW-F1 SETTLED): it lands as a new top-level `previewVelocity` field on `ComponentState` via an - **envelope bump to v6** (`src/vst/sample_map.h`), round-tripped through the processor's existing - `getState`/`setState` over `IBStream` — **not** the extension's project-scoped `persist` ext-state - (wrong owner + wrong scope; see CONTEXT.md §S-VIEW). Zones payload untouched (independent version - axes); older blobs lift to a mid default. RT discipline: no `process`-thread work. - > **Wave 1 (S-VIEW cores) ✓:** `previewVelocity` field on `ComponentState`, envelope v5→v6 - > bump, processor `getState`/`setState` round-trip, clamped 1..127 — all landed. **Wave 2 - > shell PENDING:** the preview button + velocity knob UI in `reasampler_editor.cpp`. -- [ ] **S-VIEW-5 — Browse reduced to *choosing* (modal over Sample).** Keep search + bank tabs + - captures grid + scroll + selection; add **confirm/cancel** (double-click loads). **Remove** the - large waveform preview, Mono/Stereo toggle, root keyboard-strip (all moved to Sample), and the - loop-point labels + track-root message (cut). Render as a full-window modal overlay (fork - S-VIEW-F3). -- [ ] **S-VIEW-6 — key-tracking parameter (Zone + Sample, 0%–200%, default 100%).** A per- - `PerformanceZone` scalar on keyboard pitch tracking around the root (100% = 12-tone-ET, 0% = no - tracking, 200% = double). Additive/version-bumped, defaults 100% (bit-identical). **Key-track - math lives in the pure sampler core** (unit-tested note/root/keyTrack → ratio), applied in both - Varispeed and Preserve repitch. Surfaces as a control on the Zone param panel + Sample control - strip. - > **Wave 1 (S-VIEW cores) ✓:** per-`PerformanceZone` `keyTrack` field (zones payload v6, - > default 100% bit-identical), pure ratio math, applied in both Varispeed and Preserve - > engines — landed. **Wave 2 shell PENDING:** the UI control in the Zone param panel + - > Sample control strip. -- [ ] **S-VIEW-7 — real piano-key pattern on the keyboard strip (new visual).** Overlay the actual - alternating white/black (bright/dark per palette) key pattern over the pastel spectral fill so - the strip reads as a keyboard. Pure `keyboard_strip` gains a natural/accidental predicate (12- - tone, unit-tested); shell draws the two-tone overlay. Shared by the Zone strip + Sample root - affordance. - > **Wave 1 (S-VIEW cores) ✓:** pure `isNaturalKey` predicate on `keyboard_strip`, unit-tested - > — landed. **Wave 2 shell PENDING:** `reasampler_editor.cpp` must draw the two-tone overlay - > over the spectral fill. -- [ ] **S-VIEW-8 — Zone view retained + wired.** Keep +Add Zone / Delete, the per-zone keyboard - strip (now with the piano pattern), the Low/High/Root numeric-entry legend, and the per-zone - param panel; add the key-tracking control. Nothing from today's Zones view dropped. -- [ ] **S-VIEW-9 — velocity → amp transfer-curve: pure core + engine application (foundation, r10).** - New pure `velocity_curve` module (bezier `eval(velocity 0–127)→amp 0–1`, monotonic-in-x, clamped - to the 0–127×0–1 box; control-point add/move/delete x-ordered + box-clamped; hit-test + pixel- - delta→clamped-point inverse map; mirror of `envelope_edit`; unit-tested at eval + clamp/order - boundaries). Additive `velocityCurve` field on `PerformanceZone` (instrument-owned, D-B), on the - **zones-payload version axis**, default = **flat y=1** (R10-F1 SETTLED — Option A, Daniel - 2026-07-27: "any velocity plays at full level"); older blobs lift to that default. **Back-compat - caveat: this is a deliberate non-back-compat behavior change** — the current engine does linear - `velocity/127`, so existing zones' soft hits will be louder after upgrade. Flagged and accepted. - Apply at **`Voice::start()`** — replace `velocityGain_ = velocity / 127.0` (`sampler_core.cpp:261`) - with `velocityGain_ = curve.eval(velocity)`, off the per-frame path (no new RT work). **BLOCKED BY - Wave 1 T-KEYTRK** (`PerformanceZone` schema + payload bump) — this is a LATER additive payload bump - sequencing AFTER T-KEYTRK merges (the two never collide on one payload version). NOT blocked by - T-STATE (that owns the per-instance `ComponentState` v6 axis for preview velocity). See CONTEXT.md - §S-VIEW velocity-curve sub-section. +- [x] **S-VIEW-1 through S-VIEW-9** — all landed (Wave 1 core + Wave 2 shell). See `COMPLETED.md` + §S-VIEW for the full entry set. - [ ] **S-VIEW-10 — velocity-curve editor UI (shell, Sample + Zone views, r10).** A compact transfer- curve editor band on the **Sample view** adjacent to the envelope overlay (X = velocity 0–127, Y = - amp 0–1; bezier traced in an accent hue; draggable node markers per control point; add on empty- - space click, delete on modifier-click/drag-off), drawn through the **L1 kit**; the same editor in - the Zone per-zone param panel (one curve per zone). Shell (`reasampler_editor.cpp`) does the LICE - draw + mouse routing; **all geometry/hit-test/clamp math in the pure `velocity_curve` module**. - **Wave 2 shell-integration item** — gated on S-VIEW-9 (foundation) + composes with S-VIEW-2 (Sample - face) / S-VIEW-3 (envelope overlay). + amp 0–1; spline curve traced in an accent hue; draggable node markers per control point; add on + empty-space click, delete on modifier-click/drag-off), drawn through the **L1 kit**; the same + editor in the Zone per-zone param panel (one curve per zone). Shell (`reasampler_editor.cpp`) does + the LICE draw + mouse routing; **all geometry/hit-test/clamp math in the pure `velocity_curve` + module**. Foundation (S-VIEW-9) is landed — unblocked on the module; needs the Sample-view + curve-editor UI. ### Phase S editor prerequisites — routed to staff-engineer (bug + default; NOT design forks) @@ -301,12 +225,11 @@ piano-key pattern work; drop-a-capture-onto-FX instantiates a playing instance. > **S-VIEW-BUG-1 and S-VIEW-SIZE-1 have both landed (Wave 1) — see `COMPLETED.md`.** ### Phase S editor redesign — forks (Daniel's to call) -*(S-VIEW-F1 and S-VIEW-F2 SETTLED 2026-07-27 — folded into S-VIEW-4 / S-VIEW-3 above. F1: preview -velocity persists via envelope-v6 `ComponentState`. F2: envelope nodes are draggable via the pure -`envelope_edit` module. R10-F1 SETTLED 2026-07-27 — Option A flat y=1, folded into S-VIEW-9 above. -Only F3 remains open.)* -- **S-VIEW-F3 — Browse modal presentation:** full-window overlay (**lean**) vs. a centered sheet - over a dimmed Sample. Presentation detail. +*(All forks settled: S-VIEW-F1 and S-VIEW-F2 SETTLED 2026-07-27 — folded into S-VIEW-4 / S-VIEW-3. +F1: preview velocity persists via envelope-v6 `ComponentState`. F2: envelope nodes are draggable +via the pure `envelope_edit` module. R10-F1 SETTLED 2026-07-27 — Option A flat y=1, folded into +S-VIEW-9. S-VIEW-F3 SETTLED — full-window overlay, implemented as Browse renders as a full-window +modal over Sample (landed in S-VIEW-1/S-VIEW-5). No open forks remain.)* ---