Ξ-W2-T1 re-review cleanup: soften ordering claim, bound bake guard fields, dedup gain/play-mode rationale, quiet foreign WrongProject noise

This commit is contained in:
2026-08-01 17:23:00 -04:00
parent 39c2d1cdb4
commit a09e45fc1d
8 changed files with 51 additions and 26 deletions
+4 -9
View File
@@ -21,10 +21,8 @@ decision about what the render made obsolete.
loop runs to `BakePlan::renderFrames()` and stops. That is why a Gate bake with a sustain
loop active terminates: the gate is released at `noteOffFrame` so the tail is real, but
even a pathological envelope cannot run past the window.
- **The whole signal chain is printed, master gain included.** `renderBake` scales its
output by the dialed post-mixer gain, because `resetAfterBake` hands that control back at
unity. A render that summed voices alone would return every iteration shifted by 1/gain,
and a gain dialed to silence would come back at full level.
- **The whole signal chain is printed, master gain included** — the gain multiply in
`bake_render.cpp` carries the argument for why.
- **A degenerate or unholdable window is refused, not rendered.** `planBake` returns nullopt
for a collapsed window, a non-positive rate, a window that rounds to no frames, and one
past `kMaxBakeFrames` — an unbounded window is a `bad_alloc` inside a UI tick, and the
@@ -35,11 +33,8 @@ decision about what the render made obsolete.
under-resetting applies the same processing twice while over-resetting costs a re-dial.
A new mapping fact must be added to the copy list explicitly.
- **Play mode resets to TRIGGER, not to the value struct's Gate default** — the one
classification this track made against the ratified rule rather than reading off it. The
bake's product is a finished one-shot, and Trigger is the mode that plays a finished
one-shot verbatim; Gate would re-gate the printed release tail and each iteration would
truncate the previous one's. "Neutral" here means "adds no processing", not "the struct's
own default". `bake_reset.cpp` carries the argument at the assignment.
classification this track made against the ratified rule rather than reading off it.
`bake_reset.cpp` carries the argument at the assignment.
## Modules
+13 -3
View File
@@ -21,7 +21,14 @@ constexpr std::int64_t kBlockFrames = 512;
BakeAudio renderBake(SampleData sample, const BakePlan& plan, double masterGainLinear) {
BakeAudio out;
if (!sample.playable() || plan.totalFrames <= 0 || plan.sampleRate <= 0) return out;
if (plan.leadInFrames < 0 || plan.renderFrames() > kMaxBakeFrames) return out;
// Each field bounded BEFORE the sum: renderFrames() adds them, and a hand-built plan
// (planBake already bounds both — bake_plan.cpp) could otherwise carry leadInFrames
// near INT64_MAX and signed-overflow inside the guard meant to catch exactly that.
if (plan.leadInFrames < 0 || plan.leadInFrames > kMaxBakeFrames ||
plan.totalFrames > kMaxBakeFrames) {
return out;
}
if (plan.renderFrames() > kMaxBakeFrames) return out;
// The live block is the audio thread's moving target; a render that observed it would
// depend on what the user happened to be dragging. The dialed values are already in
@@ -67,8 +74,11 @@ BakeAudio renderBake(SampleData sample, const BakePlan& plan, double masterGainL
const auto lead = static_cast<std::size_t>(plan.leadInFrames);
const auto total = static_cast<std::size_t>(plan.totalFrames);
out.interleaved.resize(total * static_cast<std::size_t>(channels));
// A flat multiply, not the processor's per-sample ramp: the gain is constant for the
// whole render, which is exactly what that ramp exists to converge to.
// Printed here rather than left for the processor: resetAfterBake hands master gain
// back to unity, so a render that only summed voices would return every iteration
// shifted by 1/gain, and a gain dialed to silence would come back at full level. A flat
// multiply, not the processor's per-sample ramp: the gain is constant for the whole
// render, which is exactly what that ramp exists to converge to.
const auto gain = static_cast<AudioSample>(masterGainLinear);
for (std::size_t f = 0; f < total; ++f) {
out.interleaved[f * channels] = left[lead + f] * gain;
+4 -3
View File
@@ -30,9 +30,10 @@ struct BakeAudio {
};
// Renders `plan` through `sample`'s own voice path, scaled by `masterGainLinear` — the
// post-mixer gain the processor applies after the engine, printed here because the bake's
// reset hands that control back at unity. The result is the plan's captured window: the
// lead-in frames are rendered and dropped. An unplayable sample yields an empty result.
// post-mixer gain the processor applies after the engine; see the gain multiply in
// bake_render.cpp for why it is printed here rather than left to the processor. The result
// is the plan's captured window: the lead-in frames are rendered and dropped. An unplayable
// sample yields an empty result.
BakeAudio renderBake(SampleData sample, const BakePlan& plan, double masterGainLinear);
} // namespace reasampler::instrument::bake
+2 -2
View File
@@ -11,8 +11,8 @@
namespace reasampler::instrument::bake {
// The two surfaces a bake resets. Master gain lives on the processor rather than in the
// parameter set, but renderBake prints it into the file, so it belongs to the same decision
// and is answered here rather than left to the shell.
// parameter set; it is answered here because renderBake prints it into the file (see
// bake_render.cpp's gain multiply) rather than left to the shell.
struct BakeReset {
map::InstrumentParams params;
double masterGainLinear = 1.0; // unity — renderBake printed the dialed gain