use super::error::{Error, Result};
use super::reader::Hrtf;
const RECEIVER_POSITION_TOLERANCE: f32 = 0.02;
pub fn validate(hrtf: &Hrtf) -> Result<()> {
validate_attributes(hrtf)?;
validate_dimensions(hrtf)?;
validate_listener_view(hrtf)?;
validate_emitter_position(hrtf)?;
validate_receiver_position(hrtf)?;
validate_source_position(hrtf)?;
validate_data_delay(hrtf)?;
validate_sampling_rate(hrtf)?;
Ok(())
}
fn validate_attributes(hrtf: &Hrtf) -> Result<()> {
match hrtf.get_attribute("Conventions") {
Some("SOFA") => {}
_ => {
return Err(Error::InvalidAttribute {
name: "Conventions",
expected: "SOFA",
});
}
}
match hrtf.get_attribute("SOFAConventions") {
Some("SimpleFreeFieldHRIR") => {}
_ => {
return Err(Error::InvalidAttribute {
name: "SOFAConventions",
expected: "SimpleFreeFieldHRIR",
});
}
}
match hrtf.get_attribute("DataType") {
Some("FIR") => {}
_ => {
return Err(Error::InvalidAttribute {
name: "DataType",
expected: "FIR",
});
}
}
Ok(())
}
fn validate_dimensions(hrtf: &Hrtf) -> Result<()> {
let dims = hrtf.dimensions();
if dims.c != 3 {
return Err(Error::InvalidDimension {
name: 'C',
value: dims.c,
expected: 3,
});
}
if dims.i != 1 {
return Err(Error::InvalidDimension {
name: 'I',
value: dims.i,
expected: 1,
});
}
if dims.e != 1 {
return Err(Error::InvalidDimension {
name: 'E',
value: dims.e,
expected: 1,
});
}
if dims.r != 2 {
return Err(Error::InvalidDimension {
name: 'R',
value: dims.r,
expected: 2,
});
}
if dims.m == 0 {
return Err(Error::InvalidDimension {
name: 'M',
value: 0,
expected: 1, });
}
Ok(())
}
fn validate_listener_view(hrtf: &Hrtf) -> Result<()> {
if hrtf.listener_view.is_empty() {
return Ok(()); }
let coord_type = hrtf.listener_view.get_attribute("Type");
match coord_type {
Some("cartesian") => {
if hrtf.listener_view.len() >= 3 {
let expected = [1.0, 0.0, 0.0];
if !values_match(&hrtf.listener_view.values, &expected) {
log::warn!("ListenerView values don't match expected [1,0,0]");
}
}
}
Some("spherical") => {
if hrtf.listener_view.len() >= 3 {
let expected = [0.0, 0.0, 1.0];
if !values_match(&hrtf.listener_view.values, &expected) {
log::warn!("ListenerView values don't match expected [0,0,1]");
}
}
}
_ => {
log::warn!("Unknown ListenerView coordinate type: {:?}", coord_type);
}
}
Ok(())
}
fn validate_emitter_position(hrtf: &Hrtf) -> Result<()> {
if hrtf.emitter_position.is_empty() {
return Ok(());
}
if hrtf.emitter_position.len() >= 3 {
let expected = [0.0, 0.0, 0.0];
if !values_match(&hrtf.emitter_position.values, &expected) {
log::warn!("EmitterPosition not at origin");
}
}
Ok(())
}
fn validate_receiver_position(hrtf: &Hrtf) -> Result<()> {
if hrtf.receiver_position.is_empty() {
log::warn!("ReceiverPosition array is empty");
return Ok(());
}
let coord_type = hrtf.receiver_position.get_attribute("Type");
if coord_type.is_some() && coord_type != Some("cartesian") {
return Err(Error::InvalidAttribute {
name: "ReceiverPosition.Type",
expected: "cartesian",
});
}
let r = hrtf.dimensions().r as usize;
let c = hrtf.dimensions().c as usize;
let expected_len = r * c;
if hrtf.receiver_position.len() < expected_len {
return Err(Error::InvalidArraySize {
name: "ReceiverPosition",
expected: expected_len,
actual: hrtf.receiver_position.len(),
});
}
if hrtf.receiver_position.len() >= 6 {
let values = &hrtf.receiver_position.values;
if values[0].abs() >= RECEIVER_POSITION_TOLERANCE {
log::warn!("Left receiver x position {} exceeds tolerance", values[0]);
}
if values[3].abs() >= RECEIVER_POSITION_TOLERANCE {
log::warn!("Right receiver x position {} exceeds tolerance", values[3]);
}
if values[2].abs() >= RECEIVER_POSITION_TOLERANCE {
log::warn!("Left receiver z position {} exceeds tolerance", values[2]);
}
if values[5].abs() >= RECEIVER_POSITION_TOLERANCE {
log::warn!("Right receiver z position {} exceeds tolerance", values[5]);
}
if (values[1] + values[4]).abs() >= RECEIVER_POSITION_TOLERANCE {
log::warn!(
"Receiver y positions not symmetric: {} + {} = {}",
values[1],
values[4],
values[1] + values[4]
);
}
}
Ok(())
}
fn validate_source_position(hrtf: &Hrtf) -> Result<()> {
if hrtf.source_position.is_empty() {
return Err(Error::MissingArray("SourcePosition"));
}
let dim_list = hrtf.source_position.get_attribute("DIMENSION_LIST");
if dim_list != Some("M,C") {
log::warn!(
"SourcePosition DIMENSION_LIST is {:?}, expected 'M,C'",
dim_list
);
}
Ok(())
}
fn validate_data_delay(hrtf: &Hrtf) -> Result<()> {
if hrtf.data_delay.is_empty() {
return Ok(()); }
let dim_list = hrtf.data_delay.get_attribute("DIMENSION_LIST");
match dim_list {
Some("I,R") | Some("M,R") => {}
_ => {
log::warn!(
"DataDelay DIMENSION_LIST is {:?}, expected 'I,R' or 'M,R'",
dim_list
);
}
}
Ok(())
}
fn validate_sampling_rate(hrtf: &Hrtf) -> Result<()> {
if hrtf.data_sampling_rate.is_empty() {
return Ok(()); }
let dim_list = hrtf.data_sampling_rate.get_attribute("DIMENSION_LIST");
if dim_list != Some("I") {
log::warn!(
"DataSamplingRate DIMENSION_LIST is {:?}, expected 'I'",
dim_list
);
}
Ok(())
}
fn values_match(values: &[f32], expected: &[f32]) -> bool {
if values.len() < expected.len() {
return false;
}
const TOLERANCE: f32 = 1e-6;
for (i, &exp) in expected.iter().enumerate() {
if (values[i] - exp).abs() > TOLERANCE {
return false;
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_values_match() {
assert!(values_match(&[1.0, 0.0, 0.0], &[1.0, 0.0, 0.0]));
assert!(values_match(&[1.0, 0.0, 0.0, 0.5], &[1.0, 0.0, 0.0]));
assert!(!values_match(&[1.0, 0.1, 0.0], &[1.0, 0.0, 0.0]));
assert!(!values_match(&[1.0], &[1.0, 0.0, 0.0]));
}
}