feat(persist): M4 bank persistence + Save-As relocation
Serialize BankIndex to project ext state ('reasampler'), reload on project
load, resolve paths project-relative. Save-As copies the bank to the new .rpp;
identity keyed off a minted GUID (not the recycled ReaProject*) so project
switches don't clobber banks. Pure classifyProjectTransition tested.
This commit is contained in:
@@ -73,4 +73,64 @@ BankPaths deriveBankPaths(const std::string& projectDir,
|
||||
return p;
|
||||
}
|
||||
|
||||
std::string resolveBankFile(const std::string& projectDir,
|
||||
const std::string& relativePath) {
|
||||
// No default-location fallback (CLAUDE.md invariant): an empty project dir or
|
||||
// relative path yields empty, not a bare relative path resolved against CWD.
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
// A Save (in place) leaves the project dir unchanged — nothing to relocate.
|
||||
// Only a Save-As to a different directory needs the bank moved.
|
||||
r.needed = (oldDir != newDir);
|
||||
return r;
|
||||
}
|
||||
|
||||
ProjectTransition classifyProjectTransition(const std::string& lastGuid,
|
||||
const std::string& lastPath,
|
||||
const std::string& currentGuid,
|
||||
const std::string& currentPath) {
|
||||
// A changed GUID is the unambiguous signal of a different project — load it.
|
||||
// This is the whole point of the content-based identity: it survives pointer
|
||||
// recycling that the old pointer-equality check could not distinguish from a
|
||||
// Save-As.
|
||||
if (currentGuid != lastGuid) {
|
||||
return ProjectTransition::Load;
|
||||
}
|
||||
|
||||
// Same GUID from here on. If it is empty, we have NO proof the two ticks saw
|
||||
// the same project (an unsaved project can't carry a stored GUID). A path
|
||||
// change under an empty GUID is therefore a first-save or a switch between
|
||||
// unsaved projects — load, never relocate (relocating would copy the wrong
|
||||
// bank over a real one, the defect being fixed).
|
||||
if (currentGuid.empty()) {
|
||||
return (currentPath == lastPath) ? ProjectTransition::NoOp
|
||||
: ProjectTransition::Load;
|
||||
}
|
||||
|
||||
// Same NON-EMPTY GUID: proven same project. A path change is a genuine
|
||||
// Save-As to a new location; an unchanged path is Save-in-place / idle.
|
||||
return (currentPath == lastPath) ? ProjectTransition::NoOp
|
||||
: ProjectTransition::SaveAsRelocate;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
|
||||
Reference in New Issue
Block a user