// Standalone tests for reasampler::wav_codec — no REAPER, no test framework. // The ONE pure WAV/RIFF owner (Q-W3, audit §4e): builds synthetic 32-bit-float WAV // byte buffers, asserts the parse geometry, the float extraction, the truncate-plan // arithmetic + size-field patch, the float32 build round-trip, and the WAV-aware // content hashes (moved here from capture_paths with the hash implementations). // // Covers: canonical stereo/mono 32-bit-float parse; a leading unknown chunk skipped; // format rejection (16-bit PCM, non-WAV, data-before-fmt, truncated data); frame // extraction (whole / tail window / clamp / out-of-range); truncate plan (kept #include #include #include using namespace reasampler; using namespace reasampler::capture; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) // --- Synthetic WAV builder --------------------------------------------------- static void putU16(std::vector& b, std::uint16_t v) { b.push_back(static_cast(v & 0xFF)); b.push_back(static_cast((v >> 8) & 0xFF)); } static void putU32(std::vector& b, std::uint32_t v) { b.push_back(static_cast(v & 0xFF)); b.push_back(static_cast((v >> 8) & 0xFF)); b.push_back(static_cast((v >> 16) & 0xFF)); b.push_back(static_cast((v >> 24) & 0xFF)); } static void putTag(std::vector& b, const char* t) { for (int i = 0; i < 4; ++i) b.push_back(static_cast(t[i])); } static void putFloat(std::vector& b, float f) { std::uint8_t tmp[4]; std::memcpy(tmp, &f, 4); for (int i = 0; i < 4; ++i) b.push_back(tmp[i]); } // A canonical 32-bit-float WAV: RIFF/WAVE, fmt (tag 3, 16-byte body), data holding // `frames` interleaved frames of `channels`. `leadingJunk` optionally inserts an // unknown chunk before fmt to exercise the chunk walk. Samples: frame f, channel c // = value(f,c). template static std::vector buildFloatWav(std::uint16_t channels, std::uint32_t sampleRate, std::size_t frames, Fn value, bool leadingJunk = false, std::uint16_t fmtTag = 3, std::uint16_t bits = 32) { const std::uint32_t dataBytes = static_cast(frames * channels * (bits / 8)); std::vector chunks; // everything after "WAVE" if (leadingJunk) { putTag(chunks, "LIST"); putU32(chunks, 4); putTag(chunks, "INFO"); // 4-byte body, even -> no pad } // fmt chunk (16-byte body). putTag(chunks, "fmt "); putU32(chunks, 16); putU16(chunks, fmtTag); // format tag putU16(chunks, channels); putU32(chunks, sampleRate); const std::uint32_t byteRate = sampleRate * channels * (bits / 8); putU32(chunks, byteRate); putU16(chunks, static_cast(channels * (bits / 8))); // block align putU16(chunks, bits); // data chunk. putTag(chunks, "data"); putU32(chunks, dataBytes); for (std::size_t f = 0; f < frames; ++f) for (std::uint16_t c = 0; c < channels; ++c) putFloat(chunks, value(f, c)); std::vector wav; putTag(wav, "RIFF"); putU32(wav, static_cast(4 + chunks.size())); // "WAVE" + chunks putTag(wav, "WAVE"); wav.insert(wav.end(), chunks.begin(), chunks.end()); return wav; } // Builds a WAVE_FORMAT_EXTENSIBLE (0xFFFE) WAV with a 40-byte fmt body. // `subFormatTag` is the 2-byte leading tag embedded in the SubFormat GUID: // 0x0003 = IEEE float, 0x0001 = PCM integer (and any other value to exercise rejection). // bitsPerSample and the PCM data are always 32-bit float bytes regardless of subFormatTag // (we're testing that the parser correctly rejects/accepts based on the GUID, not the data). template static std::vector buildExtensibleWav(std::uint16_t channels, std::uint32_t sampleRate, std::size_t frames, Fn value, std::uint16_t subFormatTag) { const std::uint32_t dataBytes = static_cast(frames * channels * 4u); // WAVEFORMATEXTENSIBLE fmt body (40 bytes): // [0..1] wFormatTag = 0xFFFE // [2..3] nChannels // [4..7] nSamplesPerSec // [8..11] nAvgBytesPerSec // [12..13] nBlockAlign // [14..15] wBitsPerSample = 32 // [16..17] cbSize = 22 (extension size beyond the 18-byte WAVEFORMATEX) // [18..19] wValidBitsPerSample = 32 // [20..23] dwChannelMask = 0 // [24..39] SubFormat GUID: first 2 bytes = subFormatTag (LE), rest = standard // KSDATAFORMAT_SUBTYPE base GUID {00000000-0000-0010-8000-00aa00389b71} std::vector fmt; putU16(fmt, 0xFFFE); // wFormatTag putU16(fmt, channels); // nChannels putU32(fmt, sampleRate); // nSamplesPerSec putU32(fmt, sampleRate * channels * 4u); // nAvgBytesPerSec putU16(fmt, static_cast(channels * 4)); // nBlockAlign putU16(fmt, 32); // wBitsPerSample putU16(fmt, 22); // cbSize putU16(fmt, 32); // wValidBitsPerSample putU32(fmt, 0); // dwChannelMask // SubFormat GUID (16 bytes): [subFormatTag, 0x0000, 0x00, 0x00, 0x10, 0x00, // 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71] putU16(fmt, subFormatTag); // bytes [24..25]: the effective format tag putU16(fmt, 0x0000); // bytes [26..27] fmt.push_back(0x00); fmt.push_back(0x00); // bytes [28..29] fmt.push_back(0x10); fmt.push_back(0x00); // bytes [30..31] fmt.push_back(0x80); fmt.push_back(0x00); // bytes [32..33] fmt.push_back(0x00); fmt.push_back(0xaa); // bytes [34..35] fmt.push_back(0x00); fmt.push_back(0x38); // bytes [36..37] fmt.push_back(0x9b); fmt.push_back(0x71); // bytes [38..39] std::vector chunks; putTag(chunks, "fmt "); putU32(chunks, static_cast(fmt.size())); // 40 chunks.insert(chunks.end(), fmt.begin(), fmt.end()); putTag(chunks, "data"); putU32(chunks, dataBytes); for (std::size_t f = 0; f < frames; ++f) for (std::uint16_t c = 0; c < channels; ++c) putFloat(chunks, value(f, c)); std::vector wav; putTag(wav, "RIFF"); putU32(wav, static_cast(4 + chunks.size())); putTag(wav, "WAVE"); wav.insert(wav.end(), chunks.begin(), chunks.end()); return wav; } // --- Parse tests ------------------------------------------------------------- static void testParseCanonicalStereo() { auto wav = buildFloatWav(2, 48000, 5, [](std::size_t f, std::uint16_t c) { return static_cast(f) + 0.1f * c; }); WavLayout L = parseWavLayout(wav); CHECK(L.valid); CHECK(L.channelCount == 2); CHECK(L.sampleRate == 48000); CHECK(L.dataByteLength == 5 * 2 * 4); CHECK(L.frameCount() == 5); // data body sits after RIFF(12) + fmt(8 header + 16 body) + data(8 header) = 44. CHECK(L.dataByteOffset == 44); CHECK(L.dataSizeFieldOffset == 40); // the 4 bytes before dataByteOffset CHECK(L.riffSizeFieldOffset == 4); } static void testParseMonoAndLeadingChunk() { // A leading LIST/INFO chunk before fmt must be skipped by the walk. auto wav = buildFloatWav(1, 44100, 3, [](std::size_t f, std::uint16_t) { return static_cast(f); }, /*leadingJunk=*/true); WavLayout L = parseWavLayout(wav); CHECK(L.valid); CHECK(L.channelCount == 1); CHECK(L.frameCount() == 3); // Data still parses correctly despite the leading chunk shifting its offset. auto pcm = extractFloatFrames(wav, L, 0, 3); CHECK(pcm.size() == 3); CHECK(pcm[0] == 0.0f && pcm[1] == 1.0f && pcm[2] == 2.0f); } static void testParseRejectsNon32BitAndNonWav() { // 16-bit PCM (tag 1, bits 16) -> rejected. auto pcm16 = buildFloatWav(2, 48000, 4, [](std::size_t, std::uint16_t) { return 0.0f; }, false, /*fmtTag=*/1, /*bits=*/16); CHECK(!parseWavLayout(pcm16).valid); // Not a RIFF file. std::vector junk = {'N','O','P','E', 0,0,0,0, 'W','A','V','E'}; CHECK(!parseWavLayout(junk).valid); // Too short to hold even the RIFF header. std::vector tiny = {'R','I','F','F'}; CHECK(!parseWavLayout(tiny).valid); } static void testParseRejectsLyingDataLength() { // Build a valid WAV, then inflate the `data` size field so it claims more bytes // than the buffer holds -> must be rejected (no OOB trust). auto wav = buildFloatWav(2, 48000, 4, [](std::size_t, std::uint16_t) { return 1.0f; }); WavLayout good = parseWavLayout(wav); CHECK(good.valid); // Overwrite the data size field with a huge value. wav[good.dataSizeFieldOffset + 0] = 0xFF; wav[good.dataSizeFieldOffset + 1] = 0xFF; wav[good.dataSizeFieldOffset + 2] = 0xFF; wav[good.dataSizeFieldOffset + 3] = 0x7F; CHECK(!parseWavLayout(wav).valid); } // --- Extraction tests -------------------------------------------------------- static void testExtractTailWindow() { // Stereo, 10 frames. Sample value encodes frame+channel so a mis-index is caught. auto wav = buildFloatWav(2, 48000, 10, [](std::size_t f, std::uint16_t c) { return static_cast(f) * 10.0f + c; }); WavLayout L = parseWavLayout(wav); CHECK(L.valid); // The "tail region" the realtime trim scans: frames 6..9 (start at frame 6). auto tail = extractFloatFrames(wav, L, 6, 100 /*clamps*/); CHECK(tail.size() == 4 * 2); // frames 6,7,8,9, 2 channels each CHECK(tail[0] == 60.0f && tail[1] == 61.0f); // frame 6: L=60,R=61 CHECK(tail[6] == 90.0f && tail[7] == 91.0f); // frame 9: L=90,R=91 // Out-of-range start -> empty. CHECK(extractFloatFrames(wav, L, 10, 4).empty()); CHECK(extractFloatFrames(wav, L, 99, 4).empty()); } // --- Truncate-plan tests ----------------------------------------------------- static void testTruncatePlanKeepFewer() { auto wav = buildFloatWav(2, 48000, 10, [](std::size_t, std::uint16_t) { return 0.0f; }); WavLayout L = parseWavLayout(wav); CHECK(L.valid); // Keep 4 of 10 frames. WavTruncatePlan p = planWavTruncate(L, 4); CHECK(p.valid); const std::size_t bpf = 2 * 4; // channels * 4 bytes CHECK(p.newDataSize == 4 * bpf); // 32 bytes of PCM kept CHECK(p.newFileByteLength == L.dataByteOffset + 4 * bpf); // 44 + 32 = 76 CHECK(p.newRiffSize == p.newFileByteLength - 8); CHECK(p.dataSizeFieldOffset == L.dataSizeFieldOffset); CHECK(p.riffSizeFieldOffset == 4); // Applying the plan yields a buffer that re-parses to exactly 4 frames. std::vector trimmed(wav.begin(), wav.begin() + p.newFileByteLength); // Patch the two size fields with the module's own patch primitive (what the // shell does before truncating on disk). patchU32LE(trimmed, p.dataSizeFieldOffset, p.newDataSize); patchU32LE(trimmed, p.riffSizeFieldOffset, p.newRiffSize); WavLayout L2 = parseWavLayout(trimmed); CHECK(L2.valid); CHECK(L2.frameCount() == 4); CHECK(L2.dataByteLength == 4 * bpf); } static void testTruncatePlanKeepAllIsNoOp() { auto wav = buildFloatWav(1, 48000, 6, [](std::size_t, std::uint16_t) { return 0.0f; }); WavLayout L = parseWavLayout(wav); WavTruncatePlan p = planWavTruncate(L, 6); // keep all CHECK(p.valid); CHECK(p.newFileByteLength == wav.size()); // unchanged CHECK(p.newDataSize == L.dataByteLength); } // --- Extensible format tests ------------------------------------------------- // A WAVE_FORMAT_EXTENSIBLE fmt with SubFormat tag 0x0001 (PCM integer) and // bitsPerSample==32 must be REJECTED — it is 32-bit integer, not 32-bit float. static void testExtensiblePcmIntegerRejected() { auto wav = buildExtensibleWav(2, 48000, 4, [](std::size_t, std::uint16_t) { return 0.0f; }, /*subFormatTag=*/0x0001); // PCM integer CHECK(!parseWavLayout(wav).valid); } // A WAVE_FORMAT_EXTENSIBLE fmt with SubFormat tag 0x0003 (IEEE float) and // bitsPerSample==32 must be ACCEPTED and parse + trim correctly. static void testExtensibleFloatAccepted() { auto wav = buildExtensibleWav(2, 48000, 5, [](std::size_t f, std::uint16_t c) { return static_cast(f) + 0.1f * c; }, /*subFormatTag=*/0x0003); // IEEE float WavLayout L = parseWavLayout(wav); CHECK(L.valid); CHECK(L.channelCount == 2); CHECK(L.sampleRate == 48000); CHECK(L.frameCount() == 5); // Frame extraction works correctly. auto pcm = extractFloatFrames(wav, L, 0, 2); CHECK(pcm.size() == 4); CHECK(pcm[0] == 0.0f); // frame 0, channel 0 CHECK(pcm[1] == 0.1f); // frame 0, channel 1 // Truncate plan is valid and re-parses cleanly. WavTruncatePlan p = planWavTruncate(L, 3); CHECK(p.valid); CHECK(p.newDataSize == 3 * 2 * 4u); std::vector trimmed(wav.begin(), wav.begin() + p.newFileByteLength); patchU32LE(trimmed, p.dataSizeFieldOffset, p.newDataSize); patchU32LE(trimmed, p.riffSizeFieldOffset, p.newRiffSize); WavLayout L2 = parseWavLayout(trimmed); CHECK(L2.valid); CHECK(L2.frameCount() == 3); } static void testTruncatePlanKeepZeroAndGrowRejected() { auto wav = buildFloatWav(2, 48000, 5, [](std::size_t, std::uint16_t) { return 0.0f; }); WavLayout L = parseWavLayout(wav); WavTruncatePlan zero = planWavTruncate(L, 0); CHECK(zero.valid); CHECK(zero.newDataSize == 0); CHECK(zero.newFileByteLength == L.dataByteOffset); // header only // keptFrames > total -> refused (never grow a file). CHECK(!planWavTruncate(L, 6).valid); // Invalid layout -> invalid plan. WavLayout bad; CHECK(!planWavTruncate(bad, 0).valid); } // --- patchU32LE (the size-field patch primitive) ------------------------------ static void testPatchU32LEWritesLittleEndian() { std::vector buf(8, 0xEE); patchU32LE(buf, 2, 0x0A0B0C0Du); CHECK(buf[0] == 0xEE && buf[1] == 0xEE); // bytes outside the field untouched CHECK(buf[2] == 0x0D && buf[3] == 0x0C && buf[4] == 0x0B && buf[5] == 0x0A); CHECK(buf[6] == 0xEE && buf[7] == 0xEE); } // --- buildFloat32Wav (the one WAV writer, absorbed from ingest — T4-10) ------- static void testBuildFloat32WavGoldenHeaderAndRoundTrip() { // 2 channels, 3 frames of known interleaved values. const std::vector pcm = {0.0, 0.5, -0.25, 1.0, -1.0, 0.125}; auto wav = buildFloat32Wav(2, 48000, 3, pcm); // Golden container shape: 44-byte header + 6 samples * 4 bytes. CHECK(wav.size() == 44u + 6u * 4u); CHECK(std::memcmp(wav.data(), "RIFF", 4) == 0); CHECK(std::memcmp(wav.data() + 8, "WAVE", 4) == 0); CHECK(std::memcmp(wav.data() + 12, "fmt ", 4) == 0); CHECK(std::memcmp(wav.data() + 36, "data", 4) == 0); CHECK(wav[20] == 0x03 && wav[21] == 0x00); // WAVE_FORMAT_IEEE_FLOAT CHECK(wav[34] == 32 && wav[35] == 0); // bitsPerSample = 32 // The build round-trips through the module's own parse + extraction, with the // documented double->float narrowing. WavLayout L = parseWavLayout(wav); CHECK(L.valid); CHECK(L.channelCount == 2); CHECK(L.sampleRate == 48000); CHECK(L.frameCount() == 3); auto back = extractFloatFrames(wav, L, 0, 3); CHECK(back.size() == 6); for (std::size_t i = 0; i < back.size(); ++i) CHECK(back[i] == static_cast(pcm[i])); } static void testBuildFloat32WavShortInputRejectedByParse() { // Fewer interleaved samples than frameCount*nch declares: the data chunk still // declares the full length; the missing tail simply is not written. The build // caller (ingest) always passes a full buffer; this locks the clamp-no-OOB shape. const std::vector pcm = {1.0}; // 1 sample for a 2-frame mono request auto wav = buildFloat32Wav(1, 44100, 2, pcm); // Declared data size covers 2 frames; actual bytes stop after 1 sample, so the // declared length overruns the buffer -> parse rejects (the honest verdict for // a short-fed build; ingest never produces this). CHECK(wav.size() == 44u + 4u); CHECK(!parseWavLayout(wav).valid); } // --- hashBytes (FNV-1a content hash — moved with the impl from capture_paths) -- static void testHashBytesOutputFormat() { // Output is always 16 lowercase hex characters. const std::uint8_t bytes[] = {0x01, 0x02, 0x03}; const std::string h = hashBytes(bytes, 3); CHECK(h.size() == 16); for (char c : h) { CHECK((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')); } } static void testHashBytesDeterministicAndDistinct() { // Same input always produces the same output; different inputs differ (no // accidental dedup of distinct files), including a one-bit flip and a // trailing-zero-byte length change. const std::uint8_t bytes[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x01}; CHECK(hashBytes(bytes, 5) == hashBytes(bytes, 5)); std::uint8_t a[] = {0x00, 0x00}; std::uint8_t b[] = {0x00, 0x01}; CHECK(hashBytes(a, 2) != hashBytes(b, 2)); const std::uint8_t short_buf[] = {0xAB, 0xCD}; const std::uint8_t long_buf[] = {0xAB, 0xCD, 0x00}; CHECK(hashBytes(short_buf, 2) != hashBytes(long_buf, 3)); } static void testHashBytesEmptyBufferIsNonEmpty() { // An empty buffer returns the FNV-1a offset basis in hex (stable, non-empty // sentinel) — capturing the contract that even empty inputs yield a 16-char hash. const std::string h = hashBytes(nullptr, 0); CHECK(h.size() == 16); } // --- hashWavContent (WAV-aware dedup hash) ----------------------------------- // // Verifies that the WAV-content hash hashes only fmt+data (skipping metadata // chunks like bext/LIST), falls back gracefully for non-WAV input, and that // different audio data yields different hashes. // Builds a minimal 32-bit-float RIFF/WAVE with an optional metadata chunk // inserted between "WAVE" and the fmt chunk. This is the shape REAPER produces: a // `bext` or `LIST` chunk before fmt with a render-time timestamp in the body. static std::vector buildMetaWav( std::uint16_t channels, std::uint32_t sampleRate, const std::vector& samples, bool insertMeta = false, const char* metaTag = "bext", const std::vector& metaBody = {}) { std::vector chunks; if (insertMeta && !metaBody.empty()) { putTag(chunks, metaTag); putU32(chunks, static_cast(metaBody.size())); chunks.insert(chunks.end(), metaBody.begin(), metaBody.end()); if (metaBody.size() & 1u) chunks.push_back(0); // RIFF pad } // fmt chunk (16-byte body, IEEE-float tag 3). const std::uint32_t dataBytes = static_cast(samples.size() * 4u); putTag(chunks, "fmt "); putU32(chunks, 16); putU16(chunks, 3); // IEEE float putU16(chunks, channels); putU32(chunks, sampleRate); putU32(chunks, sampleRate * channels * 4u); // byteRate putU16(chunks, static_cast(channels * 4)); // blockAlign putU16(chunks, 32); // bitsPerSample // data chunk. putTag(chunks, "data"); putU32(chunks, dataBytes); for (float f : samples) putFloat(chunks, f); std::vector wav; putTag(wav, "RIFF"); putU32(wav, static_cast(4 + chunks.size())); putTag(wav, "WAVE"); wav.insert(wav.end(), chunks.begin(), chunks.end()); return wav; } static void testHashWavContentIdenticalAudioSameHash() { // Two WAVs with the same audio but different metadata body -> same hash. // This is the core dedup regression: REAPER embeds a bext chunk with a // render-time origination timestamp; without WAV-aware hashing, two renders // of the same clip produce different file bytes -> no dedup collapse. const std::vector audio = {0.1f, -0.2f, 0.3f, -0.4f}; std::vector metaA(64, 0x00); // bext body, all zeros (e.g. epoch) std::vector metaB(64, 0x00); // Different origination timestamps: first 10 bytes of bext are ASCII date/time. metaB[0] = '2'; metaB[1] = '0'; metaB[2] = '2'; metaB[3] = '6'; // year auto wavA = buildMetaWav(1, 44100, audio, /*meta=*/true, "bext", metaA); auto wavB = buildMetaWav(1, 44100, audio, /*meta=*/true, "bext", metaB); // Files must differ (the bext body is different) to prove the test is valid. CHECK(wavA != wavB); // But their content hashes must be equal: same fmt+data, different metadata. CHECK(hashWavContent(wavA) == hashWavContent(wavB)); } static void testHashWavContentDifferentAudioDifferentHash() { // Different PCM data -> different content hashes (no false dedup). const std::vector audioA = {0.5f, 0.5f}; const std::vector audioB = {0.5f, 0.6f}; // last sample differs auto wavA = buildMetaWav(1, 44100, audioA); auto wavB = buildMetaWav(1, 44100, audioB); CHECK(hashWavContent(wavA) != hashWavContent(wavB)); } static void testHashWavContentDifferentFmtDifferentHash() { // Different fmt fields (sample rate) -> different content hashes. const std::vector audio = {0.1f, 0.2f}; auto wav44 = buildMetaWav(1, 44100, audio); auto wav48 = buildMetaWav(1, 48000, audio); CHECK(hashWavContent(wav44) != hashWavContent(wav48)); } static void testHashWavContentNonWavFallsBackToWholeFile() { // Non-WAV bytes -> falls back to whole-file hashBytes; result is non-empty // and equals hashBytes of the same bytes directly. Empty input likewise. std::vector notWav = {0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02}; const std::string h = hashWavContent(notWav); CHECK(!h.empty()); CHECK(h.size() == 16); CHECK(h == hashBytes(notWav.data(), notWav.size())); std::vector empty; const std::string he = hashWavContent(empty); CHECK(he.size() == 16); CHECK(he == hashBytes(nullptr, 0)); } static void testHashWavContentListMetaSkipped() { // A LIST/INFO chunk (another common metadata chunk) is likewise skipped. const std::vector audio = {1.0f, -1.0f, 0.5f}; std::vector listBody = {'I','N','F','O', 'x','x','x','x'}; auto wavClean = buildMetaWav(1, 48000, audio); auto wavList = buildMetaWav(1, 48000, audio, true, "LIST", listBody); // Content hashes must match: only the LIST chunk differs. CHECK(hashWavContent(wavClean) == hashWavContent(wavList)); } static void testHashWavContentDomainSeparationFromWholeFile() { // The content hash ('W'-prefixed) must not accidentally equal the whole-file // hash of the SAME bytes. This guards against the domain-separation prefix // being dropped or zeroed out. const std::vector audio = {0.0f}; auto wav = buildMetaWav(1, 44100, audio); const std::string contentHash = hashWavContent(wav); const std::string wholeHash = hashBytes(wav.data(), wav.size()); CHECK(contentHash != wholeHash); } static void testHashMatchesBuildOutput() { // The consolidation guarantee end-to-end: a WAV produced by the module's own // builder hashes as WAV content (not the whole-file fallback), so an imported // conversion and a captured render of identical audio can dedup-collapse. const std::vector pcm = {0.25, -0.25}; auto wav = buildFloat32Wav(1, 44100, 2, pcm); CHECK(hashWavContent(wav) != hashBytes(wav.data(), wav.size())); // chunk-aware path taken // And a metadata-bearing copy of the same audio content hashes identically. auto withMeta = buildMetaWav(1, 44100, {0.25f, -0.25f}, true, "bext", std::vector(16, 0x7A)); CHECK(hashWavContent(wav) == hashWavContent(withMeta)); } int main() { testParseCanonicalStereo(); testParseMonoAndLeadingChunk(); testParseRejectsNon32BitAndNonWav(); testParseRejectsLyingDataLength(); testExtractTailWindow(); testTruncatePlanKeepFewer(); testTruncatePlanKeepAllIsNoOp(); testTruncatePlanKeepZeroAndGrowRejected(); testExtensiblePcmIntegerRejected(); testExtensibleFloatAccepted(); testPatchU32LEWritesLittleEndian(); testBuildFloat32WavGoldenHeaderAndRoundTrip(); testBuildFloat32WavShortInputRejectedByParse(); testHashBytesOutputFormat(); testHashBytesDeterministicAndDistinct(); testHashBytesEmptyBufferIsNonEmpty(); testHashWavContentIdenticalAudioSameHash(); testHashWavContentDifferentAudioDifferentHash(); testHashWavContentDifferentFmtDifferentHash(); testHashWavContentNonWavFallsBackToWholeFile(); testHashWavContentListMetaSkipped(); testHashWavContentDomainSeparationFromWholeFile(); testHashMatchesBuildOutput(); if (g_fail == 0) std::printf("wav_codec: all tests passed\n"); else std::printf("wav_codec: %d CHECK(s) FAILED\n", g_fail); return g_fail ? 1 : 0; }