Files
reasampler/src/shell/capture/capture.h
T

170 lines
8.7 KiB
C++

#pragma once
// The shared capture seam: CaptureRequest/CaptureResult (types both backends
// speak), OfflineRenderBackend, and the helpers both backends share (naming, the
// mono collapse, the Sample stamp).
// Realtime's async begin/tick/abort surface lives in capture_realtime_shell.h.
//
// REAPER-free on purpose (bank_model only) so callers can depend on the seam
// without dragging the SDK into every include site; the .cpp is the REAPER TU.
#include <string>
#include <vector>
#include "core/model/bank_model.h"
#include "core/capture/capture_name.h" // CaptureName — label + file-stem base
#include "core/capture/render_settings.h" // TailMode — the three-state tail contract
#include "core/capture/wav_codec.h" // MonoCollapseOutcome — the collapse's report
// Forward-declared, never dereferenced here — only the REAPER-facing .cpp touches these.
class MediaTrack;
class ReaProject;
namespace reasampler::capture {
using model::Sample;
// 32-bit float is the default; rationale lives in capture.cpp next to the sink-config bytes.
enum class WavBitDepth {
Int16,
Int24,
Float32,
};
// Where the render lands. TWO VALUES, never a caller-supplied path string: the
// backend resolves each to a directory itself, which is what makes "write into the
// bank folder" inexpressible from the ProjectMedia side and vice versa.
enum class CaptureDestination {
Bank, // <projectDir>/reasampler_bank — every capture path
ProjectMedia, // the project's recording path — the render-in-place verb only
};
// One capture, independent of source mode.
struct CaptureRequest {
SourceMode sourceMode = SourceMode::MasterMix;
// Sample-accurate render bounds in project seconds — NO rounding.
double startSeconds = 0.0;
double endSeconds = 0.0;
// 1.0 = fully wet, 0.0 = fully dry. Every current capture action sets 1.0;
// true pre-FX dry isn't available via RENDER_SETTINGS (needs FX-bypass-around-render
// or the realtime pre-FX path) so this stays a seam for that future work.
double wetDry = 1.0;
// Track GUID(s) when source mode is track-scoped (SelectedTracks); empty otherwise.
// The backend only copies these onto the Sample — it never reads selection itself.
std::vector<std::string> trackGuids;
// Render tail (docs/product/capture-tail.md §The three tail states). None = exact
// bounds, no added silence — the only mode valid for null-test/verify captures.
// tailMs applies only to Manual (clamped to 8s by the pure mapping); Auto uses
// the 8s cap + -72 dB trim internally, None ignores it.
TailMode tailMode = TailMode::None;
double tailMs = 0.0;
// 0 sampleRate => follow project rate.
int sampleRate = 0;
// What the RENDER is asked for (RENDER_CHANNELS / the realtime record mode), not
// what the capture lands as: a dual-mono render is collapsed to 1 channel after
// the fact, and the Sample's count comes from the produced file.
int channelCount = 2;
WavBitDepth bitDepth = WavBitDepth::Float32;
// Sanitized by capture_paths. uniqueTag (disambiguator) is supplied by the
// backend caller so the pure naming logic stays testable.
std::string baseName = "capture";
std::string uniqueTag;
// The label the bank shows, which may legitimately differ from the file stem: the
// stem must survive sanitizeStem, the label carries the source name verbatim. Empty
// means "the stem base is also the label" — what a caller that names nothing else gets.
std::string displayName;
// The one home for that fallback rule; both backends populate Sample::displayName
// from here rather than each spelling the condition out.
std::string label() const { return displayName.empty() ? baseName : displayName; }
// Default Bank: every existing entry point renders into the bank untouched.
CaptureDestination destination = CaptureDestination::Bank;
};
// Every failure is an explicit code, never a thrown exception across the REAPER boundary.
enum class CaptureStatus {
Ok,
NoProject, // no active project to render / resolve a bank folder
EmptyRange, // start >= end: nothing to render
UnsupportedMode, // backend does not implement this source mode
UnsupportedFormat, // requested bit depth has no known REAPER blob (Float32 only)
RenderFailed, // the render action ran but produced no output file
TransportBusy, // realtime backend: transport already playing/recording — refused
MultiTrackSelection, // a selected-tracks render over >1 track — would render N files
BoundsMismatch, // the rendered file's frames are not the requested window's — or
// could not be measured to say (render_bounds_gate)
};
struct CaptureResult {
CaptureStatus status = CaptureStatus::RenderFailed;
Sample sample; // valid only when status == Ok
std::string message; // human-readable detail for the console log
// The file the render actually landed, absolute — the only handle a caller that
// banks nothing has on its own output (sample.relativePath is empty on the
// ProjectMedia destination). Set on the Ok path only.
std::string absolutePath;
};
// Deterministic offline-render backend: master mix / time selection / selected
// tracks / selected items / razor area, all wet-only, optional tail. Source
// selection + range are resolved by the caller and handed in via CaptureRequest —
// the backend drives RENDER_* and never reads the DAW selection itself.
// SourceMode::Realtime returns UnsupportedMode. Non-destructive: restores every
// RENDER_* setting it touches on every path. Plain concrete class — see the
// no-shared-interface note in capture_realtime_shell.h before adding one back.
class OfflineRenderBackend {
public:
CaptureResult capture(const CaptureRequest& request);
};
// Mints the filesystem-safe disambiguating tag for one capture's file stem +
// Sample id: "<prefix><unix-epoch-seconds>-<n>", <n> a per-session monotonic
// counter. Wall-clock seconds alone collide when batch capture drives short
// renders back-to-back, silently overwriting the first file. `prefix` is the
// backend's family marker ("" offline, "rt-" realtime).
std::string makeUniqueTag(const std::string& prefix);
// Composes one capture's label + file-stem base (core/capture/capture_name) from the
// resolved source-track names, reading the LOCAL clock for the discriminator — the one
// impure step, kept here so the composition itself stays pure and tested. `ordinal` is a
// batch unit's number (0 for a single capture); `fallback` is the scope literal, used
// only when no source name resolved.
CaptureName captureNameFor(const std::vector<std::string>& sourceNames,
int ordinal, const std::string& fallback);
// Rewrites a just-captured WAV in place as a 1-channel file when its channels are
// bit-identical (the pure `collapseToMono` decides). Every other file is left
// untouched, byte for byte, so the not-collapsed path is exactly what the backend
// produced. Must run BEFORE stampCaptureSample, which measures the landed file.
// A Failed outcome is ALSO logged to the console here, because a successful capture's
// CaptureResult::message is not printed by any caller — the return value alone would
// leave a genuine I/O failure indistinguishable from a legitimately stereo capture.
// `consoleLabel` matches each caller's own console-prefix convention (offline:
// "ReaSampler capture"; realtime: "ReaSampler realtime capture").
MonoCollapseOutcome collapseCapturedFileToMono(const std::string& absolutePath,
const char* consoleLabel = "ReaSampler capture");
// Stamps the metadata shared by both backends onto `s`: trackGuids (echoed from the
// request) + channelCount (measured from the produced file's `fmt`; 0/unknown as the
// fallback for a file that cannot be parsed — never the request's value, which is
// always 2 and was never actually measured), resolved sampleRate (request rate,
// else PROJECT_SRATE from `rateProj`), captureTempo, the capture-start time signature
// (TimeMap_GetTimeSigAtTime against `timeSigProj` — offline passes nullptr for the
// active project, realtime pins the record's own project), the WAV-aware
// contentHash of `absolutePath` (left empty when unreadable), and createdTimestamp.
// Per-backend bits (id, paths, bounds, tier, realtime's length override) stay
// with each caller.
void stampCaptureSample(Sample& s, const CaptureRequest& req,
ReaProject* rateProj, ReaProject* timeSigProj,
const std::string& absolutePath);
} // namespace reasampler::capture