firefly_audio/
processors.rs

1use crate::*;
2use micromath::F32Ext;
3
4/// Do nothing: only mix the children and pass the mix forward with no changes.
5pub struct Mix {}
6
7impl Mix {
8    #[must_use]
9    pub const fn new() -> Self {
10        Self {}
11    }
12}
13
14impl Processor for Mix {}
15
16/// Mix the inputs, stop everything if at least one input is stopped.
17pub struct AllForOne {}
18
19impl AllForOne {
20    #[must_use]
21    pub const fn new() -> Self {
22        Self {}
23    }
24}
25
26impl Processor for AllForOne {
27    fn process_children(&mut self, cn: &mut Nodes) -> Option<Frame> {
28        let mut sum = Frame::zero();
29        if cn.is_empty() {
30            return None;
31        }
32        for node in cn.iter_mut() {
33            sum = sum + &node.next_frame()?;
34        }
35        let f = sum / cn.len() as f32;
36        self.process_frame(f)
37    }
38}
39
40/// Set the gain level for every sample.
41pub struct Gain {
42    lvl: f32,
43}
44
45impl Gain {
46    #[must_use]
47    pub const fn new(lvl: f32) -> Self {
48        Self { lvl }
49    }
50}
51
52impl Processor for Gain {
53    fn set(&mut self, param: u8, val: f32) {
54        if param == 0 {
55            self.lvl = val;
56        }
57    }
58
59    fn process_sample(&mut self, s: Sample) -> Option<Sample> {
60        Some(s * self.lvl)
61    }
62}
63
64/// When the source ends, start it from the beginning.
65///
66/// If any of the sources doesn't restart on reset,
67/// the node stops.
68pub struct Loop {}
69
70impl Loop {
71    #[must_use]
72    pub const fn new() -> Self {
73        Self {}
74    }
75}
76
77impl Processor for Loop {
78    fn process_children(&mut self, cn: &mut Nodes) -> Option<Frame> {
79        let mut sum = Frame::zero();
80        for node in cn.iter_mut() {
81            let f = if let Some(f) = node.next_frame() {
82                f
83            } else {
84                node.reset();
85                node.next_frame()?
86            };
87            sum = sum + &f;
88        }
89        Some(sum / cn.len() as f32)
90    }
91}
92
93/// Play children in order, one at a time.
94pub struct Concat {}
95
96impl Concat {
97    // TODO: support fade-in/fade-out
98    #[must_use]
99    pub const fn new() -> Self {
100        Self {}
101    }
102}
103
104impl Processor for Concat {
105    fn process_children(&mut self, cn: &mut Nodes) -> Option<Frame> {
106        for node in cn {
107            if let Some(f) = node.next_frame() {
108                return Some(f);
109            }
110        }
111        None
112    }
113}
114
115pub struct Pan {
116    left_weight: f32,
117    right_weight: f32,
118}
119
120impl Pan {
121    #[must_use]
122    pub fn new(v: f32) -> Self {
123        let (left_weight, right_weight) = pan_weights(v);
124        Self {
125            left_weight,
126            right_weight,
127        }
128    }
129}
130
131#[inline]
132fn pan_weights(v: f32) -> (f32, f32) {
133    let v = v.clamp(-1., 1.);
134    let angle = (v + 1.) * core::f32::consts::FRAC_PI_4;
135    let (sin, cos) = F32Ext::sin_cos(angle);
136    (cos, sin)
137}
138
139impl Processor for Pan {
140    fn set(&mut self, param: u8, val: f32) {
141        if param == 0 {
142            (self.left_weight, self.right_weight) = pan_weights(val);
143        }
144    }
145
146    fn process_frame(&mut self, f: Frame) -> Option<Frame> {
147        let left = f.left * self.left_weight;
148        let right = f.right.map(|s| s * self.right_weight);
149        Some(Frame { left, right })
150    }
151}
152
153/// A node that can mute the audio stream.
154pub struct Mute {
155    muted: bool,
156}
157
158impl Mute {
159    #[must_use]
160    pub const fn new() -> Self {
161        Self { muted: false }
162    }
163
164    pub const fn mute(&mut self) {
165        self.muted = true;
166    }
167
168    pub const fn unmute(&mut self) {
169        self.muted = false;
170    }
171}
172
173impl Processor for Mute {
174    fn reset(&mut self) {
175        self.muted = false;
176    }
177
178    fn set(&mut self, param: u8, val: f32) {
179        if param == 0 {
180            self.muted = val < 0.5;
181        }
182    }
183
184    fn process_sample(&mut self, s: Sample) -> Option<Sample> {
185        if self.muted {
186            return Some(Sample::ZERO);
187        }
188        Some(s)
189    }
190}
191
192/// A node that can pause the audio stream.
193pub struct Pause {
194    paused: bool,
195}
196
197impl Pause {
198    #[must_use]
199    pub const fn new() -> Self {
200        Self { paused: false }
201    }
202
203    pub const fn pause(&mut self) {
204        self.paused = true;
205    }
206
207    pub const fn play(&mut self) {
208        self.paused = false;
209    }
210}
211
212impl Processor for Pause {
213    fn reset(&mut self) {
214        self.paused = false;
215    }
216
217    fn set(&mut self, param: u8, val: f32) {
218        if param == 0 {
219            self.paused = val < 0.5;
220        }
221    }
222
223    fn process_children(&mut self, cn: &mut Nodes) -> Option<Frame> {
224        if self.paused {
225            return None;
226        }
227        Mix::new().process_children(cn)
228    }
229}
230
231/// Tracks the current position (elapsed time) of the audio stream.
232pub struct TrackPosition {
233    elapsed: Position,
234}
235
236impl TrackPosition {
237    #[must_use]
238    pub const fn new() -> Self {
239        Self { elapsed: 0 }
240    }
241}
242
243impl Processor for TrackPosition {
244    fn reset(&mut self) {
245        self.elapsed = 0;
246    }
247
248    fn process_sample(&mut self, s: Sample) -> Option<Sample> {
249        self.elapsed += 1;
250        Some(s)
251    }
252}
253
254/// Low-pass/high-pass filter.
255#[derive(Default)]
256pub struct LowHighPass {
257    low: bool,
258    freq: f32,
259    q: f32,
260
261    x_n1: Sample,
262    x_n2: Sample,
263    y_n1: Sample,
264    y_n2: Sample,
265
266    b0: f32,
267    b1: f32,
268    b2: f32,
269    a1: f32,
270    a2: f32,
271}
272
273impl LowHighPass {
274    #[must_use]
275    pub fn new(low: bool, freq: f32, q: f32) -> Self {
276        let mut res = Self {
277            low,
278            freq,
279            q,
280            ..Default::default()
281        };
282        res.update_coefs();
283        res
284    }
285
286    fn update_coefs(&mut self) {
287        let w0 = core::f32::consts::TAU * self.freq / SAMPLE_RATE as f32;
288        let cos_w0 = w0.cos();
289        let alpha = w0.sin() / (2. * self.q);
290
291        if self.low {
292            let b1 = 1. - cos_w0;
293            let b0 = b1 / 2.;
294            let b2 = b0;
295            let a0 = 1. + alpha;
296            let a1 = -2. * cos_w0;
297            let a2 = 1. - alpha;
298
299            self.b0 = b0 / a0;
300            self.b1 = b1 / a0;
301            self.b2 = b2 / a0;
302            self.a1 = a1 / a0;
303            self.a2 = a2 / a0;
304        } else {
305            let b0 = f32::midpoint(1., cos_w0);
306            let b1 = -1. - cos_w0;
307            let b2 = b0;
308            let a0 = 1. + alpha;
309            let a1 = -2. * cos_w0;
310            let a2 = 1. - alpha;
311
312            self.b0 = b0 / a0;
313            self.b1 = b1 / a0;
314            self.b2 = b2 / a0;
315            self.a1 = a1 / a0;
316            self.a2 = a2 / a0;
317        }
318    }
319}
320
321impl Processor for LowHighPass {
322    fn reset(&mut self) {
323        self.y_n2 = Sample::ZERO;
324        self.x_n2 = Sample::ZERO;
325        self.y_n1 = Sample::ZERO;
326        self.x_n1 = Sample::ZERO;
327    }
328
329    fn set(&mut self, param: u8, val: f32) {
330        #[expect(clippy::float_cmp)]
331        if param == 0 && val != self.freq {
332            self.freq = val;
333            self.update_coefs();
334        }
335    }
336
337    fn process_sample(&mut self, s: Sample) -> Option<Sample> {
338        let bx0 = self.b0 * s;
339        let bx1 = self.b1 * self.x_n1;
340        let bx2 = self.b2 * self.x_n2;
341        let ay1 = self.a1 * self.y_n1;
342        let ay2 = self.a2 * self.y_n2;
343        let result = bx0 + bx1 + bx2 - ay1 - ay2;
344
345        self.y_n2 = self.y_n1;
346        self.x_n2 = self.x_n1;
347        self.y_n1 = result;
348        self.x_n1 = s;
349
350        Some(result)
351    }
352}
353
354/// Take the left (and discard the right) channel from a stereo source.
355pub struct TakeLeft {}
356
357impl TakeLeft {
358    #[must_use]
359    pub const fn new() -> Self {
360        Self {}
361    }
362}
363
364impl Processor for TakeLeft {
365    fn process_children(&mut self, cn: &mut Nodes) -> Option<Frame> {
366        let mut sum = Sample::ZERO;
367        for node in cn.iter_mut() {
368            sum += &node.next_frame()?.left;
369        }
370        let s = sum / cn.len() as f32;
371        Some(Frame::mono(s))
372    }
373}
374
375/// Take the right (and discard the left) channel from a stereo source.
376pub struct TakeRight {}
377
378impl TakeRight {
379    #[must_use]
380    pub const fn new() -> Self {
381        Self {}
382    }
383}
384
385impl Processor for TakeRight {
386    fn process_children(&mut self, cn: &mut Nodes) -> Option<Frame> {
387        let mut sum = Sample::ZERO;
388        for node in cn.iter_mut() {
389            if let Some(right) = &node.next_frame()?.right {
390                sum += right;
391            }
392        }
393        let s = sum / cn.len() as f32;
394        Some(Frame::mono(s))
395    }
396}
397
398/// Swap left and right channels.
399pub struct Swap {}
400
401impl Swap {
402    #[must_use]
403    pub const fn new() -> Self {
404        Self {}
405    }
406}
407
408impl Processor for Swap {
409    fn process_frame(&mut self, f: Frame) -> Option<Frame> {
410        if let Some(right) = f.right {
411            Some(Frame::stereo(right, f.left))
412        } else {
413            Some(f)
414        }
415    }
416}
417
418/// Clamp the amplitude onto the given interval
419pub struct Clip {
420    low: f32,
421    high: f32,
422}
423
424impl Clip {
425    #[must_use]
426    pub const fn new(low: f32, high: f32) -> Self {
427        Self { low, high }
428    }
429}
430
431impl Processor for Clip {
432    fn set(&mut self, param: u8, val: f32) {
433        if param == 0 {
434            let diff = self.high - self.low;
435            self.low = val;
436            self.high = val + diff;
437        } else if param == 1 {
438            self.low = val;
439        } else if param == 2 {
440            self.high = val;
441        }
442    }
443
444    fn process_sample(&mut self, s: Sample) -> Option<Sample> {
445        let s = s.fast_max(self.low.into());
446        let s = s.fast_min(self.high.into());
447        Some(s)
448    }
449}
450
451// TODO: DelayLeft
452// TODO: DelayRight
453// TODO: Reverb
454// TODO: Compressor
455// TODO: Overdrive
456// TODO: BitCrusher
457// TODO: Limiter
458// TODO: Bandpass
459// TODO: Chorus
460// TODO: Vibrato
461// https://www.masteringbox.com/learn/audio-effects