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:
2026-08-01 16:51:04 -04:00
parent eb777f55e1
commit 256216d670
4 changed files with 658 additions and 255 deletions
+52
View File
@@ -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