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}