Q-W3: main.cpp → pointers+entry+dispatch via 4 capture hoists; one pure wav_codec RIFF owner; ICaptureBackend deleted; capture_realtime rename + finalize split; shared stampCaptureSample; makeUniqueTag gains monotonic counter (fixes same-second batch collisions). 60/60 green.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
// realtime_lifecycle.cpp — the in-flight realtime-capture state machine + globals
|
||||
// (Q-W3 hoist out of main.cpp; the code moved verbatim, the session threaded as a
|
||||
// parameter). See the header.
|
||||
//
|
||||
// Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h
|
||||
// WITHOUT REAPERAPI_IMPLEMENT — main.cpp is the one TU that defines the API
|
||||
// pointers; here they are extern (CLAUDE.md §contract).
|
||||
|
||||
#include "shell/capture/realtime_lifecycle.h"
|
||||
|
||||
#include "persist.h" // ReaSamplerSession
|
||||
|
||||
#define REAPERAPI_MINIMAL
|
||||
#define REAPERAPI_WANT_EnumProjects
|
||||
#define REAPERAPI_WANT_ShowConsoleMsg
|
||||
#include "reaper_plugin_functions.h"
|
||||
|
||||
namespace reasampler::capture {
|
||||
|
||||
// --- M8 in-flight realtime capture (async, timer-driven) --------------------
|
||||
RealtimeRecordBackend g_rtBackend;
|
||||
RealtimeCaptureHandle g_rtCapture;
|
||||
ReaProject* g_rtCaptureProject = nullptr;
|
||||
|
||||
// Commit a finished realtime capture (a Done tick/abort with an Ok result): add the
|
||||
// Sample to the ACTIVE bank (session.bank() resolves to book.activeIndex() — B2),
|
||||
// persist + MarkProjectDirty. Shared by the tick-completion path and the abort
|
||||
// paths. On a non-Ok result, logs the failure only.
|
||||
void CommitRealtimeResult(ReaSamplerSession& session, const CaptureResult& res)
|
||||
{
|
||||
if (res.status != CaptureStatus::Ok)
|
||||
{
|
||||
ShowConsoleMsg(("ReaSampler realtime capture failed: " + res.message + "\n").c_str());
|
||||
return;
|
||||
}
|
||||
session.bank().add(res.sample);
|
||||
// B-cap: record the file the capture created in the owned-file manifest, at the same
|
||||
// point the Sample is added and before the same persist. Recorded regardless of the
|
||||
// index AddResult — even a hash-collapse still WROTE a file the tool owns, and the
|
||||
// manifest dedups a repeat path itself (Phase R prune reconciles manifest vs index).
|
||||
session.owned().add(res.sample.relativePath);
|
||||
// S9: a capture add changes what a live instance could play (a new sample landed in the
|
||||
// active bank) -> bump before the persist so the stamped generation refreshes instances.
|
||||
session.bumpBankGeneration();
|
||||
session.saveToActiveProject(); // persist book + manifest + generation + MarkProjectDirty (travels with .rpp)
|
||||
}
|
||||
|
||||
// Advance any in-flight realtime capture one tick. Cheap when none is running (a
|
||||
// null check) and fast even mid-record (tick() only reads the transport until the
|
||||
// terminal tick). Detects a project switch mid-capture and aborts+restores so the
|
||||
// capture never leaks across projects. Called from OnTimer BEFORE session.poll() so
|
||||
// poll's project-switch handling sees a cleaned-up project.
|
||||
void DriveRealtimeCapture(ReaSamplerSession& session)
|
||||
{
|
||||
if (!g_rtCapture) return;
|
||||
|
||||
// Project switch guard: if the active project is no longer the one the capture
|
||||
// belongs to, a new/other project became active mid-record — abort + restore
|
||||
// (into the ORIGINAL project the state is bound to) and drop it. Do NOT finalize
|
||||
// into the new project.
|
||||
ReaProject* active = EnumProjects(-1, nullptr, 0);
|
||||
if (active != g_rtCaptureProject)
|
||||
{
|
||||
RealtimeTickResult r = g_rtBackend.abort(*g_rtCapture);
|
||||
// Only commit if the ORIGINAL project is still open and active would be it —
|
||||
// on a switch we restored into the original but must not persist into the
|
||||
// now-active foreign project. Log the outcome without persisting. On a Failed
|
||||
// abort surface abort()'s own message — it distinguishes a clean tab-switch
|
||||
// abort from the closed-project DROP (the captured project was closed mid-record,
|
||||
// review §1: nothing restored because the pointers were already freed).
|
||||
if (r.status == RealtimeTickStatus::Done)
|
||||
ShowConsoleMsg("ReaSampler realtime capture: project switched mid-record -- "
|
||||
"captured audio restored into the original project; not "
|
||||
"persisted to avoid crossing projects.\n");
|
||||
else
|
||||
ShowConsoleMsg(("ReaSampler realtime capture: project changed mid-record -- " +
|
||||
r.result.message + "\n").c_str());
|
||||
g_rtCapture.reset();
|
||||
g_rtCaptureProject = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
RealtimeTickResult r = g_rtBackend.tick(*g_rtCapture);
|
||||
if (r.status == RealtimeTickStatus::InProgress) return;
|
||||
|
||||
// Terminal (Done or Failed): commit/log and drop the in-flight state.
|
||||
CommitRealtimeResult(session, r.result);
|
||||
g_rtCapture.reset();
|
||||
g_rtCaptureProject = nullptr;
|
||||
}
|
||||
|
||||
void AbortRealtimeCaptureForUnload(ReaSamplerSession& session)
|
||||
{
|
||||
if (!g_rtCapture) return;
|
||||
RealtimeTickResult r = g_rtBackend.abort(*g_rtCapture);
|
||||
CommitRealtimeResult(session, r.result);
|
||||
g_rtCapture.reset();
|
||||
g_rtCaptureProject = nullptr;
|
||||
}
|
||||
|
||||
} // namespace reasampler::capture
|
||||
Reference in New Issue
Block a user