Files
reasampler/src/core/capture/capture_realtime.cpp
T

104 lines
4.1 KiB
C++

// capture_realtime.cpp — pure logic for the realtime-record backend. See header.
// Unit-tested by tests/test_capture_realtime.cpp.
#include "core/capture/capture_realtime.h"
namespace reasampler::capture {
RecordModePlan recordModePlanFor(int channelCount, OutputTap tap) {
RecordModePlan p;
// REAPER's output-record modes are mono/stereo only; >2 channels still
// records stereo-out (a >2-channel realtime capture is out of scope).
p.recMode = (channelCount <= 1) ? kRecModeMonoOutLatComp
: kRecModeStereoOutLatComp;
switch (tap) {
case OutputTap::PostFader: p.recModeFlags = kRecOutPostFader; break;
case OutputTap::PreFx: p.recModeFlags = kRecOutPreFx; break;
case OutputTap::PostFxPreFader: p.recModeFlags = kRecOutPostFxPreFader; break;
}
return p;
}
OutputTap outputTapForWetDry(double wetDry) {
return (wetDry >= 1.0) ? OutputTap::PostFader : OutputTap::PreFx;
}
Sample sampleFromRecordedCapture(const RecordedCapture& cap) {
Sample s;
// Relative path tail included so two same-tag captures (shouldn't happen) still differ.
s.id = "cap-" + cap.uniqueTag + "-" + cap.relativePath;
s.displayName = cap.displayName;
s.relativePath = cap.relativePath;
s.sourceMode = cap.sourceMode;
s.sourceRange.startSeconds = cap.startSeconds;
s.sourceRange.endSeconds = cap.endSeconds;
// PPQ/beats deferred (musical-placement concern), as offline.
s.wetDry = cap.wetDry;
s.trackGuids = cap.trackGuids;
s.channelCount = cap.channelCount;
s.sampleRate = cap.sampleRate; // 0 when project rate was unknown
s.lengthSeconds = cap.endSeconds - cap.startSeconds;
s.captureTempo = cap.captureTempo;
s.captureTimeSigNum = cap.captureTimeSigNum; // 0/0 = unstamped
s.captureTimeSigDenom = cap.captureTimeSigDenom;
s.tier = Tier::Scratch;
// contentHash is left empty: this mapping runs before the file exists on
// disk; the shell patches the hash in after the move+trim.
// rootNote/loop left empty: a realtime record of wet output isn't a single
// played note, so no root note is derivable; loop points are a later action.
s.createdTimestamp = cap.createdTimestamp;
return s;
}
RecordPhase advanceRecordPhase(RecordPhase current,
const RecordTickInputs& inputs,
double rangeStartSeconds,
double rangeEndSeconds) {
switch (current) {
case RecordPhase::Recording: {
// Stopped early (user or REAPER) -> finalize what was captured so far.
if (!inputs.transport.recording) return RecordPhase::Finalizing;
// >= (not >): a cursor landing exactly on the end completes.
if (inputs.transport.playPosition >= rangeEndSeconds)
return RecordPhase::Finalizing;
// Self-defense: a stuck/looping transport that never reaches end would
// otherwise stay in Recording forever, leaking the temp track + armed sink.
const double ceiling =
(rangeEndSeconds - rangeStartSeconds) + kRecordMarginSeconds;
if (inputs.elapsedSeconds > ceiling) return RecordPhase::Finalizing;
return RecordPhase::Recording;
}
case RecordPhase::Finalizing: {
// Moving the file before it's stable would race REAPER's flush and
// yield a truncated/missing capture.
if (inputs.fileReady) return RecordPhase::Done;
if (inputs.finalizingSeconds > kFinalizeFlushCeilingSeconds)
return RecordPhase::Failed;
return RecordPhase::Finalizing;
}
case RecordPhase::Done:
case RecordPhase::Failed:
default:
return current;
}
}
bool isStopRequested(RecordPhase phase) {
return phase != RecordPhase::Recording;
}
bool isTerminalPhase(RecordPhase phase) {
return phase == RecordPhase::Done || phase == RecordPhase::Failed;
}
} // namespace reasampler::capture