1use std::f32::consts::TAU;
7
8pub fn lfo_sine(phase: f32) -> f32 {
27 (phase * TAU).sin()
28}
29
30pub fn lfo_triangle(phase: f32) -> f32 {
48 let p = (phase + 0.75) - (phase + 0.75).floor();
49 2.0 * (2.0 * p - 1.0).abs() - 1.0
50}
51
52pub fn lfo_sawtooth(phase: f32) -> f32 {
69 let p = phase - phase.floor();
70 2.0 * p - 1.0
71}
72
73pub fn lfo_cosine(phase: f32) -> f32 {
85 (phase * TAU).cos()
86}
87
88pub fn lfo_square(phase: f32, width: f32) -> f32 {
109 let p = phase - phase.floor();
110 let w = width.clamp(0.001, 0.999);
111 if p < w { 1.0 } else { -1.0 }
112}
113
114pub fn osc_step(phase: f32, freq: f32, sample_rate: f32, shape: fn(f32) -> f32) -> (f32, f32) {
136 let sr = sample_rate.max(1.0);
137 let new_phase = (phase + freq / sr).fract();
138 (shape(new_phase), new_phase)
139}
140
141#[derive(Clone, Copy, Debug, PartialEq)]
145pub struct AdsrParams {
146 pub attack: f32,
148 pub decay: f32,
150 pub sustain: f32,
152 pub release: f32,
154}
155
156#[derive(Clone, Copy, Debug, PartialEq, Eq)]
158pub enum AdsrStage {
159 Attack,
160 Decay,
161 Sustain,
162 Release,
163 Done,
164}
165
166#[derive(Clone, Copy, Debug, PartialEq)]
168pub struct AdsrState {
169 pub stage: AdsrStage,
171 pub value: f32,
173 pub elapsed: f32,
175}
176
177impl AdsrState {
178 pub const IDLE: Self = Self {
180 stage: AdsrStage::Done,
181 value: 0.0,
182 elapsed: 0.0,
183 };
184}
185
186pub fn adsr_step(state: AdsrState, params: &AdsrParams, gate: bool, dt: f32) -> (f32, AdsrState) {
216 let attack = params.attack.max(1e-4);
217 let decay = params.decay.max(1e-4);
218 let release = params.release.max(1e-4);
219 let sustain = params.sustain.clamp(0.0, 1.0);
220
221 match (state.stage, gate) {
222 (AdsrStage::Done, false) => (0.0, AdsrState::IDLE),
224
225 (_, false) if state.stage != AdsrStage::Release && state.stage != AdsrStage::Done => {
226 let new_state = AdsrState {
228 stage: AdsrStage::Release,
229 value: state.value,
230 elapsed: 0.0,
231 };
232 (state.value, new_state)
233 }
234
235 (AdsrStage::Release, false) => {
236 let t = (state.elapsed / release).min(1.0);
237 let new_val = state.value * (1.0 - t);
238 let new_elapsed = state.elapsed + dt;
239 if new_elapsed >= release {
240 (0.0, AdsrState::IDLE)
241 } else {
242 (new_val, AdsrState { stage: AdsrStage::Release, value: state.value, elapsed: new_elapsed })
243 }
244 }
245
246 (AdsrStage::Done, true) | (AdsrStage::Release, true) => {
248 let new_elapsed = state.elapsed + dt;
250 let new_val = (new_elapsed / attack).min(1.0);
251 if new_elapsed >= attack {
252 (1.0, AdsrState { stage: AdsrStage::Decay, value: 1.0, elapsed: 0.0 })
253 } else {
254 (new_val, AdsrState { stage: AdsrStage::Attack, value: new_val, elapsed: new_elapsed })
255 }
256 }
257
258 (AdsrStage::Attack, true) => {
259 let new_elapsed = state.elapsed + dt;
260 let new_val = (new_elapsed / attack).min(1.0);
261 if new_elapsed >= attack {
262 (1.0, AdsrState { stage: AdsrStage::Decay, value: 1.0, elapsed: 0.0 })
263 } else {
264 (new_val, AdsrState { stage: AdsrStage::Attack, value: new_val, elapsed: new_elapsed })
265 }
266 }
267
268 (AdsrStage::Decay, true) => {
269 let new_elapsed = state.elapsed + dt;
270 let t = (new_elapsed / decay).min(1.0);
271 let new_val = 1.0 + (sustain - 1.0) * t;
272 if new_elapsed >= decay {
273 (sustain, AdsrState { stage: AdsrStage::Sustain, value: sustain, elapsed: 0.0 })
274 } else {
275 (new_val, AdsrState { stage: AdsrStage::Decay, value: new_val, elapsed: new_elapsed })
276 }
277 }
278
279 (AdsrStage::Sustain, true) => {
280 (sustain, AdsrState { stage: AdsrStage::Sustain, value: sustain, elapsed: state.elapsed + dt })
281 }
282
283 _ => (0.0, AdsrState::IDLE),
285 }
286}
287
288#[cfg(test)]
291mod tests {
292 use super::*;
293 const EPS: f32 = 1e-5;
294
295 #[test]
297 fn sine_zero_phase() { assert!((lfo_sine(0.0)).abs() < EPS); }
298 #[test]
299 fn sine_quarter_phase() { assert!((lfo_sine(0.25) - 1.0).abs() < EPS); }
300 #[test]
301 fn sine_half_phase() { assert!((lfo_sine(0.5)).abs() < EPS); }
302 #[test]
303 fn sine_three_quarter_phase() { assert!((lfo_sine(0.75) + 1.0).abs() < EPS); }
304 #[test]
305 fn sine_range() {
306 (0..1000).map(|i| lfo_sine(i as f32 / 1000.0))
307 .for_each(|v| { assert!(v >= -1.0 - EPS && v <= 1.0 + EPS); });
308 }
309
310 #[test]
312 fn triangle_zero() { assert!((lfo_triangle(0.0)).abs() < EPS); }
313 #[test]
314 fn triangle_quarter() { assert!((lfo_triangle(0.25) - 1.0).abs() < EPS); }
315 #[test]
316 fn triangle_half() { assert!((lfo_triangle(0.5)).abs() < EPS); }
317 #[test]
318 fn triangle_three_quarter() { assert!((lfo_triangle(0.75) + 1.0).abs() < EPS); }
319 #[test]
320 fn triangle_range() {
321 (0..1000).map(|i| lfo_triangle(i as f32 / 1000.0))
322 .for_each(|v| { assert!(v >= -1.0 - EPS && v <= 1.0 + EPS); });
323 }
324
325 #[test]
327 fn sawtooth_zero() { assert!((lfo_sawtooth(0.0) + 1.0).abs() < EPS); }
328 #[test]
329 fn sawtooth_half() { assert!((lfo_sawtooth(0.5)).abs() < EPS); }
330 #[test]
331 fn sawtooth_range() {
332 (0..1000).map(|i| lfo_sawtooth(i as f32 / 1000.0))
333 .for_each(|v| { assert!(v >= -1.0 - EPS && v <= 1.0 + EPS); });
334 }
335
336 #[test]
338 fn cosine_zero_phase() { assert!((lfo_cosine(0.0) - 1.0).abs() < EPS); }
339 #[test]
340 fn cosine_quarter_phase() { assert!((lfo_cosine(0.25)).abs() < EPS); }
341 #[test]
342 fn cosine_half_phase() { assert!((lfo_cosine(0.5) + 1.0).abs() < EPS); }
343 #[test]
344 fn cosine_three_quarter_phase() { assert!((lfo_cosine(0.75)).abs() < EPS); }
345 #[test]
346 fn cosine_range() {
347 (0..1000).map(|i| lfo_cosine(i as f32 / 1000.0))
348 .for_each(|v| { assert!(v >= -1.0 - EPS && v <= 1.0 + EPS); });
349 }
350
351 #[test]
353 fn square_first_half() { assert!((lfo_square(0.1, 0.5) - 1.0).abs() < EPS); }
354 #[test]
355 fn square_second_half() { assert!((lfo_square(0.6, 0.5) + 1.0).abs() < EPS); }
356 #[test]
357 fn square_narrow_duty() {
358 assert!((lfo_square(0.05, 0.1) - 1.0).abs() < EPS);
360 assert!((lfo_square(0.15, 0.1) + 1.0).abs() < EPS);
361 }
362
363 #[test]
365 fn osc_phase_advances() {
366 let (_, p1) = osc_step(0.0, 440.0, 44100.0, lfo_sine);
367 assert!((p1 - 440.0 / 44100.0).abs() < EPS);
368 }
369 #[test]
370 fn osc_phase_wraps() {
371 let (_, p1) = osc_step(0.99, 440.0, 44100.0, lfo_sine);
372 assert!(p1 < 1.0);
373 }
374 #[test]
375 fn osc_deterministic() {
376 let (y1, _) = osc_step(0.25, 440.0, 44100.0, lfo_sine);
377 let (y2, _) = osc_step(0.25, 440.0, 44100.0, lfo_sine);
378 assert_eq!(y1, y2);
379 }
380
381 #[test]
383 fn adsr_starts_at_zero() {
384 let params = AdsrParams { attack: 0.1, decay: 0.1, sustain: 0.7, release: 0.2 };
385 let (v, _) = adsr_step(AdsrState::IDLE, ¶ms, false, 0.016);
386 assert!((v).abs() < EPS);
387 }
388 #[test]
389 fn adsr_gate_on_rises() {
390 let params = AdsrParams { attack: 0.1, decay: 0.1, sustain: 0.7, release: 0.2 };
391 let (v, _) = adsr_step(AdsrState::IDLE, ¶ms, true, 0.016);
392 assert!(v > 0.0);
393 }
394 #[test]
395 fn adsr_reaches_sustain() {
396 let params = AdsrParams { attack: 0.01, decay: 0.01, sustain: 0.7, release: 0.2 };
397 let final_state = (0..500).fold(
398 (0.0_f32, AdsrState::IDLE),
399 |(_, s), _| adsr_step(s, ¶ms, true, 0.016),
400 );
401 assert!((final_state.0 - 0.7).abs() < 0.01);
402 }
403 #[test]
404 fn adsr_release_decays_to_zero() {
405 let params = AdsrParams { attack: 0.01, decay: 0.01, sustain: 0.7, release: 0.1 };
406 let (_, sustain_state) = (0..200).fold(
408 (0.0_f32, AdsrState::IDLE),
409 |(_, s), _| adsr_step(s, ¶ms, true, 0.016),
410 );
411 let (v, _) = (0..500).fold(
413 (0.0_f32, sustain_state),
414 |(_, s), _| adsr_step(s, ¶ms, false, 0.016),
415 );
416 assert!(v.abs() < 0.01);
417 }
418}