docs: retire Phase Gamma from PLAN.md; repair stale line/symbol citations across PLAN, the Lambda build-toolchain audit note, and root CLAUDE.md
This commit is contained in:
@@ -122,9 +122,9 @@ The top-level split is by the pure/shell discipline: `core/` never includes REAP
|
|||||||
types; `shell/` is where those host types are actually touched — the discriminator is "may this
|
types; `shell/` is where those host types are actually touched — the discriminator is "may this
|
||||||
file touch a host type, REAPER *or* VST3 SDK." Subsystem directories sit beneath `core/` (see the
|
file touch a host type, REAPER *or* VST3 SDK." Subsystem directories sit beneath `core/` (see the
|
||||||
table above); `core/instrument/` further subdivides into `bake/` / `engine/` / `map/` / `note/` /
|
table above); `core/instrument/` further subdivides into `bake/` / `engine/` / `map/` / `note/` /
|
||||||
`param/` / `ui/`. Namespaces
|
`param/` / `ui/`. Namespaces mirror directories — `reasampler::<subsystem>` for `core/`, house
|
||||||
mirror directories — `reasampler::<subsystem>` for `core/`, house style for `shell/`. `app/` holds
|
style for `shell/`. `app/` holds `main.cpp` only: API-pointer ownership, `ReaperPluginEntry`, and
|
||||||
`main.cpp` only: API-pointer ownership, `ReaperPluginEntry`, and dispatch.
|
dispatch.
|
||||||
|
|
||||||
For the module list within any one directory — what lives there, its invariants — open that
|
For the module list within any one directory — what lives there, its invariants — open that
|
||||||
directory's own `CLAUDE.md` rather than looking here.
|
directory's own `CLAUDE.md` rather than looking here.
|
||||||
|
|||||||
+20
-585
@@ -190,9 +190,8 @@ in Γ-W4-T1 and changes no wave boundary.
|
|||||||
|
|
||||||
## Phase-wide acceptance criteria
|
## Phase-wide acceptance criteria
|
||||||
|
|
||||||
These bind every track in both phases and are stated once here rather than repeated
|
These bind every track in this plan and are stated once here rather than repeated
|
||||||
per track. **Phase Γ adds a set of its own**, stated in its phase header. **Phase Λ adds
|
per track. **Phase Λ adds a set of its own**, stated in its phase header.
|
||||||
a set of its own**, stated in its phase header.
|
|
||||||
|
|
||||||
### Structural (root `CLAUDE.md`, Daniel 2026-07-28)
|
### Structural (root `CLAUDE.md`, Daniel 2026-07-28)
|
||||||
|
|
||||||
@@ -241,570 +240,6 @@ touches them. The instrument adds a sixth surface that binds every Θ track:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase Γ — The instrument's control surface
|
|
||||||
|
|
||||||
**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
|
|
||||||
dynamic reported latency and a real output meter, one consistent knob interaction/taper law
|
|
||||||
across every variable control **over a stage-time range raised 2 s → 10 s**, a fix for staged
|
|
||||||
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
|
|
||||||
(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`**, and the parameter
|
|
||||||
system's is in **`docs/product/parameter-automation.md` §§6–10**. Read §1.2 (the layout
|
|
||||||
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.
|
|
||||||
|
|
||||||
**All four waves have landed — Phase Γ is complete.** W1 through W4 each carry their own
|
|
||||||
landed note below; see `docs/COMPLETED.md` for every track's full narrative.
|
|
||||||
|
|
||||||
**Fork state — SEVEN ruled, ONE OF THEM LATER REVERSED, NONE OPEN.** Indexed at spec §8,
|
|
||||||
folded into the tracks below:
|
|
||||||
- **Γ-F1** — `kEditorMinHeight` stays **680**.
|
|
||||||
- **Γ-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;*
|
|
||||||
W1-T2's scope grows accordingly — spec §3.1.1.
|
|
||||||
- **Γ-F3 — RULED, THEN REVERSED THE SAME DAY.** First ruled *"the ceiling stays 2.0 s in this
|
|
||||||
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
|
|
||||||
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
|
|
||||||
that is spec §1.6 and governs every future control addition. (**Moved afterwards:**
|
|
||||||
Γ-W3-T1 widened the row block, so §1.6's ledger is now 82 px — the ruling and its
|
|
||||||
purchasing power are unchanged, only the number.)
|
|
||||||
- **Γ-F6** — **ship dynamic latency as ruled.** The `restartComponent(kLatencyChanged)`
|
|
||||||
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
|
|
||||||
fallback, no measurement gate. *This ruling also corrected the analysis* — spec §3.1.1.
|
|
||||||
- **Γ-F7 — RULED: SIGNAL FLOW.** Daniel, 2026-08-01: *"signal flow order."* The VST3 parameter
|
|
||||||
order — both the frozen id numbering and the `getParameterInfo` presentation index — is
|
|
||||||
**PITCH/RATE → PITCH ENV → FILTER → FILTER ENV → AMP → VELOCITY → VOICE → MASTER**, the
|
|
||||||
deck's own `sampleDeckGroups` rule, with each group's cells in the semantic order the id
|
|
||||||
table freezes. The editor's visual rows after the reflow were the rejected alternative.
|
|
||||||
**The reason, because a future reader will ask why the id order does not match the screen:**
|
|
||||||
the editor's layout has already moved twice (Θ-W6-T1 grew the floor 840 → 980; Γ-W3-T1 takes
|
|
||||||
it to 1198 and re-rows every group) and within-row order is settled by width fitting, not by
|
|
||||||
meaning — so **binding a permanently-frozen id order to a demonstrably mobile layout
|
|
||||||
guarantees the two drift apart**, after which the order is neither logical nor matching.
|
|
||||||
Signal flow is the axis that does not move. Full argument and the accepted residual cost:
|
|
||||||
`docs/product/parameter-automation.md` §6.4; **the resulting 44-id table is stated at §6.2.**
|
|
||||||
|
|
||||||
**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. Four 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, **now stated in
|
|
||||||
full as 44 numbered rows in signal-flow order** (§6.2, §6.3); the exposed list is **derived
|
|
||||||
from the three-state commit predicate**, never hand-maintained (§7); and, under Ruling 3,
|
|
||||||
every parameter's **plain unit, range and display precision** (§6.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
|
|
||||||
reporting is **routine** for VST3 instruments and REAPER handles it as a matter of course;
|
|
||||||
the SDK's deactivate/reactivate requirement (`pluginterfaces/vst/ivsteditcontroller.h:105-108`)
|
|
||||||
is the normal contract, not an exotic one. What made the cycle expensive **here** was entirely
|
|
||||||
our own doing: `ReaSamplerProcessor::setActive(true)` called `reloadInstrument()` — a bridge
|
|
||||||
read plus a full WAV re-decode — where a typical plugin's `setActive` only allocates and frees
|
|
||||||
buffers. **The cost was therefore ours to reduce, and the reduction was decoupling reload from
|
|
||||||
activation — not abandoning dynamic latency.** **Landed in Γ-W3**: the activate branch
|
|
||||||
(`reasampler_processor.cpp:86-132`) now resumes the voice state around a parked `SampleData`
|
|
||||||
and reloads only when there is nothing to resume from or a bank refresh moved what the park was
|
|
||||||
decoded from; the deactivate branch parks the PCM and frees everything else. Narrative and
|
|
||||||
consequences are in `docs/COMPLETED.md`.
|
|
||||||
|
|
||||||
**Sequencing against Phase Ξ — the ordering claim is RETIRED and replaced by an owned
|
|
||||||
correction.** This plan previously asserted that Γ must run before Ξ-W2 and called it *"a
|
|
||||||
correctness point, not a preference."* **Ξ-W2-T1 ran first.** That was not a decision anyone
|
|
||||||
took — the track was live before this phase existed (Daniel: *"xi was started before I spun
|
|
||||||
you up, we'll have to correct phase xi inside gamma. wasn't a choice."*). So:
|
|
||||||
|
|
||||||
1. **The bake's reset list is incomplete as shipped, and Γ-W3-T2 amends it.** Rate, pitch
|
|
||||||
offset and the limiter flag are all **reset** under Ξ-W2's own ratified rule (spec §3.4),
|
|
||||||
so no Daniel decision is owed — only the edit, and it must be made **against what Ξ-W2-T1
|
|
||||||
actually shipped rather than against what this plan predicted it would ship.**
|
|
||||||
2. **Ruling 1 adds a second correction of the same shape, and it lands one wave later.**
|
|
||||||
Exposing the reset-class values as VST3 parameters means 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. 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.
|
|
||||||
|
|
||||||
**The organizing constraint.** Six surfaces are single-writer and dictate the wave shape:
|
|
||||||
`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
|
|
||||||
is derived from land **early** (Γ-W1-T4), so every other UI track in the phase is drawn,
|
|
||||||
tested and judged at the final 1198 × 680 window instead of at a size a later wave changes
|
|
||||||
under it. The two-row *arrangement* stays late (Γ-W3-T1), because it can only be measured
|
|
||||||
once the final PITCH/RATE and MASTER descriptors exist. The seam is stated at Γ-W1-T4.
|
|
||||||
2. **`preserve-time-stretch` moved W4 → W1-T5.** It is the longest pole in the phase and has
|
|
||||||
**zero dependency on any UI work** — a pure `core/instrument/engine/` module. Scheduling it
|
|
||||||
last was a scheduling error. Consequence: it is no longer Rate's *successor* but its
|
|
||||||
**prerequisite**, which retires the interim resample-and-cancel stand-in entirely — see
|
|
||||||
Γ-W2-T1.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
**The wave shape after Ruling 1 — three waves become four.** The parameter system cannot be
|
|
||||||
a track inside any existing wave, and the reason is a chain of hard prerequisites, not
|
|
||||||
caution:
|
|
||||||
|
|
||||||
- **after W1-T1**, because the taper and the 10 s ceiling *are* the host-facing
|
|
||||||
normalization, and Γ-W1-T1 is also what extracts them into the one module the host will
|
|
||||||
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 `deckParamCommit` 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 **14** (`map/component_state_io.h:163`) 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 v15 / v16 / v17-reserved.** Ξ-W3-T1 landed and
|
|
||||||
> consumed a rung (v14, the bake Hold division) — not Ξ-W2-T1, which took none as specced — so
|
|
||||||
> every number shifted 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
|
|
||||||
defect report.** Raising the floor without the reflow leaves the greedy whole-group wrap
|
|
||||||
packing two ragged left-aligned rows with categorically wrong membership. Γ-W1-T4 states the
|
|
||||||
exact interim layout; do not "fix" it in a track that does not own it.
|
|
||||||
|
|
||||||
**Phase-wide acceptance criteria** (in addition to the ones stated at the top of this file):
|
|
||||||
- **Bypassed means byte-identical.** With the limiter off, the per-sample output path is
|
|
||||||
byte-identical to today's bare ramped multiply — the same discipline that makes
|
|
||||||
`live == nullptr` byte-identical to the pre-live core and the filter's exact skip at
|
|
||||||
`modAmount == 0` hold the at-rest path unchanged.
|
|
||||||
- **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
|
|
||||||
bypassed. Re-tapering a knob **and raising the stage-time ceiling** (both Γ-W1-T1) change
|
|
||||||
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
|
|
||||||
load-bearing in the v12 wire format. The new Pitch knob **reads** it; it does not mint a
|
|
||||||
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, and from Γ-W4-T1 the taper IS the
|
|
||||||
host's `toPlain`/`toNormalized`.** Γ-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` / `plainParamToNormalized` (Γ-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. Under Ruling 3 this stops being an analogy: `toNormalized` is
|
|
||||||
not *like* the taper, it *is* the taper (`docs/product/parameter-automation.md` §6.7.3).
|
|
||||||
- **ONE formatter per unit category, and the editor and the host are both its callers.** The
|
|
||||||
formatter is pure and returns the **digits** of a plain value in that category's single
|
|
||||||
`units` string — no embedded unit, no magnitude-switched unit, no width-conditional
|
|
||||||
abbreviation, no caller-side branch. The editor's knob label and
|
|
||||||
`getParamStringByValue` read the same function. **The editor and the host printing
|
|
||||||
different text for the same stored value is a defect class, forbidden structurally rather
|
|
||||||
than caught at review** — same discipline, same reason, as the taper criterion above.
|
|
||||||
Consequences (existing formatters stop embedding their unit; cutoff's `k` abbreviation is
|
|
||||||
retired; the curve dial's `^` is static cell chrome, not value): spec §6.7.2.
|
|
||||||
- **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. **Ruling 3 tightens this twice
|
|
||||||
and adds one non-requirement** (§6.7.7): `defaultNormalizedValue` is **computed** as
|
|
||||||
`toNormalized(default)`, never written as a normalized literal; the assertion is made on
|
|
||||||
`toPlain(defaultNormalizedValue)`, the pair the host actually calls; and
|
|
||||||
`toNormalized(toPlain(n)) == n` at **arbitrary** n is explicitly NOT required — no log map
|
|
||||||
satisfies it in double, and demanding it would over-constrain the taper for nothing.
|
|
||||||
- **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. **This does NOT
|
|
||||||
conflict with Ruling 3's real-unit requirement**, and the two must not be read as a
|
|
||||||
collision: `toPlain` is a pure read-side mapping that never touches the stored value, so
|
|
||||||
reporting Hz / Q / drive depth means **calling** `filterCutoffHzFromNorm` and its peers, not
|
|
||||||
replacing them — which the editor's own labels already do today. The prohibition forbids
|
|
||||||
*editing* those laws; the requirement is satisfied by *calling* them. One additive gap:
|
|
||||||
drive has no published inverse and `filterNormFromDriveDepth` must be added beside the two
|
|
||||||
that exist — the analytic inverse of a frozen law is not a change to it. Spec §6.7.5.
|
|
||||||
- **Shift-snap is a drag rule; `stepCount` is a parameter property; they are independent.**
|
|
||||||
The editor's snap grid must never be exposed as `ParameterInfo::stepCount` — that would
|
|
||||||
quantize the parameter itself, permanently and for the host's automation too, freezing the
|
|
||||||
grid into the forever contract and putting continuous cents out of reach from a lane.
|
|
||||||
**All 44 exposed parameters ship `stepCount = 0`**, swept and confirmed, and the coincidence
|
|
||||||
is structural: every discrete control is reload or rebuild tier and therefore omitted by the
|
|
||||||
predicate. Spec §6.7.6.
|
|
||||||
- **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 `deckParamCommit` / `liveCommitFor`, and no list that can drift from it.
|
|
||||||
- **The window floor is 1198 × 680 and must not exceed 1280 × 720.** **Γ-W1-T4 set it at
|
|
||||||
1190, in wave 1; no other track in the phase may move it** — with ONE ruled exception,
|
|
||||||
Daniel 2026-08-02: Γ-W3-T1 widened the row block 1020 → 1028 and the floor 1190 → 1198, so
|
|
||||||
the justification law puts both rows' filter edges on one pixel (spec §1.3). That is the
|
|
||||||
only reopening, and only these two constants moved. From that point every track is
|
|
||||||
authored and judged at it. A track that pushes the floor past 1280 has failed, not overrun.
|
|
||||||
**`kEditorMinHeight` stays 680** (Γ-F1). The remaining **82 px of width headroom is the
|
|
||||||
budget for the life of this layout** — one deck cell is 60 px, so there is room for exactly
|
|
||||||
one more, once. Spec §1.6 states the ledger; read it before adding any control. Chrome-row
|
|
||||||
additions are a **separate purse** (they are paid for out of the title slot, not the floor)
|
|
||||||
and must not be charged against this one.
|
|
||||||
- **Reported latency is zero unless the limiter is on.** `getLatencySamples()` returns 0 with
|
|
||||||
the limiter bypassed, in every track and at every point in the phase. Only W1-T2 may
|
|
||||||
introduce a non-zero value, and only under the limiter-on condition.
|
|
||||||
- **Geometry stays pure.** Every new layout, cap, label and hit-test rule lands in a pure
|
|
||||||
CTest-covered module (`knob_deck`, `sample_bands`, `waveform_view`), never in a painter.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Γ-W1 — Foundations
|
|
||||||
|
|
||||||
**Depends on:** nothing in this phase.
|
|
||||||
|
|
||||||
**All seven tracks have landed** — Γ-W1-T1 (`knob-interaction-law`), Γ-W1-T2
|
|
||||||
(`master-bus-audio`), Γ-W1-T3 (`contour-trace-curves`), Γ-W1-T4 (`editor-floor-and-row-law`),
|
|
||||||
Γ-W1-T5 (`preserve-time-stretch`), Γ-W1-T6 (`exhaustive-switch gate on pure libraries`), and
|
|
||||||
Γ-W1-T7 (`psola-preserve`) — see `docs/COMPLETED.md` for the full narrative of each. **T6 has
|
|
||||||
no subsection below**, matching this plan's original choice not to give it one; its record in
|
|
||||||
`docs/COMPLETED.md` is reconstructed from the CMake change and its enforcement call site
|
|
||||||
rather than from a spec section here.
|
|
||||||
|
|
||||||
#### Γ-W1-T1 — `knob-interaction-law`
|
|
||||||
|
|
||||||
**Landed** — see `docs/COMPLETED.md` for the full narrative. One consistent interaction and
|
|
||||||
taper law across every variable control, landed before Rate/Pitch or any VST3 parameter
|
|
||||||
existed so both are authored into it rather than retrofitted. The taper is extracted into
|
|
||||||
its own pure module, `core/instrument/ui/param_taper` — the one home three consumers read
|
|
||||||
(the knob's needle, the AHDSR overlay's schematic axis and its drag inverse, and — from a
|
|
||||||
later wave — the VST3 host's `toPlain`/`toNormalized`). Shift snaps to whole units in the
|
|
||||||
control's displayed category; Ctrl scales the drag by 0.05; Shift+Ctrl resolves to Shift; a
|
|
||||||
mid-drag modifier transition re-anchors value and cursor position. **The stage-time ceiling
|
|
||||||
moves 2.0 s → 10.0 s** (`kGateStageMaxSeconds`/`kEnvTimeMaxSeconds`, moved together so they
|
|
||||||
cannot drift), reversing Γ-F3 on Daniel's later ruling, and every default now has an exact
|
|
||||||
normalized preimage under its own taper — the requirement Γ-W4-T1's `defaultNormalizedValue`
|
|
||||||
depends on, since a host's reset-to-default has no `resetDeckParam` bypass to fall back on.
|
|
||||||
The filter's four `*Norm` controls stay untouched (wire-frozen in payload v9); the change is
|
|
||||||
persistence-neutral throughout.
|
|
||||||
|
|
||||||
#### Γ-W1-T2 — `master-bus-audio`
|
|
||||||
|
|
||||||
**Landed** — see `docs/COMPLETED.md` for the full narrative. The master bus: a bypassable
|
|
||||||
true-peak limiter (baked −0.3 dBTP ceiling, default off, no makeup gain, stereo-linked,
|
|
||||||
sidechain-only oversampled detection), the meter's audio and publication half, and the
|
|
||||||
plugin's first latency report — `getLatencySamples()` returns 0 with the limiter off and the
|
|
||||||
lookahead in samples with it on, driving `restartComponent(kLatencyChanged)` on toggle. New
|
|
||||||
pure modules `core/instrument/engine/limiter` and `engine/meter_ballistics`. **Spent the
|
|
||||||
phase's first payload rung: `kParamsPayloadVersion` reaches 15**, appending the limiter
|
|
||||||
enable flag as a strict suffix.
|
|
||||||
|
|
||||||
#### Γ-W1-T3 — `contour-trace-curves`
|
|
||||||
|
|
||||||
**Landed** — see `docs/COMPLETED.md` for the full narrative. Staged envelope segments now
|
|
||||||
draw as the curve their exponent defines, closing the defect where the mid-segment knot
|
|
||||||
floated off its own trace. A new pure module, `curve_tessellate`, draws every sloped stage
|
|
||||||
through the same curve the audio's evaluators use, so the drawn stage and the sound it makes
|
|
||||||
cannot diverge. All three envelopes, both play modes, every sloped stage, share the one fix;
|
|
||||||
audio is unchanged.
|
|
||||||
|
|
||||||
#### Γ-W1-T4 — `editor-floor-and-row-law`
|
|
||||||
|
|
||||||
**Landed** — see `docs/COMPLETED.md` for the full narrative. Commits the editor's canvas
|
|
||||||
ahead of the rest of the phase's UI work: `kEditorMinWidth` moves 980 → 1190
|
|
||||||
(`kEditorMinHeight` stays 680, Γ-F1), derived from three budget constants — the row block
|
|
||||||
(1020), MASTER's reserved width (142), and the 1280 ceiling (`kEditorCeilingWidth`, relocated
|
|
||||||
into `sample_bands.h`) — leaving 90 px of headroom. Row membership becomes a property of the
|
|
||||||
group id via an exhaustive `deckRowFor(DeckGroupId)` switch (Sound / Contour / Spanning),
|
|
||||||
consumed by no one yet — **that consumption, and the fit inside the 1020 block, is
|
|
||||||
Γ-W3-T1's** to assert. No drawing code, descriptor, parameter, or audio changed in this
|
|
||||||
track. **Superseded in part:** Γ-W3-T1 asserted the fit and found 1020 could not deliver the
|
|
||||||
tie-line it was chosen for, so the block is now 1028 and the floor 1198 — see that track's
|
|
||||||
block below.
|
|
||||||
|
|
||||||
#### Γ-W1-T5 — `preserve-time-stretch`
|
|
||||||
|
|
||||||
**Landed** — see `docs/COMPLETED.md` for the full narrative. A real pitch-preserving
|
|
||||||
time-stretcher for Preserve mode, moved up from a later wave (Daniel, 2026-08-01) so Rate
|
|
||||||
ships onto a finished engine instead of a disposable stand-in — the interim
|
|
||||||
resample-and-cancel path that had been planned for Γ-W2-T1 was not built at all. New
|
|
||||||
header-only pure module `time_stretch` alongside `pitch_shift`'s existing shift-ratio
|
|
||||||
control; rate 1.0 is exactly one source frame per output frame with no residue, keeping the
|
|
||||||
unity-ratio Preserve read bit-identical to the pre-stretch engine. No new third-party
|
|
||||||
dependency, no allocation/lock in `process()`, no per-sample dispatch.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Γ-W1-T7 — `psola-preserve`
|
|
||||||
|
|
||||||
**Landed** — see `docs/COMPLETED.md` for the full narrative, including the corrected closure
|
|
||||||
status below. Preserve's splices become pitch-synchronous: a new pure module,
|
|
||||||
`core/instrument/engine/period_detect` (two-pass YIN), estimates the source's fundamental
|
|
||||||
period once at load; `pitch_shift`'s splice jump becomes the multiple of that period nearest
|
|
||||||
the fixed window, so an aligned landing point exists by construction. Detection runs off the
|
|
||||||
audio thread by link graph — `sampler_core` does not link `period_detect` — and an unknown
|
|
||||||
period restores the fixed-window geometry byte for byte. A period is derived from the audio
|
|
||||||
at load, so it is cache, not state: no `ComponentState` field, no payload rung. It gates the
|
|
||||||
Rate control (Γ-W2-T1) on the plan's own stated principle that Rate must not ship before its
|
|
||||||
Preserve engine.
|
|
||||||
|
|
||||||
**Both failure modes this track set out to close are now closed.** The **geometry** failure
|
|
||||||
mode (no phase-aligned landing existing inside the search window for low material) closed at
|
|
||||||
the original merge. The **cadence** failure mode — splices recurring faster than the output
|
|
||||||
period — was left open at that point, with unresolved review findings from a later review of
|
|
||||||
a follow-up fold; three remediation commits have since landed and a re-review confirmed the
|
|
||||||
earlier findings closed. The closing measurement (one-machine, Debug-build) is recorded in
|
|
||||||
`docs/COMPLETED.md`, not restated here.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Γ-W2 — New controls, and the overlay's marks
|
|
||||||
|
|
||||||
**Depended on Γ-W1** for the taper law and the Preserve stretcher (T1), and the contour trace
|
|
||||||
and the floor/row law (T2) — see `docs/COMPLETED.md` for the full narrative.
|
|
||||||
|
|
||||||
**Both tracks have landed** — Γ-W2-T1 (`pitch-rate-deck`) and Γ-W2-T2 (`loop-crossfade-ux`) —
|
|
||||||
see `docs/COMPLETED.md` for the full narrative of each.
|
|
||||||
|
|
||||||
#### Γ-W2-T1 — `pitch-rate-deck`
|
|
||||||
|
|
||||||
**Landed** — see `docs/COMPLETED.md` for the full narrative. PITCH became PITCH/RATE: three
|
|
||||||
knobs (`Key Trk | Rate | Pitch`) under the existing Varisp|Presrv toggle, both new controls
|
|
||||||
wired through the engine. Rate is 50–200 % on a taper linear in semitones over ±12 (the stated
|
|
||||||
exception to the centre-expansion law), note-on latched; Pitch is a ±24 st baseline offset,
|
|
||||||
live. Keytrack × rate × pitch-offset compound into a single read-increment multiply — the
|
|
||||||
per-sample path gained nothing. Merged as `9dbb8b8`; spent the phase's second payload rung,
|
|
||||||
v16, as a strict suffix, a v15 blob lifting to rate 100 % / pitch 0 st.
|
|
||||||
|
|
||||||
**`isLiveDeckParam` was renamed `deckParamCommit`** and now returns a three-state `LiveCommit`
|
|
||||||
(`Live` / `NoteOnLatched` / `Reload`) rather than a bool — one predicate widened, not a second
|
|
||||||
mechanism. **Rate classifies `NoteOnLatched`, Pitch classifies `Live`; Γ-W4-T1 derives the
|
|
||||||
VST3 exposed parameter set from this predicate**, so the classification is load-bearing two
|
|
||||||
waves out.
|
|
||||||
|
|
||||||
Code review found one Critical (the resample bake read a stale frame window at non-unity Rate;
|
|
||||||
fixed by deriving the window from the rate the voice actually reads at, proved non-vacuous by
|
|
||||||
reverting the fix) and folded in a ruling that Pitch, unlike key-track and velocity, is now
|
|
||||||
compensated against Varispeed's stage-time coupling. A Varispeed golden hash was added,
|
|
||||||
honestly witnessed from the remediation commit rather than pre-track, so it stands as a
|
|
||||||
witness for the next track rather than proof of this one.
|
|
||||||
|
|
||||||
#### Γ-W2-T2 — `loop-crossfade-ux`
|
|
||||||
|
|
||||||
**Landed** — see `docs/COMPLETED.md` for the full narrative. An explicit loop enable, a
|
|
||||||
legible mark grammar, and the crossfade painted where it is heard. **No format change** — no
|
|
||||||
`ComponentState` version moved, no new persisted field, `resolveLoop` untouched, audio
|
|
||||||
unchanged. The enable **is** `SampleLoop::hasLoop`, whose provenance changes from
|
|
||||||
marker-gesture-derived to user-owned, with the gestures as shortcuts onto it. Merged as
|
|
||||||
`a8e30a9`. A new pure module, `core/instrument/ui/loop_marks`, holds the state machine
|
|
||||||
(`resolveLoopMarks`/`applyLoopMarks`); the four marks (START/LOOP/END/XFADE) get one grammar —
|
|
||||||
line + shaped cap + label, the cap being the grip — with cap/label/suppression geometry pure
|
|
||||||
and unit-tested. The crossfade moved to `[loopEnd − crossfade, loopEnd)`, drawing as a
|
|
||||||
top-and-bottom edge wedge, never a second fill; the loop fill's peak alpha stays exactly 0.20.
|
|
||||||
|
|
||||||
**START draws in `overlay/trace`, a deliberate deviation from
|
|
||||||
`docs/product/instrument-control-surface.md` §6.3's table**, because the spec's
|
|
||||||
`accent/primary` choice would measure 1:1 against the waveform fill it marks.
|
|
||||||
|
|
||||||
Code review found three Majors, all fixed before merge. `docs/TODO.md`'s "Pre-existing
|
|
||||||
staged-envelope-node shadow at zero-attack" entry was resolved incidentally — giving START a
|
|
||||||
cap is what closed it — and rewritten in place with the recorded outcome by the track itself.
|
|
||||||
**One thing is deliberately left open, not accepted:** the audible crossfade wedge draws at
|
|
||||||
1.60:1 against the envelope trace, under the 3:1 floor, with no alpha fix available; Daniel is
|
|
||||||
judging it visually in the DAW and has not yet ruled. **Neither track has been verified in a
|
|
||||||
running DAW; both are asserted in CTest only** — 99/99, Debug config, on one machine.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Γ-W3 — The reflow, and the bake correction
|
|
||||||
|
|
||||||
**Depended on Γ-W2** for the PITCH/RATE descriptor (W2-T1) — the reflow measured the real
|
|
||||||
three-cell group rather than a forecast of it, which is the whole reason the arrangement was
|
|
||||||
scheduled late. **Depended on Γ-W1** for W1-T2's published meter/GR/clip state, which MASTER's
|
|
||||||
deck draws, and W1-T4's floor, budget constants and row predicate, which T1 consumed rather
|
|
||||||
than re-derived. **T2 depended on Phase Ξ** — `Ξ-W2-T1 (resample-bake-chain)` landing on `dev`
|
|
||||||
first, the phase's only external gate — see `docs/COMPLETED.md` for the full narrative of each
|
|
||||||
landed track.
|
|
||||||
|
|
||||||
**All three tracks have landed** — Γ-W3-T1 (`deck-reflow`), Γ-W3-T2
|
|
||||||
(`bake-reset-amendment`), and Γ-W3-T3 (`bake-prints-limiter`) — see `docs/COMPLETED.md` for
|
|
||||||
the full narrative of each.
|
|
||||||
|
|
||||||
**None of the three tracks takes a payload rung.** T1 was layout only; T2 changed a reset list,
|
|
||||||
not a format; T3 changes what the render's audio contains, not what is stored.
|
|
||||||
|
|
||||||
#### Γ-W3-T1 — `deck-reflow`
|
|
||||||
|
|
||||||
**Landed** — see `docs/COMPLETED.md` for the full narrative. The knob deck's row law stops
|
|
||||||
being a wrap outcome and becomes a property of the group descriptor: two categorical rows
|
|
||||||
(Sound — PITCH/RATE, FILTER, VELOCITY, VOICE; Contour — PITCH ENV, FILTER ENV, AMP ENVELOPE)
|
|
||||||
plus a double-height, right-anchored MASTER bus deck outside both, carrying the limiter enable
|
|
||||||
toggle, one reserved cell, the output meter column and a passive gain-reduction lamp.
|
|
||||||
`DeckRow`/`deckRowFor` (`ui/deck_groups`) is an exhaustive switch over every `DeckGroupId`, so a
|
|
||||||
group added later without a row assignment is a compile error; the greedy whole-group wrap this
|
|
||||||
replaces is gone entirely. FILTER's `Band|Notch` moves into its caption's previously-unused
|
|
||||||
second toggle slot, taking the group from 524 to 432 px; VOICE deliberately keeps its
|
|
||||||
`Retrig|Legato` row toggle, since moving it to the caption would make VOICE wider, not narrower.
|
|
||||||
|
|
||||||
**The row block widened 1020 → 1028 px and the editor floor moved 1190 → 1198 px (Daniel's
|
|
||||||
ruling, 2026-08-02).** The originally specified 1020 could not simultaneously deliver the
|
|
||||||
filter tie-line (both rows' FILTER/FILTER ENV right edges at one x) and equal,
|
|
||||||
no-narrower-than-12px gutters on both rows; at 1028 all three hold, with 82 px of headroom left
|
|
||||||
against the 1280 px ceiling.
|
|
||||||
|
|
||||||
The MASTER meter's per-block state moved from a plain overwriting store to an accumulated one —
|
|
||||||
at 48 kHz/512-frame blocks roughly 47 blocks elapse between two 500 ms UI ticks, and the
|
|
||||||
overwriting store had been displaying one block in ~47 and dropping the rest. The processor now
|
|
||||||
folds a per-channel peak max and a limiter min-gain across the whole interval, drained by
|
|
||||||
`masterBusMeter()`. **The instrument reload was decoupled from VST3 activation as part of this
|
|
||||||
track** — `setActive(false)` now parks the decoded `SampleData` and destroys only the voice
|
|
||||||
state, `setActive(true)` rebuilds the voices around the parked sample — discharging the
|
|
||||||
`docs/TODO.md` follow-up already recorded in full there. The limiter toggle's commit is split so
|
|
||||||
the audible state commits inline on the click and only the host's
|
|
||||||
`restartComponent(kLatencyChanged)` notification is deferred to the editor's existing 500 ms
|
|
||||||
sync tick.
|
|
||||||
|
|
||||||
**Not verified in a running DAW — CTest-asserted only:** the meter at its 500 ms UI cadence, the
|
|
||||||
GR lamp under real limiter action, the limiter toggle's latency renegotiation, the clip cap's
|
|
||||||
click-to-clear, and the recapture-while-editor-closed path.
|
|
||||||
|
|
||||||
#### Γ-W3-T2 — `bake-reset-amendment`
|
|
||||||
|
|
||||||
**Landed** — see `docs/COMPLETED.md` for the full narrative. The correction Phase Γ owed Phase
|
|
||||||
Ξ: Ξ-W2-T1's bake shipped ahead of the sequencing this plan asserted, so its reset list predated
|
|
||||||
rate, pitch offset, the limiter enable, and the loop enable. **`resetAfterBake` needed no code
|
|
||||||
change** — all four already reset by construction, since the function defaults everything and
|
|
||||||
copies back only survivors, and none of the four was ever named a survivor. The track shipped
|
|
||||||
field-by-field assertions over two independently-dialled fixtures, never struct equality, plus a
|
|
||||||
mutation-verified spot-check sweep confirming both fixtures actually moved every asserted field
|
|
||||||
off its default.
|
|
||||||
|
|
||||||
**One invariant correction, at the time this track landed:** `bake/CLAUDE.md` had claimed the
|
|
||||||
whole signal chain prints, master gain included. It didn't yet — the render's gain multiply was
|
|
||||||
the only master-stage value it printed; the limiter ran in the processor's block, off the bake
|
|
||||||
path entirely, so a capture baked with the limiter engaged came back unlimited. **Γ-W3-T3
|
|
||||||
(below) has since closed that gap** — the limiter is printed too now, and `bake/CLAUDE.md`'s
|
|
||||||
invariant text is corrected again to match.
|
|
||||||
|
|
||||||
**Spec:** `docs/product/instrument-control-surface.md` §3.4, and Ξ-W2-T1's own "Reset scope"
|
|
||||||
block, now in `docs/COMPLETED.md` (Phase Ξ has landed and its `PLAN.md` section is retired) —
|
|
||||||
**which is the ratified rule this track applies, not a rule it may reinterpret.**
|
|
||||||
|
|
||||||
**Neither track has been verified in a running DAW; both are asserted in CTest only.**
|
|
||||||
|
|
||||||
#### Γ-W3-T3 — `bake-prints-limiter`
|
|
||||||
|
|
||||||
**Landed** — see `docs/COMPLETED.md` for the full narrative. The bake's master stage now
|
|
||||||
prints the limiter as well as the gain multiply: `renderBake` instantiates its own `Limiter`
|
|
||||||
(the bake-only-engine precedent, never linked into `reaper_reasampler`), the lookahead is
|
|
||||||
compensated inside the render (an extra `flushFrames` of silence past the window, the capture
|
|
||||||
read out at `leadInFrames + flushFrames`), and `bake/CLAUDE.md`'s invariant text is corrected
|
|
||||||
alongside the code. All three of the track's `[propose at review]` open questions are
|
|
||||||
answered in that entry.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Γ-W4 — VST3 parameters
|
|
||||||
|
|
||||||
**Depended on every earlier wave in this phase** — W1-T1's taper module (the host-facing
|
|
||||||
normalization itself), W1-T2 and W2-T1's control inventory and three-state commit
|
|
||||||
predicate (the exposed set is exactly `Live ∪ NoteOnLatched`), W3-T1's final MASTER
|
|
||||||
inventory, and W3-T2's completed bake reset list — each a hard prerequisite rather than a
|
|
||||||
courtesy. See `docs/COMPLETED.md` for the full narrative of each.
|
|
||||||
|
|
||||||
**The track has landed** — Γ-W4-T1 (`vst3-parameter-set`) — see `docs/COMPLETED.md` for
|
|
||||||
the full narrative. One track: the storage decision governed every part of the work, so a
|
|
||||||
pure/host-wiring split would have been serial and bought no concurrency.
|
|
||||||
|
|
||||||
#### Γ-W4-T1 — `vst3-parameter-set`
|
|
||||||
|
|
||||||
**Landed** — see `docs/COMPLETED.md` for the full narrative. The instrument now reports
|
|
||||||
its automatable parameters to the host under a FOREVER-FROZEN `ParamID` table (blocks of
|
|
||||||
100 per deck group in signal-flow order, steps of 10 within a block, a curve dial at its
|
|
||||||
outer knob's id + 1) — 44 of 44 issue, derived from `deckParamCommit` / `liveCommitFor`
|
|
||||||
rather than hand-maintained, each with real units, a plain range and display precision at
|
|
||||||
the host boundary. A new pure module, `core/instrument/param`, holds the id table, the
|
|
||||||
plain-value layer, the one formatter per unit category, and the audio thread's
|
|
||||||
block-boundary merge decision; `shell/instrument/instrument_params` adapts it onto
|
|
||||||
`Steinberg::Vst::Parameter` and decides nothing.
|
|
||||||
|
|
||||||
**Both VST3 delivery channels are serviced** — the controller's `setParamNormalized` and
|
|
||||||
the audio thread's `IParameterChanges` drain, mirroring the SDK's own
|
|
||||||
`SingleComponentEffect` sample rather than the controller-only delivery an earlier pass
|
|
||||||
shipped. **A host automation point's authority is bounded**, not permanent: it outranks
|
|
||||||
the model only until the UI thread folds it in and republishes — at most one UI tick —
|
|
||||||
never a later restore, bake reset or knob move. `shell/instrument/CLAUDE.md`'s "THE
|
|
||||||
AUTHORITY MODEL" section states it, `param_merge` enforces it.
|
|
||||||
|
|
||||||
**Two rulings, both Daniel, 2026-08-02.** Pitch key-track and Trigger length promote from
|
|
||||||
`Reload` to `NoteOnLatched` — the promotion that takes the count to 44 of 44 and issues
|
|
||||||
ids 1000 and 1450, and which needed `LiveValues`/`foldLive` widened and `Voice::start`
|
|
||||||
handed the two latched values, not just a predicate flip; the new `param::valueHomeFor`
|
|
||||||
guard closes the class of bug the promotion exposed. And the curve-shape dials' ±0.01
|
|
||||||
snap-to-centre band now applies on the mouse-drag path only, never on a host-facing map.
|
|
||||||
|
|
||||||
**The bake's reset now notifies the host; the double-processing limitation (a host
|
|
||||||
automation lane outranking the reset) is a named, documented boundary, not discovered
|
|
||||||
later.** `IMidiMapping`, `IParameterFunctionName` and `IAutomationState` are all assessed
|
|
||||||
and NOT implemented.
|
|
||||||
|
|
||||||
**Not verified in a running DAW — CTest-asserted only.** `docs/TODO.md` carries the
|
|
||||||
residual DAW-verification items.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase Λ — ReaSampler on Linux: both artifacts, shipped
|
## Phase Λ — ReaSampler on Linux: both artifacts, shipped
|
||||||
|
|
||||||
**Ships:** `reaper_reasampler.so` and `reasampler_9000.vst3` built, installed and documented
|
**Ships:** `reaper_reasampler.so` and `reasampler_9000.vst3` built, installed and documented
|
||||||
@@ -1109,18 +544,18 @@ only; the `WIN32` gate is Λ-W6-T1's**), `README.md`, and root `CLAUDE.md` §"Bu
|
|||||||
|
|
||||||
**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T3 and §Λ-F2; findings Λ-01, L2-06, L2-07.
|
**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T3 and §Λ-F2; findings Λ-01, L2-06, L2-07.
|
||||||
|
|
||||||
**The defect.** `panel_window.cpp:135` is `CreateDialogParam(g_hInst,
|
**The defect.** `panel_window.cpp:169` is `CreateDialogParam(g_hInst,
|
||||||
MAKEINTRESOURCE(IDD_BANK_PANEL), …)`, which SWELL resolves out of a per-module registry
|
MAKEINTRESOURCE(IDD_BANK_PANEL), …)`, which SWELL resolves out of a per-module registry
|
||||||
populated by a **resgen-generated source file that is not in the Linux target**:
|
populated by a **resgen-generated source file that is not in the Linux target**:
|
||||||
`src/app/CMakeLists.txt:97` has the `target_sources` line commented out (`:86` for macOS).
|
`src/app/CMakeLists.txt:109` has the `target_sources` line commented out (`:98` for macOS).
|
||||||
The registry head stays null, `SWELL_CreateDialog` returns null, `:137` returns, and the
|
The registry head stays null, `SWELL_CreateDialog` returns null, `:171` returns, and the
|
||||||
toggle action is a **silent no-op** — no console line, no Actions-list checkmark. Three
|
toggle action is a **silent no-op** — no console line, no Actions-list checkmark. Three
|
||||||
defects stack inside the commented-out instructions themselves: the script named at `:96`
|
defects stack inside the commented-out instructions themselves: the script named at `:108`
|
||||||
(`mac_resgen.php`) does not exist, the output filename is wrong, and the output is an
|
(`mac_resgen.php`) does not exist, the output filename is wrong, and the output is an
|
||||||
`#include`-only artifact that cannot be a `target_sources` entry at all.
|
`#include`-only artifact that cannot be a `target_sources` entry at all.
|
||||||
|
|
||||||
**Surface boundary — owns:** `src/shell/panel/panel_window.cpp` (the `CreateDialogParam` call
|
**Surface boundary — owns:** `src/shell/panel/panel_window.cpp` (the `CreateDialogParam` call
|
||||||
at `:135–137`, the dialog proc's platform contract, the drop-accept opt-in at `:145–150`),
|
at `:169–171`, the dialog proc's platform contract, the drop-accept opt-in at `:179–184`),
|
||||||
`src/resource.rc`, `src/resource.h`, and — **under the resgen route only** — one
|
`src/resource.rc`, `src/resource.h`, and — **under the resgen route only** — one
|
||||||
`target_sources` line in `src/app/CMakeLists.txt`'s `else()` branch plus a new include-shim
|
`target_sources` line in `src/app/CMakeLists.txt`'s `else()` branch plus a new include-shim
|
||||||
TU. **Does not own:** any other panel TU, `draw_kit`, or any CMake target property.
|
TU. **Does not own:** any other panel TU, `draw_kit`, or any CMake target property.
|
||||||
@@ -1340,7 +775,7 @@ invariants Linux weakens are stated where a reviewer will read them.
|
|||||||
|
|
||||||
**Spec:** `docs/product/linux-readiness.md` §Λ-W4-T3 and §Λ-F3; findings Λ-06, L2-10, L2-11.
|
**Spec:** `docs/product/linux-readiness.md` §Λ-W4-T3 and §Λ-F3; findings Λ-06, L2-10, L2-11.
|
||||||
|
|
||||||
**Surface boundary — owns:** the `target_sources` list in `src/app/CMakeLists.txt:8–51` (**the
|
**Surface boundary — owns:** the `target_sources` list in `src/app/CMakeLists.txt:8–55` (**the
|
||||||
list; the property blocks are Λ-W2-T2's**), any new platform-sibling TU the sweep showed was
|
list; the property blocks are Λ-W2-T2's**), any new platform-sibling TU the sweep showed was
|
||||||
needed, `src/shell/actions/drag_out_win.h`'s invariant comment, and the corresponding
|
needed, `src/shell/actions/drag_out_win.h`'s invariant comment, and the corresponding
|
||||||
`src/shell/**/CLAUDE.md` invariant passages. **Does not own:** any behaviour change in a
|
`src/shell/**/CLAUDE.md` invariant passages. **Does not own:** any behaviour change in a
|
||||||
@@ -1476,7 +911,7 @@ or `.h` under `shell/instrument/`.
|
|||||||
`$APPFOLDER/vst3/`.
|
`$APPFOLDER/vst3/`.
|
||||||
- **`SWELL_PROVIDED_BY_APP` + the modstub TU** are added to this target in the **default
|
- **`SWELL_PROVIDED_BY_APP` + the modstub TU** are added to this target in the **default
|
||||||
(non-`SWELL_LOAD_SWELL_DYLIB`) branch** — the same branch the extension already uses
|
(non-`SWELL_LOAD_SWELL_DYLIB`) branch** — the same branch the extension already uses
|
||||||
(`src/app/CMakeLists.txt:91–92`). The whole modstub file is inside
|
(`src/app/CMakeLists.txt:103–104`). The whole modstub file is inside
|
||||||
`#ifdef SWELL_PROVIDED_BY_APP`, so the VST3 target must define that symbol; today it does
|
`#ifdef SWELL_PROVIDED_BY_APP`, so the VST3 target must define that symbol; today it does
|
||||||
not. **The `dlopen` itself is Λ-W7's**; what this track lands is the compiled-in, inert
|
not. **The `dlopen` itself is Λ-W7's**; what this track lands is the compiled-in, inert
|
||||||
table plus the exported `SWELL_dllMain`.
|
table plus the exported `SWELL_dllMain`.
|
||||||
@@ -1858,6 +1293,7 @@ Phase Ξ — The resample loop (W1 concurrency-safe with Θ from Θ-W
|
|||||||
T1 capture-signal-popup .................... 15 (popup abandoned; window derives)
|
T1 capture-signal-popup .................... 15 (popup abandoned; window derives)
|
||||||
|
|
||||||
Phase Γ — The instrument's control surface (none of the seventeen; ends with VST3 params)
|
Phase Γ — The instrument's control surface (none of the seventeen; ends with VST3 params)
|
||||||
|
[LANDED — all four waves (W1-W4); see docs/COMPLETED.md; full detail section removed from this file]
|
||||||
W1 Foundations — landed [seven tracks, disjoint by surface]
|
W1 Foundations — landed [seven tracks, disjoint by surface]
|
||||||
T1 knob-interaction-law ....... modifiers + ONE taper module + reset bypass
|
T1 knob-interaction-law ....... modifiers + ONE taper module + reset bypass
|
||||||
+ 10 s ceiling + AHDSR schematic axis [Ruling 2]
|
+ 10 s ceiling + AHDSR schematic axis [Ruling 2]
|
||||||
@@ -1872,23 +1308,22 @@ Phase Γ — The instrument's control surface (none of the seventeen; ends
|
|||||||
W2 New controls, and the overlay's marks — landed [2 tracks]
|
W2 New controls, and the overlay's marks — landed [2 tracks]
|
||||||
T1 pitch-rate-deck ............ Rate + Pitch, Varisp/Presrv compounding [rung 2]
|
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, and the bake correction [3 tracks; T1/T2 landed, T3 open]
|
W3 The reflow, and the bake correction — landed [3 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
|
T2 bake-reset-amendment ....... the Xi correction Gamma owns
|
||||||
T3 bake-prints-limiter ........ NOT STARTED — bake to print the limiter
|
T3 bake-prints-limiter ........ landed — bake prints the limiter through
|
||||||
through the master stage [Daniel, 2026-08-02]
|
the master stage
|
||||||
W4 VST3 parameters [1 track]
|
W4 VST3 parameters — landed [1 track]
|
||||||
T1 vst3-parameter-set ......... 44 derived params, frozen id table [Ruling 1]
|
T1 vst3-parameter-set ......... 44 derived params, frozen id table [Ruling 1]
|
||||||
[rung 3 RESERVED, spent only if verify says so]
|
[rung 3 RESERVED, unspent]
|
||||||
[OPEN: Gamma-F7, the parameter order — Daniel]
|
[Gamma-F7 RULED: signal flow order]
|
||||||
|
|
||||||
Resequenced 2026-08-01, three times: the reflow split canvas (W1-T4) from arrangement
|
Resequenced 2026-08-01, three times: the reflow split canvas (W1-T4) from arrangement
|
||||||
(W3-T1); preserve-time-stretch moved W4 -> W1-T5, retiring Rate's interim stand-in; then
|
(W3-T1); preserve-time-stretch moved W4 -> W1-T5, retiring Rate's interim stand-in; then
|
||||||
Ruling 1 added W4 and Ruling 2 grew W1-T1.
|
Ruling 1 added W4 and Ruling 2 grew W1-T1.
|
||||||
Payload rungs are RELATIVE, not absolute. On this branch at 2026-08-01, the first is already
|
Payload rungs are RELATIVE, not absolute. Both are spent — kParamsPayloadVersion == 15
|
||||||
spent — kParamsPayloadVersion == 15 (Γ-W1-T2, landed, above) — the second is the next free
|
(Γ-W1-T2, landed, above) and v16 (Γ-W2-T1, stated there) — and the third remains RESERVED
|
||||||
rung, v16 (Γ-W2-T1, stated there), and the third remains RESERVED as v17, spent only if
|
as v17, unspent: Γ-W4-T1's storage verification did not force it.
|
||||||
Γ-W4-T1's storage verification forces it.
|
|
||||||
Shared files, named: engine/CMakeLists.txt (W1-T2 | W1-T5), ui/CMakeLists.txt
|
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
|
(W1-T1 | W1-T3), editor_session.cpp (W2-T1 | W2-T2) — all textual adjacency, not
|
||||||
semantic contention. W3-T2's disjointness from W3-T1, once a CONDITIONAL risk to confirm
|
semantic contention. W3-T2's disjointness from W3-T1, once a CONDITIONAL risk to confirm
|
||||||
@@ -1907,7 +1342,7 @@ Phase Psi — The extension trust pass (none of the seventeen; a direct
|
|||||||
|
|
||||||
Three invariant amendments are track deliverables: never-touch-solo (W1-T2, three
|
Three invariant amendments are track deliverables: never-touch-solo (W1-T2, three
|
||||||
files), the action-registration contract (W1-T3, root CLAUDE.md), channel-count-
|
files), the action-registration contract (W1-T3, root CLAUDE.md), channel-count-
|
||||||
preserved (W2-T2, root CLAUDE.md:208).
|
preserved (W2-T2, root CLAUDE.md:215).
|
||||||
Shared files, named: panel_input.cpp (W1-T2 footer block | W1-T4 drag-arm block);
|
Shared files, named: panel_input.cpp (W1-T2 footer block | W1-T4 drag-arm block);
|
||||||
capture.cpp + capture_realtime_finalize.cpp (W2-T1 naming lines | W2-T2 channel
|
capture.cpp + capture_realtime_finalize.cpp (W2-T1 naming lines | W2-T2 channel
|
||||||
lines) — all textual adjacency, not semantic contention. main.cpp is W1-T3's
|
lines) — all textual adjacency, not semantic contention. main.cpp is W1-T3's
|
||||||
|
|||||||
@@ -283,10 +283,10 @@ This matters more here than the usual "forgot to set the build type" — but onl
|
|||||||
`CLAUDE.md`'s §"Build and test" (not §"Performance guardrails") states, at `CLAUDE.md:54`,
|
`CLAUDE.md`'s §"Build and test" (not §"Performance guardrails") states, at `CLAUDE.md:54`,
|
||||||
that the header-inline hot paths "presume an **optimizing** build", with no LTO configured to
|
that the header-inline hot paths "presume an **optimizing** build", with no LTO configured to
|
||||||
recover it if they don't. `peaks`' `computeEnvelope`/`lastFrameAboveThreshold` is one of
|
recover it if they don't. `peaks`' `computeEnvelope`/`lastFrameAboveThreshold` is one of
|
||||||
those paths and IS linked into the extension (`src/app/CMakeLists.txt:52`), so the finding
|
those paths and IS linked into the extension (`src/app/CMakeLists.txt:56`), so the finding
|
||||||
holds for Fork A as stated. It does **not** extend to `Voice::advanceFrame` or the three
|
holds for Fork A as stated. It does **not** extend to `Voice::advanceFrame` or the three
|
||||||
`envelopes.h` evaluators (named at `src/core/instrument/CLAUDE.md:342`, not in root
|
`envelopes.h` evaluators (named at `src/core/instrument/CLAUDE.md:378`, not in root
|
||||||
`CLAUDE.md`) — those belong to `sampler_core`, which `src/app/CMakeLists.txt:53–55` states is
|
`CLAUDE.md`) — those belong to `sampler_core`, which `src/app/CMakeLists.txt:57–59` states is
|
||||||
deliberately NOT linked into the extension and which only enters a build at all inside the
|
deliberately NOT linked into the extension and which only enters a build at all inside the
|
||||||
Windows-gated `reasampler_vst` target (§1b). On Fork A that code never compiles into
|
Windows-gated `reasampler_vst` target (§1b). On Fork A that code never compiles into
|
||||||
`reaper_reasampler` in the first place; the unoptimized-build risk to it is a Fork B concern,
|
`reaper_reasampler` in the first place; the unoptimized-build risk to it is a Fork B concern,
|
||||||
|
|||||||
Reference in New Issue
Block a user