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
+108 -79
View File
@@ -21,9 +21,12 @@
#include "actions.h"
#include <deque>
#include <string>
#include <vector>
#include "app_version.h" // channelCommandId / channelActionName — one channel-identity point
#include "bank_book.h" // BankBook, nextBankId, TransferResult, kPoolBankId (B1)
#include "bank_panel.h" // selection seam + full-height toggles (B3/B4)
#include "item_read.h" // shared MediaItem* -> GUID + fixed-lane-name reads (D2 W3-B)
@@ -57,22 +60,23 @@ namespace reasampler {
namespace {
// FOREVER-STABLE action-id strings. Same family prefix as main.cpp's capture/panel
// actions; each full string is minted into a persistent command id and user
// keybindings key off it — NEVER change these after ship.
constexpr const char* kIdToggleMode = "CEREBELLUM_REASAMPLER_VIEW_TOGGLE_MODE";
constexpr const char* kIdActivateArrange = "CEREBELLUM_REASAMPLER_VIEW_ACTIVATE_ARRANGE";
constexpr const char* kIdActivateDesign = "CEREBELLUM_REASAMPLER_VIEW_ACTIVATE_DESIGN";
constexpr const char* kIdTagDesign = "CEREBELLUM_REASAMPLER_VIEW_TAG_DESIGN";
constexpr const char* kIdTagArrange = "CEREBELLUM_REASAMPLER_VIEW_TAG_ARRANGE";
constexpr const char* kIdUntag = "CEREBELLUM_REASAMPLER_VIEW_UNTAG";
constexpr const char* kIdShowBoth = "CEREBELLUM_REASAMPLER_VIEW_SHOW_BOTH";
// FOREVER-STABLE action-id SUFFIXES (Phase V, V4). The channel family prefix is prepended
// at register time via channelCommandId (app_version), so stable rebuilds the exact shipped
// id ("CEREBELLUM_REASAMPLER_VIEW_TOGGLE_MODE") and beta yields the isolated forever-family
// id ("CEREBELLUM_REASAMPLER_BETA_VIEW_TOGGLE_MODE"). Each composed id is minted into a
// persistent command id user keybindings key off — NEVER change a shipped suffix after ship.
constexpr const char* kIdToggleMode = "VIEW_TOGGLE_MODE";
constexpr const char* kIdActivateArrange = "VIEW_ACTIVATE_ARRANGE";
constexpr const char* kIdActivateDesign = "VIEW_ACTIVATE_DESIGN";
constexpr const char* kIdTagDesign = "VIEW_TAG_DESIGN";
constexpr const char* kIdTagArrange = "VIEW_TAG_ARRANGE";
constexpr const char* kIdUntag = "VIEW_UNTAG";
constexpr const char* kIdShowBoth = "VIEW_SHOW_BOTH";
// D2 Wave 3-B item-level mode moves — the item analog of the track tag family. Same
// FOREVER-STABLE contract: minted into a persistent command id, user keybindings key off
// each — NEVER change these strings after ship.
constexpr const char* kIdMoveItemsDesign = "CEREBELLUM_REASAMPLER_VIEW_MOVE_ITEMS_DESIGN";
constexpr const char* kIdMoveItemsArrange = "CEREBELLUM_REASAMPLER_VIEW_MOVE_ITEMS_ARRANGE";
constexpr const char* kIdUntagItems = "CEREBELLUM_REASAMPLER_VIEW_UNTAG_ITEMS";
// FOREVER-STABLE contract (suffix composed with the channel prefix) — NEVER change these.
constexpr const char* kIdMoveItemsDesign = "VIEW_MOVE_ITEMS_DESIGN";
constexpr const char* kIdMoveItemsArrange = "VIEW_MOVE_ITEMS_ARRANGE";
constexpr const char* kIdUntagItems = "VIEW_UNTAG_ITEMS";
// The live session the actions mutate. Set once by designViewRegisterActions and
// read by the hookcommand handler. Not owned here (main.cpp owns g_session).
@@ -103,15 +107,35 @@ gaccel_register_t g_accelMoveItemsDesign{};
gaccel_register_t g_accelMoveItemsArrange{};
gaccel_register_t g_accelUntagItems{};
// Mints a command id from a stable string and registers its gaccel (Actions-list
// entry with `desc`). Returns the command id (0 on failure). The gaccel storage is
// caller-owned and must outlive the module (the file-scope g_accel* above).
int registerAction(reaper_plugin_info_t* rec, const char* stableId,
gaccel_register_t& accel, const char* desc) {
const int cmd = rec->Register("command_id", (void*)stableId);
// Durable store of composed, channel-qualified strings (ids + labels). A std::deque never
// invalidates references on push_back, so a c_str() handed to REAPER (a command_id at
// register, a gaccel desc for its lifetime) stays valid until process exit. Memoized by
// suffix so register and the mirror-unregister get the SAME id pointer for a given action.
std::deque<std::string> g_strStore;
// Returns the channel-qualified command id for `suffix`, interning it once. Called by BOTH
// registerAction and the unregister path, so a '-command_id' presents the identical string.
const char* channelIdFor(const char* suffix) {
const std::string composed = channelCommandId(suffix);
for (const std::string& s : g_strStore)
if (s == composed) return s.c_str();
g_strStore.push_back(composed);
return g_strStore.back().c_str();
}
// Mints a command id from a channel-qualified SUFFIX and registers its gaccel (Actions-list
// entry with a channel-qualified label PHRASE). Returns the command id (0 on failure). Both
// the composed id and label are interned durably (g_strStore) — REAPER holds the desc
// pointer, and the id must survive to the mirror-unregister. The gaccel storage itself is
// caller-owned (the file-scope g_accel* above).
int registerAction(reaper_plugin_info_t* rec, const char* suffix,
gaccel_register_t& accel, const char* phrase) {
const char* id = channelIdFor(suffix);
const int cmd = rec->Register("command_id", (void*)id);
if (cmd) {
g_strStore.push_back(channelActionName(phrase));
accel.accel.cmd = cmd;
accel.desc = desc;
accel.desc = g_strStore.back().c_str();
rec->Register("gaccel", (void*)&accel);
}
return cmd;
@@ -319,27 +343,27 @@ void designViewRegisterActions(reaper_plugin_info_t* rec, ReaSamplerSession* ses
// command_id -> gaccel for each. The single hookcommand that routes these lives
// in main.cpp (one hook per extension); designViewHandleCommand services them.
g_cmdToggleMode = registerAction(rec, kIdToggleMode, g_accelToggleMode,
"ReaSampler: toggle Design View mode");
"toggle Design View mode");
g_cmdActivateArrange = registerAction(rec, kIdActivateArrange, g_accelActivateArrange,
"ReaSampler: activate mode Arrange");
"activate mode Arrange");
g_cmdActivateDesign = registerAction(rec, kIdActivateDesign, g_accelActivateDesign,
"ReaSampler: activate mode Design");
"activate mode Design");
g_cmdTagDesign = registerAction(rec, kIdTagDesign, g_accelTagDesign,
"ReaSampler: tag selected tracks -> Design");
"tag selected tracks -> Design");
g_cmdTagArrange = registerAction(rec, kIdTagArrange, g_accelTagArrange,
"ReaSampler: tag selected tracks -> Arrange");
"tag selected tracks -> Arrange");
g_cmdUntag = registerAction(rec, kIdUntag, g_accelUntag,
"ReaSampler: untag selected tracks");
"untag selected tracks");
g_cmdShowBoth = registerAction(rec, kIdShowBoth, g_accelShowBoth,
"ReaSampler: show both for selected tracks");
"show both for selected tracks");
// Item-level mode moves (D2 W3-B): the item analog of the track tag family.
g_cmdMoveItemsDesign = registerAction(rec, kIdMoveItemsDesign, g_accelMoveItemsDesign,
"ReaSampler: move selected items -> Design");
"move selected items -> Design");
g_cmdMoveItemsArrange = registerAction(rec, kIdMoveItemsArrange, g_accelMoveItemsArrange,
"ReaSampler: move selected items -> Arrange");
"move selected items -> Arrange");
g_cmdUntagItems = registerAction(rec, kIdUntagItems, g_accelUntagItems,
"ReaSampler: untag selected items");
"untag selected items");
}
bool designViewHandleCommand(int command) {
@@ -367,26 +391,28 @@ void designViewUnregisterActions(reaper_plugin_info_t* rec) {
// Mirror-unregister with '-'-prefixed strings, per the contract's unload rule.
// gaccel first, then the command_id string (reverse of registration order — the item
// moves registered last, so they tear down first).
// Each '-command_id' re-presents the SAME interned, channel-qualified id (channelIdFor
// returns the memoized pointer registered above), so the unregister matches exactly.
rec->Register("-gaccel", (void*)&g_accelUntagItems);
rec->Register("-command_id", (void*)kIdUntagItems);
rec->Register("-command_id", (void*)channelIdFor(kIdUntagItems));
rec->Register("-gaccel", (void*)&g_accelMoveItemsArrange);
rec->Register("-command_id", (void*)kIdMoveItemsArrange);
rec->Register("-command_id", (void*)channelIdFor(kIdMoveItemsArrange));
rec->Register("-gaccel", (void*)&g_accelMoveItemsDesign);
rec->Register("-command_id", (void*)kIdMoveItemsDesign);
rec->Register("-command_id", (void*)channelIdFor(kIdMoveItemsDesign));
rec->Register("-gaccel", (void*)&g_accelShowBoth);
rec->Register("-command_id", (void*)kIdShowBoth);
rec->Register("-command_id", (void*)channelIdFor(kIdShowBoth));
rec->Register("-gaccel", (void*)&g_accelUntag);
rec->Register("-command_id", (void*)kIdUntag);
rec->Register("-command_id", (void*)channelIdFor(kIdUntag));
rec->Register("-gaccel", (void*)&g_accelTagArrange);
rec->Register("-command_id", (void*)kIdTagArrange);
rec->Register("-command_id", (void*)channelIdFor(kIdTagArrange));
rec->Register("-gaccel", (void*)&g_accelTagDesign);
rec->Register("-command_id", (void*)kIdTagDesign);
rec->Register("-command_id", (void*)channelIdFor(kIdTagDesign));
rec->Register("-gaccel", (void*)&g_accelActivateDesign);
rec->Register("-command_id", (void*)kIdActivateDesign);
rec->Register("-command_id", (void*)channelIdFor(kIdActivateDesign));
rec->Register("-gaccel", (void*)&g_accelActivateArrange);
rec->Register("-command_id", (void*)kIdActivateArrange);
rec->Register("-command_id", (void*)channelIdFor(kIdActivateArrange));
rec->Register("-gaccel", (void*)&g_accelToggleMode);
rec->Register("-command_id", (void*)kIdToggleMode);
rec->Register("-command_id", (void*)channelIdFor(kIdToggleMode));
g_session = nullptr;
}
@@ -409,20 +435,22 @@ void designViewUnregisterActions(reaper_plugin_info_t* rec) {
namespace {
// FOREVER-STABLE multi-bank action-id strings. Same CEREBELLUM_REASAMPLER_ family
// prefix; each is minted into a persistent command id user keybindings key off
// NEVER change these after ship.
constexpr const char* kIdBankCreate = "CEREBELLUM_REASAMPLER_BANK_CREATE";
constexpr const char* kIdBankRename = "CEREBELLUM_REASAMPLER_BANK_RENAME";
constexpr const char* kIdBankDelete = "CEREBELLUM_REASAMPLER_BANK_DELETE";
constexpr const char* kIdBankEvacuate = "CEREBELLUM_REASAMPLER_BANK_EVACUATE";
constexpr const char* kIdBankActivateNext = "CEREBELLUM_REASAMPLER_BANK_ACTIVATE_NEXT";
constexpr const char* kIdBankActivatePool = "CEREBELLUM_REASAMPLER_BANK_ACTIVATE_POOL";
constexpr const char* kIdBankMoveSel = "CEREBELLUM_REASAMPLER_BANK_MOVE_SELECTED";
constexpr const char* kIdBankCopySel = "CEREBELLUM_REASAMPLER_BANK_COPY_SELECTED";
constexpr const char* kIdBankRemoveSel = "CEREBELLUM_REASAMPLER_BANK_REMOVE_SELECTED";
constexpr const char* kIdBankPoolFull = "CEREBELLUM_REASAMPLER_BANK_POOL_FULLHEIGHT";
constexpr const char* kIdBankBanksFull = "CEREBELLUM_REASAMPLER_BANK_BANKS_FULLHEIGHT";
// FOREVER-STABLE multi-bank action-id SUFFIXES (Phase V, V4). The channel family prefix is
// prepended at register via channelCommandId (as with the Design View family above)
// stable rebuilds the shipped id, beta the isolated one. NEVER change a shipped suffix.
// Each suffix + the stable prefix must byte-match the pre-V4 shipped literal exactly
// (e.g. "BANK_REMOVE_SELECTED" -> "CEREBELLUM_REASAMPLER_BANK_REMOVE_SELECTED").
constexpr const char* kIdBankCreate = "BANK_CREATE";
constexpr const char* kIdBankRename = "BANK_RENAME";
constexpr const char* kIdBankDelete = "BANK_DELETE";
constexpr const char* kIdBankEvacuate = "BANK_EVACUATE";
constexpr const char* kIdBankActivateNext = "BANK_ACTIVATE_NEXT";
constexpr const char* kIdBankActivatePool = "BANK_ACTIVATE_POOL";
constexpr const char* kIdBankMoveSel = "BANK_MOVE_SELECTED";
constexpr const char* kIdBankCopySel = "BANK_COPY_SELECTED";
constexpr const char* kIdBankRemoveSel = "BANK_REMOVE_SELECTED";
constexpr const char* kIdBankPoolFull = "BANK_POOL_FULLHEIGHT";
constexpr const char* kIdBankBanksFull = "BANK_BANKS_FULLHEIGHT";
int g_cmdBankCreate = 0;
int g_cmdBankRename = 0;
@@ -831,27 +859,27 @@ void bankRegisterActions(reaper_plugin_info_t* rec, ReaSamplerSession* session)
g_session = session; // shared with the Design View family; same live session
g_cmdBankCreate = registerAction(rec, kIdBankCreate, g_accelBankCreate,
"ReaSampler: create bank");
"create bank");
g_cmdBankRename = registerAction(rec, kIdBankRename, g_accelBankRename,
"ReaSampler: rename bank");
"rename bank");
g_cmdBankDelete = registerAction(rec, kIdBankDelete, g_accelBankDelete,
"ReaSampler: delete bank");
"delete bank");
g_cmdBankEvacuate = registerAction(rec, kIdBankEvacuate, g_accelBankEvacuate,
"ReaSampler: evacuate bank to pool");
"evacuate bank to pool");
g_cmdBankActivateNext = registerAction(rec, kIdBankActivateNext, g_accelBankActivateNext,
"ReaSampler: activate next bank (cycle)");
"activate next bank (cycle)");
g_cmdBankActivatePool = registerAction(rec, kIdBankActivatePool, g_accelBankActivatePool,
"ReaSampler: activate pool");
"activate pool");
g_cmdBankMoveSel = registerAction(rec, kIdBankMoveSel, g_accelBankMoveSel,
"ReaSampler: move selected samples to bank");
"move selected samples to bank");
g_cmdBankCopySel = registerAction(rec, kIdBankCopySel, g_accelBankCopySel,
"ReaSampler: copy selected samples to bank");
"copy selected samples to bank");
g_cmdBankRemoveSel = registerAction(rec, kIdBankRemoveSel, g_accelBankRemoveSel,
"ReaSampler: remove selected samples");
"remove selected samples");
g_cmdBankPoolFull = registerAction(rec, kIdBankPoolFull, g_accelBankPoolFull,
"ReaSampler: toggle pool full-height");
"toggle pool full-height");
g_cmdBankBanksFull = registerAction(rec, kIdBankBanksFull, g_accelBankBanksFull,
"ReaSampler: toggle banks full-height");
"toggle banks full-height");
}
bool bankHandleCommand(int command) {
@@ -873,29 +901,30 @@ bool bankHandleCommand(int command) {
}
void bankUnregisterActions(reaper_plugin_info_t* rec) {
// Mirror-unregister with '-'-prefixed strings, reverse of registration order.
// Mirror-unregister with '-'-prefixed strings, reverse of registration order. Each
// '-command_id' re-presents the same interned channel-qualified id (channelIdFor).
rec->Register("-gaccel", (void*)&g_accelBankBanksFull);
rec->Register("-command_id", (void*)kIdBankBanksFull);
rec->Register("-command_id", (void*)channelIdFor(kIdBankBanksFull));
rec->Register("-gaccel", (void*)&g_accelBankPoolFull);
rec->Register("-command_id", (void*)kIdBankPoolFull);
rec->Register("-command_id", (void*)channelIdFor(kIdBankPoolFull));
rec->Register("-gaccel", (void*)&g_accelBankRemoveSel);
rec->Register("-command_id", (void*)kIdBankRemoveSel);
rec->Register("-command_id", (void*)channelIdFor(kIdBankRemoveSel));
rec->Register("-gaccel", (void*)&g_accelBankCopySel);
rec->Register("-command_id", (void*)kIdBankCopySel);
rec->Register("-command_id", (void*)channelIdFor(kIdBankCopySel));
rec->Register("-gaccel", (void*)&g_accelBankMoveSel);
rec->Register("-command_id", (void*)kIdBankMoveSel);
rec->Register("-command_id", (void*)channelIdFor(kIdBankMoveSel));
rec->Register("-gaccel", (void*)&g_accelBankActivatePool);
rec->Register("-command_id", (void*)kIdBankActivatePool);
rec->Register("-command_id", (void*)channelIdFor(kIdBankActivatePool));
rec->Register("-gaccel", (void*)&g_accelBankActivateNext);
rec->Register("-command_id", (void*)kIdBankActivateNext);
rec->Register("-command_id", (void*)channelIdFor(kIdBankActivateNext));
rec->Register("-gaccel", (void*)&g_accelBankEvacuate);
rec->Register("-command_id", (void*)kIdBankEvacuate);
rec->Register("-command_id", (void*)channelIdFor(kIdBankEvacuate));
rec->Register("-gaccel", (void*)&g_accelBankDelete);
rec->Register("-command_id", (void*)kIdBankDelete);
rec->Register("-command_id", (void*)channelIdFor(kIdBankDelete));
rec->Register("-gaccel", (void*)&g_accelBankRename);
rec->Register("-command_id", (void*)kIdBankRename);
rec->Register("-command_id", (void*)channelIdFor(kIdBankRename));
rec->Register("-gaccel", (void*)&g_accelBankCreate);
rec->Register("-command_id", (void*)kIdBankCreate);
rec->Register("-command_id", (void*)channelIdFor(kIdBankCreate));
// g_session is shared with the Design View family; designViewUnregisterActions
// also nulls it. Nulling twice is harmless. Leave it to whichever runs last.