Q-W3 review follow-ups: golden hash literal test, CLAUDE.md wav_codec bullet, dead RecordedCapture field comment, makeUniqueTag residual note

This commit is contained in:
2026-07-29 11:28:55 -04:00
parent 09f7173db2
commit 8bc5aa1257
4 changed files with 36 additions and 2 deletions
+1 -1
View File
@@ -64,7 +64,7 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde
- `bank_book` — multi-bank registry: an ordered set of banks each wrapping a `BankIndex`. **Pool privileges (un-deletable/un-renamable/un-evacuable, never zero banks) enforced in-model.** Owns create/rename/reorder/delete of named banks, active-bank id, index-only move/copy/remove of a sample between banks, and JSON round-trip. - `bank_book` — multi-bank registry: an ordered set of banks each wrapping a `BankIndex`. **Pool privileges (un-deletable/un-renamable/un-evacuable, never zero banks) enforced in-model.** Owns create/rename/reorder/delete of named banks, active-bank id, index-only move/copy/remove of a sample between banks, and JSON round-trip.
- `owned_manifest` — the set of project-relative files the capture path itself created, persisted under the `"owned_files"` ext-state key, so the prune path can distinguish the bank system's own orphans from hand-dropped files. - `owned_manifest` — the set of project-relative files the capture path itself created, persisted under the `"owned_files"` ext-state key, so the prune path can distinguish the bank system's own orphans from hand-dropped files.
- `app_version` — REAPER-free version/channel identity: CMake-sourced semver constant, ext-state stamp value, and the full set of channel-derived identity accessors. All channel strings derive from one `REASAMPLER_CHANNEL_IS_BETA` bit; no scattered `#ifdef`s in the shells. - `app_version` — REAPER-free version/channel identity: CMake-sourced semver constant, ext-state stamp value, and the full set of channel-derived identity accessors. All channel strings derive from one `REASAMPLER_CHANNEL_IS_BETA` bit; no scattered `#ifdef`s in the shells.
- `wav_trim` — 32-bit-float WAV parse + header-aware truncate plan for the realtime tail's PCM decay-scan trim. - `wav_codec` — chunk walker + layout parse + float32 build + size-field patch + content hashes; the single pure RIFF/WAV owner. (`wav_trim` is now a transitional forwarding alias onto `wav_codec`, kept only so the Q-W2v TUs it feeds compile untouched; retire it once that wave lands.)
- `provenance` — capture-recipe fingerprint: build/encode/compare a `rsprov1` fingerprint of scope, range, tail, rate/channels, track GUIDs, and FX-chain identity. **A thin reproducibility fingerprint — NOT a serialized chain to restore.** - `provenance` — capture-recipe fingerprint: build/encode/compare a `rsprov1` fingerprint of scope, range, tail, rate/channels, track GUIDs, and FX-chain identity. **A thin reproducibility fingerprint — NOT a serialized chain to restore.**
- `prune_reconcile` — pure prune core: `pruneOrphans(present, referenced, owned)` computes `(owned ∩ present) referenced`; the safety-critical "which files are orphans" decision, filesystem-free and hard-tested before any I/O exists. Gains `mergeReferenced(bankRefs, liveInstanceHeldPaths)` (pS-usage) — unions live instance holds into the prune referenced-set so the pure orphan computation includes them. - `prune_reconcile` — pure prune core: `pruneOrphans(present, referenced, owned)` computes `(owned ∩ present) referenced`; the safety-critical "which files are orphans" decision, filesystem-free and hard-tested before any I/O exists. Gains `mergeReferenced(bankRefs, liveInstanceHeldPaths)` (pS-usage) — unions live instance holds into the prune referenced-set so the pure orphan computation includes them.
- `prune_button` — pure layout/hit-test for the `bank_panel` footer Prune button. - `prune_button` — pure layout/hit-test for the `bank_panel` footer Prune button.
+10
View File
@@ -114,6 +114,16 @@ struct RecordedCapture {
std::vector<std::string> trackGuids; std::vector<std::string> trackGuids;
int channelCount = 0; int channelCount = 0;
// TEST-ONLY / dead in production (Q-W3 review follow-up): the shell no longer
// populates these five fields before calling sampleFromRecordedCapture — the
// finalize path (capture_realtime_finalize.cpp) leaves them at their defaults
// and instead calls the shared stampCaptureSample(result.sample, ...) right
// after, which writes Sample::sampleRate/captureTempo/captureTimeSigNum/
// captureTimeSigDenom/createdTimestamp directly, overwriting whatever
// sampleFromRecordedCapture set from these. Kept (not deleted) because the pure
// unit tests still construct/assert them directly; removing the fields is a
// struct-shape decision out of scope here.
int sampleRate = 0; // 0 when the project rate was unknown (as offline) int sampleRate = 0; // 0 when the project rate was unknown (as offline)
double captureTempo = 0.0; // BPM at capture time (shell reads Master_GetTempo) double captureTempo = 0.0; // BPM at capture time (shell reads Master_GetTempo)
// Time signature at capture start (L7 F1; shell reads TimeMap_GetTimeSigAtTime). // Time signature at capture start (L7 F1; shell reads TimeMap_GetTimeSigAtTime).
+6
View File
@@ -239,6 +239,12 @@ std::string makeUniqueTag(const std::string& prefix) {
// session distinct regardless of timing. NOTE: the tag varies the file NAME, // session distinct regardless of timing. NOTE: the tag varies the file NAME,
// not the audio bytes — bit-identical-repeat is about identical *content* for // not the audio bytes — bit-identical-repeat is about identical *content* for
// identical requests; two deliberate captures naturally live in two files. // identical requests; two deliberate captures naturally live in two files.
// RESIDUAL (Q-W3 review follow-up): the counter is per-process, starting over
// at 0 on every REAPER launch/extension reload, so two separate REAPER
// instances (or a reload mid-session) can still mint the same timestamp+counter
// pair in the same wall-clock second — a same-second cross-process collision
// remains theoretically possible. Scoped to per-session deliberately: this fix
// targets the reachable-in-practice single-process batch-capture case above.
static std::atomic<unsigned long long> counter{0}; static std::atomic<unsigned long long> counter{0};
const std::time_t now = std::time(nullptr); const std::time_t now = std::time(nullptr);
return prefix + std::to_string(static_cast<long long>(now)) + "-" + return prefix + std::to_string(static_cast<long long>(now)) + "-" +
+19 -1
View File
@@ -9,7 +9,9 @@
// extraction (whole / tail window / clamp / out-of-range); truncate plan (kept<all, // extraction (whole / tail window / clamp / out-of-range); truncate plan (kept<all,
// no-op keep-all, kept==0, grow rejected) with exact size-field values; patchU32LE; // no-op keep-all, kept==0, grow rejected) with exact size-field values; patchU32LE;
// buildFloat32Wav golden header + parse round-trip; hashBytes/hashWavContent // buildFloat32Wav golden header + parse round-trip; hashBytes/hashWavContent
// determinism, metadata-skip, fallback, and domain separation. // determinism, metadata-skip, fallback, and domain separation; a golden hash
// literal pinning exact hex output for a fixed input (guards persisted
// contentHash values against a silent feed-sequence drift).
#include "../src/core/capture/wav_codec.h" #include "../src/core/capture/wav_codec.h"
@@ -576,6 +578,21 @@ static void testHashMatchesBuildOutput() {
CHECK(hashWavContent(wav) == hashWavContent(withMeta)); CHECK(hashWavContent(wav) == hashWavContent(withMeta));
} }
// Golden hash literal (review follow-up, Q-W3): the tests above prove format,
// determinism, and relational properties (equal/different) of hashBytes and
// hashWavContent, but none of them pins the actual output bytes — a feed-sequence
// change (e.g. dropping the 'W' domain-separation prefix, or reordering the
// fmt/data feed) would pass every test above while silently invalidating every
// contentHash already persisted in existing projects' BankIndex JSON. This test
// locks a fixed buildFloat32Wav input's hashes against exact hex literals
// captured from the current implementation, so any such drift fails loudly here.
static void testGoldenHashLiterals() {
const std::vector<double> pcm = {0.0, 0.5, -0.25, 1.0, -1.0, 0.125};
auto wav = buildFloat32Wav(2, 48000, 3, pcm);
CHECK(hashWavContent(wav) == "7ccf298c166a670a");
CHECK(hashBytes(wav.data(), wav.size()) == "68d8a193c958fd44");
}
int main() { int main() {
testParseCanonicalStereo(); testParseCanonicalStereo();
testParseMonoAndLeadingChunk(); testParseMonoAndLeadingChunk();
@@ -600,6 +617,7 @@ int main() {
testHashWavContentListMetaSkipped(); testHashWavContentListMetaSkipped();
testHashWavContentDomainSeparationFromWholeFile(); testHashWavContentDomainSeparationFromWholeFile();
testHashMatchesBuildOutput(); testHashMatchesBuildOutput();
testGoldenHashLiterals();
if (g_fail == 0) std::printf("wav_codec: all tests passed\n"); if (g_fail == 0) std::printf("wav_codec: all tests passed\n");
else std::printf("wav_codec: %d CHECK(s) FAILED\n", g_fail); else std::printf("wav_codec: %d CHECK(s) FAILED\n", g_fail);