734672ba0e
Product doc for the extension + ReaSampler 9000 on Linux, downstream of the two Λ-W1 audits: Daniel's six rulings, seven waves, the non-REAPER-host safety contract, and four open forks.
1304 lines
79 KiB
Markdown
1304 lines
79 KiB
Markdown
# Linux readiness — product notes
|
||
|
||
Framing, rationale, and design-direction calls behind **Phase Λ — ReaSampler on Linux:
|
||
the `reaper_reasampler` extension and the ReaSampler 9000 VST3 instrument, both
|
||
shipped.** The tickable spec belongs in `docs/PLAN.md` (§Phase Λ); the architecture
|
||
detail belongs in the affected `src/**/CLAUDE.md` files once tracks land. This doc holds
|
||
the *why* — what "Linux-ready" is defined to mean here, what the tree's actual posture
|
||
is, the six decisions Daniel settled on 2026-08-02, the sequencing argument, and the four
|
||
forks still open.
|
||
|
||
**This doc is downstream of two audits and does not repeat them.**
|
||
`docs/product/audit-notes/lambda-w1-t1-build-toolchain.md` (findings Λ-01…Λ-10, verify
|
||
items V1…V10) owns the build system, toolchain, vendored dependencies, resources, test
|
||
harness and packaging. `docs/product/audit-notes/lambda-w1-t2-source-runtime.md` (findings
|
||
L2-01…L2-12) owns source portability and runtime behaviour. Both were code-reviewed against
|
||
the tree and remediated in place. **Everything below cites them by finding number rather
|
||
than re-deriving.** Grep the audits for a cited number; do not read either whole.
|
||
|
||
Status: framed by product-designer (2026-08-02) after Daniel's rulings the same day.
|
||
**Four forks are open** — Λ-F1 (CI), Λ-F2 (the dialog-resource route), Λ-F3 (the
|
||
copy-only drag-out invariant), Λ-F4 (the declared support floor) — each stated with the
|
||
evidence for both sides in §"Open forks". Λ-F2 gates one track and nothing else;
|
||
the other three gate nothing. Everything not marked as a fork is a product-designer call
|
||
with its reasoning stated; contradict it in review with an argument, not a preference.
|
||
|
||
**Slug convention.** `Λ → l`, so tracks dispatch into `pl-w<wave>-t<track>-<slug>` —
|
||
Λ-W2-T1 into `pl-w2-t1-linux-compile-blockers`. The two audits already ran under
|
||
`pl-w1-t1-build-toolchain-audit` and `pl-w1-t2-source-runtime-audit`. **`docs/PLAN.md:33–36`
|
||
lists the transliterations for Θ / Ξ / Γ / Ψ / Ε / Ρ and does not yet carry Λ — adding it
|
||
is a plan-doc edit this phase's PLAN.md entry must make.**
|
||
|
||
---
|
||
|
||
## Why now, and what "Linux-ready" means here
|
||
|
||
The reason to do this now is not demand — it is that **the cost is already sunk and the
|
||
window for it to stay sunk is closing.** Two facts from the audits:
|
||
|
||
- `docs/product/versioning-and-release.md` already commits, in writing, to
|
||
"three platform artifacts per channel per release" (T1 §1e). Linux is not a new promise;
|
||
it is an unredeemed one.
|
||
- The panel layer — the product's primary surface — was written against SWELL's
|
||
vocabulary throughout and is portable **call-for-call**, verified name-by-name against
|
||
the vendored headers (T2 §1.5, §"Surfaces checked"). That is an asset the tree acquired
|
||
by discipline, not by luck, and it decays every time someone writes a Win32 call without
|
||
a `#else`. Phase Ψ, Phase Ε and Phase Ρ are all adding extension-side surface right now.
|
||
Compiling on Linux once converts that discipline from a convention nobody can check into
|
||
a build that fails when it is broken.
|
||
|
||
**What "Linux-ready" is defined to mean in this phase.** A Linux REAPER user installs
|
||
`reaper_reasampler.so` and `reasampler_9000.vst3` and gets the same product a Windows user
|
||
gets, with four named exceptions:
|
||
|
||
1. **Prune deletes permanently.** No Recycle Bin, no trash. The confirmation says so.
|
||
(Λ-D5.)
|
||
2. **The OS drag-out's copy-only guarantee is conventional, not structural.** 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-10). Λ-F3 decides how the invariant is worded.
|
||
3. **Fonts are not Segoe UI and Consolas.** The kit gets an explicit Linux face list
|
||
(see §"Calls made here"); the faces are different, the layout is not.
|
||
4. **The ReaSampler 9000 editor comes up under REAPER only.** In any other Linux host the
|
||
plugin loads, scans, instantiates, and processes audio — and shows the host's generic
|
||
parameter UI instead of its own editor. This is a *contract*, not a shortfall; §"The
|
||
non-REAPER-host safety criterion" specifies it as an observable.
|
||
|
||
**What a Linux user explicitly does not get:** an XDG-trash prune, a macOS build, the
|
||
TCP/MCP embed strip, or an editor in Bitwig/Ardour. §"What Phase Λ does not do" states
|
||
each with its reason.
|
||
|
||
---
|
||
|
||
## The posture as found
|
||
|
||
Compressed from the two audits. Each claim below is theirs; the citations are the ones
|
||
this doc re-read against the tree while writing.
|
||
|
||
**The extension is close — two one-line compile blockers stand between the tree and a
|
||
GCC/Clang build.**
|
||
|
||
- `src/shell/panel/draw_kit.cpp:73` passes `DEFAULT_PITCH | FF_DONTCARE` to `CreateFont`.
|
||
`FF_DONTCARE` has zero occurrences anywhere in `vendor/WDL/` (L2-01); the file is not
|
||
platform-guarded, only its include is (`:70–77`). `draw_kit` links into both modules, so
|
||
nothing builds.
|
||
- `src/shell/actions/instrument_drop_win.cpp:59` calls `GetCurrentProcessId()` inside
|
||
`writeTempPreset` with no platform branch anywhere in the TU. SWELL exports
|
||
`GetCurrentThreadId` and not this (L2-02). The PID exists only to keep two concurrent
|
||
REAPER instances from colliding in the shared temp dir; the atomic counter at `:52`
|
||
already carries the intra-process half.
|
||
|
||
**`core/` is genuinely pure, and it was verified rather than assumed.** Every `#include`
|
||
under `src/core/**` is a `core/` sibling, one of 26 standard headers, or the generated
|
||
`version_generated.h` — zero REAPER, SWELL, WDL, LICE, VST3 or `windows.h` (T2 §1.1). The
|
||
whole directory contains nine preprocessor conditional lines, exactly one of which is a
|
||
platform fork, and that one (`capture_paths.cpp:18–20`, the Windows case-fold) is *correct*
|
||
for Linux with both branches already asserted by `tests/test_capture_paths.cpp`. All 91
|
||
test TUs under `tests/` are platform-neutral.
|
||
|
||
**The panel layer is already SWELL-portable, call for call.** Dialog lifecycle, docking,
|
||
double-buffered LICE `WM_PAINT`, mouse/wheel/capture, `WM_CAPTURECHANGED` rollback, all
|
||
seven cursors, menus, the keyboard accelerator path, modifier reads, tooltips, drag-out and
|
||
`DragQueryFile`/`DragFinish` were each checked by name against `swell-functions.h` /
|
||
`swell-types.h` and are present (T2 §1.5). The Windows-only escapes are three:
|
||
`DragAcceptFiles` (`panel_window.cpp:148–150`, `#ifdef _WIN32`), `SHFileOperationW`
|
||
(prune), and OLE `DoDragDrop` (drag-out) — each already carrying a non-Windows branch or a
|
||
documented reason it does not. **This is the audits' single most load-bearing finding.**
|
||
|
||
**The dock panel will not appear until the dialog-resource question is answered.**
|
||
`panel_window.cpp:135` 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:97` has the `target_sources`
|
||
line commented out (and `:86` for macOS). The registry head stays null, `SWELL_CreateDialog`
|
||
returns null, `panel_window.cpp:137` returns, and the toggle action is a silent no-op with
|
||
no console line and no Actions-list checkmark (Λ-01, L2-06). Three defects stack in the
|
||
commented-out instructions themselves: the script named at `:96` (`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 (Λ-01). Λ-F2 decides the route.
|
||
|
||
**The instrument's Linux editor is a from-scratch X11 job, and today the target does not
|
||
configure at all.** `src/shell/instrument/CMakeLists.txt:9` is
|
||
`if(WIN32 AND EXISTS "${VST3_SDK}/…/pluginfactory.cpp")` — a conjunction, so a Linux
|
||
configure silently omits `reasampler_vst` even with the submodule slice fully initialised.
|
||
Beyond the gate: the wrong module entry point is compiled, the artifact is a file where
|
||
Linux wants a directory bundle, nothing hands a VST3 plugin the SWELL function table, and
|
||
the view reports `kPlatformTypeHWND` only (B1–B4, L2-08). The paint side is the cheapest
|
||
part — every painter already draws into a `LICE_IBitmap` through the shared kit, so the
|
||
drawing survives a window-system change intact. It is the window and event plumbing that
|
||
is entirely absent.
|
||
|
||
**Nothing about the build is optimized, and the documented ship command is a no-op on
|
||
Linux.** Root `CMakeLists.txt:28–30` is the complete list of language settings — there is
|
||
no `CMAKE_BUILD_TYPE`, no `CMAKE_CXX_FLAGS`, no IPO/LTO, and no `target_compile_options`
|
||
anywhere in the tree. `--config Release` is accepted and ignored by Ninja and Make, so the
|
||
README's ship incantation produces a binary with no `-O` flag at all, on a tree whose
|
||
`peaks` hot path is documented as presuming an optimizing build (Λ-02). There is also no
|
||
symbol-visibility control (Λ-03), no thread library linked (Λ-04), no warning flags
|
||
(Λ-09), no `install()` rule anywhere, and no CI configuration in the repo on any platform
|
||
(T1 §1e).
|
||
|
||
---
|
||
|
||
## Settled decisions (Daniel, 2026-08-02)
|
||
|
||
Six rulings. They are settled; do not re-litigate them. Each is recorded with the reasoning
|
||
given, because the reasoning is what a later reader will need.
|
||
|
||
### Λ-D1 — The VST3 instrument is in scope. (Audit fork D1 = **Fork B**.)
|
||
|
||
Phase Λ ships both artifacts on Linux, not just the extension.
|
||
|
||
**Why.** The instrument is not a standalone product on any platform. The capture browser
|
||
reads the extension's bank over the bridge, and the resample bake invokes an extension
|
||
action synchronously over the VST3 host bridge — root `CLAUDE.md` §"The resample bake"
|
||
makes the crossing structural. **A non-REAPER Linux host was never getting the full
|
||
instrument regardless**, because the two features that make it ReaSampler rather than a
|
||
generic sampler both tie into the extension directly. So the marginal thing Λ-D1 buys is
|
||
precisely a REAPER-hosted Linux instrument — which is the only complete one that exists on
|
||
any platform.
|
||
|
||
**What it costs.** T1 §4 states the asymmetry plainly: Fork A's build work is small and its
|
||
unknown is source portability; Fork B's build work is genuinely large *and* carries an
|
||
unbounded source tail *and* requires reversing a recorded decision. They are not "the same
|
||
phase, one bigger" — they are different-shaped commitments, and this doc's wave structure
|
||
keeps them separable (Λ-W6…Λ-W8 can be halted without unshipping Λ-W2…Λ-W5).
|
||
|
||
**Consequence — D5 is reversed, in writing.** "No cross-platform… Windows-only, VST3-only,
|
||
REAPER-only (D5)" is a recorded invariant in three files: `src/core/instrument/CLAUDE.md`
|
||
(twice — Invariants and Non-goals), `src/shell/instrument/CLAUDE.md` (Non-goals), and
|
||
`src/shell/panel/CLAUDE.md` ("Windows-only (D5) — font/GDI/HFONT choices assume Windows; no
|
||
cross-platform font-fallback concern"). D5's *other* two clauses survive untouched: still
|
||
VST3-only, still REAPER-only. **Only the platform clause moves**, and it moves to
|
||
"Windows and Linux; the editor is REAPER-hosted." Rewriting those three passages is
|
||
audit finding B5 and is a named deliverable of Λ-W6-T1.
|
||
|
||
### Λ-D2 — The instrument reaches SWELL by `dlopen`ing REAPER's own `libSwell.so`. (D2 = **route B3a**.)
|
||
|
||
`vendor/WDL/WDL/swell/swell-modstub-generic.cpp:69–131` is the vendored implementation:
|
||
resolve the host executable's directory from `/proc/self/exe` (falling back to `dladdr`),
|
||
`dlopen` `libSwell.so` beside it, `dlsym` `SWELLAPI_GetFunc`, call `SWELL_set_app_main`,
|
||
populate the API table.
|
||
|
||
**Route B3b is rejected.** Building SWELL into the module means the 14 SWELL TUs plus 8
|
||
LICE TUs from SWELL's own Makefile and a wholly new third-party dependency
|
||
surface — GDK/GTK3, FreeType, Fontconfig, OpenGL — that the extension deliberately avoids
|
||
via `SWELL_PROVIDED_BY_APP`. That is a scope expansion in a phase whose entire cost
|
||
advantage comes from not having one.
|
||
|
||
**The consequence Daniel accepted:** B3a makes the instrument's UI load-bearing on being
|
||
hosted by REAPER specifically. That is consistent with D5's surviving REAPER-only clause
|
||
and with Λ-D1's reasoning above. **It is also why Λ-D3 exists** — the coupling has to be
|
||
safe, not merely intended.
|
||
|
||
**Important: the vendored stub cannot be used as-is.** See §"The non-REAPER-host safety
|
||
criterion" — `swell-modstub-generic.cpp:102` calls `exit(2)` on a failed `dlopen`, from a
|
||
static constructor. Route B3a is the ruled route; *how* it is implemented is specified
|
||
there and is not a re-litigation of this ruling.
|
||
|
||
### Λ-D3 — Non-REAPER hosts must be protected. First-class acceptance criterion.
|
||
|
||
Daniel, verbatim: *"REAPER-only is good for now, but we need to protext other hosts."*
|
||
|
||
A working editor on Linux is required under REAPER only. But the plugin must **degrade
|
||
safely** in Bitwig, Ardour, or any other Linux host: no crash, no host takedown, defined
|
||
behaviour when `libSwell.so` is absent or unusable. The audits flagged the absent-`libSwell`
|
||
failure mode as unverified; this phase must both determine it and hard-guard it. It is
|
||
specified as an observable contract in its own section below, and it is an acceptance
|
||
criterion of two tracks, not a footnote on one.
|
||
|
||
### Λ-D4 — macOS is out. (D3 = **out**.)
|
||
|
||
Daniel, verbatim: *"I don't have a mac to compile on."*
|
||
|
||
Λ-01, Λ-03, Λ-04, Λ-07, Λ-09 and L2-09 are all marked *shared macOS/Linux* in the audits,
|
||
meaning the same edit covers both. **Those edits still get made in their shared form and
|
||
noted as shared** — a `#ifdef _WIN32` / `#else` that is right for both costs nothing extra
|
||
and does not require a mac. What is out is: macOS as a phase deliverable, any macOS
|
||
verification, the `swell-modstub.mm`-under-a-CXX-only-`project()` question (root
|
||
`CMakeLists.txt:26` is `LANGUAGES CXX`), signing and notarization, and the macOS-only
|
||
half of L2-11 (`normalizeSlashes` under-folds on case-insensitive APFS — a real pre-existing
|
||
defect this phase surfaces and does not own).
|
||
|
||
**Do not "fix macOS while you're in there."** An unverifiable edit to the APPLE branch is
|
||
worse than no edit, because it looks tested.
|
||
|
||
### Λ-D5 — The Linux artifact is shipped, not developer-only. (D6 = **shipped**.) Hard `unlink` is acceptable. (T2 [Daniel] 2 = **yes**.)
|
||
|
||
Two rulings that travel together, because both are about what "shipped" obliges.
|
||
|
||
**Shipped** means Λ-02 (an actual Release/optimized build) and Λ-08 (a documented install
|
||
path and artifact name) are **must-fix, not nice-to-have**, and that a Linux `install()`
|
||
rule is worth authoring. A developer-only artifact could have skipped all of it.
|
||
|
||
**Hard `unlink` is acceptable**, with a **platform-aware confirmation string**. XDG trash
|
||
is not a ship blocker. The user must be told, at the confirm, that the deletion is
|
||
permanent on this platform. Note that L2-05 has a **second half the ruling does not
|
||
cover**: on Windows a bank file held open by REAPER's audio engine cannot be deleted and is
|
||
counted as "skipped"; on Linux `unlink` on an open file succeeds, the audio keeps playing
|
||
from the open fd, and the bytes are gone when it closes. That is not a trash question — it
|
||
is the disappearance of the recovery floor root `CLAUDE.md` §"The resample bake" relies on
|
||
("the superseded file survives on disk until a prune reclaims it"). Λ-W4-T1 owns it.
|
||
|
||
### Λ-D6 — Fork B's editor work is real work, and it is sequenced last.
|
||
|
||
Not a separate Daniel ruling but the direct consequence of Λ-D1 + Λ-D2 + Λ-D3: the X11
|
||
editor is the phase's only L-band source item (L2-08; T1 B4 costs it "build S, source L")
|
||
and it is the last thing sequenced. Everything before it ships something. If Λ-W8 is
|
||
halted, Linux users still have a shipped extension (Λ-W5) and a loading, processing,
|
||
generic-UI instrument (Λ-W6) — which is a real product, not a stub.
|
||
|
||
---
|
||
|
||
## The non-REAPER-host safety criterion
|
||
|
||
Λ-D3 stated as something a person can check. This section is the specification the two
|
||
instrument waves are graded against.
|
||
|
||
### The finding that makes this urgent
|
||
|
||
The audits flagged the absent-`libSwell.so` failure mode as unverified. **It is verifiable
|
||
from the vendored source, and the answer is bad.** Reading
|
||
`vendor/WDL/WDL/swell/swell-modstub-generic.cpp`:
|
||
|
||
- `:125` declares `SwellAPPInitializer m_swell_appAPIinit;` — a **file-scope static
|
||
object**, so its constructor runs when our `.so` is `dlopen`ed, i.e. **during the host's
|
||
plugin scan**, before any VST3 code of ours executes.
|
||
- `:98–103` — `dlopen(fn, RTLD_LAZY)`; if it returns null, the stub prints to stdout and
|
||
calls **`exit(2)`**.
|
||
- `:117` — if `doinit` reports any unresolved SWELL name, **`exit(1)`**.
|
||
|
||
So compiling the vendored `SWELL_LOAD_SWELL_DYLIB` path unmodified means: **a Bitwig or
|
||
Ardour plugin scan on a machine with no `libSwell.so` beside the host binary terminates the
|
||
host process.** Not a failed scan — a killed DAW, mid-scan, with the user's session. That is
|
||
precisely the host takedown Λ-D3 forbids, and it is a certainty rather than a risk.
|
||
|
||
The mitigating detail, worth knowing because it shapes the fix: `doinit` (`:53–67`) does
|
||
**not** leave unresolved entries null. It substitutes `&dummyFunc` (`:63`), and `dummyFunc`
|
||
(`:51`) is `static int dummyFunc() { return 0; }`. A *partial* SWELL therefore degrades to
|
||
"every missing call returns 0/NULL" rather than jumping through a null pointer. A partial
|
||
load is survivable; `exit()` is not.
|
||
|
||
### The implementation shape this obliges
|
||
|
||
Route B3a (Λ-D2) is the ruled route. It must be reached without the stub's own loader:
|
||
|
||
1. **Never define `SWELL_LOAD_SWELL_DYLIB`.** Compile
|
||
`swell-modstub-generic.cpp` in its default branch — the same branch the extension already
|
||
uses (`src/app/CMakeLists.txt:91–92`) — which exports `SWELL_dllMain(hInst, callMode,
|
||
GetFunc)` (`:135`) and calls `doinit` on the pointer it is handed. The whole file is
|
||
inside `#ifdef SWELL_PROVIDED_BY_APP` (`:21`), so the VST3 target must define that
|
||
symbol too; today it does not.
|
||
2. **Own the load.** A new `shell/instrument/swell_bootstrap` TU does the work the stub
|
||
would have done — 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. No vendored
|
||
file is patched. 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 is sufficient 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 the bootstrap on the host name via
|
||
`IHostApplication::getName()` **and** on the `dlopen` succeeding; either failing means no
|
||
editor. Cheap, and it makes the REAPER-only coupling explicit rather than emergent.
|
||
4. **Probe once, lazily, off the audio thread.** The result is a tri-state — untried /
|
||
available / unavailable — computed on first `createView` and never recomputed. No
|
||
`dlopen` from `process()`, no per-`createView` retry, no static-constructor work.
|
||
|
||
### The observable contract
|
||
|
||
In **any** Linux host, with or without `libSwell.so`:
|
||
|
||
- **Scan.** The host opens `reasampler_9000.vst3`, finds `ModuleEntry` / `ModuleExit` /
|
||
`GetPluginFactory`, and enumerates the one class. No crash, no hang, no process exit, no
|
||
blacklist entry.
|
||
- **Instantiate and run.** `setActive(true)` and `process()` produce audio (or silence with
|
||
nothing loaded). MIDI notes play. The component state round-trips through
|
||
`setState`/`getState` byte-identically with the Windows build.
|
||
- **Editor request.** When no usable platform surface exists, `isPlatformTypeSupported`
|
||
returns `kResultFalse` for **every** type — including `kPlatformTypeX11EmbedWindowID` —
|
||
and `createView(kEditor)` returns **`nullptr`**. It never returns a view whose
|
||
`attached()` then fails, and never returns a view that draws nothing. The host falls back
|
||
to its generic parameter UI.
|
||
- **Diagnosis.** Exactly one line, once per process, naming why the editor is unavailable
|
||
(host not REAPER / `libSwell.so` not found at `<path>` / SWELL API incomplete). Written
|
||
through the SDK's own logging or stderr — never a modal, never a message box, never
|
||
repeated per instantiation.
|
||
- **Teardown.** Unloading the module does not crash. Nothing calls `exit()`, `abort()`, or
|
||
leaves a partially-initialised SWELL table reachable by a later call.
|
||
|
||
Under REAPER on Linux the same contract holds with the editor branch taken instead: the
|
||
view reports `kPlatformTypeX11EmbedWindowID`, attaches to the host-supplied window id, and
|
||
drives its timer through `Linux::IRunLoop`.
|
||
|
||
### How it is verified
|
||
|
||
**The VST3 SDK ships a non-REAPER host harness and it is already on disk.**
|
||
`vendor/vst3sdk/public.sdk/samples/vst-hosting/validator/source/validator.cpp` is present
|
||
in the vendored slice (it lives under `public.sdk/`, which the documented narrow submodule
|
||
init already pulls). It is a scriptable, headless, REAPER-free host that instantiates a
|
||
plugin and exercises the standard suite. **It is the right instrument for this criterion**
|
||
and neither audit named it, because neither swept the SDK's `samples/` tree.
|
||
|
||
The verification set, all `[verify — Linux]`:
|
||
|
||
- `validator reasampler_9000.vst3` completes and reports no failures, on a machine with
|
||
**no** `libSwell.so` on the filesystem. This is the load-bearing check: the validator
|
||
process surviving is the direct negation of the `exit(2)` finding.
|
||
- The same, in **Ardour or Bitwig**, through a real plugin scan: the DAW finishes scanning,
|
||
the plugin appears in the browser, an instance instantiates, and the generic parameter UI
|
||
opens. A hand check, but the one that answers Daniel's actual sentence.
|
||
- The same, under **REAPER on Linux**: the editor opens.
|
||
- A **negative-control** run: rename `libSwell.so` beside a Linux REAPER, load the plugin,
|
||
and confirm the generic-UI fallback and the single diagnostic line — not a crash, not a
|
||
blank window.
|
||
|
||
**The acceptance criteria above must pass with zero VST3 parameters registered.** See
|
||
§"Cross-phase dependency: Γ-W4-T1" — today the generic UI is empty, and "the host shows an
|
||
empty generic UI" is a pass. It is a poor experience, and Γ fixes it; it is not Λ's to fix.
|
||
|
||
---
|
||
|
||
## Wave and track structure
|
||
|
||
### Why this sequence
|
||
|
||
Three constraints shape it, and they conflict.
|
||
|
||
**One: the `[verify — Linux]` items are worthless as a late checklist and near-free as an
|
||
early wave.** T1's own §6 Stage 0 says it outright — running V1 and V2 on a Linux box is
|
||
"the one action that reprices everything", and every effort band in both audits is
|
||
provisional until it happens. A plan that puts verification at the end has, in effect, no
|
||
estimates at all.
|
||
|
||
**Two: nothing can be verified from this Windows box.** Every acceptance criterion for the
|
||
first Linux edits is itself a `[verify — Linux]` item. This is not a fixable property of
|
||
the plan; it is the phase. The honest structure is to say so: **Λ-W2's edits are authored
|
||
blind, from the audits' citations, and Λ-W3 is the session that discharges Λ-W2's
|
||
acceptance criteria and answers the sweep in the same sitting.** Λ-W2 is not "done" in the
|
||
usual sense until Λ-W3 runs. Pretending otherwise would launder unverified work into
|
||
landed work, which is exactly what the audits' `[verify]` discipline exists to prevent.
|
||
|
||
**Three: the sweep cannot answer the panel-dependent items until the panel renders.**
|
||
T2 §6 step 4 flags this and notes the critical path runs through the other track. The
|
||
resolution is to pull the dialog-resource work forward into Λ-W2 — which is why Λ-F2 is
|
||
worth ruling early, and why the plan states what happens if it is not (Λ-W2-T3 slips and
|
||
Λ-W3 splits).
|
||
|
||
Two further sequencing decisions, both deliberate:
|
||
|
||
- **The locale codec (L2-04) is pulled forward into Λ-W2**, 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, and there
|
||
is no reason to spend a Linux session discovering that.
|
||
- **V9 (does REAPER's Linux install ship `libSwell.so` beside its executable?) runs in the
|
||
sweep, not in the instrument waves.** It is a one-line `ls`, and the entire Λ-D2 route
|
||
rests on it. If it is false, B3a is void and the instrument's editor needs a re-ruling —
|
||
a thing to learn in Λ-W3, not four waves later.
|
||
|
||
**On the audits' severity disagreement.** 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 the
|
||
inconsistency as a scope call for whoever prioritises the two. This plan **dissolves it
|
||
rather than resolving it**: both land in Λ-W2, in different tracks, so the relative grade
|
||
never has to be adjudicated.
|
||
|
||
### Λ-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 here so the wave numbering matches the
|
||
audit filenames and branch names; nothing to re-spec.
|
||
|
||
---
|
||
|
||
### Λ-W2 — Make it buildable, and make it honest
|
||
|
||
**Four tracks, disjoint at the file level.** Three of them are authored blind against the
|
||
audits and verified in Λ-W3; T4 is verifiable on the current box.
|
||
|
||
#### Λ-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.
|
||
|
||
**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.**
|
||
- Drop the `| FF_DONTCARE` 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.
|
||
- Replace `GetCurrentProcessId()` with a platform-neutral uniqueness source behind a guard;
|
||
the atomic counter at `:52` already carries the intra-process half (L2-02).
|
||
- On the load-failure branch, either switch `main.cpp` to `REAPERAPI_MINIMAL` plus an
|
||
explicit `WANT` list — the pattern `panel_window.cpp` and `panel_audition.cpp` already
|
||
use — or keep the full load and print the failure count via
|
||
`rec->GetFunc("ShowConsoleMsg")` before returning 0 (L2-03). The minimal list is also the
|
||
honest inventory of what this extension actually needs.
|
||
|
||
**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 targets, no `-C` flag needed.
|
||
`[verify — Linux]` = **V2**.
|
||
- A deliberately misspelled entry in the WANT list (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.
|
||
|
||
**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** — default `CMAKE_BUILD_TYPE` when neither it nor `CMAKE_CONFIGURATION_TYPES` is
|
||
set, and correct the docs' ship instruction to name the single-config-generator path. The
|
||
README currently sends a Linux user to `build/Release/`, which on Ninja/Make does not
|
||
exist; 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`; the two 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`,
|
||
and `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 the tree is not estimable from
|
||
Windows. Separately, `-Wl,--no-undefined` on the module targets, to restore 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. It is invisible
|
||
on Windows and load-bearing off it.
|
||
- **Λ-07** — record the compiler floor, 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. Runs concurrently with T1, T3, T4.
|
||
**Discharges:** Λ-02, Λ-03, Λ-04, Λ-05, Λ-07, Λ-09, Λ-10; enables V5, V6, V8.
|
||
|
||
#### Λ-W2-T3 — `panel-dialog-resource` *(gated on Λ-F2)*
|
||
|
||
**Goal.** The docked bank panel opens on Linux, and if it ever fails to, it says so.
|
||
|
||
**Surface boundary — owns:** `src/shell/panel/panel_window.cpp` (the `CreateDialogParam`
|
||
call at `:135–137`, the dialog proc's platform contract, the drop-accept opt-in at
|
||
`:145–150`), `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.
|
||
|
||
**Behavior.** Whichever route Λ-F2 picks, plus, unconditionally: 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** — land it on both platforms, not behind a guard. 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** — see the fork.
|
||
- 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 the process locale, so a
|
||
bank index written on a comma-decimal machine is not written unparseable.
|
||
|
||
**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. Add pure tests that pin the emitted text for a fractional value, and that a
|
||
fractional value survives a write/read round trip.
|
||
|
||
Why this 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 (L2-04). 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` in
|
||
the test itself. 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; 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 this change 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
|
||
|
||
**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.
|
||
|
||
**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 false answer voids Λ-D2's
|
||
route and needs a re-ruling before Λ-W6 is dispatched), 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, and any font check that needs a rendered
|
||
panel) are deferred 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
|
||
|
||
**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.
|
||
|
||
**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), 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` on
|
||
an open file succeeds and the branch never fires. Either the branch 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` relies on
|
||
("the superseded file survives on disk until a prune reclaims it") has nothing under it
|
||
on Linux otherwise.
|
||
- **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, and it is not covered by Λ-D5's ruling.
|
||
|
||
**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.
|
||
|
||
**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.
|
||
|
||
**Surface boundary — owns:** `src/shell/panel/draw_kit.cpp`'s five `loadFont` call sites 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 worth carrying into the work: `draw_kit.cpp:74`'s `if (!hf) return` guard does
|
||
**not** catch the failure mode here. SWELL's `CreateFont` always returns a non-null handle
|
||
even when the face never resolved — the failure is recorded internally as a null
|
||
`typedata`, not as a null return. So 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.
|
||
|
||
**Surface boundary — owns:** the `target_sources` list in `src/app/CMakeLists.txt:8–51`
|
||
(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** — 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. **The wording is Λ-F3's
|
||
ruling**; the edit is this track's regardless of which way it goes.
|
||
- **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
|
||
|
||
#### Λ-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.
|
||
|
||
**Surface boundary — owns:** an `install()` rule in `src/app/CMakeLists.txt`, `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 first `install()` rule in the tree, 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.** It is 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.
|
||
|
||
**Acceptance criteria.**
|
||
- A clean-machine walkthrough: clone, narrow submodule init, configure, build, install,
|
||
restart REAPER, actions present, panel docks. Following only the README.
|
||
`[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 — the Λ-02 criterion,
|
||
re-checked on the artifact that actually ships.
|
||
- `versioning-and-release.md` no longer promises an artifact this phase does not produce.
|
||
|
||
**Prerequisites.** Λ-W3-T1 (V4), Λ-W4 (all three tracks — shipping means the correctness
|
||
fixes are in). **Discharges:** Λ-08, and Λ-02's shipped half.
|
||
|
||
---
|
||
|
||
### Λ-W6 — The instrument module, and the host-safety contract
|
||
|
||
**Two tracks, disjoint: one is build + docs, the other is source.** At the end of this wave
|
||
the Linux instrument loads in any host, processes audio, and has **no editor at all** —
|
||
which is 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.
|
||
|
||
**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: platform clause
|
||
becomes "Windows and Linux; the editor is REAPER-hosted", VST3-only and REAPER-only
|
||
survive verbatim. 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`.
|
||
The `EXISTS` half stays (a fresh clone with no VST3 slice must still configure); the
|
||
`WIN32` half becomes a Windows-or-Linux predicate.
|
||
- **B2** — the artifact becomes a directory:
|
||
`reasampler_9000.vst3/Contents/<uname -m>-linux/reasampler_9000.so`, per V10's answer.
|
||
`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 `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; 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.
|
||
|
||
**Prerequisites.** Λ-W2-T2 (thread linkage, visibility, warnings), Λ-W3-T1 (V10, and 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.
|
||
|
||
**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), 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 until someone decides otherwise; see §"What Phase Λ does
|
||
not do".
|
||
- **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 in §"The non-REAPER-host safety
|
||
criterion", 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. 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** (see the Γ dependency below).
|
||
|
||
**Prerequisites.** Λ-W6-T1 (there must be a module to load). Concurrent with nothing else in
|
||
this wave. **Discharges:** Λ-D3's Λ-W6 half; L2-08's fallback half.
|
||
|
||
---
|
||
|
||
### Λ-W7 — The SWELL bootstrap
|
||
|
||
#### Λ-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.
|
||
|
||
**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 implementation shape in §"The non-REAPER-host safety
|
||
criterion": never define `SWELL_LOAD_SWELL_DYLIB`; own the load through exported surface
|
||
only (`SWELLAPI_GetFunc`, `SWELL_set_app_main`, our own `SWELL_dllMain`); gate on the host
|
||
name **and** the `dlopen`; probe once, lazily, off the audio thread.
|
||
|
||
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 one is not fatal**
|
||
— `doinit` substitutes a zero-returning `dummyFunc` for each miss, so a partial table
|
||
degrades rather than crashes; whether to accept a partial table or refuse is a judgment for
|
||
implementation 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, and **V9 answered affirmatively**. If V9 is negative,
|
||
this track does not exist and Λ-D2 needs a re-ruling.
|
||
**Discharges:** B3 (route B3a); Λ-D3's runtime half.
|
||
|
||
---
|
||
|
||
### Λ-W8 — The X11 editor
|
||
|
||
#### Λ-W8-T1 — `x11-embed-view`
|
||
|
||
**Goal.** The ReaSampler 9000 editor opens, draws and responds under REAPER on Linux.
|
||
|
||
**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 cost this at **L**, and call it a new competence rather than a port
|
||
(L2-08; T1 B4 = "build S, source L"). 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` / their unregisters
|
||
against `Linux::IRunLoop`). 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. It sits
|
||
under `public.sdk/`, so the documented narrow submodule init already pulls it. **This does
|
||
not shrink the L band** — it is a reading input, not a library — but 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, 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 (the phase-wide
|
||
structural heuristic in root `CLAUDE.md`).
|
||
- 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."
|
||
|
||
**Prerequisites.** Λ-W7-T1. **Discharges:** B4, L2-08.
|
||
|
||
---
|
||
|
||
### Concurrency, and the shared files
|
||
|
||
**Wave order is serial; tracks within a wave are concurrent.** Two deliberate exceptions to
|
||
the strict reading:
|
||
|
||
- **Λ-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.
|
||
- **Λ-W6-T2 is prerequisite-gated on Λ-W6-T1** (there must be a module before it can refuse
|
||
to show a view). They are file-disjoint but not order-free — the same shape PLAN.md
|
||
already records for Γ-W3 ("T1 before T2 — serial").
|
||
|
||
**Shared files, named** — all textual adjacency, not semantic contention, unless marked:
|
||
|
||
| 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 resource-id-0 route they are 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. |
|
||
|
||
---
|
||
|
||
## Cross-phase dependency: Γ-W4-T1
|
||
|
||
**Verified against `docs/PLAN.md` while writing this: `Γ-W4-T1 vst3-parameter-set` is
|
||
listed as pending** (PLAN.md §Γ-W4, and in the plan's summary map). Its own spec states the
|
||
current position plainly: *"Today the plugin has zero parameters"* —
|
||
`ReaSamplerProcessor::initialize` never populates `SingleComponentEffect::parameters`, so
|
||
`getParameterCount()` returns the SDK default 0. The T2 audit confirms the same by grep.
|
||
|
||
**Why it matters to Λ.** The host's fallback when a plug-in reports no usable editor is its
|
||
**generic parameter UI**. With zero parameters registered, that fallback is empty — so
|
||
today, "no editor" degrades to *nothing*, not to *controls*. Once Γ-W4-T1 lands its 44
|
||
derived parameters, the identical Λ code path degrades to a usable generic UI instead.
|
||
|
||
**The dependency and its direction:**
|
||
|
||
- **Λ does not block Γ**, and **Γ does not block Λ.** Λ-W6-T2's acceptance criteria are
|
||
written to pass with zero parameters — "the host shows its generic UI, which may be
|
||
empty" is a pass. Nothing in Λ is scheduled against Γ.
|
||
- **Γ landing strictly improves Λ's user-visible outcome**, at no cost to Λ. If Γ-W4-T1 is
|
||
already on `dev` when Λ-W6-T2 is verified, the tester will see a populated generic UI;
|
||
that is a better result, not a different criterion.
|
||
- **Λ must not register any VST3 parameter itself.** Γ-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.**
|
||
- **Another team is running Γ concurrently. Λ assumes no schedule for it** — not "Γ will
|
||
land first", not "Γ will land later". If a Λ track's brief needs a Γ fact, it reads
|
||
`dev` at dispatch time.
|
||
|
||
---
|
||
|
||
## Calls made here (product-designer calls, not forks)
|
||
|
||
Two questions the audits raised as `[Daniel]` that this doc answers rather than escalates.
|
||
Contradict either in review with an argument.
|
||
|
||
**Silent font substitution is not acceptable; the kit gets an explicit Linux face list**
|
||
(T2 [Daniel] 3, landed as Λ-W4-T2). The reasoning is `docs/product/visual-design-language.md`:
|
||
the kit's type is part of a deliberate visual identity, and the palette-role discipline
|
||
exists precisely so a visual direction is a single-file change rather than an emergent
|
||
property of the host. Letting fontconfig pick is the opposite of that. The cost is five
|
||
string literals. The one thing that would change the call is if the sweep shows the stock
|
||
substitution is *already* the intended face on every target distro — unlikely, and it would
|
||
make the face list a no-op rather than wrong.
|
||
|
||
**The `[verify — Linux]` items are not forks and do not wait for Daniel.** T2's [Daniel]
|
||
list included items that a Linux box settles (which SWELL build REAPER ships, whether
|
||
fontconfig lands Consolas on a monospace face, whether drag-out is accepted as a copy).
|
||
Those are Λ-W3 sweep items. A question is only a fork here if reading code and running a
|
||
Linux box both leave it open.
|
||
|
||
---
|
||
|
||
## What Phase Λ does not do
|
||
|
||
- **macOS.** Λ-D4. Shared-form edits are made and noted as shared; no macOS build, no macOS
|
||
verification, no signing or notarization, no `swell-modstub.mm`-under-CXX-only
|
||
investigation, no fix for L2-11's case-insensitive-filesystem defect.
|
||
- **XDG trash.** Λ-D5. Hard `unlink` with a platform-aware confirmation. The XDG spec
|
||
(`~/.local/share/Trash` plus a `.trashinfo` sidecar) is a self-contained move-plus-metadata
|
||
write and remains a clean follow-up if a Linux user ever asks for it — file it to
|
||
`docs/TODO.md`, do not smuggle it into Λ-W4-T1.
|
||
- **Route B3b's dependency surface.** Λ-D2. No vendored SWELL build, no GDK/GTK3, no
|
||
FreeType, no Fontconfig, no OpenGL, no `pkg-config` in this tree. If a track finds itself
|
||
reaching for one of those, the design has drifted and the track stops.
|
||
- **An editor in a non-REAPER host.** Λ-D3 requires safe degradation, not capability. Even
|
||
with `libSwell.so` reachable, a non-REAPER host has no SWELL message loop, so the
|
||
host-name gate refuses on purpose.
|
||
- **The TCP/MCP embed strip on Linux.** `reasampler_embed`'s
|
||
`REAPER_FXEMBED_WM_IS_SUPPORTED` keeps returning 0 off Windows. It is a second 0→1 on a
|
||
read-only secondary surface, its paint body is LICE and would port, and it becomes
|
||
genuinely cheap once Λ-W8 lands. Deliberately out, and worth revisiting after.
|
||
- **VST2 / AU / CLAP / standalone.** D5's format and host clauses survive Λ-D1 untouched.
|
||
- **CI.** Λ-F1 is open; until it is ruled, Λ ships nothing that presumes a runner.
|
||
- **Any parameter registration.** Γ-W4-T1's, exclusively. See above.
|
||
- **Windows behaviour changes.** Every track carries a "Windows unchanged" criterion. This
|
||
is a port, and a port that improves Windows by accident has also changed Windows by
|
||
accident.
|
||
|
||
---
|
||
|
||
## Open forks — Daniel's
|
||
|
||
### Λ-F1 — Does CI get built in this phase, and on what runner? *(audit D4, unruled)*
|
||
|
||
**No recommendation.** This one is genuinely a resourcing call, and both sides are
|
||
defensible enough that a product-designer preference would be noise.
|
||
|
||
**For building it now:** `versioning-and-release.md` already commits to multiple platform
|
||
artifacts per channel per release and hands "the pipeline" to dev-ops — and there is no CI
|
||
configuration anywhere in the repo, on any platform (`.github` and `.gitlab-ci.yml` are both
|
||
absent). The moment Linux ships, the tree has two toolchains and one of them is not on the
|
||
developer's machine. Every Windows-only commit after that is a Linux regression waiting to
|
||
be discovered by a user. A GitHub-Actions job running `cmake` + `ctest` on Ubuntu would
|
||
catch L2-01-class breakage — an undefined Win32 symbol — at push time, which is exactly the
|
||
class of defect this whole phase exists to clear.
|
||
|
||
**For deferring it:** CI is infrastructure, not product, and this phase is already the
|
||
largest thing in the plan. A runner also forces Λ-F4 (the support floor) to be answered
|
||
immediately, because the runner image *is* the declared floor. And CI's value is highest
|
||
*after* the first Linux build works, not before — a red pipeline on day one teaches nothing
|
||
the Λ-W3 sweep is not already teaching.
|
||
|
||
**What closing it unblocks:** whether Λ-W5-T1 writes a pipeline paragraph or a
|
||
"deferred to dev-ops" paragraph, and whether Λ-F4 must be closed in this phase or can be
|
||
carried. Nothing is gated on it before Λ-W5.
|
||
|
||
### Λ-F2 — The dialog-resource route: resource-id-0, or resgen? *(audit D7, unruled)*
|
||
|
||
**This is the only fork that gates a track.** Both audits now carry the evidence.
|
||
|
||
**Route A — the resource-id-0 escape hatch.** SWELL documents it in
|
||
`swell-functions.h:606–608`: a `CreateDialog` creating a child window may pass resource id
|
||
0, which creates an opaque child window, provided a `WNDPROC` returning `LRESULT` (cast to
|
||
`DLGPROC`) is passed instead of a real `DLGPROC`. The implementation confirms both halves —
|
||
`swell-dlg-generic.cpp` skips the resource lookup entirely when `resid` is 0. And
|
||
`IDD_BANK_PANEL` is precisely the case it was written for: `src/resource.rc:18–22` is a
|
||
`WS_CHILD` dialog with an empty `BEGIN`/`END` body and zero controls, whose own header
|
||
comment says "the bank_panel shell owns every pixel and draws the sample grid with LICE in
|
||
`WM_PAINT`". **Taking this route deletes the entire resgen pipeline from the non-Windows
|
||
builds** — the PHP dependency, the generated-file-in-the-source-tree problem, the
|
||
per-platform regeneration story, and the three stacked defects in the current commented-out
|
||
instructions. Cost: a source change in `panel_window.cpp` and the DLGPROC→WNDPROC return
|
||
contract.
|
||
|
||
**Route B — wire up resgen properly.** `find_program(PHP …)`, an `add_custom_command`
|
||
generating into `${CMAKE_CURRENT_BINARY_DIR}` (never the source tree — `.gitignore` has no
|
||
pattern for the generated name), a one-line shim TU that includes `swell-dlggen.h` then the
|
||
generated file, and that shim added to `target_sources`. This is the M end of Λ-01's band.
|
||
|
||
**The asymmetry neither audit stated, and it is the reason this is worth ruling rather than
|
||
delegating.** L2-07 establishes that SWELL's only opt-in surface for `WS_EX_ACCEPTFILES` is
|
||
the **dialog resource** — `swell_resgen` translates a dialog's `WS_EX_ACCEPTFILES` style
|
||
into `SWELL_DLG_WS_DROPTARGET`, and `swell-dlg-generic.cpp` sets the ex-style from that flag
|
||
at creation time. **Under Route A there is no resource, so that bit can never be set from
|
||
one.** L2-07's own analysis says the bit only *suppresses* the unhandled-drop
|
||
forward-to-parent and that the drop reaches `dlgProc` anyway via the GDK backend's
|
||
`ChildWindowFromPoint` descent — and notes, correctly, that with zero child controls the
|
||
descent has nowhere to land but the panel HWND itself. That reasoning is good but it is
|
||
**reasoning, not a run**. So:
|
||
|
||
- Route A is cheaper and structurally simpler, and it stakes the panel's file-drop ingest —
|
||
one of three ingest surfaces — entirely on that unverified descent.
|
||
- Route B is more machinery and one more build-time dependency, and it can set the bit
|
||
explicitly if the descent turns out not to be enough.
|
||
|
||
**What closing it unblocks:** Λ-W2-T3's dispatch, and therefore whether Λ-W3 is one Linux
|
||
session or two. **Leaving it open has a real cost and it is the only fork here that does.**
|
||
|
||
**A third option, if Daniel wants the cheap route without the exposure:** take Route A and
|
||
treat the file-drop check as a hard gate in Λ-W3's sweep — if the drop does not arrive,
|
||
Route B becomes a follow-up track rather than a rewrite, because the panel is already
|
||
rendering by then and the resgen work is additive. That is the option I would take, and it
|
||
is stated as an option rather than a recommendation because it spends a second Linux session
|
||
in the bad case.
|
||
|
||
### Λ-F3 — Does "copy-only is structural" survive as a shipped invariant? *(T2 [Daniel] 4, unruled)*
|
||
|
||
**Recommendation: keep the invariant, scope the *enforcement* claim to Windows.** The
|
||
product promise ("dragging a bank file out of ReaSampler copies it; the bank is never
|
||
raided by a drop target") stays; the *mechanism* sentence in `drag_out_win.h` becomes
|
||
"structurally enforced on Windows via `DoDragDrop`'s `DROPEFFECT_COPY`-only mask;
|
||
advertised, not enforced, on SWELL, which takes no effect mask."
|
||
|
||
**The counter-argument is real.** An invariant that one platform cannot enforce is arguably
|
||
not an invariant, and the tree's house style treats invariants as load-bearing rather than
|
||
aspirational. Weakening the *global* wording to "copy-only, best effort" would be the honest
|
||
alternative — and it would also correctly warn a Windows reader not to rely on the guarantee
|
||
in shared code. What that loses is a genuinely stronger Windows property, stated as weaker
|
||
than it is.
|
||
|
||
**Non-gating.** Λ-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, not the decision)*
|
||
|
||
The audits surface three separate manifestations of one unmade decision: Λ-07 (no stated
|
||
minimum compiler; GCC 8 needs an explicit `-lstdc++fs` for `std::filesystem`, GCC ≥ 9 does
|
||
not), V8 (the same, as a check), and V10 (which `uname -m` values the VST3 bundle must
|
||
carry — `module_linux.cpp` keys the bundle directory off exactly that string, and which
|
||
architectures REAPER itself ships for Linux is an external fact with no source in this tree).
|
||
|
||
**Recommendation: declare a narrow floor — current-stable-distro glibc, GCC ≥ 9,
|
||
`x86_64-linux` only — and widen it on request.** ReaSampler is a per-project capture tool
|
||
for people running a commercial DAW; the audience running a five-year-old toolchain on
|
||
armv7 is small, and every architecture in the bundle is a build target someone has to
|
||
maintain and verify.
|
||
|
||
**The counter:** REAPER's own Linux reputation is partly built on running well on modest and
|
||
non-x86 hardware, and `aarch64` in particular is no longer exotic. Adding it later means a
|
||
second bundle directory and a second verification pass on every future release, which is
|
||
cheaper to establish once than to retrofit.
|
||
|
||
**What closing it unblocks:** Λ-W2-T2's compiler-floor line, Λ-W5-T1's install and README
|
||
text, Λ-W6-T1's bundle directory set, and Λ-F1's runner image. **Non-gating for Λ-W2** (a
|
||
recorded floor can be widened), **gating for Λ-W5** — a shipped artifact has to say what it
|
||
runs on.
|
||
|
||
---
|
||
|
||
## Where the audits disagreed, or left a hole
|
||
|
||
Recorded so a reader knows which parts of this doc are synthesis rather than citation.
|
||
|
||
1. **The `WS_EX_ACCEPTFILES` / resource-id-0 interaction is a genuine gap between the two
|
||
audits.** T1's Λ-01 recommends the id-0 route and says it makes resgen disappear; T2's
|
||
L2-07 independently establishes that the drop-accept ex-style bit can only come from the
|
||
dialog resource. Neither doc composed the two. §Λ-F2 does, and it is the substantive
|
||
reason that fork is worth ruling rather than delegating to review.
|
||
|
||
2. **T2 flags its own severity inconsistency and hands it on.** L2-03 is graded Blocker on
|
||
failure-mode quality with no direct evidence; L2-04 is graded Major with a confirmed
|
||
mechanism and an equally severe outcome. T2 says explicitly that resolving the axis is a
|
||
scope call for whoever prioritises the two. This doc **dissolves rather than resolves**
|
||
it — both land in Λ-W2, in different tracks — so the grade never has to be adjudicated.
|
||
|
||
3. **The absent-`libSwell.so` failure mode was flagged unverified and is, in fact,
|
||
readable.** Both audits treat it as a `[verify — Linux]` question. It is not: the
|
||
vendored `swell-modstub-generic.cpp` calls `exit(2)` on a failed `dlopen` from a static
|
||
constructor, and `exit(1)` on an incomplete API table. That is a fact about a file in
|
||
this tree, and it converts Daniel's Λ-D3 from a defensive precaution into a specific
|
||
design constraint on the ruled route. Filled here, in §"The non-REAPER-host safety
|
||
criterion".
|
||
|
||
4. **Neither audit swept the VST3 SDK's `samples/` tree.** Both enumerate the vendored
|
||
*slice* the build compiles, which is correct for their questions, but
|
||
`public.sdk/samples/vst-hosting/` is on disk under the documented narrow submodule init
|
||
and contains two directly relevant things: `validator/` (a headless non-REAPER host
|
||
harness — the right instrument for Λ-D3's acceptance criteria) and
|
||
`editorhost/source/platform/linux/` (a worked `Linux::IRunLoop` implementation and an
|
||
X11-embed window). Named in Λ-W6-T2, Λ-W7-T1 and Λ-W8-T1. **This does not move the L
|
||
band on L2-08** — the audits' estimate stands; it removes a "we are guessing at the
|
||
contract" risk from it.
|
||
|
||
5. **The beta channel on Linux is uncosted in both audits.** T1 confirms the channel fork is
|
||
platform-independent by construction and stops there; neither audit builds, installs, or
|
||
coexists two channels on Linux, and neither considers the VST3 bundle's per-channel name
|
||
and UID on that platform. Almost certainly free; assigned to Λ-W5-T1 and Λ-W6-T1 as an
|
||
explicit criterion rather than an assumption.
|
||
|
||
6. **Two loose ends left as-is, deliberately.** T1's `lice_colorspace.cpp` question (SWELL's
|
||
own Makefile lists a fifth LICE TU our four-TU slice omits; the audit calls it
|
||
"unreconciled but harmless") and the `-Wl,--no-undefined` question are both answered by a
|
||
single successful Linux link. Both are Λ-W3 sweep items, not findings.
|
||
|
||
---
|
||
|
||
## Wave map
|
||
|
||
```
|
||
Phase Λ — ReaSampler on Linux (extension + ReaSampler 9000; macOS out)
|
||
W1 The audits [COMPLETE]
|
||
T1 build-toolchain-audit ....... Λ-01..Λ-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 ............. Λ-02..Λ-05, Λ-07, Λ-09, Λ-10 -> V5, V6, V8
|
||
T3 panel-dialog-resource ....... Λ-01, L2-06, L2-07 -> V7 [GATED: Λ-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 §5's seven; 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 . Λ-06, L2-10, L2-11 (Linux half)
|
||
|
||
W5 Ship the extension [1 track]
|
||
T1 linux-packaging-and-install . Λ-08 + Λ-02's shipped half + both channels
|
||
|
||
W6 The instrument module, and 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 Λ-D2 re-rules]
|
||
|
||
W8 The X11 editor [1 track]
|
||
T1 x11-embed-view .............. B4, L2-08. The phase's one L-band source item.
|
||
|
||
W6 may run concurrently with W4 and W5 — file-disjoint; the only reason to serialise
|
||
is attention. W2's edits are authored BLIND and their acceptance criteria are
|
||
discharged in W3; W2 is not "done" until W3 runs, and the plan says so rather than
|
||
pretending otherwise.
|
||
Open: Λ-F1 (CI), Λ-F2 (resource route — the only one that gates a track),
|
||
Λ-F3 (copy-only invariant wording), Λ-F4 (declared support floor — gates W5).
|
||
```
|