#pragma once // capture — the REAPER-facing capture shell (CLAUDE.md §load-bearing split). // // This header declares the capture *seam* the later milestones fill: // * CaptureRequest — everything a capture needs, source-mode-agnostic. // * ICaptureBackend — the one interface behind which OfflineRenderBackend // (M3, here) and RealtimeRecordBackend (M8) both sit. // * OfflineRenderBackend — the deterministic default; M3 implements ONLY the // time-selection master-mix case. // // It includes bank_model (pure) to hand back a populated Sample, but NO REAPER // headers — the .cpp is the REAPER-facing translation unit. Keeping this header // REAPER-free lets callers (main.cpp, future actions.cpp) depend on the seam // without dragging the SDK into every include site. #include #include #include "bank_model.h" namespace reasampler { // Audio bit-depth for the rendered wav. 32-bit float is the M3 default — // rationale lives in capture.cpp next to the sink-config bytes. enum class WavBitDepth { Int16, Int24, Float32, }; // One capture, independent of source mode. Populated by the caller (the action // handler in M3; the action family in M7) and consumed by a backend. // // M3 fills only the fields the master-mix/time-selection path needs; the rest // are declared now so M7/M8 do not reshape the struct (they are the seam). struct CaptureRequest { SourceMode sourceMode = SourceMode::MasterMix; // Sample-accurate render bounds in project seconds. For the M3 spike these // come straight from the time selection (GetSet_LoopTimeRange) — NO rounding. double startSeconds = 0.0; double endSeconds = 0.0; // 1.0 = fully wet, 0.0 = fully dry. All M7 actions set this to 1.0 (wet). // The field is kept as the seam for future true-dry work (M10 null test): // true pre-FX dry offline is NOT available via RENDER_SETTINGS — it requires // FX-bypass-around-render or the M8 realtime pre-FX path, and will be // designed alongside the M10 null test. Also recorded on the Sample. double wetDry = 1.0; // Track GUID(s) the capture came from, when the source mode is track-scoped // (SelectedTracks). Empty for master/items/razor. The action layer (M7) // resolves the selection to canonical GUID strings and passes them here; the // backend copies them onto the Sample (it does NOT itself read the selection — // it stays source-agnostic, driven entirely by the request). std::vector trackGuids; // Render tail. Default OFF for the spike (exact bounds, no added silence — // precision invariant). M7 makes this bindable. bool renderTail = false; double tailMs = 0.0; // Output format. 0 sampleRate => follow project rate (deterministic: the // project rate is fixed for a given project). int sampleRate = 0; int channelCount = 2; WavBitDepth bitDepth = WavBitDepth::Float32; // Human base name for the file stem; sanitized by capture_paths. The unique // tag (disambiguator) is supplied separately by the backend caller so the // pure naming logic stays testable. std::string baseName = "capture"; std::string uniqueTag; // e.g. a timestamp/counter; may be empty }; // Outcome of a capture attempt. `Ok` carries the populated Sample; every failure // is an explicit code (never a thrown exception across the REAPER boundary) so // the action handler can log a precise reason. 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 (M3 scope) UnsupportedFormat, // requested bit depth has no known REAPER blob (M3: Float32 only) RenderFailed, // the render action ran but produced no output file }; struct CaptureResult { CaptureStatus status = CaptureStatus::RenderFailed; Sample sample; // valid only when status == Ok std::string message; // human-readable detail for the console log }; // The capture seam. One method: run a request, return a populated Sample (or a // failure code). Backends are non-destructive — they must restore any global // state they touch before returning (OfflineRenderBackend snapshots/restores the // RENDER_* project settings). class ICaptureBackend { public: virtual ~ICaptureBackend() = default; virtual CaptureResult capture(const CaptureRequest& request) = 0; }; // Deterministic offline-render backend. M7 implements the full offline source // family — master mix / time selection, selected tracks, selected items, razor // area — all wet-only (render_settings.h) with optional tail. The source // selection + range are resolved by the caller (the action layer) and handed in // via the CaptureRequest; the backend drives RENDER_* and never reads the DAW // selection itself. SourceMode::Realtime returns UnsupportedMode (that is M8). class OfflineRenderBackend : public ICaptureBackend { public: CaptureResult capture(const CaptureRequest& request) override; }; } // namespace reasampler