feat: add M6 insert — place selected bank sample at edit cursor
InsertMedia-driven placement reading the bank panel selection, undo-wrapped. Native length by default; conform-to-tempo is an explicit second action, never a silent stretch. Pure mode-bit logic (insert_plan) unit-tested.
This commit is contained in:
+86
-2
@@ -24,6 +24,7 @@
|
||||
#include "bank_model.h"
|
||||
#include "bank_panel.h"
|
||||
#include "capture.h"
|
||||
#include "insert.h"
|
||||
#include "persist.h"
|
||||
#include "view.h"
|
||||
|
||||
@@ -52,6 +53,14 @@ static int g_cmdCaptureMasterSpike = 0;
|
||||
// shows/hides it; it never captures, inserts, or mutates the bank.
|
||||
static int g_cmdToggleBankPanel = 0;
|
||||
|
||||
// Command ids for the M6 insert actions. FOREVER-STABLE strings. Two variants that
|
||||
// differ ONLY in the InsertOptions they build: the default inserts at native length
|
||||
// (no stretch, no conform); the "conform" variant is the EXPLICIT opt-in to REAPER's
|
||||
// try-to-match-project-tempo path (CONTEXT.md §insert: conform is opt-in, never
|
||||
// silent). Both read the bank panel's current selection and place at the edit cursor.
|
||||
static int g_cmdInsertSelected = 0;
|
||||
static int g_cmdInsertSelectedConform = 0;
|
||||
|
||||
// The persistence session (M4): owns the in-memory BankIndex and bridges it to
|
||||
// project ext state. A timer tick drives g_session.poll() to detect project
|
||||
// load / Save-As; capture adds Samples to g_session.bank(); after a capture we
|
||||
@@ -125,13 +134,55 @@ static void RunCaptureMasterSpike()
|
||||
ShowConsoleMsg(log.c_str());
|
||||
}
|
||||
|
||||
// Runs the M6 insert: place the bank panel's selected sample(s) at the edit cursor
|
||||
// via InsertMedia, undo-wrapped. `conform` selects the explicit opt-in tempo-match
|
||||
// variant (never silent — it fires only from the distinct "conform" action). This
|
||||
// is the INTENDED placement path: it adds items to the arrange on purpose
|
||||
// (CONTEXT.md §load-bearing principle) and runs only from a user-invoked action.
|
||||
static void RunInsertSelected(bool conform)
|
||||
{
|
||||
reasampler::InsertRequest req;
|
||||
req.options.target = reasampler::InsertTarget::NewTrack; // sensible default: own track
|
||||
req.options.conform =
|
||||
conform ? reasampler::TempoConform::Ratio1x : reasampler::TempoConform::None;
|
||||
// preservePitch stays true: a tempo conform matches tempo without varispeeding
|
||||
// pitch. (A pitch-shifting variant is a later opt-in if wanted — YAGNI now.)
|
||||
|
||||
reasampler::InsertResult res = reasampler::runInsert(&g_session, req);
|
||||
|
||||
std::string msg;
|
||||
switch (res.status)
|
||||
{
|
||||
case reasampler::InsertStatus::Ok:
|
||||
msg = "ReaSampler: inserted " + std::to_string(res.inserted) +
|
||||
(res.inserted == 1 ? " sample" : " samples") +
|
||||
(conform ? " (conformed to tempo)" : " (native length)");
|
||||
if (res.skipped > 0)
|
||||
msg += ", skipped " + std::to_string(res.skipped) + " unresolvable";
|
||||
msg += "\n";
|
||||
break;
|
||||
case reasampler::InsertStatus::NoSelection:
|
||||
msg = "ReaSampler insert: nothing selected in the bank panel.\n";
|
||||
break;
|
||||
case reasampler::InsertStatus::NoProject:
|
||||
msg = "ReaSampler insert: no saved project, so the bank has no location.\n";
|
||||
break;
|
||||
case reasampler::InsertStatus::NothingResolved:
|
||||
msg = "ReaSampler insert: selected sample(s) could not be resolved to a file.\n";
|
||||
break;
|
||||
}
|
||||
ShowConsoleMsg(msg.c_str());
|
||||
}
|
||||
|
||||
// REAPER calls this for EVERY action fired anywhere; claim only our own id,
|
||||
// return false otherwise so REAPER keeps looking.
|
||||
static bool OnHookCommand(int command, int /*flag*/)
|
||||
{
|
||||
if (command == 0) return false;
|
||||
if (command == g_cmdCaptureMasterSpike) { RunCaptureMasterSpike(); return true; }
|
||||
if (command == g_cmdToggleBankPanel) { reasampler::bankPanelToggle(); return true; }
|
||||
if (command == g_cmdCaptureMasterSpike) { RunCaptureMasterSpike(); return true; }
|
||||
if (command == g_cmdToggleBankPanel) { reasampler::bankPanelToggle(); return true; }
|
||||
if (command == g_cmdInsertSelected) { RunInsertSelected(false); return true; }
|
||||
if (command == g_cmdInsertSelectedConform) { RunInsertSelected(true); return true; }
|
||||
// Design View action family (D4). Claims only its own ids; returns false for the
|
||||
// rest so this hook keeps looking (per the contract).
|
||||
if (reasampler::designViewHandleCommand(command)) return true;
|
||||
@@ -150,6 +201,8 @@ static int OnToggleAction(int command)
|
||||
// gaccel storage must outlive registration — REAPER holds the pointer.
|
||||
static gaccel_register_t g_accelCaptureMaster{};
|
||||
static gaccel_register_t g_accelToggleBankPanel{};
|
||||
static gaccel_register_t g_accelInsertSelected{};
|
||||
static gaccel_register_t g_accelInsertSelectedConform{};
|
||||
|
||||
extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t* rec)
|
||||
@@ -166,6 +219,12 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
// Tear down the Design View action family (D4) — mirror-unregisters each
|
||||
// gaccel + command_id with '-'-prefixed strings. After the hook is gone.
|
||||
reasampler::designViewUnregisterActions(g_rec);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelInsertSelectedConform);
|
||||
g_rec->Register("-command_id",
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "INSERT_SELECTED_CONFORM"));
|
||||
g_rec->Register("-gaccel", (void*)&g_accelInsertSelected);
|
||||
g_rec->Register("-command_id",
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "INSERT_SELECTED"));
|
||||
g_rec->Register("-gaccel", (void*)&g_accelToggleBankPanel);
|
||||
g_rec->Register("-command_id",
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "TOGGLE_BANK_PANEL"));
|
||||
@@ -221,6 +280,31 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
rec->Register("toggleaction", (void*)&OnToggleAction);
|
||||
}
|
||||
|
||||
// Register the M6 insert actions (command_id -> gaccel -> hookcommand). Two
|
||||
// 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.
|
||||
g_cmdInsertSelected = rec->Register(
|
||||
"command_id",
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "INSERT_SELECTED"));
|
||||
if (g_cmdInsertSelected)
|
||||
{
|
||||
g_accelInsertSelected.accel.cmd = g_cmdInsertSelected;
|
||||
g_accelInsertSelected.desc =
|
||||
"ReaSampler: insert selected sample at edit cursor";
|
||||
rec->Register("gaccel", (void*)&g_accelInsertSelected);
|
||||
}
|
||||
|
||||
g_cmdInsertSelectedConform = rec->Register(
|
||||
"command_id",
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "INSERT_SELECTED_CONFORM"));
|
||||
if (g_cmdInsertSelectedConform)
|
||||
{
|
||||
g_accelInsertSelectedConform.accel.cmd = g_cmdInsertSelectedConform;
|
||||
g_accelInsertSelectedConform.desc =
|
||||
"ReaSampler: insert selected sample at edit cursor (conform to tempo)";
|
||||
rec->Register("gaccel", (void*)&g_accelInsertSelectedConform);
|
||||
}
|
||||
|
||||
// Register the Design View action family (D4): toggle/activate mode, tag/untag/
|
||||
// show-both selected tracks. Each mints its own command_id + gaccel; the single
|
||||
// hookcommand below routes them via designViewHandleCommand. Registered before
|
||||
|
||||
Reference in New Issue
Block a user