use num_complex::Complex;
pub struct PhaseExtractor {
last: Complex<f32>,
}
impl PhaseExtractor {
pub fn new() -> Self {
Self {
last: Complex::new(1.0, 0.0),
}
}
pub fn process(&mut self, samples: &[Complex<f32>]) -> Vec<f32> {
let mut phases = Vec::with_capacity(samples.len());
for &sample in samples {
let d = (sample * self.last.conj()).arg();
phases.push(d);
self.last = sample;
}
phases
}
pub fn reset(&mut self) {
self.last = Complex::new(1.0, 0.0);
}
}
impl Default for PhaseExtractor {
fn default() -> Self {
Self::new()
}
}
pub struct DeemphasisFilter {
a: f32,
b: f32,
prev_y: f32,
}
impl DeemphasisFilter {
pub fn new(sample_rate: f32, tau: f32) -> Self {
let dt = 1.0 / sample_rate;
let decay = (-dt / tau).exp();
let b = 1.0 - decay;
let a = decay;
Self { a, b, prev_y: 0.0 }
}
pub fn process(&mut self, samples: &[f32]) -> Vec<f32> {
let mut y = Vec::with_capacity(samples.len());
for &x in samples {
let out = self.b * x + self.a * self.prev_y;
y.push(out);
self.prev_y = out;
}
y
}
pub fn reset(&mut self) {
self.prev_y = 0.0;
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_phase_extractor_new() {
let extractor = PhaseExtractor::new();
assert_eq!(extractor.last, Complex::new(1.0, 0.0));
}
#[test]
fn test_phase_extractor_constant_signal() {
let mut extractor = PhaseExtractor::new();
let samples = vec![Complex::new(1.0, 0.0); 10];
let phase = extractor.process(&samples);
assert_eq!(phase.len(), 10);
for &p in &phase {
assert!(
p.abs() < 0.1,
"Phase difference should be near zero for constant signal"
);
}
}
#[test]
fn test_phase_extractor_rotating_signal() {
let mut extractor = PhaseExtractor::new();
let n = 100;
let mut samples = Vec::with_capacity(n);
let freq = 0.1; for i in 0..n {
let phase = 2.0 * std::f32::consts::PI * freq * i as f32;
samples.push(Complex::new(phase.cos(), phase.sin()));
}
let phase_diff = extractor.process(&samples);
assert_eq!(phase_diff.len(), n);
if n > 10 {
let mean_phase: f32 = phase_diff[5..].iter().sum::<f32>() / (n - 5) as f32;
for &p in &phase_diff[10..] {
assert_relative_eq!(p, mean_phase, epsilon = 0.2);
}
}
}
#[test]
fn test_phase_extractor_reset() {
let mut extractor = PhaseExtractor::new();
let samples = vec![Complex::new(0.5, 0.5); 5];
let _ = extractor.process(&samples);
extractor.reset();
assert_eq!(extractor.last, Complex::new(1.0, 0.0));
}
#[test]
fn test_deemphasis_new() {
let filter = DeemphasisFilter::new(240_000.0, 50e-6);
assert_relative_eq!(filter.a + filter.b, 1.0, epsilon = 1e-6);
assert_eq!(filter.prev_y, 0.0);
}
#[test]
fn test_deemphasis_dc_signal() {
let mut filter = DeemphasisFilter::new(240_000.0, 50e-6);
let dc_value = 0.5;
let samples = vec![dc_value; 100];
let output = filter.process(&samples);
assert_eq!(output.len(), 100);
let final_value = output[output.len() - 1];
assert_relative_eq!(final_value, dc_value, epsilon = 0.01);
}
#[test]
fn test_deemphasis_impulse_response() {
let mut filter = DeemphasisFilter::new(240_000.0, 50e-6);
let mut samples = vec![0.0; 10];
samples[0] = 1.0;
let output = filter.process(&samples);
assert!(output[0] > 0.0, "First sample should be positive");
assert!(output[1] < output[0], "Should decay");
assert!(output[2] < output[1], "Should continue to decay");
}
#[test]
fn test_deemphasis_reset() {
let mut filter = DeemphasisFilter::new(240_000.0, 50e-6);
let samples = vec![1.0; 5];
let _ = filter.process(&samples);
filter.reset();
assert_eq!(filter.prev_y, 0.0);
}
#[test]
fn test_deemphasis_time_constants() {
let filter_eu = DeemphasisFilter::new(240_000.0, 50e-6);
let filter_na = DeemphasisFilter::new(240_000.0, 75e-6);
assert!(
filter_eu.a < filter_na.a,
"50μs should decay faster than 75μs"
);
}
#[test]
fn test_phase_extractor_empty_input() {
let mut extractor = PhaseExtractor::new();
let samples: Vec<Complex<f32>> = vec![];
let phase = extractor.process(&samples);
assert_eq!(phase.len(), 0);
}
#[test]
fn test_deemphasis_empty_input() {
let mut filter = DeemphasisFilter::new(240_000.0, 50e-6);
let samples: Vec<f32> = vec![];
let output = filter.process(&samples);
assert_eq!(output.len(), 0);
}
}