feat(version): V4 beta-in-isolation channel via -DREASAMPLER_CHANNEL

Compile-time channel flag forks a fully isolated reaper_reasampler_beta
(namespace, command-id prefix, action names, dock ident, -beta render) from
one auditable app_version definition. Stable identity byte-unchanged.
This commit is contained in:
2026-07-26 16:06:24 -04:00
parent 0a9d8200c7
commit 5c0e3a8ccf
13 changed files with 648 additions and 224 deletions
+100 -5
View File
@@ -1,5 +1,5 @@
#pragma once
// app_version — the REAPER-free version-identity core (Phase V, V1). The single
// app_version — the REAPER-free version-identity core (Phase V, V1 + V4). The single
// source of truth for the version STRING lives in CMake (a `REASAMPLER_VERSION`
// variable threaded in via configure_file -> version_generated.h); this module
// re-exports it as the canonical constant and owns every pure operation on it: the
@@ -7,6 +7,17 @@
// migration will lean on, and the "which version wrote this project" result that
// persist reads back from ext state (absent stamp = pre-versioning, never an error).
//
// V4 (beta-in-isolation) extends this module into the SINGLE SOURCE OF TRUTH FOR
// CHANNEL IDENTITY too. A compile-time flag (`-DREASAMPLER_CHANNEL=beta`, threaded
// through the same configure_file'd version_generated.h as REASAMPLER_CHANNEL_IS_BETA)
// selects stable (the default, absent-flag build — byte-for-byte today's identity) or
// a fully isolated beta build. Every channel-qualified identity string the shells
// register with REAPER — the display suffix, the ext-state namespace, the command-id
// prefix, the Actions-list name prefix, the binary/dock idents — is DERIVED HERE from
// the one channel bit, so no scattered #ifdef forks live across the translation units;
// the shells just consume these accessors. This keeps "what makes a beta a beta" one
// auditable definition and makes the channel-derived rendering unit-testable.
//
// PURE MODULE: NO REAPER types, NO SWELL, NO vendor/ includes. Standard library
// only. Builds and unit-tests without REAPER (mirror of bank_model / tail_control).
//
@@ -21,12 +32,96 @@
namespace reasampler {
// The canonical version string — EXACTLY the value of the CMake `REASAMPLER_VERSION`
// variable (see version_generated.h, produced by configure_file). One edit point:
// changing that variable changes this constant, the ext-state stamp, and the
// show-version action output with no other edits. Leading zero preserved verbatim.
// --- Channel identity (V4, beta-in-isolation) ---------------------------------------
//
// The build channel, fixed at compile time by REASAMPLER_CHANNEL_IS_BETA (0 = stable,
// the default absent-flag build; 1 = beta, from -DREASAMPLER_CHANNEL=beta). Stable is
// today's build with byte-identical identity in EVERY string below — any divergence on
// the stable channel is a defect. Beta forks every identity so a beta binary coexists
// with stable in one REAPER (both are dlopen'd at startup) without colliding on project
// ext-state, keybindings, or any REAPER-global registration.
enum class Channel { Stable, Beta };
// The channel this build was compiled for. Constant per binary.
Channel channel();
// True on the beta build only. Convenience over channel() == Channel::Beta.
bool isBeta();
// The user-visible version render. Stable: EXACTLY the CMake string ("0.9.01"). Beta:
// that string plus a plain "-beta" suffix ("0.9.01-beta") — a plain suffix, NOT a
// git-describe decoration (V2, Daniel-fixed). This is what the show-version action and
// the bank-panel readout display. It is NOT the ext-state stamp value (see stampVersion).
const std::string& appVersion();
// The ext-state STAMP value — the writing-version recorded into a saved project. This is
// the NUMERIC TRIPLE ONLY ("0.9.01") on BOTH channels: it deliberately carries NO channel
// suffix, so (a) parseVersion classifies it as Stamped when its own channel reads it back
// (a "-beta"-suffixed stamp would classify as Unknown — the V4 stamp-classifiability
// requirement), and (b) stable's stamp value is byte-identical regardless of the channel
// build. The channel is carried by the ISOLATED namespace (see extStateNamespace), never
// baked into the stamp. Distinct from appVersion() precisely so the display can say
// "-beta" while the stamp stays classifiable and stable-identical.
const std::string& stampVersion();
// The project ext-state namespace this channel reads and writes. Stable: "reasampler"
// (byte-identical to the pre-V4 build). Beta: "reasampler_beta". FOREVER-STABLE per
// channel once shipped — changing either orphans every already-saved project's state.
//
// ISOLATION SEMANTICS (V4, accepted — not a bug): a channel reads/writes ONLY its own
// namespace. A project saved by stable shows empty/default ReaSampler state when opened
// in beta, and vice versa. There is NO cross-namespace read, migration, or fallback in
// this wave — that isolation is the safety property (a beta can never read or rewrite a
// stable project's bank/view/tail state).
const std::string& extStateNamespace();
// The FOREVER-STABLE command-id prefix every bindable action mints its id from. Stable:
// "CEREBELLUM_REASAMPLER_" (byte-identical to the shipped ids). Beta:
// "CEREBELLUM_REASAMPLER_BETA_", a DISTINCT forever-family so beta and stable actions
// never collide in REAPER's one Actions list and their keybindings stay independent.
// Callers concatenate their per-action suffix onto this (e.g. prefix + "CAPTURE_TRACK").
// PERMANENT once a beta ships — mark any minted id FOREVER-STABLE like stable's.
const std::string& commandIdPrefix();
// The Actions-list DISPLAY-NAME prefix, so two coexisting channels are distinguishable in
// REAPER's Actions list. Stable: "ReaSampler: " (unchanged). Beta: "ReaSampler beta: ".
// Callers build a gaccel desc as actionDisplayPrefix() + "capture selected track", etc.
const std::string& actionDisplayPrefix();
// The binary/module OUTPUT NAME base. Stable: "reaper_reasampler". Beta:
// "reaper_reasampler_beta". Mirrors the CMake OUTPUT_NAME (which is the authoritative
// artifact name); exposed here for any in-binary self-identification. REAPER dlopen's
// any reaper_* module, so both channels load side-by-side.
const std::string& binaryName();
// The docked bank-panel identity strings, channel-qualified so the two panels are
// distinguishable and do not fight over one persisted dock slot (a REAPER-global
// collision surface — DockWindowAddEx's identstr keys the saved dock position).
// dockTitle() — the visible dock tab title. Stable: "ReaSampler Bank".
// Beta: "ReaSampler Bank beta".
// dockIdent() — the persisted dock-position ident. Stable: "reasampler_bank_panel".
// Beta: "reasampler_bank_panel_beta". FOREVER-STABLE per channel.
const std::string& dockTitle();
const std::string& dockIdent();
// --- Channel-qualified action id / name builders ------------------------------------
//
// The two composition helpers every action-registering shell (main.cpp, actions.cpp)
// funnels through, so command ids and Actions-list names are qualified IDENTICALLY on
// every channel from ONE definition — no shell re-implements the concatenation.
//
// channelCommandId(suffix): commandIdPrefix() + suffix. `suffix` is the per-action tail
// WITHOUT the family prefix (e.g. "CAPTURE_TRACK", "SHOW_VERSION"). Stable yields the
// exact shipped id ("CEREBELLUM_REASAMPLER_CAPTURE_TRACK"); beta yields the isolated
// forever-family id ("CEREBELLUM_REASAMPLER_BETA_CAPTURE_TRACK"). FOREVER-STABLE per
// channel — a suffix, once shipped, is as permanent as the prefix.
// channelActionName(phrase): actionDisplayPrefix() + phrase. `phrase` is the action's
// human description WITHOUT the "ReaSampler: " lead (e.g. "capture selected track(s)").
// Stable yields "ReaSampler: capture selected track(s)"; beta prefixes "ReaSampler beta: "
// so the two channels' actions are distinguishable in one Actions list.
std::string channelCommandId(const std::string& suffix);
std::string channelActionName(const std::string& phrase);
// A parsed semver triple. Kept minimal — major.minor.patch as integers, for ORDERING
// only. It deliberately does NOT round-trip back to the display string (the leading
// zero is a rendering concern owned by the authoritative string, not reconstructable