use super::super::{AudioEffectState, 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::geometry::CoordinateSystem;
use crate::hrtf::Hrtf;
use crate::num_ambisonics_channels;
use crate::{ChannelPointers, ChannelRequirement};
#[derive(Debug)]
pub struct AmbisonicsDecodeEffect {
inner: audionimbus_sys::IPLAmbisonicsDecodeEffect,
num_input_channels: u32,
num_output_channels: u32,
rendering: Rendering,
}
impl AmbisonicsDecodeEffect {
pub fn try_new(
context: &Context,
audio_settings: &AudioSettings,
ambisonics_decode_effect_settings: &AmbisonicsDecodeEffectSettings,
) -> Result<Self, SteamAudioError> {
let mut inner = std::ptr::null_mut();
let status = unsafe {
audionimbus_sys::iplAmbisonicsDecodeEffectCreate(
context.raw_ptr(),
&mut audionimbus_sys::IPLAudioSettings::from(audio_settings),
&mut audionimbus_sys::IPLAmbisonicsDecodeEffectSettings::from(
ambisonics_decode_effect_settings,
),
&raw mut inner,
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
let num_input_channels =
num_ambisonics_channels(ambisonics_decode_effect_settings.max_order);
let num_output_channels = match ambisonics_decode_effect_settings.rendering {
Rendering::Binaural => 2,
Rendering::Panning => match &ambisonics_decode_effect_settings.speaker_layout {
SpeakerLayout::Mono => 1,
SpeakerLayout::Stereo => 2,
SpeakerLayout::Quadraphonic => 4,
SpeakerLayout::Surround5_1 => 6,
SpeakerLayout::Surround7_1 => 8,
SpeakerLayout::Custom { speaker_directions } => speaker_directions.len() as u32,
},
};
let ambisonics_decode_effect = Self {
inner,
num_input_channels,
num_output_channels,
rendering: ambisonics_decode_effect_settings.rendering,
};
Ok(ambisonics_decode_effect)
}
pub fn apply<I, O, PI: ChannelPointers, PO: ChannelPointers>(
&mut self,
ambisonics_decode_effect_params: &AmbisonicsDecodeEffectParams,
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 != self.num_input_channels {
return Err(EffectError::InvalidInputChannels {
expected: ChannelRequirement::Exactly(self.num_input_channels),
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 mut ambisonics_decode_effect_params_ffi =
audionimbus_sys::IPLAmbisonicsDecodeEffectParams {
order: ambisonics_decode_effect_params.order as i32,
hrtf: ambisonics_decode_effect_params.hrtf.raw_ptr(),
orientation: ambisonics_decode_effect_params.orientation.into(),
binaural: match self.rendering {
Rendering::Binaural => audionimbus_sys::IPLbool::IPL_TRUE,
Rendering::Panning => audionimbus_sys::IPLbool::IPL_FALSE,
},
};
let state = unsafe {
audionimbus_sys::iplAmbisonicsDecodeEffectApply(
self.raw_ptr(),
&raw mut ambisonics_decode_effect_params_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::iplAmbisonicsDecodeEffectGetTail(
self.raw_ptr(),
&raw mut *output_buffer.as_ffi(),
)
}
.into();
Ok(state)
}
pub fn tail_size(&self) -> usize {
unsafe { audionimbus_sys::iplAmbisonicsDecodeEffectGetTailSize(self.raw_ptr()) as usize }
}
pub fn reset(&mut self) {
unsafe { audionimbus_sys::iplAmbisonicsDecodeEffectReset(self.raw_ptr()) };
}
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLAmbisonicsDecodeEffect {
self.inner
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLAmbisonicsDecodeEffect {
&mut self.inner
}
}
impl Drop for AmbisonicsDecodeEffect {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplAmbisonicsDecodeEffectRelease(&raw mut self.inner) }
}
}
unsafe impl Send for AmbisonicsDecodeEffect {}
unsafe impl Sync for AmbisonicsDecodeEffect {}
impl Clone for AmbisonicsDecodeEffect {
fn clone(&self) -> Self {
Self {
inner: unsafe { audionimbus_sys::iplAmbisonicsDecodeEffectRetain(self.inner) },
num_input_channels: self.num_input_channels,
num_output_channels: self.num_output_channels,
rendering: self.rendering,
}
}
}
#[derive(Debug)]
pub struct AmbisonicsDecodeEffectSettings<'a> {
pub speaker_layout: SpeakerLayout,
pub hrtf: &'a Hrtf,
pub max_order: u32,
pub rendering: Rendering,
}
impl From<&AmbisonicsDecodeEffectSettings<'_>>
for audionimbus_sys::IPLAmbisonicsDecodeEffectSettings
{
fn from(settings: &AmbisonicsDecodeEffectSettings) -> Self {
Self {
speakerLayout: audionimbus_sys::IPLSpeakerLayout::from(&settings.speaker_layout),
hrtf: settings.hrtf.raw_ptr(),
maxOrder: settings.max_order as i32,
}
}
}
#[derive(Debug)]
pub struct AmbisonicsDecodeEffectParams<'a> {
pub order: u32,
pub hrtf: &'a Hrtf,
pub orientation: CoordinateSystem,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Rendering {
Binaural,
Panning,
}
#[cfg(test)]
mod tests {
use crate::*;
mod apply {
use super::*;
#[test]
fn test_valid_first_order_binaural() {
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 mut effect = AmbisonicsDecodeEffect::try_new(
&context,
&audio_settings,
&AmbisonicsDecodeEffectSettings {
speaker_layout: SpeakerLayout::Stereo,
hrtf: &hrtf,
max_order: 1,
rendering: Rendering::Binaural,
},
)
.unwrap();
let params = AmbisonicsDecodeEffectParams {
order: 1,
hrtf: &hrtf,
orientation: CoordinateSystem::default(),
};
let input = vec![0.5; 4 * 1024];
let input_buffer = AudioBuffer::try_with_data_and_settings(
&input,
AudioBufferSettings::with_num_channels(4),
)
.unwrap();
let mut output = vec![0.0; 2 * 1024];
let output_buffer = AudioBuffer::try_with_data_and_settings(
&mut output,
AudioBufferSettings::with_num_channels(2),
)
.unwrap();
assert!(effect.apply(¶ms, &input_buffer, &output_buffer).is_ok());
}
#[test]
fn test_valid_first_order_panning() {
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 mut effect = AmbisonicsDecodeEffect::try_new(
&context,
&audio_settings,
&AmbisonicsDecodeEffectSettings {
speaker_layout: SpeakerLayout::Surround7_1,
hrtf: &hrtf,
max_order: 1,
rendering: Rendering::Panning,
},
)
.unwrap();
let params = AmbisonicsDecodeEffectParams {
order: 1,
hrtf: &hrtf,
orientation: CoordinateSystem::default(),
};
let input = vec![0.5; 4 * 1024];
let input_buffer = AudioBuffer::try_with_data_and_settings(
&input,
AudioBufferSettings::with_num_channels(4),
)
.unwrap();
let mut output = vec![0.0; 8 * 1024];
let output_buffer = AudioBuffer::try_with_data_and_settings(
&mut output,
AudioBufferSettings::with_num_channels(8),
)
.unwrap();
assert!(effect.apply(¶ms, &input_buffer, &output_buffer).is_ok());
}
#[test]
fn test_valid_invalid_input_channels() {
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 mut effect = AmbisonicsDecodeEffect::try_new(
&context,
&audio_settings,
&AmbisonicsDecodeEffectSettings {
speaker_layout: SpeakerLayout::Stereo,
hrtf: &hrtf,
max_order: 1,
rendering: Rendering::Binaural,
},
)
.unwrap();
let params = AmbisonicsDecodeEffectParams {
order: 1,
hrtf: &hrtf,
orientation: CoordinateSystem::default(),
};
let input = vec![0.5; 2 * 1024];
let input_buffer = AudioBuffer::try_with_data_and_settings(
&input,
AudioBufferSettings::with_num_channels(2),
)
.unwrap();
let mut output = vec![0.0; 2 * 1024];
let output_buffer = AudioBuffer::try_with_data_and_settings(
&mut output,
AudioBufferSettings::with_num_channels(2),
)
.unwrap();
assert_eq!(
effect.apply(¶ms, &input_buffer, &output_buffer),
Err(EffectError::InvalidInputChannels {
expected: ChannelRequirement::Exactly(4),
actual: 2,
})
);
}
#[test]
fn test_valid_invalid_output_channels() {
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 mut effect = AmbisonicsDecodeEffect::try_new(
&context,
&audio_settings,
&AmbisonicsDecodeEffectSettings {
speaker_layout: SpeakerLayout::Stereo,
hrtf: &hrtf,
max_order: 1,
rendering: Rendering::Binaural,
},
)
.unwrap();
let params = AmbisonicsDecodeEffectParams {
order: 1,
hrtf: &hrtf,
orientation: CoordinateSystem::default(),
};
let input = vec![0.5; 4 * 1024];
let input_buffer = AudioBuffer::try_with_data_and_settings(
&input,
AudioBufferSettings::with_num_channels(4),
)
.unwrap();
let mut output = vec![0.0; 3 * 1024];
let output_buffer = AudioBuffer::try_with_data_and_settings(
&mut output,
AudioBufferSettings::with_num_channels(3),
)
.unwrap();
assert_eq!(
effect.apply(¶ms, &input_buffer, &output_buffer),
Err(EffectError::InvalidOutputChannels {
expected: ChannelRequirement::Exactly(2),
actual: 3,
})
);
}
}
mod tail {
use super::*;
#[test]
fn test_valid() {
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 effect = AmbisonicsDecodeEffect::try_new(
&context,
&audio_settings,
&AmbisonicsDecodeEffectSettings {
speaker_layout: SpeakerLayout::Stereo,
hrtf: &hrtf,
max_order: 1,
rendering: Rendering::Binaural,
},
)
.unwrap();
let mut output = vec![0.0; 2 * 1024];
let output_buffer = AudioBuffer::try_with_data_and_settings(
&mut output,
AudioBufferSettings::with_num_channels(2),
)
.unwrap();
assert!(effect.tail(&output_buffer).is_ok());
}
#[test]
fn test_invalid_output_channels() {
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 effect = AmbisonicsDecodeEffect::try_new(
&context,
&audio_settings,
&AmbisonicsDecodeEffectSettings {
speaker_layout: SpeakerLayout::Stereo,
hrtf: &hrtf,
max_order: 1,
rendering: Rendering::Binaural,
},
)
.unwrap();
let mut output = vec![0.0; 4 * 1024];
let output_buffer = AudioBuffer::try_with_data_and_settings(
&mut output,
AudioBufferSettings::with_num_channels(4),
)
.unwrap();
assert_eq!(
effect.tail(&output_buffer),
Err(EffectError::InvalidOutputChannels {
expected: ChannelRequirement::Exactly(2),
actual: 4,
})
);
}
}
mod clone {
use super::*;
#[test]
fn test_clone() {
let context = Context::default();
let audio_settings = AudioSettings::default();
let hrtf = Hrtf::try_new(&context, &audio_settings, &HrtfSettings::default()).unwrap();
let effect = AmbisonicsDecodeEffect::try_new(
&context,
&audio_settings,
&AmbisonicsDecodeEffectSettings {
speaker_layout: SpeakerLayout::Stereo,
hrtf: &hrtf,
max_order: 1,
rendering: Rendering::Binaural,
},
)
.unwrap();
let clone = effect.clone();
assert_eq!(effect.raw_ptr(), clone.raw_ptr());
drop(effect);
assert!(!clone.raw_ptr().is_null());
}
}
}