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:
@@ -98,22 +98,34 @@ pure half — the frozen id table, the exposed set, the plain-value layer, the f
|
||||
next touch re-imposing, a superseded value. Master gain has its own funnel
|
||||
(`setMasterGainLinear`) because it is the one exposed control that does not ride the
|
||||
parameter set.
|
||||
- **`setState` ordering against the host's first parameter block is irrelevant by
|
||||
construction.** There is one model and one funnel per control, so whichever writes last wins
|
||||
and the host's display follows the model either way — the ordering is not assumed, it is
|
||||
removed as a question.
|
||||
- **`process()` reads no parameter queue and is unchanged by the parameter surface.** A host
|
||||
write arrives on the UI/main thread and reaches the audio thread through the SAME live block
|
||||
the editor's knobs publish into, observed once per `render()` — block boundaries, last write
|
||||
wins. `[verify — DAW]` that REAPER delivers automation to a single-component plug-in through
|
||||
`IEditController::setParamNormalized` and not through `ProcessData::inputParameterChanges`
|
||||
alone; if it is the latter only, an RT-safe drain is required and `process()` is where it
|
||||
would have to land.
|
||||
- **BOTH delivery channels are serviced, and the audio-side one is the normative one.**
|
||||
`IEditController::setParamNormalized` is the CONTROLLER channel — the SDK says a controller
|
||||
"should update the according GUI element(s) only" there, so nothing about the audio may depend
|
||||
on a host calling it. `ProcessData::inputParameterChanges` is the AUDIO channel, and the SDK's
|
||||
own single-component sample (`public.sdk/samples/vst/again/source/againsimple.cpp`) drains it
|
||||
in `process()` while also implementing `setParamNormalized`. We do both, for the same reason.
|
||||
- **The audio thread is the sole writer of the block the ENGINE reads.** Two `LiveParams`
|
||||
blocks: the model's publishers (editor commits, reload, `setState`) write `liveParams_` off
|
||||
the audio thread and may allocate on the way; `process()` merges that block with the host's
|
||||
automation points into `automationLive_`, which is what `SampleData::live` points at. Two
|
||||
blocks rather than one because the seqlock's single-writer contract is load-bearing and the two
|
||||
writers genuinely differ in thread. The merge republishes ONLY when either side moved, so a
|
||||
block carrying neither costs one relaxed load and the engine's read shape is unchanged.
|
||||
- **The automation values fold back into the model on the UI thread** (`drainAutomationToModel`,
|
||||
called from `getState`, the editor's sync tick, and the bake's reload tail). The blob is
|
||||
authoritative, so a value that never came back would be lost on save. The fold is suppressed
|
||||
from notifying the host — the values came FROM it, and echoing them would let a lane in write
|
||||
mode re-record its own playback.
|
||||
- **`setState` does not need an ordering guarantee against the host's first parameter block.**
|
||||
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 rule, not a race
|
||||
we lost.
|
||||
- **`IMidiMapping` is deliberately NOT implemented** — no conventional CC names most of what
|
||||
is exposed, an invented map would hijack CCs the user's controller already sends, and
|
||||
REAPER's own per-parameter MIDI learn covers the case without freezing anything.
|
||||
`IParameterFunctionName` and `IAutomationState` are assessed and not implemented; the reasons
|
||||
are in the product spec and are not re-surveyed here.
|
||||
`[verify — DAW]` REAPER's own per-parameter MIDI learn is expected to cover the case without
|
||||
freezing anything. `IParameterFunctionName` and `IAutomationState` are assessed and not
|
||||
implemented — `bake/CLAUDE.md` owns the `IAutomationState` reasoning, at its one consequence
|
||||
site.
|
||||
|
||||
**Non-goals / guardrails.**
|
||||
- The instrument never captures and never inserts into the arrange. Playback is a
|
||||
@@ -151,6 +163,13 @@ pure half — the frozen id table, the exposed set, the plain-value layer, the f
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **`reasampler_processor.h` is a documented ~600-line-ceiling exception** (root `CLAUDE.md`,
|
||||
structural heuristic 1), on the same footing as `voice.h`'s: it is ONE class declaration, so
|
||||
the seam the heuristic asks for does not exist — a split would be an arbitrary bisection, and
|
||||
the implementation is already split across three TUs on its real seams. Its bulk is the
|
||||
drain-slot proof, the RT-discipline constraints and the two-block automation contract, all of
|
||||
which the comment conventions name as keep-worthy. Not silent overshoot.
|
||||
|
||||
- **The bake click only ARMS; the editor's sync tick runs it.** Calling
|
||||
`Main_OnCommandEx` inline from `WM_LBUTTONDOWN` would run the extension's whole landing
|
||||
nested inside a mouse handler with `SetCapture` held, while the invoked action re-points
|
||||
|
||||
Reference in New Issue
Block a user