rill_patchbay/automaton/
lfo.rs1use crate::engine::{Automaton, NoAction, Range, Time};
6use rill_core::traits::ParamValue;
7use std::f64::consts::PI;
8
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11#[derive(Debug, Clone, Copy, PartialEq)]
12pub enum LfoWaveform {
13 Sine,
15 Triangle,
17 Saw,
19 ReverseSaw,
21 Square,
23 Pulse(f64),
25 SampleAndHold,
27 RandomWalk,
29}
30
31impl LfoWaveform {
32 pub fn name(&self) -> &'static str {
34 match self {
35 LfoWaveform::Sine => "Sine",
36 LfoWaveform::Triangle => "Triangle",
37 LfoWaveform::Saw => "Saw",
38 LfoWaveform::ReverseSaw => "Reverse Saw",
39 LfoWaveform::Square => "Square",
40 LfoWaveform::Pulse(_) => "Pulse",
41 LfoWaveform::SampleAndHold => "S&H",
42 LfoWaveform::RandomWalk => "Random Walk",
43 }
44 }
45
46 pub fn evaluate(&self, phase: f64, pulse_width: Option<f64>) -> f64 {
50 match self {
51 LfoWaveform::Sine => (phase * 2.0 * PI).sin(),
52
53 LfoWaveform::Triangle => {
54 if phase < 0.25 {
55 4.0 * phase
56 } else if phase < 0.75 {
57 2.0 - 4.0 * phase
58 } else {
59 4.0 * phase - 4.0
60 }
61 }
62
63 LfoWaveform::Saw => 2.0 * phase - 1.0,
64
65 LfoWaveform::ReverseSaw => 1.0 - 2.0 * phase,
66
67 LfoWaveform::Square => {
68 if phase < 0.5 {
69 1.0
70 } else {
71 -1.0
72 }
73 }
74
75 LfoWaveform::Pulse(width) => {
76 let w = pulse_width.unwrap_or(*width);
77 if phase < w {
78 1.0
79 } else {
80 -1.0
81 }
82 }
83
84 LfoWaveform::SampleAndHold => phase,
85
86 LfoWaveform::RandomWalk => phase,
87 }
88 }
89}
90
91#[derive(Debug, Clone)]
96pub struct LfoAutomaton {
97 name: String,
98 frequency: f64,
99 amplitude: f64,
100 offset: f64,
101 waveform: LfoWaveform,
102 range: Range,
103 pulse_width: f64,
104 walk_rate: f64,
105}
106
107impl LfoAutomaton {
108 pub fn new(
110 name: &str,
111 frequency: f64,
112 amplitude: f64,
113 offset: f64,
114 waveform: LfoWaveform,
115 ) -> Self {
116 Self {
117 name: name.to_string(),
118 frequency: frequency.max(0.001),
119 amplitude,
120 offset,
121 waveform,
122 range: Range::bipolar(),
123 pulse_width: 0.5,
124 walk_rate: 0.1,
125 }
126 }
127
128 pub fn with_range(mut self, range: Range) -> Self {
130 self.range = range;
131 self
132 }
133
134 pub fn with_pulse_width(mut self, width: f64) -> Self {
136 self.pulse_width = width.clamp(0.01, 0.99);
137 self
138 }
139
140 pub fn with_walk_rate(mut self, rate: f64) -> Self {
142 self.walk_rate = rate.max(0.0);
143 self
144 }
145}
146
147impl Automaton for LfoAutomaton {
148 type Internal = f64;
149 type Action = NoAction;
150
151 fn step(
152 &self,
153 phase: &mut Self::Internal,
154 _current: &ParamValue,
155 time: Time,
156 _action: &Self::Action,
157 ) -> ParamValue {
158 *phase = (time * self.frequency).fract();
159 let raw = match self.waveform {
160 LfoWaveform::Sine => (*phase * 2.0 * PI).sin(),
161 LfoWaveform::Triangle => {
162 if *phase < 0.5 {
163 4.0 * *phase - 1.0
164 } else {
165 3.0 - 4.0 * *phase
166 }
167 }
168 LfoWaveform::Saw => 2.0 * *phase - 1.0,
169 LfoWaveform::ReverseSaw => 1.0 - 2.0 * *phase,
170 LfoWaveform::Square => {
171 if *phase < 0.5 {
172 1.0
173 } else {
174 -1.0
175 }
176 }
177 LfoWaveform::Pulse(width) => {
178 if *phase < width {
179 1.0
180 } else {
181 -1.0
182 }
183 }
184 LfoWaveform::SampleAndHold => {
185 return ParamValue::Float((*phase * 2.0 * PI).sin() as f32);
187 }
188 LfoWaveform::RandomWalk => {
189 (*phase * 2.0 * PI).sin()
191 }
192 };
193 let val = raw * self.amplitude + self.offset;
194 ParamValue::Float(val as f32)
195 }
196
197 fn initial_internal(&self) -> Self::Internal {
198 0.0
199 }
200
201 fn name(&self) -> &str {
202 &self.name
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use float_cmp::approx_eq;
210
211 #[test]
212 fn test_sine_lfo() {
213 let lfo = LfoAutomaton::new("Sine", 1.0, 1.0, 0.0, LfoWaveform::Sine);
214 let mut phase = lfo.initial_internal();
215 let current = ParamValue::Float(0.0);
216
217 let value = lfo.step(&mut phase, ¤t, 0.0, &NoAction);
218 let val = value.as_f32().unwrap();
219 assert!(approx_eq!(f64, val as f64, 0.0, epsilon = 0.01));
220
221 let value = lfo.step(&mut phase, ¤t, 0.25, &NoAction);
222 let val = value.as_f32().unwrap();
223 assert!(approx_eq!(f64, val as f64, 1.0, epsilon = 0.01));
224 }
225}