Service both VST3 parameter channels, and promote pitch key-track and Trigger length so all 44 ids issue

The SDK's own single-component sample drains inputParameterChanges in
process() and implements setParamNormalized; automation was reading the
GUI channel alone. The audio thread now patches a block it solely owns.
This commit is contained in:
2026-08-02 16:22:17 -04:00
parent bfaa0f2614
commit de5654fb6f
44 changed files with 1205 additions and 302 deletions
+22 -9
View File
@@ -336,10 +336,18 @@ invariant Θ-W1-T1 was run to establish.
> automation and is not a defect to design away — but it has one sharp consequence for the
> resample bake, and that is §9.
**[verify] at the track, before wiring:** that REAPER calls `setState` (not
`setComponentState`) on a single-component plug-in, and the ordering of `setState` against
the first `IParameterChanges` block after a project load. Verify against the vendored SDK
and in the DAW — do not build on the paragraph above without it.
**SETTLED at the track, from the vendored SDK.** The delivery question the `[verify]` here
bundled is answered by the headers rather than by the DAW: `setParamNormalized` is documented as
the GUI-update channel (*"should update the according GUI element(s) only"*,
`ivsteditcontroller.h`), and `ProcessData::inputParameterChanges` is the audio-side one — the
SDK's own `SingleComponentEffect` sample services BOTH
(`public.sdk/samples/vst/again/source/againsimple.cpp`), and so do we. The `setState` ordering
half dissolves with it: an automation point held by the audio thread is re-applied over every
merge, so a written lane outranks the restore whichever way round the two arrive, which is
VST3's own authority rule rather than a race. **The audio thread cannot run the model path**
(`resolvePlay` copies velocity curves and spline contours, so it allocates), so the drain patches
the live block in place through one pure RT-safe function whose routing is pinned by an
exhaustive equivalence test against the model path.
### 6.2 The ID space: hand-assigned constants in one frozen table
@@ -897,11 +905,16 @@ state fits them exactly:
- **Trigger length** — *"resolves `playEnd_`, a fact about the note, not a setting of it."*
Same shape.
**[propose at review, Γ-W4-T1]** promote both. The promotion aligns the routing with the
predicate's own stated semantics — and it is what makes them automatable, since today they
would re-decode a WAV per automation point. **If either promotion is refused, that control
simply drops out of the parameter list.** The list follows the predicate; the predicate is
never bent to fill the list.
**RULED (Daniel, 2026-08-02): promote both.** Ids 1000 and 1450 issue; the count is 44 of 44.
The promotion aligns the routing with the predicate's own stated semantics — and it is what
makes them automatable, since otherwise they would re-decode a WAV per automation point.
**It was not the predicate-only change this section implied.** Key-track lives on
`InstrumentParams`, not `PlaySeconds`, so the host's write path could not reach it and id 1000
would have no-oped in both directions with nothing failing to compile; both controls also had to
reach the engine, which widened `LiveValues` and `foldLive`'s input and gave `Voice::start` the
two latched values as arguments beside the rate. `param::valueHomeFor`, asserted over the
exposed set, is what makes the next promotion of this shape a test failure instead of a silence.
**Not promoted, and not proposed for promotion: Rate to Live.** §3.5 records the cost;
that paragraph is the first thing to read if it is ever proposed.