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}