Skip to main content

rust_audio_api/nodes/
mixer.rs

1use crate::types::AudioUnit;
2
3/// A node that combines multiple audio signals into a single output.
4///
5/// It applies a gain factor to the mixed signal and performs hard clipping
6/// to ensure the output remains within the [-1.0, 1.0] range.
7///
8/// Supports dynamic gain updates via [`ControlMessage::SetParameter`](crate::graph::ControlMessage::SetParameter).
9///
10/// # Example
11/// ```no_run
12/// use rust_audio_api::nodes::{MixerNode, NodeType};
13/// use rust_audio_api::{AudioContext, NodeParameter};
14///
15/// let mut ctx = AudioContext::new().unwrap();
16///
17/// let mut mixer_id = None;
18/// let dest_id = ctx.build_graph(|builder| {
19///     let mixer = builder.add_node(NodeType::Mixer(MixerNode::with_gain(1.0)));
20///     mixer_id = Some(mixer);
21///     mixer
22/// });
23///
24/// // Dynamically reduce the master mix volume
25/// ctx.control_sender().send(
26///     rust_audio_api::graph::ControlMessage::SetParameter(
27///         mixer_id.unwrap(),
28///         NodeParameter::Gain(0.5)
29///     )
30/// ).unwrap();
31/// ```
32pub struct MixerNode {
33    gain: f32,
34    pub clipping: bool,
35}
36
37impl Default for MixerNode {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl MixerNode {
44    /// Creates a new `MixerNode` with unity gain (1.0) and clipping enabled.
45    pub fn new() -> Self {
46        Self {
47            gain: 1.0,
48            clipping: true,
49        }
50    }
51
52    /// Creates a new `MixerNode` with the specified gain factor and clipping enabled by default.
53    pub fn with_gain(gain: f32) -> Self {
54        Self {
55            gain,
56            clipping: true,
57        }
58    }
59
60    /// Sets the gain factor for the mixed output.
61    pub fn set_gain(&mut self, gain: f32) {
62        self.gain = gain;
63    }
64
65    /// MixerNode is a passive node that receives the aggregated `input` (the mixed result) from the graph,
66    /// then applies Gain and optionally performing Clipping/Limiting to ensure the final output doesn't distort.
67    #[inline(always)]
68    pub fn process(&mut self, input: Option<&AudioUnit>, output: &mut AudioUnit) {
69        if let Some(in_unit) = input {
70            output.copy_from_slice(in_unit);
71
72            if self.clipping {
73                // Apply gain and hard clipping limit to [-1.0, 1.0] to prevent distortion
74                dasp::slice::map_in_place(&mut output[..], |frame| {
75                    [
76                        (frame[0] * self.gain).clamp(-1.0, 1.0),
77                        (frame[1] * self.gain).clamp(-1.0, 1.0),
78                    ]
79                });
80            } else {
81                // Apply gain only
82                dasp::slice::map_in_place(&mut output[..], |frame| {
83                    [frame[0] * self.gain, frame[1] * self.gain]
84                });
85            }
86        } else {
87            // If no upstream input, output silence
88            dasp::slice::equilibrium(&mut output[..]);
89        }
90    }
91}