102 lines
3.6 KiB
Rust
102 lines
3.6 KiB
Rust
|
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<f32>,
|
||
|
input_buffer: Vec<Vec<f32>>,
|
||
|
output_buffer: Vec<Vec<f32>>,
|
||
|
sample_sink: rtrb::Producer<f32>,
|
||
|
}
|
||
|
|
||
|
impl Audio {
|
||
|
pub fn init() -> Result<Self> {
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
}
|