docs: audit source/runtime Linux-portability for extension and VST3 instrument
Two one-line compile blockers, a locale hazard in the persisted number codec, and a cost split showing the extension is nearly there while the instrument editor is a from-scratch X11 job.
This commit is contained in:
@@ -0,0 +1,677 @@
|
||||
# Λ-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 `#include` in `src/core/**` is either a `"core/…"` sibling, one of
|
||||
26 standard-library headers, or the CMake-generated `version_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`),
|
||||
two `NDEBUG` assert 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 in
|
||||
`normalizeSlashes`. 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). Six other files matched a
|
||||
`C:\`/`C:/` grep — every hit is 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`), 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 | Guarded blocks | Shape |
|
||||
|---|---|---|
|
||||
| `shell/instrument/` (VST3 editor + embed) | 44 of 65 | Whole-TU `#ifdef _WIN32` … `#endif` — Windows-only by design |
|
||||
| `shell/panel/` | 9 | Include-selection + two small `#else` bodies |
|
||||
| `shell/persist/`, `shell/capture/`, `shell/actions/` | 6 | Trash fallback, `localtime_r`, the SWELL drag-out branch |
|
||||
| `core/` | 1 | The case-fold |
|
||||
| `src/resource.h`, `src/ext_keys.h` | comments only | — |
|
||||
|
||||
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 in the extension.**
|
||||
|
||||
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: 7 calls in `editor_session.cpp` at
|
||||
119, 126, 139, 141, 150, 156, 174 — all within the `#ifdef _WIN32` regions opened at 105 and
|
||||
173; `editor_controls.cpp`, `editor_models.cpp`, `instrument_bake.cpp` have zero). 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`). On Linux REAPER's own
|
||||
`libSwell.so` is built with `swell-gdi-lice.o`, `-DSWELL_LICE_GDI`, `-DSWELL_FREETYPE` and
|
||||
`-lfontconfig` (`vendor/WDL/WDL/swell/Makefile:100–107,141–151`) — i.e. the GDI SWELL
|
||||
answers with is the **LICE-backed, freetype/fontconfig** one, not the null stub in
|
||||
`swell-gdi-generic.cpp` (that file is `#ifndef SWELL_PROVIDED_BY_APP`, line 24).
|
||||
|
||||
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_CreateDialog` behind the `CreateDialogParam` macro
|
||||
(`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*`, every `VK_*` used.
|
||||
- `WM_MOUSEWHEEL` really does carry **screen** coords in `lParam` on the GDK backend — the
|
||||
comment at `panel_window.cpp:100–102` is correct (`swell-generic-gdk.cpp:1442`).
|
||||
- `WM_CAPTURECHANGED` really 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–3748` vs `panel_drag.cpp:141–147,159–161`).
|
||||
- `GetAsyncKeyState` returns the `0x8000` high bit for `VK_CONTROL`/`VK_SHIFT`/`VK_MENU`
|
||||
(`swell-generic-gdk.cpp:2439–2441`), so `panel_state.h:409–411` works verbatim.
|
||||
- `GetTickCount` is SWELL-provided (`swell-functions.h:81`), so `panel_drag.cpp:308,324` — the
|
||||
only unguarded Win32-looking calls in the panel — are fine.
|
||||
- `SWELL_InitiateDragDropOfFileList` **does exist on Linux** and is a real implementation, not a
|
||||
stub: `swell-generic-gdk.cpp:3563–3592` (`gdk_drag_begin` + a nested `SWELL_RunMessageLoop`
|
||||
until capture drops, with a 500 ms no-motion timeout). The headless backend
|
||||
(`swell-generic-headless.cpp:246`) and macOS (`swell-dlg.mm:3534`) also define it; it is
|
||||
declared at `swell-functions.h:1011`. **The doc's claim holds.**
|
||||
- LICE itself is portable: `lice.h:31` includes `swell-types.h` off Windows, `lice.cpp:22`
|
||||
includes `swell.h`, `LICE_SysBitmap::__resize` has a SWELL framebuffer path
|
||||
(`lice.cpp:179–182`), and `LICE_CachedFont::DrawTextImpl` carries 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 `#include`s 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.
|
||||
**Effort: S.**
|
||||
**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), `src/core/capture/tail_control.cpp:74` (`%.17g` — the
|
||||
per-project tail setting).
|
||||
**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:183`), 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.
|
||||
**Effort: M** — three 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 three 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 `WM_DROPFILES` up to the parent
|
||||
only when the window *lacks* `WS_EX_ACCEPTFILES` (`swell-wnd-generic.cpp:7680–7688`) — i.e. the
|
||||
opt-in on SWELL is an ex-style bit, not an API call, and the message reaches the window under
|
||||
the pointer either way. So the panel's own `dlgProc` plausibly receives the drop **without any
|
||||
opt-in at all**. Two supporting details 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
|
||||
`GlobalFree`s 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.
|
||||
**Effort: S** — if the verify fails, the fix is
|
||||
`SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_ACCEPTFILES)`
|
||||
(both symbols exist: `swell-functions.h:319–320`, `swell-types.h`).
|
||||
**Direction.** Verify first, then either do nothing or add the ex-style set in the `#else`.
|
||||
`[verify — Linux]` drop a WAV onto the docked panel and check whether `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` (`:224`),
|
||||
`Linux::ITimerHandler` (`:238`), 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: `FcPatternAddString(pat, FC_FAMILY,
|
||||
lfFaceName)` → `FcFontMatch` (`swell-gdi-lice.cpp:450–487`). `FcFontMatch` never fails — it
|
||||
returns the best available substitute. So `loadFont`'s failure branch (`draw_kit.cpp:74`,
|
||||
"if (!hf) return") will not fire, and the kit will draw in *some* font with different metrics.
|
||||
Consequences: the kit's end-ellipsis clipping (`draw_kit.h:70–74`) is tuned against Segoe's
|
||||
advance widths, so labels will clip at different points; `Font::ValueMono` is only tabular if
|
||||
fontconfig's substitution for "Consolas" happens to be monospaced, which is a distro-config
|
||||
property, not a guarantee. The two WCAG `static_assert`s (`draw_kit.cpp:53–54`) are on **pixel
|
||||
height and weight**, not on the face, so they still hold — the contrast entitlement is not at
|
||||
risk.
|
||||
|
||||
**Severity: Minor** — cosmetic; nothing breaks, layout gets uglier and possibly clipped.
|
||||
**Effort: S.**
|
||||
**Direction.** Add a platform fallback face list at the one place the faces are chosen
|
||||
(`draw_kit.cpp`'s `loadFont` call sites — the header already promises "the face is chosen here so
|
||||
a change is one line", `:69`). DejaVu Sans / DejaVu Sans Mono are the safe Linux defaults; note
|
||||
that SWELL's own no-fontconfig fallback list names LiberationSans/DejaVuSans and
|
||||
LiberationMono/DejaVuSansMono (`swell-gdi-lice.cpp:505–507`), which is 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:3543–3552`) — 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.**
|
||||
**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`), and `processor_reload.cpp`'s `std::unique_ptr`
|
||||
comes from `reasampler_processor.h:12`. **No missing include found.**
|
||||
- **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 needs `typename`/`template` disambiguation.
|
||||
GCC/Clang-safe as written.
|
||||
- **Compiler extensions.** Zero `#pragma` other than `#pragma once`; zero `__declspec`,
|
||||
`__attribute__`, `__forceinline`, anonymous struct/union, or MSVC-permissive construct in
|
||||
`src/**`.
|
||||
- **`HWND__` forward declaration.** `drag_out_win.h:19` declares `struct HWND__;`; SWELL declares
|
||||
`typedef struct HWND__ *HWND;` (`swell-types.h:211`) — same tag, no `-Wmismatched-tags`.
|
||||
- **`preview_register_t` platform fork.** `panel_audition.cpp:36–41,58–63` and
|
||||
`panel_state.h:20–24` match the SDK's own fork exactly (`CRITICAL_SECTION cs` on `_WIN32`,
|
||||
`pthread_mutex_t mutex` otherwise — `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 Win32 `MessageBox`. Cross-platform by construction.
|
||||
- **Menus.** `CreatePopupMenu`/`InsertMenu`/`TrackPopupMenu(TPM_RETURNCMD)`/`DestroyMenu` are all
|
||||
SWELL-provided (`swell-functions.h:528–532`, and `TrackPopupMenu`/`DestroyMenu`/
|
||||
`CreatePopupMenu` entries); the "pos < 0 appends" assumption at `panel_bank_ops.cpp:226` matches
|
||||
SWELL's `SWELL_InsertMenu`. The comment at `:220–222` is accurate.
|
||||
- **Keyboard/accelerator path.** `accelerator_register_t` + `MSG`/`WM_KEYDOWN` + `GetFocus` +
|
||||
`GetParent` (`panel_input.cpp:503–521`) are all SWELL-provided; every `VK_*` used is in
|
||||
`swell-types.h`.
|
||||
- **Double-buffered paint.** `LICE_SysBitmap` + `getDC()` + `BitBlt(SRCCOPY)`
|
||||
(`panel_render.cpp:474,549`) — `LICE_SysBitmap` has real non-Windows paths
|
||||
(`lice.cpp:179`, `lice.h:362`), `BitBlt`/`SRCCOPY` are SWELL-provided.
|
||||
- **Byte order.** Handled explicitly everywhere it matters (see §1.1). No native-integer
|
||||
`memcpy` onto a wire buffer anywhere in `core/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 root
|
||||
`CLAUDE.md` explicitly 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:3547–3548` |
|
||||
| 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` |
|
||||
| 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
|
||||
|
||||
1. **Process locale.** Read `setlocale(LC_NUMERIC, nullptr)` inside a running REAPER-Linux
|
||||
process (print via `ShowConsoleMsg`) on a machine whose user locale uses a decimal comma.
|
||||
Decides whether L2-04 is urgent or latent.
|
||||
2. **REAPER API completeness.** Instrument `main.cpp:292` to print
|
||||
`REAPERAPI_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.
|
||||
3. **Panel file-drop routing.** With the panel docked, drag a WAV from the file manager onto it
|
||||
and observe whether `dlgProc` sees `WM_DROPFILES` (L2-07). If not, add `WS_EX_ACCEPTFILES`.
|
||||
4. **Which SWELL GDI/locale build REAPER ships.** Whether REAPER's `libSwell.so` is built with
|
||||
`SWELL_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).
|
||||
5. **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.
|
||||
6. **`SWELL_InitiateDragDropOfFileList` acceptance 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:3547–3548`) cancels a slow user gesture.
|
||||
7. **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
|
||||
|
||||
1. **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.
|
||||
2. **Is a hard `unlink` an acceptable prune on Linux**, with a platform-aware confirmation
|
||||
string — or must XDG trash be implemented before Linux ships? (L2-05.)
|
||||
3. **Is silent font substitution acceptable**, or does the kit get an explicit Linux face list?
|
||||
(L2-09.)
|
||||
4. **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.
|
||||
|
||||
1. **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.
|
||||
2. **Make it load, visibly.** L2-03 (`REAPERAPI_LoadAPI` diagnostic or `REAPERAPI_MINIMAL`) —
|
||||
must precede any runtime verification, because it is the failure mode that produces no
|
||||
evidence. Pairs naturally with `[verify — Linux]` (2).
|
||||
3. **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.
|
||||
4. **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.
|
||||
5. **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.
|
||||
6. **Deletion safety.** L2-05 (XDG trash or a platform-aware confirm) — gated on `[Daniel]` (2).
|
||||
7. **Ingest opt-in, if needed.** L2-07 — strictly gated on `[verify — Linux]` (3).
|
||||
8. **Cosmetics.** L2-09 (fonts), L2-10 (drag-out doc). Independent of everything above.
|
||||
9. **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_kit` needs regardless.
|
||||
|
||||
---
|
||||
|
||||
## 7. Referred to T1 (build-system; spotted, not analyzed)
|
||||
|
||||
- **SWELL dialog resgen is not wired for Linux.** `src/app/CMakeLists.txt:97` — the
|
||||
`target_sources(... resource.rc_mac_dlg.h)` line is commented out in the Linux `else()` branch
|
||||
(and in the `APPLE` branch, `: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.cpp` is hard-coded at `:78` where Linux needs
|
||||
`linuxmain.cpp` (`vendor/vst3sdk/public.sdk/source/main/linuxmain.cpp` exists).
|
||||
- **`SHCreateStdEnumFmtEtc` / `SHFileOperationW` / `OleInitialize` need 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:49`
|
||||
does `#include "../WDL/swell/swell.h"`, which does not exist under `vendor/reaper-sdk/`. It
|
||||
resolves only because `${WDL_INC}` is `vendor/WDL/WDL` and `vendor/WDL/WDL/../WDL/swell/swell.h`
|
||||
is 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/` and `shell/` both use
|
||||
`std::filesystem` heavily.
|
||||
- **LICE TU set.** `CMakeLists.txt:79–82` adds `lice.cpp`, `lice_line.cpp`, `lice_arc.cpp`,
|
||||
`lice_textnew.cpp` unconditionally. All four have non-Windows paths, but the Linux link needs
|
||||
them compiled against `SWELL_PROVIDED_BY_APP` consistently 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; ingest-by-drop unverified | Major `[verify]` | S | 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 |
|
||||
Reference in New Issue
Block a user