M3: offline-render capture spike (master-mix / time-selection)

ICaptureBackend/CaptureRequest seam + OfflineRenderBackend driving snapshotted
RENDER_* with dither/normalize forced off for bit-identical output, RenderFailed
via filesystem check, and a pure capture_paths lib with tests. Spike action registered.
This commit is contained in:
2026-07-22 13:07:31 -04:00
parent 93e2783098
commit d9ad8e4adf
7 changed files with 837 additions and 26 deletions
+56
View File
@@ -0,0 +1,56 @@
#pragma once
// capture_paths — the REAPER-free path arithmetic behind offline capture.
//
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO
// vendor/ includes. Standard library only. The capture shell resolves the
// current project directory via REAPER APIs, then hands the raw strings here so
// the fiddly, easy-to-get-wrong path arithmetic (bank subfolder, unique file
// name, absolute render dir, project-relative index path) is unit-tested outside
// the DAW.
//
// Path convention: this module works in forward-slash form and does NOT touch
// the filesystem. The bank subfolder name is a fixed constant so the same
// project always resolves the same bank location (determinism).
#include <string>
namespace reasampler {
// The project-relative bank subfolder. All captured wavs live here so the bank
// travels with the .rpp (CONTEXT.md §Settled decisions: per-project bank).
inline constexpr const char* kBankSubfolder = "reasampler_bank";
// A resolved pair of paths for one capture: where REAPER must be told to write
// (absolute, because RENDER_FILE wants a directory REAPER can create/open) and
// what we store in the BankIndex (project-relative, because the index is
// relative-paths-only — CLAUDE.md precision invariant).
struct BankPaths {
std::string absoluteDir; // <projectDir>/reasampler_bank (forward slash)
std::string relativePath; // reasampler_bank/<fileName> (index value)
std::string fileName; // <stem>.wav (full file name)
std::string fileStem; // <stem> (RENDER_PATTERN — REAPER appends the extension)
};
// Normalizes a path to forward slashes and strips any trailing slash. Empty in
// -> empty out. Pure string transform (does not consult the filesystem).
std::string normalizeSlashes(const std::string& path);
// Sanitizes a caller-supplied base name into a filesystem-safe stem: keeps
// [A-Za-z0-9._-], replaces every other byte (spaces, slashes, quotes, control)
// with '_', and collapses to "capture" if nothing usable remains. Deterministic:
// the same input always yields the same stem (feeds bit-identical file naming).
std::string sanitizeStem(const std::string& baseName);
// Derives the bank paths for one capture.
// projectDir : absolute directory of the current .rpp (any slash style)
// baseName : human base for the file stem (sanitized)
// uniqueTag : caller-supplied disambiguator appended to the stem (e.g. a
// timestamp or counter) so repeated captures do not collide.
// Also sanitized. May be empty.
// Produces "<stem>[_<tag>].wav". The relativePath is always project-relative and
// forward-slashed so it satisfies BankIndex::add's relative-only invariant.
BankPaths deriveBankPaths(const std::string& projectDir,
const std::string& baseName,
const std::string& uniqueTag);
} // namespace reasampler