//! Audio format parsing — XMA and XWB handling. //! //! Xbox 360 games use **XMA** (Xbox Media Audio) as their primary audio codec. //! XMA is a proprietary Microsoft codec derived from WMA Pro. //! //! ## The Challenge //! XMA decoding requires Microsoft's proprietary XMA decoder, which is only //! available in the Windows DirectX runtime. There are two approaches: //! //! ### Option A: Convert on extraction (recommended for Milestone 1) //! Use `xma2encode.exe` (from Xbox 360 SDK) or `ffmpeg` (has partial XMA support) //! to pre-convert all audio to OGG/WAV during the extraction step. //! //! ```bash //! # Convert a single XMA file to WAV using ffmpeg //! ffmpeg -i audio.xma output.wav //! //! # Or use VGMStream's test.exe for more accurate XMA decoding //! ``` //! //! ### Option B: XMA2 software decoder //! The `xma2dec` project provides an open-source XMA2 decoder. //! GitHub: https://github.com/koolkdev/xmalib (research-quality) //! //! ### XWB Wave Bank format //! Xbox 360 games typically bundle audio into XWB (Xbox Wave Bank) files. //! These are containers that hold multiple XMA streams. //! Reference: https://github.com/microsoft/DirectXTK/tree/main/Audio use thiserror::Error; #[derive(Debug, Error)] pub enum AudioError { #[error("Unknown audio format magic: {0:?}")] UnknownMagic([u8; 4]), #[error("XMA decoding requires pre-conversion. See audio.rs for instructions.")] XmaNotSupported, #[error("Parse error: {0}")] Parse(String), } /// An audio clip decoded to raw PCM, ready for Bevy's audio system. #[derive(Debug, Clone)] pub struct GameAudio { /// Raw PCM samples (interleaved for stereo: L R L R ...) pub samples: Vec, pub channels: u16, pub sample_rate: u32, } impl GameAudio { /// Parse audio from raw bytes. /// /// Currently only WAV/PCM is supported. For XMA files, pre-convert using: /// `ffmpeg -i file.xma file.wav` pub fn from_bytes(bytes: &[u8]) -> Result { // Check for WAV magic if bytes.len() >= 4 && &bytes[..4] == b"RIFF" { return Self::from_wav(bytes); } // XMA magic bytes if bytes.len() >= 4 && ( &bytes[..4] == b"XWB\0" || // Wave bank &bytes[..4] == b"XMA2" // Raw XMA2 ) { return Err(AudioError::XmaNotSupported); } Err(AudioError::UnknownMagic( bytes[..4.min(bytes.len())].try_into().unwrap_or([0u8; 4]) )) } fn from_wav(bytes: &[u8]) -> Result { // Minimal WAV parser for PCM files // A proper implementation should use the `hound` crate: // https://crates.io/crates/hound // // TODO: Add `hound = "3"` to Cargo.toml and implement properly Err(AudioError::Parse( "WAV parsing TODO: add `hound` crate and implement".to_string() )) } }