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