Files
reasampler/docs/product/audit-notes/lambda-w1-t1-build-toolchain.md
T

697 lines
47 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Λ-W1 Track 1 — Linux build/toolchain audit (build-system lens)
Static analysis of the build surface only — the 23 `CMakeLists.txt` files, `cmake/`,
`src/resource.rc`/`src/resource.h` + the SWELL resgen pipeline, the three vendored submodules
as *build inputs*, the `ctest` harness, toolchain-level flags, and the packaging story in
`docs/product/versioning-and-release.md`. 2026-08-02, branch `pl-w1-t1-build-toolchain-audit`
(cut from `dev` at 6e937b9).
**Source portability is explicitly NOT audited here** — no Win32/GDI/LICE API usage, path
handling, threading, drag/drop, dialog code, fonts, or instrument runtime code. A parallel
track (T2) owns that; what I noticed in passing is listed under "Referred to T2" at the end
and nowhere else.
**Verification posture.** I am on Windows and cannot build or run anything on Linux. Every
claim below is grounded in a file I opened in this worktree — the tree's own CMake, or a
vendored header/source under `vendor/` — and every line number was re-read immediately before
being written down. Where a claim depends on runtime behaviour I cannot observe (does REAPER's
Linux build call `SWELL_dllMain`? does GCC accept this TU?), it is marked `[verify — Linux]`
with the exact check, and it is **not** stated as fact. To get the vendored citations I ran
`git submodule update --init` for all three submodules inside this worktree; nothing in the
tracked tree was modified.
Severity vocabulary, per the dispatch: **Blocker** = the Linux build cannot produce a loadable
artifact; **Major** = it builds but is degraded or unshippable; **Minor** = ergonomics,
warnings, CI polish. Effort bands are S (< half a day), M (13 days), L (a week+), and each
names the reason for the band rather than asserting a number.
---
## 1. Baseline — what the build does today, per platform
**Two loadable modules, 84 pure libraries declared under `src/core/`, 91 CTest targets,
three vendored submodules.** (84 = the `reasampler_pure_library` + bare `add_library` count
across the 19 `src/core/**` CMake files; 46 of them are on the extension's link line.)
- Root `CMakeLists.txt` is 95 lines and declares **no targets at all**: it owns the version
string (`:24`), the channel fork (`:4054`), the `configure_file` for
`version_generated.h` (`:5861`), the four vendor path variables (`:6568`), the
four-TU `LICE_SRC` list (`:7883`), `enable_testing()` (`:90`), and three
`add_subdirectory` calls (`:9294`).
- Language/toolchain settings are exactly three lines: `CMAKE_CXX_STANDARD 17`,
`CMAKE_CXX_STANDARD_REQUIRED ON`, `CMAKE_POSITION_INDEPENDENT_CODE ON`
(`CMakeLists.txt:2830`). There is **no** `CMAKE_BUILD_TYPE`, no `CMAKE_CXX_FLAGS`, no
`CMAKE_CXX_EXTENSIONS`, no IPO/LTO setting, and no `target_compile_options` anywhere in
the tree — I grepped all 23 CMake files for `compile_options`, `find_package`,
`pkg_check`, `link_directories`, `target_link_options`, and `install(` and got zero hits
outside the two files named below.
- **Platform conditionals exist in exactly two files.** Grepping all 23 `CMakeLists.txt`
for `WIN32|APPLE|UNIX|LINUX|MSVC|GNU|Clang|CMAKE_SYSTEM` returns hits only in
`src/app/CMakeLists.txt` (`:71` `if(WIN32)`, `:76` `elseif(APPLE)`, `:88` `else()`) and
`src/shell/instrument/CMakeLists.txt` (`:9` `if(WIN32 AND EXISTS ...)`). Every other
CMake file is platform-neutral by construction: they call the two helpers in
`cmake/reasampler_targets.cmake` (`reasampler_pure_library` at `:815`,
`reasampler_test` at `:1924`) and nothing else.
### 1a. The REAPER extension (`reaper_reasampler`)
Declared as a `MODULE` library over 39 shell TUs plus `${LICE_SRC}`
(`src/app/CMakeLists.txt:851`), linking 46 pure static libraries (`:52`) with
`${SDK_INC} ${WDL_INC}` on the include path (`:56`). Output naming is
`PREFIX ""` + channel-derived `OUTPUT_NAME`, pinned to the top of the build tree by
`LIBRARY_OUTPUT_DIRECTORY`/`ARCHIVE_OUTPUT_DIRECTORY` (`:6569`).
The three-way platform block (`:7198`) is:
| Branch | State | What it does |
|---|---|---|
| `if(WIN32)` `:7174` | **Live and shipped** | adds `src/resource.rc` for the RC compiler |
| `elseif(APPLE)` `:7686` | **Partly aspirational** | adds `swell-modstub.mm`, defines `SWELL_PROVIDED_BY_APP`, links `-framework AppKit`, forces `.dylib`. The resgen `target_sources` line at `:86` is **commented out**. |
| `else()` (Linux) `:8897` | **Partly aspirational** | adds `swell-modstub-generic.cpp`, defines `SWELL_PROVIDED_BY_APP`, forces `.so`. The resgen `target_sources` line at `:97` is **commented out**. |
So on Linux the build system today produces `reaper_reasampler.so` with the SWELL modstub
compiled in and **no dialog resource of any kind**. `README.md:6768` states this is
"expected, not a bug" — the tree is honest about it.
Three build-input facts I verified in `vendor/` and that hold in the extension's favour:
- `vendor/WDL/WDL/swell/swell-modstub-generic.cpp` is entirely inside
`#ifdef SWELL_PROVIDED_BY_APP` (`:21`, closed at `:150`). Since
`SWELL_LOAD_SWELL_DYLIB` is not defined anywhere in this build, the `#else` branch at
`:133` applies: the file's whole contribution is the three
`SWELL_curmodule_*resource_head` globals (`:3133`), the API function-pointer table
built from `swell.h` (`:3749`), and the `SWELL_dllMain` export (`:135`) that resolves
every SWELL symbol from a host-supplied `GetFunc`. That is the correct stub for a
REAPER extension, and the comment at `src/app/CMakeLists.txt:90` ("With
SWELL_PROVIDED_BY_APP you can drop pkg-config / -lX11 entirely") is **correct**: no SWELL
implementation TU, no X11, no GDK is compiled or linked into the extension.
- The four-TU LICE slice is self-contained on Linux. `lice.cpp` reaches SWELL only through
`SWELL_DeleteGfxContext` (`:153`, `:218`), `SWELL_GetCtxFrameBuffer` (`:182`, `:226`) and
`SWELL_CreateMemContext` (`:224`); `lice_textnew.cpp` reaches it through
`SWELL_PushClipRegion`/`SWELL_SetClipRegion` (`:10271028`) and `SWELL_PopClipRegion`
(`:1048`). All six are entries in SWELL's API table
(`vendor/WDL/WDL/swell/swell-functions.h:857`, `:863`, `:877`, `:885887`), so the
modstub resolves them from the host. No fifth LICE TU is implied: the `_LICE_RGB2HSV`
table lives inside `lice.cpp` itself (`:3040`), and the one non-Win32-only helper
`utf8makechar` is a file-static defined at `lice_textnew.cpp:26` and used at `:241242`.
- `CMAKE_POSITION_INDEPENDENT_CODE ON` (`CMakeLists.txt:30`) is already set. This is the
single most commonly missed setting for a `MODULE` target fed by `STATIC` libraries on
Linux, and it is correct today.
### 1b. The VST3 instrument (`reasampler_vst`)
Hard-gated to Windows: `if(WIN32 AND EXISTS "${VST3_SDK}/public.sdk/source/main/pluginfactory.cpp")`
(`src/shell/instrument/CMakeLists.txt:9`). The gate is a conjunction — even with the
`vendor/vst3sdk` slice fully initialised, a Linux configure silently omits the target.
`README.md:3335` states this explicitly. Two module-level `CLAUDE.md` files record the
underlying product decision (D5) as *settled*, with "a mac/Linux build" named as a
non-goal: `src/core/instrument/CLAUDE.md` (Non-goals/guardrails) and
`src/shell/instrument/CLAUDE.md` (same). `src/shell/panel/CLAUDE.md` carries a third
instance ("Windows-only (D5) — font/GDI/HFONT choices assume Windows").
Inside the gate: a 19-source `vst3_sdk` static library (`:1539`) pinned to SDK tag
`v3.7.9_build_61` (confirmed by `git describe --tags` in `vendor/vst3sdk`), and the
`reasampler_vst` MODULE (`:4481`) which additionally compiles `dllmain.cpp` and
`moduleinit.cpp` directly (`:7879`, with the linker-stripping rationale at `:7577`) plus
`${LICE_SRC}` (`:80`). Output is a single **file** named `<name>.vst3` (`:106111`).
### 1c. Resources
`src/resource.rc` declares exactly one dialog — `IDD_BANK_PANEL`, a bare `WS_CHILD`
`DIALOG` with an empty `BEGIN`/`END` body and no controls (`:1822`). `src/resource.h`
defines the single id `IDD_BANK_PANEL 1000` (`:8`). It is consumed by
`CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_BANK_PANEL), …)` at
`src/shell/panel/panel_window.cpp:135`.
### 1d. Test harness
91 test declarations across 19 `CMakeLists.txt` files (grep count), matching 91 files in
`tests/`. 90 go through `reasampler_test` (`cmake/reasampler_targets.cmake:1924`, which is
`add_executable` + `target_link_libraries` + `add_test`); one is longhand —
`app_version_padding_tests` (`src/core/version/CMakeLists.txt:3742`), which recompiles
`app_version.cpp` against a second `configure_file` output so the include-dir substitution
works. Neither shape carries anything platform-specific.
Only two test files mention a platform at all: `tests/test_capture_paths.cpp` (five
`#ifdef _WIN32` blocks at `:19`, `:41`, `:87`, `:152`, `:205`, each with a POSIX `#else`
branch already written — e.g. `:158168` asserts `resolveBankFile("/home/d/proj", …)`), and
`tests/test_pitch_shift.cpp:3`, where the string is inside a comment explaining why the
target links nothing that would drag `<windows.h>` in. No test includes `reaper_plugin.h`,
`windows.h`, or any SWELL header. **The test harness is the healthiest part of this
surface**; see §3 for the one generator-shape caveat.
### 1e. Packaging / release, as already committed
`docs/product/versioning-and-release.md` already commits to Linux as a first-class target:
- `:361363` — "**Artifact per platform.** The binary is `reaper_*.dll|.dylib|.so`; the
macOS/Linux builds need the SWELL resgen step (CLAUDE.md §SWELL dialog resources) baked
into the pipeline. Three platform artifacts per channel per release."
- `:307310` — two named artifacts (stable + beta) per platform, "three platform artifacts
each", explicitly handed off to dev-ops.
- `:364366` — "Install is copy-in + restart… there is no server-side rollout."
What that document does **not** contain: any mention of code signing, macOS notarization, a
CI runner, or a Linux install path. I grepped it for `sign|notariz|CI|platform|linux` — the
only hits are the lines above. There is no CI configuration anywhere in the repo (`ls -a` on
the root shows `.git`, `.gitignore`, `.gitmodules`, `CLAUDE.md`, `CMakeLists.txt`, `LICENSE`,
`README.md`, `cmake`, `docs`, `src`, `tests`, `vendor` — no `.github`, no `.gitlab-ci.yml`),
and no `install()` rule in any of the 23 CMake files. So "the pipeline" is entirely notional
today, on every platform.
---
## 2. Findings
### Λ-01 — the Linux/macOS dialog-resource path is broken in three independent ways, and the fix may not need resgen at all
**Severity: Major. Effort: S (mechanical) to M (if the resgen route is taken and automated).**
**Citations.** `src/app/CMakeLists.txt:9497` (the Linux comment block) and `:8386` (the
identical macOS one); `vendor/WDL/WDL/swell/swell_resgen.php:313314`;
`vendor/WDL/WDL/swell/swell-dlggen.h:22`; `vendor/WDL/WDL/swell/sample_project/myapp.cpp:192193`;
`vendor/WDL/WDL/swell/swell-dlggen.h:214218`; `vendor/WDL/WDL/swell/swell-modstub-generic.cpp:3133`;
`vendor/WDL/WDL/swell/swell-functions.h:612619`; `src/resource.rc:1822`;
`src/shell/panel/panel_window.cpp:135`.
**What breaks.** Three defects stack, and each alone would sink the step:
1. **The named script does not exist.** Both comment blocks say
`php ${WDL_INC}/swell/mac_resgen.php src/resource.rc` (`:85` and `:96`). Listing
`vendor/WDL/WDL/swell/` shows `swell_resgen.php`, `swell_resgen.pl`, and
`swell_resgen.sh` — there is no `mac_resgen.php`. `CLAUDE.md` and `README.md:120` both
name the correct script; only the CMake comments are wrong, which is exactly the file an
implementer will be reading when they do the work.
2. **The named output file is not what the generator writes.** Both commented
`target_sources` lines (`:86`, `:97`) reference
`${REASAMPLER_SRC_DIR}/resource.rc_mac_dlg.h`. `swell_resgen.php:313314` composes its
output names as `$srcfn . "_mac_menu"` and `$srcfn . "_mac_dlg"` — so the actual outputs
are `src/resource.rc_mac_dlg` and `src/resource.rc_mac_menu`, with **no** `.h` suffix.
3. **Even the correct filename cannot be a `target_sources` entry.** `swell-dlggen.h:22`
states the contract outright: "Use swell_resgen.pl or swell_resgen.php on the .rc file,
which will produce .rc_mac_dlg, **include that**." The generated body is a run of
`SWELL_DEFINE_DIALOG_RESOURCE_BEGIN(...)` macro invocations
(`swell_resgen.php:117129`) that only expand after `swell-dlggen.h` has been included —
`swell-dlggen.h:214218` shows the macro emitting a static `SWELL_DialogRegHelper` that
registers into `SWELL_curmodule_dialogresource_head`. The canonical consumption pattern
is the SDK's own sample: `sample_project/myapp.cpp:192193` does
`#include "../WDL/swell/swell-dlggen.h"` then `#include "res.rc_mac_dlg"`. A CMake
`target_sources` entry for an extensionless file would not even get a language assigned.
**The runtime consequence, and why this is Major rather than Blocker.** The `.so` still
builds and still loads: the extension's whole action family, capture pipeline, and persist
layer are untouched. What fails is the dock panel. `swell-functions.h:615` defines
`CreateDialog(hinst,resid,par,dlgproc)` as
`SWELL_CreateDialog(SWELL_curmodule_dialogresource_head,(resid),par,dlgproc,0)`.
`swell-modstub-generic.cpp:32` *defines* that head but nothing ever populates it, because
the resgen'd registration TU does not exist — so it stays null and
`panel_window.cpp:135`'s `CreateDialogParam` gets a null resource list. The panel is the
product's primary surface, so a Linux build without it is not shippable; it is not a
Blocker only because the binary loads and the actions work.
**The route worth considering first.** `swell-functions.h:606608` documents an escape
hatch: *"If you are using CreateDialog() and creating a child window, you can use a resource
ID of 0, which creates an opaque child window. Instead of passing a DLGPROC, you should pass
a (WNDPROC) routine that returns LRESULT (and cast it to DLGPROC)."* `src/resource.rc:1822`
shows `IDD_BANK_PANEL` is precisely that: a `WS_CHILD` dialog with an empty body and zero
controls, whose comment at `:810` says "the bank_panel shell owns every pixel and draws the
sample grid with LICE in `WM_PAINT`". If the panel is created with resource id 0 on
non-Windows, **the entire resgen pipeline — the PHP dependency (`README.md:54`), the
generated-file-in-source-tree problem, and the per-platform regeneration story — disappears
from the Linux and macOS builds.** The cost is a source change in `panel_window.cpp` and the
DLGPROC→WNDPROC return-type contract; that half is T2's call, not mine, and I am naming the
option rather than choosing it. This is a shared macOS/Linux fix either way.
**If the resgen route is taken instead**, the build work is: a `find_program(PHP …)`, an
`add_custom_command` generating into `${CMAKE_CURRENT_BINARY_DIR}` (not the source tree —
`.gitignore` has no pattern matching `resource.rc_mac_dlg`, so today's manual step would
leave an untracked generated file sitting in `src/`), a new one-line shim TU that includes
`swell-dlggen.h` then the generated file, and that shim added to `target_sources`. That is
the M-effort half of the band.
---
### Λ-02 — `--config Release` is a silent no-op on Linux, so the documented ship command produces an unoptimized binary
**Severity: Major. Effort: S — one `if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)` block plus a doc correction.**
**Citations.** `CMakeLists.txt:2830` (the complete list of language settings — I read the
whole 95-line file and there is no `CMAKE_BUILD_TYPE` anywhere); `README.md:106109`;
`CLAUDE.md` §"Build and test" and §"Install / reload".
**What breaks.** Both docs prescribe `cmake --build build --config Release` as the
optimize-and-ship incantation, and `README.md:130131` tells the user to copy from
`build/Release/`. `--config` is a multi-config-generator flag; on Ninja or Unix Makefiles it
is accepted and ignored. Because no `CMAKE_BUILD_TYPE` default is set, the resulting build
passes **no `-O` flag at all** to GCC/Clang — not `-O0` explicitly, just the compiler's
unoptimized default. The failure is silent in both directions: the command succeeds, and the
artifact lands at `build/reaper_reasampler.so` rather than the documented `build/Release/`
path (see Λ-08).
This matters more here than the usual "forgot to set the build type", because `CLAUDE.md`'s
own performance guardrails section states that the header-inline hot paths (`peaks`
envelope compute, `Voice::advanceFrame`, the three envelope evaluators) "presume an
**optimizing** build", and there is no LTO configured to recover any of it. A Linux user
following the README would ship a binary whose hottest loops did not inline.
**Sequencing note.** This is not strictly Linux-specific — Ninja on Windows has the same
shape — but it becomes load-bearing the moment Linux is a shipped platform, because Makefiles
and Ninja are the default there.
---
### Λ-03 — no symbol-visibility control, and two modules in one process both export a full copy of LICE
**Severity: Major. Effort: S — one `CXX_VISIBILITY_PRESET hidden` + `VISIBILITY_INLINES_HIDDEN` pair, plus a `[verify — Linux]` load test.**
**Citations.** `CMakeLists.txt:7883` (`LICE_SRC`); `src/app/CMakeLists.txt:35` and
`src/shell/instrument/CMakeLists.txt:80` (the same four LICE TUs compiled into *both*
modules — the rationale for the duplication is at `CMakeLists.txt:7577`);
`vendor/reaper-sdk/sdk/reaper_plugin.h:52`; `src/app/main.cpp:251`;
`vendor/vst3sdk/pluginterfaces/base/fplatform.h:164`;
`vendor/WDL/WDL/swell/Makefile:20`.
**What breaks.** Nothing in the build sets a visibility preset, so GCC/Clang default to
`-fvisibility=default` and every non-static symbol in both modules — all of LICE, all the
WDL helpers, every pure library's exported functions, and the whole C++ standard-library
instantiation set — lands in the module's dynamic symbol table. On Windows this is a
non-issue: each DLL has its own symbol table and per-module resolution. On Linux ELF, symbols
from `dlopen`'d objects can participate in global interposition, so `reaper_reasampler.so`'s
`LICE_FillRect` and a hypothetical `reasampler_9000.so`'s `LICE_FillRect` are candidates to
bind to the same definition. That is a class of bug that presents as "the VST editor draws
with the extension's LICE build" and is invisible on Windows.
**Why hiding is safe here.** The two symbols that genuinely must be exported already say so
explicitly: `reaper_plugin.h:52` defines
`REAPER_PLUGIN_DLL_EXPORT __attribute__((visibility("default")))` on non-Windows, and
`main.cpp:251` uses it (`extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(...)`).
On the VST3 side, `fplatform.h:164` defines `SMTG_EXPORT_SYMBOL` the same way. SWELL's own
build already uses `-fvisibility=hidden` (`Makefile:20`), which is the precedent. The one
thing to confirm on a Linux box is that `swell-modstub-generic.cpp:135`'s `SWELL_dllMain`
survives — it carries its own `__attribute__ ((visibility ("default")))` at that line, so it
should, but the modstub is compiled by *our* build, not SWELL's.
---
### Λ-04 — nothing links a thread library, and `<pthread.h>` is in the extension's include graph
**Severity: Major (VST3 target) / Minor (extension). Effort: S — `find_package(Threads)` + `Threads::Threads`.**
**Citations.** `src/shell/panel/panel_state.h:2024`;
`vendor/vst3sdk/base/thread/source/flock.cpp:71133`;
`src/shell/instrument/CMakeLists.txt:1539`; `vendor/WDL/WDL/swell/Makefile:90`.
**What breaks.** No CMake file in the tree calls `find_package(Threads)` or links
`Threads::Threads`/`-lpthread`/`-ldl` (grep-verified across all 23). Two separate exposures:
- **Extension.** `panel_state.h:23` includes `<pthread.h>` on non-Win32. I did not find a
pthread *call* in the panel's own code, so this may be an include-only dependency that
needs no link flag on a modern glibc (where the pthread symbols are in libc proper). That
makes it Minor, and `[verify — Linux]`.
- **VST3 target.** `flock.cpp` is unambiguous: on non-Windows it calls
`pthread_mutexattr_init`/`_settype`/`_destroy` (`:7176`), `pthread_mutex_init` (`:74`),
`pthread_mutex_destroy` (`:89`), `_lock` (`:105`), `_unlock` (`:121`) and `_trylock`
(`:133`). It is unconditionally in the `vst3_sdk` source list
(`src/shell/instrument/CMakeLists.txt:27`). Any Linux VST3 target must link a thread
library. SWELL's own Makefile uses `-lpthread -ldl` (`:90`) for the same reason.
Adding `find_package(Threads REQUIRED)` + `target_link_libraries(... Threads::Threads)` is
correct on all three platforms and costs nothing on Windows.
---
### Λ-05 — `reaper_plugin.h`'s SWELL include resolves only by coincidence of the `-I` line
**Severity: Minor. Effort: S — a comment, or an `INTERFACE` target that carries both include dirs as one unit.**
**Citations.** `vendor/reaper-sdk/sdk/reaper_plugin.h:4254`; `CMakeLists.txt:6566`;
`src/app/CMakeLists.txt:56`; `src/shell/instrument/CMakeLists.txt:97`.
**What breaks — and, today, does not.** On non-Windows, `reaper_plugin.h:49` does
`#include "../WDL/swell/swell.h"`. Relative to the including file's own directory
(`vendor/reaper-sdk/sdk/`) that resolves to `vendor/reaper-sdk/WDL/swell/swell.h`, which does
not exist — listing `vendor/reaper-sdk/` shows only `README`, `reaper-plugins`, and `sdk`.
The include is nevertheless satisfiable, because the quoted-include fallback searches the
`-I` path, and `WDL_INC` is `${CMAKE_CURRENT_SOURCE_DIR}/vendor/WDL/WDL` (`CMakeLists.txt:66`) —
so `WDL_INC` + `../WDL/swell/swell.h` normalizes to `vendor/WDL/WDL/swell/swell.h`, which
exists. Both module targets pass `${WDL_INC}` (`src/app:56`, `src/shell/instrument:97`), so it
holds today.
The hazard is that this is a load-bearing coincidence with no comment anywhere, and it is
**invisible on Windows**`reaper_plugin.h:43` takes `<windows.h>` instead, so dropping
`${WDL_INC}` from any target that includes `reaper_plugin.h` would pass CI on Windows and
fail only on Linux/macOS. Worth pinning before more targets are added.
---
### Λ-06 — the build has no platform partition of its source list, so every T2 finding lands here as a CMake change
**Severity: Minor (structural). Effort: S per TU once T2 says which ones need it.**
**Citations.** `src/app/CMakeLists.txt:851` and `:7198`;
`src/shell/actions/drag_out_win.cpp:8`, `:249`, `:282`.
**Mechanism.** All 39 shell TUs are listed unconditionally (`:851`); the platform block at
`:7198` only *adds* sources, never removes or substitutes any. Every TU therefore enters a
Linux compile, and the only thing standing between the build and a compile error is each
file's own `#ifdef` discipline. Today that discipline is uneven in a way worth recording:
- `drag_out_win.cpp` is the model — `#ifdef _WIN32` at `:8` with a real
`// ---- macOS / Linux (SWELL)` branch at `:249` and `#endif` at `:282`.
- `arrange_drop_win.cpp` and `instrument_drop_win.cpp` contain **zero** occurrences of
`_WIN32` (grep count 0 each) and include no `<windows.h>` — they are `_win`-suffixed for
the surface they serve, not for a platform dependency, and appear portable by inspection.
This is not itself a defect; it is the reason the ordering sketch in §5 puts "read T2's
output, then partition the source list" as a distinct step rather than folding it into the
resgen work.
---
### Λ-07 — `std::filesystem` in a pure library, with no stated minimum compiler
**Severity: Minor. Effort: S — a `cmake_minimum_required`-adjacent compiler check or a documented floor.**
**Citations.** `src/core/capture/capture_paths.cpp:5` (`#include <filesystem>`) and `:95`
(`std::filesystem::path(rppPath).parent_path().string()`); `README.md:5054`.
**What breaks.** `capture_paths` is a *pure* library with its own CTest target
(`src/core/capture/CMakeLists.txt:12`), so this is on the test-harness path as well as the
extension's. GCC 8 requires an explicit `-lstdc++fs` for `std::filesystem`; GCC ≥ 9 and
libstdc++ ≥ 9 do not. `README.md:51` states only "A C++17 compiler" as the prerequisite, and
`cmake_minimum_required(VERSION 3.19)` (`CMakeLists.txt:1`) says nothing about the compiler.
On any current distro toolchain this is a non-issue; it becomes one on an older CI image.
Twelve other files in `src/shell/` also use `<filesystem>` (grep), but they are all in the
extension target, which shares the same fix.
---
### Λ-08 — the artifact path and the install instruction are both wrong for a single-config generator
**Severity: Minor. Effort: S — a doc fix, or an `install()` rule if packaging is ever automated.**
**Citations.** `src/app/CMakeLists.txt:68` (`LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}"`);
`README.md:130133`; `CLAUDE.md` §"Install / reload";
`docs/product/versioning-and-release.md:364366`.
**What breaks.** On a multi-config generator CMake appends the config directory, giving the
documented `build/Release/`. On Ninja/Make the module lands directly at
`build/reaper_reasampler.so`, so the README's "copy from `build/Release/`" instruction has no
matching directory on Linux. Compounding it, there is no `install()` rule anywhere in the 23
CMake files, and no target directory is documented for Linux at all — `CLAUDE.md` and
`README.md:132` say "REAPER's `UserPlugins/` folder (Options → Show REAPER resource path)",
which is a correct user-facing instruction but not a path a pipeline can use. I could not
confirm REAPER's Linux resource-path root from the SDK: grepping
`vendor/reaper-sdk/sdk/` for `UserPlugins` and `reaper_*.so` returns nothing — the
`reaper_*.dll|dylib|so` filename convention stated in `CLAUDE.md` is not documented in the
vendored headers. `[verify — Linux]`.
---
### Λ-09 — no warning flags anywhere, so the first GCC/Clang build surfaces a diagnostic set nobody has ever seen
**Severity: Minor. Effort: S to add flags; **unknowable** to fix what they reveal — that is the honest band.**
**Citations.** grep for `target_compile_options` across all 23 CMake files: zero hits (the
only `compile_definitions` hits are `SWELL_PROVIDED_BY_APP` at `src/app/CMakeLists.txt:80`,
`:92` and the SDK's `DEVELOPMENT`/`RELEASE` selector at
`src/shell/instrument/CMakeLists.txt:42`). `CMakeLists.txt:2830` for the absence of
`CMAKE_CXX_EXTENSIONS`.
**Mechanism.** The codebase has been compiled by exactly one compiler family. MSVC's default
is `/W1`; GCC/Clang's default is essentially silent. Adding `-Wall -Wextra` on the first
Linux build is the right move, but the size of the resulting diagnostic set over ~39k LOC is
not estimable from Windows, which is why the effort band is split. Do **not** add `-Werror`
in the same change.
Secondary, in the same family: `CMAKE_CXX_EXTENSIONS` is unset, so it defaults to `ON` and
GCC/Clang compile as `-std=gnu++17` rather than `-std=c++17`. Not a bug; a hygiene knob
worth flipping while the flags are being set, since it is the difference between "portable
C++17" and "whatever GNU extensions happened to be used".
---
### Λ-10 — the submodule story costs a Linux developer a large download for a target that is gated off
**Severity: Minor. Effort: S — a doc paragraph.**
**Citations.** `.gitmodules` (three entries, no `shallow`, no `branch`);
`README.md:7086`; `src/shell/instrument/CMakeLists.txt:9`; `CLAUDE.md` §"One-time submodule setup".
**Mechanism.** `README.md:7275` already warns that `git clone --recursive` pulls all of
`vendor/vst3sdk`'s nested submodules (`vstgui4`, `tutorials`, `doc`, `cmake`) that this
project never links, and prescribes the narrow init instead. What neither the README nor
`CLAUDE.md` says is that on Linux the `vendor/vst3sdk` submodule is **pure cost**: the
`WIN32 AND EXISTS` gate at `src/shell/instrument/CMakeLists.txt:9` omits the target
regardless, so `git submodule update --init vendor/reaper-sdk vendor/WDL` is the complete
Linux prerequisite. One sentence in the platform-support table would save every Linux
contributor the download.
---
## 3. Surfaces checked and found clean
Recorded so the plan author does not re-spend effort here. Each was read or grepped in this
pass.
- **All 23 `CMakeLists.txt` files were read in full**, not sampled: root; `src/app`;
`src/core`; `src/core/{audio,capture,instrument,json,model,reclaim,tracking,ui,util,version,view,wire}`;
`src/core/instrument/{bake,engine,map,note,ui}`; `src/core/instrument/engine/{filter,loop}`;
`src/shell/instrument`. Plus `cmake/reasampler_targets.cmake`. Of these, 21 are entirely
platform-neutral — they declare libraries and tests through the two helpers and nothing
else.
- **`CMAKE_POSITION_INDEPENDENT_CODE ON`** (`CMakeLists.txt:30`) — the usual Linux
`MODULE`-fed-by-`STATIC` trap, already handled.
- **`.so` naming.** `PREFIX ""` (`src/app/CMakeLists.txt:66`) + `SUFFIX ".so"` (`:93`) +
channel-derived `OUTPUT_NAME` (`:67`) yields `reaper_reasampler.so` /
`reaper_reasampler_beta.so`, matching the `reaper_*` convention. `ARCHIVE_OUTPUT_DIRECTORY`
(`:69`) is a Windows import-lib concern and harmlessly inert on Linux.
- **The channel fork is platform-independent.** `REASAMPLER_CHANNEL` (`CMakeLists.txt:4054`)
threads through `configure_file` (`:5861`) into names only; nothing in it is
Windows-specific, so beta builds fork identically on Linux.
- **The version padding canary** (`src/core/version/CMakeLists.txt:2142`) uses a
function-scoped `set()` + second `configure_file` + longhand `add_executable`. Portable;
no platform assumption.
- **The whole test corpus is platform-neutral.** 91 files in `tests/`, 91 declarations in
CMake. Only `test_capture_paths.cpp` branches on platform and it already has POSIX
expectations written; only `test_pitch_shift.cpp` mentions `windows.h`, in a comment.
- **Generator-shape handling in the docs is already correct.** `README.md:9398` explains
that `-C Debug` is required on multi-config and unnecessary on Ninja/Make. `ctest --test-dir
build` works bare on Linux; the `-C Debug` in the README's example is harmless there.
- **SWELL API coverage for the LICE slice** — all six SWELL GDI entry points LICE needs are
in the modstub-resolved table (`swell-functions.h:857`, `:863`, `:877`, `:885887`).
- **The LICE slice needs no fifth TU on Linux** (`lice.cpp:3040` colorspace table,
`lice_textnew.cpp:26` file-static `utf8makechar`).
- **`swell-modstub-generic.cpp` compiles against its own includes** — it uses `printf`
(`:61`), which arrives via `swell.h``swell-types.h:29` (`#include <stdio.h>`). The
`dlopen`/`readlink` block (`:69131`) is behind `SWELL_LOAD_SWELL_DYLIB`, which this build
never defines.
- **The Linux run-loop IIDs are already in the vendored VST3 slice.**
`public.sdk/source/common/commoniids.cpp:5054` defines `Linux::IEventHandler`,
`Linux::ITimerHandler` and `Linux::IRunLoop` under `#if SMTG_OS_LINUX`, and that file is
already in the `vst3_sdk` source list (`src/shell/instrument/CMakeLists.txt:36`). A Linux
VST3 port needs no additional SDK translation unit for the run loop.
- **`SMTG_OS_LINUX` / `SMTG_EXPORT_SYMBOL` are correct in the pinned SDK**
(`pluginterfaces/base/fplatform.h:132`, `:164`).
- **The MSVC-ABI caveat is Windows-scoped.** `reaper_plugin.h:22` warns that the C++
interfaces "require MSVC on win32, or at least the MSVC-compatible C++ ABI" — that is a
constraint on Windows toolchains (mingw), not on Linux, where REAPER and the extension both
use the Itanium ABI.
---
## 4. The VST3-on-Linux question, stated as a fork
Daniel rules on this. Below is the cost of each side with the evidence, and nothing else.
### Fork A — extension only on Linux
Scope: everything in §2 (Λ-01 through Λ-10). The VST3 gate at
`src/shell/instrument/CMakeLists.txt:9` stays exactly as it is; a Linux configure omits the
target and the `vendor/vst3sdk` submodule becomes optional there (Λ-10).
**Cost profile:** one Major that must be solved (Λ-01, the panel), two Majors that are
one-line-ish but load-bearing (Λ-02 optimization, Λ-03 visibility), one Major/Minor
(Λ-04 threads), and six Minors. No new third-party dependency. No new artifact format. No
change to any product decision on record.
**The unknown that dominates the band:** none of the extension's source portability is
audited here. The build-system work is small; whether the 39 shell TUs *compile and behave*
against SWELL's Win32 subset is T2's answer, and it, not this list, sets the phase's size.
### Fork B — extension + ReaSampler 9000 on Linux
Everything in Fork A, plus the following. Each item is a Blocker for the VST3 artifact —
without it there is no loadable `.vst3`.
- **B1 — the entry point is the wrong one, and the right one is already vendored.**
`src/shell/instrument/CMakeLists.txt:78` compiles
`public.sdk/source/main/dllmain.cpp` into the module. That file does
`#include <windows.h>` at `:41` with no `SMTG_OS_*` guard — grepping the file for
`SMTG_OS` returns nothing, and its only conditionals are
`#if defined(_MSC_VER) && defined(DEVELOPMENT)` (`:43`, `:96`) and `#ifdef __cplusplus`
(`:58`, `:87`). Linux needs
`public.sdk/source/main/linuxmain.cpp`, which is present in the slice and exports
`ModuleEntry`/`ModuleExit` (`linuxmain.cpp:4752`, `:5982`). Both are **mandatory** — the
SDK's own loader refuses the module without either
(`public.sdk/source/vst/hosting/module_linux.cpp:189204`, error strings "The shared
library does not export the required 'ModuleEntry' function" and the same for `ModuleExit`).
`GetPluginFactory` is checked at `:205`. **Effort S.**
- **B2 — the artifact is a directory bundle, not a file.** Today the target sets
`SUFFIX ".vst3"` on a MODULE (`src/shell/instrument/CMakeLists.txt:106111`), producing a
single file. `module_linux.cpp:146170` (`getSOPath`) shows what a Linux host actually
opens: the path must be a **directory**, containing `Contents/`, containing
`<machine>-linux/` where `<machine>` is `uname().machine` (`:158163`, helper at `:8897`),
containing `<stem>.so` (`:167168`). So `reasampler_9000.vst3/Contents/x86_64-linux/reasampler_9000.so`.
That is `add_custom_command(POST_BUILD)` work plus an install rule. Install roots are
`$HOME/.vst3/`, `/usr/lib/vst3/`, `/usr/local/lib/vst3/` and `$APPFOLDER/vst3/`
(`module_linux.cpp:282302`). `Contents/Resources/moduleinfo.json` is **optional**
`getModuleInfoPath` (`:356365`) returns empty when the file is absent rather than failing.
**Effort M** — mechanical, but it is a new artifact shape with an install story and a
per-architecture directory name.
- **B3 — the drawing stack is the real cost, and it is where Fork B stops being a build task.**
The VST3 module compiles the same four LICE TUs (`src/shell/instrument/CMakeLists.txt:80`),
and on non-Win32 LICE resolves its GDI through SWELL (§1a). But **a VST3 plugin is not a
REAPER extension**: `swell-modstub-generic.cpp`'s default branch exports `SWELL_dllMain`
(`:135`), which only REAPER's *extension* loader calls. Nothing hands a VST3 plugin the
SWELL function table. That leaves two routes:
- **Route B3a — `SWELL_LOAD_SWELL_DYLIB`** (`swell-modstub-generic.cpp:69131`): the stub
reads `/proc/self/exe`, strips to the directory, and `dlopen`s `libSwell.so` beside the
host binary (`:8198`), then calls `SWELL_set_app_main(SWELLAppMain)` (`:112114`). That
reference makes `SWELLAppMain` a **required definition in the plugin** — it is declared at
`swell-types.h:137` as "to be implemented by app". Whether REAPER's Linux install ships a
`libSwell.so` beside its executable is `[verify — Linux]`, and this route makes the VST3
load-bearing on being hosted by REAPER specifically, which is consistent with D5's
REAPER-only stance but is a new runtime coupling to state explicitly.
- **Route B3b — build SWELL into the module.** `vendor/WDL/WDL/swell/Makefile:100104`
enumerates the 13 SWELL TUs (`swell.o swell-ini.o swell-miscdlg-generic.o
swell-wnd-generic.o swell-menu-generic.o swell-kb-generic.o swell-dlg-generic.o
swell-gdi-generic.o swell-misc-generic.o swell-gdi-lice.o swell-generic-headless.o
swell-generic-gdk.o swell-appstub-generic.o swell-modstub-generic.o`) and `:106107` the 8
LICE TUs it needs alongside. Link requirements at `:90` (`-lpthread -ldl`), `:128130`
(pkg-config `gtk+-3.0` or `gdk-3.0`), `:145151` (`freetype2` + `-lfontconfig`) and
`:154` (`-lGL`).
**This is a wholly new third-party dependency surface** — GDK/GTK3, FreeType, Fontconfig,
OpenGL — that the extension deliberately avoids via `SWELL_PROVIDED_BY_APP`. Flagging it
as a scope expansion, per the dispatch's constraint; it is not something to slip in.
**Effort L either way**, and B3b is the larger of the two by a wide margin.
- **B4 — the editor's platform contract changes.** The view must report
`kPlatformTypeX11EmbedWindowID` (`pluginterfaces/gui/iplugview.h:79`) instead of
`kPlatformTypeHWND` (`:62`), and timers/file-descriptors must go through
`Steinberg::Linux::IRunLoop` (`iplugview.h:267279`) with `IEventHandler` (`:223229`) and
`ITimerHandler` (`:239245`) — the header states the reason at `:255256`: "On Linux the
host has to provide this interface to the plug-in as there's no global event run loop
defined as on other platforms." The whole `namespace Linux` block is `#if SMTG_OS_LINUX`
(`:209`, closed `:284`), so it compiles in automatically. The **build-side** cost is nil
(the IIDs are already vendored — see §3); the **source-side** cost is `editor_platform.cpp`
and every timer in the editor, which is T2's surface. **Effort: build S, source L.**
- **B5 — a settled product decision has to be reversed, in writing.** D5 ("Windows-only,
VST3-only, REAPER-only") is recorded as a locked decision with "a mac/Linux build" named
as a non-goal in `src/core/instrument/CLAUDE.md` (Invariants → the three locked decisions;
and again under Non-goals/guardrails) and `src/shell/instrument/CLAUDE.md`
(Non-goals/guardrails). `src/shell/panel/CLAUDE.md` carries a third dependent statement.
Fork B is not just more work — it invalidates three recorded invariants and needs Daniel's
explicit reversal before any code moves.
**The asymmetry worth naming.** Fork A's build work is small and its unknown is source
portability. Fork B's build work is genuinely large (B2, B3) *and* carries an unbounded
source-portability tail (B3b's dependency surface, B4's window/run-loop rewrite) *and*
requires reversing a settled decision. The two are not "the same phase, one bigger" — they
are different-shaped commitments.
---
## 5. Open questions
### `[verify — Linux]` — answerable by trying it on a Linux box
| # | Question | Exact check |
|---|---|---|
| V1 | Does the extension compile at all under GCC/Clang? | `cmake -B build -S . -G Ninja && cmake --build build 2>&1 \| tee build.log` — this is the single highest-information action available and gates most of T2's list too. |
| V2 | Does `ctest` pass all 91 targets? | `cmake -B build -S . -G Ninja && cmake --build build && ctest --test-dir build --output-on-failure` (no `-C` needed on Ninja). |
| V3 | Does REAPER's Linux build call `SWELL_dllMain` in a `reaper_*.so`, populating the API table? | Drop the built `.so` in `~/.config/REAPER/UserPlugins/`, restart, and check the extension's actions appear. A null table shows as immediate crash or dead actions. |
| V4 | Where exactly is `UserPlugins/` on Linux, and is `reaper_*.so` the right filename glob? | Options → Show REAPER resource path in a Linux REAPER. The SDK headers do not document either — grep of `vendor/reaper-sdk/sdk/` for `UserPlugins` and `reaper_*.so` returns nothing. |
| V5 | Does `panel_state.h:23`'s `<pthread.h>` actually require a link flag on the target glibc? | Build without `Threads::Threads`; if it links, it does not. |
| V6 | Does `-fvisibility=hidden` leave `ReaperPluginEntry` and `SWELL_dllMain` exported? | `nm -D --defined-only reaper_reasampler.so \| grep -E 'ReaperPluginEntry\|SWELL_dllMain'` after adding the preset. |
| V7 | With Λ-01 fixed by the resource-id-0 route, does `SWELL_CreateDialog` return a usable child HWND for the docker? | Panel toggle action in a Linux REAPER. |
| V8 | Does `std::filesystem` need `-lstdc++fs` on the chosen toolchain? | Build `capture_paths_tests` alone; the link error names it. |
| V9 | Does REAPER's Linux install ship a `libSwell.so` beside its executable (Fork B, route B3a)? | `ls $(dirname $(readlink -f $(which reaper)))/libSwell.so`. |
| V10 | Which `uname -m` values must the VST3 bundle carry (Fork B, B2)? | `uname -m` on each target — REAPER ships x86_64, aarch64 and armv7l Linux builds, and `module_linux.cpp:158163` keys the bundle directory off exactly this string. |
### `[Daniel]` — genuine product/scope calls
| # | Question | Why it is not answerable by reading code |
|---|---|---|
| D1 | **Fork A or Fork B?** Extension only, or extension + ReaSampler 9000 on Linux? | §4 states both costs. The choice is scope, and D5 makes it a reversal of a recorded decision, not a technical lookup. |
| D2 | If Fork B: **route B3a (dlopen the host's `libSwell.so`, requires defining `SWELLAppMain`) or B3b (vendor and build full SWELL, adds GDK/GTK3 + FreeType + Fontconfig + OpenGL)?** | B3b is a dependency-surface expansion; the dispatch requires that be flagged rather than chosen. B3a is cheaper but couples the plugin's UI to being hosted by REAPER specifically. |
| D3 | Is **macOS in or out** of the same phase? | Λ-01 is a *shared* macOS/Linux fix (both branches carry the identical broken comment block, `src/app/CMakeLists.txt:8386` vs `:9497`), and `swell_resgen.php` output is shared per `README.md:120`. Doing both at once is cheaper than doing them serially — but that is a scope call, and macOS additionally implies signing/notarization, which `versioning-and-release.md` does not mention anywhere. |
| D4 | Does **CI get built in this phase**, and on what runner? | `versioning-and-release.md:307311` and `:361363` hand "the pipeline" to dev-ops and commit to three platform artifacts per channel, but no CI exists in the repo on any platform. Adding it is infrastructure and needs an explicit ask. |
| D5 | Is the Linux artifact **shipped or developer-only** for 1.x? | Determines whether Λ-02 (Release build) and Λ-08 (install path) are must-fix or nice-to-have, and whether a Linux `install()` rule is worth authoring. |
Deliberately **not** listed as `[Daniel]`, because reading more would settle them: whether
`arrange_drop_win.cpp`/`instrument_drop_win.cpp` need platform gating (read the files — T2);
whether the SDK's `moduleinfo.json` is required (it is not — `module_linux.cpp:362364`);
whether the Linux run-loop IIDs need a new SDK TU (they do not — `commoniids.cpp:5054`).
---
## 6. Ordering sketch
Dependency order only — this is sequencing input for a plan author, not the plan.
**Stage 0 — the one action that reprices everything.** Run V1 and V2 on a Linux box against
the tree as-is. Configure will succeed (nothing in the CMake requires Windows); the compile
will produce a diagnostic set that is the actual size of this phase. Every band below is
provisional until this is done. Λ-10's doc fix rides along free, since the person doing this
will hit it first.
**Stage 1 — toolchain floor, no source dependency.** Λ-02 (`CMAKE_BUILD_TYPE` default),
Λ-03 (visibility presets), Λ-04 (`Threads::Threads`), Λ-09 (warning flags + `CXX_EXTENSIONS
OFF`), Λ-05 (pin the `WDL_INC` coincidence), Λ-07 (compiler floor). All are root- or
target-property edits with no ordering constraint among them and none touching source. Doing
them *before* Stage 2 means the resource work is done under warnings rather than retrofitted
into them. Ship as one change.
**Stage 2 — the panel resource decision.** Λ-01. This forks on the resource-id-0 question
(§2, Λ-01) and therefore needs T2's read on `panel_window.cpp` first — so it is gated on T2
output, not on Stage 1. Whichever route wins, it is a shared macOS/Linux fix (see D3).
**Stage 3 — source partition.** Λ-06. Once T2 names which TUs need a platform branch or a
non-Windows sibling, the source list in `src/app/CMakeLists.txt:851` gains its
partition. Strictly after T2 and after Stage 2 (the resgen/shim TU, if that route is taken,
is one of the entries).
**Stage 4 — packaging.** Λ-08, plus whatever D5 decides. Depends on Stage 1 (there is no
point documenting an install path for an unoptimized artifact) and on V4.
**Stage 5 — Fork B, only if D1 says so.** Internally ordered: B1 (entry point swap, S) →
B3 (SWELL strategy per D2 — this gates everything downstream, because a module that cannot
draw cannot be tested) → B2 (bundle layout + install, needs V10) → B4 (X11 embed + run loop,
mostly T2's source work). B5 (the D5 reversal) precedes all of them as a documentation act.
---
## 7. Referred to T2
Source-level items I noticed while reading build files. **Recorded, not analyzed** — the
parallel track owns each of these and I have deliberately not investigated further.
1. `src/shell/panel/panel_window.cpp:135``CreateDialogParam` + `MAKEINTRESOURCE(IDD_BANK_PANEL)`.
Under SWELL this is `SWELL_CreateDialog(SWELL_curmodule_dialogresource_head, …)`. Relevant
to Λ-01's resource-id-0 option, which requires the proc to be a `WNDPROC` returning
`LRESULT`, cast to `DLGPROC` (`swell-functions.h:606608`).
2. `src/shell/actions/arrange_drop_win.cpp` and `src/shell/actions/instrument_drop_win.cpp`
zero `_WIN32` occurrences, no `<windows.h>`, yet `_win`-suffixed. Confirm they are
genuinely portable, or add guards.
3. `src/shell/actions/drag_out_win.cpp:249282` — the `#else` "macOS / Linux (SWELL)" branch
exists but has never been compiled. Whether the SWELL drag path is functionally equivalent
to the Win32 `IDataObject`/`IDropSource` path is unaudited.
4. `src/shell/persist/prune_fs.cpp:36``<windows.h>` behind two `_WIN32` guards. Confirm the
non-Windows deletion path exists and is equivalent (this file is on the only
file-deletion path in the product, per `src/core/reclaim/CLAUDE.md`).
5. `src/shell/panel/draw_kit.cpp:1115`, `draw_kit.h:2729`, `src/shell/panel/panel_state.h:1826`
the `windows.h`-vs-`swell/swell.h` include switches. These look correct; whether the
SWELL subset actually covers every GDI call the kit makes is the question.
6. `src/shell/panel/CLAUDE.md` records "Windows-only (D5) — font/GDI/HFONT choices assume
Windows; no cross-platform font-fallback concern" as an invariant. Font selection on
Linux SWELL (FreeType/Fontconfig-backed) is the concrete instance.
7. `src/core/capture/capture_paths.cpp``normalizeSlashes` lowercases on `_WIN32` only, and
`tests/test_capture_paths.cpp:1946` already encodes the case-sensitive POSIX contract.
The test is written; the behaviour under a case-sensitive filesystem is unverified.
8. `src/shell/instrument/reasampler_editor.h:30` — unguarded `#include <windows.h>`. Only
matters under Fork B.