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
+27 -16
View File
@@ -1277,7 +1277,12 @@ value semantics, any deck geometry, or the bake's reset *membership* (W3-T2's).
sound intact; and a project with automation drawn, saved and reopened, replays against the
same plain values.
- **`process()` takes no new indirection and no new per-sample work** — the parameter read is
a block-boundary act, on the existing live-publish path.
a block-boundary act. **Correction to the original wording ("on the existing live-publish
path"):** it cannot be, and the SDK is what decides that. `IParameterChanges` is delivered ON
the audio thread, and the model's publish path allocates (`resolvePlay` copies velocity curves
and spline contours), so the drain lands in `process()` and patches the live block in place.
The block-boundary rule is unchanged and the per-sample path is untouched; what moved is which
thread performs the fold.
- **The bake's reset notifies the host**, verified by the host's displayed value following it
rather than snapping back on next touch.
- **The double-processing limitation is documented, not discovered** — a bake whose
@@ -1297,11 +1302,17 @@ value semantics, any deck geometry, or the bake's reset *membership* (W3-T2's).
- **No [Daniel] questions. Γ-F7 is RULED — signal flow** (2026-08-01, *"signal flow order."*),
and Ruling 3 (real units) arrived specified rather than forked. **There is no unanswered
[Daniel]-class question in this track or anywhere in this plan.**
- **[verify, FIRST]** 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. §6.1 is built on the SDK's own
name-collapse; **verify it in the DAW before wiring, and do not build on the paragraph
alone.**
- **CLOSED from the SDK, not the DAW.** Two things were bundled here and they separate.
(a) The DELIVERY CHANNEL: `ivsteditcontroller.h` documents `setParamNormalized` as the
GUI-update channel ("should update the according GUI element(s) only"), and the SDK's own
`SingleComponentEffect` sample (`public.sdk/samples/vst/again/source/againsimple.cpp`) drains
`ProcessData::inputParameterChanges` in `process()` while also implementing
`setParamNormalized`. **Both are serviced.** This was never a DAW question — the headers
answer it, and building on the paragraph alone is exactly what the first pass did.
(b) The ORDERING of `setState` against the first parameter block: no longer a question. 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 — VST3's own rule. What survives as
DAW work is recorded in `docs/TODO.md`, and none of it can change the frozen contract.
- **[verify]** whether REAPER renders `ParameterInfo::units` beside the string
`getParamStringByValue` returns, or shows the string alone. **We ship the SDK's own
convention** — digits in the string, unit carried separately, which is what
@@ -1309,16 +1320,16 @@ value semantics, any deck geometry, or the bake's reset *membership* (W3-T2's).
REAPER shows no unit at all, the fallback is to append the unit **inside the one formatter**:
a one-line change in one place, touching neither the frozen id table nor the editor, because
display strings are explicitly not frozen (§6.7.1). Do not discover this after shipping.
- **[propose at review]** promoting **key-track** and **Trigger length** from `Reload` to
`NoteOnLatched` (§7.4). Both are excluded from the live set *for the note-on-latch reason*
in the predicate's own words, so the promotion aligns routing with documented semantics —
and it is what makes them automatable at all. **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. **Consequence for the frozen table:** a refusal
drops ids 1000 and 1260 (key-track) or 1450 (Trigger length) and the count falls below 44.
Those slots are then simply **never issued** — not retired, since nothing shipped under
them — and remain available to the same control if it is promoted later. No other id moves;
that is what the block-and-step scheme buys.
- **RULED (Daniel, 2026-08-02): promote both.** **Key-track** and **Trigger length** move from
`Reload` to `NoteOnLatched` (§7.4) and are exposed; ids **1000** and **1450** issue and the
count is **44 of 44**. **The promotion is NOT the predicate-only change this bullet originally
advertised** — the predicate flip is the smallest part of it. Key-track lives on
`InstrumentParams`, not `PlaySeconds`, so the host's write path (`setDeckParam`/`deckParamNorm`)
structurally could not see it and id 1000 would have no-oped in both directions with no
compile-time guard; both controls also had to reach the engine, which meant widening
`LiveValues` and `foldLive`'s input and handing `Voice::start` the two latched values as
arguments beside the rate. The guard that closes the class is `param::valueHomeFor`, asserted
over the exposed set.
- **[propose at review]** whether to ship a default `IMidiMapping` CC table here or leave MIDI
control to REAPER's host-side learn. Either is defensible; **skipping it silently is not.**
- **[propose at review]** whether this track spends the reserved payload rung. §6.1 says