Skip to content

Instantly share code, notes, and snippets.

@ayyybe
Last active July 4, 2023 06:52
Show Gist options
  • Save ayyybe/d696ead1b82d4de7ea5a8fe8e38fe93f to your computer and use it in GitHub Desktop.
Save ayyybe/d696ead1b82d4de7ea5a8fe8e38fe93f to your computer and use it in GitHub Desktop.

Revisions

  1. ayyybe revised this gist Jul 4, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion live_resampling.rs
    Original file line number Diff line number Diff line change
    @@ -22,7 +22,7 @@ fn sinc_resample(
    source_hz: f64,
    target_hz: f64,
    ) -> impl Signal<Frame = f32> {
    let ring_buffer = ring_buffer::Fixed::from([f32::EQUILIBRIUM; 50]);
    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)
    }
  2. ayyybe created this gist Jul 4, 2023.
    83 changes: 83 additions & 0 deletions live_resampling.rs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,83 @@
    // 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; 50]);
    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(())
    }