cca7d1a538
Where the instrument's loop grid, crossfade and pitch handling stop making sense as a capture shrinks toward one cycle, and what "wavetable synth" would mean here. Two structural results: the pre-seam crossfade is the identity map at one-period loop lengths, and YIN cannot detect a single cycle, so the period is declared rather than detected there. Four candidate shapes, W3 recommended out. PLAN.md gets a pointer under "Flagged for awareness" only.
2218 lines
153 KiB
Markdown
2218 lines
153 KiB
Markdown
# PLAN
|
||
|
||
The post-1.0 roadmap. Seventeen queued items consolidated into overlapping areas and
|
||
sequenced into a Phase → Wave → Track hierarchy that implementation specialists can be
|
||
dispatched against directly — **plus Phase Γ**, which did not come from those seventeen
|
||
(it came from a direct interview, 2026-08-01) and is scoped in
|
||
`docs/product/instrument-control-surface.md`, **and Phase Ψ**, which likewise did not
|
||
come from the seventeen: it came from a direct list of seven defects and refinements
|
||
(Daniel, 2026-08-01); it has landed, and now that its section here is retired, the list
|
||
is carried verbatim in `docs/COMPLETED.md` — there is no backing product doc for it,
|
||
**and Phase Ε**, which likewise did not come from the
|
||
seventeen: it came from a direct request (Daniel, 2026-08-02) and is scoped in
|
||
`docs/product/bank-package.md`, **and Phase Ρ**, likewise a direct request (Daniel,
|
||
2026-08-02), scoped in `docs/product/render-in-place.md`, **and Phase Λ** — the Linux port
|
||
of both artifacts — likewise a direct request (Daniel, 2026-08-02), scoped in
|
||
`docs/product/linux-readiness.md`, **and Phase Ω**, which likewise did not come from the
|
||
seventeen: it came from a direct list of seven defects and refinements (Daniel,
|
||
2026-08-03), and like Ψ it has **no backing product doc of its own** — the seven are
|
||
carried verbatim in its phase header below as Ω.1…Ω.7. It does amend two clauses of
|
||
`docs/product/instrument-control-surface.md`, which are edited there rather than merely
|
||
contradicted here.
|
||
|
||
## What this doc is, and how it relates to the others
|
||
|
||
- **`docs/PLAN.md`** (this file) — the active on-deck specification list. Each track is
|
||
written so a specialist brief is writable from this file alone: goal, consolidated
|
||
source items, surface boundary, behavior, acceptance criteria, open questions,
|
||
prerequisites.
|
||
- **`docs/TODO-1.0.md`** — retained as the **verbatim-provenance appendix**. It holds
|
||
Daniel's raw asks and every answer round, unedited; where this plan compresses a
|
||
behavior bullet, that file is the backing record. Items are cited here by number
|
||
(e.g. "consolidates items 1, 8, 14"). It is not a work queue any more; this file is.
|
||
- **`docs/TODO.md`** — deferred follow-ups with recorded rationale, unrelated to the
|
||
seventeen (with one flagged intersection: see "Flagged for awareness" below).
|
||
- **`docs/COMPLETED.md`** / **`docs/ARCHIVE.md`** — doc-keeper's. When a track here
|
||
finishes, its point is removed from this file and appended to `COMPLETED.md` with any
|
||
deviation between landed code and spec noted.
|
||
- **`docs/product/`** — the product-design reasoning behind prior phases. Grep for a
|
||
cited section rather than reading a file whole.
|
||
|
||
**Worktree slug convention:** `p<phase>-w<wave>-t<track>-<slug>`. Greek phase letters
|
||
transliterate: **Θ → `th`**, **Ξ → `xi`**, **Γ → `g`**, **Ψ → `psi`**, **Ε → `e`**, **Ρ → `r`**,
|
||
**Λ → `l`**, **Ω → `om`**. So Θ-W1-T1 dispatches into `pth-w1-t1-zone-retirement`, Γ-W1-T1 into
|
||
`pg-w1-t1-knob-interaction-law`, Ψ-W1-T1 into `ppsi-w1-t1-capture-range-exactness`,
|
||
Λ-W2-T1 into `pl-w2-t1-linux-compile-blockers` (Phase Λ's two audit tracks already ran
|
||
under `pl-w1-t1-build-toolchain-audit` and `pl-w1-t2-source-runtime-audit`), and Ω-W1-T1
|
||
into `pom-w1-t1-mode-switch-responsiveness`.
|
||
|
||
## Decision state
|
||
|
||
Everything carried forward from `TODO-1.0.md` is classified **[verify]** (answerable by
|
||
reading code or running the DAW) or **[propose]** (a design call made at implementation
|
||
review with a proposal, not a Daniel call); that classification is preserved per
|
||
question, attached to the track that will answer it.
|
||
|
||
**Read the Phase Λ paragraph below before relying on the plan-wide "nothing is unanswered"
|
||
claim the Γ, Ε and Ρ paragraphs make.** Λ (added 2026-08-02) carries **four open
|
||
[Daniel]-class forks**, so that claim is no longer true of the plan as a whole; it is true
|
||
of every phase except Λ, and Λ's paragraph states exactly which of its four forks gate
|
||
anything.
|
||
|
||
Θ-W3-T1's two genuine **[Daniel]** questions — which no amount of code-reading could
|
||
answer — are both ruled on and the track has landed; see `docs/COMPLETED.md` for the
|
||
full narrative. **Reload tier = Grouping B** (continuous knobs live: filter cutoff/Q/
|
||
morph/drive/mod amount/key-track, every envelope stage time and level; root note, loop
|
||
span, and start frame still trigger a full reload). **Mid-stage rule = candidate (iv),
|
||
hold normalized stage position** (φ = elapsed/duration held fixed across a duration
|
||
change, then advancing at 1/newDuration). **Phase Γ opened six [Daniel]-class forks
|
||
(Γ-F1…Γ-F6) and all six are ruled** (Daniel, 2026-08-01) — the rulings are folded into the
|
||
tracks below and indexed in `docs/product/instrument-control-surface.md` §8. **Γ-F6 closed
|
||
with a correction to the analysis, not merely a ruling**: dynamic reported latency is routine
|
||
for VST3 instruments and REAPER handles it as a matter of course; what makes the mandated
|
||
restart expensive *here* was self-inflicted (`setActive(true)` called `reloadInstrument`), so
|
||
the cost was ours to reduce. **That reduction landed in Γ-W3** — see `docs/COMPLETED.md`.
|
||
|
||
**Γ-F3 was subsequently REVERSED and a seventh fork opened AND CLOSED, all 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. **Γ-F7** (the VST3 parameter
|
||
*order*) is **RULED: signal flow** — *"signal flow order."* **There is now NO unanswered
|
||
[Daniel]-class question anywhere in this plan.**
|
||
|
||
**Phase Ε (added 2026-08-02) opened three more [Daniel]-class forks and ALL THREE ARE
|
||
RULED**, same day (Daniel, 2026-08-02): **Ε-F1** container format — *"proprietary
|
||
container"*, the hand-rolled `RSBK`; **Ε-F2** import target — *"always lands as a new bank,
|
||
with an auto suffix if name collision"*; **Ε-F3** import under a degraded tracking ledger —
|
||
*"refuse mismatched import."* Ε-W1 through Ε-W3 have landed; see `docs/COMPLETED.md` for
|
||
the full narrative, and the rulings remain indexed at `docs/product/bank-package.md`
|
||
§"Rulings". **Two of the three landed somewhere other than
|
||
the framing recommendation:** Ε-F2 dropped the proposed rename prompt in favour of a
|
||
deterministic suffix, and Ε-F3 reversed allow-with-confirm to refuse — carried by the
|
||
framing's own counter-argument, that the accepted tracking residual contemplates *one*
|
||
untracked capture while a bulk import strands hundreds in a single gesture. **The plan-wide
|
||
claim above therefore holds unqualified — no unanswered [Daniel]-class question remains
|
||
anywhere in this plan, Phase Ε included — and no track in this plan is gated on a
|
||
decision.**
|
||
|
||
**Phase Ρ (added 2026-08-02) opened three more [Daniel]-class forks and ALL THREE ARE
|
||
RULED**, same day (Daniel, 2026-08-02): **Ρ-F1** multi-track — *"refuse"*, one track per
|
||
fire, a settled non-goal rather than a deferral; **Ρ-F2** the result track's mode —
|
||
*"for this action which is not a capture, the result track should always go to
|
||
arrange"*; **Ρ-F3** tail — *"follow panel tail settings."* Ρ-W1-T1 has landed; see
|
||
`docs/COMPLETED.md` for the full narrative, and the rulings remain indexed at
|
||
`docs/product/render-in-place.md` §"Rulings". **Ρ-F2
|
||
overrode the request's own original wording** ("stays in whatever mode was active") and
|
||
is the only one of the three that changed the spec: the result track is now an Arrange
|
||
member unconditionally, the A/B-on-the-bench behaviour mode-following would have enabled
|
||
is gone, and the ruling pulls in a two-line fix to the panel's auto-tag detector that
|
||
would otherwise reverse it on the next timer tick. **The plan-wide claim above therefore
|
||
still holds unqualified — no unanswered [Daniel]-class question remains anywhere in this
|
||
plan, Phase Ρ included — and no track in this plan is gated on a decision.**
|
||
|
||
**Phase Λ (added 2026-08-02) breaks that unqualified claim, and it is corrected here rather
|
||
than left to be discovered.** The claim as written in the three paragraphs above — *"no
|
||
unanswered [Daniel]-class question remains anywhere in this plan"* and *"no track in this
|
||
plan is gated on a decision"* — is now **scoped to Θ, Ξ, Γ, Ψ, Ε and Ρ.** It is still true
|
||
of all six. It is **not** true of Λ. **Λ opened four [Daniel]-class forks and NONE of them
|
||
is ruled** (Λ-F1 CI; Λ-F2 the dialog-resource route; Λ-F3 the copy-only drag-out invariant's
|
||
wording; Λ-F4 the declared support floor). Each is stated with the evidence for both sides
|
||
at `docs/product/linux-readiness.md` §"Open forks — Daniel's", and the Phase Λ section below
|
||
carries them as a table. What they gate is narrow and is not a matter of judgement:
|
||
|
||
- **Λ-F2 is the only one that gates a dispatch** — Λ-W2-T3 `panel-dialog-resource` cannot be
|
||
briefed until the route is chosen, because the two routes own different files. Leaving it
|
||
open does not stall the phase; it slips that one track to Λ-W4 and **splits Λ-W3's
|
||
verification sweep into two Linux sessions**, which is the fork's actual price.
|
||
- **Λ-F4 gates the ship wave, not a dispatch.** Λ-W2 can record a floor and widen it; Λ-W5
|
||
cannot ship an artifact that does not say what it runs on. Answerable any time before
|
||
Λ-W5-T1 is briefed.
|
||
- **Λ-F1 and Λ-F3 gate nothing at all.** Λ-F1 (CI) decides only which paragraph Λ-W5-T1
|
||
writes; Λ-F3 decides one sentence's wording inside an edit Λ-W4-T3 makes either way.
|
||
- **The other eleven Λ tracks are dispatchable against open forks**, in the sense that none
|
||
of them waits on a decision. What every Λ track waits on instead is a Linux box — a
|
||
different kind of unknown, and the reason Λ-W3 exists.
|
||
|
||
**Λ's six [Daniel] rulings of 2026-08-02 are settled** and are recorded in the phase section
|
||
below in the same shape as Γ's: the instrument is in scope; SWELL is reached by
|
||
`dlopen`ing REAPER's own `libSwell.so` rather than vendoring it; the editor is REAPER-only
|
||
but every other host must degrade safely; macOS is out; the Linux artifact is shipped rather
|
||
than developer-only; and a hard `unlink` prune is acceptable with a platform-aware
|
||
confirmation. **Do not re-litigate them.**
|
||
|
||
**Phase Ω (added 2026-08-03) opened six [Daniel]-class forks and ALL SIX ARE RULED**, the
|
||
same day the phase was framed (Daniel, 2026-08-03): **Ω-F1** the marker-alignment scope —
|
||
*"widen to the correct scope"*, the whole overlay↔waveform mapping rather than the START
|
||
mark; **Ω-F2** the loop marks — hidden outright in Trigger, drawn **grey-disabled and still
|
||
grabbable** in Gate with loop off; **Ω-F3** the single-button toggle rule extends to Filter,
|
||
Loop and Mono|Stereo, the last as a **mode selector** with no off state; **Ω-F4** the
|
||
active/focus colour is **`AccentPrimary`**, not `AccentHot`; **Ω-F5** the mode-switch symptom
|
||
is *"nothing for 2 seconds, then everything"* on **both** entry surfaces; **Ω-F6** the FX
|
||
parking **separates UI from processing** — visibility/routing/FX-enable apply synchronously,
|
||
per-FX offline/online defers. **Phase Ω therefore carries ZERO unanswered [Daniel]-class
|
||
questions and no Ω track is gated on a decision.** Two of the six moved the spec rather than
|
||
merely confirming it: Ω-F2 reverses `docs/product/instrument-control-surface.md`'s
|
||
"Disabled rather than hidden" marker principle in the Trigger case and retires its
|
||
`LOOP — GATE ONLY` caption, and Ω-F6 amends the accepted FX-parking invariant in
|
||
`src/shell/view/CLAUDE.md`. Both amendments are named track deliverables, not footnotes.
|
||
|
||
**The Λ carve-out above is unchanged and is NOT weakened by this phase.** Λ remains the one
|
||
phase in this plan with unanswered [Daniel]-class questions; the enumeration in Λ's paragraph
|
||
predates Ω, so read *"scoped to Θ, Ξ, Γ, Ψ, Ε and Ρ"* as **Θ, Ξ, Γ, Ψ, Ε, Ρ and Ω** — seven
|
||
phases, all of them fully ruled, and Λ alone excepted.
|
||
|
||
**Ruling 3 (Daniel, 2026-08-01) — real units at the host boundary.** *"The parameter values
|
||
exposed to the VST host should be in real units, such that the host automation lanes report
|
||
usable values."* Satisfied through VST3's **plain-value layer**, not its wire format (which is
|
||
normalized and cannot be otherwise): `toPlain`/`toNormalized`, `getParamStringByValue` and
|
||
`ParameterInfo::units`. Specified at **`docs/product/parameter-automation.md` §6.7** — the
|
||
per-category unit/precision table, the one-formatter invariant, the `stepCount` sweep, and the
|
||
resolution of the apparent conflict with the filter's re-taper prohibition. It lands entirely
|
||
in Γ-W4-T1 and changes no wave boundary.
|
||
|
||
### Flagged for awareness — not blocking, but decision-grade
|
||
|
||
1. **Item 15's cross-artifact seam is RESOLVED — this is no longer an unknown.** The
|
||
instrument is a read-only bank consumer by invariant (`src/shell/instrument/CLAUDE.md`),
|
||
and the one previous attempt at an instrument→extension relay (S13) closed with a
|
||
**DEGRADED** spike verdict and was deferred (`docs/TODO.md`). Resample required that
|
||
crossing. Ξ-W2-T1 ratified Decision 1 = (1b): the editor invokes the extension's bake
|
||
action directly over the VST-host bridge (`NamedCommandLookup`/`Main_OnCommandEx`), no
|
||
poller, no nonce — dissolving the S13 DEGRADED verdict rather than re-litigating it. The
|
||
read-only bank invariant held: the crossing is a bridge call, not a shell-side bank
|
||
write. See `docs/COMPLETED.md` for the full narrative.
|
||
|
||
2. **The "Γ before Ξ-W2" ordering is VIOLATED, it was never Daniel's choice, and Γ now owns
|
||
the correction.** Daniel, 2026-08-01: *"xi was started before I spun you up, we'll have to
|
||
correct phase xi inside gamma. wasn't a choice."* Ξ-W2-T1 (`resample-bake-chain`) ran
|
||
ahead of this plan's sequencing claim, so the bake's settled reset scope — which
|
||
enumerates parameters **by name** — ships incomplete: it cannot name rate, pitch offset or
|
||
the limiter flag, none of which existed when it was written.
|
||
|
||
**This is no longer a scheduling constraint to honour. It is a correction obligation with
|
||
a named owner: Γ-W3-T2 `bake-reset-amendment`.** The classification costs no Daniel
|
||
decision — `docs/product/instrument-control-surface.md` §3.4 pre-classifies all three
|
||
against Ξ-W2's own ratified rule (all **reset**) — but the amendment must be written
|
||
**against what Ξ-W2-T1 actually shipped, not against what this plan predicted it would
|
||
ship.** Ruling 1 adds a second correction of the same shape, homed on Γ-W4-T1 rather than
|
||
here: see item 3.
|
||
|
||
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.**
|
||
|
||
4. **Single-cycle material and the wavetable direction is FRAMED but is NOT a phase, and
|
||
deliberately so.** Daniel, 2026-08-03, after DAW-testing a one-cycle 60 Hz sine capture:
|
||
*"supporting smoothness at this micro scale would turn this into a wavetable synth, which
|
||
is desirable."* The product framing is `docs/product/single-cycle-and-wavetable.md`.
|
||
|
||
**It carries EIGHT open [Daniel]-class forks and none is ruled**, so it gets a pointer
|
||
here rather than a phase section: `PLAN.md` is the active on-deck specification list, and
|
||
a phase with eight unruled forks is neither briefable nor consistent with the standing
|
||
claim above that **Λ is the only phase in this plan with unanswered [Daniel]-class
|
||
questions**. That claim is unaffected by this item — there is no phase here to except.
|
||
When the forks are ruled, the doc becomes a phase's backing product doc in the ordinary
|
||
way (the Γ / Ε / Ρ / Λ pattern, not the doc-less Ψ / Ω one).
|
||
|
||
**Two things in it are decision-grade for work already in this plan, which is why it is
|
||
flagged rather than merely filed:**
|
||
|
||
- **The immediate defect it came from is NOT its subject.** `nearestZeroCrossing`
|
||
(`src/core/instrument/ui/waveform_view.cpp:181-210`) fans out across the whole buffer
|
||
with no radius bound; a snap-radius track is in flight against it. **But Phase Ω's own
|
||
acceptance criteria name the zero-crossing snap as a thing that "must come out the other
|
||
side identical", and Ω-W1-T5's outline entry repeats it.** A snap-radius change alters
|
||
exactly that behaviour. **Whoever owns Ω's criteria must reconcile the two** — this item
|
||
records the conflict, it does not resolve it.
|
||
- **Sequencing, if any of it is ever dispatched.** Every candidate touches
|
||
`waveform_view.{h,cpp}`, `editor_input_waveform.cpp` and `editor_controls.cpp`'s
|
||
`waveMarksFor` / `grabbableMarks` — Ω-W1-T5's exact surface. Nothing from that doc
|
||
should dispatch until Ω-W1-T5 and the snap-radius track have both landed.
|
||
|
||
## Phase-wide acceptance criteria
|
||
|
||
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.
|
||
|
||
### Structural (root `CLAUDE.md`, Daniel 2026-07-28)
|
||
|
||
- **More directories is a must; more files is good; ~600-line file ceiling.** The
|
||
ceiling is the *bar*; a responsibility seam is the *method*. Bisection-to-hit-the-
|
||
number is rejected. `sampler_core.cpp` (956 lines) is the standing documented
|
||
hot-path exception — Θ-W1-T1 re-seams it, and any surviving over-ceiling TU must
|
||
carry the same explicit justification.
|
||
- **Templates where earned** — compile-time dedup with zero runtime cost, off the hot
|
||
paths. Not for types that differ in name only.
|
||
- **SOLID is great, but saved CPU is better.** No dispatch-stack blowouts anywhere;
|
||
prefer static polymorphism where the types are compile-time-known.
|
||
|
||
### Performance guardrails
|
||
|
||
Root `CLAUDE.md`'s five extension-side guardrails (peaks envelope compute, audition,
|
||
realtime-capture tick, JSON, `FxBypassGuard`) are unchanged by this plan; no track here
|
||
touches them. The instrument adds a sixth surface that binds every Θ track:
|
||
|
||
- **`process()` — the per-voice-per-sample path — takes no new indirection.** The
|
||
filter tick, the envelope evaluation (staged *and* spline), the pitch-shift read, and
|
||
the loop read all sit on it. Concrete, inlineable types only: **no `IEnvelope`, no
|
||
`IFilter`, no virtual per-voice `tick()`**. A filter with two modes is a
|
||
branch-predictable switch or a compile-time-known dispatch, never a vtable. Spline
|
||
evaluation is a binary search over a point array, not a polymorphic curve object.
|
||
- **No allocation, no file I/O, no bridge call in `process()`.** The off-audio-thread
|
||
`reloadInstrument` + atomic pointer swap stays the only way new state reaches the
|
||
audio thread. Every new parameter this plan adds follows that path.
|
||
- **A split that would add a hot-path indirection is out of scope — rework it or drop
|
||
it.**
|
||
|
||
### Product invariants
|
||
|
||
- **Capture and placement are separate acts.** No track here may place a timeline item.
|
||
Item 15's bake explicitly must not, and one of its candidate architectures uses a
|
||
*temporary* arrange mutation — that candidate must leave the arrange byte-identical.
|
||
- **Prune is the single, exclusive file-deletion authority.** Item 15's "replace" never
|
||
deletes bytes; item 17's consolidation may not weaken any protection prune has today.
|
||
- **The instrument never writes the bank.** Item 15 is the first feature that needs to,
|
||
and it resolves that by *asking the extension*, not by breaching the invariant.
|
||
- **Migration bar: a project saved before a change reopens sounding identical.** Holds
|
||
everywhere except item 16's genuinely-multi-zone case, where Daniel deliberately
|
||
relaxed it.
|
||
- **Every pure module gets a `<module>_tests` target** that runs without REAPER or a
|
||
DAW. New pure modules in this plan are not optional-test.
|
||
|
||
---
|
||
|
||
## Phase Ω — Responsiveness, and the control surface's second pass
|
||
|
||
**Ships:** A mode switch that draws its result at once instead of two seconds later; a master
|
||
meter that moves at 60 FPS instead of two; a deck whose two-state toggles are single buttons
|
||
and whose envelope decks are their own overlay targets; loop marks that tell the truth about
|
||
the mode they are in; and one coordinate mapping shared by everything that rides the waveform,
|
||
so an overlay lands on the frame it names.
|
||
|
||
**Consolidates: none of the seventeen.** Phase Ω came from a direct list of seven defects and
|
||
refinements (Daniel, 2026-08-03) and has **no backing product doc of its own** — the Ψ
|
||
precedent. The seven are its provenance and are recorded here verbatim in substance:
|
||
|
||
- **Ω.1** — the ReaSampler 9000 master meter renders at 0.5–1 FPS. *(instrument)*
|
||
- **Ω.2** — Filter Mod belongs on the filter's envelope deck, and the Trigger face's knob
|
||
spacing is wrong. *(instrument)*
|
||
- **Ω.3** — a two-state toggle should be ONE button, not two segments. *(instrument)*
|
||
- **Ω.4** — the whole envelope deck should be the overlay's click target, not a 12 px corner
|
||
square. *(instrument)*
|
||
- **Ω.5** — the loop marks are meaningless in Trigger and unreadable in Gate with loop off.
|
||
*(instrument)*
|
||
- **Ω.6** — the START marker does not land on the sample position it names — **widened by Ω-F1
|
||
to the overlay↔waveform coordinate mapping as a whole.** *(instrument)*
|
||
- **Ω.7** — the arrange⇄design switch takes 1–2 s and shows nothing until it completes.
|
||
*(extension)*
|
||
|
||
**This phase is small tidy work sequenced BEFORE Λ, and it is not another Λ.** Everything here
|
||
is verifiable on the current Windows box except the DAW-observable half, which has one track of
|
||
its own (Ω-W2-T3) rather than a wave. It **supersedes nothing** and adds no format, no wire
|
||
version, no parameter id, and no new artifact.
|
||
|
||
**Two invariant amendments are track deliverables** (the Ψ/Ρ precedent), because two settled
|
||
statements become untrue the moment this phase lands:
|
||
|
||
- `docs/product/instrument-control-surface.md` §6.3(d) and §6.4 — the marks' "Disabled rather
|
||
than hidden" principle and the `LOOP — GATE ONLY` caption, reversed in the Trigger case by
|
||
Ω-F2. **Owner: Ω-W1-T5.** *(Amended in that doc as part of this phase's authoring; the track
|
||
verifies the code agrees with the amended text.)*
|
||
- `src/shell/view/CLAUDE.md`'s FX-parking caveat — today it accepts a "possible load hitch" on
|
||
the return to the active mode as the cost of the CPU reclaim. Ω-F6 splits that cost off the
|
||
synchronous path entirely. **Owner: Ω-W1-T1.**
|
||
|
||
### Rulings — Daniel's, 2026-08-03. Six, settled.
|
||
|
||
**Do not re-litigate these.** There is no product doc to index them in; this table is their one
|
||
home, and a brief for any Ω track is written against it.
|
||
|
||
| Ruling | What it settled | Bound into |
|
||
|---|---|---|
|
||
| **Ω-F1** | **Widen the marker-alignment work to the correct scope.** The defect is the overlay↔waveform coordinate mapping as a whole, not the START mark — a class defect with four independent causes, inherited by every overlay | Ω-W1-T5 |
|
||
| **Ω-F2** | **Trigger ⇒ the loop marks are HIDDEN outright. Gate with loop off ⇒ they are still drawn and still grabbable, styled GREY DISABLED rather than dim teal** — *"that was not intuitive to me."* The drag-to-set-loop gesture survives; `LOOP — GATE ONLY` goes | Ω-W1-T5 |
|
||
| **Ω-F3** | **The single-button toggle rule extends to `Filter` and chrome `Loop`** (Primary when active, **dim gray** when off) **and to `Mono\|Stereo`, which is a MODE SELECTOR** — its label reads the current mode and it has **no** dim-gray state | Ω-W1-T4 |
|
||
| **Ω-F4** | **The active/focus colour is `AccentPrimary`** — both the active single-button state and the focused envelope deck's border. **Not `AccentHot`**, which stays the hover cue | Ω-W1-T4 |
|
||
| **Ω-F5** | **The mode-switch symptom is both entry surfaces**, and it is *"nothing for 2 seconds, then everything switches"* — nothing renders until the whole synchronous body completes | Ω-W1-T1, Ω-W1-T2 |
|
||
| **Ω-F6** | **ACCEPTED, and it is a spec amendment: separate the FX parking's UI half from its processing half.** Visibility / routing / FX-enable apply **synchronously**; per-FX offline/online **defers to a later idle tick** — *"the switch doesn't happen during playback anyway… the UI is the priority when toggling modes"* | Ω-W1-T1 |
|
||
|
||
**Verified by Daniel, not a fork:** the deck knob-spacing defect is in the **Trigger** face.
|
||
**Gate is correct and must stay pixel-identical** — an acceptance criterion of Ω-W1-T4, not a
|
||
hope.
|
||
|
||
### Phase-Ω acceptance criteria
|
||
|
||
These bind every track in this phase, in addition to the plan-wide set above.
|
||
|
||
- **No behaviour changes outside the named defect.** Every track carries that criterion
|
||
explicitly. The Gate deck face, the zero-crossing snap, the ballistics constants, the 500 ms
|
||
bank/bake poll body and the audio thread are each named below as things that must come out
|
||
the other side identical.
|
||
- **Nothing is added to any hot path.** No track here touches `process()`, the `peaks` envelope
|
||
compute, audition, or the realtime-capture tick. Ω-W1-T3's work is entirely UI-thread paint
|
||
cost; Ω-W1-T1's is entirely main-thread REAPER API cost.
|
||
- **`reasampler_editor.h` is at 564 lines against the ~600 ceiling** (`docs/TODO.md`, "The
|
||
editor's drag state machine has no seam"), and **three W1 tracks add state to it.** The
|
||
budget is ~36 lines for the whole wave. **The drag-state seam that TODO entry names is OUT OF
|
||
SCOPE for Ω** — a track that finds itself over the ceiling stops and flags rather than
|
||
bisecting the header under time pressure, which is exactly the failure that entry predicts.
|
||
- **The shell layers this phase touches have no CTest coverage** — `editor_paint_*`,
|
||
`editor_input_*` and `shell/view` have no test targets. Every track therefore states, as a
|
||
criterion, **what it pushed down into a pure module and asserted there**; "verified by eye in
|
||
the DAW" is the fallback, not the plan.
|
||
- **Measurement before and after is a deliverable, not a nicety**, for the two performance
|
||
items (Ω.1, Ω.7). A track that improves a number it never measured has no acceptance
|
||
criterion, only an opinion.
|
||
|
||
---
|
||
|
||
### Ω-W1 — The five defects, in parallel
|
||
|
||
**Depends on:** nothing. **Five tracks, disjoint at the file level.** Items 2, 3 and 4 are ONE
|
||
track on purpose: they land in the same deck modules and the same pinned-geometry test files,
|
||
so splitting them means re-deriving `test_deck_groups_measured.cpp` three times. Items 5 and 6
|
||
are one track because they overlap inside `paintWaveform`'s mark-draw loop — one deletes and
|
||
gates it, the other changes the `mx` it computes.
|
||
|
||
| Track | Owns |
|
||
|---|---|
|
||
| **T1** `mode-switch-responsiveness` | `shell/view/view.{h,cpp}` + one new deferred-park TU + its drain in `app/main.cpp` |
|
||
| **T2** `mode-switch-persist` | `shell/actions/design_view_actions.cpp` + `shell/persist/ext_state_io.cpp` — dirty-key gating |
|
||
| **T3** `meter-rate` | the editor's timer/paint path + the hero-envelope cache |
|
||
| **T4** `deck-reflow-focus-toggles` | Ω.2 + Ω.3 + Ω.4 — the deck's composition, layout, paint, input and pinned tests |
|
||
| **T5** `overlay-mapping` | Ω.5 + Ω.6 — the waveform overlay's marks and its one coordinate mapping |
|
||
|
||
**Shared files in the wave, named rather than discovered at merge.** All textual adjacency
|
||
unless marked otherwise.
|
||
|
||
| File | Tracks | Nature |
|
||
|---|---|---|
|
||
| `src/shell/instrument/reasampler_editor.h` | T3 (meter clock + timer members), T4 (deck focus state), T5 (nothing new expected) | **The one to watch.** Additive member declarations in different sections of a header with ~36 lines of ceiling margin between them. Whichever lands second rebases AND re-checks the line count |
|
||
| `src/shell/instrument/editor_paint_waveform.cpp` | T3 (the envelope-cache call site at `paintWaveform`'s bin/`computeEnvelope` block), T5 (the mark, caption and mapping regions below it) | Disjoint regions of one function's body; whichever lands second rebases |
|
||
| `src/shell/instrument/editor_controls.cpp` | T4 (`faceLayout`'s `deckDescs` build), T5 (`waveMarksFor` / `grabbableMarks`) | Different functions, no shared state |
|
||
| `src/app/CMakeLists.txt` | T1 (one `target_sources` line for the new park-queue TU) | Ω's only CMake edit. **Cross-phase adjacency:** Λ-W2-T2, Λ-W2-T3, Λ-W4-T3 and Λ-W5-T1 all own regions of this file. Ω lands first; Λ rebases |
|
||
| `src/app/main.cpp` | T1 (the drain call inside `OnTimer`) | **Cross-phase adjacency:** Λ-W2-T1 owns this file's `REAPERAPI_LoadAPI` failure branch — a different region |
|
||
|
||
#### Ω-W1-T1 — `mode-switch-responsiveness`
|
||
|
||
**Goal.** A mode switch redraws immediately. The expensive half of the switch still happens; it
|
||
just stops being the thing the user waits for.
|
||
|
||
**Spec:** Ω.7 and rulings Ω-F5, Ω-F6 above; no product doc.
|
||
|
||
**Surface boundary — owns:** `src/shell/view/view.cpp` (the `applyMode` body — the undo block,
|
||
the park/restore loops, the parent-visibility loop, `resolve`), `src/shell/view/view.h`, ONE
|
||
new TU pair beside them for the deferred queue, one `target_sources` line in
|
||
`src/app/CMakeLists.txt`, the drain call in `src/app/main.cpp`'s `OnTimer`, and
|
||
`src/shell/view/CLAUDE.md`'s FX-parking caveat. **Does not own:** `core/view` (that is
|
||
Ω-W2-T1's), `design_view_actions.cpp` or `ext_state_io.cpp` (T2's), `panel_input.cpp`
|
||
(Ω-W2-T2's), or `view_solo.cpp`.
|
||
|
||
**The solo surface index is EXONERATED.** It costs ~2T extra API calls, sub-millisecond. Say so
|
||
in the brief so it is not re-litigated: the magnitude is two older O(project) costs that are
|
||
invisible on a scratch project and quadratic-feeling on a real one.
|
||
|
||
**Behavior.**
|
||
- **The undo mask (`view.cpp:563`).** `Undo_EndBlock2(proj, undoLabel.c_str(), -1)` — `-1` is
|
||
`UNDO_STATE_ALL` (`reaper_plugin.h:1540`), which includes `UNDO_STATE_FX` (`:1542`). REAPER
|
||
marshals **every plugin's state chunk in the project** into the undo record, on every switch
|
||
**and** every tag/untag reapply. The honest mask is
|
||
`UNDO_STATE_TRACKCFG|UNDO_STATE_ITEMS|UNDO_STATE_MISCCFG`, with `UNDO_STATE_FX` OR'd in only
|
||
when the park/restore loop actually toggled an offline state this apply. The second
|
||
`-1` at `:607` (`mintManagedLanes`) is a separate call site — assess it, do not assume it.
|
||
- **Unconditional `TrackFX_SetOffline`.** `parkFxOffline` (`:190-195`) calls it on **every FX of
|
||
every parked track**, including tracks already parked and already offline — unloading and
|
||
re-instantiating plugins for no state change. `restoreFxOffline` (`:200-204`) writes verbatim
|
||
from the snapshot the same way. Both compare before writing.
|
||
- **`PreventUIRefresh` is used NOWHERE in `src/`** (verified: zero matches across the tree)
|
||
though the SDK exports it (`reaper_plugin_functions.h:5583`). ~10T flag writes land with UI
|
||
refresh live. Bracket the write phase, and keep the existing
|
||
`TrackList_AdjustWindows(false)` + `UpdateArrange()` as the one deliberate rebuild after the
|
||
bracket closes. **Balanced pairing is mandatory** — an early return between the two calls
|
||
leaves REAPER's UI frozen.
|
||
- **Compare before write.** `applyFlags` (`:182-186`) and the parent-visibility loop
|
||
(`:521-531`) write unconditionally, so a reapply that changed nothing still pays every write.
|
||
- **`resolve` is a linear scan** (`:122-128`) called once per parked / restored / parent / lane
|
||
track — O(T²). `readFolderEntries` (`:103-120`) already builds `handleByGuid` and can hand
|
||
back a hash map instead of a vector of pairs.
|
||
- **Ω-F6, and it is the headline.** Split the synchronous apply from the deferred park:
|
||
visibility, routing and FX-enable apply **inside** `applyMode`; per-FX offline/online is
|
||
enqueued and drained on a later idle tick. **The spec must state what the queue does when a
|
||
second switch arrives before the first's park has run** — the answer is that a queue entry is
|
||
keyed by track GUID and the LATEST intent wins (an enqueued park superseded by a restore
|
||
cancels, not stacks), because the two are inverses and replaying both is both slower and
|
||
observably wrong. A project close, a track deletion, or a project switch drains the queue
|
||
without applying it.
|
||
- **Where the queue lives.** `src/shell/view/view.cpp` measures **642 lines** today and is
|
||
already over the ~600 ceiling with its seam blocked (`docs/TODO.md`). The queue is therefore
|
||
a **new TU beside it**, not more of `view.cpp` — `src/shell/view/` has no `CMakeLists.txt` of
|
||
its own, so this costs exactly one `target_sources` line in `src/app/CMakeLists.txt` (where
|
||
`view.cpp` and `view_solo.cpp` are already listed). This is the one structural decision in
|
||
the track and it moves `view.cpp` toward the ceiling rather than further past it.
|
||
- **The invariant amendment.** `src/shell/view/CLAUDE.md`'s "Documented caveat" bullet accepts a
|
||
load hitch on the return to the active mode. After this track the hitch is still real but no
|
||
longer sits on the switch's synchronous path — the caveat must say so, and must say what the
|
||
deferral does NOT change (the plugin still re-instantiates; un-persisted internal state is
|
||
still lost).
|
||
|
||
**Acceptance criteria.**
|
||
- On a project with enough tracks and FX to reproduce it, the mode switch **paints its new
|
||
state before the park work runs**, and the wall-clock to first paint is recorded before and
|
||
after. `[verify — DAW]`, discharged by Ω-W2-T3.
|
||
- A tag/untag reapply on an unchanged project performs **zero** `TrackFX_SetOffline` calls and
|
||
**zero** redundant flag writes.
|
||
- The undo record for one mode switch no longer carries FX state when no offline state changed;
|
||
**one switch is still one Ctrl-Z**, and Ctrl-Z still restores what it restored before.
|
||
- The park/restore round trip — the phase's trust anchor — still returns every driven flag to
|
||
its captured value, including when the deferral means the park lands on a later tick.
|
||
- Two switches in rapid succession leave every track in the state the SECOND switch specifies,
|
||
with no track left offline that should be online.
|
||
- `PreventUIRefresh` is balanced on every exit path from `applyMode`, including the two early
|
||
returns already there.
|
||
- `view.cpp` is no longer than it is today.
|
||
|
||
**Prerequisites.** None; concurrent with T2–T5. **Discharges:** Ω.7 (the structural half),
|
||
Ω-F5, Ω-F6.
|
||
|
||
#### Ω-W1-T2 — `mode-switch-persist`
|
||
|
||
**Goal.** A mode switch stops rewriting the whole session to record that one key changed.
|
||
|
||
**Spec:** Ω.7's lower-priority half; ruling Ω-F5 (both entry surfaces).
|
||
|
||
**Surface boundary — owns:** `src/shell/actions/design_view_actions.cpp` (`persistViewState` and
|
||
the two callers `doActivateMode` / `doToggleMode`) and `src/shell/persist/ext_state_io.cpp`
|
||
(`saveToActiveProject`). **Does not own:** `view.cpp`, `session.cpp`, or any panel TU.
|
||
|
||
**Behavior.**
|
||
- The panel-click path routes through `Main_OnCommand` → `doActivateMode`
|
||
(`panel_input.cpp:330-334`), which calls `persistViewState()` →
|
||
`saveToActiveProject()` — a **seven-key** ext-state write (`ext_state_io.cpp:140-190`: banks,
|
||
the legacy-key clear, view, tail, owned_files, version stamp, bank generation) plus a
|
||
`MarkProjectDirty`, every switch, when only `view_state` changed. Serializing the bank book
|
||
and the tracking ledger is the expensive part and neither moved.
|
||
- **Gate by dirty key, not by call site.** A per-key dirty flag on the session, or a
|
||
`saveViewStateOnly` entry point beside the full save — the track picks, and states why. The
|
||
full save must remain the default for every other caller.
|
||
- **`saveToActiveProject`'s return contract must not change.** `shell/persist/CLAUDE.md` is
|
||
explicit: `false` means *no active/saved project and NOTHING was written*, and it may never
|
||
grow an observational third failure mode. A narrowed write returns on the same rule.
|
||
- The degraded-ledger suppression (`tracking::ledgerDegraded`) and the version stamp both stay
|
||
exactly where they are on the full path.
|
||
|
||
**Acceptance criteria.**
|
||
- A mode switch on a saved project writes `view_state` and nothing else; a capture, a bank op
|
||
and a project save each still write everything they wrote before.
|
||
- `MarkProjectDirty` still fires on the narrowed path — the state DID change.
|
||
- Both entry surfaces (the footer segment and the bindable action) take the same path.
|
||
- The write-count difference is measured and recorded.
|
||
|
||
**Prerequisites.** None; concurrent with T1. **Discharges:** Ω.7 (the persist half).
|
||
|
||
#### Ω-W1-T3 — `meter-rate`
|
||
|
||
**Goal.** The master meter moves at 60 FPS, and a paint costs what one frame should.
|
||
|
||
**Spec:** Ω.1; no product doc. `src/shell/instrument/CLAUDE.md` already records that "a
|
||
meter-rate timer remains a separate change and is not in" — this is that change.
|
||
|
||
**Surface boundary — owns:** `src/shell/instrument/editor_platform.cpp` (the timer set-up and
|
||
the `WM_PAINT`/`invalidate` path), `editor_session.cpp` (the meter block currently inside
|
||
`onSyncTimer`), `editor_paint.cpp` (the paint entry's bitmap and dirty-rect handling), the
|
||
hero-envelope cache's accessor in `editor_paint_waveform.cpp`, and `reasampler_editor.h`'s
|
||
meter/timer/cache members. **Does not own:** `meter_ballistics.h`, `master_meter`, the
|
||
processor, any deck or overlay module.
|
||
|
||
**Ballistics need no rework.** Every constant in `core/instrument/engine/meter_ballistics.h` is
|
||
per-second, and elapsed time is measured rather than assumed — the rate is a UI-cadence
|
||
problem, not a ballistics one. **Nothing changes on the audio thread.**
|
||
|
||
**Behavior.**
|
||
- **The cause.** There is exactly ONE timer in the tree: `kSyncTimerIntervalMs = 500`
|
||
(`editor_platform.cpp:33`), the bank/bake change-detection poll. The meter's advance +
|
||
`invalidate` sit inside `onSyncTimer` (`editor_session.cpp:113-134`), so 2 FPS is the
|
||
ceiling, and `WM_TIMER` coalescing under host load takes it below that.
|
||
- **The recommended shape: a SECOND timer id on the same child window**, running only the meter
|
||
drain, advance and invalidate. **The 500 ms poll body is untouched** — `pollBankSync`,
|
||
`bakeAvailable`, `resolveBakeHoldNeeded` and the bake run must not speed up (a bridge read
|
||
plus a bank parse each), and the tick-counted UI decays (`kBakeMessageTicks`,
|
||
`dropHintTicks_`) keep their meaning for free. **The rejected alternative** is raising the
|
||
shared interval and converting those decays to elapsed time: it is one fewer timer and it
|
||
silently shortens two user-visible banners by ~15× if the conversion is missed anywhere.
|
||
- **`masterBusMeter()` is a CONSUMING drain** (exchange-to-identity) and must keep **exactly
|
||
ONE caller**, or the two clocks steal windows from each other. Moving it to the meter timer
|
||
keeps that; leaving a copy behind in `onSyncTimer` breaks it.
|
||
- **The meter block must stay AHEAD of the in-flight-drag guard.** On its own timer this is
|
||
structural rather than positional — state it, because the reason (a drag suppresses the
|
||
reload poll but the bus keeps sounding) survives the move.
|
||
- **The clock resolution is a real hazard at 16 ms.** Today the elapsed measure is
|
||
`GetTickCount64` (`editor_session.cpp:114`), whose granularity is ~15.6 ms — at 60 FPS the
|
||
measured delta quantizes to 0 or 16 and a zero-delta advance does nothing but burn a frame.
|
||
Move the meter clock to a high-resolution source off the audio thread, and skip the advance
|
||
on a zero delta rather than passing it in.
|
||
- **Three paint costs, all of which the higher rate exposes:**
|
||
1. `invalidate()` (`editor_platform.cpp:65-66`) dirties the **whole client area**
|
||
(`InvalidateRect(hwnd, nullptr, FALSE)`) and `paint` never reads `ps.rcPaint`
|
||
(`:149-155`). The meter needs its own rect invalidated and the paint needs to honour the
|
||
dirty rect.
|
||
2. `paint` allocates a **full-size `LICE_SysBitmap` per paint** (`editor_paint.cpp:28`).
|
||
Hoist it to a member resized on `WM_SIZE`, where `thumbCache_.clear()` already lives.
|
||
3. `paintWaveform` recomputes `computeEnvelope` over the **entire decoded PCM every paint**
|
||
(`editor_paint_waveform.cpp:204-212`). The decode is cached (`channelPcmFor` /
|
||
`monoPcmFor`); the hero envelope is not. Cache it keyed on sample id + bin count + lane
|
||
count, busted by `refreshFromBank` and by a resize — the same discipline `marksCache_` and
|
||
`thumbCache_` already use.
|
||
- **Target 60 FPS.** 30 is the fallback **only** if the paint-cost work cannot get there, and
|
||
choosing it requires the measurement that justifies it.
|
||
|
||
**Acceptance criteria.**
|
||
- The meter visibly tracks transients at the shipped rate; the measured frame interval is
|
||
recorded, with the rate chosen and why. `[verify — DAW]`, discharged by Ω-W2-T3.
|
||
- A meter-only frame does **not** repaint the deck, chrome or waveform bands.
|
||
- No allocation in the steady-state paint path.
|
||
- With no editor open, nothing changed: the meter timer is bound to the child window exactly as
|
||
the sync timer is.
|
||
- The bake affordance, the Hold applicability, the bake message and the drop-hint banner all
|
||
behave exactly as before — same cadence, same durations.
|
||
- `masterBusMeter()` has exactly one call site.
|
||
- The first meter read is still DISCARDED (session history, not a window) and the clip latch is
|
||
still NOT discarded with it.
|
||
|
||
**Prerequisites.** None; concurrent with T1, T2, T4, T5. **Discharges:** Ω.1.
|
||
|
||
#### Ω-W1-T4 — `deck-reflow-focus-toggles`
|
||
|
||
**Goal.** Filter Mod sits with the envelope it modulates, the Trigger face's knobs sit at their
|
||
natural pitch, a two-state toggle is one button, and clicking an envelope deck anywhere selects
|
||
its overlay.
|
||
|
||
**Spec:** Ω.2, Ω.3, Ω.4 and rulings Ω-F3, Ω-F4. Deck composition law:
|
||
`src/core/instrument/ui/CLAUDE.md`'s `knob_deck` and `deck_groups` bullets.
|
||
|
||
**Surface boundary — owns:** `src/core/instrument/ui/deck_groups.{h,cpp}`,
|
||
`knob_deck.{h,cpp}`, `deck_values.cpp` (the toggle commit seam only),
|
||
`src/core/instrument/ui/sample_chrome.{h,cpp}`, `src/shell/instrument/editor_paint_deck.cpp`,
|
||
`editor_input_deck.cpp`, `editor_paint_chrome.cpp`, `editor_input_chrome.cpp`,
|
||
`editor_input.cpp` (the focus-clear arm of the mouse-down dispatch only), `editor_controls.cpp`
|
||
(`faceLayout`'s `deckDescs` build only), and `tests/test_deck_groups.cpp`,
|
||
`tests/test_deck_groups_measured.cpp`, `tests/test_knob_deck.cpp`. **Does not own:**
|
||
`waveform_view`, `envelope_overlay`, `spline_edit`, or any `editor_*_waveform` TU (T5's), and
|
||
**no `param/` file whatsoever**.
|
||
|
||
**Behavior — Ω.2, the move.**
|
||
- `sampleDeckGroups` (`deck_groups.cpp`) is the sole deck-composition authority. Move
|
||
`kFilterModAmt` out of FILTER's `cellIds` (`:62-68`) into FILTER ENV's (`:80-89`), **in both
|
||
faces**, landing **last in the run** — the `kPitchEnvDepth` precedent (`:50-53`).
|
||
- **NO VST3 parameter-id change.** `kFilterModAmt` keeps its frozen id (`kParamFilterModAmount`
|
||
= 1240) and its unit; ids are keyed on `DeckParam`, never on deck group. `param_id.h`'s
|
||
header comment says the blocks run "per deck group in SIGNAL-FLOW order" (`:34`) — that
|
||
becomes untrue for exactly one row. **Annotate it; never renumber.** The frozen-table rule is
|
||
the whole reason the annotation is the correct fix.
|
||
- **This move costs the filter tie-line, and that cost is stated rather than discovered.**
|
||
Today `test_deck_groups_measured.cpp` pins both rows' filter groups to one right edge (x=640
|
||
block-relative) — the only reason the row block was widened 1020→1028. After the move the
|
||
Sound row loses 60 px and the Contour row gains 60, and with the two filter groups then equal
|
||
in width the tie is **unreachable at any block width** under the space-between law. The
|
||
alignment goes; the ruling stands. Re-derive that fixture from the new contract, record the
|
||
loss in it, and **do not change `kDeckRowBlockW` or the editor floor to chase it** — revisiting
|
||
the floor is a separate proposal, not this track's to make.
|
||
|
||
**Behavior — Ω.2, the spacing.**
|
||
- The defect is a **layout-algorithm artifact, not a constant**. A group's cell run is a
|
||
reserved width, and a `-1` reserve donates its slot to the surviving cells
|
||
(`knob_deck.cpp:123-138`), so Trigger's FILTER ENV gets ~100 px cells and AMP ENV ~75 against
|
||
the standard 60. Gate is uniform at 60 because it has no reserves.
|
||
- **The law: cells stay at `kDeckCellW`; the run is CENTRED in the reserve.** It is the only
|
||
candidate satisfying both of Daniel's constraints — group width stays stable across a mode
|
||
flip (which is why the reserves exist) and knobs keep their natural pitch. Gate is then
|
||
pixel-identical by construction, because with no reserves the centred run and the divided run
|
||
are the same run.
|
||
- **The two rejected laws, recorded so they are not re-proposed:** *(a) drop the reserves and
|
||
let the group narrow in Trigger* — reflows every neighbour on a mode flip, which is the thing
|
||
the reserves were introduced to prevent; *(b) keep the division but cap the cell width* —
|
||
leaves an asymmetric residue at one end and re-introduces the dead-slot look the division law
|
||
replaced.
|
||
- A consequence worth stating: once the run is centred, **where a `-1` sits in `cellIds` stops
|
||
mattering** — only the count does. The reserves become pure width accounting.
|
||
|
||
**Behavior — Ω.3, single buttons.**
|
||
- Today every deck toggle is structurally two segments (`DeckToggleDesc` `knob_deck.h:69-72`,
|
||
`placeToggle` `knob_deck.cpp:82-94`, `hitTestDeck`'s seg0/seg1 `:259-268`, `drawToggle`
|
||
`editor_paint_deck.cpp:117-137`). **Two variants replace it:**
|
||
- **Enable** — one button, label names what it controls, **`AccentPrimary` when active and
|
||
dim gray when off** (Ω-F3, Ω-F4). Applies to: the envelope enable (`kPitchEnvEnable`,
|
||
today `Off|On`) → **"Envelope"**; `kFilterEnable` → **"Filter"**; `kLimiterEnable` →
|
||
**"Limiter"** (it currently names nothing it controls); and the chrome row's
|
||
`Loop Off|Loop On` → **"Loop"**.
|
||
- **Mode selector** — one button whose **label reads the current mode**, with **no dim-gray
|
||
state** (Ω-F3). Applies to: the three `Stg|Spl` env-mode toggles → **"Stage"/"Spline"**,
|
||
and the chrome row's `Mono|Stereo`.
|
||
- **A third visual state survives and must not be collapsed into "off":** a control refused
|
||
by the mode (the chrome Loop enable in Trigger, `kFilterLaw` while the filter is off) draws
|
||
**Disabled and inert**. Off is live and clickable; Disabled is not.
|
||
- **Explicitly NOT converted, and that is a boundary rather than an oversight:** `kPlayMode`
|
||
(`Gate|Trigger`), `kPitchEngine` (`Varisp|Presrv`), `kVoiceMode` (`Poly|Mono`), `kFilterLaw`
|
||
(`Band|Notch`) and `kMonoTrigger` (`Retrig|Legato`) keep their two-segment form. Daniel named
|
||
a set; this track ships that set. The mode-selector variant generalizes to them the day he
|
||
asks.
|
||
- **Three load-bearing risks the implementation must answer:**
|
||
1. **A single button carries no `segment`**, so the commit must derive the NEXT state from the
|
||
current one. `setDeckParam`'s `segment == 1` contract (`deck_values.cpp:135-162`) is the
|
||
seam; `nextOverlaySelection` (`deck_groups.cpp:298-302`) is the existing precedent for
|
||
computing a next discrete state purely.
|
||
2. **`kLimiterEnable`'s split commit must survive VERBATIM** (`editor_input_deck.cpp:67-79`):
|
||
it commits the parameter set and calls `setLimiterEnabled`, which only ARMS the host's
|
||
`restartComponent(kLatencyChanged)` for the sync tick to deliver. Nothing on that path may
|
||
start calling into the host from inside a mouse handler.
|
||
3. **MASTER has ZERO caption slack** — its caption row (46 + toggle + radio) and its knob row
|
||
(`kDeckCellW` + `kDeckColumnGap` + `kMeterColumnW`) both measure 130, which is exactly
|
||
`kDeckSpanningW` − 2·`kDeckGroupPadX`. **So the "Limiter" button must be ≤ 64 px** (what
|
||
the two 32 px segments occupy today). Wider and `kDeckSpanningW` grows, which pushes
|
||
`kEditorMinWidth` into the 82 px budget against `kEditorCeilingWidth`. **Every other group
|
||
absorbs a wider button free** — verified: each is knob-row-bound with 50+ px of caption
|
||
headroom.
|
||
- **`hitTestKnobFace` runs no toggle-precedence pass** (`knob_deck.h:217-224`) and is only
|
||
correct while no toggle rect overlaps a knob circle. Any button that grows must be re-checked
|
||
against that, or given the same precedence order.
|
||
|
||
**Behavior — Ω.4, the deck as the overlay target.**
|
||
- Remove the three overlay radios from the env groups' descriptors. **`DeckRadioDesc` itself
|
||
must SURVIVE** — MASTER uses a passive one for the GR lamp (`deck_groups.cpp:140`), and the
|
||
`passive` skip in `hitTestDeck` (`:255-258`) is what keeps a readout from growing a gesture.
|
||
- Clicking anywhere in an envelope deck — panel background, knob, or button — focuses that
|
||
envelope's overlay. The focused deck takes an **`AccentPrimary`** border (Ω-F4). Focus clears
|
||
when the user clicks a control surface outside it; **the overlay itself is excluded from
|
||
stealing or clearing focus**, so editing the thing you selected does not deselect it.
|
||
- **Five design constraints, each of which breaks something if missed:**
|
||
1. The focus write must land **before** the `hit.kind` switch in `mouseDownDeck`
|
||
(`editor_input_deck.cpp:31-37`) and must **not consume the click**, or every knob grab and
|
||
every toggle commit on a focused deck dies.
|
||
2. **`DeckHit` carries no group id** (`knob_deck.h:190-195`). Either the shell scans
|
||
`dl.groups` for containment or `DeckHit` gains one — the latter is the smaller lie.
|
||
3. **`hitTestDeck` returns a miss for a click on group padding** (`knob_deck.cpp:252-278`),
|
||
which is exactly the "deck panel background" Daniel wants to hit. Containment against
|
||
`g.box` is the test, not the hit kind.
|
||
4. **A deck click SETS focus idempotently; it does not toggle to none.** Otherwise every knob
|
||
tweak on the focused deck unfocuses it. This retires `nextOverlaySelection`'s
|
||
re-click-clears branch (`deck_groups.cpp:301`) and replaces the radio-id map with a
|
||
group-id one.
|
||
5. **Refuse focus changes while a drag is in flight**, and keep `overlayEnv_` transient view
|
||
state with **no persistence path** — it is what is drawn, not what is played.
|
||
|
||
**Acceptance criteria.**
|
||
- **The Gate deck face is pixel-identical to today**, group boxes and cell rects both, asserted
|
||
in `test_deck_groups_measured.cpp` — except for FILTER and FILTER ENV, whose widths move by
|
||
exactly one cell in opposite directions.
|
||
- Trigger's FILTER ENV and AMP ENV cells measure `kDeckCellW`, centred in their reserves, and
|
||
each group's box width is **identical in both modes**.
|
||
- `kFilterModAmt`'s `ParamId` is unchanged; `exposedParams()` returns the same list in the same
|
||
order; no `param/` file is edited.
|
||
- Every converted control's commit produces the same audible result as the segment it replaced,
|
||
including the limiter's latency-restart arm.
|
||
- `kEditorMinWidth` and `kEditorCeilingWidth` are unchanged, and the 82 px budget is unspent.
|
||
- Clicking a knob, a button and the bare panel of an envelope deck all focus that overlay; the
|
||
first two still perform their own action; clicking the overlay does not change focus.
|
||
- The deck's pure fixtures cover the new composition, the centred run and the focus map — the
|
||
shell layer is not where any of the three is asserted.
|
||
|
||
**Prerequisites.** None; concurrent with T1, T2, T3, T5. **Discharges:** Ω.2, Ω.3, Ω.4, Ω-F3,
|
||
Ω-F4.
|
||
|
||
#### Ω-W1-T5 — `overlay-mapping`
|
||
|
||
**Goal.** The loop marks say what the mode means, and every overlay that rides the waveform
|
||
lands on the frame it names.
|
||
|
||
**Spec:** Ω.5, Ω.6 and rulings Ω-F1, Ω-F2; `docs/product/instrument-control-surface.md`
|
||
§6.3(d) and §6.4 **as amended by Ω-F2**.
|
||
|
||
**Surface boundary — owns:** `src/core/instrument/ui/waveform_view.{h,cpp}`,
|
||
`src/shell/instrument/editor_paint_waveform.cpp` (the mark, caption and overlay regions),
|
||
`editor_input_waveform.cpp`, `editor_controls.cpp` (`waveMarksFor` / `grabbableMarks` only),
|
||
`src/core/instrument/ui/spline_edit.h`'s `splineOverlayBox` and `envelope_overlay`, and
|
||
`tests/test_waveform_view.cpp` + `tests/test_component_geometry.cpp`. **Does not own:** any
|
||
deck module or `editor_*_deck` TU (T4's), the envelope-cache region of
|
||
`editor_paint_waveform.cpp` (T3's), or — **without an explicit proposal** —
|
||
`src/shell/panel/draw_kit.cpp` and `src/core/ui/component_geometry.cpp`.
|
||
|
||
**Behavior — Ω.5, the marks.**
|
||
- **Trigger ⇒ the loop pair and the crossfade mark are HIDDEN entirely.** Not Disabled, not
|
||
dim: absent. **Gate with loop off ⇒ they are still drawn and still grabbable**, styled **grey
|
||
disabled** rather than today's dim teal at `kMarkAlphaDisabled`
|
||
(`editor_paint_waveform.cpp:51-53, 285-305`). The drag-to-set-loop gesture survives, which is
|
||
the whole reason they stay grabbable.
|
||
- **The `"LOOP \xe2\x80\x94 GATE ONLY"` caption (`editor_paint_waveform.cpp:254`) is removed
|
||
unconditionally** — in Trigger there is no span left to centre it in.
|
||
- **The two sibling captions SURVIVE** (`:255`): `DRAG TO SET LOOP` when the pair is parked and
|
||
`LOOP OFF` when a span is retained. Their box derives from the marks' span, which still
|
||
exists in Gate, and they carry the one thing the grey styling cannot — *which* off state you
|
||
are in, and that dragging will fix it. **This is a [propose]-class call made here rather than
|
||
left open**; reversing it means deleting information, not styling.
|
||
- **The seams.** `waveMarksFor`'s `present[]` (`editor_controls.cpp:145`) is the correct place
|
||
to suppress — it also skips label layout. `grabbableMarks` (`:162`) is the separate
|
||
grabbability fold and must NOT follow it into hiding for the Gate-off case.
|
||
- **Warning to the track:** `resolveWaveformClaim`'s smallest-area arbitration and `capAtPoint`'s
|
||
reverse-order resolve (`editor_input_waveform.cpp:65, 89, 111, 212`) are both sensitive to
|
||
marks entering and leaving the set — `capAtPoint`'s reverse order exists precisely so a
|
||
coincident pair stays separable and the crossfade (the one mark with no column) can never be
|
||
shadowed (`core/instrument/CLAUDE.md`, `waveform_view`). Changing which marks are in the set
|
||
changes what those two resolve, which is the regression risk in this half of the track.
|
||
|
||
**Behavior — Ω.6, the mapping. This is a CLASS defect, not a START-marker instance.**
|
||
|
||
Two chains that do not agree, verified this pass:
|
||
|
||
| | Marker / overlay chain | Waveform draw chain |
|
||
|---|---|---|
|
||
| Area | `waveformOverlayArea` returns the **FULL band, no inset** (`waveform_view.cpp:23-25`) | insets **2 px each side**; `waveformColumnCount` = `width − 4` (`component_geometry.cpp:95-98`), columns start at `box.x + 2` (`draw_kit.cpp:292, 311`) |
|
||
| Domain | `[0, N]` **closed** (`clampFrame`, `waveform_view.cpp:15-19`) | `[0, N)` **half-open** (`columnMinMax`, `peaks.cpp:69-71`) |
|
||
| Rounding | **round-half-up** (`frameToX`, `:51-58`) | **truncation** |
|
||
| Phase | a 2 px marker line centred on an integer (`editor_paint_waveform.cpp:298, :301`) | a 1 px column centred at +0.5 |
|
||
|
||
Four independent discrepancies. The 2 px inset dominates — frame 0's marker sits 2 px left of
|
||
the first column and the error reverses sign toward the end — and the closed domain puts the
|
||
last frame one pixel outside the band. **Everything riding the overlay inherits all four:** the
|
||
loop span fill, the crossfade wedges, the staged envelope polyline, and the spline contour —
|
||
whose `spline_edit.h` guarantee that it uses "the FULL area, no inset, so the drawn contour
|
||
stays 1:1 with the sample's time axis" **is currently false by 2 px.**
|
||
|
||
- **Mandate ONE mapping both chains read**, so the drift cannot recur. Two routes:
|
||
- **Inset the overlay** (recommended) — instrument-only, and the overlay is already the
|
||
distinct `OverlayArea` type, so the change has one construction site.
|
||
- **De-inset the waveform** — `[propose]`, **requires justification before it is taken**: it
|
||
touches the shared `draw_kit`, which also serves the docked bank panel and the browse-card
|
||
thumbnails, so its blast radius is three surfaces rather than one.
|
||
- **The zero-crossing snap must be preserved EXACTLY.** `nearestZeroCrossing`
|
||
(`waveform_view.cpp:170-199`) fans out symmetrically, probing `t−d` before `t+d` so an
|
||
equidistant tie resolves to the lower frame; it runs on the mono cache at drag time and is
|
||
exempt for the crossfade handle. **Behaviour-identical after the change** is an acceptance
|
||
criterion, not an assumption.
|
||
- **One question the track must ANSWER, not dodge:** what "sample-accurate" means when
|
||
`frames < columns` and one frame spans many pixel columns. A marker must land somewhere
|
||
inside its own frame's column span; which end, and whether the inverse `xToFrame`
|
||
(`:60-70`) still round-trips, are the track's to specify.
|
||
- **The two pinned fixtures are re-DERIVED from the new contract, never re-baselined.**
|
||
`test_waveform_view.cpp` and `test_component_geometry.cpp` pin the current mapping by
|
||
construction; a test updated to match whatever the code now prints has stopped being a test.
|
||
|
||
**Acceptance criteria.**
|
||
- One mapping function serves both chains; a second one anywhere in the tree fails review.
|
||
- Every mark, the loop span fill, both crossfade wedges, the staged polyline and the spline
|
||
contour land on the same pixel as the waveform column for the frame they name — at frame 0,
|
||
at the last frame, and at an interior frame, asserted in the pure fixtures.
|
||
- `frames < columns` and `frames > columns` are both covered.
|
||
- The zero-crossing snap returns the identical frame for the identical input, ties included.
|
||
- Trigger shows no loop marks and no loop caption; Gate-with-loop-off shows grey marks that
|
||
still drag, and dragging one still turns the enable on.
|
||
- `docs/product/instrument-control-surface.md` §6.3(d) and §6.4 agree with the shipped
|
||
behaviour.
|
||
|
||
**Prerequisites.** None; concurrent with T1–T4. **Discharges:** Ω.5, Ω.6, Ω-F1, Ω-F2.
|
||
|
||
---
|
||
|
||
### Ω-W2 — What the measurements say, and what only a DAW can
|
||
|
||
**Depends on:** Ω-W1 in full. **Three tracks.** T1 is **measurement-gated** — it may turn out
|
||
not to be worth doing, and that is a legitimate outcome. T3's deliverable is a **RECORD, not a
|
||
fix** (the Λ-W3 precedent).
|
||
|
||
| Track | Owns |
|
||
|---|---|
|
||
| **T1** `view-topology-cache` | `src/core/view/` — tree / `parentOf` / handle-map memoization |
|
||
| **T2** `content-detect-throttle` | `src/shell/panel/panel_input.cpp` — the 30 Hz detector's cadence |
|
||
| **T3** `omega-verification-sweep` | `docs/VERIFICATION.md` — the DAW record for all of Ω-W1 |
|
||
|
||
**Shared files in the wave:** none. The three tracks touch three disjoint directories.
|
||
|
||
#### Ω-W2-T1 — `view-topology-cache` *(measurement-gated on Ω-W1-T1's result)*
|
||
|
||
**Goal.** Stop rebuilding the project's folder topology from scratch on every apply — if, after
|
||
Ω-W1-T1, that is still a cost worth paying for.
|
||
|
||
**Spec:** Ω.7's residual.
|
||
|
||
**Surface boundary — owns:** `src/core/view/` (the folder tree, `parentOf`, and the
|
||
GUID→handle map's shape). **Does not own:** `shell/view/view.cpp` — Ω-W1-T1 owns it, and this
|
||
track lands after.
|
||
|
||
**Behavior.** `readFolderEntries` + `buildFolderTree` run on every apply, and `visibleTracks`
|
||
walks the tree per parent. **The gate:** Ω-W1-T1 removes the undo-mask cost, the redundant FX
|
||
writes and the O(T²) resolve; if the remaining apply time is already inside the frame budget on
|
||
the measured project, **this track is not dispatched** and its finding is recorded instead.
|
||
`docs/TODO.md` already carries `view_mode_model.cpp` at 815 lines with a named codec seam — do
|
||
not take that seam here, and do not grow that file.
|
||
|
||
**Acceptance criteria.**
|
||
- A before/after measurement on the same project decides the track, and the number is recorded
|
||
either way.
|
||
- If taken: membership, reconcile and the park/restore round trip are all unchanged, and the
|
||
cache is invalidated by every path that can change track topology.
|
||
- `core/view` stays pure and CTest-covered.
|
||
|
||
**Prerequisites.** Ω-W1-T1 landed and measured. **Discharges:** Ω.7's residual, or records it
|
||
as not worth taking.
|
||
|
||
#### Ω-W2-T2 — `content-detect-throttle`
|
||
|
||
**Goal.** The auto-tag detector stops spending main-thread time 30 times a second on exactly
|
||
the projects where the switch is slowest.
|
||
|
||
**Spec:** Ω.7's third cost.
|
||
|
||
**Surface boundary — owns:** `src/shell/panel/panel_input.cpp` (`detectNewContent` /
|
||
`enumerateLiveGuids` and their cadence inside `bankPanelRefresh`). **Does not own:** any other
|
||
panel TU, `view.cpp`, or the auto-tag RULES — only how often they are asked.
|
||
|
||
**Behavior.** `detectNewContent` (`panel_input.cpp:146`) runs `enumerateLiveGuids` (`:106-135`)
|
||
every tick of REAPER's `"timer"` registration (`main.cpp:341`, ~30/sec), regardless of panel
|
||
state — O(T + I) REAPER string calls plus ~4(T+I) allocations per tick, on every project. Throttle
|
||
the ENUMERATION, not the detection semantics: the same GUIDs must still be detected, the
|
||
first-poll and `reloadPending` re-baseline guards must still fire before any diff, and the
|
||
lane-minting pass must still run only on a tick that tagged. A slower cadence is a latency
|
||
change to an invisible background tag, and the track states the chosen interval and why.
|
||
|
||
**Acceptance criteria.**
|
||
- New tracks and items are still auto-tagged into the active mode, and a project reload still
|
||
re-baselines rather than mass-tagging.
|
||
- The Ρ-W1-T1 rule survives: an added GUID that already carries a membership record is still
|
||
dropped, so a render-in-place result is not re-tagged to Design.
|
||
- The per-tick allocation count is measured before and after.
|
||
- The panel's own repaint cadence is unaffected.
|
||
|
||
**Prerequisites.** None beyond Ω-W1. **Discharges:** Ω.7's polling cost.
|
||
|
||
#### Ω-W2-T3 — `omega-verification-sweep`
|
||
|
||
**Goal.** The DAW-observable half of this phase is recorded rather than assumed.
|
||
|
||
**Spec:** every `[verify — DAW]` criterion in Ω-W1.
|
||
|
||
**Surface boundary — owns:** `docs/VERIFICATION.md` (a Phase-Ω section). **Does not own:** any
|
||
source file. **A fix found here opens a track; it is not applied inside this one.**
|
||
|
||
**Behavior.** One session, on a project big enough to reproduce Ω.7 (the track records how big
|
||
that turned out to be, because that number is itself a finding). Covers: switch-to-first-paint
|
||
before and after; two switches in rapid succession; the park/restore round trip after
|
||
deferral; Ctrl-Z after a switch; the meter's frame interval and its behaviour under host load;
|
||
the Gate deck face against a pre-Ω screenshot; the Trigger face's spacing; every converted
|
||
button in all three states; deck-click overlay focus; the marks in Trigger and in
|
||
Gate-with-loop-off; and a marker aligned against the waveform at both ends of a sample.
|
||
|
||
**Acceptance criteria.**
|
||
- Every `[verify — DAW]` mark in Ω-W1 is discharged by a recorded observation or explicitly
|
||
carried forward as unrun. **Inspection is not an observation.**
|
||
- The two performance numbers are recorded with the machine and project they were measured on.
|
||
|
||
**Prerequisites.** All of Ω-W1 landed. **Discharges:** Ω's DAW criteria.
|
||
|
||
### Shared files across Ω's waves, named rather than discovered at merge
|
||
|
||
All textual adjacency unless marked otherwise. Within Ω-W1 the wave's own table above is the
|
||
authority; this one covers the cross-wave and cross-phase cases.
|
||
|
||
| File | Tracks | Nature |
|
||
|---|---|---|
|
||
| `src/shell/view/view.cpp` | Ω-W1-T1 alone | **Deliberately not split.** Already 642 lines and over the ceiling with its seam blocked; T1's new code goes in a new TU beside it rather than into it |
|
||
| `src/core/view/*` | Ω-W2-T1 alone | Different wave from T1's shell edits, and gated on them |
|
||
| `src/app/CMakeLists.txt` / `src/app/main.cpp` | Ω-W1-T1 | **Cross-phase:** Λ owns other regions of both (Λ-W2-T1/T2/T3, Λ-W4-T3, Λ-W5-T1). Ω lands first; Λ rebases |
|
||
| `src/shell/panel/panel_input.cpp` | Ω-W2-T2 | **Cross-phase:** Ρ-W1-T1's `detectNewContent` edit is landed and is the same function — read what shipped, not what was specced |
|
||
| `docs/VERIFICATION.md` | Ω-W2-T3 | Λ-W3-T1 writes its own Linux record into a different section |
|
||
|
||
---
|
||
|
||
## Phase Λ — ReaSampler on Linux: both artifacts, shipped
|
||
|
||
**Ships:** `reaper_reasampler.so` and `reasampler_9000.vst3` built, installed and documented
|
||
on Linux. The extension compiles under GCC/Clang, builds optimized by default, docks its
|
||
panel, writes a bank index that survives a comma-decimal locale, and prunes without lying
|
||
about what it reclaimed. The instrument loads, scans, instantiates and processes audio in
|
||
**any** Linux host, and opens its editor under **REAPER only** — a stated contract, not a
|
||
shortfall. macOS is out.
|
||
|
||
**Four named exceptions to platform parity**, stated up front rather than discovered:
|
||
prune deletes permanently (no trash); the OS drag-out's copy-only guarantee is conventional
|
||
rather than structural, because SWELL's file-list drag takes no effect mask; the type faces
|
||
are DejaVu rather than Segoe UI and Consolas; and the ReaSampler 9000 editor comes up under
|
||
REAPER only, with every other host getting its generic parameter UI.
|
||
|
||
**Consolidates: none of the seventeen.** Phase Λ came from a direct request (Daniel,
|
||
2026-08-02) and is scoped in `docs/product/linux-readiness.md`, which is itself downstream of
|
||
two landed audits: `docs/product/audit-notes/lambda-w1-t1-build-toolchain.md` (findings
|
||
Λ-01…Λ-10, verify items V1…V10, forks D1…D7 — build system, toolchain, vendored deps,
|
||
resources, test harness, packaging) and
|
||
`docs/product/audit-notes/lambda-w1-t2-source-runtime.md` (L2-01…L2-12 — source portability
|
||
and runtime behaviour). **Grep those for a cited finding number; do not read either whole.**
|
||
This phase **supersedes nothing**, and corrects one standing promise rather than inheriting
|
||
it: `docs/product/versioning-and-release.md` already commits in writing to "three platform
|
||
artifacts per channel per release" (T1 §1e), which Λ-D4 makes **two** for now, with macOS
|
||
named as deferred rather than silently dropped.
|
||
|
||
**Nothing in this phase has been verified on a Linux machine.** Every `[verify — Linux]`
|
||
mark below is load-bearing and none may be laundered into settled voice. The structural
|
||
consequence is stated once here because it shapes the whole wave order: **Λ-W2's edits are
|
||
authored blind from the audits' citations, and Λ-W3 is the session that discharges Λ-W2's
|
||
acceptance criteria.** Λ-W2 is not "done" in the usual sense until Λ-W3 runs, and a track
|
||
that reports otherwise has laundered unverified work into landed work — which is exactly
|
||
what the audits' `[verify]` discipline exists to prevent. Both audits state their own effort
|
||
bands as provisional until the sweep runs; this plan does not restate a band the audits did
|
||
not give.
|
||
|
||
### Rulings — Daniel's, 2026-08-02. Six, settled.
|
||
|
||
Full statements with the reasoning Daniel gave: `docs/product/linux-readiness.md`
|
||
§"Settled decisions (Daniel, 2026-08-02)". **Do not re-litigate these.**
|
||
|
||
| Ruling | What it settled | Specified in | Bound into |
|
||
|---|---|---|---|
|
||
| **Λ-D1** | **The VST3 instrument is in scope** (audit fork D1 = **Fork B**). Both artifacts ship, not just the extension. **Reverses D5's platform clause in writing** — "Windows-only, VST3-only, REAPER-only" becomes "Windows and Linux; the editor is REAPER-hosted"; the VST3-only and REAPER-only clauses survive verbatim | §Λ-D1 | Λ-W6…Λ-W8; the D5 rewrite is Λ-W6-T1's first act |
|
||
| **Λ-D2** | **SWELL is reached by `dlopen`ing REAPER's own `libSwell.so`** (route **B3a**). Route B3b — building SWELL into the module, and with it GDK/GTK3, FreeType, Fontconfig, OpenGL — is **rejected**, not deferred | §Λ-D2 | Λ-W7-T1 |
|
||
| **Λ-D3** | **Non-REAPER hosts must be protected** — *"REAPER-only is good for now, but we need to protext other hosts."* A first-class acceptance criterion of two tracks, not a footnote on one | §"The non-REAPER-host safety criterion" | Λ-W6-T2 (contract), Λ-W7-T1 (runtime), Λ-W8-T1 (regression) |
|
||
| **Λ-D4** | **macOS is out** (D3 = **out**) — *"I don't have a mac to compile on."* Shared macOS/Linux edits are still made **in their shared form and noted as shared**; what is out is macOS as a deliverable, any macOS verification, signing/notarization, and L2-11's APFS half. **Do not "fix macOS while you're in there"** — an unverifiable edit to the APPLE branch is worse than none, because it looks tested | §Λ-D4 | phase-wide |
|
||
| **Λ-D5** | **The Linux artifact is shipped, not developer-only** (D6 = **shipped**), which promotes Λ-02 (a real optimized build) and Λ-08 (a documented install path) to must-fix. **And a hard `unlink` prune is acceptable**, with a **platform-aware confirmation string** saying so | §Λ-D5 | Λ-W2-T2, Λ-W4-T1, Λ-W5-T1 |
|
||
| **Λ-D6** | **The X11 editor is real work and is sequenced last.** Not a separate ruling — the direct consequence of D1+D2+D3. Everything before it ships something; halting Λ-W8 still leaves a shipped extension and a loading, processing, generic-UI instrument | §Λ-D6 | the wave order itself |
|
||
|
||
### Open forks — four, NONE ruled
|
||
|
||
Stated with the evidence for both sides at `docs/product/linux-readiness.md` §"Open forks —
|
||
Daniel's". This is the one phase in this plan with unanswered [Daniel]-class questions; see
|
||
"Decision state" above for how that reconciles against the plan-wide claim.
|
||
|
||
| Fork | Question | Recommendation | What it blocks |
|
||
|---|---|---|---|
|
||
| **Λ-F1** | Does CI get built in this phase, and on what runner? (audit D4) | **No recommendation — genuinely a resourcing call.** For: two toolchains and one is not on the developer's machine, so every Windows-only commit becomes a latent Linux regression. Against: CI is infrastructure, its value is highest *after* the first Linux build works, and a runner forces Λ-F4 immediately | **Nothing.** Decides only whether Λ-W5-T1 writes a pipeline paragraph or a "deferred to dev-ops" one |
|
||
| **Λ-F2** | The dialog-resource route: **resource-id-0** (SWELL's documented escape hatch, `swell-functions.h:606–608`; deletes the whole resgen pipeline from non-Windows builds) or **wire up resgen properly** (PHP, `add_custom_command`, an include-shim TU — the M end of Λ-01's band)? (audit D7) | **No clean recommendation; the asymmetry neither audit stated is the reason it is worth ruling.** Route A is cheaper and structurally simpler and stakes the panel's file-drop ingest on an *unverified* `ChildWindowFromPoint` descent, because with no resource there is no `WS_EX_ACCEPTFILES` bit to set (L2-07). Route B can set that bit explicitly. **A third option, if the cheap route is wanted without the exposure:** take Route A and make the file-drop check a hard gate in Λ-W3's sweep, with Route B as an additive follow-up track rather than a rewrite — it spends a second Linux session in the bad case | **Λ-W2-T3's dispatch — the ONLY fork here that gates a track.** Unruled, that track slips to Λ-W4 and Λ-W3's sweep splits into two Linux sessions |
|
||
| **Λ-F3** | Does "copy-only is structural" survive as a shipped invariant? (T2 [Daniel] 4) | **Keep the invariant, scope the *enforcement* claim to Windows** — "structurally enforced on Windows via `DoDragDrop`'s copy-only mask; advertised, not enforced, on SWELL, which takes no effect mask." The real counter: an invariant one platform cannot enforce is arguably not an invariant, and weakening the global wording would also correctly warn a Windows reader off relying on it in shared code | **Nothing.** Λ-W4-T3 makes the edit either way; only the sentence changes. Answerable at implementation review |
|
||
| **Λ-F4** | What is the declared support floor? (new — the audits raised the inputs Λ-07, V8, V10, not the decision) | **Declare a narrow floor — current-stable-distro glibc, GCC ≥ 9, `x86_64-linux` only — and widen it on request.** Counter: REAPER's Linux reputation is partly built on modest and non-x86 hardware, `aarch64` is no longer exotic, and adding it later means a second bundle directory and a second verification pass on every release | **The ship wave, not a dispatch.** Non-gating for Λ-W2 (a recorded floor can be widened); **gating for Λ-W5-T1** — a shipped artifact has to say what it runs on. Also feeds Λ-W6-T1's bundle directory set and Λ-F1's runner image |
|
||
|
||
### Phase-Λ acceptance criteria
|
||
|
||
These bind every track in this phase, in addition to the plan-wide set above.
|
||
|
||
- **Windows behaviour does not change. Every track carries that criterion explicitly.** This
|
||
is a port; a port that improves Windows by accident has also changed Windows by accident.
|
||
- **`[verify — Linux]` is a status, not a decoration.** A criterion carrying it is discharged
|
||
by a recorded observation in Λ-W3's verification record — never by inspection, never by
|
||
"should work". A track whose Linux criteria are all unrun is not landed; it is authored.
|
||
- **No vendored file is patched.** `git status` under `vendor/` stays clean through the whole
|
||
phase, including the SWELL bootstrap — which is precisely the constraint that shapes it.
|
||
- **No new third-party dependency surface.** Λ-D2 rejected route B3b for this reason: no
|
||
vendored SWELL build, no GDK/GTK3, no FreeType, no Fontconfig, no OpenGL, no `pkg-config`
|
||
in this tree. **A track that finds itself reaching for one of those has drifted and stops.**
|
||
- **Shared macOS/Linux edits are made in shared form and labelled shared** (Λ-D4). Λ-01,
|
||
Λ-03, Λ-04, Λ-07, Λ-09 and L2-09 are all shared by the audits' own marking; an
|
||
`#ifdef _WIN32` / `#else` that is right for both costs nothing and needs no mac. No macOS
|
||
verification is claimed for any of them.
|
||
- **`core/` stays pure and stays platform-neutral.** Every `#include` under `src/core/**` is
|
||
a `core/` sibling, one of 26 standard headers, or the generated `version_generated.h` (T2
|
||
§1.1) — the audits verified this rather than assuming it, and no Λ track may be the one
|
||
that breaks it. The one platform fork in the whole directory
|
||
(`capture_paths.cpp:18–20`, the Windows case-fold) is already correct for Linux with both
|
||
branches asserted by `tests/test_capture_paths.cpp`.
|
||
- **Every pure module gets a `<module>_tests` target** that runs without REAPER or a DAW.
|
||
Λ-W2-T4 is the phase's proof that this is not ceremonial: it is a `core/`-only fix, fully
|
||
testable on the current Windows box, and it lands before any Linux session.
|
||
- **The ~600-line ceiling and the structural heuristics bind unchanged.** Λ-W8-T1 is the
|
||
track most likely to strain them; its own criteria name the seam vocabulary to reuse.
|
||
|
||
**Performance posture.** No named hot path is touched by any track in this phase — not the
|
||
`peaks` envelope compute, not audition, not the realtime-capture tick's single-pointer-test
|
||
idle fast path, not the instrument's `process()`. Two consequences are stated as criteria
|
||
rather than left implicit: **nothing is added to `process()`** (no `dlopen`, no probe, no
|
||
platform branch on the per-voice-per-sample path — the SWELL availability probe is computed
|
||
once, lazily, off the audio thread), and **Λ-W2-T4's locale fix stays on the JSON/persist
|
||
path**, which root `CLAUDE.md` already declares off all hot paths. Λ-02 is the phase's one
|
||
genuine performance item and it runs the other way: today a Linux build carries **no `-O`
|
||
flag at all**, on a tree whose `peaks` path is documented as presuming an optimizing build.
|
||
|
||
### The non-REAPER-host safety contract
|
||
|
||
Λ-D3 stated as something a person can check. **This is an observable contract, and it is the
|
||
specification the two instrument waves are graded against** — the full argument is
|
||
`docs/product/linux-readiness.md` §"The non-REAPER-host safety criterion", which a brief for
|
||
Λ-W6-T2, Λ-W7-T1 or Λ-W8-T1 must be written against.
|
||
|
||
**The finding that makes it urgent, and it is a fact rather than a risk.** The vendored
|
||
`vendor/WDL/WDL/swell/swell-modstub-generic.cpp` declares a **file-scope static** at `:125`,
|
||
so its constructor runs when our `.so` is `dlopen`ed — i.e. **during the host's plugin
|
||
scan** — and `:102` calls **`exit(2)`** when `dlopen` of `libSwell.so` fails, `:117`
|
||
**`exit(1)`** on an incomplete API table. Compiling the vendored `SWELL_LOAD_SWELL_DYLIB`
|
||
path unmodified therefore means **a Bitwig or Ardour plugin scan on a machine without
|
||
`libSwell.so` terminates the host process** — a killed DAW mid-scan, with the user's
|
||
session. The mitigating detail that shapes the fix: `doinit` substitutes a zero-returning
|
||
`dummyFunc` for each unresolved name, so a *partial* table degrades rather than crashes. A
|
||
partial load is survivable; `exit()` is not.
|
||
|
||
**In any Linux host, with or without `libSwell.so`:** the module scans and enumerates its one
|
||
class with no crash, hang, process exit or blacklist entry; it instantiates, produces audio,
|
||
plays MIDI, and round-trips component state byte-identically with the Windows build; when no
|
||
usable platform surface exists `isPlatformTypeSupported` returns `kResultFalse` for **every**
|
||
type — including `kPlatformTypeX11EmbedWindowID` — and `createView(kEditor)` returns
|
||
**`nullptr`**, never a view that then fails to attach and never one that draws nothing; the
|
||
host falls back to its generic parameter UI; exactly **one** diagnostic line per process
|
||
names why the editor is unavailable, through the SDK's logging or stderr, never a modal;
|
||
teardown crashes nothing and leaves no partially-initialised SWELL table reachable.
|
||
|
||
**Verified with a harness that is already on disk and that neither audit named**, because
|
||
neither swept the SDK's `samples/` tree:
|
||
`vendor/vst3sdk/public.sdk/samples/vst-hosting/validator/` is a scriptable, headless,
|
||
REAPER-free host, under `public.sdk/` and therefore pulled by the documented narrow submodule
|
||
init. The four checks, all `[verify — Linux]`: `validator` completes with **no `libSwell.so`
|
||
on the filesystem** (the direct negation of the `exit(2)` finding, and the load-bearing one);
|
||
a real Ardour or Bitwig scan reaching browser, instantiation and generic UI; a REAPER-on-Linux
|
||
load where the editor opens; and a **negative control** — rename `libSwell.so` beside a Linux
|
||
REAPER and confirm the generic-UI fallback plus the single diagnostic line.
|
||
|
||
### Cross-phase boundary — Γ-W4-T1, and the frozen parameter table
|
||
|
||
**Λ must never register a VST3 parameter.** Γ-W4-T1's `ParamID` table is FOREVER-FROZEN from
|
||
the moment it ships, on the same footing as the command-id strings and the class UIDs. A
|
||
parameter minted in Λ to make a Linux fallback look better would collide with a table Λ does
|
||
not own. **This is a hard boundary and it is the only Γ↔Λ interaction that could actually go
|
||
wrong.**
|
||
|
||
Neither phase blocks the other. The host's fallback for a plug-in reporting no usable editor
|
||
is its generic parameter UI, and today the plugin registers zero parameters — so "no editor"
|
||
currently degrades to *nothing* rather than to *controls*. **Λ-W6-T2's acceptance criteria are
|
||
written to pass with zero parameters registered**: an empty generic UI is a pass. Once
|
||
Γ-W4-T1 lands its 44 derived parameters the identical Λ code path degrades to a usable
|
||
generic UI instead — a better result, not a different criterion. **Λ assumes no schedule for
|
||
Γ**; a Λ track needing a Γ fact reads `dev` at dispatch time.
|
||
|
||
**Concurrency.** Λ is the widest phase in this plan by file surface, and it is disjoint from
|
||
the others by *kind* rather than by directory: its edits are platform guards, CMake, docs and
|
||
one new `shell/instrument/` TU. Γ owns `core/instrument/` + `shell/instrument/` **sources**
|
||
(Λ-W6…Λ-W8 own that directory's CMake, its `CLAUDE.md` files, and `editor_platform`'s
|
||
non-Windows branch); Ε lands in the new `core/package/` + `shell/package/`; Ρ touches
|
||
`core/capture`, one new `shell/capture` TU, `panel_input.cpp` and `main.cpp`. **The genuine
|
||
adjacency to watch is CMake**: Ε appends two `add_subdirectory` lines to the root
|
||
`CMakeLists.txt` while Λ-W2-T2 owns that file's language and target settings — append-only
|
||
lines against property blocks, textual adjacency rather than semantic contention. Λ's own
|
||
shared files are named in the wave sections below.
|
||
|
||
---
|
||
|
||
### Λ-W1 — The audits *(complete)*
|
||
|
||
Two tracks, both landed: **T1 `build-toolchain-audit`** (Λ-01…Λ-10, V1…V10, D1…D7) and
|
||
**T2 `source-runtime-audit`** (L2-01…L2-12). Recorded so the wave numbering matches the audit
|
||
filenames and branch names. Nothing to re-spec; nothing to dispatch.
|
||
|
||
---
|
||
|
||
### Λ-W2 — Make it buildable, and make it honest
|
||
|
||
**Depends on:** nothing. **Four tracks, disjoint at the file level.** Three are authored
|
||
blind against the audits and verified in Λ-W3; **T4 is the one track fully verifiable on the
|
||
current Windows box.**
|
||
|
||
| Track | Owns |
|
||
|---|---|
|
||
| **T1** `linux-compile-blockers` | two source blockers + `main.cpp`'s API-load failure branch |
|
||
| **T2** `toolchain-floor` | the CMake files' language, property and platform blocks |
|
||
| **T3** `panel-dialog-resource` | `panel_window.cpp`'s dialog creation, `resource.rc` / `resource.h` — **gated on Λ-F2** |
|
||
| **T4** `locale-independent-numerics` | the number codec in four `core/` TUs |
|
||
|
||
**Shared file in the wave, named rather than discovered at merge:**
|
||
`src/app/CMakeLists.txt` — T2 takes the property and platform blocks, T3 takes **one
|
||
`target_sources` line under the resgen route only**. Disjoint regions; whichever lands second
|
||
rebases. The wave's severity question is dissolved rather than adjudicated: T2 grades L2-03 a
|
||
Blocker on failure-mode quality with no direct evidence and L2-04 a Major with a confirmed
|
||
mechanism, and flags its own inconsistency — both land here, in different tracks, so the
|
||
relative grade never has to be settled.
|
||
|
||
#### Λ-W2-T1 — `linux-compile-blockers`
|
||
|
||
**Goal.** The extension compiles and links under GCC/Clang, and when it refuses to load it
|
||
says why instead of vanishing.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T1; findings L2-01, L2-02, L2-03.
|
||
|
||
**Surface boundary — owns:** `src/shell/panel/draw_kit.cpp` (`loadFont`, `:70–77`),
|
||
`src/shell/actions/instrument_drop_win.cpp` (`writeTempPreset`, `:50–61`), `src/app/main.cpp`
|
||
(**the `REAPERAPI_LoadAPI` failure branch only**). **Does not own:** any `CMakeLists.txt`,
|
||
`panel_window.cpp`, or any `core/` file.
|
||
|
||
**Behavior.**
|
||
- **`FF_DONTCARE` (L2-01).** `draw_kit.cpp:73` passes `DEFAULT_PITCH | FF_DONTCARE` to
|
||
`CreateFont`; the symbol has **zero occurrences anywhere in `vendor/WDL/`**, and the file is
|
||
not platform-guarded, only its include is. `draw_kit` links into **both** modules, so
|
||
nothing builds until this is fixed. Drop the term or define it locally in the non-Windows
|
||
include branch. **Do not add `windows.h`** — L2-01's stated direction. The family bits are
|
||
advisory to Windows' font mapper and meaningless to fontconfig.
|
||
- **`GetCurrentProcessId()` (L2-02).** `instrument_drop_win.cpp:59` calls it with no platform
|
||
branch anywhere in the TU; SWELL exports `GetCurrentThreadId` and not this. The PID exists
|
||
only to keep two concurrent REAPER instances from colliding in the shared temp dir, and the
|
||
atomic counter at `:52` already carries the intra-process half. Replace with a
|
||
platform-neutral uniqueness source behind a guard.
|
||
- **The silent load failure (L2-03).** Either switch `main.cpp` to `REAPERAPI_MINIMAL` plus an
|
||
explicit `WANT` list — the pattern `panel_window.cpp` and `panel_audition.cpp` already use,
|
||
and the honest inventory of what this extension actually needs — or keep the full load and
|
||
print the failure count via `rec->GetFunc("ShowConsoleMsg")` before returning 0.
|
||
|
||
**Acceptance criteria.**
|
||
- `cmake -B build -S . -G Ninja && cmake --build build` produces `build/reaper_reasampler.so`
|
||
with no errors. `[verify — Linux]` = **V1**.
|
||
- `ctest --test-dir build --output-on-failure` passes all 91 test targets, **no `-C` flag
|
||
needed** on a single-config generator. `[verify — Linux]` = **V2**.
|
||
- A deliberately misspelled `WANT` entry (or a forced non-zero `failcnt`) produces a visible
|
||
REAPER console line naming the count, not a silent refusal.
|
||
- Windows build and `ctest` unchanged.
|
||
|
||
**Prerequisites.** None. **Discharges:** L2-01, L2-02, L2-03; enables V1, V2, V3.
|
||
|
||
#### Λ-W2-T2 — `toolchain-floor`
|
||
|
||
**Goal.** The Linux build is optimized when asked, links what it uses, hides what it does not
|
||
export, and reports diagnostics no one has seen yet.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T2; findings Λ-02, Λ-03, Λ-04, Λ-05, Λ-07,
|
||
Λ-09, Λ-10.
|
||
|
||
**Surface boundary — owns:** root `CMakeLists.txt`, `src/app/CMakeLists.txt` (**the target
|
||
property and platform blocks; NOT the source list — that is Λ-W4-T3's**),
|
||
`cmake/reasampler_targets.cmake`, `src/shell/instrument/CMakeLists.txt` (**thread linkage
|
||
only; the `WIN32` gate is Λ-W6-T1's**), `README.md`, and root `CLAUDE.md` §"Build and test" /
|
||
§"Install / reload" / §"One-time submodule setup". **Does not own:** any `.cpp` or `.h`.
|
||
|
||
**Behavior.**
|
||
- **Λ-02** — root `CMakeLists.txt:28–30` is the *complete* list of language settings: no
|
||
`CMAKE_BUILD_TYPE`, no `CMAKE_CXX_FLAGS`, no IPO/LTO, no `target_compile_options` anywhere
|
||
in the tree. Default `CMAKE_BUILD_TYPE` when neither it nor `CMAKE_CONFIGURATION_TYPES` is
|
||
set, and correct the docs' ship instruction: `--config Release` is *accepted and ignored*
|
||
by Ninja and Make, and the README currently sends a Linux user to `build/Release/`, which
|
||
does not exist there — the module lands at `build/reaper_reasampler.so`.
|
||
- **Λ-03** — `CXX_VISIBILITY_PRESET hidden` + `VISIBILITY_INLINES_HIDDEN` on both module
|
||
targets. SWELL's own build already uses `-fvisibility=hidden`, and the symbols that must
|
||
stay exported carry their own `visibility("default")` attributes
|
||
(`reaper_plugin.h`'s `REAPER_PLUGIN_DLL_EXPORT`, `fplatform.h`'s `SMTG_EXPORT_SYMBOL`,
|
||
`swell-modstub-generic.cpp:135`'s `SWELL_dllMain`).
|
||
- **Λ-04** — `find_package(Threads REQUIRED)` + `Threads::Threads`. Correct on all three
|
||
platforms, costs nothing on Windows.
|
||
- **Λ-09** — `-Wall -Wextra` and `CMAKE_CXX_EXTENSIONS OFF`. **No `-Werror` in this change**
|
||
(the audit is explicit): the diagnostic-set size over this tree is not estimable from
|
||
Windows. Separately `-Wl,--no-undefined` on the module targets, restoring the
|
||
fail-at-link-time behaviour MSVC gives and GNU `ld` does not — **or** a recorded reason why
|
||
`SWELL_PROVIDED_BY_APP`'s function-pointer design makes the gap moot.
|
||
- **Λ-05** — pin the `reaper_plugin.h` → `../WDL/swell/swell.h` include coincidence with a
|
||
comment or an `INTERFACE` target carrying both include dirs as one unit. Invisible on
|
||
Windows, load-bearing off it.
|
||
- **Λ-07** — record the compiler floor (**Λ-F4's input**), or add `-lstdc++fs` and document
|
||
why.
|
||
- **Λ-10** — one sentence in the platform-support docs: on Linux `vendor/vst3sdk` is optional
|
||
until Λ-W6, so `git submodule update --init vendor/reaper-sdk vendor/WDL` is the complete
|
||
extension-only prerequisite.
|
||
|
||
**Acceptance criteria.**
|
||
- `compile_commands.json` or a verbose build log shows an explicit `-O` flag on a bare
|
||
`cmake --build build`. `[verify — Linux]`.
|
||
- `nm -D --defined-only reaper_reasampler.so | grep -E 'ReaperPluginEntry|SWELL_dllMain'`
|
||
finds **both** after the visibility preset. `[verify — Linux]` = **V6**.
|
||
- The extension links with `Threads::Threads` **removed** — proving the include-only pthread
|
||
dependency needs no flag — or the symbol forcing it is named. `[verify — Linux]` = **V5**.
|
||
- `capture_paths_tests` links without an explicit `-lstdc++fs` on the declared floor, or the
|
||
flag is added and the floor documented. `[verify — Linux]` = **V8**.
|
||
- **The warning count from the first `-Wall -Wextra` build is recorded, not fixed**, and
|
||
handed to Λ-W4 as an input.
|
||
- Windows build unchanged; `--config Release` still behaves as documented there.
|
||
|
||
**Prerequisites.** None; concurrent with T1, T3, T4. **Discharges:** Λ-02, Λ-03, Λ-04, Λ-05,
|
||
Λ-07, Λ-09, Λ-10; enables V5, V6, V8.
|
||
|
||
#### Λ-W2-T3 — `panel-dialog-resource` *(GATED on Λ-F2 — do not dispatch until it is ruled)*
|
||
|
||
**Goal.** The docked bank panel opens on Linux, and if it ever fails to, it says so.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T3 and §Λ-F2; findings Λ-01, L2-06, L2-07.
|
||
|
||
**The defect.** `panel_window.cpp:169` is `CreateDialogParam(g_hInst,
|
||
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**:
|
||
`src/app/CMakeLists.txt:109` has the `target_sources` line commented out (`:98` for macOS).
|
||
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
|
||
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
|
||
`#include`-only artifact that cannot be a `target_sources` entry at all.
|
||
|
||
**Surface boundary — owns:** `src/shell/panel/panel_window.cpp` (the `CreateDialogParam` call
|
||
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
|
||
`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.
|
||
`panel_window.cpp` is **deliberately not split** across tracks: the L2-06 diagnostic and the
|
||
Λ-01 resource route are the same function, and under the id-0 route the same *line*.
|
||
|
||
**Behavior.** Whichever route Λ-F2 picks, plus — **unconditionally, and on both platforms
|
||
rather than behind a guard** — a one-line `ShowConsoleMsg` on the `!g_panel.hwnd` path naming
|
||
the missing dialog resource (L2-06). That single line converts a mystery into a two-minute
|
||
diagnosis and is worth having on Windows too. The fix is shared macOS/Linux either way
|
||
(Λ-D4: made in shared form, verified on Linux only).
|
||
|
||
**Acceptance criteria.**
|
||
- The panel toggle action docks a visible, LICE-drawn bank panel in a Linux REAPER.
|
||
`[verify — Linux]` = **V7**.
|
||
- With the resource deliberately unavailable, the toggle prints one console line rather than
|
||
doing nothing. **Verifiable on Windows by forcing the branch.**
|
||
- Dragging a WAV from the file manager onto the docked panel ingests it — or, if it does not,
|
||
the failure is understood rather than mysterious. `[verify — Linux]`, T2 §5 item 3.
|
||
**This criterion's difficulty depends on the Λ-F2 route** and is the fork's substance.
|
||
- Windows panel behaviour byte-for-byte unchanged.
|
||
|
||
**Prerequisites.** **Λ-F2 must be ruled before dispatch.** If it is not, this track slips to
|
||
Λ-W4 and Λ-W3's sweep splits into a panel-independent half (run early) and a panel-dependent
|
||
half (run after this lands) — a second Linux session, which is the cost of leaving the fork
|
||
open. **Discharges:** Λ-01, L2-06, L2-07; enables V7.
|
||
|
||
#### Λ-W2-T4 — `locale-independent-numerics`
|
||
|
||
**Goal.** A persisted float round-trips identically regardless of process locale, so a bank
|
||
index written on a comma-decimal machine is not written unparseable.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T4; finding L2-04.
|
||
|
||
**Why it is pulled forward, ahead of everything it looks like it should follow:** it is the
|
||
phase's one substantial fix that **needs no Linux box at all** — two writers and three
|
||
readers, all in `core/`, all unit-testable on Windows today — and its failure mode is that
|
||
the bank index is written unparseable and **the project's whole bank is lost on reload.** It
|
||
should land before any Linux user saves a project.
|
||
|
||
**Surface boundary — owns:** `src/core/json/json.cpp` (the `%.17g` writer and the `strtod`
|
||
reader), `src/core/model/provenance.cpp` (its own `%.17g`), `src/core/wire/wire.cpp`
|
||
(`Cursor::fieldDouble`), `src/core/capture/render_settings.cpp` (the `std::stod` over
|
||
REAPER's `P_RAZOREDITS`), and the corresponding `tests/`. **Does not own:** any shell TU, any
|
||
CMake file, `wav_codec` (its byte-order handling is already explicit and correct).
|
||
|
||
**Behavior.** Make the number codec locale-independent at its two writers and three readers —
|
||
`std::to_chars`/`std::from_chars`, or a `std::locale::classic()`-bound stream. **Do not "fix"
|
||
this by calling `setlocale`**; an extension must not mutate the host's locale. Why it is not
|
||
paranoia: on Windows the CRT starts in the `"C"` locale and nothing here calls `setlocale`,
|
||
which is why it has never fired; on Linux SWELL's GDK backend calls
|
||
`gtk_init_check`/`gdk_init_check` and never calls `gtk_disable_setlocale`, and any GTK or Qt
|
||
plugin in the same process can do the same. The readers are honestly fail-closed — they
|
||
require whole-token consumption — so the failure is "the field disappears", not "the field is
|
||
silently wrong".
|
||
|
||
**Acceptance criteria.**
|
||
- New pure tests pass **on Windows today**, under a forced comma-decimal `LC_NUMERIC` set
|
||
inside the test. **This is the one Λ-W2 track that does not wait for Λ-W3.**
|
||
- The bank index, view model, tracking ledger, provenance blob and tail setting all round-trip
|
||
a fractional value under that forced locale.
|
||
- **Byte-for-byte identical output to today's writer under the `"C"` locale** — this is a
|
||
persisted format, and a changed representation is a compatibility event.
|
||
- Nothing on a hot path is touched; the JSON/persist path is declared off all hot paths in
|
||
root `CLAUDE.md` and stays there.
|
||
|
||
**Prerequisites.** None. **Discharges:** L2-04.
|
||
**Note:** T2 §5 item 1 (read `LC_NUMERIC` inside a running REAPER-Linux process) stays in the
|
||
Λ-W3 sweep, but only to record how urgent this *was* — the work is not gated on it.
|
||
|
||
---
|
||
|
||
### Λ-W3 — First light, and the verification sweep
|
||
|
||
**Depends on:** Λ-W2. **One track, deliberately** — this is a person at a Linux box working a
|
||
checklist where each answer reprices the next; splitting it across specialists buys no
|
||
concurrency and loses the thread. Precedent: Ρ-W1 and Γ-W4 are both single-track waves for
|
||
the same reason.
|
||
|
||
#### Λ-W3-T1 — `linux-verification-sweep`
|
||
|
||
**Goal.** Discharge every acceptance criterion Λ-W2 could not check from Windows, answer every
|
||
`[verify — Linux]` item in both audits, and reprice the remaining waves against what is
|
||
actually true.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W3-T1 and §"Why this sequence".
|
||
|
||
**Surface boundary — owns: no source file and no CMake file.** Its deliverable is a
|
||
**verification record** at `docs/product/audit-notes/lambda-w3-verification.md`, one entry per
|
||
item: the exact check run, the observed result, and what it changes. **Any fix the sweep
|
||
motivates is filed to Λ-W4, not made here.** The one exception: a defect that blocks further
|
||
sweeping (the build does not link at all) is fixed in place and recorded as a deviation,
|
||
because the alternative is a wasted session.
|
||
|
||
**Behavior — the sweep set.**
|
||
|
||
| From | Items |
|
||
|---|---|
|
||
| T1 | **V1** compile, **V2** ctest, **V3** does REAPER's Linux build call `SWELL_dllMain` and populate the API table, **V4** where `UserPlugins/` actually is and whether `reaper_*.so` is the right glob, **V5** pthread link flag, **V6** visibility vs. the two exported symbols, **V7** the docked panel, **V8** `-lstdc++fs`, **V9** does `libSwell.so` sit beside REAPER's executable, **V10** which `uname -m` values the VST3 bundle must carry |
|
||
| T2 | process `LC_NUMERIC`; `REAPERAPI_LoadAPI`'s actual return value and the name of any gap; panel file-drop routing; which SWELL GDI/locale build REAPER ships; fontconfig's substitution for "Consolas"; `SWELL_InitiateDragDropOfFileList` acceptance by common targets, and whether its 500 ms no-motion timeout cancels a slow gesture; prune against an in-use file |
|
||
| New | the `-Wall -Wextra` diagnostic set, counted and categorised; whether `-Wl,--no-undefined` links clean or names an undefined set; whether the four-TU LICE slice links without `lice_colorspace.cpp` (T1 §3 leaves this an unreconciled inference) |
|
||
|
||
**Three items are load-bearing beyond their own answer and must be run FIRST:** **V1**
|
||
(nothing else is observable until it passes); **V9** (a negative answer **voids Λ-D2's route
|
||
and needs a Daniel re-ruling** before Λ-W6 is dispatched — the entire `dlopen`-REAPER's-SWELL
|
||
design rests on it, and it is a one-line `ls`); and the `REAPERAPI_LoadAPI` count (a non-zero
|
||
result promotes L2-03 from a diagnostic to a real Blocker and names the gap).
|
||
|
||
**Acceptance criteria.**
|
||
- Every item above has a recorded answer or an explicit "could not determine, because X".
|
||
**A blank is a failure of this track, not a deferral.**
|
||
- Λ-W2's four tracks each have their `[verify — Linux]` criteria marked discharged or failed,
|
||
**by name**.
|
||
- Λ-W4 and Λ-W5's scope is restated against the answers, with any effort band that moved
|
||
called out. **The audits' bands are provisional by their own statement; this is where they
|
||
stop being.**
|
||
- Λ-W6's prerequisites (V9, V10) are answered, or Λ-W6 is explicitly blocked pending a Daniel
|
||
re-ruling on Λ-D2.
|
||
|
||
**Prerequisites.** Λ-W2-T1, T2, T4. **T3 if Λ-F2 was ruled** — otherwise the panel-dependent
|
||
items (V7, file-drop routing, any font check needing a rendered panel) defer to a second
|
||
session and that deferral is recorded. **Discharges:** V1–V10 and T2 §5's seven items, **as
|
||
answers rather than as fixes**.
|
||
|
||
---
|
||
|
||
### Λ-W4 — Correctness and safety, repriced
|
||
|
||
**Depends on:** Λ-W3-T1. **Three tracks, disjoint by directory.** Everything here is known
|
||
work whose *size* the sweep may have moved.
|
||
|
||
#### Λ-W4-T1 — `prune-deletion-safety`
|
||
|
||
**Goal.** Prune on Linux deletes only what it means to, tells the truth about what it
|
||
reclaimed, and tells the user the deletion is permanent.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W4-T1; finding L2-05 (both halves) plus the
|
||
symlink row of T2 §4.
|
||
|
||
**Surface boundary — owns:** `src/shell/persist/prune_fs.cpp` (the deletion authority's
|
||
non-Windows branch and the reclaim scan) and the prune confirmation text wherever it is
|
||
composed (`shell/actions/prune_action` / `shell/panel/panel_bank_ops`). **Does not own:**
|
||
`core/reclaim/`'s orphan computation (pure, portable, unaffected), or any other persist TU.
|
||
|
||
**Behavior.**
|
||
- **The confirmation string becomes platform-aware** (Λ-D5). On Linux it states the deletion
|
||
is permanent and there is no Recycle Bin; on Windows it says what it says today. **Same code
|
||
path, one platform-dependent phrase — not a second dialog.**
|
||
- **The dead "locked" branch is reckoned with.** `if (ec) return false; // real failure
|
||
(locked/permission) -> skip` encodes Windows file-sharing semantics; on Linux `unlink` of an
|
||
open file **succeeds**, the audio keeps playing from the open fd, and the bytes are gone
|
||
when it closes — so the branch never fires. Either it is documented as Windows-only in
|
||
place, or the Linux path acquires an equivalent guard. **What it must not do is stay
|
||
silently asymmetric**: the recovery floor root `CLAUDE.md` §"The resample bake" relies on
|
||
("the superseded file survives on disk until a prune reclaims it") otherwise has nothing
|
||
under it on Linux. **This half is not covered by Λ-D5's trash ruling** — it is not a trash
|
||
question.
|
||
- **The symlink hazard is fixed.** `fs::directory_iterator` + `is_regular_file()` follows
|
||
symlinks under C++17; size is read from the target via `file_size()` but `fs::remove`
|
||
deletes the **link**, not the target — so prune reports N bytes reclaimed and reclaims
|
||
zero. Symlinked media folders are far more idiomatic on Linux than on Windows. **This is a
|
||
reporting lie, not a cosmetic issue.**
|
||
|
||
**Acceptance criteria.**
|
||
- A Linux prune of a bank file currently playing behaves as recorded in Λ-W3's sweep, and the
|
||
behaviour matches what the confirmation promised. `[verify — Linux]`.
|
||
- A symlinked bank file is either skipped or deleted with its target, and the reclaimed-byte
|
||
figure matches what actually left the disk in **both** cases. Unit-testable for the
|
||
computation; `[verify — Linux]` for the filesystem half.
|
||
- The Windows path — `SHFileOperationW` + `FOF_ALLOWUNDO`, Recycle-Bin recoverable — is
|
||
bit-for-bit unchanged.
|
||
- **Prune remains the only file-deletion path in the system** (plan-wide product invariant).
|
||
|
||
**Prerequisites.** Λ-W3-T1 (the in-use-file check). **Discharges:** L2-05 both halves, and the
|
||
symlink row of T2 §4.
|
||
|
||
#### Λ-W4-T2 — `linux-font-faces`
|
||
|
||
**Goal.** The kit asks for faces that exist on a stock Linux distro, so type is **chosen**
|
||
rather than substituted.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W4-T2 and §"Calls made here"; finding L2-09.
|
||
**This is a product-designer call, not a fork:** silent fontconfig substitution is not
|
||
acceptable because the kit's type is part of a deliberate visual identity
|
||
(`docs/product/visual-design-language.md`), and the palette-role discipline exists precisely
|
||
so a visual direction is a single-file change rather than an emergent property of the host.
|
||
The cost is five string literals. Contradict it in review with an argument.
|
||
|
||
**Surface boundary — owns:** `src/shell/panel/draw_kit.cpp`'s five `loadFont` call sites
|
||
(`:154–158`) and the face constants in `draw_kit.h`. **Does not own:** `loadFont` itself
|
||
beyond the literals, the palette, any geometry, or the two WCAG `static_assert`s — which are
|
||
on pixel height and weight, not on the face, and hold regardless.
|
||
|
||
**Behavior.** A platform face list at the five call sites: **DejaVu Sans / DejaVu Sans Mono**
|
||
as the Linux defaults, following SWELL's own no-fontconfig fallback list
|
||
(LiberationSans/DejaVuSans, LiberationMono/DejaVuSansMono) as precedent. **One code path**,
|
||
shared with macOS's eventual San Francisco/Menlo (Λ-D4: made in shared form, not verified).
|
||
The subtlety to carry into the work: `draw_kit.cpp:74`'s `if (!hf) return` guard does **not**
|
||
catch this failure mode — SWELL's `CreateFont` always returns a non-null handle even when the
|
||
face never resolved, recording the failure as a null `typedata` internally. **A wrong or
|
||
missing face is not observable at the call site, only in the rendering.**
|
||
|
||
**Acceptance criteria.**
|
||
- Every kit string renders in the intended face on a stock distro, and the numeric readouts
|
||
are tabular. `[verify — Linux]`.
|
||
- Windows renders Segoe UI and Consolas exactly as today.
|
||
- Row heights, ellipsis points and label truncation are unchanged on Windows; on Linux they
|
||
are **measured** rather than assumed correct.
|
||
|
||
**Prerequisites.** Λ-W3-T1's fontconfig answer (T2 §5 item 5), which decides whether this is
|
||
cosmetic or a readability regression. **Discharges:** L2-09.
|
||
|
||
#### Λ-W4-T3 — `source-partition-and-invariants`
|
||
|
||
**Goal.** The build's source list stops relying on every TU's own `#ifdef` discipline, and the
|
||
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.
|
||
|
||
**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
|
||
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
|
||
shipped code path.
|
||
|
||
**Behavior.**
|
||
- **Λ-06** — partition the source list where the Λ-W3 diagnostic set says a TU needs it.
|
||
`drag_out_win.cpp` is the model (a real `#ifdef _WIN32` / `#else` split);
|
||
`arrange_drop_win.cpp` and `instrument_drop_win.cpp` are `_win`-suffixed for the **surface**
|
||
they serve, not for a platform dependency, and the audits found them portable by inspection
|
||
— **confirm against the actual compile rather than re-inspecting.**
|
||
- **L2-10 — the copy-only invariant. Λ-F3 rules the wording; this track makes the edit either
|
||
way.** Make `drag_out_win.h:7–11` the doc a Linux reviewer is pointed at, and treat "MOVE is
|
||
structurally impossible" as a Windows-scoped claim: SWELL's file-list drag takes no effect
|
||
mask, so nothing at the API level forbids a target from treating the drag as a move.
|
||
- **L2-11 — no Linux action.** If the predicate is touched at all it becomes
|
||
"case-insensitive filesystem", not "Windows" — but Λ-D4 puts macOS out, so the right move is
|
||
**a comment recording the known macOS defect, not a speculative fix.**
|
||
|
||
**Acceptance criteria.**
|
||
- No TU compiles on Linux only because of an `#ifdef` that happens to be complete; every
|
||
platform-specific TU is either partitioned in CMake or carries a deliberate, commented
|
||
guard.
|
||
- The copy-only invariant's text says **the same thing** in `drag_out_win.h`, the owning
|
||
`CLAUDE.md`, and any spec text that cites it.
|
||
- Zero behaviour change on Windows.
|
||
|
||
**Prerequisites.** Λ-W3-T1. **Λ-F3 for the L2-10 wording only — non-gating:** the track can
|
||
land the partition and leave the sentence for a follow-up. **Discharges:** Λ-06, L2-10,
|
||
L2-11's Linux half.
|
||
|
||
---
|
||
|
||
### Λ-W5 — Ship the extension
|
||
|
||
**Depends on:** Λ-W3-T1 (V4) and **all three Λ-W4 tracks** — shipping means the correctness
|
||
fixes are in. **One track. Gated on Λ-F4** (a shipped artifact has to say what it runs on).
|
||
|
||
#### Λ-W5-T1 — `linux-packaging-and-install`
|
||
|
||
**Goal.** A Linux user can install a correctly-built `reaper_reasampler.so` by following a
|
||
document, and a pipeline can install it by following a rule.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W5-T1; finding Λ-08 plus Λ-02's shipped half.
|
||
|
||
**Surface boundary — owns:** an `install()` rule in `src/app/CMakeLists.txt` — **the first in
|
||
the tree** — `README.md`'s platform-support and install sections, root `CLAUDE.md`
|
||
§"Install / reload", and `docs/product/versioning-and-release.md`'s artifact and pipeline
|
||
paragraphs. **Does not own:** any source file, the toolchain properties (Λ-W2-T2's), or CI
|
||
(Λ-F1).
|
||
|
||
**Behavior.**
|
||
- Document the Linux `UserPlugins/` root **as V4 actually found it**, and the
|
||
single-config-generator output path (`build/reaper_reasampler.so`) that no doc currently
|
||
names.
|
||
- Add the `install()` rule for **both channels**. The channel fork is platform-independent by
|
||
construction — `REASAMPLER_CHANNEL` threads through `configure_file` into names only — so
|
||
`reaper_reasampler_beta.so` needs no separate mechanism, only a separate destination check.
|
||
- **Amend `versioning-and-release.md`**, which already promises three platform artifacts per
|
||
channel per release and mentions no Linux install path, no signing and no CI. Λ-D4 removes
|
||
macOS from the near-term promise: that document should say **two** platform artifacts per
|
||
channel for now, **with macOS named as deferred rather than silently dropped.**
|
||
- **The beta channel on Linux is uncosted in both audits.** Almost certainly free — the fork
|
||
is name-only — but "almost certainly" is not a ship criterion. Build and install both
|
||
channels side by side once.
|
||
- **Λ-F1 decides only which paragraph gets written here** — a pipeline paragraph, or an
|
||
explicit "deferred to dev-ops". Until it is ruled, **Λ ships nothing that presumes a
|
||
runner.**
|
||
|
||
**Acceptance criteria.**
|
||
- A clean-machine walkthrough **following only the README**: clone, narrow submodule init,
|
||
configure, build, install, restart REAPER, actions present, panel docks. `[verify — Linux]`.
|
||
- `reaper_reasampler.so` and `reaper_reasampler_beta.so` coexist in one REAPER with separate
|
||
ext-state namespaces, command ids and dock idents. `[verify — Linux]`.
|
||
- The **installed** binary shows an explicit `-O` flag in its build log — Λ-02's criterion,
|
||
re-checked on the artifact that actually ships.
|
||
- `versioning-and-release.md` no longer promises an artifact this phase does not produce.
|
||
- The declared support floor (Λ-F4) appears in the README and matches what was built.
|
||
|
||
**Prerequisites.** Λ-W3-T1 (V4), Λ-W4 (all three tracks), **Λ-F4 ruled**. **Discharges:**
|
||
Λ-08, and Λ-02's shipped half.
|
||
|
||
---
|
||
|
||
### Λ-W6 — The instrument module, and the host-safety contract
|
||
|
||
**Depends on:** Λ-W2-T2 (thread linkage, visibility, warnings) and Λ-W3-T1 (V10, plus V9 if
|
||
Λ-D2 is to survive). **Λ-W6 may run CONCURRENTLY with Λ-W4 and Λ-W5** — it touches
|
||
`src/shell/instrument/` and that directory's CMake and `CLAUDE.md` files plus
|
||
`src/core/instrument/CLAUDE.md`, none of which Λ-W4 or Λ-W5 opens; the only reason to
|
||
serialise is attention, not contention.
|
||
|
||
**Two tracks — one build + docs, one source — and they are `T1 before T2 — serial`**, the
|
||
same shape this plan already records for Γ-W3. They are file-disjoint but not order-free:
|
||
there must be a module before it can refuse to show a view.
|
||
|
||
**At the end of this wave the Linux instrument loads in any host, processes audio, and has NO
|
||
EDITOR AT ALL** — a defined, verifiable, shippable state, not a half-done one.
|
||
|
||
#### Λ-W6-T1 — `vst-linux-module`
|
||
|
||
**Goal.** `reasampler_9000.vst3` configures, builds and installs on Linux as the directory
|
||
bundle a Linux host expects, and the decision that forbade it is reversed in writing.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W6-T1; audit findings B1, B2, B5.
|
||
|
||
**Surface boundary — owns:** `src/shell/instrument/CMakeLists.txt` (the gate, the entry-point
|
||
source selection, the bundle POST_BUILD and install rule, `SWELL_PROVIDED_BY_APP`, the modstub
|
||
TU) and the three D5 passages in `src/core/instrument/CLAUDE.md`,
|
||
`src/shell/instrument/CLAUDE.md` and `src/shell/panel/CLAUDE.md`. **Does not own:** any `.cpp`
|
||
or `.h` under `shell/instrument/`.
|
||
|
||
**Behavior.**
|
||
- **B5 first, as a documentation act.** Rewrite the three D5 passages: the platform clause
|
||
becomes "Windows and Linux; the editor is REAPER-hosted"; **VST3-only and REAPER-only
|
||
survive verbatim.** `src/core/instrument/CLAUDE.md` carries it twice (Invariants and
|
||
Non-goals), `src/shell/instrument/CLAUDE.md` once (Non-goals), and
|
||
`src/shell/panel/CLAUDE.md` once (the font/GDI clause). **Nothing else in this wave may land
|
||
before this does** — the invariant files are what a future implementer reads.
|
||
- **B1** — swap `public.sdk/source/main/dllmain.cpp` for `linuxmain.cpp` on Linux. `dllmain`
|
||
includes `<windows.h>` with no `SMTG_OS_*` guard; `linuxmain.cpp` exports `ModuleEntry` and
|
||
`ModuleExit`, **both mandatory** — the SDK's own loader refuses the module without either.
|
||
Both files are already vendored; this is a source swap plus a platform `if()`.
|
||
- **Split the `WIN32 AND EXISTS` conjunction** at `src/shell/instrument/CMakeLists.txt:9`.
|
||
Today it is a conjunction, so a Linux configure silently omits `reasampler_vst` **even with
|
||
the submodule slice fully initialised.** The `EXISTS` half stays — a fresh clone with no
|
||
VST3 slice must still configure — and the `WIN32` half becomes a Windows-or-Linux predicate.
|
||
- **B2** — the artifact becomes a **directory bundle**:
|
||
`reasampler_9000.vst3/Contents/<uname -m>-linux/reasampler_9000.so`, per V10's answer and
|
||
Λ-F4's floor. `Contents/Resources/moduleinfo.json` is **optional** — the SDK's
|
||
`getModuleInfoPath` returns empty when absent rather than failing — **so do not author
|
||
one.** Install roots: `$HOME/.vst3/`, `/usr/lib/vst3/`, `/usr/local/lib/vst3/`,
|
||
`$APPFOLDER/vst3/`.
|
||
- **`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
|
||
(`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
|
||
not. **The `dlopen` itself is Λ-W7's**; what this track lands is the compiled-in, inert
|
||
table plus the exported `SWELL_dllMain`.
|
||
- **The channel fork applies:** `reasampler_9000_beta.vst3` with its own class UID. **The UID
|
||
pair is FOREVER-FROZEN and must not change** — a Linux build is a new platform, not a new
|
||
identity, and a saved project rebinds by UID.
|
||
|
||
**Acceptance criteria.**
|
||
- A Linux configure produces the `reasampler_vst` target; a Windows configure is unchanged.
|
||
- The built bundle's directory shape matches what the SDK's `module_linux.cpp` opens, and the
|
||
SDK's own `validator` loads it. `[verify — Linux]`.
|
||
- Both channels build and install side by side, and **the two class UIDs are byte-identical to
|
||
the Windows build's.**
|
||
- The three `CLAUDE.md` files no longer claim Windows-only, and **still** claim VST3-only and
|
||
REAPER-only.
|
||
- **No VST3 parameter is registered by this track or any other in this phase** — see the
|
||
Γ-W4-T1 boundary above.
|
||
|
||
**Prerequisites.** Λ-W2-T2, Λ-W3-T1 (V10; V9 if Λ-D2 is to survive). **Discharges:** B1, B2,
|
||
B5; and Λ-10's "vst3sdk is optional on Linux" caveat becomes conditional.
|
||
|
||
#### Λ-W6-T2 — `vst-host-safety-contract`
|
||
|
||
**Goal.** The plugin's editor surface refuses **cleanly** on every Linux host, so that adding
|
||
a real editor later is a change of branch taken and not a change of contract.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W6-T2 and §"The non-REAPER-host safety
|
||
criterion"; Λ-D3, L2-08's fallback half.
|
||
|
||
**Surface boundary — owns:** `src/shell/instrument/editor_platform.cpp` (the
|
||
`isPlatformTypeSupported` / `createView` decision and the non-Windows stubs),
|
||
`reasampler_processor.cpp`'s `createView` site, `reasampler_embed.cpp`'s
|
||
`REAPER_FXEMBED_WM_IS_SUPPORTED`, and a new availability-probe seam under
|
||
`shell/instrument/`. **Does not own:** any CMake file, the paint or input families (untouched
|
||
— they stay whole-region-guarded), or any pure `core/instrument/` module.
|
||
|
||
**Behavior.**
|
||
- The editor's availability becomes a **tri-state probe** — untried / available / unavailable
|
||
— computed **once, lazily, off the audio thread**. In this wave the Linux answer is
|
||
unconditionally *unavailable*; **Λ-W7 gives it a real computation without changing a single
|
||
call site.**
|
||
- `isPlatformTypeSupported` returns `kResultFalse` for **every** type when unavailable;
|
||
`createView(kEditor)` returns `nullptr`. **Never a view that fails to attach.**
|
||
- **One** diagnostic line per process, naming the reason. Never a modal.
|
||
- `reasampler_embed`'s `REAPER_FXEMBED_WM_IS_SUPPORTED` continues to return 0 off Windows.
|
||
That is correct and stays correct — the TCP/MCP embed strip on Linux is an explicit
|
||
non-goal of this phase.
|
||
- **The Windows path is not restructured to accommodate this.** The probe is a Linux branch on
|
||
an existing decision, **not a new abstraction over both.**
|
||
|
||
**Acceptance criteria.** The full observable contract above, verified by **all four** of its
|
||
checks: `validator` with no `libSwell.so` present; a real Ardour or Bitwig scan; a
|
||
REAPER-on-Linux load; and the renamed-`libSwell.so` negative control. `[verify — Linux]`.
|
||
Plus:
|
||
- Windows editor behaviour bit-for-bit unchanged — same window class, same `wndProc`, same
|
||
`CS_DBLCLKS` fall-through.
|
||
- **Nothing added to `process()`** — no `dlopen`, no probe, no branch on the
|
||
per-voice-per-sample path.
|
||
- **The criteria pass with ZERO VST3 parameters registered.** An empty generic UI is a pass;
|
||
Γ-W4-T1 improves it and Λ must not.
|
||
|
||
**Prerequisites.** Λ-W6-T1 (there must be a module to load). **Discharges:** Λ-D3's Λ-W6 half;
|
||
L2-08's fallback half.
|
||
|
||
---
|
||
|
||
### Λ-W7 — The SWELL bootstrap
|
||
|
||
**Depends on:** Λ-W6-T1, Λ-W6-T2, and **V9 answered affirmatively.** If V9 is negative, **this
|
||
track does not exist and Λ-D2 needs a re-ruling** — which is why V9 runs first in Λ-W3 rather
|
||
than being discovered here.
|
||
|
||
#### Λ-W7-T1 — `swell-dylib-bootstrap`
|
||
|
||
**Goal.** The plugin acquires a working SWELL function table under REAPER on Linux, without
|
||
the vendored stub's `exit()` behaviour and without vendoring SWELL.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W7-T1 and §"The implementation shape this
|
||
obliges"; Λ-D2 (route B3a), audit finding B3.
|
||
|
||
**Surface boundary — owns:** a new `src/shell/instrument/swell_bootstrap.{h,cpp}`, the
|
||
availability-probe computation Λ-W6-T2 left stubbed, and the `SWELLAppMain`-shaped callback.
|
||
**Does not own:** any vendored file (**nothing under `vendor/` is patched**), the CMake source
|
||
list beyond adding one TU, or the editor.
|
||
|
||
**Behavior — exactly the four-point shape in the product doc, and it is not a re-litigation of
|
||
Λ-D2 but the specification of it.**
|
||
1. **Never define `SWELL_LOAD_SWELL_DYLIB`.** Compile `swell-modstub-generic.cpp` in its
|
||
default branch, which exports `SWELL_dllMain(hInst, callMode, GetFunc)` and calls `doinit`
|
||
on the pointer it is handed.
|
||
2. **Own the load.** Resolve the host executable's directory, `dlopen` `libSwell.so`, `dlsym`
|
||
`SWELLAPI_GetFunc`, `dlsym` and call `SWELL_set_app_main`, then call our own exported
|
||
`SWELL_dllMain(hinst, DLL_PROCESS_ATTACH, getfunc)` — **using only exported surface, with
|
||
every failure returning a recorded state instead of exiting.** This also dissolves the
|
||
`SWELLAppMain`-as-a-link-requirement noted in T1 B3a: we pass an app-main because we choose
|
||
to, not because the linker demands one. `[verify — Linux]` whether a minimal app-main
|
||
suffices for a plugin that only ever creates child windows inside a host-supplied X11
|
||
window.
|
||
3. **Probe the host, not just the library.** A non-REAPER host has no SWELL message loop —
|
||
REAPER's own is what pumps SWELL windows — so even a successful `dlopen` in Ardour would
|
||
produce a window nothing drives. **Gate on the host name via `IHostApplication::getName()`
|
||
AND on the `dlopen` succeeding**; either failing means no editor.
|
||
4. **Probe once, lazily, off the audio thread.** Tri-state, computed on first `createView` and
|
||
never recomputed. No `dlopen` from `process()`, no per-`createView` retry, **no
|
||
static-constructor work.**
|
||
|
||
**The failure taxonomy the probe must distinguish**, because they need different messages:
|
||
host is not REAPER / executable path unresolvable / `libSwell.so` not present at the resolved
|
||
path / `SWELLAPI_GetFunc` missing or version-mismatched (the stub checks
|
||
`SWELLAPI_GetFunc(NULL)==(void*)0x100`) / API table incomplete. **The last is not fatal** —
|
||
`doinit` substitutes a zero-returning `dummyFunc` per miss — and whether to accept a partial
|
||
table or refuse is **[propose at review]**, with *"refuse if any name the editor actually
|
||
calls is missing"* as the starting proposal.
|
||
|
||
**Acceptance criteria.**
|
||
- Under REAPER on Linux the probe reports *available* and the SWELL table resolves with **zero
|
||
misses**. `[verify — Linux]`.
|
||
- Under `validator` with **no `libSwell.so` anywhere**, the probe reports *unavailable*, the
|
||
process **exits normally**, and one diagnostic line is printed. **This is the direct
|
||
negation of the `exit(2)` finding and is the track's headline criterion.**
|
||
`[verify — Linux]`.
|
||
- Under Ardour or Bitwig **with** a `libSwell.so` reachable, the host-name gate **still
|
||
refuses** — proving the gate is on the host, not only on the library. `[verify — Linux]`.
|
||
- No vendored file is modified; `git status` under `vendor/` is clean.
|
||
- Windows build unaffected — the whole TU is behind a platform guard.
|
||
|
||
**Prerequisites.** Λ-W6-T1, Λ-W6-T2, V9 affirmative. **Discharges:** B3 via route B3a; Λ-D3's
|
||
runtime half.
|
||
|
||
---
|
||
|
||
### Λ-W8 — The X11 editor
|
||
|
||
**Depends on:** Λ-W7-T1. **One track, and it is the phase's only L-band source item** (L2-08;
|
||
T1 B4 costs it "build S, source L"). Λ-D6 sequences it last on purpose: **halting this wave
|
||
still leaves a shipped extension and a loading, processing, generic-UI instrument** — a real
|
||
product, not a stub.
|
||
|
||
#### Λ-W8-T1 — `x11-embed-view`
|
||
|
||
**Goal.** The ReaSampler 9000 editor opens, draws and responds under REAPER on Linux.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W8-T1; audit findings B4, L2-08.
|
||
|
||
**Surface boundary — owns:** a **Linux sibling** to `src/shell/instrument/editor_platform.cpp`
|
||
(window creation and parenting, the run-loop timer, the event-driven input path), and the
|
||
`#ifdef _WIN32` region **boundaries** in `reasampler_editor.h` and the eight
|
||
`editor_input_*` / `editor_paint*` TUs — **boundaries only, not their contents.**
|
||
**Does not own:** `draw_kit`, any pure `core/instrument/` module, any painter's drawing logic,
|
||
`instrument_bake`, or the processor.
|
||
|
||
**Behavior.** The audits call this **a new competence rather than a port.** The build-side cost
|
||
is nil — the Linux IIDs are already in the vendored slice (`commoniids.cpp` defines
|
||
`Linux::IEventHandler`, `Linux::ITimerHandler`, `Linux::IRunLoop` under `#if SMTG_OS_LINUX`,
|
||
and that file is already in the `vst3_sdk` source list). What must be written:
|
||
- `kPlatformTypeX11EmbedWindowID` instead of `kPlatformTypeHWND`; `attachedToParent` receiving
|
||
an X11 window id.
|
||
- A `Linux::IRunLoop`-driven timer replacing `SetTimer`/`WM_TIMER` — **including the editor's
|
||
sync tick, which the bake's arm-then-run discipline rides on.**
|
||
- An **event-driven input path** replacing the `wndProc` switch. `RegisterClassW` /
|
||
`CreateWindowExW` / `DefWindowProcW` have **no SWELL analogue at all** — SWELL has no
|
||
window-class model, only `SWELL_CreateDialog` and raw `HWND__` construction.
|
||
- Substitutions the audit already resolved by name: `MoveWindow` → `SetWindowPos`;
|
||
`GetWindowLongPtr`/`SetWindowLongPtr` → the non-`Ptr` forms returning `LONG_PTR`;
|
||
`GetKeyState` → `GetAsyncKeyState`; `TrackMouseEvent`/`WM_MOUSELEAVE` → **nothing**, so
|
||
hover-leave needs its own derivation (the panel layer already documents the same gap);
|
||
`DragAcceptFiles`/`DragQueryFileW` → the drop path Λ-W2-T3 settled for the panel.
|
||
|
||
**A worked reference implementation is already on disk and neither audit named it.**
|
||
`vendor/vst3sdk/public.sdk/samples/vst-hosting/editorhost/source/platform/linux/` contains
|
||
`window.cpp` (returning `{kPlatformTypeX11EmbedWindowID, …}` and answering
|
||
`Linux::IRunLoop::iid` from `queryInterface`), `runloop.cpp`, and `irunloopimpl.h` (a
|
||
`RunLoopImpl` implementing `registerEventHandler` / `registerTimer` and their unregisters). It
|
||
is the **host** side of the contract rather than the plug-in side, which makes it a precise
|
||
specification of what our plug-in side must satisfy, and the documented narrow submodule init
|
||
already pulls it. **This does not shrink the L band** — it is a reading input, not a library;
|
||
it removes the "we are guessing at the contract" risk from the estimate.
|
||
|
||
**Acceptance criteria.**
|
||
- The editor opens, draws every face identically to the Windows build (compare screenshots at
|
||
the same window size), and every drag, click, wheel and keyboard interaction behaves the
|
||
same. `[verify — Linux]`.
|
||
- Hover-leave is correct on every hover surface, without `TrackMouseEvent`.
|
||
- **The bake's arm-then-run tick fires under `IRunLoop`, and a bake completes end-to-end on
|
||
Linux:** staged file, extension action invoked over the VST3 host bridge, outcome read back,
|
||
adopt and reset. `[verify — Linux]`.
|
||
- **Λ-W6-T2's contract still holds** — a non-REAPER host still gets `nullptr` from
|
||
`createView`. **Re-run all four checks; this is a regression criterion, not a new one.**
|
||
- **Nothing added to `process()`**; no dispatch added to any per-sample path.
|
||
- Every new file lands under the ~600-line ceiling **with a responsibility seam, not a
|
||
bisection.** The editor's existing band-axis split (`_chrome` / `_waveform` / `_deck` /
|
||
`_browse` / `_curve`) is the seam vocabulary to reuse — the Linux platform TU is a **sibling
|
||
to `editor_platform`**, per L2-08's own direction: *"it needs a sibling, not a rewrite."*
|
||
- Windows editor behaviour bit-for-bit unchanged.
|
||
|
||
**Prerequisites.** Λ-W7-T1. **Discharges:** B4, L2-08.
|
||
|
||
---
|
||
|
||
### Shared files across Λ's waves, named rather than discovered at merge
|
||
|
||
All textual adjacency, not semantic contention, unless marked otherwise.
|
||
|
||
| File | Tracks | Nature |
|
||
|---|---|---|
|
||
| `src/app/CMakeLists.txt` | Λ-W2-T2 (property + platform blocks), Λ-W2-T3 (one `target_sources` line, **resgen route only**), Λ-W4-T3 (the source list), Λ-W5-T1 (the `install()` rule) | Four disjoint regions of one file. Λ-W2-T2 and Λ-W2-T3 are the only pair in the same wave; one line each |
|
||
| `src/shell/panel/draw_kit.cpp` | Λ-W2-T1 (`loadFont`'s `CreateFont` args, `:73`), Λ-W4-T2 (the five call sites, `:154–158`) | Different waves |
|
||
| `src/shell/instrument/CMakeLists.txt` | Λ-W2-T2 (thread linkage), Λ-W6-T1 (gate, entry point, bundle, install), Λ-W7-T1 (one added TU) | Different waves |
|
||
| `src/shell/panel/panel_window.cpp` | Λ-W2-T3 alone | **Deliberately not split.** The L2-06 diagnostic and the Λ-01 resource route are the same function — under the id-0 route the same *line*. Splitting them would be semantic contention |
|
||
| `src/shell/instrument/editor_platform.cpp` | Λ-W6-T2 (the refusal branch), Λ-W8-T1 (the real branch) | Different waves; the second replaces the first's computation **without touching its call sites** |
|
||
|
||
---
|
||
|
||
## Traceability — all seventeen items
|
||
|
||
The check that nothing was dropped. Every row points at a track. All seventeen items are
|
||
Θ or Ξ work, and both phases have since landed in full — their track sections are retired
|
||
from this file; see `docs/COMPLETED.md` for each track's full narrative.
|
||
|
||
| # | Item (short) | Phase-Wave-Track | Worktree slug |
|
||
|---|---|---|---|
|
||
| 1 | Envelope editor: radio switch, curve dials, overlay recolor | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
|
||
| 2 | MM preamp Filter — resonant HP/LP stage | Θ-W1-T3 (DSP) **+** Θ-W2-T1 (integration) | `pth-w1-t3-filter-dsp-port`, `pth-w2-t1-filter-voice-path` |
|
||
| 3 | Alternative Spline EGs | Θ-W5-T1 | `pth-w5-t1-spline-egs` |
|
||
| 4 | Bug: end-of-sample click, Trigger × Preserve | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
|
||
| 5 | Bug: drag-out sometimes lands without audio | Θ-W1-T2 | `pth-w1-t2-capture-handoff-bugs` |
|
||
| 6 | Bug: FX-container drop loses the capture | Θ-W1-T2 | `pth-w1-t2-capture-handoff-bugs` |
|
||
| 7 | Stereo waveform shows both channels | Θ-W2-T2 | `pth-w2-t2-stereo-waveform-lanes` |
|
||
| 8 | Release anchoring; the Pitch AD becomes AHD | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
|
||
| 9 | Loop points — regression + Gate loop-sustain | Θ-W4-T1 | `pth-w4-t1-gate-loop-sustain` |
|
||
| 10 | Knob/label sizing, ms units, double-click reset | Θ-W6-T1 | `pth-w6-t1-legibility-and-antialiasing` |
|
||
| 11 | Preview glyph; VELOCITY deck; bipolar curves | Θ-W4-T2 | `pth-w4-t2-velocity-deck-and-bipolar-curves` |
|
||
| 12 | Toolbar cleanup; full-width piano strip; tooltips | Θ-W2-T3 | `pth-w2-t3-toolbar-and-piano-strip` |
|
||
| 13 | Antialiased rendering audit for high-DPI | Θ-W6-T1 | `pth-w6-t1-legibility-and-antialiasing` |
|
||
| 14 | Trigger amp/filter fade → AHD consolidation | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
|
||
| 15 | One-click in-sampler resample | Ξ-W1-T2 (note model) **+** Ξ-W2-T1 (bake chain) **+** Ξ-W3-T1 (popup abandoned by ruling; window derives itself) | `pxi-w1-t2-note-program-model`, `pxi-w2-t1-resample-bake-chain`, `pxi-w3-t1-capture-signal-popup` |
|
||
| 16 | Retire the zone mapping system | Θ-W1-T1 | `pth-w1-t1-zone-retirement` |
|
||
| 17 | Consolidate provenance/usage tracking | Ξ-W1-T1 | `pxi-w1-t1-tracking-consolidation` |
|
||
|
||
### Work in this plan that is not one of the seventeen
|
||
|
||
The table above is a completeness proof over `TODO-1.0.md` — every row points at a track,
|
||
so nothing from the source was dropped. It is deliberately **not** an index of the plan:
|
||
work that did not come from the source doc has no row, and inventing one would weaken the
|
||
proof it exists to give.
|
||
|
||
- **Θ-W3-T1 — `live-parameter-delivery`** (`pth-w3-t1-live-parameter-delivery`). Arose
|
||
from Θ-W2-T1's implementation review, not from `TODO-1.0.md`. The first such track in
|
||
this plan; see Θ-W7-T1 below for the second.
|
||
- **Θ-W7-T1 — `arc-and-spline-aa`** (`pth-w7-t1-arc-and-spline-aa`). Opened after
|
||
Θ-W6-T1 shipped, when Daniel found two rendering defects by eye in the editor — not
|
||
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
|
||
table.
|
||
- **All of Phase Γ** (`pg-*`). **Thirteen tracks across four waves** (W1 seven, W2 two, W3
|
||
three, W4 one), from a direct interview with Daniel (2026-08-01) and his four later rulings
|
||
the same day, not from `TODO-1.0.md`. Listed
|
||
here as a block rather than per track, because the whole phase is outside the source doc;
|
||
the product reasoning lives in `docs/product/instrument-control-surface.md` and the
|
||
parameter system's in `docs/product/parameter-automation.md` §§6–10. **Two `docs/TODO.md`
|
||
entries are discharged by this phase, not deferred again:** Γ-W3-T1 discharges the
|
||
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).
|
||
- **All of Phase Ψ** (`ppsi-*`). **Seven tracks across three waves**, from a direct list
|
||
of seven defects and refinements (Daniel, 2026-08-01), not from `TODO-1.0.md`. Listed
|
||
here as a block, like Γ; unlike Γ it has no backing product doc — the seven are
|
||
recorded verbatim in the phase header as its provenance (Ψ.1–Ψ.7). Phase Ψ has since
|
||
landed in full and its `PLAN.md` section is retired; the design content and the
|
||
Ψ.1–Ψ.7 list now live in `docs/COMPLETED.md`. The seventh track, Ψ-W3-T1, is not one of
|
||
the seven defects/refinements itself — it came from a review finding mid-phase; see
|
||
`docs/COMPLETED.md` for detail.
|
||
- **Γ-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.
|
||
- **Γ-W3-T3 `bake-prints-limiter` is also a CORRECTION, not a feature**, and for the same
|
||
reason as T2: it exists only because T2's own finding disproved the premise §3.4's reset
|
||
classification rested on. Unlike the twelve above, it does not come from the 2026-08-01
|
||
interview or that day's four rulings — it is a separate ruling, one day later (2026-08-02),
|
||
opened after T2 landed and found the gap.
|
||
- **All of Phase Ε** (`pe-*`). **Six tracks across three waves**, from a direct request
|
||
(Daniel, 2026-08-02), not from `TODO-1.0.md`. Listed here as a block, like Γ and Ψ; the
|
||
product reasoning lives in `docs/product/bank-package.md`. It **supersedes nothing** —
|
||
neither `docs/TODO.md` nor `docs/TODO-1.0.md` records export, import, or a package format,
|
||
so there is no deferred entry to absorb or contradict. Its three [Daniel]-class forks
|
||
(Ε-F1/F2/F3) were opened and ruled the same day it was framed, so no track here is gated on
|
||
a decision; see "Decision state" above.
|
||
- **All of Phase Ρ** (`pr-*`). **One track in one wave**, from a direct request (Daniel,
|
||
2026-08-02), not from `TODO-1.0.md`; the product reasoning lives in
|
||
`docs/product/render-in-place.md`. It **supersedes nothing** — a sweep of `docs/TODO.md`
|
||
and `docs/TODO-1.0.md` for `render.in.place|render to new track|preserve.source` returns
|
||
nothing. The smallest phase in this plan, deliberately: it is a thin third verb composed
|
||
out of machinery that already exists, and the burden was on any new machinery to prove it
|
||
unavoidable. Its three [Daniel]-class forks (Ρ-F1/F2/F3) were opened and ruled the same
|
||
day it was framed, so the track here is not gated on a decision; see "Decision state"
|
||
above. **Ρ-F2 was the one that moved the spec** — the result track goes to Arrange
|
||
unconditionally rather than following the active mode, which is also the only reason the
|
||
phase touches `panel_input.cpp` at all. **Ρ-W1-T1 has landed**; see `docs/COMPLETED.md`
|
||
for the full narrative.
|
||
- **All of Phase Λ** (`pl-*`). **Thirteen pending tracks across seven waves** (Λ-W2…Λ-W8),
|
||
plus Λ-W1's two audit tracks, which are landed. From a direct request (Daniel, 2026-08-02),
|
||
not from `TODO-1.0.md`; the product reasoning lives in `docs/product/linux-readiness.md`
|
||
and the evidence in the two audit notes it cites. It **supersedes nothing** and **corrects
|
||
one standing promise** rather than inheriting it: `docs/product/versioning-and-release.md`
|
||
commits to three platform artifacts per channel per release, and Λ-D4 (macOS out) makes
|
||
that two for now, with macOS named as deferred. **Λ is the one phase in this plan with
|
||
unanswered [Daniel]-class questions** — four forks, none ruled, of which only Λ-F2 gates a
|
||
dispatch and only Λ-F4 gates a wave; see "Decision state" above. It is also the one phase
|
||
whose acceptance criteria cannot be checked from the current box at all, which is why
|
||
Λ-W3 exists as a wave rather than as a checklist at the end.
|
||
- **All of Phase Ω** (`pom-*`). **Eight tracks across two waves** (W1 five, W2 three), from a
|
||
direct list of seven defects and refinements (Daniel, 2026-08-03), not from `TODO-1.0.md`.
|
||
Listed here as a block, like Γ, Ψ, Ε, Ρ and Λ; **like Ψ it has no backing product doc** — the
|
||
seven are recorded in its phase header as Ω.1…Ω.7, and its six rulings are indexed in the
|
||
table there rather than in `docs/product/`. It **supersedes nothing** but **amends two
|
||
settled statements**, which is what distinguishes it from the other doc-less phase: Ω-F2
|
||
reverses the marker "Disabled rather than hidden" principle in
|
||
`docs/product/instrument-control-surface.md` §6.3(d)/§6.4 for the Trigger case, and Ω-F6
|
||
amends `src/shell/view/CLAUDE.md`'s accepted FX-parking load hitch. Its six [Daniel]-class
|
||
forks were opened and ruled the same day it was framed, so no track here is gated on a
|
||
decision; see "Decision state" above. **Two `docs/TODO.md` entries constrain it without being
|
||
discharged by it:** `reasampler_editor.h`'s ceiling margin (three W1 tracks add state to it,
|
||
and the drag-state seam is explicitly out of scope) and `view.cpp`'s blocked split (Ω-W1-T1
|
||
adds a TU beside it rather than growing it).
|
||
|
||
### Deliberate compressions
|
||
|
||
Recorded so a reader of `TODO-1.0.md` can see what this plan did to the source, rather
|
||
than discovering it later:
|
||
|
||
- **Item 2 is split across two waves.** The DSP port (Θ-W1-T3) is deliberately separated
|
||
from the integration (Θ-W2-T1) so the external-input dependency on Daniel's Cortex-M4
|
||
code sits on a standalone, disjoint track instead of blocking a wave. Item 2's
|
||
acceptance criteria are split accordingly — the range/resonance assertions land in
|
||
W1-T3's tests, the audible/pipeline/deck criteria in W2-T1.
|
||
- **Item 2's filter envelope ships twice.** W2-T1 ships it in the existing staged AHDSR
|
||
shape; W3-T2 gives it the curve treatment and the mode-driven Gate→AHDSR /
|
||
Trigger→AHD shape. This is deliberate: waiting would put the filter behind the whole
|
||
envelope system.
|
||
- **Item 4 lands in Θ-W3, not Θ-W1.** Its fix region is the region item 14 retires, and
|
||
the only earlier instrument-side track owns the entire engine. Its acceptance gate is
|
||
stated as the post-consolidation gate. It is not gated behind the *whole* editor chain
|
||
— three waves of six — and the source doc itself requires re-verification under the
|
||
surviving mechanism either way.
|
||
- **Item 11's filter velocity curve is split from item 2's filter velocity
|
||
modulation.** W2-T1 wires the modulation path (following the amp/pitch precedents);
|
||
W4-T2 sets the curve's bipolar domain and default and homes its button. Neither track
|
||
can do the other's half.
|
||
- **Item 15's undo/recovery is carried as a note, not a criterion** — Daniel set it at
|
||
exactly "a plus." The guaranteed recovery floor (the superseded file surviving until
|
||
prune) is a criterion.
|
||
- **Nothing else was compressed.** Every other item's behavior bullets and acceptance
|
||
criteria are carried at full strength into the track that owns it.
|
||
|
||
---
|
||
|
||
## Outline at a glance
|
||
|
||
```
|
||
Phase Θ — ReaSampler 9000: one parameter set, filter, shapeable envelopes, legible editor
|
||
[LANDED — all seven waves (W1-W7); see docs/COMPLETED.md; full detail section removed from this file]
|
||
W1 Collapse and re-seam
|
||
T1 zone-retirement ......................... 16
|
||
T2 capture-handoff-bugs .................... 5, 6
|
||
T3 filter-dsp-port ......................... 2 (DSP) [needs Daniel's M4 code]
|
||
W2 Filter in the voice path; waveform and chrome bands
|
||
T1 filter-voice-path ....................... 2 (integration)
|
||
T2 stereo-waveform-lanes ................... 7
|
||
T3 toolbar-and-piano-strip ................. 12
|
||
W3 Live parameters, then the staged envelope system [T1 before T2 — serial]
|
||
T1 live-parameter-delivery ................. (not one of the seventeen)
|
||
T2 staged-envelope-curves .................. 1, 8, 14, 4
|
||
W4 Loop sustain and the velocity deck
|
||
T1 gate-loop-sustain ....................... 9
|
||
T2 velocity-deck-and-bipolar-curves ........ 11
|
||
W5 Spline EGs
|
||
T1 spline-egs .............................. 3
|
||
W6 Editor legibility pass
|
||
T1 legibility-and-antialiasing ............. 10, 13
|
||
W7 Arc-and-spline antialiasing fix
|
||
T1 arc-and-spline-aa ....................... (not one of the seventeen)
|
||
|
||
Phase Ξ — The resample loop (W1 concurrency-safe with Θ from Θ-W2 onward)
|
||
[LANDED — all three waves (W1-W3); see docs/COMPLETED.md; full detail section removed from this file]
|
||
W1 Consolidated tracking, and the programmed-note model
|
||
T1 tracking-consolidation .................. 17
|
||
T2 note-program-model ...................... 15 (model)
|
||
W2 The bake chain [ran AHEAD of Γ; its reset list is corrected by Γ-W3-T2]
|
||
T1 resample-bake-chain ..................... 15 (chain)
|
||
W3 The capture-signal popup [popup abandoned by ruling; bake window derives itself]
|
||
T1 capture-signal-popup .................... 15 (popup abandoned; window derives)
|
||
|
||
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]
|
||
T1 knob-interaction-law ....... modifiers + ONE taper module + reset bypass
|
||
+ 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
|
||
T4 editor-floor-and-row-law ... floor 1190x680 (W3-T1: 1198) + budget constants + row predicate
|
||
T5 preserve-time-stretch ...... real stretcher [measure-and-report gate]
|
||
T6 exhaustive-switch gate on pure libraries ... /we4062, -Werror=switch on
|
||
pure libraries [no PLAN entry — see COMPLETED.md]
|
||
T7 psola-preserve ............. PSOLA-aligned splice jump; period_detect
|
||
[cadence closure — see COMPLETED.md]
|
||
W2 New controls, and the overlay's marks — landed [2 tracks]
|
||
T1 pitch-rate-deck ............ Rate + Pitch, Varisp/Presrv compounding [rung 2]
|
||
T2 loop-crossfade-ux .......... four-mark grammar; fade painted where it is heard
|
||
W3 The reflow, and the bake correction — landed [3 tracks]
|
||
T1 deck-reflow ................ two rows + double-height MASTER, by construction
|
||
T2 bake-reset-amendment ....... the Xi correction Gamma owns
|
||
T3 bake-prints-limiter ........ landed — bake prints the limiter through
|
||
the master stage
|
||
W4 VST3 parameters — landed [1 track]
|
||
T1 vst3-parameter-set ......... 44 derived params, frozen id table [Ruling 1]
|
||
[rung 3 RESERVED, unspent]
|
||
[Gamma-F7 RULED: signal flow order]
|
||
|
||
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
|
||
Ruling 1 added W4 and Ruling 2 grew W1-T1.
|
||
Payload rungs are RELATIVE, not absolute. Both are spent — kParamsPayloadVersion == 15
|
||
(Γ-W1-T2, landed, above) and v16 (Γ-W2-T1, stated there) — and the third remains RESERVED
|
||
as v17, unspent: Γ-W4-T1's storage verification did not force it.
|
||
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, once a CONDITIONAL risk to confirm
|
||
against what Xi-W2-T1 shipped, is resolved: both tracks landed — see docs/COMPLETED.md.
|
||
|
||
Phase Psi — The extension trust pass (none of the seventeen; a direct list of seven)
|
||
[LANDED — all three waves (W1-W3); see docs/COMPLETED.md; full detail section removed from this file]
|
||
W1 Exact bounds, disciplined switches, reachable actions, resolved drops [4 tracks]
|
||
T1 capture-range-exactness ..... Psi.7 [opens with a DAW repro matrix]
|
||
T2 mode-switch-discipline ...... Psi.2 + Psi.3 [one chokepoint: applyMode]
|
||
T3 media-explorer-section ...... Psi.4 [BOTH sections; new FOREVER-STABLE id]
|
||
T4 drop-target-resolution ...... Psi.5 [gesture law, not a patch]
|
||
W2 Names and channels, over the settled render block [gated on W1-T1's render block]
|
||
T1 capture-naming .............. Psi.1 [+ the card shows the name]
|
||
T2 mono-collapse ............... Psi.6 [lossless only; ingest excluded]
|
||
|
||
Three invariant amendments are track deliverables: never-touch-solo (W1-T2, three
|
||
files), the action-registration contract (W1-T3, root CLAUDE.md), channel-count-
|
||
preserved (W2-T2, root CLAUDE.md:215).
|
||
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
|
||
lines) — all textual adjacency, not semantic contention. main.cpp is W1-T3's
|
||
exclusively.
|
||
|
||
Phase Epsilon — The bank package (none of the seventeen; a direct request 2026-08-02)
|
||
[LANDED — all three waves (W1-W3); see docs/COMPLETED.md; full detail section removed from this file]
|
||
W1 The contract, the filesystem, and the ledger's new kind [3 tracks, disjoint by dir]
|
||
T1 package-format .............. core/package: framing + TWO version ints
|
||
[E-F1 RULED: proprietary RSBK. No ZIP, no zlib]
|
||
T2 package-fs-shell ............ shell/package: atomic write, streaming, pickers
|
||
[ONE picker: REAPER GetUserFileName, mode 0/1]
|
||
T3 import-origin-kind .......... OriginKind::PackageImport = 5, append-only
|
||
W2 The two verbs [2 tracks; disjointness CONDITIONAL — see below]
|
||
T1 bank-export ................. export_plan + verb + action; project untouched
|
||
T2 bank-import ................. import_plan + verb + action + .rsbank drop
|
||
[E-F2 RULED: always a NEW bank, auto-suffix, no
|
||
merge. E-F3 RULED: REFUSE on degraded ledger]
|
||
W3 The compatibility fixtures [1 track]
|
||
T1 package-compat-fixtures ..... frozen bytes prove BOTH version directions
|
||
|
||
Version policy, both directions: older package in newer build ALWAYS imports (additive
|
||
fields default, unknown keys skipped); newer package in older build REFUSES WHOLE with a
|
||
three-part message (package needs / this build reads / writer semver). The gate is
|
||
minReaderVersion <= kPackageFormatVersion — formatVersion is for the message, not the gate.
|
||
All three E-forks were ruled 2026-08-02, the day the phase was framed: NO track here is
|
||
gated on a decision. Import name collision = first free of seed, "seed 2", "seed 3", ...
|
||
seed never re-parsed, fold is BankBook's own.
|
||
Shared files, named: root CMakeLists.txt add_subdirectory list (W1-T1 | W1-T2, one
|
||
append-only line each); main.cpp + the panel bank menu (W2-T1 | W2-T2, one registration
|
||
line and one menu row each); bank_book.{h,cpp} is W2-T2's alone (one additive public const
|
||
member). W1's three tracks are unconditionally disjoint; W2's two are
|
||
textually adjacent only — serialize T2 behind T1 if zero contention is wanted.
|
||
|
||
Phase Rho — Render in place (none of the seventeen; a direct request 2026-08-02)
|
||
[LANDED — Ρ-W1-T1; see docs/COMPLETED.md; full detail section removed from this file]
|
||
W1 The verb [ONE track, deliberately]
|
||
T1 render-in-place ............. render selected track -> new sibling track,
|
||
item placed at the exact render position,
|
||
colour + "Capture " name cloned, source -> Design,
|
||
result track -> ARRANGE always. Never the bank.
|
||
[R-F1 RULED: refuse multi-track, settled non-goal.
|
||
R-F2 RULED: result track ALWAYS Arrange, never
|
||
mode-following. R-F3 RULED: follow panel tail]
|
||
|
||
The THIRD VERB. Capture = arrange -> bank (writes it). Placement = bank -> arrange
|
||
(reads it). Render in place = arrange -> arrange (bank on neither side). That is why the
|
||
load-bearing capture/placement rule survives it; the full argument is in the phase header
|
||
and in docs/product/render-in-place.md.
|
||
Structural boundary, not a convention: the render destination reaches the backend as a
|
||
TWO-VALUED ENUM (Bank | ProjectMedia), never a caller-supplied path — so no Rho caller can
|
||
name the bank folder. A renderDir string on CaptureRequest IS the drift.
|
||
Genuinely new: ONE pure sibling-placement function (folder-parent and last-in-folder are
|
||
both audibly wrong if inserted at srcIdx+1), an idempotent "Capture " prefix, a
|
||
deriveRenderPaths sibling, and CaptureResult::absolutePath. Everything else composes.
|
||
R-F2 fallout, the ruling's only cost: the result track AND its item are tagged
|
||
kArrangeModeId explicitly, and detectNewContent must drop added GUIDs that already carry
|
||
a membership record — the auto-tag detector otherwise re-tags a Design-fired result to
|
||
Design on its next tick and reverses the ruling. Firing from Design is therefore
|
||
deliberately invisible: the render waits in Arrange. No A/B-on-the-bench behaviour exists.
|
||
Four invariant amendments are track deliverables (the Psi precedent): shell/capture and
|
||
shell/actions CLAUDE.md placing-path claims, ONE sentence in root CLAUDE.md naming
|
||
the third verb without softening the prohibition, and core/view CLAUDE.md's "new tracks
|
||
are tagged to the active mode" made conditional on carrying no membership record.
|
||
Shared files, named: capture.{h,cpp} (destination enum + result field), main.cpp (one
|
||
ActionTableRow), and panel_input.cpp (detectNewContent only; Psi's two named regions in
|
||
that file are landed and are other functions) — the only pre-existing shell files
|
||
touched. Disjoint from Gamma (core+shell/instrument) and Epsilon (core+shell/package).
|
||
|
||
Phase Omega — Responsiveness + control surface (none of the seventeen; a direct list of seven)
|
||
W1 The five defects, in parallel [5 tracks, file-disjoint]
|
||
T1 mode-switch-responsiveness .. Omega.7 structural: undo mask (-1 = UNDO_STATE_ALL,
|
||
pulls every FX chunk), unconditional SetOffline,
|
||
PreventUIRefresh (used NOWHERE in src today),
|
||
compare-before-write, resolve() map, and the
|
||
F6 split: UI/routing sync, per-FX park DEFERRED.
|
||
New TU beside view.cpp (642 lines, over ceiling).
|
||
T2 mode-switch-persist ......... Omega.7 persist: a switch rewrites all SEVEN
|
||
ext-state keys when only view_state moved.
|
||
T3 meter-rate .................. Omega.1: ONE 500ms timer today -> second timer id
|
||
for the meter; 500ms poll body UNCHANGED.
|
||
Paint: dirty rect, hoisted bitmap, hero-envelope
|
||
cache. 60 FPS target, 30 the measured fallback.
|
||
T4 deck-reflow-focus-toggles ... Omega.2 + Omega.3 + Omega.4. Filter Mod -> FILTER
|
||
ENV last-in-run (id 1240 FROZEN, annotate only);
|
||
run CENTRED at kDeckCellW; single buttons in two
|
||
variants (enable = AccentPrimary|dim gray, mode
|
||
selector = reads current mode, no off state);
|
||
whole env deck = overlay target, radios gone but
|
||
DeckRadioDesc survives for MASTER's passive lamp.
|
||
[F3 + F4 RULED. Gate face stays PIXEL-IDENTICAL.
|
||
COST: the filter tie-line is unreachable after
|
||
the move — stated, not discovered]
|
||
T5 overlay-mapping ............. Omega.5 + Omega.6. Trigger = marks HIDDEN; Gate
|
||
loop-off = GREY DISABLED and still grabbable;
|
||
GATE ONLY caption gone, the other two survive.
|
||
ONE mapping for both chains — four discrepancies
|
||
(2px inset, round vs truncate, closed vs half-open,
|
||
half-pixel phase). Overlay-inset route recommended;
|
||
draw_kit route is [propose]. Zero-crossing snap
|
||
behaviour-identical. Fixtures RE-DERIVED.
|
||
[F1 + F2 RULED]
|
||
W2 Measurement, and what only a DAW can say [3 tracks]
|
||
T1 view-topology-cache ......... core/view memoization [GATED on W1-T1's measurement;
|
||
"not worth taking" is a legitimate outcome]
|
||
T2 content-detect-throttle ..... the 30/sec enumerate, throttled not weakened
|
||
T3 omega-verification-sweep .... the deliverable is a RECORD, not a fix
|
||
|
||
Small tidy work sequenced BEFORE Lambda, and deliberately not another Lambda: no format,
|
||
no wire version, no parameter id, no new artifact. All six D-rulings SETTLED 2026-08-03
|
||
(F1 widen the mapping scope; F2 hidden in Trigger / grey-disabled in Gate; F3 single
|
||
button extends to Filter, Loop, Mono|Stereo; F4 AccentPrimary not AccentHot; F5 both
|
||
entry surfaces; F6 separate UI from processing). ZERO open forks.
|
||
Two invariant amendments are track deliverables: instrument-control-surface.md 6.3(d)
|
||
+ 6.4 (T5) and shell/view/CLAUDE.md's FX-parking load hitch (T1).
|
||
The solo surface index is EXONERATED (~2T calls, sub-ms) — do not re-litigate it.
|
||
reasampler_editor.h has ~36 lines of ceiling margin and THREE W1 tracks add state to it;
|
||
the drag-state seam TODO.md names is OUT OF SCOPE — a track over the ceiling stops and
|
||
flags. Shared files, named: reasampler_editor.h (T3|T4), editor_paint_waveform.cpp
|
||
(T3 envelope cache | T5 marks+mapping), editor_controls.cpp (T4 deckDescs | T5
|
||
waveMarksFor), app/CMakeLists.txt + main.cpp (T1, cross-phase with Lambda).
|
||
|
||
Phase Lambda — ReaSampler on Linux (none of the seventeen; a direct request 2026-08-02)
|
||
W1 The audits [COMPLETE]
|
||
T1 build-toolchain-audit ....... L-01..L-10, V1..V10, D1..D7
|
||
T2 source-runtime-audit ........ L2-01..L2-12
|
||
|
||
W2 Make it buildable, and make it honest [4 tracks, file-disjoint]
|
||
T1 linux-compile-blockers ...... L2-01, L2-02, L2-03 -> V1, V2
|
||
T2 toolchain-floor ............. L-02..L-05, L-07, L-09, L-10 -> V5, V6, V8
|
||
T3 panel-dialog-resource ....... L-01, L2-06, L2-07 -> V7 [GATED: L-F2]
|
||
T4 locale-independent-numerics . L2-04 [the one track verifiable on Windows]
|
||
W3 First light, and the verification sweep [1 track, deliberately]
|
||
T1 linux-verification-sweep .... V1..V10 + T2 section 5's seven; the deliverable
|
||
is a RECORD, not a fix. V1 / V9 / the API-load
|
||
count run FIRST — each reprices what follows.
|
||
W4 Correctness and safety, repriced [3 tracks, disjoint by dir]
|
||
T1 prune-deletion-safety ....... L2-05 both halves + the symlink reclaim lie
|
||
T2 linux-font-faces ............ L2-09
|
||
T3 source-partition-and-invariants . L-06, L2-10, L2-11 (Linux half)
|
||
W5 Ship the extension [1 track; GATED: L-F4]
|
||
T1 linux-packaging-and-install . L-08 + L-02's shipped half + both channels
|
||
W6 The instrument module + the host-safety contract [T1 before T2 — serial]
|
||
T1 vst-linux-module ............ B5 (the D5 reversal, FIRST), B1, B2
|
||
T2 vst-host-safety-contract .... D3's observable contract; passes with ZERO params
|
||
W7 The SWELL bootstrap [1 track]
|
||
T1 swell-dylib-bootstrap ....... B3 via route B3a, WITHOUT the stub's exit(2)
|
||
[prereq: V9 affirmative, else L-D2 re-rules]
|
||
W8 The X11 editor [1 track]
|
||
T1 x11-embed-view .............. B4, L2-08. The phase's one L-band source item.
|
||
|
||
NOTHING here has been verified on a Linux machine. W2's edits are authored BLIND from the
|
||
audits' citations and their acceptance criteria are discharged in W3 — W2 is not "done"
|
||
until W3 runs, and this plan says so rather than pretending otherwise.
|
||
W6 may run concurrently with W4 and W5 — file-disjoint; the only reason to serialise is
|
||
attention. Six D-rulings are SETTLED (instrument in scope; SWELL via dlopen of REAPER's
|
||
libSwell.so; REAPER-only editor but other hosts must degrade SAFELY; macOS out; shipped
|
||
not developer-only; hard unlink acceptable with a platform-aware confirmation).
|
||
OPEN, none ruled: L-F1 (CI — gates nothing), L-F2 (dialog-resource route — the ONLY fork
|
||
gating a dispatch, W2-T3), L-F3 (copy-only invariant wording — gates nothing),
|
||
L-F4 (declared support floor — gates W5, not a dispatch).
|
||
Lambda registers NO VST3 parameter, ever — that table is Gamma-W4-T1's and is frozen.
|
||
```
|