use crate::api::{Direction, Flags, Plan};
use crate::kernel::{Complex, Float};
pub fn hilbert<T: Float>(signal: &[T]) -> Vec<Complex<T>> {
if signal.is_empty() {
return Vec::new();
}
let n = signal.len();
let a_complex: Vec<Complex<T>> = signal.iter().map(|&s| Complex::new(s, T::ZERO)).collect();
let fwd_plan = match Plan::dft_1d(n, Direction::Forward, Flags::ESTIMATE) {
Some(p) => p,
None => return Vec::new(),
};
let mut spectrum = vec![Complex::<T>::zero(); n];
fwd_plan.execute(&a_complex, &mut spectrum);
let two = T::TWO;
if n.is_multiple_of(2) {
let half = n / 2;
for s in spectrum.iter_mut().take(half).skip(1) {
*s = Complex::new(s.re * two, s.im * two);
}
for s in spectrum.iter_mut().skip(half + 1) {
*s = Complex::zero();
}
} else {
let pos_end = (n + 1) / 2; for s in spectrum.iter_mut().take(pos_end).skip(1) {
*s = Complex::new(s.re * two, s.im * two);
}
for s in spectrum.iter_mut().skip(pos_end) {
*s = Complex::zero();
}
}
let inv_plan = match Plan::dft_1d(n, Direction::Backward, Flags::ESTIMATE) {
Some(p) => p,
None => return Vec::new(),
};
let mut analytic = vec![Complex::<T>::zero(); n];
inv_plan.execute(&spectrum, &mut analytic);
let scale = T::ONE / T::from_usize(n);
for a in &mut analytic {
*a = Complex::new(a.re * scale, a.im * scale);
}
analytic
}
pub fn envelope<T: Float>(signal: &[T]) -> Vec<T> {
hilbert(signal)
.iter()
.map(|c| {
let re = c.re;
let im = c.im;
Float::sqrt(re * re + im * im)
})
.collect()
}
pub fn instantaneous_phase<T: Float>(signal: &[T]) -> Vec<T> {
hilbert(signal)
.iter()
.map(|c| num_traits::Float::atan2(c.im, c.re))
.collect()
}
pub fn instantaneous_frequency<T: Float>(signal: &[T]) -> Vec<T> {
if signal.len() < 2 {
return Vec::new();
}
let phase = instantaneous_phase(signal);
let pi = <T as Float>::PI;
let two_pi = <T as Float>::TWO_PI;
phase
.windows(2)
.map(|w| {
let mut diff = w[1] - w[0];
while diff > pi {
diff = diff - two_pi;
}
while diff < -pi {
diff = diff + two_pi;
}
diff / two_pi
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hilbert_real_part_matches_input() {
let n = 64;
let signal: Vec<f64> = (0..n).map(|i| (i as f64 * 0.3).sin()).collect();
let analytic = hilbert(&signal);
assert_eq!(analytic.len(), n);
for (i, (&orig, a)) in signal.iter().zip(analytic.iter()).enumerate() {
assert!(
(a.re - orig).abs() < 1e-10,
"Real part mismatch at {i}: {} vs {}",
a.re,
orig
);
}
}
#[test]
fn test_envelope_sine_is_constant() {
let n = 512;
let signal: Vec<f64> = (0..n)
.map(|i| (2.0 * std::f64::consts::PI * 10.0 * i as f64 / n as f64).sin())
.collect();
let env = envelope(&signal);
for i in n / 4..3 * n / 4 {
assert!((env[i] - 1.0).abs() < 0.02, "Envelope at {i}: {}", env[i]);
}
}
#[test]
fn test_hilbert_empty() {
let empty: Vec<f64> = Vec::new();
assert!(hilbert(&empty).is_empty());
assert!(envelope(&empty).is_empty());
}
#[test]
fn test_instantaneous_phase_sine() {
let n = 256;
let freq = 5.0; let signal: Vec<f64> = (0..n)
.map(|i| (2.0 * std::f64::consts::PI * freq * i as f64 / n as f64).sin())
.collect();
let phase = instantaneous_phase(&signal);
assert_eq!(phase.len(), n);
}
#[test]
fn test_instantaneous_frequency_length() {
let n = 128;
let signal: Vec<f64> = (0..n).map(|i| (i as f64 * 0.2).sin()).collect();
let freq = instantaneous_frequency(&signal);
assert_eq!(freq.len(), n - 1);
}
}