re: the voice-region third chunk is a different structure from the BGM one
The port hit a 2+1 chunk signature on a resolved movie-voice region and asked
whether the bank-header explanation that closed HANDOFF Q10 also covers it,
rather than assuming it. It does not, and the discriminator is mechanical.
Disc-wide over the 95 English movie-voice regions the manifest binds:
78 open with a bank header -- bank_header_len fires, 10240 B = 5 packets
exactly, every time. That is the BGM case.
17 open with a leading headerless stream -- bank_header_len is None, and all
17 have length congruent to 1392 mod 2048, the disc s own derived data
offset. No other residue occurs.
0 begin at a RIFF.
Counting chunks does not discriminate: 8 bank-header regions also yield three
chunks. slb.rs already predicted this in its own doc comment -- the header
signature has "zero false positives on the 7993 mid-bank windows, where the
leading region IS real" -- and a voice region is a mid-bank window by
construction.
Also tested the obvious defence of dropping the leading chunk, that it is the
predecessor cue s audio: 0 of 17 leading spans lie inside any other resolved
region, 0.0 percent on every one. The test finds overlaps where they exist (16
overlapping pairs among the regions, 60 exactly-adjacent boundaries, 73 of 78
bank-header regions starting where another ends), so the zero is not the
instrument.
Left open, with reach: the census covers movie-voice regions only, and the same
stream carries the in-mission VOICE_D_* cues, which are not enumerated -- the
leading bytes plausibly belong to one of those. Could not be settled by
listening: no XMA1 decoder in this container, and sylpheed-cli audio info
reports these chunks as 16 channels / 4310 Hz / 2-bit, which is visibly wrong.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
This commit is contained in:
97
crates/sylpheed-formats/examples/voice_region_chunks.rs
Normal file
97
crates/sylpheed-formats/examples/voice_region_chunks.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
//! What ARE the chunks a movie-voice region decodes to?
|
||||
//!
|
||||
//! The port reports a resolved voice region decoding to **three** chunks — two
|
||||
//! of equal duration each spanning the whole movie, and a leading one that
|
||||
//! "matches nothing" — and notes that this is the same 2+1 signature the BGM
|
||||
//! banks showed before `bank_header_len` attributed the extra to a bank header.
|
||||
//! Two different asset kinds with one signature is worth checking, because if
|
||||
//! the same explanation applies then `bank_header_len` is incomplete, and if it
|
||||
//! does not then the leading chunk is something we are discarding.
|
||||
//!
|
||||
//! `slb.rs`'s own doc comment already predicts the answer and disagrees with
|
||||
//! "drop it": the header signature fires on 28 entries, all music banks, with
|
||||
//! "zero false positives on the 7 993 mid-bank windows, WHERE THE LEADING
|
||||
//! REGION IS REAL". A voice region is a mid-bank window by construction —
|
||||
//! `resolve_movie_voice_region` starts it at the PREDECESSOR cue's trailer.
|
||||
//!
|
||||
//! cargo run -p sylpheed-formats --example voice_region_chunks -- <disc-dir>
|
||||
use sylpheed_formats::media::{self, DirectorySource, DiscSource};
|
||||
use sylpheed_formats::slb::{self, VoiceLang};
|
||||
|
||||
fn main() {
|
||||
let disc = std::env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| std::env::var("SYLPHEED_DISC").expect("disc dir"));
|
||||
let src = DirectorySource::new(std::path::PathBuf::from(&disc));
|
||||
|
||||
// Disc-wide, not the four movies that motivated the question: every movie
|
||||
// the manifest binds a voice to.
|
||||
let movies: Vec<String> = {
|
||||
use sylpheed_formats::movie_manifest;
|
||||
let tpak = src.open_pak("dat/tables.pak").expect("tables.pak");
|
||||
let manifest = tpak
|
||||
.entries()
|
||||
.iter()
|
||||
.find_map(|e| tpak.read(e).ok().filter(|b| movie_manifest::is_manifest(b)))
|
||||
.expect("movie manifest");
|
||||
movie_manifest::parse(&manifest)
|
||||
.into_iter()
|
||||
.map(|m| m.movie)
|
||||
.collect()
|
||||
};
|
||||
println!("{} movies in the manifest\n", movies.len());
|
||||
let mut census: std::collections::BTreeMap<usize, usize> = Default::default();
|
||||
let (mut with_header, mut with_leading, mut no_leading) = (0, 0, 0);
|
||||
for movie in movies.iter().map(|s| s.as_str()) {
|
||||
let Some((start, end)) = media::resolve_movie_voice_region(&src, movie, VoiceLang::English)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let len = end - start;
|
||||
let Ok(bytes) = src.read_segment_range("dat/sound", start, len as usize) else {
|
||||
println!("{movie:8} region {start}..{end} unreadable");
|
||||
continue;
|
||||
};
|
||||
// Where does the first RIFF sit? Everything before it is the leading
|
||||
// headerless packet region.
|
||||
let first_riff = bytes
|
||||
.windows(4)
|
||||
.position(|w| w == b"RIFF")
|
||||
.map(|p| p as i64)
|
||||
.unwrap_or(-1);
|
||||
let hdr = slb::bank_header_len(&bytes);
|
||||
let riffs = slb::to_xma_riffs(&bytes);
|
||||
let kind = if first_riff <= 0 {
|
||||
no_leading += 1;
|
||||
"no leading region".to_string()
|
||||
} else if hdr == Some(first_riff as usize) {
|
||||
with_header += 1;
|
||||
format!("BANK HEADER ({first_riff} B = {} packets exactly)", first_riff / 2048)
|
||||
} else {
|
||||
with_leading += 1;
|
||||
*census.entry(first_riff as usize % 2048).or_default() += 1;
|
||||
format!(
|
||||
"leading STREAM ({first_riff} B = {} packets + {} B)",
|
||||
first_riff / 2048,
|
||||
first_riff % 2048
|
||||
)
|
||||
};
|
||||
println!(
|
||||
"{movie:10} {start:12}..{end:12} {len:9} B chunks {} {kind}",
|
||||
riffs.len()
|
||||
);
|
||||
if let Ok(dir) = std::env::var("VOICE_CHUNK_DUMP") {
|
||||
for (i, r) in riffs.iter().enumerate() {
|
||||
let _ = std::fs::write(format!("{dir}/{movie}-chunk{i}.wav"), r);
|
||||
}
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"\n{with_header} region(s) open with a BANK HEADER (bank_header_len fires)\n\
|
||||
{with_leading} open with a leading STREAM\n{no_leading} start at a RIFF"
|
||||
);
|
||||
println!("leading-stream length mod 2048, i.e. the derived data offset:");
|
||||
for (rem, n) in &census {
|
||||
println!(" {rem:5} B x{n}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user