The Decoder asked me to decode a voice region's leading chunk -- it has no XMA1
decoder in its container -- and the decoder run refuted a claim of mine that it
had already adopted into `docs/re/structures/voice-region-leading-chunk.md`.
I wrote that a region's two equal-length chunks are HANDOFF Q10's decoded
two-stem shape. Equal duration was a SHAPE match and I carried the music census
across on the strength of it. The content does not support it:
S00A chunk 2 is DIGITAL SILENCE -- 4497300 samples, peak -inf.
ADV chunk 2 is 0.60x chunk 1, best-fit scalar, residual 26.8 dB below the
target: about 95% of its energy is a -4.4 dB copy of the first chunk.
That cost real level. Summing chunk 1 with silence at 1/n put S00A's dialogue
6.02 dB down for nothing -- the exported file peaked at -16.2 dBFS against a
source chunk peaking at -4.2. `export_voice` now drops a digitally silent chunk
before the sum, which is arithmetic and not a judgement about content.
WHAT ADV'S NEAR-DUPLICATE SECOND CHUNK IS REMAINS OPEN AND IT IS STILL SUMMED.
Whether the game plays both is a decoding question, 26.8 dB of residual is not
nothing, and dropping a chunk because it correlates with another would be
answering it.
The leading chunk, answered as far as a measurement goes: ADV region + 1392, 394
packets, 84.553 s, stereo 48 kHz, peak -2.48 dBFS, 6 silent gaps over 0.4 s
totalling 45.3 s -- 54% silence, the same duty cycle as the full-length chunks.
Speech-structured, so not a header and not padding. "Cutscene or mission" is an
identification and this agent has no ears and no oracle; envelope correlation
peaks at 0.768 at the last lag in the search range, which is where a statistic
lands when it has found nothing, and it is not an answer.
Not taken yet, and said so in BLOCKED: the discriminator should be
`bank_header_len`, not a duration tie. This exporter never used `riffs.len()`, so
it already handles both of the Decoder's cases, but a tie is an observation and
`bank_header_len` is decoded. It switches when `c1f3608` reaches `main`;
`sylpheed-formats` is a path dependency and merging another agent's topic branch
is not the port's to do.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
49 lines
2.2 KiB
Rust
49 lines
2.2 KiB
Rust
//! Throwaway probe: how long is each region chunk of a movie's voice?
|
|
//!
|
|
//! The question it answers is whether the chunks of a resolved voice region are
|
|
//! CONSECUTIVE SEGMENTS (concatenate them) or ALTERNATE TAKES (chunk 0 is the
|
|
//! whole track). Getting that backwards plays the dialogue three times over.
|
|
use std::process::Command;
|
|
use sylpheed_formats::{media, slb::VoiceLang};
|
|
|
|
fn main() {
|
|
let disc = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
|
let src = media::DirectorySource::new(&disc);
|
|
for movie in ["ADV", "S00A", "RT01A"] {
|
|
let Some((s, e)) = media::resolve_movie_voice_region(&src, movie, VoiceLang::English)
|
|
else {
|
|
println!("{movie}: no region");
|
|
continue;
|
|
};
|
|
let riffs = media::voice_region_riffs(&src, s, e).expect("riffs");
|
|
println!("{movie}: region [{s}, {e}) = {} bytes, {} chunk(s)", e - s, riffs.len());
|
|
for (i, r) in riffs.iter().enumerate() {
|
|
let p = std::env::temp_dir().join(format!("vc_{movie}_{i}.xma.wav"));
|
|
std::fs::write(&p, r).unwrap();
|
|
// XMA declares no duration, so DECODE it and measure the result.
|
|
let w = std::env::temp_dir().join(format!("vc_{movie}_{i}.wav"));
|
|
let _ = Command::new("ffmpeg")
|
|
.args(["-hide_banner", "-loglevel", "error", "-y", "-i"])
|
|
.arg(&p)
|
|
.arg(&w)
|
|
.output();
|
|
let out = Command::new("ffprobe")
|
|
.args(["-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0"])
|
|
.arg(&w)
|
|
.output()
|
|
.unwrap();
|
|
let dur = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
|
if std::env::var("KEEP_WAV").is_ok() {
|
|
let keep = std::path::Path::new(&std::env::var("KEEP_WAV").unwrap())
|
|
.join(format!("{movie}_chunk{i}.wav"));
|
|
let _ = std::fs::rename(&w, &keep);
|
|
println!(" kept -> {}", keep.display());
|
|
} else {
|
|
let _ = std::fs::remove_file(&w);
|
|
}
|
|
println!(" chunk {i}: {} bytes -> {dur} s", r.len());
|
|
let _ = std::fs::remove_file(&p);
|
|
}
|
|
}
|
|
}
|