use crate::audio_settings::AudioSettings;
use crate::context::Context;
use crate::error::{to_option_error, SteamAudioError};
use std::sync::{Mutex, OnceLock};
#[derive(Debug, PartialEq, Eq)]
pub struct Hrtf(pub(crate) audionimbus_sys::IPLHRTF);
impl Hrtf {
pub fn try_new(
context: &Context,
audio_settings: &AudioSettings,
hrtf_settings: &HrtfSettings,
) -> Result<Self, SteamAudioError> {
let _guard = hrtf_creation_lock().lock().unwrap();
let mut hrtf = Self(std::ptr::null_mut());
let (mut settings_ffi, _filename_keeper) = hrtf_settings.to_ffi();
let status = unsafe {
audionimbus_sys::iplHRTFCreate(
context.raw_ptr(),
&mut audionimbus_sys::IPLAudioSettings::from(audio_settings),
&raw mut settings_ffi,
hrtf.raw_ptr_mut(),
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
Ok(hrtf)
}
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLHRTF {
self.0
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLHRTF {
&mut self.0
}
}
impl From<audionimbus_sys::IPLHRTF> for Hrtf {
fn from(ptr: audionimbus_sys::IPLHRTF) -> Self {
Self(ptr)
}
}
impl Drop for Hrtf {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplHRTFRelease(&raw mut self.0) }
}
}
unsafe impl Send for Hrtf {}
unsafe impl Sync for Hrtf {}
impl Clone for Hrtf {
fn clone(&self) -> Self {
Self(unsafe { audionimbus_sys::iplHRTFRetain(self.0) })
}
}
#[derive(Debug)]
pub struct HrtfSettings {
pub volume: f32,
pub sofa_information: Option<Sofa>,
pub volume_normalization: VolumeNormalization,
}
impl HrtfSettings {
pub fn to_ffi(&self) -> (audionimbus_sys::IPLHRTFSettings, Option<std::ffi::CString>) {
let (type_, sofa_data, sofa_data_size, filename_cstring) =
if let Some(information) = &self.sofa_information {
match information {
Sofa::Filename(filename) => {
let cstring = std::ffi::CString::new(filename.clone()).unwrap();
(
audionimbus_sys::IPLHRTFType::IPL_HRTFTYPE_SOFA,
std::ptr::null(),
0,
Some(cstring),
)
}
Sofa::Buffer(buffer) => (
audionimbus_sys::IPLHRTFType::IPL_HRTFTYPE_SOFA,
buffer.as_ptr(),
buffer.len() as i32,
None,
),
}
} else {
(
audionimbus_sys::IPLHRTFType::IPL_HRTFTYPE_DEFAULT,
std::ptr::null(),
0,
None,
)
};
let sofa_filename = filename_cstring
.as_ref()
.map_or(std::ptr::null(), |c| c.as_ptr());
let settings = audionimbus_sys::IPLHRTFSettings {
type_,
sofaFileName: sofa_filename,
sofaData: sofa_data,
sofaDataSize: sofa_data_size,
volume: self.volume,
normType: self.volume_normalization.into(),
};
(settings, filename_cstring)
}
}
impl Default for HrtfSettings {
fn default() -> Self {
Self {
volume: 1.0,
sofa_information: None,
volume_normalization: VolumeNormalization::None,
}
}
}
#[derive(Debug)]
pub enum Sofa {
Filename(String),
Buffer(Vec<u8>),
}
#[derive(Debug, Copy, Clone)]
pub enum VolumeNormalization {
None,
RootMeanSquared,
}
impl From<VolumeNormalization> for audionimbus_sys::IPLHRTFNormType {
fn from(volume_normalization: VolumeNormalization) -> Self {
match volume_normalization {
VolumeNormalization::None => Self::IPL_HRTFNORMTYPE_NONE,
VolumeNormalization::RootMeanSquared => Self::IPL_HRTFNORMTYPE_RMS,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum HrtfInterpolation {
Nearest,
Bilinear,
}
impl From<HrtfInterpolation> for audionimbus_sys::IPLHRTFInterpolation {
fn from(hrtf_interpolation: HrtfInterpolation) -> Self {
match hrtf_interpolation {
HrtfInterpolation::Nearest => Self::IPL_HRTFINTERPOLATION_NEAREST,
HrtfInterpolation::Bilinear => Self::IPL_HRTFINTERPOLATION_BILINEAR,
}
}
}
fn hrtf_creation_lock() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_try_new_hrtf_default() {
let context = Context::default();
let audio_settings = AudioSettings::default();
let hrtf_settings = HrtfSettings::default();
let hrtf_result = Hrtf::try_new(&context, &audio_settings, &hrtf_settings);
assert!(hrtf_result.is_ok());
}
#[test]
fn test_hrtf_clone() {
let context = Context::default();
let audio_settings = AudioSettings::default();
let hrtf_settings = HrtfSettings::default();
let hrtf = Hrtf::try_new(&context, &audio_settings, &hrtf_settings).unwrap();
let clone = hrtf.clone();
assert_eq!(hrtf.raw_ptr(), clone.raw_ptr());
drop(hrtf);
assert!(!clone.raw_ptr().is_null());
}
}