diff --git a/CLAUDE.md b/CLAUDE.md index 78d962e..70c15bf 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 -**ReaSampler** is a per-project audio sample-bank capture tool that 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 pure-testable-core / REAPER-facing-shell discipline is preserved throughout. CONTEXT.md is the authoritative spec — settled decisions, invariants, guardrails, and not-yet-built specs; it is large, so locate the relevant phase section by grepping its headings and read only that section with an offset rather than reading it whole. Build detail for landed phases lives in CONTEXT-ARCHIVE.md. 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. A post-S-VIEW DAW-fix pass has landed (all 52 suite tests green): envelope nodes fully editable in both modes (every Gate stage A/H/D/S/R + Trigger zero-fade-out node, param-domain schematic scaling, 8 px min node separation, all nodes clamped in-canvas); gap-free per-column waveform render (`columnMinMax` homed in `peaks`, `waveformColumnCount` in `component_geometry`, shared via `drawWaveform`); `param_slider` `Knob` primitive (7→5 o'clock arc, needle, vertical-drag); zone-bleed fix 3a (`reconcileSingleCaptureZones` in `sample_map`). The voice-system redesign is also landed: `sampler_core` gains user-parameterized voice count (1–32, default 16), `VoiceMode` Poly/Mono (last-note held-note stack, `MonoTrigger` retrigger/legato toggle), an isolated `PreviewCard` (dedicated preview voice outside the MIDI pool — never steals from/into it; unity-Preserve zero-latency bypass scoped to it), and two-tier panic (CC 123 = release, CC 120 = immediate hard-stop incl. Trigger one-shots); processor sums the preview card alongside the engine + drain, `retireIdleDrain()` retires fully-idle drain snapshots, and voice-param edits rebuild from the already-decoded PCM (no bank re-read/WAV re-decode) via the drain-slot swap; `ComponentState` envelope bumped v6→v7 (voiceCount/voiceMode/monoTrigger bytes; pre-v7 blobs lift to 16/Poly/Retrigger). **FB1 Sample-view recomposition (r11) has also landed** (suite 55/55 green): all linear sliders replaced by radial **knobs** in a fenced **knob deck** (groups: AMP ENVELOPE / PITCH / PITCH ENV / VOICE / MASTER); mode toggles are compact in the caption row, not full-width; the **hero waveform runs full-width** (elastic band, 840×620 default preserved); the inline velocity-curve box is replaced by a **28×28 curve preview button → centered popup** with right-click node delete; voice-band controls (count / Poly-Mono / Retrig-Legato) are placed in the VOICE deck group; a **post-mixer per-sample-ramped master gain** (−∞…+24 dB, no zipper) is placed in the MASTER deck group, persisted as `masterGainLinear` — `ComponentState` envelope bumped v7→v8 (pre-v8 blobs lift to unity gain). Three new pure `src/vst/` modules landed: `knob_deck` (group-box + caption-row + knob-cell geometry, deterministic wrap, hit-test), `curve_popup` (sheet/close/box geometry + outside-sheet dismissal test), `master_gain` (dB↔linear taper math, −∞…+24 dB). **FB2 Zone-panel parity (r11, 2026-07-28) has also landed** (suite 55/55 green): the Zone param panel now uses the same knob deck + curve-preview-button/popup grammar as the Sample face — one control grammar across both surfaces of the one per-zone storage site; Zone-authoring affordances (+Add Zone / Delete, the piano-key strip, Low/High/Root legend) are preserved; VOICE and MASTER groups remain Sample-only (per-instance). `param_slider`'s linear slider rows are retired on the Zone panel (the FA4 `Knob` primitive is now the only live consumer of that half of `param_slider`). **This completes the r11 editor recomposition (Wave B / Phase S editor redesign).** +**ReaSampler** is a per-project audio sample-bank capture tool that 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 pure-testable-core / REAPER-facing-shell discipline is preserved throughout. CONTEXT.md is the authoritative spec — settled decisions, invariants, guardrails, and not-yet-built specs; it is large, so locate the relevant phase section by grepping its headings and read only that section with an offset rather than reading it whole. Build detail for landed phases lives in CONTEXT-ARCHIVE.md. 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. A post-S-VIEW DAW-fix pass has landed (all 52 suite tests green): envelope nodes fully editable in both modes (every Gate stage A/H/D/S/R + Trigger zero-fade-out node, param-domain schematic scaling, 8 px min node separation, all nodes clamped in-canvas); gap-free per-column waveform render (`columnMinMax` homed in `peaks`, `waveformColumnCount` in `component_geometry`, shared via `drawWaveform`); `param_slider` `Knob` primitive (7→5 o'clock arc, needle, vertical-drag); zone-bleed fix 3a (`reconcileSingleCaptureZones` in `sample_map`). The voice-system redesign is also landed: `sampler_core` gains user-parameterized voice count (1–32, default 16), `VoiceMode` Poly/Mono (last-note held-note stack, `MonoTrigger` retrigger/legato toggle), an isolated `PreviewCard` (dedicated preview voice outside the MIDI pool — never steals from/into it; unity-Preserve zero-latency bypass scoped to it), and two-tier panic (CC 123 = release, CC 120 = immediate hard-stop incl. Trigger one-shots); processor sums the preview card alongside the engine + drain, `retireIdleDrain()` retires fully-idle drain snapshots, and voice-param edits rebuild from the already-decoded PCM (no bank re-read/WAV re-decode) via the drain-slot swap; `ComponentState` envelope bumped v6→v7 (voiceCount/voiceMode/monoTrigger bytes; pre-v7 blobs lift to 16/Poly/Retrigger). **FB1 Sample-view recomposition (r11) has also landed** (suite 55/55 green): all linear sliders replaced by radial **knobs** in a fenced **knob deck** (groups: AMP ENVELOPE / PITCH / PITCH ENV / VOICE / MASTER); mode toggles are compact in the caption row, not full-width; the **hero waveform runs full-width** (elastic band, 840×620 default preserved); the inline velocity-curve box is replaced by a **28×28 curve preview button → centered popup** with right-click node delete; voice-band controls (count / Poly-Mono / Retrig-Legato) are placed in the VOICE deck group; a **post-mixer per-sample-ramped master gain** (−∞…+24 dB, no zipper) is placed in the MASTER deck group, persisted as `masterGainLinear` — `ComponentState` envelope bumped v7→v8 (pre-v8 blobs lift to unity gain). Three new pure `src/vst/` modules landed: `knob_deck` (group-box + caption-row + knob-cell geometry, deterministic wrap, hit-test), `curve_popup` (sheet/close/box geometry + outside-sheet dismissal test), `master_gain` (dB↔linear taper math, −∞…+24 dB). **FB2 Zone-panel parity (r11, 2026-07-28) has also landed** (suite 55/55 green): the Zone param panel now uses the same knob deck + curve-preview-button/popup grammar as the Sample face — one control grammar across both surfaces of the one per-zone storage site; Zone-authoring affordances (+Add Zone / Delete, the piano-key strip, Low/High/Root legend) are preserved; VOICE and MASTER groups remain Sample-only (per-instance). `param_slider`'s linear slider rows are retired on the Zone panel (the FA4 `Knob` primitive is now the only live consumer of that half of `param_slider`). **This completes the r11 editor recomposition (Wave B / Phase S editor redesign).** A **GA post-launch DAW-fix pass** has also landed (suite 55/55 green): `pitch_shift` rewritten from dual-tap OLA (anti-phase cancellation → spectral garbage on repitched notes) to **correlation-aligned SOLA splices** with a ratio-scaled raised-cosine fade (clean pitch shift past +24 st); `Voice::start` gained a `takeoverDeclick` gate (applied on the envelope complement) for mono retrig/fallback + poly at-cap steal; the output bus is now **permanently stereo** (`ChannelMode` is decode-only; the dynamic mono↔stereo bus renegotiation is deleted) with channel mode **auto-defaulting from the loaded capture** via new `ComponentState` **v9** (`channelModeExplicit` flag) + a pure `channelModeFor` helper; `SetCapture` moved to drag-arm in `bank_panel` so a first straight-out drag arms correctly; and the Design/Arrange mode-toggle action now calls `bankPanelInvalidate()` so the panel footer reflects the new mode without requiring a button click. ## One-time submodule setup @@ -100,7 +100,7 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde **VST3 instrument (`src/vst/`) — pure core:** - `sampler_core` — polyphonic voice engine with bounded stealing, user-parameterized voice count (1–32, default 16), `VoiceMode` Poly/Mono (last-note held-note stack, `MonoTrigger` Retrigger/Legato toggle), isolated `PreviewCard` (dedicated preview voice outside the MIDI pool — never steals from/into it; unity-Preserve zero-latency bypass scoped to it), two-tier panic (CC 123 = all-notes-off release, CC 120 = immediate hard-stop including Trigger one-shots); per-zone `ZonePlayParams` (Gate/Trigger, AHDSR, pitch engine Varispeed/Preserve, AD pitch mod envelope), repitch/interpolation with loop-point-aware sustain. - `sample_map` — zone payload: zones keyed by note range. **Wall-clock times stored as rate-free SECONDS, resolved against the live project rate — NO hardcoded sample rates in `src/`** (Daniel's standing ruling, load-bearing). JSON round-trip. -- `pitch_shift` — hand-rolled overlap-add pitch shifter for the Preserve playback mode; no third-party dependencies. +- `pitch_shift` — hand-rolled **correlation-aligned SOLA** (splice-overlap-add) pitch shifter for the Preserve playback mode: one active read tap chases the write head at the shift ratio; each splice jump is refined by a cross-correlation search so the new read point is waveform-aligned, then old and new taps are crossfaded (raised-cosine, amplitude-complementary). Replaces the prior dual-tap OLA whose fixed half-window tap offset caused anti-phase cancellation on many source frequencies. No third-party dependencies; RT-discipline: no allocation in `process()`. - `bank_sync` — generation change-detection + assignment-request consume: owns the yes/no decision logic so the rules are provable without a host. The processor shell owns cadence and side effects. - `bridge_marshal` — pure marshalling helper for the REAPER VST-host bridge read: interprets the `GetProjExtState` int return against its filled buffer. - `editor_geometry` — VST3 editor layout: defines the shared `Rect` type + `contains()` hit-test; provides `EditorLayout` and `layoutEditor(w,h)`. @@ -116,10 +116,11 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde - `knob_deck` — pure knob-deck layout + hit-test (FB1): group-box / caption-row / compact-toggle / knob-cell geometry, deterministic whole-group wrap, `DeckLayout` / `DeckHit`. Mirror of `action_bar`/`param_slider`; no LICE or REAPER types. - `curve_popup` — pure curve-popup geometry + dismissal test (FB1): centered sheet over the Sample face — width/height clamps, title row, Close button rect, curve-box rect, outside-sheet dismissal test. Mirror of `overflow_menu`; no LICE or REAPER types. - `master_gain` — pure dB↔linear taper math (FB1): normalized [0,1] ↔ dB ↔ linear for the post-mixer master gain control (−∞…+24 dB, norm 0 = true silence, unity ≈ 0.714). Shared by the editor knob and the processor multiply so the needle, persisted value, and audio multiply cannot drift. +- `reasampler_uid.h` — SDK-free header owning the FOREVER-FROZEN VST3 class-UID integer macros (stable + beta pairs, `REASAMPLER_PROC_UID_*` / `REASAMPLER_PROC_UID_BETA_*`) and the `REASAMPLER_ACTIVE_UID_*` channel-selector macros. Split out of `reasampler_vst.h` so the pure extension side (`instrument_drop`) can derive the `.vstpreset` class-ID hex string without pulling in the VST3 SDK. Both `reasampler_vst.h` (runtime `FUID`) and `instrument_drop` (preset hex string) source from this single header — the binary identity and the preset-file identity cannot diverge. **VST3 instrument (`src/vst/`) — shells:** - `reaper_bridge` — READ-ONLY bank consumer: receives bank snapshots from the extension and exposes them as a read-only view. **Never writes to the extension's bank** — this is a load-bearing invariant; no mutation path exists in this module. -- `reasampler_processor` — VST3 `SingleComponentEffect` shell: declares event-input bus + stereo output, marshals MIDI note-on/off into the VoiceEngine, renders audio; owns off-audio-thread `reloadFromBank` + atomic pointer swap so `process()` does no allocation, no file I/O, no bridge calls. Sums the `PreviewCard` alongside the engine + drain in `process()`; `retireIdleDrain()` retires fully-idle drain snapshots on the UI-timer cadence. Voice-param edits (`setVoiceCount`/`setVoiceMode`/`setMonoTrigger`) rebuild the engine from the already-decoded keymap via the drain-slot swap — no bank re-read, no WAV re-decode, no audible cut to ringing tails. **FB1:** applies the post-mixer `masterGainLinear` (from `ComponentState` v8) as a per-sample ramp over the summed output — no zipper noise. +- `reasampler_processor` — VST3 `SingleComponentEffect` shell: declares event-input bus + **permanently stereo** output (GA fix: dynamic mono↔stereo bus renegotiation deleted; `ChannelMode` is now decode-only), marshals MIDI note-on/off into the VoiceEngine, renders audio; owns off-audio-thread `reloadFromBank` + atomic pointer swap so `process()` does no allocation, no file I/O, no bridge calls. Sums the `PreviewCard` alongside the engine + drain in `process()`; `retireIdleDrain()` retires fully-idle drain snapshots on the UI-timer cadence. Voice-param edits (`setVoiceCount`/`setVoiceMode`/`setMonoTrigger`) rebuild the engine from the already-decoded keymap via the drain-slot swap — no bank re-read, no WAV re-decode, no audible cut to ringing tails. **FB1:** applies the post-mixer `masterGainLinear` (from `ComponentState` v8) as a per-sample ramp over the summed output — no zipper noise. **GA v9:** `channelModeExplicit_` flag persisted; `channelModeFor()` auto-defaults the mode from the loaded capture's channel count when the flag is not set. - `reasampler_editor` — VST3 `IPlugView` LICE editor shell: hosts a LICE-drawn child window; default face is the capture browser, then single-capture setup, with opt-in zones panel. Drop-onto-editor ingest is NOT shipped (deferred). - `reasampler_embed` — implements `IReaperUIEmbedInterface` so the instrument draws inline in the TCP/MCP without a plugin-owned HWND; delegates layout/hit-test to `embed_strip`. - `vst_entry` — VST3 entry point: `GetPluginFactory` export, class registration, channel-forked class UIDs. diff --git a/COMPLETED.md b/COMPLETED.md index e6b4b80..7ecedcc 100644 --- a/COMPLETED.md +++ b/COMPLETED.md @@ -2256,16 +2256,18 @@ is dragged from the `bank_panel`, a track's TCP **FX button** lights as a drop z and dropping there instantiates a **ReaSampler 9000** on that track with the dragged capture **already loaded and selected** for playback. A third `DragGesture` — `InstrumentDrop` — added to the `drag_out` pure module. Mechanism: `TrackFX_AddByName` + -VST3 component-state injection via `TrackFX_SetNamedConfigParm(..., "vst_chunk", blob)`. -The shared `instrument_drop` pure module constructs the blob via the instrument's own -`sample_map::serializeComponentState`/`deserializeComponentState` — one serializer called -from both artifacts, so the blob format cannot drift. CONTEXT-ARCHIVE.md §Phase S (drop-and-load). +VST3 component-state injection via a Steinberg-format `.vstpreset` file built by +`instrument_drop` (applied via `TrackFX_SetPreset`). The `instrument_drop` pure module +constructs the `.vstpreset` image from the instrument's own +`sample_map::serializeComponentState` — one serializer called from both artifacts, so the +byte format cannot drift. CONTEXT-ARCHIVE.md §Phase S (drop-and-load). (`vst_chunk` write +was the original planned mechanism but is silently unappliable for VST3 — replaced by +`.vstpreset` + `TrackFX_SetPreset`, GA DAW-fix pass 2026-07-28.) **Verify (in DAW):** dragging a single capture from the dock over a track's FX button highlights it; dropping instantiates ReaSampler 9000 on that track with the dragged capture loaded, selected, and MIDI-playable immediately; OS drag-out to Explorer still works unchanged; internal bank-to-bank drag still works unchanged; no media item ever inserted -into the arrange. DAW-verify: whether REAPER's `vst_chunk` write-parm expects the plugin's -raw IComponent-state bytes or wraps them in a REAPER container header. +into the arrange. **VERIFIED (GA DAW-fix pass 2026-07-28).** **Depends on:** M11 (`drag_out` gesture machinery), Phase S S4, the load-capture seam (VST3 component-state injection — the shared blob contract). @@ -2290,18 +2292,21 @@ raw IComponent-state bytes or wraps them in a REAPER container header. - [x] **ReaSampler 9000 load-capture seam:** the instrument's existing `setState`/`getState` already round-trip the full `ComponentState` via `sample_map::serializeComponentState`/`deserializeComponentState` (S10). The extension - REUSES that exact serializer through the new pure `instrument_drop` module - (`buildInstrumentDropChunk` → `serializeComponentState` → base64). The shared-writer + REUSES that exact serializer through the new pure `instrument_drop` module (builds a + Steinberg-format `.vstpreset` image: 'VST3' header + 'Comp' chunk = serialized component + state, addressed to `vstClassIdHex()`; applied via `TrackFX_SetPreset`). The shared-writer requirement is met STRUCTURALLY — the blob format cannot drift. Pure round-trip test decodes back through the instrument's own reader and asserts the capture is selected. **`instrument_drop` lives at `src/instrument_drop.{h,cpp}`** (extension-side pure module); - CTest target `instrument_drop_tests`. + CTest target `instrument_drop_tests`. (`TrackFX_SetNamedConfigParm` "vst_chunk" was the + original mechanism but is silently unappliable for VST3 — REAPER's wrapper cannot apply + unframed raw component-state bytes; replaced by the `.vstpreset` path, S-GA-DropFX.) - [x] Tests: gesture disambiguation (inside-panel / over-REAPER-UI / left-REAPER) across single- and multi-capture payloads; M11 OS drag-out and internal bank-to-bank drag both unchanged. `test_drag_out.cpp` adds the InstrumentDrop cases with M11 cases retained as - the non-regression guard; `test_instrument_drop.cpp` is the blob round-trip + base64 codec - coverage. FX hit resolution is REAPER-API-bound (`GetThingFromPoint`) — DAW-verified in the - shell, not pure-tested (noted honestly). + the non-regression guard; `test_instrument_drop.cpp` covers the `.vstpreset` image + round-trip (structure, class-ID, chunk offset/size). FX hit resolution is REAPER-API-bound + (`GetThingFromPoint`) — DAW-verified in the shell, not pure-tested (noted honestly). **Notes/decisions:** - `resetDragState()` consolidation in `bank_panel` (prior drift cleaned up this wave). @@ -2773,3 +2778,68 @@ Zone-authoring affordances intact; no regression on the Sample face. - **Phase S editor Wave B complete.** S-VIEW-11 (knob deck + curve popup, FB1), S-VIEW-12 (full-width hero + master gain, FB1), and S-VIEW-13 (Zone-panel parity, FB2) are all landed. No open r11 or S-VIEW Wave B items remain in PLAN.md. + +--- + +## GA post-launch DAW-fix pass (2026-07-28) + +> **Merged to dev 2026-07-28. Integrated suite 55/55 green.** Six fixes found during +> GA DAW testing — not tracked as PLAN points. PLAN.md has no corresponding entries to move. + +**Preserve pitch engine (SOLA rewrite):** `pitch_shift` rewritten from a dual-tap OLA (taps hard-locked +half a window apart — fixed relative phase causes anti-phase cancellation on many source frequencies, +producing spectral garbage on repitched notes) to **correlation-aligned SOLA splices**: one active read +tap chases the write head at the shift ratio; each splice jump is refined by a cross-correlation search +so the new read point is waveform-aligned, then old and new taps are crossfaded with a raised-cosine, +amplitude-complementary fade. Clean pitch shift past +24 st; a repitched pure sine stays a single tone. + +**Voice takeover declick:** `Voice::start` gained a `takeoverDeclick` parameter (gated on the envelope +complement, applied when the flag is true) to remove the click at mono retrig/fallback and poly at-cap +steal boundaries. The "steal-all" symptom observed in DAW was diagnosed as no-loop sample exhaustion +under Preserve (voices played to silence, not stolen); the engine steals exactly one voice per note-on +as designed. + +**Stereo bus pin + channel-mode auto-default:** The output bus is now **permanently stereo** — the +dynamic mono↔stereo bus renegotiation that hard-panned dual-mono is deleted. `ChannelMode` is +decode-only (controls downmix vs. dual-mono on WAV decode). Channel mode **auto-defaults from the +loaded capture's channel count** via `channelModeFor()` (pure helper in `sample_map`) when the user +has not explicitly toggled it; explicit toggle latches the preference. `ComponentState` bumped **v8→v9**: +`channelModeExplicit` bool added; pre-v9 blobs treat the stored mode byte as an explicit preference +(no auto-override). + +**Drop-to-FX injection fix:** Injection switched from `TrackFX_SetNamedConfigParm(..., "vst_chunk", +)` (silently unappliable for VST3 — REAPER's wrapper cannot accept unframed component-state +bytes) to a **Steinberg-format `.vstpreset` file** whose 'Comp' chunk is the serialized component +state, applied via `TrackFX_SetPreset`. The `instrument_drop` pure module builds the `.vstpreset` +image; `instrument_drop_win` writes a transient temp file and calls `TrackFX_SetPreset`. FX hotspot +now prefix-matches `tcp.fx*`/`mcp.fx*`/`fx_*` (embed strip tokens excluded). `reasampler_uid.h` +split out of `reasampler_vst.h` as an SDK-free header so `instrument_drop` can derive the class-ID +hex string without the VST3 SDK. + +**Drag-out arm fix:** `SetCapture` moved to drag-arm (the `dragArmed` branch of `handleClick` in +`bank_panel`) so a first straight-out drag — pointer leaving the client rect before a second click +— correctly arms and fires the OS drag-out. Previously `SetCapture` was called only after the drag +threshold was crossed inside the panel, so a fast straight-out drag on the first attempt received no +`WM_MOUSEMOVE` messages outside the client rect and the gesture never transitioned to the OS drag. + +**Panel mode-toggle repaint:** The Design/Arrange mode-toggle actions (`doToggleMode` / +`doActivateMode` in `actions.cpp`) now call `bankPanelInvalidate()` after applying the mode switch. +Previously the panel footer `[Arrange|Design]` toggle only repainted on a button click inside the +panel, not when the mode was changed via the registered action (e.g. from the Actions list or a +keybinding). + +- [x] `pitch_shift`: dual-tap OLA replaced by correlation-aligned SOLA; CTest `pitch_shift_tests` + updated to assert single-tone output on repitched sine. +- [x] `sampler_core` `Voice::start`: `takeoverDeclick` parameter added; mono retrig/fallback + poly + at-cap steal pass `true`; `PreviewCard` and standard voices pass the flag correctly. +- [x] `reasampler_processor`: output bus permanently stereo; `channelModeExplicit_` member added; + `channelModeFor()` called on `reloadFromBank` when not explicit; `ComponentState` v8→v9 + (`channelModeExplicit` byte); pre-v9 lift treats stored mode as explicit. +- [x] `instrument_drop`: `.vstpreset` image builder replacing `vst_chunk` base64 path; `vstClassIdHex()` + sourced from `reasampler_uid.h`; `instrument_drop_tests` covers preset image structure round-trip. +- [x] `reasampler_uid.h`: new SDK-free header in `src/vst/` owning the frozen class-UID macros; + `reasampler_vst.h` and `instrument_drop` both source from it. +- [x] `instrument_drop_win`: FX hotspot prefix-set extended (`tcp.fx*`/`mcp.fx*`/`fx_*`); transient + `.vstpreset` write + `TrackFX_SetPreset` call replaces `TrackFX_SetNamedConfigParm` "vst_chunk". +- [x] `bank_panel`: `SetCapture` moved to drag-arm branch; `bankPanelInvalidate()` called from + `actions.cpp` after mode-toggle actions. diff --git a/CONTEXT.md b/CONTEXT.md index a611549..511ab34 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1813,32 +1813,19 @@ below. **Symptom (Daniel):** dropping a capture onto a track's FX chain does not instantiate + init ReaSampler 9000 — the audio-to-arrange drop works, but the "instrument init never fires." -**SDK sweep (done this pass — no false ceiling asserted):** the S17 drop-to-FX machinery is -**present and SDK-correct**. `src/instrument_drop_win.cpp` calls `TrackFX_AddByName(track, -"VST3:" + vstPluginName(), /*recFX*/false, /*instantiate*/-1)` then injects component state via -`TrackFX_SetNamedConfigParm(track, fx, "vst_chunk", )`; both signatures + semantics are -**verified present** in `reaper_plugin_functions.h` (the `VST3:` prefix, negative-instantiate = -always-new, and the `vst_chunk` base64 write-parm are all documented). The gesture is wired -(`src/bank_panel.cpp`: `decideGesture` → `InstrumentDrop` → on release `buildInstrumentDropChunk` -→ `performInstrumentDrop`), FX-hotspot detection uses `GetThingFromPoint` (`fx_*` prefix), and the -factory registers `kInstrumentSynthSampler` correctly. **So the platform CAN do this — the -capability is not in question; the wiring is present.** This is therefore a **runtime diagnosis -task, not a design decision or a platform-ceiling problem.** The likely fault domains for -staff-engineer to investigate (in order): -1. **FX-name resolution:** whether REAPER's scanned name for the plugin exactly matches `"VST3:" + - vstPluginName()` (a display-name/scan-name mismatch would make `TrackFX_AddByName` return −1). -2. **The gesture reaching `performInstrumentDrop`:** whether `decideGesture` actually resolves - `InstrumentDrop` over an FX button (vs. falling through to `OsDrag`, which produces exactly the - observed "drops as audio into arrange" symptom) — check the `overFxHotspot`/`GetThingFromPoint` - path and the single-capture arming guard. -3. **The `vst_chunk` write / blob format:** whether `TrackFX_SetNamedConfigParm` returns true and - the injected blob matches what `setState` accepts (a format drift would create-then-play-nothing - rather than fail to instantiate). - -**Routing:** flagged for **staff-engineer** as a bug diagnosis. No product fork; no redesign -gates on it, but it is a **functional prerequisite** for the drop-to-FX gesture to feel finished. +**RESOLVED (GA post-launch DAW-fix pass, 2026-07-28).** The root cause was the injection +mechanism, not the gesture wiring: `TrackFX_SetNamedConfigParm(..., "vst_chunk", )` +is silently unappliable for VST3 — REAPER's VST3 wrapper cannot apply unframed raw component-state +bytes there (the write parm returns true but the instance stays at defaults). The fix: +`instrument_drop` builds a Steinberg-format `.vstpreset` file image (header + 'Comp' chunk = +serialized component state, class ID from `reasampler_uid.h`); `instrument_drop_win` writes a +transient temp file and applies it via `TrackFX_SetPreset(track, fx, path)`, which the SDK +documents as accepting full `.vstpreset` paths for VST3 plug-ins. FX hotspot prefix-set extended +to `tcp.fx*`/`mcp.fx*`/`fx_*` (embed strip tokens excluded). `reasampler_uid.h` split out of +`reasampler_vst.h` as an SDK-free header so `instrument_drop` (pure module) can derive the +class-ID hex string without the VST3 SDK. **Verify in DAW:** drag a capture onto a track's FX button → a ReaSampler 9000 instance appears on -that track already playing that capture (one Ctrl-Z removes it). +that track already playing that capture (one Ctrl-Z removes it). **VERIFIED.** ### Engineering prerequisite 2 — default window size for 1080p (routed to implementation) @@ -1919,9 +1906,9 @@ preview velocity **persists** via envelope-v6 `ComponentState`; the envelope ove - **Editor size mechanism** — `getSize`/`setRect`/`checkSizeConstraint`/`onSize`/`canResize` **verified present** in `vendor/vst3sdk/public.sdk/source/common/pluginview.h`; confirm the exact min-size enforcement point (`checkSizeConstraint`) behaves under REAPER's host at build. -- **Drop-to-FX** — `TrackFX_AddByName` / `TrackFX_SetNamedConfigParm` / `GetThingFromPoint` all - **verified present** in `reaper_plugin_functions.h`; the remaining work is **runtime diagnosis** - (name match, gesture routing, blob format), not an SDK question. +- **Drop-to-FX** — `TrackFX_AddByName` / `TrackFX_SetPreset` / `GetThingFromPoint` all + **verified present** in `reaper_plugin_functions.h`; injection via `TrackFX_SetPreset` + + `.vstpreset` image (not `vst_chunk`) — **RESOLVED, GA DAW-fix pass 2026-07-28.** - **Preview note through the voice engine off-thread** — confirm the existing `commitAndReload` / off-thread reload idiom is the right seam to fire a one-shot preview note without touching `process` on the UI thread; no torn state on the atomic voice-engine pointer.