feat(persist): persist BankBook under banks key, retire legacy bank_index, route capture to active bank (B2)
This commit is contained in:
+60
-32
@@ -5,11 +5,15 @@
|
||||
// WITHOUT REAPERAPI_IMPLEMENT — main.cpp is the one TU that defines the API
|
||||
// pointers; here they are extern (CLAUDE.md §contract).
|
||||
//
|
||||
// Storage: SetProjExtState / GetProjExtState, namespace "reasampler", key
|
||||
// "bank_index". Ext state is stored INSIDE the .rpp, so the index travels with
|
||||
// the project automatically (CONTEXT.md §Persistence & paths). The only thing
|
||||
// that does NOT travel for free is the physical bank folder; on Save-As to a new
|
||||
// directory we relocate it so the index's relative paths still resolve.
|
||||
// Storage: SetProjExtState / GetProjExtState, namespace "reasampler". Phase B: the
|
||||
// whole BankBook (pool as bank-zero + named banks) is written under key "banks"
|
||||
// (authoritative); the legacy single-bank key "bank_index" is RETIRED — cleared on
|
||||
// save (SetProjExtState with "" deletes it) and read only once, to migrate a pre-
|
||||
// multi-bank project's index into the pool. Ext state is stored INSIDE the .rpp, so
|
||||
// the banks travel with the project automatically (CONTEXT.md §Persistence & paths).
|
||||
// The only thing that does NOT travel for free is the physical bank folder; on
|
||||
// Save-As to a new directory we relocate it so the indices' relative paths still
|
||||
// resolve.
|
||||
//
|
||||
// PROJECT-LOAD / SAVE-AS DETECTION (chosen mechanism):
|
||||
// Driven by REAPER's "timer" register (main.cpp). Each poll() reads the active
|
||||
@@ -174,12 +178,22 @@ void ReaSamplerSession::saveToActiveProject() {
|
||||
if (!proj) return; // no active project — nothing to persist
|
||||
if (rppPath.empty()) return; // unsaved project — no .rpp to store into
|
||||
|
||||
const std::string json = bank_.serialize();
|
||||
// Phase B: the whole book (pool as bank-zero + named banks) is authoritative and
|
||||
// rides in the `banks` key.
|
||||
const std::string banksJson = book_.serialize();
|
||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
||||
kProjExtIndexKey, json.c_str());
|
||||
kProjExtBanksKey, banksJson.c_str());
|
||||
|
||||
// Additive: the Design-View model rides alongside the bank in its own key.
|
||||
// Independent write — does not disturb the bank_index above.
|
||||
// Retire the legacy single-bank `bank_index` key: SetProjExtState with an empty
|
||||
// value DELETES the key (SDK header ~6288: val NULL or "" deletes the data). This
|
||||
// realizes retirement concretely — after any save, a formerly-legacy project
|
||||
// carries `banks` and NO `bank_index`, and going forward the legacy key is never
|
||||
// written. Cheap and idempotent when the key is already absent.
|
||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
||||
kProjExtIndexKey, "");
|
||||
|
||||
// Additive: the Design-View model rides alongside the banks in its own key.
|
||||
// Independent write — does not disturb the `banks` blob above.
|
||||
const std::string viewJson = view_.serialize();
|
||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
||||
kProjExtViewKey, viewJson.c_str());
|
||||
@@ -226,32 +240,46 @@ void ReaSamplerSession::loadFromProject(void* proj, const std::string& projectDi
|
||||
view_ = loadViewModel(static_cast<ReaProject*>(proj));
|
||||
|
||||
if (!proj) {
|
||||
bank_ = BankIndex{};
|
||||
book_ = BankBook{};
|
||||
return;
|
||||
}
|
||||
const std::string json =
|
||||
getProjExtStateString(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
||||
kProjExtIndexKey);
|
||||
if (json.empty()) {
|
||||
// No stored index (new or never-captured project) — start empty.
|
||||
bank_ = BankIndex{};
|
||||
return;
|
||||
}
|
||||
std::optional<BankIndex> loaded = BankIndex::deserialize(json);
|
||||
if (!loaded) {
|
||||
ShowConsoleMsg("ReaSampler: stored bank index is malformed — ignoring.\n");
|
||||
bank_ = BankIndex{};
|
||||
return;
|
||||
}
|
||||
bank_ = std::move(*loaded);
|
||||
|
||||
// Project-relative resolution is a READ-time concern: the index stores only
|
||||
// relative paths (invariant), and consumers (M5 panel, M6 insert) resolve
|
||||
// each entry against the CURRENT project dir via resolveBankFile(projectDir,
|
||||
// relativePath). We do NOT rewrite the stored paths to absolute here — that
|
||||
// would break the relative-only invariant and the travel-with-.rpp property.
|
||||
// projectDir is threaded through for those consumers; nothing to do at load
|
||||
// time beyond replacing the in-memory bank.
|
||||
// Read both possible sources: the authoritative `banks` blob and the retired-but-
|
||||
// possibly-still-present legacy `bank_index`. The precedence + migration decision
|
||||
// (`banks` wins; else the legacy index migrates into the pool; else an empty book)
|
||||
// is pure logic; it is inlined here rather than via BankBook::loadFromPersisted only
|
||||
// so a malformed `banks` blob can be warned on the console (single parse) — a corrupt
|
||||
// blob must read as "ignored", not silent loss, mirroring the prior malformed-index
|
||||
// warning. A malformed `banks` degrades to an empty book and does NOT fall back to
|
||||
// the stale legacy key (which would resurrect superseded single-bank state).
|
||||
const std::string banksJson =
|
||||
getProjExtStateString(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
||||
kProjExtBanksKey);
|
||||
if (!banksJson.empty()) {
|
||||
std::optional<BankBook> loaded = BankBook::deserialize(banksJson);
|
||||
if (!loaded) {
|
||||
ShowConsoleMsg("ReaSampler: stored banks are malformed — ignoring.\n");
|
||||
book_ = BankBook{};
|
||||
} else {
|
||||
book_ = std::move(*loaded);
|
||||
}
|
||||
} else {
|
||||
// No `banks` yet — fall back to the legacy `bank_index`, migrated into the pool
|
||||
// by BankBook's parse-time promotion. loadFromPersisted covers the legacy-or-
|
||||
// empty tail; passing "" for banksJson takes exactly that branch.
|
||||
const std::string legacyJson =
|
||||
getProjExtStateString(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
||||
kProjExtIndexKey);
|
||||
book_ = BankBook::loadFromPersisted(std::string{}, legacyJson);
|
||||
}
|
||||
|
||||
// Project-relative resolution is a READ-time concern: every BankIndex in the book
|
||||
// stores only relative paths (invariant, enforced per-bank at add()), and consumers
|
||||
// (M5 panel, M6 insert) resolve each entry against the CURRENT project dir via
|
||||
// resolveBankFile(projectDir, relativePath). We do NOT rewrite stored paths to
|
||||
// absolute here — that would break the relative-only invariant and travel-with-.rpp.
|
||||
// projectDir is threaded through for those consumers; nothing to do at load time
|
||||
// beyond replacing the in-memory book.
|
||||
(void)projectDir;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user