1use firewheel_core::node::NodeError;
2use firewheel_core::param::smoother::DEFAULT_GAIN_SPAN;
3use firewheel_core::{
4 channel_config::{ChannelConfig, ChannelCount, NonZeroChannelCount},
5 diff::{Diff, Patch},
6 dsp::{
7 fade::FadeCurve,
8 filter::smoothing_filter::DEFAULT_SMOOTH_SECONDS,
9 mix::Mix,
10 volume::{DEFAULT_MIN_AMP, Volume},
11 },
12 event::ProcEvents,
13 mask::{MaskType, SilenceMask},
14 node::{
15 AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcBuffers,
16 ProcExtra, ProcInfo, ProcStreamCtx, ProcessStatus,
17 },
18 param::smoother::{SmoothedParam, SmootherConfig},
19};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
24#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct MixNodeConfig {
27 pub channels: NonZeroChannelCount,
34}
35
36impl Default for MixNodeConfig {
37 fn default() -> Self {
38 Self {
39 channels: NonZeroChannelCount::STEREO,
40 }
41 }
42}
43
44#[derive(Diff, Patch, Debug, Clone, Copy, PartialEq)]
49#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
50#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52pub struct MixNode {
53 pub volume: Volume,
57
58 pub mix: Mix,
66
67 pub fade_curve: FadeCurve,
72
73 pub smooth_seconds: f32,
79 pub min_gain: f32,
85}
86
87impl MixNode {
88 pub const fn from_volume_mix(volume: Volume, mix: Mix) -> Self {
89 Self {
90 volume,
91 mix,
92 fade_curve: FadeCurve::EqualPower3dB,
93 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
94 min_gain: DEFAULT_MIN_AMP,
95 }
96 }
97
98 pub const fn from_mix(mix: Mix) -> Self {
99 Self {
100 volume: Volume::UNITY_GAIN,
101 mix,
102 fade_curve: FadeCurve::EqualPower3dB,
103 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
104 min_gain: DEFAULT_MIN_AMP,
105 }
106 }
107
108 pub const fn set_volume_linear(&mut self, linear: f32) {
114 self.volume = Volume::Linear(linear);
115 }
116
117 pub const fn set_volume_percent(&mut self, percent: f32) {
122 self.volume = Volume::from_percent(percent);
123 }
124
125 pub const fn set_volume_decibels(&mut self, decibels: f32) {
128 self.volume = Volume::Decibels(decibels);
129 }
130
131 pub fn compute_gains(&self, min_amp: f32) -> (f32, f32) {
132 let global_gain = self.volume.amp_clamped(min_amp);
133
134 let (mut gain_0, mut gain_1) = self.mix.compute_gains(self.fade_curve);
135
136 gain_0 *= global_gain;
137 gain_1 *= global_gain;
138
139 if gain_0 > 0.99999 && gain_0 < 1.00001 {
140 gain_0 = 1.0;
141 }
142 if gain_1 > 0.99999 && gain_1 < 1.00001 {
143 gain_1 = 1.0;
144 }
145
146 (gain_0, gain_1)
147 }
148}
149
150impl Default for MixNode {
151 fn default() -> Self {
152 Self {
153 volume: Volume::default(),
154 mix: Mix::FULLY_FIRST,
155 fade_curve: FadeCurve::default(),
156 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
157 min_gain: DEFAULT_MIN_AMP,
158 }
159 }
160}
161
162impl AudioNode for MixNode {
163 type Configuration = MixNodeConfig;
164
165 fn info(&self, config: &Self::Configuration) -> Result<AudioNodeInfo, NodeError> {
166 let num_channels = config.channels.get().get();
167
168 Ok(AudioNodeInfo::new()
169 .debug_name("mix")
170 .channel_config(ChannelConfig {
171 num_inputs: ChannelCount::new(num_channels * 2).unwrap_or_else(|| {
172 panic!(
173 "MixNodeConfig::channels cannot be greater than 32, got {}",
174 num_channels
175 )
176 }),
177 num_outputs: config.channels.get(),
178 }))
179 }
180
181 fn construct_processor(
182 &self,
183 _config: &Self::Configuration,
184 cx: ConstructProcessorContext,
185 ) -> Result<impl AudioNodeProcessor, NodeError> {
186 let min_gain = self.min_gain.max(0.0);
187
188 let (gain_0, gain_1) = self.compute_gains(self.min_gain);
189
190 Ok(Processor {
191 gain_0: SmoothedParam::new(
192 gain_0,
193 DEFAULT_GAIN_SPAN,
194 SmootherConfig {
195 smooth_seconds: self.smooth_seconds,
196 ..Default::default()
197 },
198 cx.stream_info.sample_rate,
199 ),
200 gain_1: SmoothedParam::new(
201 gain_1,
202 DEFAULT_GAIN_SPAN,
203 SmootherConfig {
204 smooth_seconds: self.smooth_seconds,
205 ..Default::default()
206 },
207 cx.stream_info.sample_rate,
208 ),
209 params: *self,
210 min_gain,
211 prev_input_settled: true,
212 })
213 }
214}
215
216struct Processor {
217 gain_0: SmoothedParam,
218 gain_1: SmoothedParam,
219
220 params: MixNode,
221
222 min_gain: f32,
223 prev_input_settled: bool,
224}
225
226impl AudioNodeProcessor for Processor {
227 fn events(&mut self, info: &ProcInfo, events: &mut ProcEvents, _extra: &mut ProcExtra) {
228 let mut updated = false;
229 for mut patch in events.drain_patches::<MixNode>() {
230 match &mut patch {
231 MixNodePatch::Mix(m) => {
232 if m.get() <= 0.00001 {
233 *m = Mix::new(0.0);
234 } else if m.get() >= 0.99999 {
235 *m = Mix::new(1.0);
236 }
237 }
238 MixNodePatch::SmoothSeconds(seconds) => {
239 self.gain_0.set_smooth_seconds(*seconds, info.sample_rate);
240 self.gain_1.set_smooth_seconds(*seconds, info.sample_rate);
241 }
242 MixNodePatch::MinGain(min_gain) => {
243 self.min_gain = (*min_gain).max(0.0);
244 }
245 _ => {}
246 }
247
248 self.params.apply(patch);
249 updated = true;
250 }
251
252 if updated {
253 let (gain_0, gain_1) = self.params.compute_gains(self.min_gain);
254 self.gain_0.set_value(gain_0);
255 self.gain_1.set_value(gain_1);
256
257 if self.prev_input_settled {
258 self.gain_0.reset_to_target();
260 self.gain_1.reset_to_target();
261 }
262 }
263 }
264
265 fn bypassed(&mut self, _bypassed: bool) {
266 self.gain_0.reset_to_target();
267 self.gain_1.reset_to_target();
268 }
269
270 fn process(
271 &mut self,
272 info: &ProcInfo,
273 buffers: ProcBuffers,
274 extra: &mut ProcExtra,
275 ) -> ProcessStatus {
276 let channels = buffers.outputs.len();
277
278 let gain_0_silent = self.gain_0.has_settled_at_or_below(self.min_gain);
279 let gain_1_silent = self.gain_1.has_settled_at_or_below(self.min_gain);
280 let has_settled = self.gain_0.has_settled() && self.gain_1.has_settled();
281
282 if (gain_0_silent && gain_1_silent)
283 || info
284 .in_silence_mask
285 .all_channels_silent(buffers.inputs.len())
286 {
287 self.gain_0.reset_to_target();
288 self.gain_1.reset_to_target();
289 self.prev_input_settled = true;
290
291 return ProcessStatus::ClearAllOutputs;
292 }
293
294 self.prev_input_settled = buffers.inputs_settled_at_zero();
295
296 let mut out_silence_mask = SilenceMask::NONE_SILENT;
297
298 if has_settled {
299 if self.params.mix.get() == 0.0 && self.gain_0.target_value() == 1.0 {
300 for (ch_i, (in_ch, out_ch)) in buffers.inputs[..channels]
302 .iter()
303 .zip(buffers.outputs.iter_mut())
304 .enumerate()
305 {
306 if info.in_silence_mask.is_channel_silent(ch_i) {
307 out_silence_mask.set_channel(ch_i, true);
308
309 if !info.out_silence_mask.is_channel_silent(ch_i) {
310 out_ch.fill(0.0);
311 }
312 } else {
313 out_ch.copy_from_slice(in_ch);
314 }
315 }
316
317 return ProcessStatus::OutputsModifiedWithMask(MaskType::Silence(out_silence_mask));
318 } else if self.params.mix.get() == 1.0 && self.gain_1.target_value() == 1.0 {
319 for (ch_i, (in_ch, out_ch)) in buffers.inputs[channels..]
321 .iter()
322 .zip(buffers.outputs.iter_mut())
323 .enumerate()
324 {
325 if info.in_silence_mask.is_channel_silent(channels + ch_i) {
326 out_silence_mask.set_channel(ch_i, true);
327
328 if !info.out_silence_mask.is_channel_silent(ch_i) {
329 out_ch.fill(0.0);
330 }
331 } else {
332 out_ch.copy_from_slice(in_ch);
333 }
334 }
335
336 return ProcessStatus::OutputsModifiedWithMask(MaskType::Silence(out_silence_mask));
337 }
338 }
339
340 match channels {
341 1 => {
342 if has_settled {
345 for ((&in0_s, &in1_s), out_s) in buffers.inputs[0]
346 .iter()
347 .zip(buffers.inputs[1].iter())
348 .zip(buffers.outputs[0].iter_mut())
349 {
350 *out_s = (in0_s * self.gain_0.target_value())
351 + (in1_s * self.gain_1.target_value());
352 }
353 } else {
354 for ((&in0_s, &in1_s), out_s) in buffers.inputs[0]
355 .iter()
356 .zip(buffers.inputs[1].iter())
357 .zip(buffers.outputs[0].iter_mut())
358 {
359 let gain_0 = self.gain_0.next_smoothed();
360 let gain_1 = self.gain_1.next_smoothed();
361
362 *out_s = (in0_s * gain_0) + (in1_s * gain_1);
363 }
364
365 self.gain_0.settle();
366 self.gain_1.settle();
367 }
368 }
369 2 => {
370 let in0_l = &buffers.inputs[0][..info.frames];
373 let in0_r = &buffers.inputs[1][..info.frames];
374 let in1_l = &buffers.inputs[2][..info.frames];
375 let in1_r = &buffers.inputs[3][..info.frames];
376
377 let (out_l, out_r) = buffers.outputs.split_first_mut().unwrap();
378 let out_l = &mut out_l[..info.frames];
379 let out_r = &mut out_r[0][..info.frames];
380
381 if has_settled {
382 for i in 0..info.frames {
383 out_l[i] = (in0_l[i] * self.gain_0.target_value())
384 + (in1_l[i] * self.gain_1.target_value());
385 out_r[i] = (in0_r[i] * self.gain_0.target_value())
386 + (in1_r[i] * self.gain_1.target_value());
387 }
388 } else {
389 for i in 0..info.frames {
390 let gain_0 = self.gain_0.next_smoothed();
391 let gain_1 = self.gain_1.next_smoothed();
392
393 out_l[i] = (in0_l[i] * gain_0) + (in1_l[i] * gain_1);
394 out_r[i] = (in0_r[i] * gain_0) + (in1_r[i] * gain_1);
395 }
396
397 self.gain_0.settle();
398 self.gain_1.settle();
399 }
400 }
401 _ => {
402 if has_settled {
403 for (ch_i, ((in0_ch, in1_ch), out_ch)) in buffers.inputs[0..channels]
404 .iter()
405 .zip(buffers.inputs[channels..].iter())
406 .zip(buffers.outputs.iter_mut())
407 .enumerate()
408 {
409 let in0_ch_silent = info.in_silence_mask.is_channel_silent(ch_i);
410 let in1_ch_silent = info.in_silence_mask.is_channel_silent(channels + ch_i);
411
412 #[allow(clippy::nonminimal_bool)]
415 let channel_silent = (in0_ch_silent && in1_ch_silent)
416 || (gain_0_silent && in1_ch_silent)
417 || (gain_1_silent && in0_ch_silent);
418
419 if channel_silent {
420 out_silence_mask.set_channel(ch_i, true);
421
422 if !info.out_silence_mask.is_channel_silent(ch_i) {
423 out_ch.fill(0.0);
424 }
425 } else {
426 for ((&in0_s, &in1_s), out_s) in
427 in0_ch.iter().zip(in1_ch.iter()).zip(out_ch.iter_mut())
428 {
429 *out_s = (in0_s * self.gain_0.target_value())
430 + (in1_s * self.gain_1.target_value());
431 }
432 }
433 }
434 } else {
435 let [gain_0_buf, gain_1_buf] = extra.scratch_buffers.channels_mut::<2>();
436 self.gain_0
437 .process_into_buffer(&mut gain_0_buf[..info.frames]);
438 self.gain_1
439 .process_into_buffer(&mut gain_1_buf[..info.frames]);
440
441 for (ch_i, ((in0_ch, in1_ch), out_ch)) in buffers.inputs[0..channels]
442 .iter()
443 .zip(buffers.inputs[channels..].iter())
444 .zip(buffers.outputs.iter_mut())
445 .enumerate()
446 {
447 let in0_ch_silent = info.in_silence_mask.is_channel_silent(ch_i);
448 let in1_ch_silent = info.in_silence_mask.is_channel_silent(channels + ch_i);
449
450 #[allow(clippy::nonminimal_bool)]
453 let channel_silent = (in0_ch_silent && in1_ch_silent)
454 || (gain_0_silent && in1_ch_silent)
455 || (gain_1_silent && in0_ch_silent);
456
457 if channel_silent {
458 out_silence_mask.set_channel(ch_i, true);
459
460 if !info.out_silence_mask.is_channel_silent(ch_i) {
461 out_ch.fill(0.0);
462 }
463 } else {
464 for ((((&in0_s, &in1_s), &gain0_s), &gain1_s), out_s) in in0_ch
465 .iter()
466 .zip(in1_ch.iter())
467 .zip(gain_0_buf.iter())
468 .zip(gain_1_buf.iter())
469 .zip(out_ch.iter_mut())
470 {
471 *out_s = (in0_s * gain0_s) + (in1_s * gain1_s);
472 }
473 }
474 }
475
476 self.gain_0.settle();
477 self.gain_1.settle();
478 }
479 }
480 }
481
482 ProcessStatus::OutputsModifiedWithMask(MaskType::Silence(out_silence_mask))
483 }
484
485 fn new_stream(
486 &mut self,
487 stream_info: &firewheel_core::StreamInfo,
488 _context: &mut ProcStreamCtx,
489 ) {
490 self.gain_0.update_sample_rate(stream_info.sample_rate);
491 self.gain_1.update_sample_rate(stream_info.sample_rate);
492 }
493}