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:
@@ -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
|
||||
Reference in New Issue
Block a user