use std::collections::HashMap;
use std::fmt;
use std::sync::{Arc, OnceLock, RwLock};
use super::atome::Effect;
pub type ProcessFn = dyn Fn(&mut [f32], u16) + Send + Sync + 'static;
enum Body {
Function(Arc<ProcessFn>),
Effect(Box<dyn Effect>),
}
impl Clone for Body {
fn clone(&self) -> Self {
match self {
Self::Function(function) => Self::Function(Arc::clone(function)),
Self::Effect(effect) => Self::Effect(effect.duplicate()),
}
}
}
#[derive(Clone)]
pub struct InternalPlugin {
name: String,
body: Body,
latency: usize,
}
impl InternalPlugin {
pub fn new<F>(name: impl Into<String>, process: F) -> Self
where
F: Fn(&mut [f32], u16) + Send + Sync + 'static,
{
Self {
name: name.into(),
body: Body::Function(Arc::new(process)),
latency: 0,
}
}
pub fn from_effect(name: impl Into<String>, effect: impl Effect) -> Self {
let latency = effect.latency();
Self {
name: name.into(),
body: Body::Effect(Box::new(effect)),
latency,
}
}
pub(crate) fn effect(&self) -> Option<&dyn Effect> {
match &self.body {
Body::Effect(effect) => Some(effect.as_ref()),
Body::Function(_) => None,
}
}
pub(crate) fn effect_mut(&mut self) -> Option<&mut dyn Effect> {
match &mut self.body {
Body::Effect(effect) => Some(effect.as_mut()),
Body::Function(_) => None,
}
}
#[must_use]
pub fn with_latency(mut self, frames: usize) -> Self {
self.latency = frames;
self
}
pub fn name(&self) -> &str {
&self.name
}
pub fn latency(&self) -> usize {
self.latency
}
pub fn process(&mut self, buffer: &mut [f32], channels: u16) {
match &mut self.body {
Body::Function(function) => function(buffer, channels),
Body::Effect(effect) => effect.process(buffer, channels),
}
}
pub fn reset(&mut self) {
if let Body::Effect(effect) = &mut self.body {
effect.reset();
}
}
}
impl fmt::Debug for InternalPlugin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InternalPlugin")
.field("name", &self.name)
.field("latency", &self.latency)
.finish_non_exhaustive()
}
}
fn registry() -> &'static RwLock<HashMap<String, InternalPlugin>> {
static REGISTRY: OnceLock<RwLock<HashMap<String, InternalPlugin>>> = OnceLock::new();
REGISTRY.get_or_init(|| RwLock::new(HashMap::new()))
}
pub fn register<F>(name: impl Into<String>, process: F) -> Option<InternalPlugin>
where
F: Fn(&mut [f32], u16) + Send + Sync + 'static,
{
register_plugin(InternalPlugin::new(name, process))
}
pub fn register_plugin(plugin: InternalPlugin) -> Option<InternalPlugin> {
let mut map = registry().write().unwrap_or_else(|e| e.into_inner());
map.insert(plugin.name.clone(), plugin)
}
pub fn get(name: &str) -> Option<InternalPlugin> {
let map = registry().read().unwrap_or_else(|e| e.into_inner());
map.get(name).cloned()
}
pub fn unregister(name: &str) -> Option<InternalPlugin> {
let mut map = registry().write().unwrap_or_else(|e| e.into_inner());
map.remove(name)
}
pub fn registered() -> Vec<String> {
let map = registry().read().unwrap_or_else(|e| e.into_inner());
map.keys().cloned().collect()
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
#[test]
fn processes_in_place() {
let mut plugin = InternalPlugin::new("double", |buffer: &mut [f32], _| {
for sample in buffer {
*sample *= 2.0;
}
});
let mut buffer = [0.25, -0.5, 0.125, 0.0];
plugin.process(&mut buffer, 2);
assert_eq!(buffer, [0.5, -1.0, 0.25, 0.0]);
}
#[test]
fn sees_the_interleaving() {
let mut plugin = InternalPlugin::new("left only", |buffer: &mut [f32], channels| {
for frame in buffer.chunks_mut(channels as usize) {
for sample in &mut frame[1..] {
*sample = 0.0;
}
}
});
let mut buffer = [1.0, 1.0, 1.0, 1.0];
plugin.process(&mut buffer, 2);
assert_eq!(buffer, [1.0, 0.0, 1.0, 0.0]);
}
#[test]
fn latency_defaults_to_zero_and_is_declarable() {
let plugin = InternalPlugin::new("noop", |_: &mut [f32], _| {});
assert_eq!(plugin.latency(), 0);
assert_eq!(plugin.with_latency(64).latency(), 64);
}
#[test]
fn registry_round_trip() {
register("registry round trip", |buffer: &mut [f32], _| {
buffer.fill(1.0);
});
let mut found = get("registry round trip").expect("just registered");
let mut buffer = [0.0; 3];
found.process(&mut buffer, 1);
assert_eq!(buffer, [1.0; 3]);
assert!(registered().iter().any(|n| n == "registry round trip"));
assert!(unregister("registry round trip").is_some());
assert!(get("registry round trip").is_none());
}
#[test]
fn state_survives_a_block_boundary() {
let filter = || {
let previous = Mutex::new(0.0_f32);
InternalPlugin::new("one-pole", move |buffer: &mut [f32], _| {
let mut previous = previous.lock().unwrap_or_else(|e| e.into_inner());
for sample in buffer {
*previous += 0.1 * (*sample - *previous);
*sample = *previous;
}
})
};
let input: Vec<f32> = (0..64).map(|n| (n as f32 * 0.1).sin()).collect();
let mut whole = input.clone();
filter().process(&mut whole, 1);
let mut blocked = input;
let mut piecewise = filter();
for block in blocked.chunks_mut(7) {
piecewise.process(block, 1);
}
assert_eq!(whole, blocked);
}
#[test]
fn a_clone_shares_the_function() {
let calls = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&calls);
let mut plugin = InternalPlugin::new("counter", move |_: &mut [f32], _| {
counter.fetch_add(1, Ordering::Relaxed);
});
let mut clone = plugin.clone();
plugin.process(&mut [], 1);
clone.process(&mut [], 1);
assert_eq!(calls.load(Ordering::Relaxed), 2);
}
}