docs: record Phase Gamma Wave 4's landed track and collapse the wave to it

This commit is contained in:
2026-08-02 20:30:04 -04:00
parent d3f5e6ec17
commit a9c166b8c2
2 changed files with 108 additions and 229 deletions
+72
View File
@@ -1248,3 +1248,75 @@ unlimited — and Daniel has ruled that a future track will change the bake to p
limiter. Until that lands this is a recorded, known limitation, not an oversight.
**Neither track has been verified in a running DAW; both are asserted in CTest only.**
### Γ-W4-T1 — vst3-parameter-set
The instrument now reports its automatable parameters to the host: 44 of 44 issue, under a
FOREVER-FROZEN `ParamID` table — blocks of 100 per deck group in signal-flow order, steps
of 10 within a block, a curve dial at its outer knob's id + 1 — each with a real plain
range, `units` string and display precision at the host boundary, not a raw normalized
float. The exposed set is DERIVED from `deckParamCommit` / `liveCommitFor`, never
hand-maintained: a control qualifies iff its class is `Live` or `NoteOnLatched`. Everything
else — play mode, the pitch engine, filter enable, the three Staged↔Spline toggles, voice
count, Poly|Mono, Retrigger|Legato, and the limiter enable — is OMITTED from the list
entirely rather than exposed read-only, a named limitation rather than a silent one. A new
pure module, `core/instrument/param` (`param_id`, `param_units`, `param_format`,
`param_live`, `param_merge`), holds the id table, the plain-value layer, the one formatter
per unit category (eight of them), and the audio thread's block-boundary merge decision;
`shell/instrument/instrument_params` adapts it onto `Steinberg::Vst::Parameter` and decides
nothing itself.
**Both VST3 delivery channels are serviced.** An earlier pass routed host automation
through `IEditController::setParamNormalized` alone — the SDK documents that as the
GUI-update channel only ("should update the according GUI element(s) only") — while
`ProcessData::inputParameterChanges` is the audio-side one; the SDK's own
`SingleComponentEffect` sample (`public.sdk/samples/vst/again/source/againsimple.cpp`)
drains the queue in `process()` *and* implements `setParamNormalized`. Both are now
serviced.
**A host automation point's authority is bounded, not permanent.** It outranks the model
only between the point landing and the UI thread folding it into the model and
republishing — at most one UI tick — never a later restore, bake reset, or knob move. An
earlier pass made the hold permanent, which silently defeated `setState`, preset load,
undo, and the bake's reset for any parameter that had ever carried an automation point.
The model is now written down in full — `shell/instrument/CLAUDE.md`'s "THE AUTHORITY
MODEL" section — and enforced by the pure `param_merge`; `test_param_merge` asserts both
halves: that a held point outranks the model until the model catches up, and that a writer
after the release reaches the audio again.
**Two rulings, both Daniel, 2026-08-02.** (1) Pitch key-track and Trigger length promote
from `Reload` to `NoteOnLatched` — the promotion that takes the count to 44 of 44 and
issues ids 1000 and 1450. It was **not** the predicate-only change the plan anticipated:
key-track lives on `InstrumentParams`, not `PlaySeconds`, so the host's write path could
not reach it without `LiveValues` and `foldLive`'s input widening and `Voice::start`
taking the two latched values as arguments beside rate; the new `param::valueHomeFor`
guard closes the class of bug this exposed (a promoted control with no home would have
no-oped silently in both directions) by asserting every exposed control has a home and
branching the shell's own read/write paths on it. (2) The curve-shape dials' ±0.01
snap-to-centre band now applies on the mouse-drag path only, never on a host-facing map —
*"our continuous ranges should be continuous."*
**Two adjacent SDK surfaces were assessed and left unimplemented, with dispositions
recorded rather than re-surveyed later.** `IMidiMapping` — no CC vocabulary fits what's
exposed, and REAPER's own per-parameter MIDI learn is expected to cover the case.
`IParameterFunctionName` and `IAutomationState` are also not implemented; the latter
reports the host's automation mode for the whole plug-in, not per parameter, so it cannot
answer the bake's "is this parameter automated" question.
**The bake's reset now notifies the host, and its one remaining gap is named rather than
hidden.** Every internal writer of an exposed parameter's value goes through the one
`beginEdit`/`performEdit`/`endEdit` path, the bake's reset included. What it cannot do:
clear a host automation lane. If a reset-class parameter carries one, the lane replays its
curve onto audio the bake already baked that processing into — double processing — and
`IAutomationState`'s whole-plugin (not per-parameter) granularity means there is no way to
detect or refuse it. Documented as a boundary of the bake's fidelity claim, not discovered
later as a bug against Phase Ξ.
**The per-sample voice path is byte-identical across the whole track.**
**Not verified in a running DAW — CTest-asserted only.** `docs/TODO.md` carries the
residual DAW-verification items: whether REAPER renders `ParameterInfo::units` beside the
formatted string, whether REAPER's MIDI learn actually covers the un-shipped `IMidiMapping`
case, the three migration round trips (a pre-parameter project, a save/reopen in an older
binary, automation drawn and replayed), whether an offline render replays automation, and
whether REAPER restores instance state through `setState` rather than `setComponentState`.