1use num_complex::Complex;
2
3use crate::{SpectrogramError, SpectrogramResult};
4
5#[inline]
15#[must_use]
16pub const fn r2c_output_size(n: usize) -> usize {
17 n / 2 + 1
18}
19pub trait R2cPlan {
26 fn n_fft(&self) -> usize;
27 fn output_len(&self) -> usize;
28
29 fn process(&mut self, input: &[f64], output: &mut [Complex<f64>]) -> SpectrogramResult<()>;
44}
45
46pub trait R2cPlanF32: Send {
50 fn n_fft(&self) -> usize;
51 fn output_len(&self) -> usize;
52 fn process(&mut self, input: &[f32], output: &mut [Complex<f32>]) -> SpectrogramResult<()>;
58}
59
60pub trait R2cPlanner {
62 type Plan: R2cPlan;
63
64 fn plan_r2c(&mut self, n_fft: usize) -> SpectrogramResult<Self::Plan>;
78}
79
80pub trait C2rPlan {
82 fn n_fft(&self) -> usize;
88
89 fn input_len(&self) -> usize;
95
96 fn process(&mut self, input: &[Complex<f64>], output: &mut [f64]) -> SpectrogramResult<()>;
111}
112
113pub trait C2rPlanner {
115 type Plan: C2rPlan;
116
117 fn plan_c2r(&mut self, n_fft: usize) -> SpectrogramResult<Self::Plan>;
131}
132
133pub trait C2cPlan: Send {
141 fn n_fft(&self) -> usize;
142 fn forward(&mut self, buf: &mut [Complex<f64>]) -> SpectrogramResult<()>;
148 fn inverse(&mut self, buf: &mut [Complex<f64>]) -> SpectrogramResult<()>;
154}
155
156pub trait C2cPlanF32: Send {
160 fn n_fft(&self) -> usize;
161 fn forward(&mut self, buf: &mut [Complex<f32>]) -> SpectrogramResult<()>;
167 fn inverse(&mut self, buf: &mut [Complex<f32>]) -> SpectrogramResult<()>;
173}
174
175#[inline]
191#[must_use]
192pub const fn r2c_output_size_2d(nrows: usize, ncols: usize) -> (usize, usize) {
193 (nrows, ncols / 2 + 1)
194}
195
196pub trait R2cPlan2d {
203 fn process(&mut self, input: &[f64], output: &mut [Complex<f64>]) -> SpectrogramResult<()>;
218}
219
220pub trait R2cPlanner2d {
222 type Plan: R2cPlan2d;
223
224 fn plan_r2c_2d(&mut self, nrows: usize, ncols: usize) -> SpectrogramResult<Self::Plan>;
239}
240
241pub trait C2rPlan2d {
243 fn process(&mut self, input: &[Complex<f64>], output: &mut [f64]) -> SpectrogramResult<()>;
258}
259
260pub trait C2rPlanner2d {
262 type Plan: C2rPlan2d;
263
264 fn plan_c2r_2d(&mut self, nrows: usize, ncols: usize) -> SpectrogramResult<Self::Plan>;
279}
280
281#[inline]
297pub const fn validate_fft_io(
298 n_fft: usize,
299 input: &[f64],
300 output: &[Complex<f64>],
301) -> SpectrogramResult<()> {
302 if input.len() != n_fft {
303 return Err(SpectrogramError::dimension_mismatch(n_fft, input.len()));
304 }
305
306 let expected_out = r2c_output_size(n_fft);
307 if output.len() != expected_out {
308 return Err(SpectrogramError::dimension_mismatch(
309 expected_out,
310 output.len(),
311 ));
312 }
313
314 Ok(())
315}
316
317#[inline]
334pub const fn validate_fft_io_2d(
335 nrows: usize,
336 ncols: usize,
337 input: &[f64],
338 output: &[Complex<f64>],
339) -> SpectrogramResult<()> {
340 let input_len = nrows * ncols;
341 if input.len() != input_len {
342 return Err(SpectrogramError::dimension_mismatch(input_len, input.len()));
343 }
344
345 let (out_rows, out_cols) = r2c_output_size_2d(nrows, ncols);
346 let output_len = out_rows * out_cols;
347 if output.len() != output_len {
348 return Err(SpectrogramError::dimension_mismatch(
349 output_len,
350 output.len(),
351 ));
352 }
353
354 Ok(())
355}
356
357#[cfg(feature = "realfft")]
358pub mod realfft_backend {
359 use std::collections::HashMap;
360 use std::sync::Arc;
361
362 use num_complex::Complex;
363 pub use realfft::{ComplexToReal, RealFftPlanner as InnerPlanner, RealToComplex};
364
365 use crate::fft_backend::{
366 C2rPlan, C2rPlanner, R2cPlan, R2cPlanner, r2c_output_size, validate_fft_io,
367 };
368 use crate::{SpectrogramError, SpectrogramResult};
369
370 #[derive(Default)]
377 pub struct RealFftPlanner {
378 inner: InnerPlanner<f64>,
379 cache_r2c: HashMap<usize, Arc<dyn RealToComplex<f64>>>,
380 cache_c2r: HashMap<usize, Arc<dyn ComplexToReal<f64>>>,
381 }
382
383 impl RealFftPlanner {
384 #[inline]
390 #[must_use]
391 pub fn new() -> Self {
392 Self::default()
393 }
394
395 pub(crate) fn get_or_create(&mut self, n_fft: usize) -> Arc<dyn RealToComplex<f64>> {
396 if let Some(p) = self.cache_r2c.get(&n_fft) {
397 return p.clone();
398 }
399 let p = self.inner.plan_fft_forward(n_fft);
400 self.cache_r2c.insert(n_fft, p.clone());
401 p
402 }
403
404 pub(crate) fn get_or_create_inverse(
405 &mut self,
406 n_fft: usize,
407 ) -> Arc<dyn ComplexToReal<f64>> {
408 if let Some(p) = self.cache_c2r.get(&n_fft) {
409 return p.clone();
410 }
411 let p = self.inner.plan_fft_inverse(n_fft);
412 self.cache_c2r.insert(n_fft, p.clone());
413 p
414 }
415 }
416
417 #[derive(Clone)]
421 pub struct RealFftPlan {
422 n_fft: usize,
423 plan: Arc<dyn RealToComplex<f64>>,
424 scratch: Vec<f64>,
425 }
426
427 impl RealFftPlan {
428 pub(crate) fn new(n_fft: usize, plan: Arc<dyn RealToComplex<f64>>) -> Self {
429 Self {
430 n_fft,
431 plan,
432 scratch: vec![0.0; n_fft],
433 }
434 }
435 }
436
437 impl R2cPlan for RealFftPlan {
438 #[inline]
439 fn n_fft(&self) -> usize {
440 self.n_fft
441 }
442
443 #[inline]
444 fn output_len(&self) -> usize {
445 r2c_output_size(self.n_fft)
446 }
447
448 #[inline]
449 fn process(&mut self, input: &[f64], output: &mut [Complex<f64>]) -> SpectrogramResult<()> {
450 validate_fft_io(self.n_fft, input, output)?;
451
452 self.scratch.copy_from_slice(input);
453
454 self.plan
455 .process(&mut self.scratch, output)
456 .map_err(|e| SpectrogramError::fft_backend("realfft", format!("{e:?}")))
457 }
458 }
459
460 impl R2cPlanner for RealFftPlanner {
461 type Plan = RealFftPlan;
462
463 #[inline]
464 fn plan_r2c(&mut self, n_fft: usize) -> SpectrogramResult<Self::Plan> {
465 let plan = self.get_or_create(n_fft);
466 Ok(RealFftPlan::new(n_fft, plan))
467 }
468 }
469
470 pub struct RealFftPlanF32 {
472 n_fft: usize,
473 plan: Arc<dyn RealToComplex<f32>>,
474 scratch: Vec<f32>,
475 }
476
477 impl RealFftPlanF32 {
478 #[must_use]
480 pub fn new(n_fft: usize) -> Self {
481 let mut planner: InnerPlanner<f32> = InnerPlanner::new();
482 let plan = planner.plan_fft_forward(n_fft);
483 Self {
484 n_fft,
485 plan,
486 scratch: vec![0.0f32; n_fft],
487 }
488 }
489 }
490
491 impl crate::fft_backend::R2cPlanF32 for RealFftPlanF32 {
492 #[inline]
493 fn n_fft(&self) -> usize {
494 self.n_fft
495 }
496
497 #[inline]
498 fn output_len(&self) -> usize {
499 r2c_output_size(self.n_fft)
500 }
501
502 #[inline]
503 fn process(&mut self, input: &[f32], output: &mut [Complex<f32>]) -> SpectrogramResult<()> {
504 if input.len() != self.n_fft {
505 return Err(SpectrogramError::dimension_mismatch(
506 self.n_fft,
507 input.len(),
508 ));
509 }
510 self.scratch.copy_from_slice(input);
511 self.plan
512 .process(&mut self.scratch, output)
513 .map_err(|e| SpectrogramError::fft_backend("realfft_f32", format!("{e:?}")))
514 }
515 }
516
517 pub struct RealFftC2cPlan {
524 n_fft: usize,
525 fwd: Arc<dyn rustfft::Fft<f64>>,
526 inv: Arc<dyn rustfft::Fft<f64>>,
527 scratch: Vec<Complex<f64>>,
528 }
529
530 impl RealFftC2cPlan {
531 #[must_use]
533 pub fn new(n_fft: usize) -> Self {
534 let mut p = RustFftPlanner::<f64>::new();
535 let fwd = p.plan_fft_forward(n_fft);
536 let inv = p.plan_fft_inverse(n_fft);
537 let scratch_len = fwd
538 .get_inplace_scratch_len()
539 .max(inv.get_inplace_scratch_len());
540 Self {
541 n_fft,
542 fwd,
543 inv,
544 scratch: vec![Complex::new(0.0, 0.0); scratch_len],
545 }
546 }
547 }
548
549 impl crate::fft_backend::C2cPlan for RealFftC2cPlan {
550 #[inline]
551 fn n_fft(&self) -> usize {
552 self.n_fft
553 }
554
555 #[inline]
556 fn forward(&mut self, buf: &mut [Complex<f64>]) -> SpectrogramResult<()> {
557 if buf.len() != self.n_fft {
558 return Err(SpectrogramError::dimension_mismatch(self.n_fft, buf.len()));
559 }
560 self.fwd.process_with_scratch(buf, &mut self.scratch);
561 Ok(())
562 }
563
564 #[inline]
565 fn inverse(&mut self, buf: &mut [Complex<f64>]) -> SpectrogramResult<()> {
566 if buf.len() != self.n_fft {
567 return Err(SpectrogramError::dimension_mismatch(self.n_fft, buf.len()));
568 }
569 self.inv.process_with_scratch(buf, &mut self.scratch);
570 Ok(())
571 }
572 }
573
574 unsafe impl Send for RealFftC2cPlan {}
576
577 pub struct RealFftC2cPlanF32 {
579 n_fft: usize,
580 fwd: Arc<dyn rustfft::Fft<f32>>,
581 inv: Arc<dyn rustfft::Fft<f32>>,
582 scratch: Vec<Complex<f32>>,
583 }
584
585 impl RealFftC2cPlanF32 {
586 #[must_use]
588 pub fn new(n_fft: usize) -> Self {
589 let mut p = RustFftPlanner::<f32>::new();
590 let fwd = p.plan_fft_forward(n_fft);
591 let inv = p.plan_fft_inverse(n_fft);
592 let scratch_len = fwd
593 .get_inplace_scratch_len()
594 .max(inv.get_inplace_scratch_len());
595 Self {
596 n_fft,
597 fwd,
598 inv,
599 scratch: vec![Complex::new(0.0f32, 0.0f32); scratch_len],
600 }
601 }
602 }
603
604 impl crate::fft_backend::C2cPlanF32 for RealFftC2cPlanF32 {
605 #[inline]
606 fn n_fft(&self) -> usize {
607 self.n_fft
608 }
609
610 #[inline]
611 fn forward(&mut self, buf: &mut [Complex<f32>]) -> SpectrogramResult<()> {
612 if buf.len() != self.n_fft {
613 return Err(SpectrogramError::dimension_mismatch(self.n_fft, buf.len()));
614 }
615 self.fwd.process_with_scratch(buf, &mut self.scratch);
616 Ok(())
617 }
618
619 #[inline]
620 fn inverse(&mut self, buf: &mut [Complex<f32>]) -> SpectrogramResult<()> {
621 if buf.len() != self.n_fft {
622 return Err(SpectrogramError::dimension_mismatch(self.n_fft, buf.len()));
623 }
624 self.inv.process_with_scratch(buf, &mut self.scratch);
625 Ok(())
626 }
627 }
628
629 unsafe impl Send for RealFftC2cPlanF32 {}
631
632 #[derive(Clone)]
637 pub struct RealFftInversePlan {
638 n_fft: usize,
639 plan: Arc<dyn ComplexToReal<f64>>,
640 scratch: Vec<Complex<f64>>,
641 }
642
643 impl RealFftInversePlan {
644 pub(crate) fn new(n_fft: usize, plan: Arc<dyn ComplexToReal<f64>>) -> Self {
645 let scratch_len = r2c_output_size(n_fft);
646 Self {
647 n_fft,
648 plan,
649 scratch: vec![Complex::new(0.0, 0.0); scratch_len],
650 }
651 }
652 }
653
654 impl C2rPlan for RealFftInversePlan {
655 #[inline]
656 fn n_fft(&self) -> usize {
657 self.n_fft
658 }
659
660 #[inline]
661 fn input_len(&self) -> usize {
662 r2c_output_size(self.n_fft)
663 }
664
665 #[inline]
666 fn process(&mut self, input: &[Complex<f64>], output: &mut [f64]) -> SpectrogramResult<()> {
667 let expected_in = r2c_output_size(self.n_fft);
668 if input.len() != expected_in {
669 return Err(SpectrogramError::dimension_mismatch(
670 expected_in,
671 input.len(),
672 ));
673 }
674 if output.len() != self.n_fft {
675 return Err(SpectrogramError::dimension_mismatch(
676 self.n_fft,
677 output.len(),
678 ));
679 }
680
681 self.scratch.copy_from_slice(input);
682
683 self.plan
684 .process(&mut self.scratch, output)
685 .map_err(|e| SpectrogramError::fft_backend("realfft", format!("{e:?}")))?;
686
687 let scale = 1.0 / self.n_fft as f64;
689 for val in output.iter_mut() {
690 *val *= scale;
691 }
692
693 Ok(())
694 }
695 }
696
697 impl C2rPlanner for RealFftPlanner {
698 type Plan = RealFftInversePlan;
699
700 #[inline]
701 fn plan_c2r(&mut self, n_fft: usize) -> SpectrogramResult<Self::Plan> {
702 let plan = self.get_or_create_inverse(n_fft);
703 Ok(RealFftInversePlan::new(n_fft, plan))
704 }
705 }
706
707 #[cfg(feature = "realfft")]
712 use rustfft::FftPlanner as RustFftPlanner;
713
714 impl RealFftPlanner {
715 pub(crate) fn get_or_create_complex(n: usize) -> Arc<dyn rustfft::Fft<f64>> {
717 let mut complex_planner = RustFftPlanner::<f64>::new();
721 complex_planner.plan_fft_forward(n)
722 }
723
724 pub(crate) fn get_or_create_complex_inverse(n: usize) -> Arc<dyn rustfft::Fft<f64>> {
725 let mut complex_planner = RustFftPlanner::<f64>::new();
726 complex_planner.plan_fft_inverse(n)
727 }
728 }
729
730 #[derive(Clone)]
742 pub struct RealFftPlan2d {
743 nrows: usize,
744 ncols: usize,
745 out_shape: (usize, usize),
746 row_plan: Arc<dyn RealToComplex<f64>>,
748 col_plan: Arc<dyn rustfft::Fft<f64>>,
750 scratch_row: Vec<f64>,
752 scratch_col: Vec<Complex<f64>>,
753 intermediate: Vec<Complex<f64>>,
755 }
756
757 impl RealFftPlan2d {
758 pub(crate) fn new(
759 nrows: usize,
760 ncols: usize,
761 row_plan: Arc<dyn RealToComplex<f64>>,
762 col_plan: Arc<dyn rustfft::Fft<f64>>,
763 ) -> Self {
764 let out_shape = crate::fft_backend::r2c_output_size_2d(nrows, ncols);
765 let intermediate_len = nrows * out_shape.1;
766
767 Self {
768 nrows,
769 ncols,
770 out_shape,
771 row_plan,
772 col_plan,
773 scratch_row: vec![0.0; ncols],
774 scratch_col: vec![Complex::new(0.0, 0.0); nrows],
775 intermediate: vec![Complex::new(0.0, 0.0); intermediate_len],
776 }
777 }
778 }
779
780 impl crate::fft_backend::R2cPlan2d for RealFftPlan2d {
781 fn process(&mut self, input: &[f64], output: &mut [Complex<f64>]) -> SpectrogramResult<()> {
782 crate::fft_backend::validate_fft_io_2d(self.nrows, self.ncols, input, output)?;
783
784 for row_idx in 0..self.nrows {
786 let row_start = row_idx * self.ncols;
787 let row_end = row_start + self.ncols;
788 self.scratch_row.copy_from_slice(&input[row_start..row_end]);
789
790 let out_start = row_idx * self.out_shape.1;
791 let out_end = out_start + self.out_shape.1;
792
793 self.row_plan
794 .process(
795 &mut self.scratch_row,
796 &mut self.intermediate[out_start..out_end],
797 )
798 .map_err(|e| SpectrogramError::fft_backend("realfft", format!("{e:?}")))?;
799 }
800
801 for col_idx in 0..self.out_shape.1 {
803 for row_idx in 0..self.nrows {
805 self.scratch_col[row_idx] =
806 self.intermediate[row_idx * self.out_shape.1 + col_idx];
807 }
808
809 self.col_plan.process(&mut self.scratch_col);
811
812 for row_idx in 0..self.nrows {
814 output[row_idx * self.out_shape.1 + col_idx] = self.scratch_col[row_idx];
815 }
816 }
817
818 Ok(())
819 }
820 }
821
822 impl crate::fft_backend::R2cPlanner2d for RealFftPlanner {
823 type Plan = RealFftPlan2d;
824
825 fn plan_r2c_2d(&mut self, nrows: usize, ncols: usize) -> SpectrogramResult<Self::Plan> {
826 let row_plan = self.get_or_create(ncols);
827 let col_plan = Self::get_or_create_complex(nrows);
828 Ok(RealFftPlan2d::new(nrows, ncols, row_plan, col_plan))
829 }
830 }
831
832 #[derive(Clone)]
833 pub struct RealFftInversePlan2d {
834 nrows: usize,
835 ncols: usize,
836 in_shape: (usize, usize),
837 col_plan: Arc<dyn rustfft::Fft<f64>>,
839 row_plan: Arc<dyn ComplexToReal<f64>>,
841 scratch_col: Vec<Complex<f64>>,
843 scratch_row: Vec<Complex<f64>>,
844 intermediate: Vec<Complex<f64>>,
846 }
847
848 impl RealFftInversePlan2d {
849 pub(crate) fn new(
850 nrows: usize,
851 ncols: usize,
852 col_plan: Arc<dyn rustfft::Fft<f64>>,
853 row_plan: Arc<dyn ComplexToReal<f64>>,
854 ) -> Self {
855 let in_shape = crate::fft_backend::r2c_output_size_2d(nrows, ncols);
856 let intermediate_len = nrows * in_shape.1;
857
858 Self {
859 nrows,
860 ncols,
861 in_shape,
862 col_plan,
863 row_plan,
864 scratch_col: vec![Complex::new(0.0, 0.0); nrows],
865 scratch_row: vec![Complex::new(0.0, 0.0); in_shape.1],
866 intermediate: vec![Complex::new(0.0, 0.0); intermediate_len],
867 }
868 }
869 }
870
871 impl crate::fft_backend::C2rPlan2d for RealFftInversePlan2d {
872 fn process(&mut self, input: &[Complex<f64>], output: &mut [f64]) -> SpectrogramResult<()> {
873 let expected_in_len = self.in_shape.0 * self.in_shape.1;
874 if input.len() != expected_in_len {
875 return Err(SpectrogramError::dimension_mismatch(
876 expected_in_len,
877 input.len(),
878 ));
879 }
880
881 let expected_out_len = self.nrows * self.ncols;
882 if output.len() != expected_out_len {
883 return Err(SpectrogramError::dimension_mismatch(
884 expected_out_len,
885 output.len(),
886 ));
887 }
888
889 self.intermediate.copy_from_slice(input);
891
892 for col_idx in 0..self.in_shape.1 {
894 for row_idx in 0..self.nrows {
896 self.scratch_col[row_idx] =
897 self.intermediate[row_idx * self.in_shape.1 + col_idx];
898 }
899
900 self.col_plan.process(&mut self.scratch_col);
902
903 for row_idx in 0..self.nrows {
905 self.intermediate[row_idx * self.in_shape.1 + col_idx] =
906 self.scratch_col[row_idx];
907 }
908 }
909
910 for row_idx in 0..self.nrows {
913 self.intermediate[row_idx * self.in_shape.1].im = 0.0;
915
916 if self.ncols.is_multiple_of(2) {
918 let nyquist_col = self.in_shape.1 - 1;
919 self.intermediate[row_idx * self.in_shape.1 + nyquist_col].im = 0.0;
920 }
921 }
922
923 for row_idx in 0..self.nrows {
925 let row_start = row_idx * self.in_shape.1;
926 let row_end = row_start + self.in_shape.1;
927 self.scratch_row
928 .copy_from_slice(&self.intermediate[row_start..row_end]);
929
930 let out_start = row_idx * self.ncols;
931 let out_end = out_start + self.ncols;
932
933 self.row_plan
934 .process(&mut self.scratch_row, &mut output[out_start..out_end])
935 .map_err(|e| SpectrogramError::fft_backend("realfft", format!("{e:?}")))?;
936 }
937
938 let scale = 1.0 / (self.nrows * self.ncols) as f64;
940 for val in output.iter_mut() {
941 *val *= scale;
942 }
943
944 Ok(())
945 }
946 }
947
948 impl crate::fft_backend::C2rPlanner2d for RealFftPlanner {
949 type Plan = RealFftInversePlan2d;
950
951 fn plan_c2r_2d(&mut self, nrows: usize, ncols: usize) -> SpectrogramResult<Self::Plan> {
952 let col_plan = Self::get_or_create_complex_inverse(nrows);
953 let row_plan = self.get_or_create_inverse(ncols);
954 Ok(RealFftInversePlan2d::new(nrows, ncols, col_plan, row_plan))
955 }
956 }
957}
958
959#[cfg(feature = "realfft")]
964pub mod plan_cache {
965 use super::{C2rPlanner, R2cPlanner, SpectrogramError, SpectrogramResult};
966
967 use std::collections::HashMap;
968 use std::sync::{Arc, Mutex};
969
970 use crate::fft_backend::realfft_backend::{RealFftInversePlan, RealFftPlan, RealFftPlanner};
971
972 static R2C_PLAN_CACHE: std::sync::LazyLock<Mutex<HashMap<usize, Arc<RealFftPlan>>>> =
975 std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
976
977 static C2R_PLAN_CACHE: std::sync::LazyLock<Mutex<HashMap<usize, Arc<RealFftInversePlan>>>> =
979 std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
980
981 const MAX_CACHED_PLANS: usize = 100;
984
985 pub fn get_or_create_r2c_plan(n_fft: usize) -> SpectrogramResult<Arc<RealFftPlan>> {
998 let mut cache = R2C_PLAN_CACHE.lock().map_err(|e| {
999 SpectrogramError::fft_backend("plan_cache", format!("mutex poisoned: {e}"))
1000 })?;
1001
1002 if let Some(plan) = cache.get(&n_fft) {
1004 return Ok(Arc::clone(plan));
1005 }
1006
1007 let mut planner = RealFftPlanner::new();
1009 let plan = planner.plan_r2c(n_fft)?;
1010 let plan = Arc::new(plan);
1011
1012 if cache.len() >= MAX_CACHED_PLANS {
1014 if let Some(key) = cache.keys().next().copied() {
1015 cache.remove(&key);
1016 }
1017 }
1018
1019 cache.insert(n_fft, Arc::clone(&plan));
1020 drop(cache);
1021 Ok(plan)
1022 }
1023
1024 #[inline]
1037 pub fn get_or_create_c2r_plan(n_fft: usize) -> SpectrogramResult<Arc<RealFftInversePlan>> {
1038 let mut cache = C2R_PLAN_CACHE.lock().map_err(|e| {
1039 SpectrogramError::fft_backend("plan_cache", format!("mutex poisoned: {e}"))
1040 })?;
1041
1042 if let Some(plan) = cache.get(&n_fft) {
1044 return Ok(Arc::clone(plan));
1045 }
1046
1047 let mut planner = RealFftPlanner::new();
1049 let plan = planner.plan_c2r(n_fft)?;
1050 let plan = Arc::new(plan);
1051
1052 if cache.len() >= MAX_CACHED_PLANS {
1054 if let Some(key) = cache.keys().next().copied() {
1055 cache.remove(&key);
1056 }
1057 }
1058
1059 cache.insert(n_fft, Arc::clone(&plan));
1060 drop(cache); Ok(plan)
1062 }
1063
1064 #[allow(unused)]
1073 #[inline]
1074 pub fn clear_plan_cache() {
1075 if let Ok(mut cache) = R2C_PLAN_CACHE.lock() {
1076 cache.clear();
1077 }
1078 if let Ok(mut cache) = C2R_PLAN_CACHE.lock() {
1079 cache.clear();
1080 }
1081 }
1082
1083 #[allow(unused)]
1087 #[inline]
1088 pub fn cache_stats() -> (usize, usize) {
1089 let r2c_count = R2C_PLAN_CACHE.lock().map(|c| c.len()).unwrap_or(0);
1090 let c2r_count = C2R_PLAN_CACHE.lock().map(|c| c.len()).unwrap_or(0);
1091 (r2c_count, c2r_count)
1092 }
1093}
1094
1095#[cfg(feature = "realfft")]
1096pub use plan_cache::{get_or_create_c2r_plan, get_or_create_r2c_plan};
1097
1098#[cfg(all(feature = "realfft", feature = "python"))]
1099pub use plan_cache::{cache_stats, clear_plan_cache};
1100
1101#[cfg(feature = "fftw")]
1102pub mod fftw_backend {
1103 use std::collections::HashMap;
1104 use std::ptr::NonNull;
1105 use std::sync::{Arc, Mutex};
1106
1107 use num_complex::Complex;
1108
1109 use crate::fft_backend::{
1110 C2rPlan, C2rPlanner, R2cPlan, R2cPlanner, r2c_output_size, validate_fft_io,
1111 };
1112 use crate::{SpectrogramError, SpectrogramResult};
1113
1114 static FFTW_PLANNER_LOCK: Mutex<()> = Mutex::new(());
1116
1117 #[derive(Debug)]
1118 struct FftwBuffer<T> {
1119 ptr: NonNull<T>,
1120 _len: usize,
1121 }
1122
1123 impl<T> FftwBuffer<T> {
1124 fn allocate(len: usize) -> SpectrogramResult<Self> {
1125 if len == 0 {
1126 return Err(SpectrogramError::invalid_input("buffer length must be > 0"));
1127 }
1128
1129 let bytes = core::mem::size_of::<T>() * len;
1130 let raw = unsafe { fftw_sys::fftw_malloc(bytes) }.cast::<T>();
1131
1132 let ptr = NonNull::new(raw).ok_or_else(|| {
1133 SpectrogramError::fft_backend("fftw", "fftw_malloc returned null")
1134 })?;
1135
1136 Ok(Self { ptr, _len: len })
1137 }
1138
1139 #[inline]
1140 const fn as_ptr(&self) -> *mut T {
1141 self.ptr.as_ptr()
1142 }
1143 }
1144
1145 impl<T> Drop for FftwBuffer<T> {
1146 fn drop(&mut self) {
1147 unsafe {
1148 fftw_sys::fftw_free(self.ptr.as_ptr().cast::<core::ffi::c_void>());
1149 }
1150 }
1151 }
1152
1153 #[derive(Debug)]
1154 pub(crate) struct PlanInner {
1155 n_fft: usize,
1156 out_len: usize,
1157 plan: fftw_sys::fftw_plan,
1158 input: Arc<FftwBuffer<f64>>,
1159 output: Arc<FftwBuffer<fftw_sys::fftw_complex>>,
1160 }
1161
1162 impl Drop for PlanInner {
1163 fn drop(&mut self) {
1164 unsafe {
1165 fftw_sys::fftw_destroy_plan(self.plan);
1166 }
1167 }
1168 }
1169
1170 #[derive(Debug)]
1171 pub(crate) struct InversePlanInner {
1172 n_fft: usize,
1173 in_len: usize,
1174 plan: fftw_sys::fftw_plan,
1175 input: Arc<FftwBuffer<fftw_sys::fftw_complex>>,
1176 output: Arc<FftwBuffer<f64>>,
1177 }
1178
1179 impl Drop for InversePlanInner {
1180 fn drop(&mut self) {
1181 unsafe {
1182 fftw_sys::fftw_destroy_plan(self.plan);
1183 }
1184 }
1185 }
1186
1187 #[derive(Default)]
1188 pub struct FftwPlanner {
1189 cache_r2c: HashMap<usize, Arc<PlanInner>>,
1190 cache_c2r: HashMap<usize, Arc<InversePlanInner>>,
1191 }
1192
1193 impl FftwPlanner {
1194 #[must_use]
1195 pub fn new() -> Self {
1196 Self::default()
1197 }
1198
1199 pub(crate) fn build_plan(n_fft: usize) -> SpectrogramResult<PlanInner> {
1200 let out_len = r2c_output_size(n_fft);
1201
1202 let input = Arc::new(FftwBuffer::<f64>::allocate(n_fft)?);
1203 let output = Arc::new(FftwBuffer::<fftw_sys::fftw_complex>::allocate(out_len)?);
1204
1205 let n_i32: i32 = n_fft
1206 .try_into()
1207 .map_err(|_| SpectrogramError::invalid_input("n_fft too large for FFTW"))?;
1208
1209 let _lock = FFTW_PLANNER_LOCK.lock().map_err(|e| {
1211 SpectrogramError::fft_backend("fftw", format!("FFT planner mutex poisoned: {}", e))
1212 })?;
1213
1214 let plan = unsafe {
1215 fftw_sys::fftw_plan_dft_r2c_1d(
1216 n_i32,
1217 input.as_ptr(),
1218 output.as_ptr(),
1219 fftw_sys::FFTW_ESTIMATE,
1220 )
1221 };
1222
1223 if plan.is_null() {
1224 return Err(SpectrogramError::fft_backend(
1225 "fftw",
1226 "failed to create FFTW plan",
1227 ));
1228 }
1229
1230 Ok(PlanInner {
1231 n_fft,
1232 out_len,
1233 plan,
1234 input,
1235 output,
1236 })
1237 }
1238
1239 pub(crate) fn get_or_create(&mut self, n_fft: usize) -> SpectrogramResult<Arc<PlanInner>> {
1240 if let Some(p) = self.cache_r2c.get(&n_fft) {
1241 return Ok(p.clone());
1242 }
1243
1244 let p = Arc::new(Self::build_plan(n_fft)?);
1245 self.cache_r2c.insert(n_fft, p.clone());
1246 Ok(p)
1247 }
1248
1249 pub(crate) fn build_inverse_plan(n_fft: usize) -> SpectrogramResult<InversePlanInner> {
1250 let in_len = r2c_output_size(n_fft);
1251
1252 let input = Arc::new(FftwBuffer::<fftw_sys::fftw_complex>::allocate(in_len)?);
1253 let output = Arc::new(FftwBuffer::<f64>::allocate(n_fft)?);
1254
1255 let n_i32: i32 = n_fft
1256 .try_into()
1257 .map_err(|_| SpectrogramError::invalid_input("n_fft too large for FFTW"))?;
1258
1259 let _lock = FFTW_PLANNER_LOCK.lock().map_err(|e| {
1261 SpectrogramError::fft_backend("fftw", format!("FFT planner mutex poisoned: {}", e))
1262 })?;
1263
1264 let plan = unsafe {
1265 fftw_sys::fftw_plan_dft_c2r_1d(
1266 n_i32,
1267 input.as_ptr(),
1268 output.as_ptr(),
1269 fftw_sys::FFTW_ESTIMATE,
1270 )
1271 };
1272
1273 if plan.is_null() {
1274 return Err(SpectrogramError::fft_backend(
1275 "fftw",
1276 "failed to create FFTW inverse plan",
1277 ));
1278 }
1279
1280 Ok(InversePlanInner {
1281 n_fft,
1282 in_len,
1283 plan,
1284 input,
1285 output,
1286 })
1287 }
1288
1289 pub(crate) fn get_or_create_inverse(
1290 &mut self,
1291 n_fft: usize,
1292 ) -> SpectrogramResult<Arc<InversePlanInner>> {
1293 if let Some(p) = self.cache_c2r.get(&n_fft) {
1294 return Ok(p.clone());
1295 }
1296
1297 let p = Arc::new(Self::build_inverse_plan(n_fft)?);
1298 self.cache_c2r.insert(n_fft, p.clone());
1299 Ok(p)
1300 }
1301 }
1302
1303 #[derive(Debug, Clone)]
1304 pub struct FftwPlan {
1305 inner: Arc<PlanInner>,
1306 }
1307
1308 impl FftwPlan {
1309 pub(crate) const fn new(plan: Arc<PlanInner>) -> Self {
1310 Self { inner: plan }
1311 }
1312 }
1313
1314 impl R2cPlan for FftwPlan {
1315 fn n_fft(&self) -> usize {
1316 self.inner.n_fft
1317 }
1318
1319 fn output_len(&self) -> usize {
1320 self.inner.out_len
1321 }
1322
1323 fn process(&mut self, input: &[f64], output: &mut [Complex<f64>]) -> SpectrogramResult<()> {
1324 validate_fft_io(self.inner.n_fft, input, output)?;
1325
1326 unsafe {
1327 core::ptr::copy_nonoverlapping(
1328 input.as_ptr(),
1329 self.inner.input.as_ptr(),
1330 self.inner.n_fft,
1331 );
1332
1333 fftw_sys::fftw_execute_dft_r2c(
1334 self.inner.plan,
1335 self.inner.input.as_ptr(),
1336 self.inner.output.as_ptr(),
1337 );
1338
1339 for i in 0..self.inner.out_len {
1340 let c = self.inner.output.as_ptr().add(i) as *const f64;
1341 let re = *c.add(0);
1342 let im = *c.add(1);
1343 output[i] = Complex::new(re, im);
1344 }
1345 }
1346
1347 Ok(())
1348 }
1349 }
1350
1351 impl R2cPlanner for FftwPlanner {
1352 type Plan = FftwPlan;
1353
1354 fn plan_r2c(&mut self, n_fft: usize) -> SpectrogramResult<Self::Plan> {
1355 Ok(FftwPlan {
1356 inner: self.get_or_create(n_fft)?,
1357 })
1358 }
1359 }
1360
1361 #[derive(Debug, Clone)]
1362 pub struct FftwInversePlan {
1363 inner: Arc<InversePlanInner>,
1364 }
1365
1366 impl C2rPlan for FftwInversePlan {
1367 fn n_fft(&self) -> usize {
1368 self.inner.n_fft
1369 }
1370
1371 fn input_len(&self) -> usize {
1372 self.inner.in_len
1373 }
1374
1375 fn process(&mut self, input: &[Complex<f64>], output: &mut [f64]) -> SpectrogramResult<()> {
1376 if input.len() != self.inner.in_len {
1377 return Err(SpectrogramError::dimension_mismatch(
1378 self.inner.in_len,
1379 input.len(),
1380 ));
1381 }
1382 if output.len() != self.inner.n_fft {
1383 return Err(SpectrogramError::dimension_mismatch(
1384 self.inner.n_fft,
1385 output.len(),
1386 ));
1387 }
1388
1389 unsafe {
1390 for i in 0..self.inner.in_len {
1392 let ptr = self.inner.input.as_ptr().add(i).cast::<f64>();
1393 *ptr.add(0) = input[i].re;
1394 *ptr.add(1) = input[i].im;
1395 }
1396
1397 fftw_sys::fftw_execute_dft_c2r(
1399 self.inner.plan,
1400 self.inner.input.as_ptr(),
1401 self.inner.output.as_ptr(),
1402 );
1403
1404 let scale = 1.0 / self.inner.n_fft as f64;
1406 for i in 0..self.inner.n_fft {
1407 output[i] = *self.inner.output.as_ptr().add(i) * scale;
1408 }
1409 }
1410
1411 Ok(())
1412 }
1413 }
1414
1415 impl C2rPlanner for FftwPlanner {
1416 type Plan = FftwInversePlan;
1417
1418 fn plan_c2r(&mut self, n_fft: usize) -> SpectrogramResult<Self::Plan> {
1419 Ok(FftwInversePlan {
1420 inner: self.get_or_create_inverse(n_fft)?,
1421 })
1422 }
1423 }
1424
1425 struct FftwC2cInner {
1431 n_fft: usize,
1432 fwd: fftw_sys::fftw_plan,
1433 inv: fftw_sys::fftw_plan,
1434 buf: Arc<FftwBuffer<fftw_sys::fftw_complex>>,
1435 }
1436
1437 impl Drop for FftwC2cInner {
1438 fn drop(&mut self) {
1439 unsafe {
1440 fftw_sys::fftw_destroy_plan(self.fwd);
1441 fftw_sys::fftw_destroy_plan(self.inv);
1442 }
1443 }
1444 }
1445
1446 pub struct FftwC2cPlan {
1451 inner: Arc<FftwC2cInner>,
1452 }
1453
1454 unsafe impl Send for FftwC2cPlan {}
1456
1457 impl FftwPlanner {
1458 pub(crate) fn build_c2c_plan(n_fft: usize) -> SpectrogramResult<FftwC2cInner> {
1460 let buf = Arc::new(FftwBuffer::<fftw_sys::fftw_complex>::allocate(n_fft)?);
1461 let ptr = buf.as_ptr();
1462
1463 let n_i32: i32 = n_fft
1464 .try_into()
1465 .map_err(|_| SpectrogramError::invalid_input("n_fft too large for FFTW"))?;
1466
1467 let _lock = FFTW_PLANNER_LOCK.lock().map_err(|e| {
1468 SpectrogramError::fft_backend("fftw", format!("FFT planner mutex poisoned: {}", e))
1469 })?;
1470
1471 let fwd = unsafe {
1472 fftw_sys::fftw_plan_dft_1d(
1473 n_i32,
1474 ptr,
1475 ptr,
1476 fftw_sys::FFTW_FORWARD as i32,
1477 fftw_sys::FFTW_ESTIMATE,
1478 )
1479 };
1480 if fwd.is_null() {
1481 return Err(SpectrogramError::fft_backend(
1482 "fftw",
1483 "failed to create FFTW C2c forward plan",
1484 ));
1485 }
1486
1487 let inv = unsafe {
1488 fftw_sys::fftw_plan_dft_1d(
1489 n_i32,
1490 ptr,
1491 ptr,
1492 fftw_sys::FFTW_BACKWARD as i32,
1493 fftw_sys::FFTW_ESTIMATE,
1494 )
1495 };
1496 if inv.is_null() {
1497 unsafe { fftw_sys::fftw_destroy_plan(fwd) };
1498 return Err(SpectrogramError::fft_backend(
1499 "fftw",
1500 "failed to create FFTW C2c inverse plan",
1501 ));
1502 }
1503
1504 Ok(FftwC2cInner {
1505 n_fft,
1506 fwd,
1507 inv,
1508 buf,
1509 })
1510 }
1511
1512 pub fn plan_c2c(&mut self, n_fft: usize) -> SpectrogramResult<FftwC2cPlan> {
1514 Ok(FftwC2cPlan {
1515 inner: Arc::new(Self::build_c2c_plan(n_fft)?),
1516 })
1517 }
1518 }
1519
1520 impl crate::fft_backend::C2cPlan for FftwC2cPlan {
1521 fn n_fft(&self) -> usize {
1522 self.inner.n_fft
1523 }
1524
1525 fn forward(&mut self, buf: &mut [Complex<f64>]) -> SpectrogramResult<()> {
1526 let n = self.inner.n_fft;
1527 if buf.len() != n {
1528 return Err(SpectrogramError::dimension_mismatch(n, buf.len()));
1529 }
1530 unsafe {
1531 let ptr = self.inner.buf.as_ptr();
1532 for i in 0..n {
1533 let dst = ptr.add(i).cast::<f64>();
1534 *dst = buf[i].re;
1535 *dst.add(1) = buf[i].im;
1536 }
1537 fftw_sys::fftw_execute_dft(self.inner.fwd, ptr, ptr);
1538 for i in 0..n {
1539 let src = ptr.add(i).cast::<f64>();
1540 buf[i] = Complex::new(*src, *src.add(1));
1541 }
1542 }
1543 Ok(())
1544 }
1545
1546 fn inverse(&mut self, buf: &mut [Complex<f64>]) -> SpectrogramResult<()> {
1547 let n = self.inner.n_fft;
1548 if buf.len() != n {
1549 return Err(SpectrogramError::dimension_mismatch(n, buf.len()));
1550 }
1551 unsafe {
1552 let ptr = self.inner.buf.as_ptr();
1553 for i in 0..n {
1554 let dst = ptr.add(i).cast::<f64>();
1555 *dst = buf[i].re;
1556 *dst.add(1) = buf[i].im;
1557 }
1558 fftw_sys::fftw_execute_dft(self.inner.inv, ptr, ptr);
1559 for i in 0..n {
1560 let src = ptr.add(i).cast::<f64>();
1561 buf[i] = Complex::new(*src, *src.add(1));
1562 }
1563 }
1564 Ok(())
1565 }
1566 }
1567
1568 #[derive(Debug)]
1573 pub(crate) struct PlanInner2d {
1574 nrows: usize,
1575 ncols: usize,
1576 out_shape: (usize, usize),
1577 plan: fftw_sys::fftw_plan,
1578 input: Arc<FftwBuffer<f64>>,
1579 output: Arc<FftwBuffer<fftw_sys::fftw_complex>>,
1580 }
1581
1582 impl Drop for PlanInner2d {
1583 fn drop(&mut self) {
1584 unsafe {
1585 fftw_sys::fftw_destroy_plan(self.plan);
1586 }
1587 }
1588 }
1589
1590 #[derive(Debug)]
1591 pub(crate) struct InversePlanInner2d {
1592 nrows: usize,
1593 ncols: usize,
1594 in_shape: (usize, usize),
1595 plan: fftw_sys::fftw_plan,
1596 input: Arc<FftwBuffer<fftw_sys::fftw_complex>>,
1597 output: Arc<FftwBuffer<f64>>,
1598 }
1599
1600 impl Drop for InversePlanInner2d {
1601 fn drop(&mut self) {
1602 unsafe {
1603 fftw_sys::fftw_destroy_plan(self.plan);
1604 }
1605 }
1606 }
1607
1608 impl FftwPlanner {
1609 pub(crate) fn build_plan_2d(nrows: usize, ncols: usize) -> SpectrogramResult<PlanInner2d> {
1610 let out_shape = crate::fft_backend::r2c_output_size_2d(nrows, ncols);
1611 let input_len = nrows * ncols;
1612 let output_len = out_shape.0 * out_shape.1;
1613
1614 let input = Arc::new(FftwBuffer::<f64>::allocate(input_len)?);
1615 let output = Arc::new(FftwBuffer::<fftw_sys::fftw_complex>::allocate(output_len)?);
1616
1617 let n0: i32 = nrows
1618 .try_into()
1619 .map_err(|_| SpectrogramError::invalid_input("nrows too large for FFTW"))?;
1620 let n1: i32 = ncols
1621 .try_into()
1622 .map_err(|_| SpectrogramError::invalid_input("ncols too large for FFTW"))?;
1623
1624 let _lock = FFTW_PLANNER_LOCK.lock().map_err(|e| {
1626 SpectrogramError::fft_backend("fftw", format!("FFT planner mutex poisoned: {}", e))
1627 })?;
1628
1629 let plan = unsafe {
1630 fftw_sys::fftw_plan_dft_r2c_2d(
1631 n0,
1632 n1,
1633 input.as_ptr(),
1634 output.as_ptr(),
1635 fftw_sys::FFTW_ESTIMATE,
1636 )
1637 };
1638
1639 if plan.is_null() {
1640 return Err(SpectrogramError::fft_backend(
1641 "fftw",
1642 "failed to create FFTW 2D plan",
1643 ));
1644 }
1645
1646 Ok(PlanInner2d {
1647 nrows,
1648 ncols,
1649 out_shape,
1650 plan,
1651 input,
1652 output,
1653 })
1654 }
1655
1656 pub(crate) fn get_or_create_2d(
1657 &mut self,
1658 nrows: usize,
1659 ncols: usize,
1660 ) -> SpectrogramResult<Arc<PlanInner2d>> {
1661 let p = Arc::new(Self::build_plan_2d(nrows, ncols)?);
1665 Ok(p)
1666 }
1667
1668 pub(crate) fn build_inverse_plan_2d(
1669 nrows: usize,
1670 ncols: usize,
1671 ) -> SpectrogramResult<InversePlanInner2d> {
1672 let in_shape = crate::fft_backend::r2c_output_size_2d(nrows, ncols);
1673 let input_len = in_shape.0 * in_shape.1;
1674 let output_len = nrows * ncols;
1675
1676 let input = Arc::new(FftwBuffer::<fftw_sys::fftw_complex>::allocate(input_len)?);
1677 let output = Arc::new(FftwBuffer::<f64>::allocate(output_len)?);
1678
1679 let n0: i32 = nrows
1680 .try_into()
1681 .map_err(|_| SpectrogramError::invalid_input("nrows too large for FFTW"))?;
1682 let n1: i32 = ncols
1683 .try_into()
1684 .map_err(|_| SpectrogramError::invalid_input("ncols too large for FFTW"))?;
1685
1686 let _lock = FFTW_PLANNER_LOCK.lock().map_err(|e| {
1688 SpectrogramError::fft_backend("fftw", format!("FFT planner mutex poisoned: {}", e))
1689 })?;
1690
1691 let plan = unsafe {
1692 fftw_sys::fftw_plan_dft_c2r_2d(
1693 n0,
1694 n1,
1695 input.as_ptr(),
1696 output.as_ptr(),
1697 fftw_sys::FFTW_ESTIMATE,
1698 )
1699 };
1700
1701 if plan.is_null() {
1702 return Err(SpectrogramError::fft_backend(
1703 "fftw",
1704 "failed to create FFTW 2D inverse plan",
1705 ));
1706 }
1707
1708 Ok(InversePlanInner2d {
1709 nrows,
1710 ncols,
1711 in_shape,
1712 plan,
1713 input,
1714 output,
1715 })
1716 }
1717
1718 pub(crate) fn get_or_create_inverse_2d(
1719 &mut self,
1720 nrows: usize,
1721 ncols: usize,
1722 ) -> SpectrogramResult<Arc<InversePlanInner2d>> {
1723 let p = Arc::new(Self::build_inverse_plan_2d(nrows, ncols)?);
1727 Ok(p)
1728 }
1729 }
1730
1731 #[derive(Debug, Clone)]
1732 pub struct FftwPlan2d {
1733 inner: Arc<PlanInner2d>,
1734 }
1735
1736 impl crate::fft_backend::R2cPlan2d for FftwPlan2d {
1737 fn process(&mut self, input: &[f64], output: &mut [Complex<f64>]) -> SpectrogramResult<()> {
1738 crate::fft_backend::validate_fft_io_2d(
1739 self.inner.nrows,
1740 self.inner.ncols,
1741 input,
1742 output,
1743 )?;
1744
1745 unsafe {
1746 core::ptr::copy_nonoverlapping(
1748 input.as_ptr(),
1749 self.inner.input.as_ptr(),
1750 input.len(),
1751 );
1752
1753 fftw_sys::fftw_execute_dft_r2c(
1755 self.inner.plan,
1756 self.inner.input.as_ptr(),
1757 self.inner.output.as_ptr(),
1758 );
1759
1760 let output_len = self.inner.out_shape.0 * self.inner.out_shape.1;
1762 for i in 0..output_len {
1763 let c = self.inner.output.as_ptr().add(i) as *const f64;
1764 let re = *c.add(0);
1765 let im = *c.add(1);
1766 output[i] = Complex::new(re, im);
1767 }
1768 }
1769
1770 Ok(())
1771 }
1772 }
1773
1774 impl crate::fft_backend::R2cPlanner2d for FftwPlanner {
1775 type Plan = FftwPlan2d;
1776
1777 fn plan_r2c_2d(&mut self, nrows: usize, ncols: usize) -> SpectrogramResult<Self::Plan> {
1778 Ok(FftwPlan2d {
1779 inner: self.get_or_create_2d(nrows, ncols)?,
1780 })
1781 }
1782 }
1783
1784 #[derive(Debug, Clone)]
1785 pub struct FftwInversePlan2d {
1786 inner: Arc<InversePlanInner2d>,
1787 }
1788
1789 impl crate::fft_backend::C2rPlan2d for FftwInversePlan2d {
1790 fn process(&mut self, input: &[Complex<f64>], output: &mut [f64]) -> SpectrogramResult<()> {
1791 let expected_in_len = self.inner.in_shape.0 * self.inner.in_shape.1;
1792 if input.len() != expected_in_len {
1793 return Err(SpectrogramError::dimension_mismatch(
1794 expected_in_len,
1795 input.len(),
1796 ));
1797 }
1798
1799 let expected_out_len = self.inner.nrows * self.inner.ncols;
1800 if output.len() != expected_out_len {
1801 return Err(SpectrogramError::dimension_mismatch(
1802 expected_out_len,
1803 output.len(),
1804 ));
1805 }
1806
1807 unsafe {
1808 for i in 0..input.len() {
1810 let ptr = self.inner.input.as_ptr().add(i).cast::<f64>();
1811 *ptr.add(0) = input[i].re;
1812 *ptr.add(1) = input[i].im;
1813 }
1814
1815 fftw_sys::fftw_execute_dft_c2r(
1817 self.inner.plan,
1818 self.inner.input.as_ptr(),
1819 self.inner.output.as_ptr(),
1820 );
1821
1822 let scale = 1.0 / (self.inner.nrows * self.inner.ncols) as f64;
1824 for i in 0..expected_out_len {
1825 output[i] = *self.inner.output.as_ptr().add(i) * scale;
1826 }
1827 }
1828
1829 Ok(())
1830 }
1831 }
1832
1833 impl crate::fft_backend::C2rPlanner2d for FftwPlanner {
1834 type Plan = FftwInversePlan2d;
1835
1836 fn plan_c2r_2d(&mut self, nrows: usize, ncols: usize) -> SpectrogramResult<Self::Plan> {
1837 Ok(FftwInversePlan2d {
1838 inner: self.get_or_create_inverse_2d(nrows, ncols)?,
1839 })
1840 }
1841 }
1842}
1843
1844#[cfg(all(test, feature = "realfft"))]
1845mod tests_c2c {
1846 use super::realfft_backend::{RealFftC2cPlan, RealFftC2cPlanF32};
1847 use crate::fft_backend::{C2cPlan, C2cPlanF32};
1848 use num_complex::Complex;
1849
1850 #[test]
1852 fn c2c_f64_dc_signal() {
1853 let n = 8;
1854 let mut plan = RealFftC2cPlan::new(n);
1855 let mut buf: Vec<Complex<f64>> = vec![Complex::new(1.0, 0.0); n];
1856 plan.forward(&mut buf).unwrap();
1857 assert!((buf[0].re - n as f64).abs() < 1e-10, "DC bin should be N");
1858 assert!(buf[0].im.abs() < 1e-10);
1859 for k in 1..n {
1860 assert!(buf[k].norm() < 1e-10, "bin {k} should be zero");
1861 }
1862 }
1863
1864 #[test]
1866 fn c2c_f64_round_trip() {
1867 let n = 16;
1868 let original: Vec<Complex<f64>> = (0..n)
1869 .map(|i| Complex::new(i as f64, -(i as f64)))
1870 .collect();
1871 let mut buf = original.clone();
1872 let mut plan = RealFftC2cPlan::new(n);
1873 plan.forward(&mut buf).unwrap();
1874 plan.inverse(&mut buf).unwrap();
1875 let scale = 1.0 / n as f64;
1876 for (a, b) in buf.iter().zip(original.iter()) {
1877 assert!((a.re * scale - b.re).abs() < 1e-10);
1878 assert!((a.im * scale - b.im).abs() < 1e-10);
1879 }
1880 }
1881
1882 #[test]
1884 fn c2c_f32_round_trip() {
1885 let n = 16;
1886 let original: Vec<Complex<f32>> = (0..n)
1887 .map(|i| Complex::new(i as f32, -(i as f32)))
1888 .collect();
1889 let mut buf = original.clone();
1890 let mut plan = RealFftC2cPlanF32::new(n);
1891 plan.forward(&mut buf).unwrap();
1892 plan.inverse(&mut buf).unwrap();
1893 let scale = 1.0f32 / n as f32;
1894 for (a, b) in buf.iter().zip(original.iter()) {
1895 assert!((a.re * scale - b.re).abs() < 1e-5);
1896 assert!((a.im * scale - b.im).abs() < 1e-5);
1897 }
1898 }
1899
1900 #[test]
1902 fn c2c_f64_dimension_mismatch() {
1903 let mut plan = RealFftC2cPlan::new(8);
1904 let mut buf = vec![Complex::new(0.0, 0.0); 7]; assert!(plan.forward(&mut buf).is_err());
1906 assert!(plan.inverse(&mut buf).is_err());
1907 }
1908}