104 lines
3.4 KiB
C++
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
|