docs: close Γ-F6 and resequence Phase Γ into three waves
Dynamic latency ships as ruled; the restart cost is re-attributed to our own setActive and filed in TODO. The reflow splits canvas (W1-T4) from arrangement (W3-T1); preserve-time-stretch moves to W1-T5.
This commit is contained in:
@@ -224,6 +224,58 @@ Forward-looking follow-ups. Deferred by decision, not oversight — each entry r
|
||||
|
||||
**Done looks like.** A stage time of several seconds is reachable by hand with no loss of resolution below 100 ms; the AHDSR overlay still reads legibly at both ends of the range; `kEnvTimeMaxSeconds` and `kGateStageMaxSeconds` still agree; and a project saved at the old ceiling reloads with identical stored seconds and identical audio.
|
||||
|
||||
## Decouple the instrument reload from VST3 activation
|
||||
|
||||
**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."*
|
||||
|
||||
**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.
|
||||
|
||||
**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 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; 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.
|
||||
|
||||
## The deck layout rework — SPECCED, and the original shape SUPERSEDED
|
||||
|
||||
**Status (2026-08-01): no longer a deferral. The design notes Daniel owed this entry have
|
||||
|
||||
Reference in New Issue
Block a user