re: the resupply banks really are missing audio — the subtitles prove it

The corpus said 0.14 s is "far too short for the spoken line". That is a
judgement about audio, and audio judgements cannot be made in this container.
The subtitle tracks settle it without listening: each carries cue START
times, and a subtitle that appears at t seconds cannot sit inside a clip
shorter than t.

FFmpeg-measured (not estimated from a compression ratio -- the first version
of this example used an 8:1 guess, which is not good enough to hang a
conclusion on):

  hokyu_LS_s02A  D_450  cue 4.00 s  audio 1.41 s  MISSING
  hokyu_LS_s09A  D_451  cue 3.70 s  audio 1.81 s  MISSING
  hokyu_LS_s02H  D_453  cue 4.70 s  audio 0.07 s  MISSING
  hokyu_DS_s13A  D_452  cue 0.00 s  audio 1.21 s  no signal
  hokyu_DS_s07H  D_454  cue 0.00 s  audio 0.21 s  no signal

Three of five are decisive; the other two have their only cue at 0.0 s and
say nothing either way. So something is genuinely missing from these banks --
established independently of the leading-region work, and measured rather
than felt.

The fmt-variation probe I recorded as the next step is INCONCLUSIVE and is
written up as such: 36 combinations over VOICE_D_453's 22-packet leading
region all produced 0 PCM bytes, including ones that should be equivalent to
the crate's own synth_xma1_fmt, which does parse. So the probe tested my
hand-built fmt chunk, not the hypothesis, and it is NOT evidence that the
region is non-XMA. The retry should use the crate's helper.

Artifact: examples/voice_len_vs_subs.rs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
Sylpheed RE agent
2026-08-25 23:45:26 +00:00
parent 26bb0ec7a6
commit 391f4bd5e6
3 changed files with 127 additions and 3 deletions

View File

@@ -0,0 +1,78 @@
//! Is audio actually missing from the resupply banks? Ask the subtitles.
//!
//! The `.slb` decode of `VOICE_D_453` yields 0.14 s, which "looks too short" —
//! but that judgement was an impression. The subtitle track for each movie
//! carries cue times, so it says independently how long the spoken line runs.
//! If the last cue lands near the decoded length, nothing is missing; if it lands
//! far past it, audio really is being lost.
use sylpheed_formats::{movie_subtitle, slb, PakArchive};
/// XMA1 at 48 kHz stereo, 16-bit → bytes per second of PCM.
const PCM_BYTES_PER_SEC: f32 = 48000.0 * 2.0 * 2.0;
/// Decode one sub-wave with FFmpeg and return its length in seconds. Measured,
/// not estimated from a compression ratio — the ratio guess was the first version
/// of this and it is not good enough to hang a conclusion on.
fn decoded_secs(riff: &[u8]) -> f32 {
use std::io::Write;
use std::process::{Command, Stdio};
let Ok(mut c) = Command::new("ffmpeg")
.args(["-v", "error", "-i", "pipe:0", "-f", "s16le", "pipe:1"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
else {
return f32::NAN;
};
let buf = riff.to_vec();
let mut stdin = c.stdin.take().unwrap();
std::thread::spawn(move || {
let _ = stdin.write_all(&buf);
});
let out = c.wait_with_output().expect("ffmpeg");
out.stdout.len() as f32 / PCM_BYTES_PER_SEC
}
fn main() {
let disc = std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC");
let snd = PakArchive::open(format!("{disc}/dat/sound.pak")).expect("sound.pak");
let lang = PakArchive::open(format!("{disc}/dat/movie/eng.pak")).expect("eng.pak");
// movie → the bank the record table binds it to.
let bound = [
("hokyu_LS_s02A", 450),
("hokyu_LS_s09A", 451),
("hokyu_DS_s13A", 452),
("hokyu_LS_s02H", 453),
("hokyu_DS_s07H", 454),
];
println!(
"{:<18} {:>6} {:>10} {:>12} {:>12}",
"movie", "bank", "last cue s", "decoded s", "verdict"
);
for (movie, n) in bound {
let cues = movie_subtitle::track_voice_cues(&lang, movie);
let last = cues.iter().map(|(_, t)| *t).fold(0.0f32, f32::max);
let path = format!("eng\\etc\\VOICE_D_{n}.slb");
let Some(entry) = snd.find_by_name(&path) else { continue };
let bytes = snd.read(entry).expect("read");
// Sum the sub-waves' payloads as the decoder currently sees them.
let riffs = slb::to_xma_riffs(&bytes);
let secs: f32 = riffs.iter().map(|r| decoded_secs(r)).sum();
// One-directional: a subtitle that appears at t seconds cannot sit inside
// a clip shorter than t. A cue at 0.0 tells us nothing either way.
let verdict = if last == 0.0 {
"no cue signal"
} else if last > secs {
"AUDIO MISSING"
} else {
"consistent"
};
println!(
"{movie:<18} {:>6} {last:>10.2} {secs:>12.2} {verdict:>12} cues={}",
format!("D_{n}"),
cues.len()
);
}
}