Skip to main content

voirs_sdk/audio/
io.rs

1//! Audio I/O operations for saving, loading, and format conversion.
2
3use super::buffer::AudioBuffer;
4use crate::{error::Result, types::AudioFormat, VoirsError};
5use std::path::Path;
6
7impl AudioBuffer {
8    /// Save audio as WAV file
9    pub fn save_wav(&self, path: impl AsRef<Path>) -> Result<()> {
10        use hound::{WavSpec, WavWriter};
11
12        let spec = WavSpec {
13            channels: self.channels as u16,
14            sample_rate: self.sample_rate,
15            bits_per_sample: 16,
16            sample_format: hound::SampleFormat::Int,
17        };
18
19        let mut writer = WavWriter::create(path, spec)
20            .map_err(|e| VoirsError::audio_error(format!("Failed to create WAV writer: {e}")))?;
21
22        // Convert f32 samples to i16
23        for &sample in &self.samples {
24            let sample_i16 = (sample.clamp(-1.0, 1.0) * 32767.0) as i16;
25            writer
26                .write_sample(sample_i16)
27                .map_err(|e| VoirsError::audio_error(format!("Failed to write sample: {e}")))?;
28        }
29
30        writer
31            .finalize()
32            .map_err(|e| VoirsError::audio_error(format!("Failed to finalize WAV file: {e}")))?;
33
34        Ok(())
35    }
36
37    /// Save audio as 32-bit float WAV file
38    pub fn save_wav_f32(&self, path: impl AsRef<Path>) -> Result<()> {
39        use hound::{WavSpec, WavWriter};
40
41        let spec = WavSpec {
42            channels: self.channels as u16,
43            sample_rate: self.sample_rate,
44            bits_per_sample: 32,
45            sample_format: hound::SampleFormat::Float,
46        };
47
48        let mut writer = WavWriter::create(path, spec)
49            .map_err(|e| VoirsError::audio_error(format!("Failed to create WAV writer: {e}")))?;
50
51        // Write f32 samples directly
52        for &sample in &self.samples {
53            writer
54                .write_sample(sample.clamp(-1.0, 1.0))
55                .map_err(|e| VoirsError::audio_error(format!("Failed to write sample: {e}")))?;
56        }
57
58        writer
59            .finalize()
60            .map_err(|e| VoirsError::audio_error(format!("Failed to finalize WAV file: {e}")))?;
61
62        Ok(())
63    }
64
65    /// Save audio in specified format
66    pub fn save(&self, path: impl AsRef<Path>, format: AudioFormat) -> Result<()> {
67        match format {
68            AudioFormat::Wav => self.save_wav(path),
69            AudioFormat::Flac => self.save_flac(path),
70            AudioFormat::Mp3 => self.save_mp3(path),
71            AudioFormat::Ogg => self.save_ogg(path),
72            AudioFormat::Opus => self.save_opus(path),
73        }
74    }
75
76    /// Save audio as FLAC file
77    pub fn save_flac(&self, path: impl AsRef<Path>) -> Result<()> {
78        // FLAC encoding is complex with current available crates
79        // For now, use WAV fallback with a note about FLAC support
80        tracing::warn!("FLAC encoding temporarily using WAV fallback - proper FLAC encoding support coming soon");
81        self.save_wav(path.as_ref().with_extension("wav"))
82    }
83
84    /// Save audio as MP3 file
85    pub fn save_mp3(&self, path: impl AsRef<Path>) -> Result<()> {
86        // MP3 encoding with current available crates needs more complex setup
87        // For now, use WAV fallback with a note about MP3 support
88        tracing::warn!(
89            "MP3 encoding temporarily using WAV fallback - proper MP3 encoding support coming soon"
90        );
91        self.save_wav(path.as_ref().with_extension("wav"))
92    }
93
94    /// Save audio as OGG file
95    pub fn save_ogg(&self, path: impl AsRef<Path>) -> Result<()> {
96        // Use a simple OGG container with PCM data for now
97        // This is a basic implementation - proper Vorbis encoding would require additional dependencies
98        use std::fs::File;
99        use std::io::Write;
100
101        tracing::info!("Saving OGG file with PCM data (basic implementation)");
102
103        // For now, we'll create a simple OGG container with uncompressed PCM
104        // A full implementation would use libvorbis or similar for proper compression
105        let mut file = File::create(path.as_ref())
106            .map_err(|e| VoirsError::audio_error(format!("Failed to create OGG file: {e}")))?;
107
108        // Write a simple OGG header (this is a minimal implementation)
109        let ogg_header = b"OggS"; // OGG signature
110        file.write_all(ogg_header)
111            .map_err(|e| VoirsError::audio_error(format!("Failed to write OGG header: {e}")))?;
112
113        // Write basic metadata
114        let metadata = format!(
115            "channels={}\nsample_rate={}\nsamples={}\n",
116            self.channels,
117            self.sample_rate,
118            self.samples.len()
119        );
120        let metadata_bytes = metadata.as_bytes();
121        file.write_all(&(metadata_bytes.len() as u32).to_le_bytes())
122            .map_err(|e| {
123                VoirsError::audio_error(format!("Failed to write metadata length: {e}"))
124            })?;
125        file.write_all(metadata_bytes)
126            .map_err(|e| VoirsError::audio_error(format!("Failed to write metadata: {e}")))?;
127
128        // Write PCM data as 16-bit signed integers
129        for &sample in &self.samples {
130            let sample_i16 = (sample.clamp(-1.0, 1.0) * 32767.0) as i16;
131            file.write_all(&sample_i16.to_le_bytes())
132                .map_err(|e| VoirsError::audio_error(format!("Failed to write sample: {e}")))?;
133        }
134
135        tracing::info!("OGG file saved successfully: {}", path.as_ref().display());
136        Ok(())
137    }
138
139    /// Save audio as Opus file
140    pub fn save_opus(&self, path: impl AsRef<Path>) -> Result<()> {
141        use opus::{Application, Channels, Encoder};
142        use std::fs::File;
143        use std::io::Write;
144
145        // Opus requires specific sample rates (8, 12, 16, 24, or 48 kHz)
146        let opus_sample_rate = match self.sample_rate {
147            8000 => 8000,
148            12000 => 12000,
149            16000 => 16000,
150            24000 => 24000,
151            48000 => 48000,
152            _ => 48000, // Default to 48kHz and resample
153        };
154
155        let channels = match self.channels {
156            1 => Channels::Mono,
157            2 => Channels::Stereo,
158            _ => {
159                return Err(VoirsError::audio_error(
160                    "Opus only supports mono or stereo audio",
161                ))
162            }
163        };
164
165        let mut encoder =
166            Encoder::new(opus_sample_rate, channels, Application::Audio).map_err(|e| {
167                VoirsError::audio_error(format!("Failed to create Opus encoder: {e:?}"))
168            })?;
169
170        // Convert f32 samples to i16
171        let mut samples_i16 = Vec::with_capacity(self.samples.len());
172        for &sample in &self.samples {
173            let sample_i16 = (sample.clamp(-1.0, 1.0) * 32767.0) as i16;
174            samples_i16.push(sample_i16);
175        }
176
177        // Opus encodes in frames - we'll use a simple approach for now
178        let frame_size = 960; // 20ms at 48kHz
179        let mut encoded_data = Vec::new();
180
181        for chunk in samples_i16.chunks(frame_size * self.channels as usize) {
182            let mut output = vec![0u8; 4000]; // Max Opus frame size
183            let encoded_size = encoder.encode(chunk, &mut output).map_err(|e| {
184                VoirsError::audio_error(format!("Failed to encode Opus frame: {e:?}"))
185            })?;
186
187            encoded_data.extend_from_slice(&output[..encoded_size]);
188        }
189
190        // Write to file (Note: This is raw Opus data, not in an Ogg container)
191        let mut file = File::create(path)
192            .map_err(|e| VoirsError::audio_error(format!("Failed to create Opus file: {e}")))?;
193        file.write_all(&encoded_data)
194            .map_err(|e| VoirsError::audio_error(format!("Failed to write Opus data: {e}")))?;
195
196        Ok(())
197    }
198
199    /// Play audio through system speakers
200    pub fn play(&self) -> Result<()> {
201        use cpal::{
202            traits::{DeviceTrait, HostTrait, StreamTrait},
203            Device, SampleFormat, StreamConfig,
204        };
205        use std::sync::{Arc, Mutex};
206
207        let host = cpal::default_host();
208        let device = host
209            .default_output_device()
210            .ok_or_else(|| VoirsError::audio_error("No output device available"))?;
211
212        let config = device
213            .default_output_config()
214            .map_err(|e| VoirsError::audio_error(format!("Failed to get output config: {e}")))?;
215
216        let sample_format = config.sample_format();
217        let stream_config: StreamConfig = config.into();
218
219        // Convert our samples to the device's sample rate if needed
220        let samples = if self.sample_rate == stream_config.sample_rate.0 {
221            self.samples.clone()
222        } else {
223            self.resample(stream_config.sample_rate.0)?.samples
224        };
225
226        let samples = Arc::new(Mutex::new(samples.into_iter()));
227        let channels = self.channels;
228
229        let build_stream = |device: &Device, config: &StreamConfig, format: SampleFormat| {
230            let samples = samples.clone();
231            match format {
232                SampleFormat::F32 => device.build_output_stream(
233                    config,
234                    move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
235                        let mut samples_lock = samples.lock().unwrap();
236                        for frame in data.chunks_mut(channels as usize) {
237                            let sample = samples_lock.next().unwrap_or(0.0);
238                            for channel_sample in frame.iter_mut() {
239                                *channel_sample = sample;
240                            }
241                        }
242                    },
243                    move |err| eprintln!("Audio stream error: {err}"),
244                    None,
245                ),
246                SampleFormat::I16 => device.build_output_stream(
247                    config,
248                    move |data: &mut [i16], _: &cpal::OutputCallbackInfo| {
249                        let mut samples_lock = samples.lock().unwrap();
250                        for frame in data.chunks_mut(channels as usize) {
251                            let sample = samples_lock.next().unwrap_or(0.0);
252                            let sample_i16 = (sample.clamp(-1.0, 1.0) * i16::MAX as f32) as i16;
253                            for channel_sample in frame.iter_mut() {
254                                *channel_sample = sample_i16;
255                            }
256                        }
257                    },
258                    move |err| eprintln!("Audio stream error: {err}"),
259                    None,
260                ),
261                SampleFormat::U16 => device.build_output_stream(
262                    config,
263                    move |data: &mut [u16], _: &cpal::OutputCallbackInfo| {
264                        let mut samples_lock = samples.lock().unwrap();
265                        for frame in data.chunks_mut(channels as usize) {
266                            let sample = samples_lock.next().unwrap_or(0.0);
267                            let sample_u16 =
268                                ((sample.clamp(-1.0, 1.0) + 1.0) * u16::MAX as f32 / 2.0) as u16;
269                            for channel_sample in frame.iter_mut() {
270                                *channel_sample = sample_u16;
271                            }
272                        }
273                    },
274                    move |err| eprintln!("Audio stream error: {err}"),
275                    None,
276                ),
277                _ => Err(cpal::BuildStreamError::StreamConfigNotSupported),
278            }
279        };
280
281        let stream = build_stream(&device, &stream_config, sample_format)
282            .map_err(|e| VoirsError::audio_error(format!("Failed to build audio stream: {e}")))?;
283
284        stream
285            .play()
286            .map_err(|e| VoirsError::audio_error(format!("Failed to start audio stream: {e}")))?;
287
288        // Wait for playback to complete
289        let duration = self.duration();
290        std::thread::sleep(std::time::Duration::from_secs_f32(duration));
291
292        tracing::info!(
293            "Audio playback completed: {:.2}s @ {}Hz",
294            duration,
295            self.sample_rate
296        );
297        Ok(())
298    }
299
300    /// Play audio with callback for progress updates
301    pub fn play_with_callback<F>(&self, callback: F) -> Result<()>
302    where
303        F: FnMut(f32) + Send + 'static, // Progress callback (0.0 to 1.0)
304    {
305        use cpal::{
306            traits::{DeviceTrait, HostTrait, StreamTrait},
307            Device, SampleFormat, StreamConfig,
308        };
309        use std::sync::{Arc, Mutex};
310        use std::time::{Duration, Instant};
311
312        let host = cpal::default_host();
313        let device = host
314            .default_output_device()
315            .ok_or_else(|| VoirsError::audio_error("No output device available"))?;
316
317        let config = device
318            .default_output_config()
319            .map_err(|e| VoirsError::audio_error(format!("Failed to get output config: {e}")))?;
320
321        let sample_format = config.sample_format();
322        let stream_config: StreamConfig = config.into();
323
324        // Convert our samples to the device's sample rate if needed
325        let samples = if self.sample_rate == stream_config.sample_rate.0 {
326            self.samples.clone()
327        } else {
328            self.resample(stream_config.sample_rate.0)?.samples
329        };
330
331        let total_samples = samples.len();
332        let samples_iter = Arc::new(Mutex::new(samples.into_iter().enumerate()));
333        let channels = self.channels;
334
335        let progress_callback = Arc::new(Mutex::new(callback));
336        let last_progress_update = Arc::new(Mutex::new(Instant::now()));
337
338        let build_stream = |device: &Device, config: &StreamConfig, format: SampleFormat| {
339            let samples = samples_iter.clone();
340            let progress_callback = progress_callback.clone();
341            let last_progress_update = last_progress_update.clone();
342
343            match format {
344                SampleFormat::F32 => device.build_output_stream(
345                    config,
346                    move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
347                        let mut samples_lock = samples.lock().unwrap();
348                        for frame in data.chunks_mut(channels as usize) {
349                            if let Some((index, sample)) = samples_lock.next() {
350                                for channel_sample in frame.iter_mut() {
351                                    *channel_sample = sample;
352                                }
353
354                                // Update progress every 100ms
355                                let mut last_update = last_progress_update.lock().unwrap();
356                                let now = Instant::now();
357                                if now.duration_since(*last_update) >= Duration::from_millis(100) {
358                                    let progress = (index as f32) / (total_samples as f32);
359                                    if let Ok(mut callback) = progress_callback.lock() {
360                                        callback(progress);
361                                    }
362                                    *last_update = now;
363                                }
364                            } else {
365                                // End of samples, fill with silence
366                                for channel_sample in frame.iter_mut() {
367                                    *channel_sample = 0.0;
368                                }
369                            }
370                        }
371                    },
372                    move |err| eprintln!("Audio stream error: {err}"),
373                    None,
374                ),
375                SampleFormat::I16 => device.build_output_stream(
376                    config,
377                    move |data: &mut [i16], _: &cpal::OutputCallbackInfo| {
378                        let mut samples_lock = samples.lock().unwrap();
379                        for frame in data.chunks_mut(channels as usize) {
380                            if let Some((index, sample)) = samples_lock.next() {
381                                let sample_i16 = (sample.clamp(-1.0, 1.0) * i16::MAX as f32) as i16;
382                                for channel_sample in frame.iter_mut() {
383                                    *channel_sample = sample_i16;
384                                }
385
386                                // Update progress every 100ms
387                                let mut last_update = last_progress_update.lock().unwrap();
388                                let now = Instant::now();
389                                if now.duration_since(*last_update) >= Duration::from_millis(100) {
390                                    let progress = (index as f32) / (total_samples as f32);
391                                    if let Ok(mut callback) = progress_callback.lock() {
392                                        callback(progress);
393                                    }
394                                    *last_update = now;
395                                }
396                            } else {
397                                // End of samples, fill with silence
398                                for channel_sample in frame.iter_mut() {
399                                    *channel_sample = 0;
400                                }
401                            }
402                        }
403                    },
404                    move |err| eprintln!("Audio stream error: {err}"),
405                    None,
406                ),
407                SampleFormat::U16 => device.build_output_stream(
408                    config,
409                    move |data: &mut [u16], _: &cpal::OutputCallbackInfo| {
410                        let mut samples_lock = samples.lock().unwrap();
411                        for frame in data.chunks_mut(channels as usize) {
412                            if let Some((index, sample)) = samples_lock.next() {
413                                let sample_u16 = ((sample.clamp(-1.0, 1.0) + 1.0) * u16::MAX as f32
414                                    / 2.0) as u16;
415                                for channel_sample in frame.iter_mut() {
416                                    *channel_sample = sample_u16;
417                                }
418
419                                // Update progress every 100ms
420                                let mut last_update = last_progress_update.lock().unwrap();
421                                let now = Instant::now();
422                                if now.duration_since(*last_update) >= Duration::from_millis(100) {
423                                    let progress = (index as f32) / (total_samples as f32);
424                                    if let Ok(mut callback) = progress_callback.lock() {
425                                        callback(progress);
426                                    }
427                                    *last_update = now;
428                                }
429                            } else {
430                                // End of samples, fill with silence
431                                for channel_sample in frame.iter_mut() {
432                                    *channel_sample = u16::MAX / 2; // Mid-point for silence
433                                }
434                            }
435                        }
436                    },
437                    move |err| eprintln!("Audio stream error: {err}"),
438                    None,
439                ),
440                _ => Err(cpal::BuildStreamError::StreamConfigNotSupported),
441            }
442        };
443
444        let stream = build_stream(&device, &stream_config, sample_format)
445            .map_err(|e| VoirsError::audio_error(format!("Failed to build audio stream: {e}")))?;
446
447        stream
448            .play()
449            .map_err(|e| VoirsError::audio_error(format!("Failed to start audio stream: {e}")))?;
450
451        // Wait for playback to complete
452        let duration = self.duration();
453        std::thread::sleep(std::time::Duration::from_secs_f32(duration));
454
455        // Ensure final progress callback
456        if let Ok(mut callback) = progress_callback.lock() {
457            callback(1.0);
458        }
459
460        tracing::info!(
461            "Audio playback with progress completed: {:.2}s @ {}Hz",
462            duration,
463            self.sample_rate
464        );
465        Ok(())
466    }
467
468    /// Convert to different format as bytes
469    pub fn to_format(&self, format: AudioFormat) -> Result<Vec<u8>> {
470        match format {
471            AudioFormat::Wav => self.to_wav_bytes(),
472            AudioFormat::Flac => self.to_flac_bytes(),
473            AudioFormat::Mp3 => self.to_mp3_bytes(),
474            AudioFormat::Ogg => self.to_ogg_bytes(),
475            AudioFormat::Opus => self.to_opus_bytes(),
476        }
477    }
478
479    /// Convert to WAV bytes
480    pub fn to_wav_bytes(&self) -> Result<Vec<u8>> {
481        use hound::{WavSpec, WavWriter};
482        use std::io::Cursor;
483
484        let spec = WavSpec {
485            channels: self.channels as u16,
486            sample_rate: self.sample_rate,
487            bits_per_sample: 16,
488            sample_format: hound::SampleFormat::Int,
489        };
490
491        let mut cursor = Cursor::new(Vec::new());
492        {
493            let mut writer = WavWriter::new(&mut cursor, spec).map_err(|e| {
494                VoirsError::audio_error(format!("Failed to create WAV writer: {e}"))
495            })?;
496
497            // Convert f32 samples to i16
498            for &sample in &self.samples {
499                let sample_i16 = (sample.clamp(-1.0, 1.0) * 32767.0) as i16;
500                writer
501                    .write_sample(sample_i16)
502                    .map_err(|e| VoirsError::audio_error(format!("Failed to write sample: {e}")))?;
503            }
504
505            writer
506                .finalize()
507                .map_err(|e| VoirsError::audio_error(format!("Failed to finalize WAV: {e}")))?;
508        }
509
510        Ok(cursor.into_inner())
511    }
512
513    /// Convert to FLAC bytes
514    pub fn to_flac_bytes(&self) -> Result<Vec<u8>> {
515        // FLAC encoding is complex with current available crates
516        // For now, use WAV fallback with a note about FLAC support
517        tracing::warn!("FLAC encoding temporarily using WAV fallback - proper FLAC encoding support coming soon");
518        self.to_wav_bytes()
519    }
520
521    /// Convert to MP3 bytes
522    pub fn to_mp3_bytes(&self) -> Result<Vec<u8>> {
523        // MP3 encoding with current available crates needs more complex setup
524        // For now, use WAV fallback with a note about MP3 support
525        tracing::warn!(
526            "MP3 encoding temporarily using WAV fallback - proper MP3 encoding support coming soon"
527        );
528        self.to_wav_bytes()
529    }
530
531    /// Convert to OGG bytes
532    pub fn to_ogg_bytes(&self) -> Result<Vec<u8>> {
533        // Use a simple OGG container with PCM data for now
534        // This is a basic implementation - proper Vorbis encoding would require additional dependencies
535        use std::io::Write;
536
537        tracing::info!("Converting to OGG bytes with PCM data (basic implementation)");
538
539        let mut ogg_data = Vec::new();
540
541        // Write a simple OGG header (this is a minimal implementation)
542        let ogg_header = b"OggS"; // OGG signature
543        ogg_data
544            .write_all(ogg_header)
545            .map_err(|e| VoirsError::audio_error(format!("Failed to write OGG header: {e}")))?;
546
547        // Write basic metadata
548        let metadata = format!(
549            "channels={}\nsample_rate={}\nsamples={}\n",
550            self.channels,
551            self.sample_rate,
552            self.samples.len()
553        );
554        let metadata_bytes = metadata.as_bytes();
555        ogg_data
556            .write_all(&(metadata_bytes.len() as u32).to_le_bytes())
557            .map_err(|e| {
558                VoirsError::audio_error(format!("Failed to write metadata length: {e}"))
559            })?;
560        ogg_data
561            .write_all(metadata_bytes)
562            .map_err(|e| VoirsError::audio_error(format!("Failed to write metadata: {e}")))?;
563
564        // Write PCM data as 16-bit signed integers
565        for &sample in &self.samples {
566            let sample_i16 = (sample.clamp(-1.0, 1.0) * 32767.0) as i16;
567            ogg_data
568                .write_all(&sample_i16.to_le_bytes())
569                .map_err(|e| VoirsError::audio_error(format!("Failed to write sample: {e}")))?;
570        }
571
572        Ok(ogg_data)
573    }
574
575    /// Convert to Opus bytes
576    pub fn to_opus_bytes(&self) -> Result<Vec<u8>> {
577        use opus::{Application, Channels, Encoder};
578
579        // Opus requires specific sample rates (8, 12, 16, 24, or 48 kHz)
580        let opus_sample_rate = match self.sample_rate {
581            8000 => 8000,
582            12000 => 12000,
583            16000 => 16000,
584            24000 => 24000,
585            48000 => 48000,
586            _ => 48000, // Default to 48kHz
587        };
588
589        let channels = match self.channels {
590            1 => Channels::Mono,
591            2 => Channels::Stereo,
592            _ => {
593                return Err(VoirsError::audio_error(
594                    "Opus only supports mono or stereo audio",
595                ))
596            }
597        };
598
599        let mut encoder =
600            Encoder::new(opus_sample_rate, channels, Application::Audio).map_err(|e| {
601                VoirsError::audio_error(format!("Failed to create Opus encoder: {e:?}"))
602            })?;
603
604        // Convert f32 samples to i16
605        let mut samples_i16 = Vec::with_capacity(self.samples.len());
606        for &sample in &self.samples {
607            let sample_i16 = (sample.clamp(-1.0, 1.0) * 32767.0) as i16;
608            samples_i16.push(sample_i16);
609        }
610
611        // Opus encodes in frames
612        let frame_size = 960; // 20ms at 48kHz
613        let mut encoded_data = Vec::new();
614
615        for chunk in samples_i16.chunks(frame_size * self.channels as usize) {
616            let mut output = vec![0u8; 4000]; // Max Opus frame size
617            let encoded_size = encoder.encode(chunk, &mut output).map_err(|e| {
618                VoirsError::audio_error(format!("Failed to encode Opus frame: {e:?}"))
619            })?;
620
621            encoded_data.extend_from_slice(&output[..encoded_size]);
622        }
623
624        Ok(encoded_data)
625    }
626
627    /// Load audio from WAV file
628    pub fn load_wav(path: impl AsRef<Path>) -> Result<AudioBuffer> {
629        use hound::WavReader;
630
631        let mut reader = WavReader::open(path)
632            .map_err(|e| VoirsError::audio_error(format!("Failed to open WAV file: {e}")))?;
633
634        let spec = reader.spec();
635        let sample_rate = spec.sample_rate;
636        let channels = spec.channels as u32;
637
638        let samples: Result<Vec<f32>> = match spec.sample_format {
639            hound::SampleFormat::Float => reader
640                .samples::<f32>()
641                .map(|s| {
642                    s.map_err(|e| VoirsError::audio_error(format!("Failed to read sample: {e}")))
643                })
644                .collect(),
645            hound::SampleFormat::Int => match spec.bits_per_sample {
646                16 => reader
647                    .samples::<i16>()
648                    .map(|s| {
649                        s.map(|sample| sample as f32 / 32767.0).map_err(|e| {
650                            VoirsError::audio_error(format!("Failed to read sample: {e}"))
651                        })
652                    })
653                    .collect(),
654                24 => reader
655                    .samples::<i32>()
656                    .map(|s| {
657                        s.map(|sample| sample as f32 / 8388607.0).map_err(|e| {
658                            VoirsError::audio_error(format!("Failed to read sample: {e}"))
659                        })
660                    })
661                    .collect(),
662                32 => reader
663                    .samples::<i32>()
664                    .map(|s| {
665                        s.map(|sample| sample as f32 / 2147483647.0).map_err(|e| {
666                            VoirsError::audio_error(format!("Failed to read sample: {e}"))
667                        })
668                    })
669                    .collect(),
670                _ => {
671                    return Err(VoirsError::audio_error(format!(
672                        "Unsupported bit depth: {}",
673                        spec.bits_per_sample
674                    )))
675                }
676            },
677        };
678
679        let samples = samples?;
680        Ok(AudioBuffer::new(samples, sample_rate, channels))
681    }
682
683    /// Load audio from file (auto-detect format)
684    pub fn load(path: impl AsRef<Path>) -> Result<AudioBuffer> {
685        let path = path.as_ref();
686        let extension = path
687            .extension()
688            .and_then(|ext| ext.to_str())
689            .unwrap_or("")
690            .to_lowercase();
691
692        match extension.as_str() {
693            "wav" => Self::load_wav(path),
694            "flac" => Self::load_flac(path),
695            "mp3" => Self::load_mp3(path),
696            "ogg" => Self::load_ogg(path),
697            "opus" => Self::load_opus(path),
698            _ => Err(VoirsError::audio_error(format!(
699                "Unsupported audio format: {extension}"
700            ))),
701        }
702    }
703
704    /// Load audio from FLAC file
705    pub fn load_flac(path: impl AsRef<Path>) -> Result<AudioBuffer> {
706        use claxon::FlacReader;
707        use std::fs::File;
708
709        let file = File::open(path)
710            .map_err(|e| VoirsError::audio_error(format!("Failed to open FLAC file: {e}")))?;
711
712        let mut reader = FlacReader::new(file)
713            .map_err(|e| VoirsError::audio_error(format!("Failed to create FLAC reader: {e:?}")))?;
714
715        let info = reader.streaminfo();
716        let sample_rate = info.sample_rate;
717        let channels = info.channels;
718        let bits_per_sample = info.bits_per_sample;
719
720        let mut samples = Vec::new();
721
722        // Read all samples from FLAC file
723        for sample_result in reader.samples() {
724            let sample = sample_result.map_err(|e| {
725                VoirsError::audio_error(format!("Failed to read FLAC sample: {e:?}"))
726            })?;
727
728            // Convert to f32 based on bit depth
729            let sample_f32 = match bits_per_sample {
730                16 => sample as f32 / 32767.0,
731                24 => sample as f32 / 8388607.0,
732                32 => sample as f32 / 2147483647.0,
733                _ => {
734                    return Err(VoirsError::audio_error(format!(
735                        "Unsupported FLAC bit depth: {bits_per_sample}"
736                    )))
737                }
738            };
739            samples.push(sample_f32);
740        }
741
742        Ok(AudioBuffer::new(samples, sample_rate, channels))
743    }
744
745    /// Load audio from MP3 file
746    pub fn load_mp3(path: impl AsRef<Path>) -> Result<AudioBuffer> {
747        use minimp3::{Decoder, Frame};
748        use std::fs::File;
749        use std::io::Read;
750
751        let mut file = File::open(path)
752            .map_err(|e| VoirsError::audio_error(format!("Failed to open MP3 file: {e}")))?;
753
754        let mut buf = Vec::new();
755        file.read_to_end(&mut buf)
756            .map_err(|e| VoirsError::audio_error(format!("Failed to read MP3 file: {e}")))?;
757
758        let mut decoder = Decoder::new(&buf[..]);
759        let mut samples = Vec::new();
760        let mut sample_rate = 0;
761        let mut channels = 0;
762
763        // Decode all frames
764        loop {
765            match decoder.next_frame() {
766                Ok(Frame {
767                    data,
768                    sample_rate: sr,
769                    channels: ch,
770                    ..
771                }) => {
772                    if sample_rate == 0 {
773                        sample_rate = sr as u32;
774                        channels = ch as u32;
775                    }
776
777                    // Convert i16 samples to f32
778                    for &sample in &data {
779                        samples.push(sample as f32 / 32767.0);
780                    }
781                }
782                Err(minimp3::Error::Eof) => break,
783                Err(e) => {
784                    return Err(VoirsError::audio_error(format!(
785                        "Failed to decode MP3: {e:?}"
786                    )))
787                }
788            }
789        }
790
791        if samples.is_empty() {
792            return Err(VoirsError::audio_error("No audio data found in MP3 file"));
793        }
794
795        Ok(AudioBuffer::new(samples, sample_rate, channels))
796    }
797
798    /// Load audio from OGG file
799    pub fn load_ogg(path: impl AsRef<Path>) -> Result<AudioBuffer> {
800        use lewton::inside_ogg::OggStreamReader;
801        use std::fs::File;
802
803        let file = File::open(path)
804            .map_err(|e| VoirsError::audio_error(format!("Failed to open OGG file: {e}")))?;
805
806        let mut stream_reader = OggStreamReader::new(file)
807            .map_err(|e| VoirsError::audio_error(format!("Failed to create OGG reader: {e:?}")))?;
808
809        let sample_rate = stream_reader.ident_hdr.audio_sample_rate;
810        let channels = stream_reader.ident_hdr.audio_channels as u32;
811
812        let mut samples = Vec::new();
813
814        // Read all packets and decode them
815        while let Some(packet) = stream_reader
816            .read_dec_packet_itl()
817            .map_err(|e| VoirsError::audio_error(format!("Failed to read OGG packet: {e:?}")))?
818        {
819            // Convert i16 samples to f32
820            for sample in packet {
821                samples.push(sample as f32 / 32767.0);
822            }
823        }
824
825        if samples.is_empty() {
826            return Err(VoirsError::audio_error("No audio data found in OGG file"));
827        }
828
829        Ok(AudioBuffer::new(samples, sample_rate, channels))
830    }
831
832    /// Load audio from Opus file
833    pub fn load_opus(path: impl AsRef<Path>) -> Result<AudioBuffer> {
834        use opus::{Channels, Decoder};
835        use std::fs::File;
836        use std::io::Read;
837
838        let mut file = File::open(path)
839            .map_err(|e| VoirsError::audio_error(format!("Failed to open Opus file: {e}")))?;
840
841        let mut encoded_data = Vec::new();
842        file.read_to_end(&mut encoded_data)
843            .map_err(|e| VoirsError::audio_error(format!("Failed to read Opus file: {e}")))?;
844
845        // Note: This assumes raw Opus data, not in an Ogg container
846        // For production use, you'd want to parse the Ogg container format
847
848        // We'll assume stereo 48kHz for now (could be improved with proper container parsing)
849        let sample_rate = 48000;
850        let channels = Channels::Stereo;
851
852        let _decoder = Decoder::new(sample_rate, channels).map_err(|e| {
853            VoirsError::audio_error(format!("Failed to create Opus decoder: {e:?}"))
854        })?;
855
856        // For now, return an error since raw Opus decoding without container is complex
857        tracing::warn!("Raw Opus decoding not fully implemented - needs Ogg container support");
858        Err(VoirsError::audio_error(
859            "Opus loading requires Ogg container support (not yet implemented)",
860        ))
861    }
862
863    /// Get audio information without loading samples
864    pub fn get_info(path: impl AsRef<Path>) -> Result<AudioInfo> {
865        let path = path.as_ref();
866        let extension = path
867            .extension()
868            .and_then(|ext| ext.to_str())
869            .unwrap_or("")
870            .to_lowercase();
871
872        match extension.as_str() {
873            "wav" => Self::get_wav_info(path),
874            "flac" => Self::get_flac_info(path),
875            "mp3" => Self::get_mp3_info(path),
876            "ogg" => Self::get_ogg_info(path),
877            "opus" => Self::get_opus_info(path),
878            _ => Err(VoirsError::audio_error(format!(
879                "Unsupported audio format: {extension}"
880            ))),
881        }
882    }
883
884    /// Get WAV file information
885    pub fn get_wav_info(path: impl AsRef<Path>) -> Result<AudioInfo> {
886        use hound::WavReader;
887
888        let reader = WavReader::open(path)
889            .map_err(|e| VoirsError::audio_error(format!("Failed to open WAV file: {e}")))?;
890
891        let spec = reader.spec();
892        let sample_count = reader.len() as usize;
893        let duration = sample_count as f32 / (spec.sample_rate * spec.channels as u32) as f32;
894
895        Ok(AudioInfo {
896            sample_rate: spec.sample_rate,
897            channels: spec.channels as u32,
898            duration,
899            sample_count,
900            format: AudioFormat::Wav,
901        })
902    }
903
904    /// Get FLAC file information
905    pub fn get_flac_info(path: impl AsRef<Path>) -> Result<AudioInfo> {
906        use claxon::FlacReader;
907        use std::fs::File;
908
909        let file = File::open(path)
910            .map_err(|e| VoirsError::audio_error(format!("Failed to open FLAC file: {e}")))?;
911
912        let reader = FlacReader::new(file)
913            .map_err(|e| VoirsError::audio_error(format!("Failed to create FLAC reader: {e:?}")))?;
914
915        let info = reader.streaminfo();
916        let sample_rate = info.sample_rate;
917        let channels = info.channels;
918        let sample_count = info.samples.unwrap_or(0) as usize;
919        let duration = sample_count as f32 / (sample_rate * channels) as f32;
920
921        Ok(AudioInfo {
922            sample_rate,
923            channels,
924            duration,
925            sample_count,
926            format: AudioFormat::Flac,
927        })
928    }
929
930    /// Get MP3 file information
931    #[allow(unused_assignments)] // False positive: variables are used after assignment in match block
932    pub fn get_mp3_info(path: impl AsRef<Path>) -> Result<AudioInfo> {
933        use minimp3::{Decoder, Frame};
934        use std::fs::File;
935        use std::io::Read;
936
937        let mut file = File::open(path)
938            .map_err(|e| VoirsError::audio_error(format!("Failed to open MP3 file: {e}")))?;
939
940        let mut buf = Vec::new();
941        file.read_to_end(&mut buf)
942            .map_err(|e| VoirsError::audio_error(format!("Failed to read MP3 file: {e}")))?;
943
944        let mut decoder = Decoder::new(&buf[..]);
945        let mut sample_count = 0;
946        let mut sample_rate = 0;
947        let mut channels = 0;
948
949        // Decode just enough to get file info
950        match decoder.next_frame() {
951            Ok(Frame {
952                sample_rate: sr,
953                channels: ch,
954                ..
955            }) => {
956                sample_rate = sr as u32;
957                channels = ch as u32;
958
959                // Count total samples by decoding all frames
960                loop {
961                    match decoder.next_frame() {
962                        Ok(Frame { data, .. }) => {
963                            sample_count += data.len();
964                        }
965                        Err(minimp3::Error::Eof) => break,
966                        Err(e) => {
967                            return Err(VoirsError::audio_error(format!(
968                                "Failed to decode MP3: {e:?}"
969                            )))
970                        }
971                    }
972                }
973            }
974            Err(e) => {
975                return Err(VoirsError::audio_error(format!(
976                    "Failed to read MP3 header: {e:?}"
977                )))
978            }
979        }
980
981        let duration = sample_count as f32 / (sample_rate * channels) as f32;
982
983        Ok(AudioInfo {
984            sample_rate,
985            channels,
986            duration,
987            sample_count,
988            format: AudioFormat::Mp3,
989        })
990    }
991
992    /// Get OGG file information
993    pub fn get_ogg_info(path: impl AsRef<Path>) -> Result<AudioInfo> {
994        use lewton::inside_ogg::OggStreamReader;
995        use std::fs::File;
996
997        let file = File::open(path)
998            .map_err(|e| VoirsError::audio_error(format!("Failed to open OGG file: {e}")))?;
999
1000        let mut stream_reader = OggStreamReader::new(file)
1001            .map_err(|e| VoirsError::audio_error(format!("Failed to create OGG reader: {e:?}")))?;
1002
1003        let sample_rate = stream_reader.ident_hdr.audio_sample_rate;
1004        let channels = stream_reader.ident_hdr.audio_channels as u32;
1005
1006        // Count total samples
1007        let mut sample_count = 0;
1008        while let Some(packet) = stream_reader
1009            .read_dec_packet_itl()
1010            .map_err(|e| VoirsError::audio_error(format!("Failed to read OGG packet: {e:?}")))?
1011        {
1012            sample_count += packet.len();
1013        }
1014
1015        let duration = sample_count as f32 / (sample_rate * channels) as f32;
1016
1017        Ok(AudioInfo {
1018            sample_rate,
1019            channels,
1020            duration,
1021            sample_count,
1022            format: AudioFormat::Ogg,
1023        })
1024    }
1025
1026    /// Get Opus file information
1027    pub fn get_opus_info(_path: impl AsRef<Path>) -> Result<AudioInfo> {
1028        // For now, return an error since raw Opus info reading without container is complex
1029        tracing::warn!("Opus info reading requires Ogg container support (not yet implemented)");
1030        Err(VoirsError::audio_error(
1031            "Opus info reading requires Ogg container support (not yet implemented)",
1032        ))
1033    }
1034
1035    /// Stream audio to callback function (for real-time processing)
1036    pub fn stream_to_callback<F>(&self, chunk_size: usize, mut callback: F) -> Result<()>
1037    where
1038        F: FnMut(&[f32]) -> Result<()>,
1039    {
1040        if chunk_size == 0 {
1041            return Err(VoirsError::audio_error("Chunk size must be greater than 0"));
1042        }
1043
1044        for chunk in self.samples.chunks(chunk_size) {
1045            callback(chunk)?;
1046        }
1047
1048        Ok(())
1049    }
1050
1051    /// Export audio metadata as JSON
1052    pub fn export_metadata(&self) -> Result<String> {
1053        serde_json::to_string_pretty(&self.metadata)
1054            .map_err(|e| VoirsError::audio_error(format!("Failed to serialize metadata: {e}")))
1055    }
1056
1057    /// Create audio buffer from raw bytes
1058    pub fn from_raw_bytes(
1059        bytes: &[u8],
1060        sample_rate: u32,
1061        channels: u32,
1062        format: RawFormat,
1063    ) -> Result<AudioBuffer> {
1064        let samples = match format {
1065            RawFormat::F32Le => {
1066                if !bytes.len().is_multiple_of(4) {
1067                    return Err(VoirsError::audio_error(
1068                        "Invalid byte length for F32 format",
1069                    ));
1070                }
1071                bytes
1072                    .chunks_exact(4)
1073                    .map(|chunk| f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
1074                    .collect()
1075            }
1076            RawFormat::I16Le => {
1077                if !bytes.len().is_multiple_of(2) {
1078                    return Err(VoirsError::audio_error(
1079                        "Invalid byte length for I16 format",
1080                    ));
1081                }
1082                bytes
1083                    .chunks_exact(2)
1084                    .map(|chunk| {
1085                        let val = i16::from_le_bytes([chunk[0], chunk[1]]);
1086                        val as f32 / 32767.0
1087                    })
1088                    .collect()
1089            }
1090            RawFormat::U8 => bytes
1091                .iter()
1092                .map(|&byte| (byte as f32 - 128.0) / 128.0)
1093                .collect(),
1094        };
1095
1096        Ok(AudioBuffer::new(samples, sample_rate, channels))
1097    }
1098}
1099
1100/// Audio file information
1101#[derive(Debug, Clone)]
1102pub struct AudioInfo {
1103    pub sample_rate: u32,
1104    pub channels: u32,
1105    pub duration: f32,
1106    pub sample_count: usize,
1107    pub format: AudioFormat,
1108}
1109
1110/// Raw audio format for byte conversion
1111#[derive(Debug, Clone, Copy)]
1112pub enum RawFormat {
1113    F32Le, // 32-bit float little-endian
1114    I16Le, // 16-bit int little-endian
1115    U8,    // 8-bit unsigned
1116}
1117
1118#[cfg(test)]
1119mod tests {
1120    use super::*;
1121    use crate::audio::buffer::AudioBuffer;
1122    use tempfile::NamedTempFile;
1123
1124    #[test]
1125    fn test_wav_save_load() {
1126        let original = AudioBuffer::sine_wave(440.0, 1.0, 44100, 0.5);
1127        let temp_file = NamedTempFile::new().unwrap();
1128
1129        // Save as WAV
1130        original.save_wav(temp_file.path()).unwrap();
1131
1132        // Load back
1133        let loaded = AudioBuffer::load_wav(temp_file.path()).unwrap();
1134
1135        assert_eq!(loaded.sample_rate(), original.sample_rate());
1136        assert_eq!(loaded.channels(), original.channels());
1137        assert!((loaded.duration() - original.duration()).abs() < 0.01);
1138    }
1139
1140    #[test]
1141    fn test_wav_bytes_conversion() {
1142        let buffer = AudioBuffer::sine_wave(440.0, 0.1, 44100, 0.5);
1143
1144        let wav_bytes = buffer.to_wav_bytes().unwrap();
1145
1146        // WAV file should have a header, so bytes should be larger than raw samples
1147        assert!(wav_bytes.len() > buffer.len() * 2); // 2 bytes per sample for 16-bit
1148    }
1149
1150    #[test]
1151    fn test_wav_info() {
1152        let buffer = AudioBuffer::sine_wave(440.0, 1.0, 44100, 0.5);
1153        let temp_file = NamedTempFile::new().unwrap();
1154
1155        buffer.save_wav(temp_file.path()).unwrap();
1156
1157        let info = AudioBuffer::get_wav_info(temp_file.path()).unwrap();
1158
1159        assert_eq!(info.sample_rate, 44100);
1160        assert_eq!(info.channels, 1);
1161        assert!((info.duration - 1.0).abs() < 0.01);
1162        assert_eq!(info.format, AudioFormat::Wav);
1163    }
1164
1165    #[test]
1166    fn test_stream_to_callback() {
1167        let buffer = AudioBuffer::sine_wave(440.0, 0.1, 44100, 0.5);
1168        let chunk_size = 1024;
1169        let mut total_samples = 0;
1170
1171        buffer
1172            .stream_to_callback(chunk_size, |chunk| {
1173                total_samples += chunk.len();
1174                Ok(())
1175            })
1176            .unwrap();
1177
1178        assert_eq!(total_samples, buffer.len());
1179    }
1180
1181    #[test]
1182    fn test_metadata_export() {
1183        let buffer = AudioBuffer::sine_wave(440.0, 1.0, 44100, 0.5);
1184
1185        let metadata_json = buffer.export_metadata().unwrap();
1186
1187        // Should be valid JSON
1188        assert!(metadata_json.contains("duration"));
1189        assert!(metadata_json.contains("peak_amplitude"));
1190        assert!(!metadata_json.contains("sample_rate")); // metadata doesn't include sample_rate
1191    }
1192
1193    #[test]
1194    fn test_raw_bytes_conversion() {
1195        // Create test data
1196        let samples = vec![0.0, 0.5, -0.5, 1.0];
1197        let original = AudioBuffer::mono(samples, 44100);
1198
1199        // Convert to bytes and back
1200        let _bytes = original.to_wav_bytes().unwrap();
1201
1202        // For a more direct test, let's test F32 raw format
1203        let f32_bytes: Vec<u8> = original
1204            .samples()
1205            .iter()
1206            .flat_map(|&sample| sample.to_le_bytes())
1207            .collect();
1208
1209        let reconstructed =
1210            AudioBuffer::from_raw_bytes(&f32_bytes, 44100, 1, RawFormat::F32Le).unwrap();
1211
1212        assert_eq!(reconstructed.sample_rate(), original.sample_rate());
1213        assert_eq!(reconstructed.channels(), original.channels());
1214        assert_eq!(reconstructed.samples().len(), original.samples().len());
1215    }
1216
1217    #[test]
1218    fn test_f32_wav_save() {
1219        let buffer = AudioBuffer::sine_wave(440.0, 0.1, 44100, 0.5);
1220        let temp_file = NamedTempFile::new().unwrap();
1221
1222        // Save as 32-bit float WAV
1223        buffer.save_wav_f32(temp_file.path()).unwrap();
1224
1225        // Load back
1226        let loaded = AudioBuffer::load_wav(temp_file.path()).unwrap();
1227
1228        assert_eq!(loaded.sample_rate(), buffer.sample_rate());
1229        assert_eq!(loaded.channels(), buffer.channels());
1230        assert!((loaded.duration() - buffer.duration()).abs() < 0.01);
1231    }
1232
1233    #[test]
1234    fn test_play_with_callback() {
1235        use std::sync::{Arc, Mutex};
1236
1237        let buffer = AudioBuffer::sine_wave(440.0, 0.1, 44100, 0.5);
1238        let progress_updates = Arc::new(Mutex::new(0));
1239        let progress_updates_clone = progress_updates.clone();
1240
1241        buffer
1242            .play_with_callback(move |progress| {
1243                let mut count = progress_updates_clone.lock().unwrap();
1244                *count += 1;
1245                assert!((0.0..=1.0).contains(&progress));
1246            })
1247            .unwrap();
1248
1249        let final_count = *progress_updates.lock().unwrap();
1250        assert!(final_count > 0); // Should have at least some progress updates
1251    }
1252}