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}