use num_complex::Complex64;
use rustfft::FftPlanner;
#[derive(Debug, Clone, Default)]
pub struct SpectralFeats {
pub spectral_centroid: f64,
pub spectral_skewness: f64,
pub spectral_kurtosis: f64,
pub spectral_entropy: f64,
pub spectral_spread: Vec<f64>,
pub spectral_flatness: f64,
pub spectral_rolloff: Vec<Complex64>,
pub spectral_flux: f64,
pub spectral_mean: Complex64,
pub spectral_rms: Complex64,
pub spectral_std: f64,
pub spectral_variance: f64,
}
pub fn spectral_centroid(fs: usize, spectrum: &[Complex64], order: usize) -> f64 {
let magnitude = magnitude_spectrum(spectrum);
let freqs = fftfreq_abs(spectrum.len(), fs);
let denom = magnitude.iter().sum::<f64>();
magnitude
.iter()
.zip(freqs.iter())
.map(|(mag, freq)| mag * freq.powi(order as i32))
.sum::<f64>()
/ denom
}
pub fn spectral_skewness(_sig: &[f64], fs: usize, spectrum: &[Complex64]) -> f64 {
let magnitude = magnitude_spectrum(spectrum);
let freqs = fftfreq_abs(spectrum.len(), fs);
let mu1 = spectral_centroid(fs, spectrum, 1);
let mu2 = spectral_centroid(fs, spectrum, 2);
magnitude
.iter()
.zip(freqs.iter())
.map(|(mag, freq)| mag * (freq - mu1).powi(3))
.sum::<f64>()
/ (magnitude.iter().sum::<f64>() * mu2.powi(3))
}
pub fn spectral_kurtosis(_sig: &[f64], fs: usize, spectrum: &[Complex64]) -> f64 {
let magnitude = magnitude_spectrum(spectrum);
let freqs = fftfreq_abs(spectrum.len(), fs);
let mu1 = spectral_centroid(fs, spectrum, 1);
let mu2 = spectral_centroid(fs, spectrum, 2);
magnitude
.iter()
.zip(freqs.iter())
.map(|(mag, freq)| mag * (freq - mu1).powi(4))
.sum::<f64>()
/ (magnitude.iter().sum::<f64>() * mu2.powi(4))
}
pub fn spectral_entropy(spectrum: &[Complex64]) -> f64 {
let magnitude = magnitude_spectrum(spectrum);
-magnitude.iter().map(|mag| mag * mag.ln()).sum::<f64>() / (magnitude.len() as f64).ln()
}
pub fn spectral_spread(_sig: &[f64], fs: usize, spectrum: &[Complex64]) -> Vec<f64> {
let magnitude = magnitude_spectrum(spectrum);
let freqs = fftfreq_abs(spectrum.len(), fs);
let mu1 = spectral_centroid(fs, spectrum, 1);
let sum_delta = freqs.iter().map(|freq| freq - mu1).sum::<f64>();
let denom = magnitude.iter().sum::<f64>();
magnitude
.iter()
.map(|mag| ((sum_delta.powi(2) * mag) / denom).sqrt())
.collect()
}
pub fn spectral_flatness(spectrum: &[Complex64]) -> f64 {
let magnitude = magnitude_spectrum(spectrum);
if magnitude.contains(&0.0) {
return 0.0;
}
let gmean =
(magnitude.iter().map(|value| value.ln()).sum::<f64>() / magnitude.len() as f64).exp();
gmean / (magnitude.iter().sum::<f64>() / magnitude.len() as f64)
}
pub fn spectral_rolloff(spectrum: &[Complex64], k: f64) -> f64 {
k * magnitude_spectrum(spectrum).iter().sum::<f64>()
}
pub fn spectral_flux(spectrum: &[Complex64], p: usize) -> f64 {
let magnitude = magnitude_spectrum(spectrum);
magnitude
.windows(2)
.map(|pair| (pair[1] - pair[0]).abs().powi(p as i32))
.sum::<f64>()
.powf(1.0 / p as f64)
}
pub fn extract_feats(sig: &[f64], fs: usize) -> SpectralFeats {
extract_feats_with_nfft(sig, fs, 512)
}
pub fn extract_feats_with_nfft(sig: &[f64], fs: usize, nfft: usize) -> SpectralFeats {
let spectrum = rfft(sig, nfft);
let squared = spectrum
.iter()
.map(|value| *value * *value)
.collect::<Vec<_>>();
SpectralFeats {
spectral_centroid: spectral_centroid(fs, &spectrum, 1),
spectral_skewness: spectral_skewness(sig, fs, &spectrum),
spectral_kurtosis: spectral_kurtosis(sig, fs, &spectrum),
spectral_entropy: spectral_entropy(&spectrum),
spectral_spread: spectral_spread(sig, fs, &spectrum),
spectral_flatness: spectral_flatness(&spectrum),
spectral_rolloff: spectrum.iter().map(|value| *value * fs as f64).collect(),
spectral_flux: spectral_flux(&spectrum, 2),
spectral_mean: complex_mean(&spectrum),
spectral_rms: complex_mean(&squared).sqrt(),
spectral_std: complex_variance(&spectrum).sqrt(),
spectral_variance: complex_variance(&spectrum),
}
}
fn rfft(sig: &[f64], nfft: usize) -> Vec<Complex64> {
let mut planner = FftPlanner::<f64>::new();
let fft = planner.plan_fft_forward(nfft);
let mut buffer = vec![Complex64::new(0.0, 0.0); nfft];
for (dst, src) in buffer.iter_mut().zip(sig.iter()) {
dst.re = *src;
}
fft.process(&mut buffer);
buffer[..(nfft / 2 + 1)].to_vec()
}
fn magnitude_spectrum(spectrum: &[Complex64]) -> Vec<f64> {
spectrum.iter().map(|value| value.norm()).collect()
}
fn fftfreq_abs(n: usize, fs: usize) -> Vec<f64> {
let step = fs as f64 / n as f64;
(0..n)
.map(|i| {
let value = if i <= (n - 1) / 2 {
i as f64
} else {
i as f64 - n as f64
};
(value * step).abs()
})
.collect()
}
fn complex_mean(values: &[Complex64]) -> Complex64 {
values.iter().sum::<Complex64>() / values.len() as f64
}
fn complex_variance(values: &[Complex64]) -> f64 {
let mean = complex_mean(values);
values
.iter()
.map(|value| (*value - mean).norm_sqr())
.sum::<f64>()
/ values.len() as f64
}