Files
reasampler/src/shell/panel/panel_audition.cpp
T

104 lines
3.4 KiB
C++

// panel_audition.cpp — the audition/preview engine seam of the docked bank panel.
// Hot-path guardrail: the preview path stays a direct free-function call-through —
// no interface, no virtual dispatch, no added header->TU indirection.
//
// DAW-verified, not unit tested. main.cpp owns the API pointers; here they are extern.
#include <string>
#include "shell/panel/panel_state.h"
// PlayPreview/StopPreview (stock, not SWS-only) drive a caller-owned preview_register_t.
#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 is preview playback only — never inserts into the arrange or mutates
// the project/bank. PlayPreview streams a caller-owned PCM_source through
// REAPER's preview bus.
//
// Runtime assumptions not documented in the SDK header (DAW-verify, not asserted):
// 1. The audio thread reads preview_register_t by pointer while active, so it
// must outlive playback — held in g_panel (static), never on the stack.
// 2. StopPreview is assumed to detach the source before returning, so
// PCM_Source_Destroy immediately after is safe (SWS' preview helpers rely on
// the same contract). If a race ever surfaces, the fix is a StartPreviewFade
// + deferred free.
// 3. m_out_chan == 0 routes to the first hardware output pair (stereo, not mono).
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;
}
// `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