diff --git a/Cargo.lock b/Cargo.lock index 61a8b50..61f4149 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,6 +280,43 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "audio-core" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ebbf82d06013f4c41fe71303feb980cddd78496d904d06be627972de51a24" + +[[package]] +name = "audioadapter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25c5bb54993ad4693d8b68b6f29f872c5fd9f92a6469d0acb0cbaf80a13d0f9" +dependencies = [ + "audio-core", + "num-traits", +] + +[[package]] +name = "audioadapter-buffers" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6af89882334c4e501faa08992888593ada468f9e1ab211635c32f9ada7786e0" +dependencies = [ + "audioadapter", + "audioadapter-sample", + "num-traits", +] + +[[package]] +name = "audioadapter-sample" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9a3d502fec0b21aa420febe0b110875cf8a7057c49e83a0cace1df6a73e03e" +dependencies = [ + "audio-core", + "num-traits", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -1972,6 +2009,7 @@ dependencies = [ "anyhow", "atoi", "atomic", + "audioadapter-buffers", "bitflags 2.10.0", "bytemuck", "cc", @@ -3530,14 +3568,18 @@ checksum = "ad8388ea1a9e0ea807e442e8263a699e7edcb320ecbcd21b4fa8ff859acce3ba" [[package]] name = "rubato" -version = "0.16.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5258099699851cfd0082aeb645feb9c084d9a5e1f1b8d5372086b989fc5e56a1" +checksum = "90173154a8a14e6adb109ea641743bc95ec81c093d94e70c6763565f7108ebeb" dependencies = [ + "audioadapter", + "audioadapter-buffers", "num-complex", "num-integer", "num-traits", "realfft", + "visibility", + "windowfunctions", ] [[package]] @@ -4543,6 +4585,17 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -5023,6 +5076,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "windowfunctions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90628d739333b7c5d2ee0b70210b97b8cddc38440c682c96fd9e2c24c2db5f3a" +dependencies = [ + "num-traits", +] + [[package]] name = "windows" version = "0.54.0" diff --git a/Cargo.toml b/Cargo.toml index 67282c7..0e81648 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ edition = "2024" [dependencies] anyhow = "1" atoi = "2" +audioadapter-buffers = "2" atomic = "0.6" bitflags = { version = "2", features = ["serde"] } bytemuck = { version = "1", features = ["derive"] } @@ -38,7 +39,7 @@ pollster = "0.4" rand = "0.9" rfd = "0.17" rtrb = "0.3" -rubato = "0.16" +rubato = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" thread-priority = "3" diff --git a/src/audio.rs b/src/audio.rs index 351c9f7..b095b77 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -1,17 +1,17 @@ use std::time::Duration; use anyhow::{Result, bail}; +use audioadapter_buffers::direct::InterleavedSlice; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use itertools::Itertools; -use rubato::{FastFixedOut, Resampler}; +use rubato::Resampler; use tracing::error; pub struct Audio { #[allow(unused)] stream: cpal::Stream, - sampler: FastFixedOut, - input_buffer: Vec>, - output_buffer: Vec>, + sampler: rubato::Async, + input_buffer: Vec, + output_buffer: Vec, sample_sink: rtrb::Producer, } @@ -32,17 +32,18 @@ impl Audio { let mut config = config.with_max_sample_rate().config(); let resample_ratio = config.sample_rate.0 as f64 / VB_FREQUENCY as f64; let chunk_size = (834.0 * resample_ratio) as usize; - let sampler = FastFixedOut::new( + let sampler = rubato::Async::new_poly( resample_ratio, 64.0, rubato::PolynomialDegree::Cubic, chunk_size, 2, + rubato::FixedAsync::Output, )?; 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 input_buffer = Vec::with_capacity(sampler.nbr_channels() * sampler.input_frames_max()); + let output_buffer = vec![0.0; sampler.nbr_channels() * sampler.output_frames_max()]; let (sample_sink, mut sample_source) = rtrb::RingBuffer::new(sampler.output_frames_max() * 4); @@ -78,34 +79,37 @@ impl Audio { }) } - 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) + pub fn update(&mut self, mut samples: &[f32]) { + while self.input_buffer.len() + samples.len() >= self.sampler.input_frames_next() * 2 { + let samples_needed = (self.sampler.input_frames_next() * 2) - self.input_buffer.len(); + let (current_samples, future_samples) = samples.split_at(samples_needed); + self.input_buffer.extend_from_slice(current_samples); + samples = future_samples; + + let buffer_in = + InterleavedSlice::new(&self.input_buffer, 2, self.sampler.input_frames_next()) .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(); + let mut buffer_out = InterleavedSlice::new_mut( + &mut self.output_buffer, + 2, + self.sampler.output_frames_next(), + ) + .unwrap(); + let (_, output_samples) = self + .sampler + .process_into_buffer(&buffer_in, &mut buffer_out, 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() } - } + }; + chunk.fill_from_iter(self.output_buffer[..output_samples * 2].iter().copied()); + + self.input_buffer.clear(); } + self.input_buffer.extend_from_slice(samples); while self.sample_sink.slots() < self.sampler.output_frames_max() * 2 { std::thread::sleep(Duration::from_micros(500));