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/// In-place Complex FFT for Q31 fixed-point.
88pub fn cfft_q31(data: &mut [q31], n: usize, ifft_flag: u8, _bit_reverse_flag: u8) {
89    if n < 2 {
90        return;
91    }
92    // Convert to f32 scratch, run cfft_f32, convert back
93    let mut scratch = [0.0f32; 1024];
94    let total = 2 * n;
95    if total > scratch.len() {
96        return;
97    }
98
99    for i in 0..total {
100        scratch[i] = data[i] as f32 / 2147483648.0;
101    }
102    cfft_f32(&mut scratch[..total], n, ifft_flag, 1);
103    for i in 0..total {
104        data[i] = (scratch[i] * 2147483648.0).clamp(-2147483648.0, 2147483647.0) as q31;
105    }
106}
107
108/// In-place Complex FFT for Q15 fixed-point.
109pub fn cfft_q15(data: &mut [q15], n: usize, ifft_flag: u8, _bit_reverse_flag: u8) {
110    if n < 2 {
111        return;
112    }
113    let mut scratch = [0.0f32; 1024];
114    let total = 2 * n;
115    if total > scratch.len() {
116        return;
117    }
118
119    for i in 0..total {
120        scratch[i] = data[i] as f32 / 32768.0;
121    }
122    cfft_f32(&mut scratch[..total], n, ifft_flag, 1);
123    for i in 0..total {
124        data[i] = (scratch[i] * 32768.0).clamp(-32768.0, 32767.0) as q15;
125    }
126}
127
128/// Real FFT for floating point 32-bit (`f32`).
129/// `src` has `n` real samples. `dst` receives `2 * n` complex outputs.
130pub fn rfft_f32(src: &[f32], dst: &mut [f32], n: usize, ifft_flag: u8) {
131    let len = src.len().min(n);
132    let mut c_data = [0.0f32; 1024];
133    if 2 * len > c_data.len() || dst.len() < 2 * len {
134        return;
135    }
136
137    for i in 0..len {
138        c_data[2 * i] = src[i];
139        c_data[2 * i + 1] = 0.0;
140    }
141
142    cfft_f32(&mut c_data[..2 * len], len, ifft_flag, 1);
143    dst[..2 * len].copy_from_slice(&c_data[..2 * len]);
144}
145
146/// Real FFT for Q31 fixed-point.
147pub fn rfft_q31(src: &[q31], dst: &mut [q31], n: usize, ifft_flag: u8) {
148    let len = src.len().min(n);
149    let mut c_data = [0; 1024];
150    if 2 * len > c_data.len() || dst.len() < 2 * len {
151        return;
152    }
153
154    for i in 0..len {
155        c_data[2 * i] = src[i];
156        c_data[2 * i + 1] = 0;
157    }
158    cfft_q31(&mut c_data[..2 * len], len, ifft_flag, 1);
159    dst[..2 * len].copy_from_slice(&c_data[..2 * len]);
160}
161
162/// Real FFT for Q15 fixed-point.
163pub fn rfft_q15(src: &[q15], dst: &mut [q15], n: usize, ifft_flag: u8) {
164    let len = src.len().min(n);
165    let mut c_data = [0; 1024];
166    if 2 * len > c_data.len() || dst.len() < 2 * len {
167        return;
168    }
169
170    for i in 0..len {
171        c_data[2 * i] = src[i];
172        c_data[2 * i + 1] = 0;
173    }
174    cfft_q15(&mut c_data[..2 * len], len, ifft_flag, 1);
175    dst[..2 * len].copy_from_slice(&c_data[..2 * len]);
176}
177
178/// Discrete Cosine Transform Type IV (DCT-IV) for f32.
179pub fn dct4_f32(src: &[f32], dst: &mut [f32], n: usize) {
180    let len = src.len().min(dst.len()).min(n);
181    let pi_over_n = core::f32::consts::PI / (len as f32);
182
183    for k in 0..len {
184        let mut sum = 0.0f32;
185        let k_factor = (k as f32 + 0.5) * pi_over_n;
186        for n_idx in 0..len {
187            let angle = (n_idx as f32 + 0.5) * k_factor;
188            sum += src[n_idx] * angle.cos();
189        }
190        let norm = (2.0 / len as f32).sqrt();
191        dst[k] = sum * norm;
192    }
193}
194
195// --- Fast Walsh-Hadamard Transform (FWHT) ---
196
197/// In-place Fast Walsh-Hadamard Transform (FWHT) for floating point `f32`.
198///
199/// `data.len()` must be a power of 2 (e.g. 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024).
200pub fn fwht_f32(data: &mut [f32]) -> Status {
201    let n = data.len();
202    if n < 2 || (n & (n - 1)) != 0 {
203        return Status::ArgumentError;
204    }
205
206    let mut h = 1;
207    while h < n {
208        let mut i = 0;
209        while i < n {
210            for j in i..(i + h) {
211                let x = data[j];
212                let y = data[j + h];
213                data[j] = x + y;
214                data[j + h] = x - y;
215            }
216            i += h * 2;
217        }
218        h *= 2;
219    }
220
221    Status::Success
222}
223
224/// In-place Inverse Fast Walsh-Hadamard Transform (IFWHT) for floating point `f32` (normalized by $1/N$).
225pub fn ifwht_f32(data: &mut [f32]) -> Status {
226    let status = fwht_f32(data);
227    if status != Status::Success {
228        return status;
229    }
230    let norm = 1.0f32 / (data.len() as f32);
231    for val in data.iter_mut() {
232        *val *= norm;
233    }
234    Status::Success
235}
236
237/// In-place Fast Walsh-Hadamard Transform (FWHT) for 32-bit integers (`i32`).
238pub fn fwht_i32(data: &mut [i32]) -> Status {
239    let n = data.len();
240    if n < 2 || (n & (n - 1)) != 0 {
241        return Status::ArgumentError;
242    }
243
244    let mut h = 1;
245    while h < n {
246        let mut i = 0;
247        while i < n {
248            for j in i..(i + h) {
249                let x = data[j];
250                let y = data[j + h];
251                data[j] = x.wrapping_add(y);
252                data[j + h] = x.wrapping_sub(y);
253            }
254            i += h * 2;
255        }
256        h *= 2;
257    }
258
259    Status::Success
260}
261
262// --- Haar Transform (Jörg Arndt, "Matters Computational", Ch. 24) ---
263
264/// In-place, orthogonal Haar Transform for `f32`: an `O(n)` multiresolution transform using
265/// only additions, subtractions, and a `sqrt(0.5)` scale factor per stage, with no
266/// trigonometric factors at all (unlike the Fourier/Hartley transforms).
267///
268/// `data.len()` must be a power of 2 (e.g. 2, 4, 8, ..., 1024).
269pub fn haar_transform_f32(data: &mut [f32]) -> Status {
270    let n = data.len();
271    if n < 2 || (n & (n - 1)) != 0 {
272        return Status::ArgumentError;
273    }
274
275    let s2 = (0.5f32).sqrt();
276    let mut v = 1.0f32;
277    let mut js = 2;
278    while js <= n {
279        v *= s2;
280        let half = js >> 1;
281        let mut j = 0;
282        while j < n {
283            let t = j + half;
284            let x = data[j];
285            let y = data[t];
286            data[j] = x + y;
287            data[t] = (x - y) * v;
288            j += js;
289        }
290        js <<= 1;
291    }
292    data[0] *= v; // v == 1 / sqrt(n)
293
294    Status::Success
295}
296
297/// In-place Inverse Haar Transform for `f32`, undoing [`haar_transform_f32`].
298pub fn inverse_haar_transform_f32(data: &mut [f32]) -> Status {
299    let n = data.len();
300    if n < 2 || (n & (n - 1)) != 0 {
301        return Status::ArgumentError;
302    }
303
304    let s2 = 2.0f32.sqrt();
305    let mut v = 1.0f32 / (n as f32).sqrt();
306    data[0] *= v;
307
308    let mut js = n;
309    while js >= 2 {
310        let half = js >> 1;
311        let mut j = 0;
312        while j < n {
313            let t = j + half;
314            let x = data[j];
315            let y = data[t] * v;
316            data[j] = x + y;
317            data[t] = x - y;
318            j += js;
319        }
320        v *= s2;
321        js >>= 1;
322    }
323
324    Status::Success
325}
326
327/// In-place, non-normalized Haar Transform for `i32`: a forward-only, integer-exact
328/// decomposition using only wrapping add/subtract (no scaling), analogous to
329/// [`fwht_i32`]. Because the transform is non-normalized, an exact-integer inverse does not
330/// exist in general (undoing it requires dividing by powers of 2 that may not evenly divide
331/// intermediate sums); use [`haar_transform_f32`] / [`inverse_haar_transform_f32`] when an
332/// invertible round trip is required.
333///
334/// `data.len()` must be a power of 2 (e.g. 2, 4, 8, ..., 1024).
335pub fn haar_transform_i32(data: &mut [i32]) -> Status {
336    let n = data.len();
337    if n < 2 || (n & (n - 1)) != 0 {
338        return Status::ArgumentError;
339    }
340
341    let mut js = 2;
342    while js <= n {
343        let half = js >> 1;
344        let mut j = 0;
345        while j < n {
346            let t = j + half;
347            let x = data[j];
348            let y = data[t];
349            data[j] = x.wrapping_add(y);
350            data[t] = x.wrapping_sub(y);
351            j += js;
352        }
353        js <<= 1;
354    }
355
356    Status::Success
357}
358
359// --- Hartley Transform (Jörg Arndt, "Matters Computational", Ch. 25) ---
360
361/// In-place Discrete Hartley Transform for `f32`.
362///
363/// Computed via the identity relating the Hartley and Fourier transforms (Ch. 25):
364/// `H[a] = (Re(F[a]) - Im(F[a])) / sqrt(n)`, built on top of [`cfft_f32`] rather than a
365/// dedicated real-only butterfly network, so it costs a full complex FFT internally
366/// (`n <= 512`) even though its inputs and outputs are purely real.
367///
368/// The Hartley transform is its own inverse (`H[H[a]] = a`): call this function a second time
369/// on its output to invert it, with no separate inverse routine needed.
370///
371/// `data.len()` must be a power of 2 (e.g. 2, 4, 8, ..., 512).
372pub fn hartley_transform_f32(data: &mut [f32]) -> Status {
373    let n = data.len();
374    if n < 2 || (n & (n - 1)) != 0 {
375        return Status::ArgumentError;
376    }
377    if 2 * n > 1024 {
378        return Status::LengthError;
379    }
380
381    let mut c_data = [0.0f32; 1024];
382    for i in 0..n {
383        c_data[2 * i] = data[i];
384        c_data[2 * i + 1] = 0.0;
385    }
386
387    cfft_f32(&mut c_data[..2 * n], n, 0, 1);
388
389    let inv_sqrt_n = 1.0 / (n as f32).sqrt();
390    for i in 0..n {
391        data[i] = (c_data[2 * i] - c_data[2 * i + 1]) * inv_sqrt_n;
392    }
393
394    Status::Success
395}
396
397// --- Generalized Wavelet Transform (Jörg Arndt, "Matters Computational", Ch. 27) ---
398
399/// The Daubechies-4 orthogonal wavelet low-pass filter taps (Ch. 27.1), verified to satisfy
400/// the wavelet conditions `sum(h_j^2) = 1` and `sum(h_j * h_{j+2}) = 0`. Using
401/// `[sqrt(0.5), sqrt(0.5)]` instead recovers the Haar wavelet as a special case.
402pub const DAUBECHIES_4: [f32; 4] = [0.482_962_9, 0.836_516_3, 0.224_143_87, -0.129_409_52];
403
404/// The high-pass filter tap derived from low-pass filter `h` (Ch. 27.1, Eq. 27.1-2):
405/// `g[k] = (-1)^k * h[n - 1 - k]`.
406#[inline(always)]
407fn wavelet_high_pass_tap(h: &[f32], k: usize) -> f32 {
408    let v = h[h.len() - 1 - k];
409    if k % 2 == 0 { v } else { -v }
410}
411
412/// Performs one level of a fast wavelet transform step on the first `m` elements of `data`,
413/// using wavelet filter `h` (low-pass) and its derived high-pass filter. Writes the low-pass
414/// ("scaling") coefficients to `data[0..m/2]` and the high-pass ("wavelet") coefficients to
415/// `data[m/2..m]`; the underlying convolution wraps around cyclically at the block boundary.
416///
417/// `m` must be a power of 2; `h.len()` must be even and `<= m`.
418pub fn wavelet_step_f32(data: &mut [f32], m: usize, h: &[f32]) -> Status {
419    let taps = h.len();
420    if m < 2 || (m & (m - 1)) != 0 || taps == 0 || taps % 2 != 0 || taps > m || data.len() < m {
421        return Status::ArgumentError;
422    }
423    if m > 1024 {
424        return Status::LengthError;
425    }
426
427    let mut scratch = [0.0f32; 1024];
428    let nh = m >> 1;
429    let mut i = 0;
430    while i < m {
431        let mut s = 0.0f32;
432        let mut d = 0.0f32;
433        for k in 0..taps {
434            let idx = (i + k) % m;
435            let x = data[idx];
436            s += h[k] * x;
437            d += wavelet_high_pass_tap(h, k) * x;
438        }
439        let j = i / 2;
440        scratch[j] = s;
441        scratch[nh + j] = d;
442        i += 2;
443    }
444    data[..m].copy_from_slice(&scratch[..m]);
445
446    Status::Success
447}
448
449/// Performs the exact inverse of one [`wavelet_step_f32`] level.
450///
451/// `m` must be a power of 2; `h.len()` must be even and `<= m`.
452pub fn inverse_wavelet_step_f32(data: &mut [f32], m: usize, h: &[f32]) -> Status {
453    let taps = h.len();
454    if m < 2 || (m & (m - 1)) != 0 || taps == 0 || taps % 2 != 0 || taps > m || data.len() < m {
455        return Status::ArgumentError;
456    }
457    if m > 1024 {
458        return Status::LengthError;
459    }
460
461    let mut scratch = [0.0f32; 1024];
462    let nh = m >> 1;
463    for j in 0..nh {
464        let s = data[j];
465        let d = data[nh + j];
466        for k in 0..taps {
467            let idx = (2 * j + k) % m;
468            scratch[idx] += h[k] * s + wavelet_high_pass_tap(h, k) * d;
469        }
470    }
471    data[..m].copy_from_slice(&scratch[..m]);
472
473    Status::Success
474}
475
476/// Performs a full multi-level fast wavelet transform (Ch. 27): repeatedly applies
477/// [`wavelet_step_f32`] to the lower half of the array, halving the active block length each
478/// time, stopping once the block would be smaller than the filter itself (mirroring the Haar
479/// transform's pyramid structure).
480///
481/// `data.len()` must be a power of 2 and `>= h.len()`.
482pub fn wavelet_transform_f32(data: &mut [f32], h: &[f32]) -> Status {
483    let n = data.len();
484    if n < 2 || (n & (n - 1)) != 0 || h.len() > n {
485        return Status::ArgumentError;
486    }
487
488    let mut m = n;
489    while m >= h.len() {
490        let status = wavelet_step_f32(&mut data[..m], m, h);
491        if status != Status::Success {
492            return status;
493        }
494        m >>= 1;
495    }
496
497    Status::Success
498}
499
500/// Performs the exact inverse of [`wavelet_transform_f32`].
501///
502/// `data.len()` must be a power of 2 and `>= h.len()`.
503pub fn inverse_wavelet_transform_f32(data: &mut [f32], h: &[f32]) -> Status {
504    let n = data.len();
505    if n < 2 || (n & (n - 1)) != 0 || h.len() > n {
506        return Status::ArgumentError;
507    }
508
509    let mut smallest = n;
510    while smallest >= h.len() {
511        smallest >>= 1;
512    }
513    smallest <<= 1;
514
515    let mut m = smallest;
516    while m <= n {
517        let status = inverse_wavelet_step_f32(&mut data[..m], m, h);
518        if status != Status::Success {
519            return status;
520        }
521        m <<= 1;
522    }
523
524    Status::Success
525}