Decouple the instrument reload from VST3 activation, and make the master meter's accumulate exact
This commit is contained in:
+22
-46
@@ -240,57 +240,33 @@ alpha and this entry is re-filed against the new value.
|
||||
|
||||
**Nothing here is actionable as a TODO.** Delete this entry when Γ-W1-T1 lands.
|
||||
|
||||
## Decouple the instrument reload from VST3 activation
|
||||
## The editor's drag state machine has no seam, and `reasampler_editor.h` is near the ceiling
|
||||
|
||||
**Context (Daniel, 2026-08-01 — Phase Γ fork Γ-F6, ruled closed).** Γ-W1-T2 ships the plugin's
|
||||
first latency reporting: `getLatencySamples()` returns 0 with the limiter off and the lookahead
|
||||
with it on, and the toggle calls `IComponentHandler::restartComponent(kLatencyChanged)`. The
|
||||
vendored SDK defines that flag as a host **deactivate/reactivate**
|
||||
(`pluginterfaces/vst/ivsteditcontroller.h:105-108`). **Dynamic latency reporting is routine for
|
||||
VST3 instruments and REAPER handles it as a matter of course** — the deactivate/reactivate is
|
||||
the normal contract, and for a typical plugin `setActive` only allocates and frees buffers.
|
||||
Γ-F6 was originally posed as "is this SDK cost acceptable?"; Daniel's answer relocated it:
|
||||
*"you have to have missed something, I used plenty of VST3s inside of REAPER that report PDC
|
||||
dynamically... Toggling the limiter killing the voices isn't a deal breaker though, the limiter
|
||||
will either be on or off on its instance, toggling during playback is not a use case."*
|
||||
**Context (Γ-W3, meter re-review).** `reasampler_editor.h` stands at **563 lines** against the
|
||||
~600-line ceiling — 37 lines of margin — and it keeps growing because every new surface on the
|
||||
Sample face adds its transient state there. The obvious seam is the drag state machine: `drag_`
|
||||
plus the per-gesture anchors it is read against.
|
||||
|
||||
**The wart — and it is ours, not the SDK's.** `ReaSamplerProcessor::setActive(true)` calls
|
||||
`reloadInstrument()` (`src/shell/instrument/reasampler_processor.cpp:89-97`) — a bridge read
|
||||
plus a **full WAV re-decode** plus a fresh engine. `setActive(false)` frees `live_`,
|
||||
`draining_` and the graveyard (`:98-107`). So every host-driven activation cycle — a
|
||||
latency-change restart, an offline-render bracket, any host that deactivates around transport
|
||||
state — pays a disk read and a decode that nothing about activation requires. **Activation
|
||||
currently means two things at once**: "the audio thread may run" and "the decoded `SampleData`
|
||||
is (re)built." Dynamic latency is simply the first feature that makes the cycle
|
||||
user-triggerable.
|
||||
**Why it was declined rather than taken.** `drag_` has **42 references across 13 shell TUs**
|
||||
(measured over `src/shell/instrument/*.cpp`; the declaration in the header is additional), and
|
||||
every input TU both writes it and branches on it. Extracting it is a real refactor of the
|
||||
editor's input half, not a header move — and doing it inside a wave whose subject is the MASTER
|
||||
deck would have put an unrelated high-blast-radius change in the same diff. Declining was right;
|
||||
leaving it unrecorded was not.
|
||||
|
||||
**Intended fix.** Separate the two lifetimes: keep the decoded `SampleData` alive across a
|
||||
deactivate and rebuild only the voice state on reactivate. The mechanism already exists in this
|
||||
file — `rebuildVoiceEngine` performs exactly that shape (drain-slot swap around the
|
||||
already-decoded `SampleData`, no bank re-read, no WAV re-decode) for voice-count and voice-mode
|
||||
edits. This is a lifetime split, not a new mechanism.
|
||||
**The shape a fix would take.** A `DragState` type owning the kind plus its anchor payload,
|
||||
with the input TUs mutating it through named transitions rather than assigning `drag_` and its
|
||||
anchors independently — which is also what would let the invariant "an anchor is only readable
|
||||
while its own `DragKind` is in flight" be enforced rather than observed. `editor_interaction.h`
|
||||
already holds the `DragKind` vocabulary and is the natural home.
|
||||
|
||||
**The constraint the fix MUST handle.** The deactivate's destruction is deliberate and its
|
||||
reason is documented at the call site: a surviving `live_` would be displaced into the drain
|
||||
slot on reactivate and *"resurrect stale sustained voices as ghosts."* **Voice state must still
|
||||
die across the cycle** — only the decoded PCM survives, and those are two different lifetimes
|
||||
currently collapsed into one. Second constraint: `setActive(true)` is also the non-editor
|
||||
legacy-lift trigger for a pre-v10 blob (its opportunistic `refreshRefsFromBank` copies refs in
|
||||
once the bank blob is readable), so a path that skips the bridge read must keep that lift
|
||||
reachable — the comment at `:90-96` records the residual load-order race it exists to cover.
|
||||
**Priority / risk.** Low, but the margin is the clock: the next surface that adds two members to
|
||||
the header takes it over the ceiling, and at that point the seam gets chosen under time pressure
|
||||
by whoever is unlucky. Take it before that, not after.
|
||||
|
||||
**Priority / risk.** Low; deferred by ruling. Nothing is incorrect today, only wasteful, and
|
||||
Daniel has explicitly accepted the user-visible consequence (held notes cut on a limiter
|
||||
toggle). **Trigger conditions — revisit when any one of these holds:** (a) a second
|
||||
latency-changing control appears, so the cycle stops being a once-per-patch event; (b) the
|
||||
limiter enable is ever wanted automatable, which `docs/product/parameter-automation.md` §3.8
|
||||
currently forbids *because* of this cost; or (c) the re-decode is observed to be perceptible in
|
||||
REAPER — Γ-W1-T2's review records that observation for exactly this purpose.
|
||||
|
||||
**Done looks like.** A host-driven deactivate/reactivate cycle costs no disk I/O and no WAV
|
||||
decode; sounding voices are still destroyed across it, with no ghost-resurrection regression;
|
||||
a pre-v10 blob still lifts; and `getLatencySamples()` still derives from persisted state rather
|
||||
than from a transient the deactivate cleared.
|
||||
**Done looks like.** `reasampler_editor.h` is back under the ceiling with room; no TU assigns
|
||||
`drag_` and an anchor as two independent writes; and the transitions are named where the
|
||||
`DragKind` catalogue already lives.
|
||||
|
||||
## `Sample::sourceMode` has no value meaning "produced by the instrument"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user