firewheel_core/dsp/
mix.rs

1use core::num::NonZeroU32;
2
3use crate::{
4    diff::{Diff, EventQueue, Patch, PatchError, PathBuilder},
5    dsp::fade::FadeCurve,
6    event::ParamData,
7    param::smoother::{SmoothedParam, SmootherConfig},
8};
9
10/// A value representing the mix between two audio signals (e.g. second/first mix)
11///
12/// This is a normalized value in the range `[0.0, 1.0]`, where `0.0` is fully
13/// the first signal, `1.0` is fully the second signal, and `0.5` is an equal
14/// mix of both.
15#[repr(transparent)]
16#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
17#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct Mix(f32);
20
21impl Mix {
22    /// Only use the first (first) signal.
23    pub const FULLY_FIRST: Self = Self(0.0);
24    /// Only use the second (second) signal.
25    pub const FULLY_SECOND: Self = Self(1.0);
26
27    /// Only use the dry (first) signal.
28    pub const FULLY_DRY: Self = Self(0.0);
29    /// Only use the wet (second) signal.
30    pub const FULLY_WET: Self = Self(1.0);
31
32    /// An equal mix of both signals
33    pub const CENTER: Self = Self(0.5);
34
35    /// Construct a value representing the mix between two audio signals
36    /// (e.g. wet/drt mix)
37    ///
38    /// `mix` is a normalized value in the range `[0.0, 1.0]`, where `0.0` is fully
39    /// the first (dry) signal, `1.0` is fully the second (wet) signal, and `0.5` is
40    /// an equal mix of both.
41    pub const fn new(mix: f32) -> Self {
42        Self(mix.clamp(0.0, 1.0))
43    }
44
45    /// Construct a value representing the mix between two audio signals
46    /// (e.g. wet/dry mix)
47    ///
48    /// `percent` is a value in the range `[0.0, 100.0]`, where `0.0` is fully the
49    /// first (dry) signal, `100.0` is fully the second (wet) signal, and `50.0` is an
50    /// equal mix of both.
51    pub const fn from_percent(percent: f32) -> Self {
52        Self::new(percent / 100.0)
53    }
54
55    pub const fn get(&self) -> f32 {
56        self.0
57    }
58
59    pub const fn to_percent(self) -> f32 {
60        self.0 * 100.0
61    }
62
63    /// Compute the raw gain values for both inputs.
64    pub fn compute_gains(&self, fade_curve: FadeCurve) -> (f32, f32) {
65        fade_curve.compute_gains_0_to_1(self.0)
66    }
67}
68
69impl From<f32> for Mix {
70    fn from(value: f32) -> Self {
71        Self::new(value)
72    }
73}
74
75impl From<f64> for Mix {
76    fn from(value: f64) -> Self {
77        Self::new(value as f32)
78    }
79}
80
81impl From<Mix> for f32 {
82    fn from(value: Mix) -> Self {
83        value.get()
84    }
85}
86
87impl From<Mix> for f64 {
88    fn from(value: Mix) -> Self {
89        value.get() as f64
90    }
91}
92
93impl Diff for Mix {
94    fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
95        if self != baseline {
96            event_queue.push_param(ParamData::F32(self.0), path);
97        }
98    }
99}
100
101impl Patch for Mix {
102    type Patch = Self;
103
104    fn patch(data: &ParamData, _: &[u32]) -> Result<Self::Patch, PatchError> {
105        match data {
106            ParamData::F32(v) => Ok(Self::new(*v)),
107            _ => Err(PatchError::InvalidData),
108        }
109    }
110
111    fn apply(&mut self, value: Self::Patch) {
112        *self = value;
113    }
114}
115
116/// A DSP helper struct that efficiently mixes two signals together.
117#[derive(Debug, Clone, Copy, PartialEq)]
118pub struct MixDSP {
119    gain_0: SmoothedParam,
120    gain_1: SmoothedParam,
121}
122
123impl MixDSP {
124    pub fn new(
125        mix: Mix,
126        fade_curve: FadeCurve,
127        config: SmootherConfig,
128        sample_rate: NonZeroU32,
129    ) -> Self {
130        let (gain_0, gain_1) = mix.compute_gains(fade_curve);
131
132        Self {
133            gain_0: SmoothedParam::new(gain_0, config, sample_rate),
134            gain_1: SmoothedParam::new(gain_1, config, sample_rate),
135        }
136    }
137
138    pub fn set_mix(&mut self, mix: Mix, fade_curve: FadeCurve) {
139        let (gain_0, gain_1) = mix.compute_gains(fade_curve);
140
141        self.gain_0.set_value(gain_0);
142        self.gain_1.set_value(gain_1);
143    }
144
145    /// Reset the internal smoothing filter to the current target value.
146    pub fn reset_to_target(&mut self) {
147        self.gain_0.reset_to_target();
148        self.gain_1.reset_to_target();
149    }
150
151    pub fn update_sample_rate(&mut self, sample_rate: NonZeroU32) {
152        self.gain_0.update_sample_rate(sample_rate);
153        self.gain_1.update_sample_rate(sample_rate);
154    }
155
156    pub fn is_smoothing(&self) -> bool {
157        self.gain_0.is_smoothing() || self.gain_1.is_smoothing()
158    }
159
160    pub fn has_settled(&self) -> bool {
161        self.gain_0.has_settled() && self.gain_1.has_settled()
162    }
163
164    pub fn mix_dry_into_wet_mono(&mut self, dry: &[f32], wet: &mut [f32], frames: usize) {
165        self.mix_first_into_second_mono(dry, wet, frames);
166    }
167
168    pub fn mix_dry_into_wet_stereo(
169        &mut self,
170        dry_l: &[f32],
171        dry_r: &[f32],
172        wet_l: &mut [f32],
173        wet_r: &mut [f32],
174        frames: usize,
175    ) {
176        self.mix_first_into_second_stereo(dry_l, dry_r, wet_l, wet_r, frames);
177    }
178
179    pub fn mix_dry_into_wet<VF: AsRef<[f32]>, VS: AsMut<[f32]>>(
180        &mut self,
181        frames: usize,
182        dry: &[VF],
183        wet: &mut [VS],
184        scratch_buffer_0: &mut [f32],
185        scratch_buffer_1: &mut [f32],
186    ) {
187        self.mix_first_into_second(frames, dry, wet, scratch_buffer_0, scratch_buffer_1);
188    }
189
190    pub fn mix_first_into_second_mono(&mut self, first: &[f32], second: &mut [f32], frames: usize) {
191        let first = &first[..frames];
192        let second = &mut second[..frames];
193
194        if self.is_smoothing() {
195            for (first_s, second_s) in first.iter().zip(second.iter_mut()) {
196                let gain_first = self.gain_0.next_smoothed();
197                let gain_second = self.gain_1.next_smoothed();
198
199                *second_s = first_s * gain_first + *second_s * gain_second;
200            }
201
202            self.gain_0.settle();
203            self.gain_1.settle();
204        } else {
205            if self.gain_1.target_value() <= 0.00001 && self.gain_0.target_value() >= 0.99999 {
206                // Simply copy first signal to output.
207                second.copy_from_slice(first);
208            } else if self.gain_0.target_value() <= 0.00001 && self.gain_1.target_value() >= 0.99999
209            {
210                // Signal is already fully second
211                return;
212            } else {
213                for (first_s, second_s) in first.iter().zip(second.iter_mut()) {
214                    *second_s = first_s * self.gain_0.target_value()
215                        + *second_s * self.gain_1.target_value();
216                }
217            }
218        }
219    }
220
221    pub fn mix_first_into_second_stereo(
222        &mut self,
223        first_l: &[f32],
224        first_r: &[f32],
225        second_l: &mut [f32],
226        second_r: &mut [f32],
227        frames: usize,
228    ) {
229        let first_l = &first_l[..frames];
230        let first_r = &first_r[..frames];
231        let second_l = &mut second_l[..frames];
232        let second_r = &mut second_r[..frames];
233
234        if self.is_smoothing() {
235            for i in 0..frames {
236                let gain_0 = self.gain_0.next_smoothed();
237                let gain_1 = self.gain_1.next_smoothed();
238
239                second_l[i] = first_l[i] * gain_0 + second_l[i] * gain_1;
240                second_r[i] = first_r[i] * gain_0 + second_r[i] * gain_1;
241            }
242
243            self.gain_0.settle();
244            self.gain_1.settle();
245        } else {
246            if self.gain_1.target_value() <= 0.00001 && self.gain_0.target_value() >= 0.99999 {
247                // Simply copy first signal to output.
248                second_l.copy_from_slice(first_l);
249                second_r.copy_from_slice(first_r);
250            } else if self.gain_0.target_value() <= 0.00001 && self.gain_1.target_value() >= 0.99999
251            {
252                // Signal is already fully second
253                return;
254            } else {
255                for i in 0..frames {
256                    second_l[i] = first_l[i] * self.gain_0.target_value()
257                        + second_l[i] * self.gain_1.target_value();
258                    second_r[i] = first_r[i] * self.gain_0.target_value()
259                        + second_r[i] * self.gain_1.target_value();
260                }
261            }
262        }
263    }
264
265    pub fn mix_first_into_second<VF: AsRef<[f32]>, VS: AsMut<[f32]>>(
266        &mut self,
267        frames: usize,
268        first: &[VF],
269        second: &mut [VS],
270        scratch_buffer_0: &mut [f32],
271        scratch_buffer_1: &mut [f32],
272    ) {
273        if second.len() == 1 {
274            self.mix_first_into_second_mono(first[0].as_ref(), second[0].as_mut(), frames);
275        } else if second.len() == 2 {
276            let (second_l, second_r) = second.split_first_mut().unwrap();
277            self.mix_first_into_second_stereo(
278                first[0].as_ref(),
279                first[1].as_ref(),
280                second_l.as_mut(),
281                second_r[0].as_mut(),
282                frames,
283            );
284        }
285
286        if self.is_smoothing() {
287            self.gain_0
288                .process_into_buffer(&mut scratch_buffer_0[..frames]);
289            self.gain_1
290                .process_into_buffer(&mut scratch_buffer_1[..frames]);
291
292            for (first_ch, second_ch) in first[..second.len()].iter().zip(second.iter_mut()) {
293                for (((&first_s, &g0), &g1), second_s) in first_ch.as_ref()[..frames]
294                    .iter()
295                    .zip(scratch_buffer_0[..frames].iter())
296                    .zip(scratch_buffer_1[..frames].iter())
297                    .zip(second_ch.as_mut()[..frames].iter_mut())
298                {
299                    *second_s = first_s * g0 + *second_s * g1;
300                }
301            }
302
303            self.gain_0.settle();
304            self.gain_1.settle();
305        } else {
306            if self.gain_1.target_value() <= 0.00001 && self.gain_0.target_value() >= 0.99999 {
307                // Simply copy input 0 to output.
308                for (first_ch, second_ch) in first[..second.len()].iter().zip(second.iter_mut()) {
309                    second_ch.as_mut()[..frames].copy_from_slice(&first_ch.as_ref()[..frames]);
310                }
311            } else if self.gain_0.target_value() <= 0.00001 && self.gain_1.target_value() >= 0.99999
312            {
313                // Signal is already fully second
314                return;
315            } else {
316                for (first_ch, second_ch) in first[..second.len()].iter().zip(second.iter_mut()) {
317                    for (&first_s, second_s) in first_ch.as_ref()[..frames]
318                        .iter()
319                        .zip(second_ch.as_mut()[..frames].iter_mut())
320                    {
321                        *second_s = first_s * self.gain_0.target_value()
322                            + *second_s * self.gain_1.target_value();
323                    }
324                }
325            }
326        }
327    }
328
329    /*
330    pub fn process_mono(&mut self, in_0: &[f32], in_1: &[f32], out: &mut [f32], frames: usize) {
331        let in_0 = &in_0[..frames];
332        let in_1 = &in_1[..frames];
333        let out = &mut out[..frames];
334
335        if self.is_smoothing() {
336            for ((&s0, &s1), out_s) in in_0.iter().zip(in_1.iter()).zip(out.iter_mut()) {
337                let gain_0 = self.gain_0.next_smoothed();
338                let gain_1 = self.gain_1.next_smoothed();
339
340                *out_s = s0 * gain_0 + s1 * gain_1;
341            }
342
343            self.gain_0.settle();
344            self.gain_1.settle();
345        } else {
346            if self.gain_1.target_value() <= 0.00001 && self.gain_0.target_value() >= 0.99999 {
347                // Simply copy first signal to output.
348                out.copy_from_slice(in_0);
349            } else if self.gain_0.target_value() <= 0.00001 && self.gain_1.target_value() >= 0.99999
350            {
351                // Simply copy second signal to output.
352                out.copy_from_slice(in_1);
353            } else {
354                for ((&s0, &s1), out_s) in in_0.iter().zip(in_1.iter()).zip(out.iter_mut()) {
355                    *out_s = s0 * self.gain_0.target_value() + s1 * self.gain_1.target_value();
356                }
357            }
358        }
359    }
360
361    pub fn process_stereo(
362        &mut self,
363        in_0_l: &[f32],
364        in_0_r: &[f32],
365        in_1_l: &[f32],
366        in_1_r: &[f32],
367        out_l: &mut [f32],
368        out_r: &mut [f32],
369        frames: usize,
370    ) {
371        let in_0_l = &in_0_l[..frames];
372        let in_0_r = &in_0_r[..frames];
373        let in_1_l = &in_1_l[..frames];
374        let in_1_r = &in_1_r[..frames];
375        let out_l = &mut out_l[..frames];
376        let out_r = &mut out_r[..frames];
377
378        if self.is_smoothing() {
379            for i in 0..frames {
380                let gain_0 = self.gain_0.next_smoothed();
381                let gain_1 = self.gain_1.next_smoothed();
382
383                out_l[i] = in_0_l[i] * gain_0 + in_1_l[i] * gain_1;
384                out_r[i] = in_0_r[i] * gain_0 + in_1_r[i] * gain_1;
385            }
386
387            self.gain_0.settle();
388            self.gain_1.settle();
389        } else {
390            if self.gain_1.target_value() <= 0.00001 && self.gain_0.target_value() >= 0.99999 {
391                // Simply copy first signal to output.
392                out_l.copy_from_slice(in_0_l);
393                out_r.copy_from_slice(in_0_r);
394            } else if self.gain_0.target_value() <= 0.00001 && self.gain_1.target_value() >= 0.99999
395            {
396                // Simply copy second signal to output.
397                out_l.copy_from_slice(in_1_l);
398                out_r.copy_from_slice(in_1_r);
399            } else {
400                for i in 0..frames {
401                    out_l[i] = in_0_l[i] * self.gain_0.target_value()
402                        + in_1_l[i] * self.gain_1.target_value();
403                    out_r[i] = in_0_r[i] * self.gain_0.target_value()
404                        + in_1_r[i] * self.gain_1.target_value();
405                }
406            }
407        }
408    }
409
410    pub fn process<
411        VW: AsRef<[f32]>,
412        VD: AsRef<[f32]>,
413        VO: AsMut<[f32]>,
414        const NUM_SCRATCH_BUFFERS: usize,
415    >(
416        &mut self,
417        frames: usize,
418        scratch_buffer_0: &mut [f32],
419        scratch_buffer_1: &mut [f32],
420        in_0: &[VW],
421        in_1: &[VD],
422        out: &mut [VO],
423    ) {
424        if out.len() == 1 {
425            self.process_mono(in_0[0].as_ref(), in_1[0].as_ref(), out[0].as_mut(), frames);
426        } else if out.len() == 2 {
427            let (out_l, out_r) = out.split_first_mut().unwrap();
428            self.process_stereo(
429                in_0[0].as_ref(),
430                in_0[1].as_ref(),
431                in_1[0].as_ref(),
432                in_1[1].as_ref(),
433                out_l.as_mut(),
434                out_r[0].as_mut(),
435                frames,
436            );
437        }
438
439        if self.is_smoothing() {
440            self.gain_0
441                .process_into_buffer(&mut scratch_buffer_0[..frames]);
442            self.gain_1
443                .process_into_buffer(&mut scratch_buffer_1[..frames]);
444
445            for ((in_0_ch, in_1_ch), out_ch) in in_0[..out.len()]
446                .iter()
447                .zip(in_1[..out.len()].iter())
448                .zip(out.iter_mut())
449            {
450                for ((((&s0, &s1), &g0), &g1), out_s) in in_0_ch.as_ref()[..frames]
451                    .iter()
452                    .zip(in_1_ch.as_ref()[..frames].iter())
453                    .zip(scratch_buffer_0[..frames].iter())
454                    .zip(scratch_buffer_1[..frames].iter())
455                    .zip(out_ch.as_mut()[..frames].iter_mut())
456                {
457                    *out_s = s0 * g0 + s1 * g1;
458                }
459            }
460
461            self.gain_0.settle();
462            self.gain_1.settle();
463        } else {
464            if self.gain_1.target_value() <= 0.00001 && self.gain_0.target_value() >= 0.99999 {
465                // Simply copy input 0 to output.
466                for (in_ch, out_ch) in in_0[..out.len()].iter().zip(out.iter_mut()) {
467                    out_ch.as_mut()[..frames].copy_from_slice(&in_ch.as_ref()[..frames]);
468                }
469            } else if self.gain_0.target_value() <= 0.00001 && self.gain_1.target_value() >= 0.99999
470            {
471                // Simply copy input 1 to output.
472                for (in_ch, out_ch) in in_1[..out.len()].iter().zip(out.iter_mut()) {
473                    out_ch.as_mut()[..frames].copy_from_slice(&in_ch.as_ref()[..frames]);
474                }
475            } else {
476                for ((in_0_ch, in_1_ch), out_ch) in in_0[..out.len()]
477                    .iter()
478                    .zip(in_1[..out.len()].iter())
479                    .zip(out.iter_mut())
480                {
481                    for ((&s0, &s1), out_s) in in_0_ch.as_ref()[..frames]
482                        .iter()
483                        .zip(in_1_ch.as_ref()[..frames].iter())
484                        .zip(out_ch.as_mut()[..frames].iter_mut())
485                    {
486                        *out_s = s0 * self.gain_0.target_value() + s1 * self.gain_1.target_value();
487                    }
488                }
489            }
490        }
491    }
492    */
493}