docs: schedule VST3 parameters into Phase Γ
Automation ships as Γ-W4; the stage ceiling goes to 10 s in W1-T1 ahead of the one-way door; a new Γ-W3-T2 corrects Ξ's bake reset list. Four waves, ten tracks. Opens Γ-F7 on parameter order.
This commit is contained in:
+656
-125
@@ -48,7 +48,15 @@ with a correction to the analysis, not merely a ruling**: dynamic reported laten
|
|||||||
for VST3 instruments and REAPER handles it as a matter of course; what makes the mandated
|
for VST3 instruments and REAPER handles it as a matter of course; what makes the mandated
|
||||||
restart expensive *here* is self-inflicted (`setActive(true)` calls `reloadInstrument`), so
|
restart expensive *here* is self-inflicted (`setActive(true)` calls `reloadInstrument`), so
|
||||||
the cost is ours to reduce and the reduction is filed in `docs/TODO.md` rather than designed
|
the cost is ours to reduce and the reduction is filed in `docs/TODO.md` rather than designed
|
||||||
around. **No track in this plan carries an unanswered [Daniel]-class question.**
|
around.
|
||||||
|
|
||||||
|
**Γ-F3 was subsequently REVERSED and a seventh fork opened, both by Daniel's later rulings
|
||||||
|
of 2026-08-01.** Γ-F3 (*"the stage-time ceiling stays 2.0 s"*) is replaced by *"extend the
|
||||||
|
stage lengths to 10s"* — the ceiling moves in Γ-W1-T1. And **Γ-F7 is OPEN**: the VST3
|
||||||
|
parameter *order* (signal flow, or the editor's visual rows), a forever commitment that
|
||||||
|
cannot be closed by proposal at review. It blocks nothing until **Γ-W4 dispatches**, which is
|
||||||
|
the last track of the last wave of the phase. **Exactly one track in this plan carries an
|
||||||
|
unanswered [Daniel]-class question: Γ-W4-T1, and it is Γ-F7.**
|
||||||
|
|
||||||
### Flagged for awareness — not blocking, but decision-grade
|
### Flagged for awareness — not blocking, but decision-grade
|
||||||
|
|
||||||
@@ -62,23 +70,39 @@ around. **No track in this plan carries an unanswered [Daniel]-class question.**
|
|||||||
verification, item 15's "one click from inside the VST" framing is what gives, not
|
verification, item 15's "one click from inside the VST" framing is what gives, not
|
||||||
the read-only invariant — the fallback is a bindable extension-side action.
|
the read-only invariant — the fallback is a bindable extension-side action.
|
||||||
|
|
||||||
2. **Phase Γ must land before Ξ-W2, and this is a correctness point, not a preference.**
|
2. **The "Γ before Ξ-W2" ordering is VIOLATED, it was never Daniel's choice, and Γ now owns
|
||||||
Ξ-W2's settled reset scope enumerates parameters by name; Γ adds rate, pitch offset and
|
the correction.** Daniel, 2026-08-01: *"xi was started before I spun you up, we'll have to
|
||||||
the limiter flag, so shipping the bake first means its reset list is incomplete on the
|
correct phase xi inside gamma. wasn't a choice."* Ξ-W2-T1 (`resample-bake-chain`) ran
|
||||||
day it lands. Γ's product doc pre-classifies all three against the ratified rule (all
|
ahead of this plan's sequencing claim, so the bake's settled reset scope — which
|
||||||
**reset**), so this costs no Daniel decision — only ordering. Second, weaker reason: Γ
|
enumerates parameters **by name** — ships incomplete: it cannot name rate, pitch offset or
|
||||||
owns params-payload v14 and v15, and Ξ-W3's programmed-signal persistence will want the
|
the limiter flag, none of which existed when it was written.
|
||||||
next rung; two phases contending for the ladder is the fight Θ's organizing constraint
|
|
||||||
exists to avoid.
|
|
||||||
|
|
||||||
3. **The taper work inside Γ-W1-T1 is a one-way door with respect to automation.** Once
|
**This is no longer a scheduling constraint to honour. It is a correction obligation with
|
||||||
VST3 parameters exist, the taper *is* the host-facing normalization, and re-tapering
|
a named owner: Γ-W3-T2 `bake-reset-amendment`.** The classification costs no Daniel
|
||||||
re-interprets every recorded automation point in project files we do not own and cannot
|
decision — `docs/product/instrument-control-surface.md` §3.4 pre-classifies all three
|
||||||
migrate. Re-tapering is free today and permanently expensive afterwards. See
|
against Ξ-W2's own ratified rule (all **reset**) — but the amendment must be written
|
||||||
`docs/product/parameter-automation.md` §4 — that doc is scoping only, nothing in it is
|
**against what Ξ-W2-T1 actually shipped, not against what this plan predicted it would
|
||||||
scheduled here. **The same door applies to the stage-time ceiling**, which Γ-F3 left at
|
ship.** Ruling 1 adds a second correction of the same shape, homed on Γ-W4-T1 rather than
|
||||||
2.0 s with a 10 s ambition recorded in `docs/TODO.md`: if that ceiling is ever raised, it
|
here: see item 3.
|
||||||
wants to happen before the parameter system, not after.
|
|
||||||
|
3. **The one-way doors are now IN-PHASE, and the sweep for them is a delivered artifact.**
|
||||||
|
Ruling 1 (Daniel, 2026-08-01) schedules VST3 parameter reporting **inside Phase Γ**, as
|
||||||
|
Γ-W4-T1. Everything that participates in a parameter's normalization therefore freezes at
|
||||||
|
the end of this phase rather than at the start of some later one, and anything that ought
|
||||||
|
to move must move first.
|
||||||
|
|
||||||
|
- **The taper** (Γ-W1-T1) — known, and the reason this phase was ordered as it was.
|
||||||
|
- **The stage-time ceiling 2.0 → 10.0 s** (Γ-W1-T1) — Γ-F3 **reversed** by Daniel's
|
||||||
|
*"extend the stage lengths to 10s."* A range endpoint is normalization exactly as much
|
||||||
|
as the curve between the endpoints is.
|
||||||
|
- **Two further doors that need action, both new**, both landing on Γ-W1-T1: every
|
||||||
|
default must have an **exact normalized preimage** (a host's reset-to-default has no
|
||||||
|
`resetDeckParam` bypass to use), and the filter's four `*Norm` controls **must not be
|
||||||
|
re-tapered** (their laws are already wire-frozen in payload v9).
|
||||||
|
- **Six more constants freeze without needing to change**, and three are already frozen
|
||||||
|
for unrelated reasons; the complete sweep, with dispositions and with what was checked,
|
||||||
|
is `docs/product/parameter-automation.md` §8. **That doc is no longer scoping-only —
|
||||||
|
§§6–10 are the specification Γ-W4-T1 is built from.**
|
||||||
|
|
||||||
## Phase-wide acceptance criteria
|
## Phase-wide acceptance criteria
|
||||||
|
|
||||||
@@ -400,6 +424,12 @@ stays musical-division-only, and an offset stores the denomination it was entere
|
|||||||
computable (T1) and the programmed-note record the offline pass renders (T2). **Also
|
computable (T1) and the programmed-note record the offline pass renders (T2). **Also
|
||||||
depends on all of Phase Θ** — see the phase note above.
|
depends on all of Phase Θ** — see the phase note above.
|
||||||
|
|
||||||
|
**This wave ran AHEAD of Phase Γ, and that was not a choice.** The plan asserted Γ must land
|
||||||
|
first so the bake's reset list would be complete on the day it shipped; Ξ-W2-T1 was already
|
||||||
|
live. The consequence is owned, not absorbed: **Γ-W3-T2 `bake-reset-amendment`** completes
|
||||||
|
the list afterwards, and **Γ-W4-T1** adds the host-notification obligation once parameters
|
||||||
|
exist. Neither is this wave's work, and neither is a defect report against it.
|
||||||
|
|
||||||
**One track.** The bake is one gesture and one chain; the architecture decision at its
|
**One track.** The bake is one gesture and one chain; the architecture decision at its
|
||||||
head governs every step after it.
|
head governs every step after it.
|
||||||
|
|
||||||
@@ -567,6 +597,11 @@ DEGRADED, escalate rather than improvise — the fallback is (1c) + (2a).
|
|||||||
review. Not a new Daniel call. (Θ adds: curve exponents → reset; spline contours →
|
review. Not a new Daniel call. (Θ adds: curve exponents → reset; spline contours →
|
||||||
reset, already named; the filter's velocity/key-tracking mod → reset with the filter;
|
reset, already named; the filter's velocity/key-tracking mod → reset with the filter;
|
||||||
loop crossfade → reset with the loop points; the Staged/Spline mode flag → classify.)
|
loop crossfade → reset with the loop points; the Staged/Spline mode flag → classify.)
|
||||||
|
**Phase Γ's additions are NOT this track's** — this track ran ahead of Γ, so the values Γ
|
||||||
|
introduces (rate, pitch offset, the limiter flag, the loop enable) are amended in by
|
||||||
|
**Γ-W3-T2 `bake-reset-amendment`** afterwards, against what this track actually shipped.
|
||||||
|
Γ-W4-T1 then adds the host-notification obligation over the completed list. Nothing here
|
||||||
|
needs to anticipate either.
|
||||||
- **Naming and lineage [propose, jointly with Ξ-W1-T1's lineage-record question].** When
|
- **Naming and lineage [propose, jointly with Ξ-W1-T1's lineage-record question].** When
|
||||||
add-distinct fires, the new capture needs a display name (derived from the original?),
|
add-distinct fires, the new capture needs a display name (derived from the original?),
|
||||||
and the bank some way to read iteration lineage across repeated bakes. One proposal,
|
and the bank some way to read iteration lineage across repeated bakes. One proposal,
|
||||||
@@ -644,23 +679,31 @@ must be closed.
|
|||||||
**Ships:** the deck reflowed into two categorical rows with a double-height MASTER bus deck,
|
**Ships:** the deck reflowed into two categorical rows with a double-height MASTER bus deck,
|
||||||
a PITCH/RATE deck with playback-rate and baseline-pitch controls, a master limiter with
|
a PITCH/RATE deck with playback-rate and baseline-pitch controls, a master limiter with
|
||||||
dynamic reported latency and a real output meter, one consistent knob interaction/taper law
|
dynamic reported latency and a real output meter, one consistent knob interaction/taper law
|
||||||
across every variable control, a fix for staged contour traces drawing straight, and a
|
across every variable control **over a stage-time range raised 2 s → 10 s**, a fix for staged
|
||||||
re-approached loop/crossfade marker UX under an explicit chrome-row loop enable.
|
contour traces drawing straight, a re-approached loop/crossfade marker UX under an explicit
|
||||||
|
chrome-row loop enable, **the Phase Ξ bake's reset list corrected**, and — as the phase's last
|
||||||
|
track — **the instrument's first VST3 automatable parameters, reported to the host under a
|
||||||
|
frozen id contract.**
|
||||||
|
|
||||||
**Consolidates:** none of the seventeen. Phase Γ came from a direct interview with Daniel
|
**Consolidates:** none of the seventeen. Phase Γ came from a direct interview with Daniel
|
||||||
(2026-08-01); the product reasoning, the measured layout table, the invariant collisions and
|
(2026-08-01); the product reasoning, the measured layout table, the invariant collisions and
|
||||||
the fork rulings are in **`docs/product/instrument-control-surface.md`**. Read §1.2 (the
|
the fork rulings are in **`docs/product/instrument-control-surface.md`**, and the parameter
|
||||||
layout table) and §7 (collisions) before dispatching any track here — every number in this
|
system's is in **`docs/product/parameter-automation.md` §§6–10**. Read §1.2 (the layout
|
||||||
phase is derived there, and `docs/TODO.md`'s old deck-rework geometry is superseded.
|
table) and §7 (collisions) before dispatching any track here — every number in this phase is
|
||||||
|
derived there, and `docs/TODO.md`'s old deck-rework geometry is superseded.
|
||||||
|
|
||||||
**Fork state — all six forks are ruled; nothing in this phase awaits Daniel.** Indexed at
|
**Fork state — six ruled, ONE OF THEM LATER REVERSED, and one open.** Indexed at spec §8,
|
||||||
spec §8, folded into the tracks below:
|
folded into the tracks below:
|
||||||
- **Γ-F1** — `kEditorMinHeight` stays **680**.
|
- **Γ-F1** — `kEditorMinHeight` stays **680**.
|
||||||
- **Γ-F2** — the limiter has **lookahead with DYNAMIC reported latency** (zero when off,
|
- **Γ-F2** — the limiter has **lookahead with DYNAMIC reported latency** (zero when off,
|
||||||
the lookahead when on, reported to host PDC). *This inverted the product recommendation;*
|
the lookahead when on, reported to host PDC). *This inverted the product recommendation;*
|
||||||
W1-T2's scope grows accordingly — spec §3.1.1.
|
W1-T2's scope grows accordingly — spec §3.1.1.
|
||||||
- **Γ-F3** — the stage-time ceiling stays **2.0 s** in this phase. The 10 s ambition is
|
- **Γ-F3 — RULED, THEN REVERSED THE SAME DAY.** First ruled *"the ceiling stays 2.0 s in this
|
||||||
carried in `docs/TODO.md` with its rationale and its prerequisites.
|
phase"*; then Daniel: ***"extend the stage lengths to 10s."*** `kEnvTimeMaxSeconds` /
|
||||||
|
`kGateStageMaxSeconds` move **2.0 → 10.0 in Γ-W1-T1**, and the `docs/TODO.md` entry that
|
||||||
|
carried the ambition is discharged rather than deferred. **The reversal's cause is Ruling 1**
|
||||||
|
— parameters now ship in-phase, so the ceiling is a one-way door that must be walked through
|
||||||
|
before them, not after. Spec §4.3.1.
|
||||||
- **Γ-F4** — there **is** an explicit loop enable, and it lives on the **chrome row**, not
|
- **Γ-F4** — there **is** an explicit loop enable, and it lives on the **chrome row**, not
|
||||||
in a deck. W2-T2's scope grows accordingly — spec §6.4.
|
in a deck. W2-T2's scope grows accordingly — spec §6.4.
|
||||||
- **Γ-F5** — MASTER's reserved slot is **one** cell. The 90 px headroom argument behind
|
- **Γ-F5** — MASTER's reserved slot is **one** cell. The 90 px headroom argument behind
|
||||||
@@ -669,6 +712,26 @@ spec §8, folded into the tracks below:
|
|||||||
deactivate/reactivate the SDK mandates is accepted: *"the limiter will either be on or off
|
deactivate/reactivate the SDK mandates is accepted: *"the limiter will either be on or off
|
||||||
on its instance, toggling during playback is not a use case."* No constant-latency
|
on its instance, toggling during playback is not a use case."* No constant-latency
|
||||||
fallback, no measurement gate. *This ruling also corrected the analysis* — spec §3.1.1.
|
fallback, no measurement gate. *This ruling also corrected the analysis* — spec §3.1.1.
|
||||||
|
- **Γ-F7 — OPEN, and it is the only unanswered [Daniel] question in this plan.** The VST3
|
||||||
|
parameter **order**: signal-flow order (the deck's own documented rule, layout-independent)
|
||||||
|
or the editor's visual row order after the reflow. Same membership, different sequence,
|
||||||
|
frozen forever the day parameters ship. **Recommendation: signal flow**, because the visual
|
||||||
|
layout has already moved twice and this phase moves it again. Reasoning and both arguments:
|
||||||
|
`docs/product/parameter-automation.md` §6.4. **Blocks nothing until Γ-W4 dispatches**; it
|
||||||
|
cannot be closed by proposal at review.
|
||||||
|
|
||||||
|
**Ruling 1 (Daniel, 2026-08-01) — VST3 parameter reporting ships in this phase.** Verbatim
|
||||||
|
intent: *"correct the phase gamma plan to account for complying with the VST3 standard for
|
||||||
|
parameter reporting… by the end of gamma we have the automatable params reported. Make the
|
||||||
|
parameter order logical."* `docs/product/parameter-automation.md` was written as scoping and
|
||||||
|
has been **promoted in place**: §§1–5 are the original analysis, **§§6–10 are the
|
||||||
|
specification** Γ-W4-T1 is built from. Three things it decides that the scoping pass left
|
||||||
|
open: the blob stays authoritative and parameters are a third surface onto the one model
|
||||||
|
(§6.1); the id space is an independent, hand-assigned, FOREVER-FROZEN table (§6.2, §6.3); and
|
||||||
|
the exposed list is **derived from the three-state commit predicate**, never hand-maintained
|
||||||
|
(§7). Today the plugin has **zero** parameters — `ReaSamplerProcessor::initialize`
|
||||||
|
(`reasampler_processor.cpp:56-73`) never populates `SingleComponentEffect::parameters`, so
|
||||||
|
`getParameterCount()` returns the SDK default 0.
|
||||||
|
|
||||||
**What the Γ-F6 ruling changed in the analysis, not just in the plan.** Dynamic latency
|
**What the Γ-F6 ruling changed in the analysis, not just in the plan.** Dynamic latency
|
||||||
reporting is **routine** for VST3 instruments and REAPER handles it as a matter of course;
|
reporting is **routine** for VST3 instruments and REAPER handles it as a matter of course;
|
||||||
@@ -682,27 +745,38 @@ ours to reduce if it ever matters, and the reduction is decoupling reload from a
|
|||||||
not abandoning dynamic latency.** That improvement is filed as a `docs/TODO.md` entry with its
|
not abandoning dynamic latency.** That improvement is filed as a `docs/TODO.md` entry with its
|
||||||
trigger condition; it is not scheduled in this phase.
|
trigger condition; it is not scheduled in this phase.
|
||||||
|
|
||||||
**Sequencing against Phase Ξ — Γ runs BEFORE Ξ-W2.** Two reasons, both the same shape as
|
**Sequencing against Phase Ξ — the ordering claim is RETIRED and replaced by an owned
|
||||||
Ξ's own stated gate:
|
correction.** This plan previously asserted that Γ must run before Ξ-W2 and called it *"a
|
||||||
1. **The bake bakes the control surface.** Ξ-W2's settled reset scope enumerates parameters
|
correctness point, not a preference."* **Ξ-W2-T1 ran first.** That was not a decision anyone
|
||||||
by name; Γ adds rate, pitch offset and the limiter flag. Shipping Ξ-W2 first means its
|
took — the track was live before this phase existed (Daniel: *"xi was started before I spun
|
||||||
reset list is incomplete on the day it lands. (Γ's doc §3.4 pre-classifies all three
|
you up, we'll have to correct phase xi inside gamma. wasn't a choice."*). So:
|
||||||
against the ratified rule — all **reset** — so this is a sequencing point, not a new
|
|
||||||
Daniel question.)
|
|
||||||
2. **One params-payload ladder.** Γ takes v14 and v15. Ξ-W2 does not currently bump the
|
|
||||||
payload, but Ξ-W3's programmed-signal persistence will, and two phases contending for the
|
|
||||||
ladder is exactly the fight Θ's organizing constraint calls out.
|
|
||||||
|
|
||||||
**The organizing constraint.** Five surfaces are single-writer and dictate the wave shape:
|
1. **The bake's reset list is incomplete as shipped, and Γ-W3-T2 amends it.** Rate, pitch
|
||||||
`ui/deck_values.cpp` (the taper law, then the two new controls), `editor_paint_waveform.cpp`
|
offset and the limiter flag are all **reset** under Ξ-W2's own ratified rule (spec §3.4),
|
||||||
(the contour trace, then the loop marks), `ui/deck_groups.cpp` (the row predicate, then the
|
so no Daniel decision is owed — only the edit, and it must be made **against what Ξ-W2-T1
|
||||||
PITCH/RATE descriptor, then the reflow's row consumption — three tracks, three waves),
|
actually shipped rather than against what this plan predicted it would ship.**
|
||||||
`engine/voice.cpp` (the Preserve read path, then the rate compounding into it), and the
|
2. **Ruling 1 adds a second correction of the same shape, and it lands one wave later.**
|
||||||
params-payload ladder. **Every wave boundary below is one of those collisions**, not a
|
Exposing the reset-class values as VST3 parameters means the bake's reset must notify the
|
||||||
preference. Where a wave has more than one track, the tracks are disjoint by surface.
|
host, and a host automation lane on a reset-class parameter re-imposes its curve onto
|
||||||
|
already-baked audio. Both are Γ-W4-T1's acceptance criteria — that track creates the
|
||||||
|
condition, so it carries it (`docs/product/parameter-automation.md` §9).
|
||||||
|
3. **The payload-ladder half of the old claim needs re-checking, not restating** — see the
|
||||||
|
ladder block below, which now states rungs **relatively** rather than by number.
|
||||||
|
|
||||||
**Resequenced 2026-08-01 (Daniel), two changes.** The prior four-wave shape put the reflow at
|
**The organizing constraint.** Six surfaces are single-writer and dictate the wave shape:
|
||||||
W3 and the Preserve stretcher at W4; both moved.
|
`ui/deck_values.cpp` **and the taper module extracted from it** (the taper law and the new
|
||||||
|
ceiling, then the two new controls, then the host normalization — three tracks, three waves),
|
||||||
|
`editor_paint_waveform.cpp` (the contour trace, then the loop marks), `ui/deck_groups.cpp`
|
||||||
|
(the row predicate, then the PITCH/RATE descriptor, then the reflow's row consumption — three
|
||||||
|
tracks, three waves), `engine/voice.cpp` (the Preserve read path, then the rate compounding
|
||||||
|
into it), `shell/instrument/reasampler_processor` (the limiter chain and latency, then the
|
||||||
|
parameter surface), and the params-payload ladder. **Every wave boundary below is one of
|
||||||
|
those collisions**, not a preference. Where a wave has more than one track, the tracks are
|
||||||
|
disjoint by surface.
|
||||||
|
|
||||||
|
**Resequenced 2026-08-01 (Daniel), three changes.** The prior four-wave shape put the reflow
|
||||||
|
at W3 and the Preserve stretcher at W4; both moved. **Ruling 1 then added a fourth wave** —
|
||||||
|
see "The wave shape after Ruling 1" below.
|
||||||
|
|
||||||
1. **The reflow is split, canvas from arrangement.** The window floor and the width budget it
|
1. **The reflow is split, canvas from arrangement.** The window floor and the width budget it
|
||||||
is derived from land **early** (Γ-W1-T4), so every other UI track in the phase is drawn,
|
is derived from land **early** (Γ-W1-T4), so every other UI track in the phase is drawn,
|
||||||
@@ -718,13 +792,51 @@ W3 and the Preserve stretcher at W4; both moved.
|
|||||||
Net: four waves become three, and both of the phase's DSP unknowns (the limiter, the
|
Net: four waves become three, and both of the phase's DSP unknowns (the limiter, the
|
||||||
stretcher) are exposed in wave 1 rather than one of them landing last.
|
stretcher) are exposed in wave 1 rather than one of them landing last.
|
||||||
|
|
||||||
**The params-payload ladder is unchanged by the resequencing, and re-checked against the new
|
**The wave shape after Ruling 1 — three waves become four.** The parameter system cannot be
|
||||||
membership.** Exactly one bump per wave, owned by exactly one track: **W1-T2 owns v14** (the
|
a track inside any existing wave, and the reason is a chain of hard prerequisites, not
|
||||||
limiter flag), **W2-T1 owns v15** (rate + pitch offset), and **every other track in the phase
|
caution:
|
||||||
owns no rung** — W1-T4 changes no persisted field, W1-T5 adds no parameter, W2-T2's loop
|
|
||||||
enable maps onto the already-persisted `SampleLoop::hasLoop`, and W3-T1 is layout only. The
|
- **after W1-T1**, because the taper and the 10 s ceiling *are* the host-facing
|
||||||
ordering still works because v14 lands a whole wave before v15, and neither of the two tracks
|
normalization, and Γ-W1-T1 is also what extracts them into the one module the host will
|
||||||
that moved touches the codec.
|
read through;
|
||||||
|
- **after W1-T2 and W2-T1**, because every control that could be a parameter must exist
|
||||||
|
before the list is declared — the list is derived from the control inventory, and an
|
||||||
|
inventory that is still growing produces a list that has to be re-frozen;
|
||||||
|
- **after W2-T1 specifically**, because `isLiveDeckParam` becoming three-valued is the
|
||||||
|
*prerequisite* of the classification, not an incidental of it: the exposed set is exactly
|
||||||
|
`Live ∪ NoteOnLatched`;
|
||||||
|
- **after W3-T1**, because MASTER's inventory (the limiter toggle, the GR bubble, the
|
||||||
|
reserved cell) is the last change to what controls exist at all;
|
||||||
|
- **after W3-T2**, so the bake's reset list is already complete when Γ-W4-T1 adds the
|
||||||
|
host-notification obligation over it — one amendment instead of an amendment to an
|
||||||
|
amendment.
|
||||||
|
|
||||||
|
The result is a single-track **Γ-W4**, which is the right shape for it anyway: the storage
|
||||||
|
decision governs every part of the work, exactly as Ξ-W2-T1's crossing decision governs its
|
||||||
|
chain. **And it satisfies Daniel's own framing literally** — *"by the end of gamma we have
|
||||||
|
the automatable params reported."*
|
||||||
|
|
||||||
|
**The params-payload ladder — re-checked, and now stated RELATIVELY.** The old block named
|
||||||
|
v14 and v15 as absolutes. **That is no longer safe to assume**, because Ξ ran ahead of its
|
||||||
|
sequencing and this plan is not the record of what Ξ-W2-T1 actually took. On `dev` today
|
||||||
|
`kParamsPayloadVersion` is **13** (`map/component_state_io.h:154`) and Ξ-W2-T1 was specced to
|
||||||
|
take no rung — but the plan's prediction is not evidence. So:
|
||||||
|
|
||||||
|
> **Γ owns the next three rungs above whatever `dev` carries when Γ-W1-T2 dispatches, and
|
||||||
|
> that number is READ, not assumed.** In order: **the first rung to W1-T2** (the limiter
|
||||||
|
> enable flag), **the second to W2-T1** (rate + pitch offset), **the third RESERVED for
|
||||||
|
> W4-T1** — spent only if the storage-architecture verification forces a persisted field,
|
||||||
|
> which the specification says it will not (`docs/product/parameter-automation.md` §6.1,
|
||||||
|
> §10). If unspent, that rung falls through to the next phase unclaimed.
|
||||||
|
>
|
||||||
|
> **On `dev` as of 2026-08-01 that resolves to v14 / v15 / v16-reserved.** If Ξ-W2-T1 landed
|
||||||
|
> a bump, every number shifts by one and **nothing else about the ownership changes** — which
|
||||||
|
> is the whole point of stating it relatively.
|
||||||
|
|
||||||
|
**Every other track in the phase owns no rung**: W1-T1 changes no persisted field (the payload
|
||||||
|
stores raw engine doubles, so both the taper and the new ceiling are persistence-neutral),
|
||||||
|
W1-T3, W1-T4 and W1-T5 add no field, W2-T2's loop enable maps onto the already-persisted
|
||||||
|
`SampleLoop::hasLoop`, W3-T1 is layout only, and W3-T2 changes a reset list, not a format.
|
||||||
|
|
||||||
**The editor's deck is knowingly mis-composed between Γ-W1-T4 and Γ-W3-T1, and that is not a
|
**The editor's deck is knowingly mis-composed between Γ-W1-T4 and Γ-W3-T1, and that is not a
|
||||||
defect report.** Raising the floor without the reflow leaves the greedy whole-group wrap
|
defect report.** Raising the floor without the reflow leaves the greedy whole-group wrap
|
||||||
@@ -738,11 +850,37 @@ exact interim layout; do not "fix" it in a track that does not own it.
|
|||||||
`modAmount == 0` hold the at-rest path unchanged.
|
`modAmount == 0` hold the at-rest path unchanged.
|
||||||
- **No `ComponentState` sound change.** A project saved before this phase reopens sounding
|
- **No `ComponentState` sound change.** A project saved before this phase reopens sounding
|
||||||
identical: absent rate lifts to 100 %, absent pitch offset to 0 st, absent limiter flag to
|
identical: absent rate lifts to 100 %, absent pitch offset to 0 st, absent limiter flag to
|
||||||
bypassed. Re-tapering a knob (Γ-W1-T1) changes needle angles only — the payload stores raw
|
bypassed. Re-tapering a knob **and raising the stage-time ceiling** (both Γ-W1-T1) change
|
||||||
engine doubles, so saved values reload bit-identical.
|
needle angles only — the payload stores raw engine doubles, so saved values reload
|
||||||
|
bit-identical, and a 3 s stage saved at the old ceiling is simply unreachable-by-hand
|
||||||
|
rather than altered.
|
||||||
- **`kVelocityPitchRangeSemitones` / `kPitchDepthMaxSemis` (24.0) does not move.** It is
|
- **`kVelocityPitchRangeSemitones` / `kPitchDepthMaxSemis` (24.0) does not move.** It is
|
||||||
load-bearing in the v12 wire format. The new Pitch knob **reads** it; it does not mint a
|
load-bearing in the v12 wire format. The new Pitch knob **reads** it; it does not mint a
|
||||||
second ±24 constant.
|
second ±24 constant. **From Γ-W4-T1 it is also a frozen host normalization** — one more
|
||||||
|
reason, not a new rule.
|
||||||
|
- **The taper has exactly ONE home and three consumers.** Γ-W1-T1 extracts it into a pure
|
||||||
|
module; the knob's needle (`deck_values`), the AHDSR overlay's schematic axis
|
||||||
|
(`envelope_overlay` + `envelope_edit`), and the host's `normalizedParamToPlain` (Γ-W4-T1)
|
||||||
|
all call the same function. **Three functions that agree today is a defect, not an
|
||||||
|
implementation choice** — the failure it prevents is a host automation lane that means one
|
||||||
|
value and a needle that draws another.
|
||||||
|
- **Every default value has an EXACT normalized preimage under its own taper.** Binds
|
||||||
|
Γ-W1-T1 (which designs the taper) and Γ-W4-T1 (which declares
|
||||||
|
`ParameterInfo::defaultNormalizedValue`). `resetDeckParam` bypasses the taper; **a host's
|
||||||
|
reset-to-default cannot**, so exactness in the map itself is the only thing that makes the
|
||||||
|
editor's reset and the host's reset land on the same value.
|
||||||
|
- **Nothing in this phase may re-map the filter's four normalized controls.** Cutoff, Q,
|
||||||
|
morph and drive persist as `*Norm` doubles in payload v9 — their laws are already
|
||||||
|
wire-frozen, and re-tapering them would re-tune every saved project independently of
|
||||||
|
automation. The snap-unit table names them; that is display, not law.
|
||||||
|
- **From Γ-W4-T1, the parameter-id table is FOREVER-FROZEN**, on the same footing as the
|
||||||
|
extension's `"STABLE_FOREVER_STRING"` command ids, the two VST3 class UIDs, and the
|
||||||
|
params-payload field order. No id is reassigned, reused or re-pointed; no exposed
|
||||||
|
parameter's normalization ever changes; a retired control's id is retired with it.
|
||||||
|
Full wording: `docs/product/parameter-automation.md` §6.3.
|
||||||
|
- **The exposed parameter set is DERIVED, never hand-maintained.** A control is a parameter
|
||||||
|
if and only if its commit class is `Live` or `NoteOnLatched`. There is no second table
|
||||||
|
beside `isLiveDeckParam` / `liveCommitFor`, and no list that can drift from it.
|
||||||
- **The window floor is 1190 × 680 and must not exceed 1280 × 720.** **Γ-W1-T4 sets it, in
|
- **The window floor is 1190 × 680 and must not exceed 1280 × 720.** **Γ-W1-T4 sets it, in
|
||||||
wave 1; no other track in the phase may move it**, and from that point every track is
|
wave 1; no other track in the phase may move it**, and from that point every track is
|
||||||
authored and judged at it. A track that pushes the floor past 1280 has failed, not overrun.
|
authored and judged at it. A track that pushes the floor past 1280 has failed, not overrun.
|
||||||
@@ -766,23 +904,43 @@ against this membership rather than carried over from the four-wave shape:
|
|||||||
|
|
||||||
| Track | Owns |
|
| Track | Owns |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **T1** `knob-interaction-law` | `ui/deck_values`, `ui/param_slider`, the three `shell/instrument/editor_input_*` drag paths, the shared modifier helper in `editor_internal.h` |
|
| **T1** `knob-interaction-law` | a **new pure taper module** under `core/instrument/ui/`, `ui/deck_values`, `ui/envelope_overlay` + `ui/envelope_edit` (the AHDSR schematic axis and its drag inverse), `ui/param_slider`, the three `shell/instrument/editor_input_*` drag paths, `editor_controls.cpp`'s `envClampBounds` only, the shared modifier helper in `editor_internal.h` |
|
||||||
| **T2** `master-bus-audio` | new pure `engine/limiter` + `engine/meter_ballistics`, `shell/instrument/reasampler_processor` + `processor_state`, `map/component_state_io` + `params_payload` (**v14**) |
|
| **T2** `master-bus-audio` | new pure `engine/limiter` + `engine/meter_ballistics`, `shell/instrument/reasampler_processor` + `processor_state`, `map/component_state_io` + `params_payload` (**the wave's payload rung**) |
|
||||||
| **T3** `contour-trace-curves` | `shell/instrument/editor_paint_waveform.cpp`'s staged trace + a **pure** tessellation helper |
|
| **T3** `contour-trace-curves` | `shell/instrument/editor_paint_waveform.cpp`'s staged trace + a **new pure** tessellation module |
|
||||||
| **T4** `editor-floor-and-row-law` | `ui/sample_bands.h` (the floor), `ui/knob_deck.h` (budget constants + two invalidated header notes), `ui/deck_groups` (the row predicate **only**), five test fixtures |
|
| **T4** `editor-floor-and-row-law` | `ui/sample_bands.h` (the floor), `ui/knob_deck.h` (budget constants + two invalidated header notes), `ui/deck_groups` (the row predicate **only**), five test fixtures |
|
||||||
| **T5** `preserve-time-stretch` | `engine/pitch_shift` + a new pure stretcher module, `engine/voice.{h,cpp}`'s Preserve read path |
|
| **T5** `preserve-time-stretch` | `engine/pitch_shift` + a new pure stretcher module, `engine/voice.{h,cpp}`'s Preserve read path |
|
||||||
|
|
||||||
**One shared file in the wave, named rather than discovered at merge:
|
**Two shared files in the wave, named rather than discovered at merge.**
|
||||||
`src/core/instrument/engine/CMakeLists.txt`.** T2 declares two new pure libraries and their
|
`src/core/instrument/engine/CMakeLists.txt` — T2 declares two new pure libraries and their
|
||||||
test targets there; T5 declares one. Both are append-only additions in separate blocks —
|
test targets there, T5 declares one. **And, newly, `src/core/instrument/ui/CMakeLists.txt`** —
|
||||||
**textual merge adjacency, not semantic contention.** Whichever lands second rebases.
|
T1 declares the taper module and its test target, T3 declares the tessellation module and
|
||||||
|
its. All four are append-only additions in separate blocks — **textual merge adjacency, not
|
||||||
|
semantic contention.** Whichever lands second rebases.
|
||||||
|
|
||||||
**Two near-misses that are avoided by construction, and must stay avoided.** (a) T3's
|
**Three near-misses that are avoided by construction, and must stay avoided.**
|
||||||
tessellation helper **lands pure** (`ui/envelope_overlay` or a new pure module), *not* in
|
|
||||||
`editor_internal.h`, which T1 is editing — this also satisfies the phase's geometry-stays-pure
|
(a) **T3's tessellation helper lands in a NEW pure module — explicitly NOT
|
||||||
criterion, so it costs nothing. (b) T4 touches `deck_groups` but adds **only** the new row
|
`ui/envelope_overlay`, which T1 now owns**, and not in `editor_internal.h`, which T1 is also
|
||||||
predicate; it does not touch `sampleDeckGroups`, which W2-T1 and W3-T1 own in later waves, and
|
editing. The prior wording offered `envelope_overlay` as an option; Ruling 2 removed it,
|
||||||
it does not touch `deck_values`, which is T1's.
|
because T1's schematic-axis work rewrites that module's whole time→x map. This still
|
||||||
|
satisfies the phase's geometry-stays-pure criterion, so it costs nothing.
|
||||||
|
|
||||||
|
(b) **T1 and T3 are disjoint by file but coupled by data, and the coupling has a stated
|
||||||
|
resolution.** T1 owns where an AHDSR's vertices *land*; T3 owns the stroke *between*
|
||||||
|
vertices. T3's tessellation is over φ across a segment's pixel span, so the tapered axis
|
||||||
|
changes nothing about the curve it draws — **but T3's tests must assert against the returned
|
||||||
|
vertices, not against absolute pixel literals**, or they break when T1 lands. Whichever
|
||||||
|
track lands second rebases; expressing T3's assertions relatively makes that rebase free.
|
||||||
|
|
||||||
|
(c) **T4 touches `deck_groups` but adds only the new row predicate**; it does not touch
|
||||||
|
`sampleDeckGroups`, which W2-T1 and W3-T1 own in later waves, and it does not touch
|
||||||
|
`deck_values`, which is T1's.
|
||||||
|
|
||||||
|
**Two consumption boundaries worth stating, because they look like collisions and are not.**
|
||||||
|
T1 **consumes** `engine/master_gain`'s dB taper for its whole-dB snap and does not edit it;
|
||||||
|
T2 does not touch it either. And T2's payload rung is the wave's only format change —
|
||||||
|
T1's taper and ceiling changes are persistence-neutral by construction (the payload stores
|
||||||
|
raw engine doubles).
|
||||||
|
|
||||||
**Both of the phase's DSP unknowns are in this wave** — T2's limiter and T5's stretcher. That
|
**Both of the phase's DSP unknowns are in this wave** — T2's limiter and T5's stretcher. That
|
||||||
is deliberate: they are the two tracks whose gate can fail, and failing in wave 1 is
|
is deliberate: they are the two tracks whose gate can fail, and failing in wave 1 is
|
||||||
@@ -791,16 +949,38 @@ recoverable in a way that failing in the last wave is not.
|
|||||||
#### Γ-W1-T1 — `knob-interaction-law`
|
#### Γ-W1-T1 — `knob-interaction-law`
|
||||||
|
|
||||||
**Goal.** One consistent, unit-category-driven interaction and taper rule across every
|
**Goal.** One consistent, unit-category-driven interaction and taper rule across every
|
||||||
variable control, landed **before** any new control is added so the new ones are authored
|
variable control, **over a stage-time range raised 2 s → 10 s**, landed **before** any new
|
||||||
into it rather than retro-fitted.
|
control is added so the new ones are authored into it rather than retro-fitted — and before
|
||||||
|
any parameter is declared, so the law is what the host is handed rather than something the
|
||||||
|
host has to be reconciled with later.
|
||||||
|
|
||||||
**Spec:** `docs/product/instrument-control-surface.md` §4.
|
**Spec:** `docs/product/instrument-control-surface.md` §4, **§4.3.1 (the 10 s ceiling and the
|
||||||
|
overlay-legibility design — new, read it before scoping this track)**, and
|
||||||
|
`docs/product/parameter-automation.md` §8 (the one-way-door sweep this track discharges).
|
||||||
|
|
||||||
**Surface boundary — owns:** `core/instrument/ui/deck_values` (the taper maps, the
|
**Surface boundary — owns:** a **new pure taper module** under `core/instrument/ui/` (the
|
||||||
snap-unit table, `resetDeckParam`), `core/instrument/ui/param_slider` (the drag law),
|
ms/semitone/exponent maps, extracted so they have one home),
|
||||||
`shell/instrument/editor_input_*` (modifier read + re-anchor), and the modifier-reading
|
`core/instrument/ui/deck_values` (the bindings, the snap-unit table, `resetDeckParam`),
|
||||||
helper the three input paths share. **Does not own** any deck descriptor, any parameter, or
|
`core/instrument/ui/envelope_overlay` (the ceiling constant **and** the AHDSR schematic
|
||||||
the waveform painter.
|
axis) and `core/instrument/ui/envelope_edit` (its drag inverse),
|
||||||
|
`core/instrument/ui/param_slider` (the drag law), `shell/instrument/editor_input_*`
|
||||||
|
(modifier read + re-anchor), `shell/instrument/editor_controls.cpp`'s `envClampBounds`
|
||||||
|
**only** (it reads `kEnvTimeMaxSeconds`), and the modifier-reading helper the three input
|
||||||
|
paths share. **Does not own** any deck descriptor, any parameter, the waveform painter, or
|
||||||
|
`engine/master_gain` (consumed, not edited).
|
||||||
|
|
||||||
|
**Why this track does NOT split, asked and answered.** Ruling 2 makes it materially bigger —
|
||||||
|
tapers, modifiers, re-anchor, reset bypass, the ceiling, and the overlay's schematic scale.
|
||||||
|
Two splits were considered and both are **serial, not parallel**, so neither buys any
|
||||||
|
concurrency: an *interaction* half (modifiers, snap, re-anchor) needs the *domain* half's
|
||||||
|
taper and snap-unit table to exist first; and a standalone *overlay-axis* track needs the
|
||||||
|
taper module and the final ceiling before it can define a stage's slot width. Splitting
|
||||||
|
would therefore cost a wave and gain nothing, while putting the single most
|
||||||
|
identity-critical function in the phase across a wave boundary — the same function the host
|
||||||
|
will normalize against three waves later. **The seam that matters is internal and is a
|
||||||
|
deliverable: the taper is extracted into its own pure module**, which is what makes "the
|
||||||
|
taper IS the host-facing normalization" structurally true rather than a comment. The
|
||||||
|
~600-line ceiling is a per-file bar, and the extraction is what keeps every file under it.
|
||||||
|
|
||||||
**Behavior.**
|
**Behavior.**
|
||||||
- **Shift snaps to whole numbers in the control's displayed unit**; **Ctrl scales the drag by
|
- **Shift snaps to whole numbers in the control's displayed unit**; **Ctrl scales the drag by
|
||||||
@@ -817,9 +997,47 @@ the waveform painter.
|
|||||||
`(1 − 0.05) ×` the accumulated delta.
|
`(1 − 0.05) ×` the accumulated delta.
|
||||||
- **Millisecond knobs become log-scaled.** Exactly 0 s at norm 0 and exactly
|
- **Millisecond knobs become log-scaled.** Exactly 0 s at norm 0 and exactly
|
||||||
`kEnvTimeMaxSeconds` at norm 1, monotone throughout; **10 ms lands within 0.12–0.20 of
|
`kEnvTimeMaxSeconds` at norm 1, monotone throughout; **10 ms lands within 0.12–0.20 of
|
||||||
travel and 100 ms within 0.42–0.52**. The ceiling stays **2.0 s** — it reads
|
travel and 100 ms within 0.42–0.52**.
|
||||||
`kGateStageMaxSeconds`, which the AHDSR overlay's schematic scale is derived from, and the
|
- **The stage-time ceiling moves 2.0 s → 10.0 s** (Daniel, reversing Γ-F3):
|
||||||
two must agree.
|
`kGateStageMaxSeconds` (`envelope_overlay.h:85`) and, through it, `kEnvTimeMaxSeconds`
|
||||||
|
(`deck_values.h:22`). **The two move together or not at all** — `deck_values.h` reads the
|
||||||
|
overlay's constant rather than restating it precisely so they cannot drift
|
||||||
|
(`deck_values.h:19-22`). The taper's landmarks above are fit against the **new** ceiling,
|
||||||
|
which is why the ceiling cannot be a follow-up: fitting the taper twice is the only other
|
||||||
|
way to get there.
|
||||||
|
- **`resetDeckParam`'s bypass becomes MANDATORY rather than merely required-anyway.**
|
||||||
|
`deck_values.h:42-46` records that exact default recovery depends on the ceiling being a
|
||||||
|
power of two; **2.0 is, 10.0 is not**, and the log taper compounds it. Nothing here may be
|
||||||
|
"simplified" back into a norm round-trip under any circumstance.
|
||||||
|
- **NEW, and the sharpest requirement in the track: every default must have an EXACT
|
||||||
|
normalized preimage under its taper.** `ParameterInfo::defaultNormalizedValue` (Γ-W4-T1)
|
||||||
|
is normalized, so a host's reset-to-default arrives as `toPlain(defaultNorm)` — and **the
|
||||||
|
host has no `resetDeckParam` bypass to use**. The bypass fixes the editor's reset and
|
||||||
|
cannot fix the host's; only exactness in the map itself makes the two land on the same
|
||||||
|
value. This binds the taper's *shape*, so it belongs here and cannot be handed forward.
|
||||||
|
Master gain's unity (≈ 0.714 norm) is the case where a hair off is audible.
|
||||||
|
- **The AHDSR overlay's schematic axis becomes the taper — the ceiling's real cost, and it
|
||||||
|
is design work, not a constant change.** Each of the four timed stages gets an equal slot
|
||||||
|
and today maps seconds across it linearly (`gatePxPerSecond`,
|
||||||
|
`envelope_overlay.cpp:33-34`). At 2 s a 30 ms attack is 1.5 % of its stage's domain; **at
|
||||||
|
10 s it is 0.3 %, under a pixel at the floor width.** The fix: a stage's slot width becomes
|
||||||
|
`slotPx × taperNorm(seconds)` instead of `slotPx × seconds / ceiling`, so a node's position
|
||||||
|
within its slot **is** its knob's needle position. Legibility becomes ceiling-independent by
|
||||||
|
construction; the one-model invariant gets stronger rather than strained; and **the drawn
|
||||||
|
curve is unaffected**, because the taper decides only where a stage's end node lands while φ
|
||||||
|
still runs linearly across the stage's pixel span — so Γ-W1-T3's φ^p trace composes with it
|
||||||
|
rather than fighting it. **The AHD policy is untouched**: an AHD maps 1:1 onto the
|
||||||
|
waveform's own PCM-aligned time axis and stays linear in seconds. Two alternatives
|
||||||
|
(content-fit auto-scale; a minimum drawn stage width) were considered and rejected — spec
|
||||||
|
§4.3.1 names why, and neither is to be reintroduced as a "simplification."
|
||||||
|
- **`envelope_edit`'s drag inverse must remain the EXACT inverse of the draw.** Both read the
|
||||||
|
same taper module; a node dragged to a pixel and the knob's value at that pixel are one
|
||||||
|
number, not two that agree.
|
||||||
|
- **The taper is EXTRACTED into its own pure module**, with its own `<module>_tests` target,
|
||||||
|
because it now has three consumers in two different dependency layers: `deck_values` (which
|
||||||
|
sits above `envelope_overlay`), `envelope_overlay`/`envelope_edit` (which sit below it),
|
||||||
|
and — from Γ-W4-T1 — the host. Leaving it inside `deck_values` would force an inverted
|
||||||
|
include edge. **Do not solve that by copying the map.**
|
||||||
- **Semitone knobs become log2/centre-expanded.** Symmetric, exactly 0 at centre, exactly
|
- **Semitone knobs become log2/centre-expanded.** Symmetric, exactly 0 at centre, exactly
|
||||||
±`kPitchDepthMaxSemis` at the ends, monotone; **±7 st reached at 50–58 % of each
|
±`kPitchDepthMaxSemis` at the ends, monotone; **±7 st reached at 50–58 % of each
|
||||||
half-travel**.
|
half-travel**.
|
||||||
@@ -842,17 +1060,48 @@ the waveform painter.
|
|||||||
- Double-clicking any knob (outer ring and inner dial independently) lands **exactly** on its
|
- Double-clicking any knob (outer ring and inner dial independently) lands **exactly** on its
|
||||||
default at every taper, verified against a default-constructed `PlaySeconds` rather than a
|
default at every taper, verified against a default-constructed `PlaySeconds` rather than a
|
||||||
round trip.
|
round trip.
|
||||||
- The log/log2 landmark positions above are asserted in `deck_values`' own tests.
|
- The log/log2 landmark positions above are asserted in the **taper module's** own tests, and
|
||||||
|
hold at the **10 s** ceiling — the fit is against the new ceiling, not the old one.
|
||||||
|
- **Every default round-trips exactly through `norm → value`**, asserted per unit category
|
||||||
|
against a default-constructed `PlaySeconds` and against `master_gain`'s unity. This is the
|
||||||
|
criterion Γ-W4-T1 will declare `defaultNormalizedValue` from; it fails here, not there.
|
||||||
|
- **A stage time of several seconds is reachable by hand with no loss of resolution below
|
||||||
|
100 ms**, and `kEnvTimeMaxSeconds == kGateStageMaxSeconds` is asserted, not assumed.
|
||||||
|
- **A project saved at the 2 s ceiling reloads with identical stored seconds and identical
|
||||||
|
audio** — the ceiling change is persistence-neutral for the same reason the taper is.
|
||||||
|
- **The AHDSR overlay reads legibly at both ends of the new range**: a default 3 ms attack is
|
||||||
|
a visible, grabbable node at the floor width, and a 10 s decay still lands its end node at
|
||||||
|
its slot's edge. Assert the node separation, then judge the result by eye in the DAW.
|
||||||
|
- **The overlay's drag inverse is the exact inverse of its draw** at the tapered axis —
|
||||||
|
`nodeAtPoint` / `resolveNodeDrag` and `buildEnvelopePolyline` round-trip.
|
||||||
|
- **The AHD 1:1 policy is unchanged**, asserted: a sustain-less envelope's x-axis stays
|
||||||
|
wall-clock over the waveform.
|
||||||
|
- **The taper module is pure, CTest-covered, and is the ONLY definition of each map** — a
|
||||||
|
grep finds no second copy in `deck_values`, `envelope_overlay`, or the shell.
|
||||||
|
- **The filter's four `*Norm` controls are untouched by the taper pass** — cutoff, Q, morph
|
||||||
|
and drive are already wire-frozen in payload v9; a regression baseline proves their audio
|
||||||
|
is unchanged.
|
||||||
- One shared modifier-read helper serves all drag surfaces; no second modifier grammar exists.
|
- One shared modifier-read helper serves all drag surfaces; no second modifier grammar exists.
|
||||||
|
|
||||||
**Open questions.** None **[Daniel]** — fork Γ-F3 is ruled: **the ceiling stays 2.0 s.** The
|
**Open questions.**
|
||||||
10 s ambition Daniel described (*"a horrifically long decay with tight exp"*) is carried as a
|
- **No [Daniel] questions.** Fork **Γ-F3 is REVERSED**: the ceiling moves to **10.0 s, in this
|
||||||
`docs/TODO.md` entry, and **this track lands both of its prerequisites**: the log taper (which
|
track.** Daniel's *"a horrifically long decay with tight exp"* is the case it serves, and
|
||||||
is what makes a higher ceiling usable at the low end rather than unusable) and the reset
|
the `docs/TODO.md` entry that carried it is discharged rather than deferred again. **The
|
||||||
bypass (which retires the power-of-two dependency — 2.0 is a power of two, 10.0 is not).
|
reversal's cause is Ruling 1** — a range endpoint is host-facing normalization, free to
|
||||||
**Neither is optional on that basis alone** — they are already required by this track — but
|
move now and permanently expensive after Γ-W4-T1. Both of the prerequisites the deferred
|
||||||
the engineer should know the reset bypass is doing double duty, and should not "simplify" it
|
entry named are in this track anyway: the log taper (which is what makes a higher ceiling
|
||||||
back into a norm round-trip.
|
usable at the low end rather than unusable) and the reset bypass (which retires the
|
||||||
|
power-of-two dependency — 2.0 is a power of two, 10.0 is not). The engineer should know the
|
||||||
|
reset bypass is now doing triple duty and must not be "simplified" back into a norm
|
||||||
|
round-trip under any circumstance.
|
||||||
|
- **[propose at review]** the exact shape of the taper, subject to the landmark bounds **and**
|
||||||
|
the exact-default-preimage requirement. Those two together are tighter than either alone,
|
||||||
|
and the second is easy to satisfy by accident and easy to lose in a refactor — **assert it,
|
||||||
|
do not observe it.**
|
||||||
|
- **[propose at review]** whether the tapered schematic axis wants a visible tick or
|
||||||
|
gradation cue, now that it is no longer linear in time. The plan's lean is **no** — the ms
|
||||||
|
labels carry the number and the editor's no-decoration policy stands — but a reader who
|
||||||
|
finds the axis illegible in the DAW should say so rather than silently adding one.
|
||||||
|
|
||||||
#### Γ-W1-T2 — `master-bus-audio`
|
#### Γ-W1-T2 — `master-bus-audio`
|
||||||
|
|
||||||
@@ -867,9 +1116,12 @@ first; it was rewritten when Γ-F6 closed, so an older reading of it is wrong)**
|
|||||||
**Surface boundary — owns:** a new pure limiter module and a new pure meter-ballistics
|
**Surface boundary — owns:** a new pure limiter module and a new pure meter-ballistics
|
||||||
module under `core/instrument/engine/` (each with its own `<module>_tests` target),
|
module under `core/instrument/engine/` (each with its own `<module>_tests` target),
|
||||||
`shell/instrument/reasampler_processor` (the chain, the published block state, **and the
|
`shell/instrument/reasampler_processor` (the chain, the published block state, **and the
|
||||||
`getLatencySamples` / `restartComponent(kLatencyChanged)` path**), and **params payload v14**
|
`getLatencySamples` / `restartComponent(kLatencyChanged)` path**), and **the phase's FIRST
|
||||||
(the limiter enable flag). **Does not own** MASTER's deck geometry or any drawing — that is
|
params-payload rung** (the limiter enable flag). **Does not own** MASTER's deck geometry or
|
||||||
Γ-W3-T1.
|
any drawing — that is Γ-W3-T1. **Read `kParamsPayloadVersion` on `dev` and take the next rung
|
||||||
|
above it rather than assuming the number** — Phase Ξ ran ahead of this plan's sequencing, so
|
||||||
|
the plan is not the record of what the ladder currently carries. On `dev` as of 2026-08-01
|
||||||
|
that resolves to **v14**.
|
||||||
|
|
||||||
**Behavior.**
|
**Behavior.**
|
||||||
- **Chain:** `voice mixer → master gain (existing ramped multiply) → limiter (bypassable) →
|
- **Chain:** `voice mixer → master gain (existing ramped multiply) → limiter (bypassable) →
|
||||||
@@ -936,8 +1188,8 @@ module under `core/instrument/engine/` (each with its own `<module>_tests` targe
|
|||||||
- **Meter ballistics (pure, unit-tested):** instantaneous rise; **fall 20 dB/s**; peak-hold
|
- **Meter ballistics (pure, unit-tested):** instantaneous rise; **fall 20 dB/s**; peak-hold
|
||||||
latched at the running max, **held 1.5 s**, then falling at the same rate; scale **linear in
|
latched at the running max, **held 1.5 s**, then falling at the same rate; scale **linear in
|
||||||
dB over −60…+6 dBFS**; clip latches at block peak ≥ 0 dBFS and is cleared on request.
|
dB over −60…+6 dBFS**; clip latches at block peak ≥ 0 dBFS and is cleared on request.
|
||||||
- **`ComponentState` payload v14** appends the limiter flag as a strict suffix on the existing
|
- **The `ComponentState` payload rung** appends the limiter flag as a strict suffix on the
|
||||||
discipline; a v13 blob is a strict prefix and lifts to bypassed.
|
existing discipline; the preceding version's blob is a strict prefix and lifts to bypassed.
|
||||||
|
|
||||||
**Acceptance criteria.**
|
**Acceptance criteria.**
|
||||||
- **With the limiter bypassed the rendered output is byte-identical to the pre-change build**,
|
- **With the limiter bypassed the rendered output is byte-identical to the pre-change build**,
|
||||||
@@ -982,8 +1234,16 @@ mid-segment knot stops floating off its own trace.
|
|||||||
**Spec:** `docs/product/instrument-control-surface.md` §5.
|
**Spec:** `docs/product/instrument-control-surface.md` §5.
|
||||||
|
|
||||||
**Surface boundary — owns:** `shell/instrument/editor_paint_waveform.cpp`'s staged-envelope
|
**Surface boundary — owns:** `shell/instrument/editor_paint_waveform.cpp`'s staged-envelope
|
||||||
trace and any pure tessellation helper it needs. **Does not own** the loop/crossfade marks
|
trace and a **new pure tessellation module** for it. **Does not own** the loop/crossfade
|
||||||
(Γ-W2-T2), `envelope_overlay`'s vertex model, or the drawn-EG (spline) trace.
|
marks (Γ-W2-T2), `envelope_overlay`'s vertex model, or the drawn-EG (spline) trace.
|
||||||
|
|
||||||
|
**The helper's home is now constrained, not a choice.** `ui/envelope_overlay` was previously
|
||||||
|
offered as a candidate home for the tessellation helper; **Γ-W1-T1 now owns that module**
|
||||||
|
(the AHDSR schematic axis, per Ruling 2), so the helper lands in a **new** pure module under
|
||||||
|
`core/instrument/ui/`. T1 also owns where an AHDSR's vertices land — this track owns only the
|
||||||
|
stroke *between* vertices, and tessellates over φ across a segment's pixel span, so the
|
||||||
|
tapered axis changes nothing about the curve drawn. **Express this track's assertions against
|
||||||
|
the returned vertices, not against absolute pixel literals**, and the rebase onto T1 is free.
|
||||||
|
|
||||||
**Behavior.** The defect is verified: `editor_paint_waveform.cpp:218` drops knots
|
**Behavior.** The defect is verified: `editor_paint_waveform.cpp:218` drops knots
|
||||||
(`if (v.knot) continue;`) and joins the remaining vertices with straight strokes, and
|
(`if (v.knot) continue;`) and joins the remaining vertices with straight strokes, and
|
||||||
@@ -1218,10 +1478,18 @@ are otherwise single-writer surfaces, so it is stated rather than discovered at
|
|||||||
track lands second rebases onto the first. No *new* in-wave adjacency was created by the
|
track lands second rebases onto the first. No *new* in-wave adjacency was created by the
|
||||||
resequencing: T2 touches neither `deck_values` nor `deck_groups` nor `voice`.
|
resequencing: T2 touches neither `deck_values` nor `deck_groups` nor `voice`.
|
||||||
|
|
||||||
|
**One cross-wave hand-off, new with Ruling 2 and NOT a contention.** Rate's taper — linear in
|
||||||
|
semitones over ±12, the stated exception to the centre-expansion law — belongs in the **taper
|
||||||
|
module Γ-W1-T1 extracts**, since that module is the one home of every map. T1 therefore
|
||||||
|
appends a law to a module a previous wave created. Serial across waves by construction, the
|
||||||
|
same shape as its `engine/voice.{h,cpp}` hand-off from W1-T5. **What would be wrong is a
|
||||||
|
second taper defined inside `deck_values`' binding** — one home, appended to, not forked.
|
||||||
|
|
||||||
**The format ladder stays clean.** The loop enable maps onto the existing
|
**The format ladder stays clean.** The loop enable maps onto the existing
|
||||||
`SampleLoop::hasLoop`, which is already persisted and whose `start`/`end` are already written
|
`SampleLoop::hasLoop`, which is already persisted and whose `start`/`end` are already written
|
||||||
unconditionally — **no new field, no version bump** — so T1 keeps sole ownership of payload v15
|
unconditionally — **no new field, no version bump** — so T1 keeps sole ownership of **the
|
||||||
exactly as specced.
|
phase's second payload rung** exactly as specced (v15 on `dev` as of 2026-08-01; read the
|
||||||
|
ladder rather than assuming the number).
|
||||||
|
|
||||||
#### Γ-W2-T1 — `pitch-rate-deck`
|
#### Γ-W2-T1 — `pitch-rate-deck`
|
||||||
|
|
||||||
@@ -1232,7 +1500,8 @@ existing Varisp|Presrv toggle, with both new controls wired through the engine.
|
|||||||
|
|
||||||
**Surface boundary — owns:** `core/instrument/engine/play_params.h` +
|
**Surface boundary — owns:** `core/instrument/engine/play_params.h` +
|
||||||
`core/instrument/map/play_seconds.h` (the two new fields),
|
`core/instrument/map/play_seconds.h` (the two new fields),
|
||||||
`core/instrument/map/component_state_io` + `params_payload` (**payload v15**),
|
`core/instrument/map/component_state_io` + `params_payload` (**the phase's SECOND payload
|
||||||
|
rung** — v15 on `dev` as of 2026-08-01; read the ladder, do not assume the number),
|
||||||
`core/instrument/engine/voice.{h,cpp}` (the compounding and the note-on latch),
|
`core/instrument/engine/voice.{h,cpp}` (the compounding and the note-on latch),
|
||||||
`core/instrument/ui/deck_groups` (the PITCH/RATE descriptor **and** the three-state live
|
`core/instrument/ui/deck_groups` (the PITCH/RATE descriptor **and** the three-state live
|
||||||
predicate), `core/instrument/ui/deck_values` (the two new bindings). **Does not own** the
|
predicate), `core/instrument/ui/deck_values` (the two new bindings). **Does not own** the
|
||||||
@@ -1273,8 +1542,12 @@ by the time this track runs).
|
|||||||
caption row overtakes the 180 px knob row and the group exceeds 192). If the text will not
|
caption row overtakes the 180 px knob row and the group exceeds 192). If the text will not
|
||||||
fit at 80, narrow the `Varisp|Presrv` segments 48 → 44 (ceiling becomes 88) — **do not widen
|
fit at 80, narrow the `Varisp|Presrv` segments 48 → 44 (ceiling becomes 88) — **do not widen
|
||||||
the group**.
|
the group**.
|
||||||
- **Payload v15** appends both fields as a strict suffix; a v14 blob lifts to rate 100 % /
|
- **The payload rung** appends both fields as a strict suffix; the preceding version's blob
|
||||||
pitch 0 st, bit-identical playback.
|
lifts to rate 100 % / pitch 0 st, bit-identical playback.
|
||||||
|
- **Both new `DeckParam`s are classified in the three-state predicate, and that classification
|
||||||
|
is what puts them in the VST3 parameter list three waves later** — Rate `NoteOnLatched`,
|
||||||
|
Pitch `Live`. Γ-W4-T1 derives the exposed set from this predicate rather than from a list
|
||||||
|
of its own, so a mis-classification here is a mis-declared parameter there.
|
||||||
|
|
||||||
**Acceptance criteria.**
|
**Acceptance criteria.**
|
||||||
- Rate at 50 % plays an octave down and half speed under Varispeed; at 200 %, an octave up and
|
- Rate at 50 % plays an octave down and half speed under Varispeed; at 200 %, an octave up and
|
||||||
@@ -1293,7 +1566,8 @@ by the time this track runs).
|
|||||||
waveform marker.
|
waveform marker.
|
||||||
- The PITCH/RATE group measures **exactly 192 px**; adding the two `DeckParam`s produces a
|
- The PITCH/RATE group measures **exactly 192 px**; adding the two `DeckParam`s produces a
|
||||||
compile error in `isLiveDeckParam`'s exhaustive switch until they are classified.
|
compile error in `isLiveDeckParam`'s exhaustive switch until they are classified.
|
||||||
- A v14 project reopens at rate 100 % / pitch 0 st and sounds identical.
|
- A project saved at the preceding payload version reopens at rate 100 % / pitch 0 st and
|
||||||
|
sounds identical.
|
||||||
|
|
||||||
**Open questions.**
|
**Open questions.**
|
||||||
- **None [Daniel].**
|
- **None [Daniel].**
|
||||||
@@ -1449,21 +1723,40 @@ track changes drawing, hit-testing and one editor-state retention rule — no fo
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Γ-W3 — The reflow
|
### Γ-W3 — The reflow, and the bake correction
|
||||||
|
|
||||||
**Depends on Γ-W2 for:** the PITCH/RATE descriptor (W2-T1) — the reflow measures the real
|
**Depends on Γ-W2 for:** the PITCH/RATE descriptor (W2-T1) — the reflow measures the real
|
||||||
three-cell group, and laying it out against a forecast of that group means re-measuring
|
three-cell group, and laying it out against a forecast of that group means re-measuring
|
||||||
afterward. **This is the whole reason the arrangement is late**, and it is why the canvas was
|
afterward. **This is the whole reason the arrangement is late**, and it is why the canvas was
|
||||||
split out of it into W1-T4.
|
split out of it into W1-T4. **T2 depends on the same wave for a different reason:** rate and
|
||||||
|
pitch offset must exist before the bake's reset list can name them.
|
||||||
|
|
||||||
**Depends on Γ-W1 for:** W1-T2's published meter/GR/clip state, which MASTER's deck draws
|
**Depends on Γ-W1 for:** W1-T2's published meter/GR/clip state, which MASTER's deck draws
|
||||||
(drawing against a stub would mean building the meter twice), and W1-T4's floor, budget
|
(drawing against a stub would mean building the meter twice), and W1-T4's floor, budget
|
||||||
constants and row predicate, which this track **consumes rather than re-derives**.
|
constants and row predicate, which T1 **consumes rather than re-derives**. **T2 depends on
|
||||||
|
W1-T2 for the limiter enable flag**, the third of the three values it must add.
|
||||||
|
|
||||||
**One track.** The row law, the group inventory and the double-height deck are one geometry
|
**Depends on Phase Ξ for T2 — the phase's only EXTERNAL gate.** `Ξ-W2-T1
|
||||||
decision spread over `knob_deck`, `deck_groups` and the deck painter. Splitting it would put two
|
(resample-bake-chain)` must have landed on `dev` before T2 dispatches. T2 amends what that
|
||||||
tracks in the same pure modules. **The window floor is no longer part of it** — W1-T4 set it
|
track shipped; it cannot amend a branch.
|
||||||
two waves ago, and this track must not move it.
|
|
||||||
|
**Two tracks, disjoint by surface — but T2's disjointness is CONDITIONAL and must be
|
||||||
|
confirmed, not assumed.** T1 owns `ui/knob_deck`, `ui/deck_groups` (row-predicate
|
||||||
|
consumption, FILTER's caption move, MASTER's inventory) and `shell/instrument/editor_paint_deck`.
|
||||||
|
T2 owns the bake's reset step wherever Ξ-W2-T1 put it. **T2's first act is to read what
|
||||||
|
actually shipped and confirm its reset surface touches none of T1's three modules.** If the
|
||||||
|
shipped reset enumerates controls through `deck_groups` or `deck_values`, the two are not
|
||||||
|
disjoint and **T2 serializes behind T1 inside the wave** — a named contingency, taken openly,
|
||||||
|
not discovered at merge. That risk is real precisely because this plan cannot predict the
|
||||||
|
shipped shape; predicting it is what put the phase in this position.
|
||||||
|
|
||||||
|
**Why T1 is one track.** The row law, the group inventory and the double-height deck are one
|
||||||
|
geometry decision spread over `knob_deck`, `deck_groups` and the deck painter. Splitting it
|
||||||
|
would put two tracks in the same pure modules. **The window floor is no longer part of it** —
|
||||||
|
W1-T4 set it two waves ago, and this track must not move it.
|
||||||
|
|
||||||
|
**Neither track takes a payload rung.** T1 is layout only; T2 changes a reset list, not a
|
||||||
|
format.
|
||||||
|
|
||||||
#### Γ-W3-T1 — `deck-reflow`
|
#### Γ-W3-T1 — `deck-reflow`
|
||||||
|
|
||||||
@@ -1572,6 +1865,226 @@ waveform band.
|
|||||||
is on row 2 (AMP binds at 55). No change is required; the comment stating the old ceiling
|
is on row 2 (AMP binds at 55). No change is required; the comment stating the old ceiling
|
||||||
stops being true and must be corrected (spec §7.2).
|
stops being true and must be corrected (spec §7.2).
|
||||||
|
|
||||||
|
#### Γ-W3-T2 — `bake-reset-amendment`
|
||||||
|
|
||||||
|
**Goal.** Complete the resample bake's reset list against the control surface that now
|
||||||
|
exists — the correction Phase Γ owes Phase Ξ because Ξ-W2-T1 shipped ahead of the sequencing
|
||||||
|
this plan asserted.
|
||||||
|
|
||||||
|
**Consolidates:** nothing from the seventeen. It is a **correction obligation**, not a
|
||||||
|
feature (see "Flagged for awareness" item 2).
|
||||||
|
|
||||||
|
**Spec:** `docs/product/instrument-control-surface.md` §3.4, and Ξ-W2-T1's own "Reset scope"
|
||||||
|
block above — **which is the ratified rule this track applies, not a rule it may reinterpret.**
|
||||||
|
|
||||||
|
**Surface boundary — owns:** the bake's parameter-reset step, wherever Ξ-W2-T1 landed it, and
|
||||||
|
its tests. **Does not own** the bake chain, the crossing architecture, the replace-vs-add
|
||||||
|
decision, the capture path, any deck module, any painter, any parameter, or any
|
||||||
|
`ComponentState` version. **It changes what a shipped list contains — nothing else.**
|
||||||
|
|
||||||
|
**Behavior.**
|
||||||
|
- **Three values join the reset list**, all classified against Ξ-W2's own ratified rule
|
||||||
|
("reset what the bake baked in"), all **reset**, none of them a new Daniel decision:
|
||||||
|
**rate**, **pitch offset**, and **limiter enabled**. The limiter's reasoning is worth
|
||||||
|
carrying rather than re-deriving: master gain is already on the reset list, so the bake
|
||||||
|
includes the master stage, so the limiter's effect is in the audio.
|
||||||
|
- **Verified against what shipped, not against what was predicted.** This plan named three
|
||||||
|
values before either the bake or the controls existed. **Read Ξ-W2-T1's landed reset list
|
||||||
|
first** and reconcile: if it already anticipated any of the three, say so and drop it; if
|
||||||
|
it classified something differently from `docs/product/instrument-control-surface.md` §3.4,
|
||||||
|
**the landed code is the fact and this plan is the prediction** — escalate the difference,
|
||||||
|
do not silently overwrite either.
|
||||||
|
- **Re-run Ξ-W2-T1's own "genuinely new parameter" check over everything Phase Γ added**, not
|
||||||
|
just the three named. Γ also ships the loop enable (W2-T2) and raises the stage-time
|
||||||
|
ceiling (W1-T1). Classify each **against the rule**: the loop enable is a loop fact whose
|
||||||
|
effect is in the rendered audio (**reset**, with the loop points it travels with); the
|
||||||
|
ceiling is not a parameter at all. State each disposition; silence is not one.
|
||||||
|
- **Root note still survives.** The bake's most load-bearing exception is untouched by this
|
||||||
|
track — capturing at root is what makes root survivable, and resetting it would detune
|
||||||
|
every subsequent iteration.
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- After a bake, **rate reads 100 %, pitch offset 0 st, and the limiter reads bypassed** — and
|
||||||
|
the root note, key-tracking and the VOICE group are still untouched.
|
||||||
|
- **The bake stays audible and faithful with the new controls dialled in**: dial rate, pitch
|
||||||
|
offset and the limiter, bake, and the neutral instrument playing the programmed note sounds
|
||||||
|
as the dialled one did — the criterion Ξ-W2-T1 already carries, now actually exercised over
|
||||||
|
Γ's controls.
|
||||||
|
- **A reconciliation note in the track's review** stating, per value, whether the landed code
|
||||||
|
already covered it, and recording any difference between what shipped and what §3.4
|
||||||
|
predicted.
|
||||||
|
- **No format change, no new field, no version bump, no change to the crossing architecture
|
||||||
|
or the replace-vs-add decision.** A regression baseline proves the bake's audio is
|
||||||
|
otherwise unchanged.
|
||||||
|
|
||||||
|
**Open questions.**
|
||||||
|
- **No [Daniel] questions.** The rule is ratified and §3.4's classification is derived from
|
||||||
|
it.
|
||||||
|
- **[verify, FIRST]** the disjointness contingency in the wave header: read the shipped reset
|
||||||
|
step and confirm it touches none of Γ-W3-T1's modules. If it does, serialize behind T1 and
|
||||||
|
say so.
|
||||||
|
- **Explicitly NOT this track's:** the two consequences automation adds to the bake — the
|
||||||
|
reset having to notify the host, and a host lane re-imposing its curve onto baked audio.
|
||||||
|
Both are **Γ-W4-T1's**, because that track creates them. Doing this correction once, before
|
||||||
|
automation, and letting Γ-W4-T1 add its own obligation on top is deliberate: the
|
||||||
|
alternative is an amendment to an amendment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Γ-W4 — VST3 parameters
|
||||||
|
|
||||||
|
**Depends on every earlier wave, and each dependency is a hard prerequisite rather than a
|
||||||
|
courtesy:**
|
||||||
|
|
||||||
|
1. **← W1-T1.** The taper and the 10 s ceiling **are** the host-facing normalization, and
|
||||||
|
W1-T1 is also what extracts them into the one pure module the host reads through. Declaring
|
||||||
|
parameters against a taper that is still moving is the one-way door this whole phase is
|
||||||
|
ordered around.
|
||||||
|
2. **← W1-T2 and W2-T1.** Every control that could be a parameter must exist before the list
|
||||||
|
is declared. The list is derived from the control inventory; an inventory still growing
|
||||||
|
produces a list that has to be re-frozen, and it cannot be.
|
||||||
|
3. **← W2-T1 specifically.** `isLiveDeckParam` becoming three-valued is the *prerequisite* of
|
||||||
|
the classification, not an incidental: the exposed set is exactly `Live ∪ NoteOnLatched`.
|
||||||
|
4. **← W3-T1.** MASTER's inventory (limiter toggle, GR bubble, reserved cell) is the last
|
||||||
|
change to what controls exist at all.
|
||||||
|
5. **← W3-T2.** The bake's reset list must already be complete, so this track adds the
|
||||||
|
host-notification obligation once rather than amending an amendment.
|
||||||
|
|
||||||
|
**One track.** The storage decision governs every part of the work — the projection rule, the
|
||||||
|
migration path, what `getParamNormalized` returns, and what the bake's reset must do — exactly
|
||||||
|
as Ξ-W2-T1's crossing decision governs its chain. Every candidate split (a pure
|
||||||
|
model/classification half and a host-wiring half) is **serial**, so it buys no concurrency and
|
||||||
|
puts the decision on one side of a boundary and its consequences on the other.
|
||||||
|
|
||||||
|
#### Γ-W4-T1 — `vst3-parameter-set`
|
||||||
|
|
||||||
|
**Goal.** The instrument reports its automatable parameters to the host, under a frozen id
|
||||||
|
contract and a logical order — which also hands it REAPER's whole per-parameter modulation
|
||||||
|
block (LFO, envelope follower, MIDI link, parameter linking) for free.
|
||||||
|
|
||||||
|
**Consolidates:** nothing from the seventeen. **Ruling 1** (Daniel, 2026-08-01): *"correct the
|
||||||
|
phase gamma plan to account for complying with the VST3 standard for parameter reporting… by
|
||||||
|
the end of gamma we have the automatable params reported. Make the parameter order logical."*
|
||||||
|
|
||||||
|
**Spec:** `docs/product/parameter-automation.md` — **§§6–10 are the specification; §§1–5 are
|
||||||
|
the analysis behind it.** Read §6.1 (storage), §6.3 (the freeze), §7 (the classification) and
|
||||||
|
§8 (the one-way-door sweep) before scoping. **Today the plugin has zero parameters:**
|
||||||
|
`ReaSamplerProcessor::initialize` (`reasampler_processor.cpp:56-73`) never populates
|
||||||
|
`SingleComponentEffect::parameters`, so `getParameterCount()` returns the SDK default 0. This
|
||||||
|
track introduces the whole surface.
|
||||||
|
|
||||||
|
**Surface boundary — owns:** a **new pure parameter-identity module** (the frozen id table,
|
||||||
|
the `DeckParam` ↔ `ParamID` mapping, the derived exposed set, the unit assignment — with its
|
||||||
|
own `<module>_tests` target), `shell/instrument/reasampler_processor` + `processor_state` (the
|
||||||
|
`IEditController` parameter surface and the `IParameterChanges` read),
|
||||||
|
`shell/instrument/editor_controls.cpp` and the editor's drag-commit sites (the
|
||||||
|
`beginEdit`/`performEdit`/`endEdit` bracketing), and the bake's reset step **for the
|
||||||
|
notification path only**. **Does not own** the taper (W1-T1's module, consumed), any control's
|
||||||
|
value semantics, any deck geometry, or the bake's reset *membership* (W3-T2's).
|
||||||
|
|
||||||
|
**Behavior.**
|
||||||
|
- **The blob stays authoritative; a parameter is a THIRD SURFACE onto the one model** —
|
||||||
|
a peer of the deck knob and the overlay node, not a second copy of the value.
|
||||||
|
`docs/product/parameter-automation.md` §6.1 states the load, host→plugin, plugin→host and
|
||||||
|
save rules, and the two verified findings that closed the fork: this plugin is a
|
||||||
|
`SingleComponentEffect`, where the SDK itself collapses `IComponent::setState` and
|
||||||
|
`IEditController::setState` (`vstsinglecomponenteffect.h:41-47`), so there is one state and
|
||||||
|
§3.3's drift hazard describes a split-component design we do not use; and the blob is a
|
||||||
|
**cross-artifact contract** the extension's `instrument_drop` writes, which
|
||||||
|
parameters-as-truth would silently make partial.
|
||||||
|
- **`ParamID` is an independent, hand-assigned, FOREVER-FROZEN table** — blocks of 100 per
|
||||||
|
deck group, steps of 10 within a block, a curve dial at its outer knob's id + 1, blocks
|
||||||
|
starting at 1000. §6.2 for the layout and why hand-assignment beats derivation; **§6.3 for
|
||||||
|
the freeze invariant, which is to be stated in the table's header with the same force as
|
||||||
|
the command-id strings, the class UIDs and the payload field order.**
|
||||||
|
- **The exposed set is DERIVED from `isLiveDeckParam` / `liveCommitFor`, never
|
||||||
|
hand-maintained** — a control is a parameter iff its class is `Live` or `NoteOnLatched`.
|
||||||
|
**44 parameters** at the end of Γ-W3, enumerated by group in §7.1.
|
||||||
|
- **Everything else is OMITTED from the list entirely**, not exposed-and-flagged: the reload
|
||||||
|
and rebuild tiers, all structural state, and the limiter enable (§3.8, settled). §7.2
|
||||||
|
states why omission beats `kIsReadOnly`, and names the limitation plainly — the user cannot
|
||||||
|
automate filter on/off, play mode, the pitch engine, Staged↔Spline or polyphony, and the
|
||||||
|
unlock is to give the control a live path first.
|
||||||
|
- **`normalizedParamToPlain` / `plainParamToNormalized` / `getParamStringByValue` /
|
||||||
|
`getParamValueByString` route through W1-T1's taper module and the editor's own
|
||||||
|
formatters.** Three functions that agree today is a defect; the host's normalization, the
|
||||||
|
needle angle and the overlay node must be the same function.
|
||||||
|
- **`IParameterChanges` is observed at BLOCK boundaries, stated in the header** — the last
|
||||||
|
point in a block wins. Sample-accurate application would put a per-sample "did anything
|
||||||
|
change" question on the per-voice-per-sample path, which the phase-wide guardrail forbids.
|
||||||
|
- **A host parameter change takes the control's existing commit tier and no other.** Nothing
|
||||||
|
on the automation path may reach `reloadInstrument` or `rebuildVoiceEngine` — which §7.2's
|
||||||
|
omissions guarantee structurally rather than by care.
|
||||||
|
- **`IUnitInfo`: one unit per deck group.** Order per **Γ-F7** (§6.4) — **recommendation
|
||||||
|
signal flow; Daniel's call, and it must be closed before this track dispatches.**
|
||||||
|
- **No `kIsBypass` on anything.** The plugin is an instrument and exposes no bypass
|
||||||
|
parameter; the limiter is a safety device, not a bypass, and binding it there would hand
|
||||||
|
the host a control that restarts the component.
|
||||||
|
- **The bake's reset gains a notification obligation** (§9): every internal writer of a value
|
||||||
|
that is an exposed parameter must go through the one `beginEdit`/`performEdit`/`endEdit`
|
||||||
|
path, and the bake's reset is the codebase's first non-gesture writer. **Enumerating those
|
||||||
|
sites is part of this track**, not a follow-up.
|
||||||
|
- **Two adjacent SDK surfaces are assessed, with dispositions, so they are not re-surveyed:**
|
||||||
|
`IMidiMapping` is **in scope and nearly free** (a CC → `ParamID` map, one function);
|
||||||
|
`IParameterFunctionName` is **not implemented** (its vocabulary is compressor/panner
|
||||||
|
semantics that name nothing here); `IAutomationState` is **not implemented** (it reports the
|
||||||
|
host's automation mode for the whole plug-in, not per parameter, so it cannot answer the one
|
||||||
|
question §9 would have wanted it for).
|
||||||
|
|
||||||
|
**Acceptance criteria.**
|
||||||
|
- **The host lists exactly the derived set, in the ruled order, with no parameter the
|
||||||
|
predicate does not classify `Live` or `NoteOnLatched`** — asserted against the predicate,
|
||||||
|
not against a literal count.
|
||||||
|
- **Every id in the table is asserted unique, in its group's block, and on its step** — and a
|
||||||
|
test fails if any id changes value, which is what makes the freeze mechanical rather than
|
||||||
|
cultural.
|
||||||
|
- **Every parameter round-trips `plain → normalized → plain` exactly at its default**, so a
|
||||||
|
host's reset-to-default and the editor's double-click land on the same value. If this fails,
|
||||||
|
it is a W1-T1 defect surfacing here, not a defect of this track.
|
||||||
|
- **`getParamStringByValue` prints what the editor prints**, unit for unit, at the same
|
||||||
|
values.
|
||||||
|
- **A host automation lane moving a Live parameter moves a sounding note; a lane moving a
|
||||||
|
NoteOnLatched parameter takes effect on the next note and does NOT trigger a reload or an
|
||||||
|
engine rebuild** — assert the tier, not just the sound.
|
||||||
|
- **No automation path reaches `reloadInstrument` or `rebuildVoiceEngine`.**
|
||||||
|
- **A project saved before this change opens with every parameter reading the blob's value
|
||||||
|
and sounds identical**; a project saved by this build opens in an older binary with its
|
||||||
|
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.
|
||||||
|
- **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
|
||||||
|
reset-class parameter carries a host lane is a named boundary of the bake's fidelity claim
|
||||||
|
(§9), stated in the product doc and in this track's review.
|
||||||
|
|
||||||
|
**Open questions.**
|
||||||
|
- **[Daniel] — Γ-F7, the parameter order.** Signal flow (recommended) or the editor's visual
|
||||||
|
rows. **This is the only unanswered [Daniel]-class question in this plan.** It blocks
|
||||||
|
nothing until this track dispatches, and it cannot be closed by proposal at review: a
|
||||||
|
forever commitment is Daniel's. §6.4.
|
||||||
|
- **[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.**
|
||||||
|
- **[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.
|
||||||
|
- **[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
|
||||||
|
nothing new is persisted and therefore it should not; if the `setState` verification says
|
||||||
|
otherwise, it takes the reserved rung and says so.
|
||||||
|
- **Closed, do not reopen:** Rate lifted from latched to live (§3.5 records the cost); the
|
||||||
|
limiter enable made automatable (§3.8 — its one reopening condition is the `docs/TODO.md`
|
||||||
|
reload/activation decoupling, and the answer is to do that first, not to re-litigate the
|
||||||
|
classification).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Traceability — all seventeen items
|
## Traceability — all seventeen items
|
||||||
@@ -1613,13 +2126,20 @@ proof it exists to give.
|
|||||||
from `TODO-1.0.md`, and not a track this plan originally scoped. The second such track
|
from `TODO-1.0.md`, and not a track this plan originally scoped. The second such track
|
||||||
in this plan today; if others appear, they belong on this list rather than in the
|
in this plan today; if others appear, they belong on this list rather than in the
|
||||||
table.
|
table.
|
||||||
- **All of Phase Γ** (`pg-*`). Eight tracks across three waves, from a direct interview with
|
- **All of Phase Γ** (`pg-*`). **Ten tracks across four waves**, from a direct interview with
|
||||||
Daniel (2026-08-01), not from `TODO-1.0.md`. Listed here as a block rather than per track,
|
Daniel (2026-08-01) and his four later rulings the same day, not from `TODO-1.0.md`. Listed
|
||||||
because the whole phase is outside the source doc; the product reasoning lives in
|
here as a block rather than per track, because the whole phase is outside the source doc;
|
||||||
`docs/product/instrument-control-surface.md` and the automation scoping it defers in
|
the product reasoning lives in `docs/product/instrument-control-surface.md` and the
|
||||||
`docs/product/parameter-automation.md`. Γ-W3-T1 additionally **discharges** the
|
parameter system's in `docs/product/parameter-automation.md` §§6–10. **Two `docs/TODO.md`
|
||||||
`docs/TODO.md` deck-rework entry, whose original "one row of taller decks with
|
entries are discharged by this phase, not deferred again:** Γ-W3-T1 discharges the
|
||||||
within-deck stacking" shape Daniel explicitly superseded.
|
deck-rework entry (whose original "one row of taller decks with within-deck stacking" shape
|
||||||
|
Daniel explicitly superseded), and **Γ-W1-T1 discharges "Raise the stage-time ceiling above
|
||||||
|
2 s"** (Γ-F3 reversed).
|
||||||
|
- **Γ-W3-T2 `bake-reset-amendment` is a CORRECTION, not a feature**, and belongs on this list
|
||||||
|
for a different reason from the others: it exists only because Ξ-W2-T1 shipped ahead of this
|
||||||
|
plan's sequencing claim. If more corrections of this shape appear, they belong here rather
|
||||||
|
than in the table — the table is a completeness proof over `TODO-1.0.md`, and a correction
|
||||||
|
has no source row to point at.
|
||||||
|
|
||||||
### Deliberate compressions
|
### Deliberate compressions
|
||||||
|
|
||||||
@@ -1681,26 +2201,37 @@ Phase Ξ — The resample loop (W1 concurrency-safe with Θ from Θ-W
|
|||||||
W1 Consolidated tracking, and the programmed-note model
|
W1 Consolidated tracking, and the programmed-note model
|
||||||
T1 tracking-consolidation .................. 17
|
T1 tracking-consolidation .................. 17
|
||||||
T2 note-program-model ...................... 15 (model)
|
T2 note-program-model ...................... 15 (model)
|
||||||
W2 The bake chain [requires all of Phase Θ, and Phase Γ before it]
|
W2 The bake chain [ran AHEAD of Γ; its reset list is corrected by Γ-W3-T2]
|
||||||
T1 resample-bake-chain ..................... 15 (chain)
|
T1 resample-bake-chain ..................... 15 (chain)
|
||||||
W3 The capture-signal popup
|
W3 The capture-signal popup
|
||||||
T1 capture-signal-popup .................... 15 (popup)
|
T1 capture-signal-popup .................... 15 (popup)
|
||||||
|
|
||||||
Phase Γ — The instrument's control surface (none of the seventeen; runs before Ξ-W2)
|
Phase Γ — The instrument's control surface (none of the seventeen; ends with VST3 params)
|
||||||
W1 Foundations [5 tracks, disjoint by surface]
|
W1 Foundations [5 tracks, disjoint by surface]
|
||||||
T1 knob-interaction-law ....... modifiers + ms/semitone tapers + reset bypass
|
T1 knob-interaction-law ....... modifiers + ONE taper module + reset bypass
|
||||||
T2 master-bus-audio ........... limiter + meter ballistics + dynamic PDC [payload v14]
|
+ 10 s ceiling + AHDSR schematic axis [Ruling 2]
|
||||||
|
T2 master-bus-audio ........... limiter + meter ballistics + dynamic PDC [rung 1]
|
||||||
T3 contour-trace-curves ....... staged traces draw curved, knot on its trace
|
T3 contour-trace-curves ....... staged traces draw curved, knot on its trace
|
||||||
T4 editor-floor-and-row-law ... floor 1190x680 + budget constants + row predicate
|
T4 editor-floor-and-row-law ... floor 1190x680 + budget constants + row predicate
|
||||||
T5 preserve-time-stretch ...... real stretcher [measure-and-report gate]
|
T5 preserve-time-stretch ...... real stretcher [measure-and-report gate]
|
||||||
W2 New controls, and the overlay's marks [2 tracks]
|
W2 New controls, and the overlay's marks [2 tracks]
|
||||||
T1 pitch-rate-deck ............ Rate + Pitch, Varisp/Presrv compounding [payload v15]
|
T1 pitch-rate-deck ............ Rate + Pitch, Varisp/Presrv compounding [rung 2]
|
||||||
T2 loop-crossfade-ux .......... four-mark grammar; fade painted where it is heard
|
T2 loop-crossfade-ux .......... four-mark grammar; fade painted where it is heard
|
||||||
W3 The reflow [1 track]
|
W3 The reflow, and the bake correction [2 tracks]
|
||||||
T1 deck-reflow ................ two rows + double-height MASTER, by construction
|
T1 deck-reflow ................ two rows + double-height MASTER, by construction
|
||||||
|
T2 bake-reset-amendment ....... the Xi correction Gamma owns [needs Xi-W2-T1 on dev]
|
||||||
|
W4 VST3 parameters [1 track]
|
||||||
|
T1 vst3-parameter-set ......... 44 derived params, frozen id table [Ruling 1]
|
||||||
|
[rung 3 RESERVED, spent only if verify says so]
|
||||||
|
[OPEN: Gamma-F7, the parameter order — Daniel]
|
||||||
|
|
||||||
Resequenced 2026-08-01: the reflow split canvas (W1-T4) from arrangement (W3-T1), and
|
Resequenced 2026-08-01, three times: the reflow split canvas (W1-T4) from arrangement
|
||||||
preserve-time-stretch moved W4 -> W1-T5, which retires Rate's interim stand-in.
|
(W3-T1); preserve-time-stretch moved W4 -> W1-T5, retiring Rate's interim stand-in; then
|
||||||
Shared files, named: engine/CMakeLists.txt (W1-T2 | W1-T5) and editor_session.cpp
|
Ruling 1 added W4 and Ruling 2 grew W1-T1.
|
||||||
(W2-T1 | W2-T2) — both textual adjacency, not semantic contention.
|
Payload rungs are RELATIVE, not absolute — read kParamsPayloadVersion on dev and take the
|
||||||
|
next three above it. On dev at 2026-08-01 that is v14 / v15 / v16-reserved.
|
||||||
|
Shared files, named: engine/CMakeLists.txt (W1-T2 | W1-T5), ui/CMakeLists.txt
|
||||||
|
(W1-T1 | W1-T3), editor_session.cpp (W2-T1 | W2-T2) — all textual adjacency, not
|
||||||
|
semantic contention. W3-T2's disjointness from W3-T1 is CONDITIONAL: confirm it against
|
||||||
|
what Xi-W2-T1 shipped, and serialize behind T1 if it does not hold.
|
||||||
```
|
```
|
||||||
|
|||||||
+5
-11
@@ -208,21 +208,15 @@ Forward-looking follow-ups. Deferred by decision, not oversight — each entry r
|
|||||||
|
|
||||||
**Done looks like.** Switching projects mid-record leaves the recorded file with a ledger record in the project it belongs to, so a later prune of that project can reclaim it normally.
|
**Done looks like.** Switching projects mid-record leaves the recorded file with a ledger record in the project it belongs to, so a later prune of that project can reclaim it normally.
|
||||||
|
|
||||||
## Raise the stage-time ceiling above 2 s for long-decay sound design
|
## ~~Raise the stage-time ceiling above 2 s for long-decay sound design~~ — SCHEDULED, no longer deferred
|
||||||
|
|
||||||
**Context (Daniel, 2026-08-01 — Phase Γ fork Γ-F3).** Every stage-time knob spans `[0, kEnvTimeMaxSeconds]` (`core/instrument/ui/deck_values.h:22`), which reads the AHDSR overlay's schematic scale `kGateStageMaxSeconds = 2.0` (`core/instrument/ui/envelope_overlay.h:85`). Γ-F3 asked whether Phase Γ's log taper should also raise that ceiling. **Ruled: not in Phase Γ — the ceiling stays 2.0 s.** But the ambition is real and is recorded here rather than dropped: Daniel *"can foresee wanting 10-second stages"* for certain sound-design cases, his example being **"a horrifically long decay with tight exp[onent]"** — a very long stage whose curve exponent puts almost all the motion at one end. That shape is unreachable at a 2 s ceiling.
|
**This entry is discharged into `docs/PLAN.md` at Γ-W1-T1 and is retained only as a pointer.** Daniel reversed Γ-F3 the same day he ruled it (2026-08-01): *"extend the stage lengths to 10s."* `kEnvTimeMaxSeconds` / `kGateStageMaxSeconds` move **2.0 → 10.0 in Γ-W1-T1**, beside the taper work rather than after it.
|
||||||
|
|
||||||
**Why it is deferred rather than done.** It is a musical-range question, not a correctness one, and the right time to answer it is with the new taper in the DAW under the hand — a 5× ceiling change judged from a spec is a guess.
|
**Why the reversal, since the deferral's reasoning was sound.** The deferral said the right time to judge a 5× range change is with the new taper in the DAW under the hand. What changed is not that judgement but the **cost of waiting**: Ruling 1 schedules VST3 parameters inside the same phase (Γ-W4-T1), and a range endpoint is part of the host-facing normalization exactly as much as the curve between the endpoints is. Raising the ceiling is free this wave and permanently expensive four waves later — the same one-way door `docs/product/parameter-automation.md` §4 states for the taper itself, and §8 sweeps for exhaustively.
|
||||||
|
|
||||||
**Two things landing in Phase Γ make it cheap afterwards.**
|
**What this entry contributed, and where it now lives.** Its two prerequisites (the log taper; `resetDeckParam` bypassing the taper, since 2.0 is a power of two and 10.0 is not) were already in Γ-W1-T1 and are now load-bearing rather than incidental. Its named hard part — *"the constant change is trivial; keeping the drawing legible is not"* — is now in-scope design work, specified at `docs/product/instrument-control-surface.md` §4.3.1: at 10 s a 30 ms attack is 0.3 % of the AHDSR schematic's stage domain, and the answer is to make the schematic axis **be** the taper, so a node's position within its stage slot is its knob's needle position.
|
||||||
1. **The log taper (Γ-W1-T1) is what makes a higher ceiling usable rather than unusable.** Under the current linear map, a 10 s ceiling would put 10 ms at 0.001 of travel — one pixel of a 128 px drag — so the ceiling and the low-end resolution were in direct conflict. Under the log taper the low end keeps its resolution regardless of where the ceiling sits (Γ-W1-T1's landmarks: 10 ms within 0.12–0.20 of travel, 100 ms within 0.42–0.52). **The taper is the prerequisite, and it is why this is a follow-up rather than a dead end.**
|
|
||||||
2. **`resetDeckParam` bypassing the taper (also Γ-W1-T1) removes the power-of-two blocker.** `deck_values.h:42-46` records that exact default recovery currently depends on the ceiling being a power of two — `x/2^n*2^n` is lossless, an arbitrary ceiling is not. **2.0 is a power of two; 10.0 is not**, so under today's norm round-trip a 10 s ceiling would land every stage-time reset a mantissa bit off its own default. Γ-W1-T1 makes reset write the default value directly, which retires that dependency outright.
|
|
||||||
|
|
||||||
**The constraint the change MUST handle.** `kEnvTimeMaxSeconds` and `kGateStageMaxSeconds` **move together, or not at all.** The AHDSR overlay's schematic scale is derived from the ceiling so that a maxed knob lands exactly at the canvas edge — the agreement requirement is documented at `deck_values.h:19-22`, and `deck_values.h` reads the overlay's constant rather than restating it precisely so the two cannot drift. Raising the ceiling therefore also stretches every drawn AHDSR: at 10 s, a 30 ms attack occupies 0.3% of the schematic's stage domain and becomes visually indistinguishable from zero. **Whether the overlay's schematic scale should stay linear in seconds at a 10 s ceiling, or take a taper of its own, is the real design question underneath this entry** — the constant change is trivial; keeping the drawing legible is not.
|
**Nothing here is actionable as a TODO.** Delete this entry when Γ-W1-T1 lands.
|
||||||
|
|
||||||
**Priority / risk.** Low / deferred by ruling. No correctness risk: the persisted payload stores raw seconds, so a ceiling change re-tapers needle angles only and every saved project reloads bit-identical (the same persistence-neutrality Γ-W1-T1's own taper changes are held to). **One timing risk that is NOT low:** if VST3 automation parameters ship before this, the ceiling becomes part of the host-facing normalization and moving it silently re-interprets every recorded automation point — the same one-way door `docs/product/parameter-automation.md` §4 states for the taper itself. **If this is wanted, it wants to happen before the parameter system, not after.**
|
|
||||||
|
|
||||||
**Done looks like.** A stage time of several seconds is reachable by hand with no loss of resolution below 100 ms; the AHDSR overlay still reads legibly at both ends of the range; `kEnvTimeMaxSeconds` and `kGateStageMaxSeconds` still agree; and a project saved at the old ceiling reloads with identical stored seconds and identical audio.
|
|
||||||
|
|
||||||
## Decouple the instrument reload from VST3 activation
|
## Decouple the instrument reload from VST3 activation
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,16 @@ own width formula, not carried over from a prior measurement. The stale geometry
|
|||||||
sits under it. The core finding behind the re-approach: three identical bars draw a
|
sits under it. The core finding behind the re-approach: three identical bars draw a
|
||||||
*point* and the two ends of a *span* in the same ink, and the crossfade is painted where
|
*point* and the two ends of a *span* in the same ink, and the crossfade is painted where
|
||||||
its *ingredient* lives rather than where the *event* is heard. §6.
|
its *ingredient* lives rather than where the *event* is heard. §6.
|
||||||
|
- **VST3 automation parameters SHIP IN THIS PHASE, as its last track** (Ruling 1, Daniel
|
||||||
|
2026-08-01). The specification is `docs/product/parameter-automation.md` §§6–10: 44
|
||||||
|
exposed parameters derived from the three-state commit predicate, a hand-assigned
|
||||||
|
FOREVER-FROZEN id table in blocks of 100 with steps of 10, and the blob left
|
||||||
|
authoritative with parameters as a third surface onto the one model.
|
||||||
|
- **The stage-time ceiling moves 2 s → 10 s, in wave 1** (Daniel, reversing Γ-F3),
|
||||||
|
*because* parameters now ship in-phase — a range endpoint is host-facing normalization,
|
||||||
|
free to move now and permanently expensive afterwards. Its real cost is not the constant
|
||||||
|
but keeping the AHDSR overlay legible when a 30 ms attack is 0.3 % of the schematic
|
||||||
|
domain; the answer is to make the schematic axis *be* the taper. §4.3.1.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -668,10 +678,24 @@ reduction is applied.**
|
|||||||
### 3.4 Where the master controls sit in the reset scope
|
### 3.4 Where the master controls sit in the reset scope
|
||||||
|
|
||||||
Phase Ξ-W2's resample reset scope is settled by rule ("reset what the bake baked in").
|
Phase Ξ-W2's resample reset scope is settled by rule ("reset what the bake baked in").
|
||||||
Derived against that rule, surfaced for Ξ-W2's review rather than as a Daniel call:
|
Derived against that rule — **no new Daniel call**: **rate → reset**, **pitch offset →
|
||||||
**rate → reset**, **pitch offset → reset**, **limiter enabled → reset** (master gain is
|
reset**, **limiter enabled → reset** (master gain is already on the reset list, so the bake
|
||||||
already on the reset list, so the bake includes the master stage, so the limiter's effect
|
includes the master stage, so the limiter's effect is in the audio).
|
||||||
is in the audio).
|
|
||||||
|
**This is now a CORRECTION, not a sequencing note.** The original plan required Phase Γ to
|
||||||
|
land before Ξ-W2 so the bake's reset list would be complete on the day it shipped. **That
|
||||||
|
ordering was never Daniel's choice and it is already violated** — Ξ-W2-T1 was underway
|
||||||
|
before this phase was scoped (Daniel: *"xi was started before I spun you up, we'll have to
|
||||||
|
correct phase xi inside gamma. wasn't a choice."*). So the three classifications above are
|
||||||
|
an amendment Phase Γ **owes** to a shipped bake, and Γ-W3-T2 is the track that pays it. The
|
||||||
|
amendment is verified **against what Ξ-W2-T1 actually shipped**, never against what this
|
||||||
|
document predicted it would ship.
|
||||||
|
|
||||||
|
**A second correction of the same shape arrives with Ruling 1**, and it is Γ-W4-T1's, not
|
||||||
|
this one's: once these values are exposed as VST3 parameters, the bake's reset must notify
|
||||||
|
the host, and a host automation lane on a reset-class parameter re-imposes its curve onto
|
||||||
|
already-baked audio. Full analysis and the disposition:
|
||||||
|
`docs/product/parameter-automation.md` §9.
|
||||||
|
|
||||||
### 3.5 Assessment: the `temp_cortex/` limiter reference
|
### 3.5 Assessment: the `temp_cortex/` limiter reference
|
||||||
|
|
||||||
@@ -775,16 +799,87 @@ shape):
|
|||||||
- Exactly **0 s at norm 0** and exactly `kEnvTimeMaxSeconds` at norm 1. Monotone and
|
- Exactly **0 s at norm 0** and exactly `kEnvTimeMaxSeconds` at norm 1. Monotone and
|
||||||
continuous throughout.
|
continuous throughout.
|
||||||
- **10 ms lands within 0.12 … 0.20 of travel; 100 ms within 0.42 … 0.52.**
|
- **10 ms lands within 0.12 … 0.20 of travel; 100 ms within 0.42 … 0.52.**
|
||||||
- `kEnvTimeMaxSeconds` is **2.0 s** (`deck_values.h:22`, reading `kGateStageMaxSeconds` at
|
- `kEnvTimeMaxSeconds` **moves 2.0 s → 10.0 s IN THIS PHASE, in this track**
|
||||||
`envelope_overlay.h:85`) and **does not move in this phase** — settled, Γ-F3. The AHDSR
|
(`deck_values.h:22`, which reads `kGateStageMaxSeconds` at `envelope_overlay.h:85` — the
|
||||||
overlay's schematic scale is derived from it and the two must agree so a maxed knob lands
|
two move together or not at all). **This reverses Γ-F3** (Daniel, 2026-08-01:
|
||||||
exactly at the canvas edge; the agreement requirement is documented at
|
*"extend the stage lengths to 10s"*), and §4.3.1 below is the design work it pulls in.
|
||||||
`deck_values.h:19-22`. **Daniel foresees wanting a 10 s ceiling** for sound-design cases
|
|
||||||
— his example, *"a horrifically long decay with tight exp"* — and that ambition is
|
#### 4.3.1 The 10 s ceiling — why it moved, and what it costs
|
||||||
carried as a `docs/TODO.md` entry rather than dropped. Two things make it cheap later and
|
|
||||||
both land in this phase: the log taper is precisely what makes a higher ceiling *usable*
|
**Γ-F3 was ruled "not in this phase" and is now reversed.** The reversal is not a change of
|
||||||
rather than unusable at the low end, and the reset-bypasses-the-taper change below
|
mind about the musical range; it is a consequence of Ruling 1 — VST3 parameters now ship at
|
||||||
removes the power-of-two dependency that would otherwise block a 10.0 s ceiling outright.
|
the end of this same phase (§4.4, `docs/product/parameter-automation.md` §8). The moment
|
||||||
|
parameters exist, a range endpoint is part of the host-facing normalization exactly as much
|
||||||
|
as the curve between the endpoints is: re-ceiling re-interprets every recorded automation
|
||||||
|
point in project files we do not own and cannot migrate. **Raising the ceiling is free this
|
||||||
|
wave and permanently expensive four waves later.** The `docs/TODO.md` entry that carried the
|
||||||
|
ambition is discharged here rather than deferred again.
|
||||||
|
|
||||||
|
**Three constraints carry forward unchanged.**
|
||||||
|
|
||||||
|
1. **`kEnvTimeMaxSeconds` and `kGateStageMaxSeconds` move together.** `deck_values.h` reads
|
||||||
|
the overlay's constant rather than restating it precisely so the two cannot drift, and the
|
||||||
|
agreement requirement is documented at `deck_values.h:19-22`.
|
||||||
|
2. **The reset bypass is now MANDATORY, not merely required-anyway.** `resetDeckParam`'s
|
||||||
|
exact-default recovery depends on the ceiling being a power of two
|
||||||
|
(`deck_values.h:42-46`); **2.0 is, 10.0 is not**, and the log taper compounds it. The
|
||||||
|
bypass below was already required by this track — it is now also the only thing that
|
||||||
|
makes the new ceiling correct, so it is not a candidate for "simplification" back into a
|
||||||
|
norm round-trip under any circumstance.
|
||||||
|
3. **A new, harder correctness case arrives with the parameters, and it is NOT solved by the
|
||||||
|
bypass.** `ParameterInfo::defaultNormalizedValue` is normalized; a host's reset-to-default
|
||||||
|
arrives back as `toPlain(defaultNorm)`, and **the host has no bypass to offer**. The taper
|
||||||
|
must therefore be designed so **every default has an exact normalized preimage** — see
|
||||||
|
`docs/product/parameter-automation.md` §8 door 3. This binds the taper's *shape*, so it
|
||||||
|
belongs to this track and cannot be handed forward.
|
||||||
|
|
||||||
|
**The real design problem is legibility, and it is in scope here.** The AHDSR overlay's
|
||||||
|
schematic gives each of the four timed stages an equal slot and maps seconds across it
|
||||||
|
linearly (`gatePxPerSecond`, `envelope_overlay.cpp:33-34`). At a 2 s ceiling a 30 ms attack
|
||||||
|
occupies 1.5 % of its stage's domain — small but drawn. **At 10 s it occupies 0.3 %, under a
|
||||||
|
pixel at the floor width, and becomes visually indistinguishable from zero.** A 5× ceiling
|
||||||
|
that makes the default attack invisible is not a feature.
|
||||||
|
|
||||||
|
**Three directions were considered.**
|
||||||
|
|
||||||
|
1. **Content-fit auto-scale** — the schematic's domain follows the largest current stage, so
|
||||||
|
short envelopes draw large. Rejected: the axis moves under the hand while you drag, every
|
||||||
|
node shifts when any node moves, and it breaks the documented anchor that a maxed knob
|
||||||
|
lands exactly at the canvas edge.
|
||||||
|
2. **A minimum drawn stage width** — every stage gets at least *n* px regardless of value.
|
||||||
|
Rejected: it decouples the drawn position from the value, so `envelope_edit`'s drag
|
||||||
|
inverse can no longer be the exact inverse of the draw — which is the one property the
|
||||||
|
node/knot/knob "surfaces onto ONE model" invariant rests on.
|
||||||
|
3. **The schematic axis BECOMES the taper — recommended.** A stage's slot width is
|
||||||
|
`slotPx × taperNorm(seconds)` instead of `slotPx × seconds / ceiling`. The node's position
|
||||||
|
within its slot then *is* its knob's needle position, drawn a second way.
|
||||||
|
|
||||||
|
**Recommended: direction 3.** It is the smallest change that is also the most principled one:
|
||||||
|
|
||||||
|
- **Legibility becomes ceiling-independent by construction.** The taper's own landmarks
|
||||||
|
(10 ms within 0.12–0.20 of travel, 100 ms within 0.42–0.52) are landmarks on the overlay
|
||||||
|
too, at any ceiling this or a future phase picks.
|
||||||
|
- **It strengthens the one-model invariant rather than straining it.** A node and its knob
|
||||||
|
become the same normalized quantity; today they are two maps that happen to agree.
|
||||||
|
- **It costs the drawn curve nothing.** The taper decides only *where a stage's end node
|
||||||
|
lands*. Within a stage, φ still runs linearly across the stage's pixel span, so a φ^p
|
||||||
|
segment draws as φ^p exactly as Γ-W1-T3 specifies — the two tracks compose rather than
|
||||||
|
fight.
|
||||||
|
- **The AHD policy is untouched.** An AHD maps 1:1 onto the waveform's own time axis and is
|
||||||
|
PCM-aligned; it must stay linear in seconds, and nothing here changes it. **Only the AHDSR
|
||||||
|
schematic is tapered**, and it was already documented as schematic-not-time-aligned.
|
||||||
|
|
||||||
|
**The cost, named:** within-stage horizontal extent stops being proportional to time, so two
|
||||||
|
stages can no longer be compared by eye at a 10× ratio the way they can at 2×. The ms labels
|
||||||
|
Θ-W6-T1 landed carry the actual number, which is what that comparison is actually made
|
||||||
|
against; and the alternative — a 30 ms attack drawn as zero — loses the comparison entirely.
|
||||||
|
|
||||||
|
**The structural consequence, and it is the important one.** The taper is now read by three
|
||||||
|
consumers: the knob (`deck_values`), the overlay (`envelope_overlay` + `envelope_edit`), and
|
||||||
|
— from Γ-W4-T1 — the host (`normalizedParamToPlain`). **It must be extracted into one pure
|
||||||
|
module** rather than living inside `deck_values`, which sits above `envelope_overlay` in the
|
||||||
|
dependency order. That extraction is what makes "the taper IS the host-facing normalization"
|
||||||
|
structurally true instead of a comment somebody has to remember.
|
||||||
|
|
||||||
**Semitone knobs become log2-scaled.** More resolution across −7 … +7 st while still
|
**Semitone knobs become log2-scaled.** More resolution across −7 … +7 st while still
|
||||||
allowing the extremes:
|
allowing the extremes:
|
||||||
@@ -818,11 +913,17 @@ allowing the extremes:
|
|||||||
### 4.4 Sequencing — why item D goes first
|
### 4.4 Sequencing — why item D goes first
|
||||||
|
|
||||||
Item D lands **before** the two new PITCH/RATE knobs, so Rate and Pitch are authored into
|
Item D lands **before** the two new PITCH/RATE knobs, so Rate and Pitch are authored into
|
||||||
the finished law rather than retro-fitted into it. It lands **well before** any VST3
|
the finished law rather than retro-fitted into it. It lands **three waves before Γ-W4-T1's
|
||||||
parameter work, for a much sharper reason: once parameters are exposed, the taper *is* the
|
VST3 parameter work**, for a much sharper reason: once parameters are exposed, the taper
|
||||||
host-facing normalization, and re-tapering silently re-interprets every recorded automation
|
*is* the host-facing normalization, and re-tapering silently re-interprets every recorded
|
||||||
point in every saved project. **Taper changes are free now and permanently expensive
|
automation point in every saved project. **Taper changes are free now and permanently
|
||||||
later.** See `docs/product/parameter-automation.md` §4.
|
expensive later.** See `docs/product/parameter-automation.md` §4.
|
||||||
|
|
||||||
|
That argument used to justify Γ running ahead of a future phase; since Ruling 1 it governs
|
||||||
|
**wave order inside this one**, which is strictly better — the taper and the parameters that
|
||||||
|
freeze it are now reviewed against each other rather than across a phase boundary. It is
|
||||||
|
also why the ceiling moved (§4.3.1) and why §8's one-way-door sweep in the automation doc is
|
||||||
|
a deliverable rather than a caution.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1251,18 +1352,20 @@ shape is deliberate and its reasoning is documented at the call site.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Forks — all six ruled, none open
|
## 8. Forks — six ruled (one later reversed), one open
|
||||||
|
|
||||||
### 8.1 Ruled by Daniel, 2026-08-01
|
### 8.1 Ruled by Daniel, 2026-08-01
|
||||||
|
|
||||||
Every fork this doc opened is closed. The rulings are folded into the sections that
|
The rulings are folded into the sections that depend on them; this table is the index, not a
|
||||||
depend on them; this table is the index, not a second copy of the reasoning.
|
second copy of the reasoning. **Γ-F3 was ruled and then REVERSED the same day** — the row
|
||||||
|
below carries both, because a reader who acts on the first ruling would ship the wrong
|
||||||
|
ceiling.
|
||||||
|
|
||||||
| Fork | Question | Ruling | Where it landed |
|
| Fork | Question | Ruling | Where it landed |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **Γ-F1** | Does `kEditorMinHeight` move 680 → 720? | **No — stays 680.** The reflow's 112 px goes entirely to the waveform. | §1.2 / §1.5, unchanged |
|
| **Γ-F1** | Does `kEditorMinHeight` move 680 → 720? | **No — stays 680.** The reflow's 112 px goes entirely to the waveform. | §1.2 / §1.5, unchanged |
|
||||||
| **Γ-F2** | Limiter lookahead, or zero-latency? | **Lookahead with DYNAMIC reported latency** — zero when off, the lookahead when on, reported to the host's PDC. *Overrides this doc's zero-lookahead recommendation.* | **§3.1.1** (new), §7.10 |
|
| **Γ-F2** | Limiter lookahead, or zero-latency? | **Lookahead with DYNAMIC reported latency** — zero when off, the lookahead when on, reported to the host's PDC. *Overrides this doc's zero-lookahead recommendation.* | **§3.1.1** (new), §7.10 |
|
||||||
| **Γ-F3** | Does the log taper raise the 2 s stage-time ceiling? | **Not in this phase — stays 2.0 s.** The 10 s ambition is preserved as a `docs/TODO.md` entry with its rationale. | §4.3, `docs/TODO.md` |
|
| **Γ-F3** | Does the log taper raise the 2 s stage-time ceiling? | **REVERSED, same day. Ruled first "not in this phase — stays 2.0 s"; then Daniel: _"extend the stage lengths to 10s."_ The ceiling moves 2.0 → 10.0 in Γ-W1-T1.** The reversal's cause is Ruling 1: parameters now ship in-phase, so the ceiling is a one-way door that has to be walked through *before* them. | **§4.3.1** (new), §4.3; `docs/TODO.md` entry discharged |
|
||||||
| **Γ-F4** | Explicit loop enable? | **Yes — on the CHROME ROW.** Not a deck cell; loop is a waveform-overlay concept and has no deck. | **§6.4** (new), §6.5, §7.9 |
|
| **Γ-F4** | Explicit loop enable? | **Yes — on the CHROME ROW.** Not a deck cell; loop is a waveform-overlay concept and has no deck. | **§6.4** (new), §6.5, §7.9 |
|
||||||
| **Γ-F5** | MASTER's reserved slot: one cell or two? | **One cell.** Two would spend 60 of the 90 px headroom on an unnamed control and freeze row 1 forever. | **§1.6** (new), §1.4 |
|
| **Γ-F5** | MASTER's reserved slot: one cell or two? | **One cell.** Two would spend 60 of the 90 px headroom on an unnamed control and freeze row 1 forever. | **§1.6** (new), §1.4 |
|
||||||
| **Γ-F6** | Is the `kLatencyChanged` deactivate/reactivate acceptable as the cost of the toggle? | **Yes — ship dynamic latency as ruled.** No constant-latency fallback, no measurement gate. *Corrected this doc's analysis: the cost is self-inflicted, not SDK-imposed.* | **§3.1.1** (rewritten), §7.10, §7.11, `docs/TODO.md` |
|
| **Γ-F6** | Is the `kLatencyChanged` deactivate/reactivate acceptable as the cost of the toggle? | **Yes — ship dynamic latency as ruled.** No constant-latency fallback, no measurement gate. *Corrected this doc's analysis: the cost is self-inflicted, not SDK-imposed.* | **§3.1.1** (rewritten), §7.10, §7.11, `docs/TODO.md` |
|
||||||
@@ -1322,18 +1425,39 @@ than just counting:
|
|||||||
`restartComponent`), not about this flag.
|
`restartComponent`), not about this flag.
|
||||||
5. **The reduction is filed**, with a trigger condition, in `docs/TODO.md`.
|
5. **The reduction is filed**, with a trigger condition, in `docs/TODO.md`.
|
||||||
|
|
||||||
*No fork in this phase is open. Nothing in §§1–7 awaits a Daniel answer.*
|
### 8.3 Γ-F7 — OPEN. The parameter order
|
||||||
|
|
||||||
|
**Opened 2026-08-01 by Ruling 1** (*"Make the parameter order logical"*), because "logical"
|
||||||
|
resolves two ways and the choice is frozen forever the day parameters ship.
|
||||||
|
|
||||||
|
> **Signal-flow order** — PITCH/RATE → PITCH ENV → FILTER → FILTER ENV → AMP → VELOCITY →
|
||||||
|
> VOICE → MASTER, the deck's own documented ordering rule, layout-independent.
|
||||||
|
> **OR the editor's visual row order** after the Γ-W3 reflow — row 1 then row 2 then MASTER,
|
||||||
|
> matching what the user's eye scans.
|
||||||
|
|
||||||
|
Same membership, different sequence; the recommendation, both arguments, and why the
|
||||||
|
grouping (`IUnitInfo`, one unit per deck group) is settled either way are in
|
||||||
|
`docs/product/parameter-automation.md` §6.4. **Recommendation: signal flow**, because the
|
||||||
|
visual layout has moved twice already and this phase moves it again, and freezing a forever
|
||||||
|
identity to a thing that moves is the wrong coupling.
|
||||||
|
|
||||||
|
**Urgency: low, but not zero.** Three waves sit in front of Γ-W4-T1 and nothing before it
|
||||||
|
depends on the answer. It must close **before Γ-W4 dispatches**, and it cannot be closed by
|
||||||
|
proposal at review — a forever commitment is a Daniel call.
|
||||||
|
|
||||||
|
*Every other fork in this phase is ruled. Nothing in §§1–7 awaits a Daniel answer.*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. Build shape
|
## 9. Build shape
|
||||||
|
|
||||||
Sequenced into `docs/PLAN.md` as **Phase Γ** (worktree slug prefix `pg-`), **three waves**
|
Sequenced into `docs/PLAN.md` as **Phase Γ** (worktree slug prefix `pg-`), **four waves**
|
||||||
(resequenced by Daniel, 2026-08-01 — see below):
|
(resequenced by Daniel twice on 2026-08-01 — see below):
|
||||||
|
|
||||||
```
|
```
|
||||||
Γ-W1 Foundations [5 tracks, disjoint by surface]
|
Γ-W1 Foundations [5 tracks, disjoint by surface]
|
||||||
T1 knob-interaction-law ............ item D (editor input + deck_values tapers)
|
T1 knob-interaction-law ............ item D (editor input + the ONE taper module +
|
||||||
|
10 s ceiling + AHDSR schematic scale)
|
||||||
T2 master-bus-audio ................ item C (pure limiter + meter ballistics +
|
T2 master-bus-audio ................ item C (pure limiter + meter ballistics +
|
||||||
processor + LATENCY REPORTING)
|
processor + LATENCY REPORTING)
|
||||||
T3 contour-trace-curves ............ item E (waveform painter)
|
T3 contour-trace-curves ............ item E (waveform painter)
|
||||||
@@ -1344,11 +1468,17 @@ Sequenced into `docs/PLAN.md` as **Phase Γ** (worktree slug prefix `pg-`), **th
|
|||||||
T1 pitch-rate-deck ................. item A (params + engine + deck descriptor)
|
T1 pitch-rate-deck ................. item A (params + engine + deck descriptor)
|
||||||
T2 loop-crossfade-ux ............... item F (waveform painter + pure marker geometry
|
T2 loop-crossfade-ux ............... item F (waveform painter + pure marker geometry
|
||||||
+ the chrome-row loop enable)
|
+ the chrome-row loop enable)
|
||||||
Γ-W3 The reflow [1 track]
|
Γ-W3 The reflow, and the bake correction [2 tracks]
|
||||||
T1 deck-reflow ..................... item B's ARRANGEMENT half + C's UI half
|
T1 deck-reflow ..................... item B's ARRANGEMENT half + C's UI half
|
||||||
|
T2 bake-reset-amendment ............ the Phase Ξ correction Γ owns (§3.4)
|
||||||
|
Γ-W4 VST3 parameters [1 track]
|
||||||
|
T1 vst3-parameter-set .............. Ruling 1 (parameter-automation.md §§6-10)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Two resequencing decisions, both Daniel's (2026-08-01).**
|
**Three resequencing decisions, all Daniel's (2026-08-01).** The third is Ruling 1: the
|
||||||
|
parameter system moves from "a future phase" into **Γ-W4**, which is what put the 10 s
|
||||||
|
ceiling into W1 (§4.3.1) and turned the Ξ ordering constraint into an owned correction
|
||||||
|
(§3.4). The first two:
|
||||||
|
|
||||||
1. **Item B splits: canvas early, arrangement late.** The window floor, the width budget it
|
1. **Item B splits: canvas early, arrangement late.** The window floor, the width budget it
|
||||||
derives from, and each group's row membership land in W1-T4 so every other UI track is
|
derives from, and each group's row membership land in W1-T4 so every other UI track is
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
# VST3 automation parameters — scoping, not scheduling
|
# VST3 automation parameters — the Phase Γ specification
|
||||||
|
|
||||||
> Daniel, 2026-08-01: *"we need to scope the whole parameter system for automation, which
|
> Daniel, 2026-08-01: *"we need to scope the whole parameter system for automation, which
|
||||||
> will require redesigning the existing wiring."* And, on Rate being latched at note-on:
|
> will require redesigning the existing wiring."* And, on Rate being latched at note-on:
|
||||||
> *"we will need to analyze ALL the controls for LIVE use/automation use."*
|
> *"we will need to analyze ALL the controls for LIVE use/automation use."*
|
||||||
|
|
||||||
**This is NOT Phase Γ work.** It is framing and open questions, written now because Phase Γ
|
**Superseding ruling, Daniel 2026-08-01 (later the same day):** *"correct the phase gamma
|
||||||
makes two decisions whose cost changes permanently the day parameters ship, and because
|
plan to account for complying with the VST3 standard for parameter reporting… by the end of
|
||||||
knowing that is what makes Γ's ordering correct rather than arbitrary. Nothing here is
|
gamma we have the automatable params reported. Make the parameter order logical."*
|
||||||
scheduled; nothing here is settled.
|
|
||||||
|
|
||||||
The one thing this doc *does* assert is a sequencing constraint — §4.
|
**This IS Phase Γ work.** It ships as **Γ-W4-T1 `vst3-parameter-set`**, the phase's last
|
||||||
|
track. This doc was written as scoping and has been promoted in place: §§1–5 are the
|
||||||
|
original analysis (still correct, still the reasoning), and **§§6–10 are the specification**
|
||||||
|
— the ID scheme and its freeze invariant, the per-control classification, the one-way-door
|
||||||
|
sweep, the resample interaction, and the build shape.
|
||||||
|
|
||||||
|
Two things that were open in the scoping pass are now **decided** rather than deferred:
|
||||||
|
the storage architecture (§3.3, decided in §6.1) and the ID space (§3.1, decided in §6.2).
|
||||||
|
One thing is **open and wants Daniel** before Γ-W4 dispatches: **Γ-F7**, the parameter
|
||||||
|
*order* (§6.4). It is not urgent — three waves sit in front of it — but it is a forever
|
||||||
|
commitment, so it is not mine to close silently.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -61,11 +70,12 @@ class UIDs already carry, and the same discipline the params-payload version lad
|
|||||||
wants to renumber, regroup or reorder that enum has to happen first. This is not a
|
wants to renumber, regroup or reorder that enum has to happen first. This is not a
|
||||||
theoretical concern: Phase Γ adds two entries to it.
|
theoretical concern: Phase Γ adds two entries to it.
|
||||||
|
|
||||||
Open: is `ParamID` the `DeckParam` value directly, or an independent id space with an
|
**DECIDED — §6.2: an independent id space with an explicit frozen table.** Direct
|
||||||
explicit mapping table? Direct is simpler and tempting; an independent space is what lets
|
`DeckParam` values were simpler and were rejected: they would freeze an enum whose own
|
||||||
the enum keep being a UI-ordering convenience. **Lean: independent id space with an explicit
|
header declares it *"runtime-only… free to change"*, and they cannot express the cases
|
||||||
frozen table**, on the same reasoning the command-id family already uses — the display order
|
where the two spaces genuinely differ (a `DeckParam` that is not a parameter, a parameter
|
||||||
and the wire identity should not be the same number.
|
with no deck cell). The reasoning is the command-id family's: display order and wire
|
||||||
|
identity should not be the same number.
|
||||||
|
|
||||||
### 3.2 The taper becomes the host-facing contract
|
### 3.2 The taper becomes the host-facing contract
|
||||||
|
|
||||||
@@ -101,8 +111,11 @@ Two coherent answers, both viable, neither obviously right:
|
|||||||
blob contract intact; risks exactly the drift class above if the precedence is not
|
blob contract intact; risks exactly the drift class above if the precedence is not
|
||||||
airtight.
|
airtight.
|
||||||
|
|
||||||
Open. This is the load-bearing architectural decision of that phase and it should be made
|
**DECIDED — (b), the blob stays authoritative. See §6.1 for the reasoning, the two
|
||||||
first, the way Ξ-W2-T1's crossing decision was.
|
findings that settled it, and the precedence rule that closes the drift risk.** This was
|
||||||
|
going to be handed to the track as its opening architecture decision, the way Ξ-W2-T1's
|
||||||
|
crossing decision was. It is not, because two verified facts collapse the fork rather
|
||||||
|
than balancing it — a fork with a dominated option in it is not a fork.
|
||||||
|
|
||||||
### 3.4 Which controls can be parameters at all — three classes
|
### 3.4 Which controls can be parameters at all — three classes
|
||||||
|
|
||||||
@@ -212,7 +225,7 @@ decoupling first, not to re-litigate the classification.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. The sequencing assertion — the one thing this doc claims
|
## 4. The sequencing assertion — now satisfied inside one phase
|
||||||
|
|
||||||
**The knob-taper work (Phase Γ item D) must land before the parameter system. Not
|
**The knob-taper work (Phase Γ item D) must land before the parameter system. Not
|
||||||
alongside; before.**
|
alongside; before.**
|
||||||
@@ -232,30 +245,438 @@ update; doing it after parameters ship costs a frozen-id decision.
|
|||||||
the parameter system touch `deck_values.cpp` and `editor_controls.cpp`, and the ordering is
|
the parameter system touch `deck_values.cpp` and `editor_controls.cpp`, and the ordering is
|
||||||
not a coin flip: item D first, by a wide margin.
|
not a coin flip: item D first, by a wide margin.
|
||||||
|
|
||||||
|
**What changed when the parameter system moved INTO Phase Γ.** The assertion above was
|
||||||
|
written to justify Γ running before a later phase. It now governs **wave order inside one
|
||||||
|
phase**: Γ-W1-T1 lands the taper, Γ-W4-T1 lands the parameters, and two whole waves of new
|
||||||
|
controls sit between them so that every parameter exists before any parameter is declared.
|
||||||
|
The argument is unchanged; only its blast radius shrank, which is strictly better — the two
|
||||||
|
halves are now reviewed against each other rather than across a phase boundary.
|
||||||
|
|
||||||
|
**And it grew a second clause.** Daniel's ruling *"extend the stage lengths to 10 s"*
|
||||||
|
reverses Γ-F3 and moves `kEnvTimeMaxSeconds` 2.0 → 10.0 into **Γ-W1-T1, beside the taper**,
|
||||||
|
for exactly the reason stated here: a range endpoint is part of the host-facing
|
||||||
|
normalization every bit as much as the curve between the endpoints is. §8 sweeps for every
|
||||||
|
other constant with that property.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Open questions, collected
|
## 5. Open questions — status after the promotion
|
||||||
|
|
||||||
Not one of these is a Phase Γ blocker. They are what a future phase's first wave answers.
|
The scoping pass collected eight. Seven are closed below; one is open and is a Daniel call.
|
||||||
|
|
||||||
1. **Blob vs. parameters as the source of truth** (§3.3) — the architectural decision, made
|
| # | Question | Status |
|
||||||
first, before any wiring.
|
|---|---|---|
|
||||||
2. **`ParamID` space** — direct `DeckParam` values, or an independent frozen table (§3.1).
|
| 1 | Blob vs. parameters as the source of truth (§3.3) | **CLOSED — (b), blob authoritative.** §6.1 |
|
||||||
*Lean: independent.*
|
| 2 | `ParamID` space (§3.1) | **CLOSED — independent frozen table.** §6.2 |
|
||||||
3. **The discrete/rebuild-tier controls** (§3.4) — for each of voice count, Poly/Mono,
|
| 3 | The discrete/rebuild-tier controls (§3.4) | **CLOSED — omitted from the list entirely**, not exposed-and-flagged. §7.2 |
|
||||||
Retrig/Legato, Gate/Trigger, Staged/Spline, pitch engine, filter law: gain a live path,
|
| 4 | Per-parameter live/latched classification (§3.5) | **CLOSED — the list is DERIVED from the three-state predicate**, never hand-maintained. §7 |
|
||||||
expose as non-automatable, or omit from the parameter list entirely.
|
| 5 | Unit tree shape (§3.6) | **CLOSED — the deck's groups, in signal-flow order**, layout-independent. §6.4 |
|
||||||
4. **Per-parameter live/latched classification** (§3.5) — the three-state predicate Phase Γ
|
| 6 | Sample-accurate vs. block-boundary (§3.7) | **CLOSED — block boundary, stated explicitly.** §6.5 |
|
||||||
opens must be filled in for every exposed parameter, including whether Rate is lifted to
|
| 7 | Does the extension's `instrument_drop` need to know? | **CLOSED — no.** It was the sharpest cost of option (a), and (a) is not taken. The blob a drop writes still fully determines the sound. §6.1 |
|
||||||
live and at what cost.
|
| 8 | Migration | **CLOSED as a design question, OPEN as a verification.** §6.6 |
|
||||||
5. **Unit tree shape** (§3.6) — mirror the deck's visual grouping, or the signal flow.
|
| **Γ-F7** | **Parameter ORDER — signal flow, or the editor's visual rows?** | **OPEN — Daniel.** Recommendation and reasoning at §6.4. Must close before Γ-W4 dispatches; blocks nothing before then. |
|
||||||
6. **Sample-accurate vs. block-boundary** (§3.7). *Lean: block boundary, stated explicitly.*
|
|
||||||
7. **Does the extension's `instrument_drop` need to know?** It writes the same
|
---
|
||||||
`ComponentState` bytes. If parameters become the source of truth for automatable values
|
|
||||||
(option (a)), a blob the extension writes would no longer fully determine the instrument's
|
## 6. The specification — architecture, identity, order
|
||||||
sound. That is a cross-artifact contract change and it is the sharpest hidden cost of
|
|
||||||
option (a).
|
### 6.1 Storage: the blob stays authoritative; parameters are a projection (option (b))
|
||||||
8. **Migration.** Every saved project predates parameters. On first load a parameter's value
|
|
||||||
comes from the blob; after that the host owns it. Confirm that path is lossless, and
|
**Decided, not deferred.** Two verified facts collapse §3.3's fork:
|
||||||
confirm what happens to a project saved *by* the new build and opened by an older one
|
|
||||||
(the blob still carries everything, so it should be fine — verify, do not assume).
|
1. **This plugin is a `SingleComponentEffect`, so there is only ONE state.** The SDK's own
|
||||||
|
header `#define`s `setState`/`getState` to `setEditorState`/`getEditorState` around its
|
||||||
|
include of `vsteditcontroller.h`, *"work around for the name clash of
|
||||||
|
IComponent::setState and IEditController::setState"*
|
||||||
|
(`public.sdk/source/vst/vstsinglecomponenteffect.h:41-47`). `setComponentState` exists to
|
||||||
|
feed a **separate** controller in a split-component plug-in; we have no separate
|
||||||
|
controller. §3.3's "a value that lives in both can drift, depending on call order"
|
||||||
|
describes a hazard of a design we do not use. **The drift class it warns about is
|
||||||
|
substantially smaller than the scoping pass assumed.**
|
||||||
|
2. **The blob is a cross-artifact contract.** The extension's `instrument_drop` writes the
|
||||||
|
identical bytes to build a transient `.vstpreset` (`map/component_state_io.h`). Under
|
||||||
|
option (a) a blob the extension writes would no longer fully determine the sound — a
|
||||||
|
shipped seam would silently become partial. That is not a trade, it is a regression.
|
||||||
|
|
||||||
|
Add the repo's own invariant — *"the instrument holds ONE loaded capture and ONE set of
|
||||||
|
playback parameters… every playback parameter edits in exactly one place"* — and option (a),
|
||||||
|
which splits the parameter set across two storage mechanisms, is arguing against the
|
||||||
|
invariant Θ-W1-T1 was run to establish.
|
||||||
|
|
||||||
|
**The rule, stated so it cannot drift:**
|
||||||
|
|
||||||
|
> **`InstrumentParams` / `PlaySeconds` is THE model. The `ComponentState` blob is its only
|
||||||
|
> persistence. A VST3 parameter is a THIRD SURFACE onto that model — a peer of the deck
|
||||||
|
> knob and the overlay node, not a second copy of the value.**
|
||||||
|
>
|
||||||
|
> - **Load:** the blob restores the model; every exposed parameter's normalized value is
|
||||||
|
> then *derived* from the model and pushed to the controller. The SDK is explicit that
|
||||||
|
> this push must not be reflected back to the host: *"The controller must never pass this
|
||||||
|
> value-change back to the host via the IComponentHandler. It should update the according
|
||||||
|
> GUI element(s) only"* (`ivsteditcontroller.h:430-433`).
|
||||||
|
> - **Host → plugin:** a parameter change writes the model through the **existing commit
|
||||||
|
> tiers** (`shell/instrument/CLAUDE.md`) — live publish or note-on latch. It never takes a
|
||||||
|
> fourth route.
|
||||||
|
> - **Plugin → host:** an editor gesture writes the model AND notifies the host, bracketed
|
||||||
|
> `beginEdit` / `performEdit` / `endEdit` (`ivsteditcontroller.h:175-185`), on the UI
|
||||||
|
> thread as the SDK requires.
|
||||||
|
> - **Save:** `getState` serializes the model. Nothing new is persisted.
|
||||||
|
>
|
||||||
|
> **The residual risk is not drift, it is authority:** a host automation lane, once written,
|
||||||
|
> outranks anything the plugin sets, because the host replays it. That is inherent to
|
||||||
|
> 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.
|
||||||
|
|
||||||
|
### 6.2 The ID space: hand-assigned constants in one frozen table
|
||||||
|
|
||||||
|
**`ParamID` is an independent space from `DeckParam`, defined by a hand-written table of
|
||||||
|
named constants in one pure module.** Not derived, not computed, not an enum cast.
|
||||||
|
|
||||||
|
**Why hand-assigned rather than derived.** A derived id is a function of something else —
|
||||||
|
enum position, group index, a hash of a name — and every one of those inputs is a thing
|
||||||
|
that must then never change. Deriving does not remove the freeze; it hides it behind a
|
||||||
|
mechanism, and the day someone reorders the source enum the ids move silently. A literal
|
||||||
|
table makes the freeze **visible at the point of change**: you cannot renumber it by
|
||||||
|
accident, because renumbering it means editing the numbers.
|
||||||
|
|
||||||
|
**The layout — blocks of 100 per deck group, steps of 10 within a block:**
|
||||||
|
|
||||||
|
| Block | Group | Reserved |
|
||||||
|
|---|---|---|
|
||||||
|
| `1000` | PITCH/RATE | 1000–1099 |
|
||||||
|
| `1100` | PITCH ENV | 1100–1199 |
|
||||||
|
| `1200` | FILTER | 1200–1299 |
|
||||||
|
| `1300` | FILTER ENV | 1300–1399 |
|
||||||
|
| `1400` | AMP ENVELOPE | 1400–1499 |
|
||||||
|
| `1500` | VELOCITY | 1500–1599 (reserved; empty today — §7.3) |
|
||||||
|
| `1600` | VOICE | 1600–1699 (reserved; empty today — §7.2) |
|
||||||
|
| `1700` | MASTER | 1700–1799 |
|
||||||
|
|
||||||
|
- **Within a block, ids step by 10** in the group's own left-to-right cell order. Nine free
|
||||||
|
slots sit between every pair of neighbours, so **a control added later lands numerically
|
||||||
|
adjacent to its siblings instead of appended at the end of the whole table.** That is the
|
||||||
|
concrete content of "make the parameter order logical": the order is a property of the
|
||||||
|
numbering scheme, maintained by construction, not by remembering to re-sort.
|
||||||
|
- **A curve-exponent inner dial takes its outer knob's id + 1.** The dial is a property of
|
||||||
|
the knob it sits inside — the codebase already says so (`curveParamFor`, "each is the
|
||||||
|
INNER DIAL of the stage knob it shapes") — so its id says so too. This is why the step is
|
||||||
|
10 and not 2: it leaves room for the dial *and* for a future sibling knob.
|
||||||
|
- **Blocks start at 1000, not 0.** `0` is a plausible accident (a default-initialised
|
||||||
|
`ParamID`), and a scheme whose first legitimate id is also its most likely bug value is a
|
||||||
|
scheme that cannot tell them apart.
|
||||||
|
- **An empty block is still reserved.** VELOCITY and VOICE expose nothing today. Their
|
||||||
|
blocks exist so that if either ever gains an automatable control it lands in its own
|
||||||
|
group's range rather than in whatever range happened to be free.
|
||||||
|
|
||||||
|
### 6.3 The freeze invariant
|
||||||
|
|
||||||
|
State this in the ID table's header, with the same force as its three siblings.
|
||||||
|
|
||||||
|
> **THE PARAMETER-ID TABLE IS FOREVER-FROZEN.** A VST3 `ParamID` is recorded by the host
|
||||||
|
> into automation lanes inside project files this repo does not own and cannot migrate.
|
||||||
|
> From the first shipped build that exposes parameters:
|
||||||
|
>
|
||||||
|
> - **No id is ever reassigned, reused, or re-pointed.** If a control's meaning genuinely
|
||||||
|
> changes, it takes a NEW id and the old one is **retired** — marked dead in the table,
|
||||||
|
> never re-issued.
|
||||||
|
> - **No exposed parameter's normalization ever changes** — not its taper, not either range
|
||||||
|
> endpoint, not its `stepCount`. The normalization *is* the meaning of every recorded
|
||||||
|
> automation point. §8 enumerates every constant this now freezes.
|
||||||
|
> - **A parameter's meaning never depends on a mode.** The Gate-face and Trigger-face stage
|
||||||
|
> times are separate stored fields and take separate ids; an id whose plain value means
|
||||||
|
> one thing in Gate and another in Trigger is unautomatable by construction.
|
||||||
|
> - **A new control takes the next free slot inside its own group's block**, never the next
|
||||||
|
> number at the end of the table.
|
||||||
|
>
|
||||||
|
> This sits on the same footing as the extension's `"STABLE_FOREVER_STRING"` command ids
|
||||||
|
> (root `CLAUDE.md`, "REAPER extension contract"), the two VST3 class UIDs
|
||||||
|
> (`core/wire/reasampler_uid.h`, FOREVER-FROZEN), and the params-payload field order
|
||||||
|
> (`map/component_state_io.h`, "EVERY wire format below is FROZEN"). It is the fourth
|
||||||
|
> member of that family, not a new kind of rule.
|
||||||
|
|
||||||
|
**One property the table BUYS, and it is the reason to prefer it over deriving:**
|
||||||
|
`DeckParam`'s header says the enum is *"runtime-only… free to change."* With an independent
|
||||||
|
table that stays true — the reflow, a future regrouping, a new deck can all reorder the enum
|
||||||
|
freely. Deriving ids from it would have quietly repealed that sentence.
|
||||||
|
|
||||||
|
### 6.4 Order and grouping — Γ-F7, OPEN
|
||||||
|
|
||||||
|
Two orders exist and they are not the same thing:
|
||||||
|
|
||||||
|
- **Identity order** — the id numbers, frozen by §6.3.
|
||||||
|
- **Presentation order** — the sequence `getParameterInfo(index)` reports, which is what a
|
||||||
|
host's FX-parameter list and every automation-lane picker display.
|
||||||
|
|
||||||
|
They should agree (a list whose numbering runs backwards against its display is a trap), so
|
||||||
|
this is one decision, and it is a forever one on both counts.
|
||||||
|
|
||||||
|
**Recommendation: signal-flow order — PITCH/RATE → PITCH ENV → FILTER → FILTER ENV → AMP →
|
||||||
|
VELOCITY → VOICE → MASTER**, which is the deck's own documented ordering rule
|
||||||
|
(`sampleDeckGroups`, *"left to right, in SIGNAL-FLOW order: pitch → filter → amp, then the
|
||||||
|
two instance-wide groups"*), with each group's cells in their own left-to-right order.
|
||||||
|
|
||||||
|
**The alternative is the editor's visual rows** after Γ-W3-T1 — row 1 PITCH/RATE, FILTER,
|
||||||
|
VELOCITY, VOICE; row 2 PITCH ENV, FILTER ENV, AMP; MASTER spanning. Same membership,
|
||||||
|
different sequence.
|
||||||
|
|
||||||
|
**Why the recommendation goes to signal flow:** the visual layout is a canvas-constrained
|
||||||
|
artifact that has already moved twice (Θ-W6-T1 grew the floor 840 → 980, Γ-W3-T1 takes it to
|
||||||
|
1190 and re-rows every group), and within-row order is decided by *width fitting*, not by
|
||||||
|
meaning. Freezing the host-facing order to a layout that demonstrably moves is the wrong
|
||||||
|
coupling; signal flow has never moved and is the deck's own stated rule. §3.6 asked whether
|
||||||
|
the unit tree mirrors the visual grouping or the signal flow and noted "they currently
|
||||||
|
agree" — the reflow is exactly what makes them stop agreeing, so the question has to be
|
||||||
|
answered rather than deferred again.
|
||||||
|
|
||||||
|
**Why it is nonetheless Daniel's:** the counter-argument is real — a user hunting a
|
||||||
|
parameter in REAPER's flat list finds it fastest if the list reads in the order their eyes
|
||||||
|
scan the plugin window. If Daniel weights find-it-on-screen over layout-independence, visual
|
||||||
|
order is the right answer and it is not a mistake; it just also means the parameter order
|
||||||
|
stops being free the next time the deck is re-laid.
|
||||||
|
|
||||||
|
**Grouping is settled either way: `IUnitInfo`, one unit per deck group**, mirroring the
|
||||||
|
group inventory (not the rows). Only the sequence is at issue.
|
||||||
|
|
||||||
|
### 6.5 What the plugin must implement, as behaviour
|
||||||
|
|
||||||
|
Not architecture — the engineer's call is *how*. These are the observable contracts:
|
||||||
|
|
||||||
|
- **`getParameterCount` / `getParameterInfo`** report exactly the §7 list, in §6.4's order,
|
||||||
|
each with: the frozen `id`; a `title` that survives truncation and a distinct
|
||||||
|
`shortTitle`; `units` (`"ms"`, `"st"`, `"%"`, `"dB"`, `""` for exponents) matching what the
|
||||||
|
editor prints, so the host's readout and the knob's label never disagree; `stepCount = 0`
|
||||||
|
(every exposed parameter is continuous — §7.2 is why); `defaultNormalizedValue` per §8's
|
||||||
|
exactness rule; the group's `unitId`; and `kCanAutomate`.
|
||||||
|
- **`kIsBypass` is not set on anything.** The limiter is a safety device, not a bypass, and
|
||||||
|
binding it to `kIsBypass` would hand the host a control that restarts the component
|
||||||
|
(§3.8). The plugin exposes **no** bypass parameter — it is an instrument.
|
||||||
|
- **`setParamNormalized` / `getParamNormalized`** are the model projection of §6.1, not a
|
||||||
|
cached shadow value.
|
||||||
|
- **`normalizedParamToPlain` / `plainParamToNormalized` / `getParamStringByValue` /
|
||||||
|
`getParamValueByString` all route through the ONE taper module Γ-W1-T1 extracts.** This is
|
||||||
|
the structural point of that extraction: the host's normalization, the knob's needle
|
||||||
|
angle, and the overlay node's position must be the *same function*, not three functions
|
||||||
|
that agree today. `getParamStringByValue` reuses the editor's own formatters
|
||||||
|
(`formatEnvTimeMs` and its peers) for the same reason.
|
||||||
|
- **`IParameterChanges` is observed at BLOCK boundaries** — the last point in the block wins
|
||||||
|
for that block (§3.7). Sample-accurate application would put a per-sample "did anything
|
||||||
|
change" question on the per-voice-per-sample path, which the phase-wide guardrail forbids.
|
||||||
|
**State it in the header rather than leaving it to be discovered**; revisit only if
|
||||||
|
audible stepping is reported on a fast curve.
|
||||||
|
- **A host parameter change takes the control's existing commit tier and no other.** Live
|
||||||
|
parameters publish; note-on-latched parameters publish and are read by `snapLive`. Nothing
|
||||||
|
on the automation path may reach `reloadInstrument` or `rebuildVoiceEngine` — which is
|
||||||
|
precisely what §7.2's omissions guarantee.
|
||||||
|
- **`IMidiMapping` is in scope and nearly free** (`ivsteditcontroller.h:558-568`): declaring
|
||||||
|
a CC → `ParamID` map costs one function and gives every exposed parameter MIDI control.
|
||||||
|
**[propose at review]** whether to ship a default map in this track or leave it to REAPER's
|
||||||
|
host-side MIDI learn; do not silently skip it without saying so.
|
||||||
|
- **`IParameterFunctionName` is assessed and NOT implemented**
|
||||||
|
(`ivstparameterfunctionname.h`). Its vocabulary is compressor/panner/dry-wet semantics
|
||||||
|
(`Comp:GainReduction`, `DryWetMix`, `Randomize`); none of it names anything this
|
||||||
|
instrument exposes. Recorded so it is not re-surveyed.
|
||||||
|
- **`IAutomationState` is assessed and NOT implemented**
|
||||||
|
(`ivstautomationstate.h`). It reports the host's automation mode for the **whole plug-in**
|
||||||
|
(`kNoAutomation` / `kReadState` / `kWriteState`), not per parameter — so it cannot answer
|
||||||
|
"is THIS parameter automated," which is the only question §9 would have wanted it for.
|
||||||
|
|
||||||
|
### 6.6 Migration — the verification, not a design
|
||||||
|
|
||||||
|
Every saved project predates parameters, and under §6.1 that is a non-event: the blob
|
||||||
|
restores the model and the parameters are derived from it. Three things are nonetheless
|
||||||
|
**[verify], not assume**:
|
||||||
|
|
||||||
|
1. A pre-parameter project opens with every parameter reading the blob's value, and sounds
|
||||||
|
identical.
|
||||||
|
2. A project saved by the new build and opened by an **older** binary still restores fully —
|
||||||
|
the blob carries everything, so it should; prove it.
|
||||||
|
3. A project with automation drawn on a parameter, saved and reopened, replays that
|
||||||
|
automation against the same plain values — the round trip through the frozen taper is
|
||||||
|
exact at both ends.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. The classification — which controls become parameters
|
||||||
|
|
||||||
|
**The list is DERIVED, never hand-maintained.** Its rule is one line:
|
||||||
|
|
||||||
|
> **A control is an exposed VST3 parameter if and only if its commit class is `Live` or
|
||||||
|
> `NoteOnLatched`.** Everything else is omitted from the parameter list entirely.
|
||||||
|
|
||||||
|
That makes `isLiveDeckParam` / `liveCommitFor` — already *"THE home for why each excluded
|
||||||
|
control is excluded"* — the single source for the parameter list too, which is the standing
|
||||||
|
rule (`core/instrument/CLAUDE.md`: *"which controls are live is ONE decision, recorded in ONE
|
||||||
|
place"*) applied once more rather than a second table opened beside it.
|
||||||
|
|
||||||
|
### 7.1 Exposed — 44 parameters
|
||||||
|
|
||||||
|
Counts are against the control inventory as it stands at the end of Γ-W3; the track asserts
|
||||||
|
the count against the predicate rather than against this table.
|
||||||
|
|
||||||
|
| Group | Parameters | Class |
|
||||||
|
|---|---|---|
|
||||||
|
| **PITCH/RATE** (3) | Rate; Pitch offset; Key-track | Rate **NoteOnLatched** (Γ, settled); Pitch **Live**; Key-track **NoteOnLatched** (see §7.4) |
|
||||||
|
| **PITCH ENV** (6) | attack, hold-fraction, decay, depth + attack/decay curve exponents | **Live** |
|
||||||
|
| **FILTER** (7) | morph, cutoff, Q, drive, mod amount, velocity amount, key-track | **Live** |
|
||||||
|
| **FILTER ENV** (13) | AHDSR A/H/D/S/R; Trigger AHD A/H/D; five curve exponents | **Live** |
|
||||||
|
| **AMP ENVELOPE** (14) | AHDSR A/H/D/S/R; Trigger AHD A/H/D; Trigger length; five curve exponents | **Live**, except Trigger length **NoteOnLatched** (§7.4) |
|
||||||
|
| **VELOCITY** (0) | — | its three cells are curve popups, not scalars (§7.3) |
|
||||||
|
| **VOICE** (0) | — | rebuild tier (§7.2) |
|
||||||
|
| **MASTER** (1) | Master gain | **Live** |
|
||||||
|
|
||||||
|
**Both play-mode faces are exposed simultaneously**, because both are stored simultaneously
|
||||||
|
and always have been. A Trigger stage time is a real parameter whose effect is silent while
|
||||||
|
Gate is selected — which is the same relationship a disabled filter's cutoff already has, and
|
||||||
|
far better than an id whose meaning depends on a mode (§6.3).
|
||||||
|
|
||||||
|
### 7.2 Omitted — the reload and rebuild tiers, and why "omit" beats "expose read-only"
|
||||||
|
|
||||||
|
**Discrete / reload tier:** play mode (Gate|Trigger), pitch engine (Varispeed|Preserve),
|
||||||
|
filter enable, filter morph law, pitch-envelope enable, the three Staged|Spline mode
|
||||||
|
toggles. **Rebuild tier:** voice count, Poly|Mono, Retrigger|Legato. **Latency-changing:**
|
||||||
|
the limiter enable (§3.8 — settled, and its one reopening condition is named there).
|
||||||
|
|
||||||
|
The scoping pass left three options (gain a live path / expose non-automatable / omit).
|
||||||
|
**Omit.** An exposed-but-not-automatable parameter is still listed in the host's generic
|
||||||
|
panel and is still *settable from it*, which would let a generic-panel drag reach
|
||||||
|
`reloadInstrument` from a host thread — strictly worse than the control being absent. VST3's
|
||||||
|
`kIsReadOnly` would prevent that, at the cost of a visible row the user cannot move, in a
|
||||||
|
list whose whole purpose is things you can move. Neither beats absence.
|
||||||
|
|
||||||
|
**Named limitation, stated rather than discovered:** the user cannot automate filter on/off,
|
||||||
|
play mode, the pitch engine, Staged↔Spline, or polyphony. The unlock is written down and is
|
||||||
|
not this track's: **give the control a live path first, then it qualifies by the same
|
||||||
|
rule that excluded it.** Nothing about the parameter system needs to change to admit it.
|
||||||
|
|
||||||
|
**One door this closes by omission** — if voice count were exposed, its 1–32 ceiling would
|
||||||
|
become a frozen `stepCount` and could never be raised. It is not exposed, so it is not
|
||||||
|
frozen; a future "expose voice count" proposal must accept that freeze as part of the price.
|
||||||
|
|
||||||
|
### 7.3 Omitted — structural state
|
||||||
|
|
||||||
|
The loaded capture and `SampleRefs`; root note; loop start/end, the loop crossfade, and
|
||||||
|
**the new loop enable** (Γ-W2-T2, user-owned from that track on); start frame; channel mode;
|
||||||
|
the three velocity transfer curves; the three spline contours; the three overlay-select
|
||||||
|
radios (view state, never persisted); preview velocity (a utility, not a musical parameter).
|
||||||
|
|
||||||
|
None of these are scalars with a taper, and several are not scalars at all. They stay in the
|
||||||
|
blob, which is exactly what §6.1's split is for.
|
||||||
|
|
||||||
|
### 7.4 Two promotions the three-state predicate makes available
|
||||||
|
|
||||||
|
Γ-W2-T1 introduces `NoteOnLatched` for Rate. Two controls that are excluded from the live
|
||||||
|
set **for the note-on-latch reason** currently route through the reload tier, and the new
|
||||||
|
state fits them exactly:
|
||||||
|
|
||||||
|
- **Key-track** — `isLiveDeckParam`'s header already says it *"feed[s] values a voice
|
||||||
|
latches at note-on by design (the pitch ratio…), so live delivery would retune… a note
|
||||||
|
already struck."* That sentence describes `NoteOnLatched`, not `Reload`.
|
||||||
|
- **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.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. One-way doors — the sweep
|
||||||
|
|
||||||
|
**Deliverable, run 2026-08-01 against `dev`.** Once parameters ship, anything that
|
||||||
|
participates in a parameter's normalization is frozen as hard as the id itself. This is the
|
||||||
|
complete list of such constants, with a disposition for each. **Two were already known; five
|
||||||
|
are new; three are already frozen for an unrelated reason and are recorded so they are not
|
||||||
|
"tidied" later.**
|
||||||
|
|
||||||
|
**Scheduled ahead of the parameter track — action required:**
|
||||||
|
|
||||||
|
| # | Constant / property | Disposition |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | **The ms and semitone tapers** (`ui/deck_values`) | Γ-W1-T1. Known. The taper *is* the host normalization. |
|
||||||
|
| 2 | **`kEnvTimeMaxSeconds` / `kGateStageMaxSeconds` 2.0 → 10.0** | Γ-W1-T1, per Daniel's ruling. A range endpoint is normalization. Γ-F3 reversed. |
|
||||||
|
| 3 | **Every default must have an EXACT normalized preimage** — NEW | Γ-W1-T1 acceptance criterion. `ParameterInfo::defaultNormalizedValue` is normalized, so a host's reset-to-default arrives as `toPlain(defaultNorm)`. Γ-W1-T1 makes `resetDeckParam` **bypass** the taper, which fixes the editor's reset and **cannot fix the host's** — the host has no bypass. The taper must therefore be designed so every default round-trips exactly, or the host's reset lands a hair off and the "double-click lands exactly on its default" criterion holds on one surface and not the other. **This is the sharpest new finding in the sweep.** |
|
||||||
|
| 4 | **Do NOT re-taper the filter's four normalized controls** — NEW | Γ-W1-T1 exclusion. Cutoff/Q/morph/drive persist as `cutoffNorm`/`resonanceNorm`/`morphNorm`/`driveNorm` in the **v9 payload** — their laws are *already* wire-frozen today, and re-mapping them would re-tune every saved project independently of automation. §4.2's snap-unit table names them, which is a display concern; nothing in the taper pass may touch their norm↔plain law. |
|
||||||
|
|
||||||
|
**Frozen by this work — record only, no change wanted:**
|
||||||
|
|
||||||
|
| # | Constant | Note |
|
||||||
|
|---|---|---|
|
||||||
|
| 5 | `kKeyTrackMax` = 2.0 (0–200 %) | NEW. Becomes the frozen normalization of both key-track parameters. If 0–200 % is ever going to be thought wrong, now is when it is free. Recommendation: **no change.** |
|
||||||
|
| 6 | The curve-exponent domain 0.1 … 10 and its log travel (`core/util/curve_law.h`) | NEW. Twelve exposed inner dials. Recommendation: **no change.** |
|
||||||
|
| 7 | Master gain's dB taper, −∞ … +24 dB, unity ≈ 0.714 norm (`engine/master_gain`) | NEW. Already a shared pure module, which is why the needle and the audio cannot drift; the host joins as a third consumer. Unity is a default → it is a **#3 case**, and the sharpest one, because a host reset that lands a hair off unity is audible as a gain error. |
|
||||||
|
| 8 | `kPitchDepthMaxSemis` / `kVelocityPitchRangeSemitones` = 24.0 | Already frozen — the v12 wire format stores the pitch curve's y as a fraction of it. Now doubly frozen. The plan already forbids minting a second ±24 constant. |
|
||||||
|
| 9 | Rate's 50–200 % (±12 st linear) and Pitch's ±24 st centre-expanded laws | Set at birth in Γ-W2-T1, two waves before parameters. **Closed by ordering** — recorded so nobody "tunes Rate's range" in a later phase. |
|
||||||
|
| 10 | The parameter INDEX order and the `IUnitInfo` unit ids | NEW, softer than the id freeze but user-facing and cached by some hosts. Set once, at Γ-W4-T1. §6.4. |
|
||||||
|
|
||||||
|
**Swept and clear — not doors:**
|
||||||
|
|
||||||
|
- **The params-payload ladder.** Under §6.1 the blob stays authoritative, so payload growth
|
||||||
|
continues on its own independent axis exactly as before. Automation adds no constraint to
|
||||||
|
it.
|
||||||
|
- **`stepCount`.** No discrete parameter is exposed (§7.2), so no step count is frozen.
|
||||||
|
- **Hold fractions, sustain level, Trigger length.** Natural `[0,1]`, identity taper, nothing
|
||||||
|
to freeze beyond the identity.
|
||||||
|
- **The limiter's −0.3 dBTP ceiling, `kMaxCurvePoints` = 128, the editor floor/budget
|
||||||
|
constants, loop and crossfade frame domains.** None participate in a parameter
|
||||||
|
normalization.
|
||||||
|
|
||||||
|
**Nothing further was found.** The sweep covered: every constant `ui/deck_values` reads or
|
||||||
|
writes; every `*Norm` field in the payload ladder; `core/util/curve_law.h`;
|
||||||
|
`engine/master_gain`; `engine/play_params.h`'s range constants; and the two range constants
|
||||||
|
Phase Γ itself introduces.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. What automation does to the resample bake — and it is not small
|
||||||
|
|
||||||
|
Ξ-W2's bake ends by **resetting the baked-in parameters to default**, "destructive to the
|
||||||
|
dialled settings by design." Exposing those same values as VST3 parameters changes that step
|
||||||
|
in two ways. Both are **Γ-W4-T1's** to carry, because Γ-W4-T1 is what creates them.
|
||||||
|
|
||||||
|
1. **The reset must notify the host.** After §6.1, writing the model without a
|
||||||
|
`beginEdit`/`performEdit`/`endEdit` bracket leaves the host displaying — and, on the next
|
||||||
|
touch, re-imposing — the pre-bake value. **Every internal writer of a model value that is
|
||||||
|
an exposed parameter must go through the one notification path**, and the bake's reset is
|
||||||
|
the first non-gesture writer in the codebase. Enumerating those sites is part of the
|
||||||
|
track, not a follow-up.
|
||||||
|
|
||||||
|
2. **A host automation lane outranks the reset, and we cannot clear it.** An automation lane
|
||||||
|
lives in the host's project data. If a reset-class parameter carries one, the lane replays
|
||||||
|
its curve onto audio that already has that processing baked in — **double processing**,
|
||||||
|
and Ξ-W2-T1's own acceptance criterion ("after the click… sounds as the dialled instrument
|
||||||
|
sounded just before the click") does not hold in that case.
|
||||||
|
|
||||||
|
**There is no detection available.** `IAutomationState` reports the host's automation mode
|
||||||
|
for the whole plug-in, not per parameter (`ivstautomationstate.h:43-53`), and nothing else
|
||||||
|
in the SDK's edit-controller surface answers "is this parameter automated." So "refuse the
|
||||||
|
bake when an automated parameter would be reset" and "reset only the un-automated ones"
|
||||||
|
are both unbuildable, not merely unattractive.
|
||||||
|
|
||||||
|
**Disposition: a named, documented limitation.** The bake resets the plugin's values; it
|
||||||
|
cannot clear the host's lanes, and the user's remedy is to remove the lane. Stated in the
|
||||||
|
product doc and in the track's acceptance criteria as a *known* boundary of the bake's
|
||||||
|
fidelity claim — **not discovered later as a bug report against Ξ.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Build shape
|
||||||
|
|
||||||
|
**Γ-W4-T1 `vst3-parameter-set`** — one track, the last in Phase Γ, specified in
|
||||||
|
`docs/PLAN.md`. It depends on all three earlier waves: on Γ-W1-T1 for the taper module that
|
||||||
|
becomes the host normalization, on Γ-W2-T1 for the three-state predicate and the two new
|
||||||
|
controls, and on Γ-W3 for the final control inventory and a bake whose reset list is already
|
||||||
|
complete (`docs/PLAN.md`, Γ-W3-T2).
|
||||||
|
|
||||||
|
**It takes no rung of the params-payload ladder under §6.1** — nothing new is persisted. A
|
||||||
|
rung is nonetheless **reserved** for it in case the architecture verification (§6.1's
|
||||||
|
`[verify]`) forces a persisted field; if unspent, the rung falls through to the next phase.
|
||||||
|
|||||||
Reference in New Issue
Block a user