0ab4673887
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.
132 lines
6.1 KiB
C++
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
|