use crate::compute_rms;
#[must_use]
pub fn signal_to_noise_ratio(signal: &[f32], noise: &[f32]) -> f32 {
if signal.is_empty() || noise.is_empty() {
return 0.0;
}
let signal_power = compute_rms(signal).powi(2);
let noise_power = compute_rms(noise).powi(2);
if noise_power > 0.0 {
signal_power / noise_power
} else {
f32::INFINITY
}
}
#[must_use]
pub fn compute_snr_db(signal: &[f32], noise: &[f32]) -> f32 {
let snr = signal_to_noise_ratio(signal, noise);
if snr > 0.0 && snr.is_finite() {
10.0 * snr.log10()
} else if snr.is_infinite() {
100.0 } else {
-100.0 }
}
#[must_use]
pub fn estimate_snr(samples: &[f32]) -> f32 {
if samples.is_empty() {
return 0.0;
}
let mut sorted: Vec<f32> = samples.iter().map(|&x| x.abs()).collect();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let noise_cutoff = sorted.len() / 4;
let noise_samples = &sorted[..noise_cutoff];
let signal_samples = &sorted[noise_cutoff..];
signal_to_noise_ratio(signal_samples, noise_samples)
}
#[must_use]
pub fn estimate_snr_db(samples: &[f32]) -> f32 {
let snr = estimate_snr(samples);
if snr > 0.0 && snr.is_finite() {
10.0 * snr.log10()
} else {
-100.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_snr_computation() {
let signal = vec![1.0; 100];
let noise = vec![0.1; 100];
let snr = signal_to_noise_ratio(&signal, &noise);
assert!(snr > 10.0);
let snr_db = compute_snr_db(&signal, &noise);
assert!(snr_db > 10.0);
}
#[test]
fn test_snr_estimation() {
let mut samples = vec![0.01; 100]; samples.extend(vec![1.0; 100]);
let snr_db = estimate_snr_db(&samples);
assert!(snr_db > 0.0);
}
}