use std::{ffi::CString, io, path::Path};
const DEFAULT_CACHED: bool = false;
const DEFAULT_NORMALIZED: bool = true;
const DEFAULT_SAMPLE_RATE: f32 = 48000.0;
const DEFAULT_NEIGHBOR_ANGLE_STEP: f32 = ffi::MYSOFA_DEFAULT_NEIGH_STEP_ANGLE as f32;
const DEFAULT_NEIGHBOR_RADIUS_STEP: f32 = ffi::MYSOFA_DEFAULT_NEIGH_STEP_RADIUS as f32;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("IO error")]
Io(#[from] std::io::Error),
#[error("The owls are not what they seem")]
InternalError,
#[error("Invalid data format")]
InvalidFormat,
#[error("Format is not supported")]
UnsupportedFormat,
#[error("Invalid attributes")]
InvalidAttributes,
#[error("Invalid dimensions")]
InvalidDimensions,
#[error("Invalid dimension list")]
InvalidDimensionList,
#[error("Invalid coordinate type")]
InvalidCoordinateType,
#[error("Invalid receiver position")]
InvalidReceiverPositions,
#[error("Emitters without ECI are not supported")]
OnlyEmitterWithEciSupported,
#[error("Delays without IR or MR are not supported")]
OnlyDelaysWithIrOrMrSupported,
#[error("Sources without MC are not supported")]
OnlySourcesWithMcSupported,
#[error("Sampling rates differ")]
OnlyTheSameSamplingRateSupported,
}
impl Error {
pub(crate) fn from_raw(err: i32) -> Error {
use Error::*;
match err {
ffi::MYSOFA_INVALID_FORMAT => InvalidFormat,
ffi::MYSOFA_UNSUPPORTED_FORMAT => UnsupportedFormat,
ffi::MYSOFA_INVALID_ATTRIBUTES => InvalidAttributes,
ffi::MYSOFA_INVALID_DIMENSIONS => InvalidDimensions,
ffi::MYSOFA_INVALID_DIMENSION_LIST => InvalidDimensionList,
ffi::MYSOFA_INVALID_COORDINATE_TYPE => InvalidCoordinateType,
ffi::MYSOFA_INVALID_RECEIVER_POSITIONS => InvalidReceiverPositions,
ffi::MYSOFA_ONLY_EMITTER_WITH_ECI_SUPPORTED => OnlyEmitterWithEciSupported,
ffi::MYSOFA_ONLY_DELAYS_WITH_IR_OR_MR_SUPPORTED => OnlyDelaysWithIrOrMrSupported,
ffi::MYSOFA_ONLY_SOURCES_WITH_MC_SUPPORTED => OnlySourcesWithMcSupported,
ffi::MYSOFA_ONLY_THE_SAME_SAMPLING_RATE_SUPPORTED => OnlyTheSameSamplingRateSupported,
ffi::MYSOFA_READ_ERROR => Io(io::Error::new(
io::ErrorKind::NotFound,
"Unable to read from file",
)),
ffi::MYSOFA_NO_MEMORY => Io(io::Error::new(
io::ErrorKind::OutOfMemory,
"Ran out of memory",
)),
_ => Error::InternalError,
}
}
}
#[derive(Clone, Debug)]
pub struct OpenOptions {
sample_rate: f32,
neighbor_angle_step: f32,
neighbor_radius_step: f32,
cached: bool,
normalized: bool,
}
impl OpenOptions {
pub fn new() -> Self {
Default::default()
}
pub fn sample_rate(&mut self, sample_rate: f32) -> &mut Self {
self.sample_rate = sample_rate;
self
}
pub fn neighbor_angle_step(&mut self, neighbor_angle_step: f32) -> &mut Self {
self.neighbor_angle_step = neighbor_angle_step;
self
}
pub fn neighbor_radius_step(&mut self, neighbor_radius_step: f32) -> &mut Self {
self.neighbor_radius_step = neighbor_radius_step;
self
}
pub fn cached(&mut self, cached: bool) -> &mut Self {
self.cached = cached;
self
}
pub fn normalized(&mut self, normalized: bool) -> &mut Self {
self.normalized = normalized;
self
}
pub fn open<P: AsRef<Path>>(&self, path: P) -> Result<Sofar, Error> {
let path = cstr(path.as_ref())?;
let mut filter_len = 0;
let mut err = 0;
let raw = unsafe {
match self.cached {
true => ffi::mysofa_open_cached(
path.as_ptr(),
self.sample_rate,
&mut filter_len,
&mut err,
),
false => ffi::mysofa_open_advanced(
path.as_ptr(),
self.sample_rate,
&mut filter_len,
&mut err,
self.normalized,
self.neighbor_angle_step,
self.neighbor_radius_step,
),
}
};
if raw.is_null() || err != ffi::MYSOFA_OK {
return Err(Error::from_raw(err));
}
Ok(Sofar {
raw,
filter_len: filter_len as usize,
cached: self.cached,
})
}
}
impl Default for OpenOptions {
fn default() -> Self {
OpenOptions {
sample_rate: DEFAULT_SAMPLE_RATE,
neighbor_angle_step: DEFAULT_NEIGHBOR_ANGLE_STEP,
neighbor_radius_step: DEFAULT_NEIGHBOR_RADIUS_STEP,
cached: DEFAULT_CACHED,
normalized: DEFAULT_NORMALIZED,
}
}
}
pub struct Filter {
pub left: Box<[f32]>,
pub right: Box<[f32]>,
pub ldelay: f32,
pub rdelay: f32,
}
impl Filter {
pub fn new(filt_len: usize) -> Self {
Self {
left: vec![0.0; filt_len].into_boxed_slice(),
right: vec![0.0; filt_len].into_boxed_slice(),
ldelay: 0.0,
rdelay: 0.0,
}
}
}
pub struct Sofar {
raw: *mut ffi::MYSOFA_EASY,
filter_len: usize,
cached: bool,
}
impl Sofar {
pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Sofar, Error> {
OpenOptions::new().open(path)
}
pub fn filter_len(&self) -> usize {
self.filter_len
}
pub fn filter(&self, x: f32, y: f32, z: f32, filter: &mut Filter) {
assert!(filter.left.len() >= self.filter_len);
assert!(filter.right.len() >= self.filter_len);
unsafe {
ffi::mysofa_getfilter_float(
self.raw,
x,
y,
z,
filter.left.as_mut_ptr(),
filter.right.as_mut_ptr(),
&mut filter.ldelay,
&mut filter.rdelay,
);
}
}
pub fn filter_nointerp(&self, x: f32, y: f32, z: f32, filter: &mut Filter) {
assert!(filter.left.len() >= self.filter_len);
assert!(filter.right.len() >= self.filter_len);
unsafe {
ffi::mysofa_getfilter_float_nointerp(
self.raw,
x,
y,
z,
filter.left.as_mut_ptr(),
filter.right.as_mut_ptr(),
&mut filter.ldelay,
&mut filter.rdelay,
);
}
}
}
impl Drop for Sofar {
fn drop(&mut self) {
unsafe {
match self.cached {
true => ffi::mysofa_close_cached(self.raw),
false => ffi::mysofa_close(self.raw),
}
}
}
}
unsafe impl Send for Sofar {}
unsafe impl Sync for Sofar {}
#[cfg(unix)]
fn cstr(path: &Path) -> std::io::Result<CString> {
use std::os::unix::ffi::OsStrExt;
Ok(CString::new(path.as_os_str().as_bytes())?)
}
#[cfg(windows)]
fn cstr(path: &Path) -> std::io::Result<CString> {
Ok(CString::new(path.as_os_str().to_str().unwrap().as_bytes())?)
}