117 lines
6.2 KiB
C++
117 lines
6.2 KiB
C++
#pragma once
|
|
// capture_paths — the REAPER-free path arithmetic behind offline capture. The
|
|
// capture shell resolves the current project directory via REAPER APIs, then
|
|
// hands the raw strings here. Forward-slash form throughout, no filesystem
|
|
// access; the bank subfolder name is a fixed constant.
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace reasampler::capture {
|
|
|
|
// The project-relative bank subfolder. All captured wavs live here so the bank
|
|
// travels with the .rpp.
|
|
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 BankModel (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 (does not
|
|
// consult the filesystem). On Windows (_WIN32) also lowercases the result so
|
|
// paths differing only in casing compare equal; macOS/Linux preserve case.
|
|
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 with '_', and collapses to
|
|
// "capture" if nothing usable remains. Deterministic.
|
|
std::string sanitizeStem(const std::string& baseName);
|
|
|
|
// Derives the bank paths for one capture: baseName is the sanitized file-stem
|
|
// source, uniqueTag an optional sanitized disambiguator (timestamp/counter) so
|
|
// repeated captures don't collide. Produces "<stem>[_<tag>].wav".
|
|
BankPaths deriveBankPaths(const std::string& projectDir,
|
|
const std::string& baseName,
|
|
const std::string& uniqueTag);
|
|
|
|
// The project-relative index spelling for a bank file known only by its file
|
|
// name (bare entry, no directory) — the prune shell uses this to spell an
|
|
// enumerated folder entry the SAME way deriveBankPaths spelled it at capture
|
|
// time; a divergence here could make a referenced file look like an orphan.
|
|
std::string bankRelativeForName(const std::string& fileName);
|
|
|
|
// --- Persist-side path arithmetic -------------------------------------------
|
|
//
|
|
// The index stores relative paths only; on project load the persist shell
|
|
// turns each relativePath back into an absolute path against the current
|
|
// project directory — the inverse of deriveBankPaths.
|
|
|
|
// Returns "<projectDir>/<relativePath>" forward-slashed, or empty if either
|
|
// input is empty (no default-location fallback — an unsaved/unset project
|
|
// fails loudly rather than resolving against CWD).
|
|
std::string resolveBankFile(const std::string& projectDir,
|
|
const std::string& relativePath);
|
|
|
|
// The project directory that holds a .rpp: parent directory, forward-slashed,
|
|
// trailing slash stripped. Empty in -> empty out (an unsaved project reports
|
|
// an empty .rpp path). Pure so the VST3 instrument resolves audio paths the
|
|
// same way persist does.
|
|
std::string projectDirOfRpp(const std::string& rppPath);
|
|
|
|
// A relocation plan for the physical bank folder on Save-As to a new project
|
|
// location. The index's relative paths do NOT change (they are relative to the
|
|
// project dir, which moved with the .rpp), so relocation is purely a folder
|
|
// move. Both dirs are absolute, forward-slashed, trailing-slash-stripped.
|
|
struct BankRelocation {
|
|
std::string oldBankDir; // <oldProjectDir>/reasampler_bank
|
|
std::string newBankDir; // <newProjectDir>/reasampler_bank
|
|
bool needed = false; // false when old==new (Save in place, not Save-As)
|
|
};
|
|
|
|
// Derives the relocation plan: `needed` is true iff the normalized old/new
|
|
// project dirs differ (a genuine Save-As-to-new-dir); empty dirs/needed=false
|
|
// when either input is empty.
|
|
BankRelocation deriveRelocationPlan(const std::string& oldProjectDir,
|
|
const std::string& newProjectDir);
|
|
|
|
// --- Project-identity transition ---------------------------------------------
|
|
//
|
|
// What the persist timer must do on each tick. GUID is checked FIRST because
|
|
// two prior pointer-primary/GUID-only designs each broke a real case: a
|
|
// GUID-only check misreads a Save-As fork as the same project (fork and
|
|
// parent share a GUID on disk); a pointer-primary check misreads REAPER
|
|
// recycling a closed project's ReaProject* address onto an unrelated project
|
|
// (a different project, same recycled pointer, read as NoOp/SaveAsRelocate —
|
|
// the bank never reloads). Checking GUID first catches recycling; the pointer
|
|
// (sameProjectObject) then separates a forked sibling (Load) from a genuine
|
|
// Save-As (SaveAsRelocate).
|
|
enum class ProjectTransition {
|
|
NoOp, // same object, same GUID, same location — nothing to do
|
|
Load, // a different project is active — load ITS index from ext state
|
|
SaveAsRelocate, // SAME object + SAME GUID, new .rpp location — relocate the bank
|
|
};
|
|
|
|
// Classifies what a poll tick observed. sameProjectObject is passed as a bool
|
|
// (not the raw pointer) to keep the classifier REAPER-free and testable;
|
|
// lastGuid/lastPath is the project persist last acted on, currentGuid/
|
|
// currentPath the now-active project (both "" if unsaved/unwritten).
|
|
// Evaluated in order: currentGuid!=lastGuid -> Load; !sameProjectObject ->
|
|
// Load (forked sibling); currentPath!=lastPath -> SaveAsRelocate (also covers
|
|
// first save of an unsaved project); else NoOp.
|
|
ProjectTransition classifyProjectTransition(bool sameProjectObject,
|
|
const std::string& lastGuid,
|
|
const std::string& lastPath,
|
|
const std::string& currentGuid,
|
|
const std::string& currentPath);
|
|
|
|
} // namespace reasampler::capture
|