docs(product): spec S18 — VST3 channel isolation (beta ReaSampler 9000 pairs with beta extension only)
This commit is contained in:
+57
@@ -2001,6 +2001,63 @@ a multi-capture drag over the FX button is either rejected or loads the first).
|
||||
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.
|
||||
|
||||
## VST3 channel identity — the UID pair + the pairing surface (S18; extends Phase V V4)
|
||||
|
||||
**Decided (Daniel, 2026-07-26):** the beta/stable channel split Phase V V4 gave the
|
||||
*extension* extends to the **ReaSampler 9000 VST3 instrument** — a beta-built VST pairs with
|
||||
the beta extension only, a stable VST with stable only, both installable side-by-side in one
|
||||
REAPER. This is the instrument-side companion to V4 and mirrors its philosophy exactly:
|
||||
**one channel per binary; all channel identity derives from the ONE
|
||||
`REASAMPLER_CHANNEL_IS_BETA` bit via the pure `app_version` module — no scattered `#ifdef`s
|
||||
in the VST shell.**
|
||||
|
||||
**What is already isolated (structural, not added by S18).** The wire/data pairing is
|
||||
already done and needs no per-key work: `ext_keys.h`'s `kProjExtNamespace()` delegates to
|
||||
`app_version::extStateNamespace()`, so a beta-compiled VST's bridge reads `"reasampler_beta"`.
|
||||
Every wire key — `banks`, `assign_request`, S9's bank-generation key (in-flight), S17's
|
||||
component-state contract, and **any future key** — is a plain constant *under* that
|
||||
namespace, so channel data-isolation is **structural: no per-key opt-in, and a future key
|
||||
that forgets to isolate is impossible by construction** (it keys off the namespace accessor,
|
||||
not a raw literal). What S18 adds is only the missing *plugin identity* layer.
|
||||
|
||||
- **The UID-pair invariant (the permanent commitment).** The VST3 class UID is the plugin's
|
||||
identity — a saved REAPER project records it and rebinds a saved instance by it. Today
|
||||
`reasampler_vst.h` holds **one** forever-locked UID (`kReaSamplerProcessorUID`,
|
||||
`REASAMPLER_PROC_UID_1..4`, S-NAME-1). A beta VST with the **same** UID cannot coexist with
|
||||
stable in one install (same UID = identity collision / arbitrary rebind). So beta needs its
|
||||
**own** forever-stable UID: a second constant, minted once, locked exactly as the first.
|
||||
**Invariant: BOTH UIDs are frozen forever once shipped; the channel bit selects which is
|
||||
compiled into this binary** (one `DEF_CLASS2`, one class per binary — not both classes in
|
||||
one binary; that mirrors V4's fully-isolated-binary philosophy and keeps a beta build from
|
||||
ever presenting the stable identity). Saved-project isolation follows directly: a project
|
||||
saved with beta instances rebinds only to the beta VST; a stable-saved instance opened
|
||||
where only the beta extension has banks resolves the stable UID and shows a clean empty
|
||||
"pick a capture" state (S10 policy), not an error.
|
||||
- **Binary + display identity, channel-derived.** Mirror the extension's `OUTPUT_NAME` fork
|
||||
(`reaper_reasampler` / `reaper_reasampler_beta`): the VST3 module's on-disk name forks
|
||||
`reasampler_9000` / `reasampler_9000_beta`, its factory display name "ReaSampler 9000" /
|
||||
"ReaSampler 9000 beta", and its editor title band + S6 embed-strip label are channel-aware
|
||||
— **all sourced from `app_version` channel accessors (a VST-name accessor beside
|
||||
`binaryName()`/`dockTitle()`), never a literal in `reasampler_vst.h`/`vst_entry.cpp`.** The
|
||||
factory version string carries the `-beta` render where V4's `appVersion()` already does;
|
||||
vendor/url/email stay shared unless V4 qualified the equivalent (V4 kept the lane-name
|
||||
prefix shared — shared-where-V4-shares is the default).
|
||||
- **The complete pairing surface (the guarantee to state, not new code).** A channel's VST
|
||||
talks to that channel's extension **only**, because (1) plugin identity — UID + filename +
|
||||
display — is channel-forked (above), and (2) **all** wire keys live under the
|
||||
channel-derived `kProjExtNamespace()`. The two together make pairing complete and
|
||||
structural: identity keeps the *plugins* distinct; the namespace keeps the *data* distinct.
|
||||
No per-key or per-seam isolation work is ever needed — S8's assignment key, S9's generation
|
||||
key, and S17's blob-injection key all inherit it. **Verify all identity/factory wiring
|
||||
against the vendored Steinberg SDK** (`DEF_CLASS2` / `INLINE_UID` / `FUID` from
|
||||
`pluginfactory.h` + `funknown.h`); the pure `app_version` name accessors are CTest-tested.
|
||||
- **Fork S18-F1 (flagged — Daniel's call): mint the beta UID now vs. at first beta release.**
|
||||
Lean **mint now** — mirrors the stable UID (minted at the S1 spike, locked long before
|
||||
ship), removes a "remember to mint before shipping beta" landmine, zero cost for an
|
||||
unused-until-beta constant. The alternative (a locked-once placeholder replaced before the
|
||||
first beta VST ships) defers the commitment but adds a release-gate step. Flagged only
|
||||
because the UID is a forever commitment.
|
||||
|
||||
## Non-goals / guardrails
|
||||
|
||||
- **The instrument never captures and never inserts into the arrange.** Playback is a
|
||||
|
||||
@@ -1027,6 +1027,104 @@ artifact, not extension-only.
|
||||
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.
|
||||
|
||||
## S18 — VST3 channel isolation: a beta ReaSampler 9000 that pairs with the beta extension only
|
||||
**Goal:** Extend Phase V's beta/stable channel split (V4 — the *extension* is fully
|
||||
isolated per channel) to the **ReaSampler 9000 VST3 instrument**, so a beta-built VST is a
|
||||
distinct plugin that pairs only with the beta extension, and a stable VST pairs only with
|
||||
stable — installable side-by-side in one REAPER with no collision. **What already works
|
||||
(established from the tree, not re-derived): data pairing is done.** `ext_keys.h`'s
|
||||
namespace is channel-derived (`kProjExtNamespace()` → `app_version::extStateNamespace()`),
|
||||
so a beta-compiled VST already *reads* `"reasampler_beta"`; every wire key (`banks`,
|
||||
`assign_request`, S9's future generation key, any future key) is a plain constant *under*
|
||||
that namespace, so channel data-isolation is **structural, not per-key** — new keys inherit
|
||||
it automatically. **What is missing is the VST's *plugin identity*:** its class UID, binary
|
||||
filename, and display strings are single-valued today (same for both channels → a
|
||||
UID/filename collision if both are installed). This wave closes that. Mirrors V4's
|
||||
philosophy: **one channel per binary; all identity derives from the ONE
|
||||
`REASAMPLER_CHANNEL_IS_BETA` bit via `app_version`, no scattered `#ifdef`s.** CONTEXT.md
|
||||
§Phase S (VST3 channel identity — the UID-pair invariant + the pairing surface). Product
|
||||
framing: `docs/product/midi-playback.md` §Addendum — VST channel isolation.
|
||||
**Consistent with V4 (make the invariant explicit):** V4 already committed the extension to
|
||||
*two* forever-stable command-id families and *two* forever-stable ext-state namespaces. This
|
||||
wave commits the *instrument* to the parallel permanent cost: **two forever-stable VST3 class
|
||||
UIDs** — the existing stable UID (S-NAME-1, locked) and a second beta UID (minted once,
|
||||
locked forever the same way). Both are frozen forever; the channel bit selects which is
|
||||
*compiled into this binary*. **One class per binary, not both in one binary** — this mirrors
|
||||
V4's fully-isolated-binary philosophy (a channel build carries only its own identity), keeps
|
||||
the factory registration a single `DEF_CLASS2`, and means a beta-installed VST cannot present
|
||||
the stable identity.
|
||||
**Verify (in DAW):** the stable VST3 (`reasampler_9000.vst3`, existing UID) and a beta VST3
|
||||
(`reasampler_9000_beta.vst3`, the new UID) install side-by-side in one REAPER and both appear
|
||||
in the FX browser as distinct plugins ("ReaSampler 9000" and "ReaSampler 9000 beta"); a beta
|
||||
instance reads only the beta extension's banks (its browser shows the beta banks, never
|
||||
stable's) and a stable instance only stable's; a project saved with a **beta** instance
|
||||
reopens rebinding to the beta VST (not the stable one) and restores its state; a project
|
||||
saved with a **stable** instance opened where only the beta extension has banks shows a clean
|
||||
empty "pick a capture" state (S10 policy), **not** an error; the S-NAME-1 save-rename-reopen
|
||||
compat test extends to the beta UID (a beta instance rebinds by its own UID). **Nothing plays
|
||||
differently** — this is an identity/pairing wave, no engine change.
|
||||
**Depends on:** V4 (the `app_version` channel-identity single-source this extends), S1 (the
|
||||
VST3 factory + `reasampler_vst.h` identity constants + the CMake second target it edits).
|
||||
Consumes the S4 live-state seam only to *demonstrate* pairing; adds no new seam. **Disjoint
|
||||
from the in-flight waves:** touches `vst_entry.cpp`, `reasampler_vst.h`, and the CMake VST3
|
||||
block — S9 (ext_keys additions) and S15/S16 (processor/editor) are elsewhere. **ext_keys
|
||||
coordination note:** S9's new generation key is a plain constant under the channel-derived
|
||||
namespace, so it inherits this isolation with no S18 change; no coordination edit is needed —
|
||||
only the shared awareness that all wire keys must live under `kProjExtNamespace()` (already
|
||||
the rule). Dispatchable in parallel with, or immediately after, the in-flight waves.
|
||||
|
||||
- [ ] **Beta VST3 class UID (the permanent commitment).** Mint a second FOREVER-STABLE
|
||||
class UID in `reasampler_vst.h` alongside the existing `kReaSamplerProcessorUID`
|
||||
(`REASAMPLER_PROC_UID_1..4`) — a distinct forever-frozen constant (e.g.
|
||||
`REASAMPLER_PROC_UID_BETA_1..4` + `kReaSamplerProcessorUIDBeta`). The channel bit selects
|
||||
which UID the factory registers (`DEF_CLASS2`'s `INLINE_UID` + the runtime `FUID`) —
|
||||
compile-time, one class per binary. **Both UIDs are frozen forever once shipped** — the
|
||||
same lock the stable UID carries (a saved project records the UID and rebinds by it; a
|
||||
changed UID orphans every saved beta instance). Record the invariant in the header's
|
||||
UID-lock comment. **Fork S18-F1 (flagged — Daniel's call):** whether the beta UID is
|
||||
minted *now* (locked from this wave) or deferred to *first beta release* of the VST
|
||||
(leaving a compile-time placeholder that must be replaced-once-and-locked before any beta
|
||||
VST ships). Lean: **mint now** — mirrors how the stable UID was minted at the S1 spike
|
||||
(locked long before ship), removes a "remember to mint before shipping" landmine, and the
|
||||
cost of an unused-until-beta constant is zero. Flagged because it is a forever commitment.
|
||||
- [ ] **Channel-derived binary + display identity (no scattered `#ifdef`s — the V4
|
||||
invariant).** Route all VST identity through `app_version` accessors, mirroring how the
|
||||
extension's `OUTPUT_NAME` already derives from `REASAMPLER_OUTPUT_NAME`. (a) **Binary
|
||||
name:** the CMake VST3 target's `OUTPUT_NAME` forks by channel — `reasampler_9000`
|
||||
(stable) / `reasampler_9000_beta` (beta) — driven by the same `REASAMPLER_CHANNEL` config
|
||||
the extension target reads (add a `REASAMPLER_VST_OUTPUT_NAME` beside
|
||||
`REASAMPLER_OUTPUT_NAME`, or reuse the one channel bit; **check what the extension does and
|
||||
mirror it exactly**). (b) **Display name:** the factory `DEF_CLASS2` plug-in display string
|
||||
is "ReaSampler 9000" / "ReaSampler 9000 beta" — sourced from an `app_version` VST-name
|
||||
accessor (a new channel-derived accessor beside `binaryName()`/`dockTitle()`, e.g.
|
||||
`vstPluginName()`), not a literal in `reasampler_vst.h`/`vst_entry.cpp`. (c) **Editor title
|
||||
band + S6 embed-strip label** are channel-aware from the same accessor. All fan out from the
|
||||
ONE channel bit — no per-file `#ifdef`.
|
||||
- [ ] **Factory vendor/version strings channel-aware where V4 does the equivalent.** The
|
||||
`BEGIN_FACTORY` vendor/url/email and the `PClassInfo2` version string
|
||||
(`REASAMPLER_VST_VERSION`, currently a fixed `"0.1.0.0"`) align with the channel where the
|
||||
extension's V4 equivalents do — the version display carries the `-beta` render
|
||||
(`appVersion()` already yields `"0.9.01-beta"` on beta); the vendor strings stay shared
|
||||
unless V4 qualified them (**check V4's treatment and match it** — V4 kept the lane-name
|
||||
prefix shared, so shared-where-V4-shares is the default). No new forever-stable string
|
||||
beyond the UID and the id-family V4 already owns.
|
||||
- [ ] **Pairing-surface invariant recorded (no new code — a documented guarantee).** Write
|
||||
the complete pairing surface as an invariant in CONTEXT.md: a channel's VST talks to that
|
||||
channel's extension **only**, because (1) plugin identity (UID + filename + display) is
|
||||
channel-forked here, and (2) **all** wire keys — `banks`, `assign_request`, S9's
|
||||
generation key, and any future key — live under the channel-derived `kProjExtNamespace()`,
|
||||
so the beta VST's bridge reads only `"reasampler_beta"`. The invariant to write: **channel
|
||||
isolation is structural — no per-key opt-in — so a future wire key that forgets to isolate
|
||||
is impossible by construction** (it keys under the namespace accessor, not a raw literal).
|
||||
This is the guard that S9/S8/S17's cross-artifact keys inherit isolation for free.
|
||||
- [ ] **DAW-verify contract (the acceptance gate, no unit test — identity is a shell fact).**
|
||||
Both channels installed side-by-side: each browser sees only its channel's banks; a
|
||||
stable-project + beta-VST opens clean-empty (not error); the S-NAME-1 rename/rebind compat
|
||||
test extended to the beta UID (save a beta instance, confirm it rebinds by the beta UID on
|
||||
reopen). The pure `app_version` accessors (binary/display name per channel) are CTest-green
|
||||
in `app_version_tests` (extend the existing V4 channel-string assertions to the new VST
|
||||
accessors); the UID selection + factory wiring are DAW-verified (no host-free test path).
|
||||
|
||||
## Phase S — held and optional-forever (noted, not specified)
|
||||
- **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
|
||||
|
||||
@@ -918,6 +918,51 @@ This reshapes S16 and **flips the r7 WDL verdict** on `WDL_SimplePitchShifter`.
|
||||
with forks S16-F1/F2 and the corrected WDL finding) and the S15 × S16 interaction note;
|
||||
CONTEXT.md §Pitch engine modes — Varispeed vs Preserve + the corrected WDL surface finding.
|
||||
|
||||
### Addendum — VST channel isolation (Daniel, 2026-07-26)
|
||||
|
||||
**Daniel's directive (2026-07-26, settled):** *"support the beta/stable channels for the VST
|
||||
as well. The VST in beta should talk to the beta extension only."* This extends Phase V's V4
|
||||
beta/stable split — which fully isolated the **extension** per channel — to the **ReaSampler
|
||||
9000 VST3 instrument**. Spec'd as **S18**, an immediate Phase S wave, dispatchable in parallel
|
||||
with or right after the in-flight waves (S9 ext_keys, S15/S16 processor/editor) — it touches
|
||||
`vst_entry.cpp` / `reasampler_vst.h` / the CMake VST3 block, mostly disjoint from those.
|
||||
|
||||
**Established honestly from the tree — what already works vs. what's missing:**
|
||||
- **Already isolated (the V4↔S4 reconcile did this): data pairing.** A beta-built VST already
|
||||
*reads* the beta namespace — `ext_keys.h`'s `kProjExtNamespace()` delegates to
|
||||
`app_version::extStateNamespace()`, and every wire key (`banks`, `assign_request`, S9's
|
||||
generation key, S17's blob key, any future key) is a plain constant *under* that namespace.
|
||||
Channel data-isolation is therefore **structural, not per-key** — new keys inherit it for
|
||||
free. No S18 work here.
|
||||
- **Missing: the VST's *plugin identity*.** Its class UID, binary filename, and display
|
||||
strings are single-valued (same for both channels), so two installed channels would collide
|
||||
on UID and filename. S18 closes exactly this.
|
||||
|
||||
**The shape of S18 (mirrors V4's philosophy — one channel per binary, one bit drives it):**
|
||||
1. **A UID pair.** The stable class UID is locked forever (S-NAME-1). Beta needs its own
|
||||
forever-stable UID (a second constant, minted once, locked identically). Both frozen
|
||||
forever; the channel bit selects which is compiled in. **One class per binary, not both**
|
||||
— the V4 fully-isolated-binary philosophy, so a beta build never presents the stable
|
||||
identity. Saved-project isolation follows: a beta-saved instance rebinds only to the beta
|
||||
VST. *Fork S18-F1 (Daniel's call):* mint the beta UID **now** (lean — mirrors the stable
|
||||
UID minted at the S1 spike, removes a pre-ship landmine, zero cost unused) vs. defer to
|
||||
first beta release behind a locked-once placeholder.
|
||||
2. **Channel-derived binary + display identity.** `reasampler_9000` / `reasampler_9000_beta`
|
||||
filename (mirror the extension's `OUTPUT_NAME` fork); "ReaSampler 9000" / "ReaSampler 9000
|
||||
beta" display; editor title + embed label channel-aware — all from the ONE bit via
|
||||
`app_version` accessors, no scattered `#ifdef`s (the V4 invariant).
|
||||
3. **The pairing guarantee, stated as an invariant.** A channel's VST talks to that channel's
|
||||
extension only, because identity keeps the plugins distinct and the channel-derived
|
||||
namespace keeps the data distinct. **Structural, not per-key** — S8/S9/S17's cross-artifact
|
||||
keys all inherit it; a future key that forgets to isolate is impossible by construction.
|
||||
4. **DAW-verify contract.** Both channels installed side-by-side; each browser sees only its
|
||||
channel's banks; stable-project + beta-VST = clean empty (not error); the S-NAME-1
|
||||
rename/rebind test extends to the beta UID.
|
||||
|
||||
**Where the spec lives:** PLAN.md §S18; CONTEXT.md §VST3 channel identity — the UID pair + the
|
||||
pairing surface. The pairing surface's data half is already load-bearing V4 machinery; S18
|
||||
adds only the identity fork on top.
|
||||
|
||||
---
|
||||
|
||||
## Where this landed
|
||||
|
||||
Reference in New Issue
Block a user