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>,
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),
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));
}
}