feat(S8): ingest through the bank — capture/import/drop into bank + assign to active instance
This commit is contained in:
+101
-5
@@ -32,6 +32,7 @@
|
||||
#include "bank_panel.h"
|
||||
#include "batch_capture.h"
|
||||
#include "capture.h"
|
||||
#include "ingest.h"
|
||||
#include "insert.h"
|
||||
#include "persist.h"
|
||||
#include "provenance.h"
|
||||
@@ -151,6 +152,16 @@ static int g_cmdCaptureTrackRealtime = 0;
|
||||
// explicit action, allowed by the console policy).
|
||||
static int g_cmdRecaptureFromSource = 0;
|
||||
|
||||
// Command id for the S8 "capture selected item / time-selection into bank + assign"
|
||||
// action. NEW FOREVER-STABLE string (suffix CAPTURE_ITEM_ASSIGN). Reuses the offline
|
||||
// Item-scope capture path (RunCapture) verbatim — same razor-else-time range, same
|
||||
// FX-scope neutralize, same bank/persist landing — then writes an S8 assignment request
|
||||
// so the active sampler instance plays the just-captured sample on its next reload. NEVER
|
||||
// inserts a timeline item (the capture/placement separation holds; assign is a bank-index
|
||||
// + instance-selection act). Lives in the capture family (not the ingest family) because
|
||||
// it leans on main.cpp's capture render machinery, which is not exposed cross-module.
|
||||
static int g_cmdCaptureItemAssign = 0;
|
||||
|
||||
// Command id for the M8 "cancel realtime capture" action. FOREVER-STABLE string.
|
||||
// Aborts the in-flight realtime capture (stop + restore, non-destructive) so a user
|
||||
// who started a long capture can bail without waiting for the range end or hunting for
|
||||
@@ -738,6 +749,11 @@ static reasampler::CaptureResult renderOffline(
|
||||
// a bank index entry ONLY; never touches the arrange/timeline. Non-destructive: the
|
||||
// out-of-scope FX/fader/pan chain is fully restored on every path (FxBypassGuard),
|
||||
// and the backend restores every RENDER_* setting.
|
||||
//
|
||||
// On success, res.sample.id carries the LANDED bank-index id (S8): the newly-added id
|
||||
// on a fresh add, or the EXISTING entry's id on a hash-dedup collapse — so the S8
|
||||
// capture+assign path can target the sample actually in the bank. Batch callers ignore
|
||||
// it; the plain capture actions are unaffected.
|
||||
static reasampler::CaptureResult captureAndIndexOne(
|
||||
reasampler::CaptureScope scope,
|
||||
const ResolvedSource& src,
|
||||
@@ -780,13 +796,26 @@ static reasampler::CaptureResult captureAndIndexOne(
|
||||
// resample-from-sample; otherwise the optional stays empty, per M1's contract).
|
||||
res.sample.provenance = prov;
|
||||
|
||||
// Add to the ACTIVE bank: g_session.bank() resolves to book.activeIndex() (B2).
|
||||
g_session.bank().add(res.sample);
|
||||
// Add to the ACTIVE bank: g_session.bank() resolves to book.activeIndex() (B2). The
|
||||
// AddResult tells a fresh add from a hash-dedup collapse, so the assign path (S8) can
|
||||
// target the sample actually in the bank (the existing entry on a collapse).
|
||||
const reasampler::AddResult addResult = g_session.bank().add(res.sample);
|
||||
// B-cap: record the created file in the owned-file manifest, at the same point the
|
||||
// Sample is added. Recorded regardless of the index AddResult — even a hash-collapse
|
||||
// still WROTE a file the tool owns, and the manifest dedups a repeat path itself
|
||||
// (Phase R prune reconciles manifest vs index later).
|
||||
g_session.owned().add(res.sample.relativePath);
|
||||
|
||||
// Resolve the LANDED bank-index id into res.sample.id for the S8 assign path: the new
|
||||
// id on a fresh Added (already in res.sample.id); the EXISTING entry's id on a
|
||||
// Collapsed (the file we just rendered deduped onto an already-present sample — assign
|
||||
// THAT one). Batch/plain-capture callers ignore this field; behaviour unchanged.
|
||||
if (addResult == reasampler::AddResult::Collapsed && !res.sample.contentHash.empty())
|
||||
{
|
||||
if (const reasampler::Sample* existing =
|
||||
g_session.bank().findByHash(res.sample.contentHash))
|
||||
res.sample.id = existing->id;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -794,14 +823,19 @@ static reasampler::CaptureResult captureAndIndexOne(
|
||||
// record via captureAndIndexOne, then persist + mark dirty. The load-bearing principle
|
||||
// holds structurally — this path writes a file + a bank index entry ONLY; it never
|
||||
// calls InsertMedia or touches the arrange/timeline.
|
||||
static void RunCapture(const reasampler::CaptureActionDef& def)
|
||||
// Returns the bank-index id of the sample the capture landed on: the newly-added id on a
|
||||
// fresh capture, or the EXISTING id on a hash-dedup collapse (so an ingest-with-assign
|
||||
// targets the sample actually in the bank). Empty on any failure / no-op. The S8 arrange
|
||||
// capture+assign path reads this to write an assignment request; the plain capture actions
|
||||
// ignore it (their behaviour is unchanged — capture still writes a file + index entry only).
|
||||
static std::string RunCapture(const reasampler::CaptureActionDef& def)
|
||||
{
|
||||
ResolvedSource src;
|
||||
std::string why;
|
||||
if (!ResolveScopeSource(def.scope, src, why))
|
||||
{
|
||||
ShowConsoleMsg(("ReaSampler capture: " + why + ".\n").c_str());
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
reasampler::CaptureResult res =
|
||||
@@ -809,14 +843,42 @@ static void RunCapture(const reasampler::CaptureActionDef& def)
|
||||
if (res.status != reasampler::CaptureStatus::Ok)
|
||||
{
|
||||
ShowConsoleMsg(("ReaSampler capture failed: " + res.message + "\n").c_str());
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
// captureAndIndexOne has already stamped provenance, added the Sample to the ACTIVE
|
||||
// bank, and recorded the created file in the owned-file manifest (WITHOUT persisting).
|
||||
// Persist the updated book AND manifest into the active project's ext state (the
|
||||
// `banks` + `owned_files` keys) so the capture survives Save / close+reopen (M4) and
|
||||
// travels with the .rpp. saveToActiveProject also clears the retired legacy key and
|
||||
// calls MarkProjectDirty. Non-destructive: writes only our own ext-state keys.
|
||||
g_session.saveToActiveProject();
|
||||
|
||||
// Hand the LANDED bank-index id back to the assign path (S8): captureAndIndexOne
|
||||
// resolved res.sample.id to the fresh id on a new add or the existing entry's id on a
|
||||
// hash-dedup collapse. Empty on any reject (unreachable here — status was Ok above).
|
||||
return res.sample.id;
|
||||
}
|
||||
|
||||
// S8 arrange ingest: capture the selected item / time-selection into the active bank
|
||||
// (reusing the Item-scope capture path verbatim) and, on success, write an assignment
|
||||
// request so the active sampler instance plays the new sample on its next reload. The
|
||||
// capture itself is unchanged — RunCapture writes a file + an index entry and NEVER
|
||||
// inserts a timeline item (load-bearing principle); the only addition here is the
|
||||
// bank-index-id -> assignment-request write after the sample lands. If the capture
|
||||
// failed / no-op'd (empty id), no assignment is written (nothing to assign).
|
||||
static void RunCaptureItemAssign()
|
||||
{
|
||||
// Reuse the Item-scope def from the capture table (index 0) — same range logic, same
|
||||
// FX-scope neutralize, same bank/persist landing as the plain "capture item" action.
|
||||
const std::string sampleId =
|
||||
RunCapture(reasampler::captureActionTable()[0]);
|
||||
if (sampleId.empty()) return; // capture failed / no-op — RunCapture already reported
|
||||
|
||||
reasampler::ingestAssignActiveInstance(g_session.book().activeBankId(), sampleId);
|
||||
reasampler::bankPanelRefresh();
|
||||
ShowConsoleMsg("ReaSampler ingest: captured into the bank and assigned to the active "
|
||||
"instance.\n");
|
||||
}
|
||||
|
||||
// --- M11: batch capture (per selected item / per razor area) ----------------
|
||||
@@ -1422,6 +1484,7 @@ static bool OnHookCommand(int command, int /*flag*/)
|
||||
return true;
|
||||
}
|
||||
if (command == g_cmdToggleBankPanel) { reasampler::bankPanelToggle(); return true; }
|
||||
if (command == g_cmdCaptureItemAssign) { RunCaptureItemAssign(); return true; }
|
||||
if (command == g_cmdInsertSelected) { RunInsertSelected(false); return true; }
|
||||
if (command == g_cmdInsertSelectedConform) { RunInsertSelected(true); return true; }
|
||||
if (command == g_cmdCaptureBatchItems) { RunBatchCaptureItems(); return true; }
|
||||
@@ -1440,6 +1503,8 @@ static bool OnHookCommand(int command, int /*flag*/)
|
||||
if (reasampler::designViewHandleCommand(command)) return true;
|
||||
// Multi-bank action family (B3). Same contract: claims only its own ids.
|
||||
if (reasampler::bankHandleCommand(command)) return true;
|
||||
// S8 ingest action family (Media-Explorer import). Same contract.
|
||||
if (reasampler::ingestHandleCommand(command)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1455,6 +1520,7 @@ static int OnToggleAction(int command)
|
||||
// gaccel storage must outlive registration — REAPER holds the pointer.
|
||||
// (The capture family's accels live in g_captureAccels, sized to the table.)
|
||||
static gaccel_register_t g_accelToggleBankPanel{};
|
||||
static gaccel_register_t g_accelCaptureItemAssign{};
|
||||
static gaccel_register_t g_accelInsertSelected{};
|
||||
static gaccel_register_t g_accelInsertSelectedConform{};
|
||||
static gaccel_register_t g_accelCaptureBatchItems{};
|
||||
@@ -1468,6 +1534,7 @@ static gaccel_register_t g_accelShowVersion{};
|
||||
// (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_descCaptureItemAssign;
|
||||
static std::string g_descInsertSelected;
|
||||
static std::string g_descInsertSelectedConform;
|
||||
static std::string g_descCaptureBatchItems;
|
||||
@@ -1480,6 +1547,7 @@ 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_idCaptureItemAssign = nullptr;
|
||||
static const char* g_idInsertSelected = nullptr;
|
||||
static const char* g_idInsertSelectedConform = nullptr;
|
||||
static const char* g_idCaptureBatchItems = nullptr;
|
||||
@@ -1519,6 +1587,8 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
reasampler::designViewUnregisterActions(g_rec);
|
||||
// Tear down the multi-bank action family (B3) — same mirror-unregister.
|
||||
reasampler::bankUnregisterActions(g_rec);
|
||||
// Tear down the S8 ingest action family — same mirror-unregister.
|
||||
reasampler::ingestUnregisterActions(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);
|
||||
@@ -1537,6 +1607,8 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
g_rec->Register("-command_id", (void*)g_idInsertSelectedConform);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelInsertSelected);
|
||||
g_rec->Register("-command_id", (void*)g_idInsertSelected);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelCaptureItemAssign);
|
||||
g_rec->Register("-command_id", (void*)g_idCaptureItemAssign);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelToggleBankPanel);
|
||||
g_rec->Register("-command_id", (void*)g_idToggleBankPanel);
|
||||
// Mirror-unregister the capture family: gaccel + command_id per row, with
|
||||
@@ -1627,6 +1699,24 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
rec->Register("toggleaction", (void*)&OnToggleAction);
|
||||
}
|
||||
|
||||
// Register the S8 "capture selected item / time-selection into bank + assign" action
|
||||
// (command_id -> gaccel -> hookcommand). Reuses the Item-scope offline capture path and
|
||||
// writes an assignment request so the active instance plays the new sample. Channel-
|
||||
// qualified FOREVER-STABLE id (suffix CAPTURE_ITEM_ASSIGN). MIDI-bindable like every
|
||||
// capture action. Registered in the capture family (main.cpp) because it leans on the
|
||||
// capture render machinery here; the other two ingest surfaces live in the ingest family
|
||||
// (Media-Explorer import) and the panel drop callback.
|
||||
g_idCaptureItemAssign = internCmdId("CAPTURE_ITEM_ASSIGN");
|
||||
g_cmdCaptureItemAssign = rec->Register("command_id", (void*)g_idCaptureItemAssign);
|
||||
if (g_cmdCaptureItemAssign)
|
||||
{
|
||||
g_descCaptureItemAssign = reasampler::channelActionName(
|
||||
"capture selected item into bank + assign to active instance");
|
||||
g_accelCaptureItemAssign.accel.cmd = g_cmdCaptureItemAssign;
|
||||
g_accelCaptureItemAssign.desc = g_descCaptureItemAssign.c_str();
|
||||
rec->Register("gaccel", (void*)&g_accelCaptureItemAssign);
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -1745,6 +1835,12 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
// by the same hookcommand via bankHandleCommand. Registered before the hook.
|
||||
reasampler::bankRegisterActions(rec, &g_session);
|
||||
|
||||
// Register the S8 ingest action family: the Media-Explorer import-into-bank+assign
|
||||
// action. Shares g_session with the other families; routed by the same hookcommand via
|
||||
// ingestHandleCommand. (The arrange capture+assign action is registered in the capture
|
||||
// family above; the drop path is a bank_panel callback, not a bindable action.)
|
||||
reasampler::ingestRegisterActions(rec, &g_session);
|
||||
|
||||
// One hookcommand routes every ReaSampler action (spike + toggle + Design View).
|
||||
// Registered once, after all command ids are minted.
|
||||
rec->Register("hookcommand", (void*)&OnHookCommand);
|
||||
|
||||
Reference in New Issue
Block a user