M3: offline-render capture spike (master-mix / time-selection)
ICaptureBackend/CaptureRequest seam + OfflineRenderBackend driving snapshotted RENDER_* with dither/normalize forced off for bit-identical output, RenderFailed via filesystem check, and a pure capture_paths lib with tests. Spike action registered.
This commit is contained in:
+87
-25
@@ -18,6 +18,11 @@
|
||||
#include "reaper_plugin.h"
|
||||
#include "reaper_plugin_functions.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "bank_model.h"
|
||||
#include "capture.h"
|
||||
|
||||
// Persistent action-id prefix for the ReaSampler action family.
|
||||
// Every bindable action (capture / insert / slot / verify) mints its command id
|
||||
// from a string beginning with this prefix, e.g. "CEREBELLUM_REASAMPLER_CAPTURE_MASTER".
|
||||
@@ -29,30 +34,68 @@
|
||||
REAPER_PLUGIN_HINSTANCE g_hInst = nullptr; // this module's instance handle
|
||||
reaper_plugin_info_t* g_rec = nullptr; // REAPER's dispatch struct
|
||||
|
||||
// ---- Action registration seam (reusable pattern, no action wired yet) ------
|
||||
// The capture/insert/slot action family (PLAN.md M3+) reinstates the pattern
|
||||
// below. Kept as a commented template so the shape is not re-derived each time.
|
||||
//
|
||||
// static int g_cmdCaptureMaster = 0;
|
||||
//
|
||||
// // REAPER calls this for EVERY action fired anywhere; claim only your own id,
|
||||
// // return false otherwise so REAPER keeps looking.
|
||||
// static bool OnHookCommand(int command, int /*flag*/)
|
||||
// {
|
||||
// if (command == 0) return false;
|
||||
// if (command == g_cmdCaptureMaster) { /* route to capture module */ return true; }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// Registration, inside the load branch below:
|
||||
// 1) g_cmdCaptureMaster =
|
||||
// rec->Register("command_id", (void*)(REASAMPLER_ACTION_PREFIX "CAPTURE_MASTER"));
|
||||
// 2) static gaccel_register_t sAccel{};
|
||||
// sAccel.accel.cmd = g_cmdCaptureMaster;
|
||||
// sAccel.desc = "ReaSampler: capture master mix";
|
||||
// rec->Register("gaccel", (void*)&sAccel);
|
||||
// 3) rec->Register("hookcommand", (void*)&OnHookCommand);
|
||||
// On unload (rec == nullptr), mirror-unregister with the same strings prefixed '-'.
|
||||
// ---- Action registration seam (M3 spike: capture master mix) ---------------
|
||||
// First live use of the action pattern the seam left templated. The full capture
|
||||
// action family (selected tracks/items/razor, wet/dry, tail) is M7; this is ONE
|
||||
// temporary action driving the M3 offline-render spike.
|
||||
|
||||
// Command id for "ReaSampler: capture master mix (spike)". FOREVER-STABLE string
|
||||
// (user keybindings key off it) — see the prefix note above.
|
||||
static int g_cmdCaptureMasterSpike = 0;
|
||||
|
||||
// In-memory bank for the spike. M4 replaces this with project ext-state persist;
|
||||
// for M3 the index lives only for the session, proving the capture->Sample->add
|
||||
// path end to end.
|
||||
static reasampler::BankIndex g_bank;
|
||||
|
||||
// Runs the M3 spike: render the time-selection master mix, add the Sample, log.
|
||||
static void RunCaptureMasterSpike()
|
||||
{
|
||||
// Time selection -> exact render bounds (no rounding). GetSet_LoopTimeRange
|
||||
// with isSet=false reads the current time selection (isLoop=false).
|
||||
double start = 0.0, end = 0.0;
|
||||
GetSet_LoopTimeRange(false, false, &start, &end, false);
|
||||
|
||||
reasampler::CaptureRequest req;
|
||||
req.sourceMode = reasampler::SourceMode::TimeSelection;
|
||||
req.startSeconds = start;
|
||||
req.endSeconds = end;
|
||||
req.wetDry = 1.0; // wet master mix
|
||||
req.renderTail = false; // exact bounds, no tail
|
||||
req.sampleRate = 0; // follow project rate
|
||||
req.channelCount = 2;
|
||||
req.bitDepth = reasampler::WavBitDepth::Float32; // deterministic, no dither
|
||||
req.baseName = "master_mix";
|
||||
|
||||
reasampler::OfflineRenderBackend backend;
|
||||
reasampler::CaptureResult res = backend.capture(req);
|
||||
|
||||
if (res.status != reasampler::CaptureStatus::Ok)
|
||||
{
|
||||
ShowConsoleMsg(("ReaSampler capture failed: " + res.message + "\n").c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
reasampler::AddResult added = g_bank.add(res.sample);
|
||||
std::string log = "ReaSampler: " + res.message + "\n";
|
||||
log += " bank size now " + std::to_string(g_bank.size()) +
|
||||
(added == reasampler::AddResult::Added ? " (added)\n"
|
||||
: added == reasampler::AddResult::Collapsed ? " (collapsed on hash)\n"
|
||||
: " (rejected)\n");
|
||||
ShowConsoleMsg(log.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; }
|
||||
return false;
|
||||
}
|
||||
|
||||
// gaccel storage must outlive registration — REAPER holds the pointer.
|
||||
static gaccel_register_t g_accelCaptureMaster{};
|
||||
|
||||
extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t* rec)
|
||||
@@ -60,7 +103,14 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
if (!rec)
|
||||
{
|
||||
// rec == nullptr => REAPER is UNLOADING us. Mirror-unregister every
|
||||
// callback here (same strings prefixed with '-') once actions are wired.
|
||||
// callback with the same strings prefixed '-' (per the contract).
|
||||
if (g_rec)
|
||||
{
|
||||
g_rec->Register("-hookcommand", (void*)&OnHookCommand);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelCaptureMaster);
|
||||
g_rec->Register("-command_id",
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "CAPTURE_MASTER_SPIKE"));
|
||||
}
|
||||
g_rec = nullptr;
|
||||
return 0;
|
||||
}
|
||||
@@ -77,6 +127,18 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
g_hInst = hInstance;
|
||||
g_rec = rec;
|
||||
|
||||
// Register the M3 spike action (command_id -> gaccel -> hookcommand).
|
||||
g_cmdCaptureMasterSpike = rec->Register(
|
||||
"command_id",
|
||||
(void*)(REASAMPLER_ACTION_PREFIX "CAPTURE_MASTER_SPIKE"));
|
||||
if (g_cmdCaptureMasterSpike)
|
||||
{
|
||||
g_accelCaptureMaster.accel.cmd = g_cmdCaptureMasterSpike;
|
||||
g_accelCaptureMaster.desc = "ReaSampler: capture master mix (spike)";
|
||||
rec->Register("gaccel", (void*)&g_accelCaptureMaster);
|
||||
rec->Register("hookcommand", (void*)&OnHookCommand);
|
||||
}
|
||||
|
||||
ShowConsoleMsg("ReaSampler loaded.\n");
|
||||
|
||||
return 1; // success — REAPER keeps us loaded
|
||||
|
||||
Reference in New Issue
Block a user