//! Audio output and file export module //! //! This module provides functionality for real-time audio playback and //! exporting generated music to audio files. use crate::SAMPLE_RATE; use crate::core::Composition; use crate::sequencer::Sequencer; use std::fs; use std::path::Path; use std::sync::{Arc, Mutex}; /// Audio output configuration #[derive(Debug, Clone)] pub struct AudioConfig { pub sample_rate: f32, pub buffer_size: usize, pub channels: usize, } impl Default for AudioConfig { fn default() -> Self { Self { sample_rate: SAMPLE_RATE, buffer_size: 512, channels: 2, // Stereo } } } /// Real-time audio player using cpal pub struct AudioPlayer { sequencer: Arc>, is_playing: bool, } impl AudioPlayer { /// Create a new audio player pub fn new(_config: AudioConfig) -> Result { let sequencer = Arc::new(Mutex::new(Sequencer::new(120.0))); Ok(Self { sequencer, is_playing: false, }) } /// Load a composition into the player pub fn load_composition(&mut self, composition: &Composition) -> Result<(), String> { let mut sequencer = self .sequencer .lock() .map_err(|e| format!("Lock error: {}", e))?; sequencer.load_composition(composition) } /// Start real-time audio playback pub fn start_playback(&mut self) -> Result<(), String> { use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; let host = cpal::default_host(); let device = host .default_output_device() .ok_or("No output device available")?; let supported_config = device .default_output_config() .map_err(|e| format!("Failed to get default config: {}", e))?; let sample_format = supported_config.sample_format(); let config = supported_config.into(); let sequencer = Arc::clone(&self.sequencer); let stream = match sample_format { cpal::SampleFormat::F32 => { device.build_output_stream( &config, move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { if let Ok(mut seq) = sequencer.lock() { let _ = seq.process_audio(data); } else { // Fill with silence if we can't lock for sample in data.iter_mut() { *sample = 0.0; } } }, |err| eprintln!("Audio stream error: {}", err), None, ) } cpal::SampleFormat::I16 => { device.build_output_stream( &config, move |data: &mut [i16], _: &cpal::OutputCallbackInfo| { let mut float_buffer = vec![0.0f32; data.len()]; if let Ok(mut seq) = sequencer.lock() { let _ = seq.process_audio(&mut float_buffer); } // Convert f32 to i16 for (i, &sample) in float_buffer.iter().enumerate() { data[i] = (sample * i16::MAX as f32) as i16; } }, |err| eprintln!("Audio stream error: {}", err), None, ) } cpal::SampleFormat::U16 => { device.build_output_stream( &config, move |data: &mut [u16], _: &cpal::OutputCallbackInfo| { let mut float_buffer = vec![0.0f32; data.len()]; if let Ok(mut seq) = sequencer.lock() { let _ = seq.process_audio(&mut float_buffer); } // Convert f32 to u16 for (i, &sample) in float_buffer.iter().enumerate() { let sample_u16 = ((sample + 1.0) * 0.5 * u16::MAX as f32) as u16; data[i] = sample_u16; } }, |err| eprintln!("Audio stream error: {}", err), None, ) } _ => { return Err("Unsupported sample format".to_string()); } } .map_err(|e| format!("Failed to build stream: {}", e))?; stream .play() .map_err(|e| format!("Failed to play stream: {}", e))?; // Start the sequencer if let Ok(mut seq) = self.sequencer.lock() { seq.play(); } self.is_playing = true; // Keep the stream alive (in a real application, you'd want better lifecycle management) std::mem::forget(stream); Ok(()) } /// Stop playback pub fn stop_playback(&mut self) -> Result<(), String> { if let Ok(mut seq) = self.sequencer.lock() { seq.stop(); } self.is_playing = false; Ok(()) } /// Pause playback pub fn pause_playback(&mut self) -> Result<(), String> { if let Ok(mut seq) = self.sequencer.lock() { seq.pause(); } Ok(()) } /// Resume playback pub fn resume_playback(&mut self) -> Result<(), String> { if let Ok(mut seq) = self.sequencer.lock() { seq.play(); } Ok(()) } /// Set playback position pub fn set_position(&mut self, position: f32) -> Result<(), String> { if let Ok(mut seq) = self.sequencer.lock() { seq.set_position(position); } Ok(()) } /// Set tempo pub fn set_tempo(&mut self, tempo: f32) -> Result<(), String> { if let Ok(mut seq) = self.sequencer.lock() { seq.set_tempo(tempo); } Ok(()) } /// Get sequencer for direct control pub fn get_sequencer(&self) -> Arc> { Arc::clone(&self.sequencer) } } /// Audio file exporter pub struct AudioExporter { sample_rate: f32, } impl AudioExporter { /// Create a new audio exporter pub fn new(sample_rate: f32) -> Self { Self { sample_rate } } /// Ensure output directory exists fn ensure_output_dir() -> Result<(), String> { let output_dir = Path::new("output"); if !output_dir.exists() { fs::create_dir_all(output_dir) .map_err(|e| format!("Failed to create output directory: {}", e))?; } Ok(()) } /// Get full path for output file fn get_output_path(filename: &str) -> String { format!("output/{}", filename) } /// Export a composition to a WAV file /// /// # Arguments /// * `composition` - The composition to export /// * `filename` - Output filename /// * `duration_seconds` - Duration to export in seconds (None for full composition) pub fn export_wav( &self, composition: &Composition, filename: &str, duration_seconds: Option, ) -> Result<(), String> { Self::ensure_output_dir()?; let output_path = Self::get_output_path(filename); let spec = hound::WavSpec { channels: 1, // Mono for simplicity sample_rate: self.sample_rate as u32, bits_per_sample: 16, sample_format: hound::SampleFormat::Int, }; let mut writer = hound::WavWriter::create(&output_path, spec) .map_err(|e| format!("Failed to create WAV file: {}", e))?; // Create a sequencer for rendering let mut sequencer = Sequencer::new(composition.params.tempo); sequencer .load_composition(composition) .map_err(|e| format!("Failed to load composition: {}", e))?; sequencer.play(); // Calculate total samples to render let export_duration = duration_seconds .unwrap_or(composition.total_duration * 60.0 / composition.params.tempo); let total_samples = (export_duration * self.sample_rate) as usize; // Render audio in chunks let chunk_size = 1024; let mut buffer = vec![0.0f32; chunk_size]; let mut samples_rendered = 0; while samples_rendered < total_samples { let samples_to_render = (total_samples - samples_rendered).min(chunk_size); buffer.resize(samples_to_render, 0.0); // Process audio if let Err(e) = sequencer.process_audio(&mut buffer) { return Err(format!("Audio processing error: {}", e)); } // Convert to i16 and write to file for &sample in &buffer { let sample_i16 = (sample * i16::MAX as f32) as i16; writer .write_sample(sample_i16) .map_err(|e| format!("Failed to write sample: {}", e))?; } samples_rendered += samples_to_render; // Progress indication (optional) if samples_rendered % (self.sample_rate as usize) == 0 { let progress = samples_rendered as f32 / total_samples as f32 * 100.0; println!("Export progress: {:.1}%", progress); } } writer .finalize() .map_err(|e| format!("Failed to finalize WAV file: {}", e))?; println!("Successfully exported to {}", output_path); Ok(()) } /// Export a composition to a stereo WAV file with separate channels for different tracks pub fn export_stereo_wav( &self, composition: &Composition, filename: &str, duration_seconds: Option, ) -> Result<(), String> { Self::ensure_output_dir()?; let output_path = Self::get_output_path(filename); let spec = hound::WavSpec { channels: 2, // Stereo sample_rate: self.sample_rate as u32, bits_per_sample: 16, sample_format: hound::SampleFormat::Int, }; let mut writer = hound::WavWriter::create(&output_path, spec) .map_err(|e| format!("Failed to create WAV file: {}", e))?; // Create a sequencer for rendering let mut sequencer = Sequencer::new(composition.params.tempo); sequencer .load_composition(composition) .map_err(|e| format!("Failed to load composition: {}", e))?; sequencer.play(); // Calculate total samples to render let export_duration = duration_seconds .unwrap_or(composition.total_duration * 60.0 / composition.params.tempo); let total_samples = (export_duration * self.sample_rate) as usize; // Render audio in chunks let chunk_size = 1024; let mut buffer = vec![0.0f32; chunk_size]; let mut samples_rendered = 0; while samples_rendered < total_samples { let samples_to_render = (total_samples - samples_rendered).min(chunk_size); buffer.resize(samples_to_render, 0.0); // Process audio if let Err(e) = sequencer.process_audio(&mut buffer) { return Err(format!("Audio processing error: {}", e)); } // Write stereo samples (duplicate mono to both channels) for &sample in &buffer { let sample_i16 = (sample * i16::MAX as f32) as i16; writer .write_sample(sample_i16) // Left channel .map_err(|e| format!("Failed to write left sample: {}", e))?; writer .write_sample(sample_i16) // Right channel .map_err(|e| format!("Failed to write right sample: {}", e))?; } samples_rendered += samples_to_render; // Progress indication if samples_rendered % (self.sample_rate as usize) == 0 { let progress = samples_rendered as f32 / total_samples as f32 * 100.0; println!("Export progress: {:.1}%", progress); } } writer .finalize() .map_err(|e| format!("Failed to finalize WAV file: {}", e))?; println!("Successfully exported stereo to {}", output_path); Ok(()) } /// Export multiple takes of a composition with different parameters pub fn export_variations( &self, base_composition: &Composition, filename_prefix: &str, variations: usize, duration_seconds: Option, ) -> Result<(), String> { Self::ensure_output_dir()?; for i in 0..variations { // Create variation by modifying parameters let mut params = base_composition.params.clone(); params.complexity = (i as f32 / variations as f32).clamp(0.1, 1.0); params.rhythmic_density = 0.5 + (i as f32 / variations as f32) * 0.4; let mut variation = Composition::new(params); variation .generate() .map_err(|e| format!("Failed to generate variation {}: {}", i, e))?; let filename = format!("{}_{:02}.wav", filename_prefix, i + 1); self.export_wav(&variation, &filename, duration_seconds)?; } println!("Successfully exported {} variations", variations); Ok(()) } /// Export composition as raw audio data (for further processing) pub fn export_raw_audio( &self, composition: &Composition, duration_seconds: Option, ) -> Result, String> { // Create a sequencer for rendering let mut sequencer = Sequencer::new(composition.params.tempo); sequencer .load_composition(composition) .map_err(|e| format!("Failed to load composition: {}", e))?; sequencer.play(); // Calculate total samples to render let export_duration = duration_seconds .unwrap_or(composition.total_duration * 60.0 / composition.params.tempo); let total_samples = (export_duration * self.sample_rate) as usize; let mut audio_data = Vec::with_capacity(total_samples); // Render audio in chunks let chunk_size = 1024; let mut buffer = vec![0.0f32; chunk_size]; let mut samples_rendered = 0; while samples_rendered < total_samples { let samples_to_render = (total_samples - samples_rendered).min(chunk_size); buffer.resize(samples_to_render, 0.0); // Process audio if let Err(e) = sequencer.process_audio(&mut buffer) { return Err(format!("Audio processing error: {}", e)); } audio_data.extend_from_slice(&buffer); samples_rendered += samples_to_render; } Ok(audio_data) } } impl Default for AudioExporter { fn default() -> Self { Self::new(SAMPLE_RATE) } } /// Simple audio analysis utilities pub struct AudioAnalyzer; impl AudioAnalyzer { /// Calculate RMS (Root Mean Square) level of audio data pub fn calculate_rms(audio_data: &[f32]) -> f32 { if audio_data.is_empty() { return 0.0; } let sum_squares: f32 = audio_data.iter().map(|&x| x * x).sum(); (sum_squares / audio_data.len() as f32).sqrt() } /// Find peak amplitude in audio data pub fn find_peak(audio_data: &[f32]) -> f32 { audio_data.iter().map(|&x| x.abs()).fold(0.0, f32::max) } /// Calculate dynamic range (ratio of peak to RMS) pub fn calculate_dynamic_range(audio_data: &[f32]) -> f32 { let peak = Self::find_peak(audio_data); let rms = Self::calculate_rms(audio_data); if rms > 0.0 { 20.0 * (peak / rms).log10() // In dB } else { f32::INFINITY } } /// Simple frequency analysis using zero-crossing rate pub fn zero_crossing_rate(audio_data: &[f32]) -> f32 { if audio_data.len() < 2 { return 0.0; } let mut crossings = 0; for i in 1..audio_data.len() { if (audio_data[i] >= 0.0) != (audio_data[i - 1] >= 0.0) { crossings += 1; } } crossings as f32 / audio_data.len() as f32 } } #[cfg(test)] mod tests { use super::*; use crate::core::{CompositionBuilder, CompositionStyle}; #[test] fn test_audio_config() { let config = AudioConfig::default(); assert_eq!(config.sample_rate, SAMPLE_RATE); assert_eq!(config.channels, 2); assert!(config.buffer_size > 0); } #[test] fn test_audio_exporter_creation() { let exporter = AudioExporter::new(44100.0); assert_eq!(exporter.sample_rate, 44100.0); } #[test] fn test_raw_audio_export() { let mut composition = CompositionBuilder::new() .style(CompositionStyle::Electronic) .measures(2) .tempo(120.0) .build(); let _ = composition.generate(); let exporter = AudioExporter::new(44100.0); let result = exporter.export_raw_audio(&composition, Some(1.0)); assert!(result.is_ok()); let audio_data = result.unwrap(); assert_eq!(audio_data.len(), 44100); // 1 second at 44.1kHz } #[test] fn test_audio_analysis() { // Test with a simple sine wave let sample_rate = 44100.0; let frequency = 440.0; let duration = 1.0; let samples = (sample_rate * duration) as usize; let mut audio_data = Vec::with_capacity(samples); for i in 0..samples { let t = i as f32 / sample_rate; let sample = (2.0 * std::f32::consts::PI * frequency * t).sin() * 0.5; audio_data.push(sample); } let rms = AudioAnalyzer::calculate_rms(&audio_data); let peak = AudioAnalyzer::find_peak(&audio_data); let zcr = AudioAnalyzer::zero_crossing_rate(&audio_data); assert!(rms > 0.0); assert!(peak > rms); assert!(zcr > 0.0); // For a sine wave, RMS should be approximately peak / sqrt(2) let expected_rms = 0.5 / (2.0_f32).sqrt(); assert!((rms - expected_rms).abs() < 0.01); } #[test] fn test_dynamic_range_calculation() { let audio_data = vec![0.0, 0.5, -0.3, 0.8, -0.2, 0.1]; let dynamic_range = AudioAnalyzer::calculate_dynamic_range(&audio_data); assert!(dynamic_range > 0.0); assert!(dynamic_range.is_finite()); } #[test] fn test_zero_crossing_rate() { // Test with alternating positive/negative values let audio_data = vec![1.0, -1.0, 1.0, -1.0, 1.0, -1.0]; let zcr = AudioAnalyzer::zero_crossing_rate(&audio_data); // Should have high zero-crossing rate assert!(zcr > 0.5); } #[test] fn test_audio_player_creation() { let config = AudioConfig::default(); let result = AudioPlayer::new(config); assert!(result.is_ok()); } }