M10: provenance populate + re-capture from source (bank-only)
Pure provenance core (recipe fingerprint, FX-chain identity, parent detection) + shell reads; capture stamps provenance on resample-from-sample; re-capture regenerates a provenanced sample from its source, never touching the timeline. Adds BankIndex/BankBook in-place update. CTest-covered.
This commit is contained in:
+323
-10
@@ -31,10 +31,14 @@
|
||||
#include "capture.h"
|
||||
#include "insert.h"
|
||||
#include "persist.h"
|
||||
#include "provenance.h"
|
||||
#include "provenance_shell.h"
|
||||
#include "render_settings.h"
|
||||
#include "track_guid.h"
|
||||
#include "view.h"
|
||||
|
||||
#include <filesystem> // project-dir derivation for provenance parent resolution
|
||||
|
||||
// Persistent action-id family (Phase V, V4 — channel-qualified). Every bindable action
|
||||
// mints its command id from commandIdPrefix() + a per-action SUFFIX, and its Actions-list
|
||||
// name from actionDisplayPrefix() + a phrase, both derived from the ONE channel bit in the
|
||||
@@ -128,6 +132,14 @@ static int g_cmdInsertSelectedConform = 0;
|
||||
// backend. Dialog-free. (Replaces the removed CAPTURE_MASTER_REALTIME action.)
|
||||
static int g_cmdCaptureTrackRealtime = 0;
|
||||
|
||||
// Command id for the M10 "re-capture from source" action. NEW FOREVER-STABLE string
|
||||
// (suffix RECAPTURE_FROM_SOURCE). Regenerates the bank panel's selected PROVENANCED
|
||||
// sample from its recorded source's current state and updates the Sample in place —
|
||||
// BANK-ONLY, never places on the timeline (load-bearing principle). Reports the no-
|
||||
// provenance / vanished-source / drift cases to the console (a direct response to an
|
||||
// explicit action, allowed by the console policy).
|
||||
static int g_cmdRecaptureFromSource = 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
|
||||
@@ -392,6 +404,90 @@ static bool resolveRange(double& start, double& end, std::string& why)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Current project's directory (parent of its .rpp), forward-slashed, no trailing
|
||||
// slash — the same derivation capture.cpp does internally, needed here so M10 can
|
||||
// resolve the bank's relative paths to absolute for parent detection. Empty for an
|
||||
// unsaved project (EnumProjects writes an empty .rpp path), which makes every bank
|
||||
// file resolve empty -> no false parentage. Read-only; mutates nothing.
|
||||
static std::string currentProjectDir()
|
||||
{
|
||||
std::vector<char> buf(4096, '\0');
|
||||
EnumProjects(-1, buf.data(), static_cast<int>(buf.size()));
|
||||
const std::string rpp(buf.data());
|
||||
if (rpp.empty()) return {};
|
||||
namespace fs = std::filesystem;
|
||||
std::string dir = fs::path(rpp).parent_path().string();
|
||||
for (char& c : dir) if (c == '\\') c = '/';
|
||||
if (dir.size() > 1 && dir.back() == '/') dir.pop_back();
|
||||
return dir;
|
||||
}
|
||||
|
||||
// Maps a capture FX scope onto the pure provenance scope (kept decoupled so the
|
||||
// pure provenance module does not depend on render_settings).
|
||||
static reasampler::ProvenanceScope provenanceScopeFor(reasampler::CaptureScope scope)
|
||||
{
|
||||
return scope == reasampler::CaptureScope::Item ? reasampler::ProvenanceScope::Item
|
||||
: reasampler::ProvenanceScope::Track;
|
||||
}
|
||||
|
||||
// Builds the M10 provenance for a capture IF it genuinely resamples from a bank
|
||||
// sample, else returns nullopt (the common, non-resample case). Detection rule
|
||||
// (stated honestly): the capture's source item media file(s) must all resolve, by
|
||||
// exact normalized absolute path, to ONE bank sample's file (detectParent). On a
|
||||
// match, records that sample's id as the parent plus a THIN capture-recipe
|
||||
// fingerprint (P1=a) — scope + source mode + exact range + tail + rate + channels +
|
||||
// source track GUIDs + the in-scope source FX-chain identity — so "re-capture from
|
||||
// source" can replay the request and report drift. NEVER a serialized chain to
|
||||
// restore. Item scope folds an empty FX identity (take/item FX are not enumerable
|
||||
// via TrackFX_*); the drift signal then keys on scope+range, which is honest.
|
||||
static std::optional<reasampler::Provenance> buildCaptureProvenance(
|
||||
const reasampler::CaptureRequest& req,
|
||||
reasampler::CaptureScope scope,
|
||||
const ResolvedSource& src)
|
||||
{
|
||||
const std::string projectDir = currentProjectDir();
|
||||
const std::vector<reasampler::BankFileRef> bankFiles =
|
||||
reasampler::bankFileRefs(g_session.book(), projectDir);
|
||||
|
||||
// The "what audio is being captured" source set depends on scope: item scope uses
|
||||
// the SELECTED items (the user picked them); track scope uses the range-overlapping
|
||||
// items ON the source tracks (the user picked the track, not the item).
|
||||
const std::vector<std::string> sourceFiles =
|
||||
scope == reasampler::CaptureScope::Item
|
||||
? reasampler::selectedItemSourceFiles()
|
||||
: reasampler::trackItemSourceFiles(src.sourceTracks, req.startSeconds,
|
||||
req.endSeconds);
|
||||
|
||||
const std::optional<std::string> parentId =
|
||||
reasampler::detectParent(sourceFiles, bankFiles);
|
||||
if (!parentId) return std::nullopt; // not a resample-from-sample — no provenance
|
||||
|
||||
reasampler::CaptureRecipe recipe;
|
||||
recipe.scope = provenanceScopeFor(scope);
|
||||
recipe.sourceMode = static_cast<int>(req.sourceMode);
|
||||
recipe.startSeconds = req.startSeconds;
|
||||
recipe.endSeconds = req.endSeconds;
|
||||
recipe.tailMode = static_cast<int>(req.tailMode);
|
||||
recipe.tailMs = req.tailMs;
|
||||
recipe.sampleRate = req.sampleRate;
|
||||
recipe.channelCount = req.channelCount;
|
||||
recipe.trackGuids = req.trackGuids;
|
||||
// The in-scope FX-chain identity is the per-track chains combined in track order
|
||||
// (Track scope), length-prefixed so distinct partitions never collide. Item scope
|
||||
// has no readable take-FX chain, so each track folds to an empty identity and the
|
||||
// combined result stays stable/honest (drift then keys on scope + range).
|
||||
std::vector<std::string> perTrack;
|
||||
perTrack.reserve(src.sourceTracks.size());
|
||||
for (MediaTrack* tr : src.sourceTracks)
|
||||
perTrack.push_back(reasampler::fxChainIdentityForTrack(tr));
|
||||
recipe.fxChainIdentity = reasampler::combineChainIdentities(perTrack);
|
||||
|
||||
reasampler::Provenance prov;
|
||||
prov.parentSampleId = *parentId;
|
||||
prov.fxChainSnapshot = reasampler::buildFingerprint(recipe);
|
||||
return prov;
|
||||
}
|
||||
|
||||
// Collects the selected tracks (Track scope) into out.sourceTracks + GUIDs.
|
||||
static bool collectSelectedTracks(ResolvedSource& out)
|
||||
{
|
||||
@@ -589,6 +685,23 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
// Renders one CaptureRequest through the offline backend under the scope's
|
||||
// FX-bypass guard, returning the backend's CaptureResult. Shared by RunCapture and
|
||||
// RunRecaptureFromSource so the FX-scope neutralize + render recipe lives in ONE
|
||||
// place: the out-of-scope FX / fader / pan chain is snapshotted, neutralized for the
|
||||
// render, and fully restored on every path (RAII). Non-destructive; touches no
|
||||
// timeline item (load-bearing principle) — it writes a file only.
|
||||
static reasampler::CaptureResult renderOffline(
|
||||
reasampler::CaptureScope scope,
|
||||
const std::vector<MediaTrack*>& sourceTracks,
|
||||
const reasampler::CaptureRequest& req)
|
||||
{
|
||||
ReaProject* proj = EnumProjects(-1, nullptr, 0);
|
||||
FxBypassGuard fxGuard(scope, sourceTracks, proj);
|
||||
reasampler::OfflineRenderBackend backend;
|
||||
return backend.capture(req);
|
||||
}
|
||||
|
||||
// Runs one capture-action-table row: resolve its scope source + range, snapshot &
|
||||
// clear the out-of-scope FX AND neutralize their fader gain + pan chain (RAII),
|
||||
// render via the offline backend, add the Sample to the bank, persist + mark dirty.
|
||||
@@ -625,17 +738,17 @@ static void RunCapture(const reasampler::CaptureActionDef& def)
|
||||
req.baseName = def.baseName;
|
||||
req.trackGuids = src.trackGuids; // recorded on the Sample (provenance)
|
||||
|
||||
// Bypass the out-of-scope FX and neutralize their fader gain (D_VOL -> unity)
|
||||
// AND full pan chain (D_PAN/D_WIDTH/D_PANLAW/I_PANMODE -> uncolored) for the
|
||||
// duration of the render — so parent/master fader level AND pan/width/law/mode
|
||||
// are not baked into the file (see the FxBypassGuard header comment for the
|
||||
// authoritative neutralize set). Restored on EVERY exit path below (RAII),
|
||||
// including backend failures. proj = active project.
|
||||
ReaProject* proj = EnumProjects(-1, nullptr, 0);
|
||||
FxBypassGuard fxGuard(def.scope, src.sourceTracks, proj);
|
||||
// M10: compute provenance BEFORE the FxBypassGuard neutralizes the in-scope chain —
|
||||
// the source FX-chain identity must be read from the LIVE (un-bypassed) chain, and
|
||||
// the source selection is still live here. Returns nullopt unless this capture
|
||||
// genuinely resamples from a bank sample (detectParent). Read-only.
|
||||
const std::optional<reasampler::Provenance> prov =
|
||||
buildCaptureProvenance(req, def.scope, src);
|
||||
|
||||
reasampler::OfflineRenderBackend backend;
|
||||
reasampler::CaptureResult res = backend.capture(req);
|
||||
// Render under the scope's FX-bypass guard (out-of-scope FX / fader / pan chain
|
||||
// neutralized for the render, fully restored on every path — see renderOffline
|
||||
// and the FxBypassGuard header comment). Non-destructive; writes a file only.
|
||||
reasampler::CaptureResult res = renderOffline(def.scope, src.sourceTracks, req);
|
||||
|
||||
if (res.status != reasampler::CaptureStatus::Ok)
|
||||
{
|
||||
@@ -643,6 +756,10 @@ static void RunCapture(const reasampler::CaptureActionDef& def)
|
||||
return;
|
||||
}
|
||||
|
||||
// Stamp provenance onto the captured Sample (only set when this was a genuine
|
||||
// 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);
|
||||
// B-cap: record the created file in the owned-file manifest, at the same point the
|
||||
@@ -657,6 +774,182 @@ static void RunCapture(const reasampler::CaptureActionDef& def)
|
||||
g_session.saveToActiveProject();
|
||||
}
|
||||
|
||||
// --- M10: re-capture from source --------------------------------------------
|
||||
//
|
||||
// Regenerates a PROVENANCED bank sample's file from its recorded source's CURRENT
|
||||
// state, then updates the bank Sample IN PLACE. BANK-ONLY — it renders a file and
|
||||
// refreshes the index entry; it NEVER calls InsertMedia / touches the timeline (the
|
||||
// load-bearing capture-never-places line, structurally visible: this function has no
|
||||
// insert path at all). Non-destructive to the source (FxBypassGuard snapshot/restore
|
||||
// via renderOffline). Fork P2=a: refresh the bank entry only; the user re-places
|
||||
// manually if they want the new version on the timeline.
|
||||
//
|
||||
// Failure modes are handled explicitly and reported to the user (a direct response
|
||||
// to an explicit action is allowed by the console policy):
|
||||
// * the selected sample has no provenance (not a resample) -> reported, no-op.
|
||||
// * the recorded fingerprint is unparseable (legacy/corrupt) -> reported, no-op.
|
||||
// * the recorded source track(s) no longer exist -> reported, no-op.
|
||||
// * the render itself fails to satisfy the recorded request -> reported, no-op.
|
||||
// On success, if the source FX chain drifted since capture (recorded vs current
|
||||
// identity differ) the user is told — the re-capture still reflects the source AS IT
|
||||
// IS NOW (P1=a: the fingerprint detects drift, it does not freeze the source).
|
||||
static void RunRecaptureFromSource()
|
||||
{
|
||||
const std::vector<std::string> selected = reasampler::bankPanelSelectedSampleIds();
|
||||
if (selected.empty())
|
||||
{
|
||||
ShowConsoleMsg("ReaSampler re-capture: select a sample in the bank panel first.\n");
|
||||
return;
|
||||
}
|
||||
if (selected.size() > 1)
|
||||
{
|
||||
ShowConsoleMsg("ReaSampler re-capture: select a single sample to re-capture.\n");
|
||||
return;
|
||||
}
|
||||
const std::string sampleId = selected.front();
|
||||
|
||||
// Resolve the sample from the bank it lives in (the focused region's displayed bank).
|
||||
const std::string srcBankId = reasampler::bankPanelSelectedSourceBankId();
|
||||
const reasampler::Bank* bank = g_session.book().bank(srcBankId);
|
||||
const reasampler::Sample* orig = bank ? bank->index.query(sampleId) : nullptr;
|
||||
if (!orig)
|
||||
{
|
||||
ShowConsoleMsg("ReaSampler re-capture: the selected sample is no longer in the bank.\n");
|
||||
return;
|
||||
}
|
||||
if (!orig->provenance)
|
||||
{
|
||||
ShowConsoleMsg("ReaSampler re-capture: this sample has no provenance "
|
||||
"(it was not resampled from a bank sample).\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the recorded capture recipe from the fingerprint. A legacy / corrupt
|
||||
// string fails gracefully — never a partial re-capture.
|
||||
const std::string recordedParentId = orig->provenance->parentSampleId;
|
||||
const std::string recordedFingerprint = orig->provenance->fxChainSnapshot;
|
||||
const std::optional<reasampler::CaptureRecipe> recipe =
|
||||
reasampler::parseFingerprint(recordedFingerprint);
|
||||
if (!recipe)
|
||||
{
|
||||
ShowConsoleMsg("ReaSampler re-capture: this sample's provenance is unreadable "
|
||||
"(recorded by an older/incompatible build); cannot re-capture.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve the recorded source track GUID(s) to live tracks. Any missing track is a
|
||||
// hard failure — we will not silently re-capture a different source.
|
||||
std::vector<MediaTrack*> sourceTracks;
|
||||
for (const std::string& g : recipe->trackGuids)
|
||||
{
|
||||
MediaTrack* tr = reasampler::trackByGuid(g);
|
||||
if (!tr)
|
||||
{
|
||||
ShowConsoleMsg("ReaSampler re-capture: a recorded source track no longer "
|
||||
"exists in this project; cannot re-capture from source.\n");
|
||||
return;
|
||||
}
|
||||
sourceTracks.push_back(tr);
|
||||
}
|
||||
if (sourceTracks.empty())
|
||||
{
|
||||
// The recipe recorded no source tracks (e.g. an item-scope capture whose source
|
||||
// tracks were not track-scoped). Without a resolvable source we cannot re-run.
|
||||
ShowConsoleMsg("ReaSampler re-capture: no resolvable recorded source for this "
|
||||
"sample; cannot re-capture from source.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const reasampler::CaptureScope scope =
|
||||
recipe->scope == reasampler::ProvenanceScope::Item
|
||||
? reasampler::CaptureScope::Item
|
||||
: reasampler::CaptureScope::Track;
|
||||
|
||||
// Rebuild the capture request verbatim from the recorded recipe — the SAME request,
|
||||
// re-run against the source's CURRENT state (P1=a). Exact bounds, tail, rate,
|
||||
// channels, bit depth all match the original so an unchanged source produces a
|
||||
// byte-identical file (bit-identical-repeats invariant, consumed as a feature).
|
||||
reasampler::CaptureRequest req;
|
||||
req.sourceMode = static_cast<reasampler::SourceMode>(recipe->sourceMode);
|
||||
req.startSeconds = recipe->startSeconds;
|
||||
req.endSeconds = recipe->endSeconds;
|
||||
req.wetDry = 1.0;
|
||||
req.tailMode = static_cast<reasampler::TailMode>(recipe->tailMode);
|
||||
req.tailMs = recipe->tailMs;
|
||||
req.sampleRate = recipe->sampleRate;
|
||||
req.channelCount = recipe->channelCount;
|
||||
req.bitDepth = reasampler::WavBitDepth::Float32;
|
||||
req.baseName = orig->displayName.empty() ? "recapture" : orig->displayName;
|
||||
req.trackGuids = recipe->trackGuids;
|
||||
|
||||
// Read the CURRENT source FX-chain identity BEFORE the render bypasses it, to
|
||||
// compare against the recorded identity for drift reporting.
|
||||
std::vector<std::string> perTrackNow;
|
||||
perTrackNow.reserve(sourceTracks.size());
|
||||
for (MediaTrack* tr : sourceTracks)
|
||||
perTrackNow.push_back(reasampler::fxChainIdentityForTrack(tr));
|
||||
const std::string currentIdentity = reasampler::combineChainIdentities(perTrackNow);
|
||||
const bool drifted = (currentIdentity != recipe->fxChainIdentity);
|
||||
|
||||
// Render (bank-only; renderOffline never touches the timeline).
|
||||
reasampler::CaptureResult res = renderOffline(scope, sourceTracks, req);
|
||||
if (res.status != reasampler::CaptureStatus::Ok)
|
||||
{
|
||||
ShowConsoleMsg(("ReaSampler re-capture failed: " + res.message + "\n").c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the Sample IN PLACE: keep its identity (id) and its provenance thread
|
||||
// (same parent + a REFRESHED fingerprint reflecting the source as re-captured), but
|
||||
// adopt the regenerated file's path / hash / length / rate / timestamp. The
|
||||
// fingerprint is rebuilt from the recipe with the CURRENT FX identity so a
|
||||
// subsequent re-capture measures drift from this point, not the original.
|
||||
reasampler::CaptureRecipe refreshed = *recipe;
|
||||
refreshed.fxChainIdentity = currentIdentity;
|
||||
|
||||
reasampler::Sample updated = *orig; // copy: preserves id, displayName, tier, key
|
||||
updated.relativePath = res.sample.relativePath;
|
||||
updated.contentHash = res.sample.contentHash;
|
||||
updated.sourceMode = res.sample.sourceMode;
|
||||
updated.sourceRange = res.sample.sourceRange;
|
||||
updated.channelCount = res.sample.channelCount;
|
||||
updated.sampleRate = res.sample.sampleRate;
|
||||
updated.lengthSeconds = res.sample.lengthSeconds;
|
||||
updated.captureTempo = res.sample.captureTempo;
|
||||
updated.trackGuids = res.sample.trackGuids;
|
||||
updated.createdTimestamp = res.sample.createdTimestamp;
|
||||
reasampler::Provenance prov;
|
||||
prov.parentSampleId = recordedParentId;
|
||||
prov.fxChainSnapshot = reasampler::buildFingerprint(refreshed);
|
||||
updated.provenance = prov;
|
||||
|
||||
// Single batched undo point around the in-place bank mutation (mirrors the bank
|
||||
// action family's R-B pattern). The mutation is index-only ext-state; the render
|
||||
// wrote a new file but placed nothing on the timeline.
|
||||
Undo_BeginBlock2(nullptr);
|
||||
const bool changed = g_session.book().updateSampleInPlace(sampleId, updated);
|
||||
if (changed)
|
||||
{
|
||||
// Record the regenerated file in the owned manifest (a new file the tool wrote);
|
||||
// the superseded old file becomes an orphan reclaimed by Phase R prune.
|
||||
g_session.owned().add(updated.relativePath);
|
||||
const bool persisted = g_session.saveToActiveProject(); // book + manifest + MarkProjectDirty
|
||||
Undo_EndBlock2(nullptr, persisted ? "ReaSampler: re-capture from source" : "",
|
||||
persisted ? UNDO_STATE_MISCCFG : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Undo_EndBlock2(nullptr, "", 0); // nothing mutated -> discard the empty point
|
||||
}
|
||||
|
||||
reasampler::bankPanelRefresh(); // reflect the regenerated file in the docked grid
|
||||
|
||||
if (drifted)
|
||||
ShowConsoleMsg("ReaSampler re-capture: the source FX chain changed since the "
|
||||
"original capture -- the sample was regenerated from the source's "
|
||||
"current state.\n");
|
||||
}
|
||||
|
||||
// STARTS the REALTIME track capture and returns immediately — the record runs across
|
||||
// timer ticks (DriveRealtimeCapture), so REAPER's UI stays responsive. Resolves the
|
||||
// selected tracks + the range (razor-else-time, the same orthogonal range logic as the
|
||||
@@ -803,6 +1096,7 @@ static bool OnHookCommand(int command, int /*flag*/)
|
||||
if (command == g_cmdInsertSelectedConform) { RunInsertSelected(true); return true; }
|
||||
if (command == g_cmdCaptureTrackRealtime) { RunCaptureRealtimeTrack(); return true; }
|
||||
if (command == g_cmdCancelRealtime) { RunCancelRealtime(); return true; }
|
||||
if (command == g_cmdRecaptureFromSource) { RunRecaptureFromSource(); return true; }
|
||||
if (command == g_cmdShowVersion)
|
||||
{
|
||||
// On-demand version readout — the ONLY version output on any path.
|
||||
@@ -833,6 +1127,7 @@ static gaccel_register_t g_accelInsertSelected{};
|
||||
static gaccel_register_t g_accelInsertSelectedConform{};
|
||||
static gaccel_register_t g_accelCaptureTrackRealtime{};
|
||||
static gaccel_register_t g_accelCancelRealtime{};
|
||||
static gaccel_register_t g_accelRecaptureFromSource{};
|
||||
static gaccel_register_t g_accelShowVersion{};
|
||||
|
||||
// gaccel desc storage. The Actions-list label is channel-qualified at runtime
|
||||
@@ -843,6 +1138,7 @@ static std::string g_descInsertSelected;
|
||||
static std::string g_descInsertSelectedConform;
|
||||
static std::string g_descCaptureTrackRealtime;
|
||||
static std::string g_descCancelRealtime;
|
||||
static std::string g_descRecaptureFromSource;
|
||||
static std::string g_descShowVersion;
|
||||
|
||||
// Composed command-id strings (channel-qualified), interned so register and the mirroring
|
||||
@@ -852,6 +1148,7 @@ static const char* g_idInsertSelected = nullptr;
|
||||
static const char* g_idInsertSelectedConform = nullptr;
|
||||
static const char* g_idCaptureTrackRealtime = nullptr;
|
||||
static const char* g_idCancelRealtime = nullptr;
|
||||
static const char* g_idRecaptureFromSource = nullptr;
|
||||
static const char* g_idShowVersion = nullptr;
|
||||
|
||||
extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
@@ -888,6 +1185,8 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
// used at register (g_id*), so the mirror-unregister matches exactly.
|
||||
g_rec->Register("-gaccel", (void*)&g_accelShowVersion);
|
||||
g_rec->Register("-command_id", (void*)g_idShowVersion);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelRecaptureFromSource);
|
||||
g_rec->Register("-command_id", (void*)g_idRecaptureFromSource);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelCancelRealtime);
|
||||
g_rec->Register("-command_id", (void*)g_idCancelRealtime);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelCaptureTrackRealtime);
|
||||
@@ -1038,6 +1337,20 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
rec->Register("gaccel", (void*)&g_accelCancelRealtime);
|
||||
}
|
||||
|
||||
// Register the M10 "re-capture from source" action (command_id -> gaccel ->
|
||||
// hookcommand). Regenerates the selected provenanced sample from its recorded
|
||||
// source's current state; bank-only, never places on the timeline. Channel-
|
||||
// qualified FOREVER-STABLE id (suffix RECAPTURE_FROM_SOURCE).
|
||||
g_idRecaptureFromSource = internCmdId("RECAPTURE_FROM_SOURCE");
|
||||
g_cmdRecaptureFromSource = rec->Register("command_id", (void*)g_idRecaptureFromSource);
|
||||
if (g_cmdRecaptureFromSource)
|
||||
{
|
||||
g_descRecaptureFromSource = reasampler::channelActionName("re-capture from source");
|
||||
g_accelRecaptureFromSource.accel.cmd = g_cmdRecaptureFromSource;
|
||||
g_accelRecaptureFromSource.desc = g_descRecaptureFromSource.c_str();
|
||||
rec->Register("gaccel", (void*)&g_accelRecaptureFromSource);
|
||||
}
|
||||
|
||||
// Register the Phase V "show version" action (command_id -> gaccel -> hookcommand).
|
||||
// On-demand only — prints the CMake-sourced version to the console when fired; no
|
||||
// startup print. Channel-qualified FOREVER-STABLE id; label carries the channel prefix
|
||||
|
||||
Reference in New Issue
Block a user