docs: record GA DAW-fix pass (Preserve SOLA, voice declick, stereo bus-pin+v9, .vstpreset drop-fx, drag-out, panel toggle); correct stale vst_chunk mechanism

This commit is contained in:
2026-07-28 07:12:07 -04:00
parent 3e3b79d78a
commit e99bdcf264
3 changed files with 101 additions and 43 deletions
+4 -3
View File
@@ -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 (132, 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 (132, 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 (132, 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.
+82 -12
View File
@@ -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",
<base64>)` (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.
+15 -28
View File
@@ -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", <base64>)`; 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", <base64>)`
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.