1use num_complex::Complex;
8use rill_core::Transcendental;
9
10use crate::complex_fft::ComplexFft;
11
12pub struct RealFft<T: Transcendental> {
23 size: usize,
24 half_size: usize,
25 complex_fft: ComplexFft<T>,
26 scratch: Vec<Complex<T>>,
27}
28
29impl<T: Transcendental> RealFft<T> {
30 pub fn new(size: usize) -> Self {
36 assert!(
37 size.is_power_of_two(),
38 "FFT size must be a power of two, got {size}"
39 );
40 assert!(size >= 4, "FFT size must be at least 4, got {size}");
41
42 let half_size = size / 2;
43 let complex_fft = ComplexFft::new(half_size);
44 let scratch = vec![Complex::new(T::ZERO, T::ZERO); half_size];
45
46 Self {
47 size,
48 half_size,
49 complex_fft,
50 scratch,
51 }
52 }
53
54 pub fn size(&self) -> usize {
56 self.size
57 }
58
59 pub fn forward(&mut self, input: &[T], output: &mut [Complex<T>]) {
67 assert_eq!(
68 input.len(),
69 self.size,
70 "input length ({}) must match FFT size ({})",
71 input.len(),
72 self.size
73 );
74 assert_eq!(
75 output.len(),
76 self.half_size + 1,
77 "output length ({}) must be half_size + 1 ({})",
78 output.len(),
79 self.half_size + 1
80 );
81
82 for i in 0..self.half_size {
83 self.scratch[i] = Complex::new(input[2 * i], input[2 * i + 1]);
84 }
85
86 self.complex_fft.forward(&mut self.scratch);
87
88 let z = &self.scratch;
89 let z0 = z[0];
90 output[0] = Complex::new(z0.re + z0.im, T::ZERO);
91 output[self.half_size] = Complex::new(z0.re - z0.im, T::ZERO);
92
93 let twopi = T::from_f64(2.0 * std::f64::consts::PI);
94 let size_t = T::from_usize(self.size);
95 for k in 1..self.half_size {
96 let theta = twopi * T::from_usize(k) / size_t;
97 let w_cos = theta.cos();
98 let w_sin = theta.sin();
99
100 let a = z[k];
101 let b = Complex::new(z[self.half_size - k].re, -z[self.half_size - k].im);
102
103 let c_re = a.re + b.re;
104 let c_im = a.im + b.im;
105 let d_re = a.re - b.re;
106 let d_im = a.im - b.im;
107
108 let half = T::from_f64(0.5);
109 output[k].re = half * (c_re - w_sin * d_re + w_cos * d_im);
110 output[k].im = half * (c_im - w_sin * d_im - w_cos * d_re);
111 }
112 }
113
114 pub fn inverse(&mut self, input: &[Complex<T>], output: &mut [T]) {
123 assert_eq!(
124 input.len(),
125 self.half_size + 1,
126 "input length ({}) must be half_size + 1 ({})",
127 input.len(),
128 self.half_size + 1
129 );
130 assert_eq!(
131 output.len(),
132 self.size,
133 "output length ({}) must match FFT size ({})",
134 output.len(),
135 self.size
136 );
137
138 let x = input;
139
140 let half = T::from_f64(0.5);
141 self.scratch[0] = Complex::new(
142 half * (x[0].re + x[self.half_size].re),
143 half * (x[0].re - x[self.half_size].re),
144 );
145
146 let twopi = T::from_f64(2.0 * std::f64::consts::PI);
147 let size_t = T::from_usize(self.size);
148 for k in 1..self.half_size {
149 let theta = twopi * T::from_usize(k) / size_t;
150 let w_cos = theta.cos();
151 let w_sin = theta.sin();
152
153 let a = x[k];
154 let b = Complex::new(x[self.half_size - k].re, -x[self.half_size - k].im);
155
156 let c_re = a.re + b.re;
157 let c_im = a.im + b.im;
158 let d_re = a.re - b.re;
159 let d_im = a.im - b.im;
160
161 let half = T::from_f64(0.5);
162 self.scratch[k].re = half * (c_re - w_sin * d_re - w_cos * d_im);
163 self.scratch[k].im = half * (c_im - w_sin * d_im + w_cos * d_re);
164 }
165
166 self.complex_fft.inverse(&mut self.scratch);
167
168 for i in 0..self.half_size {
169 output[2 * i] = self.scratch[i].re;
170 output[2 * i + 1] = self.scratch[i].im;
171 }
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_real_fft_size_4_roundtrip() {
181 let mut fft = RealFft::<f32>::new(4);
182 let input = [1.0f32, 2.0, 3.0, 4.0];
183 let mut spectrum = vec![Complex::new(0.0, 0.0); 3];
184 let mut output = [0.0f32; 4];
185
186 fft.forward(&input, &mut spectrum);
187 fft.inverse(&spectrum, &mut output);
188
189 for (a, b) in input.iter().zip(output.iter()) {
190 assert!((a - b).abs() < 1e-3, "expected {a}, got {b}");
191 }
192 }
193
194 #[test]
195 fn test_real_fft_size_8_roundtrip() {
196 let mut fft = RealFft::<f32>::new(8);
197 let input: Vec<f32> = (0..8).map(|i| (i as f32 * 0.7).sin()).collect();
198 let mut spectrum = vec![Complex::new(0.0, 0.0); 5];
199 let mut output = vec![0.0f32; 8];
200
201 fft.forward(&input, &mut spectrum);
202 fft.inverse(&spectrum, &mut output);
203
204 for (a, b) in input.iter().zip(output.iter()) {
205 assert!((a - b).abs() < 1e-3, "expected {a}, got {b}");
206 }
207 }
208
209 #[test]
210 fn test_real_fft_size_1024_roundtrip() {
211 let mut fft = RealFft::<f32>::new(1024);
212 let input: Vec<f32> = (0..1024)
213 .map(|i| {
214 let x = i as f32 * 0.05;
215 x.sin() + 0.5 * (x * 2.3).cos()
216 })
217 .collect();
218 let mut spectrum = vec![Complex::new(0.0, 0.0); 513];
219 let mut output = vec![0.0f32; 1024];
220
221 fft.forward(&input, &mut spectrum);
222 fft.inverse(&spectrum, &mut output);
223
224 for (a, b) in input.iter().zip(output.iter()) {
225 assert!((a - b).abs() < 5e-4, "at index: expected {a}, got {b}");
226 }
227 }
228
229 #[test]
230 fn test_real_fft_size_16_roundtrip() {
231 let mut fft = RealFft::<f32>::new(16);
232 let input: Vec<f32> = (0..16).map(|i| i as f32).collect();
233 let mut spectrum = vec![Complex::new(0.0, 0.0); 9];
234 let mut output = vec![0.0f32; 16];
235
236 fft.forward(&input, &mut spectrum);
237 fft.inverse(&spectrum, &mut output);
238
239 for (a, b) in input.iter().zip(output.iter()) {
240 assert!((a - b).abs() < 5e-4, "expected {a}, got {b}");
241 }
242 }
243
244 #[test]
245 fn test_real_fft_f64_roundtrip() {
246 let mut fft = RealFft::<f64>::new(8);
247 let input: Vec<f64> = (0..8).map(|i| (i as f64 * 0.5).sin()).collect();
248 let mut spectrum = vec![Complex::new(0.0, 0.0); 5];
249 let mut output = vec![0.0f64; 8];
250
251 fft.forward(&input, &mut spectrum);
252 fft.inverse(&spectrum, &mut output);
253
254 for (a, b) in input.iter().zip(output.iter()) {
255 assert!((a - b).abs() < 1e-10, "expected {a}, got {b}");
256 }
257 }
258
259 #[test]
260 fn test_real_fft_dc_input() {
261 let mut fft = RealFft::<f32>::new(16);
262 let input = [2.5f32; 16];
263 let mut spectrum = vec![Complex::new(0.0, 0.0); 9];
264
265 fft.forward(&input, &mut spectrum);
266
267 assert!((spectrum[0].re - 40.0).abs() < 1e-3);
268 assert!(spectrum[0].im.abs() < 1e-3);
269 }
270
271 #[test]
272 fn test_real_fft_nyquist_bin() {
273 let mut fft = RealFft::<f32>::new(8);
274 let input = [1.0f32, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0];
275 let mut spectrum = vec![Complex::new(0.0, 0.0); 5];
276
277 fft.forward(&input, &mut spectrum);
278
279 assert!(spectrum[4].re.abs() > 0.1);
280 }
281}