Files
reasampler/src/shell/capture/realtime_lifecycle.cpp
T
daniel f3be4d8cce Q-W6: registration table (OCP) in main.cpp; bank verbs -> shell/bank_ops(Session&); persist.h + wav_trim + namespaces.h shims deleted; 61/61
capture.h realtime seam split to capture_realtime_shell.h; GetProjExtState grow-loop rehomed to core/wire/ext_state_read; stale persist.cpp/bank_panel.cpp comment refs fixed; CLAUDE.md persist/bank_book/actions bullets updated. Command-id suffixes, display phrases, and undo labels byte-identical.
2026-07-29 13:40:09 -04:00

102 lines
4.7 KiB
C++

// 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 "shell/persist/session.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