#include "core/capture/capture_paths.h" #include #include #include namespace reasampler::capture { std::string normalizeSlashes(const std::string& path) { std::string out = path; for (char& c : out) { if (c == '\\') c = '/'; } // Strip a trailing slash but preserve a lone "/" (root). if (out.size() > 1 && out.back() == '/') { out.pop_back(); } #ifdef _WIN32 for (char& c : out) c = static_cast(std::tolower(static_cast(c))); #endif return out; } std::string sanitizeStem(const std::string& baseName) { std::string out; out.reserve(baseName.size()); for (unsigned char c : baseName) { const bool keep = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-'; out.push_back(keep ? static_cast(c) : '_'); } // Collapse to a stable default if nothing alnum survived. bool hasAlnum = false; for (unsigned char c : out) { if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { hasAlnum = true; break; } } if (out.empty() || !hasAlnum) { return "capture"; } return out; } RenderPaths deriveRenderPaths(const std::string& absoluteDir, const std::string& baseName, const std::string& uniqueTag) { std::string stem = sanitizeStem(baseName); if (!uniqueTag.empty()) { stem += "_" + sanitizeStem(uniqueTag); } RenderPaths r; r.fileStem = stem; // stem only — REAPER appends the extension r.fileName = stem + ".wav"; r.absoluteDir = normalizeSlashes(absoluteDir); return r; } BankPaths deriveBankPaths(const std::string& projectDir, const std::string& baseName, const std::string& uniqueTag) { const std::string dir = normalizeSlashes(projectDir); // Precondition: caller must resolve a non-empty project directory — an // empty one would otherwise fall back to a bare relative path (forbidden). // Assert in debug; leave absoluteDir empty in release so a caller that // ignores it fails at the render/stat step, not silently onto CWD. assert(!dir.empty() && "deriveBankPaths: projectDir must not be empty"); const RenderPaths r = deriveRenderPaths( dir.empty() ? std::string{} : dir + "/" + kBankSubfolder, baseName, uniqueTag); BankPaths p; p.fileStem = r.fileStem; p.fileName = r.fileName; p.relativePath = bankRelativeForName(r.fileName); p.absoluteDir = r.absoluteDir; return p; } std::string bankRelativeForName(const std::string& fileName) { if (fileName.empty()) return {}; return std::string(kBankSubfolder) + "/" + fileName; } std::string resolveBankFile(const std::string& projectDir, const std::string& relativePath) { if (projectDir.empty() || relativePath.empty()) { return {}; } const std::string dir = normalizeSlashes(projectDir); const std::string rel = normalizeSlashes(relativePath); if (dir.empty() || rel.empty()) { return {}; } return dir + "/" + rel; } std::string projectDirOfRpp(const std::string& rppPath) { if (rppPath.empty()) return {}; // unsaved project: keep empty, no fallback std::string dir = std::filesystem::path(rppPath).parent_path().string(); return normalizeSlashes(dir); } BankRelocation deriveRelocationPlan(const std::string& oldProjectDir, const std::string& newProjectDir) { BankRelocation r; if (oldProjectDir.empty() || newProjectDir.empty()) { return r; // needed=false, empty dirs — nothing to relocate } const std::string oldDir = normalizeSlashes(oldProjectDir); const std::string newDir = normalizeSlashes(newProjectDir); r.oldBankDir = oldDir + "/" + kBankSubfolder; r.newBankDir = newDir + "/" + kBankSubfolder; r.needed = (oldDir != newDir); // Save-in-place leaves the dir unchanged return r; } ProjectTransition classifyProjectTransition(bool sameProjectObject, const std::string& lastGuid, const std::string& lastPath, const std::string& currentGuid, const std::string& currentPath) { // See capture_paths.h for the GUID-primary rationale and rule order. if (currentGuid != lastGuid) { return ProjectTransition::Load; } if (!sameProjectObject) { return ProjectTransition::Load; // forked sibling: same GUID, different object } if (currentPath != lastPath) { return ProjectTransition::SaveAsRelocate; } return ProjectTransition::NoOp; } } // namespace reasampler::capture