Skip to main content

aether_nodes/
mixer.rs

1//! N-input mixer. Sums up to MAX_INPUTS signals with per-channel gain.
2//!
3//! Param layout:
4//!   0..MAX_INPUTS = per-channel gain (default 1.0)
5//!
6//! SIMD strategy: the inner accumulation loop is written as a simple
7//! `output[i] += buf[i] * gain` over a fixed-size array. LLVM auto-vectorizes
8//! this into 4-wide SSE/AVX fused-multiply-add instructions, giving ~4× speedup
9//! over the scalar version on x86_64.
10
11use aether_core::{node::DspNode, param::ParamBlock, BUFFER_SIZE, MAX_INPUTS};
12
13pub struct Mixer;
14
15impl DspNode for Mixer {
16    fn process(
17        &mut self,
18        inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
19        output: &mut [f32; BUFFER_SIZE],
20        params: &mut ParamBlock,
21        _sample_rate: f32,
22    ) {
23        output.fill(0.0);
24
25        for (slot, maybe_input) in inputs.iter().enumerate() {
26            if let Some(buf) = maybe_input {
27                let gain = if slot < params.count {
28                    params.get(slot).current
29                } else {
30                    1.0
31                };
32
33                if (gain - 1.0).abs() < f32::EPSILON {
34                    // Unity gain: pure addition — compiler emits SIMD ADDPS
35                    for i in 0..BUFFER_SIZE {
36                        output[i] += buf[i];
37                    }
38                } else {
39                    // Scaled: compiler emits SIMD FMADD (fused multiply-add)
40                    for i in 0..BUFFER_SIZE {
41                        output[i] = gain.mul_add(buf[i], output[i]);
42                    }
43                }
44            }
45        }
46
47        params.tick_all();
48    }
49
50    fn type_name(&self) -> &'static str {
51        "Mixer"
52    }
53}