use crate::api::{Direction, Flags, Plan};
use crate::kernel::{Complex, Float};
extern crate alloc;
use alloc::vec::Vec;
pub fn unwrap_phase<T: Float>(phases: &[T]) -> Vec<T> {
let n = phases.len();
if n == 0 {
return Vec::new();
}
let pi = <T as Float>::PI;
let two_pi = <T as Float>::TWO_PI;
let mut unwrapped = Vec::with_capacity(n);
unwrapped.push(phases[0]);
for k in 1..n {
let mut diff = phases[k] - phases[k - 1];
while diff > pi {
diff = diff - two_pi;
}
while diff < -pi {
diff = diff + two_pi;
}
let prev = unwrapped[k - 1];
unwrapped.push(prev + diff);
}
unwrapped
}
pub fn real_cepstrum<T: Float>(signal: &[T]) -> Vec<T> {
let n = signal.len();
if n == 0 {
return Vec::new();
}
let spectrum: Vec<Complex<T>> = signal.iter().map(|&s| Complex::new(s, T::ZERO)).collect();
let mut fft_out = vec![Complex::<T>::zero(); n];
let fft_plan = match Plan::dft_1d(n, Direction::Forward, Flags::ESTIMATE) {
Some(p) => p,
None => return Vec::new(),
};
fft_plan.execute(&spectrum, &mut fft_out);
let eps = T::from_f64(1e-30);
let floor = T::from_f64(-30.0);
let log_spectrum: Vec<Complex<T>> = fft_out
.iter()
.map(|c| {
let mag = c.norm();
let log_mag = if mag > eps {
num_traits::Float::ln(mag)
} else {
floor
};
Complex::new(log_mag, T::ZERO)
})
.collect();
let ifft_plan = match Plan::dft_1d(n, Direction::Backward, Flags::ESTIMATE) {
Some(p) => p,
None => return Vec::new(),
};
let mut cepstrum_out = vec![Complex::<T>::zero(); n];
ifft_plan.execute(&log_spectrum, &mut cepstrum_out);
let scale = T::ONE / T::from_usize(n);
cepstrum_out.iter().map(|c| c.re * scale).collect()
}
pub fn complex_cepstrum<T: Float>(signal: &[T]) -> Vec<T> {
let n = signal.len();
if n == 0 {
return Vec::new();
}
let input: Vec<Complex<T>> = signal.iter().map(|&s| Complex::new(s, T::ZERO)).collect();
let mut fft_out = vec![Complex::<T>::zero(); n];
let fft_plan = match Plan::dft_1d(n, Direction::Forward, Flags::ESTIMATE) {
Some(p) => p,
None => return Vec::new(),
};
fft_plan.execute(&input, &mut fft_out);
let eps = T::from_f64(1e-30);
let floor = T::from_f64(-30.0);
let mut log_spectrum: Vec<Complex<T>> = fft_out
.iter()
.map(|c| {
let mag = c.norm();
let log_mag = if mag > eps {
num_traits::Float::ln(mag)
} else {
floor
};
let phase = c.arg();
Complex::new(log_mag, phase)
})
.collect();
let raw_phases: Vec<T> = log_spectrum.iter().map(|c| c.im).collect();
let unwrapped = unwrap_phase(&raw_phases);
for (c, &ph) in log_spectrum.iter_mut().zip(unwrapped.iter()) {
c.im = ph;
}
let ifft_plan = match Plan::dft_1d(n, Direction::Backward, Flags::ESTIMATE) {
Some(p) => p,
None => return Vec::new(),
};
let mut cepstrum_out = vec![Complex::<T>::zero(); n];
ifft_plan.execute(&log_spectrum, &mut cepstrum_out);
let scale = T::ONE / T::from_usize(n);
cepstrum_out.iter().map(|c| c.re * scale).collect()
}
pub fn minimum_phase<T: Float>(signal: &[T]) -> Vec<T> {
let n = signal.len();
if n == 0 {
return Vec::new();
}
let rcep = real_cepstrum(signal);
if rcep.is_empty() {
return Vec::new();
}
let mut liftered: Vec<Complex<T>> = vec![Complex::zero(); n];
liftered[0] = Complex::new(rcep[0], T::ZERO);
let half = n / 2;
let two = T::TWO;
if n.is_multiple_of(2) {
for k in 1..half {
liftered[k] = Complex::new(rcep[k] * two, T::ZERO);
}
liftered[half] = Complex::new(rcep[half], T::ZERO);
} else {
let upper = n.div_ceil(2);
for k in 1..upper {
liftered[k] = Complex::new(rcep[k] * two, T::ZERO);
}
}
let fft_plan = match Plan::dft_1d(n, Direction::Forward, Flags::ESTIMATE) {
Some(p) => p,
None => return Vec::new(),
};
let mut log_spectrum = vec![Complex::<T>::zero(); n];
fft_plan.execute(&liftered, &mut log_spectrum);
for c in &mut log_spectrum {
let amp = num_traits::Float::exp(c.re);
let (sin_b, cos_b) = Float::sin_cos(c.im);
c.re = amp * cos_b;
c.im = amp * sin_b;
}
let ifft_plan = match Plan::dft_1d(n, Direction::Backward, Flags::ESTIMATE) {
Some(p) => p,
None => return Vec::new(),
};
let mut result = vec![Complex::<T>::zero(); n];
ifft_plan.execute(&log_spectrum, &mut result);
let scale = T::ONE / T::from_usize(n);
result.iter().map(|c| c.re * scale).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_real_cepstrum_empty() {
let c: Vec<f64> = real_cepstrum(&[]);
assert!(c.is_empty());
}
#[test]
fn test_real_cepstrum_length() {
let signal: Vec<f64> = (0..64).map(|i| (f64::from(i) * 0.5).sin()).collect();
let c = real_cepstrum(&signal);
assert_eq!(c.len(), signal.len());
}
#[test]
fn test_real_cepstrum_symmetry() {
let n = 64;
let signal: Vec<f64> = (0..n).map(|i| (i as f64 * 0.3).sin() + 0.5).collect();
let c = real_cepstrum(&signal);
for k in 1..n / 4 {
let diff = (c[k] - c[n - k]).abs();
assert!(diff < 1e-8, "Asymmetry at k={k}: {} vs {}", c[k], c[n - k]);
}
}
#[test]
fn test_complex_cepstrum_length() {
let signal: Vec<f64> = (0..128).map(|i| (f64::from(i) * 0.2).sin() + 0.1).collect();
let c = complex_cepstrum(&signal);
assert_eq!(c.len(), 128);
}
#[test]
fn test_minimum_phase_energy() {
let n = 64;
let signal: Vec<f64> = (0..n).map(|i| (i as f64 * 0.3).sin() + 0.5).collect();
let mp = minimum_phase(&signal);
assert_eq!(mp.len(), n);
let orig_energy: f64 = signal.iter().map(|&x| x * x).sum();
let mp_energy: f64 = mp.iter().map(|&x| x * x).sum();
let ratio = mp_energy / orig_energy;
assert!(
ratio > 0.5 && ratio < 2.0,
"Energy ratio {ratio} out of expected range"
);
}
#[test]
fn test_unwrap_phase_simple() {
use std::f64::consts::PI;
let phases = vec![0.0f64, PI * 0.9, -PI * 0.9, -PI * 0.7];
let unwrapped = unwrap_phase(&phases);
assert_eq!(unwrapped.len(), 4);
for i in 1..unwrapped.len() {
let diff = (unwrapped[i] - unwrapped[i - 1]).abs();
assert!(diff < PI, "Jump of {diff} > pi between {} and {}", i - 1, i);
}
}
}