170 lines
8.7 KiB
C++
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
|