The Decoder lost a finding whose only record was a script comment and asked whether I have the same. Audited every measurement-shaped token in comments across the exporter, the GDScript and the tools against everything in docs/. Seven candidates, six were my matcher (thousands separators, ranges written differently, precision). The findings are all in DECISIONS, including the leaf comment's capture-measured centres and the 11.5 px residual. The one real defect is the opposite: check-capture's control table and AUDIO-VERIFICATION.md had DRIFTED -- 53.3% against 53.2%, twice each, for one control whose file is gone so neither can be re-measured. They lost a finding to having one record; I lost a digit to having two with nothing keeping them equal. Fixed by citing rather than restating. Also corrects a message: I told them my computation reproduces their published centres to half a pixel. True, and MODEL against MODEL -- against the capture this corpus already records 992.0/467.2, an 11.5 px residual. The half-pixel agreement is two derivations of one model, the correlated-instrument shape I have been careful about all week and did not apply to my own message. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
263 lines
13 KiB
Bash
Executable File
263 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Provenance check for a multichannel capture, BEFORE anybody analyses it.
|
|
#
|
|
# tools/port/check-capture /path/to/capture.wav
|
|
#
|
|
# WHY THIS EXISTS. A 6-channel capture of the game's own output was analysed at
|
|
# length -- three controls, a drift test, a written-up negative -- and the file
|
|
# was corrupt. PulseAudio was remapping between two mismatched channel maps, and
|
|
# a 6-channel remap SILENTLY DROPS AND DUPLICATES: right duration, right channel
|
|
# count, plausible per-channel levels, no error anywhere. Two of the six channels
|
|
# were byte-identical copies of two others and two source channels were simply
|
|
# gone.
|
|
#
|
|
# The Decoder proved it with a control that needs no emulator and no disc: six
|
|
# channels each carrying a different tone through the same sink and the same
|
|
# `parec` invocation. Channels came back 400 / 3200 / 200 / 800 / 800 / 200 for
|
|
# an input of 400 / 800 / 200 / 1600 / 3200 / 6400 -- see
|
|
# `docs/re/audio-capture-channel-map-trap.md`. Setting the sink's `channel_map`
|
|
# to the guest's own and passing the same map to `parec` returns all six.
|
|
#
|
|
# THE DETECTABLE SIGNATURE IS AN EXACT DUPLICATE PAIR. Two channels of a real
|
|
# surround mix are never byte-identical over 70 s. Levels are not enough to catch
|
|
# it -- the corrupt file's per-channel peaks looked entirely reasonable, and it
|
|
# was only equal peak AND equal RMS to six decimals that prompted a hash.
|
|
#
|
|
# This is a NECESSARY check, not a sufficient one: passing it means the capture
|
|
# has no duplicated channels, not that it recorded the right thing.
|
|
set -euo pipefail
|
|
f="${1:?usage: check-capture FILE.wav}"
|
|
|
|
# Queried one field at a time. A combined `-show_entries` prints two values on
|
|
# ONE comma-separated line, and `read -r ch rate dur` then puts "48000,6" in
|
|
# `$ch` -- which every later arithmetic test rejects, in a script whose whole
|
|
# job is to be trusted about a file.
|
|
probe() { ffprobe -v error -select_streams a:0 -show_entries "$1" -of csv=p=0:nk=1 "$f" | head -1; }
|
|
ch=$(probe stream=channels)
|
|
rate=$(probe stream=sample_rate)
|
|
dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0:nk=1 "$f" | head -1)
|
|
printf '%s: %sch %sHz %.3fs\n' "$f" "$ch" "$rate" "$dur"
|
|
|
|
|
|
# ⚠️ MONO SKIPS THE DUPLICATE TEST AND STILL GETS THE STARVATION ONE. An earlier
|
|
# version returned immediately for a single channel, so the mono voice track --
|
|
# one of this tool's four controls -- was never actually run through the check it
|
|
# was supposed to control. A control that does not execute is not a control.
|
|
dupes=0
|
|
if [ "$ch" -lt 2 ]; then
|
|
echo " single channel -- no duplicate test, starvation still checked"
|
|
else
|
|
layout=5.1; [ "$ch" = 2 ] && layout=stereo
|
|
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
|
|
map=""; for i in $(seq 0 $((ch-1))); do map="$map -map [c$i] $tmp/c$i.wav"; done
|
|
split=""; for i in $(seq 0 $((ch-1))); do split="$split[c$i]"; done
|
|
# shellcheck disable=SC2086
|
|
ffmpeg -hide_banner -v error -y -i "$f" \
|
|
-filter_complex "channelsplit=channel_layout=$layout$split" $map
|
|
|
|
declare -a sums
|
|
for i in $(seq 0 $((ch-1))); do
|
|
s=$(ffmpeg -hide_banner -v error -i "$tmp/c$i.wav" -f md5 - | cut -d= -f2)
|
|
peak=$(ffmpeg -hide_banner -v info -i "$tmp/c$i.wav" -af astats -f null - 2>&1 \
|
|
| grep -m1 "Peak level dB" | sed 's/.*: //')
|
|
sums[i]="$s"
|
|
printf ' ch%-2d peak %-12s %s\n' "$i" "$peak" "$s"
|
|
done
|
|
|
|
for i in $(seq 0 $((ch-1))); do
|
|
for j in $(seq $((i+1)) $((ch-1))); do
|
|
if [ "${sums[i]}" = "${sums[j]}" ]; then
|
|
echo " 🔴 ch$i and ch$j are BYTE-IDENTICAL"
|
|
dupes=1
|
|
fi
|
|
done
|
|
done
|
|
fi
|
|
|
|
# STARVATION: the second way a capture looks perfect and carries nothing.
|
|
#
|
|
# A monitor sink advances at WALL-CLOCK rate and substitutes silence whenever the
|
|
# producer is late. An emulator running below real time therefore yields a file
|
|
# of exactly the right duration, right channel count, no duplicated channels --
|
|
# and chopped into fragments with holes punched between them, thousands of times
|
|
# over. Envelope correlation against such a file is destroyed by construction:
|
|
# what dominates the envelope is the dropout schedule, not the content.
|
|
#
|
|
# Measured on the capture that prompted this: 35.6 % of frames silent on all six
|
|
# channels, 10 482 alternating runs, median burst 13.5 ms and median gap 3.9 ms
|
|
# -- a 17.4 ms period, 57 Hz. The Decoder measured the untruncated original at
|
|
# 39.3 % and 10 595 runs; the two agree.
|
|
#
|
|
# THE DISCRIMINATOR IS THE RUN STRUCTURE, NOT THE SILENCE FRACTION. Real audio is
|
|
# full of silence -- a voice track is more than half gaps -- but those are TENS of
|
|
# runs of HUNDREDS of milliseconds. Dropout chop is THOUSANDS of runs of a few
|
|
# milliseconds. So the test is: many short all-channel gaps.
|
|
set +e
|
|
python3 - "$f" <<'PYEOF'
|
|
import array, struct, sys
|
|
d = open(sys.argv[1], 'rb').read()
|
|
i, fmt, off = 12, None, None
|
|
while i + 8 <= len(d):
|
|
cid = d[i:i+4]; sz = struct.unpack('<I', d[i+4:i+8])[0]
|
|
if cid == b'fmt ': fmt = d[i+8:i+8+sz]
|
|
elif cid == b'data':
|
|
off, declared = i + 8, sz; break
|
|
i += 8 + sz + (sz & 1)
|
|
if fmt is None or off is None:
|
|
print(" (not a plain WAV -- starvation check skipped)"); raise SystemExit(0)
|
|
tag = struct.unpack('<H', fmt[0:2])[0]
|
|
ch = struct.unpack('<H', fmt[2:4])[0]; rate = struct.unpack('<I', fmt[4:8])[0]
|
|
bits = struct.unpack('<H', fmt[14:16])[0] if len(fmt) >= 16 else 16
|
|
# REFUSE A FORMAT THIS CANNOT READ, rather than mis-reading it confidently.
|
|
#
|
|
# Everything below assumes 16-bit signed. An ALSA `type file` tee writes
|
|
# **float32** (`SND_PCM_FORMAT_FLOAT_LE`), and read as s16 it produces a
|
|
# plausible-looking file: the Decoder measured one and its only giveaway was
|
|
# per-channel peaks alternating EXACTLY -0.00 / -4.82, which is the two halves
|
|
# of each float landing in alternate channels. A checker that mis-reads a format
|
|
# is worse than one that has no opinion -- it is the shape of every failure this
|
|
# tool exists to catch.
|
|
#
|
|
# tag 1 = PCM, 3 = IEEE float, 0xFFFE = WAVE_FORMAT_EXTENSIBLE.
|
|
#
|
|
# ⚠️ EXTENSIBLE IS ACCEPTED AT 16 BITS, and the first version of this guard was
|
|
# not -- it rejected one of this tool's own controls, a file `ffprobe` correctly
|
|
# calls `pcm_s16le`. A format guard that refuses a legitimate capture is the same
|
|
# defect as one that mis-reads an illegitimate one, pointing the other way.
|
|
# `wBitsPerSample` is what actually decides how the samples are laid out here, so
|
|
# it is what the check turns on; a float tee is 32-bit and is still caught.
|
|
if tag not in (1, 0xFFFE) or bits != 16:
|
|
print(" 🔴 format tag %d, %d-bit -- this tool reads 16-bit PCM only." % (tag, bits))
|
|
print(" Read as s16 a float32 tee looks plausible and is not: its tell is")
|
|
print(" per-channel peaks alternating exactly, one float split across two")
|
|
print(" channels. Convert first: ffmpeg -i in.wav -c:a pcm_s16le out.wav")
|
|
raise SystemExit(4)
|
|
avail = len(d) - off
|
|
if declared == 0 or declared > avail:
|
|
# A streaming writer that never patched its header. The file may also be a
|
|
# copy taken while it was still being written -- which happened, and made a
|
|
# provenance claim wrong.
|
|
print(" ⚠️ data chunk declares %d bytes, %d present -- header never patched;"
|
|
% (declared, avail))
|
|
print(" treat the duration as unverified and check the file is complete.")
|
|
n = avail // (2 * ch)
|
|
a = array.array('h'); a.frombytes(d[off:off + n * 2 * ch])
|
|
sil = bytearray(n)
|
|
for f_ in range(n):
|
|
b = f_ * ch
|
|
if not any(a[b+c] for c in range(ch)): sil[f_] = 1
|
|
tot = sum(sil)
|
|
# A GAP IS A RUN, NOT A SAMPLE. The first version of this counted every frame
|
|
# whose channels were all exactly zero, and real audio crosses zero constantly --
|
|
# it scored a clean voice track at 5 947 "gaps" of median 0.0 ms and called it
|
|
# starved. The known-good control caught it. Only runs of at least 1 ms (48
|
|
# frames at 48 kHz) count: a zero-crossing is one sample, a dropout is hundreds.
|
|
MINGAP = max(1, rate // 1000)
|
|
runs_s, runs_n = [], []
|
|
cur, ln = sil[0], 0
|
|
for v in sil:
|
|
if v == cur: ln += 1
|
|
else:
|
|
(runs_s if cur else runs_n).append(ln); cur = v; ln = 1
|
|
(runs_s if cur else runs_n).append(ln)
|
|
runs_s = [r for r in runs_s if r >= MINGAP]
|
|
if not runs_s:
|
|
print(" all-channel silence 0.0% -- no gaps at all"); raise SystemExit(0)
|
|
rs = sorted(runs_s); med = 1000.0 * rs[len(rs)//2] / rate
|
|
secs = n / float(rate)
|
|
rate_per_s = len(runs_s) / secs
|
|
print(" all-channel silence %.1f%%, %d gap(s) over 1 ms (%.1f/s), median gap %.1f ms"
|
|
% (100.0*tot/n, len(runs_s), rate_per_s, med))
|
|
# THE THRESHOLD IS SET FROM CONTROLS, and the first two I invented were both
|
|
# wrong -- they failed real audio. Measured:
|
|
#
|
|
# the starved capture 32.9 gaps/s, median 3.9 ms, 35.6 % silent
|
|
# a real music+SFX bed 3.3 gaps/s, median 1.4 ms, 1.1 % silent
|
|
# a voice track, 53 % pauses 0.03 gaps/s
|
|
#
|
|
# Real audio does contain short all-zero runs -- a quiet passage in 16-bit is
|
|
# genuinely zero for milliseconds -- so neither the gap COUNT nor the median
|
|
# length separates them. The RATE does, by an order of magnitude in both
|
|
# directions, and 20/s sits between with a 1.6x margin below the bad case and
|
|
# 6x above the worst good one.
|
|
# TWO NUMBERS, BECAUSE ONE CANNOT SEE THE FAILURE NEXT DOOR.
|
|
#
|
|
# The first version of this tested the gap RATE alone, at 20/s. The Decoder then
|
|
# measured what a LARGER client buffer does, and the relationship is not
|
|
# monotonic: raising `PULSE_LATENCY_MSEC` keeps cutting the rate while total
|
|
# silence bottoms out and then doubles, because an over-large buffer starves in a
|
|
# few enormous holes instead of many small ones. Its 500 ms capture scores
|
|
# **1.3 gaps/s -- better than a genuine music bed at 3.3 -- while being 50 %
|
|
# silence**, and my bar passed it. Reproduced here on a file I hold: `bigholes`,
|
|
# a real bed with 350 ms holes punched in, is 46.3 % silence at 3.2 gaps/s.
|
|
#
|
|
# That is the same shape as the level table that could not see a duplicated
|
|
# channel. One number, blind to the neighbouring failure.
|
|
#
|
|
# Controls, all four measured here. 🔴 THE FIGURES LIVE IN THE DOC, NOT HERE.
|
|
#
|
|
# This table used to restate them, and two of the numbers had DRIFTED from
|
|
# `AUDIO-VERIFICATION.md`: 53.3 % here against 53.2 % there, in two places each,
|
|
# for the same control. Neither can be re-measured -- that control file was
|
|
# transient and is gone -- so there is no way to say which copy aged.
|
|
#
|
|
# That is the mirror of the trap the Decoder named the same day: they lost a
|
|
# finding because its only record was a script comment; this lost a digit because
|
|
# a finding had TWO records and nothing kept them equal. A number copied into a
|
|
# second place will drift from the first, and the drift is invisible because both
|
|
# copies look authoritative.
|
|
#
|
|
# So the doc is the record and this cites it.
|
|
#
|
|
# real music bed 1.1 % silence, 3.3 gaps/s PASS
|
|
# voice track, mono see AUDIO-VERIFICATION.md PASS (real pauses)
|
|
# bed with big holes 46.3 % silence, 3.2 gaps/s FAIL
|
|
# the starved capture 35.6 % silence, 30.9 gaps/s FAIL
|
|
#
|
|
# Rate alone cannot separate rows 2 and 3; silence alone cannot separate rows 1
|
|
# and 3, nor 2 and 3. The pair does.
|
|
if tot / float(n) >= 0.10 and rate_per_s >= 1.0:
|
|
print(" 🔴 STARVED: %.1f%% of the file is silent on every channel, in %.1f gaps"
|
|
% (100.0 * tot / n, rate_per_s))
|
|
print(" per second (median %.1f ms). Real audio is either mostly not" % med)
|
|
print(" silent, or silent in a few long stretches -- not both at once.")
|
|
raise SystemExit(3)
|
|
|
|
# ⚠️ THE REGIME THIS TOOL CANNOT JUDGE, said out loud rather than passed
|
|
# silently. High silence with FEW gaps is what a real voice track looks like
|
|
# (AUDIO-VERIFICATION.md §7 has the figure) and also what an over-buffered
|
|
# capture looks like. No
|
|
# statistic here separates them, and inventing a bar for a regime I have no
|
|
# control in is how the last two bars in this file came to be wrong.
|
|
if tot / float(n) >= 0.10:
|
|
print(" ⚠️ %.1f%% silent in only %.1f gaps/s -- UNJUDGED. That is the shape of"
|
|
% (100.0 * tot / n, rate_per_s))
|
|
print(" a real voice track AND of an over-buffered capture, and this tool")
|
|
print(" cannot tell them apart. Check it against a known source before")
|
|
print(" concluding anything from it.")
|
|
PYEOF
|
|
starved=$?
|
|
set -e
|
|
if [ "$starved" = 4 ]; then
|
|
# The duplicate test ran (bytes are bytes) but starvation did not. Saying
|
|
# "PASS" here would be the tool claiming a check it skipped.
|
|
echo "PARTIAL: channels checked, starvation NOT checked -- unreadable sample format."
|
|
exit 2
|
|
fi
|
|
if [ "$starved" = 3 ]; then
|
|
echo "FAIL: the recording is starved. A monitor sink advances at wall-clock rate"
|
|
echo " and substitutes silence when the producer is late, so this file has"
|
|
echo " the right duration and holes punched through the content. Correlation"
|
|
echo " against it is meaningless. See docs/port/AUDIO-VERIFICATION.md §7."
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$dupes" = 1 ]; then
|
|
echo "FAIL: duplicated channels. A surround remap drops and duplicates silently;"
|
|
echo " channels are missing from this file. Do not analyse it -- fix the"
|
|
echo " sink's channel_map and re-record. See docs/port/AUDIO-VERIFICATION.md."
|
|
exit 1
|
|
fi
|
|
echo "PASS: no duplicated channels. (Necessary, not sufficient -- this says"
|
|
echo " nothing about whether the right thing was recorded.)"
|