bbx_dsp/blocks/effectors/
vca.rs

1//! Voltage Controlled Amplifier (VCA) block.
2//!
3//! Multiplies an audio signal by a control signal, typically from an envelope.
4
5use crate::{block::Block, context::DspContext, parameter::ModulationOutput, sample::Sample};
6
7/// A voltage controlled amplifier that multiplies audio by a control signal.
8///
9/// The VCA takes two inputs:
10/// - Input 0: Audio signal
11/// - Input 1: Control signal (typically 0.0 to 1.0 from an envelope)
12///
13/// The output is the sample-by-sample product of both inputs.
14pub struct VcaBlock<S: Sample> {
15    _phantom: std::marker::PhantomData<S>,
16}
17
18impl<S: Sample> VcaBlock<S> {
19    /// Create a new VCA block.
20    pub fn new() -> Self {
21        Self {
22            _phantom: std::marker::PhantomData,
23        }
24    }
25}
26
27impl<S: Sample> Default for VcaBlock<S> {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl<S: Sample> Block<S> for VcaBlock<S> {
34    #[inline]
35    fn process(&mut self, inputs: &[&[S]], outputs: &mut [&mut [S]], _modulation_values: &[S], _context: &DspContext) {
36        let output = match outputs.first_mut() {
37            Some(out) => out,
38            None => return,
39        };
40
41        let audio_input = inputs.first().copied().unwrap_or(&[]);
42        let control_input = inputs.get(1).copied().unwrap_or(&[]);
43
44        for (i, out_sample) in output.iter_mut().enumerate() {
45            let audio = audio_input.get(i).copied().unwrap_or(S::ZERO);
46            let control = control_input.get(i).copied().unwrap_or(S::ONE);
47            *out_sample = audio * control;
48        }
49    }
50
51    #[inline]
52    fn input_count(&self) -> usize {
53        2
54    }
55
56    #[inline]
57    fn output_count(&self) -> usize {
58        1
59    }
60
61    #[inline]
62    fn modulation_outputs(&self) -> &[ModulationOutput] {
63        &[]
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::{channel::ChannelLayout, context::DspContext};
71
72    fn test_context(buffer_size: usize) -> DspContext {
73        DspContext {
74            sample_rate: 44100.0,
75            num_channels: 1,
76            buffer_size,
77            current_sample: 0,
78            channel_layout: ChannelLayout::default(),
79        }
80    }
81
82    #[test]
83    fn test_vca_multiplication() {
84        let mut vca = VcaBlock::<f32>::new();
85        let context = test_context(4);
86
87        let audio = [1.0f32, 0.5, -0.5, -1.0];
88        let control = [1.0f32, 0.5, 0.5, 0.0];
89        let mut output = [0.0f32; 4];
90
91        let inputs: [&[f32]; 2] = [&audio, &control];
92        let mut outputs: [&mut [f32]; 1] = [&mut output];
93
94        vca.process(&inputs, &mut outputs, &[], &context);
95
96        assert!((output[0] - 1.0).abs() < 1e-6);
97        assert!((output[1] - 0.25).abs() < 1e-6);
98        assert!((output[2] - -0.25).abs() < 1e-6);
99        assert!((output[3] - 0.0).abs() < 1e-6);
100    }
101
102    #[test]
103    fn test_vca_missing_control_defaults_to_unity() {
104        let mut vca = VcaBlock::<f32>::new();
105        let context = test_context(4);
106
107        let audio = [0.5f32, 0.5, 0.5, 0.5];
108        let mut output = [0.0f32; 4];
109
110        let inputs: [&[f32]; 1] = [&audio];
111        let mut outputs: [&mut [f32]; 1] = [&mut output];
112
113        vca.process(&inputs, &mut outputs, &[], &context);
114
115        for sample in output.iter() {
116            assert!((sample - 0.5).abs() < 1e-6);
117        }
118    }
119
120    #[test]
121    fn test_vca_input_output_counts_f32() {
122        let vca = VcaBlock::<f32>::new();
123        assert_eq!(vca.input_count(), 2);
124        assert_eq!(vca.output_count(), 1);
125    }
126
127    #[test]
128    fn test_vca_input_output_counts_f64() {
129        let vca = VcaBlock::<f64>::new();
130        assert_eq!(vca.input_count(), 2);
131        assert_eq!(vca.output_count(), 1);
132    }
133
134    #[test]
135    fn test_vca_multiplication_f64() {
136        let mut vca = VcaBlock::<f64>::new();
137        let context = test_context(4);
138
139        let audio = [1.0f64, 0.5, -0.5, -1.0];
140        let control = [1.0f64, 0.5, 0.5, 0.0];
141        let mut output = [0.0f64; 4];
142
143        let inputs: [&[f64]; 2] = [&audio, &control];
144        let mut outputs: [&mut [f64]; 1] = [&mut output];
145
146        vca.process(&inputs, &mut outputs, &[], &context);
147
148        assert!((output[0] - 1.0).abs() < 1e-12);
149        assert!((output[1] - 0.25).abs() < 1e-12);
150        assert!((output[2] - -0.25).abs() < 1e-12);
151        assert!((output[3] - 0.0).abs() < 1e-12);
152    }
153
154    #[test]
155    fn test_vca_default() {
156        let vca = VcaBlock::<f32>::default();
157        assert_eq!(vca.input_count(), 2);
158        assert_eq!(vca.output_count(), 1);
159    }
160
161    #[test]
162    fn test_vca_modulation_outputs_empty() {
163        let vca = VcaBlock::<f32>::new();
164        assert!(vca.modulation_outputs().is_empty());
165    }
166}