From ba364dba5ae08911b3ce4119a2eea17732104369 Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Sat, 29 Aug 2026 16:40:24 +0000 Subject: [PATCH] port: check-capture needed two numbers -- the rate alone passed a 50%-silent file The Decoder found a blind spot in the bar I shipped last iteration. Raising the PulseAudio client buffer keeps cutting the gap RATE while total silence bottoms out and then doubles -- 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. My 20/s bar passed it. Same shape as the level table that cannot see a duplicated channel: one number, blind to the failure next door. I did not set a bar on their numbers, because I do not hold those files and the last two bars in this tool were wrong precisely from being invented. Instead I built a control in that regime -- `bigholes`, a real bed with 350 ms holes punched in -- and set the rule from four controls I can run: real music+SFX bed 1.1% silence, 3.3 gaps/s PASS voice track, mono, pauses 53.2% silence, 0.3 gaps/s PASS bed with 350 ms 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 1 and 3. The pair does: fail when >=10% is silent on every channel AND there is at least one gap per second. Real audio is either mostly not silent, or silent in a few long stretches -- not both at once. AND THE REGIME IT STILL CANNOT JUDGE IS PRINTED RATHER THAN PASSED. High silence with very few gaps is what a real voice track looks like and what an over-buffered capture looks like; nothing here separates them, so the tool says UNJUDGED and tells the reader to check against a known source. Inventing a bar for a regime with no control in it is how the previous two bars came to be wrong. A CONTROL THAT DOES NOT EXECUTE IS NOT A CONTROL: the tool returned immediately for single-channel input, so the mono voice track -- one of the four controls -- was never run through the check it was meant to control. Mono now skips only the duplicate test. Also recorded: the Decoder has withdrawn "the monitor-sink route cannot be fixed by configuration". A ~200 ms client buffer is worth a retry BEFORE anyone spends a session on a Canary rebuild. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF --- docs/port/AUDIO-VERIFICATION.md | 61 ++++++++++++++++++++++++++++----- tools/port/check-capture | 55 +++++++++++++++++++++++++---- 2 files changed, 101 insertions(+), 15 deletions(-) diff --git a/docs/port/AUDIO-VERIFICATION.md b/docs/port/AUDIO-VERIFICATION.md index 39da8956..bdaf357d 100644 --- a/docs/port/AUDIO-VERIFICATION.md +++ b/docs/port/AUDIO-VERIFICATION.md @@ -215,19 +215,62 @@ it produced said nothing about the game. gaps at a median of 1.4 ms** — quiet 16-bit passages really are zero for milliseconds — so neither statistic separates it from a starved file. -**What does separate them is the RATE**, by an order of magnitude in both -directions: +3. 🔴 **The gap RATE alone.** This one shipped, and the Decoder found it: raising + the client buffer 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 `PULSE_LATENCY_MSEC=500` capture scores + **1.3 gaps/s — better than a genuine music bed at 3.3 — while being 50 % + silence**, and a 20/s bar passed it. -| | gaps/s | median gap | all-channel silence | +**It takes two numbers, because either one alone is blind to the failure next +door** — the same shape as a level table that cannot see a duplicated channel. +Reproduced on a file held here (`bigholes`: a real bed with 350 ms holes punched +into it) so the regime is controlled rather than quoted: + +| control | all-channel silence | gaps/s | verdict | |---|---|---|---| -| the starved capture | **32.9** | 3.9 ms | 35.6 % | -| a real music+SFX bed | **3.3** | 1.4 ms | 1.1 % | -| a voice track, 53 % pauses | **0.03** | — | — | +| real music+SFX bed | 1.1 % | 3.3 | **PASS** | +| voice track, mono, real pauses | 53.2 % | 0.3 | **PASS** | +| bed with 350 ms holes | **46.3 %** | 3.2 | **FAIL** | +| the starved capture | **35.6 %** | 30.9 | **FAIL** | -The bar is **20 gaps/s** — 1.6× below the bad case, 6× above the worst good one, -and set from those controls rather than chosen and then justified. +Rate alone cannot separate rows 2 and 3; silence alone cannot separate rows 1 and +3. **The pair does:** fail when ≥ 10 % of the file is silent on every channel +*and* there is at least 1 gap per second. Real audio is either mostly not silent, +or silent in a few long stretches — not both at once. -### 🔴 The monitor-sink route cannot be fixed by configuration +### ⚠️ The regime this tool cannot judge, and says so + +**High silence with very few gaps is what a real voice track looks like (53.2 % +in 0.3 gaps/s) and also what an over-buffered capture looks like.** No statistic +here separates them. The tool prints `UNJUDGED` and tells you to check the file +against a known source rather than passing it silently — because inventing a bar +for a regime with no control in it is how the two bars above came to be wrong. + +⚠️ **A control that does not execute is not a control.** An earlier version +returned immediately for a single-channel file, so the mono voice track — one of +the four controls — was never actually run through the check it was meant to +control. Mono now skips only the duplicate test. + +### 🟡 The monitor-sink route may be fixable after all — retry before rebuilding + +An earlier version of this section said the route *"cannot be fixed by +configuration"*. **Withdrawn.** That inferred from the holes that the guest runs +below real time, without testing the alternative: **the client buffer is simply +tiny.** Xenia asks SDL for 256 samples — **5.33 ms** at 6 ch — against a stock +`daemon.conf` with no fragment tuning. + +| client buffer | silence | gaps/s | +|---|---|---| +| Xenia default (~5.3 ms) | 39.3 % | 30.5 | +| `PULSE_LATENCY_MSEC=200` | **15.6 %** | 3.5 | +| `PULSE_LATENCY_MSEC=500` | 50.1 % | 1.3 | + +⚠️ Not clean, and not like-for-like — 88 s against 347 s, and the short run covers +the splash logos where silence is real. But **the capture route deserves a retry +at ~200 ms before anyone spends a session on a Canary rebuild.** + +### The tap, if configuration is not enough `parec` reads a monitor that advances at wall-clock rate and substitutes silence, so **every moment the emulator runs below real time is a hole**, and the timebase diff --git a/tools/port/check-capture b/tools/port/check-capture index 8e626aa3..eaf19323 100755 --- a/tools/port/check-capture +++ b/tools/port/check-capture @@ -38,8 +38,15 @@ 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" -if [ "$ch" -lt 2 ]; then echo " single channel -- nothing to compare"; exit 0; fi +# ⚠️ 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 @@ -57,7 +64,6 @@ for i in $(seq 0 $((ch-1))); do printf ' ch%-2d peak %-12s %s\n' "$i" "$peak" "$s" done -dupes=0 for i in $(seq 0 $((ch-1))); do for j in $(seq $((i+1)) $((ch-1))); do if [ "${sums[i]}" = "${sums[j]}" ]; then @@ -66,6 +72,7 @@ for i in $(seq 0 $((ch-1))); do fi done done +fi # STARVATION: the second way a capture looks perfect and carries nothing. # @@ -147,11 +154,47 @@ print(" all-channel silence %.1f%%, %d gap(s) over 1 ms (%.1f/s), median gap %. # 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. -if rate_per_s >= 20.0 and med < 50.0: - print(" 🔴 STARVED: %.1f gaps per second at a median of %.1f ms." - % (rate_per_s, med)) - print(" The producer was not keeping the sink fed.") +# 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: +# +# real music bed 1.1 % silence, 3.3 gaps/s PASS +# voice track, mono 53.3 % silence, 0.3 gaps/s 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 +# (53.3 % in 0.3 gaps/s) 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