fix(dedup): hash WAV fmt+data only, skipping render-varying metadata chunks

hashWavContent walks RIFF chunks and feeds only fmt body + data payload
through FNV-1a ('W' domain-separation prefix), skipping bext/iXML/LIST that
REAPER embeds with per-render origination timestamps. Fallback to whole-file
hashBytes for non-WAV. Wired into both capture commit paths.

Legacy entries keep their stored whole-file hash; a re-capture will not
collapse onto a pre-fix entry -- a one-time clean capture resolves it.
This commit is contained in:
2026-07-26 17:09:58 -04:00
parent 6a6d305cf2
commit 78fc5bad94
5 changed files with 291 additions and 11 deletions
+7 -4
View File
@@ -499,15 +499,18 @@ CaptureResult OfflineRenderBackend::capture(const CaptureRequest& request) {
s.lengthSeconds = request.endSeconds - request.startSeconds;
s.captureTempo = Master_GetTempo(); // BPM at capture time (verified ~4651)
s.tier = Tier::Scratch; // captures land in scratch by default
// Content hash: FNV-1a over the rendered file bytes so hashReferencedElsewhere
// can identify copies in other banks and suppress the last-reference confirm when
// another bank still holds the same file. Best-effort: an unreadable file leaves
// Content hash: WAV-aware FNV-1a over the rendered file's fmt+data chunks so
// hashReferencedElsewhere can identify copies in other banks and suppress the
// last-reference confirm when another bank still holds the same file. Using
// hashWavContent (not the raw hashBytes) skips render-varying metadata chunks
// (bext origination timestamp, iXML, LIST/INFO, etc.) so two renders of identical
// audio collapse to the same hash. Best-effort: an unreadable file leaves
// contentHash empty — the safe, confirm-eliciting direction (bank_model treats
// "" as non-participating in dedup, which is the existing fallback semantics).
{
const std::vector<std::uint8_t> fileBytes = readFileBytes(expectedPath);
if (!fileBytes.empty()) {
s.contentHash = hashBytes(fileBytes.data(), fileBytes.size());
s.contentHash = hashWavContent(fileBytes);
}
}
s.createdTimestamp = static_cast<std::int64_t>(std::time(nullptr));