This branch predates CI on `main`. `cargo fmt --all` only; no behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94 lines
3.3 KiB
Rust
94 lines
3.3 KiB
Rust
//! F2 — is a per-cue or per-bus GAIN on the disc?
|
||
//!
|
||
//! The port has no gain value anywhere in its export: `confirm` peaks at
|
||
//! −0.0 dBFS and sits 3 dB above the music. A cue record commonly carries a
|
||
//! volume beside its wave index. This asks the disc directly rather than
|
||
//! choosing a number.
|
||
//!
|
||
//! Method: dump every token of every `tables.pak` object whose tokens mention
|
||
//! SOUND/BANK/SE/BGM, so a gain field would appear as a token if one exists.
|
||
//! ⚠️ A NEGATIVE here is only as good as its coverage, so this prints the token
|
||
//! count per object and does not filter — a field missed by a filter would read
|
||
//! exactly like a field that is not there.
|
||
//!
|
||
//! cargo run --release -p sylpheed-formats --example sound_cue_fields -- $SYLPHEED_DISC
|
||
use sylpheed_formats::{idxd::IdxdObject, pak::PakArchive};
|
||
|
||
fn main() {
|
||
let a: Vec<String> = std::env::args().collect();
|
||
let root = a
|
||
.get(1)
|
||
.cloned()
|
||
.unwrap_or_else(|| std::env::var("SYLPHEED_DISC").unwrap());
|
||
let arc = PakArchive::open(format!("{root}/dat/tables.pak")).unwrap();
|
||
|
||
// Anything a gain would plausibly be called, plus the audio nouns.
|
||
const GAINY: &[&str] = &["VOL", "GAIN", "LEVEL", "DB", "ATTEN", "AMP", "MIX", "LOUD"];
|
||
let mut audio_objs = 0usize;
|
||
let mut gain_hits: Vec<(usize, String)> = Vec::new();
|
||
|
||
for (i, e) in arc.entries().iter().enumerate() {
|
||
let Ok(b) = arc.read(e) else { continue };
|
||
let Ok(o) = IdxdObject::parse(&b) else {
|
||
continue;
|
||
};
|
||
let t = o.tokens();
|
||
let up: Vec<String> = t.iter().map(|s| s.to_uppercase()).collect();
|
||
let is_audio = up.iter().any(|s| {
|
||
s.contains("SOUND")
|
||
|| s.contains("BANK_")
|
||
|| s.starts_with("SE_")
|
||
|| s.starts_with("BGM_")
|
||
});
|
||
if !is_audio {
|
||
continue;
|
||
}
|
||
audio_objs += 1;
|
||
println!(
|
||
"audio object #{i}: schema {:08x}, {} tokens",
|
||
o.schema_hash,
|
||
t.len()
|
||
);
|
||
for (j, tok) in up.iter().enumerate() {
|
||
if GAINY.iter().any(|g| tok.contains(g)) {
|
||
gain_hits.push((i, t[j].clone()));
|
||
}
|
||
}
|
||
}
|
||
|
||
println!("\naudio-bearing objects examined: {audio_objs}");
|
||
println!("tokens matching {GAINY:?}: {}", gain_hits.len());
|
||
for (i, tok) in &gain_hits {
|
||
println!(" object #{i}: {tok}");
|
||
}
|
||
if gain_hits.is_empty() {
|
||
println!("\n==> NO gain-like token in any audio object of tables.pak.");
|
||
}
|
||
|
||
// CONTROL: the search must be able to FIND a token when one is present.
|
||
// Without this, "no hits" is indistinguishable from a broken matcher.
|
||
let mut ctrl = 0usize;
|
||
for e in arc.entries() {
|
||
let Ok(b) = arc.read(e) else { continue };
|
||
let Ok(o) = IdxdObject::parse(&b) else {
|
||
continue;
|
||
};
|
||
ctrl += o
|
||
.tokens()
|
||
.iter()
|
||
.filter(|s| s.to_uppercase().contains("SE_UI"))
|
||
.count();
|
||
}
|
||
println!(
|
||
"\nCONTROL — the same matcher looking for a token known to exist (\"SE_UI\"): {ctrl} hits"
|
||
);
|
||
println!(
|
||
" {}",
|
||
if ctrl > 0 {
|
||
"PASS: the matcher finds tokens that are there"
|
||
} else {
|
||
"FAIL: matcher is broken, the negative above means nothing"
|
||
}
|
||
);
|
||
}
|