Skip to main content

firewheel_core/dsp/
mix.rs

1use core::num::NonZeroU32;
2use core::ops::Range;
3
4use crate::{
5    diff::{Diff, EventQueue, Patch, PatchError, PathBuilder},
6    dsp::fade::FadeCurve,
7    event::ParamData,
8    param::smoother::{DEFAULT_GAIN_SPAN, SmoothedParam, SmootherConfig},
9};
10
11/// A value representing the mix between two audio signals (e.g. second/first mix)
12///
13/// This is a normalized value in the range `[0.0, 1.0]`, where `0.0` is fully
14/// the first signal, `1.0` is fully the second signal, and `0.5` is an equal
15/// mix of both.
16#[repr(transparent)]
17#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
18#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct Mix(f32);
21
22impl Mix {
23    /// Only use the first (first) signal.
24    pub const FULLY_FIRST: Self = Self(0.0);
25    /// Only use the second (second) signal.
26    pub const FULLY_SECOND: Self = Self(1.0);
27
28    /// Only use the dry (first) signal.
29    pub const FULLY_DRY: Self = Self(0.0);
30    /// Only use the wet (second) signal.
31    pub const FULLY_WET: Self = Self(1.0);
32
33    /// An equal mix of both signals
34    pub const CENTER: Self = Self(0.5);
35
36    /// Construct a value representing the mix between two audio signals
37    /// (e.g. wet/drt mix)
38    ///
39    /// `mix` is a normalized value in the range `[0.0, 1.0]`, where `0.0` is fully
40    /// the first (dry) signal, `1.0` is fully the second (wet) signal, and `0.5` is
41    /// an equal mix of both.
42    pub const fn new(mix: f32) -> Self {
43        Self(mix.clamp(0.0, 1.0))
44    }
45
46    /// Construct a value representing the mix between two audio signals
47    /// (e.g. wet/dry mix)
48    ///
49    /// `percent` is a value in the range `[0.0, 100.0]`, where `0.0` is fully the
50    /// first (dry) signal, `100.0` is fully the second (wet) signal, and `50.0` is an
51    /// equal mix of both.
52    pub const fn from_percent(percent: f32) -> Self {
53        Self::new(percent / 100.0)
54    }
55
56    pub const fn get(&self) -> f32 {
57        self.0
58    }
59
60    pub const fn to_percent(self) -> f32 {
61        self.0 * 100.0
62    }
63
64    /// Compute the raw gain values for both inputs.
65    pub fn compute_gains(&self, fade_curve: FadeCurve) -> (f32, f32) {
66        fade_curve.compute_gains_0_to_1(self.0)
67    }
68}
69
70impl From<f32> for Mix {
71    fn from(value: f32) -> Self {
72        Self::new(value)
73    }
74}
75
76impl From<f64> for Mix {
77    fn from(value: f64) -> Self {
78        Self::new(value as f32)
79    }
80}
81
82impl From<Mix> for f32 {
83    fn from(value: Mix) -> Self {
84        value.get()
85    }
86}
87
88impl From<Mix> for f64 {
89    fn from(value: Mix) -> Self {
90        value.get() as f64
91    }
92}
93
94impl Diff for Mix {
95    fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
96        if self != baseline {
97            event_queue.push_param(ParamData::F32(self.0), path);
98        }
99    }
100}
101
102impl Patch for Mix {
103    type Patch = Self;
104
105    fn patch(data: &ParamData, _: &[u32]) -> Result<Self::Patch, PatchError> {
106        match data {
107            ParamData::F32(v) => Ok(Self::new(*v)),
108            _ => Err(PatchError::InvalidData),
109        }
110    }
111
112    fn apply(&mut self, value: Self::Patch) {
113        *self = value;
114    }
115}
116
117/// A DSP helper struct that efficiently mixes two signals together.
118#[derive(Debug, Clone, Copy, PartialEq)]
119pub struct MixDSP {
120    gain_0: SmoothedParam,
121    gain_1: SmoothedParam,
122}
123
124impl MixDSP {
125    pub fn new(
126        mix: Mix,
127        fade_curve: FadeCurve,
128        config: SmootherConfig,
129        sample_rate: NonZeroU32,
130    ) -> Self {
131        let (gain_0, gain_1) = mix.compute_gains(fade_curve);
132
133        Self {
134            gain_0: SmoothedParam::new(gain_0, DEFAULT_GAIN_SPAN, config, sample_rate),
135            gain_1: SmoothedParam::new(gain_1, DEFAULT_GAIN_SPAN, config, sample_rate),
136        }
137    }
138
139    pub fn set_mix(&mut self, mix: Mix, fade_curve: FadeCurve) {
140        let (gain_0, gain_1) = mix.compute_gains(fade_curve);
141
142        self.gain_0.set_value(gain_0);
143        self.gain_1.set_value(gain_1);
144    }
145
146    /// Reset the internal smoothing filter to the current target value.
147    pub fn reset_to_target(&mut self) {
148        self.gain_0.reset_to_target();
149        self.gain_1.reset_to_target();
150    }
151
152    pub fn update_sample_rate(&mut self, sample_rate: NonZeroU32) {
153        self.gain_0.update_sample_rate(sample_rate);
154        self.gain_1.update_sample_rate(sample_rate);
155    }
156
157    pub fn is_smoothing(&self) -> bool {
158        self.gain_0.is_smoothing() || self.gain_1.is_smoothing()
159    }
160
161    pub fn has_settled(&self) -> bool {
162        self.gain_0.has_settled() && self.gain_1.has_settled()
163    }
164
165    pub fn mix_dry_into_wet_mono(&mut self, dry: &[f32], wet: &mut [f32], frames: usize) {
166        self.mix_first_into_second_mono(dry, wet, frames);
167    }
168
169    pub fn first_gain_target(&self) -> f32 {
170        self.gain_0.target_value()
171    }
172
173    pub fn second_gain_target(&self) -> f32 {
174        self.gain_1.target_value()
175    }
176
177    pub fn mix_dry_into_wet_stereo(
178        &mut self,
179        dry_l: &[f32],
180        dry_r: &[f32],
181        wet_l: &mut [f32],
182        wet_r: &mut [f32],
183        frames: usize,
184    ) {
185        self.mix_first_into_second_stereo(dry_l, dry_r, wet_l, wet_r, frames);
186    }
187
188    pub fn mix_dry_into_wet<VF: AsRef<[f32]>, VS: AsMut<[f32]>>(
189        &mut self,
190        dry: &[VF],
191        wet: &mut [VS],
192        dry_range: Range<usize>,
193        wet_range: Range<usize>,
194        scratch_buffer_0: &mut [f32],
195        scratch_buffer_1: &mut [f32],
196    ) {
197        self.mix_first_into_second(
198            dry,
199            wet,
200            dry_range,
201            wet_range,
202            scratch_buffer_0,
203            scratch_buffer_1,
204        );
205    }
206
207    pub fn mix_first_into_second_mono(&mut self, first: &[f32], second: &mut [f32], frames: usize) {
208        let first = &first[..frames];
209        let second = &mut second[..frames];
210
211        if self.is_smoothing() {
212            for (first_s, second_s) in first.iter().zip(second.iter_mut()) {
213                let gain_first = self.gain_0.next_smoothed();
214                let gain_second = self.gain_1.next_smoothed();
215
216                *second_s = first_s * gain_first + *second_s * gain_second;
217            }
218
219            self.gain_0.settle();
220            self.gain_1.settle();
221        } else if self.gain_1.target_value() <= 0.00001 && self.gain_0.target_value() >= 0.99999 {
222            // Simply copy first signal to output.
223            second.copy_from_slice(first);
224        } else if self.gain_0.target_value() <= 0.00001 && self.gain_1.target_value() >= 0.99999 {
225            // Signal is already fully second
226        } else {
227            for (first_s, second_s) in first.iter().zip(second.iter_mut()) {
228                *second_s =
229                    first_s * self.gain_0.target_value() + *second_s * self.gain_1.target_value();
230            }
231        }
232    }
233
234    pub fn mix_first_into_second_stereo(
235        &mut self,
236        first_l: &[f32],
237        first_r: &[f32],
238        second_l: &mut [f32],
239        second_r: &mut [f32],
240        frames: usize,
241    ) {
242        let first_l = &first_l[..frames];
243        let first_r = &first_r[..frames];
244        let second_l = &mut second_l[..frames];
245        let second_r = &mut second_r[..frames];
246
247        if self.is_smoothing() {
248            for i in 0..frames {
249                let gain_0 = self.gain_0.next_smoothed();
250                let gain_1 = self.gain_1.next_smoothed();
251
252                second_l[i] = first_l[i] * gain_0 + second_l[i] * gain_1;
253                second_r[i] = first_r[i] * gain_0 + second_r[i] * gain_1;
254            }
255
256            self.gain_0.settle();
257            self.gain_1.settle();
258        } else if self.gain_1.target_value() <= 0.00001 && self.gain_0.target_value() >= 0.99999 {
259            // Simply copy first signal to output.
260            second_l.copy_from_slice(first_l);
261            second_r.copy_from_slice(first_r);
262        } else if self.gain_0.target_value() <= 0.00001 && self.gain_1.target_value() >= 0.99999 {
263            // Signal is already fully second
264        } else {
265            for i in 0..frames {
266                second_l[i] = first_l[i] * self.gain_0.target_value()
267                    + second_l[i] * self.gain_1.target_value();
268                second_r[i] = first_r[i] * self.gain_0.target_value()
269                    + second_r[i] * self.gain_1.target_value();
270            }
271        }
272    }
273
274    pub fn mix_first_into_second<VF: AsRef<[f32]>, VS: AsMut<[f32]>>(
275        &mut self,
276        first: &[VF],
277        second: &mut [VS],
278        first_range: Range<usize>,
279        second_range: Range<usize>,
280        scratch_buffer_0: &mut [f32],
281        scratch_buffer_1: &mut [f32],
282    ) {
283        let frames =
284            (first_range.end - first_range.start).min(second_range.end - second_range.start);
285
286        if second.len() == 1 {
287            self.mix_first_into_second_mono(
288                &first[0].as_ref()[first_range.clone()],
289                &mut second[0].as_mut()[second_range.clone()],
290                frames,
291            );
292        } else if second.len() == 2 {
293            let (second_l, second_r) = second.split_first_mut().unwrap();
294            self.mix_first_into_second_stereo(
295                &first[0].as_ref()[first_range.clone()],
296                &first[1].as_ref()[first_range.clone()],
297                &mut second_l.as_mut()[second_range.clone()],
298                &mut second_r[0].as_mut()[second_range.clone()],
299                frames,
300            );
301        } else if self.is_smoothing() {
302            self.gain_0
303                .process_into_buffer(&mut scratch_buffer_0[..frames]);
304            self.gain_1
305                .process_into_buffer(&mut scratch_buffer_1[..frames]);
306
307            for (first_ch, second_ch) in first[..second.len()].iter().zip(second.iter_mut()) {
308                for (((&first_s, &g0), &g1), second_s) in first_ch.as_ref()
309                    [first_range.start..first_range.start + frames]
310                    .iter()
311                    .zip(scratch_buffer_0[..frames].iter())
312                    .zip(scratch_buffer_1[..frames].iter())
313                    .zip(
314                        second_ch.as_mut()[second_range.start..second_range.start + frames]
315                            .iter_mut(),
316                    )
317                {
318                    *second_s = first_s * g0 + *second_s * g1;
319                }
320            }
321
322            self.gain_0.settle();
323            self.gain_1.settle();
324        } else if self.gain_1.target_value() <= 0.00001 && self.gain_0.target_value() >= 0.99999 {
325            // Simply copy input 0 to output.
326            for (first_ch, second_ch) in first[..second.len()].iter().zip(second.iter_mut()) {
327                second_ch.as_mut()[second_range.start..second_range.start + frames]
328                    .copy_from_slice(
329                        &first_ch.as_ref()[first_range.start..first_range.start + frames],
330                    );
331            }
332        } else if self.gain_0.target_value() <= 0.00001 && self.gain_1.target_value() >= 0.99999 {
333            // Signal is already fully second
334        } else {
335            for (first_ch, second_ch) in first[..second.len()].iter().zip(second.iter_mut()) {
336                for (&first_s, second_s) in first_ch.as_ref()
337                    [first_range.start..first_range.start + frames]
338                    .iter()
339                    .zip(
340                        second_ch.as_mut()[second_range.start..second_range.start + frames]
341                            .iter_mut(),
342                    )
343                {
344                    *second_s = first_s * self.gain_0.target_value()
345                        + *second_s * self.gain_1.target_value();
346                }
347            }
348        }
349    }
350}