57 KiB
Λ-W1 Track 2 — source/runtime Linux-portability audit
Static analysis of src/**/*.{cpp,h} (293 files) plus the vendored SWELL / WDL / LICE /
REAPER-SDK / VST3-SDK headers those files consume, 2026-08-02, branch
pl-w1-t2-source-runtime-audit (cut from dev at 6e937b9). Answers one question: what in
the source blocks or degrades a Linux build/run, for the extension (reaper_reasampler) and,
separately, for the ReaSampler 9000 VST3 instrument (reasampler_vst).
Build-system surface (CMake, cmake/, resgen as a build step, vendor configuration,
packaging, install) is a parallel track's; anything spotted here is listed under
§7 Referred to T1 rather than analyzed. macOS is out of scope except where a Linux fix
shares its path — noted inline, one line, never a separate finding.
Evidence discipline. Every capability claim about SWELL / LICE / the REAPER SDK / the VST3
SDK cites the vendor file it was read in. The author is on Windows and cannot build or run on
Linux: nothing below was compiled or executed on Linux. Claims that need a Linux box carry
[verify — Linux] with the exact check. Line numbers were re-read against the working tree
immediately before writing.
1. Baseline — the portability posture today, by layer
1.1 core/ — genuinely portable. Verified, not assumed.
Two independent checks, both exhaustive over the directory:
- Include surface. Every
#includeinsrc/core/**is either a"core/…"sibling, one of 26 standard-library headers, or the CMake-generatedversion_generated.h(src/core/version/app_version.cpp:12,src/core/wire/reasampler_uid.h:13). Zero REAPER, SWELL, WDL, LICE, VST3-SDK,windows.h, or other vendor includes. The 26 headers are<algorithm> <array> <atomic> <cassert> <cctype> <cerrno> <climits> <cmath> <cstddef> <cstdint> <cstdio> <cstdlib> <cstring> <filesystem> <fstream> <limits> <map> <optional> <set> <sstream> <string> <type_traits> <unordered_map> <unordered_set> <utility> <vector>— all C++17, all present on libstdc++/libc++. - Preprocessor surface. The whole of
core/contains exactly nine preprocessor conditional lines, in three places: the channel fork (core/wire/reasampler_uid.h:29,34,39), twoNDEBUGassert guards (core/view/view_mode_model.cpp:326,328,331,334), and one platform conditional —core/capture/capture_paths.cpp:18–20, the Windows-only case-fold innormalizeSlashes. That is the entire platform-dependence of the pure core.
Byte-order is explicitly handled rather than assumed: core/wire/bytes.h:26–31,62–74 builds and
reads little-endian by shift, not by memcpy of a native integer, so it is byte-order-neutral
by construction. core/capture/wav_codec.h:52–53 states the one real assumption ("target is
x86/ARM-LE only, no big-endian byte-swap") and wav_codec.cpp:165–168 memcpys floats under it —
correct on x86-64 and aarch64 Linux alike.
Sweeps that returned nothing across src/core and src/app: sprintf_s/strcpy_s/
_stricmp/_snprintf/_alloca/__forceinline/__declspec/#pragma comment/#pragma warning/
#pragma pack/__int64/LPSTR/LPCSTR/LPWSTR/wchar_t/TCHAR/_T(/MAX_PATH. There is
no #pragma of any kind anywhere in src/** other than #pragma once, and no
__attribute__ / __declspec / anonymous-struct extension.
long appears in a handful of pure modules (core/ui/card_meta.cpp:39–41,55,
core/instrument/ui/envelope_overlay.cpp:45). All are local computations from clamped doubles;
LP64's wider long is strictly safer than LLP64's. One stale comment
(core/instrument/ui/envelope_overlay.cpp:19, "a huge t would overflow a 32-bit long") is
Windows-specific but the clamp it describes runs regardless. Not a defect.
Verdict: core/ builds and behaves identically on Linux. The one conditional
(capture_paths.cpp:18) is correct for Linux — case-sensitive paths must not be folded — and
the pure tests already assert both branches (tests/test_capture_paths.cpp:19–33,41–51), which
is direct evidence the pure layer was written with a non-Windows target in mind.
1.2 tests/ — portable.
91 test TUs. Only tests/test_capture_paths.cpp forks on _WIN32, and it asserts the
non-Windows behaviour explicitly (lines 28–33, 50–51). A C:\/C:/ grep over tests/ hits
six files total, one of which is test_capture_paths.cpp itself; the five OTHER files — every
hit opaque string data fed to a pure function
(test_bank_model.cpp:186–187,243–245, test_bake_wire.cpp:77, test_drag_out.cpp:291–293,388,
test_origin_ledger.cpp:56, test_sample_usage.cpp:502) — are not platform behaviour. They pass
identically on Linux.
1.3 app/ — portable.
src/app/main.cpp is one TU of pointers + entry + dispatch. REAPER_PLUGIN_DLL_EXPORT and
REAPER_PLUGIN_HINSTANCE are already GCC/Clang-correct in the SDK
(vendor/reaper-sdk/sdk/reaper_plugin.h:52–53 → __attribute__((visibility("default"))) and
void *), and REAPER_PLUGIN_ENTRYPOINT is platform-neutral (:56). No Win32 call, no Win32
type, no conditional. The one hazard is behavioural, not structural — see L2-03.
1.4 shell/ — the whole cost sits here, and it is unevenly distributed.
65 platform-token lines across 30 of the 293 source files (grep:
_WIN32|WIN32|__APPLE__|_MSC_VER|__linux__|__GNUC__|_WIN64 over src/**/*.{cpp,h}). Their
distribution is the story:
| Area | Lines | Shape |
|---|---|---|
shell/instrument/ (VST3 editor + embed) |
51 of 65 | Whole-TU #ifdef _WIN32 … #endif — Windows-only by design |
shell/panel/ |
8 | Include-selection + two small #else bodies |
shell/persist/, shell/capture/, shell/actions/ |
4 | Trash fallback, localtime_r, the SWELL drag-out branch |
core/ |
2 | The case-fold |
Two shell/ directories are absent from this table because they were swept and came back
completely clean, not because they went unchecked: shell/view/ and shell/bank_ops/ both
return zero hits on the same grep (confirmed). Naming them here, since the table above is
by hit-count and gives a reader no way to tell a zero-hit sweep from an unswept directory.
src/resource.h and src/ext_keys.h mention platform names only in comments — zero hits on
this specific grep (re-run and confirmed), so they are not a row of this distribution; see
resource.h/resource.rc under §7 for their actual (build-system) relevance.
The extension's guards are all complete: every #ifdef _WIN32 that gates a definition
has an #else (draw_kit.cpp:12, capture.cpp:218, panel_audition.cpp:36,58,
panel_state.h:20, prune_fs.cpp:169, drag_out_win.cpp:8); the ones without an #else
gate only an #include or a single statement (panel_window.cpp:19,148, prune_fs.cpp:35,
draw_kit.h:27, capture_paths.cpp:18). There is no undefined-symbol gap from this guard
structure — every #ifdef that needs an #else has one. This is narrower than "no
undefined-symbol gap in the extension" full stop: L2-02's GetCurrentProcessId
(instrument_drop_win.cpp:59) is an undefined symbol on Linux precisely because it carries
no #ifdef at all, so it falls outside what this guard-completeness check measures.
The instrument's guards are complete in the opposite direction: reasampler_editor.h:156–269
wraps the entire paint/input/timer/wndProc/invalidate family in #ifdef _WIN32, and
editor_platform.cpp:298–306 stubs only the three IPlugView overrides that must exist. Every
invalidate() call site is inside a guard (verified exhaustively over src/shell/instrument/:
53 call sites across eight files — editor_input.cpp (5), editor_input_browse.cpp (11),
editor_input_chrome.cpp (10), editor_input_curve.cpp (4), editor_input_deck.cpp (9),
editor_input_waveform.cpp (4), editor_platform.cpp (3, excluding the definition itself at
:65), and editor_session.cpp (7, at 119, 126, 139, 141, 150, 156, 174, excluding a
same-named mention inside a comment at :106) — each file whole-file- or whole-region-guarded
by #ifdef _WIN32 (e.g. editor_input_browse.cpp:8 … :177); reasampler_editor.h:266 is
the declaration, not a call site. editor_controls.cpp, editor_models.cpp,
instrument_bake.cpp have zero call sites, confirmed). So the editor links off Windows — it
just does nothing.
1.5 What the platform layer actually provides (vendor-verified)
The extension's non-Windows path is SWELL-provided-by-host
(SWELL_PROVIDED_BY_APP → function pointers resolved through SWELL_dllMain,
vendor/WDL/WDL/swell/swell-modstub-generic.cpp:135–145). [verify — Linux] What is stated
here about REAPER's own libSwell.so is only evidence about WDL's own Makefile, not about what
Cockos actually ships — vendor/WDL/WDL/swell/Makefile:100–107,141–151 shows
swell-gdi-lice.o/-DSWELL_LICE_GDI built whenever GDK is enabled (ifndef NOGDK, :111),
with -DSWELL_FREETYPE gated behind ifndef NOFREETYPE (:144) and -lfontconfig gated a
level deeper behind ifndef NOFONTCONFIG (:149) — i.e. the Makefile makes the LICE-backed,
freetype/fontconfig SWELL the default build, not a certainty about the binary REAPER
distributes. Every downstream claim built on "REAPER's Linux SWELL is the fontconfig-backed
GDK build" inherits this same unstated assumption — that includes L2-09's entire mechanism,
the GDK-backend evidence cited for L2-07, and the cursor (GDK_*) / modifier-key
(GetAsyncKeyState) claims below, none of which are re-flagged individually; this paragraph is
their one shared source of doubt. Contrast with the null stub in swell-gdi-generic.cpp
(#ifndef SWELL_PROVIDED_BY_APP, line 24), which is not in play for a REAPER-hosted extension
either way.
Everything the panel layer calls was checked by name against
vendor/WDL/WDL/swell/swell-functions.h and swell-types.h. Present and real:
- Window/dialog:
SWELL_CreateDialogbehind theCreateDialogParammacro (swell-functions.h:616,619),DestroyWindow,SetFocus,GetFocus,GetParent,GetCapture/SetCapture/ReleaseCapture,IsWindowVisible,InvalidateRect,ScreenToClient/ClientToScreen,BeginPaint/EndPaint,SetTimer/KillTimer,GetWindowLong/SetWindowLong(:319–320),SetWindowPos(:286). - Messages/macros:
GET_X_LPARAM,GET_Y_LPARAM,HIWORD,MAKEINTRESOURCE(swell-types.h:1220),PAINTSTRUCT,MSG(:262),SRCCOPY,WM_PAINT,WM_MOUSEMOVE,WM_MOUSEWHEEL,WM_CAPTURECHANGED,WM_DROPFILES,WM_KEYDOWN,WM_TIMER,WM_ERASEBKGND,TPM_RETURNCMD,MF_*,MB_*,ID*, everyVK_*used. WM_MOUSEWHEELreally does carry screen coords inlParamon the GDK backend — the comment atpanel_window.cpp:100–102is correct (swell-generic-gdk.cpp:1442).WM_CAPTURECHANGEDreally is delivered on capture loss (swell-generic-gdk.cpp:1795,swell-wnd-generic.cpp:7141,7150), so the panel's rollback handler (panel_window.cpp:90–98) has a live trigger.- Cursors:
LoadCursor→SWELL_LoadCursor(swell-functions.h:728–730),SetCursor(:740–741), and all seven IDCs the panel uses map to real GDK cursors —IDC_ARROW→GDK_LEFT_PTR,IDC_HAND→GDK_HAND1,IDC_UPARROW→GDK_CENTER_PTR,IDC_SIZEWE→GDK_RIGHT_SIDE,IDC_SIZEALL→GDK_FLEUR,IDC_IBEAM→GDK_XTERM,IDC_NO→GDK_PIRATE(swell-generic-gdk.cpp:3736–3748vspanel_drag.cpp:141–147,159–161). GetAsyncKeyStatereturns the0x8000high bit forVK_CONTROL/VK_SHIFT/VK_MENU(swell-generic-gdk.cpp:2439–2441), sopanel_state.h:409–411works verbatim.GetTickCountis SWELL-provided (swell-functions.h:81), sopanel_drag.cpp:308,324— the only unguarded Win32-looking calls in the panel — are fine.SWELL_InitiateDragDropOfFileListdoes exist on Linux, and is a GDK implementation on the GDK backend:swell-generic-gdk.cpp:3563–3592sets up a hidden drop-source window and spins a nestedSWELL_RunMessageLoopuntil capture drops, with a 500 ms no-motion timeout; the actualgdk_drag_begincall is inside the shareddropSourceWndProchelper it invokes (:3446), not textually inside the3563–3592range itself. The headless backend defines the same-named function as an empty no-op stub —swell-generic-headless.cpp:246–248isvoid SWELL_InitiateDragDropOfFileList(...) { }— so the "not a stub" framing only holds for the GDK backend specifically. The two backends are mutually exclusive (swell-generic-headless.cpp:28is#ifndef SWELL_TARGET_GDK), so nothing breaks in a GDK build, but a reader should not take "does exist on Linux" to mean "is implemented in every Linux SWELL backend." macOS (swell-dlg.mm:3534) also defines it; it is declared atswell-functions.h:1011.- LICE itself is portable:
lice.h:31includesswell-types.hoff Windows,lice.cpp:22includesswell.h,LICE_SysBitmap::__resizehas a SWELL framebuffer path (lice.cpp:179–182), andLICE_CachedFont::DrawTextImplcarries real non-Windows branches (lice_textnew.cpp:236–249,829,1027–1028,1040).
Absent from SWELL, verified by grep over vendor/WDL/WDL/swell/: FF_DONTCARE (zero hits
anywhere in vendor/WDL/), GetCurrentProcessId (only GetCurrentThreadId,
swell-functions.h:822), GetKeyState, WM_MOUSELEAVE/TrackMouseEvent, DragAcceptFiles,
RegisterClass*, CreateWindowEx*, DefWindowProc*, MoveWindow,
GetWindowLongPtr/SetWindowLongPtr, WHEEL_DELTA, and any move-to-trash surface.
2. Findings
L2-01 — FF_DONTCARE does not exist off Windows; draw_kit.cpp will not compile
Location: src/shell/panel/draw_kit.cpp:73 (DEFAULT_PITCH | FF_DONTCARE), reached on
non-Windows through draw_kit.cpp:12–16 (which includes swell/swell.h, not windows.h).
Mechanism. swell-types.h defines DEFAULT_PITCH, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FW_BOLD, FW_NORMAL,
FW_SEMIBOLD, TRANSPARENT and every DT_* the kit uses — but not FF_DONTCARE. A grep
for FF_DONTCARE over the whole of vendor/WDL/ returns nothing; on Windows it comes from
<wingdi.h> via windows.h. draw_kit.cpp is not platform-guarded (only its include is), so
the CreateFont call is compiled on every platform.
Severity: Blocker — error: 'FF_DONTCARE' was not declared in this scope at
draw_kit.cpp:73; draw_kit is linked into both loadable modules, so the extension does not
build at all.
Effort: S — the argument is DEFAULT_PITCH | FF_DONTCARE, and FF_DONTCARE is 0x00 in
wingdi.h; the family bits are advisory to Windows' font mapper and meaningless to fontconfig.
Direction. Drop the | FF_DONTCARE term, or define it locally in the non-Windows include
branch. Do not add windows.h.
L2-02 — GetCurrentProcessId() is called with no platform branch and SWELL does not export it
Location: src/shell/actions/instrument_drop_win.cpp:59 (temp .vstpreset filename). The TU
has no _WIN32 conditional anywhere (verified: its only #includes are <atomic> <cstdint> <filesystem> <fstream> <string> <system_error> <vector> plus project/SDK headers, lines 6–27).
Mechanism. On non-Windows the declaration would have to come from reaper_plugin.h →
swell.h. swell-functions.h declares GetCurrentThreadId (:822) and no
GetCurrentProcessId; the only two occurrences in the whole WDL tree are inside
WDL/shm_msgreply.cpp:26 and WDL/win32_utf8.c:244, neither of which is a SWELL export.
Severity: Blocker — error: 'GetCurrentProcessId' was not declared in this scope; the
extension does not build.
Effort: S — the PID exists only to keep two concurrent REAPER instances from colliding in
the shared temp dir (comment at :56–57). getpid() behind a guard, or the already-imported
GetCurrentThreadId() plus the existing atomic counter, satisfies the same requirement.
Direction. Replace with a platform-neutral uniqueness source; the atomic counter at :52
already carries the intra-process half.
L2-03 — REAPERAPI_LoadAPI is all-or-nothing over ~869 API names and fails silently
Location: src/app/main.cpp:292–293 (if (REAPERAPI_LoadAPI(rec->GetFunc) != 0) return 0;).
main.cpp does not define REAPERAPI_MINIMAL, so the full table is loaded
(vendor/reaper-sdk/sdk/reaper_plugin_functions.h:44–48; 1738 REAPERAPI_WANT_ guards ≈ 869
entries).
Mechanism. The SDK's loader accumulates one failcnt across the entire table
(reaper_plugin_functions.h, REAPERAPI_LoadAPI body: failcnt += !(*table[i].dest = getAPI(table[i].name)); return failcnt;). If a Linux REAPER build does not export any single
one of those 869 names, main.cpp returns 0 and the extension never loads — with no console
message, no log line, nothing. The SDK header carries no "Windows only" annotation on any
entry (grep for windows only|win32 only|not on mac|not on linux returns nothing), so nothing
here says a gap exists — but nothing rules it out either, and the failure mode is maximally
opaque.
Severity: Blocker (conditional) [verify — Linux] — a silent load refusal with no
diagnostic is the worst possible first-run experience for a port. Scope note: the
all-or-nothing mechanism itself is identical on Windows too
(reaper_plugin_functions.h:45–46 — "an older version of REAPER may not succeed in loading",
no platform fork in the loader) — nothing about the mechanism is Linux-specific, only whether
this particular build happens to be missing an entry is unknown. Graded here as a Linux
Blocker on failure-mode quality alone (per the Mechanism above), the same axis L2-04 is
graded on below, despite L2-04's mechanism being certain rather than speculative — see L2-04's
calculus note. Arguably this belongs as a cross-platform robustness note rather than a
Linux-exclusive finding; left here because the fix is trivial and worth doing regardless of
platform.
Effort: S — confirmed against the two Direction options below: the ShowConsoleMsg
diagnostic is one new line on the existing failure branch (main.cpp:293), and the
REAPERAPI_MINIMAL swap follows a pattern already used elsewhere in this codebase
(panel_window.cpp:26–31, panel_audition.cpp:12–16) rather than inventing a new one.
Direction. Either switch main.cpp to REAPERAPI_MINIMAL + an explicit WANT list (the
other TUs already do this — e.g. panel_window.cpp:26–31, panel_audition.cpp:12–16), or keep
the full load but print the failure count via rec->GetFunc("ShowConsoleMsg") before returning
0. The minimal list is also the honest inventory of what this extension actually needs.
L2-04 — every persisted floating-point number is LC_NUMERIC-dependent, in both directions
Location (writers): src/core/json/json.cpp:39–43 (%.17g — the bank index, view model and
tracking ledger all serialize through it), src/core/model/provenance.cpp:40 (%.17g — the
provenance blob inside the bank JSON). Two writers, not three: tail_control.cpp:74 is a
comment describing the format ("Byte-identical to the former snprintf writer:
{"mode":%d,"manualMs":%.17g}"), not a write site — the actual per-project tail-setting write
is tail_control.cpp:78–79, which already goes through json::numToStr (the same
json.cpp:39–43 codec cited above), so it is the same writer, not a third one.
Location (readers): src/core/json/json.cpp:190–196 (std::strtod),
src/core/wire/wire.cpp:121–130 (Cursor::fieldDouble, std::strtod),
src/core/capture/render_settings.cpp:178–186 (std::stod over REAPER's own P_RAZOREDITS).
Mechanism. snprintf("%.17g"), strtod and stod all honour LC_NUMERIC. Under a
comma-decimal locale the writers emit 1,5, which makes the bank JSON structurally invalid
(an extra separator inside an object) — the whole index fails to parse on the next load. The
readers are honestly fail-closed (all three require whole-token consumption:
json.cpp:194, wire.cpp:127, render_settings.cpp:184), so they degrade to "malformed"
rather than silently truncating — but that means a razor range or a bank field simply
disappears. On Windows the CRT's start-up locale is "C" and nothing in this codebase calls
setlocale, which is why this has never fired. On Linux the process locale is far more likely
to be set by something else in the address space: SWELL's GDK backend calls gtk_init_check
when built with SWELL_SUPPORT_GTK (swell-generic-gdk.cpp:366; the #else branch uses
gdk_init_check, :368) and never calls gtk_disable_setlocale (grep over
vendor/WDL/WDL/swell/ returns no hit), and any GTK/Qt-based plugin loaded into the same
process can do the same.
Severity: Major — builds and runs; under a non-C LC_NUMERIC the bank index is written
unparseable and the project's whole bank is lost on reload. Calculus note: unlike L2-03
(graded Blocker above on zero direct evidence, purely on failure-mode quality), this finding's
mechanism is confirmed by reading the actual writer/reader call sites, and its failure mode —
the entire bank index becomes unparseable — is at least as severe as L2-03's. It is graded only
Major here because it additionally requires a non-C LC_NUMERIC in the hosting process, a
likelihood factor L2-03 does not apply to its own claim. Reading both on the same axis (either
both by failure-mode quality, or both by likelihood) would put them closer together than
Blocker/Major suggests; resolving that is a scope call for whoever prioritizes the two, not a
fact this audit can settle statically.
Effort: M — two writers and three readers, all in core/, all unit-testable; the fix is
a locale-independent path (std::to_chars/std::from_chars, C++17, or an explicit
std::locale::classic()-bound stream), not a setlocale call in a plugin.
Direction. Make the number codec locale-independent at its two writers and three readers;
add a pure test that pins the emitted text for a fractional value. Do not "fix" this by
calling setlocale — an extension must not mutate the host's locale.
[verify — Linux] — read LC_NUMERIC inside a running REAPER-Linux process (e.g.
ShowConsoleMsg(setlocale(LC_NUMERIC, nullptr))) before sizing the work.
L2-05 — prune loses the Recycle Bin and the "file is locked" backstop at the same time
Location: src/shell/persist/prune_fs.cpp:167–209; the non-Windows branch is :200–208.
Mechanism. Two Windows properties are load-bearing for the deletion authority and neither
survives. (a) The Windows path routes through SHFileOperationW + FOF_ALLOWUNDO (:186–188)
— deletions are recoverable from the Recycle Bin; the #else is a hard fs::remove (:203).
The header already states this honestly (:30–34, :162–166). (b) Less obviously, the failure
taxonomy at :205 (if (ec) return false; // real failure (locked/permission) -> skip) encodes
Windows file-sharing semantics: a bank file currently open by REAPER's audio engine (an
active PCM_source, an item playing from it) cannot be deleted on Windows, so it is counted as
"skipped". On Linux unlink() on an open file succeeds — the directory entry vanishes while the
open fd keeps playing, and when the fd closes the bytes are gone with no trash to recover from.
So the Linux prune can delete a file that is audibly in use, silently, and the user's only
recovery floor (the superseded-file-survives-until-prune rule, root CLAUDE.md §resample bake)
now has nothing under it.
Severity: Major — irreversible user-data loss where the Windows build is recoverable; the
"locked" skip branch is dead code on Linux.
Effort: M — trash is genuinely non-portable, but the XDG trash spec (~/.local/share/Trash
with a .trashinfo sidecar) is a self-contained move-plus-metadata write, and the confirm gate
already exists upstream.
Direction. Either implement an XDG-trash move in the #else (preferred — the deletion
authority is a single ~40-line function and this is exactly where the platform seam belongs), or
make the prune confirmation text platform-aware so a Linux user is told the deletion is
permanent. Do not leave the current silent asymmetry.
L2-06 — the docked panel never opens on Linux, and fails silently when it doesn't
Location: src/shell/panel/panel_window.cpp:135–137.
Mechanism. CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_BANK_PANEL), …) maps on SWELL to
SWELL_CreateDialog(SWELL_curmodule_dialogresource_head, (resid), …)
(swell-functions.h:616,619) — it resolves the template out of a per-module registry populated
by the resgen-generated source, not out of a linked .rc. That generated source is not
currently part of the Linux target (the target_sources line is commented out;
build-system detail → §7). The source-side consequence is what matters here: SWELL_CreateDialog
returns nullptr, panel_window.cpp:137 does if (!g_panel.hwnd) return;, and the toggle
action is a silent no-op — no console line, no message box, and bankPanelIsOpen() keeps
reporting false so the Actions-list checkmark never lights. The user's only symptom is "the
button does nothing." Note also that MAKEINTRESOURCE becomes ((const char*)(UINT_PTR)(x)) on
SWELL (swell-types.h:1220), so the id is a pointer-shaped integer — the resgen output must
agree on IDD_BANK_PANEL == 1000 (src/resource.h:8).
Severity: Major — the extension loads, every action works, and the primary UI surface is
absent with no diagnostic.
Effort: S on the source side (one failure branch), separate from T1's resgen wiring.
Direction. Add a one-line ShowConsoleMsg on the !g_panel.hwnd path naming the missing
dialog resource. That single line converts a mystery into a two-minute diagnosis and is worth
having on Windows too.
L2-07 — file-drop ingest onto the panel has no opt-in on SWELL
Location: src/shell/panel/panel_window.cpp:145–150 (the DragAcceptFiles call is
#ifdef _WIN32), handler at :47–61,65–67.
Mechanism. The comment at :145–147 is accurate — SWELL exposes no DragAcceptFiles
(grep over vendor/WDL/WDL/swell/ finds only DragQueryFile and DragFinish,
swell-functions.h:1006–1007). What the comment does not say is how a drop would arrive
instead. Reading the GDK backend: on a URI-list selection-notify, SWELL resolves the top-level
window, walks down with ChildWindowFromPoint (which is descending — for(;;) loop,
swell-wnd-generic.cpp:6956–6981), and SendMessage(cw, WM_DROPFILES, (WPARAM)gobj, 0)
(swell-generic-gdk.cpp:1622). The default child proc forwards an unhandled WM_DROPFILES
up to the parent only when the window lacks WS_EX_ACCEPTFILES
(swell-wnd-generic.cpp:7681) — but that bit's only effect is to suppress the up-forward;
it does not enable acceptance, and the message already reaches the window under the pointer via
the ChildWindowFromPoint descent regardless of the bit's state. So the panel's own dlgProc
plausibly receives the drop without any opt-in at all, but the earlier "ex-style bit, not
an API call" framing was backwards about what the bit does. The real SWELL opt-in surface for
WS_EX_ACCEPTFILES is the dialog resource, not a runtime call: swell_resgen.pl:10 and
swell_resgen.php:204 translate a dialog's WS_EX_ACCEPTFILES style to
SWELL_DLG_WS_DROPTARGET, and swell-dlg-generic.cpp:320–321 sets
h->m_exstyle |= WS_EX_ACCEPTFILES from that flag at dialog-creation time. src/resource.rc:19
declares STYLE WS_CHILD only — no WS_EX_ACCEPTFILES — so the bit is off on Linux regardless
of anything panel_window.cpp does at runtime. That makes L2-07's real seam src/resource.rc +
resgen, the SAME work as L2-06 and §7 bullet 1, not an independent one-liner: a runtime
SetWindowLong(..., GWL_EXSTYLE, ... | WS_EX_ACCEPTFILES) would only suppress the
unhandled-drop forward-to-parent — it would not change whether the drop reaches dlgProc in
the first place, which already happens via the ChildWindowFromPoint descent independent of
the bit. Note also the doc's own conclusion is better-supported than it knew: src/resource.rc:21–22
is BEGIN/END — zero child controls — so the GDK ChildWindowFromPoint descent has nowhere
to land but the panel HWND itself. Two supporting details still check out: SWELL's
DragQueryFile reads the same DROPFILES layout the handler assumes
(swell-wnd-generic.cpp:7730+), and SWELL's DragFinish is a documented no-op ("caller will
free hdrops", :7725–7728) while SWELL itself GlobalFrees the handle right after
SendMessage (swell-generic-gdk.cpp:1623–1624) — so panel_window.cpp:59 calling
DragFinish is safe, not a double-free.
Severity: Major [verify — Linux] — if the routing does not reach a docked child dialog,
one of the three ingest surfaces (file drop onto the bank panel) is silently dead with no error.
The evidence above says it probably works; it is not proof, and it cannot be exercised until
the panel itself renders (T1's resgen wiring — see L2-06).
Effort: no independent source-side fix exists — this is the same resgen work already
costed under L2-06 / §7 bullet 1. Do not add an Effort: S line here; adding the
SetWindowLong call would compile, do nothing observable, and send an implementer looking for
a bug that isn't where they'd look.
Direction. No separate fix. Once the resgen work lands the panel dialog with
WS_EX_ACCEPTFILES set from the resource, verify: [verify — Linux] drop a WAV onto the
docked panel and confirm WM_DROPFILES reaches dlgProc.
L2-08 — the VST3 instrument has no Linux editor, and no automatable parameters to fall back to
Location: src/shell/instrument/editor_platform.cpp:37–42 (isPlatformTypeSupported
returns kResultTrue only for kPlatformTypeHWND, and only inside #ifdef _WIN32);
:298–306 (the non-Windows stubs); reasampler_editor.h:156–269 (the entire paint/input family
is #ifdef _WIN32); reasampler_embed.cpp:135–141 (REAPER_FXEMBED_WM_IS_SUPPORTED returns 0
off Windows). reasampler_processor.cpp:360–365 still hands the host a ReaSamplerEditor.
Mechanism. The VST3 SDK does support Linux — kPlatformTypeX11EmbedWindowID
(vendor/vst3sdk/pluginterfaces/gui/iplugview.h:79), Linux::IRunLoop with
registerEventHandler/registerTimer (:267–279), Linux::IEventHandler (:223),
Linux::ITimerHandler (:239), and a linuxmain.cpp module entry
(vendor/vst3sdk/public.sdk/source/main/linuxmain.cpp). The instrument simply does not
implement any of it. The consequence compounds: with isPlatformTypeSupported false for every
type, the host falls back to a generic parameter UI — and a grep for
addParameter|parameters\.add|getParameterCount across src/shell/instrument/*.cpp returns
nothing. Zero VST3 parameters are registered as of this branch's base. A Linux user would
get an instrument with no editor and no controls whatsoever. (Phase Γ-W4-T1
vst3-parameter-set — docs/product/parameter-automation.md — is landing the parameter set
concurrently, which materially improves this fallback; the editor gap is unaffected.)
The specific Win32 dependencies a Linux editor would have to replace, each verified absent from
SWELL: RegisterClassW/CreateWindowExW/DefWindowProcW (editor_platform.cpp:79–103,294) —
SWELL has no window-class model at all, only SWELL_CreateDialog and raw HWND__ construction;
MoveWindow (:133) — SWELL has SetWindowPos (swell-functions.h:286) instead;
GetWindowLongPtr/SetWindowLongPtr (:74,105,142) — SWELL has the non-Ptr forms returning
LONG_PTR (:319–320); TrackMouseEvent/WM_MOUSELEAVE (:176–198) — SWELL has neither, a
gap the panel layer already documents at panel_state.h:245–246; GetKeyState
(editor_input_waveform.cpp:38, editor_input_curve.cpp:50) — SWELL has only
GetAsyncKeyState (swell-functions.h:712); DragAcceptFiles/DragQueryFileW
(:109,283); GetModuleHandle (:75).
Severity: Major (instrument only) — the plugin would load and process audio; it would be
unplayable and uneditable.
Effort: L — an X11-embed IPlugView (attachedToParent receiving an X11 window id rather
than an HWND), an IRunLoop-driven timer replacing SetTimer/WM_TIMER, an event-driven
input path replacing the wndProc switch, and a LICE surface bound to that window. This is a
new competence, not a port of the existing one.
Direction. Treat as a separate, later decision from the extension (see §3). If it is ever
taken, the natural shape is a small platform seam under shell/instrument/ — editor_platform
already is that seam; it needs a sibling, not a rewrite.
L2-09 — the draw kit's font faces do not exist on Linux and substitute silently
Location: src/shell/panel/draw_kit.cpp:70–77 (loadFont), and the two literal faces it is
called with — Segoe UI and Consolas, per draw_kit.h:63–64.
Mechanism. On Linux CreateFont goes through fontconfig when built with
-DSWELL_FONTCONFIG (gated per the [verify — Linux] note in §1.5):
FcPatternAddString(pat, FC_FAMILY, lfFaceName) → FcConfigSubstitute/FcDefaultSubstitute →
FcFontMatch (swell-gdi-lice.cpp:450–487). Whether FcFontMatch itself can return failure is
a claim about fontconfig's own internals — fontconfig is not vendored here, so it is dropped
rather than asserted uncited (this doc's own evidence-discipline rule). What the vendored code
does show: even a successful match doesn't guarantee a loadable face —
swell-gdi-lice.cpp:480–484 only sets face when FcPatternGetString(hit, FC_FILE, …)
resolves to a nonempty string AND the following FT_New_Face succeeds; either step can fail,
leaving face NULL, and in this build path there is no further fallback — the
MatchFont/LiberationSans/DejaVuSans list at :493–539 compiles only in the #else
(#ifndef SWELL_FONTCONFIG) branch. However swell-gdi-lice.cpp:400–401,561,564 show
CreateFont always allocates and returns a non-null HGDIOBJ__* regardless of whether face
resolved — the failure is recorded internally (font->typedata = NULL), not as a null return —
so draw_kit.cpp:74's own if (!hf) return guard does not catch this failure mode; hf
comes back non-null either way. Whatever degraded outcome exists here comes from
LICE_CachedFont's own handling of a null-typedata font (§1.5 already cites real
non-Windows branches in lice_textnew.cpp:236–249,829,1027–1028,1040), not from loadFont's
stated guard — [verify — Linux]. The two WCAG static_asserts (draw_kit.cpp:53–54) are on
pixel height and weight, not on the face, so they still hold regardless.
Severity: Minor [verify — Linux] — cosmetic at best; whether it degrades further than
wrong metrics (e.g. to no text at all) rests on the unconfirmed downstream null-face handling
above, not on anything this audit can read statically.
Effort: S — confirmed against the actual call sites: five total (draw_kit.cpp:154–158,
four using "Segoe UI", one "Consolas"), each a single string-literal argument. A platform
fallback is a #ifdef-guarded literal swap at those five sites, not a new mechanism —
draw_kit.cpp:69's comment ("the face is chosen here so a change is one line") describes one
call's literal, not all five; the S estimate rests on there being only five call sites, not on
that comment's wording.
Direction. Add a platform fallback face list at the five loadFont call sites
(draw_kit.cpp:154–158). DejaVu Sans / DejaVu Sans Mono are the safe Linux defaults; SWELL's
own no-fontconfig fallback list names LiberationSans/DejaVuSans and
LiberationMono/DejaVuSansMono (swell-gdi-lice.cpp:505–507), a reasonable precedent to copy.
This same change fixes macOS (San Francisco / Menlo), one code path.
L2-10 — OS drag-out on SWELL loses the copy-only mask, the readiness probe, and the outcome
Location: src/shell/actions/drag_out_win.cpp:249–282 (the #else branch);
drag_out_win.h:7–11,29–33 already states the first and third honestly.
Mechanism. Three separate degradations, all real but none fatal. (a) DoDragDrop's
DROPEFFECT_COPY-only mask (:230) is a structural guarantee that no target can MOVE a
bank file out of the folder; SWELL_InitiateDragDropOfFileList takes no effect mask
(swell-functions.h:1011), so the guarantee reduces to whatever the GDK drag advertises.
(b) canInitiateDragOut degrades to !paths.empty() (:276–278), so the caller's careful
coupling — "do not tear down the internal drag until the OS is known ready"
(panel_drag.cpp:227–244) — has nothing to check against. (c) initiateDragOut returns true
unconditionally (:270), so the advisory success return is meaningless. The caller ignores it,
so (c) is inert today. Note the call path is already Linux-correct in one important respect:
handOffToOs releases capture and resets drag state (panel_drag.cpp:240–241) before
invoking the drag, which matters because SWELL's implementation takes capture on its own hidden
window and spins a nested SWELL_RunMessageLoop until capture drops
(swell-generic-gdk.cpp:3575–3584, inside SWELL_InitiateDragDropOfFileList — the function
drag_out_win.cpp:270 actually calls; the body is identical to the sibling
SWELL_InitiateDragDrop at :3531–3560, which is where an earlier draft of this citation
pointed) — with a 500 ms no-motion timeout that has no Windows analog.
Severity: Minor — the feature works; one safety property becomes conventional rather than
structural.
Effort: S — mostly documentation; there is no SWELL surface to restore the mask with.
Direction. Leave the implementation; make sure the copy-only invariant's home
(drag_out_win.h:7–11) is the doc a Linux reviewer is pointed at, and treat "MOVE is
structurally impossible" as a Windows-only claim in any future spec text.
L2-11 — normalizeSlashes case-folds on _WIN32 only, which is right for Linux and wrong for macOS
Location: src/core/capture/capture_paths.cpp:18–20.
Mechanism. The one platform conditional in core/. Linux filesystems are case-sensitive, so
not folding is correct and the existing tests already assert it
(tests/test_capture_paths.cpp:50–51). The shared-path note: macOS's default APFS/HFS+ is
case-insensitive, so the same #ifdef _WIN32 under-folds there — a pre-existing macOS
defect this audit surfaces but does not own.
Severity: Minor (no Linux defect). Effort: S — confirmed: the one platform conditional
in core/ is a single #ifdef _WIN32 block (capture_paths.cpp:18–20) with no other call
site depending on the token; a predicate swap is a one-block change.
Direction. No Linux action. If macOS is ever targeted, the predicate wants to be
"case-insensitive filesystem", not "Windows".
L2-12 — fs::path::string() narrowing is strictly better on Linux (informational)
Location: prune_fs.cpp:118, ingest.cpp:222,254, capture_paths.cpp:95,
panel_bank_ops.cpp:39, scope_resolve.cpp:207, insert.cpp:71,
capture_realtime_shell.cpp:306, capture.cpp:408, instrument_bake.cpp:166.
Mechanism. On MSVC std::filesystem::path::string() narrows through the active code page;
on Linux path is already char-based, so .string() is a byte passthrough and UTF-8 survives.
The one site that deliberately works around the Windows behaviour —
instrument_drop_win.cpp:123–128, using u8string() — is harmless on Linux (C++17, so
u8string() returns std::string; CMakeLists.txt:28 pins CMAKE_CXX_STANDARD 17, and under
C++20 the .c_str() at :128 would become a const char8_t* type error — worth knowing before
anyone bumps the standard).
Severity: Minor / informational. Effort: none. Direction: no action; do not "fix" it.
Surfaces checked and found clean
Recorded so the sweep's negative results are as auditable as its findings.
- Missing standard includes (the MSVC-transitively-provides class). Ran a use-vs-include diff
across all 293 files for
<cstring> <cstdio> <memory> <atomic> <algorithm> <limits> <cmath> <functional> <ctime> <cstdlib>. Five raw hits; all five verified false positives on read — four were the symbol name appearing in a comment (render_settings.h:53,ext_state_read.h:20,action_registry.h:7) or a correct<cmath>std::abs(velocity_curve.cpp:229,<cmath>at:6), andprocessor_reload.cpp'sstd::unique_ptrcomes fromreasampler_processor.h:12. No missing include found — but this only covers the ten headers above. The two headers most likely to produce the classic MSVC-transitively-provides failure under libstdc++,<vector>and<string>, were excluded from that sweep; re-run including them: 45 files usestd::vectorwithout#include <vector>, and 52 usestd::stringwithout#include <string>(raw grep-diff counts, unread).<map>and<set>add 2 and 3 more respectively. These 102 raw hits were not individually verified the way the five above were (that would mean reading 102 files); the claim this sweep actually supports is "up to 102 files may rely on transitive inclusion for<vector>/<string>/<map>/<set>, unconfirmed one by one" — not "no missing include found." The two header cases that matter most for the extension's own build (shell/capture/capture_orchestrator.h,src/ext_keys.h) are confirmed satisfied transitively. - Templates / two-phase lookup. Exactly 7 templates in the tree
(
core/wire/bytes.h:25,61,core/wire/ext_state_read.h:37,core/instrument/engine/play_params.h:191,203,208,218). None derives from a dependent base, none calls an unqualified dependent name, none needstypename/templatedisambiguation. GCC/Clang-safe as written. - Compiler extensions. Zero
#pragmaother than#pragma once; zero__declspec,__attribute__,__forceinline, anonymous struct/union, or MSVC-permissive construct insrc/**. HWND__forward declaration.drag_out_win.h:19declaresstruct HWND__;; SWELL declarestypedef struct HWND__ *HWND;(swell-types.h:211) — same tag, no-Wmismatched-tags.preview_register_tplatform fork.panel_audition.cpp:36–41,58–63andpanel_state.h:20–24match the SDK's own fork exactly (CRITICAL_SECTION cson_WIN32,pthread_mutex_t mutexotherwise —vendor/reaper-sdk/sdk/reaper_plugin.h:1308–1312).<pthread.h>is included on the non-Windows branch. Correct.- Prompts and message boxes. Every user prompt goes through REAPER's own
GetUserInputs/ShowMessageBox/ShowConsoleMsg(panel_bank_ops.cpp:24–25,81,100,126,381), never a Win32MessageBox. Cross-platform by construction. - Menus.
CreatePopupMenu/InsertMenu/TrackPopupMenu(TPM_RETURNCMD)/DestroyMenuare all SWELL-provided (swell-functions.h:528–532, andTrackPopupMenu/DestroyMenu/CreatePopupMenuentries); the "pos < 0 appends" assumption atpanel_bank_ops.cpp:226matches SWELL'sSWELL_InsertMenu. The comment at:220–222is accurate. - Keyboard/accelerator path.
accelerator_register_t+MSG/WM_KEYDOWN+GetFocus+GetParent(panel_input.cpp:503–521) are all SWELL-provided; everyVK_*used is inswell-types.h. - Double-buffered paint.
LICE_SysBitmap+getDC()+BitBlt(SRCCOPY)(panel_render.cpp:474,549) —LICE_SysBitmaphas real non-Windows paths (lice.cpp:179,lice.h:362),BitBlt/SRCCOPYare SWELL-provided. - Byte order. Handled explicitly everywhere it matters (see §1.1). No native-integer
memcpyonto a wire buffer anywhere incore/wire. - Hot-path guardrails. Nothing in this audit's remediation directions touches
peaks::computeEnvelope(still a free function), the audition call-through (panel_audition.cpp— direct calls, no interface), or the realtime tick's single-pointer-test idle path (main.cpp:156). L2-04's number-codec change is on the JSON/persist path, which rootCLAUDE.mdexplicitly declares off all hot paths. No recommendation here adds a hot-path indirection.
3. Extension vs. instrument — the cost split
The two artifacts are cleanly separable, and the evidence says the split is very lopsided.
3.1 Extension only (reaper_reasampler)
What must change in source: two compile Blockers (L2-01, L2-02 — both one-line), one load
hazard (L2-03), one silent-failure diagnostic (L2-06), one correctness/safety fix (L2-05), one
data-integrity fix (L2-04), and cosmetics (L2-09). Nothing here is architectural. The panel's
whole SWELL/LICE surface — dialog lifecycle, docking, WM_PAINT double-buffered LICE draw,
mouse/wheel/capture, cursors, menus, keyboard accelerator, modifier keys, tooltips, drag-out,
drop-in — was checked call-by-call against the vendor headers and is already SWELL-portable;
see §1.5 and "Surfaces checked". This is the single most load-bearing finding of the audit: the
panel was written against SWELL's vocabulary throughout, and the Windows-only escapes are three
small ones (DragAcceptFiles, SHFileOperationW, OLE DoDragDrop) that each already carry a
non-Windows branch or a documented reason they do not.
Estimate shape: S+S+S+S+M+M+S. No L item.
3.2 Instrument additionally (reasampler_vst)
What must be written from nothing: an X11-embed IPlugView (window creation and parenting
without RegisterClass/CreateWindowEx), an IRunLoop/ITimerHandler-driven replacement for
the SetTimer/WM_TIMER sync poll, an event-driven input path replacing the entire wndProc
switch (~150 lines of editor_platform.cpp:139–296), hover-leave detection without
TrackMouseEvent, modifier reads via GetAsyncKeyState instead of GetKeyState, and drop-accept
without DragAcceptFiles. The paint side is the cheapest part — every painter already draws into
a LICE_IBitmap through the shared kit (editor_paint.cpp:28,51), so the drawing survives a
window-system change intact; it is the window and event plumbing that is entirely absent. The
TCP/MCP embed strip (reasampler_embed.cpp:135–141) is a smaller, separate 0→1
(REAPER_FXEMBED_WM_IS_SUPPORTED currently returns 0 off Windows; the paint body at :174+ is
LICE and would port).
Estimate shape: one L, and it is a genuinely new competence rather than a port.
3.3 The consequence for sequencing
The extension is a Linux target that is largely already met and needs a handful of small,
well-localized fixes. The instrument is a separate project. The two share exactly one file —
shell/panel/draw_kit — and that file's only Linux blocker is L2-01. Daniel rules on whether the
instrument is in scope at all; the evidence says the extension does not wait on it.
4. Feature-degradation list — what builds but behaves differently, or not at all
Blunt column: GONE = the capability does not exist on Linux; DIFFERENT = it exists with changed behaviour; AT RISK = depends on an unverified assumption.
| Feature | Verdict | What actually happens | Cite |
|---|---|---|---|
| Prune → Recycle Bin | GONE | Hard unlink, unrecoverable |
prune_fs.cpp:200–208 |
| Prune's "file is locked" skip | GONE | unlink on an in-use file succeeds; the skip branch is dead code |
prune_fs.cpp:205 |
| Docked bank panel | GONE (today) | SWELL_CreateDialog returns null; toggle is a silent no-op until the resgen source is added (§7) |
panel_window.cpp:135–137 |
| VST3 editor | GONE | isPlatformTypeSupported false for every type; host shows a generic UI |
editor_platform.cpp:37–42 |
| VST3 generic-UI fallback | GONE (today) | Zero parameters registered as of this base; Phase Γ-W4-T1 changes this | grep addParameter over shell/instrument/*.cpp → no hits |
| TCP/MCP embed strip | GONE | REAPER_FXEMBED_WM_IS_SUPPORTED returns 0 |
reasampler_embed.cpp:135–141 |
| Kit fonts (Segoe UI / Consolas) | DIFFERENT | fontconfig substitutes silently; metrics and ellipsis points shift; ValueMono may lose tabular alignment | swell-gdi-lice.cpp:450–487 |
| OS drag-out copy-only guarantee | DIFFERENT | No effect mask; copy-only is conventional not structural | drag_out_win.cpp:257–270 |
| OS drag-out readiness/outcome | DIFFERENT | Probe degrades to "non-empty"; return is always true; 500 ms no-motion timeout has no Windows analog |
drag_out_win.cpp:276–278, swell-generic-gdk.cpp:3579–3580 |
| Panel file-drop ingest | AT RISK | Depends on SWELL delivering WM_DROPFILES without the Win32 opt-in |
panel_window.cpp:145–150 |
| Bank index float round-trip | AT RISK | Unparseable under a non-C LC_NUMERIC |
json.cpp:39–43,190–196 |
| Extension load | AT RISK | Any one unresolved API name in ~869 = silent refusal | main.cpp:292–293 |
| Path case sensitivity | DIFFERENT (correct) | No case-fold; correct for Linux, and the tests already assert it | capture_paths.cpp:18–20 |
| Prune reclaim on a symlinked bank file | AT RISK | fs::directory_iterator + is_regular_file() follows symlinks (C++17); size is read from the target via file_size() but fs::remove deletes the link, not the target — prune reports N bytes reclaimed and reclaims zero. Symlinked media folders are far more idiomatic on Linux than Windows. |
prune_fs.cpp:113–124 |
| Audition / preview | INTACT | preview_register_t fork matches the SDK; PlayPreview/StopPreview are REAPER API |
panel_audition.cpp:36–63 |
| Docking, menus, cursors, keyboard, wheel, capture rollback, tooltips | INTACT | All SWELL-provided; verified call-by-call | §1.5 |
| Capture pillar (offline + realtime), persist, tracking, prune computation | INTACT | Pure core/ + REAPER API only |
§1.1 |
5. Open questions
[verify — Linux] — answerable only on a Linux box
- Process locale. Read
setlocale(LC_NUMERIC, nullptr)inside a running REAPER-Linux process (print viaShowConsoleMsg) on a machine whose user locale uses a decimal comma. Decides whether L2-04 is urgent or latent. - REAPER API completeness. Instrument
main.cpp:292to printREAPERAPI_LoadAPI's return value instead of discarding it, load once, and record the count. Non-zero decides whether L2-03 is a real Blocker and names the gap. - Panel file-drop routing. With the panel docked, drag a WAV from the file manager onto it
and observe whether
dlgProcseesWM_DROPFILES(L2-07). If not, addWS_EX_ACCEPTFILES. - Which SWELL GDI/locale build REAPER ships. Whether REAPER's
libSwell.sois built withSWELL_SUPPORT_GTK(→gtk_init_check,swell-generic-gdk.cpp:366) or without (→gdk_init_check,:368) changes the likelihood in (1). Observable indirectly via (1). - fontconfig substitution for "Consolas". Whether it lands on a monospaced face on a stock distro decides whether L2-09 is cosmetic or a real readability regression on numeric readouts.
SWELL_InitiateDragDropOfFileListacceptance semantics. Whether a GDK-initiated file drag is accepted as a copy by common targets (a file manager, another DAW), and whether the 500 ms no-motion timeout (swell-generic-gdk.cpp:3579–3580) cancels a slow user gesture.- Prune against an in-use file. Play an item from a bank file, prune it, confirm the audio survives until the fd closes and the file is then unrecoverable — the concrete demonstration behind L2-05(b).
[Daniel] — scope decisions, not knowledge gaps
- Is the VST3 instrument in scope for Linux at all? §3 supplies the cost split; the extension does not depend on the answer. This is the phase's one real fork.
- Is a hard
unlinkan acceptable prune on Linux, with a platform-aware confirmation string — or must XDG trash be implemented before Linux ships? (L2-05.) - Is silent font substitution acceptable, or does the kit get an explicit Linux face list? (L2-09.)
- Does "copy-only is structural" survive as a shipped invariant when one platform can only offer it conventionally? (L2-10.)
6. Ordering sketch
Dependency order only — this is not a plan, and it assigns no waves.
- Make it compile. L2-01 (
FF_DONTCARE), L2-02 (GetCurrentProcessId). Nothing else can be observed until these land; both are one-line and independent of each other. - Make it load, visibly. L2-03 (
REAPERAPI_LoadAPIdiagnostic orREAPERAPI_MINIMAL) — must precede any runtime verification, because it is the failure mode that produces no evidence. Pairs naturally with[verify — Linux](2). - Make the panel appear. L2-06 (the missing-dialog diagnostic) alongside T1's resgen wiring. The diagnostic should land first so the resgen step can be confirmed rather than assumed.
- Then, and only then, verify.
[verify — Linux]items 1, 3, 5, 6, 7 all need a running panel. Item 2 is already answered by step 2. The critical path for this step runs through the parallel build-system track, not this one: "a running panel" means T1's resgen wiring (§7 bullet 1) has landed — this doc doesn't own that work and doesn't know its schedule, so step 3's "alongside T1's resgen wiring" is doing real scheduling work, not just sequencing flavor. Anyone driving this ordering sketch needs to coordinate with T1 before step 4 can start, not just after step 3 finishes on this track's own items. - Data integrity. L2-04 (locale-independent number codec) — pure
core/work, unit-testable on Windows, and it should land before any Linux user saves a project. Sequenced after the verify only so its urgency is known; the work is not blocked. - Deletion safety. L2-05 (XDG trash or a platform-aware confirm) — gated on
[Daniel](2). - Ingest opt-in, if needed. L2-07 — no independent fix; it is the resgen work already
sequenced in step 3, strictly gated on
[verify — Linux](3) to confirm it's needed at all. - Cosmetics. L2-09 (fonts), L2-10 (drag-out doc). Independent of everything above.
- Separately, if ruled in. L2-08 — the instrument's X11 editor. No dependency in either
direction on 1–8 except L2-01, which the shared
draw_kitneeds regardless.
7. Referred to T1 (build-system; spotted, not analyzed)
- SWELL dialog resgen is not wired for Linux.
src/app/CMakeLists.txt:97— thetarget_sources(... resource.rc_mac_dlg.h)line is commented out in the Linuxelse()branch (and in theAPPLEbranch,:86). This is the mechanical cause of L2-06. - VST3 target is
if(WIN32 …)-gated.src/shell/instrument/CMakeLists.txt:9— the target is not configured at all off Windows;dllmain.cppis hard-coded at:78where Linux needslinuxmain.cpp(vendor/vst3sdk/public.sdk/source/main/linuxmain.cppexists). SHCreateStdEnumFmtEtc/SHFileOperationW/OleInitializeneed shell32/ole32. Only on the Windows branch, but worth confirming the Linux branch links none of them.reaper_plugin.h's relative SWELL include.vendor/reaper-sdk/sdk/reaper_plugin.h:49does#include "../WDL/swell/swell.h", which does not exist undervendor/reaper-sdk/. It resolves only because${WDL_INC}isvendor/WDL/WDLandvendor/WDL/WDL/../WDL/swell/swell.his the same file. Works, but it is a coincidence worth knowing before anyone moves an include path.<filesystem>link requirement. GCC < 9 needs-lstdc++fs;core/andshell/both usestd::filesystemheavily.- LICE TU set.
CMakeLists.txt:79–82addslice.cpp,lice_line.cpp,lice_arc.cpp,lice_textnew.cppunconditionally. All four have non-Windows paths, but the Linux link needs them compiled againstSWELL_PROVIDED_BY_APPconsistently with the module.
Summary table
| ID | Finding | Severity | Effort | Artifact |
|---|---|---|---|---|
| L2-01 | FF_DONTCARE undefined off Windows (draw_kit.cpp:73) |
Blocker | S | both |
| L2-02 | GetCurrentProcessId() unguarded, absent from SWELL |
Blocker | S | extension |
| L2-03 | REAPERAPI_LoadAPI all-or-nothing over ~869 names, silent |
Blocker [verify] |
S | extension |
| L2-04 | Persisted floats are LC_NUMERIC-dependent, both directions |
Major | M | both |
| L2-05 | Prune loses trash AND the locked-file backstop | Major | M | extension |
| L2-06 | Panel dialog never created; silent no-op | Major | S | extension |
| L2-07 | No drop-accept opt-in on SWELL; real seam is resgen, same as L2-06 | Major [verify] |
— (see L2-06) | extension |
| L2-08 | No Linux VST3 editor; no parameters to fall back to | Major | L | instrument |
| L2-09 | Segoe UI / Consolas substitute silently via fontconfig | Minor | S | both |
| L2-10 | SWELL drag-out: no copy mask, no probe, no outcome | Minor | S | extension |
| L2-11 | Case-fold is _WIN32-only (right for Linux, wrong for macOS) |
Minor | S | both |
| L2-12 | fs::path::string() narrowing — Linux is strictly better |
Informational | — | both |