WIP push-to-talk Letta chat frontend
1use crate::devices::types::AudioDeviceError;
2use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
3use cpal::{default_host, Device, SampleFormat, Stream};
4use dasp::interpolate::sinc::Sinc;
5use dasp::ring_buffer;
6use dasp::signal::{self, Signal};
7use dasp::Frame;
8use std::sync::{mpsc::channel, Arc};
9use tauri::async_runtime::Mutex;
10
11pub struct InputDeviceManager {
12 device: Device,
13 stream: Arc<Mutex<Option<Stream>>>,
14}
15
16fn resample(
17 signal: impl Signal<Frame = i16>,
18 source_hz: f64,
19 target_hz: f64,
20) -> impl Signal<Frame = i16> {
21 let ring_buffer = ring_buffer::Fixed::from([i16::EQUILIBRIUM; 10]);
22 let sinc = Sinc::new(ring_buffer);
23 signal.from_hz_to_hz(sinc, source_hz, target_hz)
24}
25
26impl InputDeviceManager {
27 pub fn new() -> Result<Self, AudioDeviceError> {
28 let host = default_host();
29 let device = host
30 .default_input_device()
31 .ok_or(AudioDeviceError::NoDevicesFound)?;
32 let manager = InputDeviceManager {
33 device,
34 stream: Arc::new(Mutex::new(None)),
35 };
36
37 Ok(manager)
38 }
39
40 pub async fn start_listening(&self) -> impl Signal<Frame = i16> {
41 let config = self
42 .device
43 .supported_input_configs()
44 .expect("failed to get supported input configs")
45 .find(|c| c.sample_format() == SampleFormat::I16)
46 .expect("failed to find PCM s16 config")
47 .with_max_sample_rate();
48 let channels = config.channels() as usize;
49 let (tx, rx) = channel::<Vec<i16>>();
50 let stream = self
51 .device
52 .build_input_stream(
53 &config.config(),
54 move |data: &[i16], _: &cpal::InputCallbackInfo| {
55 let mono = data
56 .chunks(channels)
57 .map(|frame| (frame.iter().sum::<i16>() / channels as i16).to_le());
58
59 let _ = tx.send(mono.collect());
60 },
61 |err| eprintln!("encountered error streaming input: {}", err),
62 None,
63 )
64 .expect("failed to create input stream");
65 stream.play().expect("failed to start input stream");
66
67 let samples = rx.into_iter().flat_map(|x| x.into_iter());
68 let signal = signal::from_iter(samples);
69 let signal = resample(signal, config.sample_rate().0 as f64, 16000.0);
70
71 let mut s = self.stream.lock().await;
72 *s = Some(stream);
73
74 signal
75 }
76
77 pub async fn stop_listening(&self) {
78 let mut s = self.stream.lock().await;
79 *s = None;
80 }
81}