capture: state the bounds tolerance as empirical, refuse unmeasurable renders, keep refused ones for diagnosis
The one-frame bound is not provable for a per-edge renderer; the test now shows where it breaks. Refused renders move out of the bank instead of being deleted, so the DAW experiment has something to read.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
// render_bounds_gate.cpp — see the header.
|
||||
|
||||
#include "shell/capture/render_bounds_gate.h"
|
||||
|
||||
#include <cmath>
|
||||
#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
|
||||
|
||||
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 -- its WAV "
|
||||
"header did not parse, or 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;
|
||||
|
||||
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)) + ")." +
|
||||
retainRefusedRender(renderedPath, projectDir);
|
||||
return v;
|
||||
}
|
||||
|
||||
} // namespace reasampler::capture
|
||||
Reference in New Issue
Block a user