lock-free-vumeter/src/audio.rs

76 lines
2.3 KiB
Rust

extern crate cpal;
use cpal::{EventLoop, Sample, StreamData, UnknownTypeInputBuffer};
use std::{
i16,
sync::atomic::{AtomicBool, AtomicI16, Ordering},
};
pub struct AudioState {
pub max_value: AtomicI16,
pub gui_up_to_date: AtomicBool,
}
impl AudioState {
pub fn new() -> Self {
AudioState {
max_value: AtomicI16::new(0),
gui_up_to_date: AtomicBool::new(false),
}
}
}
// Open the default audio device, and start the audio routine:
pub fn start(state: &AudioState) -> Result<(), String> {
let event_loop = EventLoop::new();
let device = cpal::default_input_device().ok_or("Could not find a default input device")?;
let mut supported_formats_range = device
.supported_input_formats()
.map_err(|_| "Could not get supported input formats")?;
let format = supported_formats_range
.next()
.ok_or("Could not get supported formats range")?
.with_max_sample_rate();
let stream_id = event_loop
.build_input_stream(&device, &format)
.map_err(|_| "Could not build input stream")?;
event_loop.play_stream(stream_id);
// Define the actual audio callback.
// All we do is extract the enum variant, deference the buffer
// and then lend it to process_audio:
event_loop.run(|_stream_id, stream_data| match stream_data {
StreamData::Input {
buffer: UnknownTypeInputBuffer::I16(buffer),
} => process_audio(&*buffer, state),
StreamData::Input {
buffer: UnknownTypeInputBuffer::U16(buffer),
} => process_audio(&*buffer, state),
StreamData::Input {
buffer: UnknownTypeInputBuffer::F32(buffer),
} => process_audio(&*buffer, state),
_ => unreachable!(),
});
}
// process_audio<T> takes a reference to a slice of sample-friendly
// primitives, calculates the max value and atomically updates the AudioState.
fn process_audio<T>(buffer: &[T], state: &AudioState)
where
T: cpal::Sample,
{
let max = buffer
.iter()
.map(Sample::to_i16)
.max()
.map(|s| s.max(i16::min_value() + 1))
.unwrap_or(0)
.abs();
state.max_value.store(max, Ordering::SeqCst);
state.gui_up_to_date.store(false, Ordering::SeqCst);
}