Files
reasampler/src/shell/capture/realtime_lifecycle.cpp
T
daniel 6287534454 fix: classify future-version ledgers with changed record shape correctly, not as corrupt
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.
2026-07-30 20:25:46 -04:00

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