Q-W2: split bank_panel.cpp (3459 LOC) into eight shell/panel TUs — reasampler::panel internals, per-seam public headers, shim retired; zero behavior change, 60/60 green

This commit is contained in:
2026-07-29 10:55:58 -04:00
parent b5788c82f6
commit 30a4ffd01b
20 changed files with 4084 additions and 3596 deletions
+117
View File
@@ -0,0 +1,117 @@
// panel_audition.cpp — the audition/preview engine seam of the docked bank panel
// (Q-W2 split of bank_panel.cpp; M5 Wave B). HOT PATH GUARDRAIL (T4-28 / Q-W2): the
// preview path stays a DIRECT free-function call-through — no interface, no virtual
// dispatch, no added header->TU indirection; the idle path is unchanged in shape.
//
// Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h
// WITHOUT REAPERAPI_IMPLEMENT — main.cpp owns the API pointers; here they are
// extern (CLAUDE.md §contract). DAW-verified, not unit tested.
#include <string>
#include "shell/panel/panel_state.h"
// Stock preview API (verified against reaper_plugin.h / reaper_plugin_functions.h):
// PlayPreview/StopPreview drive a caller-owned preview_register_t. These are the
// STOCK symbols (not SWS-only) — see the audition section below.
#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_PlayPreview
#define REAPERAPI_WANT_StopPreview
#define REAPERAPI_WANT_PCM_Source_CreateFromFile
#define REAPERAPI_WANT_PCM_Source_Destroy
#include "reaper_plugin_functions.h"
namespace reasampler::panel {
// --- Audition preview ---------------------------------------------------------
//
// READ-ONLY / NON-DESTRUCTIVE (load-bearing principle): audition is PREVIEW
// playback only. It NEVER inserts into the arrange, creates items/tracks, or
// mutates the project or bank. PlayPreview streams a caller-owned PCM_source
// through REAPER's preview bus and touches nothing in the project.
//
// FLAGGED RUNTIME ASSUMPTIONS (header does not specify these; verified only by
// signature/struct, not semantics — DAW-verify):
// 1. REAPER's audio thread reads the preview_register_t by POINTER while the
// preview is active (the struct's own comment mandates a cs/mutex we init),
// so the register must outlive playback — we hold it in g_panel (static),
// never on the stack.
// 2. StopPreview is assumed to detach the source from the audio thread BEFORE it
// returns, making it safe to PCM_Source_Destroy the source immediately after.
// This is the conventional contract (SWS' preview helpers rely on it) but is
// NOT documented in the header — flagged. If a rare race surfaced, the fix is
// a StartPreviewFade + deferred free; not done now (YAGNI, no evidence).
// 3. m_out_chan == 0 routes to the first hardware output pair (stereo). We do not
// set mono (&1024). volume 1.0, loop false, curpos 0.
void initPreview() {
if (g_panel.previewInited) return;
#ifdef _WIN32
InitializeCriticalSection(&g_panel.preview.cs);
#else
pthread_mutex_init(&g_panel.preview.mutex, nullptr);
#endif
g_panel.previewInited = true;
}
void stopAudition() {
if (g_panel.previewActive) {
StopPreview(&g_panel.preview);
g_panel.previewActive = false;
}
if (g_panel.previewSrc) {
PCM_Source_Destroy(g_panel.previewSrc);
g_panel.previewSrc = nullptr;
}
g_panel.preview.src = nullptr;
}
void deinitPreview() {
if (!g_panel.previewInited) return;
#ifdef _WIN32
DeleteCriticalSection(&g_panel.preview.cs);
#else
pthread_mutex_destroy(&g_panel.preview.mutex);
#endif
g_panel.previewInited = false;
}
// Auditions the sample at selection ordinal `idx` of the FOCUSED region's displayed bank.
// L7: `idx` is a DISPLAY-order (slot) ordinal, resolved through orderedIds, not a raw
// BankModel position.
void startAudition(int idx) {
stopAudition();
const BankModel* index = indexForRegion(g_panel.focusedRegion);
if (!index) return;
const RegionDisplay disp = focusedDisplay();
if (idx < 0 || idx >= disp.occupiedCount()) return;
const Sample* s = index->query(disp.orderedIds[static_cast<std::size_t>(idx)]);
if (!s) return;
const std::string projectDir = currentProjectDir();
const std::string abs = resolveBankFile(projectDir, s->relativePath);
if (abs.empty()) return;
PCM_source* src = PCM_Source_CreateFromFile(abs.c_str());
if (!src) return;
g_panel.preview.src = src;
g_panel.preview.m_out_chan = 0;
g_panel.preview.curpos = 0.0;
g_panel.preview.loop = false;
g_panel.preview.volume = 1.0;
g_panel.preview.peakvol[0] = 0.0;
g_panel.preview.peakvol[1] = 0.0;
g_panel.preview.preview_track = nullptr;
if (PlayPreview(&g_panel.preview) != 0) {
g_panel.previewSrc = src;
g_panel.previewActive = true;
} else {
PCM_Source_Destroy(src);
g_panel.preview.src = nullptr;
}
}
} // namespace reasampler::panel