use anyhow::{bail, Result}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use itertools::Itertools; use rubato::{FftFixedInOut, Resampler}; pub struct Audio { #[allow(unused)] stream: cpal::Stream, sampler: FftFixedInOut, input_buffer: Vec>, output_buffer: Vec>, sample_sink: rtrb::Producer, } impl Audio { pub fn init() -> Result { let host = cpal::default_host(); let Some(device) = host.default_output_device() else { bail!("No output device available"); }; let Some(config) = device .supported_output_configs()? .find(|c| c.channels() == 2 && c.sample_format().is_float()) else { bail!("No suitable output config available"); }; let mut config = config.with_max_sample_rate().config(); let sampler = FftFixedInOut::new(41700, config.sample_rate.0 as usize, 834, 2)?; config.buffer_size = cpal::BufferSize::Fixed(sampler.output_frames_max() as u32); let input_buffer = sampler.input_buffer_allocate(true); let output_buffer = sampler.output_buffer_allocate(true); let (sample_sink, mut sample_source) = rtrb::RingBuffer::new(sampler.output_frames_max() * 4); let stream = device.build_output_stream( &config, move |data: &mut [f32], _| { let requested = data.len(); let chunk = match sample_source.read_chunk(data.len()) { Ok(c) => c, Err(rtrb::chunks::ChunkError::TooFewSlots(n)) => { sample_source.read_chunk(n).unwrap() } }; let len = chunk.len(); let (first, second) = chunk.as_slices(); data[0..first.len()].copy_from_slice(first); data[first.len()..len].copy_from_slice(second); for rest in &mut data[len..requested] { *rest = 0.0; } chunk.commit_all(); }, move |err| eprintln!("stream error: {err}"), None, )?; stream.play()?; Ok(Self { stream, sampler, input_buffer, output_buffer, sample_sink, }) } pub fn update(&mut self, samples: &[f32]) { for sample in samples.chunks_exact(2) { for (channel, value) in self.input_buffer.iter_mut().zip(sample) { channel.push(*value); } if self.input_buffer[0].len() >= self.sampler.input_frames_next() { let (_, output_samples) = self .sampler .process_into_buffer(&self.input_buffer, &mut self.output_buffer, None) .unwrap(); let chunk = match self.sample_sink.write_chunk_uninit(output_samples * 2) { Ok(c) => c, Err(rtrb::chunks::ChunkError::TooFewSlots(n)) => { self.sample_sink.write_chunk_uninit(n).unwrap() } }; let interleaved = self.output_buffer[0] .iter() .interleave(self.output_buffer[1].iter()) .cloned(); chunk.fill_from_iter(interleaved); for channel in &mut self.input_buffer { channel.clear(); } } } while self.sample_sink.slots() < self.sampler.output_frames_max() * 2 { std::hint::spin_loop(); } } }