use bevy_ecs::resource::Resource;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use std::{
sync::{
Arc, Mutex,
mpsc::{Receiver, SyncSender, sync_channel},
},
thread::JoinHandle,
};
#[derive(Resource)]
pub(crate) struct AudioPiper {
mic: Arc<Mutex<Receiver<Vec<f32>>>>,
sink: SyncSender<Vec<f32>>,
_ih: JoinHandle<()>,
_oh: JoinHandle<()>,
}
const BUF: usize = 100;
const LEN: usize = 600;
impl AudioPiper {
pub(crate) fn new() -> Self {
let (mic, ih) = mic_source();
let (sink, oh) = out_sink();
Self {
mic: Arc::new(Mutex::new(mic)),
sink,
_ih: ih,
_oh: oh,
}
}
pub(crate) fn sink(&self, data: &[f32]) {
for d in data.chunks(LEN) {
let _ = self.sink.send(d.to_vec());
}
}
pub(crate) fn read(&self) -> Vec<f32> {
let mut res = vec![];
let Ok(c) = self.mic.lock() else {
return res;
};
while let Ok(v) = c.try_recv() {
res.extend_from_slice(&v);
}
res
}
}
fn mic_source() -> (Receiver<Vec<f32>>, JoinHandle<()>) {
let (tx, rx) = sync_channel(BUF);
let h = std::thread::spawn(move || {
let dev = cpal::default_host().default_input_device().unwrap();
let mut conf = dev
.supported_input_configs()
.unwrap()
.next()
.unwrap()
.with_max_sample_rate()
.config();
conf.buffer_size = cpal::BufferSize::Fixed(LEN as u32);
let is = dev
.build_input_stream(
&conf,
move |data: &[f32], _: &cpal::InputCallbackInfo| {
let _ = tx.try_send(data.to_vec());
},
move |_err| {},
None,
)
.unwrap();
is.play().unwrap();
loop {
std::thread::yield_now();
}
});
(rx, h)
}
fn out_sink() -> (SyncSender<Vec<f32>>, JoinHandle<()>) {
let (tx, rx) = sync_channel::<Vec<f32>>(BUF);
let h = std::thread::spawn(move || {
let output_dev = cpal::default_host().default_output_device().unwrap();
let mut output_conf = output_dev
.supported_output_configs()
.unwrap()
.next()
.unwrap()
.with_max_sample_rate()
.config();
output_conf.buffer_size = cpal::BufferSize::Fixed(LEN as u32);
let os = output_dev
.build_output_stream(
&output_conf,
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
if let Ok(d) = rx.recv() {
let l = d.len().min(data.len());
data[0..l].copy_from_slice(&d[..l]);
}
},
move |_err| {},
None,
)
.unwrap();
os.play().unwrap();
loop {
std::thread::yield_now();
}
});
(tx, h)
}
#[cfg(test)]
mod test {
use std::time::{Duration, Instant};
use super::*;
#[test]
fn test() {
let start = Instant::now();
let (input, _is) = mic_source();
let (output, _os) = out_sink();
while let Ok(d) = input.recv()
&& start.elapsed() < Duration::from_secs(20)
{
let _ = output.send(d);
}
}
}