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
|
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*.
|
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**
|
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
|
(the `phase-s` artifact), not the extension.
|
||||||
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`**).
|
|
||||||
|
|
||||||
Lean: **(A)** keeps the artifacts loosely coupled through the one ext-state seam they
|
**Mechanism (SETTLED — (B) VST3 component-state injection).** Right after
|
||||||
already share and avoids the extension hard-coding the instrument's state format — but
|
`TrackFX_AddByName` returns the new FX index, the extension writes the instance's component
|
||||||
it inherits the claim/clear race. Daniel to decide (open question below).
|
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
|
**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
|
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.
|
predicate; the M11 `drag_out` unit tests must not regress.
|
||||||
|
|
||||||
**Open questions (Daniel / Phase S team to decide).**
|
**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
|
- **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;
|
`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.
|
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
|
- **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
|
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).
|
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).**
|
**Must-verify before build (drop-and-load).**
|
||||||
- `TrackFX_AddByName` — **verified present** (`reaper_plugin_functions.h`): signature and
|
- `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
|
`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"
|
TCP as a whole is an open verification that also decides the "hotspot vs. whole TCP"
|
||||||
question.
|
question.
|
||||||
- **Instance state injection (only if seam mechanism (B) is chosen)** — whether the
|
- **Instance state injection (seam mechanism (B) — SETTLED, load-bearing prerequisite)** —
|
||||||
extension can set a just-added FX's state via the REAPER API
|
**verified present** in `reaper_plugin_functions.h`: `bool
|
||||||
(`TrackFX_SetNamedConfigParm` or similar) — **not yet confirmed against
|
TrackFX_SetNamedConfigParm(MediaTrack* track, int fx, const char* parmname, const char*
|
||||||
`reaper_plugin_functions.h`.** Moot if (A) is chosen.
|
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
|
## Non-goals / guardrails
|
||||||
|
|
||||||
|
|||||||
@@ -956,9 +956,25 @@ The current Phase S spec gives the instrument a *live-state read* seam (it reads
|
|||||||
index + mapping from `"reasampler"` ext-state via the bridge) but **no entry point for an
|
index + mapping from `"reasampler"` ext-state via the bridge) but **no entry point for an
|
||||||
external actor to say "instantiate playing *this specific* capture."** This wave is the
|
external actor to say "instantiate playing *this specific* capture."** This wave is the
|
||||||
reason to add that seam. It is a Phase S dependency, not extension-side, and must land in
|
reason to add that seam. It is a Phase S dependency, not extension-side, and must land in
|
||||||
the instrument before drop-and-load's drop half can work end-to-end. See CONTEXT.md
|
the instrument before drop-and-load's drop half can work end-to-end. **Mechanism (SETTLED
|
||||||
§Phase S for the two candidate seam mechanisms (fresh-instance ext-state handshake vs.
|
— (B) VST3 component-state injection):** after `TrackFX_AddByName`, the extension writes the
|
||||||
VST3 `setState` preset injection) and the open question on which is chosen.
|
new instance's component state directly via `TrackFX_SetNamedConfigParm(track, fx,
|
||||||
|
"vst_chunk", <blob>)` — **verified present** in `reaper_plugin_functions.h` (`bool
|
||||||
|
TrackFX_SetNamedConfigParm(MediaTrack*, int fx, const char* parmname, const char* value)`;
|
||||||
|
the header documents the `vst_chunk`/`vst_chunk_program` parms as the base64-encoded
|
||||||
|
VST-specific chunk write-path). Deterministic, no shared-state race, no cross-process
|
||||||
|
handshake. **Load-bearing caveat:** `vst_chunk` is the plugin's **own** base64-encoded
|
||||||
|
serialized chunk (its `getChunk`/`setChunk` FXP/FXB-style blob), **not** a raw VST3
|
||||||
|
`IComponent::setState` stream REAPER re-marshals — so the extension must construct exactly
|
||||||
|
ReaSampler 9000's own state-blob bytes with the target capture pre-selected. That makes the
|
||||||
|
**component-state blob format a shared cross-artifact contract** — still being defined in
|
||||||
|
Phase S — and a coordination dependency between the extension and the instrument: both must
|
||||||
|
agree on the exact byte layout the instrument's `setChunk` accepts. Rejected alternative —
|
||||||
|
**(A) fresh-instance ext-state handshake** (extension writes a "pending load" hint into
|
||||||
|
`"reasampler"` ext-state, the fresh instance claims + clears it on init): loosely coupled
|
||||||
|
through the existing bridge, but loses on the claim/clear race — "which instance claims
|
||||||
|
which hint" needs a stable key and a cross-process handshake to get right. See CONTEXT.md
|
||||||
|
§Phase S (drop-and-load) for the full seam decision.
|
||||||
**Coexistence with the M11 OS drag-out (disambiguation, load-bearing):** the two drag
|
**Coexistence with the M11 OS drag-out (disambiguation, load-bearing):** the two drag
|
||||||
modes are disambiguated by **where the pointer goes**, not by a mode toggle. Inside the
|
modes are disambiguated by **where the pointer goes**, not by a mode toggle. Inside the
|
||||||
panel client rect → `Internal` (unchanged). Left the panel but still over REAPER's own
|
panel client rect → `Internal` (unchanged). Left the panel but still over REAPER's own
|
||||||
@@ -976,7 +992,9 @@ still works unchanged; no media item is ever inserted into the arrange; the inst
|
|||||||
holds no private copy (it reads the one authoritative bank).
|
holds no private copy (it reads the one authoritative bank).
|
||||||
**Depends on:** M11 (`drag_out` gesture machinery — the mode it extends); **Phase S S4**
|
**Depends on:** M11 (`drag_out` gesture machinery — the mode it extends); **Phase S S4**
|
||||||
(a loadable, playing ReaSampler 9000 instance must exist) **AND the new load-capture seam
|
(a loadable, playing ReaSampler 9000 instance must exist) **AND the new load-capture seam
|
||||||
added inside ReaSampler 9000**. Composes with — but is distinct from — **S8** (ingest
|
added inside ReaSampler 9000 via (B) component-state injection — the shared component-state
|
||||||
|
blob contract must be defined so the extension can construct it and the instrument's
|
||||||
|
`setChunk` accept it**. Composes with — but is distinct from — **S8** (ingest
|
||||||
through the bank: capture/import/drop *into* the bank) and **S13** (drop-to-load *inside*
|
through the bank: capture/import/drop *into* the bank) and **S13** (drop-to-load *inside*
|
||||||
the editor). S17 is the third integration gesture: drop *onto a track's FX button* to
|
the editor). S17 is the third integration gesture: drop *onto a track's FX button* to
|
||||||
instantiate a player. Gated on the rest of Phase S; the instrument seam is a Phase S
|
instantiate a player. Gated on the rest of Phase S; the instrument seam is a Phase S
|
||||||
@@ -998,21 +1016,17 @@ artifact, not extension-only.
|
|||||||
capture. Batched into one REAPER undo point (`Undo_BeginBlock2`/`EndBlock2`) so the
|
capture. Batched into one REAPER undo point (`Undo_BeginBlock2`/`EndBlock2`) so the
|
||||||
whole gesture is one Ctrl-Z (mirrors the bank-verb undo discipline).
|
whole gesture is one Ctrl-Z (mirrors the bank-verb undo discipline).
|
||||||
- [ ] **ReaSampler 9000 (Phase S artifact):** add the **load-capture seam** — the entry
|
- [ ] **ReaSampler 9000 (Phase S artifact):** add the **load-capture seam** — the entry
|
||||||
point that lets the just-added instance be told which capture to play (mechanism chosen
|
point that lets the just-added instance be told which capture to play via **(B)
|
||||||
per the CONTEXT.md open question). This is the cross-artifact half; it lands in the
|
component-state injection**: the instrument's `getChunk`/`setChunk` must round-trip a
|
||||||
instrument, not the extension.
|
component-state blob whose byte layout is the **shared cross-artifact contract** (defined
|
||||||
|
in Phase S) that the extension constructs with the target capture pre-selected and writes
|
||||||
|
via `TrackFX_SetNamedConfigParm(..., "vst_chunk", ...)`. This is the cross-artifact half;
|
||||||
|
it lands in the instrument, not the extension. **Coordination dependency:** the blob
|
||||||
|
format must be agreed between extension and instrument before either half is final.
|
||||||
- [ ] Tests: gesture disambiguation (inside-panel / over-REAPER-UI / left-REAPER) across
|
- [ ] Tests: gesture disambiguation (inside-panel / over-REAPER-UI / left-REAPER) across
|
||||||
single- and multi-capture payloads; FX-button hit resolution (pure geometry where it
|
single- and multi-capture payloads; FX-button hit resolution (pure geometry where it
|
||||||
can be factored out); M11 OS drag-out and internal bank-to-bank drag both unchanged.
|
can be factored out); M11 OS drag-out and internal bank-to-bank drag both unchanged.
|
||||||
|
|
||||||
> **Numbering reconciliation (resolved on merge to dev).** This wave was authored on
|
|
||||||
> **dev** as a provisional **S7** while dev's Phase S ran only S1–S6. On merge with the
|
|
||||||
> `phase-s` worktree, the worktree's authoritative numbering (S7 stereo, S8 ingest, S9
|
|
||||||
> change-detection, S10–S16) took the lower labels, so drop-and-load was renumbered to
|
|
||||||
> **S17** — the next free label past the worktree's Phase S set. It 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).
|
|
||||||
|
|
||||||
## Phase S — held and optional-forever (noted, not specified)
|
## Phase S — held and optional-forever (noted, not specified)
|
||||||
- **Tier 2 — "expressive" (HELD).** Velocity layers, round-robin (anti-machine-gun),
|
- **Tier 2 — "expressive" (HELD).** Velocity layers, round-robin (anti-machine-gun),
|
||||||
full ADSR, per-sample tuning/gain trim, sustain loops. The next depth increment once
|
full ADSR, per-sample tuning/gain trim, sustain loops. The next depth increment once
|
||||||
|
|||||||
Reference in New Issue
Block a user