Skip to main content

embedded_dsp/
transform.rs

1//! Fast Fourier Transform (FFT), Real FFT (RFFT), Discrete Cosine Transform (DCT-IV), and Bit Reversal functions.
2
3#[allow(unused_imports)]
4use crate::math::FloatMath;
5use crate::types::*;
6
7/// Bit reversal function for interleaved complex array of size `2 * n`.
8pub fn bit_reversal(data: &mut [f32], n: usize) {
9    let mut j = 0;
10    for i in 0..n {
11        if i < j {
12            data.swap(2 * i, 2 * j);
13            data.swap(2 * i + 1, 2 * j + 1);
14        }
15        let mut m = n >> 1;
16        while m >= 1 && j >= m {
17            j -= m;
18            m >>= 1;
19        }
20        j += m;
21    }
22}
23
24/// In-place Complex FFT for floating point 32-bit (`f32`).
25/// `data` is interleaved complex array of size `2 * n` (`[re0, im0, re1, im1, ...]`).
26/// `ifft_flag`: 0 for forward FFT, 1 for inverse FFT (IFFT).
27/// `bit_reverse_flag`: 1 to enable bit reversal, 0 to disable.
28pub fn cfft_f32(data: &mut [f32], n: usize, ifft_flag: u8, bit_reverse_flag: u8) {
29    if n < 2 || (n & (n - 1)) != 0 {
30        return;
31    }
32
33    if bit_reverse_flag != 0 {
34        bit_reversal(data, n);
35    }
36
37    let mut len = 2;
38    while len <= n {
39        let half_len = len / 2;
40        let angle =
41            (if ifft_flag != 0 { 2.0 } else { -2.0 }) * core::f32::consts::PI / (len as f32);
42        let w_step_re = angle.cos();
43        let w_step_im = angle.sin();
44
45        let mut i = 0;
46        while i < n {
47            let mut w_re = 1.0f32;
48            let mut w_im = 0.0f32;
49
50            for j in 0..half_len {
51                let u_idx = 2 * (i + j);
52                let v_idx = 2 * (i + j + half_len);
53
54                let u_re = data[u_idx];
55                let u_im = data[u_idx + 1];
56
57                let v_re = data[v_idx];
58                let v_im = data[v_idx + 1];
59
60                let t_re = v_re * w_re - v_im * w_im;
61                let t_im = v_re * w_im + v_im * w_re;
62
63                data[u_idx] = u_re + t_re;
64                data[u_idx + 1] = u_im + t_im;
65
66                data[v_idx] = u_re - t_re;
67                data[v_idx + 1] = u_im - t_im;
68
69                let next_w_re = w_re * w_step_re - w_im * w_step_im;
70                let next_w_im = w_re * w_step_im + w_im * w_step_re;
71                w_re = next_w_re;
72                w_im = next_w_im;
73            }
74            i += len;
75        }
76        len <<= 1;
77    }
78
79    if ifft_flag != 0 {
80        let norm = 1.0 / (n as f32);
81        for i in 0..(2 * n) {
82            data[i] *= norm;
83        }
84    }
85}
86
87/// Twiddle table length: supports radix-2 FFT sizes up to 512 (interleaved `2*n <= 1024`).
88const TWIDDLE_N: usize = 512;
89
90const fn wrap_pi(mut x: f32) -> f32 {
91    while x > core::f32::consts::PI {
92        x -= 2.0 * core::f32::consts::PI;
93    }
94    while x < -core::f32::consts::PI {
95        x += 2.0 * core::f32::consts::PI;
96    }
97    x
98}
99
100const fn cos_taylor(x: f32) -> f32 {
101    let x = wrap_pi(x);
102    let x2 = x * x;
103    let x4 = x2 * x2;
104    let x6 = x4 * x2;
105    let x8 = x4 * x4;
106    1.0 - x2 / 2.0 + x4 / 24.0 - x6 / 720.0 + x8 / 40320.0
107}
108
109const fn sin_taylor(x: f32) -> f32 {
110    let x = wrap_pi(x);
111    let x2 = x * x;
112    let x3 = x2 * x;
113    let x5 = x3 * x2;
114    let x7 = x5 * x2;
115    let x9 = x7 * x2;
116    x - x3 / 6.0 + x5 / 120.0 - x7 / 5040.0 + x9 / 362880.0
117}
118
119const fn gen_cos_q15() -> [i16; TWIDDLE_N] {
120    let mut t = [0i16; TWIDDLE_N];
121    let mut i = 0;
122    while i < TWIDDLE_N {
123        let a = (i as f32) * 2.0 * core::f32::consts::PI / TWIDDLE_N as f32;
124        let v = cos_taylor(a) * 32767.0;
125        t[i] = if v >= 32767.0 {
126            32767
127        } else if v <= -32768.0 {
128            -32768
129        } else {
130            v as i16
131        };
132        i += 1;
133    }
134    t
135}
136
137const fn gen_sin_q15() -> [i16; TWIDDLE_N] {
138    let mut t = [0i16; TWIDDLE_N];
139    let mut i = 0;
140    while i < TWIDDLE_N {
141        let a = (i as f32) * 2.0 * core::f32::consts::PI / TWIDDLE_N as f32;
142        let v = sin_taylor(a) * 32767.0;
143        t[i] = if v >= 32767.0 {
144            32767
145        } else if v <= -32768.0 {
146            -32768
147        } else {
148            v as i16
149        };
150        i += 1;
151    }
152    t
153}
154
155const COS_Q15: [i16; TWIDDLE_N] = gen_cos_q15();
156const SIN_Q15: [i16; TWIDDLE_N] = gen_sin_q15();
157
158fn twiddle_q15(k: usize, n: usize) -> (i16, i16) {
159    let idx = k.wrapping_mul(TWIDDLE_N / n) & (TWIDDLE_N - 1);
160    (COS_Q15[idx], SIN_Q15[idx])
161}
162
163fn bit_reversal_q15(data: &mut [q15], n: usize) {
164    let mut j = 0;
165    for i in 0..n {
166        if i < j {
167            data.swap(2 * i, 2 * j);
168            data.swap(2 * i + 1, 2 * j + 1);
169        }
170        let mut m = n >> 1;
171        while m >= 1 && j >= m {
172            j -= m;
173            m >>= 1;
174        }
175        j += m;
176    }
177}
178
179fn bit_reversal_q31(data: &mut [q31], n: usize) {
180    let mut j = 0;
181    for i in 0..n {
182        if i < j {
183            data.swap(2 * i, 2 * j);
184            data.swap(2 * i + 1, 2 * j + 1);
185        }
186        let mut m = n >> 1;
187        while m >= 1 && j >= m {
188            j -= m;
189            m >>= 1;
190        }
191        j += m;
192    }
193}
194
195#[inline]
196fn sat_q15(v: i32) -> q15 {
197    q15::from_bits(v.clamp(i16::MIN as i32, i16::MAX as i32) as i16)
198}
199
200#[inline]
201fn sat_q31(v: i64) -> q31 {
202    q31::from_bits(v.clamp(i32::MIN as i64, i32::MAX as i64) as i32)
203}
204
205/// In-place radix-2 DIT Complex FFT for Q31.
206///
207/// Each stage arithmetic-shifts right by 1 so a full-scale input does not wrap;
208/// a forward transform of length `n` is therefore scaled by about `1/n` versus
209/// [`cfft_f32`]. Inverse uses conjugated twiddles and the same per-stage shift
210/// (no extra `1/n`), so `ifft(fft(x)) ≈ x / n`.
211///
212/// `n` must be a power of two in `2..=512`. `data` is interleaved `[re, im, ...]`.
213pub fn cfft_q31(data: &mut [q31], n: usize, ifft_flag: u8, bit_reverse_flag: u8) {
214    if n < 2 || n > TWIDDLE_N || (n & (n - 1)) != 0 || data.len() < 2 * n {
215        return;
216    }
217
218    if bit_reverse_flag != 0 {
219        bit_reversal_q31(data, n);
220    }
221
222    let mut len = 2;
223    while len <= n {
224        let half_len = len / 2;
225        let mut i = 0;
226        while i < n {
227            for j in 0..half_len {
228                let (w_re_s, w_im_s) = twiddle_q15(j, len);
229                let w_re = (w_re_s as i32) << 16;
230                let mut w_im = (w_im_s as i32) << 16;
231                if ifft_flag == 0 {
232                    w_im = -w_im;
233                }
234
235                let u_idx = 2 * (i + j);
236                let v_idx = 2 * (i + j + half_len);
237                let u_re = data[u_idx].to_bits() as i64;
238                let u_im = data[u_idx + 1].to_bits() as i64;
239                let v_re = data[v_idx].to_bits() as i64;
240                let v_im = data[v_idx + 1].to_bits() as i64;
241                let wr = w_re as i64;
242                let wi = w_im as i64;
243
244                let t_re = (v_re * wr - v_im * wi) >> 31;
245                let t_im = (v_re * wi + v_im * wr) >> 31;
246
247                data[u_idx] = sat_q31((u_re + t_re) >> 1);
248                data[u_idx + 1] = sat_q31((u_im + t_im) >> 1);
249                data[v_idx] = sat_q31((u_re - t_re) >> 1);
250                data[v_idx + 1] = sat_q31((u_im - t_im) >> 1);
251            }
252            i += len;
253        }
254        len <<= 1;
255    }
256}
257
258/// In-place radix-2 DIT Complex FFT for Q15.
259///
260/// Same scaling as [`cfft_q31`]: about `1/n` per forward or inverse transform.
261/// `n` must be a power of two in `2..=512`.
262pub fn cfft_q15(data: &mut [q15], n: usize, ifft_flag: u8, bit_reverse_flag: u8) {
263    if n < 2 || n > TWIDDLE_N || (n & (n - 1)) != 0 || data.len() < 2 * n {
264        return;
265    }
266
267    if bit_reverse_flag != 0 {
268        bit_reversal_q15(data, n);
269    }
270
271    let mut len = 2;
272    while len <= n {
273        let half_len = len / 2;
274        let mut i = 0;
275        while i < n {
276            for j in 0..half_len {
277                let (w_re_s, mut w_im_s) = twiddle_q15(j, len);
278                if ifft_flag == 0 {
279                    w_im_s = w_im_s.saturating_neg();
280                }
281
282                let u_idx = 2 * (i + j);
283                let v_idx = 2 * (i + j + half_len);
284                let u_re = data[u_idx].to_bits() as i32;
285                let u_im = data[u_idx + 1].to_bits() as i32;
286                let v_re = data[v_idx].to_bits() as i32;
287                let v_im = data[v_idx + 1].to_bits() as i32;
288                let wr = w_re_s as i32;
289                let wi = w_im_s as i32;
290
291                let t_re = (v_re * wr - v_im * wi) >> 15;
292                let t_im = (v_re * wi + v_im * wr) >> 15;
293
294                data[u_idx] = sat_q15((u_re + t_re) >> 1);
295                data[u_idx + 1] = sat_q15((u_im + t_im) >> 1);
296                data[v_idx] = sat_q15((u_re - t_re) >> 1);
297                data[v_idx + 1] = sat_q15((u_im - t_im) >> 1);
298            }
299            i += len;
300        }
301        len <<= 1;
302    }
303}
304
305/// In-place Block Floating-Point (BFP) Complex FFT for Q15.
306///
307/// Unlike standard fixed-point FFT which down-shifts by 1 bit at every stage (losing `log2(N)` bits
308/// of SNR), Block Floating-Point dynamically scans maximum stage amplitude and only divides by 2
309/// when overflow is imminent.
310///
311/// Returns the total scale count `scale_count: u16` (the block exponent).
312/// The true mathematical frequency amplitude is `output[k] * 2^{scale_count}`.
313pub fn cfft_bfp_q15(data: &mut [q15], n: usize, ifft_flag: u8, bit_reverse_flag: u8) -> u16 {
314    if n < 2 || n > TWIDDLE_N || (n & (n - 1)) != 0 || data.len() < 2 * n {
315        return 0;
316    }
317
318    if bit_reverse_flag != 0 {
319        bit_reversal_q15(data, n);
320    }
321
322    let mut scale_count: u16 = 0;
323    let mut len = 2;
324    while len <= n {
325        let half_len = len / 2;
326
327        // Stage headroom check: find max absolute value
328        let mut max_val: i16 = 0;
329        for i in 0..2 * n {
330            let val = data[i].abs().to_bits();
331            if val > max_val {
332                max_val = val;
333            }
334        }
335
336        // Butterfly addition can double magnitude: if max_val > 16383, scale stage down by 1 bit.
337        let stage_shift = if max_val > 16383 {
338            scale_count += 1;
339            1
340        } else {
341            0
342        };
343
344        let mut i = 0;
345        while i < n {
346            for j in 0..half_len {
347                let (w_re_s, mut w_im_s) = twiddle_q15(j, len);
348                if ifft_flag == 0 {
349                    w_im_s = w_im_s.saturating_neg();
350                }
351
352                let u_idx = 2 * (i + j);
353                let v_idx = 2 * (i + j + half_len);
354                let u_re = data[u_idx].to_bits() as i32;
355                let u_im = data[u_idx + 1].to_bits() as i32;
356                let v_re = data[v_idx].to_bits() as i32;
357                let v_im = data[v_idx + 1].to_bits() as i32;
358                let wr = w_re_s as i32;
359                let wi = w_im_s as i32;
360
361                let t_re = (v_re * wr - v_im * wi) >> 15;
362                let t_im = (v_re * wi + v_im * wr) >> 15;
363
364                data[u_idx] = sat_q15((u_re + t_re) >> stage_shift);
365                data[u_idx + 1] = sat_q15((u_im + t_im) >> stage_shift);
366                data[v_idx] = sat_q15((u_re - t_re) >> stage_shift);
367                data[v_idx + 1] = sat_q15((u_im - t_im) >> stage_shift);
368            }
369            i += len;
370        }
371        len <<= 1;
372    }
373
374    scale_count
375}
376
377/// In-place Block Floating-Point (BFP) Complex FFT for Q31.
378///
379/// Dynamically scales only when overflow is imminent, returning total `scale_count`.
380pub fn cfft_bfp_q31(data: &mut [q31], n: usize, ifft_flag: u8, bit_reverse_flag: u8) -> u16 {
381    if n < 2 || n > TWIDDLE_N || (n & (n - 1)) != 0 || data.len() < 2 * n {
382        return 0;
383    }
384
385    if bit_reverse_flag != 0 {
386        bit_reversal_q31(data, n);
387    }
388
389    let mut scale_count: u16 = 0;
390    let mut len = 2;
391    while len <= n {
392        let half_len = len / 2;
393
394        let mut max_val: i32 = 0;
395        for i in 0..2 * n {
396            let val = data[i].abs().to_bits();
397            if val > max_val {
398                max_val = val;
399            }
400        }
401
402        let stage_shift = if max_val > 1073741823 {
403            scale_count += 1;
404            1
405        } else {
406            0
407        };
408
409        let mut i = 0;
410        while i < n {
411            for j in 0..half_len {
412                let (w_re_s, w_im_s) = twiddle_q15(j, len);
413                let w_re = (w_re_s as i32) << 16;
414                let mut w_im = (w_im_s as i32) << 16;
415                if ifft_flag == 0 {
416                    w_im = -w_im;
417                }
418
419                let u_idx = 2 * (i + j);
420                let v_idx = 2 * (i + j + half_len);
421                let u_re = data[u_idx].to_bits() as i64;
422                let u_im = data[u_idx + 1].to_bits() as i64;
423                let v_re = data[v_idx].to_bits() as i64;
424                let v_im = data[v_idx + 1].to_bits() as i64;
425                let wr = w_re as i64;
426                let wi = w_im as i64;
427
428                let t_re = (v_re * wr - v_im * wi) >> 31;
429                let t_im = (v_re * wi + v_im * wr) >> 31;
430
431                data[u_idx] = sat_q31((u_re + t_re) >> stage_shift);
432                data[u_idx + 1] = sat_q31((u_im + t_im) >> stage_shift);
433                data[v_idx] = sat_q31((u_re - t_re) >> stage_shift);
434                data[v_idx + 1] = sat_q31((u_im - t_im) >> stage_shift);
435            }
436            i += len;
437        }
438        len <<= 1;
439    }
440
441    scale_count
442}
443
444/// Real Cepstrum: `c(n) = IFFT(ln |FFT(x)|)`.
445///
446/// Computes homomorphic deconvolution of `src` into `cepstrum_out`.
447/// Used in echo detection, sonar multipath analysis, pitch tracking, and seismic deconvolution.
448/// `n` must be a power of two `<= 512`.
449pub fn real_cepstrum_f32(src: &[f32], cepstrum_out: &mut [f32]) -> Status {
450    let n = src.len();
451    if n < 2 || (n & (n - 1)) != 0 || n > 512 {
452        return Status::ArgumentError;
453    }
454    if cepstrum_out.len() < n {
455        return Status::LengthError;
456    }
457
458    let mut c_buf = [0.0f32; 1024];
459    for i in 0..n {
460        c_buf[2 * i] = src[i];
461        c_buf[2 * i + 1] = 0.0;
462    }
463
464    // 1. Forward FFT
465    cfft_f32(&mut c_buf[..2 * n], n, 0, 1);
466
467    // 2. Log magnitude
468    for i in 0..n {
469        let re = c_buf[2 * i];
470        let im = c_buf[2 * i + 1];
471        let mag = (re * re + im * im).sqrt().max(1e-12);
472        c_buf[2 * i] = mag.ln();
473        c_buf[2 * i + 1] = 0.0;
474    }
475
476    // 3. Inverse FFT
477    cfft_f32(&mut c_buf[..2 * n], n, 1, 1);
478
479    // 4. Output real part scaled by 1/n
480    let inv_n = 1.0 / (n as f32);
481    for i in 0..n {
482        cepstrum_out[i] = c_buf[2 * i] * inv_n;
483    }
484
485    Status::Success
486}
487
488/// Real FFT for floating point 32-bit (`f32`).
489/// `src` has `n` real samples. `dst` receives `2 * n` complex outputs.
490pub fn rfft_f32(src: &[f32], dst: &mut [f32], n: usize, ifft_flag: u8) {
491    let len = src.len().min(n);
492    let mut c_data = [0.0f32; 1024];
493    if 2 * len > c_data.len() || dst.len() < 2 * len {
494        return;
495    }
496
497    for i in 0..len {
498        c_data[2 * i] = src[i];
499        c_data[2 * i + 1] = 0.0;
500    }
501
502    cfft_f32(&mut c_data[..2 * len], len, ifft_flag, 1);
503    dst[..2 * len].copy_from_slice(&c_data[..2 * len]);
504}
505
506/// Packed real FFT (N/2-point complex FFT of even/odd samples, then unpack).
507/// Forward only (`ifft_flag == 0`); inverse still uses a complex FFT of real+0j.
508fn packed_rfft_q15_forward(src: &[q15], dst: &mut [q15], n: usize) {
509    let m = n / 2;
510    let mut z = [q15::ZERO; 1024];
511    if 2 * m > z.len() {
512        return;
513    }
514    for k in 0..m {
515        z[2 * k] = src[2 * k];
516        z[2 * k + 1] = src[2 * k + 1];
517    }
518    cfft_q15(&mut z[..2 * m], m, 0, 1);
519
520    let z0r = z[0].to_bits() as i32;
521    let z0i = z[1].to_bits() as i32;
522    dst[0] = sat_q15((z0r + z0i) >> 1);
523    dst[1] = q15::ZERO;
524    dst[n] = sat_q15((z0r - z0i) >> 1);
525    dst[n + 1] = q15::ZERO;
526
527    for k in 1..m {
528        let zr = z[2 * k].to_bits() as i32;
529        let zi = z[2 * k + 1].to_bits() as i32;
530        let znr = z[2 * (m - k)].to_bits() as i32;
531        let zni = z[2 * (m - k) + 1].to_bits() as i32;
532
533        let xe_re = (zr + znr) >> 1;
534        let xe_im = (zi - zni) >> 1;
535        let xo_re = (zi + zni) >> 1;
536        let xo_im = (znr - zr) >> 1;
537
538        let (wr, wi_s) = twiddle_q15(k, n);
539        let wr = wr as i32;
540        let wi = -(wi_s as i32);
541
542        let t_re = (xo_re * wr - xo_im * wi) >> 15;
543        let t_im = (xo_re * wi + xo_im * wr) >> 15;
544
545        dst[2 * k] = sat_q15((xe_re + t_re) >> 1);
546        dst[2 * k + 1] = sat_q15((xe_im + t_im) >> 1);
547        dst[2 * (n - k)] = sat_q15((xe_re - t_re) >> 1);
548        dst[2 * (n - k) + 1] = sat_q15((t_im - xe_im) >> 1);
549    }
550}
551
552fn packed_rfft_q31_forward(src: &[q31], dst: &mut [q31], n: usize) {
553    let m = n / 2;
554    let mut z = [q31::ZERO; 1024];
555    if 2 * m > z.len() {
556        return;
557    }
558    for k in 0..m {
559        z[2 * k] = src[2 * k];
560        z[2 * k + 1] = src[2 * k + 1];
561    }
562    cfft_q31(&mut z[..2 * m], m, 0, 1);
563
564    let z0r = z[0].to_bits() as i64;
565    let z0i = z[1].to_bits() as i64;
566    dst[0] = sat_q31((z0r + z0i) >> 1);
567    dst[1] = q31::ZERO;
568    dst[n] = sat_q31((z0r - z0i) >> 1);
569    dst[n + 1] = q31::ZERO;
570
571    for k in 1..m {
572        let zr = z[2 * k].to_bits() as i64;
573        let zi = z[2 * k + 1].to_bits() as i64;
574        let znr = z[2 * (m - k)].to_bits() as i64;
575        let zni = z[2 * (m - k) + 1].to_bits() as i64;
576
577        let xe_re = (zr + znr) >> 1;
578        let xe_im = (zi - zni) >> 1;
579        let xo_re = (zi + zni) >> 1;
580        let xo_im = (znr - zr) >> 1;
581
582        let (wr_s, wi_s) = twiddle_q15(k, n);
583        let wr = (wr_s as i64) << 16;
584        let wi = -((wi_s as i64) << 16);
585
586        let t_re = (xo_re * wr - xo_im * wi) >> 31;
587        let t_im = (xo_re * wi + xo_im * wr) >> 31;
588
589        dst[2 * k] = sat_q31((xe_re + t_re) >> 1);
590        dst[2 * k + 1] = sat_q31((xe_im + t_im) >> 1);
591        dst[2 * (n - k)] = sat_q31((xe_re - t_re) >> 1);
592        dst[2 * (n - k) + 1] = sat_q31((t_im - xe_im) >> 1);
593    }
594}
595
596fn packed_irfft_q15(src: &[q15], dst: &mut [q15], n: usize) {
597    let m = n / 2;
598    let mut z = [q15::ZERO; 1024];
599    if 2 * m > z.len() {
600        return;
601    }
602
603    let dc = src[0].to_bits() as i32;
604    let ny = src[n].to_bits() as i32;
605    z[0] = sat_q15(dc + ny);
606    z[1] = sat_q15(dc - ny);
607
608    for k in 1..m {
609        let xkr = src[2 * k].to_bits() as i32;
610        let xki = src[2 * k + 1].to_bits() as i32;
611        let xnr = src[2 * (n - k)].to_bits() as i32;
612        let xni = src[2 * (n - k) + 1].to_bits() as i32;
613
614        let xe_re = xkr + xnr;
615        let xe_im = xki - xni;
616        let t_re = xkr - xnr;
617        let t_im = xki + xni;
618
619        let (wr, wi_s) = twiddle_q15(k, n);
620        let wr = wr as i32;
621        let wi = wi_s as i32;
622        let xo_re = (t_re * wr - t_im * wi) >> 15;
623        let xo_im = (t_re * wi + t_im * wr) >> 15;
624
625        z[2 * k] = sat_q15(xe_re - xo_im);
626        z[2 * k + 1] = sat_q15(xe_im + xo_re);
627    }
628
629    cfft_q15(&mut z[..2 * m], m, 1, 1);
630    for k in 0..m {
631        dst[2 * k] = sat_q15((z[2 * k].to_bits() as i32) >> 1);
632        dst[2 * k + 1] = sat_q15((z[2 * k + 1].to_bits() as i32) >> 1);
633    }
634}
635
636fn packed_irfft_q31(src: &[q31], dst: &mut [q31], n: usize) {
637    let m = n / 2;
638    let mut z = [q31::ZERO; 1024];
639    if 2 * m > z.len() {
640        return;
641    }
642
643    let dc = src[0].to_bits() as i64;
644    let ny = src[n].to_bits() as i64;
645    z[0] = sat_q31(dc + ny);
646    z[1] = sat_q31(dc - ny);
647
648    for k in 1..m {
649        let xkr = src[2 * k].to_bits() as i64;
650        let xki = src[2 * k + 1].to_bits() as i64;
651        let xnr = src[2 * (n - k)].to_bits() as i64;
652        let xni = src[2 * (n - k) + 1].to_bits() as i64;
653
654        let xe_re = xkr + xnr;
655        let xe_im = xki - xni;
656        let t_re = xkr - xnr;
657        let t_im = xki + xni;
658
659        let (wr_s, wi_s) = twiddle_q15(k, n);
660        let wr = (wr_s as i64) << 16;
661        let wi = (wi_s as i64) << 16;
662        let xo_re = (t_re * wr - t_im * wi) >> 31;
663        let xo_im = (t_re * wi + t_im * wr) >> 31;
664
665        z[2 * k] = sat_q31(xe_re - xo_im);
666        z[2 * k + 1] = sat_q31(xe_im + xo_re);
667    }
668
669    cfft_q31(&mut z[..2 * m], m, 1, 1);
670    for k in 0..m {
671        dst[2 * k] = sat_q31((z[2 * k].to_bits() as i64) >> 1);
672        dst[2 * k + 1] = sat_q31((z[2 * k + 1].to_bits() as i64) >> 1);
673    }
674}
675
676fn rfft_q_can_pack(len: usize, ifft_flag: u8) -> bool {
677    ifft_flag == 0 && len >= 4 && len <= TWIDDLE_N && (len & (len - 1)) == 0
678}
679
680/// Real FFT for Q31 fixed-point.
681///
682/// Forward (`ifft_flag == 0`) uses a packed N/2 complex FFT of even/odd samples
683/// (same output layout as a zero-padded [`cfft_q31`]: `2*n` interleaved bins).
684/// Inverse (`ifft_flag != 0`) still runs an `n`-point complex FFT of real+0j;
685/// use [`irfft_q31`] to invert a packed spectrum.
686pub fn rfft_q31(src: &[q31], dst: &mut [q31], n: usize, ifft_flag: u8) {
687    let len = src.len().min(n);
688    if dst.len() < 2 * len {
689        return;
690    }
691
692    if rfft_q_can_pack(len, ifft_flag) {
693        packed_rfft_q31_forward(&src[..len], dst, len);
694        return;
695    }
696
697    let mut c_data = [q31::ZERO; 1024];
698    if 2 * len > c_data.len() {
699        return;
700    }
701
702    for i in 0..len {
703        c_data[2 * i] = src[i];
704        c_data[2 * i + 1] = q31::ZERO;
705    }
706    cfft_q31(&mut c_data[..2 * len], len, ifft_flag, 1);
707    dst[..2 * len].copy_from_slice(&c_data[..2 * len]);
708}
709
710/// Real FFT for Q15 fixed-point.
711///
712/// Forward packed N/2 algorithm; see [`rfft_q31`]. Scale versus [`rfft_f32`] is
713/// about `1/n`, matching [`cfft_q15`].
714pub fn rfft_q15(src: &[q15], dst: &mut [q15], n: usize, ifft_flag: u8) {
715    let len = src.len().min(n);
716    if dst.len() < 2 * len {
717        return;
718    }
719
720    if rfft_q_can_pack(len, ifft_flag) {
721        packed_rfft_q15_forward(&src[..len], dst, len);
722        return;
723    }
724
725    let mut c_data = [q15::ZERO; 1024];
726    if 2 * len > c_data.len() {
727        return;
728    }
729
730    for i in 0..len {
731        c_data[2 * i] = src[i];
732        c_data[2 * i + 1] = q15::ZERO;
733    }
734    cfft_q15(&mut c_data[..2 * len], len, ifft_flag, 1);
735    dst[..2 * len].copy_from_slice(&c_data[..2 * len]);
736}
737
738/// Inverse packed real FFT. `src` is `2 * n` interleaved bins from [`rfft_q31`];
739/// `dst` receives `n` real samples. Combined with a forward transform,
740/// `irfft(rfft(x)) ≈ x / n` (same convention as [`cfft_q31`]).
741pub fn irfft_q31(src: &[q31], dst: &mut [q31], n: usize) {
742    if n < 4 || n > TWIDDLE_N || (n & (n - 1)) != 0 || src.len() < 2 * n || dst.len() < n {
743        return;
744    }
745    packed_irfft_q31(&src[..2 * n], dst, n);
746}
747
748/// Inverse packed real FFT. `src` is `2 * n` interleaved bins from [`rfft_q15`];
749/// `dst` receives `n` real samples. Combined with a forward transform,
750/// `irfft(rfft(x)) ≈ x / n` (same convention as [`cfft_q15`]).
751pub fn irfft_q15(src: &[q15], dst: &mut [q15], n: usize) {
752    if n < 4 || n > TWIDDLE_N || (n & (n - 1)) != 0 || src.len() < 2 * n || dst.len() < n {
753        return;
754    }
755    packed_irfft_q15(&src[..2 * n], dst, n);
756}
757
758/// Discrete Cosine Transform Type IV (DCT-IV) for f32.
759pub fn dct4_f32(src: &[f32], dst: &mut [f32], n: usize) {
760    let len = src.len().min(dst.len()).min(n);
761    let pi_over_n = core::f32::consts::PI / (len as f32);
762
763    for k in 0..len {
764        let mut sum = 0.0f32;
765        let k_factor = (k as f32 + 0.5) * pi_over_n;
766        for n_idx in 0..len {
767            let angle = (n_idx as f32 + 0.5) * k_factor;
768            sum += src[n_idx] * angle.cos();
769        }
770        let norm = (2.0 / len as f32).sqrt();
771        dst[k] = sum * norm;
772    }
773}
774
775// --- Fast Walsh-Hadamard Transform (FWHT) ---
776
777/// In-place Fast Walsh-Hadamard Transform (FWHT) for floating point `f32`.
778///
779/// `data.len()` must be a power of 2 (e.g. 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024).
780pub fn fwht_f32(data: &mut [f32]) -> Status {
781    let n = data.len();
782    if n < 2 || (n & (n - 1)) != 0 {
783        return Status::ArgumentError;
784    }
785
786    let mut h = 1;
787    while h < n {
788        let mut i = 0;
789        while i < n {
790            for j in i..(i + h) {
791                let x = data[j];
792                let y = data[j + h];
793                data[j] = x + y;
794                data[j + h] = x - y;
795            }
796            i += h * 2;
797        }
798        h *= 2;
799    }
800
801    Status::Success
802}
803
804/// In-place Inverse Fast Walsh-Hadamard Transform (IFWHT) for floating point `f32` (normalized by $1/N$).
805pub fn ifwht_f32(data: &mut [f32]) -> Status {
806    let status = fwht_f32(data);
807    if status != Status::Success {
808        return status;
809    }
810    let norm = 1.0f32 / (data.len() as f32);
811    for val in data.iter_mut() {
812        *val *= norm;
813    }
814    Status::Success
815}
816
817/// In-place Fast Walsh-Hadamard Transform (FWHT) for 32-bit integers (`i32`).
818pub fn fwht_i32(data: &mut [i32]) -> Status {
819    let n = data.len();
820    if n < 2 || (n & (n - 1)) != 0 {
821        return Status::ArgumentError;
822    }
823
824    let mut h = 1;
825    while h < n {
826        let mut i = 0;
827        while i < n {
828            for j in i..(i + h) {
829                let x = data[j];
830                let y = data[j + h];
831                data[j] = x.wrapping_add(y);
832                data[j + h] = x.wrapping_sub(y);
833            }
834            i += h * 2;
835        }
836        h *= 2;
837    }
838
839    Status::Success
840}
841
842// --- Haar Transform (Jörg Arndt, "Matters Computational", Ch. 24) ---
843
844/// In-place, orthogonal Haar Transform for `f32`: an `O(n)` multiresolution transform using
845/// only additions, subtractions, and a `sqrt(0.5)` scale factor per stage, with no
846/// trigonometric factors at all (unlike the Fourier/Hartley transforms).
847///
848/// `data.len()` must be a power of 2 (e.g. 2, 4, 8, ..., 1024).
849pub fn haar_transform_f32(data: &mut [f32]) -> Status {
850    let n = data.len();
851    if n < 2 || (n & (n - 1)) != 0 {
852        return Status::ArgumentError;
853    }
854
855    let s2 = (0.5f32).sqrt();
856    let mut v = 1.0f32;
857    let mut js = 2;
858    while js <= n {
859        v *= s2;
860        let half = js >> 1;
861        let mut j = 0;
862        while j < n {
863            let t = j + half;
864            let x = data[j];
865            let y = data[t];
866            data[j] = x + y;
867            data[t] = (x - y) * v;
868            j += js;
869        }
870        js <<= 1;
871    }
872    data[0] *= v; // v == 1 / sqrt(n)
873
874    Status::Success
875}
876
877/// In-place Inverse Haar Transform for `f32`, undoing [`haar_transform_f32`].
878pub fn inverse_haar_transform_f32(data: &mut [f32]) -> Status {
879    let n = data.len();
880    if n < 2 || (n & (n - 1)) != 0 {
881        return Status::ArgumentError;
882    }
883
884    let s2 = 2.0f32.sqrt();
885    let mut v = 1.0f32 / (n as f32).sqrt();
886    data[0] *= v;
887
888    let mut js = n;
889    while js >= 2 {
890        let half = js >> 1;
891        let mut j = 0;
892        while j < n {
893            let t = j + half;
894            let x = data[j];
895            let y = data[t] * v;
896            data[j] = x + y;
897            data[t] = x - y;
898            j += js;
899        }
900        v *= s2;
901        js >>= 1;
902    }
903
904    Status::Success
905}
906
907/// In-place, non-normalized Haar Transform for `i32`: a forward-only, integer-exact
908/// decomposition using only wrapping add/subtract (no scaling), analogous to
909/// [`fwht_i32`]. Because the transform is non-normalized, an exact-integer inverse does not
910/// exist in general (undoing it requires dividing by powers of 2 that may not evenly divide
911/// intermediate sums); use [`haar_transform_f32`] / [`inverse_haar_transform_f32`] when an
912/// invertible round trip is required.
913///
914/// `data.len()` must be a power of 2 (e.g. 2, 4, 8, ..., 1024).
915pub fn haar_transform_i32(data: &mut [i32]) -> Status {
916    let n = data.len();
917    if n < 2 || (n & (n - 1)) != 0 {
918        return Status::ArgumentError;
919    }
920
921    let mut js = 2;
922    while js <= n {
923        let half = js >> 1;
924        let mut j = 0;
925        while j < n {
926            let t = j + half;
927            let x = data[j];
928            let y = data[t];
929            data[j] = x.wrapping_add(y);
930            data[t] = x.wrapping_sub(y);
931            j += js;
932        }
933        js <<= 1;
934    }
935
936    Status::Success
937}
938
939// --- Hartley Transform (Jörg Arndt, "Matters Computational", Ch. 25) ---
940
941/// In-place Discrete Hartley Transform for `f32`.
942///
943/// Computed via the identity relating the Hartley and Fourier transforms (Ch. 25):
944/// `H[a] = (Re(F[a]) - Im(F[a])) / sqrt(n)`, built on top of [`cfft_f32`] rather than a
945/// dedicated real-only butterfly network, so it costs a full complex FFT internally
946/// (`n <= 512`) even though its inputs and outputs are purely real.
947///
948/// The Hartley transform is its own inverse (`H[H[a]] = a`): call this function a second time
949/// on its output to invert it, with no separate inverse routine needed.
950///
951/// `data.len()` must be a power of 2 (e.g. 2, 4, 8, ..., 512).
952pub fn hartley_transform_f32(data: &mut [f32]) -> Status {
953    let n = data.len();
954    if n < 2 || (n & (n - 1)) != 0 {
955        return Status::ArgumentError;
956    }
957    if 2 * n > 1024 {
958        return Status::LengthError;
959    }
960
961    let mut c_data = [0.0f32; 1024];
962    for i in 0..n {
963        c_data[2 * i] = data[i];
964        c_data[2 * i + 1] = 0.0;
965    }
966
967    cfft_f32(&mut c_data[..2 * n], n, 0, 1);
968
969    let inv_sqrt_n = 1.0 / (n as f32).sqrt();
970    for i in 0..n {
971        data[i] = (c_data[2 * i] - c_data[2 * i + 1]) * inv_sqrt_n;
972    }
973
974    Status::Success
975}
976
977// --- Generalized Wavelet Transform (Jörg Arndt, "Matters Computational", Ch. 27) ---
978
979/// The Daubechies-4 orthogonal wavelet low-pass filter taps (Ch. 27.1), verified to satisfy
980/// the wavelet conditions `sum(h_j^2) = 1` and `sum(h_j * h_{j+2}) = 0`. Using
981/// `[sqrt(0.5), sqrt(0.5)]` instead recovers the Haar wavelet as a special case.
982pub const DAUBECHIES_4: [f32; 4] = [0.482_962_9, 0.836_516_3, 0.224_143_87, -0.129_409_52];
983
984/// The high-pass filter tap derived from low-pass filter `h` (Ch. 27.1, Eq. 27.1-2):
985/// `g[k] = (-1)^k * h[n - 1 - k]`.
986#[inline(always)]
987fn wavelet_high_pass_tap(h: &[f32], k: usize) -> f32 {
988    let v = h[h.len() - 1 - k];
989    if k % 2 == 0 { v } else { -v }
990}
991
992/// Performs one level of a fast wavelet transform step on the first `m` elements of `data`,
993/// using wavelet filter `h` (low-pass) and its derived high-pass filter. Writes the low-pass
994/// ("scaling") coefficients to `data[0..m/2]` and the high-pass ("wavelet") coefficients to
995/// `data[m/2..m]`; the underlying convolution wraps around cyclically at the block boundary.
996///
997/// `m` must be a power of 2; `h.len()` must be even and `<= m`.
998pub fn wavelet_step_f32(data: &mut [f32], m: usize, h: &[f32]) -> Status {
999    let taps = h.len();
1000    if m < 2 || (m & (m - 1)) != 0 || taps == 0 || taps % 2 != 0 || taps > m || data.len() < m {
1001        return Status::ArgumentError;
1002    }
1003    if m > 1024 {
1004        return Status::LengthError;
1005    }
1006
1007    let mut scratch = [0.0f32; 1024];
1008    let nh = m >> 1;
1009    let mut i = 0;
1010    while i < m {
1011        let mut s = 0.0f32;
1012        let mut d = 0.0f32;
1013        for k in 0..taps {
1014            let idx = (i + k) % m;
1015            let x = data[idx];
1016            s += h[k] * x;
1017            d += wavelet_high_pass_tap(h, k) * x;
1018        }
1019        let j = i / 2;
1020        scratch[j] = s;
1021        scratch[nh + j] = d;
1022        i += 2;
1023    }
1024    data[..m].copy_from_slice(&scratch[..m]);
1025
1026    Status::Success
1027}
1028
1029/// Performs the exact inverse of one [`wavelet_step_f32`] level.
1030///
1031/// `m` must be a power of 2; `h.len()` must be even and `<= m`.
1032pub fn inverse_wavelet_step_f32(data: &mut [f32], m: usize, h: &[f32]) -> Status {
1033    let taps = h.len();
1034    if m < 2 || (m & (m - 1)) != 0 || taps == 0 || taps % 2 != 0 || taps > m || data.len() < m {
1035        return Status::ArgumentError;
1036    }
1037    if m > 1024 {
1038        return Status::LengthError;
1039    }
1040
1041    let mut scratch = [0.0f32; 1024];
1042    let nh = m >> 1;
1043    for j in 0..nh {
1044        let s = data[j];
1045        let d = data[nh + j];
1046        for k in 0..taps {
1047            let idx = (2 * j + k) % m;
1048            scratch[idx] += h[k] * s + wavelet_high_pass_tap(h, k) * d;
1049        }
1050    }
1051    data[..m].copy_from_slice(&scratch[..m]);
1052
1053    Status::Success
1054}
1055
1056/// Performs a full multi-level fast wavelet transform (Ch. 27): repeatedly applies
1057/// [`wavelet_step_f32`] to the lower half of the array, halving the active block length each
1058/// time, stopping once the block would be smaller than the filter itself (mirroring the Haar
1059/// transform's pyramid structure).
1060///
1061/// `data.len()` must be a power of 2 and `>= h.len()`.
1062pub fn wavelet_transform_f32(data: &mut [f32], h: &[f32]) -> Status {
1063    let n = data.len();
1064    if n < 2 || (n & (n - 1)) != 0 || h.len() > n {
1065        return Status::ArgumentError;
1066    }
1067
1068    let mut m = n;
1069    while m >= h.len() {
1070        let status = wavelet_step_f32(&mut data[..m], m, h);
1071        if status != Status::Success {
1072            return status;
1073        }
1074        m >>= 1;
1075    }
1076
1077    Status::Success
1078}
1079
1080/// Performs the exact inverse of [`wavelet_transform_f32`].
1081///
1082/// `data.len()` must be a power of 2 and `>= h.len()`.
1083pub fn inverse_wavelet_transform_f32(data: &mut [f32], h: &[f32]) -> Status {
1084    let n = data.len();
1085    if n < 2 || (n & (n - 1)) != 0 || h.len() > n {
1086        return Status::ArgumentError;
1087    }
1088
1089    let mut smallest = n;
1090    while smallest >= h.len() {
1091        smallest >>= 1;
1092    }
1093    smallest <<= 1;
1094
1095    let mut m = smallest;
1096    while m <= n {
1097        let status = inverse_wavelet_step_f32(&mut data[..m], m, h);
1098        if status != Status::Success {
1099            return status;
1100        }
1101        m <<= 1;
1102    }
1103
1104    Status::Success
1105}