use std::collections::HashMap;
use crate::math::Transcendental;
use crate::traits::ParamValue;
pub trait SampleBuiltin<T: Transcendental>: Send + Sync {
fn process_sample(&mut self, inputs: &[T]) -> T;
fn init(&mut self, _sample_rate: f32) {}
fn reset(&mut self);
fn set_param(&mut self, _index: usize, _value: &ParamValue) {}
}
pub trait BlockBuiltin<T: Transcendental>: crate::traits::Algorithm<T> {
fn set_param(&mut self, _index: usize, _value: &ParamValue) {}
}
pub trait MultichannelBlockBuiltin<T: Transcendental>:
crate::traits::MultichannelAlgorithm<T> + Send + Sync
{
fn set_param(&mut self, _index: usize, _value: &ParamValue) {}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinKind {
Sample,
Block,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParamType {
Signal,
Float,
Int,
String,
Bool,
Record(RecordSchema),
Enum(&'static [&'static str]),
Variadic(Box<ParamType>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct RecordSchema {
pub fields: Vec<RecordField>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RecordField {
pub name: &'static str,
pub ty: ParamType,
pub default: Option<f64>,
}
impl RecordSchema {
pub fn new(fields: Vec<RecordField>) -> Self {
Self { fields }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BuiltinSig {
pub name: &'static str,
pub params: Vec<ParamType>,
pub signal_outs: usize,
pub kind: BuiltinKind,
pub param_names: Vec<&'static str>,
}
impl BuiltinSig {
pub fn simple(
name: &'static str,
signal_ins: usize,
signal_outs: usize,
num_params: usize,
kind: BuiltinKind,
) -> Self {
let mut params = Vec::with_capacity(signal_ins + num_params);
for _ in 0..signal_ins {
params.push(ParamType::Signal);
}
for _ in 0..num_params {
params.push(ParamType::Float);
}
Self {
name,
params,
signal_outs,
kind,
param_names: Vec::new(),
}
}
pub fn with_names(mut self, names: Vec<&'static str>) -> Self {
self.param_names = names;
self
}
pub fn signal_ins(&self) -> usize {
self.params
.iter()
.filter(|p| matches!(p, ParamType::Signal))
.count()
}
pub fn min_args(&self) -> usize {
let mut count = 0;
for p in &self.params {
match p {
ParamType::Signal | ParamType::Variadic(_) => {}
_ => count += 1,
}
}
count
}
pub fn max_args(&self) -> Option<usize> {
if self
.params
.iter()
.any(|p| matches!(p, ParamType::Variadic(_)))
{
None
} else {
Some(
self.params
.iter()
.filter(|p| !matches!(p, ParamType::Signal))
.count(),
)
}
}
}
type SampleFactory<T> = Box<dyn Fn(&[f64], f32) -> Box<dyn SampleBuiltin<T>> + Send + Sync>;
type BlockFactory<T> = Box<dyn Fn(&[f64], f32) -> Box<dyn BlockBuiltin<T>> + Send + Sync>;
type MultichannelBlockFactory<T> =
Box<dyn Fn(&[f64], f32) -> Box<dyn MultichannelBlockBuiltin<T>> + Send + Sync>;
enum Factory<T: Transcendental> {
Sample(SampleFactory<T>),
Block(BlockFactory<T>),
MultichannelBlock(MultichannelBlockFactory<T>),
}
pub struct Entry<T: Transcendental> {
pub sig: BuiltinSig,
factory: Factory<T>,
}
impl<T: Transcendental> Entry<T> {
pub fn build_sample(
&self,
params: &[f64],
sample_rate: f32,
) -> Option<Box<dyn SampleBuiltin<T>>> {
match &self.factory {
Factory::Sample(f) => Some(f(params, sample_rate)),
Factory::Block(_) | Factory::MultichannelBlock(_) => None,
}
}
pub fn build_block(
&self,
params: &[f64],
sample_rate: f32,
) -> Option<Box<dyn BlockBuiltin<T>>> {
match &self.factory {
Factory::Block(f) => Some(f(params, sample_rate)),
Factory::Sample(_) | Factory::MultichannelBlock(_) => None,
}
}
pub fn build_multichannel_block(
&self,
params: &[f64],
sample_rate: f32,
) -> Option<Box<dyn MultichannelBlockBuiltin<T>>> {
match &self.factory {
Factory::MultichannelBlock(f) => Some(f(params, sample_rate)),
_ => None,
}
}
}
pub struct Registry<T: Transcendental> {
entries: HashMap<String, Entry<T>>,
}
impl<T: Transcendental> Default for Registry<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Transcendental> Registry<T> {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
}
}
pub fn register_sample(
&mut self,
sig: BuiltinSig,
factory: impl Fn(&[f64], f32) -> Box<dyn SampleBuiltin<T>> + Send + Sync + 'static,
) {
debug_assert_eq!(sig.kind, BuiltinKind::Sample);
self.entries.insert(
sig.name.to_string(),
Entry {
sig,
factory: Factory::Sample(Box::new(factory)),
},
);
}
pub fn register_block(
&mut self,
sig: BuiltinSig,
factory: impl Fn(&[f64], f32) -> Box<dyn BlockBuiltin<T>> + Send + Sync + 'static,
) {
debug_assert_eq!(sig.kind, BuiltinKind::Block);
self.entries.insert(
sig.name.to_string(),
Entry {
sig,
factory: Factory::Block(Box::new(factory)),
},
);
}
pub fn register_multichannel_block(
&mut self,
sig: BuiltinSig,
factory: impl Fn(&[f64], f32) -> Box<dyn MultichannelBlockBuiltin<T>> + Send + Sync + 'static,
) {
debug_assert_eq!(sig.kind, BuiltinKind::Block);
self.entries.insert(
sig.name.to_string(),
Entry {
sig,
factory: Factory::MultichannelBlock(Box::new(factory)),
},
);
}
pub fn get(&self, name: &str) -> Option<&Entry<T>> {
self.entries.get(name)
}
}
pub trait SignatureSource {
fn builtin_sig(&self, name: &str) -> Option<&BuiltinSig>;
}
impl<T: Transcendental> SignatureSource for Registry<T> {
fn builtin_sig(&self, name: &str) -> Option<&BuiltinSig> {
self.entries.get(name).map(|e| &e.sig)
}
}
pub struct NoSigs;
impl SignatureSource for NoSigs {
fn builtin_sig(&self, _name: &str) -> Option<&BuiltinSig> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Gain {
k: f32,
}
impl SampleBuiltin<f32> for Gain {
fn process_sample(&mut self, inputs: &[f32]) -> f32 {
inputs[0] * self.k
}
fn reset(&mut self) {}
}
#[test]
fn register_and_lookup_sample() {
let mut reg = Registry::<f32>::new();
reg.register_sample(
BuiltinSig::simple("gain", 1, 1, 1, BuiltinKind::Sample),
|params, _sr| {
Box::new(Gain {
k: params[0] as f32,
})
},
);
let sig = reg.builtin_sig("gain").unwrap();
assert_eq!((sig.signal_ins(), sig.params.len()), (1, 2));
let mut inst = reg
.get("gain")
.unwrap()
.build_sample(&[0.5], 44100.0)
.unwrap();
assert_eq!(inst.process_sample(&[2.0]), 1.0);
assert!(reg.builtin_sig("missing").is_none());
}
}