Ξ-W2-T1 remediation: print master gain into the bake, derive the window from the dialed sound, reset play mode to Trigger
This commit is contained in:
@@ -6,11 +6,13 @@
|
||||
#include "shell/capture/bake_land.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "core/capture/capture_paths.h" // deriveBankPaths / projectDirOfRpp
|
||||
@@ -51,6 +53,13 @@ using wire::BakeOutcome;
|
||||
using wire::BakeRequest;
|
||||
using wire::BakeStatus;
|
||||
|
||||
// The whole chain is a call and a return inside ONE editor tick, so a request older than
|
||||
// this has no reader left: it is a crash leftover, and it is CLEARED rather than landed.
|
||||
// Without the guard a stranded request would be banked on the next unrelated instance's
|
||||
// click, and the outcome written back to a key nobody will collect would persist into the
|
||||
// .rpp forever.
|
||||
constexpr std::int64_t kMaxRequestAgeSeconds = 30;
|
||||
|
||||
BakeOutcome refuse(BakeStatus status, std::string message, std::int64_t generation) {
|
||||
BakeOutcome out;
|
||||
out.status = status;
|
||||
@@ -59,12 +68,6 @@ BakeOutcome refuse(BakeStatus status, std::string message, std::int64_t generati
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string currentProjectDir() {
|
||||
std::vector<char> buf(4096, '\0');
|
||||
EnumProjects(-1, buf.data(), static_cast<int>(buf.size()));
|
||||
return projectDirOfRpp(std::string(buf.data()));
|
||||
}
|
||||
|
||||
bool writeFileBytes(const std::string& path, const std::vector<std::uint8_t>& bytes) {
|
||||
std::ofstream f(path, std::ios::binary | std::ios::trunc);
|
||||
if (!f) return false;
|
||||
@@ -85,11 +88,12 @@ const Sample* findSourceSample(const BankBook& book, const std::string& sampleId
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Lands ONE request. Mutates the book + ledger without persisting; the caller persists
|
||||
// once for the batch. Every refusal path leaves the book untouched and writes no file, so
|
||||
// a failed bake is invisible to the project.
|
||||
BakeOutcome landOne(ReaSamplerSession& session, const BakeRequest& request) {
|
||||
const std::string projectDir = currentProjectDir();
|
||||
// Lands ONE request into `session`'s book, which must already be the book of the project
|
||||
// `projectDir` names. Mutates book + ledger without persisting; the caller persists once for
|
||||
// the batch. Every refusal path leaves the book untouched and writes no file, so a failed
|
||||
// bake is invisible to the project.
|
||||
BakeOutcome landOne(ReaSamplerSession& session, const std::string& projectDir,
|
||||
const BakeRequest& request) {
|
||||
if (projectDir.empty())
|
||||
return refuse(BakeStatus::NoProject,
|
||||
"no saved project, so the bank has no location", request.generation);
|
||||
@@ -190,9 +194,9 @@ BakeOutcome landOne(ReaSamplerSession& session, const BakeRequest& request) {
|
||||
: (book.index(bankId) &&
|
||||
book.index(bankId)->add(landed) == AddResult::Added);
|
||||
if (!indexed) {
|
||||
// Self-cleanup of a file this call wrote seconds ago and never indexed — not the
|
||||
// prune's deletion authority, which governs files the bank knows about. Leaving it
|
||||
// would deposit an untracked orphan for every refused bake.
|
||||
// Self-cleanup of a file this call wrote seconds ago and never indexed — the
|
||||
// carve-out shell/persist/CLAUDE.md states, not prune's authority over the bank's
|
||||
// known bytes. Leaving it would deposit an untracked orphan per refused bake.
|
||||
fs::remove(destPath, ec);
|
||||
return refuse(BakeStatus::IndexRejected,
|
||||
replace ? "the bank refused the replacement"
|
||||
@@ -209,7 +213,7 @@ BakeOutcome landOne(ReaSamplerSession& session, const BakeRequest& request) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Every "rsbake_*" key in the project, keys only — the value can outgrow
|
||||
// Every "rsbake_*" key in one project, keys only — the value can outgrow
|
||||
// EnumProjExtState's fixed buffer, so it is read separately by the growing reader.
|
||||
std::vector<std::string> pendingBakeKeys(ReaProject* proj) {
|
||||
std::vector<std::string> keys;
|
||||
@@ -234,28 +238,82 @@ std::optional<std::string> readKey(ReaProject* proj, const std::string& key) {
|
||||
return read.value;
|
||||
}
|
||||
|
||||
struct OpenProject {
|
||||
ReaProject* proj = nullptr;
|
||||
std::string dir; // the project's own directory; empty for a never-saved project
|
||||
};
|
||||
|
||||
// Every open project tab. The request key lives in the INSTANCE's project, which is not
|
||||
// necessarily the focused one, so the scan cannot assume the current tab holds it.
|
||||
std::vector<OpenProject> openProjects() {
|
||||
std::vector<OpenProject> out;
|
||||
std::vector<char> buf(4096, '\0');
|
||||
for (int idx = 0;; ++idx) {
|
||||
buf[0] = '\0';
|
||||
ReaProject* proj = EnumProjects(idx, buf.data(), static_cast<int>(buf.size()));
|
||||
if (!proj) break;
|
||||
out.push_back(OpenProject{proj, projectDirOfRpp(std::string(buf.data()))});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// One answer to write back after the undo block closes.
|
||||
struct Answer {
|
||||
ReaProject* proj = nullptr;
|
||||
std::string key;
|
||||
std::string wire; // empty = clear the key instead of answering it
|
||||
std::string console; // empty = nothing to print
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void RunResampleBake(ReaSamplerSession& session) {
|
||||
ReaProject* proj = EnumProjects(-1, nullptr, 0);
|
||||
if (!proj) return;
|
||||
// Three projects have to agree before anything may land: the tab the request was found
|
||||
// in, the tab whose book/ledger poll() last loaded, and the tab saveToActiveProject
|
||||
// will persist into. Land on a disagreement and one tab's bake is written into
|
||||
// another's bank. Whether REAPER makes Main_OnCommandEx's `proj` current for the
|
||||
// action's duration is unverified in the DAW; this holds either way, and a request it
|
||||
// cannot land is told why rather than silently ignored.
|
||||
const void* loaded = session.loadedProject();
|
||||
const void* active = EnumProjects(-1, nullptr, 0);
|
||||
const bool sessionUsable = loaded != nullptr && loaded == active;
|
||||
const std::int64_t nowSec = static_cast<std::int64_t>(std::time(nullptr));
|
||||
|
||||
std::vector<Answer> answers;
|
||||
int landedCount = 0;
|
||||
Undo_BeginBlock2(nullptr);
|
||||
for (const std::string& key : pendingBakeKeys(proj)) {
|
||||
const std::optional<std::string> raw = readKey(proj, key);
|
||||
if (!raw) continue;
|
||||
const std::optional<BakeRequest> request = wire::decodeBakeRequest(*raw);
|
||||
// Not a request: an outcome this instance has not yet collected, or a value from a
|
||||
// build we do not read. Leave it — the writing instance owns clearing its own key.
|
||||
if (!request) continue;
|
||||
for (const OpenProject& open : openProjects()) {
|
||||
for (const std::string& key : pendingBakeKeys(open.proj)) {
|
||||
const std::optional<std::string> raw = readKey(open.proj, key);
|
||||
if (!raw) continue;
|
||||
const std::optional<BakeRequest> request = wire::decodeBakeRequest(*raw);
|
||||
// Not a request: an outcome this instance has not yet collected, or a value from
|
||||
// a build we do not read. Leave it — the writing instance owns clearing its key.
|
||||
if (!request) continue;
|
||||
|
||||
const BakeOutcome outcome = landOne(session, *request);
|
||||
if (outcome.status == BakeStatus::Ok) ++landedCount;
|
||||
SetProjExtState(proj, kProjExtNamespace(), key.c_str(),
|
||||
wire::encodeBakeOutcome(outcome).c_str());
|
||||
if (outcome.status != BakeStatus::Ok)
|
||||
ShowConsoleMsg(("ReaSampler resample: " + outcome.message + ".\n").c_str());
|
||||
// Stale (either direction, so a clock moved backwards is caught too): clear the
|
||||
// key, never answer it. The instance that could read an answer is gone.
|
||||
if (std::llabs(nowSec - request->generation) > kMaxRequestAgeSeconds) {
|
||||
answers.push_back(Answer{open.proj, key, std::string{}, std::string{}});
|
||||
continue;
|
||||
}
|
||||
|
||||
BakeOutcome outcome;
|
||||
if (!sessionUsable || static_cast<const void*>(open.proj) != loaded) {
|
||||
outcome = refuse(BakeStatus::WrongProject,
|
||||
"this bake's project tab is not the one the extension has "
|
||||
"loaded -- focus that tab and try again",
|
||||
request->generation);
|
||||
} else {
|
||||
outcome = landOne(session, open.dir, *request);
|
||||
if (outcome.status == BakeStatus::Ok) ++landedCount;
|
||||
}
|
||||
answers.push_back(Answer{
|
||||
open.proj, key, wire::encodeBakeOutcome(outcome),
|
||||
outcome.status == BakeStatus::Ok
|
||||
? std::string{}
|
||||
: "ReaSampler resample: " + outcome.message + ".\n"});
|
||||
}
|
||||
}
|
||||
|
||||
if (landedCount > 0) {
|
||||
@@ -266,10 +324,19 @@ void RunResampleBake(ReaSamplerSession& session) {
|
||||
Undo_EndBlock2(nullptr,
|
||||
persisted ? "ReaSampler: resample bake into bank" : "",
|
||||
persisted ? UNDO_STATE_MISCCFG : 0);
|
||||
bankPanelRefresh();
|
||||
} else {
|
||||
Undo_EndBlock2(nullptr, "", 0); // nothing landed — record no empty undo point
|
||||
}
|
||||
|
||||
// OUTSIDE the undo block on purpose: the answer is transient handshake state, and an
|
||||
// undo point that captured it could restore the consumed request on the next Ctrl-Z —
|
||||
// which the following bake would then re-land against a temp file that is long gone.
|
||||
for (const Answer& answer : answers) {
|
||||
SetProjExtState(answer.proj, kProjExtNamespace(), answer.key.c_str(),
|
||||
answer.wire.c_str());
|
||||
if (!answer.console.empty()) ShowConsoleMsg(answer.console.c_str());
|
||||
}
|
||||
if (landedCount > 0) bankPanelRefresh();
|
||||
}
|
||||
|
||||
} // namespace reasampler::capture
|
||||
|
||||
Reference in New Issue
Block a user