// 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 #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(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