Merge Phase D3: persist ViewModeModel in project ext state

This commit is contained in:
2026-07-22 21:19:38 -04:00
2 changed files with 55 additions and 0 deletions
+36
View File
@@ -163,10 +163,46 @@ void ReaSamplerSession::saveToActiveProject() {
const std::string json = bank_.serialize(); const std::string json = bank_.serialize();
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace, SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
kProjExtIndexKey, json.c_str()); 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<ReaProject*>(proj), kProjExtNamespace,
kProjExtViewKey, viewJson.c_str());
MarkProjectDirty(static_cast<ReaProject*>(proj)); MarkProjectDirty(static_cast<ReaProject*>(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<ViewModeModel> 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) { 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<ReaProject*>(proj));
if (!proj) { if (!proj) {
bank_ = BankIndex{}; bank_ = BankIndex{};
return; return;
+19
View File
@@ -20,6 +20,7 @@
#include <string> #include <string>
#include "bank_model.h" #include "bank_model.h"
#include "view_mode_model.h"
namespace reasampler { namespace reasampler {
@@ -31,6 +32,12 @@ inline constexpr const char* kProjExtNamespace = "reasampler";
// serialized BankIndex). FOREVER-STABLE for the same reason. // serialized BankIndex). FOREVER-STABLE for the same reason.
inline constexpr const char* kProjExtIndexKey = "bank_index"; 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 // 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 // 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 // tell a genuine Save-As (same GUID, new .rpp path) apart from a project switch
@@ -62,6 +69,13 @@ public:
BankIndex& bank() { return bank_; } BankIndex& bank() { return bank_; }
const BankIndex& bank() const { 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 // Serialize the current bank to the active project's ext state (namespace
// "reasampler"). Non-destructive beyond writing our own ext-state key. Safe // "reasampler"). Non-destructive beyond writing our own ext-state key. Safe
// to call when there is no active/saved project (it no-ops). // to call when there is no active/saved project (it no-ops).
@@ -75,6 +89,11 @@ public:
private: private:
BankIndex bank_; 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. // 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 // Identity is the GUID we mint per project (kProjExtGuidKey), NOT the raw
// ReaProject* pointer — see the class comment for why. The .rpp path is // ReaProject* pointer — see the class comment for why. The .rpp path is