Three-crate Cargo workspace structured per PROJECT.md spec: - crates/sylpheed-formats — Xbox 360 format parsers (no Bevy) - crates/sylpheed-viewer — Bevy 0.15 asset viewer + egui UI - crates/sylpheed-cli — CLI tools (extract/list/sniff/texture) Milestone 1 features: - XISO disc image reading via xdvdfs 0.8 - XPR2 texture container parsing + Morton de-tiling - D3DFORMAT → wgpu TextureFormat mapping (DXT1/3/5, DXN, ARGB) - Custom Bevy AssetLoader for .xpr files - Orbit camera (LMB orbit, RMB pan, scroll zoom) - egui file browser + RE notes panel - CLI: extract / list / sniff / texture info / texture export - GitHub Actions CI (Linux, macOS, Windows, WASM) - Trunk WASM build config Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
86 lines
2.9 KiB
Rust
86 lines
2.9 KiB
Rust
//! 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<f32>,
|
|
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<Self, AudioError> {
|
|
// 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<Self, AudioError> {
|
|
// 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()
|
|
))
|
|
}
|
|
}
|