firefly_audio/
modulators.rs

1//! A collection of modulators.
2
3use crate::SAMPLE_DURATION;
4use core::f32;
5use micromath::F32Ext;
6
7/// An audio node parameter modulator.
8///
9/// Includes both [envelopes] and [LFOs].
10///
11/// [envelopes]: https://en.wikipedia.org/wiki/Envelope_(music)
12/// [LFOs]: https://en.wikipedia.org/wiki/Low-frequency_oscillation
13pub trait Modulator {
14    /// Get the modulator value at the given time (in samples).
15    ///
16    /// The time usually increases. It can go down if it wraps, which happens only
17    /// if the audio plays for a very long time. Or it can be intentionally reset to 0.
18    ///
19    /// The time value is not sequential: 8 might be followed by 200.
20    fn get(&self, now: u32) -> f32;
21}
22
23/// Emit first value before the given moment and the second value after.
24pub struct Hold {
25    v1: f32,
26    v2: f32,
27    time: u32,
28}
29
30impl Hold {
31    #[must_use]
32    pub const fn new(v1: f32, v2: f32, time: u32) -> Self {
33        Self { v1, v2, time }
34    }
35}
36
37impl Modulator for Hold {
38    fn get(&self, now: u32) -> f32 {
39        if now < self.time {
40            self.v1
41        } else {
42            self.v2
43        }
44    }
45}
46
47/// Linearly ramp up or cut down from one value to another on the given time interval.
48pub struct Linear {
49    start: f32,
50    end: f32,
51    start_at: u32,
52    end_at: u32,
53}
54
55impl Linear {
56    #[must_use]
57    pub const fn new(start: f32, end: f32, start_at: u32, end_at: u32) -> Self {
58        Self {
59            start,
60            end,
61            start_at,
62            end_at,
63        }
64    }
65}
66
67impl Modulator for Linear {
68    fn get(&self, now: u32) -> f32 {
69        if now <= self.start_at {
70            return self.start;
71        }
72        if now >= self.end_at {
73            return self.end;
74        }
75        let duration = self.end_at.saturating_sub(self.start_at);
76        if duration == 0 {
77            return self.end;
78        }
79        let elapsed = now - self.start_at;
80        let ratio = elapsed as f32 / duration as f32;
81        (self.end - self.start).mul_add(ratio, self.start)
82    }
83}
84
85/// Sine wave low-frequency oscillator.
86pub struct Sine {
87    s: f32,
88    mid: f32,
89    amp: f32,
90}
91
92impl Sine {
93    /// TODO: make initial phase configurable.
94    #[must_use]
95    pub fn new(freq: f32, low: f32, high: f32) -> Self {
96        let s = core::f32::consts::TAU * freq * SAMPLE_DURATION;
97        let amp = (high - low) / 2.;
98        let mid = low + amp;
99        Self { s, mid, amp }
100    }
101}
102
103impl Modulator for Sine {
104    fn get(&self, now: u32) -> f32 {
105        let s = F32Ext::sin(self.s * now as f32);
106        self.amp.mul_add(s, self.mid)
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    #![allow(clippy::float_cmp)]
113    use super::*;
114
115    fn assert_close(a: f32, b: f32) {
116        let diff = a - b;
117        assert!(diff < 0.00001, "{a} != {b}");
118        assert!(diff > -0.00001, "{a} != {b}");
119    }
120
121    #[test]
122    fn switch() {
123        let lfo = Hold::new(2., 4., 10);
124        assert_eq!(lfo.get(0), 2.);
125        assert_eq!(lfo.get(6), 2.);
126        assert_eq!(lfo.get(9), 2.);
127
128        assert_eq!(lfo.get(10), 4.);
129        assert_eq!(lfo.get(11), 4.);
130        assert_eq!(lfo.get(12), 4.);
131        assert_eq!(lfo.get(21), 4.);
132        assert_eq!(lfo.get(100), 4.);
133    }
134
135    #[test]
136    fn ramp_up() {
137        let lfo = Linear::new(2., 4., 10, 20);
138        assert_eq!(lfo.get(0), 2.);
139        assert_eq!(lfo.get(8), 2.);
140        assert_eq!(lfo.get(10), 2.);
141
142        assert_eq!(lfo.get(20), 4.);
143        assert_eq!(lfo.get(23), 4.);
144        assert_eq!(lfo.get(100), 4.);
145
146        assert_eq!(lfo.get(13), 2.6);
147        assert_eq!(lfo.get(15), 3.);
148        assert_eq!(lfo.get(17), 3.4);
149    }
150
151    #[test]
152    fn cut_down() {
153        let lfo = Linear::new(4., 2., 10, 20);
154        assert_eq!(lfo.get(0), 4.);
155        assert_eq!(lfo.get(8), 4.);
156        assert_eq!(lfo.get(10), 4.);
157
158        assert_eq!(lfo.get(20), 2.);
159        assert_eq!(lfo.get(23), 2.);
160        assert_eq!(lfo.get(100), 2.);
161
162        assert_eq!(lfo.get(13), 3.4);
163        assert_eq!(lfo.get(15), 3.);
164        assert_eq!(lfo.get(17), 2.6);
165    }
166
167    #[test]
168    fn sine() {
169        const R: u32 = 44_100; // sample rate
170        let lfo = Sine::new(1., -1., 1.);
171        assert_eq!(lfo.get(0), 0.);
172        assert!(lfo.get(1) > 0.);
173        assert_eq!(lfo.get(R / 4), 1.);
174        assert_close(lfo.get(R / 2), 0.);
175        assert!(lfo.get(R / 2 + 1) < 0.);
176        assert_eq!(lfo.get(R * 3 / 4), -1.);
177        assert_close(lfo.get(R), 0.);
178    }
179}