bake: make the landing scan say what it saw, so a no-answer names its own cause
Splits Ignore into unreadable vs not-a-request and counts every verdict; the report prints only when the pass answered nobody, so its absence proves the action never ran.
This commit is contained in:
@@ -56,7 +56,7 @@ detail not covered there:
|
||||
- `render_selection` (`shell/capture`) — the transient track selection a selected-tracks render (`&128`) requires, as a stack RAII guard: REAPER prints whatever tracks are selected, so `renderOffline` makes the request's own tracks BE the selection for the render's duration and restores the user's set on every exit path. Engaged ONLY for that source mode, which leaves a stated residual: a `&32` selected-items render still prints whatever ITEMS the user has selected. Live captures are unaffected (that selection is the source), but a recipe replay of a `SelectedItems` capture renders against whatever happens to be selected then — the recipe stores tracks and a range, never item GUIDs, so this guard cannot close it. Filed in `docs/TODO.md`.
|
||||
- `render_isolation` (`shell/capture`) — the transient upstream silencing a ranged ITEM render needs, as a stack RAII guard alongside the two above: the selected-tracks source prints everything flowing INTO the track, so each direct folder child's `B_MAINSEND` and each of the track's receives' `B_MUTE` are cut for the render and restored on every exit path. Direct children only — a grandchild reaches the track through the child that owns it. The child-set walk is pure (`core/capture/track_topology`).
|
||||
- `capture_orchestrator` (`shell/capture`) — single-capture orchestration + the realtime/insert action bodies (Q-W3 hoist, T4-02): `renderOffline` (one offline render under the scope's FX-bypass guard), `captureAndIndexOne` (render + provenance stamp + bank add + tracking-ledger record, unpersisted), `RunCapture`/`RunCaptureItemAssign`, `RunCaptureRealtimeTrack`/`RunCancelRealtime` (the realtime action bodies — the in-flight state lives in `realtime_lifecycle`), and `RunInsertSelected` (the ONE deliberate exception to capture-never-places).
|
||||
- `bake_land` (`shell/capture`) — the EXTENSION's half of the resample chain: scans every open project tab for pending `rsbake_*` requests, lands the ones belonging to the project this session has loaded, and refuses the rest with `WrongProject` — one undo point for the batch, each answered over its own key inside the invoking instance's synchronous action call. The per-key verdict itself is NOT this TU's: it is `core/wire`'s pure `classifyBakeScan`, so this shell only enumerates, reads, and applies. It RENDERS NOTHING — the instrument already did, through its own engine in its own process, which is what makes the baked audio the sound the user approved and what keeps the voice engine out of the extension's link graph. Replace-vs-add comes from `tracking::resampleLanding`; a replace keeps the entry's id and slot and never deletes the superseded file. Hash-dedup applies on the add path only, before the disk write, matching `updateSampleInPlace`'s "an in-place refresh is not an insert". A refused index withdraws the bytes this call had just written — the self-cleanup carve-out from prune's deletion authority, stated in `prune_fs.cpp`'s header.
|
||||
- `bake_land` (`shell/capture`) — the EXTENSION's half of the resample chain: scans every open project tab for pending `rsbake_*` requests, lands the ones belonging to the project this session has loaded, and refuses the rest with `WrongProject` — one undo point for the batch, each answered over its own key inside the invoking instance's synchronous action call. The per-key verdict itself is NOT this TU's: it is `core/wire`'s pure `classifyBakeScan`, so this shell only enumerates, reads, and applies — counting every verdict into a `wire::BakeScanTally` as it goes, and printing `wire::describeBakeScan` when the pass answered nobody. Each key is materialized before any answer is written, so no `SetProjExtState` in this action mutates a set the enumerator is still walking. It RENDERS NOTHING — the instrument already did, through its own engine in its own process, which is what makes the baked audio the sound the user approved and what keeps the voice engine out of the extension's link graph. Replace-vs-add comes from `tracking::resampleLanding`; a replace keeps the entry's id and slot and never deletes the superseded file. Hash-dedup applies on the add path only, before the disk write, matching `updateSampleInPlace`'s "an in-place refresh is not an insert". A refused index withdraws the bytes this call had just written — the self-cleanup carve-out from prune's deletion authority, stated in `prune_fs.cpp`'s header.
|
||||
- `capture_batch` (`shell/capture`) — the batch-capture family + re-capture-from-source (Q-W3 hoist, T4-02): `RunBatchCaptureItems` (one sample per selected item), `RunBatchCaptureRazor` (one sample per razor area), `RunRecaptureFromSource` (regenerate a provenanced sample from its recorded source's current state, bank-only). Every unit routes through `capture_orchestrator` so every precision invariant holds; persist is batched to one ext-state write per action.
|
||||
- `realtime_lifecycle` (`shell/capture`) — the in-flight realtime-capture state machine + globals (Q-W3 hoist): the action starts it, `OnTimer` drives it per tick via `DriveRealtimeCapture` (a single-pointer-test idle fast path — load-bearing hot-path guardrail), `CommitRealtimeResult` lands a finished capture in the bank, `AbortRealtimeCaptureForUnload` tears down cleanly on extension unload.
|
||||
- `capture_realtime_shell` (`shell/capture`) — the async realtime-record backend surface (Q-W6 split of the former fat `capture.h`): `RealtimeRecordBackend::begin`/`tick`/`abort`, transport-driven across timer ticks (a realtime record cannot block REAPER's UI for its own duration). Deliberately shares NO interface with the offline backend — the lifecycles genuinely differ (the former `ICaptureBackend` interface was deleted in Q-W3, T4-26).
|
||||
|
||||
@@ -272,22 +272,34 @@ void RunResampleBake(ReaSamplerSession& session) {
|
||||
const std::int64_t nowSec = static_cast<std::int64_t>(std::time(nullptr));
|
||||
|
||||
std::vector<Answer> answers;
|
||||
int landedCount = 0;
|
||||
wire::BakeScanTally tally; // every verdict below is counted, skips included
|
||||
Undo_BeginBlock2(nullptr);
|
||||
for (const OpenProject& open : openProjects()) {
|
||||
++tally.tabsScanned;
|
||||
const bool activeTab = static_cast<const void*>(open.proj) == active;
|
||||
for (const std::string& key : pendingBakeKeys(open.proj)) {
|
||||
++tally.keysFound;
|
||||
if (activeTab) ++tally.activeTabKeys;
|
||||
const std::optional<std::string> raw = readKey(open.proj, key);
|
||||
const std::optional<BakeRequest> request =
|
||||
raw ? wire::decodeBakeRequest(*raw) : std::nullopt;
|
||||
const wire::BakeScanKey scanKey{
|
||||
request.has_value(), request ? request->generation : 0,
|
||||
raw.has_value(), request.has_value(), request ? request->generation : 0,
|
||||
static_cast<const void*>(open.proj) == loaded};
|
||||
const wire::BakeScanVerdict verdict =
|
||||
wire::classifyBakeScan(scanContext, scanKey, nowSec);
|
||||
|
||||
// Leave it — the writing instance owns clearing its own key.
|
||||
if (verdict == wire::BakeScanVerdict::Ignore) continue;
|
||||
if (verdict == wire::BakeScanVerdict::IgnoreUnreadable) {
|
||||
++tally.unreadable;
|
||||
continue;
|
||||
}
|
||||
if (verdict == wire::BakeScanVerdict::IgnoreNotARequest) {
|
||||
++tally.notARequest;
|
||||
continue;
|
||||
}
|
||||
if (verdict == wire::BakeScanVerdict::ClearStale) {
|
||||
++tally.staleCleared;
|
||||
answers.push_back(Answer{open.proj, key, std::string{}, std::string{}});
|
||||
continue;
|
||||
}
|
||||
@@ -300,17 +312,17 @@ void RunResampleBake(ReaSamplerSession& session) {
|
||||
request->generation);
|
||||
} else {
|
||||
outcome = landOne(session, open.dir, *request);
|
||||
if (outcome.status == BakeStatus::Ok) ++landedCount;
|
||||
if (outcome.status == BakeStatus::Ok) ++tally.landed;
|
||||
}
|
||||
++tally.answered;
|
||||
// A WrongProject request left sitting in a tab this call did not come from
|
||||
// prints again on every OTHER tab's bake, since the scan revisits every open
|
||||
// project each time. `active` is this call's own proxy for "the invoking tab" —
|
||||
// only that tab's own refusal is fresh feedback to a user who just clicked
|
||||
// bake; every other one is a rescan repeat.
|
||||
const bool ownRequest = static_cast<const void*>(open.proj) == active;
|
||||
std::string console;
|
||||
if (outcome.status != BakeStatus::Ok &&
|
||||
(outcome.status != BakeStatus::WrongProject || ownRequest)) {
|
||||
(outcome.status != BakeStatus::WrongProject || activeTab)) {
|
||||
console = "ReaSampler resample: " + outcome.message + ".\n";
|
||||
}
|
||||
answers.push_back(
|
||||
@@ -318,7 +330,7 @@ void RunResampleBake(ReaSamplerSession& session) {
|
||||
}
|
||||
}
|
||||
|
||||
if (landedCount > 0) {
|
||||
if (tally.landed > 0) {
|
||||
// A bake changes what a live instance would play, so the generation bump rides the
|
||||
// persist — every other open instance refreshes hands-free.
|
||||
session.bumpBankGeneration();
|
||||
@@ -338,7 +350,10 @@ void RunResampleBake(ReaSamplerSession& session) {
|
||||
answer.wire.c_str());
|
||||
if (!answer.console.empty()) ShowConsoleMsg(answer.console.c_str());
|
||||
}
|
||||
if (landedCount > 0) bankPanelRefresh();
|
||||
// Empty unless the pass answered nobody, so a bake that spoke for itself stays quiet.
|
||||
const std::string report = wire::describeBakeScan(tally);
|
||||
if (!report.empty()) ShowConsoleMsg(report.c_str());
|
||||
if (tally.landed > 0) bankPanelRefresh();
|
||||
}
|
||||
|
||||
} // namespace reasampler::capture
|
||||
|
||||
Reference in New Issue
Block a user