1use rill_core::Transcendental;
12
13#[inline(always)]
27pub fn db_to_linear<T: Transcendental>(db: T) -> T {
28 T::from_f32(10.0_f32.powf(db.to_f32() / 20.0))
29}
30
31#[inline(always)]
36pub fn linear_to_db<T: Transcendental>(linear: T) -> T {
37 T::from_f32(20.0 * linear.to_f32().log10())
38}
39
40#[inline(always)]
45pub fn midi_to_freq<T: Transcendental>(note: u8) -> T {
46 let exp = (note as f32 - 69.0) / 12.0;
47 T::from_f32(440.0 * 2.0_f32.powf(exp))
48}
49
50#[inline(always)]
52pub fn freq_to_midi<T: Transcendental>(freq: T) -> f32 {
53 69.0 + 12.0 * (freq.to_f32() / 440.0).log2()
54}
55
56#[inline(always)]
58pub fn samples_to_seconds(samples: usize, sample_rate: f32) -> f32 {
59 samples as f32 / sample_rate
60}
61
62#[inline(always)]
64pub fn seconds_to_samples(seconds: f32, sample_rate: f32) -> usize {
65 (seconds * sample_rate) as usize
66}
67
68#[inline(always)]
76pub fn fast_exp<T: Transcendental>(x: T) -> T {
77 let xf = x.to_f32();
78
79 let mut result = 1.0 + xf / 16.0;
82 result *= result; result *= result; result *= result; result *= result; T::from_f32(result)
88}
89
90#[inline(always)]
94pub fn fast_tanh<T: Transcendental>(x: T) -> T {
95 let xf = x.to_f32();
96
97 let x2 = xf * xf;
100 let numerator = xf * (27.0 + x2);
101 let denominator = 27.0 + 9.0 * x2;
102
103 T::from_f32(numerator / denominator)
104}
105
106#[inline(always)]
110pub fn fast_sin<T: Transcendental>(x: T) -> T {
111 let xf = x.to_f32();
112
113 let x2 = xf * xf;
115 let x3 = x2 * xf;
116 let x5 = x3 * x2;
117
118 T::from_f32(xf - x3 / 6.0 + x5 / 120.0)
119}
120
121#[inline(always)]
123pub fn soft_clip<T: Transcendental>(x: T, threshold: T) -> T {
124 let xf = x.to_f32();
125 let t = threshold.to_f32();
126
127 if xf > t {
128 T::from_f32(t + (xf - t) / (1.0 + ((xf - t) / (1.0 - t)).powi(2)))
129 } else if xf < -t {
130 T::from_f32(-t - (-xf - t) / (1.0 + ((-xf - t) / (1.0 - t)).powi(2)))
131 } else {
132 x
133 }
134}
135
136#[inline(always)]
142pub fn sine_phase<T: Transcendental>(phase: T) -> T {
143 (phase * T::from_f32(2.0) * T::PI).sin()
144}
145
146#[inline(always)]
148pub fn saw_phase<T: Transcendental>(phase: T) -> T {
149 phase.mul(T::from_f32(2.0)).sub(T::from_f32(1.0))
151}
152
153#[inline(always)]
155pub fn triangle_phase<T: Transcendental>(phase: T) -> T {
156 let p = phase.sub(T::from_f32(0.5));
158 let abs_p = p.abs();
159 abs_p.mul(T::from_f32(4.0)).sub(T::from_f32(1.0))
160}
161
162#[inline(always)]
164pub fn square_phase<T: Transcendental>(phase: T, pulse_width: T) -> T {
165 if phase.to_f32() < pulse_width.to_f32() {
166 T::from_f32(1.0)
167 } else {
168 T::from_f32(-1.0)
169 }
170}
171
172#[inline(always)]
178pub fn hann_window<T: Transcendental>(x: T) -> T {
179 let cos_term = (x * T::from_f32(2.0) * T::PI).cos();
181 T::from_f32(0.5) * (T::from_f32(1.0) - cos_term)
182}
183
184#[inline(always)]
186pub fn hamming_window<T: Transcendental>(x: T) -> T {
187 let cos_term = (x * T::from_f32(2.0) * T::PI).cos();
189 T::from_f32(0.54) - T::from_f32(0.46) * cos_term
190}
191
192#[inline(always)]
194pub fn blackman_window<T: Transcendental>(x: T) -> T {
195 let cos1 = (x * T::from_f32(2.0) * T::PI).cos();
197 let cos2 = (x * T::from_f32(4.0) * T::PI).cos();
198
199 T::from_f32(0.42) - T::from_f32(0.5) * cos1 + T::from_f32(0.08) * cos2
200}
201
202#[inline(always)]
204pub fn variable_window<T: Transcendental>(x: T, shape: T) -> T {
205 let one = T::from_f32(1.0);
206 let rect = one;
207 let hann = hann_window(x);
208
209 rect.mul(one.sub(shape)).add(hann.mul(shape))
211}
212
213#[inline(always)]
219pub fn lerp<T: Transcendental>(a: T, b: T, t: T) -> T {
220 a.add(b.sub(a).mul(t))
221}
222
223#[inline(always)]
225pub fn cubic_interpolate<T: Transcendental>(y0: T, y1: T, y2: T, y3: T, t: T) -> T {
226 let t2 = t.mul(t);
227 let t3 = t2.mul(t);
228
229 let a0 = y3.sub(y2).sub(y0.sub(y1));
230 let a1 = y0.sub(y1).sub(a0);
231 let a2 = y2.sub(y0);
232 let a3 = y1;
233
234 a0.mul(t3).add(a1.mul(t2)).add(a2.mul(t)).add(a3)
235}
236
237#[inline(always)]
239pub fn lagrange_interpolate<T: Transcendental>(y: &[T; 4], x: T) -> T {
240 let x0 = T::from_f32(0.0);
241 let x1 = T::from_f32(1.0);
242 let x2 = T::from_f32(2.0);
243 let x3 = T::from_f32(3.0);
244
245 let term0 = y[0].mul(
246 (x.sub(x1))
247 .mul(x.sub(x2))
248 .mul(x.sub(x3))
249 .div((x0.sub(x1)).mul(x0.sub(x2)).mul(x0.sub(x3))),
250 );
251
252 let term1 = y[1].mul(
253 (x.sub(x0))
254 .mul(x.sub(x2))
255 .mul(x.sub(x3))
256 .div((x1.sub(x0)).mul(x1.sub(x2)).mul(x1.sub(x3))),
257 );
258
259 let term2 = y[2].mul(
260 (x.sub(x0))
261 .mul(x.sub(x1))
262 .mul(x.sub(x3))
263 .div((x2.sub(x0)).mul(x2.sub(x1)).mul(x2.sub(x3))),
264 );
265
266 let term3 = y[3].mul(
267 (x.sub(x0))
268 .mul(x.sub(x1))
269 .mul(x.sub(x2))
270 .div((x3.sub(x0)).mul(x3.sub(x1)).mul(x3.sub(x2))),
271 );
272
273 term0.add(term1).add(term2).add(term3)
274}
275
276#[derive(Debug, Clone)]
282pub struct Smoother<T: Transcendental> {
283 current: T,
284 target: T,
285 coeff: T,
286}
287
288impl<T: Transcendental> Smoother<T> {
289 pub fn new(coeff: T) -> Self {
291 Self {
292 current: T::ZERO,
293 target: T::ZERO,
294 coeff,
295 }
296 }
297
298 #[inline(always)]
300 pub fn set_target(&mut self, target: T) {
301 self.target = target;
302 }
303
304 #[allow(clippy::should_implement_trait)]
306 #[inline(always)]
307 pub fn next(&mut self) -> T {
308 self.current = self
309 .current
310 .add(self.target.sub(self.current).mul(self.coeff));
311 self.current
312 }
313
314 #[inline(always)]
316 pub fn process_sample(&mut self, input: T) -> T {
317 self.current = self.current.add(input.sub(self.current).mul(self.coeff));
318 self.current
319 }
320
321 #[inline(always)]
323 pub fn set_current(&mut self, value: T) {
324 self.current = value;
325 self.target = value;
326 }
327
328 #[inline(always)]
330 pub fn current(&self) -> T {
331 self.current
332 }
333}
334
335#[cfg(test)]
340mod tests {
341 use super::*;
342
343 const EPSILON: f32 = 1e-4; const EPSILON_WINDOW: f32 = 1e-3; #[test]
348 fn test_midi_conversion() {
349 println!("\n=== Testing MIDI conversion ===");
350
351 let freq: f32 = midi_to_freq(69);
352 println!("MIDI 69 -> frequency: {:.6} Hz", freq);
353 assert!(
354 (freq - 440.0).abs() < 1.0,
355 "MIDI 69 should be ≈440 Hz, got {:.6}",
356 freq
357 );
358
359 let midi: f32 = freq_to_midi(440.0f32);
360 println!("440 Hz -> MIDI: {:.6}", midi);
361 assert!(
362 (midi - 69.0).abs() < 0.1,
363 "440 Hz should be ≈69, got {:.6}",
364 midi
365 );
366
367 let freq_low: f32 = midi_to_freq(0);
368 println!("MIDI 0 -> frequency: {:.6} Hz", freq_low);
369 assert!(
370 freq_low > 0.0 && freq_low < 100.0,
371 "MIDI 0 should be low frequency, got {}",
372 freq_low
373 );
374
375 let freq_high: f32 = midi_to_freq(127);
376 println!("MIDI 127 -> frequency: {:.6} Hz", freq_high);
377 assert!(
378 freq_high > 10000.0,
379 "MIDI 127 should be high frequency, got {}",
380 freq_high
381 );
382 }
383
384 #[test]
385 fn test_fast_tanh() {
386 println!("\n=== Testing fast tanh approximation ===");
387
388 let test_values: [f32; 7] = [-3.0, -1.0, -0.5, 0.0, 0.5, 1.0, 3.0];
390
391 for &x in &test_values {
392 let exact: f32 = x.tanh();
393 let fast: f32 = fast_tanh(x);
394 let diff: f32 = (exact - fast).abs();
395
396 println!(
397 "x = {:4.1}: exact = {:8.6}, fast = {:8.6}, diff = {:8.6}",
398 x, exact, fast, diff
399 );
400
401 assert!(
402 diff < 0.1,
403 "Fast tanh at x={} differs too much: exact={}, fast={}",
404 x,
405 exact,
406 fast
407 );
408 }
409 }
410
411 #[test]
412 fn test_windows() {
413 println!("\n=== Testing window functions ===");
414
415 let test_positions: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
417
418 println!("Hann window:");
419 for &x in &test_positions {
420 let val: f32 = hann_window(x);
421 println!(" x = {:4.2}: {:.6}", x, val);
422
423 if (x - 0.0).abs() < EPSILON_WINDOW {
424 assert!(
425 (val - 0.0).abs() < EPSILON_WINDOW,
426 "Hann at 0 should be ≈0, got {}",
427 val
428 );
429 }
430 if (x - 0.5).abs() < EPSILON_WINDOW {
431 assert!(
432 (val - 1.0).abs() < EPSILON_WINDOW,
433 "Hann at 0.5 should be ≈1.0, got {}",
434 val
435 );
436 }
437 if (x - 1.0).abs() < EPSILON_WINDOW {
438 assert!(
439 (val - 0.0).abs() < EPSILON_WINDOW,
440 "Hann at 1.0 should be ≈0, got {}",
441 val
442 );
443 }
444 }
445
446 println!("Hamming window:");
447 for &x in &test_positions {
448 let val: f32 = hamming_window(x);
449 println!(" x = {:4.2}: {:.6}", x, val);
450
451 if (x - 0.0).abs() < EPSILON_WINDOW {
452 assert!(
453 (val - 0.08).abs() < EPSILON_WINDOW * 10.0, "Hamming at 0 should be ≈0.08, got {}",
455 val
456 );
457 }
458 if (x - 0.5).abs() < EPSILON_WINDOW {
459 assert!(
460 (val - 1.0).abs() < EPSILON_WINDOW,
461 "Hamming at 0.5 should be ≈1.0, got {}",
462 val
463 );
464 }
465 }
466
467 println!("Blackman window:");
468 for &x in &test_positions {
469 let val: f32 = blackman_window(x);
470 println!(" x = {:4.2}: {:.6}", x, val);
471 }
472 }
473
474 #[test]
475 fn test_smoother() {
476 println!("\n=== Testing smoother ===");
477
478 let mut smooth = Smoother::new(0.1f32);
479 smooth.set_target(1.0f32);
480
481 println!("Smoothing from 0 to 1 with coeff=0.1:");
482
483 let mut values: Vec<f32> = Vec::new();
484 for i in 0..10 {
485 let val: f32 = smooth.next();
486 values.push(val);
487 println!(" step {}: {:.6}", i, val);
488 }
489
490 for i in 1..values.len() {
491 assert!(
492 values[i] >= values[i - 1] - 1e-6,
493 "Smoother should increase monotonically: {} < {}",
494 values[i],
495 values[i - 1]
496 );
497 }
498
499 for _ in 0..100 {
500 smooth.next();
501 }
502 let final_val: f32 = smooth.next();
503 println!("Final value after many steps: {:.6}", final_val);
504 assert!(
505 (final_val - 1.0).abs() < 0.1,
506 "Smoother should approach 1.0, got {}",
507 final_val
508 );
509 }
510
511 #[test]
512 fn test_lerp() {
513 println!("\n=== Testing linear interpolation ===");
514
515 let test_cases: [(f32, f32, f32, f32); 4] = [
517 (0.0, 10.0, 0.0, 0.0),
518 (0.0, 10.0, 0.5, 5.0),
519 (0.0, 10.0, 1.0, 10.0),
520 (-5.0, 5.0, 0.25, -2.5),
521 ];
522
523 for (a, b, t, expected) in test_cases {
524 let result: f32 = lerp(a, b, t);
525 println!(
526 "lerp({}, {}, {}) = {}, expected {}",
527 a, b, t, result, expected
528 );
529 assert!(
530 (result - expected).abs() < 1e-6,
531 "lerp({}, {}, {}) = {}, expected {}",
532 a,
533 b,
534 t,
535 result,
536 expected
537 );
538 }
539 }
540
541 #[test]
542 fn test_seconds_to_samples() {
543 println!("\n=== Testing time conversions ===");
544
545 let sample_rate: f32 = 44100.0;
546
547 let test_cases: [(f32, usize); 4] = [(0.0, 0), (0.5, 22050), (1.0, 44100), (2.0, 88200)];
549
550 for (seconds, expected) in test_cases {
551 let samples: usize = seconds_to_samples(seconds, sample_rate);
552 println!("{} seconds = {} samples", seconds, samples);
553 assert_eq!(
554 samples, expected,
555 "{} seconds should be {} samples",
556 seconds, expected
557 );
558
559 let back_to_seconds: f32 = samples_to_seconds(samples, sample_rate);
560 println!(" back to seconds: {:.6}", back_to_seconds);
561 assert!(
562 (back_to_seconds - seconds).abs() < 1e-6,
563 "Round trip failed: {} -> {} -> {}",
564 seconds,
565 samples,
566 back_to_seconds
567 );
568 }
569 }
570
571 #[test]
572 fn test_sine_phase() {
573 println!("\n=== Testing sine phase generation ===");
574
575 let test_phases: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
577
578 for &phase in &test_phases {
579 let val: f32 = sine_phase(phase);
580 println!("sine_phase({}) = {:.6}", phase, val);
581
582 if (phase - 0.0).abs() < EPSILON {
584 assert!(
585 (val - 0.0).abs() < EPSILON,
586 "sin(0) should be 0, got {}",
587 val
588 );
589 }
590 if (phase - 0.25).abs() < EPSILON {
591 assert!(
592 (val - 1.0).abs() < EPSILON,
593 "sin(π/2) should be 1, got {}",
594 val
595 );
596 }
597 if (phase - 0.5).abs() < EPSILON {
598 assert!(
599 (val - 0.0).abs() < EPSILON,
600 "sin(π) should be 0, got {}",
601 val
602 );
603 }
604 }
605 }
606
607 #[test]
608 fn test_saw_phase() {
609 println!("\n=== Testing saw phase generation ===");
610
611 let test_phases: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
612
613 for &phase in &test_phases {
614 let val: f32 = saw_phase(phase);
615 println!("saw_phase({}) = {:.6}", phase, val);
616
617 let expected: f32 = 2.0 * phase - 1.0;
619 assert!(
620 (val - expected).abs() < EPSILON,
621 "saw_phase({}) should be {}, got {}",
622 phase,
623 expected,
624 val
625 );
626 }
627 }
628
629 #[inline(always)]
631 pub fn triangle_phase<T: Transcendental>(phase: T) -> T {
632 let p = phase.to_f32();
636 if p < 0.5 {
637 T::from_f32(4.0 * p - 1.0)
638 } else {
639 T::from_f32(3.0 - 4.0 * p)
640 }
641 }
642
643 #[test]
646 fn test_db_conversion() {
647 println!("\n=== Testing dB conversion ===");
648
649 let linear: f32 = db_to_linear(0.0f32);
651 println!("0 dB -> linear: {:.6}", linear);
652 assert!(
653 (linear - 1.0).abs() < 1e-4,
654 "0 dB should be ≈1.0, got {:.6}",
655 linear
656 );
657
658 let linear: f32 = db_to_linear(-6.0f32);
660 println!("-6 dB -> linear: {:.6}", linear);
661 let expected: f32 = 10.0_f32.powf(-6.0 / 20.0);
662 println!("Expected: {:.6}", expected);
663 assert!(
664 (linear - expected).abs() < 1e-4,
665 "-6 dB should be ≈{:.6}, got {:.6}",
666 expected,
667 linear
668 );
669
670 let linear: f32 = db_to_linear(6.0f32);
672 println!("+6 dB -> linear: {:.6}", linear);
673 let expected: f32 = 10.0_f32.powf(6.0 / 20.0);
674 assert!(
675 (linear - expected).abs() < 1e-4,
676 "+6 dB should be ≈{:.6}, got {:.6}",
677 expected,
678 linear
679 );
680
681 let db: f32 = linear_to_db(0.5f32);
683 println!("0.5 linear -> dB: {:.6}", db);
684 let expected_db: f32 = 20.0 * 0.5f32.log10();
685 assert!(
686 (db - expected_db).abs() < 1e-4,
687 "0.5 should be ≈{:.6} dB, got {:.6}",
688 expected_db,
689 db
690 );
691 }
692
693 #[test]
694 fn test_triangle_phase() {
695 println!("\n=== Testing triangle phase generation ===");
696
697 let test_phases: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
698
699 for &phase in &test_phases {
700 let val: f32 = triangle_phase(phase);
701 println!("triangle_phase({}) = {:.6}", phase, val);
702
703 if (phase - 0.0).abs() < 1e-6 {
704 assert!(
705 (val - -1.0).abs() < 1e-4,
706 "triangle(0) should be -1, got {}",
707 val
708 );
709 } else if (phase - 0.25).abs() < 1e-6 {
710 assert!(
711 (val - 0.0).abs() < 1e-4,
712 "triangle(0.25) should be 0, got {}",
713 val
714 );
715 } else if (phase - 0.5).abs() < 1e-6 {
716 assert!(
717 (val - 1.0).abs() < 1e-4,
718 "triangle(0.5) should be 1, got {}",
719 val
720 );
721 } else if (phase - 0.75).abs() < 1e-6 {
722 assert!(
723 (val - 0.0).abs() < 1e-4,
724 "triangle(0.75) should be 0, got {}",
725 val
726 );
727 } else if (phase - 1.0).abs() < 1e-6 {
728 assert!(
729 (val - -1.0).abs() < 1e-4,
730 "triangle(1.0) should be -1, got {}",
731 val
732 );
733 }
734 }
735 }
736}