diff --git a/src/persist.cpp b/src/persist.cpp index 5016b14..924b653 100644 --- a/src/persist.cpp +++ b/src/persist.cpp @@ -163,10 +163,46 @@ void ReaSamplerSession::saveToActiveProject() { const std::string json = bank_.serialize(); SetProjExtState(static_cast(proj), kProjExtNamespace, kProjExtIndexKey, json.c_str()); + + // Additive: the Design-View model rides alongside the bank in its own key. + // Independent write — does not disturb the bank_index above. + const std::string viewJson = view_.serialize(); + SetProjExtState(static_cast(proj), kProjExtNamespace, + kProjExtViewKey, viewJson.c_str()); + MarkProjectDirty(static_cast(proj)); } +namespace { + +// Load the Design-View model from a project's view_state key, or return a fresh +// default. An absent/empty key (older project with no view state) yields a +// default-constructed model (Arrange + Design seeded, active = Arrange) — graceful, +// never a crash. Malformed JSON is warned and also falls back to default, mirroring +// the bank's malformed-index handling. The whole model round-trips: modes, +// membership, show-both, snapshots, and active mode all ride inside the one blob. +ViewModeModel loadViewModel(ReaProject* proj) { + if (!proj) return ViewModeModel{}; + const std::string viewJson = + getProjExtStateString(proj, kProjExtNamespace, kProjExtViewKey); + if (viewJson.empty()) return ViewModeModel{}; // no stored view state -> default + std::optional loaded = ViewModeModel::deserialize(viewJson); + if (!loaded) { + ShowConsoleMsg("ReaSampler: stored view state is malformed — ignoring.\n"); + return ViewModeModel{}; + } + return std::move(*loaded); +} + +} // namespace + void ReaSamplerSession::loadFromProject(void* proj, const std::string& projectDir) { + // The view model is restored on EVERY load path (peer-symmetry with the bank + // reset below): switching to a project with no view state must clear stale + // in-memory state, not inherit the previous project's. D3 restores MODEL STATE + // only — no visibility/processing is applied here (that is D4). + view_ = loadViewModel(static_cast(proj)); + if (!proj) { bank_ = BankIndex{}; return; diff --git a/src/persist.h b/src/persist.h index 453b36d..bab2a8a 100644 --- a/src/persist.h +++ b/src/persist.h @@ -20,6 +20,7 @@ #include #include "bank_model.h" +#include "view_mode_model.h" namespace reasampler { @@ -31,6 +32,12 @@ inline constexpr const char* kProjExtNamespace = "reasampler"; // serialized BankIndex). FOREVER-STABLE for the same reason. inline constexpr const char* kProjExtIndexKey = "bank_index"; +// The ext-state key the Design-View ViewModeModel JSON is stored under (one key +// holds the whole serialized model: modes + membership + show-both + snapshots + +// active mode). Distinct from kProjExtIndexKey — one namespace, two keys. +// FOREVER-STABLE: changing it orphans every already-saved project's view state. +inline constexpr const char* kProjExtViewKey = "view_state"; + // The ext-state key holding a GUID we mint per project to establish CONTENT-BASED // project identity (REAPER exposes no stable per-project GUID). poll() uses it to // tell a genuine Save-As (same GUID, new .rpp path) apart from a project switch @@ -62,6 +69,13 @@ public: BankIndex& bank() { return bank_; } const BankIndex& bank() const { return bank_; } + // The in-memory Design-View model. The view/action layer mutates it (tag, + // toggle, snapshot); persist serializes it on save and replaces it on project + // load — exactly as it treats the bank. D3 persists MODEL STATE only; applying + // visibility/processing (reapply-on-open) is D4's job, not this member's. + ViewModeModel& view() { return view_; } + const ViewModeModel& view() const { return view_; } + // Serialize the current bank to the active project's ext state (namespace // "reasampler"). Non-destructive beyond writing our own ext-state key. Safe // to call when there is no active/saved project (it no-ops). @@ -75,6 +89,11 @@ public: private: BankIndex bank_; + // The Design-View model. Default-constructed = Arrange + Design seeded, active + // = Arrange; loadFromProject leaves this default when a project has no stored + // view_state (older project), so an absent key is graceful, not a crash. + ViewModeModel view_; + // The project identity last observed by poll(), used to detect load/Save-As. // Identity is the GUID we mint per project (kProjExtGuidKey), NOT the raw // ReaProject* pointer — see the class comment for why. The .rpp path is