use super::audio_effect_state::AudioEffectState;
use super::{EffectError, SpeakerLayout};
use crate::audio_buffer::{AudioBuffer, Sample};
use crate::audio_settings::AudioSettings;
use crate::context::Context;
use crate::error::{to_option_error, SteamAudioError};
use crate::ffi_wrapper::FFIWrapper;
use crate::geometry::CoordinateSystem;
use crate::hrtf::Hrtf;
use crate::num_ambisonics_channels;
use crate::{ChannelPointers, ChannelRequirement};
#[cfg(doc)]
use crate::baking::PathBaker;
#[cfg(doc)]
use crate::geometry::Scene;
#[cfg(doc)]
use crate::probe::ProbeBatch;
#[cfg(doc)]
use crate::simulation::{SimulationOutputs, Simulator, Source};
#[derive(Debug)]
pub struct PathEffect {
inner: audionimbus_sys::IPLPathEffect,
num_output_channels: u32,
}
impl PathEffect {
pub fn try_new(
context: &Context,
audio_settings: &AudioSettings,
path_effect_settings: &PathEffectSettings,
) -> Result<Self, SteamAudioError> {
let mut inner = std::ptr::null_mut();
let status = unsafe {
audionimbus_sys::iplPathEffectCreate(
context.raw_ptr(),
&mut audionimbus_sys::IPLAudioSettings::from(audio_settings),
&mut audionimbus_sys::IPLPathEffectSettings::from(path_effect_settings),
&raw mut inner,
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
let path_effect = Self {
inner,
num_output_channels: num_ambisonics_channels(path_effect_settings.max_order),
};
Ok(path_effect)
}
pub fn apply<I, O, PI: ChannelPointers, PO: ChannelPointers>(
&mut self,
path_effect_params: &PathEffectParams,
input_buffer: &AudioBuffer<I, PI>,
output_buffer: &AudioBuffer<O, PO>,
) -> Result<AudioEffectState, EffectError>
where
I: AsRef<[Sample]>,
O: AsRef<[Sample]> + AsMut<[Sample]>,
{
let num_input_channels = input_buffer.num_channels();
if num_input_channels != 1 {
return Err(EffectError::InvalidInputChannels {
expected: ChannelRequirement::Exactly(1),
actual: num_input_channels,
});
}
let num_output_channels = output_buffer.num_channels();
if num_output_channels != self.num_output_channels {
return Err(EffectError::InvalidOutputChannels {
expected: ChannelRequirement::Exactly(self.num_output_channels),
actual: num_output_channels,
});
}
let state = unsafe {
audionimbus_sys::iplPathEffectApply(
self.raw_ptr(),
&raw mut *path_effect_params.as_ffi(),
&raw mut *input_buffer.as_ffi(),
&raw mut *output_buffer.as_ffi(),
)
}
.into();
Ok(state)
}
pub fn tail<O>(&self, output_buffer: &AudioBuffer<O>) -> Result<AudioEffectState, EffectError>
where
O: AsRef<[Sample]> + AsMut<[Sample]>,
{
let num_output_channels = output_buffer.num_channels();
if num_output_channels != self.num_output_channels {
return Err(EffectError::InvalidOutputChannels {
expected: ChannelRequirement::Exactly(self.num_output_channels),
actual: num_output_channels,
});
}
let state = unsafe {
audionimbus_sys::iplPathEffectGetTail(self.raw_ptr(), &raw mut *output_buffer.as_ffi())
}
.into();
Ok(state)
}
pub fn tail_size(&self) -> usize {
unsafe { audionimbus_sys::iplPathEffectGetTailSize(self.raw_ptr()) as usize }
}
pub fn reset(&mut self) {
unsafe { audionimbus_sys::iplPathEffectReset(self.raw_ptr()) };
}
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLPathEffect {
self.inner
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLPathEffect {
&mut self.inner
}
}
impl Drop for PathEffect {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplPathEffectRelease(&raw mut self.inner) }
}
}
unsafe impl Send for PathEffect {}
unsafe impl Sync for PathEffect {}
impl Clone for PathEffect {
fn clone(&self) -> Self {
Self {
inner: unsafe { audionimbus_sys::iplPathEffectRetain(self.inner) },
num_output_channels: self.num_output_channels,
}
}
}
#[derive(Debug)]
pub struct PathEffectSettings<'a> {
pub max_order: u32,
pub spatialization: Option<Spatialization<'a>>,
}
impl From<&PathEffectSettings<'_>> for audionimbus_sys::IPLPathEffectSettings {
fn from(settings: &PathEffectSettings) -> Self {
let (spatialize, speaker_layout, hrtf) = if let Some(Spatialization {
speaker_layout,
hrtf,
}) = &settings.spatialization
{
(
audionimbus_sys::IPLbool::IPL_TRUE,
speaker_layout.clone(),
hrtf.raw_ptr(),
)
} else {
(
audionimbus_sys::IPLbool::IPL_FALSE,
SpeakerLayout::Mono,
std::ptr::null_mut(),
)
};
Self {
maxOrder: settings.max_order as i32,
spatialize,
speakerLayout: (&speaker_layout).into(),
hrtf,
}
}
}
#[derive(Debug)]
pub struct Spatialization<'a> {
pub speaker_layout: SpeakerLayout,
pub hrtf: &'a Hrtf,
}
#[derive(Debug)]
pub struct PathEffectParams {
pub eq_coeffs: [f32; 3],
pub sh_coeffs: ShCoeffs,
pub order: u32,
pub binaural: bool,
pub hrtf: Hrtf,
pub listener: CoordinateSystem,
pub normalize_eq: bool,
}
#[derive(Debug)]
pub struct ShCoeffs(pub *mut f32);
unsafe impl Send for ShCoeffs {}
impl ShCoeffs {
pub const fn raw_ptr(&self) -> *mut f32 {
self.0
}
}
impl From<audionimbus_sys::IPLPathEffectParams> for PathEffectParams {
fn from(params: audionimbus_sys::IPLPathEffectParams) -> Self {
Self {
eq_coeffs: params.eqCoeffs,
sh_coeffs: ShCoeffs(params.shCoeffs),
order: params.order as u32,
binaural: params.binaural == audionimbus_sys::IPLbool::IPL_TRUE,
hrtf: params.hrtf.into(),
listener: params.listener.into(),
normalize_eq: params.normalizeEQ == audionimbus_sys::IPLbool::IPL_TRUE,
}
}
}
impl PathEffectParams {
pub(crate) fn as_ffi(&self) -> FFIWrapper<'_, audionimbus_sys::IPLPathEffectParams, Self> {
let path_effect_params = audionimbus_sys::IPLPathEffectParams {
eqCoeffs: self.eq_coeffs,
shCoeffs: self.sh_coeffs.raw_ptr(),
order: self.order as i32,
binaural: if self.binaural {
audionimbus_sys::IPLbool::IPL_TRUE
} else {
audionimbus_sys::IPLbool::IPL_FALSE
},
hrtf: self.hrtf.raw_ptr(),
listener: self.listener.into(),
normalizeEQ: if self.normalize_eq {
audionimbus_sys::IPLbool::IPL_TRUE
} else {
audionimbus_sys::IPLbool::IPL_FALSE
},
};
FFIWrapper::new(path_effect_params)
}
}
#[cfg(test)]
mod tests {
use crate::*;
mod apply {
use super::*;
#[test]
fn test_valid() {
let context = Context::default();
const FRAME_SIZE: u32 = 1024;
const MAX_ORDER: u32 = 1;
const NUM_SH_COEFFS: usize = (MAX_ORDER as usize + 1).pow(2);
let audio_settings = AudioSettings::default();
let path_effect_settings = PathEffectSettings {
max_order: MAX_ORDER,
spatialization: None,
};
let mut path_effect =
PathEffect::try_new(&context, &audio_settings, &path_effect_settings).unwrap();
let input_container = vec![0.5; FRAME_SIZE as usize];
let input_buffer = AudioBuffer::try_with_data(&input_container).unwrap();
let mut output_container = vec![0.0; 4 * input_buffer.num_samples() as usize];
let output_buffer = AudioBuffer::try_with_data_and_settings(
&mut output_container,
AudioBufferSettings::with_num_channels(4),
)
.unwrap();
let hrtf_settings = HrtfSettings::default();
let hrtf = Hrtf::try_new(&context, &audio_settings, &hrtf_settings).unwrap();
let mut sh_storage = vec![0.0f32; NUM_SH_COEFFS];
sh_storage[0] = 1.0;
let sh_coeffs = ShCoeffs(sh_storage.as_mut_ptr());
let path_effect_params = PathEffectParams {
eq_coeffs: [1.0, 1.0, 1.0],
sh_coeffs,
order: MAX_ORDER,
binaural: false,
hrtf,
listener: CoordinateSystem::default(),
normalize_eq: false,
};
assert!(path_effect
.apply(&path_effect_params, &input_buffer, &output_buffer)
.is_ok());
}
#[test]
fn test_invalid_input_num_channels() {
let context = Context::default();
const FRAME_SIZE: u32 = 1024;
const MAX_ORDER: u32 = 1;
const NUM_SH_COEFFS: usize = (MAX_ORDER as usize + 1).pow(2);
let audio_settings = AudioSettings::default();
let path_effect_settings = PathEffectSettings {
max_order: MAX_ORDER,
spatialization: None,
};
let mut path_effect =
PathEffect::try_new(&context, &audio_settings, &path_effect_settings).unwrap();
let input_container = vec![0.5; 2 * FRAME_SIZE as usize];
let input_buffer = AudioBuffer::try_with_data_and_settings(
&input_container,
AudioBufferSettings::with_num_channels(2),
)
.unwrap();
let mut output_container = vec![0.0; 4 * input_buffer.num_samples() as usize];
let output_buffer = AudioBuffer::try_with_data_and_settings(
&mut output_container,
AudioBufferSettings::with_num_channels(4),
)
.unwrap();
let hrtf_settings = HrtfSettings::default();
let hrtf = Hrtf::try_new(&context, &audio_settings, &hrtf_settings).unwrap();
let mut sh_storage = vec![0.0f32; NUM_SH_COEFFS];
sh_storage[0] = 1.0;
let sh_coeffs = ShCoeffs(sh_storage.as_mut_ptr());
let path_effect_params = PathEffectParams {
eq_coeffs: [1.0, 1.0, 1.0],
sh_coeffs,
order: MAX_ORDER,
binaural: false,
hrtf,
listener: CoordinateSystem::default(),
normalize_eq: false,
};
assert_eq!(
path_effect.apply(&path_effect_params, &input_buffer, &output_buffer),
Err(EffectError::InvalidInputChannels {
expected: ChannelRequirement::Exactly(1),
actual: 2
})
);
}
#[test]
fn test_invalid_output_num_channels() {
let context = Context::default();
const FRAME_SIZE: u32 = 1024;
const MAX_ORDER: u32 = 1;
const NUM_SH_COEFFS: usize = (MAX_ORDER as usize + 1).pow(2);
let audio_settings = AudioSettings::default();
let path_effect_settings = PathEffectSettings {
max_order: MAX_ORDER,
spatialization: None,
};
let mut path_effect =
PathEffect::try_new(&context, &audio_settings, &path_effect_settings).unwrap();
let input_container = vec![0.5; FRAME_SIZE as usize];
let input_buffer = AudioBuffer::try_with_data(&input_container).unwrap();
let mut output_container = vec![0.0; 2 * input_buffer.num_samples() as usize];
let output_buffer = AudioBuffer::try_with_data_and_settings(
&mut output_container,
AudioBufferSettings::with_num_channels(2),
)
.unwrap();
let hrtf_settings = HrtfSettings::default();
let hrtf = Hrtf::try_new(&context, &audio_settings, &hrtf_settings).unwrap();
let mut sh_storage = vec![0.0f32; NUM_SH_COEFFS];
sh_storage[0] = 1.0;
let sh_coeffs = ShCoeffs(sh_storage.as_mut_ptr());
let path_effect_params = PathEffectParams {
eq_coeffs: [1.0, 1.0, 1.0],
sh_coeffs,
order: MAX_ORDER,
binaural: false,
hrtf,
listener: CoordinateSystem::default(),
normalize_eq: false,
};
assert_eq!(
path_effect.apply(&path_effect_params, &input_buffer, &output_buffer),
Err(EffectError::InvalidOutputChannels {
expected: ChannelRequirement::Exactly(4),
actual: 2
})
);
}
}
mod tail {
use super::*;
#[test]
fn test_valid() {
let context = Context::default();
const FRAME_SIZE: usize = 1024;
const MAX_ORDER: u32 = 1;
let audio_settings = AudioSettings::default();
let path_effect_settings = PathEffectSettings {
max_order: MAX_ORDER,
spatialization: None,
};
let path_effect =
PathEffect::try_new(&context, &audio_settings, &path_effect_settings).unwrap();
let mut output_container = vec![0.0; 4 * FRAME_SIZE];
let output_buffer = AudioBuffer::try_with_data_and_settings(
&mut output_container,
AudioBufferSettings::with_num_channels(4),
)
.unwrap();
assert!(path_effect.tail(&output_buffer).is_ok());
}
#[test]
fn test_invalid_output_num_channels() {
let context = Context::default();
const FRAME_SIZE: usize = 1024;
const MAX_ORDER: u32 = 1;
let audio_settings = AudioSettings::default();
let path_effect_settings = PathEffectSettings {
max_order: MAX_ORDER,
spatialization: None,
};
let path_effect =
PathEffect::try_new(&context, &audio_settings, &path_effect_settings).unwrap();
let mut output_container = vec![0.0; 2 * FRAME_SIZE];
let output_buffer = AudioBuffer::try_with_data_and_settings(
&mut output_container,
AudioBufferSettings::with_num_channels(2),
)
.unwrap();
assert_eq!(
path_effect.tail(&output_buffer),
Err(EffectError::InvalidOutputChannels {
expected: ChannelRequirement::Exactly(4),
actual: 2
})
);
}
}
mod clone {
use super::*;
#[test]
fn test_clone() {
let context = Context::default();
let audio_settings = AudioSettings::default();
let effect = PathEffect::try_new(
&context,
&audio_settings,
&PathEffectSettings {
max_order: 1,
spatialization: None,
},
)
.unwrap();
let clone = effect.clone();
assert_eq!(effect.raw_ptr(), clone.raw_ptr());
drop(effect);
assert!(!clone.raw_ptr().is_null());
}
}
}