bbx_dsp/blocks/effectors/
mixer.rs1use std::marker::PhantomData;
4
5use crate::{
6 block::Block, channel::ChannelConfig, context::DspContext, graph::MAX_BLOCK_INPUTS, parameter::ModulationOutput,
7 sample::Sample,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum NormalizationStrategy {
13 Average,
15 #[default]
17 ConstantPower,
18}
19
20pub struct MixerBlock<S: Sample> {
36 num_sources: usize,
37 num_channels: usize,
38 normalization: NormalizationStrategy,
39 _phantom: PhantomData<S>,
40}
41
42impl<S: Sample> MixerBlock<S> {
43 pub fn new(num_sources: usize, num_channels: usize) -> Self {
52 assert!(num_sources > 0, "Must have at least one source");
53 assert!(num_channels > 0, "Must have at least one channel");
54 assert!(
55 num_sources * num_channels <= MAX_BLOCK_INPUTS,
56 "Total inputs {} exceeds MAX_BLOCK_INPUTS {}",
57 num_sources * num_channels,
58 MAX_BLOCK_INPUTS
59 );
60 Self {
61 num_sources,
62 num_channels,
63 normalization: NormalizationStrategy::ConstantPower,
64 _phantom: PhantomData,
65 }
66 }
67
68 pub fn stereo(num_sources: usize) -> Self {
70 Self::new(num_sources, 2)
71 }
72
73 pub fn with_normalization(mut self, normalization: NormalizationStrategy) -> Self {
75 self.normalization = normalization;
76 self
77 }
78
79 pub fn num_sources(&self) -> usize {
81 self.num_sources
82 }
83
84 pub fn num_channels(&self) -> usize {
86 self.num_channels
87 }
88}
89
90impl<S: Sample> Block<S> for MixerBlock<S> {
91 fn process(&mut self, inputs: &[&[S]], outputs: &mut [&mut [S]], _modulation_values: &[S], _context: &DspContext) {
92 let num_channels = self.num_channels.min(outputs.len());
93 if num_channels == 0 || inputs.is_empty() {
94 return;
95 }
96
97 let num_samples = outputs[0].len();
98 let normalization_factor = match self.normalization {
99 NormalizationStrategy::Average => S::from_f64(1.0 / self.num_sources as f64),
100 NormalizationStrategy::ConstantPower => S::from_f64(1.0 / (self.num_sources as f64).sqrt()),
101 };
102
103 for (ch, output) in outputs.iter_mut().enumerate().take(num_channels) {
104 for sample in output.iter_mut().take(num_samples) {
105 *sample = S::ZERO;
106 }
107
108 for source_idx in 0..self.num_sources {
109 let input_idx = source_idx * self.num_channels + ch;
110 if let Some(input) = inputs.get(input_idx) {
111 let len = num_samples.min(input.len());
112 for i in 0..len {
113 output[i] += input[i];
114 }
115 }
116 }
117
118 for sample in output.iter_mut().take(num_samples) {
119 *sample *= normalization_factor;
120 }
121 }
122 }
123
124 #[inline]
125 fn input_count(&self) -> usize {
126 self.num_sources * self.num_channels
127 }
128
129 #[inline]
130 fn output_count(&self) -> usize {
131 self.num_channels
132 }
133
134 #[inline]
135 fn modulation_outputs(&self) -> &[ModulationOutput] {
136 &[]
137 }
138
139 #[inline]
140 fn channel_config(&self) -> ChannelConfig {
141 ChannelConfig::Explicit
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::channel::ChannelLayout;
149
150 fn test_context() -> DspContext {
151 DspContext {
152 sample_rate: 44100.0,
153 num_channels: 2,
154 buffer_size: 4,
155 current_sample: 0,
156 channel_layout: ChannelLayout::Stereo,
157 }
158 }
159
160 #[test]
161 fn test_mixer_two_stereo_sources() {
162 let mut mixer = MixerBlock::<f32>::stereo(2);
163 let context = test_context();
164
165 let src0_l = [1.0f32; 4];
168 let src0_r = [2.0f32; 4];
169 let src1_l = [3.0f32; 4];
170 let src1_r = [4.0f32; 4];
171 let mut out_l = [0.0f32; 4];
172 let mut out_r = [0.0f32; 4];
173
174 let inputs: [&[f32]; 4] = [&src0_l, &src0_r, &src1_l, &src1_r];
175 let mut outputs: [&mut [f32]; 2] = [&mut out_l, &mut out_r];
176
177 mixer.process(&inputs, &mut outputs, &[], &context);
178
179 let sqrt2 = 2.0_f32.sqrt();
181 for &sample in &out_l {
182 assert!((sample - 4.0 / sqrt2).abs() < 1e-6);
183 }
184 for &sample in &out_r {
185 assert!((sample - 6.0 / sqrt2).abs() < 1e-6);
186 }
187 }
188
189 #[test]
190 fn test_mixer_average_normalization() {
191 let mut mixer = MixerBlock::<f32>::stereo(2).with_normalization(NormalizationStrategy::Average);
192 let context = test_context();
193
194 let src0_l = [2.0f32; 4];
195 let src0_r = [4.0f32; 4];
196 let src1_l = [2.0f32; 4];
197 let src1_r = [4.0f32; 4];
198 let mut out_l = [0.0f32; 4];
199 let mut out_r = [0.0f32; 4];
200
201 let inputs: [&[f32]; 4] = [&src0_l, &src0_r, &src1_l, &src1_r];
202 let mut outputs: [&mut [f32]; 2] = [&mut out_l, &mut out_r];
203
204 mixer.process(&inputs, &mut outputs, &[], &context);
205
206 for &sample in &out_l {
208 assert!((sample - 2.0).abs() < 1e-6);
209 }
210 for &sample in &out_r {
211 assert!((sample - 4.0).abs() < 1e-6);
212 }
213 }
214
215 #[test]
216 fn test_mixer_constant_power_normalization() {
217 let mut mixer = MixerBlock::<f32>::stereo(4).with_normalization(NormalizationStrategy::ConstantPower);
218 let context = test_context();
219
220 let inputs: Vec<[f32; 4]> = vec![[1.0f32; 4]; 8]; let input_refs: Vec<&[f32]> = inputs.iter().map(|a| a.as_slice()).collect();
223 let mut out_l = [0.0f32; 4];
224 let mut out_r = [0.0f32; 4];
225 let mut outputs: [&mut [f32]; 2] = [&mut out_l, &mut out_r];
226
227 mixer.process(&input_refs, &mut outputs, &[], &context);
228
229 for &sample in &out_l {
231 assert!((sample - 2.0).abs() < 1e-6);
232 }
233 }
234
235 #[test]
236 fn test_mixer_mono_three_sources() {
237 let mut mixer = MixerBlock::<f32>::new(3, 1);
238 let context = test_context();
239
240 let src0 = [1.0f32; 4];
241 let src1 = [2.0f32; 4];
242 let src2 = [3.0f32; 4];
243 let mut output = [0.0f32; 4];
244
245 let inputs: [&[f32]; 3] = [&src0, &src1, &src2];
246 let mut outputs: [&mut [f32]; 1] = [&mut output];
247
248 mixer.process(&inputs, &mut outputs, &[], &context);
249
250 let expected = 6.0 / 3.0_f32.sqrt();
252 for &sample in &output {
253 assert!((sample - expected).abs() < 1e-6);
254 }
255 }
256
257 #[test]
258 fn test_mixer_input_output_counts() {
259 let mixer = MixerBlock::<f32>::new(3, 2);
260 assert_eq!(mixer.input_count(), 6); assert_eq!(mixer.output_count(), 2);
262 assert_eq!(mixer.channel_config(), ChannelConfig::Explicit);
263 }
264
265 #[test]
266 fn test_mixer_f64() {
267 let mut mixer = MixerBlock::<f64>::stereo(2);
268 let context = test_context();
269
270 let src0_l = [0.5f64; 4];
271 let src0_r = [0.25f64; 4];
272 let src1_l = [0.5f64; 4];
273 let src1_r = [0.25f64; 4];
274 let mut out_l = [0.0f64; 4];
275 let mut out_r = [0.0f64; 4];
276
277 let inputs: [&[f64]; 4] = [&src0_l, &src0_r, &src1_l, &src1_r];
278 let mut outputs: [&mut [f64]; 2] = [&mut out_l, &mut out_r];
279
280 mixer.process(&inputs, &mut outputs, &[], &context);
281
282 let sqrt2 = 2.0_f64.sqrt();
284 for &sample in &out_l {
285 assert!((sample - 1.0 / sqrt2).abs() < 1e-12);
286 }
287 for &sample in &out_r {
288 assert!((sample - 0.5 / sqrt2).abs() < 1e-12);
289 }
290 }
291
292 #[test]
293 #[should_panic(expected = "Must have at least one source")]
294 fn test_mixer_zero_sources_panics() {
295 let _ = MixerBlock::<f32>::new(0, 2);
296 }
297
298 #[test]
299 #[should_panic(expected = "Must have at least one channel")]
300 fn test_mixer_zero_channels_panics() {
301 let _ = MixerBlock::<f32>::new(2, 0);
302 }
303
304 #[test]
305 #[should_panic(expected = "exceeds MAX_BLOCK_INPUTS")]
306 fn test_mixer_exceeds_max_inputs_panics() {
307 let _ = MixerBlock::<f32>::new(9, 2); }
309}