use super::coords::cartesian_to_spherical;
use super::reader::Hrtf;
pub fn loudness(samples: &[f32]) -> f32 {
samples.iter().map(|s| s * s).sum()
}
pub fn find_frontal_index(hrtf: &Hrtf) -> Option<usize> {
let source_pos = &hrtf.source_position.values;
let c = hrtf.dimensions().c as usize;
let m = hrtf.dimensions().m as usize;
if c != 3 || source_pos.is_empty() {
return None;
}
let mut best_idx = 0;
let mut best_sum = f32::MAX;
let mut best_radius = f32::MIN;
for i in 0..m {
let offset = i * c;
if offset + 2 >= source_pos.len() {
break;
}
let pos = [
source_pos[offset],
source_pos[offset + 1],
source_pos[offset + 2],
];
let spherical = cartesian_to_spherical(pos);
let azimuth = spherical[0];
let elevation = spherical[1];
let radius = spherical[2];
let azimuth_norm = if azimuth > 180.0 {
azimuth - 360.0
} else {
azimuth
};
let sum = azimuth_norm.abs() + elevation.abs();
if sum < best_sum || (sum == best_sum && radius > best_radius) {
best_sum = sum;
best_radius = radius;
best_idx = i;
}
}
Some(best_idx)
}
pub fn normalize(hrtf: &mut Hrtf) -> Option<f32> {
let frontal_idx = find_frontal_index(hrtf)?;
let n = hrtf.dimensions().n as usize;
let r = hrtf.dimensions().r as usize;
let ir_offset = frontal_idx * r * n;
let ir_end = ir_offset + r * n;
if ir_end > hrtf.data_ir.values.len() {
return None;
}
let frontal_energy = loudness(&hrtf.data_ir.values[ir_offset..ir_end]);
if frontal_energy < 1e-10 {
return None;
}
let factor = (2.0 / frontal_energy).sqrt();
if (factor - 1.0).abs() < 1e-6 {
return Some(1.0);
}
for sample in &mut hrtf.data_ir.values {
*sample *= factor;
}
Some(factor)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_loudness() {
assert!((loudness(&[1.0, 1.0, 1.0, 1.0]) - 4.0).abs() < 1e-6);
assert!((loudness(&[0.5, 0.5, 0.5, 0.5]) - 1.0).abs() < 1e-6);
assert!((loudness(&[3.0, 4.0]) - 25.0).abs() < 1e-6);
assert_eq!(loudness(&[]), 0.0);
}
}