firefly_audio/
modulators.rs1use crate::SAMPLE_DURATION;
4use core::f32;
5use micromath::F32Ext;
6
7pub trait Modulator {
14 fn get(&self, now: u32) -> f32;
21}
22
23pub 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
47pub 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
85pub struct Sine {
87 s: f32,
88 mid: f32,
89 amp: f32,
90}
91
92impl Sine {
93 #[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; 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}