Files
reasampler/src/shell/capture/render_bounds_gate.cpp
T
daniel 0ab4673887 Fix eight review findings on the render-bounds diagnostics
Corrects a false comment example, fixes two tests that couldn't detect their
own regressions, adds two more read-back checkpoints around Main_OnCommand so
a drift report self-locates, guards a spurious zero-vs-zero coincidence match,
and softens two sentences that overclaimed cause or defect.
2026-08-02 14:45:42 -04:00

132 lines
6.1 KiB
C++

// render_bounds_gate.cpp — see the header.
#include "shell/capture/render_bounds_gate.h"
#include <cmath>
#include <cstdint>
#include <filesystem>
#include <system_error>
#include <vector>
#include "core/capture/render_settings.h"
#include "core/capture/render_window.h"
#include "core/capture/wav_codec.h"
#include "core/util/file_bytes.h"
namespace reasampler::capture {
namespace {
// Deliberately a sibling of the bank folder, never inside it: prune enumerates the bank
// directory, and a refused render must be reachable by the person debugging it and by
// nothing else.
constexpr const char* kRefusedSubfolder = "reasampler_refused";
// Moves a refused render out of the bank and returns the sentence naming where it went.
// A failed move leaves the file where the renderer wrote it and says so — never a path
// that does not exist.
std::string retainRefusedRender(const std::string& renderedPath,
const std::string& projectDir) {
namespace fs = std::filesystem;
std::error_code ec;
const std::string dir = projectDir + "/" + kRefusedSubfolder;
fs::create_directories(dir, ec);
if (!ec) {
const std::string dest =
dir + "/" + fs::path(renderedPath).filename().string();
fs::rename(renderedPath, dest, ec);
if (!ec)
return " Nothing was added to the bank. The refused render was KEPT for "
"diagnosis at " + dest + " -- outside the bank, indexed by nothing; "
"delete it when done.";
}
return " Nothing was added to the bank. The refused render was kept for diagnosis "
"but could not be moved out of the bank folder; it is still at " +
renderedPath + ", indexed by nothing. Delete it when done.";
}
} // namespace
std::string refusedRenderFolder(const std::string& projectDir) {
return projectDir + "/" + kRefusedSubfolder;
}
BoundsVerdict checkRenderedFileNotEmpty(const std::string& renderedPath,
const std::string& projectDir) {
BoundsVerdict v;
std::error_code ec;
const std::uintmax_t size = std::filesystem::file_size(renderedPath, ec);
if (ec || size != 0) return v; // stat failure isn't this check's job — leave it be
v.refused = true;
v.message = "Render at " + renderedPath + " is 0 bytes -- REAPER produced an empty "
"file, so there is nothing to check the requested range against." +
retainRefusedRender(renderedPath, projectDir);
return v;
}
BoundsVerdict checkRenderedBounds(const std::string& renderedPath,
const std::string& projectDir,
const CaptureRequest& request) {
BoundsVerdict v;
if (request.tailMode != TailMode::None) return v;
// Whole-file read, not a bounded/header-only one: parseWavLayout marks the data
// chunk valid only when the buffer holds its FULL declared body, so a truncated
// read would read as invalid on every real capture — and invalid refuses here.
const WavLayout layout = parseWavLayout(util::readFileBytes(renderedPath));
// Why the refusal names the render source at all: render_settings.h's renderSourceLabel.
const std::string source =
std::string(" Render source: ") + renderSourceLabel(request.sourceMode) + ".";
// An unmeasurable render used to SKIP this gate and land in the bank with an
// unknown channel count. It is a refusal now: exact bounds cannot be asserted over
// a file whose frames were never counted.
if (!layout.valid || layout.sampleRate == 0) {
v.refused = true;
v.message = "Render at " + renderedPath + " could not be measured -- it could "
"not be read (locked, missing, or a permissions error), its WAV "
"header did not parse, or it declared no sample rate -- so the "
"frames it holds were never checked against the requested range." +
source + retainRefusedRender(renderedPath, projectDir);
return v;
}
const int rate = static_cast<int>(layout.sampleRate);
const long long expectedFrames =
frameCountFor(request.startSeconds, request.endSeconds, rate);
const long long actualFrames = static_cast<long long>(layout.frameCount());
if (renderHonoredBounds(expectedFrames, actualFrames)) return v;
// Says whether this shortfall has the one shape two live short renders already
// matched to the frame: the END alone floored to the millisecond. Checked against
// the END only -- a refusal whose START is also off-grid and independently floored
// would not match this shape, and this note's silence on that refusal is this
// check not covering it, not the coincidence breaking. Excludes 0, which every
// sub-millisecond window (a legitimate day-one capture) also floors to, and which
// would otherwise match a render that produced nothing. A count coincidence only —
// it does not establish how the render resolved anything.
const long long msFlooredEnd =
msFlooredEndFrameCount(request.startSeconds, request.endSeconds, rate);
const std::string msNote =
(msFlooredEnd > 0 && actualFrames == msFlooredEnd)
? " Those are exactly the frames this window holds with its end floored to"
" the millisecond -- a match on the count, not a measured cause."
: std::string();
v.refused = true;
v.message = "Render produced " + std::to_string(actualFrames) +
" frames but the requested range is " + std::to_string(expectedFrames) +
" at " + std::to_string(rate) +
" Hz -- the render did not honor the requested bounds." + source +
" Requested [" + std::to_string(request.startSeconds) + "s, " +
std::to_string(request.endSeconds) + "s) -> frame indices [" +
std::to_string(std::llround(request.startSeconds * rate)) + ", " +
std::to_string(std::llround(request.endSeconds * rate)) + ")." +
msNote + retainRefusedRender(renderedPath, projectDir);
return v;
}
} // namespace reasampler::capture