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:
+27
-16
@@ -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
|
||||
|
||||
@@ -819,3 +819,20 @@ doc-keeper edit.
|
||||
|
||||
**Done looks like.** The enumeration distinguishes "in the project's state" from "on disk
|
||||
in the `.rpp`", and does not gain a second home for the distinction.
|
||||
|
||||
## The VST3 parameter surface's DAW-verifiable claims
|
||||
|
||||
**Context (what shipped — Γ-W4-T1).** The instrument reports 44 automatable parameters under the frozen id table, services both delivery channels (the controller's `setParamNormalized` and the audio thread's `IParameterChanges` drain), and folds automated values back into the blob on the UI thread.
|
||||
|
||||
**What is settled without a DAW.** The channel question itself is answered by the vendored SDK, not by observation: `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`. Servicing both is what the SDK's own precedent does; it needs no verification, only exercise.
|
||||
|
||||
**What genuinely needs a running REAPER, and why none of it can change the design.** Each item below is a host BEHAVIOUR, not a contract — the plug-in is correct under either answer, so discovering the answer costs a display fix at worst:
|
||||
|
||||
1. **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). If REAPER shows no unit at all, the fallback is to append the unit inside the one formatter — one line in one place, touching neither the frozen id table nor the editor, because display strings are explicitly not frozen.
|
||||
2. **Whether REAPER's own per-parameter MIDI learn covers what a shipped `IMidiMapping` CC table would have.** The decision to ship no default map rests on it; if learn does not reach these parameters, a CC table is additive and frozen by nothing.
|
||||
3. **That the three migration round trips hold**: a pre-parameter project opens with every parameter reading the blob's value and sounds identical; a project saved by this build restores fully in an older binary; a project with automation drawn, saved and reopened, replays against the same plain values.
|
||||
4. **That an offline render replays automation** — the sharpest case for the audio-side drain, because the host drives `process()` and may never touch the controller.
|
||||
|
||||
**Priority / risk.** Low. Nothing here is load-bearing on the frozen contract: the id table, the plain ranges and the norm↔plain laws are all decided and tested without a host.
|
||||
|
||||
**Done looks like.** Each of the four exercised once in REAPER, with the unit-rendering answer recorded and, if it went the other way, the one-line formatter change made.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user