Last active
July 4, 2023 06:52
-
-
Save ayyybe/d696ead1b82d4de7ea5a8fe8e38fe93f to your computer and use it in GitHub Desktop.
rust cpal + dasp + channel = realtime audio processing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// live recording and realtime resampling of any input to 16kHz mono for whisper | |
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; | |
use dasp::{interpolate::sinc::Sinc, ring_buffer, signal, Frame, Signal}; | |
use std::{sync::mpsc, thread}; | |
fn get_voicemeeter_input_device(host: &cpal::Host) -> cpal::Device { | |
host.input_devices() | |
.expect("Failed to get input devices") | |
.find(|device| { | |
device | |
.name() | |
.unwrap() | |
.to_lowercase() | |
.contains("voicemeeter") | |
}) | |
.expect("Failed to find input device") | |
} | |
fn sinc_resample( | |
signal: impl Signal<Frame = f32>, | |
source_hz: f64, | |
target_hz: f64, | |
) -> impl Signal<Frame = f32> { | |
let ring_buffer = ring_buffer::Fixed::from([f32::EQUILIBRIUM; 10]); | |
let sinc = Sinc::new(ring_buffer); | |
signal.from_hz_to_hz(sinc, source_hz, target_hz) | |
} | |
fn main() -> anyhow::Result<()> { | |
let host = cpal::default_host(); | |
let device = get_voicemeeter_input_device(&host); | |
let config = device.default_input_config()?; | |
let sample_rate = config.sample_rate().0 as f64; | |
let channels = config.channels() as usize; | |
let (tx, rx) = mpsc::channel::<Vec<f32>>(); | |
let stream = device.build_input_stream( | |
&config.into(), | |
move |data: &[f32], _info: &cpal::InputCallbackInfo| { | |
let mono = data | |
.chunks(channels) | |
.map(|frame| frame.iter().sum::<f32>() / channels as f32); | |
tx.send(mono.collect()).unwrap(); | |
}, | |
|err| { | |
eprintln!("warning: input stream error: {}", err); | |
}, | |
None, | |
)?; | |
stream.play()?; | |
let samples = rx.into_iter().flat_map(|x| x.into_iter()); | |
let signal = signal::from_iter(samples); | |
let signal = sinc_resample(signal, sample_rate, 16000.0); | |
// now use the signal however you want (warning: next()/until_exhausted() will block until the stream is dropped) | |
// example: write to a wav file for 10 seconds | |
let processing_thread = thread::spawn(move || { | |
let mut writer = hound::WavWriter::create( | |
"output.wav", | |
hound::WavSpec { | |
channels: 1, | |
sample_rate: 16000, | |
bits_per_sample: 32, | |
sample_format: hound::SampleFormat::Float, | |
}, | |
) | |
.unwrap(); | |
for sample in signal.until_exhausted() { | |
writer.write_sample(sample).unwrap(); | |
} | |
writer.finalize().unwrap(); | |
}); | |
thread::sleep(std::time::Duration::from_millis(10000)); | |
drop(stream); | |
println!("dropped stream"); | |
processing_thread.join().unwrap(); | |
println!("finished processing"); | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment