use core::ffi::c_ulong;
use crate::{plugin::Plugin, port::Ports, raw, realtime::RealtimeContext};
pub(crate) struct Instance<P: Plugin> {
plugin: P,
port_ptrs: Box<[*mut raw::Data]>,
}
impl<P: Plugin> Instance<P> {
fn new(plugin: P) -> Self {
let port_count = P::ports().len();
let port_ptrs = vec![core::ptr::null_mut(); port_count].into_boxed_slice();
Self { plugin, port_ptrs }
}
}
pub unsafe extern "C" fn instantiate_shim<P: Plugin>(
_descriptor: *const raw::Descriptor,
sample_rate: c_ulong,
) -> raw::Handle {
let sample_rate = sample_rate as u32;
match P::instantiate(sample_rate) {
Ok(plugin) => {
let instance = Box::new(Instance::<P>::new(plugin));
Box::into_raw(instance) as raw::Handle
}
Err(_) => core::ptr::null_mut(),
}
}
pub unsafe extern "C" fn connect_port_shim<P: Plugin>(
handle: raw::Handle,
port: c_ulong,
data: *mut raw::Data,
) {
let instance = unsafe { &mut *(handle as *mut Instance<P>) };
let port = port as usize;
debug_assert!(
port < instance.port_ptrs.len(),
"connect_port: index {port} out of range (port count {})",
instance.port_ptrs.len(),
);
instance.port_ptrs[port] = data;
}
pub unsafe extern "C" fn activate_shim<P: Plugin>(handle: raw::Handle) {
let instance = unsafe { &mut *(handle as *mut Instance<P>) };
instance.plugin.activate();
}
pub unsafe extern "C" fn run_shim<P: Plugin>(handle: raw::Handle, sample_count: c_ulong) {
let instance = unsafe { &mut *(handle as *mut Instance<P>) };
let frames = sample_count as usize;
let rt = RealtimeContext::new();
let mut ports = Ports {
ptrs: &instance.port_ptrs,
descriptors: P::ports(),
frames,
};
instance.plugin.run(&rt, frames, &mut ports);
}
pub unsafe extern "C" fn deactivate_shim<P: Plugin>(handle: raw::Handle) {
let instance = unsafe { &mut *(handle as *mut Instance<P>) };
instance.plugin.deactivate();
}
pub unsafe extern "C" fn cleanup_shim<P: Plugin>(handle: raw::Handle) {
let instance = unsafe { Box::from_raw(handle as *mut Instance<P>) };
drop(instance);
}
#[cfg(test)]
mod tests {
#![allow(clippy::undocumented_unsafe_blocks)]
use super::*;
use crate::{
descriptor::{Callbacks, DescriptorBundle},
error::InstantiateError,
port::PortDescriptor,
};
use core::sync::atomic::{AtomicUsize, Ordering};
struct Recording;
static ACTIVATE_COUNT: AtomicUsize = AtomicUsize::new(0);
static RUN_COUNT: AtomicUsize = AtomicUsize::new(0);
static DEACTIVATE_COUNT: AtomicUsize = AtomicUsize::new(0);
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
impl Plugin for Recording {
const UNIQUE_ID: u32 = 0x0001_DEAD;
const LABEL: &'static str = "recording";
const NAME: &'static str = "Recording";
const MAKER: &'static str = "tests";
const COPYRIGHT: &'static str = "MIT";
fn ports() -> &'static [PortDescriptor] {
static PORTS: &[PortDescriptor] = &[
PortDescriptor::audio_input("In"),
PortDescriptor::audio_output("Out"),
PortDescriptor::control_input("Gain"),
];
PORTS
}
fn instantiate(_sample_rate: u32) -> Result<Self, InstantiateError> {
Ok(Self)
}
fn activate(&mut self) {
ACTIVATE_COUNT.fetch_add(1, Ordering::SeqCst);
}
fn run(&mut self, _rt: &RealtimeContext, _frames: usize, ports: &mut Ports<'_>) {
RUN_COUNT.fetch_add(1, Ordering::SeqCst);
let gain = ports.control_input(2);
let (input, output) = ports.audio_in_out(0, 1);
for (i, o) in input.iter().zip(output.iter_mut()) {
*o = *i * gain;
}
}
fn deactivate(&mut self) {
DEACTIVATE_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
impl Drop for Recording {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
fn callbacks() -> Callbacks {
Callbacks {
instantiate: instantiate_shim::<Recording>,
connect_port: connect_port_shim::<Recording>,
run: run_shim::<Recording>,
cleanup: cleanup_shim::<Recording>,
activate: Some(activate_shim::<Recording>),
deactivate: Some(deactivate_shim::<Recording>),
}
}
#[test]
fn full_lifecycle_drives_plugin_methods() {
let bundle = DescriptorBundle::<Recording>::build(callbacks());
let d = unsafe { &*bundle.descriptor_ptr() };
let handle = unsafe { (d.instantiate.unwrap())(d as *const _, 48_000) };
assert!(!handle.is_null());
let mut input: [raw::Data; 4] = [0.5, 1.0, 1.5, 2.0];
let mut output: [raw::Data; 4] = [0.0; 4];
let mut gain: raw::Data = 2.0;
unsafe {
(d.connect_port.unwrap())(handle, 0, input.as_mut_ptr());
(d.connect_port.unwrap())(handle, 1, output.as_mut_ptr());
(d.connect_port.unwrap())(handle, 2, &mut gain as *mut _);
}
let before_activate = ACTIVATE_COUNT.load(Ordering::SeqCst);
unsafe { (d.activate.unwrap())(handle) };
assert_eq!(ACTIVATE_COUNT.load(Ordering::SeqCst), before_activate + 1);
let before_run = RUN_COUNT.load(Ordering::SeqCst);
unsafe { (d.run.unwrap())(handle, 4) };
assert_eq!(RUN_COUNT.load(Ordering::SeqCst), before_run + 1);
assert_eq!(output, [1.0, 2.0, 3.0, 4.0]);
let before_deactivate = DEACTIVATE_COUNT.load(Ordering::SeqCst);
unsafe { (d.deactivate.unwrap())(handle) };
assert_eq!(
DEACTIVATE_COUNT.load(Ordering::SeqCst),
before_deactivate + 1
);
let before_drop = DROP_COUNT.load(Ordering::SeqCst);
unsafe { (d.cleanup.unwrap())(handle) };
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), before_drop + 1);
}
#[test]
fn multi_instance_state_is_per_handle() {
let bundle = DescriptorBundle::<Recording>::build(callbacks());
let d = unsafe { &*bundle.descriptor_ptr() };
let h1 = unsafe { (d.instantiate.unwrap())(d as *const _, 48_000) };
let h2 = unsafe { (d.instantiate.unwrap())(d as *const _, 48_000) };
assert_ne!(h1, h2, "instances must have distinct handles");
let mut in1: [raw::Data; 2] = [1.0, 2.0];
let mut out1: [raw::Data; 2] = [0.0; 2];
let mut g1: raw::Data = 3.0;
let mut in2: [raw::Data; 2] = [4.0, 5.0];
let mut out2: [raw::Data; 2] = [0.0; 2];
let mut g2: raw::Data = 10.0;
unsafe {
(d.connect_port.unwrap())(h1, 0, in1.as_mut_ptr());
(d.connect_port.unwrap())(h1, 1, out1.as_mut_ptr());
(d.connect_port.unwrap())(h1, 2, &mut g1);
(d.connect_port.unwrap())(h2, 0, in2.as_mut_ptr());
(d.connect_port.unwrap())(h2, 1, out2.as_mut_ptr());
(d.connect_port.unwrap())(h2, 2, &mut g2);
(d.run.unwrap())(h1, 2);
(d.run.unwrap())(h2, 2);
(d.cleanup.unwrap())(h1);
(d.cleanup.unwrap())(h2);
}
assert_eq!(out1, [3.0, 6.0]);
assert_eq!(out2, [40.0, 50.0]);
}
}