use std::ffi::c_void;
use audio_core_bsd::{
AudioFrame, AudioNode, PortDescriptor, PortDirection, ProcessContext, SampleFormat,
};
use crate::metadata::PluginMetadata;
use crate::symbols::{CreateFn, DestroyFn};
pub trait Plugin: Send + Sync {
fn metadata(&self) -> &PluginMetadata;
fn instantiate(&self) -> Box<dyn AudioNode>;
}
pub(crate) struct HostPlugin {
metadata: PluginMetadata,
create_fn: CreateFn,
destroy_fn: Option<DestroyFn>,
}
impl HostPlugin {
pub(crate) fn new(
metadata: PluginMetadata,
create_fn: CreateFn,
destroy_fn: Option<DestroyFn>,
) -> Self {
Self {
metadata,
create_fn,
destroy_fn,
}
}
}
impl Plugin for HostPlugin {
fn metadata(&self) -> &PluginMetadata {
&self.metadata
}
fn instantiate(&self) -> Box<dyn AudioNode> {
let handle = unsafe { (self.create_fn)() };
Box::new(AudioNodeAdapter::new(handle, self.destroy_fn))
}
}
pub(crate) struct AudioNodeAdapter {
handle: *mut c_void,
destroy_fn: Option<DestroyFn>,
in_port: [PortDescriptor; 1],
out_port: [PortDescriptor; 1],
}
unsafe impl Send for AudioNodeAdapter {}
impl AudioNodeAdapter {
pub(crate) fn new(handle: *mut c_void, destroy_fn: Option<DestroyFn>) -> Self {
Self {
handle,
destroy_fn,
in_port: [PortDescriptor::new(
PortDirection::Input,
1,
SampleFormat::F32,
)],
out_port: [PortDescriptor::new(
PortDirection::Output,
1,
SampleFormat::F32,
)],
}
}
}
impl AudioNode for AudioNodeAdapter {
fn inputs(&self) -> &[PortDescriptor] {
&self.in_port
}
fn outputs(&self) -> &[PortDescriptor] {
&self.out_port
}
fn process(
&mut self,
_ctx: &mut ProcessContext,
in_frames: &[AudioFrame],
out_frames: &mut [AudioFrame],
) {
let _ = self.handle;
let Some(inp) = in_frames.first() else {
return;
};
let Some(out) = out_frames.get_mut(0) else {
return;
};
let n = inp.samples.len().min(out.samples.len());
out.samples[..n].copy_from_slice(&inp.samples[..n]);
}
}
impl Drop for AudioNodeAdapter {
fn drop(&mut self) {
if let Some(destroy) = self.destroy_fn {
unsafe { destroy(self.handle) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adapter_is_pass_through_preserving_samples() {
let mut adapter = AudioNodeAdapter::new(std::ptr::null_mut(), None);
let mut ctx = ProcessContext::new(4, 0, 48_000);
let input = AudioFrame::from_planar(1, 48_000, vec![0.1, 0.2, -0.3, 0.5]);
let mut output = AudioFrame::silence(1, 4, 48_000);
let mut out_slice = [output];
adapter.process(&mut ctx, [input.clone()].as_slice(), &mut out_slice);
output = out_slice.into_iter().next().unwrap();
for (a, b) in output.samples.iter().zip(input.samples.iter()) {
assert!(((a - b).abs() < 1e-6));
}
}
#[test]
fn adapter_declares_one_mono_f32_input_and_output() {
let adapter = AudioNodeAdapter::new(std::ptr::null_mut(), None);
assert_eq!(adapter.inputs().len(), 1);
assert_eq!(adapter.outputs().len(), 1);
assert_eq!(adapter.inputs()[0].channels, 1);
assert_eq!(adapter.inputs()[0].sample_format, SampleFormat::F32);
assert_eq!(adapter.outputs()[0].direction, PortDirection::Output);
}
#[test]
fn adapter_process_with_empty_frames_does_not_panic() {
let mut adapter = AudioNodeAdapter::new(std::ptr::null_mut(), None);
let mut ctx = ProcessContext::new(0, 0, 48_000);
adapter.process(&mut ctx, &[], &mut []);
}
#[test]
fn adapter_process_clamps_to_shorter_buffer() {
let mut adapter = AudioNodeAdapter::new(std::ptr::null_mut(), None);
let mut ctx = ProcessContext::new(2, 0, 48_000);
let input = AudioFrame::from_planar(1, 48_000, vec![0.5, -0.5]);
let mut output = AudioFrame::silence(1, 4, 48_000);
let mut out_slice = [output];
adapter.process(&mut ctx, [input].as_slice(), &mut out_slice);
output = out_slice.into_iter().next().unwrap();
assert!(((output.samples[0] - 0.5).abs() < 1e-6));
assert!(((output.samples[1] + 0.5).abs() < 1e-6));
assert!(((output.samples[2]).abs() < 1e-6));
assert!(((output.samples[3]).abs() < 1e-6));
}
}