Collapse a dual-mono resample bake to one channel, like every other capture
prepareLanding takes the shared collapse on the staged buffer before the hash and the channel-count read, so hash, entry and file all derive from one buffer. A true-stereo bake stays byte-identical.
This commit is contained in:
+121
-1
@@ -14,7 +14,8 @@
|
||||
// contentHash values against a silent feed-sequence drift); and the lossless mono
|
||||
// collapse (bit-identical N-channel fold, the one-sample-differs and signed-zero
|
||||
// declines, already-mono, zero/single-frame, an odd padded leading chunk, and the
|
||||
// content-hash consequence).
|
||||
// content-hash consequence) plus its buffer-side wrapper applyMonoCollapse (the
|
||||
// bytes/layout pairing, the byte-identical decline, one-ULP, and double-apply).
|
||||
|
||||
#include "../src/core/capture/wav_codec.h"
|
||||
|
||||
@@ -795,6 +796,119 @@ static void testCollapseOutcomeSuffixesAreDistinctStrings() {
|
||||
CHECK(collapsed.find("failed") == std::string::npos);
|
||||
}
|
||||
|
||||
// --- applyMonoCollapse: the buffer-side collapse a bake landing takes ---------
|
||||
|
||||
// A dead-center instrument render is dual-mono, and must land 1-channel: the returned
|
||||
// layout says one channel, and it is the parse OF the returned bytes, so the caller's
|
||||
// channelCount, its hash and the file it writes cannot come from different buffers.
|
||||
static void testApplyCollapseDualMonoLandsOneChannel() {
|
||||
auto wav = buildFloatWav(2, 48000, 6,
|
||||
[](std::size_t f, std::uint16_t) {
|
||||
return 0.25f * static_cast<float>(f) - 0.5f;
|
||||
});
|
||||
const CollapsedWav staged = applyMonoCollapse(wav);
|
||||
CHECK(staged.collapsed);
|
||||
CHECK(staged.layout.valid);
|
||||
CHECK(staged.layout.channelCount == 1);
|
||||
|
||||
const WavLayout reparsed = parseWavLayout(staged.bytes);
|
||||
CHECK(reparsed.valid);
|
||||
CHECK(reparsed.channelCount == staged.layout.channelCount);
|
||||
CHECK(reparsed.sampleRate == staged.layout.sampleRate);
|
||||
CHECK(reparsed.frameCount() == staged.layout.frameCount());
|
||||
CHECK(reparsed.dataByteOffset == staged.layout.dataByteOffset);
|
||||
CHECK(reparsed.dataByteLength == staged.layout.dataByteLength);
|
||||
|
||||
const auto pcm = extractFloatFrames(staged.bytes, staged.layout, 0, 6);
|
||||
CHECK(pcm.size() == 6);
|
||||
for (std::size_t f = 0; f < 6 && f < pcm.size(); ++f)
|
||||
CHECK(pcm[f] == 0.25f * static_cast<float>(f) - 0.5f);
|
||||
}
|
||||
|
||||
// A true-stereo bake must land exactly the bytes it staged — this is the regression the
|
||||
// collapse must not cause, so it is asserted on the bytes themselves, not on the verdict.
|
||||
static void testApplyCollapseTrueStereoIsByteIdentical() {
|
||||
auto wav = buildFloatWav(2, 48000, 5,
|
||||
[](std::size_t f, std::uint16_t ch) {
|
||||
return ch == 0 ? static_cast<float>(f)
|
||||
: -static_cast<float>(f);
|
||||
});
|
||||
const CollapsedWav staged = applyMonoCollapse(wav);
|
||||
CHECK(!staged.collapsed);
|
||||
CHECK(staged.bytes == wav);
|
||||
CHECK(staged.layout.channelCount == 2);
|
||||
CHECK(staged.layout.frameCount() == 5);
|
||||
// The dedup key a declined bake writes is the one it would have written before the
|
||||
// collapse existed.
|
||||
CHECK(hashWavContent(staged.bytes) == hashWavContent(wav));
|
||||
}
|
||||
|
||||
// One float ULP apart in ONE sample is a difference, not an epsilon: the buffer path
|
||||
// must decline it exactly as the predicate does, and hand the bytes back untouched.
|
||||
static void testApplyCollapseDeclinesOnOneUlpDifference() {
|
||||
auto wav = buildFloatWav(2, 48000, 8,
|
||||
[](std::size_t f, std::uint16_t ch) {
|
||||
float v = 1.0f + static_cast<float>(f);
|
||||
if (f == 4 && ch == 1) v = nextafterf(v, 2.0f);
|
||||
return v;
|
||||
});
|
||||
const CollapsedWav staged = applyMonoCollapse(wav);
|
||||
CHECK(!staged.collapsed);
|
||||
CHECK(staged.bytes == wav);
|
||||
CHECK(staged.layout.channelCount == 2);
|
||||
}
|
||||
|
||||
// A sound that was already mono comes back untouched, and a collapsed buffer fed back
|
||||
// through does not collapse a second time (the rebuild would otherwise re-hash).
|
||||
static void testApplyCollapseAlreadyMonoIsUntouched() {
|
||||
auto mono = buildFloatWav(1, 44100, 4,
|
||||
[](std::size_t f, std::uint16_t) {
|
||||
return static_cast<float>(f);
|
||||
});
|
||||
const CollapsedWav staged = applyMonoCollapse(mono);
|
||||
CHECK(!staged.collapsed);
|
||||
CHECK(staged.bytes == mono);
|
||||
CHECK(staged.layout.channelCount == 1);
|
||||
|
||||
auto dual = buildFloatWav(2, 44100, 4,
|
||||
[](std::size_t f, std::uint16_t) {
|
||||
return static_cast<float>(f);
|
||||
});
|
||||
const CollapsedWav once = applyMonoCollapse(dual);
|
||||
CHECK(once.collapsed);
|
||||
const CollapsedWav twice = applyMonoCollapse(once.bytes);
|
||||
CHECK(!twice.collapsed);
|
||||
CHECK(twice.bytes == once.bytes);
|
||||
}
|
||||
|
||||
// The collapse is permitted only because it is lossless: frame count, sample rate and
|
||||
// bit depth survive it, and only the interleave stride changes.
|
||||
static void testApplyCollapsePreservesFramesRateAndDepth() {
|
||||
auto wav = buildFloatWav(2, 44100, 7,
|
||||
[](std::size_t f, std::uint16_t) {
|
||||
return 0.5f - 0.125f * static_cast<float>(f);
|
||||
});
|
||||
const WavLayout before = parseWavLayout(wav);
|
||||
const CollapsedWav staged = applyMonoCollapse(wav);
|
||||
CHECK(staged.collapsed);
|
||||
CHECK(staged.layout.frameCount() == before.frameCount());
|
||||
CHECK(staged.layout.sampleRate == before.sampleRate);
|
||||
// `valid` implies 32-bit float (the parser accepts nothing else), and 4 bytes per
|
||||
// frame at one channel is that depth spelled out in the data chunk's own length.
|
||||
CHECK(staged.layout.valid);
|
||||
CHECK(staged.layout.dataByteLength == before.frameCount() * 4u);
|
||||
}
|
||||
|
||||
// Bytes that never parsed keep `collapsed` false AND `layout.valid` false — the pair a
|
||||
// caller refuses on, and the reason an invalid layout can only mean a bad INPUT.
|
||||
static void testApplyCollapseUnparseableInputIsReportedInvalid() {
|
||||
std::vector<std::uint8_t> junk = {'N','O','P','E', 0,0,0,0, 'W','A','V','E'};
|
||||
const CollapsedWav staged = applyMonoCollapse(junk);
|
||||
CHECK(!staged.collapsed);
|
||||
CHECK(!staged.layout.valid);
|
||||
CHECK(staged.bytes == junk);
|
||||
}
|
||||
|
||||
int main() {
|
||||
testParseCanonicalStereo();
|
||||
testParseMonoAndLeadingChunk();
|
||||
@@ -831,6 +945,12 @@ int main() {
|
||||
testCollapseChangesContentHash();
|
||||
testCollapsePreservesQuietNaNBitPattern();
|
||||
testCollapseOutcomeSuffixesAreDistinctStrings();
|
||||
testApplyCollapseDualMonoLandsOneChannel();
|
||||
testApplyCollapseTrueStereoIsByteIdentical();
|
||||
testApplyCollapseDeclinesOnOneUlpDifference();
|
||||
testApplyCollapseAlreadyMonoIsUntouched();
|
||||
testApplyCollapsePreservesFramesRateAndDepth();
|
||||
testApplyCollapseUnparseableInputIsReportedInvalid();
|
||||
|
||||
if (g_fail == 0) std::printf("wav_codec: all tests passed\n");
|
||||
else std::printf("wav_codec: %d CHECK(s) FAILED\n", g_fail);
|
||||
|
||||
Reference in New Issue
Block a user