1use crate::kernel::{Complex, Float};
44use crate::prelude::*;
45
46#[derive(Clone, Debug)]
58pub struct SlidingDft<T: Float> {
59 n: usize,
61 buffer: Vec<Complex<T>>,
63 spectrum: Vec<Complex<T>>,
65 twiddles: Vec<Complex<T>>,
67 pos: usize,
69 samples_pushed: usize,
71}
72
73impl<T: Float> SlidingDft<T> {
74 pub fn new(n: usize) -> Self {
80 assert!(n > 0, "SlidingDft window size must be > 0");
81
82 let twiddles = Self::compute_twiddles(n);
83
84 Self {
85 n,
86 buffer: vec![Complex::zero(); n],
87 spectrum: vec![Complex::zero(); n],
88 twiddles,
89 pos: 0,
90 samples_pushed: 0,
91 }
92 }
93
94 pub fn push(&mut self, sample: Complex<T>) {
96 let x_old = self.buffer[self.pos];
97 self.buffer[self.pos] = sample;
98 self.pos = (self.pos + 1) % self.n;
99
100 if self.samples_pushed < self.n {
101 self.samples_pushed += 1;
102 if self.samples_pushed == self.n {
103 self.compute_initial_dft();
105 }
106 return;
107 }
108
109 let diff = sample - x_old;
111 for k in 0..self.n {
112 self.spectrum[k] = (self.spectrum[k] + diff) * self.twiddles[k];
113 }
114 }
115
116 #[inline]
118 pub fn push_real(&mut self, sample: T) {
119 self.push(Complex::new(sample, T::ZERO));
120 }
121
122 #[inline]
126 pub fn spectrum(&self) -> &[Complex<T>] {
127 &self.spectrum
128 }
129
130 #[inline]
134 pub fn bin(&self, k: usize) -> Complex<T> {
135 if k < self.n {
136 self.spectrum[k]
137 } else {
138 Complex::zero()
139 }
140 }
141
142 pub fn magnitude_spectrum(&self) -> Vec<T> {
144 self.spectrum.iter().map(|c| c.norm()).collect()
145 }
146
147 pub fn power_spectrum(&self) -> Vec<T> {
149 self.spectrum.iter().map(|c| c.norm_sqr()).collect()
150 }
151
152 #[inline]
154 pub fn window_size(&self) -> usize {
155 self.n
156 }
157
158 #[inline]
160 pub fn is_initialized(&self) -> bool {
161 self.samples_pushed >= self.n
162 }
163
164 pub fn reset(&mut self) {
166 for v in &mut self.buffer {
167 *v = Complex::zero();
168 }
169 for v in &mut self.spectrum {
170 *v = Complex::zero();
171 }
172 self.pos = 0;
173 self.samples_pushed = 0;
174 }
175
176 fn compute_twiddles(n: usize) -> Vec<Complex<T>> {
180 let n_f = T::from_usize(n);
181 (0..n)
182 .map(|k| {
183 let angle = T::TWO_PI * T::from_usize(k) / n_f;
184 Complex::cis(angle)
185 })
186 .collect()
187 }
188
189 fn compute_initial_dft(&mut self) {
191 let n_f = T::from_usize(self.n);
192 for k in 0..self.n {
193 let mut sum = Complex::zero();
194 for m in 0..self.n {
195 let idx = (self.pos + m) % self.n;
197 let angle = -T::TWO_PI * T::from_usize(k) * T::from_usize(m) / n_f;
198 sum = sum + self.buffer[idx] * Complex::cis(angle);
199 }
200 self.spectrum[k] = sum;
201 }
202 }
203}
204
205#[derive(Clone, Debug)]
229pub struct ModulatedSdft<T: Float> {
230 n: usize,
232 buffer: Vec<Complex<T>>,
234 y_spectrum: Vec<Complex<T>>,
236 twiddles: Vec<Complex<T>>,
238 inv_twiddles: Vec<Complex<T>>,
240 pos: usize,
242 samples_pushed: usize,
244}
245
246impl<T: Float> ModulatedSdft<T> {
247 pub fn new(n: usize) -> Self {
253 assert!(n > 0, "ModulatedSdft window size must be > 0");
254
255 let n_f = T::from_usize(n);
256 let twiddles: Vec<Complex<T>> = (0..n)
257 .map(|k| {
258 let angle = T::TWO_PI * T::from_usize(k) / n_f;
259 Complex::cis(angle)
260 })
261 .collect();
262 let inv_twiddles: Vec<Complex<T>> = (0..n)
263 .map(|k| {
264 let angle = -T::TWO_PI * T::from_usize(k) / n_f;
265 Complex::cis(angle)
266 })
267 .collect();
268
269 Self {
270 n,
271 buffer: vec![Complex::zero(); n],
272 y_spectrum: vec![Complex::zero(); n],
273 twiddles,
274 inv_twiddles,
275 pos: 0,
276 samples_pushed: 0,
277 }
278 }
279
280 pub fn push(&mut self, sample: Complex<T>) {
282 let x_old = self.buffer[self.pos];
283 self.buffer[self.pos] = sample;
284 self.pos = (self.pos + 1) % self.n;
285
286 if self.samples_pushed < self.n {
287 self.samples_pushed += 1;
288 if self.samples_pushed == self.n {
289 self.compute_initial_dft();
290 }
291 return;
292 }
293
294 let diff = sample - x_old;
296 for k in 0..self.n {
297 self.y_spectrum[k] = (self.y_spectrum[k] + diff) * self.twiddles[k];
299 }
300 }
301
302 #[inline]
304 pub fn push_real(&mut self, sample: T) {
305 self.push(Complex::new(sample, T::ZERO));
306 }
307
308 pub fn spectrum(&self) -> Vec<Complex<T>> {
312 if self.samples_pushed < self.n {
313 return vec![Complex::zero(); self.n];
314 }
315 let mut out = Vec::with_capacity(self.n);
318 for k in 0..self.n {
319 let phase_idx = (k * self.pos) % self.n;
321 let correction = self.inv_twiddles[phase_idx];
322 out.push(self.y_spectrum[k] * correction);
323 }
324 out
325 }
326
327 pub fn bin(&self, k: usize) -> Complex<T> {
329 if k >= self.n || self.samples_pushed < self.n {
330 return Complex::zero();
331 }
332 let phase_idx = (k * self.pos) % self.n;
333 let correction = self.inv_twiddles[phase_idx];
334 self.y_spectrum[k] * correction
335 }
336
337 pub fn magnitude_spectrum(&self) -> Vec<T> {
339 self.spectrum().iter().map(|c| c.norm()).collect()
340 }
341
342 pub fn power_spectrum(&self) -> Vec<T> {
344 self.spectrum().iter().map(|c| c.norm_sqr()).collect()
345 }
346
347 #[inline]
349 pub fn window_size(&self) -> usize {
350 self.n
351 }
352
353 #[inline]
355 pub fn is_initialized(&self) -> bool {
356 self.samples_pushed >= self.n
357 }
358
359 pub fn reset(&mut self) {
361 for v in &mut self.buffer {
362 *v = Complex::zero();
363 }
364 for v in &mut self.y_spectrum {
365 *v = Complex::zero();
366 }
367 self.pos = 0;
368 self.samples_pushed = 0;
369 }
370
371 fn compute_initial_dft(&mut self) {
375 let n_f = T::from_usize(self.n);
376 for k in 0..self.n {
377 let mut sum = Complex::zero();
378 for m in 0..self.n {
379 let idx = (self.pos + m) % self.n;
380 let angle = -T::TWO_PI * T::from_usize(k) * T::from_usize(m) / n_f;
381 sum = sum + self.buffer[idx] * Complex::cis(angle);
382 }
383 self.y_spectrum[k] = sum;
384 }
385 }
386}
387
388#[derive(Clone, Debug)]
397pub struct SingleBinTracker<T: Float> {
398 k: usize,
400 n: usize,
402 buffer: Vec<Complex<T>>,
404 value: Complex<T>,
406 twiddle: Complex<T>,
408 pos: usize,
410 samples_pushed: usize,
412}
413
414impl<T: Float> SingleBinTracker<T> {
415 pub fn new(frequency_bin: usize, window_size: usize) -> Self {
426 assert!(window_size > 0, "SingleBinTracker window size must be > 0");
427 assert!(
428 frequency_bin < window_size,
429 "frequency_bin ({frequency_bin}) must be < window_size ({window_size})"
430 );
431
432 let n_f = T::from_usize(window_size);
433 let angle = T::TWO_PI * T::from_usize(frequency_bin) / n_f;
434 let twiddle = Complex::cis(angle);
435
436 Self {
437 k: frequency_bin,
438 n: window_size,
439 buffer: vec![Complex::zero(); window_size],
440 value: Complex::zero(),
441 twiddle,
442 pos: 0,
443 samples_pushed: 0,
444 }
445 }
446
447 pub fn push(&mut self, sample: Complex<T>) {
449 let x_old = self.buffer[self.pos];
450 self.buffer[self.pos] = sample;
451 self.pos = (self.pos + 1) % self.n;
452
453 if self.samples_pushed < self.n {
454 self.samples_pushed += 1;
455 if self.samples_pushed == self.n {
456 self.compute_initial_bin();
457 }
458 return;
459 }
460
461 self.value = (self.value + sample - x_old) * self.twiddle;
463 }
464
465 #[inline]
467 pub fn push_real(&mut self, sample: T) {
468 self.push(Complex::new(sample, T::ZERO));
469 }
470
471 #[inline]
473 pub fn value(&self) -> Complex<T> {
474 self.value
475 }
476
477 #[inline]
479 pub fn magnitude(&self) -> T {
480 self.value.norm()
481 }
482
483 #[inline]
485 pub fn phase(&self) -> T {
486 self.value.arg()
487 }
488
489 #[inline]
491 pub fn bin_index(&self) -> usize {
492 self.k
493 }
494
495 #[inline]
497 pub fn window_size(&self) -> usize {
498 self.n
499 }
500
501 #[inline]
503 pub fn is_initialized(&self) -> bool {
504 self.samples_pushed >= self.n
505 }
506
507 pub fn reset(&mut self) {
509 for v in &mut self.buffer {
510 *v = Complex::zero();
511 }
512 self.value = Complex::zero();
513 self.pos = 0;
514 self.samples_pushed = 0;
515 }
516
517 fn compute_initial_bin(&mut self) {
521 let n_f = T::from_usize(self.n);
522 let mut sum = Complex::zero();
523 for m in 0..self.n {
524 let idx = (self.pos + m) % self.n;
525 let angle = -T::TWO_PI * T::from_usize(self.k) * T::from_usize(m) / n_f;
526 sum = sum + self.buffer[idx] * Complex::cis(angle);
527 }
528 self.value = sum;
529 }
530}
531
532#[inline]
538pub fn sliding_dft(n: usize) -> SlidingDft<f64> {
539 SlidingDft::new(n)
540}
541
542#[inline]
544pub fn single_bin_tracker(bin: usize, window: usize) -> SingleBinTracker<f64> {
545 SingleBinTracker::new(bin, window)
546}
547
548#[cfg(test)]
553mod tests {
554 use super::*;
555
556 fn reference_dft(x: &[Complex<f64>]) -> Vec<Complex<f64>> {
558 let n = x.len();
559 let n_f = n as f64;
560 (0..n)
561 .map(|k| {
562 let mut sum = Complex::<f64>::zero();
563 for (m, xm) in x.iter().enumerate() {
564 let angle = -core::f64::consts::TAU * (k as f64) * (m as f64) / n_f;
565 sum = sum + *xm * Complex::cis(angle);
566 }
567 sum
568 })
569 .collect()
570 }
571
572 fn assert_spectrum_close(a: &[Complex<f64>], b: &[Complex<f64>], tol: f64) {
574 assert_eq!(a.len(), b.len(), "spectrum length mismatch");
575 for (i, (ai, bi)) in a.iter().zip(b.iter()).enumerate() {
576 let diff = (*ai - *bi).norm();
577 assert!(diff < tol, "bin {i}: |{ai:?} - {bi:?}| = {diff} >= {tol}");
578 }
579 }
580
581 #[test]
584 fn sdft_matches_reference_dft() {
585 let n = 16;
586 let mut sdft = SlidingDft::<f64>::new(n);
587
588 let samples: Vec<Complex<f64>> = (0..2 * n)
590 .map(|i| Complex::new(i as f64, -(i as f64) * 0.5))
591 .collect();
592
593 for s in &samples {
594 sdft.push(*s);
595 }
596
597 let window = &samples[n..];
599 let ref_spectrum = reference_dft(window);
600
601 assert_spectrum_close(sdft.spectrum(), &ref_spectrum, 1e-9);
602 }
603
604 #[test]
605 fn sdft_real_input_convenience() {
606 let n = 8;
607 let mut sdft = SlidingDft::<f64>::new(n);
608
609 let reals: Vec<f64> = (0..n).map(|i| (i as f64) * 1.5).collect();
610 for &r in &reals {
611 sdft.push_real(r);
612 }
613
614 let complex_in: Vec<Complex<f64>> = reals.iter().map(|&r| Complex::new(r, 0.0)).collect();
615
616 let ref_spectrum = reference_dft(&complex_in);
617 assert_spectrum_close(sdft.spectrum(), &ref_spectrum, 1e-12);
618 }
619
620 #[test]
621 fn sdft_magnitude_and_power() {
622 let n = 8;
623 let mut sdft = SlidingDft::<f64>::new(n);
624 for i in 0..n {
625 sdft.push_real(i as f64);
626 }
627
628 let mag = sdft.magnitude_spectrum();
629 let pow = sdft.power_spectrum();
630 assert_eq!(mag.len(), n);
631 assert_eq!(pow.len(), n);
632
633 for i in 0..n {
634 let expected_mag = sdft.spectrum()[i].norm();
635 let expected_pow = sdft.spectrum()[i].norm_sqr();
636 assert!((mag[i] - expected_mag).abs() < 1e-14);
637 assert!((pow[i] - expected_pow).abs() < 1e-14);
638 }
639 }
640
641 #[test]
642 fn sdft_reset() {
643 let n = 4;
644 let mut sdft = SlidingDft::<f64>::new(n);
645 for i in 0..n {
646 sdft.push_real(i as f64);
647 }
648 assert!(sdft.is_initialized());
649
650 sdft.reset();
651 assert!(!sdft.is_initialized());
652
653 for &bin in sdft.spectrum() {
655 assert!((bin.norm()) < 1e-15);
656 }
657 }
658
659 #[test]
660 fn sdft_edge_n1() {
661 let mut sdft = SlidingDft::<f64>::new(1);
662 sdft.push_real(42.0);
663 assert!(sdft.is_initialized());
664 assert!((sdft.bin(0).re - 42.0).abs() < 1e-14);
665
666 sdft.push_real(7.0);
667 assert!((sdft.bin(0).re - 7.0).abs() < 1e-14);
668 }
669
670 #[test]
671 fn sdft_edge_n2() {
672 let mut sdft = SlidingDft::<f64>::new(2);
673 sdft.push_real(1.0);
674 sdft.push_real(2.0);
675 assert!(sdft.is_initialized());
676
677 let window = [Complex::new(1.0, 0.0), Complex::new(2.0, 0.0)];
678 let ref_spec = reference_dft(&window);
679 assert_spectrum_close(sdft.spectrum(), &ref_spec, 1e-14);
680 }
681
682 #[test]
683 fn sdft_pure_sinusoid() {
684 let n = 64;
685 let mut sdft = SlidingDft::<f64>::new(n);
686
687 let bin_freq = 5;
689 for i in 0..2 * n {
690 let t = core::f64::consts::TAU * (bin_freq as f64) * (i as f64) / (n as f64);
691 sdft.push_real(t.cos());
692 }
693
694 let mag = sdft.magnitude_spectrum();
696 let peak = mag[bin_freq];
697 for (k, &m) in mag.iter().enumerate() {
698 if k != bin_freq && k != n - bin_freq {
699 assert!(
700 m < peak * 0.01,
701 "bin {k} magnitude {m} is too large compared to peak {peak}"
702 );
703 }
704 }
705 }
706
707 #[test]
708 fn sdft_bin_out_of_range() {
709 let sdft = SlidingDft::<f64>::new(4);
710 let v = sdft.bin(100);
711 assert!(v.norm() < 1e-15);
712 }
713
714 #[test]
717 fn modulated_sdft_matches_reference() {
718 let n = 16;
719 let mut msdft = ModulatedSdft::<f64>::new(n);
720
721 let samples: Vec<Complex<f64>> = (0..2 * n)
722 .map(|i| Complex::new((i as f64).sin(), (i as f64).cos()))
723 .collect();
724
725 for s in &samples {
726 msdft.push(*s);
727 }
728
729 let window = &samples[n..];
730 let ref_spectrum = reference_dft(window);
731 let got = msdft.spectrum();
732
733 assert_spectrum_close(&got, &ref_spectrum, 1e-9);
734 }
735
736 #[test]
737 fn modulated_sdft_stable_over_long_run() {
738 let n = 32;
740 let mut msdft = ModulatedSdft::<f64>::new(n);
741 let mut plain_sdft = SlidingDft::<f64>::new(n);
742
743 let total = 12_000;
744 let mut recent = Vec::with_capacity(n);
745
746 for i in 0..total {
747 let val = Complex::new(((i as f64) * 0.1).sin(), ((i as f64) * 0.07).cos());
748 msdft.push(val);
749 plain_sdft.push(val);
750
751 recent.push(val);
753 if recent.len() > n {
754 recent.remove(0);
755 }
756 }
757
758 let ref_spectrum = reference_dft(&recent);
759 let mod_spec = msdft.spectrum();
760
761 assert_spectrum_close(&mod_spec, &ref_spectrum, 1e-6);
763
764 let _plain_spec = plain_sdft.spectrum();
766 }
767
768 #[test]
769 fn modulated_sdft_reset() {
770 let n = 8;
771 let mut msdft = ModulatedSdft::<f64>::new(n);
772 for i in 0..n {
773 msdft.push_real(i as f64);
774 }
775 assert!(msdft.is_initialized());
776
777 msdft.reset();
778 assert!(!msdft.is_initialized());
779
780 let spec = msdft.spectrum();
781 for bin in &spec {
782 assert!(bin.norm() < 1e-15);
783 }
784 }
785
786 #[test]
787 fn modulated_sdft_magnitude_power() {
788 let n = 8;
789 let mut msdft = ModulatedSdft::<f64>::new(n);
790 for i in 0..n {
791 msdft.push_real(i as f64);
792 }
793
794 let spec = msdft.spectrum();
795 let mag = msdft.magnitude_spectrum();
796 let pow = msdft.power_spectrum();
797
798 for i in 0..n {
799 assert!((mag[i] - spec[i].norm()).abs() < 1e-14);
800 assert!((pow[i] - spec[i].norm_sqr()).abs() < 1e-14);
801 }
802 }
803
804 #[test]
805 fn modulated_sdft_single_bin() {
806 let n = 16;
807 let mut msdft = ModulatedSdft::<f64>::new(n);
808
809 for i in 0..2 * n {
810 msdft.push_real((i as f64) * 0.3);
811 }
812
813 let full = msdft.spectrum();
814 for k in 0..n {
815 let single = msdft.bin(k);
816 let diff = (single - full[k]).norm();
817 assert!(diff < 1e-12, "bin {k} mismatch: {diff}");
818 }
819 }
820
821 #[test]
824 fn single_bin_matches_full_sdft() {
825 let n = 16;
826 let k = 5;
827 let mut sdft = SlidingDft::<f64>::new(n);
828 let mut tracker = SingleBinTracker::<f64>::new(k, n);
829
830 let samples: Vec<Complex<f64>> = (0..3 * n)
831 .map(|i| Complex::new((i as f64).sin(), (i as f64 * 0.3).cos()))
832 .collect();
833
834 for s in &samples {
835 sdft.push(*s);
836 tracker.push(*s);
837 }
838
839 let diff = (sdft.bin(k) - tracker.value()).norm();
840 assert!(diff < 1e-9, "tracker vs sdft bin {k}: diff = {diff}");
841 }
842
843 #[test]
844 fn single_bin_magnitude_phase() {
845 let n = 8;
846 let k = 3;
847 let mut tracker = SingleBinTracker::<f64>::new(k, n);
848
849 for i in 0..n {
850 tracker.push_real(i as f64);
851 }
852
853 let v = tracker.value();
854 assert!((tracker.magnitude() - v.norm()).abs() < 1e-14);
855 assert!((tracker.phase() - v.arg()).abs() < 1e-14);
856 }
857
858 #[test]
859 fn single_bin_reset() {
860 let n = 8;
861 let mut tracker = SingleBinTracker::<f64>::new(2, n);
862 for i in 0..n {
863 tracker.push_real(i as f64);
864 }
865 assert!(tracker.is_initialized());
866
867 tracker.reset();
868 assert!(!tracker.is_initialized());
869 assert!(tracker.value().norm() < 1e-15);
870 }
871
872 #[test]
873 fn single_bin_accessors() {
874 let tracker = SingleBinTracker::<f64>::new(3, 16);
875 assert_eq!(tracker.bin_index(), 3);
876 assert_eq!(tracker.window_size(), 16);
877 assert!(!tracker.is_initialized());
878 }
879
880 #[test]
881 fn single_bin_tone_detection() {
882 let n = 64;
884 let target_bin = 7;
885 let mut tracker = SingleBinTracker::<f64>::new(target_bin, n);
886 let mut other_tracker = SingleBinTracker::<f64>::new(13, n);
887
888 for i in 0..2 * n {
889 let t = core::f64::consts::TAU * (target_bin as f64) * (i as f64) / (n as f64);
890 let sample = Complex::new(t.cos(), 0.0);
891 tracker.push(sample);
892 other_tracker.push(sample);
893 }
894
895 assert!(tracker.magnitude() > 20.0);
898 assert!(other_tracker.magnitude() < 1.0);
899 }
900
901 #[test]
904 fn sdft_f32_works() {
905 let n = 8;
906 let mut sdft = SlidingDft::<f32>::new(n);
907 for i in 0..n {
908 sdft.push_real(i as f32);
909 }
910 assert!(sdft.is_initialized());
911 assert_eq!(sdft.spectrum().len(), n);
912 }
913
914 #[test]
915 fn modulated_sdft_f32_works() {
916 let n = 8;
917 let mut msdft = ModulatedSdft::<f32>::new(n);
918 for i in 0..n {
919 msdft.push_real(i as f32);
920 }
921 assert!(msdft.is_initialized());
922 assert_eq!(msdft.spectrum().len(), n);
923 }
924
925 #[test]
926 fn single_bin_f32_works() {
927 let mut tracker = SingleBinTracker::<f32>::new(2, 8);
928 for i in 0..8 {
929 tracker.push_real(i as f32);
930 }
931 assert!(tracker.is_initialized());
932 assert!(tracker.magnitude() > 0.0);
933 }
934
935 #[test]
938 fn convenience_sliding_dft() {
939 let mut s = sliding_dft(4);
940 for i in 0..4 {
941 s.push_real(i as f64);
942 }
943 assert!(s.is_initialized());
944 }
945
946 #[test]
947 fn convenience_single_bin_tracker() {
948 let mut t = single_bin_tracker(1, 4);
949 for i in 0..4 {
950 t.push_real(i as f64);
951 }
952 assert!(t.is_initialized());
953 }
954}