unity_asset_decode/audio/
mod.rs

1//! Audio processing module
2//!
3//! This module provides comprehensive audio processing capabilities for Unity assets,
4//! organized following UnityPy and unity-rs best practices.
5//!
6//! # Architecture
7//!
8//! The module is organized into several sub-modules:
9//! - `formats` - Audio format definitions and metadata
10//! - `types` - Core data structures (AudioClip, DecodedAudio, etc.)
11//! - `converter` - Main conversion logic from Unity objects
12//! - `decoder` - Audio decoding using Symphonia
13//! - `export` - Audio export functionality
14//!
15//! # Examples
16//!
17//! ```rust,no_run
18//! use unity_asset_decode::audio::{AudioCompressionFormat, AudioClipConverter, AudioDecoder};
19//! use unity_asset_decode::unity_version::UnityVersion;
20//!
21//! // Create a converter
22//! let converter = AudioClipConverter::new(UnityVersion::default());
23//!
24//! // Convert Unity object to AudioClip (assuming you have a UnityObject)
25//! // let audio_clip = converter.from_unity_object(&unity_object)?;
26//!
27//! // Create a decoder and decode to audio
28//! let decoder = AudioDecoder::new();
29//! // let decoded_audio = decoder.decode(&audio_clip)?;
30//!
31//! // Export the audio
32//! // AudioExporter::export_wav(&decoded_audio, "output.wav")?;
33//! ```
34
35pub mod converter;
36pub mod decoder;
37pub mod export;
38pub mod formats;
39pub mod types;
40
41// Re-export main types for easy access
42pub use converter::{AudioClipConverter, AudioClipProcessor}; // Processor is legacy alias
43pub use decoder::AudioDecoder;
44pub use export::{AudioExporter, AudioFormat, ExportOptions};
45pub use formats::{AudioCompressionFormat, AudioFormatInfo, FMODSoundType};
46pub use types::{
47    AudioAnalysis, AudioClip, AudioClipMeta, AudioInfo, AudioProperties, DecodedAudio,
48    StreamingInfo,
49};
50
51/// Main audio processing facade
52///
53/// This struct provides a high-level interface for audio processing,
54/// combining conversion, decoding, and export functionality.
55pub struct AudioProcessor {
56    converter: AudioClipConverter,
57    decoder: AudioDecoder,
58}
59
60impl AudioProcessor {
61    /// Create a new audio processor
62    pub fn new(version: crate::unity_version::UnityVersion) -> Self {
63        Self {
64            converter: AudioClipConverter::new(version),
65            decoder: AudioDecoder::new(),
66        }
67    }
68
69    /// Process Unity object to AudioClip
70    pub fn convert_object(
71        &self,
72        obj: &crate::object::UnityObject,
73    ) -> crate::error::Result<AudioClip> {
74        self.converter.from_unity_object(obj)
75    }
76
77    /// Decode audio clip to PCM data
78    pub fn decode_audio(&self, clip: &AudioClip) -> crate::error::Result<DecodedAudio> {
79        self.decoder.decode(clip)
80    }
81
82    /// Get audio data (either embedded or streamed)
83    pub fn get_audio_data(&self, clip: &AudioClip) -> crate::error::Result<Vec<u8>> {
84        self.converter.get_audio_data(clip)
85    }
86
87    /// Full pipeline: convert object -> decode -> export
88    pub fn process_and_export<P: AsRef<std::path::Path>>(
89        &self,
90        obj: &crate::object::UnityObject,
91        output_path: P,
92    ) -> crate::error::Result<()> {
93        let audio_clip = self.convert_object(obj)?;
94        let decoded_audio = self.decode_audio(&audio_clip)?;
95        AudioExporter::export_auto(&decoded_audio, output_path)
96    }
97
98    /// Check if a format can be processed
99    pub fn can_process(&self, format: AudioCompressionFormat) -> bool {
100        self.converter.can_process(format) && self.decoder.can_decode(format)
101    }
102
103    /// Get list of supported formats
104    pub fn supported_formats(&self) -> Vec<AudioCompressionFormat> {
105        let converter_formats = self.converter.supported_formats();
106        let decoder_formats = self.decoder.supported_formats();
107
108        // Return intersection of both lists
109        converter_formats
110            .into_iter()
111            .filter(|format| decoder_formats.contains(format))
112            .collect()
113    }
114
115    /// Load streaming data from external file
116    pub fn load_streaming_data(&self, clip: &AudioClip) -> crate::error::Result<Vec<u8>> {
117        self.converter.load_streaming_data(clip)
118    }
119}
120
121impl Default for AudioProcessor {
122    fn default() -> Self {
123        Self::new(crate::unity_version::UnityVersion::default())
124    }
125}
126
127/// Convenience functions for common operations
128/// Create an audio processor with default settings
129pub fn create_processor() -> AudioProcessor {
130    AudioProcessor::default()
131}
132
133/// Quick function to check if a format is supported
134pub fn is_format_supported(format: AudioCompressionFormat) -> bool {
135    let decoder = AudioDecoder::new();
136    decoder.can_decode(format)
137}
138
139/// Get all supported audio formats
140pub fn get_supported_formats() -> Vec<AudioCompressionFormat> {
141    let decoder = AudioDecoder::new();
142    decoder.supported_formats()
143}
144
145/// Quick function to decode audio data
146pub fn decode_audio_data(
147    format: AudioCompressionFormat,
148    data: Vec<u8>,
149) -> crate::error::Result<DecodedAudio> {
150    let audio_clip = AudioClip {
151        name: "decoded_audio".to_string(),
152        meta: types::AudioClipMeta::Modern {
153            compression_format: format,
154            channels: 2,
155            frequency: 44100,
156            bits_per_sample: 16,
157            length: 0.0,
158            load_type: 0,
159            is_tracker_format: false,
160            subsound_index: 0,
161            preload_audio_data: true,
162            load_in_background: false,
163            legacy_3d: false,
164        },
165        data,
166        ..Default::default()
167    };
168
169    let decoder = AudioDecoder::new();
170    decoder.decode(&audio_clip)
171}
172
173/// Quick function to export audio with automatic format detection
174pub fn export_audio<P: AsRef<std::path::Path>>(
175    audio: &DecodedAudio,
176    path: P,
177) -> crate::error::Result<()> {
178    AudioExporter::export_auto(audio, path)
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn test_format_support() {
187        assert!(is_format_supported(AudioCompressionFormat::PCM));
188        assert!(is_format_supported(AudioCompressionFormat::Vorbis));
189        assert!(is_format_supported(AudioCompressionFormat::MP3));
190    }
191
192    #[test]
193    fn test_processor_creation() {
194        let processor = create_processor();
195        // Basic test - processor should be created successfully
196        assert!(
197            !processor.supported_formats().is_empty() || processor.supported_formats().is_empty()
198        );
199    }
200
201    #[test]
202    fn test_supported_formats_list() {
203        let formats = get_supported_formats();
204        assert!(!formats.is_empty());
205    }
206
207    #[test]
208    fn test_audio_format_info() {
209        let format = AudioCompressionFormat::PCM;
210        let info = format.info();
211        assert_eq!(info.name, "PCM");
212        assert_eq!(info.extension, "wav");
213        assert!(!info.compressed);
214        assert!(!info.lossy);
215        assert!(info.supported);
216    }
217
218    #[test]
219    fn test_format_properties() {
220        assert!(AudioCompressionFormat::PCM.is_supported());
221        assert!(!AudioCompressionFormat::PCM.is_compressed());
222        assert!(!AudioCompressionFormat::PCM.is_lossy());
223        assert_eq!(AudioCompressionFormat::PCM.extension(), "wav");
224
225        assert!(AudioCompressionFormat::Vorbis.is_compressed());
226        assert!(AudioCompressionFormat::Vorbis.is_lossy());
227        assert_eq!(AudioCompressionFormat::Vorbis.extension(), "ogg");
228    }
229
230    #[test]
231    fn test_audio_clip_creation() {
232        let clip = AudioClip::new("test".to_string(), AudioCompressionFormat::PCM);
233        assert_eq!(clip.name, "test");
234        assert_eq!(clip.compression_format(), AudioCompressionFormat::PCM);
235    }
236}