1pub mod fft;
7pub mod filters;
8pub mod analysis;
9
10pub use fft::{
11 Complex32, Fft, RealFft, FftPlanner, FftPlan, Spectrum, Stft, StftConfig,
12 Cqt, Autocorrelation, MelFilterbank, Mfcc, Chroma,
13};
14pub use filters::{
15 Biquad, BiquadType, BiquadDesign, FilterChain, Butterworth, Chebyshev1, Bessel,
16 FirFilter, FirDesign, Convolution, OlaConvolver,
17 SvfFilter, SvfMode, CombFilter, CombMode, AllpassDelay,
18 MovingAverage, KalmanFilter1D, PllFilter,
19};
20pub use analysis::{
21 OnsetDetector, SpectralFluxOnset, HfcOnset, ComplexDomainOnset,
22 BeatTracker, PitchDetector, LoudnessMeters, Rms, Leq, Lufs, DynamicRange,
23 TransientAnalysis, HarmonicAnalyzer, Correlogram, DynamicsAnalyzer, SignalSimilarity,
24};
25
26use std::f32::consts::PI;
27
28#[derive(Debug, Clone)]
34pub struct Signal<T: Clone> {
35 pub samples: Vec<T>,
36 pub sample_rate: f32,
37}
38
39impl<T: Clone + Default> Signal<T> {
40 pub fn new(samples: Vec<T>, sample_rate: f32) -> Self {
42 assert!(sample_rate > 0.0, "sample_rate must be positive");
43 Self { samples, sample_rate }
44 }
45
46 pub fn duration_secs(&self) -> f32 {
48 self.samples.len() as f32 / self.sample_rate
49 }
50
51 pub fn len(&self) -> usize {
53 self.samples.len()
54 }
55
56 pub fn is_empty(&self) -> bool {
57 self.samples.is_empty()
58 }
59
60 pub fn window(&self, start: usize, len: usize) -> Signal<T> {
63 let end = (start + len).min(self.samples.len());
64 let start = start.min(end);
65 Signal {
66 samples: self.samples[start..end].to_vec(),
67 sample_rate: self.sample_rate,
68 }
69 }
70}
71
72impl Signal<f32> {
73 pub fn downsample(&self, factor: usize) -> Signal<f32> {
75 assert!(factor >= 1);
76 let samples: Vec<f32> = self.samples.iter().step_by(factor).copied().collect();
77 Signal {
78 samples,
79 sample_rate: self.sample_rate / factor as f32,
80 }
81 }
82
83 pub fn upsample(&self, factor: usize) -> Signal<f32> {
85 assert!(factor >= 1);
86 let mut samples = vec![0.0f32; self.samples.len() * factor];
87 for (i, &s) in self.samples.iter().enumerate() {
88 samples[i * factor] = s;
89 }
90 Signal {
91 samples,
92 sample_rate: self.sample_rate * factor as f32,
93 }
94 }
95
96 pub fn resample(&self, new_rate: f32) -> Signal<f32> {
98 assert!(new_rate > 0.0);
99 if (new_rate - self.sample_rate).abs() < 1e-3 {
100 return self.clone();
101 }
102 let ratio = self.sample_rate / new_rate;
103 let new_len = (self.samples.len() as f32 / ratio).round() as usize;
104 let mut out = Vec::with_capacity(new_len);
105 for i in 0..new_len {
106 let pos = i as f32 * ratio;
107 let idx = pos as usize;
108 let frac = pos - idx as f32;
109 let a = self.samples.get(idx).copied().unwrap_or(0.0);
110 let b = self.samples.get(idx + 1).copied().unwrap_or(a);
111 out.push(a + frac * (b - a));
112 }
113 Signal { samples: out, sample_rate: new_rate }
114 }
115
116 pub fn rms(&self) -> f32 {
118 if self.samples.is_empty() { return 0.0; }
119 let sum_sq: f32 = self.samples.iter().map(|&x| x * x).sum();
120 (sum_sq / self.samples.len() as f32).sqrt()
121 }
122
123 pub fn peak(&self) -> f32 {
125 self.samples.iter().map(|&x| x.abs()).fold(0.0f32, f32::max)
126 }
127
128 pub fn zero_crossing_rate(&self) -> f32 {
130 if self.samples.len() < 2 { return 0.0; }
131 let crossings = self.samples.windows(2)
132 .filter(|w| (w[0] >= 0.0) != (w[1] >= 0.0))
133 .count();
134 crossings as f32 * self.sample_rate / self.samples.len() as f32
135 }
136
137 pub fn energy(&self) -> f32 {
139 self.samples.iter().map(|&x| x * x).sum()
140 }
141
142 pub fn normalize(&mut self) {
144 let pk = self.peak();
145 if pk > 1e-10 {
146 for s in self.samples.iter_mut() {
147 *s /= pk;
148 }
149 }
150 }
151
152 pub fn mix_in(&mut self, other: &Signal<f32>, gain: f32) {
154 let len = self.samples.len().min(other.samples.len());
155 for i in 0..len {
156 self.samples[i] += other.samples[i] * gain;
157 }
158 }
159}
160
161#[derive(Debug, Clone)]
167pub struct ComplexSignal {
168 pub samples: Vec<Complex32>,
169 pub sample_rate: f32,
170}
171
172impl ComplexSignal {
173 pub fn new(samples: Vec<Complex32>, sample_rate: f32) -> Self {
174 Self { samples, sample_rate }
175 }
176
177 pub fn magnitude_signal(&self) -> Signal<f32> {
178 Signal {
179 samples: self.samples.iter().map(|c| c.norm()).collect(),
180 sample_rate: self.sample_rate,
181 }
182 }
183
184 pub fn phase_signal(&self) -> Signal<f32> {
185 Signal {
186 samples: self.samples.iter().map(|c| c.arg()).collect(),
187 sample_rate: self.sample_rate,
188 }
189 }
190}
191
192pub struct SignalGenerator;
198
199impl SignalGenerator {
200 pub fn sine(freq_hz: f32, amplitude: f32, duration_secs: f32, sample_rate: f32) -> Signal<f32> {
202 let n = (duration_secs * sample_rate) as usize;
203 let samples: Vec<f32> = (0..n)
204 .map(|i| amplitude * (2.0 * PI * freq_hz * i as f32 / sample_rate).sin())
205 .collect();
206 Signal::new(samples, sample_rate)
207 }
208
209 pub fn square(freq_hz: f32, amplitude: f32, duration_secs: f32, sample_rate: f32) -> Signal<f32> {
211 let n = (duration_secs * sample_rate) as usize;
212 let harmonics = 50usize;
213 let samples: Vec<f32> = (0..n)
214 .map(|i| {
215 let t = i as f32 / sample_rate;
216 let mut v = 0.0f32;
217 for k in 0..harmonics {
218 let h = 2 * k + 1;
219 v += (2.0 * PI * freq_hz * h as f32 * t).sin() / h as f32;
220 }
221 amplitude * (4.0 / PI) * v
222 })
223 .collect();
224 Signal::new(samples, sample_rate)
225 }
226
227 pub fn triangle(freq_hz: f32, amplitude: f32, duration_secs: f32, sample_rate: f32) -> Signal<f32> {
229 let n = (duration_secs * sample_rate) as usize;
230 let period = sample_rate / freq_hz;
231 let samples: Vec<f32> = (0..n)
232 .map(|i| {
233 let phase = (i as f32 % period) / period; let v = if phase < 0.5 {
235 4.0 * phase - 1.0
236 } else {
237 3.0 - 4.0 * phase
238 };
239 amplitude * v
240 })
241 .collect();
242 Signal::new(samples, sample_rate)
243 }
244
245 pub fn sawtooth(freq_hz: f32, amplitude: f32, duration_secs: f32, sample_rate: f32) -> Signal<f32> {
247 let n = (duration_secs * sample_rate) as usize;
248 let period = sample_rate / freq_hz;
249 let samples: Vec<f32> = (0..n)
250 .map(|i| {
251 let phase = (i as f32 % period) / period; amplitude * (2.0 * phase - 1.0)
253 })
254 .collect();
255 Signal::new(samples, sample_rate)
256 }
257
258 pub fn noise(amplitude: f32, duration_secs: f32, sample_rate: f32) -> Signal<f32> {
260 let n = (duration_secs * sample_rate) as usize;
261 let mut state: u32 = 0x12345678;
263 let samples: Vec<f32> = (0..n)
264 .map(|_| {
265 state = state.wrapping_mul(1664525).wrapping_add(1013904223);
266 let norm = (state as f32 / u32::MAX as f32) * 2.0 - 1.0;
267 amplitude * norm
268 })
269 .collect();
270 Signal::new(samples, sample_rate)
271 }
272
273 pub fn chirp(f0_hz: f32, f1_hz: f32, amplitude: f32, duration_secs: f32, sample_rate: f32) -> Signal<f32> {
275 let n = (duration_secs * sample_rate) as usize;
276 let k = (f1_hz - f0_hz) / (2.0 * duration_secs);
277 let samples: Vec<f32> = (0..n)
278 .map(|i| {
279 let t = i as f32 / sample_rate;
280 let phase = 2.0 * PI * (f0_hz * t + k * t * t);
281 amplitude * phase.sin()
282 })
283 .collect();
284 Signal::new(samples, sample_rate)
285 }
286
287 pub fn pulse(amplitude: f32, pulse_width_secs: f32, duration_secs: f32, sample_rate: f32) -> Signal<f32> {
289 let n = (duration_secs * sample_rate) as usize;
290 let pulse_samples = (pulse_width_secs * sample_rate) as usize;
291 let samples: Vec<f32> = (0..n)
292 .map(|i| if i < pulse_samples { amplitude } else { 0.0 })
293 .collect();
294 Signal::new(samples, sample_rate)
295 }
296
297 pub fn impulse(amplitude: f32, duration_secs: f32, sample_rate: f32) -> Signal<f32> {
299 let n = (duration_secs * sample_rate) as usize;
300 let mut samples = vec![0.0f32; n.max(1)];
301 if !samples.is_empty() {
302 samples[0] = amplitude;
303 }
304 Signal::new(samples, sample_rate)
305 }
306
307 pub fn gabor(freq_hz: f32, sigma: f32, center_secs: f32, amplitude: f32, duration_secs: f32, sample_rate: f32) -> Signal<f32> {
309 let n = (duration_secs * sample_rate) as usize;
310 let samples: Vec<f32> = (0..n)
311 .map(|i| {
312 let t = i as f32 / sample_rate;
313 let gauss = (-((t - center_secs).powi(2)) / (2.0 * sigma * sigma)).exp();
314 amplitude * gauss * (2.0 * PI * freq_hz * t).sin()
315 })
316 .collect();
317 Signal::new(samples, sample_rate)
318 }
319}
320
321#[derive(Debug, Clone, Copy, PartialEq)]
327pub enum WindowFunction {
328 Rectangular,
330 Hann,
332 Hamming,
334 Blackman,
336 Kaiser(f32),
338 FlatTop,
340 Nuttall,
342 Gaussian(f32),
344 Bartlett,
346 Welch,
348}
349
350impl WindowFunction {
351 pub fn coefficient(&self, n: usize, len: usize) -> f32 {
353 if len <= 1 { return 1.0; }
354 let n = n as f32;
355 let n_minus_1 = (len - 1) as f32;
356 match self {
357 WindowFunction::Rectangular => 1.0,
358 WindowFunction::Hann => {
359 0.5 * (1.0 - (2.0 * PI * n / n_minus_1).cos())
360 }
361 WindowFunction::Hamming => {
362 0.54 - 0.46 * (2.0 * PI * n / n_minus_1).cos()
363 }
364 WindowFunction::Blackman => {
365 0.42 - 0.5 * (2.0 * PI * n / n_minus_1).cos()
366 + 0.08 * (4.0 * PI * n / n_minus_1).cos()
367 }
368 WindowFunction::Kaiser(beta) => {
369 let half = n_minus_1 / 2.0;
370 let arg = beta * (1.0 - ((n - half) / half).powi(2)).sqrt();
371 Self::bessel_i0(arg) / Self::bessel_i0(*beta)
372 }
373 WindowFunction::FlatTop => {
374 let a0 = 0.21557895;
375 let a1 = 0.41663158;
376 let a2 = 0.277263158;
377 let a3 = 0.083578947;
378 let a4 = 0.006947368;
379 a0 - a1 * (2.0 * PI * n / n_minus_1).cos()
380 + a2 * (4.0 * PI * n / n_minus_1).cos()
381 - a3 * (6.0 * PI * n / n_minus_1).cos()
382 + a4 * (8.0 * PI * n / n_minus_1).cos()
383 }
384 WindowFunction::Nuttall => {
385 let a0 = 0.355768;
386 let a1 = 0.487396;
387 let a2 = 0.144232;
388 let a3 = 0.012604;
389 a0 - a1 * (2.0 * PI * n / n_minus_1).cos()
390 + a2 * (4.0 * PI * n / n_minus_1).cos()
391 - a3 * (6.0 * PI * n / n_minus_1).cos()
392 }
393 WindowFunction::Gaussian(sigma) => {
394 let half = n_minus_1 / 2.0;
395 let exponent = -0.5 * ((n - half) / (sigma * half)).powi(2);
396 exponent.exp()
397 }
398 WindowFunction::Bartlett => {
399 let half = n_minus_1 / 2.0;
400 1.0 - ((n - half) / half).abs()
401 }
402 WindowFunction::Welch => {
403 let half = n_minus_1 / 2.0;
404 1.0 - ((n - half) / half).powi(2)
405 }
406 }
407 }
408
409 pub fn apply(&self, samples: &mut [f32]) {
411 let len = samples.len();
412 for (i, s) in samples.iter_mut().enumerate() {
413 *s *= self.coefficient(i, len);
414 }
415 }
416
417 pub fn generate(&self, len: usize) -> Vec<f32> {
419 (0..len).map(|i| self.coefficient(i, len)).collect()
420 }
421
422 pub fn coherent_gain(&self, len: usize) -> f32 {
424 let sum: f32 = (0..len).map(|i| self.coefficient(i, len)).sum();
425 sum / len as f32
426 }
427
428 pub fn power_gain(&self, len: usize) -> f32 {
430 let sum_sq: f32 = (0..len).map(|i| {
431 let c = self.coefficient(i, len);
432 c * c
433 }).sum();
434 (sum_sq / len as f32).sqrt()
435 }
436
437 fn bessel_i0(x: f32) -> f32 {
439 let ax = x.abs();
441 if ax < 3.75 {
442 let y = (x / 3.75).powi(2);
443 1.0 + y * (3.5156229 + y * (3.0899424 + y * (1.2067492
444 + y * (0.2659732 + y * (0.0360768 + y * 0.0045813)))))
445 } else {
446 let y = 3.75 / ax;
447 (ax.exp() / ax.sqrt())
448 * (0.39894228 + y * (0.01328592 + y * (0.00225319
449 + y * (-0.00157565 + y * (0.00916281
450 + y * (-0.02057706 + y * (0.02635537
451 + y * (-0.01647633 + y * 0.00392377))))))))
452 }
453 }
454}
455
456#[derive(Debug, Clone)]
462pub struct Envelope {
463 pub attack_secs: f32,
465 pub release_secs: f32,
467 pub hold_secs: f32,
469 sample_rate: f32,
470 level: f32,
472 hold_counter: f32,
473 attack_coeff: f32,
474 release_coeff: f32,
475}
476
477impl Envelope {
478 pub fn new(attack_secs: f32, release_secs: f32, hold_secs: f32, sample_rate: f32) -> Self {
479 let attack_coeff = Self::time_to_coeff(attack_secs, sample_rate);
480 let release_coeff = Self::time_to_coeff(release_secs, sample_rate);
481 Self {
482 attack_secs,
483 release_secs,
484 hold_secs,
485 sample_rate,
486 level: 0.0,
487 hold_counter: 0.0,
488 attack_coeff,
489 release_coeff,
490 }
491 }
492
493 fn time_to_coeff(time_secs: f32, sample_rate: f32) -> f32 {
494 if time_secs <= 0.0 { return 0.0; }
495 (-1.0 / (time_secs * sample_rate)).exp()
496 }
497
498 pub fn process(&mut self, input: f32) -> f32 {
500 let abs_in = input.abs();
501 if abs_in >= self.level {
502 self.level = self.attack_coeff * self.level + (1.0 - self.attack_coeff) * abs_in;
504 self.hold_counter = self.hold_secs * self.sample_rate;
505 } else if self.hold_counter > 0.0 {
506 self.hold_counter -= 1.0;
508 } else {
509 self.level = self.release_coeff * self.level + (1.0 - self.release_coeff) * abs_in;
511 }
512 self.level
513 }
514
515 pub fn process_buffer(&mut self, buf: &[f32]) -> Vec<f32> {
517 buf.iter().map(|&x| self.process(x)).collect()
518 }
519
520 pub fn reset(&mut self) {
522 self.level = 0.0;
523 self.hold_counter = 0.0;
524 }
525
526 pub fn level(&self) -> f32 {
528 self.level
529 }
530}
531
532#[derive(Debug, Clone)]
538pub struct PeakMeter {
539 pub hold_secs: f32,
541 pub fallback_db_per_sec: f32,
543 sample_rate: f32,
544 peak_linear: f32,
545 hold_counter: f32,
546 display_level_db: f32,
547 clip: bool,
548}
549
550impl PeakMeter {
551 pub fn new(hold_secs: f32, fallback_db_per_sec: f32, sample_rate: f32) -> Self {
552 Self {
553 hold_secs,
554 fallback_db_per_sec,
555 sample_rate,
556 peak_linear: 0.0,
557 hold_counter: 0.0,
558 display_level_db: -f32::INFINITY,
559 clip: false,
560 }
561 }
562
563 pub fn process(&mut self, buf: &[f32]) {
565 let block_peak = buf.iter().map(|&x| x.abs()).fold(0.0f32, f32::max);
566 if block_peak >= 1.0 {
567 self.clip = true;
568 }
569 if block_peak >= self.peak_linear {
570 self.peak_linear = block_peak;
571 self.hold_counter = self.hold_secs * self.sample_rate;
572 } else if self.hold_counter > 0.0 {
573 self.hold_counter -= buf.len() as f32;
574 } else {
575 let fall_db = self.fallback_db_per_sec * buf.len() as f32 / self.sample_rate;
577 let current_db = linear_to_db(self.peak_linear).max(-144.0);
578 let new_db = current_db - fall_db;
579 self.peak_linear = db_to_linear(new_db).max(0.0);
580 }
581 self.display_level_db = linear_to_db(self.peak_linear);
582 }
583
584 pub fn peak_db(&self) -> f32 {
586 self.display_level_db
587 }
588
589 pub fn is_clipping(&self) -> bool {
591 self.clip
592 }
593
594 pub fn reset_clip(&mut self) {
596 self.clip = false;
597 }
598
599 pub fn reset(&mut self) {
601 self.peak_linear = 0.0;
602 self.hold_counter = 0.0;
603 self.display_level_db = -f32::INFINITY;
604 self.clip = false;
605 }
606}
607
608#[inline]
614pub fn linear_to_db(linear: f32) -> f32 {
615 if linear <= 0.0 { return -f32::INFINITY; }
616 20.0 * linear.log10()
617}
618
619#[inline]
621pub fn db_to_linear(db: f32) -> f32 {
622 10.0f32.powf(db / 20.0)
623}
624
625#[inline]
627pub fn next_power_of_two(n: usize) -> usize {
628 if n <= 1 { return 1; }
629 let mut p = 1usize;
630 while p < n { p <<= 1; }
631 p
632}
633
634#[inline]
636pub fn is_power_of_two(n: usize) -> bool {
637 n > 0 && (n & (n - 1)) == 0
638}
639
640#[inline]
642pub fn sinc(x: f32) -> f32 {
643 if x.abs() < 1e-10 { return 1.0; }
644 let px = PI * x;
645 px.sin() / px
646}
647
648#[inline]
650pub fn freq_to_midi(freq: f32) -> f32 {
651 69.0 + 12.0 * (freq / 440.0).log2()
652}
653
654#[inline]
656pub fn midi_to_freq(midi: f32) -> f32 {
657 440.0 * 2.0f32.powf((midi - 69.0) / 12.0)
658}
659
660#[inline]
662pub fn hz_to_mel(hz: f32) -> f32 {
663 2595.0 * (1.0 + hz / 700.0).log10()
664}
665
666#[inline]
668pub fn mel_to_hz(mel: f32) -> f32 {
669 700.0 * (10.0f32.powf(mel / 2595.0) - 1.0)
670}
671
672#[cfg(test)]
677mod tests {
678 use super::*;
679
680 #[test]
681 fn test_signal_new_and_properties() {
682 let sig = Signal::new(vec![0.0f32; 44100], 44100.0);
683 assert_eq!(sig.len(), 44100);
684 assert!((sig.duration_secs() - 1.0).abs() < 1e-5);
685 }
686
687 #[test]
688 fn test_signal_window() {
689 let sig = Signal::new((0..100).map(|i| i as f32).collect(), 100.0);
690 let w = sig.window(10, 20);
691 assert_eq!(w.len(), 20);
692 assert!((w.samples[0] - 10.0).abs() < 1e-5);
693 }
694
695 #[test]
696 fn test_signal_rms() {
697 let sig = SignalGenerator::sine(440.0, 1.0, 1.0, 44100.0);
699 let rms = sig.rms();
700 assert!((rms - (1.0f32 / 2.0f32.sqrt())).abs() < 0.01, "rms={}", rms);
701 }
702
703 #[test]
704 fn test_signal_peak() {
705 let sig = SignalGenerator::sine(440.0, 0.5, 0.1, 44100.0);
706 let pk = sig.peak();
707 assert!(pk <= 0.5 + 1e-4);
708 assert!(pk > 0.4);
709 }
710
711 #[test]
712 fn test_signal_energy() {
713 let sig = Signal::new(vec![1.0f32; 100], 100.0);
714 assert!((sig.energy() - 100.0).abs() < 1e-5);
715 }
716
717 #[test]
718 fn test_signal_downsample() {
719 let sig = Signal::new(vec![1.0f32; 100], 100.0);
720 let ds = sig.downsample(2);
721 assert_eq!(ds.len(), 50);
722 assert!((ds.sample_rate - 50.0).abs() < 1e-5);
723 }
724
725 #[test]
726 fn test_signal_upsample() {
727 let sig = Signal::new(vec![1.0f32; 10], 10.0);
728 let us = sig.upsample(3);
729 assert_eq!(us.len(), 30);
730 assert!((us.sample_rate - 30.0).abs() < 1e-5);
731 }
732
733 #[test]
734 fn test_signal_resample() {
735 let sig = SignalGenerator::sine(440.0, 1.0, 1.0, 44100.0);
736 let resampled = sig.resample(22050.0);
737 assert_eq!(resampled.len(), 22050);
738 assert!((resampled.sample_rate - 22050.0).abs() < 1e-3);
739 }
740
741 #[test]
742 fn test_window_hann_endpoints() {
743 let w = WindowFunction::Hann;
744 assert!((w.coefficient(0, 1024)).abs() < 1e-5);
746 }
747
748 #[test]
749 fn test_window_rectangular() {
750 let w = WindowFunction::Rectangular;
751 for i in 0..64 {
752 assert!((w.coefficient(i, 64) - 1.0).abs() < 1e-6);
753 }
754 }
755
756 #[test]
757 fn test_window_apply() {
758 let mut buf = vec![1.0f32; 64];
759 WindowFunction::Hann.apply(&mut buf);
760 let mid = buf[32];
762 assert!(mid > 0.9, "mid={}", mid);
763 }
764
765 #[test]
766 fn test_envelope() {
767 let mut env = Envelope::new(0.001, 0.1, 0.0, 44100.0);
768 env.process(1.0);
769 assert!(env.level() > 0.0);
770 for _ in 0..10000 {
772 env.process(0.0);
773 }
774 assert!(env.level() < 0.5);
775 }
776
777 #[test]
778 fn test_peak_meter() {
779 let mut meter = PeakMeter::new(1.0, 60.0, 44100.0);
780 let buf: Vec<f32> = vec![0.5; 512];
781 meter.process(&buf);
782 let db = meter.peak_db();
783 assert!(db > -7.0 && db < -5.0, "db={}", db);
784 }
785
786 #[test]
787 fn test_db_conversions() {
788 assert!((linear_to_db(1.0) - 0.0).abs() < 1e-5);
789 assert!((db_to_linear(0.0) - 1.0).abs() < 1e-5);
790 assert!((linear_to_db(0.5) - (-6.0206)).abs() < 0.01);
791 }
792
793 #[test]
794 fn test_next_power_of_two() {
795 assert_eq!(next_power_of_two(1), 1);
796 assert_eq!(next_power_of_two(5), 8);
797 assert_eq!(next_power_of_two(8), 8);
798 assert_eq!(next_power_of_two(1000), 1024);
799 }
800
801 #[test]
802 fn test_midi_freq_roundtrip() {
803 let midi = 69.0;
804 let freq = midi_to_freq(midi);
805 assert!((freq - 440.0).abs() < 0.01);
806 let back = freq_to_midi(freq);
807 assert!((back - midi).abs() < 0.01);
808 }
809
810 #[test]
811 fn test_signal_generator_chirp() {
812 let sig = SignalGenerator::chirp(20.0, 2000.0, 1.0, 1.0, 44100.0);
813 assert_eq!(sig.len(), 44100);
814 let pk = sig.peak();
815 assert!(pk > 0.9 && pk <= 1.0 + 1e-4);
816 }
817
818 #[test]
819 fn test_signal_generator_impulse() {
820 let sig = SignalGenerator::impulse(1.0, 0.1, 44100.0);
821 assert_eq!(sig.samples[0], 1.0);
822 assert_eq!(sig.samples[1], 0.0);
823 }
824
825 #[test]
826 fn test_zero_crossing_rate() {
827 let sig = SignalGenerator::sine(440.0, 1.0, 1.0, 44100.0);
828 let zcr = sig.zero_crossing_rate();
829 assert!(zcr > 800.0 && zcr < 1000.0, "zcr={}", zcr);
831 }
832}