use anyhow::{Context, Result};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use crossbeam_channel::{Receiver, Sender, bounded};
use crate::audio::processor::SynthProcessor;
use crate::params::AudioEvent;
pub use crate::audio::processor::NUM_CHANNELS;
const SCOPE_CHANNEL_CAPACITY: usize = 32;
const EVENT_CHANNEL_CAPACITY: usize = 1024;
const SCOPE_DECIMATION: usize = 4;
const SCOPE_BATCH: usize = 128;
pub trait Sequencer: Send + 'static {
fn tick(&mut self, sample_rate: f32, out: &mut Vec<AudioEvent>) -> usize;
}
impl Sequencer for () {
fn tick(&mut self, _sample_rate: f32, _out: &mut Vec<AudioEvent>) -> usize {
usize::MAX
}
}
pub fn setup_audio_n_with_error_handler<const N: usize, F>(
buffer_size: cpal::BufferSize,
on_error: F,
) -> Result<(cpal::Stream, Sender<AudioEvent>, Receiver<Vec<f32>>)>
where
F: Fn(cpal::Error) + Send + 'static,
{
setup_audio_n_with_sequencer::<N, _, _>(buffer_size, (), on_error)
}
pub fn setup_audio_n_with_sequencer<const N: usize, S, F>(
buffer_size: cpal::BufferSize,
mut sequencer: S,
on_error: F,
) -> Result<(cpal::Stream, Sender<AudioEvent>, Receiver<Vec<f32>>)>
where
S: Sequencer,
F: Fn(cpal::Error) + Send + 'static,
{
let (event_tx, event_rx) = bounded::<AudioEvent>(EVENT_CHANNEL_CAPACITY);
let (scope_tx, scope_rx) = bounded::<Vec<f32>>(SCOPE_CHANNEL_CAPACITY);
let host = cpal::default_host();
let device = host
.default_output_device()
.context("no default audio output device")?;
let config = device
.default_output_config()
.context("failed to query default output config")?;
#[allow(clippy::cast_precision_loss)]
let sample_rate = config.sample_rate() as f32;
let hw_channels = config.channels() as usize;
let stream_config = cpal::StreamConfig {
channels: config.channels(),
sample_rate: config.sample_rate(),
buffer_size,
};
let mut processor = SynthProcessor::<N>::new(sample_rate);
let mut events_buf: Vec<AudioEvent> = Vec::with_capacity(EVENT_CHANNEL_CAPACITY);
let mut scope_accum: Vec<f32> = Vec::with_capacity(SCOPE_BATCH * 2);
let mut scope_dec_counter: usize = 0;
let mut until_tick: usize = 0;
let stream = device
.build_output_stream(
stream_config,
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
events_buf.clear();
while let Ok(event) = event_rx.try_recv() {
events_buf.push(event);
}
let frames = data.len() / hw_channels;
let mut offset = 0;
loop {
if until_tick == 0 {
until_tick = sequencer.tick(sample_rate, &mut events_buf).max(1);
}
let n = until_tick.min(frames - offset);
processor.process_block(
&events_buf,
&mut data[offset * hw_channels..(offset + n) * hw_channels],
hw_channels,
);
events_buf.clear();
offset += n;
until_tick -= n;
if offset >= frames {
break;
}
}
for frame in data.chunks(hw_channels) {
scope_dec_counter += 1;
if scope_dec_counter >= SCOPE_DECIMATION {
scope_dec_counter = 0;
if let Some(&sample) = frame.first() {
scope_accum.push(sample);
}
if scope_accum.len() >= SCOPE_BATCH {
let batch = std::mem::replace(
&mut scope_accum,
Vec::with_capacity(SCOPE_BATCH * 2),
);
let _ = scope_tx.try_send(batch);
}
}
}
},
on_error,
None,
)
.context("failed to build output stream")?;
stream.play().context("failed to start audio stream")?;
Ok((stream, event_tx, scope_rx))
}
pub fn setup_audio_n<const N: usize>()
-> Result<(cpal::Stream, Sender<AudioEvent>, Receiver<Vec<f32>>)> {
setup_audio_n_with_error_handler::<N, _>(cpal::BufferSize::Default, |err| {
eprintln!("audio stream error: {err}");
})
}
pub fn setup_audio() -> Result<(cpal::Stream, Sender<AudioEvent>, Receiver<Vec<f32>>)> {
setup_audio_n::<NUM_CHANNELS>()
}
pub fn setup_audio_silenced() -> Result<(cpal::Stream, Sender<AudioEvent>, Receiver<Vec<f32>>)> {
setup_audio_silenced_with_error_handler(|_| {})
}
pub fn setup_audio_silenced_with_error_handler<F>(
on_error: F,
) -> Result<(cpal::Stream, Sender<AudioEvent>, Receiver<Vec<f32>>)>
where
F: Fn(cpal::Error) + Send + 'static,
{
setup_audio_silenced_with_sequencer((), on_error)
}
pub fn setup_audio_silenced_with_sequencer<S, F>(
sequencer: S,
on_error: F,
) -> Result<(cpal::Stream, Sender<AudioEvent>, Receiver<Vec<f32>>)>
where
S: Sequencer,
F: Fn(cpal::Error) + Send + 'static,
{
setup_audio_n_with_sequencer::<NUM_CHANNELS, _, _>(
cpal::BufferSize::Fixed(4096),
sequencer,
on_error,
)
}