feat(capture): M11 batch capture - one bank sample per selected item / razor area
Pure batch_capture module (plan + mixed-result aggregation, unit-tested); CAPTURE_BATCH_ITEMS/RAZOR actions with transient per-unit selection restored on all paths, per-unit invariants + provenance, single persist. Conform-on-insert already shipped.
This commit is contained in:
+385
-49
@@ -18,9 +18,11 @@
|
||||
#include "reaper_plugin.h"
|
||||
#include "reaper_plugin_functions.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <vector>
|
||||
|
||||
@@ -28,6 +30,7 @@
|
||||
#include "app_version.h"
|
||||
#include "bank_model.h"
|
||||
#include "bank_panel.h"
|
||||
#include "batch_capture.h"
|
||||
#include "capture.h"
|
||||
#include "insert.h"
|
||||
#include "persist.h"
|
||||
@@ -124,6 +127,14 @@ static int g_cmdToggleBankPanel = 0;
|
||||
static int g_cmdInsertSelected = 0;
|
||||
static int g_cmdInsertSelectedConform = 0;
|
||||
|
||||
// Command ids for the M11 batch-capture actions. NEW FOREVER-STABLE strings. One action
|
||||
// fires N captures: CAPTURE_BATCH_ITEMS -> one bank sample per selected item (item scope);
|
||||
// CAPTURE_BATCH_RAZOR -> one bank sample per razor area (track scope, each area's range).
|
||||
// Each unit honors every precision invariant; the original selection is restored on every
|
||||
// exit path. Bank-only, never places on the timeline (load-bearing principle).
|
||||
static int g_cmdCaptureBatchItems = 0;
|
||||
static int g_cmdCaptureBatchRazor = 0;
|
||||
|
||||
// Command id for the "capture selected track (realtime)" action. NEW FOREVER-STABLE
|
||||
// string. Records the selected track's OWN output in realtime (transport-driven) into
|
||||
// a hidden temp track via RealtimeRecordBackend, then moves the recorded file into the
|
||||
@@ -714,13 +725,75 @@ static reasampler::CaptureResult renderOffline(
|
||||
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.
|
||||
// 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.
|
||||
// Non-destructive: FX-enable + fader gain + pan/width/law/mode are fully restored
|
||||
// on every path by FxBypassGuard, and the backend restores every RENDER_* setting.
|
||||
// Renders ONE capture request under the scope's FX-bypass guard, stamps provenance,
|
||||
// and adds the resulting Sample to the ACTIVE bank + records the created file in the
|
||||
// owned-file manifest — WITHOUT persisting. The caller persists once (single-capture:
|
||||
// right after; batch: once at the end) so a batch does not write ext state N times.
|
||||
//
|
||||
// Provenance is read from the LIVE selection here, so a batch that transiently
|
||||
// selects exactly one item per unit gets per-unit-correct provenance. `src` supplies
|
||||
// the source tracks (FX bypass + Sample GUIDs); `scope` drives the bypass plan and
|
||||
// provenance scope. Returns the backend's CaptureResult (status + message) so the
|
||||
// caller can report success/failure. Load-bearing principle holds: writes a file +
|
||||
// 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.
|
||||
static reasampler::CaptureResult captureAndIndexOne(
|
||||
reasampler::CaptureScope scope,
|
||||
const ResolvedSource& src,
|
||||
const std::string& baseName,
|
||||
double startSeconds,
|
||||
double endSeconds)
|
||||
{
|
||||
// The tail mode is a PANEL SETTING (docked bank panel's toggle), not a per-action
|
||||
// variant: the capture actions apply whatever the panel is set to. Default is None
|
||||
// (exact bounds / byte-identical to today) until the user opts in via the toggle.
|
||||
const reasampler::TailSetting tail = reasampler::bankPanelTailSetting();
|
||||
|
||||
reasampler::CaptureRequest req;
|
||||
req.sourceMode = reasampler::sourceModeForScope(scope);
|
||||
req.startSeconds = startSeconds; // exact bounds — no rounding
|
||||
req.endSeconds = endSeconds;
|
||||
req.wetDry = 1.0; // wet post the FX left enabled by the scope
|
||||
req.tailMode = tail.mode; // None / Auto / Manual, from the panel toggle
|
||||
req.tailMs = tail.manualMs; // Manual-only (clamped); ignored for None/Auto
|
||||
req.sampleRate = 0; // follow project rate
|
||||
req.channelCount = 2;
|
||||
req.bitDepth = reasampler::WavBitDepth::Float32; // deterministic, no dither
|
||||
req.baseName = baseName;
|
||||
req.trackGuids = src.trackGuids; // recorded on the Sample (provenance)
|
||||
|
||||
// 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, scope, src);
|
||||
|
||||
// Render under the scope's FX-bypass guard (out-of-scope FX / fader / pan chain
|
||||
// neutralized for the render, fully restored on every path). Writes a file only.
|
||||
reasampler::CaptureResult res = renderOffline(scope, src.sourceTracks, req);
|
||||
if (res.status != reasampler::CaptureStatus::Ok)
|
||||
return res;
|
||||
|
||||
// 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
|
||||
// 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);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Runs one capture-action-table row: resolve its scope source + range, render + add +
|
||||
// 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)
|
||||
{
|
||||
ResolvedSource src;
|
||||
@@ -731,54 +804,14 @@ static void RunCapture(const reasampler::CaptureActionDef& def)
|
||||
return;
|
||||
}
|
||||
|
||||
// The tail mode is a PANEL SETTING (docked bank panel's toggle), not a per-action
|
||||
// variant: the plain capture actions apply whatever the panel is set to. Default
|
||||
// is None (exact bounds / byte-identical to today) until the user opts in via the
|
||||
// toggle. tailMs is meaningful only for Manual and is pre-clamped by the panel.
|
||||
const reasampler::TailSetting tail = reasampler::bankPanelTailSetting();
|
||||
|
||||
reasampler::CaptureRequest req;
|
||||
req.sourceMode = reasampler::sourceModeForScope(def.scope);
|
||||
req.startSeconds = src.startSeconds; // exact bounds — no rounding
|
||||
req.endSeconds = src.endSeconds;
|
||||
req.wetDry = 1.0; // wet post the FX left enabled by the scope
|
||||
req.tailMode = tail.mode; // None / Auto / Manual, from the panel toggle
|
||||
req.tailMs = tail.manualMs; // Manual-only (clamped); ignored for None/Auto
|
||||
req.sampleRate = 0; // follow project rate
|
||||
req.channelCount = 2;
|
||||
req.bitDepth = reasampler::WavBitDepth::Float32; // deterministic, no dither
|
||||
req.baseName = def.baseName;
|
||||
req.trackGuids = src.trackGuids; // recorded on the Sample (provenance)
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
reasampler::CaptureResult res =
|
||||
captureAndIndexOne(def.scope, src, def.baseName, src.startSeconds, src.endSeconds);
|
||||
if (res.status != reasampler::CaptureStatus::Ok)
|
||||
{
|
||||
ShowConsoleMsg(("ReaSampler capture failed: " + res.message + "\n").c_str());
|
||||
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
|
||||
// Sample is added and before the same persist. 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);
|
||||
// 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
|
||||
@@ -786,6 +819,271 @@ static void RunCapture(const reasampler::CaptureActionDef& def)
|
||||
g_session.saveToActiveProject();
|
||||
}
|
||||
|
||||
// --- M11: batch capture (per selected item / per razor area) ----------------
|
||||
//
|
||||
// One action fires N captures — one bank sample per selected item (item scope) or per
|
||||
// razor area (track scope, each area's own range). Each individual capture honors every
|
||||
// precision invariant via captureAndIndexOne (exact bounds, non-destructive FX/fader/pan
|
||||
// neutralize, relative paths, channel preservation) and M10 provenance stamping applies
|
||||
// per capture where its detection rule matches. The load-bearing principle holds: each
|
||||
// unit writes a file + a bank index entry ONLY; nothing lands in the arrange.
|
||||
//
|
||||
// Per-unit FILE NAMING: the offline backend's unique tag is 1-second-granular. The
|
||||
// per-item render already takes real wall-clock time (REAPER's offline-render dialog per
|
||||
// unit), so consecutive units naturally land in distinct seconds; belt-and-braces, each
|
||||
// unit's baseName also carries its ordinal ("item-1", "item-2", ...) so two units are
|
||||
// never asked to write the same stem within one batch. (Residual, DAW-verify: two BATCHES
|
||||
// fired within the same wall-clock second with identical ordinals could still collide —
|
||||
// unreachable in practice given the per-unit render latency, noted for completeness.)
|
||||
|
||||
// RAII snapshot/restore of the project's media-item selection. Batch item capture must
|
||||
// transiently select exactly one item per render (RENDER_SETTINGS &32 renders whatever is
|
||||
// selected); the user's ORIGINAL selection must be restored on EVERY exit path — including
|
||||
// a mid-batch failure or early return — because selection restoration is part of the
|
||||
// non-destructive invariant. Snapshot on construct (the currently-selected item set),
|
||||
// restore on destruct (deselect everything, then re-select exactly the snapshot).
|
||||
class ItemSelectionGuard
|
||||
{
|
||||
public:
|
||||
ItemSelectionGuard()
|
||||
{
|
||||
const int n = CountSelectedMediaItems(nullptr);
|
||||
for (int i = 0; i < n; ++i)
|
||||
if (MediaItem* it = GetSelectedMediaItem(nullptr, i))
|
||||
selected_.push_back(it);
|
||||
}
|
||||
|
||||
~ItemSelectionGuard()
|
||||
{
|
||||
// Deselect every item in the project, then re-select the snapshot — restoring the
|
||||
// exact original set regardless of what the batch selected in between. Iterate ALL
|
||||
// items (not just the currently-selected) so any transient selection is cleared.
|
||||
const int total = CountMediaItems(nullptr);
|
||||
for (int i = 0; i < total; ++i)
|
||||
if (MediaItem* it = GetMediaItem(nullptr, i))
|
||||
SetMediaItemSelected(it, false);
|
||||
for (MediaItem* it : selected_)
|
||||
SetMediaItemSelected(it, true);
|
||||
UpdateArrange(); // reflect the restored selection in the arrange view
|
||||
}
|
||||
|
||||
ItemSelectionGuard(const ItemSelectionGuard&) = delete;
|
||||
ItemSelectionGuard& operator=(const ItemSelectionGuard&) = delete;
|
||||
|
||||
private:
|
||||
std::vector<MediaItem*> selected_;
|
||||
};
|
||||
|
||||
// Selects exactly `item` (deselect-all then select-one) so the offline render's
|
||||
// selected-items bit (&32) captures a single item. Used inside the batch loop under the
|
||||
// ItemSelectionGuard, which restores the user's original selection afterward.
|
||||
static void selectOnlyItem(MediaItem* item)
|
||||
{
|
||||
const int total = CountMediaItems(nullptr);
|
||||
for (int i = 0; i < total; ++i)
|
||||
if (MediaItem* it = GetMediaItem(nullptr, i))
|
||||
SetMediaItemSelected(it, it == item);
|
||||
}
|
||||
|
||||
// Batch item capture: one bank sample per SELECTED item, item scope. Snapshots the
|
||||
// selection (RAII restore on every path), then for each selected item transiently selects
|
||||
// only it, renders its exact [pos, pos+len] range under item-scope FX neutralize, adds the
|
||||
// Sample, and records a per-unit verdict. Persists ONCE at the end (one ext-state write for
|
||||
// the whole batch). Reports a mixed-result summary (explicit-action response — allowed).
|
||||
static void RunBatchCaptureItems()
|
||||
{
|
||||
// Read the selected items up front (pointers stay valid — batch mutates only selection
|
||||
// flags, never adds/removes items). Also capture each item's exact bounds and owning
|
||||
// track NOW, while the full selection is live, before any transient re-selection.
|
||||
struct ItemUnit { MediaItem* item; MediaTrack* track; double start; double end; };
|
||||
std::vector<ItemUnit> itemUnits;
|
||||
{
|
||||
const int n = CountSelectedMediaItems(nullptr);
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
MediaItem* it = GetSelectedMediaItem(nullptr, i);
|
||||
if (!it) continue;
|
||||
MediaTrack* tr = GetMediaItem_Track(it);
|
||||
if (!tr) continue;
|
||||
const double pos = GetMediaItemInfo_Value(it, "D_POSITION");
|
||||
const double len = GetMediaItemInfo_Value(it, "D_LENGTH");
|
||||
itemUnits.push_back({it, tr, pos, pos + len});
|
||||
}
|
||||
}
|
||||
if (itemUnits.empty())
|
||||
{
|
||||
ShowConsoleMsg("ReaSampler batch capture: select at least one media item.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Plan the exact source ranges -> validated, ordinal-assigned units (pure). Empty/
|
||||
// inverted item ranges (a zero-length item) are dropped here so no stray render runs.
|
||||
std::vector<reasampler::BatchRange> ranges;
|
||||
ranges.reserve(itemUnits.size());
|
||||
for (const ItemUnit& u : itemUnits)
|
||||
ranges.push_back({u.start, u.end});
|
||||
const std::vector<reasampler::CaptureUnit> plan = reasampler::planCaptureUnits(ranges);
|
||||
|
||||
reasampler::BatchOutcome outcome;
|
||||
bool anyAdded = false;
|
||||
{
|
||||
// Restore the user's ORIGINAL item selection on every exit path (incl. early
|
||||
// return / mid-batch failure) — non-destructive invariant.
|
||||
ItemSelectionGuard selGuard;
|
||||
|
||||
// The plan and itemUnits are parallel over the KEPT units. Walk itemUnits, but only
|
||||
// for those whose range survived planning (same drop rule), matching by ordinal.
|
||||
std::size_t planIdx = 0;
|
||||
for (const ItemUnit& u : itemUnits)
|
||||
{
|
||||
if (!(u.end > u.start)) continue; // dropped by planCaptureUnits — skip in lockstep
|
||||
const reasampler::CaptureUnit& unit = plan[planIdx++];
|
||||
|
||||
// Transiently select ONLY this item so the item-scope render captures exactly it.
|
||||
selectOnlyItem(u.item);
|
||||
|
||||
ResolvedSource src;
|
||||
src.startSeconds = unit.startSeconds;
|
||||
src.endSeconds = unit.endSeconds;
|
||||
src.sourceTracks.push_back(u.track);
|
||||
if (std::string g = reasampler::guidString(u.track); !g.empty())
|
||||
src.trackGuids.push_back(std::move(g));
|
||||
|
||||
const std::string baseName = "item-" + std::to_string(unit.ordinal);
|
||||
reasampler::CaptureResult res = captureAndIndexOne(
|
||||
reasampler::CaptureScope::Item, src, baseName,
|
||||
unit.startSeconds, unit.endSeconds);
|
||||
|
||||
const bool ok = (res.status == reasampler::CaptureStatus::Ok);
|
||||
outcome.record(unit.ordinal, ok, ok ? std::string{} : res.message);
|
||||
if (ok) anyAdded = true;
|
||||
}
|
||||
} // selGuard restores the original selection here, on every path
|
||||
|
||||
// Persist ONCE for the whole batch (one ext-state write) — only if something landed.
|
||||
if (anyAdded)
|
||||
g_session.saveToActiveProject();
|
||||
|
||||
ShowConsoleMsg((outcome.summaryLine("item") + "\n").c_str());
|
||||
}
|
||||
|
||||
// Collects every track's razor AUDIO areas as (owning track, range) pairs, preserving
|
||||
// track order then area order — the batch analog of resolveRazorRange, which unions them.
|
||||
// Read-only (never clears the razor selection). Reuses the pure parseRazorEdits parser.
|
||||
static std::vector<std::pair<MediaTrack*, reasampler::RazorRange>> collectRazorAreas()
|
||||
{
|
||||
std::vector<std::pair<MediaTrack*, reasampler::RazorRange>> areas;
|
||||
const int n = CountTracks(nullptr);
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
MediaTrack* tr = GetTrack(nullptr, i);
|
||||
if (!tr) continue;
|
||||
std::vector<char> buf(8192, '\0');
|
||||
if (!GetSetMediaTrackInfo_String(tr, "P_RAZOREDITS", buf.data(), false))
|
||||
continue;
|
||||
for (const reasampler::RazorRange& r :
|
||||
reasampler::parseRazorEdits(std::string(buf.data())))
|
||||
areas.push_back({tr, r});
|
||||
}
|
||||
return areas;
|
||||
}
|
||||
|
||||
// RAII snapshot/restore of the project's TRACK selection. Batch razor capture must
|
||||
// transiently select exactly the area's owning track per render (track scope's &128 bit
|
||||
// renders whatever TRACKS are selected); the user's original track selection is restored
|
||||
// on EVERY exit path (part of the non-destructive invariant). Mirror of ItemSelectionGuard.
|
||||
class TrackSelectionGuard
|
||||
{
|
||||
public:
|
||||
TrackSelectionGuard()
|
||||
{
|
||||
const int n = CountSelectedTracks(nullptr);
|
||||
for (int i = 0; i < n; ++i)
|
||||
if (MediaTrack* tr = GetSelectedTrack(nullptr, i))
|
||||
selected_.push_back(tr);
|
||||
}
|
||||
|
||||
~TrackSelectionGuard()
|
||||
{
|
||||
// Deselect every track, then re-select the snapshot — the exact original set.
|
||||
const int total = CountTracks(nullptr);
|
||||
for (int i = 0; i < total; ++i)
|
||||
if (MediaTrack* tr = GetTrack(nullptr, i))
|
||||
SetTrackSelected(tr, false);
|
||||
for (MediaTrack* tr : selected_)
|
||||
SetTrackSelected(tr, true);
|
||||
}
|
||||
|
||||
TrackSelectionGuard(const TrackSelectionGuard&) = delete;
|
||||
TrackSelectionGuard& operator=(const TrackSelectionGuard&) = delete;
|
||||
|
||||
private:
|
||||
std::vector<MediaTrack*> selected_;
|
||||
};
|
||||
|
||||
// Batch razor capture: one bank sample per razor AREA, track scope over that area's own
|
||||
// range (the area's owning track is the source track). Track scope renders the selected
|
||||
// TRACKS via master (&128), so each unit transiently selects ONLY its owning track
|
||||
// (SetOnlyTrackSelected) under the TrackSelectionGuard, which restores the user's original
|
||||
// track selection on every path. The razor selection itself is read-only and left intact.
|
||||
// Persists ONCE at the end. Reports a mixed-result summary.
|
||||
static void RunBatchCaptureRazor()
|
||||
{
|
||||
const std::vector<std::pair<MediaTrack*, reasampler::RazorRange>> areas =
|
||||
collectRazorAreas();
|
||||
if (areas.empty())
|
||||
{
|
||||
ShowConsoleMsg("ReaSampler batch capture: make at least one razor area first.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<reasampler::BatchRange> ranges;
|
||||
ranges.reserve(areas.size());
|
||||
for (const auto& a : areas)
|
||||
ranges.push_back({a.second.startSeconds, a.second.endSeconds});
|
||||
const std::vector<reasampler::CaptureUnit> plan = reasampler::planCaptureUnits(ranges);
|
||||
|
||||
reasampler::BatchOutcome outcome;
|
||||
bool anyAdded = false;
|
||||
{
|
||||
// Restore the user's ORIGINAL track selection on every exit path.
|
||||
TrackSelectionGuard selGuard;
|
||||
|
||||
std::size_t planIdx = 0;
|
||||
for (const auto& a : areas)
|
||||
{
|
||||
if (!(a.second.endSeconds > a.second.startSeconds)) continue; // dropped — lockstep
|
||||
const reasampler::CaptureUnit& unit = plan[planIdx++];
|
||||
MediaTrack* tr = a.first;
|
||||
|
||||
// Transiently select ONLY this track so the track-scope render (&128) captures
|
||||
// exactly it via master (over the custom time bounds we set per unit).
|
||||
SetOnlyTrackSelected(tr);
|
||||
|
||||
ResolvedSource src;
|
||||
src.startSeconds = unit.startSeconds;
|
||||
src.endSeconds = unit.endSeconds;
|
||||
src.sourceTracks.push_back(tr);
|
||||
if (std::string g = reasampler::guidString(tr); !g.empty())
|
||||
src.trackGuids.push_back(std::move(g));
|
||||
|
||||
const std::string baseName = "razor-" + std::to_string(unit.ordinal);
|
||||
reasampler::CaptureResult res = captureAndIndexOne(
|
||||
reasampler::CaptureScope::Track, src, baseName,
|
||||
unit.startSeconds, unit.endSeconds);
|
||||
|
||||
const bool ok = (res.status == reasampler::CaptureStatus::Ok);
|
||||
outcome.record(unit.ordinal, ok, ok ? std::string{} : res.message);
|
||||
if (ok) anyAdded = true;
|
||||
}
|
||||
} // selGuard restores the original track selection here, on every path
|
||||
|
||||
if (anyAdded)
|
||||
g_session.saveToActiveProject();
|
||||
|
||||
ShowConsoleMsg((outcome.summaryLine("razor area") + "\n").c_str());
|
||||
}
|
||||
|
||||
// --- M10: re-capture from source --------------------------------------------
|
||||
//
|
||||
// Regenerates a PROVENANCED bank sample's file from its recorded source's CURRENT
|
||||
@@ -1124,6 +1422,8 @@ static bool OnHookCommand(int command, int /*flag*/)
|
||||
if (command == g_cmdToggleBankPanel) { reasampler::bankPanelToggle(); 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; }
|
||||
if (command == g_cmdCaptureBatchRazor) { RunBatchCaptureRazor(); return true; }
|
||||
if (command == g_cmdCaptureTrackRealtime) { RunCaptureRealtimeTrack(); return true; }
|
||||
if (command == g_cmdCancelRealtime) { RunCancelRealtime(); return true; }
|
||||
if (command == g_cmdRecaptureFromSource) { RunRecaptureFromSource(); return true; }
|
||||
@@ -1155,6 +1455,8 @@ static int OnToggleAction(int command)
|
||||
static gaccel_register_t g_accelToggleBankPanel{};
|
||||
static gaccel_register_t g_accelInsertSelected{};
|
||||
static gaccel_register_t g_accelInsertSelectedConform{};
|
||||
static gaccel_register_t g_accelCaptureBatchItems{};
|
||||
static gaccel_register_t g_accelCaptureBatchRazor{};
|
||||
static gaccel_register_t g_accelCaptureTrackRealtime{};
|
||||
static gaccel_register_t g_accelCancelRealtime{};
|
||||
static gaccel_register_t g_accelRecaptureFromSource{};
|
||||
@@ -1166,6 +1468,8 @@ static gaccel_register_t g_accelShowVersion{};
|
||||
static std::string g_descToggleBankPanel;
|
||||
static std::string g_descInsertSelected;
|
||||
static std::string g_descInsertSelectedConform;
|
||||
static std::string g_descCaptureBatchItems;
|
||||
static std::string g_descCaptureBatchRazor;
|
||||
static std::string g_descCaptureTrackRealtime;
|
||||
static std::string g_descCancelRealtime;
|
||||
static std::string g_descRecaptureFromSource;
|
||||
@@ -1176,6 +1480,8 @@ static std::string g_descShowVersion;
|
||||
static const char* g_idToggleBankPanel = nullptr;
|
||||
static const char* g_idInsertSelected = nullptr;
|
||||
static const char* g_idInsertSelectedConform = nullptr;
|
||||
static const char* g_idCaptureBatchItems = nullptr;
|
||||
static const char* g_idCaptureBatchRazor = nullptr;
|
||||
static const char* g_idCaptureTrackRealtime = nullptr;
|
||||
static const char* g_idCancelRealtime = nullptr;
|
||||
static const char* g_idRecaptureFromSource = nullptr;
|
||||
@@ -1221,6 +1527,10 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
g_rec->Register("-command_id", (void*)g_idCancelRealtime);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelCaptureTrackRealtime);
|
||||
g_rec->Register("-command_id", (void*)g_idCaptureTrackRealtime);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelCaptureBatchRazor);
|
||||
g_rec->Register("-command_id", (void*)g_idCaptureBatchRazor);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelCaptureBatchItems);
|
||||
g_rec->Register("-command_id", (void*)g_idCaptureBatchItems);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelInsertSelectedConform);
|
||||
g_rec->Register("-command_id", (void*)g_idInsertSelectedConform);
|
||||
g_rec->Register("-gaccel", (void*)&g_accelInsertSelected);
|
||||
@@ -1340,6 +1650,32 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
rec->Register("gaccel", (void*)&g_accelInsertSelectedConform);
|
||||
}
|
||||
|
||||
// Register the M11 batch-capture actions (command_id -> gaccel -> hookcommand). Each
|
||||
// fires N captures (one bank sample per selected item / per razor area), honoring every
|
||||
// precision invariant per unit and restoring the original selection on every path.
|
||||
// Channel-qualified FOREVER-STABLE ids.
|
||||
g_idCaptureBatchItems = internCmdId("CAPTURE_BATCH_ITEMS");
|
||||
g_cmdCaptureBatchItems = rec->Register("command_id", (void*)g_idCaptureBatchItems);
|
||||
if (g_cmdCaptureBatchItems)
|
||||
{
|
||||
g_descCaptureBatchItems =
|
||||
reasampler::channelActionName("batch capture selected items (one per item)");
|
||||
g_accelCaptureBatchItems.accel.cmd = g_cmdCaptureBatchItems;
|
||||
g_accelCaptureBatchItems.desc = g_descCaptureBatchItems.c_str();
|
||||
rec->Register("gaccel", (void*)&g_accelCaptureBatchItems);
|
||||
}
|
||||
|
||||
g_idCaptureBatchRazor = internCmdId("CAPTURE_BATCH_RAZOR");
|
||||
g_cmdCaptureBatchRazor = rec->Register("command_id", (void*)g_idCaptureBatchRazor);
|
||||
if (g_cmdCaptureBatchRazor)
|
||||
{
|
||||
g_descCaptureBatchRazor =
|
||||
reasampler::channelActionName("batch capture razor areas (one per area)");
|
||||
g_accelCaptureBatchRazor.accel.cmd = g_cmdCaptureBatchRazor;
|
||||
g_accelCaptureBatchRazor.desc = g_descCaptureBatchRazor.c_str();
|
||||
rec->Register("gaccel", (void*)&g_accelCaptureBatchRazor);
|
||||
}
|
||||
|
||||
// Register the "capture selected track (realtime)" action (command_id -> gaccel ->
|
||||
// hookcommand). Realtime sibling of the offline CAPTURE_TRACK scope: records the
|
||||
// selected track's own output in realtime into a hidden temp track, moves it into
|
||||
|
||||
Reference in New Issue
Block a user