1use crate::control::{Automaton, Range, Time};
6use std::f64::consts::PI;
7
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum LfoWaveform {
12 Sine,
14 Triangle,
16 Saw,
18 ReverseSaw,
20 Square,
22 Pulse(f64),
24 SampleAndHold,
26 RandomWalk,
28}
29
30impl LfoWaveform {
31 pub fn name(&self) -> &'static str {
33 match self {
34 LfoWaveform::Sine => "Sine",
35 LfoWaveform::Triangle => "Triangle",
36 LfoWaveform::Saw => "Saw",
37 LfoWaveform::ReverseSaw => "Reverse Saw",
38 LfoWaveform::Square => "Square",
39 LfoWaveform::Pulse(_) => "Pulse",
40 LfoWaveform::SampleAndHold => "S&H",
41 LfoWaveform::RandomWalk => "Random Walk",
42 }
43 }
44
45 pub fn evaluate(&self, phase: f64, pulse_width: Option<f64>) -> f64 {
49 match self {
50 LfoWaveform::Sine => (phase * 2.0 * PI).sin(),
51
52 LfoWaveform::Triangle => {
53 if phase < 0.25 {
54 4.0 * phase
55 } else if phase < 0.75 {
56 2.0 - 4.0 * phase
57 } else {
58 4.0 * phase - 4.0
59 }
60 }
61
62 LfoWaveform::Saw => 2.0 * phase - 1.0,
63
64 LfoWaveform::ReverseSaw => 1.0 - 2.0 * phase,
65
66 LfoWaveform::Square => {
67 if phase < 0.5 {
68 1.0
69 } else {
70 -1.0
71 }
72 }
73
74 LfoWaveform::Pulse(width) => {
75 let w = pulse_width.unwrap_or(*width);
76 if phase < w {
77 1.0
78 } else {
79 -1.0
80 }
81 }
82
83 LfoWaveform::SampleAndHold => phase,
84
85 LfoWaveform::RandomWalk => phase,
86 }
87 }
88}
89
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94#[derive(Debug, Clone)]
95pub struct LfoState {
96 pub phase: f64,
98 pub value: f64,
100 pub hold_counter: usize,
102 pub rng_state: u64,
104 pub last_time: f64,
106}
107
108#[derive(Debug, Clone)]
113pub struct LfoAutomaton {
114 name: String,
115 frequency: f64,
116 amplitude: f64,
117 offset: f64,
118 waveform: LfoWaveform,
119 range: Range,
120 pulse_width: f64,
121 walk_rate: f64,
122}
123
124impl LfoAutomaton {
125 pub fn new(
127 name: &str,
128 frequency: f64,
129 amplitude: f64,
130 offset: f64,
131 waveform: LfoWaveform,
132 ) -> Self {
133 Self {
134 name: name.to_string(),
135 frequency: frequency.max(0.001),
136 amplitude,
137 offset,
138 waveform,
139 range: Range::bipolar(),
140 pulse_width: 0.5,
141 walk_rate: 0.1,
142 }
143 }
144
145 pub fn with_range(mut self, range: Range) -> Self {
147 self.range = range;
148 self
149 }
150
151 pub fn with_pulse_width(mut self, width: f64) -> Self {
153 self.pulse_width = width.clamp(0.01, 0.99);
154 self
155 }
156
157 pub fn with_walk_rate(mut self, rate: f64) -> Self {
159 self.walk_rate = rate.max(0.0);
160 self
161 }
162
163 fn random(&self, state: &mut u64) -> f64 {
165 let mut x = *state;
166 x ^= x << 13;
167 x ^= x >> 7;
168 x ^= x << 17;
169 *state = x;
170 (x as f64 / u64::MAX as f64) * 2.0 - 1.0
171 }
172
173 fn update_random_walk(&self, state: &mut LfoState, dt: f64) {
175 let step = (self.random(&mut state.rng_state) - 0.5) * self.walk_rate * dt * 100.0;
176 state.value = (state.value + step).clamp(-1.0, 1.0);
177 }
178}
179
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
182#[derive(Debug, Clone, Default)]
183pub enum LfoAction {
184 #[default]
185 None,
187 Reset,
189}
190
191impl Automaton for LfoAutomaton {
192 type State = LfoState;
193 type Action = LfoAction;
194
195 fn step(
196 &self,
197 time: Time,
198 action: &Self::Action,
199 state: &Self::State,
200 ) -> (Self::State, Option<f64>) {
201 let mut new_state = state.clone();
202
203 if let LfoAction::Reset = action {
204 new_state.phase = 0.0;
205 new_state.last_time = time;
206 }
207
208 let dt = time - new_state.last_time;
209
210 new_state.phase += self.frequency * dt;
211 if new_state.phase >= 1.0 {
212 new_state.phase -= 1.0;
213 if let LfoWaveform::SampleAndHold = self.waveform {
214 new_state.value = self.random(&mut new_state.rng_state);
215 }
216 }
217 new_state.last_time = time;
218
219 if let LfoWaveform::RandomWalk = self.waveform {
220 self.update_random_walk(&mut new_state, dt);
221 }
222
223 let raw_value = match self.waveform {
224 LfoWaveform::SampleAndHold => new_state.value,
225 LfoWaveform::RandomWalk => new_state.value,
226 _ => self
227 .waveform
228 .evaluate(new_state.phase, Some(self.pulse_width)),
229 };
230
231 let value = raw_value * self.amplitude + self.offset;
232 let clamped = self.range.clamp(value);
233
234 (new_state, Some(clamped))
235 }
236
237 fn initial_state(&self) -> Self::State {
238 LfoState {
239 phase: 0.0,
240 value: 0.0,
241 hold_counter: 0,
242 rng_state: 123456789,
243 last_time: 0.0,
244 }
245 }
246
247 fn name(&self) -> &str {
248 &self.name
249 }
250
251 fn extract_value(&self, state: &Self::State) -> f64 {
252 let raw = match self.waveform {
253 LfoWaveform::SampleAndHold => state.value,
254 LfoWaveform::RandomWalk => state.value,
255 _ => self.waveform.evaluate(state.phase, Some(self.pulse_width)),
256 };
257 self.range.clamp(raw * self.amplitude + self.offset)
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use float_cmp::approx_eq;
265
266 #[test]
267 fn test_sine_lfo() {
268 let lfo = LfoAutomaton::new("Sine", 1.0, 1.0, 0.0, LfoWaveform::Sine);
269 let state = lfo.initial_state();
270
271 let (new_state, value) = lfo.step(0.0, &LfoAction::None, &state);
272 assert!(approx_eq!(f64, value.unwrap(), 0.0, epsilon = 0.01));
273
274 let (_, value) = lfo.step(0.25, &LfoAction::None, &new_state);
275 assert!(approx_eq!(f64, value.unwrap(), 1.0, epsilon = 0.01));
276 }
277
278 #[test]
279 fn test_reset_action() {
280 let lfo = LfoAutomaton::new("Test", 1.0, 1.0, 0.0, LfoWaveform::Sine);
281 let mut state = lfo.initial_state();
282 state.phase = 0.5;
283
284 let (new_state, _) = lfo.step(1.0, &LfoAction::Reset, &state);
285 assert!(approx_eq!(f64, new_state.phase, 0.0, epsilon = 0.01));
286 }
287}