use crate::port::{GraphModule, PortDef, PortId, PortSpec, PortValues, SignalKind};
use alloc::string::String;
use alloc::vec;
use core::marker::PhantomData;
pub trait Module: Send {
type In;
type Out;
fn tick(&mut self, input: Self::In) -> Self::Out;
fn process(&mut self, input: &[Self::In], output: &mut [Self::Out])
where
Self::In: Clone,
{
for (i, o) in input.iter().zip(output.iter_mut()) {
*o = self.tick(i.clone());
}
}
fn reset(&mut self);
fn set_sample_rate(&mut self, _sample_rate: f64) {}
}
pub trait ModuleExt: Module + Sized {
fn then<M: Module<In = Self::Out>>(self, next: M) -> Chain<Self, M> {
Chain {
first: self,
second: next,
}
}
fn parallel<M: Module>(self, other: M) -> Parallel<Self, M> {
Parallel {
left: self,
right: other,
}
}
fn fanout<M: Module<In = Self::In>>(self, other: M) -> Fanout<Self, M>
where
Self::In: Clone,
{
Fanout {
left: self,
right: other,
}
}
fn map<F, U>(self, f: F) -> Map<Self, F>
where
F: Fn(Self::Out) -> U,
{
Map { module: self, f }
}
fn contramap<F, U>(self, f: F) -> Contramap<Self, F, U>
where
F: Fn(U) -> Self::In,
{
Contramap {
module: self,
f,
_phantom: PhantomData,
}
}
fn feedback<F>(self, combine: F) -> Feedback<Self, F>
where
Self::Out: Default + Clone,
{
Feedback {
module: self,
combine,
delay_buffer: Self::Out::default(),
}
}
fn first<C>(self) -> First<Self, C> {
First {
module: self,
_phantom: PhantomData,
}
}
fn second<C>(self) -> Second<Self, C> {
Second {
module: self,
_phantom: PhantomData,
}
}
}
impl<M: Module> ModuleExt for M {}
pub struct Chain<A, B> {
pub first: A,
pub second: B,
}
impl<A, B> Module for Chain<A, B>
where
A: Module,
B: Module<In = A::Out>,
{
type In = A::In;
type Out = B::Out;
#[inline]
fn tick(&mut self, input: Self::In) -> Self::Out {
self.second.tick(self.first.tick(input))
}
fn reset(&mut self) {
self.first.reset();
self.second.reset();
}
fn set_sample_rate(&mut self, sample_rate: f64) {
self.first.set_sample_rate(sample_rate);
self.second.set_sample_rate(sample_rate);
}
}
pub struct Parallel<A, B> {
pub left: A,
pub right: B,
}
impl<A, B> Module for Parallel<A, B>
where
A: Module,
B: Module,
{
type In = (A::In, B::In);
type Out = (A::Out, B::Out);
#[inline]
fn tick(&mut self, (a, b): Self::In) -> Self::Out {
(self.left.tick(a), self.right.tick(b))
}
fn reset(&mut self) {
self.left.reset();
self.right.reset();
}
fn set_sample_rate(&mut self, sample_rate: f64) {
self.left.set_sample_rate(sample_rate);
self.right.set_sample_rate(sample_rate);
}
}
pub struct Fanout<A, B> {
pub left: A,
pub right: B,
}
impl<A, B> Module for Fanout<A, B>
where
A: Module,
B: Module<In = A::In>,
A::In: Clone,
{
type In = A::In;
type Out = (A::Out, B::Out);
#[inline]
fn tick(&mut self, input: Self::In) -> Self::Out {
(self.left.tick(input.clone()), self.right.tick(input))
}
fn reset(&mut self) {
self.left.reset();
self.right.reset();
}
fn set_sample_rate(&mut self, sample_rate: f64) {
self.left.set_sample_rate(sample_rate);
self.right.set_sample_rate(sample_rate);
}
}
pub struct Feedback<M: Module, F> {
pub module: M,
pub combine: F,
pub delay_buffer: M::Out,
}
impl<M, F, Combined> Module for Feedback<M, F>
where
M: Module<In = Combined>,
F: Fn(M::Out, M::Out) -> Combined + Send,
M::Out: Default + Clone + Send,
{
type In = M::Out;
type Out = M::Out;
fn tick(&mut self, input: Self::In) -> Self::Out {
let combined = (self.combine)(input, self.delay_buffer.clone());
let output = self.module.tick(combined);
self.delay_buffer = output.clone();
output
}
fn reset(&mut self) {
self.module.reset();
self.delay_buffer = M::Out::default();
}
fn set_sample_rate(&mut self, sample_rate: f64) {
self.module.set_sample_rate(sample_rate);
}
}
pub struct Map<M, F> {
pub module: M,
pub f: F,
}
impl<M, F, U> Module for Map<M, F>
where
M: Module,
F: Fn(M::Out) -> U + Send,
{
type In = M::In;
type Out = U;
#[inline]
fn tick(&mut self, input: Self::In) -> Self::Out {
(self.f)(self.module.tick(input))
}
fn reset(&mut self) {
self.module.reset();
}
fn set_sample_rate(&mut self, sample_rate: f64) {
self.module.set_sample_rate(sample_rate);
}
}
pub struct Contramap<M, F, U> {
pub module: M,
pub f: F,
pub _phantom: PhantomData<U>,
}
impl<M, F, U> Module for Contramap<M, F, U>
where
M: Module,
F: Fn(U) -> M::In + Send,
U: Send,
{
type In = U;
type Out = M::Out;
#[inline]
fn tick(&mut self, input: Self::In) -> Self::Out {
self.module.tick((self.f)(input))
}
fn reset(&mut self) {
self.module.reset();
}
fn set_sample_rate(&mut self, sample_rate: f64) {
self.module.set_sample_rate(sample_rate);
}
}
pub struct Split<T> {
_phantom: PhantomData<T>,
}
impl<T> Split<T> {
pub fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<T> Default for Split<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Clone + Send> Module for Split<T> {
type In = T;
type Out = (T, T);
#[inline]
fn tick(&mut self, input: Self::In) -> Self::Out {
(input.clone(), input)
}
fn reset(&mut self) {}
}
pub struct Merge<T, F> {
pub f: F,
_phantom: PhantomData<T>,
}
impl<T, F> Merge<T, F>
where
F: Fn(T, T) -> T,
{
pub fn new(f: F) -> Self {
Self {
f,
_phantom: PhantomData,
}
}
}
impl<T, F> Module for Merge<T, F>
where
T: Send,
F: Fn(T, T) -> T + Send,
{
type In = (T, T);
type Out = T;
#[inline]
fn tick(&mut self, (a, b): Self::In) -> Self::Out {
(self.f)(a, b)
}
fn reset(&mut self) {}
}
pub struct Swap<A, B> {
_phantom: PhantomData<(A, B)>,
}
impl<A, B> Swap<A, B> {
pub fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<A, B> Default for Swap<A, B> {
fn default() -> Self {
Self::new()
}
}
impl<A: Send, B: Send> Module for Swap<A, B> {
type In = (A, B);
type Out = (B, A);
#[inline]
fn tick(&mut self, (a, b): Self::In) -> Self::Out {
(b, a)
}
fn reset(&mut self) {}
}
pub struct First<M, C> {
pub module: M,
pub _phantom: PhantomData<C>,
}
impl<M, C> Module for First<M, C>
where
M: Module,
C: Send,
{
type In = (M::In, C);
type Out = (M::Out, C);
#[inline]
fn tick(&mut self, (a, c): Self::In) -> Self::Out {
(self.module.tick(a), c)
}
fn reset(&mut self) {
self.module.reset();
}
fn set_sample_rate(&mut self, sample_rate: f64) {
self.module.set_sample_rate(sample_rate);
}
}
pub struct Second<M, C> {
pub module: M,
pub _phantom: PhantomData<C>,
}
impl<M, C> Module for Second<M, C>
where
M: Module,
C: Send,
{
type In = (C, M::In);
type Out = (C, M::Out);
#[inline]
fn tick(&mut self, (c, a): Self::In) -> Self::Out {
(c, self.module.tick(a))
}
fn reset(&mut self) {
self.module.reset();
}
fn set_sample_rate(&mut self, sample_rate: f64) {
self.module.set_sample_rate(sample_rate);
}
}
pub struct Identity<T> {
_phantom: PhantomData<T>,
}
impl<T> Identity<T> {
pub fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<T> Default for Identity<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Send> Module for Identity<T> {
type In = T;
type Out = T;
#[inline]
fn tick(&mut self, input: Self::In) -> Self::Out {
input
}
fn reset(&mut self) {}
}
pub struct Constant<T> {
pub value: T,
}
impl<T> Constant<T> {
pub fn new(value: T) -> Self {
Self { value }
}
}
impl<T: Clone + Send> Module for Constant<T> {
type In = ();
type Out = T;
#[inline]
fn tick(&mut self, _input: Self::In) -> Self::Out {
self.value.clone()
}
fn reset(&mut self) {}
}
pub struct Arr<F, A> {
f: F,
_phantom: PhantomData<A>,
}
pub fn arr<F, A, B>(f: F) -> Arr<F, A>
where
F: Fn(A) -> B,
{
Arr {
f,
_phantom: PhantomData,
}
}
impl<F, A, B> Module for Arr<F, A>
where
F: Fn(A) -> B + Send,
A: Send,
{
type In = A;
type Out = B;
#[inline]
fn tick(&mut self, input: Self::In) -> Self::Out {
(self.f)(input)
}
fn reset(&mut self) {}
}
pub struct GraphModuleAdapter<G: GraphModule> {
module: G,
input_port: PortId,
output_port: PortId,
inputs: PortValues,
outputs: PortValues,
}
impl<G: GraphModule> GraphModuleAdapter<G> {
pub fn new(module: G, input_port: PortId, output_port: PortId) -> Self {
let mut inputs = PortValues::new();
for port in &module.port_spec().inputs {
inputs.set(port.id, port.default);
}
Self {
module,
input_port,
output_port,
inputs,
outputs: PortValues::new(),
}
}
pub fn from_audio_ports(module: G) -> Option<Self> {
let (input_port, output_port) = {
let spec = module.port_spec();
let input_port = spec.inputs.iter().find(|p| p.kind == SignalKind::Audio)?.id;
let output_port = spec
.outputs
.iter()
.find(|p| p.kind == SignalKind::Audio)?
.id;
(input_port, output_port)
};
Some(Self::new(module, input_port, output_port))
}
pub fn inner(&self) -> &G {
&self.module
}
pub fn into_inner(self) -> G {
self.module
}
}
impl<G: GraphModule> Module for GraphModuleAdapter<G> {
type In = f64;
type Out = f64;
#[inline]
fn tick(&mut self, input: Self::In) -> Self::Out {
self.inputs.set(self.input_port, input);
self.outputs.clear();
self.module.tick(&self.inputs, &mut self.outputs);
self.outputs.get_or(self.output_port, 0.0)
}
fn reset(&mut self) {
self.module.reset();
}
fn set_sample_rate(&mut self, sample_rate: f64) {
self.module.set_sample_rate(sample_rate);
}
}
pub struct ModuleGraphAdapter<M> {
module: M,
spec: PortSpec,
}
impl<M> ModuleGraphAdapter<M>
where
M: Module<In = f64, Out = f64>,
{
pub fn new(module: M) -> Self {
Self::with_ports(module, "in", "out")
}
pub fn with_ports(
module: M,
input_name: impl Into<String>,
output_name: impl Into<String>,
) -> Self {
let spec = PortSpec {
inputs: vec![PortDef::new(0, input_name, SignalKind::Audio)],
outputs: vec![PortDef::new(10, output_name, SignalKind::Audio)],
};
Self { module, spec }
}
}
impl<M> GraphModule for ModuleGraphAdapter<M>
where
M: Module<In = f64, Out = f64> + Sync,
{
fn port_spec(&self) -> &PortSpec {
&self.spec
}
fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
let x = inputs.get_or(0, 0.0);
let y = self.module.tick(x);
outputs.set(10, y);
}
fn reset(&mut self) {
self.module.reset();
}
fn set_sample_rate(&mut self, sample_rate: f64) {
self.module.set_sample_rate(sample_rate);
}
fn type_id(&self) -> &'static str {
"combinator_chain"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::port::{GraphModule, PortDef, PortSpec, PortValues, SignalKind};
struct Gain {
factor: f64,
}
impl Module for Gain {
type In = f64;
type Out = f64;
fn tick(&mut self, input: Self::In) -> Self::Out {
input * self.factor
}
fn reset(&mut self) {}
}
#[test]
fn test_chain() {
let mut chain = Gain { factor: 2.0 }.then(Gain { factor: 3.0 });
assert!((chain.tick(1.0) - 6.0).abs() < 1e-10);
}
#[test]
fn test_parallel() {
let mut par = Gain { factor: 2.0 }.parallel(Gain { factor: 3.0 });
let (a, b) = par.tick((1.0, 1.0));
assert!((a - 2.0).abs() < 1e-10);
assert!((b - 3.0).abs() < 1e-10);
}
#[test]
fn test_fanout() {
let mut fan = Gain { factor: 2.0 }.fanout(Gain { factor: 3.0 });
let (a, b) = fan.tick(1.0);
assert!((a - 2.0).abs() < 1e-10);
assert!((b - 3.0).abs() < 1e-10);
}
#[test]
fn test_map() {
let mut mapped = Gain { factor: 2.0 }.map(|x| x + 1.0);
assert!((mapped.tick(1.0) - 3.0).abs() < 1e-10);
}
#[test]
fn test_identity() {
let mut id = Identity::<f64>::new();
assert!((id.tick(42.0) - 42.0).abs() < 1e-10);
}
#[test]
fn test_constant() {
let mut c = Constant::new(42.0_f64);
assert!((c.tick(()) - 42.0).abs() < 1e-10);
}
#[test]
fn test_split() {
let mut split = Split::<f64>::new();
let (a, b) = split.tick(5.0);
assert!((a - 5.0).abs() < 1e-10);
assert!((b - 5.0).abs() < 1e-10);
}
#[test]
fn test_merge() {
let mut merge = Merge::new(|a: f64, b: f64| a + b);
assert!((merge.tick((2.0, 3.0)) - 5.0).abs() < 1e-10);
}
#[test]
fn test_swap() {
let mut swap = Swap::<i32, f64>::new();
assert_eq!(swap.tick((1, 2.0)), (2.0, 1));
}
struct SampleRateAware {
sample_rate: f64,
count: u32,
}
impl SampleRateAware {
fn new() -> Self {
Self {
sample_rate: 44100.0,
count: 0,
}
}
}
impl Module for SampleRateAware {
type In = f64;
type Out = f64;
fn tick(&mut self, input: Self::In) -> Self::Out {
self.count += 1;
input * self.sample_rate / 44100.0
}
fn reset(&mut self) {
self.count = 0;
}
fn set_sample_rate(&mut self, sample_rate: f64) {
self.sample_rate = sample_rate;
}
}
#[test]
fn test_chain_reset_and_sample_rate() {
let mut chain = SampleRateAware::new().then(SampleRateAware::new());
chain.tick(1.0);
chain.tick(1.0);
chain.reset();
assert_eq!(chain.first.count, 0);
assert_eq!(chain.second.count, 0);
chain.set_sample_rate(48000.0);
assert_eq!(chain.first.sample_rate, 48000.0);
assert_eq!(chain.second.sample_rate, 48000.0);
}
#[test]
fn test_parallel_reset_and_sample_rate() {
let mut par = SampleRateAware::new().parallel(SampleRateAware::new());
par.tick((1.0, 1.0));
par.tick((1.0, 1.0));
par.reset();
par.set_sample_rate(48000.0);
let result = par.tick((1.0, 1.0));
assert!(result.0.abs() < 10.0);
}
#[test]
fn test_fanout_reset_and_sample_rate() {
let mut fan = SampleRateAware::new().fanout(SampleRateAware::new());
fan.tick(1.0);
fan.tick(1.0);
fan.reset();
fan.set_sample_rate(48000.0);
let result = fan.tick(1.0);
assert!(result.0.abs() < 10.0);
}
#[test]
fn test_feedback_reset_and_sample_rate() {
let feedback_fn = |x: f64, prev: f64| x + prev * 0.5;
let mut fb = SampleRateAware::new().feedback(feedback_fn);
for _ in 0..10 {
fb.tick(1.0);
}
fb.reset();
fb.set_sample_rate(48000.0);
}
#[test]
fn test_map_reset_and_sample_rate() {
let mut mapped = SampleRateAware::new().map(|x| x + 1.0);
mapped.tick(1.0);
mapped.tick(1.0);
mapped.reset();
mapped.set_sample_rate(48000.0);
let result = mapped.tick(1.0);
assert!(result.abs() < 10.0);
}
#[test]
fn test_contramap() {
let mut contra = Gain { factor: 2.0 }.contramap(|x: f64| x + 1.0);
assert!((contra.tick(1.0) - 4.0).abs() < 1e-10);
contra.reset();
contra.set_sample_rate(48000.0);
}
#[test]
fn test_contramap_reset_and_sample_rate() {
let mut contra = SampleRateAware::new().contramap(|x: f64| x * 2.0);
contra.tick(1.0);
contra.reset();
contra.set_sample_rate(48000.0);
let result = contra.tick(1.0);
assert!(result.abs() < 10.0);
}
#[test]
fn test_first() {
let mut first = Gain { factor: 2.0 }.first::<i32>();
let (a, b) = first.tick((3.0, 42));
assert!((a - 6.0).abs() < 1e-10);
assert_eq!(b, 42);
}
#[test]
fn test_first_reset_and_sample_rate() {
let mut first = SampleRateAware::new().first::<i32>();
first.tick((1.0, 0));
first.reset();
first.set_sample_rate(48000.0);
let (result, _) = first.tick((1.0, 0));
assert!(result.abs() < 10.0);
}
#[test]
fn test_second() {
let mut second = Gain { factor: 2.0 }.second::<i32>();
let (a, b) = second.tick((42, 3.0));
assert_eq!(a, 42);
assert!((b - 6.0).abs() < 1e-10);
}
#[test]
fn test_second_reset_and_sample_rate() {
let mut second = SampleRateAware::new().second::<i32>();
second.tick((0, 1.0));
second.reset();
second.set_sample_rate(48000.0);
let (_, result) = second.tick((0, 1.0));
assert!(result.abs() < 10.0);
}
#[test]
fn test_identity_reset() {
let mut id = Identity::<f64>::new();
id.reset(); assert!((id.tick(42.0) - 42.0).abs() < 1e-10);
}
#[test]
fn test_identity_default() {
let id: Identity<f64> = Identity::default();
assert!(std::mem::size_of_val(&id) == 0);
}
#[test]
fn test_constant_reset() {
let mut c = Constant::new(42.0_f64);
c.reset(); assert!((c.tick(()) - 42.0).abs() < 1e-10);
}
#[test]
fn test_split_reset() {
let mut split = Split::<f64>::new();
split.reset(); }
#[test]
fn test_split_default() {
let split: Split<f64> = Split::default();
let (a, b) = Split::<f64>::new().tick(1.0);
assert!((a - 1.0).abs() < 1e-10);
assert!((b - 1.0).abs() < 1e-10);
let _ = split;
}
#[test]
fn test_merge_reset() {
let mut merge = Merge::new(|a: f64, b: f64| a + b);
merge.reset(); }
#[test]
fn test_swap_reset() {
let mut swap = Swap::<i32, f64>::new();
swap.reset(); }
#[test]
fn test_swap_default() {
let swap: Swap<i32, f64> = Swap::default();
let _ = swap;
}
#[test]
fn test_process_block() {
let mut gain = Gain { factor: 2.0 };
let input = vec![1.0, 2.0, 3.0, 4.0];
let mut output = vec![0.0; 4];
gain.process(&input, &mut output);
assert_eq!(output, vec![2.0, 4.0, 6.0, 8.0]);
}
struct Accum {
sum: f64,
}
impl Accum {
fn new() -> Self {
Self { sum: 0.0 }
}
}
impl Module for Accum {
type In = f64;
type Out = f64;
fn tick(&mut self, input: Self::In) -> Self::Out {
self.sum += input;
self.sum
}
fn reset(&mut self) {
self.sum = 0.0;
}
}
const SEQ: [f64; 8] = [0.5, -0.3, 1.0, 2.0, -1.5, 0.25, 3.0, -0.7];
fn run_mono<M: Module<In = f64, Out = f64>>(mut m: M) -> Vec<f64> {
SEQ.iter().map(|&x| m.tick(x)).collect()
}
fn run_pair<M: Module<In = (f64, f64), Out = (f64, f64)>>(mut m: M) -> Vec<(f64, f64)> {
SEQ.iter()
.enumerate()
.map(|(i, &x)| m.tick((x, i as f64)))
.collect()
}
fn assert_seq_close(a: &[f64], b: &[f64]) {
assert_eq!(a.len(), b.len());
for (x, y) in a.iter().zip(b) {
assert!((x - y).abs() < 1e-12, "sequence mismatch: {x} != {y}");
}
}
#[test]
fn test_arr_basic() {
let mut m = arr(|x: f64| x * 2.0 + 1.0);
assert!((m.tick(3.0) - 7.0).abs() < 1e-12);
m.reset();
m.set_sample_rate(48_000.0);
assert!((m.tick(0.0) - 1.0).abs() < 1e-12);
}
#[test]
fn arrow_law_identity() {
let reference = run_mono(Accum::new());
let left_id = run_mono(Identity::<f64>::new().then(Accum::new()));
let right_id = run_mono(Accum::new().then(Identity::<f64>::new()));
assert_seq_close(&left_id, &reference);
assert_seq_close(&right_id, &reference);
}
#[test]
fn arrow_law_associativity() {
let lhs = run_mono((Accum::new().then(Gain { factor: 2.0 })).then(Accum::new()));
let rhs = run_mono(Accum::new().then(Gain { factor: 2.0 }.then(Accum::new())));
assert_seq_close(&lhs, &rhs);
}
#[test]
fn arrow_law_first_distributes() {
let lhs = run_pair(Accum::new().then(Gain { factor: 3.0 }).first::<f64>());
let rhs = run_pair(
Accum::new()
.first::<f64>()
.then(Gain { factor: 3.0 }.first::<f64>()),
);
assert_eq!(lhs.len(), rhs.len());
for (a, b) in lhs.iter().zip(&rhs) {
assert!((a.0 - b.0).abs() < 1e-12, "processed element differs");
assert!((a.1 - b.1).abs() < 1e-12, "pass-through element differs");
}
for (i, pair) in lhs.iter().enumerate() {
assert!((pair.1 - i as f64).abs() < 1e-12);
}
}
struct DoublerGm {
spec: PortSpec,
}
impl DoublerGm {
fn new() -> Self {
Self {
spec: PortSpec {
inputs: vec![PortDef::new(0, "in", SignalKind::Audio)],
outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
},
}
}
}
impl GraphModule for DoublerGm {
fn port_spec(&self) -> &PortSpec {
&self.spec
}
fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
outputs.set(10, inputs.get_or(0, 0.0) * 2.0);
}
fn reset(&mut self) {}
fn set_sample_rate(&mut self, _sample_rate: f64) {}
}
#[test]
fn test_graph_module_adapter_drives_selected_ports() {
let mut adapter = GraphModuleAdapter::new(DoublerGm::new(), 0, 10);
assert!((adapter.tick(3.0) - 6.0).abs() < 1e-12);
assert!((adapter.tick(-2.5) - (-5.0)).abs() < 1e-12);
assert_eq!(adapter.inner().port_spec().outputs[0].id, 10);
adapter.reset();
adapter.set_sample_rate(48_000.0);
}
struct GatedEmit {
spec: PortSpec,
}
impl GatedEmit {
fn new() -> Self {
Self {
spec: PortSpec {
inputs: vec![PortDef::new(0, "trig", SignalKind::Audio)],
outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
},
}
}
}
impl GraphModule for GatedEmit {
fn port_spec(&self) -> &PortSpec {
&self.spec
}
fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
if inputs.get_or(0, 0.0) > 0.5 {
outputs.set(10, 1.0);
}
}
fn reset(&mut self) {}
fn set_sample_rate(&mut self, _sample_rate: f64) {}
}
#[test]
fn test_graph_module_adapter_clears_stale_outputs() {
let mut adapter = GraphModuleAdapter::new(GatedEmit::new(), 0, 10);
assert!((adapter.tick(1.0) - 1.0).abs() < 1e-12);
assert!(
adapter.tick(0.0).abs() < 1e-12,
"unwritten output must read as default 0.0, not the stale prior value"
);
assert!((adapter.tick(1.0) - 1.0).abs() < 1e-12);
}
#[test]
fn test_graph_module_adapter_from_audio_ports() {
use crate::modules::{Svf, Vco};
let svf = GraphModuleAdapter::from_audio_ports(Svf::new(44_100.0));
assert!(svf.is_some());
assert!(GraphModuleAdapter::from_audio_ports(Vco::new(44_100.0)).is_none());
}
#[test]
fn test_graph_module_adapter_vco_svf_chain_produces_audio() {
use crate::modules::{Svf, Vco};
let vco = GraphModuleAdapter::new(Vco::new(44_100.0), 0, 12); let svf = GraphModuleAdapter::from_audio_ports(Svf::new(44_100.0)).unwrap();
let mut chain = vco.then(svf);
let mut peak = 0.0_f64;
for _ in 0..512 {
peak = peak.max(chain.tick(0.0).abs()); }
assert!(
peak > 1e-6,
"expected nonzero audio from Vco -> Svf combinator chain, got {peak}"
);
}
#[test]
fn test_module_graph_adapter_direct() {
let mut node = ModuleGraphAdapter::new(arr(|x: f64| x * 3.0));
assert_eq!(node.port_spec().inputs.len(), 1);
assert_eq!(node.port_spec().outputs.len(), 1);
assert_eq!(node.port_spec().inputs[0].id, 0);
assert_eq!(node.port_spec().outputs[0].id, 10);
assert_eq!(node.type_id(), "combinator_chain");
let mut inputs = PortValues::new();
inputs.set(0, 2.0);
let mut outputs = PortValues::new();
node.tick(&inputs, &mut outputs);
assert!((outputs.get_or(10, 0.0) - 6.0).abs() < 1e-12);
node.reset();
node.set_sample_rate(48_000.0);
}
#[test]
fn test_module_graph_adapter_in_patch() {
use crate::graph::Patch;
use crate::modules::{StereoOutput, Vco};
let mut patch = Patch::new(44_100.0);
let chain = arr(|x: f64| x * 0.5).then(arr(|x: f64| x + 0.1));
let node = patch.add("chain", ModuleGraphAdapter::new(chain));
let vco = patch.add("vco", Vco::new(44_100.0));
let out = patch.add("out", StereoOutput::new());
patch.connect(vco.out("saw"), node.in_("in")).unwrap();
patch.connect(node.out("out"), out.in_("left")).unwrap();
patch.set_output(out.id());
patch.compile().unwrap();
let mut peak = 0.0_f64;
for _ in 0..512 {
let (left, _right) = patch.tick();
peak = peak.max(left.abs());
}
assert!(
peak > 1e-6,
"combinator chain wrapped as a GraphModule should tick inside a Patch, got {peak}"
);
}
}