docs(product): S17 load-capture seam — settle on (B) component-state injection
Fold the decision into the existing phase-s S17 spec: (B) via TrackFX_SetNamedConfigParm vst_chunk; (A) ext-state handshake rejected. Flag the shared component-state blob format as an extension<->instrument coordination dependency.
This commit is contained in:
+43
-32
@@ -1913,27 +1913,40 @@ reads bank index + mapping from `"reasampler"` ext-state via the bridge — §Th
|
||||
but **no entry point for an external actor to say "this fresh instance should play *this
|
||||
specific* capture."** Reading the bank is not the same as being *pointed at one sample*.
|
||||
This wave is the reason to add that seam, and the seam lands **inside the instrument**
|
||||
(the `phase-s` artifact), not the extension. Two candidate mechanisms — **the choice is a
|
||||
Phase S open question:**
|
||||
- **(A) Fresh-instance ext-state handshake.** The extension writes a small "pending load"
|
||||
hint into `"reasampler"` ext-state keyed to the target track/FX (e.g. the capture id +
|
||||
a target GUID); a freshly-instantiated ReaSampler 9000 reads it on init via the same
|
||||
bridge it already uses, claims + clears the hint, and self-selects that capture. **Pro:**
|
||||
reuses the existing bridge; no new VST3 surface. **Con:** a cross-process handshake with
|
||||
a claim/clear race to get right; "which instance claims which hint" needs a stable key.
|
||||
- **(B) VST3 `setState` / preset injection.** The extension builds the instance's
|
||||
component state (the same blob `getState`/`setState` round-trips) with the capture
|
||||
pre-selected and injects it right after `TrackFX_AddByName`. **Pro:** deterministic, no
|
||||
shared-state race, uses the instrument's own persistence format. **Con:** the extension
|
||||
must know and construct the instrument's state blob format — a tighter cross-artifact
|
||||
coupling to a format that is itself still being built in Phase S; verify whether the
|
||||
extension can set an added FX's state through the REAPER API (candidate:
|
||||
`TrackFX_SetNamedConfigParm` / a state-set path — **not yet confirmed against
|
||||
`reaper_plugin_functions.h`**).
|
||||
(the `phase-s` artifact), not the extension.
|
||||
|
||||
Lean: **(A)** keeps the artifacts loosely coupled through the one ext-state seam they
|
||||
already share and avoids the extension hard-coding the instrument's state format — but
|
||||
it inherits the claim/clear race. Daniel to decide (open question below).
|
||||
**Mechanism (SETTLED — (B) VST3 component-state injection).** Right after
|
||||
`TrackFX_AddByName` returns the new FX index, the extension writes the instance's component
|
||||
state directly — the same blob the instrument's `getChunk`/`setChunk` round-trips — with
|
||||
the target capture pre-selected. Deterministic, no shared-state race, no cross-process
|
||||
handshake; it uses the instrument's own persistence format. The state-set path is
|
||||
`TrackFX_SetNamedConfigParm` — **verified present** in `reaper_plugin_functions.h`: `bool
|
||||
TrackFX_SetNamedConfigParm(MediaTrack* track, int fx, const char* parmname, const char*
|
||||
value)`, and the header documents the write-parms `vst_chunk` / `vst_chunk_program` as the
|
||||
base64-encoded VST-specific chunk. So the injection call is
|
||||
`TrackFX_SetNamedConfigParm(track, fx, "vst_chunk", <base64 blob>)`.
|
||||
|
||||
**Load-bearing caveat — `vst_chunk` is the plugin's own serialized chunk.** `vst_chunk` is
|
||||
ReaSampler 9000's **own** base64-encoded serialized state (its `getChunk`/`setChunk`
|
||||
FXP/FXB-style blob), **not** a raw VST3 `IComponent::setState` stream that REAPER
|
||||
re-marshals into the plugin. The extension therefore has to construct **exactly the
|
||||
instrument's own state-blob bytes** with the capture pre-selected — REAPER does not
|
||||
translate a neutral state representation on its behalf. This makes the **component-state
|
||||
blob format a shared cross-artifact contract** — one that is **still being defined in Phase
|
||||
S** — and a **coordination dependency between the extension and the instrument:** both must
|
||||
agree on the exact byte layout that ReaSampler 9000's `setChunk` accepts before either half
|
||||
is final. The load-capture seam and the component-state persistence work (§Where the toggle
|
||||
lives / component-state version bumps) share this one blob format.
|
||||
|
||||
**Rejected alternative — (A) fresh-instance ext-state handshake.** The extension writes a
|
||||
small "pending load" hint into `"reasampler"` ext-state keyed to the target track/FX (a
|
||||
capture id + a target GUID); a freshly-instantiated ReaSampler 9000 reads it on init via
|
||||
the bridge it already uses, claims + clears the hint, and self-selects that capture. It
|
||||
would keep the artifacts loosely coupled through the one ext-state seam they already share
|
||||
and avoid the extension hard-coding the instrument's state format — but it **loses on the
|
||||
claim/clear race:** "which instance claims which hint" needs a stable key and a
|
||||
cross-process handshake to get right, and (B) sidesteps that entirely by writing the state
|
||||
directly and deterministically.
|
||||
|
||||
**Coexistence with the OS drag-out (disambiguation contract).** The two OS-vs-internal
|
||||
modes are disambiguated **by pointer location, not a mode toggle** — the user never picks
|
||||
@@ -1962,20 +1975,12 @@ a multi-capture drag over the FX button is either rejected or loads the first).
|
||||
predicate; the M11 `drag_out` unit tests must not regress.
|
||||
|
||||
**Open questions (Daniel / Phase S team to decide).**
|
||||
- **Load-capture seam mechanism (A vs. B above).** The single load-bearing Phase S design
|
||||
choice this wave forces. Lean is (A) for loose coupling; needs Daniel's call.
|
||||
- **Multi-capture drag over an FX button** — reject (only single-capture drags arm
|
||||
`InstrumentDrop`), or load the first / a keymap of all? Tier-0 leans reject-or-first;
|
||||
a multi-capture keymap load is a Tier-1 stretch.
|
||||
- **FX-button hotspot vs. whole TCP.** Does the drop zone have to be the FX button
|
||||
specifically, or is dropping anywhere on the target track's TCP enough (simpler hit
|
||||
resolution, arguably clearer target)? Depends on what the SDK exposes (see must-verify).
|
||||
- **Numbering vs. the `phase-s` worktree (reconciled on merge to dev).** Authored on dev
|
||||
as a provisional S7; renumbered to **S17** on merge, since the worktree's authoritative
|
||||
Phase S set (S7 stereo, S8 ingest, S9 change-detection, S10–S16) took the lower labels.
|
||||
Drop-and-load stays a distinct integration gesture (drop onto a track's FX button) —
|
||||
a sibling of but not the same as S8 (ingest into the bank) and S13 (drop-to-load inside
|
||||
the editor). See PLAN.md §S17.
|
||||
|
||||
**Must-verify before build (drop-and-load).**
|
||||
- `TrackFX_AddByName` — **verified present** (`reaper_plugin_functions.h`): signature and
|
||||
@@ -1985,10 +1990,16 @@ a multi-capture drag over the FX button is either rejected or loads the first).
|
||||
`reaper_plugin_functions.h`); whether the FX button specifically is addressable vs. the
|
||||
TCP as a whole is an open verification that also decides the "hotspot vs. whole TCP"
|
||||
question.
|
||||
- **Instance state injection (only if seam mechanism (B) is chosen)** — whether the
|
||||
extension can set a just-added FX's state via the REAPER API
|
||||
(`TrackFX_SetNamedConfigParm` or similar) — **not yet confirmed against
|
||||
`reaper_plugin_functions.h`.** Moot if (A) is chosen.
|
||||
- **Instance state injection (seam mechanism (B) — SETTLED, load-bearing prerequisite)** —
|
||||
**verified present** in `reaper_plugin_functions.h`: `bool
|
||||
TrackFX_SetNamedConfigParm(MediaTrack* track, int fx, const char* parmname, const char*
|
||||
value)`, with the header documenting `vst_chunk` / `vst_chunk_program` as the
|
||||
base64-encoded VST-specific chunk write-parms. The injection call is
|
||||
`TrackFX_SetNamedConfigParm(track, fx, "vst_chunk", <base64 blob>)`. The remaining
|
||||
prerequisite is **not** the API but the **shared component-state blob format**: `vst_chunk`
|
||||
carries the instrument's *own* serialized chunk (its `setChunk` input), so the extension
|
||||
must construct exactly ReaSampler 9000's state bytes — the cross-artifact contract still
|
||||
being defined in Phase S. Blocks the drop half until the blob format is agreed.
|
||||
|
||||
## Non-goals / guardrails
|
||||
|
||||
|
||||
Reference in New Issue
Block a user