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:
+32
-3
@@ -18,8 +18,33 @@ set(CMAKE_CXX_STANDARD 17)
|
|||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
|
||||||
# Generate version_generated.h from the one REASAMPLER_VERSION variable. Regenerated at
|
# ---------------------------------------------------------------------------
|
||||||
# configure time whenever the variable changes; the exact string is substituted verbatim.
|
# Channel — SINGLE SOURCE OF TRUTH for beta-in-isolation (Phase V, V4). One flag,
|
||||||
|
# `-DREASAMPLER_CHANNEL=beta`, forks the whole channel identity from one build tree:
|
||||||
|
# absent (or `stable`) = today's build with BYTE-IDENTICAL identity (binary name,
|
||||||
|
# ext-state namespace, command-id strings, version render); `beta` = a fully isolated
|
||||||
|
# `reaper_reasampler_beta` that coexists with stable in one REAPER. The flag reduces to
|
||||||
|
# ONE bit (REASAMPLER_CHANNEL_IS_BETA) threaded through the SAME configure_file'd header
|
||||||
|
# as the version string, so the pure app_version module derives every channel-qualified
|
||||||
|
# identity from it — no scattered #ifdefs. Any value other than exactly `beta` is treated
|
||||||
|
# as stable (a typo must not silently produce a half-forked build), and we hard-error on
|
||||||
|
# an unrecognized non-empty value so a misspelled `-DREASAMPLER_CHANNEL=betaa` is caught
|
||||||
|
# at configure time rather than shipping stable identity under a beta intent.
|
||||||
|
set(REASAMPLER_CHANNEL "stable" CACHE STRING "Build channel: stable (default) or beta")
|
||||||
|
if(REASAMPLER_CHANNEL STREQUAL "beta")
|
||||||
|
set(REASAMPLER_CHANNEL_IS_BETA 1)
|
||||||
|
set(REASAMPLER_OUTPUT_NAME "reaper_reasampler_beta")
|
||||||
|
elseif(REASAMPLER_CHANNEL STREQUAL "stable")
|
||||||
|
set(REASAMPLER_CHANNEL_IS_BETA 0)
|
||||||
|
set(REASAMPLER_OUTPUT_NAME "reaper_reasampler")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"REASAMPLER_CHANNEL must be 'stable' or 'beta' (got '${REASAMPLER_CHANNEL}')")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Generate version_generated.h from the one REASAMPLER_VERSION variable + the channel bit.
|
||||||
|
# Regenerated at configure time whenever either changes; the exact string is substituted
|
||||||
|
# verbatim and the channel bit fans out through app_version.
|
||||||
configure_file(
|
configure_file(
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/version_generated.h.in
|
${CMAKE_CURRENT_SOURCE_DIR}/src/version_generated.h.in
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/generated/version_generated.h
|
${CMAKE_CURRENT_BINARY_DIR}/generated/version_generated.h
|
||||||
@@ -343,7 +368,11 @@ add_library(reaper_reasampler MODULE
|
|||||||
)
|
)
|
||||||
target_link_libraries(reaper_reasampler PRIVATE bank_model capture_paths peaks bank_grid mode_switch tab_strip view_mode_model insert_plan render_settings tail_control realtime_record bank_book wav_trim owned_manifest app_version)
|
target_link_libraries(reaper_reasampler PRIVATE bank_model capture_paths peaks bank_grid mode_switch tab_strip view_mode_model insert_plan render_settings tail_control realtime_record bank_book wav_trim owned_manifest app_version)
|
||||||
target_include_directories(reaper_reasampler PRIVATE ${SDK_INC} ${WDL_INC})
|
target_include_directories(reaper_reasampler PRIVATE ${SDK_INC} ${WDL_INC})
|
||||||
set_target_properties(reaper_reasampler PROPERTIES PREFIX "" OUTPUT_NAME "reaper_reasampler")
|
# OUTPUT_NAME is channel-derived (Phase V, V4): "reaper_reasampler" (stable, default) or
|
||||||
|
# "reaper_reasampler_beta" (beta). REAPER dlopen's any reaper_* module, so both channels'
|
||||||
|
# artifacts load side-by-side. The CMake TARGET name stays "reaper_reasampler" for both
|
||||||
|
# configs — one source tree, one target; only the emitted file name forks by channel.
|
||||||
|
set_target_properties(reaper_reasampler PROPERTIES PREFIX "" OUTPUT_NAME "${REASAMPLER_OUTPUT_NAME}")
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
# Native Win32. REAPER provides nothing extra to link. The bank_panel dialog
|
# Native Win32. REAPER provides nothing extra to link. The bank_panel dialog
|
||||||
|
|||||||
+108
-79
@@ -21,9 +21,12 @@
|
|||||||
|
|
||||||
#include "actions.h"
|
#include "actions.h"
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app_version.h" // channelCommandId / channelActionName — one channel-identity point
|
||||||
|
|
||||||
#include "bank_book.h" // BankBook, nextBankId, TransferResult, kPoolBankId (B1)
|
#include "bank_book.h" // BankBook, nextBankId, TransferResult, kPoolBankId (B1)
|
||||||
#include "bank_panel.h" // selection seam + full-height toggles (B3/B4)
|
#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)
|
#include "item_read.h" // shared MediaItem* -> GUID + fixed-lane-name reads (D2 W3-B)
|
||||||
@@ -57,22 +60,23 @@ namespace reasampler {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// FOREVER-STABLE action-id strings. Same family prefix as main.cpp's capture/panel
|
// FOREVER-STABLE action-id SUFFIXES (Phase V, V4). The channel family prefix is prepended
|
||||||
// actions; each full string is minted into a persistent command id and user
|
// at register time via channelCommandId (app_version), so stable rebuilds the exact shipped
|
||||||
// keybindings key off it — NEVER change these after ship.
|
// id ("CEREBELLUM_REASAMPLER_VIEW_TOGGLE_MODE") and beta yields the isolated forever-family
|
||||||
constexpr const char* kIdToggleMode = "CEREBELLUM_REASAMPLER_VIEW_TOGGLE_MODE";
|
// id ("CEREBELLUM_REASAMPLER_BETA_VIEW_TOGGLE_MODE"). Each composed id is minted into a
|
||||||
constexpr const char* kIdActivateArrange = "CEREBELLUM_REASAMPLER_VIEW_ACTIVATE_ARRANGE";
|
// persistent command id user keybindings key off — NEVER change a shipped suffix after ship.
|
||||||
constexpr const char* kIdActivateDesign = "CEREBELLUM_REASAMPLER_VIEW_ACTIVATE_DESIGN";
|
constexpr const char* kIdToggleMode = "VIEW_TOGGLE_MODE";
|
||||||
constexpr const char* kIdTagDesign = "CEREBELLUM_REASAMPLER_VIEW_TAG_DESIGN";
|
constexpr const char* kIdActivateArrange = "VIEW_ACTIVATE_ARRANGE";
|
||||||
constexpr const char* kIdTagArrange = "CEREBELLUM_REASAMPLER_VIEW_TAG_ARRANGE";
|
constexpr const char* kIdActivateDesign = "VIEW_ACTIVATE_DESIGN";
|
||||||
constexpr const char* kIdUntag = "CEREBELLUM_REASAMPLER_VIEW_UNTAG";
|
constexpr const char* kIdTagDesign = "VIEW_TAG_DESIGN";
|
||||||
constexpr const char* kIdShowBoth = "CEREBELLUM_REASAMPLER_VIEW_SHOW_BOTH";
|
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
|
// 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
|
// FOREVER-STABLE contract (suffix composed with the channel prefix) — NEVER change these.
|
||||||
// each — NEVER change these strings after ship.
|
constexpr const char* kIdMoveItemsDesign = "VIEW_MOVE_ITEMS_DESIGN";
|
||||||
constexpr const char* kIdMoveItemsDesign = "CEREBELLUM_REASAMPLER_VIEW_MOVE_ITEMS_DESIGN";
|
constexpr const char* kIdMoveItemsArrange = "VIEW_MOVE_ITEMS_ARRANGE";
|
||||||
constexpr const char* kIdMoveItemsArrange = "CEREBELLUM_REASAMPLER_VIEW_MOVE_ITEMS_ARRANGE";
|
constexpr const char* kIdUntagItems = "VIEW_UNTAG_ITEMS";
|
||||||
constexpr const char* kIdUntagItems = "CEREBELLUM_REASAMPLER_VIEW_UNTAG_ITEMS";
|
|
||||||
|
|
||||||
// The live session the actions mutate. Set once by designViewRegisterActions and
|
// The live session the actions mutate. Set once by designViewRegisterActions and
|
||||||
// read by the hookcommand handler. Not owned here (main.cpp owns g_session).
|
// 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_accelMoveItemsArrange{};
|
||||||
gaccel_register_t g_accelUntagItems{};
|
gaccel_register_t g_accelUntagItems{};
|
||||||
|
|
||||||
// Mints a command id from a stable string and registers its gaccel (Actions-list
|
// Durable store of composed, channel-qualified strings (ids + labels). A std::deque never
|
||||||
// entry with `desc`). Returns the command id (0 on failure). The gaccel storage is
|
// invalidates references on push_back, so a c_str() handed to REAPER (a command_id at
|
||||||
// caller-owned and must outlive the module (the file-scope g_accel* above).
|
// register, a gaccel desc for its lifetime) stays valid until process exit. Memoized by
|
||||||
int registerAction(reaper_plugin_info_t* rec, const char* stableId,
|
// suffix so register and the mirror-unregister get the SAME id pointer for a given action.
|
||||||
gaccel_register_t& accel, const char* desc) {
|
std::deque<std::string> g_strStore;
|
||||||
const int cmd = rec->Register("command_id", (void*)stableId);
|
|
||||||
|
// 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) {
|
if (cmd) {
|
||||||
|
g_strStore.push_back(channelActionName(phrase));
|
||||||
accel.accel.cmd = cmd;
|
accel.accel.cmd = cmd;
|
||||||
accel.desc = desc;
|
accel.desc = g_strStore.back().c_str();
|
||||||
rec->Register("gaccel", (void*)&accel);
|
rec->Register("gaccel", (void*)&accel);
|
||||||
}
|
}
|
||||||
return cmd;
|
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
|
// command_id -> gaccel for each. The single hookcommand that routes these lives
|
||||||
// in main.cpp (one hook per extension); designViewHandleCommand services them.
|
// in main.cpp (one hook per extension); designViewHandleCommand services them.
|
||||||
g_cmdToggleMode = registerAction(rec, kIdToggleMode, g_accelToggleMode,
|
g_cmdToggleMode = registerAction(rec, kIdToggleMode, g_accelToggleMode,
|
||||||
"ReaSampler: toggle Design View mode");
|
"toggle Design View mode");
|
||||||
g_cmdActivateArrange = registerAction(rec, kIdActivateArrange, g_accelActivateArrange,
|
g_cmdActivateArrange = registerAction(rec, kIdActivateArrange, g_accelActivateArrange,
|
||||||
"ReaSampler: activate mode Arrange");
|
"activate mode Arrange");
|
||||||
g_cmdActivateDesign = registerAction(rec, kIdActivateDesign, g_accelActivateDesign,
|
g_cmdActivateDesign = registerAction(rec, kIdActivateDesign, g_accelActivateDesign,
|
||||||
"ReaSampler: activate mode Design");
|
"activate mode Design");
|
||||||
g_cmdTagDesign = registerAction(rec, kIdTagDesign, g_accelTagDesign,
|
g_cmdTagDesign = registerAction(rec, kIdTagDesign, g_accelTagDesign,
|
||||||
"ReaSampler: tag selected tracks -> Design");
|
"tag selected tracks -> Design");
|
||||||
g_cmdTagArrange = registerAction(rec, kIdTagArrange, g_accelTagArrange,
|
g_cmdTagArrange = registerAction(rec, kIdTagArrange, g_accelTagArrange,
|
||||||
"ReaSampler: tag selected tracks -> Arrange");
|
"tag selected tracks -> Arrange");
|
||||||
g_cmdUntag = registerAction(rec, kIdUntag, g_accelUntag,
|
g_cmdUntag = registerAction(rec, kIdUntag, g_accelUntag,
|
||||||
"ReaSampler: untag selected tracks");
|
"untag selected tracks");
|
||||||
g_cmdShowBoth = registerAction(rec, kIdShowBoth, g_accelShowBoth,
|
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.
|
// Item-level mode moves (D2 W3-B): the item analog of the track tag family.
|
||||||
g_cmdMoveItemsDesign = registerAction(rec, kIdMoveItemsDesign, g_accelMoveItemsDesign,
|
g_cmdMoveItemsDesign = registerAction(rec, kIdMoveItemsDesign, g_accelMoveItemsDesign,
|
||||||
"ReaSampler: move selected items -> Design");
|
"move selected items -> Design");
|
||||||
g_cmdMoveItemsArrange = registerAction(rec, kIdMoveItemsArrange, g_accelMoveItemsArrange,
|
g_cmdMoveItemsArrange = registerAction(rec, kIdMoveItemsArrange, g_accelMoveItemsArrange,
|
||||||
"ReaSampler: move selected items -> Arrange");
|
"move selected items -> Arrange");
|
||||||
g_cmdUntagItems = registerAction(rec, kIdUntagItems, g_accelUntagItems,
|
g_cmdUntagItems = registerAction(rec, kIdUntagItems, g_accelUntagItems,
|
||||||
"ReaSampler: untag selected items");
|
"untag selected items");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool designViewHandleCommand(int command) {
|
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.
|
// Mirror-unregister with '-'-prefixed strings, per the contract's unload rule.
|
||||||
// gaccel first, then the command_id string (reverse of registration order — the item
|
// gaccel first, then the command_id string (reverse of registration order — the item
|
||||||
// moves registered last, so they tear down first).
|
// 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("-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("-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("-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("-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("-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("-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("-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("-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("-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("-gaccel", (void*)&g_accelToggleMode);
|
||||||
rec->Register("-command_id", (void*)kIdToggleMode);
|
rec->Register("-command_id", (void*)channelIdFor(kIdToggleMode));
|
||||||
|
|
||||||
g_session = nullptr;
|
g_session = nullptr;
|
||||||
}
|
}
|
||||||
@@ -409,20 +435,22 @@ void designViewUnregisterActions(reaper_plugin_info_t* rec) {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// FOREVER-STABLE multi-bank action-id strings. Same CEREBELLUM_REASAMPLER_ family
|
// FOREVER-STABLE multi-bank action-id SUFFIXES (Phase V, V4). The channel family prefix is
|
||||||
// prefix; each is minted into a persistent command id user keybindings key off —
|
// prepended at register via channelCommandId (as with the Design View family above) —
|
||||||
// NEVER change these after ship.
|
// stable rebuilds the shipped id, beta the isolated one. NEVER change a shipped suffix.
|
||||||
constexpr const char* kIdBankCreate = "CEREBELLUM_REASAMPLER_BANK_CREATE";
|
// Each suffix + the stable prefix must byte-match the pre-V4 shipped literal exactly
|
||||||
constexpr const char* kIdBankRename = "CEREBELLUM_REASAMPLER_BANK_RENAME";
|
// (e.g. "BANK_REMOVE_SELECTED" -> "CEREBELLUM_REASAMPLER_BANK_REMOVE_SELECTED").
|
||||||
constexpr const char* kIdBankDelete = "CEREBELLUM_REASAMPLER_BANK_DELETE";
|
constexpr const char* kIdBankCreate = "BANK_CREATE";
|
||||||
constexpr const char* kIdBankEvacuate = "CEREBELLUM_REASAMPLER_BANK_EVACUATE";
|
constexpr const char* kIdBankRename = "BANK_RENAME";
|
||||||
constexpr const char* kIdBankActivateNext = "CEREBELLUM_REASAMPLER_BANK_ACTIVATE_NEXT";
|
constexpr const char* kIdBankDelete = "BANK_DELETE";
|
||||||
constexpr const char* kIdBankActivatePool = "CEREBELLUM_REASAMPLER_BANK_ACTIVATE_POOL";
|
constexpr const char* kIdBankEvacuate = "BANK_EVACUATE";
|
||||||
constexpr const char* kIdBankMoveSel = "CEREBELLUM_REASAMPLER_BANK_MOVE_SELECTED";
|
constexpr const char* kIdBankActivateNext = "BANK_ACTIVATE_NEXT";
|
||||||
constexpr const char* kIdBankCopySel = "CEREBELLUM_REASAMPLER_BANK_COPY_SELECTED";
|
constexpr const char* kIdBankActivatePool = "BANK_ACTIVATE_POOL";
|
||||||
constexpr const char* kIdBankRemoveSel = "CEREBELLUM_REASAMPLER_BANK_REMOVE_SELECTED";
|
constexpr const char* kIdBankMoveSel = "BANK_MOVE_SELECTED";
|
||||||
constexpr const char* kIdBankPoolFull = "CEREBELLUM_REASAMPLER_BANK_POOL_FULLHEIGHT";
|
constexpr const char* kIdBankCopySel = "BANK_COPY_SELECTED";
|
||||||
constexpr const char* kIdBankBanksFull = "CEREBELLUM_REASAMPLER_BANK_BANKS_FULLHEIGHT";
|
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_cmdBankCreate = 0;
|
||||||
int g_cmdBankRename = 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_session = session; // shared with the Design View family; same live session
|
||||||
|
|
||||||
g_cmdBankCreate = registerAction(rec, kIdBankCreate, g_accelBankCreate,
|
g_cmdBankCreate = registerAction(rec, kIdBankCreate, g_accelBankCreate,
|
||||||
"ReaSampler: create bank");
|
"create bank");
|
||||||
g_cmdBankRename = registerAction(rec, kIdBankRename, g_accelBankRename,
|
g_cmdBankRename = registerAction(rec, kIdBankRename, g_accelBankRename,
|
||||||
"ReaSampler: rename bank");
|
"rename bank");
|
||||||
g_cmdBankDelete = registerAction(rec, kIdBankDelete, g_accelBankDelete,
|
g_cmdBankDelete = registerAction(rec, kIdBankDelete, g_accelBankDelete,
|
||||||
"ReaSampler: delete bank");
|
"delete bank");
|
||||||
g_cmdBankEvacuate = registerAction(rec, kIdBankEvacuate, g_accelBankEvacuate,
|
g_cmdBankEvacuate = registerAction(rec, kIdBankEvacuate, g_accelBankEvacuate,
|
||||||
"ReaSampler: evacuate bank to pool");
|
"evacuate bank to pool");
|
||||||
g_cmdBankActivateNext = registerAction(rec, kIdBankActivateNext, g_accelBankActivateNext,
|
g_cmdBankActivateNext = registerAction(rec, kIdBankActivateNext, g_accelBankActivateNext,
|
||||||
"ReaSampler: activate next bank (cycle)");
|
"activate next bank (cycle)");
|
||||||
g_cmdBankActivatePool = registerAction(rec, kIdBankActivatePool, g_accelBankActivatePool,
|
g_cmdBankActivatePool = registerAction(rec, kIdBankActivatePool, g_accelBankActivatePool,
|
||||||
"ReaSampler: activate pool");
|
"activate pool");
|
||||||
g_cmdBankMoveSel = registerAction(rec, kIdBankMoveSel, g_accelBankMoveSel,
|
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,
|
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,
|
g_cmdBankRemoveSel = registerAction(rec, kIdBankRemoveSel, g_accelBankRemoveSel,
|
||||||
"ReaSampler: remove selected samples");
|
"remove selected samples");
|
||||||
g_cmdBankPoolFull = registerAction(rec, kIdBankPoolFull, g_accelBankPoolFull,
|
g_cmdBankPoolFull = registerAction(rec, kIdBankPoolFull, g_accelBankPoolFull,
|
||||||
"ReaSampler: toggle pool full-height");
|
"toggle pool full-height");
|
||||||
g_cmdBankBanksFull = registerAction(rec, kIdBankBanksFull, g_accelBankBanksFull,
|
g_cmdBankBanksFull = registerAction(rec, kIdBankBanksFull, g_accelBankBanksFull,
|
||||||
"ReaSampler: toggle banks full-height");
|
"toggle banks full-height");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bankHandleCommand(int command) {
|
bool bankHandleCommand(int command) {
|
||||||
@@ -873,29 +901,30 @@ bool bankHandleCommand(int command) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void bankUnregisterActions(reaper_plugin_info_t* rec) {
|
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("-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("-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("-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("-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("-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("-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("-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("-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("-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("-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("-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
|
// g_session is shared with the Design View family; designViewUnregisterActions
|
||||||
// also nulls it. Nulling twice is harmless. Leave it to whichever runs last.
|
// also nulls it. Nulling twice is harmless. Leave it to whichever runs last.
|
||||||
|
|||||||
+84
-9
@@ -1,24 +1,99 @@
|
|||||||
// app_version.cpp — implementation of the pure version-identity core (Phase V, V1).
|
// app_version.cpp — implementation of the pure version-identity core (Phase V, V1 + V4).
|
||||||
// See app_version.h for the contract. The version STRING itself comes from
|
// See app_version.h for the contract. The version STRING and the channel bit both come
|
||||||
// version_generated.h (produced by CMake configure_file from the one REASAMPLER_VERSION
|
// from version_generated.h (produced by CMake configure_file from the one
|
||||||
// variable) — this TU just re-exports it and owns the pure parse/compare/classify logic.
|
// 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 "app_version.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "version_generated.h" // REASAMPLER_VERSION_STRING — configure_file'd from CMake
|
#include "version_generated.h" // REASAMPLER_VERSION_STRING + REASAMPLER_CHANNEL_IS_BETA
|
||||||
|
|
||||||
namespace reasampler {
|
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() {
|
const std::string& appVersion() {
|
||||||
// Function-local static: initialized once from the compile-time string, returned by
|
// The user-visible render. Stable: EXACTLY the CMake string (leading zero and all).
|
||||||
// const ref so callers share the one authoritative instance. The macro is the exact
|
// Beta: the same numeric string plus a plain "-beta" suffix (V2). Function-local
|
||||||
// CMake value, leading zero and all.
|
// static so callers share one authoritative instance.
|
||||||
static const std::string kVersion = REASAMPLER_VERSION_STRING;
|
static const std::string kVersion =
|
||||||
|
kIsBeta ? std::string(REASAMPLER_VERSION_STRING) + "-beta"
|
||||||
|
: std::string(REASAMPLER_VERSION_STRING);
|
||||||
return kVersion;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
std::optional<Version> parseVersion(const std::string& s) {
|
||||||
// Split on '.' into exactly three non-empty all-digit components. No sign, no
|
// 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
|
// whitespace, no trailing garbage. Leading zeros are allowed (0.9.01 parses to
|
||||||
|
|||||||
+100
-5
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#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`
|
// source of truth for the version STRING lives in CMake (a `REASAMPLER_VERSION`
|
||||||
// variable threaded in via configure_file -> version_generated.h); this module
|
// 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
|
// 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
|
// 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).
|
// 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
|
// 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).
|
// only. Builds and unit-tests without REAPER (mirror of bank_model / tail_control).
|
||||||
//
|
//
|
||||||
@@ -21,12 +32,96 @@
|
|||||||
|
|
||||||
namespace reasampler {
|
namespace reasampler {
|
||||||
|
|
||||||
// The canonical version string — EXACTLY the value of the CMake `REASAMPLER_VERSION`
|
// --- Channel identity (V4, beta-in-isolation) ---------------------------------------
|
||||||
// variable (see version_generated.h, produced by configure_file). One edit point:
|
//
|
||||||
// changing that variable changes this constant, the ext-state stamp, and the
|
// The build channel, fixed at compile time by REASAMPLER_CHANNEL_IS_BETA (0 = stable,
|
||||||
// show-version action output with no other edits. Leading zero preserved verbatim.
|
// 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();
|
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
|
// 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
|
// 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
|
// zero is a rendering concern owned by the authoritative string, not reconstructable
|
||||||
|
|||||||
+23
-2
@@ -148,6 +148,9 @@ constexpr int kFooterHeight = 26;
|
|||||||
const LICE_pixel kColFooterBg = LICE_RGBA(20, 20, 22, 255);
|
const LICE_pixel kColFooterBg = LICE_RGBA(20, 20, 22, 255);
|
||||||
const LICE_pixel kColFooterBorder = LICE_RGBA(70, 70, 76, 255);
|
const LICE_pixel kColFooterBorder = LICE_RGBA(70, 70, 76, 255);
|
||||||
const COLORREF kRgbFooterText = RGB(190, 205, 198);
|
const COLORREF kRgbFooterText = RGB(190, 205, 198);
|
||||||
|
// Dimmer than the tail label — the version/channel readout is passive identification,
|
||||||
|
// not an interactive control, so it recedes visually (V3 unobtrusive placement).
|
||||||
|
const COLORREF kRgbFooterVersion = RGB(120, 128, 124);
|
||||||
|
|
||||||
// --- Vertical split + region headers + tab strip (Phase B4) -------------------
|
// --- Vertical split + region headers + tab strip (Phase B4) -------------------
|
||||||
//
|
//
|
||||||
@@ -528,13 +531,27 @@ void drawTailFooter(LICE_IBitmap* bmp, int w, int h) {
|
|||||||
|
|
||||||
HDC dc = bmp->getDC();
|
HDC dc = bmp->getDC();
|
||||||
if (!dc) return;
|
if (!dc) return;
|
||||||
|
SetBkMode(dc, TRANSPARENT);
|
||||||
|
|
||||||
|
// Tail-mode toggle, left-aligned (the interactive control — footer clicks cycle it).
|
||||||
const std::string label = tailToggleLabel(currentTail());
|
const std::string label = tailToggleLabel(currentTail());
|
||||||
RECT rc = f;
|
RECT rc = f;
|
||||||
rc.left += 8;
|
rc.left += 8;
|
||||||
SetTextColor(dc, kRgbFooterText);
|
SetTextColor(dc, kRgbFooterText);
|
||||||
SetBkMode(dc, TRANSPARENT);
|
|
||||||
DrawText(dc, label.c_str(), -1, &rc,
|
DrawText(dc, label.c_str(), -1, &rc,
|
||||||
DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
|
||||||
|
|
||||||
|
// Version/channel readout (Phase V, V3/V4), right-aligned in the same footer strip so
|
||||||
|
// it is always visible but unobtrusive. appVersion() renders "0.9.01" on stable and
|
||||||
|
// "0.9.01-beta" on beta, so a beta panel self-identifies its channel here. Right inset
|
||||||
|
// matches the left inset; DT_RIGHT keeps it clear of the left-aligned tail label
|
||||||
|
// (the two never overlap at normal panel widths — the label is short, the readout is
|
||||||
|
// ~10 chars, and DT_END_ELLIPSIS on both degrades gracefully if a panel is ever tiny).
|
||||||
|
RECT vrc = f;
|
||||||
|
vrc.right -= 8;
|
||||||
|
SetTextColor(dc, kRgbFooterVersion);
|
||||||
|
DrawText(dc, reasampler::appVersion().c_str(), -1, &vrc,
|
||||||
|
DT_RIGHT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// True iff client-relative (x, y) falls inside the (non-degenerate) footer strip.
|
// True iff client-relative (x, y) falls inside the (non-degenerate) footer strip.
|
||||||
@@ -1993,7 +2010,11 @@ void openPanel() {
|
|||||||
GetMainHwnd(), dlgProc, 0);
|
GetMainHwnd(), dlgProc, 0);
|
||||||
if (!g_panel.hwnd) return;
|
if (!g_panel.hwnd) return;
|
||||||
|
|
||||||
DockWindowAddEx(g_panel.hwnd, "ReaSampler Bank", "reasampler_bank_panel", true);
|
// Channel-qualified dock identity (Phase V, V4). The title and the persisted-position
|
||||||
|
// identstr both come from app_version, so a beta panel is distinguishable ("ReaSampler
|
||||||
|
// Bank beta") and does not fight over stable's saved dock slot (the identstr is a
|
||||||
|
// REAPER-global collision surface — it keys the persisted dock position).
|
||||||
|
DockWindowAddEx(g_panel.hwnd, dockTitle().c_str(), dockIdent().c_str(), true);
|
||||||
DockWindowActivate(g_panel.hwnd);
|
DockWindowActivate(g_panel.hwnd);
|
||||||
g_panel.open = true;
|
g_panel.open = true;
|
||||||
|
|
||||||
|
|||||||
+121
-69
@@ -18,6 +18,7 @@
|
|||||||
#include "reaper_plugin.h"
|
#include "reaper_plugin.h"
|
||||||
#include "reaper_plugin_functions.h"
|
#include "reaper_plugin_functions.h"
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -34,12 +35,28 @@
|
|||||||
#include "track_guid.h"
|
#include "track_guid.h"
|
||||||
#include "view.h"
|
#include "view.h"
|
||||||
|
|
||||||
// Persistent action-id prefix for the ReaSampler action family.
|
// Persistent action-id family (Phase V, V4 — channel-qualified). Every bindable action
|
||||||
// Every bindable action (capture / insert / slot / verify) mints its command id
|
// mints its command id from commandIdPrefix() + a per-action SUFFIX, and its Actions-list
|
||||||
// from a string beginning with this prefix, e.g. "CEREBELLUM_REASAMPLER_CAPTURE_TRACK".
|
// name from actionDisplayPrefix() + a phrase, both derived from the ONE channel bit in the
|
||||||
// FOREVER-STABLE once shipped: user keybindings key off these strings, so the
|
// pure app_version module (channelCommandId / channelActionName). Stable rebuilds the exact
|
||||||
// prefix and any minted id must never change after release.
|
// shipped id ("CEREBELLUM_REASAMPLER_CAPTURE_TRACK"); beta yields the isolated forever-
|
||||||
#define REASAMPLER_ACTION_PREFIX "CEREBELLUM_REASAMPLER_"
|
// family id ("CEREBELLUM_REASAMPLER_BETA_CAPTURE_TRACK"). FOREVER-STABLE per channel: a
|
||||||
|
// shipped suffix is as permanent as the prefix; user keybindings key off the composed id.
|
||||||
|
//
|
||||||
|
// The composed id strings are held here for the module's lifetime (idStore) so both the
|
||||||
|
// register call and the mirroring '-command_id' unregister pass the SAME stable pointer.
|
||||||
|
// A std::deque (NOT vector) is used deliberately: it never invalidates references to
|
||||||
|
// existing elements on push_back, so a c_str() handed out early stays valid after later
|
||||||
|
// interning — the unload path re-presents these same pointers.
|
||||||
|
static std::deque<std::string> g_idStore;
|
||||||
|
|
||||||
|
// Interns a composed command-id string for the module lifetime and returns its C string.
|
||||||
|
// Appended-to only during startup registration and read on unload; never cleared until
|
||||||
|
// process exit, and deque guarantees the returned pointer stays valid.
|
||||||
|
static const char* internCmdId(const std::string& suffix) {
|
||||||
|
g_idStore.push_back(reasampler::channelCommandId(suffix));
|
||||||
|
return g_idStore.back().c_str();
|
||||||
|
}
|
||||||
|
|
||||||
// Globals other files reference via `extern`.
|
// Globals other files reference via `extern`.
|
||||||
REAPER_PLUGIN_HINSTANCE g_hInst = nullptr; // this module's instance handle
|
REAPER_PLUGIN_HINSTANCE g_hInst = nullptr; // this module's instance handle
|
||||||
@@ -63,9 +80,16 @@ reaper_plugin_info_t* g_rec = nullptr; // REAPER's dispatch struct
|
|||||||
// scope and sized to the table. FOREVER-STABLE id strings live in the table.
|
// scope and sized to the table. FOREVER-STABLE id strings live in the table.
|
||||||
static std::vector<int> g_captureCmdIds;
|
static std::vector<int> g_captureCmdIds;
|
||||||
static std::vector<gaccel_register_t> g_captureAccels;
|
static std::vector<gaccel_register_t> g_captureAccels;
|
||||||
|
// Channel-qualified capture-action labels, one per table row. REAPER holds each gaccel's
|
||||||
|
// `desc` pointer, so the composed strings live here for the module lifetime (parallel to
|
||||||
|
// g_captureAccels; never resized after the registration loop sets it).
|
||||||
|
static std::vector<std::string> g_captureDescs;
|
||||||
|
|
||||||
// Retired capture-action command-id strings. Kept ONLY to mirror-unregister them on
|
// Retired capture-action command-id SUFFIXES. Kept ONLY to mirror-unregister them on
|
||||||
// unload so a user's stale keybindings are cleaned up. Never re-register these.
|
// unload so a user's stale keybindings are cleaned up. Never re-register these. Composed
|
||||||
|
// through the channel prefix at unload (channelCommandId) so a beta unload clears beta-
|
||||||
|
// qualified retired ids and a stable unload clears stable's — each channel cleans up only
|
||||||
|
// its own family.
|
||||||
// * The M7 four-mode ids (tracks/items/razor WET).
|
// * The M7 four-mode ids (tracks/items/razor WET).
|
||||||
// * CAPTURE_MASTER and CAPTURE_MASTER_REALTIME — the master offline scope and the
|
// * CAPTURE_MASTER and CAPTURE_MASTER_REALTIME — the master offline scope and the
|
||||||
// master realtime action are REMOVED (capture is now item + track only; realtime
|
// master realtime action are REMOVED (capture is now item + track only; realtime
|
||||||
@@ -73,14 +97,14 @@ static std::vector<gaccel_register_t> g_captureAccels;
|
|||||||
// * CAPTURE_ITEM_TAIL and CAPTURE_TRACK_TAIL — the former per-action tail variants
|
// * CAPTURE_ITEM_TAIL and CAPTURE_TRACK_TAIL — the former per-action tail variants
|
||||||
// are REMOVED; tail is now a panel-setting toggle, not a paired action. Retired so
|
// are REMOVED; tail is now a panel-setting toggle, not a paired action. Retired so
|
||||||
// old keybindings clear.
|
// old keybindings clear.
|
||||||
static const char* const kRetiredCaptureCmdStrings[] = {
|
static const char* const kRetiredCaptureCmdSuffixes[] = {
|
||||||
"CEREBELLUM_REASAMPLER_CAPTURE_TRACKS_WET",
|
"CAPTURE_TRACKS_WET",
|
||||||
"CEREBELLUM_REASAMPLER_CAPTURE_ITEMS_WET",
|
"CAPTURE_ITEMS_WET",
|
||||||
"CEREBELLUM_REASAMPLER_CAPTURE_RAZOR_WET",
|
"CAPTURE_RAZOR_WET",
|
||||||
"CEREBELLUM_REASAMPLER_CAPTURE_MASTER",
|
"CAPTURE_MASTER",
|
||||||
"CEREBELLUM_REASAMPLER_CAPTURE_MASTER_REALTIME",
|
"CAPTURE_MASTER_REALTIME",
|
||||||
"CEREBELLUM_REASAMPLER_CAPTURE_ITEM_TAIL",
|
"CAPTURE_ITEM_TAIL",
|
||||||
"CEREBELLUM_REASAMPLER_CAPTURE_TRACK_TAIL",
|
"CAPTURE_TRACK_TAIL",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Command id for "ReaSampler: toggle bank panel" (M5). FOREVER-STABLE string.
|
// Command id for "ReaSampler: toggle bank panel" (M5). FOREVER-STABLE string.
|
||||||
@@ -811,6 +835,25 @@ static gaccel_register_t g_accelCaptureTrackRealtime{};
|
|||||||
static gaccel_register_t g_accelCancelRealtime{};
|
static gaccel_register_t g_accelCancelRealtime{};
|
||||||
static gaccel_register_t g_accelShowVersion{};
|
static gaccel_register_t g_accelShowVersion{};
|
||||||
|
|
||||||
|
// gaccel desc storage. The Actions-list label is channel-qualified at runtime
|
||||||
|
// (channelActionName) so it cannot be a string literal; REAPER holds the gaccel's `desc`
|
||||||
|
// pointer, so each label lives here for the module lifetime. Composed once at registration.
|
||||||
|
static std::string g_descToggleBankPanel;
|
||||||
|
static std::string g_descInsertSelected;
|
||||||
|
static std::string g_descInsertSelectedConform;
|
||||||
|
static std::string g_descCaptureTrackRealtime;
|
||||||
|
static std::string g_descCancelRealtime;
|
||||||
|
static std::string g_descShowVersion;
|
||||||
|
|
||||||
|
// Composed command-id strings (channel-qualified), interned so register and the mirroring
|
||||||
|
// '-command_id' unregister pass the SAME pointer. Set during registration; read on unload.
|
||||||
|
static const char* g_idToggleBankPanel = nullptr;
|
||||||
|
static const char* g_idInsertSelected = nullptr;
|
||||||
|
static const char* g_idInsertSelectedConform = nullptr;
|
||||||
|
static const char* g_idCaptureTrackRealtime = nullptr;
|
||||||
|
static const char* g_idCancelRealtime = nullptr;
|
||||||
|
static const char* g_idShowVersion = nullptr;
|
||||||
|
|
||||||
extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||||
REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t* rec)
|
REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t* rec)
|
||||||
{
|
{
|
||||||
@@ -841,40 +884,42 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
|||||||
reasampler::designViewUnregisterActions(g_rec);
|
reasampler::designViewUnregisterActions(g_rec);
|
||||||
// Tear down the multi-bank action family (B3) — same mirror-unregister.
|
// Tear down the multi-bank action family (B3) — same mirror-unregister.
|
||||||
reasampler::bankUnregisterActions(g_rec);
|
reasampler::bankUnregisterActions(g_rec);
|
||||||
|
// Each '-command_id' re-presents the SAME interned, channel-qualified pointer
|
||||||
|
// used at register (g_id*), so the mirror-unregister matches exactly.
|
||||||
g_rec->Register("-gaccel", (void*)&g_accelShowVersion);
|
g_rec->Register("-gaccel", (void*)&g_accelShowVersion);
|
||||||
g_rec->Register("-command_id",
|
g_rec->Register("-command_id", (void*)g_idShowVersion);
|
||||||
(void*)(REASAMPLER_ACTION_PREFIX "SHOW_VERSION"));
|
|
||||||
g_rec->Register("-gaccel", (void*)&g_accelCancelRealtime);
|
g_rec->Register("-gaccel", (void*)&g_accelCancelRealtime);
|
||||||
g_rec->Register("-command_id",
|
g_rec->Register("-command_id", (void*)g_idCancelRealtime);
|
||||||
(void*)(REASAMPLER_ACTION_PREFIX "CANCEL_REALTIME_CAPTURE"));
|
|
||||||
g_rec->Register("-gaccel", (void*)&g_accelCaptureTrackRealtime);
|
g_rec->Register("-gaccel", (void*)&g_accelCaptureTrackRealtime);
|
||||||
g_rec->Register("-command_id",
|
g_rec->Register("-command_id", (void*)g_idCaptureTrackRealtime);
|
||||||
(void*)(REASAMPLER_ACTION_PREFIX "CAPTURE_TRACK_REALTIME"));
|
|
||||||
g_rec->Register("-gaccel", (void*)&g_accelInsertSelectedConform);
|
g_rec->Register("-gaccel", (void*)&g_accelInsertSelectedConform);
|
||||||
g_rec->Register("-command_id",
|
g_rec->Register("-command_id", (void*)g_idInsertSelectedConform);
|
||||||
(void*)(REASAMPLER_ACTION_PREFIX "INSERT_SELECTED_CONFORM"));
|
|
||||||
g_rec->Register("-gaccel", (void*)&g_accelInsertSelected);
|
g_rec->Register("-gaccel", (void*)&g_accelInsertSelected);
|
||||||
g_rec->Register("-command_id",
|
g_rec->Register("-command_id", (void*)g_idInsertSelected);
|
||||||
(void*)(REASAMPLER_ACTION_PREFIX "INSERT_SELECTED"));
|
|
||||||
g_rec->Register("-gaccel", (void*)&g_accelToggleBankPanel);
|
g_rec->Register("-gaccel", (void*)&g_accelToggleBankPanel);
|
||||||
g_rec->Register("-command_id",
|
g_rec->Register("-command_id", (void*)g_idToggleBankPanel);
|
||||||
(void*)(REASAMPLER_ACTION_PREFIX "TOGGLE_BANK_PANEL"));
|
// Mirror-unregister the capture family: gaccel + command_id per row, with
|
||||||
// Mirror-unregister the capture family: gaccel + command_id per row,
|
// '-'-prefixed strings (per the contract). The command id is re-composed from
|
||||||
// with '-'-prefixed strings (per the contract). The FOREVER-STABLE id
|
// the same suffix + channel prefix used at register — identical string.
|
||||||
// strings come from the same table used to register them.
|
|
||||||
{
|
{
|
||||||
const auto& table = reasampler::captureActionTable();
|
const auto& table = reasampler::captureActionTable();
|
||||||
for (std::size_t i = 0; i < table.size(); ++i)
|
for (std::size_t i = 0; i < table.size(); ++i)
|
||||||
{
|
{
|
||||||
if (i < g_captureAccels.size())
|
if (i < g_captureAccels.size())
|
||||||
g_rec->Register("-gaccel", (void*)&g_captureAccels[i]);
|
g_rec->Register("-gaccel", (void*)&g_captureAccels[i]);
|
||||||
g_rec->Register("-command_id", (void*)table[i].commandString);
|
const std::string id =
|
||||||
|
reasampler::channelCommandId(table[i].commandSuffix);
|
||||||
|
g_rec->Register("-command_id", (void*)id.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Retire the removed M7 command ids (command_id only — we never held a
|
// Retire the removed M7 command ids (command_id only — we never held a gaccel
|
||||||
// gaccel for them this session). Clears stale user keybindings on unload.
|
// for them this session). Clears stale user keybindings on unload. Composed
|
||||||
for (const char* id : kRetiredCaptureCmdStrings)
|
// per channel so a beta clears beta-qualified retired ids, stable clears its own.
|
||||||
g_rec->Register("-command_id", (void*)id);
|
for (const char* suffix : kRetiredCaptureCmdSuffixes)
|
||||||
|
{
|
||||||
|
const std::string id = reasampler::channelCommandId(suffix);
|
||||||
|
g_rec->Register("-command_id", (void*)id.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Destroy the docked window and release cached thumbnails before we drop
|
// Destroy the docked window and release cached thumbnails before we drop
|
||||||
// the API pointers (DockWindowRemove/DestroyWindow need them live).
|
// the API pointers (DockWindowRemove/DestroyWindow need them live).
|
||||||
@@ -903,15 +948,21 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
|||||||
const auto& table = reasampler::captureActionTable();
|
const auto& table = reasampler::captureActionTable();
|
||||||
g_captureCmdIds.assign(table.size(), 0);
|
g_captureCmdIds.assign(table.size(), 0);
|
||||||
g_captureAccels.assign(table.size(), gaccel_register_t{});
|
g_captureAccels.assign(table.size(), gaccel_register_t{});
|
||||||
|
g_captureDescs.assign(table.size(), std::string{});
|
||||||
for (std::size_t i = 0; i < table.size(); ++i)
|
for (std::size_t i = 0; i < table.size(); ++i)
|
||||||
{
|
{
|
||||||
|
// Compose the channel-qualified id (prefix + suffix) and label
|
||||||
|
// ("ReaSampler[ beta]: " + phrase). The id is interned so unregister re-presents
|
||||||
|
// the same pointer; the label lives in g_captureDescs for the gaccel's lifetime.
|
||||||
const int cmd =
|
const int cmd =
|
||||||
rec->Register("command_id", (void*)table[i].commandString);
|
rec->Register("command_id", (void*)internCmdId(table[i].commandSuffix));
|
||||||
g_captureCmdIds[i] = cmd;
|
g_captureCmdIds[i] = cmd;
|
||||||
if (cmd)
|
if (cmd)
|
||||||
{
|
{
|
||||||
|
g_captureDescs[i] =
|
||||||
|
reasampler::channelActionName(table[i].descriptionPhrase);
|
||||||
g_captureAccels[i].accel.cmd = cmd;
|
g_captureAccels[i].accel.cmd = cmd;
|
||||||
g_captureAccels[i].desc = table[i].description;
|
g_captureAccels[i].desc = g_captureDescs[i].c_str();
|
||||||
rec->Register("gaccel", (void*)&g_captureAccels[i]);
|
rec->Register("gaccel", (void*)&g_captureAccels[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -923,14 +974,14 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
|||||||
reasampler::bankPanelInit(&g_session);
|
reasampler::bankPanelInit(&g_session);
|
||||||
|
|
||||||
// Register the M5 "toggle bank panel" action (command_id -> gaccel ->
|
// Register the M5 "toggle bank panel" action (command_id -> gaccel ->
|
||||||
// hookcommand + toggleaction for the checked state).
|
// hookcommand + toggleaction for the checked state). Id + label are channel-qualified.
|
||||||
g_cmdToggleBankPanel = rec->Register(
|
g_idToggleBankPanel = internCmdId("TOGGLE_BANK_PANEL");
|
||||||
"command_id",
|
g_cmdToggleBankPanel = rec->Register("command_id", (void*)g_idToggleBankPanel);
|
||||||
(void*)(REASAMPLER_ACTION_PREFIX "TOGGLE_BANK_PANEL"));
|
|
||||||
if (g_cmdToggleBankPanel)
|
if (g_cmdToggleBankPanel)
|
||||||
{
|
{
|
||||||
|
g_descToggleBankPanel = reasampler::channelActionName("toggle bank panel");
|
||||||
g_accelToggleBankPanel.accel.cmd = g_cmdToggleBankPanel;
|
g_accelToggleBankPanel.accel.cmd = g_cmdToggleBankPanel;
|
||||||
g_accelToggleBankPanel.desc = "ReaSampler: toggle bank panel";
|
g_accelToggleBankPanel.desc = g_descToggleBankPanel.c_str();
|
||||||
rec->Register("gaccel", (void*)&g_accelToggleBankPanel);
|
rec->Register("gaccel", (void*)&g_accelToggleBankPanel);
|
||||||
rec->Register("toggleaction", (void*)&OnToggleAction);
|
rec->Register("toggleaction", (void*)&OnToggleAction);
|
||||||
}
|
}
|
||||||
@@ -938,65 +989,66 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
|||||||
// Register the M6 insert actions (command_id -> gaccel -> hookcommand). Two
|
// Register the M6 insert actions (command_id -> gaccel -> hookcommand). Two
|
||||||
// variants: native-length (default, no stretch) and the EXPLICIT conform-to-
|
// variants: native-length (default, no stretch) and the EXPLICIT conform-to-
|
||||||
// tempo opt-in. Both read the bank panel selection and place at the edit cursor.
|
// tempo opt-in. Both read the bank panel selection and place at the edit cursor.
|
||||||
g_cmdInsertSelected = rec->Register(
|
g_idInsertSelected = internCmdId("INSERT_SELECTED");
|
||||||
"command_id",
|
g_cmdInsertSelected = rec->Register("command_id", (void*)g_idInsertSelected);
|
||||||
(void*)(REASAMPLER_ACTION_PREFIX "INSERT_SELECTED"));
|
|
||||||
if (g_cmdInsertSelected)
|
if (g_cmdInsertSelected)
|
||||||
{
|
{
|
||||||
|
g_descInsertSelected =
|
||||||
|
reasampler::channelActionName("insert selected sample at edit cursor");
|
||||||
g_accelInsertSelected.accel.cmd = g_cmdInsertSelected;
|
g_accelInsertSelected.accel.cmd = g_cmdInsertSelected;
|
||||||
g_accelInsertSelected.desc =
|
g_accelInsertSelected.desc = g_descInsertSelected.c_str();
|
||||||
"ReaSampler: insert selected sample at edit cursor";
|
|
||||||
rec->Register("gaccel", (void*)&g_accelInsertSelected);
|
rec->Register("gaccel", (void*)&g_accelInsertSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_cmdInsertSelectedConform = rec->Register(
|
g_idInsertSelectedConform = internCmdId("INSERT_SELECTED_CONFORM");
|
||||||
"command_id",
|
g_cmdInsertSelectedConform = rec->Register("command_id", (void*)g_idInsertSelectedConform);
|
||||||
(void*)(REASAMPLER_ACTION_PREFIX "INSERT_SELECTED_CONFORM"));
|
|
||||||
if (g_cmdInsertSelectedConform)
|
if (g_cmdInsertSelectedConform)
|
||||||
{
|
{
|
||||||
|
g_descInsertSelectedConform = reasampler::channelActionName(
|
||||||
|
"insert selected sample at edit cursor (conform to tempo)");
|
||||||
g_accelInsertSelectedConform.accel.cmd = g_cmdInsertSelectedConform;
|
g_accelInsertSelectedConform.accel.cmd = g_cmdInsertSelectedConform;
|
||||||
g_accelInsertSelectedConform.desc =
|
g_accelInsertSelectedConform.desc = g_descInsertSelectedConform.c_str();
|
||||||
"ReaSampler: insert selected sample at edit cursor (conform to tempo)";
|
|
||||||
rec->Register("gaccel", (void*)&g_accelInsertSelectedConform);
|
rec->Register("gaccel", (void*)&g_accelInsertSelectedConform);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the "capture selected track (realtime)" action (command_id -> gaccel ->
|
// Register the "capture selected track (realtime)" action (command_id -> gaccel ->
|
||||||
// hookcommand). Realtime sibling of the offline CAPTURE_TRACK scope: records the
|
// hookcommand). Realtime sibling of the offline CAPTURE_TRACK scope: records the
|
||||||
// selected track's own output in realtime into a hidden temp track, moves it into
|
// selected track's own output in realtime into a hidden temp track, moves it into
|
||||||
// the bank. Dialog-free. NEW FOREVER-STABLE id string.
|
// the bank. Dialog-free. Channel-qualified FOREVER-STABLE id.
|
||||||
g_cmdCaptureTrackRealtime = rec->Register(
|
g_idCaptureTrackRealtime = internCmdId("CAPTURE_TRACK_REALTIME");
|
||||||
"command_id",
|
g_cmdCaptureTrackRealtime = rec->Register("command_id", (void*)g_idCaptureTrackRealtime);
|
||||||
(void*)(REASAMPLER_ACTION_PREFIX "CAPTURE_TRACK_REALTIME"));
|
|
||||||
if (g_cmdCaptureTrackRealtime)
|
if (g_cmdCaptureTrackRealtime)
|
||||||
{
|
{
|
||||||
|
g_descCaptureTrackRealtime =
|
||||||
|
reasampler::channelActionName("capture selected track (realtime)");
|
||||||
g_accelCaptureTrackRealtime.accel.cmd = g_cmdCaptureTrackRealtime;
|
g_accelCaptureTrackRealtime.accel.cmd = g_cmdCaptureTrackRealtime;
|
||||||
g_accelCaptureTrackRealtime.desc =
|
g_accelCaptureTrackRealtime.desc = g_descCaptureTrackRealtime.c_str();
|
||||||
"ReaSampler: capture selected track (realtime)";
|
|
||||||
rec->Register("gaccel", (void*)&g_accelCaptureTrackRealtime);
|
rec->Register("gaccel", (void*)&g_accelCaptureTrackRealtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel-in-flight sibling: aborts a running realtime capture (stop + restore).
|
// Cancel-in-flight sibling: aborts a running realtime capture (stop + restore).
|
||||||
// FOREVER-STABLE id string.
|
// Channel-qualified FOREVER-STABLE id.
|
||||||
g_cmdCancelRealtime = rec->Register(
|
g_idCancelRealtime = internCmdId("CANCEL_REALTIME_CAPTURE");
|
||||||
"command_id",
|
g_cmdCancelRealtime = rec->Register("command_id", (void*)g_idCancelRealtime);
|
||||||
(void*)(REASAMPLER_ACTION_PREFIX "CANCEL_REALTIME_CAPTURE"));
|
|
||||||
if (g_cmdCancelRealtime)
|
if (g_cmdCancelRealtime)
|
||||||
{
|
{
|
||||||
|
g_descCancelRealtime = reasampler::channelActionName("cancel realtime capture");
|
||||||
g_accelCancelRealtime.accel.cmd = g_cmdCancelRealtime;
|
g_accelCancelRealtime.accel.cmd = g_cmdCancelRealtime;
|
||||||
g_accelCancelRealtime.desc = "ReaSampler: cancel realtime capture";
|
g_accelCancelRealtime.desc = g_descCancelRealtime.c_str();
|
||||||
rec->Register("gaccel", (void*)&g_accelCancelRealtime);
|
rec->Register("gaccel", (void*)&g_accelCancelRealtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the Phase V "show version" action (command_id -> gaccel -> hookcommand).
|
// Register the Phase V "show version" action (command_id -> gaccel -> hookcommand).
|
||||||
// On-demand only — prints the CMake-sourced version to the console when fired; no
|
// On-demand only — prints the CMake-sourced version to the console when fired; no
|
||||||
// startup print. FOREVER-STABLE id string.
|
// startup print. Channel-qualified FOREVER-STABLE id; label carries the channel prefix
|
||||||
g_cmdShowVersion = rec->Register(
|
// so a beta's "show version" is distinguishable from stable's in the Actions list.
|
||||||
"command_id",
|
g_idShowVersion = internCmdId("SHOW_VERSION");
|
||||||
(void*)(REASAMPLER_ACTION_PREFIX "SHOW_VERSION"));
|
g_cmdShowVersion = rec->Register("command_id", (void*)g_idShowVersion);
|
||||||
if (g_cmdShowVersion)
|
if (g_cmdShowVersion)
|
||||||
{
|
{
|
||||||
|
g_descShowVersion = reasampler::channelActionName("show version");
|
||||||
g_accelShowVersion.accel.cmd = g_cmdShowVersion;
|
g_accelShowVersion.accel.cmd = g_cmdShowVersion;
|
||||||
g_accelShowVersion.desc = "ReaSampler: show version";
|
g_accelShowVersion.desc = g_descShowVersion.c_str();
|
||||||
rec->Register("gaccel", (void*)&g_accelShowVersion);
|
rec->Register("gaccel", (void*)&g_accelShowVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-22
@@ -194,7 +194,7 @@ bool ReaSamplerSession::saveToActiveProject() {
|
|||||||
// Phase B: the whole book (pool as bank-zero + named banks) is authoritative and
|
// Phase B: the whole book (pool as bank-zero + named banks) is authoritative and
|
||||||
// rides in the `banks` key.
|
// rides in the `banks` key.
|
||||||
const std::string banksJson = book_.serialize();
|
const std::string banksJson = book_.serialize();
|
||||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
SetProjExtState(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||||
kProjExtBanksKey, banksJson.c_str());
|
kProjExtBanksKey, banksJson.c_str());
|
||||||
|
|
||||||
// Retire the legacy single-bank `bank_index` key: SetProjExtState with an empty
|
// Retire the legacy single-bank `bank_index` key: SetProjExtState with an empty
|
||||||
@@ -202,37 +202,40 @@ bool ReaSamplerSession::saveToActiveProject() {
|
|||||||
// realizes retirement concretely — after any save, a formerly-legacy project
|
// realizes retirement concretely — after any save, a formerly-legacy project
|
||||||
// carries `banks` and NO `bank_index`, and going forward the legacy key is never
|
// carries `banks` and NO `bank_index`, and going forward the legacy key is never
|
||||||
// written. Cheap and idempotent when the key is already absent.
|
// written. Cheap and idempotent when the key is already absent.
|
||||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
SetProjExtState(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||||
kProjExtIndexKey, "");
|
kProjExtIndexKey, "");
|
||||||
|
|
||||||
// Additive: the Design-View model rides alongside the banks in its own key.
|
// Additive: the Design-View model rides alongside the banks in its own key.
|
||||||
// Independent write — does not disturb the `banks` blob above.
|
// Independent write — does not disturb the `banks` blob above.
|
||||||
const std::string viewJson = view_.serialize();
|
const std::string viewJson = view_.serialize();
|
||||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
SetProjExtState(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||||
kProjExtViewKey, viewJson.c_str());
|
kProjExtViewKey, viewJson.c_str());
|
||||||
|
|
||||||
// Additive: the docked panel's tail setting rides alongside in its own key, so the
|
// Additive: the docked panel's tail setting rides alongside in its own key, so the
|
||||||
// tail choice travels inside the .rpp. Independent write — does not disturb the
|
// tail choice travels inside the .rpp. Independent write — does not disturb the
|
||||||
// bank_index or view_state above.
|
// bank_index or view_state above.
|
||||||
const std::string tailJson = serializeTailSetting(tail_);
|
const std::string tailJson = serializeTailSetting(tail_);
|
||||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
SetProjExtState(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||||
kProjExtTailKey, tailJson.c_str());
|
kProjExtTailKey, tailJson.c_str());
|
||||||
|
|
||||||
// Additive: the owned-file manifest (Phase B B-cap) rides alongside in its own
|
// Additive: the owned-file manifest (Phase B B-cap) rides alongside in its own
|
||||||
// `owned_files` key. Independent write — does not disturb the blobs above. Written
|
// `owned_files` key. Independent write — does not disturb the blobs above. Written
|
||||||
// on EVERY save so a capture's manifest record survives Save / Save-As / reopen,
|
// on EVERY save so a capture's manifest record survives Save / Save-As / reopen,
|
||||||
// and so the manifest and the bank stay in lockstep on disk (both persisted by the
|
// and so the manifest and the bank stay in lockstep on disk (both persisted by the
|
||||||
// same saveToActiveProject the capture add-path calls).
|
// same saveToActiveProject the capture add-path calls). Uses the channel-derived
|
||||||
|
// namespace (projExtNamespace) like its sibling keys — V4 isolation applies here too.
|
||||||
const std::string ownedJson = owned_.serialize();
|
const std::string ownedJson = owned_.serialize();
|
||||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
SetProjExtState(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||||
kProjExtOwnedKey, ownedJson.c_str());
|
kProjExtOwnedKey, ownedJson.c_str());
|
||||||
|
|
||||||
// Phase V (V1): stamp the WRITING version — the build producing this save — under the
|
// Phase V (V1/V4): stamp the WRITING version — the build producing this save — under
|
||||||
// version key, on the SAME seam as the keys above so the stamp and MarkProjectDirty
|
// the version key, on the SAME seam as the keys above so the stamp and MarkProjectDirty
|
||||||
// stay paired (no drifting ad-hoc SetProjExtState). appVersion() is the one CMake-
|
// stay paired (no drifting ad-hoc SetProjExtState). stampVersion() (NOT appVersion()) is
|
||||||
// sourced constant; every save records the exact current build into the .rpp.
|
// the NUMERIC TRIPLE ONLY on both channels — no "-beta" suffix — so the stamp parses as
|
||||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
// Stamped on read-back and stays byte-identical to stable regardless of channel; the
|
||||||
kProjExtVersionKey, appVersion().c_str());
|
// channel is already carried by the isolated namespace (projExtNamespace) this writes to.
|
||||||
|
SetProjExtState(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||||
|
kProjExtVersionKey, stampVersion().c_str());
|
||||||
|
|
||||||
MarkProjectDirty(static_cast<ReaProject*>(proj));
|
MarkProjectDirty(static_cast<ReaProject*>(proj));
|
||||||
return true;
|
return true;
|
||||||
@@ -249,7 +252,7 @@ namespace {
|
|||||||
ViewModeModel loadViewModel(ReaProject* proj) {
|
ViewModeModel loadViewModel(ReaProject* proj) {
|
||||||
if (!proj) return ViewModeModel{};
|
if (!proj) return ViewModeModel{};
|
||||||
const std::string viewJson =
|
const std::string viewJson =
|
||||||
getProjExtStateString(proj, kProjExtNamespace, kProjExtViewKey);
|
getProjExtStateString(proj, projExtNamespace(), kProjExtViewKey);
|
||||||
if (viewJson.empty()) return ViewModeModel{}; // no stored view state -> default
|
if (viewJson.empty()) return ViewModeModel{}; // no stored view state -> default
|
||||||
std::optional<ViewModeModel> loaded = ViewModeModel::deserialize(viewJson);
|
std::optional<ViewModeModel> loaded = ViewModeModel::deserialize(viewJson);
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
@@ -266,7 +269,7 @@ ViewModeModel loadViewModel(ReaProject* proj) {
|
|||||||
TailSetting loadTailSetting(ReaProject* proj) {
|
TailSetting loadTailSetting(ReaProject* proj) {
|
||||||
if (!proj) return TailSetting{};
|
if (!proj) return TailSetting{};
|
||||||
const std::string tailJson =
|
const std::string tailJson =
|
||||||
getProjExtStateString(proj, kProjExtNamespace, kProjExtTailKey);
|
getProjExtStateString(proj, projExtNamespace(), kProjExtTailKey);
|
||||||
if (tailJson.empty()) return TailSetting{}; // no stored setting -> default
|
if (tailJson.empty()) return TailSetting{}; // no stored setting -> default
|
||||||
std::optional<TailSetting> loaded = deserializeTailSetting(tailJson);
|
std::optional<TailSetting> loaded = deserializeTailSetting(tailJson);
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
@@ -285,7 +288,7 @@ TailSetting loadTailSetting(ReaProject* proj) {
|
|||||||
OwnedFileManifest loadOwnedManifest(ReaProject* proj) {
|
OwnedFileManifest loadOwnedManifest(ReaProject* proj) {
|
||||||
if (!proj) return OwnedFileManifest{};
|
if (!proj) return OwnedFileManifest{};
|
||||||
const std::string ownedJson =
|
const std::string ownedJson =
|
||||||
getProjExtStateString(proj, kProjExtNamespace, kProjExtOwnedKey);
|
getProjExtStateString(proj, projExtNamespace(), kProjExtOwnedKey);
|
||||||
if (ownedJson.empty()) return OwnedFileManifest{}; // no stored manifest -> empty
|
if (ownedJson.empty()) return OwnedFileManifest{}; // no stored manifest -> empty
|
||||||
std::optional<OwnedFileManifest> loaded = OwnedFileManifest::deserialize(ownedJson);
|
std::optional<OwnedFileManifest> loaded = OwnedFileManifest::deserialize(ownedJson);
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
@@ -329,7 +332,7 @@ void ReaSamplerSession::loadFromProject(void* proj, const std::string& projectDi
|
|||||||
// an error). getProjExtStateString returns "" for an absent key, which is exactly the
|
// an error). getProjExtStateString returns "" for an absent key, which is exactly the
|
||||||
// PreVersioning input classifyWritingVersion expects. proj == nullptr -> "" -> default.
|
// PreVersioning input classifyWritingVersion expects. proj == nullptr -> "" -> default.
|
||||||
writingVersion_ = classifyWritingVersion(
|
writingVersion_ = classifyWritingVersion(
|
||||||
proj ? getProjExtStateString(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
proj ? getProjExtStateString(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||||
kProjExtVersionKey)
|
kProjExtVersionKey)
|
||||||
: std::string{});
|
: std::string{});
|
||||||
|
|
||||||
@@ -347,7 +350,7 @@ void ReaSamplerSession::loadFromProject(void* proj, const std::string& projectDi
|
|||||||
// warning. A malformed `banks` degrades to an empty book and does NOT fall back to
|
// warning. A malformed `banks` degrades to an empty book and does NOT fall back to
|
||||||
// the stale legacy key (which would resurrect superseded single-bank state).
|
// the stale legacy key (which would resurrect superseded single-bank state).
|
||||||
const std::string banksJson =
|
const std::string banksJson =
|
||||||
getProjExtStateString(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
getProjExtStateString(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||||
kProjExtBanksKey);
|
kProjExtBanksKey);
|
||||||
if (!banksJson.empty()) {
|
if (!banksJson.empty()) {
|
||||||
std::optional<BankBook> loaded = BankBook::deserialize(banksJson);
|
std::optional<BankBook> loaded = BankBook::deserialize(banksJson);
|
||||||
@@ -362,7 +365,7 @@ void ReaSamplerSession::loadFromProject(void* proj, const std::string& projectDi
|
|||||||
// by BankBook's parse-time promotion. loadFromPersisted covers the legacy-or-
|
// by BankBook's parse-time promotion. loadFromPersisted covers the legacy-or-
|
||||||
// empty tail; passing "" for banksJson takes exactly that branch.
|
// empty tail; passing "" for banksJson takes exactly that branch.
|
||||||
const std::string legacyJson =
|
const std::string legacyJson =
|
||||||
getProjExtStateString(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
getProjExtStateString(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||||
kProjExtIndexKey);
|
kProjExtIndexKey);
|
||||||
book_ = BankBook::loadFromPersisted(std::string{}, legacyJson);
|
book_ = BankBook::loadFromPersisted(std::string{}, legacyJson);
|
||||||
}
|
}
|
||||||
@@ -391,7 +394,7 @@ std::string ensureProjectGuid(void* proj, const std::string& rppPath,
|
|||||||
if (!proj || rppPath.empty()) return {}; // unsaved -> cannot store a GUID
|
if (!proj || rppPath.empty()) return {}; // unsaved -> cannot store a GUID
|
||||||
if (!currentGuid.empty()) return currentGuid;
|
if (!currentGuid.empty()) return currentGuid;
|
||||||
const std::string minted = genProjectGuidString();
|
const std::string minted = genProjectGuidString();
|
||||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
SetProjExtState(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||||
kProjExtGuidKey, minted.c_str());
|
kProjExtGuidKey, minted.c_str());
|
||||||
return minted;
|
return minted;
|
||||||
}
|
}
|
||||||
@@ -416,7 +419,7 @@ void ReaSamplerSession::poll() {
|
|||||||
void* proj = readActiveProject(rppPath);
|
void* proj = readActiveProject(rppPath);
|
||||||
const std::string currentGuid =
|
const std::string currentGuid =
|
||||||
proj ? getProjExtStateString(static_cast<ReaProject*>(proj),
|
proj ? getProjExtStateString(static_cast<ReaProject*>(proj),
|
||||||
kProjExtNamespace, kProjExtGuidKey)
|
projExtNamespace(), kProjExtGuidKey)
|
||||||
: std::string{};
|
: std::string{};
|
||||||
|
|
||||||
if (!primed_) {
|
if (!primed_) {
|
||||||
@@ -482,7 +485,7 @@ void ReaSamplerSession::poll() {
|
|||||||
if (proj && !sameProjectObject && !currentGuid.empty() &&
|
if (proj && !sameProjectObject && !currentGuid.empty() &&
|
||||||
currentGuid == lastGuid_ && !rppPath.empty()) {
|
currentGuid == lastGuid_ && !rppPath.empty()) {
|
||||||
const std::string fresh = genProjectGuidString();
|
const std::string fresh = genProjectGuidString();
|
||||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
SetProjExtState(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||||
kProjExtGuidKey, fresh.c_str());
|
kProjExtGuidKey, fresh.c_str());
|
||||||
MarkProjectDirty(static_cast<ReaProject*>(proj));
|
MarkProjectDirty(static_cast<ReaProject*>(proj));
|
||||||
loadFromProject(proj, projectDirOf(rppPath));
|
loadFromProject(proj, projectDirOf(rppPath));
|
||||||
@@ -521,7 +524,7 @@ void ReaSamplerSession::poll() {
|
|||||||
// to the new .rpp on the next normal save / close-prompt.
|
// to the new .rpp on the next normal save / close-prompt.
|
||||||
const std::string fresh = genProjectGuidString();
|
const std::string fresh = genProjectGuidString();
|
||||||
if (proj) {
|
if (proj) {
|
||||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
SetProjExtState(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||||
kProjExtGuidKey, fresh.c_str());
|
kProjExtGuidKey, fresh.c_str());
|
||||||
MarkProjectDirty(static_cast<ReaProject*>(proj));
|
MarkProjectDirty(static_cast<ReaProject*>(proj));
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-3
@@ -28,9 +28,16 @@
|
|||||||
|
|
||||||
namespace reasampler {
|
namespace reasampler {
|
||||||
|
|
||||||
// The ext-state namespace the index JSON is stored under. FOREVER-STABLE once
|
// The ext-state namespace every ReaSampler key is stored under. CHANNEL-DERIVED (Phase V,
|
||||||
// shipped: changing it orphans every already-saved project's index.
|
// V4): the pure app_version module owns the one channel-qualified string — "reasampler" on
|
||||||
inline constexpr const char* kProjExtNamespace = "reasampler";
|
// stable (byte-identical to the pre-V4 build) or "reasampler_beta" on the isolated beta
|
||||||
|
// build. FOREVER-STABLE per channel once shipped: changing either orphans every already-
|
||||||
|
// saved project's state. Beta reads/writes ONLY its own namespace — a project saved by
|
||||||
|
// stable shows empty/default state in beta and vice versa; that isolation is the accepted
|
||||||
|
// V4 safety property (no cross-namespace read, migration, or fallback), not a bug.
|
||||||
|
// Returns const char* (not a constexpr literal) because the string is channel-derived at
|
||||||
|
// build time; the accessor is the single call point for all persist reads/writes below.
|
||||||
|
inline const char* projExtNamespace() { return extStateNamespace().c_str(); }
|
||||||
|
|
||||||
// The RETIRED legacy ext-state key: pre-multi-bank projects stored the whole
|
// The RETIRED legacy ext-state key: pre-multi-bank projects stored the whole
|
||||||
// serialized BankIndex here (single bank). Phase B2 no longer WRITES it — on save
|
// serialized BankIndex here (single bank). Phase B2 no longer WRITES it — on save
|
||||||
|
|||||||
@@ -207,14 +207,15 @@ const std::vector<CaptureActionDef>& captureActionTable() {
|
|||||||
// main.cpp); the CAPTURE_MASTER scope action is REMOVED (its id is likewise
|
// main.cpp); the CAPTURE_MASTER scope action is REMOVED (its id is likewise
|
||||||
// mirror-unregistered) — to capture the master you render a track.
|
// mirror-unregistered) — to capture the master you render a track.
|
||||||
static const std::vector<CaptureActionDef> table = {
|
static const std::vector<CaptureActionDef> table = {
|
||||||
// Item scope — item/take FX only.
|
// Item scope — item/take FX only. Suffix + phrase are channel-agnostic; the shell
|
||||||
{"CEREBELLUM_REASAMPLER_CAPTURE_ITEM",
|
// composes the FOREVER-STABLE id (prefix + "CAPTURE_ITEM") and the display name.
|
||||||
"ReaSampler: capture selected item(s)", "item",
|
{"CAPTURE_ITEM",
|
||||||
|
"capture selected item(s)", "item",
|
||||||
CaptureScope::Item},
|
CaptureScope::Item},
|
||||||
|
|
||||||
// Track scope — item FX + the track's own FX.
|
// Track scope — item FX + the track's own FX.
|
||||||
{"CEREBELLUM_REASAMPLER_CAPTURE_TRACK",
|
{"CAPTURE_TRACK",
|
||||||
"ReaSampler: capture selected track(s)", "track",
|
"capture selected track(s)", "track",
|
||||||
CaptureScope::Track},
|
CaptureScope::Track},
|
||||||
};
|
};
|
||||||
return table;
|
return table;
|
||||||
|
|||||||
+12
-4
@@ -233,11 +233,19 @@ RazorRange razorUnionBounds(const std::vector<RazorRange>& ranges);
|
|||||||
// at fire time (see tail_control + bank_panel), so a single pair of actions covers
|
// at fire time (see tail_control + bank_panel), so a single pair of actions covers
|
||||||
// every tail state. Bounded, discoverable, NO dialogs (the tool's no-clutter ethos).
|
// every tail state. Bounded, discoverable, NO dialogs (the tool's no-clutter ethos).
|
||||||
//
|
//
|
||||||
// commandString is FOREVER-STABLE (user keybindings key off it) — never change a
|
// Phase V (V4): the row stores the channel-AGNOSTIC pieces — a command-id SUFFIX (the
|
||||||
// shipped value. baseName feeds the file stem (sanitized by capture_paths).
|
// tail after the family prefix) and a description PHRASE (the label after the "ReaSampler:
|
||||||
|
// " lead). The registering shell composes the full, channel-qualified id/name via
|
||||||
|
// app_version's channelCommandId / channelActionName (commandIdPrefix + suffix /
|
||||||
|
// actionDisplayPrefix + phrase). This keeps the pure table free of any channel branch:
|
||||||
|
// stable rebuilds the exact shipped id "CEREBELLUM_REASAMPLER_CAPTURE_TRACK" from
|
||||||
|
// prefix + "CAPTURE_TRACK"; beta yields "CEREBELLUM_REASAMPLER_BETA_CAPTURE_TRACK".
|
||||||
|
//
|
||||||
|
// commandSuffix is FOREVER-STABLE (user keybindings key off the composed id) — never
|
||||||
|
// change a shipped value. baseName feeds the file stem (sanitized by capture_paths).
|
||||||
struct CaptureActionDef {
|
struct CaptureActionDef {
|
||||||
const char* commandString; // CEREBELLUM_REASAMPLER_… FOREVER-STABLE id string
|
const char* commandSuffix; // e.g. "CAPTURE_TRACK" — FOREVER-STABLE (composed w/ prefix)
|
||||||
const char* description; // Actions-list label
|
const char* descriptionPhrase; // e.g. "capture selected track(s)" — Actions-list phrase
|
||||||
const char* baseName; // file-stem base for this capture
|
const char* baseName; // file-stem base for this capture
|
||||||
CaptureScope scope; // FX scope (item / track)
|
CaptureScope scope; // FX scope (item / track)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// version_generated.h.in — configure_file TEMPLATE. CMake substitutes @REASAMPLER_VERSION@
|
// version_generated.h.in — configure_file TEMPLATE. CMake substitutes @REASAMPLER_VERSION@
|
||||||
// (the ONE source-of-truth variable in CMakeLists.txt) and writes the result to the build
|
// (the ONE version source-of-truth variable in CMakeLists.txt) and @REASAMPLER_CHANNEL_IS_BETA@
|
||||||
// tree as version_generated.h. DO NOT edit the generated header — edit REASAMPLER_VERSION
|
// (0 for the stable/default build, 1 for -DREASAMPLER_CHANNEL=beta) and writes the result to
|
||||||
// in CMakeLists.txt; that single edit re-generates this and re-threads the exact string
|
// the build tree as version_generated.h. DO NOT edit the generated header — edit
|
||||||
// (leading zero preserved verbatim) into the binary constant, the ext-state stamp, and the
|
// REASAMPLER_VERSION / the channel flag in CMakeLists.txt; that single edit re-generates this
|
||||||
// show-version action. See app_version.h / .cpp.
|
// and re-threads the exact string (leading zero preserved verbatim) plus the channel bit into
|
||||||
|
// the binary constants, the ext-state namespace/stamp, the command-id prefix, and the panel
|
||||||
|
// readout. See app_version.h / .cpp.
|
||||||
|
//
|
||||||
|
// Two channel-derived facts flow from here and NOWHERE ELSE, so "what makes a beta a beta" is
|
||||||
|
// one auditable definition (Phase V, V4 — beta-in-isolation):
|
||||||
|
// * REASAMPLER_VERSION_STRING — the numeric semver triple, IDENTICAL on both channels
|
||||||
|
// (leading zero preserved). This is the ext-state STAMP value: it stays the numeric triple
|
||||||
|
// so parseVersion classifies it as Stamped when its own channel reads it back, and it is
|
||||||
|
// byte-identical to stable's stamp regardless of channel (the channel is carried by the
|
||||||
|
// ISOLATED namespace, not baked into the stamp — see app_version.h).
|
||||||
|
// * REASAMPLER_CHANNEL_IS_BETA — 0 (stable, the default absent-flag build) or 1 (beta). The
|
||||||
|
// one bit app_version fans out into every channel-qualified identity (display suffix,
|
||||||
|
// namespace, command-id prefix, action-name prefix, binary/dock idents).
|
||||||
|
|
||||||
#define REASAMPLER_VERSION_STRING "@REASAMPLER_VERSION@"
|
#define REASAMPLER_VERSION_STRING "@REASAMPLER_VERSION@"
|
||||||
|
#define REASAMPLER_CHANNEL_IS_BETA @REASAMPLER_CHANNEL_IS_BETA@
|
||||||
|
|||||||
@@ -17,14 +17,95 @@ static int g_fail = 0;
|
|||||||
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
||||||
|
|
||||||
// --- appVersion: exact-string fidelity, leading zero preserved ----------------
|
// --- appVersion: exact-string fidelity, leading zero preserved ----------------
|
||||||
|
//
|
||||||
|
// The channel is a COMPILE-TIME fact (REASAMPLER_CHANNEL_IS_BETA, threaded through the same
|
||||||
|
// configure_file'd header): the stable `build` tree compiles these tests with the flag = 0,
|
||||||
|
// the `build-beta` tree with = 1. So each config's ctest run validates ITS OWN channel's
|
||||||
|
// derivation. The channel-dependent assertions below branch on isBeta() so the ONE test
|
||||||
|
// source is correct in both configs — and each branch would fail if the derivation regressed
|
||||||
|
// (a beta build rendering "0.9.01" without the suffix, or stable rendering "-beta", trips it).
|
||||||
|
|
||||||
static void testVersionConstantRendersExactString() {
|
static void testVersionConstantRendersExactString() {
|
||||||
// The Daniel-fixed string: EXACTLY "0.9.01", two-digit zero-padded patch. This is
|
// The Daniel-fixed base string: EXACTLY "0.9.01", two-digit zero-padded patch — the
|
||||||
// the whole point of sourcing the STRING (not reconstructing from numeric components):
|
// numeric triple, IDENTICAL on both channels (it is also the stamp value). This is the
|
||||||
// a normalized "0.9.1" here would be a leading-zero-fidelity regression, and this
|
// whole point of sourcing the STRING (not reconstructing from numeric components): a
|
||||||
// assertion fails against the actual CMake-configured value, not a re-derivation.
|
// normalized "0.9.1" here would be a leading-zero-fidelity regression. stampVersion()
|
||||||
|
// is the pure numeric triple regardless of channel.
|
||||||
|
CHECK(stampVersion() == "0.9.01");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- channel-derived rendering (V4) -------------------------------------------
|
||||||
|
|
||||||
|
static void testChannelDerivedRendering() {
|
||||||
|
// The DISPLAY render. Stable: exactly the numeric string. Beta: numeric + "-beta"
|
||||||
|
// (a plain suffix, V2). This is the show-version + panel-readout value. Each branch is
|
||||||
|
// the assertion the OTHER config's build would need to fail — i.e. a stable build that
|
||||||
|
// wrongly rendered "-beta", or a beta build that dropped it, is caught here.
|
||||||
|
if (isBeta()) {
|
||||||
|
CHECK(channel() == Channel::Beta);
|
||||||
|
CHECK(appVersion() == "0.9.01-beta");
|
||||||
|
} else {
|
||||||
|
CHECK(channel() == Channel::Stable);
|
||||||
CHECK(appVersion() == "0.9.01");
|
CHECK(appVersion() == "0.9.01");
|
||||||
}
|
}
|
||||||
|
// The STAMP value is the numeric triple on BOTH channels — never suffixed — so it stays
|
||||||
|
// classifiable (see the stamp-classifiability test) and byte-identical to stable.
|
||||||
|
CHECK(stampVersion() == "0.9.01");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testChannelDerivedIdentityStrings() {
|
||||||
|
// Namespace, command-id prefix, action-name prefix, binary + dock idents all fork from
|
||||||
|
// the one channel bit. Stable values are BYTE-IDENTICAL to the pre-V4 build — any drift
|
||||||
|
// in the stable branch is a shipped-identity defect.
|
||||||
|
if (isBeta()) {
|
||||||
|
CHECK(extStateNamespace() == "reasampler_beta");
|
||||||
|
CHECK(commandIdPrefix() == "CEREBELLUM_REASAMPLER_BETA_");
|
||||||
|
CHECK(actionDisplayPrefix() == "ReaSampler beta: ");
|
||||||
|
CHECK(binaryName() == "reaper_reasampler_beta");
|
||||||
|
CHECK(dockTitle() == "ReaSampler Bank beta");
|
||||||
|
CHECK(dockIdent() == "reasampler_bank_panel_beta");
|
||||||
|
} else {
|
||||||
|
CHECK(extStateNamespace() == "reasampler");
|
||||||
|
CHECK(commandIdPrefix() == "CEREBELLUM_REASAMPLER_");
|
||||||
|
CHECK(actionDisplayPrefix() == "ReaSampler: ");
|
||||||
|
CHECK(binaryName() == "reaper_reasampler");
|
||||||
|
CHECK(dockTitle() == "ReaSampler Bank");
|
||||||
|
CHECK(dockIdent() == "reasampler_bank_panel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testChannelQualifiedIdAndNameComposition() {
|
||||||
|
// The two composition helpers the shells funnel through. A representative shipped id
|
||||||
|
// (CAPTURE_TRACK) and phrase must compose to the exact channel-qualified strings — this
|
||||||
|
// is what guarantees stable rebuilds its shipped id and beta gets the isolated one.
|
||||||
|
if (isBeta()) {
|
||||||
|
CHECK(channelCommandId("CAPTURE_TRACK") == "CEREBELLUM_REASAMPLER_BETA_CAPTURE_TRACK");
|
||||||
|
CHECK(channelActionName("capture selected track(s)") ==
|
||||||
|
"ReaSampler beta: capture selected track(s)");
|
||||||
|
} else {
|
||||||
|
CHECK(channelCommandId("CAPTURE_TRACK") == "CEREBELLUM_REASAMPLER_CAPTURE_TRACK");
|
||||||
|
CHECK(channelActionName("capture selected track(s)") ==
|
||||||
|
"ReaSampler: capture selected track(s)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testStampClassifiesAsStampedOnOwnChannel() {
|
||||||
|
// The V4 stamp-classifiability requirement: the value a channel WRITES (stampVersion())
|
||||||
|
// must classify as Stamped when that same channel reads it back — on BOTH channels. A
|
||||||
|
// "-beta"-suffixed stamp would classify as Unknown, so this fails if a build ever stamped
|
||||||
|
// appVersion() instead of stampVersion().
|
||||||
|
WritingVersion wv = classifyWritingVersion(stampVersion());
|
||||||
|
CHECK(wv.kind == WritingVersion::Kind::Stamped);
|
||||||
|
CHECK(wv.raw == "0.9.01");
|
||||||
|
CHECK(wv.parsed.major == 0 && wv.parsed.minor == 9 && wv.parsed.patch == 1);
|
||||||
|
|
||||||
|
// The OTHER channel's raw stamp value is handled without throwing per the design: since
|
||||||
|
// both channels stamp the identical numeric triple, the other channel's value is the same
|
||||||
|
// "0.9.01" and also classifies Stamped. (The DISPLAY string "0.9.01-beta", by contrast,
|
||||||
|
// is intentionally NOT the stamp value — confirm it classifies Unknown, proving why the
|
||||||
|
// stamp must stay the numeric triple.)
|
||||||
|
CHECK(classifyWritingVersion("0.9.01-beta").kind == WritingVersion::Kind::Unknown);
|
||||||
|
}
|
||||||
|
|
||||||
// --- parseVersion: well-formed, leading zeros, and rejection ------------------
|
// --- parseVersion: well-formed, leading zeros, and rejection ------------------
|
||||||
|
|
||||||
@@ -122,6 +203,10 @@ static void testStampedStampIsOrderableAgainstCurrent() {
|
|||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
testVersionConstantRendersExactString();
|
testVersionConstantRendersExactString();
|
||||||
|
testChannelDerivedRendering();
|
||||||
|
testChannelDerivedIdentityStrings();
|
||||||
|
testChannelQualifiedIdAndNameComposition();
|
||||||
|
testStampClassifiesAsStampedOnOwnChannel();
|
||||||
testParseWellFormed();
|
testParseWellFormed();
|
||||||
testParseRejectsMalformed();
|
testParseRejectsMalformed();
|
||||||
testOrderingByPatch();
|
testOrderingByPatch();
|
||||||
|
|||||||
@@ -252,11 +252,15 @@ static void testTableHasBothScopes() {
|
|||||||
std::set<std::string> ids;
|
std::set<std::string> ids;
|
||||||
int item = 0, track = 0;
|
int item = 0, track = 0;
|
||||||
for (const auto& def : table) {
|
for (const auto& def : table) {
|
||||||
// Every id is a non-empty CEREBELLUM_REASAMPLER_ string and is UNIQUE
|
// Every command SUFFIX (Phase V, V4 — the channel prefix is prepended by the shell)
|
||||||
// (duplicate ids would collide on registration).
|
// is a non-empty, UNIQUE string (duplicate suffixes would collide once composed).
|
||||||
std::string id = def.commandString;
|
std::string suffix = def.commandSuffix;
|
||||||
CHECK(id.rfind("CEREBELLUM_REASAMPLER_", 0) == 0);
|
CHECK(!suffix.empty());
|
||||||
CHECK(ids.insert(id).second); // false if duplicate
|
// The suffix is NOT prefixed with the channel family here — that is composed at
|
||||||
|
// register time. A leftover "CEREBELLUM_REASAMPLER_" in the table would be a
|
||||||
|
// double-prefix bug, so assert its ABSENCE.
|
||||||
|
CHECK(suffix.rfind("CEREBELLUM_REASAMPLER_", 0) != 0);
|
||||||
|
CHECK(ids.insert(suffix).second); // false if duplicate
|
||||||
// Every scope resolves to a supported offline source.
|
// Every scope resolves to a supported offline source.
|
||||||
CHECK(renderSettingsFor(sourceModeForScope(def.scope), 1.0).supported);
|
CHECK(renderSettingsFor(sourceModeForScope(def.scope), 1.0).supported);
|
||||||
|
|
||||||
@@ -269,16 +273,17 @@ static void testTableHasBothScopes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void testScopeActionIdsAreTheShippedStrings() {
|
static void testScopeActionIdsAreTheShippedStrings() {
|
||||||
// Pin the shipped CAPTURE_ITEM / CAPTURE_TRACK ids so a future edit that silently
|
// Pin the shipped CAPTURE_ITEM / CAPTURE_TRACK SUFFIXES so a future edit that silently
|
||||||
// changes them (breaking user keybindings) fails the gate.
|
// changes them (breaking user keybindings once composed with the channel prefix) fails
|
||||||
|
// the gate. The full stable id is prefix + suffix ("CEREBELLUM_REASAMPLER_CAPTURE_ITEM").
|
||||||
const auto& table = captureActionTable();
|
const auto& table = captureActionTable();
|
||||||
std::string itemId, trackId;
|
std::string itemId, trackId;
|
||||||
for (const auto& def : table) {
|
for (const auto& def : table) {
|
||||||
if (def.scope == CaptureScope::Item) itemId = def.commandString;
|
if (def.scope == CaptureScope::Item) itemId = def.commandSuffix;
|
||||||
if (def.scope == CaptureScope::Track) trackId = def.commandString;
|
if (def.scope == CaptureScope::Track) trackId = def.commandSuffix;
|
||||||
}
|
}
|
||||||
CHECK(itemId == "CEREBELLUM_REASAMPLER_CAPTURE_ITEM");
|
CHECK(itemId == "CAPTURE_ITEM");
|
||||||
CHECK(trackId == "CEREBELLUM_REASAMPLER_CAPTURE_TRACK");
|
CHECK(trackId == "CAPTURE_TRACK");
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
Reference in New Issue
Block a user