1use crate::C64;
25use num_complex::Complex32 as C32;
26use rustfft::FftPlanner;
27use std::cell::RefCell;
28use std::collections::HashMap;
29use std::sync::Arc;
30
31#[derive(Clone)]
33pub struct Fft {
34 n: usize,
35 fwd: Arc<dyn rustfft::Fft<f64>>,
36 inv: Arc<dyn rustfft::Fft<f64>>,
37 fwd_scratch: usize,
39 inv_scratch: usize,
40}
41
42impl std::fmt::Debug for Fft {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 write!(f, "Fft(n = {})", self.n)
45 }
46}
47
48thread_local! {
49 static PLANS: RefCell<(FftPlanner<f64>, HashMap<usize, Fft>)> = RefCell::new((FftPlanner::new(), HashMap::new()));
50 static SCRATCH: RefCell<Vec<C64>> = const { RefCell::new(Vec::new()) };
52 static PLANS_F32: RefCell<(FftPlanner<f32>, HashMap<usize, FftF32>)> =
54 RefCell::new((FftPlanner::new(), HashMap::new()));
55 static SCRATCH_F32: RefCell<Vec<C32>> = const { RefCell::new(Vec::new()) };
56}
57
58fn with_scratch(len: usize, f: impl FnOnce(&mut [C64])) {
60 SCRATCH.with(|c| {
61 if let Ok(mut s) = c.try_borrow_mut() {
64 if s.len() < len {
65 s.resize(len, C64::new(0.0, 0.0));
66 }
67 f(&mut s[..len]);
68 } else {
69 f(&mut vec![C64::new(0.0, 0.0); len]);
70 }
71 });
72}
73
74#[derive(Clone)]
78pub(crate) struct FftF32 {
79 fwd: Arc<dyn rustfft::Fft<f32>>,
80 inv: Arc<dyn rustfft::Fft<f32>>,
81 scratch: usize,
82}
83
84pub(crate) fn plan_f32(n: usize) -> FftF32 {
86 PLANS_F32.with(|cell| {
87 let mut guard = cell.borrow_mut();
88 let (planner, cache) = &mut *guard;
89 if let Some(p) = cache.get(&n) {
90 return p.clone();
91 }
92 let (fwd, inv) = (planner.plan_fft_forward(n), planner.plan_fft_inverse(n));
93 let scratch = fwd.get_inplace_scratch_len().max(inv.get_inplace_scratch_len());
94 let p = FftF32 { fwd, inv, scratch };
95 cache.insert(n, p.clone());
96 p
97 })
98}
99
100impl FftF32 {
101 fn with_scratch(&self, f: impl FnOnce(&mut [C32])) {
103 SCRATCH_F32.with(|c| {
104 if let Ok(mut s) = c.try_borrow_mut() {
105 if s.len() < self.scratch {
106 s.resize(self.scratch, C32::new(0.0, 0.0));
107 }
108 f(&mut s[..self.scratch]);
109 } else {
110 f(&mut vec![C32::new(0.0, 0.0); self.scratch]);
111 }
112 });
113 }
114
115 pub(crate) fn forward(&self, x: &mut [C32]) {
117 self.with_scratch(|s| self.fwd.process_with_scratch(x, s));
118 }
119
120 pub(crate) fn inverse_unnormalized(&self, x: &mut [C32]) {
122 self.with_scratch(|s| self.inv.process_with_scratch(x, s));
123 }
124}
125
126pub fn plan(n: usize) -> Fft {
128 assert!(n >= 1, "FFT length must be positive");
129 PLANS.with(|cell| {
130 let mut guard = cell.borrow_mut();
131 let (planner, cache) = &mut *guard;
132 if let Some(p) = cache.get(&n) {
133 return p.clone();
134 }
135 let (fwd, inv) = (planner.plan_fft_forward(n), planner.plan_fft_inverse(n));
136 let (fwd_scratch, inv_scratch) = (fwd.get_inplace_scratch_len(), inv.get_inplace_scratch_len());
137 let p = Fft { n, fwd, inv, fwd_scratch, inv_scratch };
138 cache.insert(n, p.clone());
139 p
140 })
141}
142
143impl Fft {
144 pub fn len(&self) -> usize {
146 self.n
147 }
148
149 pub fn is_empty(&self) -> bool {
151 self.n == 0
152 }
153
154 pub fn forward(&self, x: &mut [C64]) {
159 assert_eq!(x.len(), self.n, "FFT length mismatch");
160 with_scratch(self.fwd_scratch, |s| self.fwd.process_with_scratch(x, s));
161 }
162
163 pub fn inverse(&self, x: &mut [C64]) {
168 assert_eq!(x.len(), self.n, "FFT length mismatch");
169 with_scratch(self.inv_scratch, |s| self.inv.process_with_scratch(x, s));
170 let s = 1.0 / self.n as f64;
171 for v in x.iter_mut() {
172 *v *= s;
173 }
174 }
175}
176
177pub fn fft(x: &[C64]) -> Vec<C64> {
179 let mut y = x.to_vec();
180 if !y.is_empty() {
181 plan(y.len()).forward(&mut y);
182 }
183 y
184}
185
186pub fn ifft(x: &[C64]) -> Vec<C64> {
188 let mut y = x.to_vec();
189 if !y.is_empty() {
190 plan(y.len()).inverse(&mut y);
191 }
192 y
193}
194
195pub fn fft_unitary_in_place(x: &mut [C64]) {
209 if x.is_empty() {
210 return;
211 }
212 plan(x.len()).forward(x);
213 let s = 1.0 / (x.len() as f64).sqrt();
214 for v in x.iter_mut() {
215 *v *= s;
216 }
217}
218
219pub fn ifft_unitary_in_place(x: &mut [C64]) {
221 if x.is_empty() {
222 return;
223 }
224 plan(x.len()).inverse(x);
225 let s = (x.len() as f64).sqrt();
226 for v in x.iter_mut() {
227 *v *= s;
228 }
229}
230
231pub fn centred_bin(i: usize, k: usize, n: usize) -> usize {
241 (i as isize - (k / 2) as isize).rem_euclid(n as isize) as usize
242}
243
244pub fn fftshift<T: Copy>(x: &[T]) -> Vec<T> {
254 let n = x.len();
255 (0..n).map(|i| x[(i + n.div_ceil(2)) % n]).collect()
256}
257
258pub fn filter(x: &[C64], h: impl IntoIterator<Item = C64>) -> Vec<C64> {
275 let mut y = fft(x);
276 let mut h = h.into_iter();
277 for v in &mut y {
278 match h.next() {
279 Some(hk) => *v *= hk,
280 None => panic!("fft::filter: fewer response values than the {} samples", x.len()),
281 }
282 }
283 assert!(h.next().is_none(), "fft::filter: more response values than the {} samples", x.len());
284 if !y.is_empty() {
285 plan(y.len()).inverse(&mut y);
286 }
287 y
288}
289
290pub fn fftfreq(n: usize, spacing_s: f64) -> Vec<f64> {
293 (0..n)
294 .map(|k| {
295 let s = if k < n.div_ceil(2) { k as f64 } else { k as f64 - n as f64 };
296 s / (n as f64 * spacing_s)
297 })
298 .collect()
299}
300
301pub fn angular_frequencies(n: usize, fs: f64) -> Vec<f64> {
303 fftfreq(n, 1.0 / fs).into_iter().map(|f| 2.0 * std::f64::consts::PI * f).collect()
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
314 fn the_shared_work_space_gives_the_bits_of_rustfft_process() {
315 let mut planner = FftPlanner::<f64>::new();
316 for &n in &[1, 2, 7, 64, 97, 360, 1024, 1031, 12, 4093, 3] {
317 let x: Vec<C64> = (0..n).map(|k| C64::new((k as f64 * 0.37).sin(), (k as f64 * 1.3).cos())).collect();
318 let p = plan(n);
319 let (mut a, mut b) = (x.clone(), x.clone());
320 p.forward(&mut a);
321 planner.plan_fft_forward(n).process(&mut b);
322 assert!(
323 a.iter().zip(&b).all(|(u, v)| u.re.to_bits() == v.re.to_bits() && u.im.to_bits() == v.im.to_bits())
324 );
325 let (mut a, mut b) = (x.clone(), x.clone());
326 p.inverse(&mut a);
327 planner.plan_fft_inverse(n).process(&mut b);
328 let s = 1.0 / n as f64;
329 assert!(a.iter().zip(&b).all(|(u, v)| *u == v * s));
330 }
331 }
332
333 #[test]
334 #[should_panic(expected = "fewer response values")]
335 fn filter_rejects_a_short_response_instead_of_leaving_bins_unfiltered() {
336 let _ = filter(&[C64::new(1.0, 0.0); 4], vec![C64::new(0.0, 0.0); 3]);
337 }
338
339 #[test]
340 #[should_panic(expected = "more response values")]
341 fn filter_rejects_a_long_response() {
342 let _ = filter(&[C64::new(1.0, 0.0); 4], vec![C64::new(0.0, 0.0); 5]);
343 }
344}