use crate::{BeatInfo, StrategyKind};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{BufferSize, Device, Host, InputCallbackInfo, SampleFormat, StreamConfig, StreamError};
use std::collections::{BTreeMap, HashMap};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread::{spawn, JoinHandle};
use std::time::Instant;
pub fn start_listening(
on_beat_cb: impl Fn(BeatInfo) + Send + 'static,
input_dev: Option<Device>,
strategy: StrategyKind,
keep_recording: Arc<AtomicBool>,
) -> Result<JoinHandle<()>, String> {
if !keep_recording.load(Ordering::SeqCst) {
return Err("Variable keep_recording is false from the beginning!?".to_string());
}
let in_dev = input_dev.map(Ok).unwrap_or_else(|| {
let host = cpal::default_host();
host.default_input_device()
.ok_or_else(|| "Must have input device!".to_string())
})?;
let in_dev_cfg = in_dev.default_input_config().unwrap();
let sampling_rate = in_dev_cfg.sample_rate();
let sample_format = in_dev_cfg.sample_format();
eprintln!("Using input device: {:?}", in_dev.name().unwrap());
eprintln!(" sampling_rate: {}", sampling_rate.0);
eprintln!(" sample_format: {:?}", sample_format);
let err_cb = |err: StreamError| {
eprintln!("Record error occurred: {:#?}", err);
};
#[cfg(not(target_os = "linux"))]
let preferred_window_length = 1024;
let in_stream_cfg = StreamConfig {
channels: 1,
sample_rate: sampling_rate,
#[cfg(not(target_os = "linux"))]
buffer_size: BufferSize::Fixed(preferred_window_length),
#[cfg(target_os = "linux")]
buffer_size: BufferSize::Default,
};
let detector = strategy.detector(sampling_rate.0);
let handle = spawn(move || {
let stream = match sample_format {
SampleFormat::F32 => in_dev.build_input_stream(
&in_stream_cfg,
move |data: &[f32], _info: &InputCallbackInfo| {
let now = Instant::now();
if let Some(info) = detector.is_beat(&f32_data_to_i16(data)) {
on_beat_cb(info);
}
let millis = now.elapsed().as_millis();
if millis > 20 {
eprintln!("calculation took {}ms", millis);
}
},
err_cb,
),
SampleFormat::I16 => in_dev.build_input_stream(
&in_stream_cfg,
move |data: &[i16], _info: &InputCallbackInfo| {
let now = Instant::now();
if let Some(info) = detector.is_beat(data) {
on_beat_cb(info);
}
let millis = now.elapsed().as_millis();
if millis > 20 {
eprintln!("calculation took {}ms", millis);
}
},
err_cb,
),
SampleFormat::U16 => in_dev.build_input_stream(
&in_stream_cfg,
move |data: &[u16], _info: &InputCallbackInfo| {
let now = Instant::now();
if let Some(info) = detector.is_beat(&u16_data_to_i16(data)) {
on_beat_cb(info);
}
let millis = now.elapsed().as_millis();
if millis > 20 {
eprintln!("calculation took {}ms", millis);
}
},
err_cb,
),
}
.map_err(|err| format!("Can't open stream: {:?}", err))
.unwrap();
stream.play().unwrap();
loop {
if !keep_recording.load(Ordering::SeqCst) {
break;
}
}
});
Ok(handle)
}
#[inline(always)]
fn u16_data_to_i16(data: &[u16]) -> Vec<i16> {
data.iter()
.map(|x| *x as i32)
.map(|x| x - i16::MAX as i32 / 2)
.map(|x| x as i16)
.collect()
}
#[inline(always)]
fn f32_data_to_i16(data: &[f32]) -> Vec<i16> {
data.iter()
.map(|x| x * i16::MAX as f32)
.map(|x| x as i16)
.collect()
}
pub fn audio_input_device_list() -> BTreeMap<String, Device> {
let host = cpal::default_host();
let mut map = BTreeMap::new();
for (i, dev) in host.input_devices().unwrap().enumerate() {
map.insert(dev.name().unwrap_or(format!("Unknown device #{}", i)), dev);
}
map
}
pub fn print_audio_input_device_configs() {
let host = cpal::default_host();
for (i, dev) in host.input_devices().unwrap().enumerate() {
eprintln!("--------");
let name = dev.name().unwrap_or(format!("Unknown device #{}", i));
eprintln!("[{}] default config:", name);
eprintln!("{:#?}", dev.default_input_config().unwrap());
}
}
pub fn get_backends() -> HashMap<String, Host> {
cpal::available_hosts()
.into_iter()
.map(|id| (format!("{:?}", id), cpal::host_from_id(id).unwrap()))
.collect::<HashMap<_, _>>()
}
#[cfg(test)]
mod tests {
use super::*;
}