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 => {
84 phase
85 }
86
87 LfoWaveform::RandomWalk => {
88 phase
89 }
90 }
91 }
92}
93
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
98#[derive(Debug, Clone)]
99pub struct LfoState {
100 pub phase: f64,
102 pub value: f64,
104 pub hold_counter: usize,
106 pub rng_state: u64,
108 pub last_time: f64,
110}
111
112#[derive(Debug, Clone)]
117pub struct LfoAutomaton {
118 name: String,
119 frequency: f64,
120 amplitude: f64,
121 offset: f64,
122 waveform: LfoWaveform,
123 range: Range,
124 pulse_width: f64,
125 walk_rate: f64,
126}
127
128impl LfoAutomaton {
129 pub fn new(
131 name: &str,
132 frequency: f64,
133 amplitude: f64,
134 offset: f64,
135 waveform: LfoWaveform,
136 ) -> Self {
137 Self {
138 name: name.to_string(),
139 frequency: frequency.max(0.001),
140 amplitude,
141 offset,
142 waveform,
143 range: Range::bipolar(),
144 pulse_width: 0.5,
145 walk_rate: 0.1,
146 }
147 }
148
149 pub fn with_range(mut self, range: Range) -> Self {
151 self.range = range;
152 self
153 }
154
155 pub fn with_pulse_width(mut self, width: f64) -> Self {
157 self.pulse_width = width.clamp(0.01, 0.99);
158 self
159 }
160
161 pub fn with_walk_rate(mut self, rate: f64) -> Self {
163 self.walk_rate = rate.max(0.0);
164 self
165 }
166
167 fn random(&self, state: &mut u64) -> f64 {
169 let mut x = *state;
170 x ^= x << 13;
171 x ^= x >> 7;
172 x ^= x << 17;
173 *state = x;
174 (x as f64 / u64::MAX as f64) * 2.0 - 1.0
175 }
176
177 fn update_random_walk(&self, state: &mut LfoState, dt: f64) {
179 let step = (self.random(&mut state.rng_state) - 0.5) * self.walk_rate * dt * 100.0;
180 state.value = (state.value + step).clamp(-1.0, 1.0);
181 }
182}
183
184#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
186#[derive(Debug, Clone, Default)]
187pub enum LfoAction {
188 #[default]
189 None,
191 Reset,
193}
194
195impl Automaton for LfoAutomaton {
196 type State = LfoState;
197 type Action = LfoAction;
198
199 fn step(
200 &self,
201 time: Time,
202 action: &Self::Action,
203 state: &Self::State,
204 ) -> (Self::State, Option<f64>) {
205 let mut new_state = state.clone();
206
207 if let LfoAction::Reset = action {
208 new_state.phase = 0.0;
209 new_state.last_time = time;
210 }
211
212 let dt = time - new_state.last_time;
213
214 new_state.phase += self.frequency * dt;
215 if new_state.phase >= 1.0 {
216 new_state.phase -= 1.0;
217 if let LfoWaveform::SampleAndHold = self.waveform {
218 new_state.value = self.random(&mut new_state.rng_state);
219 }
220 }
221 new_state.last_time = time;
222
223 if let LfoWaveform::RandomWalk = self.waveform {
224 self.update_random_walk(&mut new_state, dt);
225 }
226
227 let raw_value = match self.waveform {
228 LfoWaveform::SampleAndHold => new_state.value,
229 LfoWaveform::RandomWalk => new_state.value,
230 _ => self
231 .waveform
232 .evaluate(new_state.phase, Some(self.pulse_width)),
233 };
234
235 let value = raw_value * self.amplitude + self.offset;
236 let clamped = self.range.clamp(value);
237
238 (new_state, Some(clamped))
239 }
240
241 fn initial_state(&self) -> Self::State {
242 LfoState {
243 phase: 0.0,
244 value: 0.0,
245 hold_counter: 0,
246 rng_state: 123456789,
247 last_time: 0.0,
248 }
249 }
250
251 fn name(&self) -> &str {
252 &self.name
253 }
254
255 fn extract_value(&self, state: &Self::State) -> f64 {
256 let raw = match self.waveform {
257 LfoWaveform::SampleAndHold => state.value,
258 LfoWaveform::RandomWalk => state.value,
259 _ => self.waveform.evaluate(state.phase, Some(self.pulse_width)),
260 };
261 self.range.clamp(raw * self.amplitude + self.offset)
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268 use float_cmp::approx_eq;
269
270 #[test]
271 fn test_sine_lfo() {
272 let lfo = LfoAutomaton::new("Sine", 1.0, 1.0, 0.0, LfoWaveform::Sine);
273 let state = lfo.initial_state();
274
275 let (new_state, value) = lfo.step(0.0, &LfoAction::None, &state);
276 assert!(approx_eq!(f64, value.unwrap(), 0.0, epsilon = 0.01));
277
278 let (_, value) = lfo.step(0.25, &LfoAction::None, &new_state);
279 assert!(approx_eq!(f64, value.unwrap(), 1.0, epsilon = 0.01));
280 }
281
282 #[test]
283 fn test_reset_action() {
284 let lfo = LfoAutomaton::new("Test", 1.0, 1.0, 0.0, LfoWaveform::Sine);
285 let mut state = lfo.initial_state();
286 state.phase = 0.5;
287
288 let (new_state, _) = lfo.step(1.0, &LfoAction::Reset, &state);
289 assert!(approx_eq!(f64, new_state.phase, 0.0, epsilon = 0.01));
290 }
291}