6287534454
Version-check now runs on the parseLedger failure path too, so a v3 blob whose record shape actually changed reports FutureVersion instead of Unreadable, avoiding the corrupt-blob "clear it" advice. Also closes the six minor findings.
116 lines
5.4 KiB
C++
116 lines
5.4 KiB
C++
// realtime_lifecycle.cpp — the in-flight realtime-capture state machine + globals.
|
|
// 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.
|
|
|
|
#include "shell/capture/realtime_lifecycle.h"
|
|
|
|
#include "shell/persist/session.h" // ReaSamplerSession
|
|
|
|
#define REAPERAPI_MINIMAL
|
|
#define REAPERAPI_WANT_EnumProjects
|
|
#define REAPERAPI_WANT_GetProjectName
|
|
#define REAPERAPI_WANT_ShowConsoleMsg
|
|
#include "reaper_plugin_functions.h"
|
|
|
|
namespace reasampler::capture {
|
|
|
|
RealtimeRecordBackend g_rtBackend;
|
|
RealtimeCaptureHandle g_rtCapture;
|
|
ReaProject* g_rtCaptureProject = nullptr;
|
|
|
|
// Commits a finished realtime capture (a Done tick/abort with an Ok result): adds
|
|
// the Sample to the active bank, persists + MarkProjectDirty. Shared by the
|
|
// tick-completion and 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);
|
|
session.recordCreated(res.sample, tracking::OriginKind::Capture);
|
|
// A capture add changes what a live instance could play, so bump the generation
|
|
// before persisting to refresh instances.
|
|
session.bumpBankGeneration();
|
|
session.saveToActiveProject(); // persist book + ledger + generation + MarkProjectDirty
|
|
}
|
|
|
|
// Advances any in-flight realtime capture one 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 own project-switch handling
|
|
// sees an already-cleaned-up project.
|
|
void DriveRealtimeCapture(ReaSamplerSession& session)
|
|
{
|
|
if (!g_rtCapture) return;
|
|
|
|
// If the active project is no longer the one the capture belongs to, a project
|
|
// switch happened mid-record: abort + restore into the original project the
|
|
// state is bound to, and drop it — never finalize into the new project.
|
|
ReaProject* active = EnumProjects(-1, nullptr, 0);
|
|
if (active != g_rtCaptureProject)
|
|
{
|
|
RealtimeTickResult r = g_rtBackend.abort(*g_rtCapture);
|
|
// Log without persisting — we restored into the original project but must
|
|
// not persist into the now-active foreign one: the ledger and bank we would
|
|
// have to write belong to the project we just left. A Failed abort surfaces
|
|
// abort()'s own message, distinguishing a clean tab-switch abort from the
|
|
// closed-project case (nothing restored because the pointers were already
|
|
// freed).
|
|
//
|
|
// The abort already moved the recorded WAV into the ORIGINAL project's bank
|
|
// folder, so it survives here untracked — deleting a user's just-recorded
|
|
// audio is the destructive direction and is not this shell's call, so the
|
|
// path is NAMED instead and the operator decides (docs/TODO.md).
|
|
//
|
|
// RealtimeTickStatus::Done implies CaptureStatus::Ok (RealtimeRecordBackend::
|
|
// abort only sets Done on that path) — the Ok branch below is therefore the
|
|
// whole Done case; anything else is Failed and falls to the last branch.
|
|
if (r.status == RealtimeTickStatus::Done && r.result.status == CaptureStatus::Ok) {
|
|
// g_rtCaptureProject is still valid here (reset only below) — it names the
|
|
// ORIGINAL project, not whatever is active now, which is the whole point:
|
|
// the user has already switched away from it.
|
|
char nameBuf[512] = {0};
|
|
GetProjectName(g_rtCaptureProject, nameBuf, sizeof(nameBuf));
|
|
const std::string projName = nameBuf[0] ? nameBuf : "(unsaved project)";
|
|
ShowConsoleMsg(("ReaSampler realtime capture: project switched mid-record -- "
|
|
"captured audio restored into the original project (" +
|
|
projName + "); not persisted to avoid crossing projects. The "
|
|
"recorded file was left in that project's bank folder as '" +
|
|
r.result.sample.relativePath +
|
|
"', untracked. Re-importing it there does NOT adopt this file "
|
|
"-- it copies the audio in under a new name -- so after "
|
|
"re-importing, delete this untracked original by hand.\n")
|
|
.c_str());
|
|
} 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
|