bbx_dsp/blocks/effectors/
matrix_mixer.rs1use crate::{
4 block::Block,
5 channel::ChannelConfig,
6 context::DspContext,
7 graph::{MAX_BLOCK_INPUTS, MAX_BLOCK_OUTPUTS},
8 parameter::ModulationOutput,
9 sample::Sample,
10};
11
12pub struct MatrixMixerBlock<S: Sample> {
22 num_inputs: usize,
23 num_outputs: usize,
24 gains: [[S; MAX_BLOCK_INPUTS]; MAX_BLOCK_OUTPUTS],
25}
26
27impl<S: Sample> MatrixMixerBlock<S> {
28 pub fn new(inputs: usize, outputs: usize) -> Self {
35 assert!(inputs > 0 && inputs <= MAX_BLOCK_INPUTS);
36 assert!(outputs > 0 && outputs <= MAX_BLOCK_OUTPUTS);
37 Self {
38 num_inputs: inputs,
39 num_outputs: outputs,
40 gains: [[S::ZERO; MAX_BLOCK_INPUTS]; MAX_BLOCK_OUTPUTS],
41 }
42 }
43
44 pub fn identity(channels: usize) -> Self {
52 let mut mixer = Self::new(channels, channels);
53 for ch in 0..channels {
54 mixer.gains[ch][ch] = S::ONE;
55 }
56 mixer
57 }
58
59 pub fn set_gain(&mut self, input: usize, output: usize, gain: S) {
69 assert!(input < self.num_inputs);
70 assert!(output < self.num_outputs);
71 self.gains[output][input] = gain;
72 }
73
74 pub fn get_gain(&self, input: usize, output: usize) -> S {
79 assert!(input < self.num_inputs);
80 assert!(output < self.num_outputs);
81 self.gains[output][input]
82 }
83
84 pub fn num_inputs(&self) -> usize {
86 self.num_inputs
87 }
88
89 pub fn num_outputs(&self) -> usize {
91 self.num_outputs
92 }
93}
94
95impl<S: Sample> Block<S> for MatrixMixerBlock<S> {
96 fn process(&mut self, inputs: &[&[S]], outputs: &mut [&mut [S]], _modulation_values: &[S], _context: &DspContext) {
97 let num_inputs = self.num_inputs.min(inputs.len());
98 let num_outputs = self.num_outputs.min(outputs.len());
99
100 if num_inputs == 0 || num_outputs == 0 {
101 return;
102 }
103
104 let num_samples = inputs[0].len().min(outputs[0].len());
105
106 for (out_ch, output) in outputs.iter_mut().enumerate().take(num_outputs) {
107 for i in 0..num_samples {
108 let mut sum = S::ZERO;
109 for (in_ch, input) in inputs.iter().enumerate().take(num_inputs) {
110 sum += input[i] * self.gains[out_ch][in_ch];
111 }
112 output[i] = sum;
113 }
114 }
115 }
116
117 #[inline]
118 fn input_count(&self) -> usize {
119 self.num_inputs
120 }
121
122 #[inline]
123 fn output_count(&self) -> usize {
124 self.num_outputs
125 }
126
127 #[inline]
128 fn modulation_outputs(&self) -> &[ModulationOutput] {
129 &[]
130 }
131
132 #[inline]
133 fn channel_config(&self) -> ChannelConfig {
134 ChannelConfig::Explicit
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::channel::ChannelLayout;
142
143 fn test_context() -> DspContext {
144 DspContext {
145 sample_rate: 44100.0,
146 num_channels: 2,
147 buffer_size: 4,
148 current_sample: 0,
149 channel_layout: ChannelLayout::Stereo,
150 }
151 }
152
153 #[test]
154 fn test_matrix_mixer_identity() {
155 let mut mixer = MatrixMixerBlock::<f32>::identity(2);
156 let context = test_context();
157
158 let left_in = [1.0f32, 2.0, 3.0, 4.0];
159 let right_in = [5.0f32, 6.0, 7.0, 8.0];
160 let mut left_out = [0.0f32; 4];
161 let mut right_out = [0.0f32; 4];
162
163 let inputs: [&[f32]; 2] = [&left_in, &right_in];
164 let mut outputs: [&mut [f32]; 2] = [&mut left_out, &mut right_out];
165
166 mixer.process(&inputs, &mut outputs, &[], &context);
167
168 assert_eq!(left_out, left_in);
169 assert_eq!(right_out, right_in);
170 }
171
172 #[test]
173 fn test_matrix_mixer_mono_sum() {
174 let mut mixer = MatrixMixerBlock::<f32>::new(2, 1);
175 mixer.set_gain(0, 0, 0.5);
176 mixer.set_gain(1, 0, 0.5);
177 let context = test_context();
178
179 let left_in = [1.0f32, 2.0, 3.0, 4.0];
180 let right_in = [1.0f32, 2.0, 3.0, 4.0];
181 let mut mono_out = [0.0f32; 4];
182
183 let inputs: [&[f32]; 2] = [&left_in, &right_in];
184 let mut outputs: [&mut [f32]; 1] = [&mut mono_out];
185
186 mixer.process(&inputs, &mut outputs, &[], &context);
187
188 let expected = [1.0, 2.0, 3.0, 4.0];
189 for (actual, exp) in mono_out.iter().zip(expected.iter()) {
190 assert!((actual - exp).abs() < 1e-6, "Mono sum mismatch: {} vs {}", actual, exp);
191 }
192 }
193
194 #[test]
195 fn test_matrix_mixer_swap_channels() {
196 let mut mixer = MatrixMixerBlock::<f32>::new(2, 2);
197 mixer.set_gain(0, 1, 1.0); mixer.set_gain(1, 0, 1.0); let context = test_context();
200
201 let left_in = [1.0f32, 2.0, 3.0, 4.0];
202 let right_in = [5.0f32, 6.0, 7.0, 8.0];
203 let mut left_out = [0.0f32; 4];
204 let mut right_out = [0.0f32; 4];
205
206 let inputs: [&[f32]; 2] = [&left_in, &right_in];
207 let mut outputs: [&mut [f32]; 2] = [&mut left_out, &mut right_out];
208
209 mixer.process(&inputs, &mut outputs, &[], &context);
210
211 assert_eq!(left_out, right_in);
212 assert_eq!(right_out, left_in);
213 }
214
215 #[test]
216 fn test_matrix_mixer_counts() {
217 let mixer = MatrixMixerBlock::<f32>::new(4, 2);
218 assert_eq!(mixer.input_count(), 4);
219 assert_eq!(mixer.output_count(), 2);
220 assert_eq!(mixer.channel_config(), ChannelConfig::Explicit);
221 }
222
223 #[test]
224 #[should_panic]
225 fn test_matrix_mixer_zero_inputs_panics() {
226 let _ = MatrixMixerBlock::<f32>::new(0, 2);
227 }
228
229 #[test]
230 #[should_panic]
231 fn test_matrix_mixer_zero_outputs_panics() {
232 let _ = MatrixMixerBlock::<f32>::new(2, 0);
233 }
234}