avila_fft/
lib.rs

1//! Avila FFT - Fast Fourier Transform
2//! Implementação científica pura em Rust - algoritmo Cooley-Tukey
3//!
4//! # Características
5//! - FFT iterativa in-place com bit-reversal
6//! - Suporte genérico para f32 e f64
7//! - Cache de twiddle factors
8//! - FFT para sinais reais (RFFT)
9//! - Funções de janelamento científicas
10//! - Zero dependências externas
11
12use std::f64::consts::PI;
13
14/// Trait para operações de ponto flutuante genéricas
15pub trait Float: Copy + Clone + std::fmt::Debug +
16    std::ops::Add<Output = Self> +
17    std::ops::Sub<Output = Self> +
18    std::ops::Mul<Output = Self> +
19    std::ops::Div<Output = Self> +
20    std::cmp::PartialOrd {
21    const PI: Self;
22    const ZERO: Self;
23    const ONE: Self;
24    const TWO: Self;    fn sin(self) -> Self;
25    fn cos(self) -> Self;
26    fn atan2(self, other: Self) -> Self;
27    fn sqrt(self) -> Self;
28    fn exp(self) -> Self;
29    fn powf(self, n: Self) -> Self;
30    fn abs(self) -> Self;
31    fn from_usize(n: usize) -> Self;
32    fn from_f64(f: f64) -> Self;
33    fn to_f64(self) -> f64;
34    fn is_nan(self) -> bool;
35    fn is_infinite(self) -> bool;
36}
37
38impl Float for f64 {
39    const PI: Self = std::f64::consts::PI;
40    const ZERO: Self = 0.0;
41    const ONE: Self = 1.0;
42    const TWO: Self = 2.0;
43
44    #[inline]
45    fn sin(self) -> Self { self.sin() }
46    #[inline]
47    fn cos(self) -> Self { self.cos() }
48    #[inline]
49    fn atan2(self, other: Self) -> Self { self.atan2(other) }
50    #[inline]
51    fn sqrt(self) -> Self { self.sqrt() }
52    #[inline]
53    fn exp(self) -> Self { self.exp() }
54    #[inline]
55    fn powf(self, n: Self) -> Self { self.powf(n) }
56    #[inline]
57    fn abs(self) -> Self { self.abs() }
58    #[inline]
59    fn from_usize(n: usize) -> Self { n as f64 }
60    #[inline]
61    fn from_f64(f: f64) -> Self { f }
62    #[inline]
63    fn to_f64(self) -> f64 { self }
64    #[inline]
65    fn is_nan(self) -> bool { self.is_nan() }
66    #[inline]
67    fn is_infinite(self) -> bool { self.is_infinite() }
68}
69
70impl Float for f32 {
71    const PI: Self = std::f32::consts::PI;
72    const ZERO: Self = 0.0;
73    const ONE: Self = 1.0;
74    const TWO: Self = 2.0;
75
76    #[inline]
77    fn sin(self) -> Self { self.sin() }
78    #[inline]
79    fn cos(self) -> Self { self.cos() }
80    #[inline]
81    fn atan2(self, other: Self) -> Self { self.atan2(other) }
82    #[inline]
83    fn sqrt(self) -> Self { self.sqrt() }
84    #[inline]
85    fn exp(self) -> Self { self.exp() }
86    #[inline]
87    fn powf(self, n: Self) -> Self { self.powf(n) }
88    #[inline]
89    fn abs(self) -> Self { self.abs() }
90    #[inline]
91    fn from_usize(n: usize) -> Self { n as f32 }
92    #[inline]
93    fn from_f64(f: f64) -> Self { f as f32 }
94    #[inline]
95    fn to_f64(self) -> f64 { self as f64 }
96    #[inline]
97    fn is_nan(self) -> bool { self.is_nan() }
98    #[inline]
99    fn is_infinite(self) -> bool { self.is_infinite() }
100}
101
102/// Número complexo genérico com suporte a f32 e f64
103#[derive(Clone, Copy, Debug)]
104pub struct Complex<T: Float> {
105    pub re: T,
106    pub im: T,
107}
108
109impl<T: Float> Complex<T> {
110    #[inline]
111    pub fn new(re: T, im: T) -> Self {
112        Self { re, im }
113    }
114
115    #[inline]
116    pub fn zero() -> Self {
117        Self { re: T::ZERO, im: T::ZERO }
118    }
119
120    #[inline]
121    pub fn one() -> Self {
122        Self { re: T::ONE, im: T::ZERO }
123    }
124
125    #[inline]
126    pub fn i() -> Self {
127        Self { re: T::ZERO, im: T::ONE }
128    }
129
130    /// Norma (módulo) do número complexo: |z| = √(re² + im²)
131    #[inline]
132    pub fn norm(&self) -> T {
133        (self.re * self.re + self.im * self.im).sqrt()
134    }
135
136    /// Norma quadrada (mais eficiente): |z|² = re² + im²
137    #[inline]
138    pub fn norm_sqr(&self) -> T {
139        self.re * self.re + self.im * self.im
140    }
141
142    /// Conjugado complexo: z* = re - i·im
143    #[inline]
144    pub fn conj(&self) -> Self {
145        Self {
146            re: self.re,
147            im: T::ZERO - self.im,
148        }
149    }
150
151    /// Argumento (fase) do número complexo: arg(z) = atan2(im, re)
152    #[inline]
153    pub fn arg(&self) -> T {
154        self.im.atan2(self.re)
155    }
156
157    /// Exponencial complexa: exp(z) = exp(re) · (cos(im) + i·sin(im))
158    #[inline]
159    pub fn exp(&self) -> Self {
160        let r = self.re.exp();
161        Self {
162            re: r * self.im.cos(),
163            im: r * self.im.sin(),
164        }
165    }
166
167    /// Potência de número complexo: z^n usando forma polar
168    #[inline]
169    pub fn powf(&self, n: T) -> Self {
170        let r = self.norm();
171        let theta = self.arg();
172        let r_n = r.powf(n);
173        let n_theta = n * theta;
174        Self {
175            re: r_n * n_theta.cos(),
176            im: r_n * n_theta.sin(),
177        }
178    }
179
180    /// Verifica se contém NaN
181    #[inline]
182    pub fn is_nan(&self) -> bool {
183        self.re.is_nan() || self.im.is_nan()
184    }
185
186    /// Verifica se contém infinito
187    #[inline]
188    pub fn is_infinite(&self) -> bool {
189        self.re.is_infinite() || self.im.is_infinite()
190    }
191
192    /// Verifica se é finito (não NaN nem infinito)
193    #[inline]
194    pub fn is_finite(&self) -> bool {
195        !self.is_nan() && !self.is_infinite()
196    }
197}
198
199impl<T: Float> std::ops::Add for Complex<T> {
200    type Output = Self;
201    #[inline]
202    fn add(self, rhs: Self) -> Self {
203        Self {
204            re: self.re + rhs.re,
205            im: self.im + rhs.im,
206        }
207    }
208}
209
210impl<T: Float> std::ops::Sub for Complex<T> {
211    type Output = Self;
212    #[inline]
213    fn sub(self, rhs: Self) -> Self {
214        Self {
215            re: self.re - rhs.re,
216            im: self.im - rhs.im,
217        }
218    }
219}
220
221impl<T: Float> std::ops::Mul for Complex<T> {
222    type Output = Self;
223    #[inline]
224    fn mul(self, rhs: Self) -> Self {
225        Self {
226            re: self.re * rhs.re - self.im * rhs.im,
227            im: self.re * rhs.im + self.im * rhs.re,
228        }
229    }
230}
231
232impl<T: Float> std::ops::Mul for &Complex<T> {
233    type Output = Complex<T>;
234    #[inline]
235    fn mul(self, rhs: Self) -> Complex<T> {
236        Complex {
237            re: self.re * rhs.re - self.im * rhs.im,
238            im: self.re * rhs.im + self.im * rhs.re,
239        }
240    }
241}
242
243impl<T: Float> std::ops::Div for Complex<T> {
244    type Output = Self;
245    #[inline]
246    fn div(self, rhs: Self) -> Self {
247        let denom = rhs.re * rhs.re + rhs.im * rhs.im;
248        Self {
249            re: (self.re * rhs.re + self.im * rhs.im) / denom,
250            im: (self.im * rhs.re - self.re * rhs.im) / denom,
251        }
252    }
253}
254
255impl<T: Float> std::ops::Div<T> for Complex<T> {
256    type Output = Self;
257    #[inline]
258    fn div(self, rhs: T) -> Self {
259        Self {
260            re: self.re / rhs,
261            im: self.im / rhs,
262        }
263    }
264}
265
266impl<T: Float> std::ops::Mul<T> for Complex<T> {
267    type Output = Self;
268    #[inline]
269    fn mul(self, rhs: T) -> Self {
270        Self {
271            re: self.re * rhs,
272            im: self.im * rhs,
273        }
274    }
275}
276
277impl<T: Float> std::ops::AddAssign for Complex<T> {
278    #[inline]
279    fn add_assign(&mut self, rhs: Self) {
280        self.re = self.re + rhs.re;
281        self.im = self.im + rhs.im;
282    }
283}
284
285impl<T: Float> std::ops::SubAssign for Complex<T> {
286    #[inline]
287    fn sub_assign(&mut self, rhs: Self) {
288        self.re = self.re - rhs.re;
289        self.im = self.im - rhs.im;
290    }
291}
292
293impl<T: Float> std::ops::MulAssign<T> for Complex<T> {
294    #[inline]
295    fn mul_assign(&mut self, rhs: T) {
296        self.re = self.re * rhs;
297        self.im = self.im * rhs;
298    }
299}
300
301impl<T: Float> std::iter::Sum for Complex<T> {
302    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
303        iter.fold(Complex::zero(), |acc, x| acc + x)
304    }
305}
306
307impl<'a, T: Float> std::iter::Sum<&'a Complex<T>> for Complex<T> {
308    fn sum<I: Iterator<Item = &'a Complex<T>>>(iter: I) -> Self {
309        iter.fold(Complex::zero(), |acc, x| acc + *x)
310    }
311}
312
313/// Erros da biblioteca FFT
314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
315pub enum FftError {
316    /// Tamanho não é potência de 2
317    InvalidSize,
318    /// Input contém NaN
319    ContainsNaN,
320    /// Input contém infinito
321    ContainsInfinity,
322    /// Tamanho zero
323    EmptyInput,
324}
325
326impl std::fmt::Display for FftError {
327    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
328        match self {
329            FftError::InvalidSize => write!(f, "Tamanho deve ser potência de 2"),
330            FftError::ContainsNaN => write!(f, "Input contém NaN"),
331            FftError::ContainsInfinity => write!(f, "Input contém infinito"),
332            FftError::EmptyInput => write!(f, "Input vazio"),
333        }
334    }
335}
336
337impl std::error::Error for FftError {}
338
339pub type Result<T> = std::result::Result<T, FftError>;
340
341/// Verifica se n é potência de 2
342#[inline]
343fn is_power_of_two(n: usize) -> bool {
344    n > 0 && (n & (n - 1)) == 0
345}
346
347/// Calcula log₂(n) para potências de 2
348#[inline]
349fn log2(mut n: usize) -> usize {
350    let mut log = 0;
351    while n > 1 {
352        n >>= 1;
353        log += 1;
354    }
355    log
356}
357
358/// Bit-reversal permutation
359/// Reorganiza array de acordo com índices bit-reversed
360#[inline]
361fn bit_reverse_permutation<T: Float>(data: &mut [Complex<T>]) {
362    let n = data.len();
363    let log_n = log2(n);
364
365    for i in 0..n {
366        let j = reverse_bits(i, log_n);
367        if i < j {
368            data.swap(i, j);
369        }
370    }
371}
372
373/// Reverte bits de um número
374#[inline]
375fn reverse_bits(mut x: usize, bits: usize) -> usize {
376    let mut result = 0;
377    for _ in 0..bits {
378        result = (result << 1) | (x & 1);
379        x >>= 1;
380    }
381    result
382}
383
384/// Planner para FFT - pré-calcula e cacheia twiddle factors
385pub struct FftPlanner<T: Float> {
386    size: usize,
387    twiddles: Vec<Complex<T>>,
388    inverse: bool,
389}
390
391impl<T: Float> FftPlanner<T> {
392    /// Cria novo planner para tamanho específico
393    pub fn new(size: usize, inverse: bool) -> Result<Self> {
394        if size == 0 {
395            return Err(FftError::EmptyInput);
396        }
397        if !is_power_of_two(size) {
398            return Err(FftError::InvalidSize);
399        }
400
401        let twiddles = Self::precompute_twiddles(size, inverse);
402        Ok(Self { size, twiddles, inverse })
403    }
404
405    /// Pré-computa twiddle factors: W_N^k = exp(-2πik/N)
406    fn precompute_twiddles(n: usize, inverse: bool) -> Vec<Complex<T>> {
407        let sign = if inverse { T::ONE } else { T::ZERO - T::ONE };
408        let mut twiddles = Vec::with_capacity(n / 2);
409
410        for k in 0..n/2 {
411            let angle = sign * T::TWO * T::PI * T::from_usize(k) / T::from_usize(n);
412            twiddles.push(Complex::new(angle.cos(), angle.sin()));
413        }
414
415        twiddles
416    }
417
418    /// Executa FFT iterativa in-place usando Cooley-Tukey
419    pub fn process_in_place(&self, data: &mut [Complex<T>]) -> Result<()> {
420        if data.len() != self.size {
421            return Err(FftError::InvalidSize);
422        }
423
424        // Validação científica
425        for c in data.iter() {
426            if c.is_nan() {
427                return Err(FftError::ContainsNaN);
428            }
429            if c.is_infinite() {
430                return Err(FftError::ContainsInfinity);
431            }
432        }
433
434        // Bit-reversal permutation
435        bit_reverse_permutation(data);
436
437        // Algoritmo iterativo Cooley-Tukey
438        let n = data.len();
439        let mut size = 2;
440
441        while size <= n {
442            let half_size = size / 2;
443            let step = n / size;
444
445            for i in (0..n).step_by(size) {
446                for j in 0..half_size {
447                    let k = j * step;
448                    let twiddle = self.twiddles[k];
449
450                    let t = twiddle * data[i + j + half_size];
451                    let u = data[i + j];
452
453                    data[i + j] = u + t;
454                    data[i + j + half_size] = u - t;
455                }
456            }
457
458            size *= 2;
459        }
460
461        // Normalização para IFFT
462        if self.inverse {
463            let scale = T::ONE / T::from_usize(n);
464            for c in data.iter_mut() {
465                *c *= scale;
466            }
467        }
468
469        Ok(())
470    }
471
472    /// Processa e retorna novo vetor
473    pub fn process(&self, input: &[Complex<T>]) -> Result<Vec<Complex<T>>> {
474        let mut output = input.to_vec();
475        self.process_in_place(&mut output)?;
476        Ok(output)
477    }
478}
479
480pub struct Fft {
481    size: usize,
482}
483
484impl Fft {
485    pub fn new(size: usize) -> Self {
486        Self { size }
487    }
488
489    pub fn process(&self, input: &[Complex<f64>]) -> Vec<Complex<f64>> {
490        if input.len() != self.size {
491            panic!("Input size mismatch");
492        }
493        fft_recursive(input)
494    }
495
496    pub fn process_with_scratch(&self, input: &mut [Complex<f64>], _scratch: &mut [Complex<f64>]) {
497        let result = self.process(input);
498        input.copy_from_slice(&result);
499    }
500}
501
502fn fft_recursive(x: &[Complex<f64>]) -> Vec<Complex<f64>> {
503    let n = x.len();
504
505    if n <= 1 {
506        return x.to_vec();
507    }
508
509    // Divide
510    let mut even = Vec::with_capacity(n / 2);
511    let mut odd = Vec::with_capacity(n / 2);
512
513    for (i, &val) in x.iter().enumerate() {
514        if i % 2 == 0 {
515            even.push(val);
516        } else {
517            odd.push(val);
518        }
519    }
520
521    // Conquer
522    let even_fft = fft_recursive(&even);
523    let odd_fft = fft_recursive(&odd);
524
525    // Combine
526    let mut result = vec![Complex::zero(); n];
527
528    for k in 0..n / 2 {
529        let angle = -2.0 * PI * (k as f64) / (n as f64);
530        let twiddle = Complex::new(angle.cos(), angle.sin());
531        let t = twiddle * odd_fft[k];
532
533        result[k] = even_fft[k] + t;
534        result[k + n / 2] = even_fft[k] - t;
535    }
536
537    result
538}
539
540/// FFT usando algoritmo recursivo (compatibilidade)
541pub fn fft(input: &[Complex<f64>]) -> Vec<Complex<f64>> {
542    fft_recursive(input)
543}
544
545/// FFT genérica - recomendado usar FftPlanner para melhor performance
546pub fn fft_generic<T: Float>(input: &[Complex<T>]) -> Result<Vec<Complex<T>>> {
547    let planner = FftPlanner::new(input.len(), false)?;
548    planner.process(input)
549}
550
551/// IFFT usando algoritmo recursivo (compatibilidade)
552pub fn ifft(input: &[Complex<f64>]) -> Vec<Complex<f64>> {
553    let n = input.len();
554
555    // Conjugate
556    let conjugated: Vec<Complex<f64>> = input
557        .iter()
558        .map(|c| Complex::new(c.re, -c.im))
559        .collect();
560
561    // FFT
562    let result = fft_recursive(&conjugated);
563
564    // Conjugate and scale
565    result
566        .iter()
567        .map(|c| Complex::new(c.re / n as f64, -c.im / n as f64))
568        .collect()
569}
570
571/// IFFT genérica - recomendado usar FftPlanner
572pub fn ifft_generic<T: Float>(input: &[Complex<T>]) -> Result<Vec<Complex<T>>> {
573    let planner = FftPlanner::new(input.len(), true)?;
574    planner.process(input)
575}
576
577/// FFT para sinais reais - aproveita simetria Hermitiana
578/// Retorna apenas frequências positivas (N/2+1 pontos)
579pub fn rfft<T: Float>(input: &[T]) -> Result<Vec<Complex<T>>> {
580    let n = input.len();
581    if !is_power_of_two(n) {
582        return Err(FftError::InvalidSize);
583    }
584
585    // Converte para complexo
586    let complex_input: Vec<Complex<T>> = input
587        .iter()
588        .map(|&x| Complex::new(x, T::ZERO))
589        .collect();
590
591    // FFT completa
592    let full_fft = fft_generic(&complex_input)?;
593
594    // Retorna apenas metade (simetria Hermitiana)
595    Ok(full_fft[..n/2 + 1].to_vec())
596}
597
598/// IFFT para sinais reais - reconstrução a partir de espectro Hermitiano
599pub fn irfft<T: Float>(input: &[Complex<T>], n: usize) -> Result<Vec<T>> {
600    if !is_power_of_two(n) {
601        return Err(FftError::InvalidSize);
602    }
603
604    // Reconstrói espectro completo usando simetria Hermitiana
605    let mut full_spectrum = vec![Complex::zero(); n];
606
607    // Copia metade positiva
608    for (i, &val) in input.iter().enumerate() {
609        full_spectrum[i] = val;
610    }
611
612    // Reconstrói metade negativa (conjugado reverso)
613    for i in 1..n/2 {
614        full_spectrum[n - i] = full_spectrum[i].conj();
615    }
616
617    // IFFT
618    let complex_output = ifft_generic(&full_spectrum)?;
619
620    // Extrai parte real
621    Ok(complex_output.iter().map(|c| c.re).collect())
622}
623
624/// Funções de janelamento para análise espectral
625pub mod window {
626    use super::Float;
627
628    /// Janela de Hamming: w(n) = 0.54 - 0.46·cos(2πn/(N-1))
629    pub fn hamming<T: Float>(n: usize) -> Vec<T> {
630        let n_f = T::from_usize(n);
631        (0..n)
632            .map(|i| {
633                let i_f = T::from_usize(i);
634                let a0 = T::from_usize(54) / T::from_usize(100);
635                let a1 = T::from_usize(46) / T::from_usize(100);
636                let arg = T::TWO * T::PI * i_f / (n_f - T::ONE);
637                a0 - a1 * arg.cos()
638            })
639            .collect()
640    }
641
642    /// Janela de Hann: w(n) = 0.5·(1 - cos(2πn/(N-1)))
643    pub fn hann<T: Float>(n: usize) -> Vec<T> {
644        let n_f = T::from_usize(n);
645        (0..n)
646            .map(|i| {
647                let i_f = T::from_usize(i);
648                let arg = T::TWO * T::PI * i_f / (n_f - T::ONE);
649                (T::ONE - arg.cos()) / T::TWO
650            })
651            .collect()
652    }
653
654    /// Janela de Blackman: w(n) = 0.42 - 0.5·cos(2πn/(N-1)) + 0.08·cos(4πn/(N-1))
655    pub fn blackman<T: Float>(n: usize) -> Vec<T> {
656        let n_f = T::from_usize(n);
657        (0..n)
658            .map(|i| {
659                let i_f = T::from_usize(i);
660                let a0 = T::from_usize(42) / T::from_usize(100);
661                let a1 = T::from_usize(50) / T::from_usize(100);
662                let a2 = T::from_usize(8) / T::from_usize(100);
663                let arg = T::TWO * T::PI * i_f / (n_f - T::ONE);
664                a0 - a1 * arg.cos() + a2 * (T::TWO * arg).cos()
665            })
666            .collect()
667    }
668
669    /// Janela de Blackman-Harris: 4 termos, atenuação ~92dB
670    pub fn blackman_harris<T: Float>(n: usize) -> Vec<T> {
671        let n_f = T::from_usize(n);
672        (0..n)
673            .map(|i| {
674                let i_f = T::from_usize(i);
675                let a0 = T::from_usize(35875) / T::from_usize(100000);
676                let a1 = T::from_usize(48829) / T::from_usize(100000);
677                let a2 = T::from_usize(14128) / T::from_usize(100000);
678                let a3 = T::from_usize(1168) / T::from_usize(100000);
679
680                let arg = T::TWO * T::PI * i_f / (n_f - T::ONE);
681                a0
682                    - a1 * arg.cos()
683                    + a2 * (T::TWO * arg).cos()
684                    - a3 * (T::TWO * T::from_usize(3) * arg / T::TWO).cos()
685            })
686            .collect()
687    }
688
689    /// Janela triangular (Bartlett): w(n) = 1 - |2n/(N-1) - 1|
690    pub fn bartlett<T: Float>(n: usize) -> Vec<T> {
691        let n_f = T::from_usize(n);
692        (0..n)
693            .map(|i| {
694                let i_f = T::from_usize(i);
695                let x = T::TWO * i_f / (n_f - T::ONE) - T::ONE;
696                T::ONE - x.abs()
697            })
698            .collect()
699    }
700
701    /// Janela retangular (todas as amostras = 1)
702    pub fn rectangular<T: Float>(n: usize) -> Vec<T> {
703        vec![T::ONE; n]
704    }
705
706    /// Aplica janela a um sinal
707    pub fn apply<T: Float>(signal: &[T], window: &[T]) -> Vec<T> {
708        signal.iter()
709            .zip(window.iter())
710            .map(|(&s, &w)| s * w)
711            .collect()
712    }
713}
714
715/// Processamento de imagens 2D via FFT
716pub mod fft2d {
717    use super::*;
718
719    /// Representa uma imagem/matriz 2D
720    #[derive(Clone, Debug)]
721    pub struct Image2D<T: Float> {
722        pub width: usize,
723        pub height: usize,
724        pub data: Vec<Complex<T>>,
725    }
726
727    impl<T: Float> Image2D<T> {
728        /// Cria nova imagem 2D
729        pub fn new(width: usize, height: usize) -> Self {
730            Self {
731                width,
732                height,
733                data: vec![Complex::zero(); width * height],
734            }
735        }
736
737        /// Cria imagem a partir de dados reais
738        pub fn from_real(width: usize, height: usize, data: Vec<T>) -> Result<Self> {
739            if data.len() != width * height {
740                return Err(FftError::InvalidSize);
741            }
742            Ok(Self {
743                width,
744                height,
745                data: data.iter().map(|&x| Complex::new(x, T::ZERO)).collect(),
746            })
747        }
748
749        /// Cria imagem a partir de dados complexos
750        pub fn from_complex(width: usize, height: usize, data: Vec<Complex<T>>) -> Result<Self> {
751            if data.len() != width * height {
752                return Err(FftError::InvalidSize);
753            }
754            Ok(Self { width, height, data })
755        }
756
757        /// Acessa pixel em (x, y)
758        #[inline]
759        pub fn get(&self, x: usize, y: usize) -> Complex<T> {
760            self.data[y * self.width + x]
761        }
762
763        /// Define pixel em (x, y)
764        #[inline]
765        pub fn set(&mut self, x: usize, y: usize, value: Complex<T>) {
766            self.data[y * self.width + x] = value;
767        }
768
769        /// Extrai linha
770        pub fn get_row(&self, y: usize) -> Vec<Complex<T>> {
771            let start = y * self.width;
772            self.data[start..start + self.width].to_vec()
773        }
774
775        /// Define linha
776        pub fn set_row(&mut self, y: usize, row: &[Complex<T>]) {
777            let start = y * self.width;
778            self.data[start..start + self.width].copy_from_slice(row);
779        }
780
781        /// Extrai coluna
782        pub fn get_column(&self, x: usize) -> Vec<Complex<T>> {
783            (0..self.height)
784                .map(|y| self.get(x, y))
785                .collect()
786        }
787
788        /// Define coluna
789        pub fn set_column(&mut self, x: usize, column: &[Complex<T>]) {
790            for (y, &value) in column.iter().enumerate() {
791                self.set(x, y, value);
792            }
793        }
794
795        /// Calcula magnitude de cada pixel
796        pub fn magnitude(&self) -> Vec<T> {
797            self.data.iter().map(|c| c.norm()).collect()
798        }
799
800        /// Calcula espectro de potência
801        pub fn power_spectrum(&self) -> Vec<T> {
802            self.data.iter().map(|c| c.norm_sqr()).collect()
803        }
804
805        /// Shift FFT (move DC para centro)
806        pub fn fftshift(&mut self) {
807            let half_h = self.height / 2;
808            let half_w = self.width / 2;
809
810            // Swap quadrantes
811            for y in 0..half_h {
812                for x in 0..half_w {
813                    // Q1 <-> Q3
814                    let temp = self.get(x, y);
815                    self.set(x, y, self.get(x + half_w, y + half_h));
816                    self.set(x + half_w, y + half_h, temp);
817
818                    // Q2 <-> Q4
819                    let temp = self.get(x + half_w, y);
820                    self.set(x + half_w, y, self.get(x, y + half_h));
821                    self.set(x, y + half_h, temp);
822                }
823            }
824        }
825    }
826
827    /// Planner para FFT 2D
828    pub struct Fft2DPlanner<T: Float> {
829        width: usize,
830        height: usize,
831        row_planner: FftPlanner<T>,
832        col_planner: FftPlanner<T>,
833        #[allow(dead_code)]
834        inverse: bool,
835    }
836
837    impl<T: Float> Fft2DPlanner<T> {
838        /// Cria novo planner 2D
839        pub fn new(width: usize, height: usize, inverse: bool) -> Result<Self> {
840            let row_planner = FftPlanner::new(width, inverse)?;
841            let col_planner = FftPlanner::new(height, inverse)?;
842
843            Ok(Self {
844                width,
845                height,
846                row_planner,
847                col_planner,
848                inverse,
849            })
850        }
851
852        /// Executa FFT 2D usando algoritmo row-column
853        /// 1. FFT em cada linha
854        /// 2. FFT em cada coluna
855        pub fn process(&self, image: &Image2D<T>) -> Result<Image2D<T>> {
856            if image.width != self.width || image.height != self.height {
857                return Err(FftError::InvalidSize);
858            }
859
860            let mut result = image.clone();
861
862            // FFT em cada linha
863            for y in 0..self.height {
864                let row = result.get_row(y);
865                let transformed = self.row_planner.process(&row)?;
866                result.set_row(y, &transformed);
867            }
868
869            // FFT em cada coluna
870            for x in 0..self.width {
871                let col = result.get_column(x);
872                let transformed = self.col_planner.process(&col)?;
873                result.set_column(x, &transformed);
874            }
875
876            Ok(result)
877        }
878
879        /// Processa in-place
880        pub fn process_in_place(&self, image: &mut Image2D<T>) -> Result<()> {
881            if image.width != self.width || image.height != self.height {
882                return Err(FftError::InvalidSize);
883            }
884
885            // FFT em cada linha
886            for y in 0..self.height {
887                let mut row = image.get_row(y);
888                self.row_planner.process_in_place(&mut row)?;
889                image.set_row(y, &row);
890            }
891
892            // FFT em cada coluna
893            for x in 0..self.width {
894                let mut col = image.get_column(x);
895                self.col_planner.process_in_place(&mut col)?;
896                image.set_column(x, &col);
897            }
898
899            Ok(())
900        }
901    }
902
903    /// FFT 2D conveniente
904    pub fn fft2d<T: Float>(image: &Image2D<T>) -> Result<Image2D<T>> {
905        let planner = Fft2DPlanner::new(image.width, image.height, false)?;
906        planner.process(image)
907    }
908
909    /// IFFT 2D conveniente
910    pub fn ifft2d<T: Float>(image: &Image2D<T>) -> Result<Image2D<T>> {
911        let planner = Fft2DPlanner::new(image.width, image.height, true)?;
912        planner.process(image)
913    }
914}
915
916/// Filtros espaciais no domínio da frequência
917pub mod filters {
918    use super::*;
919    use super::fft2d::*;
920
921    /// Tipos de filtro
922    #[derive(Debug, Clone, Copy)]
923    pub enum FilterType {
924        LowPass,
925        HighPass,
926        BandPass,
927        BandReject,
928    }
929
930    /// Filtro ideal no domínio da frequência
931    pub struct IdealFilter<T: Float> {
932        width: usize,
933        height: usize,
934        filter_type: FilterType,
935        cutoff: T,
936        cutoff2: Option<T>, // Para band-pass/reject
937    }
938
939    impl<T: Float> IdealFilter<T> {
940        /// Cria novo filtro ideal
941        pub fn new(width: usize, height: usize, filter_type: FilterType, cutoff: T) -> Self {
942            Self {
943                width,
944                height,
945                filter_type,
946                cutoff,
947                cutoff2: None,
948            }
949        }
950
951        /// Cria filtro banda (requer dois cutoffs)
952        pub fn new_band(
953            width: usize,
954            height: usize,
955            filter_type: FilterType,
956            cutoff1: T,
957            cutoff2: T,
958        ) -> Self {
959            Self {
960                width,
961                height,
962                filter_type,
963                cutoff: cutoff1,
964                cutoff2: Some(cutoff2),
965            }
966        }
967
968        /// Calcula distância do centro
969        #[inline]
970        fn distance(&self, x: usize, y: usize) -> T {
971            let cx = T::from_usize(self.width) / T::TWO;
972            let cy = T::from_usize(self.height) / T::TWO;
973            let dx = T::from_usize(x) - cx;
974            let dy = T::from_usize(y) - cy;
975            (dx * dx + dy * dy).sqrt()
976        }
977
978        /// Aplica filtro à imagem no domínio da frequência
979        pub fn apply(&self, freq_image: &mut Image2D<T>) {
980            for y in 0..self.height {
981                for x in 0..self.width {
982                    let dist = self.distance(x, y);
983                    let factor = self.filter_factor(dist);
984                    let pixel = freq_image.get(x, y);
985                    freq_image.set(x, y, pixel * factor);
986                }
987            }
988        }
989
990        /// Calcula fator de atenuação baseado no tipo de filtro
991        fn filter_factor(&self, distance: T) -> T {
992            match self.filter_type {
993                FilterType::LowPass => {
994                    if distance <= self.cutoff {
995                        T::ONE
996                    } else {
997                        T::ZERO
998                    }
999                }
1000                FilterType::HighPass => {
1001                    if distance > self.cutoff {
1002                        T::ONE
1003                    } else {
1004                        T::ZERO
1005                    }
1006                }
1007                FilterType::BandPass => {
1008                    let cutoff2 = self.cutoff2.unwrap_or(self.cutoff);
1009                    if distance >= self.cutoff && distance <= cutoff2 {
1010                        T::ONE
1011                    } else {
1012                        T::ZERO
1013                    }
1014                }
1015                FilterType::BandReject => {
1016                    let cutoff2 = self.cutoff2.unwrap_or(self.cutoff);
1017                    if distance < self.cutoff || distance > cutoff2 {
1018                        T::ONE
1019                    } else {
1020                        T::ZERO
1021                    }
1022                }
1023            }
1024        }
1025    }
1026
1027    /// Filtro Gaussiano (suave, sem ringing)
1028    pub struct GaussianFilter<T: Float> {
1029        width: usize,
1030        height: usize,
1031        sigma: T,
1032        high_pass: bool,
1033    }
1034
1035    impl<T: Float> GaussianFilter<T> {
1036        /// Cria filtro Gaussiano
1037        pub fn new(width: usize, height: usize, sigma: T, high_pass: bool) -> Self {
1038            Self {
1039                width,
1040                height,
1041                sigma,
1042                high_pass,
1043            }
1044        }
1045
1046        /// Calcula distância do centro
1047        #[inline]
1048        fn distance(&self, x: usize, y: usize) -> T {
1049            let cx = T::from_usize(self.width) / T::TWO;
1050            let cy = T::from_usize(self.height) / T::TWO;
1051            let dx = T::from_usize(x) - cx;
1052            let dy = T::from_usize(y) - cy;
1053            (dx * dx + dy * dy).sqrt()
1054        }
1055
1056        /// Aplica filtro Gaussiano
1057        pub fn apply(&self, freq_image: &mut Image2D<T>) {
1058            for y in 0..self.height {
1059                for x in 0..self.width {
1060                    let dist = self.distance(x, y);
1061                    let dist_sqr = dist * dist;
1062                    let sigma_sqr = self.sigma * self.sigma;
1063
1064                    // H(u,v) = exp(-D²(u,v) / 2σ²)
1065                    let factor = (T::ZERO - dist_sqr / (T::TWO * sigma_sqr)).exp();
1066
1067                    let final_factor = if self.high_pass {
1068                        T::ONE - factor // High-pass = 1 - Low-pass
1069                    } else {
1070                        factor
1071                    };
1072
1073                    let pixel = freq_image.get(x, y);
1074                    freq_image.set(x, y, pixel * final_factor);
1075                }
1076            }
1077        }
1078    }
1079
1080    /// Convolução 2D via FFT (rápida para kernels grandes)
1081    pub fn convolve2d<T: Float>(
1082        image: &Image2D<T>,
1083        kernel: &Image2D<T>,
1084    ) -> Result<Image2D<T>> {
1085        // Ambas devem ter mesmo tamanho (pad se necessário)
1086        if image.width != kernel.width || image.height != kernel.height {
1087            return Err(FftError::InvalidSize);
1088        }
1089
1090        // FFT de ambas
1091        let freq_image = fft2d(image)?;
1092        let freq_kernel = fft2d(kernel)?;
1093
1094        // Multiplicação ponto-a-ponto no domínio da frequência
1095        let mut result = freq_image.clone();
1096        for i in 0..result.data.len() {
1097            result.data[i] = freq_image.data[i] * freq_kernel.data[i];
1098        }
1099
1100        // IFFT para voltar ao domínio espacial
1101        ifft2d(&result)
1102    }
1103}
1104
1105/// Análise tempo-frequência (STFT e Espectrograma)
1106pub mod timefreq {
1107    use super::*;
1108
1109    /// Configuração de overlap entre janelas
1110    #[derive(Debug, Clone, Copy)]
1111    pub struct OverlapConfig {
1112        /// Tamanho da janela FFT
1113        pub window_size: usize,
1114        /// Passo entre janelas (hop size)
1115        pub hop_size: usize,
1116    }
1117
1118    impl OverlapConfig {
1119        /// Cria configuração com overlap de 50%
1120        pub fn overlap_50(window_size: usize) -> Self {
1121            Self {
1122                window_size,
1123                hop_size: window_size / 2,
1124            }
1125        }
1126
1127        /// Cria configuração com overlap de 75%
1128        pub fn overlap_75(window_size: usize) -> Self {
1129            Self {
1130                window_size,
1131                hop_size: window_size / 4,
1132            }
1133        }
1134
1135        /// Cria configuração customizada
1136        pub fn new(window_size: usize, hop_size: usize) -> Result<Self> {
1137            if hop_size == 0 || hop_size > window_size {
1138                return Err(FftError::InvalidSize);
1139            }
1140            Ok(Self { window_size, hop_size })
1141        }
1142
1143        /// Calcula número de frames
1144        pub fn num_frames(&self, signal_len: usize) -> usize {
1145            if signal_len < self.window_size {
1146                return 0;
1147            }
1148            (signal_len - self.window_size) / self.hop_size + 1
1149        }
1150
1151        /// Calcula percentual de overlap
1152        pub fn overlap_percent(&self) -> f64 {
1153            100.0 * (1.0 - self.hop_size as f64 / self.window_size as f64)
1154        }
1155    }
1156
1157    /// Espectrograma - matriz tempo-frequência
1158    #[derive(Clone, Debug)]
1159    pub struct Spectrogram<T: Float> {
1160        /// Dados [freq][time]
1161        pub data: Vec<Vec<Complex<T>>>,
1162        /// Número de frames temporais
1163        pub num_frames: usize,
1164        /// Número de bins de frequência
1165        pub num_freqs: usize,
1166        /// Taxa de amostragem
1167        pub sample_rate: T,
1168        /// Configuração de overlap
1169        pub config: OverlapConfig,
1170    }
1171
1172    impl<T: Float> Spectrogram<T> {
1173        /// Cria espectrograma vazio
1174        pub fn new(
1175            num_frames: usize,
1176            num_freqs: usize,
1177            sample_rate: T,
1178            config: OverlapConfig,
1179        ) -> Self {
1180            let data = vec![vec![Complex::zero(); num_frames]; num_freqs];
1181            Self {
1182                data,
1183                num_frames,
1184                num_freqs,
1185                sample_rate,
1186                config,
1187            }
1188        }
1189
1190        /// Acessa valor em (freq, time)
1191        #[inline]
1192        pub fn get(&self, freq_idx: usize, time_idx: usize) -> Complex<T> {
1193            self.data[freq_idx][time_idx]
1194        }
1195
1196        /// Define valor em (freq, time)
1197        #[inline]
1198        pub fn set(&mut self, freq_idx: usize, time_idx: usize, value: Complex<T>) {
1199            self.data[freq_idx][time_idx] = value;
1200        }
1201
1202        /// Calcula magnitude do espectrograma
1203        pub fn magnitude(&self) -> Vec<Vec<T>> {
1204            self.data
1205                .iter()
1206                .map(|freq_row| freq_row.iter().map(|c| c.norm()).collect())
1207                .collect()
1208        }
1209
1210        /// Calcula potência (magnitude²)
1211        pub fn power(&self) -> Vec<Vec<T>> {
1212            self.data
1213                .iter()
1214                .map(|freq_row| freq_row.iter().map(|c| c.norm_sqr()).collect())
1215                .collect()
1216        }
1217
1218        /// Calcula fase
1219        pub fn phase(&self) -> Vec<Vec<T>> {
1220            self.data
1221                .iter()
1222                .map(|freq_row| freq_row.iter().map(|c| c.arg()).collect())
1223                .collect()
1224        }
1225
1226        /// Converte magnitude para dB
1227        pub fn magnitude_db(&self) -> Vec<Vec<T>> {
1228            let mag = self.magnitude();
1229            let twenty = T::from_usize(20);
1230            let epsilon = T::from_usize(1) / T::from_usize(1000000);
1231            mag.iter()
1232                .map(|row| {
1233                    row.iter()
1234                        .map(|&m| {
1235                            // Evita log(0)
1236                            let m_safe = if m > T::ZERO { m } else { epsilon };
1237                            let log_val = T::from_f64(m_safe.to_f64().log10());
1238                            twenty * log_val
1239                        })
1240                        .collect()
1241                })
1242                .collect()
1243        }
1244
1245        /// Retorna vetor de frequências (Hz)
1246        pub fn frequencies(&self) -> Vec<T> {
1247            (0..self.num_freqs)
1248                .map(|k| {
1249                    T::from_usize(k) * self.sample_rate / T::from_usize(self.config.window_size)
1250                })
1251                .collect()
1252        }
1253
1254        /// Retorna vetor de tempos (segundos)
1255        pub fn times(&self) -> Vec<T> {
1256            (0..self.num_frames)
1257                .map(|frame| {
1258                    T::from_usize(frame * self.config.hop_size) / self.sample_rate
1259                })
1260                .collect()
1261        }
1262
1263        /// Centróide espectral por frame (centro de massa do espectro)
1264        pub fn spectral_centroid(&self) -> Vec<T> {
1265            let freqs = self.frequencies();
1266            let mag = self.magnitude();
1267
1268            (0..self.num_frames)
1269                .map(|frame| {
1270                    let mut weighted_sum = T::ZERO;
1271                    let mut total_mag = T::ZERO;
1272
1273                    for freq_idx in 0..self.num_freqs {
1274                        let m = mag[freq_idx][frame];
1275                        weighted_sum = weighted_sum + freqs[freq_idx] * m;
1276                        total_mag = total_mag + m;
1277                    }
1278
1279                    if total_mag > T::ZERO {
1280                        weighted_sum / total_mag
1281                    } else {
1282                        T::ZERO
1283                    }
1284                })
1285                .collect()
1286        }
1287
1288        /// Largura de banda espectral por frame
1289        pub fn spectral_bandwidth(&self) -> Vec<T> {
1290            let freqs = self.frequencies();
1291            let mag = self.magnitude();
1292            let centroid = self.spectral_centroid();
1293
1294            (0..self.num_frames)
1295                .map(|frame| {
1296                    let mut variance = T::ZERO;
1297                    let mut total_mag = T::ZERO;
1298
1299                    for freq_idx in 0..self.num_freqs {
1300                        let m = mag[freq_idx][frame];
1301                        let diff = freqs[freq_idx] - centroid[frame];
1302                        variance = variance + (diff * diff) * m;
1303                        total_mag = total_mag + m;
1304                    }
1305
1306                    if total_mag > T::ZERO {
1307                        (variance / total_mag).sqrt()
1308                    } else {
1309                        T::ZERO
1310                    }
1311                })
1312                .collect()
1313        }
1314
1315        /// Flatness espectral (razão entre média geométrica e aritmética)
1316        /// Valores próximos de 1 = ruído branco, próximos de 0 = tons puros
1317        pub fn spectral_flatness(&self) -> Vec<T> {
1318            let mag = self.magnitude();
1319
1320            (0..self.num_frames)
1321                .map(|frame| {
1322                    let mut geometric_mean = T::ONE;
1323                    let mut arithmetic_mean = T::ZERO;
1324                    let n = T::from_usize(self.num_freqs);
1325
1326                    for freq_idx in 0..self.num_freqs {
1327                        let m = mag[freq_idx][frame];
1328                        let m_safe = if m > T::ZERO { m } else { T::from_usize(1) / T::from_usize(1000000) };
1329                        geometric_mean = geometric_mean * m_safe;
1330                        arithmetic_mean = arithmetic_mean + m;
1331                    }
1332
1333                    geometric_mean = geometric_mean.powf(T::ONE / n);
1334                    arithmetic_mean = arithmetic_mean / n;
1335
1336                    if arithmetic_mean > T::ZERO {
1337                        geometric_mean / arithmetic_mean
1338                    } else {
1339                        T::ZERO
1340                    }
1341                })
1342                .collect()
1343        }
1344
1345        /// Rolloff espectral (frequência abaixo da qual está X% da energia)
1346        pub fn spectral_rolloff(&self, threshold_percent: T) -> Vec<T> {
1347            let freqs = self.frequencies();
1348            let power = self.power();
1349
1350            (0..self.num_frames)
1351                .map(|frame| {
1352                    // Calcula energia total
1353                    let mut total_energy = T::ZERO;
1354                    for freq_idx in 0..self.num_freqs {
1355                        total_energy = total_energy + power[freq_idx][frame];
1356                    }
1357
1358                    let threshold = total_energy * threshold_percent / T::from_usize(100);
1359                    let mut cumulative = T::ZERO;
1360
1361                    // Encontra frequência onde energia acumulada passa threshold
1362                    for freq_idx in 0..self.num_freqs {
1363                        cumulative = cumulative + power[freq_idx][frame];
1364                        if cumulative >= threshold {
1365                            return freqs[freq_idx];
1366                        }
1367                    }
1368
1369                    freqs[self.num_freqs - 1]
1370                })
1371                .collect()
1372        }
1373    }
1374
1375    /// Calculador de STFT
1376    pub struct StftProcessor<T: Float> {
1377        config: OverlapConfig,
1378        window: Vec<T>,
1379        planner: FftPlanner<T>,
1380    }
1381
1382    impl<T: Float> StftProcessor<T> {
1383        /// Cria novo processador STFT
1384        pub fn new(config: OverlapConfig, window_type: WindowType) -> Result<Self> {
1385            if !is_power_of_two(config.window_size) {
1386                return Err(FftError::InvalidSize);
1387            }
1388
1389            let window = match window_type {
1390                WindowType::Hann => window::hann(config.window_size),
1391                WindowType::Hamming => window::hamming(config.window_size),
1392                WindowType::Blackman => window::blackman(config.window_size),
1393                WindowType::BlackmanHarris => window::blackman_harris(config.window_size),
1394                WindowType::Rectangle => window::rectangular(config.window_size),
1395            };
1396
1397            let planner = FftPlanner::new(config.window_size, false)?;
1398
1399            Ok(Self {
1400                config,
1401                window,
1402                planner,
1403            })
1404        }
1405
1406        /// Processa sinal gerando espectrograma
1407        pub fn process(&self, signal: &[T], sample_rate: T) -> Result<Spectrogram<T>> {
1408            let num_frames = self.config.num_frames(signal.len());
1409            if num_frames == 0 {
1410                return Err(FftError::InvalidSize);
1411            }
1412
1413            let num_freqs = self.config.window_size / 2 + 1; // Apenas frequências positivas
1414            let mut spec = Spectrogram::new(num_frames, num_freqs, sample_rate, self.config);
1415
1416            // Processa cada frame
1417            for frame_idx in 0..num_frames {
1418                let start = frame_idx * self.config.hop_size;
1419                let end = start + self.config.window_size;
1420
1421                // Extrai e janela o frame
1422                let mut frame: Vec<Complex<T>> = signal[start..end]
1423                    .iter()
1424                    .zip(self.window.iter())
1425                    .map(|(&s, &w)| Complex::new(s * w, T::ZERO))
1426                    .collect();
1427
1428                // FFT do frame
1429                self.planner.process_in_place(&mut frame)?;
1430
1431                // Armazena apenas frequências positivas
1432                for freq_idx in 0..num_freqs {
1433                    spec.set(freq_idx, frame_idx, frame[freq_idx]);
1434                }
1435            }
1436
1437            Ok(spec)
1438        }
1439
1440        /// ISTFT - reconstrói sinal do espectrograma
1441        pub fn inverse(&self, spec: &Spectrogram<T>) -> Result<Vec<T>> {
1442            let signal_len = (spec.num_frames - 1) * self.config.hop_size + self.config.window_size;
1443            let mut signal = vec![T::ZERO; signal_len];
1444            let mut window_sum = vec![T::ZERO; signal_len];
1445
1446            let ifft_planner = FftPlanner::new(self.config.window_size, true)?;
1447
1448            for frame_idx in 0..spec.num_frames {
1449                // Reconstrói espectro completo (simetria Hermitiana)
1450                let mut full_spectrum = vec![Complex::zero(); self.config.window_size];
1451
1452                for freq_idx in 0..spec.num_freqs {
1453                    full_spectrum[freq_idx] = spec.get(freq_idx, frame_idx);
1454                }
1455
1456                // Preenche parte negativa (conjugado espelhado)
1457                for freq_idx in 1..spec.num_freqs - 1 {
1458                    full_spectrum[self.config.window_size - freq_idx] =
1459                        full_spectrum[freq_idx].conj();
1460                }
1461
1462                // IFFT
1463                ifft_planner.process_in_place(&mut full_spectrum)?;
1464
1465                // Overlap-add com janela
1466                let start = frame_idx * self.config.hop_size;
1467                for i in 0..self.config.window_size {
1468                    let pos = start + i;
1469                    signal[pos] = signal[pos] + full_spectrum[i].re * self.window[i];
1470                    window_sum[pos] = window_sum[pos] + self.window[i] * self.window[i];
1471                }
1472            }
1473
1474            // Normaliza pelo window sum
1475            for i in 0..signal_len {
1476                if window_sum[i] > T::ZERO {
1477                    signal[i] = signal[i] / window_sum[i];
1478                }
1479            }
1480
1481            Ok(signal)
1482        }
1483    }
1484
1485    /// Tipos de janela para STFT
1486    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1487    pub enum WindowType {
1488        Hann,
1489        Hamming,
1490        Blackman,
1491        BlackmanHarris,
1492        Rectangle,
1493    }
1494}
1495
1496/// Módulo de compatibilidade com num-complex
1497pub mod num_complex {
1498    pub use super::Complex;
1499    pub type Complex64 = Complex<f64>;
1500    pub type Complex32 = Complex<f32>;
1501}
1502
1503#[cfg(test)]
1504mod tests {
1505    use super::*;
1506
1507    const EPSILON_F64: f64 = 1e-10;
1508    const EPSILON_F32: f32 = 1e-5;
1509
1510    #[test]
1511    fn test_is_power_of_two() {
1512        assert!(is_power_of_two(1));
1513        assert!(is_power_of_two(2));
1514        assert!(is_power_of_two(4));
1515        assert!(is_power_of_two(1024));
1516        assert!(!is_power_of_two(0));
1517        assert!(!is_power_of_two(3));
1518        assert!(!is_power_of_two(100));
1519    }
1520
1521    #[test]
1522    fn test_bit_reverse() {
1523        assert_eq!(reverse_bits(0b000, 3), 0b000);
1524        assert_eq!(reverse_bits(0b001, 3), 0b100);
1525        assert_eq!(reverse_bits(0b010, 3), 0b010);
1526        assert_eq!(reverse_bits(0b011, 3), 0b110);
1527        assert_eq!(reverse_bits(0b100, 3), 0b001);
1528    }
1529
1530    #[test]
1531    fn test_fft_simple() {
1532        let input = vec![
1533            Complex::new(1.0, 0.0),
1534            Complex::new(0.0, 0.0),
1535            Complex::new(1.0, 0.0),
1536            Complex::new(0.0, 0.0),
1537        ];
1538
1539        let output = fft(&input);
1540        assert_eq!(output.len(), 4);
1541    }
1542
1543    #[test]
1544    fn test_fft_planner() {
1545        let input = vec![
1546            Complex::new(1.0, 0.0),
1547            Complex::new(2.0, 0.0),
1548            Complex::new(3.0, 0.0),
1549            Complex::new(4.0, 0.0),
1550        ];
1551
1552        let planner = FftPlanner::new(4, false).unwrap();
1553        let output = planner.process(&input).unwrap();
1554        assert_eq!(output.len(), 4);
1555    }
1556
1557    #[test]
1558    fn test_ifft_roundtrip() {
1559        let input = vec![
1560            Complex::new(1.0, 0.0),
1561            Complex::new(2.0, 0.0),
1562            Complex::new(3.0, 0.0),
1563            Complex::new(4.0, 0.0),
1564        ];
1565
1566        let freq = fft(&input);
1567        let recovered = ifft(&freq);
1568
1569        for (orig, rec) in input.iter().zip(recovered.iter()) {
1570            assert!((orig.re - rec.re).abs() < EPSILON_F64);
1571            assert!((orig.im - rec.im).abs() < EPSILON_F64);
1572        }
1573    }
1574
1575    #[test]
1576    fn test_fft_planner_roundtrip() {
1577        let input = vec![
1578            Complex::new(1.0, 0.5),
1579            Complex::new(2.0, -0.3),
1580            Complex::new(3.0, 0.7),
1581            Complex::new(4.0, -0.9),
1582        ];
1583
1584        let fft_planner = FftPlanner::new(4, false).unwrap();
1585        let ifft_planner = FftPlanner::new(4, true).unwrap();
1586
1587        let freq = fft_planner.process(&input).unwrap();
1588        let recovered = ifft_planner.process(&freq).unwrap();
1589
1590        for (orig, rec) in input.iter().zip(recovered.iter()) {
1591            assert!((orig.re - rec.re).abs() < EPSILON_F64,
1592                "re: {} vs {}", orig.re, rec.re);
1593            assert!((orig.im - rec.im).abs() < EPSILON_F64,
1594                "im: {} vs {}", orig.im, rec.im);
1595        }
1596    }
1597
1598    #[test]
1599    fn test_fft_impulse() {
1600        // Teste científico: impulso unitário -> espectro constante
1601        let mut input = vec![Complex::zero(); 8];
1602        input[0] = Complex::new(1.0, 0.0);
1603
1604        let output = fft(&input);
1605
1606        // Todas as frequências devem ter magnitude ~1
1607        for c in output.iter() {
1608            assert!((c.norm() - 1.0).abs() < EPSILON_F64);
1609        }
1610    }
1611
1612    #[test]
1613    fn test_fft_dc_signal() {
1614        // Teste científico: sinal DC -> energia apenas em freq 0
1615        let input = vec![Complex::new(1.0, 0.0); 8];
1616        let output = fft(&input);
1617
1618        // Primeira frequência deve ter toda a energia
1619        assert!((output[0].re - 8.0).abs() < EPSILON_F64);
1620        assert!(output[0].im.abs() < EPSILON_F64);
1621
1622        // Outras frequências devem ser ~0
1623        for i in 1..8 {
1624            assert!(output[i].norm() < EPSILON_F64);
1625        }
1626    }
1627
1628    #[test]
1629    fn test_fft_sine_wave() {
1630        // Teste científico: senoide pura -> picos em ±freq
1631        let n = 16;
1632        let k = 2; // frequência normalizada
1633        let input: Vec<Complex<f64>> = (0..n)
1634            .map(|i| {
1635                let angle = 2.0 * PI * (k as f64) * (i as f64) / (n as f64);
1636                Complex::new(angle.sin(), 0.0)
1637            })
1638            .collect();
1639
1640        let output = fft(&input);
1641
1642        // Picos em k e n-k
1643        assert!(output[k].norm() > 5.0); // magnitude significativa
1644        assert!(output[n - k].norm() > 5.0);
1645
1646        // Outras frequências devem ser pequenas
1647        for i in 0..n {
1648            if i != k && i != n - k {
1649                assert!(output[i].norm() < 0.1);
1650            }
1651        }
1652    }
1653
1654    #[test]
1655    fn test_parseval_theorem() {
1656        // Teorema de Parseval: energia no tempo = energia na frequência
1657        let input = vec![
1658            Complex::new(1.0, 0.5),
1659            Complex::new(2.0, -0.3),
1660            Complex::new(3.0, 0.7),
1661            Complex::new(4.0, -0.9),
1662        ];
1663
1664        let energy_time: f64 = input.iter().map(|c| c.norm_sqr()).sum();
1665
1666        let output = fft(&input);
1667        let energy_freq: f64 = output.iter().map(|c| c.norm_sqr()).sum::<f64>() / (input.len() as f64);
1668
1669        assert!((energy_time - energy_freq).abs() < EPSILON_F64);
1670    }
1671
1672    #[test]
1673    fn test_linearity() {
1674        // Teste de linearidade: FFT(a·x + b·y) = a·FFT(x) + b·FFT(y)
1675        let x = vec![
1676            Complex::new(1.0, 0.0),
1677            Complex::new(2.0, 0.0),
1678            Complex::new(3.0, 0.0),
1679            Complex::new(4.0, 0.0),
1680        ];
1681
1682        let y = vec![
1683            Complex::new(0.5, 0.5),
1684            Complex::new(1.0, -0.5),
1685            Complex::new(1.5, 0.3),
1686            Complex::new(2.0, -0.7),
1687        ];
1688
1689        let a = 2.0;
1690        let b = 3.0;
1691
1692        // a·x + b·y
1693        let combined: Vec<Complex<f64>> = x.iter()
1694            .zip(y.iter())
1695            .map(|(&xi, &yi)| xi * a + yi * b)
1696            .collect();
1697
1698        let fft_combined = fft(&combined);
1699
1700        // a·FFT(x) + b·FFT(y)
1701        let fft_x = fft(&x);
1702        let fft_y = fft(&y);
1703        let expected: Vec<Complex<f64>> = fft_x.iter()
1704            .zip(fft_y.iter())
1705            .map(|(&fx, &fy)| fx * a + fy * b)
1706            .collect();
1707
1708        for (c, e) in fft_combined.iter().zip(expected.iter()) {
1709            assert!((c.re - e.re).abs() < EPSILON_F64);
1710            assert!((c.im - e.im).abs() < EPSILON_F64);
1711        }
1712    }
1713
1714    #[test]
1715    fn test_rfft_real_signal() {
1716        let input = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
1717        let output = rfft(&input).unwrap();
1718
1719        // Deve retornar N/2 + 1 = 5 pontos
1720        assert_eq!(output.len(), 5);
1721
1722        // Primeira frequência (DC) deve ser real
1723        assert!(output[0].im.abs() < EPSILON_F64);
1724    }
1725
1726    #[test]
1727    fn test_rfft_roundtrip() {
1728        let input = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
1729        let spectrum = rfft(&input).unwrap();
1730        let recovered = irfft(&spectrum, input.len()).unwrap();
1731
1732        for (orig, rec) in input.iter().zip(recovered.iter()) {
1733            assert!((orig - rec).abs() < EPSILON_F64);
1734        }
1735    }
1736
1737    #[test]
1738    fn test_hamming_window() {
1739        let window = window::hamming::<f64>(8);
1740        assert_eq!(window.len(), 8);
1741
1742        // Janela de Hamming: extremos próximos de 0.08
1743        assert!((window[0] - 0.08).abs() < 0.01);
1744        assert!((window[7] - 0.08).abs() < 0.01);
1745
1746        // Máximo no centro
1747        let max = window.iter().cloned().fold(0.0, f64::max);
1748        assert!(max > 0.9);
1749    }
1750
1751    #[test]
1752    fn test_hann_window() {
1753        let window = window::hann::<f64>(8);
1754        assert_eq!(window.len(), 8);
1755
1756        // Janela de Hann: extremos = 0
1757        assert!(window[0].abs() < EPSILON_F64);
1758        assert!(window[7].abs() < EPSILON_F64);
1759    }
1760
1761    #[test]
1762    fn test_window_apply() {
1763        let signal = vec![1.0, 2.0, 3.0, 4.0];
1764        let window = window::rectangular::<f64>(4);
1765        let windowed = window::apply(&signal, &window);
1766
1767        // Janela retangular não deve alterar o sinal
1768        for (s, w) in signal.iter().zip(windowed.iter()) {
1769            assert!((s - w).abs() < EPSILON_F64);
1770        }
1771    }
1772
1773    #[test]
1774    fn test_f32_support() {
1775        let input = vec![
1776            Complex::new(1.0f32, 0.0f32),
1777            Complex::new(2.0f32, 0.0f32),
1778            Complex::new(3.0f32, 0.0f32),
1779            Complex::new(4.0f32, 0.0f32),
1780        ];
1781
1782        let planner = FftPlanner::new(4, false).unwrap();
1783        let output = planner.process(&input).unwrap();
1784        assert_eq!(output.len(), 4);
1785    }
1786
1787    #[test]
1788    fn test_error_handling() {
1789        // Tamanho não potência de 2
1790        let input = vec![Complex::new(1.0, 0.0); 3];
1791        assert!(fft_generic(&input).is_err());
1792
1793        // Tamanho zero
1794        assert!(FftPlanner::<f64>::new(0, false).is_err());
1795
1796        // Tamanho não potência de 2
1797        assert!(FftPlanner::<f64>::new(5, false).is_err());
1798    }
1799
1800    #[test]
1801    fn test_nan_detection() {
1802        let mut input = vec![Complex::new(1.0, 0.0); 4];
1803        input[2] = Complex::new(f64::NAN, 0.0);
1804
1805        let planner = FftPlanner::new(4, false).unwrap();
1806        assert!(planner.process(&input).is_err());
1807    }
1808
1809    #[test]
1810    fn test_infinity_detection() {
1811        let mut input = vec![Complex::new(1.0, 0.0); 4];
1812        input[1] = Complex::new(f64::INFINITY, 0.0);
1813
1814        let planner = FftPlanner::new(4, false).unwrap();
1815        assert!(planner.process(&input).is_err());
1816    }
1817
1818    // Testes FFT 2D
1819    mod test_fft2d {
1820        use super::*;
1821        use crate::fft2d::*;
1822
1823        #[test]
1824        fn test_fft2d_basic() {
1825            // Imagem 4x4 simples
1826            let data = vec![1.0; 16];
1827            let image = Image2D::from_real(4, 4, data).unwrap();
1828
1829            let freq = fft2d(&image).unwrap();
1830            assert_eq!(freq.data.len(), 16);
1831        }
1832
1833        #[test]
1834        fn test_fft2d_roundtrip() {
1835            // Cria imagem de teste 8x8
1836            let data: Vec<f64> = (0..64)
1837                .map(|i| (i as f64) * 0.1)
1838                .collect();
1839            let image = Image2D::from_real(8, 8, data).unwrap();
1840
1841            // FFT -> IFFT
1842            let freq = fft2d(&image).unwrap();
1843            let recovered = ifft2d(&freq).unwrap();
1844
1845            // Verifica reversibilidade
1846            for i in 0..64 {
1847                let diff = (image.data[i].re - recovered.data[i].re).abs();
1848                assert!(diff < EPSILON_F64, "Erro em pixel {}: {}", i, diff);
1849            }
1850        }
1851
1852        #[test]
1853        fn test_fft2d_separability() {
1854            // Propriedade: FFT 2D = FFT(colunas) depois FFT(linhas)
1855            // Já implementado via row-column algorithm
1856            let data = vec![1.0, 2.0, 3.0, 4.0];
1857            let image = Image2D::from_real(2, 2, data).unwrap();
1858
1859            let result = fft2d(&image).unwrap();
1860
1861            // DC component deve ser soma de todos pixels
1862            let dc = result.get(0, 0);
1863            assert!((dc.re - 10.0).abs() < EPSILON_F64);
1864        }
1865
1866        #[test]
1867        fn test_fft2d_parseval() {
1868            // Teorema de Parseval 2D: energia conservada
1869            let data: Vec<f64> = (0..64)
1870                .map(|i| ((i as f64) * 0.1).sin())
1871                .collect();
1872            let image = Image2D::from_real(8, 8, data).unwrap();
1873
1874            let energy_spatial: f64 = image.data.iter()
1875                .map(|c| c.norm_sqr())
1876                .sum();
1877
1878            let freq = fft2d(&image).unwrap();
1879            let energy_freq: f64 = freq.data.iter()
1880                .map(|c| c.norm_sqr())
1881                .sum::<f64>() / 64.0;
1882
1883            let diff = (energy_spatial - energy_freq).abs();
1884            assert!(diff < EPSILON_F64 * 100.0, "Parseval: {}", diff);
1885        }
1886
1887        #[test]
1888        fn test_image2d_fftshift() {
1889            let mut image = Image2D::new(4, 4);
1890
1891            // Define padrão conhecido
1892            image.set(0, 0, Complex::new(1.0, 0.0));
1893            image.set(3, 3, Complex::new(2.0, 0.0));
1894
1895            image.fftshift();
1896
1897            // Após shift, cantos devem estar no centro
1898            assert_eq!(image.get(2, 2).re, 1.0);
1899            assert_eq!(image.get(1, 1).re, 2.0);
1900        }
1901
1902        #[test]
1903        fn test_gaussian_filter() {
1904            use crate::filters::*;
1905
1906            let data = vec![1.0; 64];
1907            let image = Image2D::from_real(8, 8, data).unwrap();
1908
1909            let mut freq = fft2d(&image).unwrap();
1910
1911            let filter = GaussianFilter::new(8, 8, 2.0, false);
1912            filter.apply(&mut freq);
1913
1914            let filtered = ifft2d(&freq).unwrap();
1915
1916            // Filtro passa-baixas deve suavizar (reduzir valores)
1917            let original_max = 1.0;
1918            let filtered_max = filtered.magnitude().iter()
1919                .cloned()
1920                .fold(0.0, f64::max);
1921
1922            assert!(filtered_max <= original_max);
1923        }
1924
1925        #[test]
1926        fn test_ideal_lowpass_filter() {
1927            use crate::filters::*;
1928
1929            let data = vec![1.0; 64];
1930            let image = Image2D::from_real(8, 8, data).unwrap();
1931
1932            let mut freq = fft2d(&image).unwrap();
1933
1934            let filter = IdealFilter::new(8, 8, FilterType::LowPass, 2.0);
1935            filter.apply(&mut freq);
1936
1937            // Deve zerar frequências além do cutoff
1938            let result = ifft2d(&freq).unwrap();
1939            assert_eq!(result.data.len(), 64);
1940        }
1941
1942        #[test]
1943        fn test_convolve2d() {
1944            use crate::filters::convolve2d;
1945
1946            // Imagem 4x4
1947            let img_data = vec![
1948                1.0, 0.0, 0.0, 0.0,
1949                0.0, 0.0, 0.0, 0.0,
1950                0.0, 0.0, 0.0, 0.0,
1951                0.0, 0.0, 0.0, 0.0,
1952            ];
1953            let image = Image2D::from_real(4, 4, img_data).unwrap();
1954
1955            // Kernel identidade (simplificado)
1956            let kernel_data = vec![
1957                0.0, 0.0, 0.0, 0.0,
1958                0.0, 1.0, 0.0, 0.0,
1959                0.0, 0.0, 0.0, 0.0,
1960                0.0, 0.0, 0.0, 0.0,
1961            ];
1962            let kernel = Image2D::from_real(4, 4, kernel_data).unwrap();
1963
1964            let result = convolve2d(&image, &kernel).unwrap();
1965            assert_eq!(result.data.len(), 16);
1966        }
1967    }
1968}
1969
1970/// Módulo de benchmarks científicos (executar com --release)
1971#[cfg(test)]
1972mod bench {
1973    use super::*;
1974    use std::time::Instant;
1975
1976    fn benchmark_fft(size: usize, iterations: usize) -> f64 {
1977        let input: Vec<Complex<f64>> = (0..size)
1978            .map(|i| Complex::new((i as f64).sin(), (i as f64).cos()))
1979            .collect();
1980
1981        let planner = FftPlanner::new(size, false).unwrap();
1982
1983        let start = Instant::now();
1984        for _ in 0..iterations {
1985            let _ = planner.process(&input).unwrap();
1986        }
1987        let duration = start.elapsed();
1988
1989        duration.as_secs_f64() / (iterations as f64)
1990    }
1991
1992    #[test]
1993    #[ignore] // Executar com: cargo test --release -- --ignored bench
1994    fn bench_fft_sizes() {
1995        println!("\n=== Benchmark FFT (tempo médio por operação) ===");
1996
1997        for &size in &[64, 128, 256, 512, 1024, 2048, 4096] {
1998            let iterations = 1000000 / size; // Mais iterações para tamanhos menores
1999            let avg_time = benchmark_fft(size, iterations);
2000            let throughput = (size as f64) / avg_time / 1e6; // Msamples/s
2001
2002            println!("N={:5} : {:.3} µs/op  ({:.1} Msamples/s)",
2003                size, avg_time * 1e6, throughput);
2004        }
2005    }
2006
2007    #[test]
2008    #[ignore]
2009    fn bench_comparison_recursive_vs_iterative() {
2010        let size = 1024;
2011        let iterations = 1000;
2012
2013        let input: Vec<Complex<f64>> = (0..size)
2014            .map(|i| Complex::new(i as f64, 0.0))
2015            .collect();
2016
2017        // Recursive
2018        let start = Instant::now();
2019        for _ in 0..iterations {
2020            let _ = fft(&input);
2021        }
2022        let recursive_time = start.elapsed().as_secs_f64() / (iterations as f64);
2023
2024        // Iterative
2025        let planner = FftPlanner::new(size, false).unwrap();
2026        let start = Instant::now();
2027        for _ in 0..iterations {
2028            let _ = planner.process(&input).unwrap();
2029        }
2030        let iterative_time = start.elapsed().as_secs_f64() / (iterations as f64);
2031
2032        println!("\n=== Comparação Recursiva vs Iterativa (N={}) ===", size);
2033        println!("Recursiva  : {:.3} µs", recursive_time * 1e6);
2034        println!("Iterativa  : {:.3} µs", iterative_time * 1e6);
2035        println!("Speedup    : {:.2}x", recursive_time / iterative_time);
2036    }
2037
2038    // Testes STFT e análise tempo-frequência
2039    mod test_stft {
2040        use super::*;
2041        use crate::timefreq::*;
2042
2043        #[test]
2044        fn test_overlap_config() {
2045            let config = OverlapConfig::overlap_50(512);
2046            assert_eq!(config.window_size, 512);
2047            assert_eq!(config.hop_size, 256);
2048            assert_eq!(config.overlap_percent(), 50.0);
2049
2050            let config = OverlapConfig::overlap_75(512);
2051            assert_eq!(config.hop_size, 128);
2052            assert_eq!(config.overlap_percent(), 75.0);
2053        }
2054
2055        #[test]
2056        fn test_num_frames() {
2057            let config = OverlapConfig::overlap_50(512);
2058
2059            // Sinal com 1536 amostras: (1536 - 512) / 256 + 1 = 5 frames
2060            assert_eq!(config.num_frames(1536), 5);
2061
2062            // Sinal menor que janela
2063            assert_eq!(config.num_frames(256), 0);
2064        }
2065
2066        #[test]
2067        fn test_stft_chirp() {
2068            // Chirp: frequência varia linearmente no tempo
2069            // f(t) = f0 + (f1 - f0) * t / duration
2070            let sample_rate = 4096.0;
2071            let duration = 2.0; // segundos
2072            let n_samples = (sample_rate * duration) as usize;
2073
2074            let f0 = 100.0; // Hz inicial
2075            let f1 = 800.0; // Hz final
2076            let chirp_rate = (f1 - f0) / duration;
2077
2078            let signal: Vec<f64> = (0..n_samples)
2079                .map(|i| {
2080                    let t = i as f64 / sample_rate;
2081                    let freq = f0 + chirp_rate * t;
2082                    (2.0 * std::f64::consts::PI * freq * t).sin()
2083                })
2084                .collect();
2085
2086            // STFT
2087            let config = OverlapConfig::overlap_75(512);
2088            let processor = StftProcessor::new(config, WindowType::Hann).unwrap();
2089            let spec = processor.process(&signal, sample_rate).unwrap();
2090
2091            // Verifica dimensões
2092            assert_eq!(spec.num_freqs, 257); // 512/2 + 1
2093            assert!(spec.num_frames > 0);
2094
2095            // Verifica que frequência dominante aumenta ao longo do tempo
2096            let freqs = spec.frequencies();
2097            let mag = spec.magnitude();
2098
2099            let mut dominant_freqs = Vec::new();
2100            for frame in 0..spec.num_frames {
2101                let mut max_mag = 0.0;
2102                let mut max_idx = 0;
2103
2104                for freq_idx in 0..spec.num_freqs {
2105                    if mag[freq_idx][frame] > max_mag {
2106                        max_mag = mag[freq_idx][frame];
2107                        max_idx = freq_idx;
2108                    }
2109                }
2110                dominant_freqs.push(freqs[max_idx]);
2111            }
2112
2113            // Verifica tendência crescente
2114            for i in 1..dominant_freqs.len() {
2115                assert!(
2116                    dominant_freqs[i] >= dominant_freqs[i - 1] - 50.0,
2117                    "Frequência dominante deve aumentar: {} -> {}",
2118                    dominant_freqs[i - 1], dominant_freqs[i]
2119                );
2120            }
2121        }
2122
2123        #[test]
2124        fn test_stft_reversibility() {
2125            // Sinal de teste: soma de senóides
2126            let sample_rate = 8192.0;
2127            let duration = 1.0;
2128            let n_samples = (sample_rate * duration) as usize;
2129
2130            let signal: Vec<f64> = (0..n_samples)
2131                .map(|i| {
2132                    let t = i as f64 / sample_rate;
2133                    // 3 componentes: 200Hz, 400Hz, 800Hz
2134                    (2.0 * std::f64::consts::PI * 200.0 * t).sin()
2135                        + 0.5 * (2.0 * std::f64::consts::PI * 400.0 * t).sin()
2136                        + 0.3 * (2.0 * std::f64::consts::PI * 800.0 * t).sin()
2137                })
2138                .collect();
2139
2140            // STFT -> ISTFT
2141            let config = OverlapConfig::overlap_75(512);
2142            let processor = StftProcessor::new(config, WindowType::Hann).unwrap();
2143
2144            let spec = processor.process(&signal, sample_rate).unwrap();
2145            let reconstructed = processor.inverse(&spec).unwrap();
2146
2147            // Verifica reversibilidade (ignora bordas pela janela)
2148            let margin = 512; // Ignora 1 janela em cada borda
2149            let mut max_error: f64 = 0.0;
2150            let mut rms_error = 0.0;
2151
2152            for i in margin..(n_samples - margin) {
2153                if i < reconstructed.len() {
2154                    let error = (signal[i] - reconstructed[i]).abs();
2155                    max_error = max_error.max(error);
2156                    rms_error += error * error;
2157                }
2158            }
2159
2160            let n_samples_checked = n_samples - 2 * margin;
2161            rms_error = (rms_error / (n_samples_checked as f64)).sqrt();
2162
2163            println!("STFT Reversibilidade - Max error: {:.3e}, RMS error: {:.3e}", max_error, rms_error);
2164
2165            // Tolerância relaxada devido ao efeito de windowing
2166            assert!(rms_error < 0.01, "RMS error muito alto: {}", rms_error);
2167        }
2168
2169        #[test]
2170        fn test_spectral_features() {
2171            // Sinal de teste: tom puro + ruído
2172            let sample_rate = 8192.0;
2173            let n_samples = 8192;
2174
2175            let signal: Vec<f64> = (0..n_samples)
2176                .map(|i| {
2177                    let t = i as f64 / sample_rate;
2178                    // Tom puro em 440 Hz (nota A4)
2179                    (2.0 * std::f64::consts::PI * 440.0 * t).sin()
2180                })
2181                .collect();
2182
2183            let config = OverlapConfig::overlap_50(512);
2184            let processor = StftProcessor::new(config, WindowType::Hann).unwrap();
2185            let spec = processor.process(&signal, sample_rate).unwrap();
2186
2187            // Centróide espectral
2188            let centroids = spec.spectral_centroid();
2189            assert!(centroids.len() > 0);
2190
2191            // Para tom puro, centróide deve estar próximo de 440 Hz
2192            let avg_centroid: f64 = centroids.iter().sum::<f64>() / (centroids.len() as f64);
2193            println!("Centróide espectral médio: {:.1} Hz (esperado ~440 Hz)", avg_centroid);
2194            assert!((avg_centroid - 440.0).abs() < 100.0);
2195
2196            // Largura de banda
2197            let bandwidths = spec.spectral_bandwidth();
2198            assert!(bandwidths.len() > 0);
2199
2200            // Tom puro tem largura de banda pequena
2201            let avg_bandwidth: f64 = bandwidths.iter().sum::<f64>() / (bandwidths.len() as f64);
2202            println!("Largura de banda média: {:.1} Hz", avg_bandwidth);
2203            assert!(avg_bandwidth < 200.0);
2204
2205            // Flatness
2206            let flatness = spec.spectral_flatness();
2207            let avg_flatness: f64 = flatness.iter().sum::<f64>() / (flatness.len() as f64);
2208            println!("Flatness média: {:.4} (esperado próximo de 0 para tom puro)", avg_flatness);
2209
2210            // Tom puro tem flatness baixo (não é ruído branco)
2211            assert!(avg_flatness < 0.3);
2212
2213            // Rolloff
2214            let rolloff = spec.spectral_rolloff(85.0); // 85% da energia
2215            assert!(rolloff.len() > 0);
2216            let avg_rolloff: f64 = rolloff.iter().sum::<f64>() / (rolloff.len() as f64);
2217            println!("Rolloff 85% médio: {:.1} Hz", avg_rolloff);
2218        }
2219
2220        #[test]
2221        fn test_spectrogram_magnitude_db() {
2222            let sample_rate = 8192.0;
2223            let n_samples = 4096;
2224
2225            let signal: Vec<f64> = (0..n_samples)
2226                .map(|i| {
2227                    let t = i as f64 / sample_rate;
2228                    (2.0 * std::f64::consts::PI * 440.0 * t).sin()
2229                })
2230                .collect();
2231
2232            let config = OverlapConfig::overlap_50(256);
2233            let processor = StftProcessor::new(config, WindowType::Hann).unwrap();
2234            let spec = processor.process(&signal, sample_rate).unwrap();
2235
2236            let mag = spec.magnitude();
2237            let mag_db = spec.magnitude_db();
2238
2239            // Verifica que dB está em escala logarítmica apropriada
2240            assert_eq!(mag.len(), mag_db.len());
2241            assert_eq!(mag[0].len(), mag_db[0].len());
2242
2243            // Para tom puro, pico deve estar acima de -20 dB
2244            let mut max_db = -1000.0;
2245            for freq_row in &mag_db {
2246                for &db_val in freq_row {
2247                    if db_val > max_db {
2248                        max_db = db_val;
2249                    }
2250                }
2251            }
2252
2253            println!("Pico em dB: {:.1} dB", max_db);
2254            assert!(max_db > -20.0);
2255        }
2256
2257        #[test]
2258        fn test_window_comparison() {
2259            // Compara diferentes janelas no mesmo sinal
2260            let sample_rate = 8192.0;
2261            let n_samples = 4096;
2262
2263            let signal: Vec<f64> = (0..n_samples)
2264                .map(|i| {
2265                    let t = i as f64 / sample_rate;
2266                    // Dois tons próximos: 440 Hz e 480 Hz
2267                    (2.0 * std::f64::consts::PI * 440.0 * t).sin()
2268                        + (2.0 * std::f64::consts::PI * 480.0 * t).sin()
2269                })
2270                .collect();
2271
2272            let config = OverlapConfig::overlap_50(512);
2273
2274            for window_type in [WindowType::Rectangle, WindowType::Hann,
2275                               WindowType::Hamming, WindowType::Blackman] {
2276                let processor = StftProcessor::new(config, window_type).unwrap();
2277                let spec = processor.process(&signal, sample_rate).unwrap();
2278
2279                let freqs = spec.frequencies();
2280                let mag = spec.magnitude();
2281
2282                // Encontra picos
2283                let mut peaks = Vec::new();
2284                for frame in 0..spec.num_frames.min(5) {
2285                    let mut local_max = 0.0;
2286                    let mut local_idx = 0;
2287
2288                    for freq_idx in 10..spec.num_freqs - 10 {
2289                        if mag[freq_idx][frame] > local_max {
2290                            local_max = mag[freq_idx][frame];
2291                            local_idx = freq_idx;
2292                        }
2293                    }
2294                    peaks.push(freqs[local_idx]);
2295                }
2296
2297                println!("Janela {:?}: picos em ~{:.0} Hz", window_type, peaks[0]);
2298            }
2299        }
2300    }
2301}
2302
2303/// Parallel processing module for multi-threaded FFT and STFT
2304pub mod parallel;
2305
2306/// Streaming processing module for large files
2307pub mod streaming;
2308
2309/// SIMD optimizations for 2-4x speedup
2310pub mod simd;
2311
2312/// Cache optimization for planner and window reuse
2313pub mod cache;
2314
2315/// Advanced FFT algorithms (Bluestein, split-radix, PFA)
2316pub mod advanced;