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:
@@ -0,0 +1,183 @@
|
||||
// bank_ops.cpp — the promptless bank-verb seam (Q-W6 lift; see bank_ops.h for the
|
||||
// contract). The ONE implementation home of the bank verbs (create / rename /
|
||||
// delete / evacuate / activate / move / copy / remove): each mutates the given
|
||||
// session's book() then persists via persistBankOp() (one bank op = one Ctrl-Z; a
|
||||
// true index no-op opens NO undo point). It DOES mutate the bank BOOK — but only
|
||||
// the index/model + ext-state, never the arrange, never a sample file on disk
|
||||
// (bank ops are index-only; files stay put — CONTEXT.md §Multi-bank).
|
||||
// REFERENCE-INVALIDATION GUARDRAIL: after a STRUCTURAL mutation any
|
||||
// Bank*/BankModel& is invalid — verbs take ids and resolve fresh per model call.
|
||||
//
|
||||
// Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h
|
||||
// WITHOUT REAPERAPI_IMPLEMENT — main.cpp owns the API pointers; here they are
|
||||
// extern (CLAUDE.md §contract). DAW-verified, not unit tested.
|
||||
|
||||
#include "shell/bank_ops/bank_ops.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/model/bank_book.h" // BankBook / TransferResult / RemoveScope
|
||||
#include "shell/persist/session.h" // ReaSamplerSession — the session the verbs mutate
|
||||
|
||||
#define REAPERAPI_MINIMAL
|
||||
#define REAPERAPI_WANT_genGuid
|
||||
#define REAPERAPI_WANT_guidToString
|
||||
#define REAPERAPI_WANT_Undo_BeginBlock2
|
||||
#define REAPERAPI_WANT_Undo_EndBlock2
|
||||
#include "reaper_plugin_functions.h"
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
namespace {
|
||||
|
||||
// Mints a fresh, genuine REAPER GUID string as a stable bank id (the B2/model
|
||||
// design: ids are caller-supplied and stable; the model stays pure and mints none).
|
||||
// Distinct from a track GUID by origin only — both are canonical guidToString output.
|
||||
std::string mintBankId() {
|
||||
GUID g{};
|
||||
genGuid(&g);
|
||||
char buf[64] = {0}; // guidToString needs >=64 chars (SDK contract)
|
||||
guidToString(&g, buf);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Persists a completed bank-index verb as a SINGLE batched REAPER undo point (R-B) —
|
||||
// one bank op = one Ctrl-Z.
|
||||
//
|
||||
// WHY THIS WRAPS AND saveToActiveProject() DOES NOT: a bank verb mutates ONLY our
|
||||
// project ext-state (SetProjExtState under "reasampler"), which REAPER's undo system
|
||||
// captures iff UNDO_STATE_MISCCFG is set in the Undo_EndBlock2 flags — the SDK
|
||||
// documents MISCCFG as covering "extensions!" project ext-state (reaper_plugin.h
|
||||
// ~1544, ~1199). We pass exactly UNDO_STATE_MISCCFG (not -1 / UNDO_STATE_ALL as the
|
||||
// item-move family does): a bank verb touches no tracks, FX, items, or envelopes, so
|
||||
// snapshotting them would be both heavier and semantically wrong. The persist runs
|
||||
// INSIDE the block so the post-mutation ext-state is the block's "after" image.
|
||||
//
|
||||
// UNSAVED-PROJECT GUARDRAIL: on an unsaved / no-active project saveToActiveProject()
|
||||
// no-ops (nothing is written to ext state). We must still CLOSE the block we opened,
|
||||
// but with an EMPTY label and a zero flag so REAPER DISCARDS the point instead of
|
||||
// recording a no-effect undo entry — mirroring view.cpp's empty-plan close. The
|
||||
// in-session model change stands and persists on the user's next save; it just earns
|
||||
// no undo point until there is a project to persist into (undo of an unsaved bank op
|
||||
// has nothing to roll back to anyway). The Begin/End must still be balanced, hence
|
||||
// the close-either-way. (Quiet persist by design — mirrors the CAPTURE path, NOT the
|
||||
// Design-View path; deliberately NO Save-As prompt.)
|
||||
void persistBankOp(ReaSamplerSession& session, const char* label,
|
||||
bool bumpGeneration) {
|
||||
Undo_BeginBlock2(nullptr);
|
||||
// S9: bump the bank-generation counter INSIDE the block, before the persist, so the
|
||||
// fresh generation rides the same ext-state write (saveToActiveProject() stamps
|
||||
// bankGeneration()). Bumped only for content-changing verbs (the caller decides); a
|
||||
// pure-organizational verb passes false and leaves the counter be, so a
|
||||
// rename/activate does not needlessly refresh live instances.
|
||||
if (bumpGeneration) session.bumpBankGeneration();
|
||||
const bool persisted = session.saveToActiveProject();
|
||||
if (persisted)
|
||||
Undo_EndBlock2(nullptr, label, UNDO_STATE_MISCCFG);
|
||||
else
|
||||
Undo_EndBlock2(nullptr, "", 0); // no ext-state write -> discard the empty point
|
||||
}
|
||||
|
||||
// --- Promptless inner bank verbs (one home) ------------------------------------
|
||||
// Model op + persistBankOp only; NO UX. Callers own prompts/confirms/nudges and the
|
||||
// session-liveness question. Each verb persists ONLY after the model accepted — a
|
||||
// rejected op opens no undo point.
|
||||
|
||||
std::string bankOpCreate(ReaSamplerSession& session, const std::string& name) {
|
||||
const std::string id = mintBankId();
|
||||
if (!session.book().createBank(id, name)) return {}; // duplicate display name (model rule)
|
||||
persistBankOp(session, "ReaSampler: create bank");
|
||||
return id;
|
||||
}
|
||||
|
||||
bool bankOpRename(ReaSamplerSession& session, const std::string& bankId,
|
||||
const std::string& newName) {
|
||||
if (!session.book().renameBank(bankId, newName)) return false; // pool / name in use
|
||||
persistBankOp(session, "ReaSampler: rename bank");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bankOpDelete(ReaSamplerSession& session, const std::string& bankId,
|
||||
bool bumpGeneration) {
|
||||
if (!session.book().deleteBank(bankId)) return false; // pool un-deletable (model rule)
|
||||
persistBankOp(session, "ReaSampler: delete bank", bumpGeneration);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bankOpEvacuate(ReaSamplerSession& session, const std::string& bankId) {
|
||||
if (!session.book().evacuate(bankId)) return false; // pool is a destination, not a source
|
||||
// S9: evacuate moves members between banks (bank membership changes) -> bump.
|
||||
persistBankOp(session, "ReaSampler: evacuate bank", /*bumpGeneration=*/true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bankOpActivate(ReaSamplerSession& session, const std::string& bankId) {
|
||||
if (!session.book().setActiveBank(bankId)) return false; // rejects an unknown id
|
||||
persistBankOp(session, "ReaSampler: activate bank");
|
||||
return true;
|
||||
}
|
||||
|
||||
// NO-OP GUARDRAIL — VERB-AWARE (a collapse means different things per verb):
|
||||
// * MOVE collapse: the source entry WAS removed (bank_book moveSample removes
|
||||
// unconditionally before the dest add collapses on hash), so the index DID
|
||||
// mutate — it counts toward opening an undo point.
|
||||
// * COPY collapse: the source is left intact AND the dest already held the hash,
|
||||
// so NOTHING changed — a true index no-op. It must NOT open an undo point.
|
||||
// Hence: copy counts only real gains; move counts gains OR collapses. Ids pass
|
||||
// straight to the model op — no BankModel& cached across the loop's mutations.
|
||||
bool bankOpTransfer(ReaSamplerSession& session,
|
||||
const std::vector<std::string>& sampleIds,
|
||||
const std::string& srcBankId, const std::string& destBankId,
|
||||
bool copy) {
|
||||
BankBook& b = session.book();
|
||||
if (sampleIds.empty() || srcBankId == destBankId) return false;
|
||||
if (!b.bank(srcBankId) || !b.bank(destBankId)) return false;
|
||||
int ok = 0, collapsed = 0;
|
||||
for (const std::string& sid : sampleIds) {
|
||||
const TransferResult r =
|
||||
copy ? b.copySample(sid, srcBankId, destBankId)
|
||||
: b.moveSample(sid, srcBankId, destBankId);
|
||||
switch (r) {
|
||||
case TransferResult::Moved:
|
||||
case TransferResult::Copied: ++ok; break;
|
||||
case TransferResult::Collapsed: ++collapsed; break;
|
||||
case TransferResult::RejectedUnknownBank:
|
||||
case TransferResult::RejectedSampleAbsent:
|
||||
case TransferResult::RejectedSameBank: break;
|
||||
}
|
||||
}
|
||||
const bool mutated = copy ? (ok > 0) : (ok > 0 || collapsed > 0);
|
||||
if (!mutated) return false; // nothing changed — no persist, no undo point
|
||||
// S9: a move/copy changes bank membership (a sample arrives in / leaves a bank an
|
||||
// instance may reference) -> bump so assigned instances refresh hands-free.
|
||||
persistBankOp(session,
|
||||
copy ? "ReaSampler: copy sample(s)" : "ReaSampler: move sample(s)",
|
||||
/*bumpGeneration=*/true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Index-only, this-bank scope (fork R-A: the sole surfaced verb; RemoveScope::AllBanks
|
||||
// stays latent in the model). Non-destructive to the file: a last-reference remove
|
||||
// leaves the file on disk, orphaned until Phase R prune — remove NEVER deletes bytes
|
||||
// (the manifest is untouched). Silent: recoverability is the batched undo (R-B).
|
||||
bool bankOpRemove(ReaSamplerSession& session,
|
||||
const std::vector<std::string>& sampleIds,
|
||||
const std::string& srcBankId) {
|
||||
BankBook& b = session.book();
|
||||
if (sampleIds.empty() || !b.bank(srcBankId)) return false;
|
||||
int removed = 0;
|
||||
for (const std::string& sid : sampleIds)
|
||||
if (b.removeSample(sid, srcBankId, RemoveScope::ThisBank) ==
|
||||
RemoveResult::Removed)
|
||||
++removed;
|
||||
if (removed == 0) return false; // every id already absent — no undo point
|
||||
// S9: a remove drops a sample from a bank (an instance referencing it must refresh —
|
||||
// it will resolve to silence, per the stale-id policy) -> bump.
|
||||
persistBankOp(session, "ReaSampler: remove sample(s)", /*bumpGeneration=*/true);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
Reference in New Issue
Block a user