use std::{thread, time::Duration};
use crate::{
cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
*,
},
Mixer,
};
use crate::{
Amplitude, BuildSystemAudioError, BuildSystemAudioResult, DeviceIoBuilder, Frame, Source,
};
pub fn default_output<F: Frame>() -> BuildSystemAudioResult<OutputDeviceMixer<F>> {
DeviceIoBuilder::default_output().build_output()
}
pub fn default_output_device() -> Option<Device> {
default_host().default_output_device()
}
pub struct OutputDeviceMixer<F> {
mixer: Mixer<F>,
_stream: Stream,
sample_rate: u32,
}
impl<F> OutputDeviceMixer<F>
where
F: Frame,
{
pub fn sample_rate(&self) -> f64 {
self.sample_rate as f64
}
pub fn block(&mut self) {
while self
.mixer
.sources
.try_lock()
.map_or(true, |sources| !sources.is_empty())
{
thread::sleep(Duration::from_millis(1));
}
}
pub fn add<S>(&self, source: S)
where
S: Source<Frame = F> + Send + 'static,
{
self.mixer.add(source);
}
pub fn mixer(&self) -> &Mixer<F> {
&self.mixer
}
pub(crate) fn from_builder(builder: DeviceIoBuilder) -> BuildSystemAudioResult<Self> {
let device = if let Some(device) = builder.device {
device
} else {
default_output_device().ok_or(BuildSystemAudioError::NoDevice)?
};
let config = if let Some(config) = builder.config {
config
} else {
device.default_output_config()?
};
let sample_format = config.sample_format();
let config = StreamConfig::from(config);
let err_fn = |err| eprintln!("an error occurred on the output audio stream: {err}");
let mixer = Mixer::new();
let mixer_clone = mixer.clone();
macro_rules! output_stream {
($sample:ty) => {
device.build_output_stream(
&config,
write_sources::<F, $sample>(mixer_clone, &config),
err_fn,
None,
)
};
}
let stream = match sample_format {
SampleFormat::F32 => output_stream!(f32),
SampleFormat::I16 => output_stream!(i16),
SampleFormat::U16 => output_stream!(u16),
SampleFormat::I8 => output_stream!(i8),
SampleFormat::I32 => output_stream!(i32),
SampleFormat::I64 => output_stream!(i64),
SampleFormat::U8 => output_stream!(u8),
SampleFormat::U32 => output_stream!(u32),
SampleFormat::U64 => output_stream!(u64),
SampleFormat::F64 => output_stream!(f64),
_ => return Err(BuildSystemAudioError::UnsupportedSampleFormat),
}
.unwrap();
stream.play()?;
Ok(OutputDeviceMixer {
mixer,
_stream: stream,
sample_rate: config.sample_rate.0,
})
}
}
fn write_sources<F, A>(
mut mixer: Mixer<F>,
config: &StreamConfig,
) -> impl FnMut(&mut [A], &OutputCallbackInfo)
where
F: Frame,
A: Amplitude,
{
let channels = config.channels as usize;
let sample_rate = config.sample_rate.0 as f64;
let mut frame_buffer = vec![0.0; channels];
let mut i = channels;
move |buffer, _| {
buffer.fill(A::MIDPOINT);
for out_sample in buffer {
if i >= channels {
i = 0;
if let Some(frame) = mixer.next(sample_rate) {
frame.write_slice(&mut frame_buffer);
} else {
break;
}
}
*out_sample = A::from_f64(frame_buffer[i]);
i += 1;
}
}
}