use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{self, Receiver, Sender}; use std::sync::{Arc, Mutex}; use std::thread; #[allow(dead_code)] pub enum AudioCommand { Play(PathBuf), Pause, Resume, Stop, SetVolume(f32), } pub struct AudioEngine { command_tx: Sender, is_playing: Arc, current_track: Arc>>, track_ended: Arc, } impl AudioEngine { pub fn new() -> Self { let (tx, rx) = mpsc::channel(); let is_playing = Arc::new(AtomicBool::new(false)); let is_playing_clone = Arc::clone(&is_playing); let current_track = Arc::new(Mutex::new(None)); let current_track_clone = Arc::clone(¤t_track); let track_ended = Arc::new(AtomicBool::new(false)); let track_ended_clone = Arc::clone(&track_ended); let volume = Arc::new(Mutex::new(1.0f32)); let volume_clone = Arc::clone(&volume); thread::spawn(move || { audio_thread( rx, is_playing_clone, current_track_clone, volume_clone, track_ended_clone, ); }); AudioEngine { command_tx: tx, is_playing, current_track, track_ended, } } pub fn play(&self, path: PathBuf) { self.track_ended.store(false, Ordering::SeqCst); if let Ok(mut track) = self.current_track.lock() { *track = Some(path.clone()); } let _ = self.command_tx.send(AudioCommand::Play(path)); } pub fn pause(&self) { let _ = self.command_tx.send(AudioCommand::Pause); } pub fn resume(&self) { let _ = self.command_tx.send(AudioCommand::Resume); } pub fn stop(&self) { let _ = self.command_tx.send(AudioCommand::Stop); } pub fn set_volume(&self, volume: f32) { let _ = self.command_tx.send(AudioCommand::SetVolume(volume)); } pub fn is_playing(&self) -> bool { self.is_playing.load(Ordering::SeqCst) } pub fn current_track(&self) -> Option { self.current_track.lock().ok().and_then(|g| g.clone()) } pub fn poll_track_ended(&self) -> bool { self.track_ended.swap(false, Ordering::SeqCst) } } struct AudioData { format: Box, decoder: Box, sample_rate: u32, channels: u16, sample_buffer: Vec, buffer_pos: usize, resample_ratio: Option, } fn resample_linear(input: &[f32], ratio: f64, channels: usize) -> Vec { if ratio == 1.0 { return input.to_vec(); } let input_frames = input.len() / channels; let output_frames = (input_frames as f64 * ratio).ceil() as usize; let mut output = Vec::with_capacity(output_frames * channels); for i in 0..output_frames { let input_pos = i as f64 / ratio; let idx = input_pos.floor() as usize; let frac = input_pos.fract() as f32; for ch in 0..channels { let cur = if idx * channels + ch < input.len() { input[idx * channels + ch] } else { 0.0 }; let next = if (idx + 1) * channels + ch < input.len() { input[(idx + 1) * channels + ch] } else { cur }; output.push(cur + frac * (next - cur)); } } output } fn audio_thread( rx: Receiver, is_playing: Arc, current_track: Arc>>, volume: Arc>, track_ended: Arc, ) { use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; let host = cpal::default_host(); let device = match host.default_output_device() { Some(d) => d, None => { eprintln!("No output device available"); return; } }; let config = match device.default_output_config() { Ok(c) => c, Err(e) => { eprintln!("Failed to get output config: {}", e); return; } }; let device_sample_rate = config.sample_rate(); let channels = config.channels(); eprintln!( "Default output: {} Hz, {} channels", device_sample_rate, channels ); let mut current_stream: Option = None; let audio_data: Arc>> = Arc::new(Mutex::new(None)); while let Ok(cmd) = rx.recv() { match cmd { AudioCommand::Play(path) => { is_playing.store(true, Ordering::SeqCst); if let Ok(mut track) = current_track.lock() { *track = Some(path.clone()); } // Drop the current stream before creating a new one current_stream = None; match open_audio_file(&path) { Ok(mut data) => { eprintln!("File: {} Hz, {} channels", data.sample_rate, data.channels); if data.sample_rate != device_sample_rate { eprintln!( "Resampling from {} Hz to {} Hz", data.sample_rate, device_sample_rate ); data.resample_ratio = Some(device_sample_rate as f64 / data.sample_rate as f64); } let stream_config: cpal::StreamConfig = device.default_output_config().unwrap().into(); let audio_data_clone = Arc::clone(&audio_data); let vol = Arc::clone(&volume); let ended = Arc::clone(&track_ended); let is_playing_clone = Arc::clone(&is_playing); if let Ok(mut d) = audio_data.lock() { *d = Some(data); } match device.build_output_stream( &stream_config, move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { let vol = vol.lock().map(|v| *v).unwrap_or(1.0); if let Ok(mut audio_data) = audio_data_clone.lock() { if let Some(ref mut state) = *audio_data { let channels = state.channels as usize; let mut offset = 0usize; // Keep decoding until we fill the buffer or run out of data while offset < data.len() { // First, copy any buffered samples if state.buffer_pos < state.sample_buffer.len() { let remaining = &state.sample_buffer[state.buffer_pos..]; let to_copy = remaining.len().min(data.len() - offset); data[offset..offset + to_copy] .copy_from_slice(&remaining[..to_copy]); state.buffer_pos += to_copy; offset += to_copy; // If we've consumed all buffered samples, clear the buffer if state.buffer_pos >= state.sample_buffer.len() { state.sample_buffer.clear(); state.buffer_pos = 0; } continue; // Try to fill more of the buffer } // Decode more data match state.format.next_packet() { Ok(packet) => { match state.decoder.decode(&packet) { Ok(decoded) => { let spec = *decoded.spec(); let mut sample_buf = symphonia::core::audio::SampleBuffer::< f32, >::new( decoded.capacity() as u64, spec ); sample_buf .copy_interleaved_ref(decoded); let samples = sample_buf.samples(); // Resample if needed let resampled = if let Some(ratio) = state.resample_ratio { resample_linear( samples, ratio, channels, ) } else { samples.to_vec() }; // Copy what we can to output let to_copy = resampled .len() .min(data.len() - offset); data[offset..offset + to_copy] .copy_from_slice( &resampled[..to_copy], ); offset += to_copy; // Buffer the rest if to_copy < resampled.len() { state.sample_buffer.clear(); state .sample_buffer .extend_from_slice( &resampled[to_copy..], ); state.buffer_pos = 0; } } Err(_) => break, } } Err(_) => break, } } // Apply volume for sample in data.iter_mut().take(offset) { *sample *= vol; } // Fill remaining with silence for sample in data.iter_mut().skip(offset) { *sample = 0.0; } // If no more data and offset is 0, track ended if offset == 0 && state.sample_buffer.is_empty() { ended.store(true, Ordering::SeqCst); is_playing_clone.store(false, Ordering::SeqCst); // Clear audio data so we don't keep triggering ended if let Ok(mut audio_data) = audio_data_clone.lock() { *audio_data = None; } } } else { for sample in data.iter_mut() { *sample = 0.0; } } } }, |err| { eprintln!("Stream error: {}", err); }, None, ) { Ok(stream) => { if let Err(e) = stream.play() { eprintln!("Failed to start stream: {}", e); is_playing.store(false, Ordering::SeqCst); track_ended.store(true, Ordering::SeqCst); } else { current_stream = Some(stream); } } Err(e) => { eprintln!("Failed to create stream: {}", e); is_playing.store(false, Ordering::SeqCst); track_ended.store(true, Ordering::SeqCst); } } } Err(e) => { eprintln!("Failed to open audio: {}", e); is_playing.store(false, Ordering::SeqCst); track_ended.store(true, Ordering::SeqCst); } } } AudioCommand::Pause => { if let Some(ref stream) = current_stream { if let Err(e) = stream.pause() { eprintln!("Failed to pause stream: {}", e); } is_playing.store(false, Ordering::SeqCst); } } AudioCommand::Resume => { if let Some(ref stream) = current_stream { if let Err(e) = stream.play() { eprintln!("Failed to resume stream: {}", e); } else { is_playing.store(true, Ordering::SeqCst); } } } AudioCommand::Stop => { current_stream = None; if let Ok(mut d) = audio_data.lock() { *d = None; } is_playing.store(false, Ordering::SeqCst); if let Ok(mut track) = current_track.lock() { *track = None; } } AudioCommand::SetVolume(vol) => { if let Ok(mut volume_ref) = volume.lock() { *volume_ref = vol; } } } } } fn open_audio_file(path: &PathBuf) -> Result> { use symphonia::core::audio::Channels; use symphonia::core::io::MediaSourceStream; let file = std::fs::File::open(path)?; let mss = MediaSourceStream::new(Box::new(file), Default::default()); let hint = symphonia::core::probe::Hint::new(); let probed = symphonia::default::get_probe().format( &hint, mss, &Default::default(), &Default::default(), )?; let format = probed.format; let track = format .tracks() .iter() .find(|t| t.codec_params.sample_rate.is_some()) .ok_or("No suitable track found")?; let sample_rate = track.codec_params.sample_rate.unwrap_or(44100); let channels = track .codec_params .channels .unwrap_or(Channels::FRONT_LEFT | Channels::FRONT_RIGHT) .count() as u16; let decoder = symphonia::default::get_codecs().make(&track.codec_params, &Default::default())?; Ok(AudioData { format, decoder, sample_rate, channels, sample_buffer: Vec::new(), buffer_pos: 0, resample_ratio: None, }) }