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
+9 -7
View File
@@ -512,16 +512,18 @@ CaptureResult finalizeRecording(RealtimeCaptureState& st) {
result.status = CaptureStatus::Ok;
result.sample = sampleFromRecordedCapture(cap);
// Content hash: FNV-1a over the (possibly trimmed) bank 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 contentHash empty — the safe,
// confirm-eliciting direction (bank_model treats "" as non-participating).
// Content hash: WAV-aware FNV-1a over the (possibly trimmed) bank 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 records 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).
{
const std::vector<std::uint8_t> fileBytes = readAllBytes(destPath);
if (!fileBytes.empty()) {
result.sample.contentHash =
hashBytes(fileBytes.data(), fileBytes.size());
result.sample.contentHash = hashWavContent(fileBytes);
}
}