Cut core/capture and core/version comment bloat ~45% (comments only, zero code change)

This commit is contained in:
2026-07-29 20:49:23 -04:00
parent 1f24c4b095
commit 12ffe377e5
16 changed files with 475 additions and 991 deletions
+38 -103
View File
@@ -1,39 +1,9 @@
#pragma once
// wav_codec — the ONE pure owner of the WAV/RIFF byte format (Q-W3, audit §4e:
// T2-08 / T4-10 / T4-23 consolidation). Chunk walker + layout parse + float32
// build + size-field patch + the WAV-aware content hash, in one tested module.
//
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO
// vendor/ includes. Standard library only. Builds and unit-tests without REAPER.
//
// Before this module, RIFF container knowledge (chunk-header arithmetic, even-byte
// padding, size fields) was minted at four sites: wav_trim's layout parse,
// capture_paths' content-hash chunk walk, ingest's hand-built float32 writer, and
// capture_realtime's in-place size patch. A drift in any one (e.g. pad-byte
// handling) would desynchronize hashing from decoding — the dedup-by-hash and
// null-test invariants both sit on this. Now every walker/builder/patcher is here,
// on ONE chunk-traversal implementation.
//
// WHY TRIM EXISTS (docs/product/capture-tail.md §The realtime path). The realtime
// backend records a generous tail window, then trims the trailing decay by
// truncating the recorded WAV at a frame boundary. Truncating a WAV correctly is
// not "chop the bytes": the RIFF container's size fields (the top-level RIFF chunk
// size and the `data` sub-chunk size) must be patched to the kept byte count, or
// the file is a corrupt / mis-lengthed WAV. That header arithmetic — chunk walking,
// format verification, and the size-field patch offsets — is exactly the fiddly,
// easy-to-get-wrong logic the discipline unit-tests OUTSIDE the DAW. The REAPER
// shell does only the file I/O: read the bytes, call the pure parse, run the decay
// scan, call the pure plan, patch + write the truncated bytes.
//
// FORMAT ASSUMPTION (flagged for DAW-verify). We record 32-bit float WAV
// (capture.cpp kRenderFormatWavFloat32; realtime records via REAPER's project
// record format, which the manual procedure sets to WAV/32-bit-float). The parser
// therefore verifies canonical PCM/IEEE-float WAV: a RIFF/WAVE container, a `fmt `
// chunk declaring 32-bit float (format tag 3, or tag 0xFFFE WAVE_FORMAT_EXTENSIBLE
// with 32 bits), and a `data` chunk of interleaved little-endian float32. Anything
// else (a different depth, a non-WAV, a compressed source) is reported invalid and
// the shell SKIPS the trim (keeps the untrimmed window) rather than corrupting a
// file it does not understand. This is deliberately conservative.
// wav_codec — the pure owner of the WAV/RIFF byte format: chunk walker, layout
// parse, float32 build, size-field patch, and the WAV-aware content hash — one
// chunk traversal shared by all of them so hashing and decoding cannot desync.
// Handles 32-bit float WAV only (RIFF/WAVE, `fmt ` tag 3 or 0xFFFE-extensible
// w/ float subformat, float32 `data`); anything else parses as invalid.
#include <cstddef>
#include <cstdint>
@@ -49,22 +19,20 @@ using audio::AudioSample;
// --- Layout parse ------------------------------------------------------------
// The parsed geometry of a canonical 32-bit-float WAV. `valid` is false when the
// bytes are not a WAV we can safely trim (see FORMAT ASSUMPTION); every other field
// is meaningful only when valid.
// bytes are not a WAV we can safely trim; every other field is meaningful only
// when valid.
struct WavLayout {
bool valid = false;
std::uint16_t channelCount = 0; // from `fmt ` (the interleave stride)
std::uint32_t sampleRate = 0; // from `fmt ` (for frame<->seconds, if needed)
std::uint16_t channelCount = 0; // from `fmt ` (interleave stride)
std::uint32_t sampleRate = 0;
// The `data` chunk: byte offset of its first PCM byte within the file, and its
// declared PCM byte length. frameCount = dataByteLength / (channelCount * 4).
// The `data` chunk: PCM byte offset + declared length.
// frameCount = dataByteLength / (channelCount * 4).
std::size_t dataByteOffset = 0;
std::size_t dataByteLength = 0;
// Byte offset of the two little-endian uint32 size fields the truncate patch
// rewrites: the top-level RIFF chunk size (bytes 4..7) and the `data` sub-chunk
// size (the 4 bytes immediately before dataByteOffset).
// Offsets of the two LE uint32 size fields the truncate patch rewrites.
std::size_t riffSizeFieldOffset = 4; // always 4 for a RIFF file
std::size_t dataSizeFieldOffset = 0;
@@ -74,19 +42,15 @@ struct WavLayout {
}
};
// Parses a WAV byte buffer's header geometry. Returns {valid=false} for anything
// that is not a canonical 32-bit-float RIFF/WAVE with a `fmt ` and a `data` chunk,
// or whose declared `data` length runs past the buffer. Does NOT copy PCM — it only
// locates it (extractFloatFrames does the copy). Pure + total (no throw, no UB).
// Parses a WAV byte buffer's header geometry; {valid=false} for anything not a
// canonical float32 RIFF/WAVE, or a `data` length running past the buffer.
// Does not copy PCM, only locates it. Pure + total (no throw, no UB).
WavLayout parseWavLayout(const std::vector<std::uint8_t>& bytes);
// Copies `frameCount` interleaved float frames starting at `startFrame` out of the
// WAV's `data` region into a flat [f0c0,f0c1,...] buffer (the shape peaks consumes).
// Clamps to the frames the buffer actually holds — never reads past `data`. Returns
// empty for an invalid layout or an out-of-range start. The floats are read
// little-endian via std::memcpy (no aliasing UB); on a big-endian host they would
// need a byte-swap — flagged, not handled, because the target (Windows/macOS/Linux
// on x86/ARM-LE) is little-endian and REAPER writes LE WAV.
// Copies `frameCount` interleaved float frames starting at `startFrame` out of
// the WAV's `data` region into a flat [f0c0,f0c1,...] buffer, clamped to frames
// actually present; never reads past `data`. Reads little-endian via memcpy —
// target is x86/ARM-LE only, no big-endian byte-swap.
std::vector<AudioSample> extractFloatFrames(const std::vector<std::uint8_t>& bytes,
const WavLayout& layout,
std::size_t startFrame,
@@ -94,10 +58,8 @@ std::vector<AudioSample> extractFloatFrames(const std::vector<std::uint8_t>& byt
// --- Truncate plan + size-field patch ---------------------------------------
// The plan to truncate a parsed WAV to `keptFrames` frames: the new total file byte
// length and the two size-field values to patch. `valid` is false if the layout is
// invalid or keptFrames exceeds the file's frames (never GROW a file — the caller
// clamps beforehand; this guards it too).
// The plan to truncate a parsed WAV to `keptFrames` frames. `valid` is false if
// the layout is invalid or keptFrames exceeds the file's frames (never grow).
struct WavTruncatePlan {
bool valid = false;
@@ -109,64 +71,37 @@ struct WavTruncatePlan {
// the 8-byte "RIFF"+size prefix)
};
// Computes the truncate plan to keep exactly `keptFrames` frames of a parsed WAV.
// keptFrames == layout.frameCount() is a valid no-op plan (file unchanged). Pure +
// total. The shell applies it: patch the two size fields in the byte buffer
// (patchU32LE), then truncate the file to newFileByteLength.
// Computes the truncate plan to keep exactly `keptFrames` frames. The shell
// applies it: patch the two size fields (patchU32LE), then truncate to
// newFileByteLength.
WavTruncatePlan planWavTruncate(const WavLayout& layout, std::size_t keptFrames);
// Patches a little-endian uint32 into a byte buffer at `off` — the RIFF/data size
// fields the truncate plan names. The caller guarantees off + 4 <= bytes.size()
// (the plan's offsets came from a valid parse of the same buffer).
// Patches a little-endian uint32 into a byte buffer at `off`. Caller guarantees
// off + 4 <= bytes.size() (the plan's offsets came from a valid parse of the same
// buffer).
void patchU32LE(std::vector<std::uint8_t>& bytes, std::size_t off, std::uint32_t v);
// --- Float32 WAV build -------------------------------------------------------
// Builds a minimal canonical 32-bit-float RIFF/WAVE byte buffer from interleaved
// double samples: RIFF chunk, WAVE form, fmt chunk (tag 3 = WAVE_FORMAT_IEEE_FLOAT,
// 16-byte body), data chunk (interleaved little-endian float32). `nch` channels,
// `rate` Hz, `frameCount` frames (total samples = frameCount * nch). Each double is
// narrowed to float by cast — the bank contract is 32-bit float (see FORMAT
// ASSUMPTION above); the reduction is intentional. The output round-trips through
// parseWavLayout/extractFloatFrames. The ingest shell decodes any non-canonical
// source through REAPER's PCM_source, then writes the bank copy with this.
// Builds a minimal canonical float32 RIFF/WAVE byte buffer from interleaved
// double samples (narrowed to float by cast). Round-trips through
// parseWavLayout/extractFloatFrames.
std::vector<std::uint8_t> buildFloat32Wav(int nch, std::uint32_t rate,
std::size_t frameCount,
const std::vector<double>& interleaved);
// --- Content identity (dedup hashes) -----------------------------------------
// Computes a deterministic FNV-1a 64-bit content hash over `len` bytes at `data`
// and returns it as a 16-character lowercase hex string. Designed to fill
// Sample::contentHash so the confirm-on-last-reference guardrail
// (BankBook::hashReferencedElsewhere) can distinguish "no other bank holds this
// file" from "another bank holds the same file." An empty buffer returns the bare
// FNV-1a 64-bit offset basis in hex (a stable, non-empty sentinel that two empty
// files would share, but real WAV files are never empty).
// Deterministic FNV-1a 64-bit content hash over `len` bytes, as 16-char lowercase
// hex. Fills Sample::contentHash for the confirm-on-last-reference dedup guardrail.
std::string hashBytes(const std::uint8_t* data, std::size_t len);
// WAV-aware content hash: hashes only the audio-defining content of a 32-bit-float
// RIFF/WAVE file — the `fmt ` chunk body + the `data` chunk payload — skipping all
// other RIFF chunks (e.g. `bext` origination timestamp, `iXML`, `LIST`/`INFO`, SMED).
//
// WHY: REAPER's offline renderer embeds render-varying metadata chunks (at minimum a
// `bext` chunk containing the origination date/time) even when the format config blob
// requests no BWF metadata. Two renders of identical audio therefore differ in those
// bytes, making whole-file hashes diverge and preventing dedup collapse.
//
// DOMAIN SEPARATION: the FNV-1a input is prefixed with the tag byte 'W' (0x57) before
// the fmt/data bytes are fed in, so a content hash can never equal a whole-file
// hashBytes result for a different file of the same size.
//
// FALLBACK: if `bytes` does not parse as a valid RIFF/WAVE with both a `fmt ` and a
// `data` chunk, the function falls back to whole-file hashBytes (no prefix tag) —
// identical to calling hashBytes(bytes.data(), bytes.size()). This ensures that an
// unrecognized or malformed file still gets a non-empty hash rather than silently
// skipping dedup.
//
// Called by both capture commit paths (offline and realtime) and the ingest import
// in place of the raw hashBytes call. Walks the container with the SAME chunk
// traversal parseWavLayout uses, so hashing and decoding can never desynchronize.
// WAV-aware content hash: hashes only the `fmt ` body + `data` payload, skipping
// other chunks. WHY: REAPER's offline renderer embeds a render-varying `bext`
// timestamp chunk even with no BWF metadata requested, so two renders of
// identical audio would otherwise hash differently and never dedup. Prefixed
// with tag byte 'W' so it can't collide with a same-size hashBytes result.
// Falls back to whole-file hashBytes (no prefix) for a file that doesn't parse.
std::string hashWavContent(const std::vector<std::uint8_t>& bytes);
} // namespace reasampler::capture