fe55a9a96e
Fork the VST3 class UID (beta UID minted+frozen), on-disk name, and display name by the one channel bit via app_version; couple UID selection to the channel at compile time so a refactor can't split identity from data.
170 lines
6.6 KiB
C++
170 lines
6.6 KiB
C++
// app_version.cpp — implementation of the pure version-identity core (Phase V, V1 + V4).
|
|
// See app_version.h for the contract. The version STRING and the channel bit both come
|
|
// from version_generated.h (produced by CMake configure_file from the one
|
|
// REASAMPLER_VERSION variable + the REASAMPLER_CHANNEL flag) — this TU re-exports them and
|
|
// owns every pure derivation: the channel-qualified identity strings (V4) and the
|
|
// parse/compare/classify logic (V1). No #ifdef forks leak beyond this file; the shells
|
|
// consume the accessors below, so channel identity is one auditable definition.
|
|
|
|
#include "app_version.h"
|
|
|
|
#include <string>
|
|
|
|
#include "version_generated.h" // REASAMPLER_VERSION_STRING + REASAMPLER_CHANNEL_IS_BETA
|
|
|
|
namespace reasampler {
|
|
|
|
namespace {
|
|
// The one channel predicate every derivation below branches on — the single point the
|
|
// configure_file'd bit enters the pure module. constexpr so the branches fold at compile
|
|
// time; the accessors still return by const ref for a stable shared instance.
|
|
constexpr bool kIsBeta = (REASAMPLER_CHANNEL_IS_BETA != 0);
|
|
} // namespace
|
|
|
|
Channel channel() { return kIsBeta ? Channel::Beta : Channel::Stable; }
|
|
|
|
bool isBeta() { return kIsBeta; }
|
|
|
|
const std::string& appVersion() {
|
|
// The user-visible render. Stable: EXACTLY the CMake string (leading zero and all).
|
|
// Beta: the same numeric string plus a plain "-beta" suffix (V2). Function-local
|
|
// static so callers share one authoritative instance.
|
|
static const std::string kVersion =
|
|
kIsBeta ? std::string(REASAMPLER_VERSION_STRING) + "-beta"
|
|
: std::string(REASAMPLER_VERSION_STRING);
|
|
return kVersion;
|
|
}
|
|
|
|
const std::string& stampVersion() {
|
|
// The ext-state stamp value — the NUMERIC TRIPLE ONLY, IDENTICAL on both channels.
|
|
// No "-beta" suffix: it must parse as Stamped on read-back (a suffixed stamp classifies
|
|
// as Unknown), and stable's stamp stays byte-identical regardless of the channel build.
|
|
// The channel is carried by extStateNamespace(), never baked into the stamp.
|
|
static const std::string kStamp = REASAMPLER_VERSION_STRING;
|
|
return kStamp;
|
|
}
|
|
|
|
const std::string& extStateNamespace() {
|
|
// Stable "reasampler" is byte-identical to the pre-V4 build; beta is isolated.
|
|
// FOREVER-STABLE per channel.
|
|
static const std::string kNs = kIsBeta ? "reasampler_beta" : "reasampler";
|
|
return kNs;
|
|
}
|
|
|
|
const std::string& commandIdPrefix() {
|
|
// Stable prefix is byte-identical to every shipped command id; beta is a distinct
|
|
// forever-family. FOREVER-STABLE per channel.
|
|
static const std::string kPrefix =
|
|
kIsBeta ? "CEREBELLUM_REASAMPLER_BETA_" : "CEREBELLUM_REASAMPLER_";
|
|
return kPrefix;
|
|
}
|
|
|
|
const std::string& actionDisplayPrefix() {
|
|
// Actions-list legibility: two channels must be distinguishable by name. Trailing
|
|
// space so callers append the action phrase directly.
|
|
static const std::string kDisp = kIsBeta ? "ReaSampler beta: " : "ReaSampler: ";
|
|
return kDisp;
|
|
}
|
|
|
|
const std::string& binaryName() {
|
|
static const std::string kName =
|
|
kIsBeta ? "reaper_reasampler_beta" : "reaper_reasampler";
|
|
return kName;
|
|
}
|
|
|
|
const std::string& dockTitle() {
|
|
static const std::string kTitle =
|
|
kIsBeta ? "ReaSampler Bank beta" : "ReaSampler Bank";
|
|
return kTitle;
|
|
}
|
|
|
|
const std::string& dockIdent() {
|
|
// Persisted dock-position ident — FOREVER-STABLE per channel (changing it strands the
|
|
// saved dock slot). Beta qualified so the two panels do not fight over one slot.
|
|
static const std::string kIdent =
|
|
kIsBeta ? "reasampler_bank_panel_beta" : "reasampler_bank_panel";
|
|
return kIdent;
|
|
}
|
|
|
|
const std::string& vstOutputName() {
|
|
// The .vst3 module OUTPUT_NAME base — FOREVER-STABLE per channel. Stable is
|
|
// byte-identical to pre-S18 ("reasampler_9000"); beta is isolated so both install
|
|
// side-by-side without a filename collision.
|
|
static const std::string kName =
|
|
kIsBeta ? "reasampler_9000_beta" : "reasampler_9000";
|
|
return kName;
|
|
}
|
|
|
|
const std::string& vstPluginName() {
|
|
// The factory display name / editor title / embed label. Stable is byte-identical to
|
|
// pre-S18 ("ReaSampler 9000"); beta appends " beta" so the two channels are distinct
|
|
// plugins in the FX browser.
|
|
static const std::string kName =
|
|
kIsBeta ? "ReaSampler 9000 beta" : "ReaSampler 9000";
|
|
return kName;
|
|
}
|
|
|
|
std::string channelCommandId(const std::string& suffix) {
|
|
return commandIdPrefix() + suffix;
|
|
}
|
|
|
|
std::string channelActionName(const std::string& phrase) {
|
|
return actionDisplayPrefix() + phrase;
|
|
}
|
|
|
|
std::optional<Version> parseVersion(const std::string& s) {
|
|
// Split on '.' into exactly three non-empty all-digit components. No sign, no
|
|
// whitespace, no trailing garbage. Leading zeros are allowed (0.9.01 parses to
|
|
// {0,9,1}) — the display fidelity of the zero is the string's job, not this parse's.
|
|
Version v;
|
|
int component = 0; // 0=major, 1=minor, 2=patch
|
|
long long acc = 0; // current component accumulator (long long guards overflow)
|
|
bool digitsInComponent = false;
|
|
int* const out[3] = {&v.major, &v.minor, &v.patch};
|
|
|
|
for (char c : s) {
|
|
if (c == '.') {
|
|
if (!digitsInComponent) return std::nullopt; // empty component (".", "1..2")
|
|
if (component >= 2) return std::nullopt; // too many dots
|
|
*out[component] = static_cast<int>(acc);
|
|
++component;
|
|
acc = 0;
|
|
digitsInComponent = false;
|
|
continue;
|
|
}
|
|
if (c < '0' || c > '9') return std::nullopt; // non-digit (sign, letter, ws)
|
|
acc = acc * 10 + (c - '0');
|
|
if (acc > 1'000'000'000LL) return std::nullopt; // absurdly large -> reject
|
|
digitsInComponent = true;
|
|
}
|
|
if (component != 2 || !digitsInComponent) return std::nullopt; // too few components
|
|
*out[2] = static_cast<int>(acc);
|
|
return v;
|
|
}
|
|
|
|
bool versionLess(const Version& a, const Version& b) {
|
|
if (a.major != b.major) return a.major < b.major;
|
|
if (a.minor != b.minor) return a.minor < b.minor;
|
|
return a.patch < b.patch;
|
|
}
|
|
|
|
WritingVersion classifyWritingVersion(const std::string& rawStamp) {
|
|
WritingVersion wv;
|
|
if (rawStamp.empty()) {
|
|
wv.kind = WritingVersion::Kind::PreVersioning; // no stamp -> pre-versioning
|
|
return wv;
|
|
}
|
|
std::optional<Version> parsed = parseVersion(rawStamp);
|
|
if (!parsed) {
|
|
wv.kind = WritingVersion::Kind::Unknown; // present but malformed -> ignored
|
|
wv.raw = rawStamp;
|
|
return wv;
|
|
}
|
|
wv.kind = WritingVersion::Kind::Stamped;
|
|
wv.raw = rawStamp;
|
|
wv.parsed = *parsed;
|
|
return wv;
|
|
}
|
|
|
|
} // namespace reasampler
|