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 fn setup_audio_n<const N: usize>()
-> Result<(cpal::Stream, Sender<AudioEvent>, Receiver<Vec<f32>>)> {
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 = config.into();
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 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);
}
processor.process_block(&events_buf, data, hw_channels);
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);
}
}
}
},
|err| eprintln!("audio stream error: {err}"),
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() -> Result<(cpal::Stream, Sender<AudioEvent>, Receiver<Vec<f32>>)> {
setup_audio_n::<NUM_CHANNELS>()
}