From dc5d36ff4321c4452731e8a6db005d28706cfa24 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Sun, 26 Jul 2026 18:46:17 -0400 Subject: [PATCH] =?UTF-8?q?fix(vst):=20channel-derive=20the=20shared=20ext?= =?UTF-8?q?-state=20namespace=20(V4=E2=86=94S4=20reconcile)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ext_keys.h's kProjExtNamespace now delegates to app_version::extStateNamespace() so the beta instrument reads "reasampler_beta" — the namespace the beta extension writes — instead of stale stable. Link app_version into reasampler_vst. --- CMakeLists.txt | 5 ++++- src/ext_keys.h | 20 +++++++++++++++----- src/vst/reaper_bridge.cpp | 12 +++++++----- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5da172f..0cd069d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -829,8 +829,11 @@ if(WIN32) # give the shell TUs their headers (ext_keys.h, bank_book.h, sampler_core.h, ...). # embed_strip (S6): the pure inline-strip layout + hit-test the embed shell marshals # into; it links editor_geometry transitively (shared Rect). + # app_version: ext_keys.h's channel-derived namespace accessor (V4) delegates to it, so + # the instrument reads the SAME namespace the extension writes; its PUBLIC include dir + # (build/generated) carries version_generated.h for the channel bit. target_link_libraries(reasampler_vst PRIVATE vst3_sdk editor_geometry bridge_marshal - sample_map capture_paths embed_strip) + sample_map capture_paths embed_strip app_version) # SDK_INC gives reaper_vst3_interfaces.h + reaper_plugin_functions.h for the bridge; # WDL_INC gives LICE for the editor. The VST3 SDK headers come from vst3_sdk PUBLIC. target_include_directories(reasampler_vst PRIVATE ${SDK_INC} ${WDL_INC}) diff --git a/src/ext_keys.h b/src/ext_keys.h index 66ef9a2..9461094 100644 --- a/src/ext_keys.h +++ b/src/ext_keys.h @@ -5,18 +5,28 @@ // wire contract cannot drift between the two artifacts (the S4 reviewer flagged the // spike's duplicated constants as a drift risk). // -// PURE HEADER: NO REAPER types, NO VST3 types, NO SWELL, NO vendor/ includes. Just -// string constants, so both the REAPER-facing persist shell and the SDK-facing VST -// bridge can include it without pulling either SDK. +// PURE HEADER: NO REAPER types, NO VST3 types, NO SWELL, NO vendor/ includes. The key +// spellings are string constants; the NAMESPACE is channel-derived (Phase V, V4) so it +// delegates to the pure app_version module (also REAPER-free / VST3-free). Both the +// REAPER-facing persist shell and the SDK-facing VST bridge include this without pulling +// either SDK. // // FOREVER-STABLE once shipped: these strings key every already-saved project's // stored state. Changing any of them orphans that state. See persist.h for the // per-key retirement / migration semantics — this header only owns the spellings. +#include "app_version.h" + namespace reasampler { -// The ext-state namespace all ReaSampler project state is stored under. -inline constexpr const char* kProjExtNamespace = "reasampler"; +// The ext-state namespace all ReaSampler project state is stored under. CHANNEL-DERIVED +// (Phase V, V4): delegates to the ONE app_version symbol so the extension (writer) and the +// VST3 instrument (reader) resolve the SAME namespace per channel — "reasampler" on stable, +// "reasampler_beta" on the isolated beta build. An accessor (not a constexpr literal) +// because the value is fixed by the channel bit at build time. This is the wire-contract +// reconciliation between S4 (shared ext_keys) and V4 (channel-isolated namespace): without +// it a beta instrument would read the stable namespace and see empty state. +inline const char* kProjExtNamespace() { return extStateNamespace().c_str(); } // The multi-bank key: the whole serialized BankBook (pool + named banks). This is // the key the VST3 instrument reads to see the live bank (read-only, S4). persist.h diff --git a/src/vst/reaper_bridge.cpp b/src/vst/reaper_bridge.cpp index f58e567..5df2429 100644 --- a/src/vst/reaper_bridge.cpp +++ b/src/vst/reaper_bridge.cpp @@ -29,10 +29,12 @@ namespace Steinberg { // interface (FUnknownPtr uses the iid), so the definition lives with its sole use. DEF_CLASS_IID(Steinberg::IReaperHostApplication) -// The "reasampler" ext-state namespace is the SHARED wire contract between the -// extension (writer) and this instrument (reader); it lives in ext_keys.h (pure, -// REAPER-free) — reasampler::kProjExtNamespace — so the two artifacts read one symbol -// and cannot drift. The S1 spike duplicated it locally; that duplication is retired. +// The ext-state namespace is the SHARED wire contract between the extension (writer) +// and this instrument (reader); it lives in ext_keys.h (pure, REAPER-free) — +// reasampler::kProjExtNamespace() — so the two artifacts read one symbol and cannot +// drift. Channel-derived (Phase V, V4): the accessor returns "reasampler" (stable) or +// "reasampler_beta" (beta), matching whatever the extension wrote. The S1 spike +// duplicated it locally; that duplication is retired. namespace reasampler::vst { @@ -83,7 +85,7 @@ std::optional ReaperBridge::readReasamplerExtState(const std::strin // complete, else grow and retry up to a 16 MB ceiling. for (int cap = 1 << 16; cap <= (1 << 24); cap <<= 2) { std::vector buf(static_cast(cap), '\0'); - const int rv = getProjExtState_(proj, kProjExtNamespace, key.c_str(), + const int rv = getProjExtState_(proj, kProjExtNamespace(), key.c_str(), buf.data(), cap); if (rv <= 0) return std::nullopt; // absent / empty key std::string s(buf.data());