unity_asset_decode/audio/
types.rs

1//! Audio data structures
2//!
3//! This module defines the core data structures used for audio processing.
4
5use super::formats::AudioCompressionFormat;
6use serde::{Deserialize, Serialize};
7
8/// Streaming info for external audio data
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10pub struct StreamingInfo {
11    pub offset: u64,
12    pub size: u32,
13    pub path: String,
14}
15
16/// AudioClip metadata variants for different Unity versions
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub enum AudioClipMeta {
19    /// Legacy format (Unity < 5.0)
20    Legacy {
21        format: i32,
22        type_: i32,
23        is_3d: bool,
24        use_hardware: bool,
25    },
26    /// Modern format (Unity >= 5.0)
27    Modern {
28        load_type: i32,
29        channels: i32,
30        frequency: i32,
31        bits_per_sample: i32,
32        length: f32,
33        is_tracker_format: bool,
34        subsound_index: i32,
35        preload_audio_data: bool,
36        load_in_background: bool,
37        legacy_3d: bool,
38        compression_format: AudioCompressionFormat,
39    },
40}
41
42impl Default for AudioClipMeta {
43    fn default() -> Self {
44        AudioClipMeta::Modern {
45            load_type: 0,
46            channels: 2,
47            frequency: 44100,
48            bits_per_sample: 16,
49            length: 0.0,
50            is_tracker_format: false,
51            subsound_index: 0,
52            preload_audio_data: true,
53            load_in_background: false,
54            legacy_3d: false,
55            compression_format: AudioCompressionFormat::PCM,
56        }
57    }
58}
59
60/// AudioClip object representation
61///
62/// This structure contains all the data needed to represent a Unity AudioClip object.
63/// It includes both metadata and the actual audio data.
64#[derive(Debug, Clone, Serialize, Deserialize, Default)]
65pub struct AudioClip {
66    pub name: String,
67    pub meta: AudioClipMeta,
68    pub source: Option<String>,
69    pub offset: u64,
70    pub size: u64,
71    pub stream_info: StreamingInfo,
72    pub data: Vec<u8>,
73
74    // Version-specific fields
75    pub ambisonic: Option<bool>,
76}
77
78impl AudioClip {
79    /// Create a new AudioClip with basic parameters
80    pub fn new(name: String, format: AudioCompressionFormat) -> Self {
81        Self {
82            name,
83            meta: AudioClipMeta::Modern {
84                compression_format: format,
85                load_type: 0,
86                channels: 2,
87                frequency: 44100,
88                bits_per_sample: 16,
89                length: 0.0,
90                is_tracker_format: false,
91                subsound_index: 0,
92                preload_audio_data: true,
93                load_in_background: false,
94                legacy_3d: false,
95            },
96            ..Default::default()
97        }
98    }
99
100    /// Get compression format
101    pub fn compression_format(&self) -> AudioCompressionFormat {
102        match &self.meta {
103            AudioClipMeta::Legacy { .. } => AudioCompressionFormat::Unknown,
104            AudioClipMeta::Modern {
105                compression_format, ..
106            } => *compression_format,
107        }
108    }
109
110    /// Get audio properties
111    pub fn properties(&self) -> AudioProperties {
112        match &self.meta {
113            AudioClipMeta::Legacy { .. } => AudioProperties {
114                channels: 2,
115                sample_rate: 44100,
116                bits_per_sample: 16,
117                length: 0.0,
118            },
119            AudioClipMeta::Modern {
120                channels,
121                frequency,
122                bits_per_sample,
123                length,
124                ..
125            } => AudioProperties {
126                channels: *channels,
127                sample_rate: *frequency,
128                bits_per_sample: *bits_per_sample,
129                length: *length,
130            },
131        }
132    }
133
134    /// Check if audio has data
135    pub fn has_data(&self) -> bool {
136        !self.data.is_empty()
137    }
138
139    /// Check if audio uses external streaming
140    pub fn is_streamed(&self) -> bool {
141        !self.stream_info.path.is_empty() && self.stream_info.size > 0
142    }
143
144    /// Get audio info
145    pub fn info(&self) -> AudioInfo {
146        let format = self.compression_format();
147        let properties = self.properties();
148
149        AudioInfo {
150            name: self.name.clone(),
151            format,
152            format_info: format.info(),
153            properties,
154            has_data: self.has_data(),
155            is_streamed: self.is_streamed(),
156            data_size: self.data.len(),
157        }
158    }
159
160    /// Validate audio clip data consistency
161    pub fn validate(&self) -> Result<(), String> {
162        if self.name.is_empty() {
163            return Err("AudioClip name cannot be empty".to_string());
164        }
165
166        let properties = self.properties();
167        if properties.channels <= 0 {
168            return Err("Invalid channel count".to_string());
169        }
170
171        if properties.sample_rate <= 0 {
172            return Err("Invalid sample rate".to_string());
173        }
174
175        if !self.has_data() && !self.is_streamed() {
176            return Err("No audio data available and not streamed".to_string());
177        }
178
179        Ok(())
180    }
181}
182
183/// Audio properties structure
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct AudioProperties {
186    pub channels: i32,
187    pub sample_rate: i32,
188    pub bits_per_sample: i32,
189    pub length: f32,
190}
191
192/// Audio information structure
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct AudioInfo {
195    pub name: String,
196    pub format: AudioCompressionFormat,
197    pub format_info: super::formats::AudioFormatInfo,
198    pub properties: AudioProperties,
199    pub has_data: bool,
200    pub is_streamed: bool,
201    pub data_size: usize,
202}
203
204/// Decoded audio data
205#[derive(Debug, Clone)]
206pub struct DecodedAudio {
207    pub samples: Vec<f32>,
208    pub sample_rate: u32,
209    pub channels: u32,
210    pub duration: f32,
211}
212
213impl DecodedAudio {
214    /// Create new decoded audio
215    pub fn new(samples: Vec<f32>, sample_rate: u32, channels: u32) -> Self {
216        let duration = samples.len() as f32 / (sample_rate * channels) as f32;
217        Self {
218            samples,
219            sample_rate,
220            channels,
221            duration,
222        }
223    }
224
225    /// Get total sample count
226    pub fn sample_count(&self) -> usize {
227        self.samples.len()
228    }
229
230    /// Get frame count (samples per channel)
231    pub fn frame_count(&self) -> usize {
232        self.samples.len() / self.channels as usize
233    }
234
235    /// Convert to interleaved i16 samples
236    pub fn to_i16_samples(&self) -> Vec<i16> {
237        self.samples
238            .iter()
239            .map(|&sample| (sample.clamp(-1.0, 1.0) * i16::MAX as f32) as i16)
240            .collect()
241    }
242
243    /// Convert to interleaved i32 samples
244    pub fn to_i32_samples(&self) -> Vec<i32> {
245        self.samples
246            .iter()
247            .map(|&sample| (sample.clamp(-1.0, 1.0) * i32::MAX as f32) as i32)
248            .collect()
249    }
250}
251
252/// Audio analysis results
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct AudioAnalysis {
255    pub duration: f32,
256    pub sample_rate: u32,
257    pub channels: u32,
258    pub bit_depth: u32,
259    pub format: AudioCompressionFormat,
260    pub file_size: usize,
261    pub peak_amplitude: f32,
262    pub rms_amplitude: f32,
263}
264
265impl AudioAnalysis {
266    /// Create analysis from decoded audio
267    pub fn from_decoded(
268        decoded: &DecodedAudio,
269        format: AudioCompressionFormat,
270        file_size: usize,
271    ) -> Self {
272        let peak_amplitude = decoded
273            .samples
274            .iter()
275            .map(|&s| s.abs())
276            .fold(0.0f32, f32::max);
277
278        let rms_amplitude = if !decoded.samples.is_empty() {
279            let sum_squares: f32 = decoded.samples.iter().map(|&s| s * s).sum();
280            (sum_squares / decoded.samples.len() as f32).sqrt()
281        } else {
282            0.0
283        };
284
285        Self {
286            duration: decoded.duration,
287            sample_rate: decoded.sample_rate,
288            channels: decoded.channels,
289            bit_depth: 32, // f32 samples
290            format,
291            file_size,
292            peak_amplitude,
293            rms_amplitude,
294        }
295    }
296}