#![allow(dead_code)]
use ndarray::Array1;
use num_complex::Complex64;
use rustfft::FftPlanner;
use std::f64::consts::PI;
pub fn reconstruct_minimum_phase(_freq: &Array1<f64>, spl: &Array1<f64>) -> Array1<f64> {
let _n = spl.len();
let ln_mag: Vec<f64> = spl.iter().map(|&s| s / 20.0 * 10.0_f64.ln()).collect();
let phase_rad = hilbert_transform(&ln_mag);
let phase_deg: Vec<f64> = phase_rad.iter().map(|&p| -p.to_degrees()).collect();
Array1::from_vec(phase_deg)
}
fn hilbert_transform(signal: &[f64]) -> Vec<f64> {
let n = signal.len();
if n == 0 {
return Vec::new();
}
let n_fft = n.next_power_of_two();
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(n_fft);
let ifft = planner.plan_fft_inverse(n_fft);
let mut spectrum: Vec<Complex64> = signal
.iter()
.map(|&x| Complex64::new(x, 0.0))
.chain(std::iter::repeat_n(Complex64::new(0.0, 0.0), n_fft - n))
.collect();
fft.process(&mut spectrum);
let half = n_fft / 2;
for s in spectrum.iter_mut().take(half).skip(1) {
*s *= Complex64::new(2.0, 0.0);
}
for s in spectrum.iter_mut().skip(half + 1) {
*s = Complex64::new(0.0, 0.0);
}
ifft.process(&mut spectrum);
spectrum[..n].iter().map(|c| c.im / n_fft as f64).collect()
}
pub fn compute_excess_phase(total_phase: &Array1<f64>, min_phase: &Array1<f64>) -> Array1<f64> {
total_phase - min_phase
}
pub fn estimate_delay_from_excess_phase(
freq: &Array1<f64>,
excess_phase: &Array1<f64>,
) -> (f64, Array1<f64>) {
let n = freq.len();
if n < 2 {
return (0.0, excess_phase.clone());
}
let sum_f: f64 = freq.iter().sum();
let sum_phi: f64 = excess_phase.iter().map(|&p| p.to_radians()).sum();
let sum_f2: f64 = freq.iter().map(|&f| f * f).sum();
let sum_f_phi: f64 = freq
.iter()
.zip(excess_phase.iter())
.map(|(&f, &p)| f * p.to_radians())
.sum();
let n_f = n as f64;
let denom = n_f * sum_f2 - sum_f * sum_f;
if denom.abs() < 1e-12 {
return (0.0, excess_phase.clone());
}
let slope = (n_f * sum_f_phi - sum_f * sum_phi) / denom;
let intercept = (sum_phi - slope * sum_f) / n_f;
let delay_s = -slope / (2.0 * PI);
let delay_ms = delay_s * 1000.0;
let residual: Vec<f64> = freq
.iter()
.zip(excess_phase.iter())
.map(|(&f, &phi)| {
let linear_component = (slope * f + intercept).to_degrees();
phi - linear_component
})
.collect();
(delay_ms, Array1::from_vec(residual))
}
pub fn unwrap_phase_degrees(phase_deg: &Array1<f64>) -> Array1<f64> {
let mut unwrapped = Vec::with_capacity(phase_deg.len());
if phase_deg.is_empty() {
return Array1::from_vec(unwrapped);
}
let mut prev = phase_deg[0];
unwrapped.push(prev);
let mut offset = 0.0;
for &p in phase_deg.iter().skip(1) {
let diff = p - prev;
let wraps = (diff / 360.0).round();
offset -= wraps * 360.0;
unwrapped.push(p + offset);
prev = p;
}
Array1::from_vec(unwrapped)
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_approx_eq(a: f64, b: f64, epsilon: f64) {
assert!(
(a - b).abs() < epsilon,
"assertion failed: {} ≈ {} (diff = {}, epsilon = {})",
a,
b,
(a - b).abs(),
epsilon
);
}
#[test]
fn test_hilbert_transform_dc() {
let signal = vec![1.0, 1.0, 1.0, 1.0];
let hilbert = hilbert_transform(&signal);
for h in hilbert {
assert!(h.abs() < 0.01, "Hilbert of DC should be ~0, got {}", h);
}
}
#[test]
fn test_unwrap_phase_no_wrap() {
let phase = Array1::from_vec(vec![0.0, 10.0, 20.0, 30.0]);
let unwrapped = unwrap_phase_degrees(&phase);
assert_eq!(unwrapped, phase);
}
#[test]
fn test_unwrap_phase_with_wrap() {
let phase = Array1::from_vec(vec![160.0, 170.0, -170.0, -160.0]);
let unwrapped = unwrap_phase_degrees(&phase);
assert_approx_eq(unwrapped[0], 160.0, 0.01);
assert_approx_eq(unwrapped[1], 170.0, 0.01);
assert_approx_eq(unwrapped[2], 190.0, 0.01); assert_approx_eq(unwrapped[3], 200.0, 0.01); }
#[test]
fn test_estimate_delay_linear_phase() {
let delay_ms = 1.0;
let delay_s = delay_ms / 1000.0;
let freq = Array1::linspace(100.0, 1000.0, 100);
let excess_phase: Array1<f64> = freq.map(|&f| -360.0 * f * delay_s);
let (estimated_delay, residual) = estimate_delay_from_excess_phase(&freq, &excess_phase);
assert_approx_eq(estimated_delay, delay_ms, 0.01);
let max_residual = residual.iter().map(|&r| r.abs()).fold(0.0, f64::max);
assert!(
max_residual < 1.0,
"Residual should be < 1 degree, got {}",
max_residual
);
}
#[test]
fn test_reconstruct_minimum_phase_flat() {
let freq = Array1::linspace(20.0, 20000.0, 100);
let spl = Array1::from_elem(100, 85.0);
let phase = reconstruct_minimum_phase(&freq, &spl);
let mean_phase = phase.iter().sum::<f64>() / phase.len() as f64;
assert!(
mean_phase.abs() < 100.0,
"Flat response mean phase should be near 0, got {}",
mean_phase
);
}
#[test]
fn test_unwrap_phase_multi_wrap() {
let phase = Array1::from_vec(vec![0.0, 10.0, -350.0, -710.0]);
let unwrapped = unwrap_phase_degrees(&phase);
assert_approx_eq(unwrapped[0], 0.0, 0.01);
assert_approx_eq(unwrapped[1], 10.0, 0.01);
assert_approx_eq(unwrapped[2], 10.0, 0.01); assert_approx_eq(unwrapped[3], 10.0, 0.01); }
#[test]
fn test_compute_excess_phase() {
let total = Array1::from_vec(vec![-45.0, -90.0, -135.0]);
let min = Array1::from_vec(vec![-30.0, -60.0, -90.0]);
let excess = compute_excess_phase(&total, &min);
assert_approx_eq(excess[0], -15.0, 0.01);
assert_approx_eq(excess[1], -30.0, 0.01);
assert_approx_eq(excess[2], -45.0, 0.01);
}
#[test]
fn test_reconstruct_minimum_phase_lowpass() {
let n = 256;
let freq = Array1::linspace(20.0, 20000.0, n);
let fc = 1000.0;
let spl: Array1<f64> = freq.map(|&f| {
let ratio: f64 = f / fc;
-10.0 * (1.0 + ratio.powi(2)).log10()
});
let phase = reconstruct_minimum_phase(&freq, &spl);
let phase_range = phase.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
- phase.iter().cloned().fold(f64::INFINITY, f64::min);
assert!(
phase_range > 10.0,
"Phase should have significant variation for lowpass, got range {:.1}°",
phase_range
);
let mid_idx = n / 2;
let mid_phase = phase[mid_idx];
let edge_avg = (phase[5] + phase[n - 5]) / 2.0;
assert!(
(mid_phase - edge_avg).abs() > 1.0,
"Phase near cutoff ({:.1}°) should differ from edge average ({:.1}°)",
mid_phase,
edge_avg
);
}
#[test]
fn test_excess_phase_pure_delay() {
let n = 100;
let freq = Array1::linspace(100.0, 5000.0, n);
let delay_ms = 2.0;
let delay_s = delay_ms / 1000.0;
let spl = Array1::from_elem(n, 85.0);
let min_phase = reconstruct_minimum_phase(&freq, &spl);
let delay_phase: Array1<f64> = freq.map(|&f| -360.0 * f * delay_s);
let total_phase = &min_phase + &delay_phase;
let excess = compute_excess_phase(&total_phase, &min_phase);
let (estimated_delay, residual) = estimate_delay_from_excess_phase(&freq, &excess);
assert!(
(estimated_delay - delay_ms).abs() < 0.2,
"Estimated delay {:.3}ms should be close to {:.1}ms",
estimated_delay,
delay_ms
);
let max_residual = residual.iter().map(|&r| r.abs()).fold(0.0_f64, f64::max);
assert!(
max_residual < 5.0,
"Residual should be small, got max {:.1}°",
max_residual
);
}
#[test]
fn test_estimate_delay_accuracy() {
let n = 200;
let freq = Array1::linspace(100.0, 8000.0, n);
let delay_ms = 3.0;
let delay_s = delay_ms / 1000.0;
let phase: Array1<f64> = freq.map(|&f| -360.0 * f * delay_s);
let (estimated, residual) = estimate_delay_from_excess_phase(&freq, &phase);
assert!(
(estimated - delay_ms).abs() < 0.1,
"Expected {:.1}ms, got {:.3}ms (error {:.4}ms)",
delay_ms,
estimated,
(estimated - delay_ms).abs()
);
let max_residual = residual.iter().map(|&r| r.abs()).fold(0.0_f64, f64::max);
assert!(
max_residual < 1.0,
"Residual should be < 1°, got {:.3}°",
max_residual
);
}
#[test]
fn test_reconstruct_minimum_phase_flat_128() {
let n = 128;
let freq = Array1::linspace(20.0, 20000.0, n);
let spl = Array1::from_elem(n, 80.0);
let phase = reconstruct_minimum_phase(&freq, &spl);
assert!(
phase.iter().all(|p| p.is_finite()),
"all phase values should be finite"
);
let mean = phase.iter().sum::<f64>() / phase.len() as f64;
assert!(
mean.abs() < 50.0,
"Flat response mean phase should be near 0, got {:.1}°",
mean
);
}
#[test]
fn test_reconstruct_minimum_phase_lowpass_256() {
let n = 256;
let freq = Array1::linspace(20.0, 20000.0, n);
let fc: f64 = 1000.0;
let spl: Array1<f64> = freq.map(|&f| {
let ratio: f64 = f / fc;
-10.0 * (1.0 + ratio * ratio).log10()
});
let phase = reconstruct_minimum_phase(&freq, &spl);
assert!(
phase.iter().all(|p| p.is_finite()),
"all phase values should be finite"
);
let range = phase.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
- phase.iter().cloned().fold(f64::INFINITY, f64::min);
assert!(
range > 5.0,
"phase should have significant variation for lowpass, got {:.1}°",
range
);
}
#[test]
fn test_hilbert_transform_empty_and_single() {
let empty = hilbert_transform(&[]);
assert!(empty.is_empty());
let single = hilbert_transform(&[1.0]);
assert_eq!(single.len(), 1);
assert!(single[0].is_finite());
}
#[test]
fn test_estimate_delay_single_element() {
let freq = Array1::from_vec(vec![100.0]);
let phase = Array1::from_vec(vec![-45.0]);
let (delay, residual) = estimate_delay_from_excess_phase(&freq, &phase);
assert_eq!(delay, 0.0);
assert_eq!(residual.len(), 1);
}
#[test]
fn test_unwrap_phase_empty() {
let phase = Array1::from_vec(vec![]);
let unwrapped = unwrap_phase_degrees(&phase);
assert!(unwrapped.is_empty());
}
}