makepad/libs/game/audio/examples/vorbis_exact.rs
Admin acb315614d Vorbis: 556/556 files decode sample-exact (was 115/160)
The bug was NOT residue type 2 — that lead was a reasonable inference from
"stereo-only, transient-heavy", and it was wrong. The cause was overlap-add
placement of early long blocks.

A block's window is centred on `center` and reaches n/2 either side. A file
opening [256, 256, 2048, ...] puts the first long block's centre at 832, so it
starts at -192 — before sample zero. Those leading samples lie outside the
stream and must be DROPPED. The code used center.saturating_sub(n/2), clamping
the start to 0, which slid the whole block 192 samples later. Every sample was
corrupted until the centres grew past n/2, then decoding was perfect again.

That shape is exactly why it read as a residue fault: a wrong head with a
correct body looks like "specific blocks have wrong amplitude", and
correlation averaged it to 0.82. Mono appeared flawless only because no mono
file in this corpus happens to open with an early long block — a corpus
accident, not a decoder property.

  mono    47/47 exact, mean 1.00000  ->  186 files, mean 1.00000, min 1.00000
  stereo  68/107 exact, mean 0.826   ->  370 files, mean 1.00000, min 1.00000
  corpus  115/160 exact              ->  556/556, zero decode errors

The 73 "frame-count mismatches" are afconvert trimming further than the
container specifies; afinfo's valid-frame counts match OUR output exactly and
every file still correlates at 1.0000.

The fix is extracted into a shared overlap_add because decode and debug_raw
each had their own copy — a diagnostic that can disagree with the decoder it
diagnoses is worse than no diagnostic.

New test is fixtured on a file that opens [256, 256, 2048, ...] and asserts
PER-SAMPLE agreement, not just correlation: correlation alone hid this at 0.82.

Decode cost 5.16 ms/file; 11.5 MB compressed expands to 143.3 MB of f32 PCM.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 09:38:51 +02:00

77 lines
2.8 KiB
Rust

//! Is a mismatch a TRIM bug or a CONTENT bug?
//!
//! Correlation cannot tell those apart: a transient decoded correctly but
//! offset scores badly, and a transient decoded wrongly can still score 0.8 on
//! envelope shape alone. This sweeps whole-frame lags looking for an EXACT
//! sample match instead. A lag where the overlap matches bit-for-bit means the
//! decode is right and only the trim is wrong; no such lag anywhere means the
//! samples themselves differ and the trim is a red herring.
//!
//! Usage: vorbis_exact <file.ogg> <reference.wav> [max_lag_frames]
use makepad_game_audio as audio;
fn main() {
let args: Vec<String> = std::env::args().collect();
let ogg = std::fs::read(&args[1]).expect("read ogg");
let refwav = std::fs::read(&args[2]).expect("read ref");
let max_lag: i64 = args.get(3).and_then(|s| s.parse().ok()).unwrap_or(2048);
let got = audio::decode(&ogg).expect("decode ogg");
let want = audio::wav::decode(&refwav).expect("decode ref");
let ch = got.channels as usize;
println!(
"got {}ch {} frames | ref {}ch {} frames | delta {}",
got.channels,
got.frames(),
want.channels,
want.frames(),
got.frames() as i64 - want.frames() as i64
);
let g = &got.samples;
let w = &want.samples;
// afconvert writes 16-bit, so our f32 can only ever match to quantisation.
// Compare at that resolution or every sample "differs" for no useful reason.
let q = |x: f32| (x * 32768.0).round() as i32;
let mut best = (0i64, 0usize, 0usize, f64::INFINITY);
for lag in -max_lag..=max_lag {
let off = lag * ch as i64;
let (gs, ws) = if off >= 0 {
(off as usize, 0usize)
} else {
(0usize, (-off) as usize)
};
if gs >= g.len() || ws >= w.len() {
continue;
}
let n = (g.len() - gs).min(w.len() - ws);
if n < ch * 256 {
continue;
}
let mut exact = 0usize;
let mut sad = 0f64;
for i in 0..n {
let (a, b) = (g[gs + i], w[ws + i]);
if q(a) == q(b) {
exact += 1;
}
sad += (a - b).abs() as f64;
}
let mean_abs = sad / n as f64;
if exact > best.1 || (exact == best.1 && mean_abs < best.3) {
best = (lag, exact, n, mean_abs);
}
}
let (lag, exact, n, mean_abs) = best;
println!(
"best lag {lag} frames: {exact}/{n} samples exact ({:.2}%), mean|diff| {mean_abs:.6}",
100.0 * exact as f64 / n as f64
);
if exact * 100 / n.max(1) >= 99 {
println!("VERDICT: decode is correct, trim is off by {lag} frames");
} else {
println!("VERDICT: content differs — trim alone cannot explain this");
}
}