Q-W6: registration table (OCP) in main.cpp; bank verbs -> shell/bank_ops(Session&); persist.h + wav_trim + namespaces.h shims deleted; 61/61

capture.h realtime seam split to capture_realtime_shell.h; GetProjExtState grow-loop rehomed to core/wire/ext_state_read; stale persist.cpp/bank_panel.cpp comment refs fixed; CLAUDE.md persist/bank_book/actions bullets updated. Command-id suffixes, display phrases, and undo labels byte-identical.
This commit is contained in:
2026-07-29 13:40:09 -04:00
parent 4831e0e172
commit f3be4d8cce
81 changed files with 970 additions and 1085 deletions
+51 -3
View File
@@ -1,9 +1,10 @@
// action_registry.cpp — shared registration plumbing (Q-W4 split of actions.cpp).
// See action_registry.h. Needs no REAPER API pointers: rec->Register is a member
// call on the dispatch struct REAPER hands the entry point.
// action_registry.cpp — shared registration plumbing (Q-W4) + the registration
// table (Q-W6). See action_registry.h. Needs no REAPER API pointers: rec->Register
// is a member call on the dispatch struct REAPER hands the entry point.
#include "shell/actions/action_registry.h"
#include <cstring>
#include <deque>
#include <string>
@@ -23,6 +24,18 @@ using version::channelCommandId;
// pointer for a given action.
std::deque<std::string> g_strStore;
// One registered table row: the row data plus the registry-owned registration
// artifacts (interned id, minted cmd, gaccel storage REAPER holds a pointer to).
// A std::deque so element addresses never move after push_back — REAPER keeps each
// &accel until the mirror-unregister.
struct TableEntry {
ActionTableRow row;
const char* id = nullptr; // interned channel-qualified command id
int cmd = 0; // minted command id (0 = mint failed / inert row)
gaccel_register_t accel{}; // Actions-list entry; address handed to REAPER
};
std::deque<TableEntry> g_table;
} // namespace
const char* channelIdFor(const char* suffix) {
@@ -46,4 +59,39 @@ int registerAction(reaper_plugin_info_t* rec, const char* suffix,
return cmd;
}
void registerActionTable(reaper_plugin_info_t* rec, const ActionTableRow* rows,
std::size_t count) {
for (std::size_t i = 0; i < count; ++i) {
g_table.push_back(TableEntry{rows[i]});
TableEntry& e = g_table.back();
e.id = channelIdFor(e.row.suffix);
e.cmd = registerAction(rec, e.row.suffix, e.accel, e.row.phrase);
}
}
bool actionTableHandleCommand(int command) {
if (command == 0) return false;
for (const TableEntry& e : g_table)
if (e.cmd != 0 && command == e.cmd) {
e.row.run(e.row.arg);
return true;
}
return false;
}
int actionTableCommandId(const char* suffix) {
for (const TableEntry& e : g_table)
if (std::strcmp(e.row.suffix, suffix) == 0) return e.cmd;
return 0;
}
void unregisterActionTable(reaper_plugin_info_t* rec) {
// Reverse table order, mirroring the register loop. Each '-command_id'
// re-presents the SAME interned pointer channelIdFor handed out at register.
for (auto it = g_table.rbegin(); it != g_table.rend(); ++it) {
rec->Register("-gaccel", (void*)&it->accel);
rec->Register("-command_id", (void*)it->id);
}
}
} // namespace reasampler
+61 -7
View File
@@ -1,13 +1,30 @@
#pragma once
// action_registry — shared registration plumbing for the bindable action families
// (Q-W4 split of actions.cpp). Owns the durable interned-string store both the
// Design View and multi-bank families register through, so a composed command id
// keeps ONE stable pointer from register to the mirror-unregister, and the
// register-a-command_id-then-gaccel sequence has one implementation.
// action_registry — shared registration plumbing + the Q-W6 registration TABLE.
//
// Two layers, one TU:
//
// * The Q-W4 plumbing (channelIdFor / registerAction): the durable interned-string
// store the action families register through, so a composed command id keeps ONE
// stable pointer from register to the mirror-unregister, and the
// register-a-command_id-then-gaccel sequence has one implementation. The
// design_view / bank / ingest families still register row-by-row through this.
//
// * The Q-W6 registration TABLE (ActionTableRow + registerActionTable /
// actionTableHandleCommand / actionTableCommandId / unregisterActionTable): the
// data-driven home of main.cpp's own action family (capture scopes, panel toggle,
// insert, batch, realtime, recapture, version). One row = one action (FOREVER-
// STABLE id suffix, display phrase, flat function-pointer handler); registration
// iterates the rows, hookcommand dispatch walks the same rows, and unload
// mirror-unregisters from them — adding an action touches the table only (OCP).
// Handlers are plain function pointers (a static dispatch walk, no std::function,
// no virtual — the §3 performance guardrail); gaccel + interned-id storage is
// owned here for the module lifetime, so REAPER's held pointers stay valid and
// the '-command_id' unregister re-presents the IDENTICAL pointer registered.
//
// Includes reaper_plugin.h (gaccel_register_t / reaper_plugin_info_t full defs);
// only the action-family TUs include this header. Q-W6's registration table
// subsumes this helper when the hand-written blocks become data.
// only the action-family TUs and main.cpp include this header.
#include <cstddef>
#include "reaper_plugin.h" // reaper_plugin_info_t, gaccel_register_t
@@ -26,4 +43,41 @@ const char* channelIdFor(const char* suffix);
int registerAction(reaper_plugin_info_t* rec, const char* suffix,
gaccel_register_t& accel, const char* phrase);
// --- The registration table (Q-W6) -------------------------------------------
// One bindable action. `suffix` and `phrase` are the channel-AGNOSTIC pieces (the
// registry composes the full id/label via channelCommandId / channelActionName);
// both must have static storage duration (string literals, or a pure static table
// like captureActionTable()). `run` fires when the minted command does; `arg` is an
// opaque per-row value passed through to it (e.g. a captureActionTable row index, or
// a bool-like flag), so sibling actions can share one handler without captures.
struct ActionTableRow {
const char* suffix; // FOREVER-STABLE command-id suffix — never change shipped
const char* phrase; // Actions-list display phrase (after the channel prefix)
void (*run)(int arg); // handler — a flat function pointer, no state
int arg = 0; // opaque per-row handler argument
};
// Registers every row (command_id -> gaccel, via the same interning plumbing as
// registerAction) in table order. Rows are COPIED into registry-owned storage whose
// element addresses never move (REAPER holds each gaccel pointer until unload).
// Call once at load; a failed command_id mint (cmd 0) leaves that row inert but
// still mirror-unregistered on unload (harmless, matches the pre-table behavior).
void registerActionTable(reaper_plugin_info_t* rec, const ActionTableRow* rows,
std::size_t count);
// Dispatches one fired command: fires the matching row's handler and returns true;
// false when the command belongs to no table row (caller's hookcommand keeps
// looking, per the claim-only contract). A flat walk over the registered rows.
bool actionTableHandleCommand(int command);
// The minted command id for `suffix` (0 when unregistered / mint failed). For the
// callers that need a raw command id outside dispatch — e.g. the toggleaction
// checked-state hook resolving TOGGLE_BANK_PANEL once at load.
int actionTableCommandId(const char* suffix);
// Mirror-unregisters every table row (reverse table order): '-gaccel' with the same
// held storage, '-command_id' with the SAME interned pointer used at register.
void unregisterActionTable(reaper_plugin_info_t* rec);
} // namespace reasampler
+20 -18
View File
@@ -1,11 +1,12 @@
// bank_actions.cpp — the multi-bank bindable action family (Phase B3; Q-W4 split of
// actions.cpp). See bank_actions.h.
//
// Q-W4 dedupe: each mutating handler is a THIN UX SKIN — text prompts (promptBankName),
// name resolution, and console feedback — over the promptless bankOp* inner verbs
// homed in panel_bank_ops (model op + persistBankOp, one bank op = one Ctrl-Z). The
// book's rules (pool privileges, collapse-by-hash, active-fallback-to-pool) all live
// in bank_book; these handlers only drive the verbs and react to the boolean.
// Q-W4 dedupe / Q-W6 seam: each mutating handler is a THIN UX SKIN — text prompts
// (promptBankName), name resolution, and console feedback — over the promptless
// bankOp* inner verbs homed in shell/bank_ops (model op + persistBankOp, one bank op
// = one Ctrl-Z), driven against this family's registered session. The book's rules
// (pool privileges, collapse-by-hash, active-fallback-to-pool) all live in
// bank_book; these handlers only drive the verbs and react to the boolean.
//
// REFERENCE-INVALIDATION GUARDRAIL (B2 review): book().activeIndex() / bank()->index
// return a reference INTO the book's internal vector, which a create/delete can
@@ -27,9 +28,10 @@
#include "shell/actions/prune_action.h" // doBankPruneFolder — the guarded prune body
#include "core/model/bank_book.h" // BankBook, nextBankId, kPoolBankId (B1)
#include "persist.h" // ReaSamplerSession (owns book())
#include "shell/panel/panel_bank_ops.h" // bankOp* inner verbs + promptBankName + selection seam
#include "shell/bank_ops/bank_ops.h" // bankOp* promptless verbs (Q-W6 non-UI seam)
#include "shell/panel/panel_bank_ops.h" // promptBankName + the panel selection seam
#include "shell/panel/panel_layout.h" // full-height toggles (B3)
#include "shell/persist/session.h" // ReaSamplerSession (owns book())
#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_ShowConsoleMsg
@@ -61,9 +63,9 @@ constexpr const char* kIdBankBanksFull = "BANK_BANKS_FULLHEIGHT";
// and R3 extends the confirm-and-delete step behind this SAME id — never a throwaway id.
constexpr const char* kIdBankPruneFolder = "BANK_PRUNE_FOLDER";
// The live session the actions read (name resolution, member counts, prune). The
// mutations themselves run through the bankOp* verbs, which resolve the same session
// via the panel seam. Set once by bankRegisterActions; not owned here.
// The live session the actions read (name resolution, member counts, prune) and
// pass to the bankOp* verbs by reference (bankHandleCommand guards it non-null
// before any handler runs). Set once by bankRegisterActions; not owned here.
ReaSamplerSession* g_session = nullptr;
int g_cmdBankCreate = 0;
@@ -116,7 +118,7 @@ std::string bankIdByDisplayName(const std::string& name) {
void doBankCreate() {
std::string name;
if (!promptBankName("ReaSampler: create bank", "Bank name:", "", name)) return;
if (bankOpCreate(name).empty()) {
if (bankOpCreate(*g_session, name).empty()) {
ShowConsoleMsg(
("ReaSampler: could not create bank \"" + name +
"\" (a bank with that name already exists).\n")
@@ -139,7 +141,7 @@ void doBankRename() {
}
std::string newName;
if (!promptBankName("ReaSampler: rename bank", "New name:", which, newName)) return;
if (!bankOpRename(id, newName)) {
if (!bankOpRename(*g_session, id, newName)) {
// The verb rejects the pool (un-renamable) or a name already used by another
// bank (unique display names, trimmed + case-insensitive).
ShowConsoleMsg("ReaSampler: cannot rename that bank (the pool is un-renamable, "
@@ -184,7 +186,7 @@ void doBankDelete() {
}
// S9: bump only when the deleted bank held samples — dropping them changes what a live
// instance referencing one could play. Deleting an EMPTY bank is purely organizational.
if (!bankOpDelete(id, /*bumpGeneration=*/members > 0)) {
if (!bankOpDelete(*g_session, id, /*bumpGeneration=*/members > 0)) {
ShowConsoleMsg("ReaSampler: cannot delete that bank (the pool is un-deletable).\n");
}
}
@@ -202,7 +204,7 @@ void doBankEvacuate() {
ShowConsoleMsg(("ReaSampler: no bank named \"" + which + "\".\n").c_str());
return;
}
if (!bankOpEvacuate(id)) {
if (!bankOpEvacuate(*g_session, id)) {
ShowConsoleMsg("ReaSampler: cannot evacuate that bank (the pool is the "
"destination, not a source).\n");
}
@@ -218,13 +220,13 @@ void doBankActivateNext() {
for (const Bank& b : g_session->book().banks()) ids.push_back(b.id);
const std::string target = nextBankId(ids, g_session->book().activeBankId());
if (target.empty()) return; // degenerate (no banks) — cannot happen (pool seeded)
bankOpActivate(target);
bankOpActivate(*g_session, target);
}
// Activate the pool directly (the common "back to the default target" jump). Bindable
// direct-by-id form; a general activate-bank-by-name/menu is a panel affordance.
void doBankActivatePool() {
bankOpActivate(kPoolBankId);
bankOpActivate(*g_session, kPoolBankId);
}
// Move or copy the panel's selected samples into a named destination bank (prompted
@@ -257,7 +259,7 @@ void doBankTransferSelected(bool copy) {
ShowConsoleMsg("ReaSampler: source and destination are the same bank.\n");
return;
}
bankOpTransfer(selected, srcId, destId, copy);
bankOpTransfer(*g_session, selected, srcId, destId, copy);
}
// Remove the panel's selected samples from the SOURCE bank (the focused region's
@@ -275,7 +277,7 @@ void doBankRemoveSelected() {
ShowConsoleMsg("ReaSampler: the selection's bank no longer exists.\n");
return;
}
bankOpRemove(selected, srcId);
bankOpRemove(*g_session, selected, srcId);
}
} // namespace
+1 -1
View File
@@ -29,7 +29,7 @@
#include "core/view/lane_keys.h" // view::isOnManualLane — the single managed/manual predicate
#include "core/view/view_mode_model.h"
#include "persist.h" // ReaSamplerSession (owns view() model)
#include "shell/persist/session.h" // ReaSamplerSession (owns view() model)
#include "shell/capture/item_read.h" // shared MediaItem* -> GUID + fixed-lane-name reads (D2 W3-B)
#include "shell/capture/track_guid.h" // shared MediaTrack* -> canonical GUID key
#include "shell/panel/panel_window.h" // bankPanelInvalidate — footer toggle repaint
-1
View File
@@ -1,4 +1,3 @@
#include "core/namespaces.h"
// drag_out_win — OS/COM initiation of native OS drag-out (M11). See drag_out_win.h.
//
// Windows path (primary): a hand-rolled minimal IDataObject exposing exactly one format,
-1
View File
@@ -1,5 +1,4 @@
#pragma once
#include "core/namespaces.h"
// drag_out_win — the OS/COM initiation half of native OS drag-out (Milestone 11). The pure
// gesture-boundary decision and path-list assembly live in drag_out.*; THIS is the platform
// shell that hands a resolved, existing-file path list to the operating system's drag-drop
+5 -2
View File
@@ -1,4 +1,3 @@
#include "core/namespaces.h"
// instrument_drop_win — the REAPER shell for S17 drop-and-load. See instrument_drop_win.h.
//
// Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h WITHOUT
@@ -30,6 +29,10 @@
namespace reasampler {
// Real-namespace-home using-declarations (Q-W6: the namespaces.h shim is retired).
using version::vstPluginName;
using wire::infoNamesFxHotspot;
namespace {
// Write `bytes` to a fresh uniquely-named .vstpreset in the OS temp dir and return its path;
@@ -43,7 +46,7 @@ namespace {
//
// Non-throwing: every std::filesystem call uses the error_code overload. The whole body is
// wrapped in try/catch to guarantee no exception crosses the REAPER C callback boundary
// (the same discipline persist.cpp uses — see its non-throwing scanPruneOrphans comment).
// (the same discipline the prune shell uses — see prune_fs.cpp's non-throwing scan comment).
//
// Returns the path object (not a narrow string) so the caller can:
// (a) pass path.u8string() to TrackFX_SetPreset — UTF-8 on MSVC, not ACP-converted,
-1
View File
@@ -1,5 +1,4 @@
#pragma once
#include "core/namespaces.h"
// instrument_drop_win — the REAPER-facing shell half of S17 drop-and-load. The pure gesture
// decision lives in drag_out (DragGesture::InstrumentDrop) and the pure payload construction
// in instrument_drop; THIS is the platform shell that (a) resolves a screen point to a track
+3 -3
View File
@@ -9,7 +9,7 @@
#include <string>
#include <vector>
#include "persist.h" // ReaSamplerSession — pruneDryRun / pruneOrphanSet / pruneReclaim
#include "shell/persist/session.h" // ReaSamplerSession — pruneDryRun / pruneOrphanSet / pruneReclaim
#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_ShowConsoleMsg
@@ -31,7 +31,7 @@ namespace reasampler {
// is opened (file deletion is not REAPER-undoable and pruneReclaim mutates no project
// state) — a Ctrl-Z after a prune correctly cannot claim to restore deleted files.
void doBankPruneFolder(ReaSamplerSession& session) {
const PruneReport report = session.pruneDryRun();
const reclaim::PruneReport report = session.pruneDryRun();
// pS-usage FAIL-SAFE: a present instance-usage record could not be read — the
// protected set is unknowable, so the prune HALTS outright (deletes nothing) rather
@@ -85,7 +85,7 @@ void doBankPruneFolder(ReaSamplerSession& session) {
}
// Confirmed -> delete exactly the confirmed set (recomputed fresh, stale entries skipped).
const PruneDeletionResult del = session.pruneReclaim(orphanSet);
const reclaim::PruneDeletionResult del = session.pruneReclaim(orphanSet);
std::string done = "ReaSampler prune: reclaimed " +
std::to_string(del.reclaimedCount) + " file(s), " +