1use crate::{lookup_tables, phase_accumulator::PhaseAccumulator, utils::*};
26
27#[derive(Debug, Clone, Copy, PartialEq)]
29pub struct Lfo {
30 phase_accumulator: PhaseAccumulator<TOT_NUM_ACCUM_BITS, NUM_LUT_INDEX_BITS>,
31}
32
33impl Lfo {
34 pub fn new(sample_rate_hz: f32) -> Self {
36 Self {
37 phase_accumulator: PhaseAccumulator::new(sample_rate_hz),
38 }
39 }
40
41 pub fn tick(&mut self) {
43 self.phase_accumulator.tick()
44 }
45
46 pub fn set_frequency(&mut self, freq: f32) {
48 self.phase_accumulator.set_frequency(freq)
49 }
50
51 pub fn reset(&mut self) {
53 self.phase_accumulator.reset()
54 }
55
56 pub fn set_phase(&mut self, phase: f32) {
60 self.phase_accumulator.set_phase(phase)
61 }
62
63 pub fn get(&self, waveshape: Waveshape) -> f32 {
65 match waveshape {
66 Waveshape::Sine => {
67 let lut_idx = self.phase_accumulator.index();
68 let next_lut_idx = (lut_idx + 1) % (lookup_tables::SINE_LUT_SIZE - 1);
69 let y0 = lookup_tables::SINE_TABLE[lut_idx];
70 let y1 = lookup_tables::SINE_TABLE[next_lut_idx];
71 linear_interp(y0, y1, self.phase_accumulator.fraction())
72 }
73 Waveshape::Triangle => {
74 let raw_ramp = self.phase_accumulator.ramp() * 4.0;
76 if raw_ramp < 1.0_f32 {
77 raw_ramp
79 } else if raw_ramp < 3.0_f32 {
80 2.0_f32 - raw_ramp
82 } else {
83 raw_ramp - 4.0_f32
85 }
86 }
87 Waveshape::UpSaw => (self.phase_accumulator.ramp() * 2.0_f32) - 1.0_f32,
88 Waveshape::DownSaw => -self.get(Waveshape::UpSaw),
89 Waveshape::Square => {
90 if self.phase_accumulator.ramp() < 0.5 {
91 1.0
92 } else {
93 -1.0
94 }
95 }
96 }
97 }
98}
99
100#[derive(Clone, Debug, Copy, PartialEq)]
104pub enum Waveshape {
105 Sine,
106 Triangle,
107 UpSaw,
108 DownSaw,
109 Square,
110}
111
112const TOT_NUM_ACCUM_BITS: u32 = 24;
116
117const NUM_LUT_INDEX_BITS: u32 = ilog_2(lookup_tables::SINE_LUT_SIZE);
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn sqr_starts_high_and_then_goes_low() {
128 let mut lfo = Lfo::new(1_000.0_f32);
129 lfo.set_frequency(1.0);
130
131 assert_eq!(lfo.get(Waveshape::Square), 1.0);
132
133 for _ in 0..500 {
135 lfo.tick();
136 }
137 assert_eq!(lfo.get(Waveshape::Square), 1.0);
138
139 lfo.tick();
141 assert_eq!(lfo.get(Waveshape::Square), -1.0);
142 }
143
144 #[test]
145 fn triangle_goes_up_then_down_then_back_up() {
146 let epsilon = 0.0001;
147
148 let mut lfo = Lfo::new(1_000.0_f32);
149 lfo.set_frequency(1.0);
150
151 assert_eq!(lfo.get(Waveshape::Triangle), 0.0);
152
153 for _ in 0..250 {
155 lfo.tick();
156 }
157 assert!(is_almost(lfo.get(Waveshape::Triangle), 1.0, epsilon));
158
159 for _ in 0..250 {
161 lfo.tick();
162 }
163 assert!(is_almost(lfo.get(Waveshape::Triangle), 0.0, epsilon));
164
165 for _ in 0..250 {
167 lfo.tick();
168 }
169 assert!(is_almost(lfo.get(Waveshape::Triangle), -1.0, epsilon));
170 }
171
172 #[test]
173 fn check_a_few_sine_points() {
174 let epsilon = 0.001;
175
176 let mut lfo = Lfo::new(10_000.0_f32);
177 lfo.set_frequency(1.0);
178
179 for _ in 0..1_000 {
181 lfo.tick();
182 }
183
184 assert!(is_almost(
185 lfo.get(Waveshape::Sine),
186 f32::sin(core::f32::consts::PI / 5.),
187 epsilon
188 ));
189
190 for _ in 0..250 {
192 lfo.tick();
193 }
194 assert!(
195 (1. / 2.) < lfo.get(Waveshape::Sine) && lfo.get(Waveshape::Sine) < (f32::sqrt(3.) / 2.)
196 );
197
198 for _ in 0..7915 {
200 lfo.tick();
201 }
202 assert!((-1. / 2.) < lfo.get(Waveshape::Sine) && lfo.get(Waveshape::Sine) < 0.);
203 }
204
205 #[test]
206 fn up_saw_is_monotonic_rising() {
207 let mut lfo = Lfo::new(100.0_f32);
208 lfo.set_frequency(1.0);
209
210 let mut last_val = -1.1;
211
212 for _ in 0..100 {
213 lfo.tick();
214 assert!(last_val < lfo.get(Waveshape::UpSaw));
215 last_val = lfo.get(Waveshape::UpSaw);
216 }
217
218 lfo.tick();
220 assert!(lfo.get(Waveshape::UpSaw) < last_val);
221 }
222
223 #[test]
224 fn down_saw_is_just_negated_up_saw() {
225 let mut lfo = Lfo::new(100.0_f32);
226 lfo.set_frequency(1.0);
227
228 for _ in 0..100 {
229 lfo.tick();
230 assert_eq!(lfo.get(Waveshape::UpSaw), -lfo.get(Waveshape::DownSaw));
231 }
232 }
233
234 #[test]
235 fn set_phase_values() {
236 let mut lfo = Lfo::new(100.0_f32);
237 lfo.set_frequency(1.0);
238
239 let epsilon = 0.001;
240
241 lfo.set_phase(0.0);
243 assert!(is_almost(lfo.get(Waveshape::Triangle), 0.0, epsilon));
244
245 lfo.set_phase(-2.0);
247 assert!(is_almost(lfo.get(Waveshape::Sine), 0.0, epsilon));
248
249 for _ in 0..25 {
251 lfo.tick();
252 }
253 assert!(is_almost(lfo.get(Waveshape::Triangle), 1.0, epsilon));
255 assert!(is_almost(lfo.get(Waveshape::UpSaw), -0.5, epsilon));
257 }
258}