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 #[inline(always)]
306 pub fn next(&mut self) -> T {
307 self.current = self
308 .current
309 .add(self.target.sub(self.current).mul(self.coeff));
310 self.current
311 }
312
313 #[inline(always)]
315 pub fn process_sample(&mut self, input: T) -> T {
316 self.current = self.current.add(input.sub(self.current).mul(self.coeff));
317 self.current
318 }
319
320 #[inline(always)]
322 pub fn set_current(&mut self, value: T) {
323 self.current = value;
324 self.target = value;
325 }
326
327 #[inline(always)]
329 pub fn current(&self) -> T {
330 self.current
331 }
332}
333
334#[cfg(test)]
343mod tests {
344 use super::*;
345 use float_cmp::approx_eq;
346
347 const EPSILON: f32 = 1e-4; const EPSILON_DB: f32 = 0.1; const EPSILON_WINDOW: f32 = 1e-3; #[test]
353 fn test_midi_conversion() {
354 println!("\n=== Testing MIDI conversion ===");
355
356 let freq: f32 = midi_to_freq(69);
357 println!("MIDI 69 -> frequency: {:.6} Hz", freq);
358 assert!(
359 (freq - 440.0).abs() < 1.0,
360 "MIDI 69 should be ≈440 Hz, got {:.6}",
361 freq
362 );
363
364 let midi: f32 = freq_to_midi(440.0f32);
365 println!("440 Hz -> MIDI: {:.6}", midi);
366 assert!(
367 (midi - 69.0).abs() < 0.1,
368 "440 Hz should be ≈69, got {:.6}",
369 midi
370 );
371
372 let freq_low: f32 = midi_to_freq(0);
373 println!("MIDI 0 -> frequency: {:.6} Hz", freq_low);
374 assert!(
375 freq_low > 0.0 && freq_low < 100.0,
376 "MIDI 0 should be low frequency, got {}",
377 freq_low
378 );
379
380 let freq_high: f32 = midi_to_freq(127);
381 println!("MIDI 127 -> frequency: {:.6} Hz", freq_high);
382 assert!(
383 freq_high > 10000.0,
384 "MIDI 127 should be high frequency, got {}",
385 freq_high
386 );
387 }
388
389 #[test]
390 fn test_fast_tanh() {
391 println!("\n=== Testing fast tanh approximation ===");
392
393 let test_values: [f32; 7] = [-3.0, -1.0, -0.5, 0.0, 0.5, 1.0, 3.0];
395
396 for &x in &test_values {
397 let exact: f32 = x.tanh();
398 let fast: f32 = fast_tanh(x);
399 let diff: f32 = (exact - fast).abs();
400
401 println!(
402 "x = {:4.1}: exact = {:8.6}, fast = {:8.6}, diff = {:8.6}",
403 x, exact, fast, diff
404 );
405
406 assert!(
407 diff < 0.1,
408 "Fast tanh at x={} differs too much: exact={}, fast={}",
409 x,
410 exact,
411 fast
412 );
413 }
414 }
415
416 #[test]
417 fn test_windows() {
418 println!("\n=== Testing window functions ===");
419
420 let test_positions: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
422
423 println!("Hann window:");
424 for &x in &test_positions {
425 let val: f32 = hann_window(x);
426 println!(" x = {:4.2}: {:.6}", x, val);
427
428 if (x - 0.0).abs() < EPSILON_WINDOW {
429 assert!(
430 (val - 0.0).abs() < EPSILON_WINDOW,
431 "Hann at 0 should be ≈0, got {}",
432 val
433 );
434 }
435 if (x - 0.5).abs() < EPSILON_WINDOW {
436 assert!(
437 (val - 1.0).abs() < EPSILON_WINDOW,
438 "Hann at 0.5 should be ≈1.0, got {}",
439 val
440 );
441 }
442 if (x - 1.0).abs() < EPSILON_WINDOW {
443 assert!(
444 (val - 0.0).abs() < EPSILON_WINDOW,
445 "Hann at 1.0 should be ≈0, got {}",
446 val
447 );
448 }
449 }
450
451 println!("Hamming window:");
452 for &x in &test_positions {
453 let val: f32 = hamming_window(x);
454 println!(" x = {:4.2}: {:.6}", x, val);
455
456 if (x - 0.0).abs() < EPSILON_WINDOW {
457 assert!(
458 (val - 0.08).abs() < EPSILON_WINDOW * 10.0, "Hamming at 0 should be ≈0.08, got {}",
460 val
461 );
462 }
463 if (x - 0.5).abs() < EPSILON_WINDOW {
464 assert!(
465 (val - 1.0).abs() < EPSILON_WINDOW,
466 "Hamming at 0.5 should be ≈1.0, got {}",
467 val
468 );
469 }
470 }
471
472 println!("Blackman window:");
473 for &x in &test_positions {
474 let val: f32 = blackman_window(x);
475 println!(" x = {:4.2}: {:.6}", x, val);
476 }
477 }
478
479 #[test]
480 fn test_smoother() {
481 println!("\n=== Testing smoother ===");
482
483 let mut smooth = Smoother::new(0.1f32);
484 smooth.set_target(1.0f32);
485
486 println!("Smoothing from 0 to 1 with coeff=0.1:");
487
488 let mut values: Vec<f32> = Vec::new();
489 for i in 0..10 {
490 let val: f32 = smooth.next();
491 values.push(val);
492 println!(" step {}: {:.6}", i, val);
493 }
494
495 for i in 1..values.len() {
496 assert!(
497 values[i] >= values[i - 1] - 1e-6,
498 "Smoother should increase monotonically: {} < {}",
499 values[i],
500 values[i - 1]
501 );
502 }
503
504 for _ in 0..100 {
505 smooth.next();
506 }
507 let final_val: f32 = smooth.next();
508 println!("Final value after many steps: {:.6}", final_val);
509 assert!(
510 (final_val - 1.0).abs() < 0.1,
511 "Smoother should approach 1.0, got {}",
512 final_val
513 );
514 }
515
516 #[test]
517 fn test_lerp() {
518 println!("\n=== Testing linear interpolation ===");
519
520 let test_cases: [(f32, f32, f32, f32); 4] = [
522 (0.0, 10.0, 0.0, 0.0),
523 (0.0, 10.0, 0.5, 5.0),
524 (0.0, 10.0, 1.0, 10.0),
525 (-5.0, 5.0, 0.25, -2.5),
526 ];
527
528 for (a, b, t, expected) in test_cases {
529 let result: f32 = lerp(a, b, t);
530 println!(
531 "lerp({}, {}, {}) = {}, expected {}",
532 a, b, t, result, expected
533 );
534 assert!(
535 (result - expected).abs() < 1e-6,
536 "lerp({}, {}, {}) = {}, expected {}",
537 a,
538 b,
539 t,
540 result,
541 expected
542 );
543 }
544 }
545
546 #[test]
547 fn test_seconds_to_samples() {
548 println!("\n=== Testing time conversions ===");
549
550 let sample_rate: f32 = 44100.0;
551
552 let test_cases: [(f32, usize); 4] = [(0.0, 0), (0.5, 22050), (1.0, 44100), (2.0, 88200)];
554
555 for (seconds, expected) in test_cases {
556 let samples: usize = seconds_to_samples(seconds, sample_rate);
557 println!("{} seconds = {} samples", seconds, samples);
558 assert_eq!(
559 samples, expected,
560 "{} seconds should be {} samples",
561 seconds, expected
562 );
563
564 let back_to_seconds: f32 = samples_to_seconds(samples, sample_rate);
565 println!(" back to seconds: {:.6}", back_to_seconds);
566 assert!(
567 (back_to_seconds - seconds).abs() < 1e-6,
568 "Round trip failed: {} -> {} -> {}",
569 seconds,
570 samples,
571 back_to_seconds
572 );
573 }
574 }
575
576 #[test]
577 fn test_sine_phase() {
578 println!("\n=== Testing sine phase generation ===");
579
580 let test_phases: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
582
583 for &phase in &test_phases {
584 let val: f32 = sine_phase(phase);
585 println!("sine_phase({}) = {:.6}", phase, val);
586
587 if (phase - 0.0).abs() < EPSILON {
589 assert!(
590 (val - 0.0).abs() < EPSILON,
591 "sin(0) should be 0, got {}",
592 val
593 );
594 }
595 if (phase - 0.25).abs() < EPSILON {
596 assert!(
597 (val - 1.0).abs() < EPSILON,
598 "sin(π/2) should be 1, got {}",
599 val
600 );
601 }
602 if (phase - 0.5).abs() < EPSILON {
603 assert!(
604 (val - 0.0).abs() < EPSILON,
605 "sin(π) should be 0, got {}",
606 val
607 );
608 }
609 }
610 }
611
612 #[test]
613 fn test_saw_phase() {
614 println!("\n=== Testing saw phase generation ===");
615
616 let test_phases: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
617
618 for &phase in &test_phases {
619 let val: f32 = saw_phase(phase);
620 println!("saw_phase({}) = {:.6}", phase, val);
621
622 let expected: f32 = 2.0 * phase - 1.0;
624 assert!(
625 (val - expected).abs() < EPSILON,
626 "saw_phase({}) should be {}, got {}",
627 phase,
628 expected,
629 val
630 );
631 }
632 }
633
634 #[inline(always)]
636 pub fn triangle_phase<T: Transcendental>(phase: T) -> T {
637 let p = phase.to_f32();
641 if p < 0.5 {
642 T::from_f32(4.0 * p - 1.0)
643 } else {
644 T::from_f32(3.0 - 4.0 * p)
645 }
646 }
647
648 #[test]
651 fn test_db_conversion() {
652 println!("\n=== Testing dB conversion ===");
653
654 let linear: f32 = db_to_linear(0.0f32);
656 println!("0 dB -> linear: {:.6}", linear);
657 assert!(
658 (linear - 1.0).abs() < 1e-4,
659 "0 dB should be ≈1.0, got {:.6}",
660 linear
661 );
662
663 let linear: f32 = db_to_linear(-6.0f32);
665 println!("-6 dB -> linear: {:.6}", linear);
666 let expected: f32 = 10.0_f32.powf(-6.0 / 20.0);
667 println!("Expected: {:.6}", expected);
668 assert!(
669 (linear - expected).abs() < 1e-4,
670 "-6 dB should be ≈{:.6}, got {:.6}",
671 expected,
672 linear
673 );
674
675 let linear: f32 = db_to_linear(6.0f32);
677 println!("+6 dB -> linear: {:.6}", linear);
678 let expected: f32 = 10.0_f32.powf(6.0 / 20.0);
679 assert!(
680 (linear - expected).abs() < 1e-4,
681 "+6 dB should be ≈{:.6}, got {:.6}",
682 expected,
683 linear
684 );
685
686 let db: f32 = linear_to_db(0.5f32);
688 println!("0.5 linear -> dB: {:.6}", db);
689 let expected_db: f32 = 20.0 * 0.5f32.log10();
690 assert!(
691 (db - expected_db).abs() < 1e-4,
692 "0.5 should be ≈{:.6} dB, got {:.6}",
693 expected_db,
694 db
695 );
696 }
697
698 #[test]
699 fn test_triangle_phase() {
700 println!("\n=== Testing triangle phase generation ===");
701
702 let test_phases: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
703
704 for &phase in &test_phases {
705 let val: f32 = triangle_phase(phase);
706 println!("triangle_phase({}) = {:.6}", phase, val);
707
708 if (phase - 0.0).abs() < 1e-6 {
709 assert!(
710 (val - -1.0).abs() < 1e-4,
711 "triangle(0) should be -1, got {}",
712 val
713 );
714 } else if (phase - 0.25).abs() < 1e-6 {
715 assert!(
716 (val - 0.0).abs() < 1e-4,
717 "triangle(0.25) should be 0, got {}",
718 val
719 );
720 } else if (phase - 0.5).abs() < 1e-6 {
721 assert!(
722 (val - 1.0).abs() < 1e-4,
723 "triangle(0.5) should be 1, got {}",
724 val
725 );
726 } else if (phase - 0.75).abs() < 1e-6 {
727 assert!(
728 (val - 0.0).abs() < 1e-4,
729 "triangle(0.75) should be 0, got {}",
730 val
731 );
732 } else if (phase - 1.0).abs() < 1e-6 {
733 assert!(
734 (val - -1.0).abs() < 1e-4,
735 "triangle(1.0) should be -1, got {}",
736 val
737 );
738 }
739 }
740 }
741}