feat(formats,viewer): standalone voice player covers all line categories
list_voice_clips now enumerates every spoken-line category so the standalone audio player covers them all — bound movie voices (\Movie\), in-mission radio (\Voice\, \etc\), and mission briefings (\Briefing\, whose BR<NN>_<MM> names lack "VOICE"); music (BGM_*) stays excluded. Viewer voice-library fixes: invalidate the library when a new game source loads, and don't latch an empty result as "loaded" (sounds.tbl always has thousands of clips, so empty = a failed read) so re-opening the menu retries. Voice decode reverted to the first sub-wave (base behaviour): a naive multi-sub-wave concat produced the wrong track for RT movies. The per-sub-wave splitter (to_xma_riffs) is kept + documented for the eventual real fix, which needs ground-truth playback instrumentation to resolve segments-vs-takes-vs-timecode placement. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1698,6 +1698,10 @@ fn poll_loader_channel(
|
||||
browser.files = files;
|
||||
browser.selected = None;
|
||||
browser.filter.clear();
|
||||
// A new game source is loaded — invalidate the voice library so the
|
||||
// browser re-reads sounds.tbl from THIS source next time it opens
|
||||
// (otherwise a stale/failed empty result from before load persists).
|
||||
*voice_lib = VoiceLibrary::default();
|
||||
info!("Loaded {} files", browser.files.len());
|
||||
}
|
||||
Ok(IsoLoaderMsg::FileLoaded { path, bytes }) => {
|
||||
@@ -1773,9 +1777,17 @@ fn poll_loader_channel(
|
||||
}
|
||||
}
|
||||
Ok(IsoLoaderMsg::VoiceLibraryLoaded { clips }) => {
|
||||
voice_lib.clips = clips;
|
||||
voice_lib.loaded = true;
|
||||
voice_lib.loading = false;
|
||||
// sounds.tbl always has thousands of clips, so an empty result means
|
||||
// the read failed (e.g. no game source, or opened only a bare pak).
|
||||
// Leave `loaded=false` so re-opening the menu retries instead of
|
||||
// latching an empty list forever.
|
||||
if clips.is_empty() {
|
||||
voice_lib.loaded = false;
|
||||
} else {
|
||||
voice_lib.clips = clips;
|
||||
voice_lib.loaded = true;
|
||||
}
|
||||
}
|
||||
Ok(IsoLoaderMsg::Cancelled) => {
|
||||
iso_state.loading = false;
|
||||
@@ -3128,6 +3140,10 @@ fn decode_sound_clip(
|
||||
use sylpheed_formats::{hash::name_hash, slb};
|
||||
let key = name_hash(clip_name);
|
||||
let bytes = read_sound_entry(source, key)?;
|
||||
// Decode the first sub-wave (base behaviour). NOTE: multi-sub-wave assembly
|
||||
// (segments vs alternate takes vs timecode-placed lines) is UNRESOLVED — a
|
||||
// naive concat produced the wrong track for RT movies. Pending ground-truth
|
||||
// voice-playback instrumentation (Canary APU/XMA) before a real fix.
|
||||
let riff = slb::to_xma_riff(&bytes).ok_or("not a decodable .slb")?;
|
||||
|
||||
let dir = std::env::temp_dir();
|
||||
@@ -3143,8 +3159,6 @@ fn decode_sound_clip(
|
||||
let status = Command::new("ffmpeg")
|
||||
.args(["-hide_banner", "-y", "-i"])
|
||||
.arg(&xma_path)
|
||||
// Downmix to mono using the left channel (holds the full signal for both
|
||||
// left-only and dual-mono sources), then clamp to the media length.
|
||||
.args(["-af", "pan=mono|c0=c0", "-t"])
|
||||
.arg(format!("{dur}"))
|
||||
.arg(&out_path)
|
||||
|
||||
Reference in New Issue
Block a user