use std::sync::{Arc, Mutex, MutexGuard};
use crate::{
audio::{AudioBackend, AudioBuffers, AudioConfig, AudioStream},
error::{Error, Result},
plugin::Plugin,
realtime::{RealtimePluginRunner, RtControl},
};
pub struct AudioHandle {
_stream: Box<dyn AudioStream>,
_input_stream: Option<Box<dyn AudioStream>>,
plugin: Arc<Mutex<Plugin>>,
}
impl AudioHandle {
pub fn lock(&self) -> MutexGuard<'_, Plugin> {
self.plugin
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
pub fn plugin(&self) -> Arc<Mutex<Plugin>> {
Arc::clone(&self.plugin)
}
pub fn stop(self) {}
}
pub(crate) fn interleave_outputs(outputs: &[Vec<f32>], out: &mut [f32], channels: usize) {
if channels == 0 {
return;
}
let frames = out.len() / channels;
for ch in 0..channels.min(outputs.len()) {
let src = &outputs[ch];
for frame in 0..frames.min(src.len()) {
out[frame * channels + ch] = src[frame];
}
}
}
fn prepare_scratch(scratch: &mut AudioBuffers, frames: usize) {
for ch in &mut scratch.outputs {
if ch.len() != frames {
ch.resize(frames, 0.0);
}
ch.fill(0.0);
}
for ch in &mut scratch.inputs {
if ch.len() != frames {
ch.resize(frames, 0.0);
}
ch.fill(0.0);
}
scratch.block_size = frames;
}
pub fn play_with_backend<B: AudioBackend>(
backend: &B,
plugin: Plugin,
config: AudioConfig,
) -> Result<AudioHandle> {
let device = backend
.default_output_device()
.ok_or_else(|| Error::AudioBackendError("No default output device available".into()))?;
let channels = config.output_channels;
let sample_rate = config.sample_rate;
let plugin = Arc::new(Mutex::new(plugin));
plugin
.lock()
.unwrap_or_else(|p| p.into_inner())
.start_processing()?;
let plugin_cb = Arc::clone(&plugin);
let mut scratch = AudioBuffers::new(0, channels, config.block_size, sample_rate);
let data_cb = Box::new(move |data: &mut [f32]| {
data.fill(0.0);
if channels == 0 {
return;
}
let frames = data.len() / channels;
prepare_scratch(&mut scratch, frames);
if let Ok(mut p) = plugin_cb.lock() {
if p.process_audio(&mut scratch).is_ok() {
interleave_outputs(&scratch.outputs, data, channels);
}
}
});
let err_cb = Box::new(|e: B::Error| {
log::error!("audio stream error: {}", e);
});
let stream = backend
.create_output_stream(&device, config, data_cb, err_cb)
.map_err(|e| Error::AudioBackendError(format!("Failed to create output stream: {}", e)))?;
stream
.play()
.map_err(|e| Error::AudioBackendError(format!("Failed to start stream: {}", e)))?;
Ok(AudioHandle {
_stream: Box::new(stream),
_input_stream: None,
plugin,
})
}
pub fn play_with_input_backend<B: AudioBackend>(
backend: &B,
plugin: Plugin,
config: AudioConfig,
) -> Result<AudioHandle> {
let in_device = backend
.default_input_device()
.ok_or_else(|| Error::AudioBackendError("No default input device available".into()))?;
let out_device = backend
.default_output_device()
.ok_or_else(|| Error::AudioBackendError("No default output device available".into()))?;
let in_channels = config.input_channels.max(1);
let out_channels = config.output_channels;
let sample_rate = config.sample_rate;
let plugin = Arc::new(Mutex::new(plugin));
plugin
.lock()
.unwrap_or_else(|p| p.into_inner())
.start_processing()?;
let ring_cap = (config.block_size * in_channels * 8).max(2048);
let (mut producer, mut consumer) = rtrb::RingBuffer::<f32>::new(ring_cap);
let in_data_cb = Box::new(move |data: &[f32]| {
for &s in data {
let _ = producer.push(s);
}
});
let in_err_cb = Box::new(|e: B::Error| log::error!("input stream error: {}", e));
let input_stream = backend
.create_input_stream(&in_device, config, in_data_cb, in_err_cb)
.map_err(|e| Error::AudioBackendError(format!("Failed to create input stream: {}", e)))?;
let plugin_cb = Arc::clone(&plugin);
let mut scratch = AudioBuffers::new(in_channels, out_channels, config.block_size, sample_rate);
let out_data_cb = Box::new(move |data: &mut [f32]| {
data.fill(0.0);
if out_channels == 0 {
return;
}
let frames = data.len() / out_channels;
prepare_scratch(&mut scratch, frames);
for f in 0..frames {
for ch in scratch.inputs.iter_mut() {
ch[f] = consumer.pop().unwrap_or(0.0);
}
}
if let Ok(mut p) = plugin_cb.lock() {
if p.process_audio(&mut scratch).is_ok() {
interleave_outputs(&scratch.outputs, data, out_channels);
}
}
});
let out_err_cb = Box::new(|e: B::Error| log::error!("output stream error: {}", e));
let output_stream = backend
.create_output_stream(&out_device, config, out_data_cb, out_err_cb)
.map_err(|e| Error::AudioBackendError(format!("Failed to create output stream: {}", e)))?;
input_stream
.play()
.map_err(|e| Error::AudioBackendError(format!("Failed to start input stream: {}", e)))?;
output_stream
.play()
.map_err(|e| Error::AudioBackendError(format!("Failed to start output stream: {}", e)))?;
Ok(AudioHandle {
_stream: Box::new(output_stream),
_input_stream: Some(Box::new(input_stream)),
plugin,
})
}
pub struct RtAudioHandle {
_stream: Box<dyn AudioStream>,
control: RtControl,
}
impl RtAudioHandle {
pub fn control(&mut self) -> &mut RtControl {
&mut self.control
}
pub fn stop(self) {}
}
pub fn play_realtime_with_backend<B: AudioBackend>(
backend: &B,
plugin: Plugin,
config: AudioConfig,
command_capacity: usize,
) -> Result<RtAudioHandle> {
let device = backend
.default_output_device()
.ok_or_else(|| Error::AudioBackendError("No default output device available".into()))?;
let channels = config.output_channels;
let sample_rate = config.sample_rate;
let (mut runner, control) = RealtimePluginRunner::new(plugin, command_capacity);
runner.start()?;
let mut scratch = AudioBuffers::new(0, channels, config.block_size, sample_rate);
let data_cb = Box::new(move |data: &mut [f32]| {
data.fill(0.0);
if channels == 0 {
return;
}
let frames = data.len() / channels;
prepare_scratch(&mut scratch, frames);
if runner.process(&mut scratch).is_ok() {
interleave_outputs(&scratch.outputs, data, channels);
}
});
let err_cb = Box::new(|e: B::Error| {
log::error!("audio stream error: {}", e);
});
let stream = backend
.create_output_stream(&device, config, data_cb, err_cb)
.map_err(|e| Error::AudioBackendError(format!("Failed to create output stream: {}", e)))?;
stream
.play()
.map_err(|e| Error::AudioBackendError(format!("Failed to start stream: {}", e)))?;
Ok(RtAudioHandle {
_stream: Box::new(stream),
control,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn interleaves_two_channels() {
let outputs = vec![vec![1.0, 2.0, 3.0], vec![-1.0, -2.0, -3.0]];
let mut out = vec![0.0; 6]; interleave_outputs(&outputs, &mut out, 2);
assert_eq!(out, vec![1.0, -1.0, 2.0, -2.0, 3.0, -3.0]);
}
#[test]
fn ignores_extra_plugin_channels() {
let outputs = vec![vec![1.0, 2.0], vec![3.0, 4.0], vec![9.0, 9.0]];
let mut out = vec![0.0; 4];
interleave_outputs(&outputs, &mut out, 2);
assert_eq!(out, vec![1.0, 3.0, 2.0, 4.0]);
}
#[test]
fn leaves_missing_channels_as_silence() {
let outputs = vec![vec![0.5, 0.6]];
let mut out = vec![0.0; 4];
interleave_outputs(&outputs, &mut out, 2);
assert_eq!(out, vec![0.5, 0.0, 0.6, 0.0]);
}
#[test]
fn zero_channels_is_a_noop() {
let outputs = vec![vec![1.0, 2.0]];
let mut out = vec![7.0, 7.0];
interleave_outputs(&outputs, &mut out, 0);
assert_eq!(out, vec![7.0, 7.0]);
}
#[test]
fn prepare_scratch_resizes_and_clears() {
let mut scratch = AudioBuffers::new(1, 2, 4, 48000.0);
scratch.outputs[0][0] = 9.0;
prepare_scratch(&mut scratch, 8);
assert_eq!(scratch.block_size, 8);
assert!(scratch.outputs.iter().all(|c| c.len() == 8));
assert!(scratch.inputs.iter().all(|c| c.len() == 8));
assert!(scratch.outputs.iter().flatten().all(|&s| s == 0.0));
}
}