use super::audio_effect_state::AudioEffectState;
use super::{EffectError, Equalizer};
use crate::ChannelRequirement;
use crate::audio_buffer::{AudioBuffer, AudioBufferMut, read_as_ffi};
use crate::audio_settings::AudioSettings;
use crate::context::Context;
use crate::error::{SteamAudioError, to_option_error};
use crate::ffi_wrapper::FFIWrapper;
use std::hash::{Hash, Hasher};
#[derive(Debug)]
pub struct DirectEffect {
inner: audionimbus_sys::IPLDirectEffect,
num_channels: u32,
}
impl DirectEffect {
pub fn try_new(
context: &Context,
audio_settings: &AudioSettings,
direct_effect_settings: &DirectEffectSettings,
) -> Result<Self, SteamAudioError> {
let mut inner = std::ptr::null_mut();
let status = unsafe {
audionimbus_sys::iplDirectEffectCreate(
context.raw_ptr(),
&mut audionimbus_sys::IPLAudioSettings::from(audio_settings),
&mut audionimbus_sys::IPLDirectEffectSettings::from(direct_effect_settings),
&raw mut inner,
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
let direct_effect = Self {
inner,
num_channels: direct_effect_settings.num_channels,
};
Ok(direct_effect)
}
pub fn apply(
&mut self,
direct_effect_params: &DirectEffectParams,
input_buffer: &impl AudioBuffer,
output_buffer: &mut AudioBufferMut<'_>,
) -> Result<AudioEffectState, EffectError> {
let num_input_channels = input_buffer.num_channels() as u32;
if num_input_channels != self.num_channels {
return Err(EffectError::InvalidInputChannels {
expected: ChannelRequirement::Exactly(self.num_channels),
actual: num_input_channels,
});
}
let num_output_channels = output_buffer.num_channels() as u32;
if num_output_channels != self.num_channels {
return Err(EffectError::InvalidOutputChannels {
expected: ChannelRequirement::Exactly(self.num_channels),
actual: num_output_channels,
});
}
let mut input_buffer = read_as_ffi(input_buffer);
let mut output_buffer = output_buffer.as_ffi_mut();
let state = unsafe {
audionimbus_sys::iplDirectEffectApply(
self.raw_ptr(),
&raw mut *direct_effect_params.as_ffi(),
&raw mut *input_buffer,
&raw mut *output_buffer,
)
}
.into();
Ok(state)
}
pub fn apply_in_place(
&mut self,
direct_effect_params: &DirectEffectParams,
buffer: &mut AudioBufferMut<'_>,
) -> Result<AudioEffectState, EffectError> {
let num_channels = buffer.num_channels() as u32;
if num_channels != self.num_channels {
return Err(EffectError::InvalidInputChannels {
expected: ChannelRequirement::Exactly(self.num_channels),
actual: num_channels,
});
}
let mut buffer = buffer.as_ffi_mut();
let state = unsafe {
audionimbus_sys::iplDirectEffectApply(
self.raw_ptr(),
&raw mut *direct_effect_params.as_ffi(),
&raw mut *buffer,
&raw mut *buffer,
)
}
.into();
Ok(state)
}
pub fn tail(
&self,
output_buffer: &mut AudioBufferMut<'_>,
) -> Result<AudioEffectState, EffectError> {
let num_output_channels = output_buffer.num_channels() as u32;
if num_output_channels != self.num_channels {
return Err(EffectError::InvalidOutputChannels {
expected: ChannelRequirement::Exactly(self.num_channels),
actual: num_output_channels,
});
}
let mut output_buffer = output_buffer.as_ffi_mut();
let state = unsafe {
audionimbus_sys::iplDirectEffectGetTail(self.raw_ptr(), &raw mut *output_buffer)
}
.into();
Ok(state)
}
pub fn tail_size(&self) -> usize {
unsafe { audionimbus_sys::iplDirectEffectGetTailSize(self.raw_ptr()) as usize }
}
pub fn reset(&mut self) {
unsafe { audionimbus_sys::iplDirectEffectReset(self.raw_ptr()) };
}
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLDirectEffect {
self.inner
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLDirectEffect {
&mut self.inner
}
}
impl Drop for DirectEffect {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplDirectEffectRelease(&raw mut self.inner) }
}
}
unsafe impl Send for DirectEffect {}
unsafe impl Sync for DirectEffect {}
impl Clone for DirectEffect {
fn clone(&self) -> Self {
Self {
inner: unsafe { audionimbus_sys::iplDirectEffectRetain(self.inner) },
num_channels: self.num_channels,
}
}
}
impl PartialEq for DirectEffect {
fn eq(&self, other: &Self) -> bool {
self.raw_ptr() == other.raw_ptr()
}
}
impl Eq for DirectEffect {}
impl Hash for DirectEffect {
fn hash<H: Hasher>(&self, state: &mut H) {
std::ptr::hash(self.raw_ptr(), state);
}
}
#[derive(Debug)]
pub struct DirectEffectSettings {
pub num_channels: u32,
}
impl From<&DirectEffectSettings> for audionimbus_sys::IPLDirectEffectSettings {
fn from(settings: &DirectEffectSettings) -> Self {
Self {
numChannels: settings.num_channels as i32,
}
}
}
#[derive(Default, Clone, Debug, PartialEq)]
pub struct DirectEffectParams {
pub distance_attenuation: Option<f32>,
pub air_absorption: Option<Equalizer<3>>,
pub directivity: Option<f32>,
pub occlusion: Option<f32>,
pub transmission: Option<Transmission>,
}
impl From<audionimbus_sys::IPLDirectEffectParams> for DirectEffectParams {
fn from(params: audionimbus_sys::IPLDirectEffectParams) -> Self {
let distance_attenuation = Some(params.distanceAttenuation);
let air_absorption = Some(Equalizer(params.airAbsorption));
let directivity = Some(params.directivity);
let occlusion = Some(params.occlusion);
let transmission = Some(match params.transmissionType {
audionimbus_sys::IPLTransmissionType::IPL_TRANSMISSIONTYPE_FREQINDEPENDENT => {
Transmission::FrequencyIndependent(Equalizer(params.transmission))
}
audionimbus_sys::IPLTransmissionType::IPL_TRANSMISSIONTYPE_FREQDEPENDENT => {
Transmission::FrequencyDependent(Equalizer(params.transmission))
}
});
Self {
distance_attenuation,
air_absorption,
directivity,
occlusion,
transmission,
}
}
}
impl DirectEffectParams {
pub(crate) fn as_ffi(&self) -> FFIWrapper<'_, audionimbus_sys::IPLDirectEffectParams, Self> {
let mut flags = audionimbus_sys::IPLDirectEffectFlags(<_>::default());
let distance_attenuation = self.distance_attenuation.unwrap_or_default();
if self.distance_attenuation.is_some() {
flags |= audionimbus_sys::IPLDirectEffectFlags::IPL_DIRECTEFFECTFLAGS_APPLYDISTANCEATTENUATION;
}
let default_air_absorption = Equalizer::default();
let air_absorption = self
.air_absorption
.as_ref()
.unwrap_or(&default_air_absorption);
if self.air_absorption.is_some() {
flags |=
audionimbus_sys::IPLDirectEffectFlags::IPL_DIRECTEFFECTFLAGS_APPLYAIRABSORPTION;
}
let directivity = self.directivity.unwrap_or_default();
if self.directivity.is_some() {
flags |= audionimbus_sys::IPLDirectEffectFlags::IPL_DIRECTEFFECTFLAGS_APPLYDIRECTIVITY;
}
let occlusion = self.occlusion.unwrap_or_default();
if self.occlusion.is_some() {
flags |= audionimbus_sys::IPLDirectEffectFlags::IPL_DIRECTEFFECTFLAGS_APPLYOCCLUSION;
}
let (transmission_type, transmission) = if let Some(transmission) = &self.transmission {
flags |= audionimbus_sys::IPLDirectEffectFlags::IPL_DIRECTEFFECTFLAGS_APPLYTRANSMISSION;
match transmission {
Transmission::FrequencyIndependent(equalizer) => (
audionimbus_sys::IPLTransmissionType::IPL_TRANSMISSIONTYPE_FREQINDEPENDENT,
equalizer,
),
Transmission::FrequencyDependent(equalizer) => (
audionimbus_sys::IPLTransmissionType::IPL_TRANSMISSIONTYPE_FREQDEPENDENT,
equalizer,
),
}
} else {
(
audionimbus_sys::IPLTransmissionType::IPL_TRANSMISSIONTYPE_FREQINDEPENDENT,
&Equalizer::default(),
)
};
let direct_effect_params = audionimbus_sys::IPLDirectEffectParams {
flags,
transmissionType: transmission_type,
distanceAttenuation: distance_attenuation,
airAbsorption: **air_absorption,
directivity,
occlusion,
transmission: **transmission,
};
FFIWrapper::new(direct_effect_params)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Transmission {
FrequencyIndependent(Equalizer<3>),
FrequencyDependent(Equalizer<3>),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::AudioBufferRef;
mod apply {
use super::*;
#[test]
fn test_valid() {
let input_container = vec![0.5; 1024];
let input_buffer = AudioBufferRef::try_from(&input_container[..]).unwrap();
let mut output_container = vec![0.0; input_buffer.num_samples() as usize];
let mut output_buffer = AudioBufferMut::try_from(&mut output_container[..]).unwrap();
let context = Context::default();
let audio_settings = AudioSettings {
frame_size: input_buffer.num_samples() as u32,
..Default::default()
};
let direct_effect_settings = DirectEffectSettings { num_channels: 1 };
let mut direct_effect =
DirectEffect::try_new(&context, &audio_settings, &direct_effect_settings).unwrap();
let direct_effect_params = DirectEffectParams {
distance_attenuation: Some(0.6),
air_absorption: Some(Equalizer([0.9, 0.7, 0.5])),
directivity: Some(0.7),
occlusion: Some(0.4),
transmission: Some(Transmission::FrequencyIndependent(Equalizer([
0.3, 0.2, 0.1,
]))),
};
assert!(
direct_effect
.apply(&direct_effect_params, &input_buffer, &mut output_buffer)
.is_ok()
);
}
#[test]
fn test_invalid_input_num_channels() {
const FRAME_SIZE: usize = 1024;
let input_container = vec![0.5; 4 * FRAME_SIZE];
let input_buffer = AudioBufferRef::try_new(&input_container, 4).unwrap();
let mut output_container = vec![0.0; FRAME_SIZE];
let mut output_buffer = AudioBufferMut::try_from(&mut output_container[..]).unwrap();
let context = Context::default();
let audio_settings = AudioSettings {
frame_size: FRAME_SIZE as u32,
..Default::default()
};
let direct_effect_settings = DirectEffectSettings { num_channels: 1 };
let mut direct_effect =
DirectEffect::try_new(&context, &audio_settings, &direct_effect_settings).unwrap();
let direct_effect_params = DirectEffectParams {
distance_attenuation: Some(0.6),
air_absorption: Some(Equalizer([0.9, 0.7, 0.5])),
directivity: Some(0.7),
occlusion: Some(0.4),
transmission: Some(Transmission::FrequencyIndependent(Equalizer([
0.3, 0.2, 0.1,
]))),
};
assert_eq!(
direct_effect.apply(&direct_effect_params, &input_buffer, &mut output_buffer),
Err(EffectError::InvalidInputChannels {
expected: ChannelRequirement::Exactly(1),
actual: 4
})
);
}
#[test]
fn test_in_place() {
let mut samples = vec![0.5; 1024];
let mut buffer = AudioBufferMut::try_from(&mut samples[..]).unwrap();
let context = Context::default();
let audio_settings = AudioSettings::default();
let mut effect = DirectEffect::try_new(
&context,
&audio_settings,
&DirectEffectSettings { num_channels: 1 },
)
.unwrap();
let params = DirectEffectParams {
distance_attenuation: Some(0.5),
..Default::default()
};
assert!(effect.apply_in_place(¶ms, &mut buffer).is_ok());
}
#[test]
fn test_invalid_output_num_channels() {
const FRAME_SIZE: usize = 1024;
let mut input_container = vec![0.0; FRAME_SIZE];
let input_buffer = AudioBufferMut::try_from(&mut input_container[..]).unwrap();
let mut output_container = vec![0.5; 4 * FRAME_SIZE];
let mut output_buffer = AudioBufferMut::try_new(&mut output_container, 4).unwrap();
let context = Context::default();
let audio_settings = AudioSettings {
frame_size: FRAME_SIZE as u32,
..Default::default()
};
let direct_effect_settings = DirectEffectSettings { num_channels: 1 };
let mut direct_effect =
DirectEffect::try_new(&context, &audio_settings, &direct_effect_settings).unwrap();
let direct_effect_params = DirectEffectParams {
distance_attenuation: Some(0.6),
air_absorption: Some(Equalizer([0.9, 0.7, 0.5])),
directivity: Some(0.7),
occlusion: Some(0.4),
transmission: Some(Transmission::FrequencyIndependent(Equalizer([
0.3, 0.2, 0.1,
]))),
};
assert_eq!(
direct_effect.apply(&direct_effect_params, &input_buffer, &mut output_buffer),
Err(EffectError::InvalidOutputChannels {
expected: ChannelRequirement::Exactly(1),
actual: 4
})
);
}
}
mod tail {
use super::*;
#[test]
fn test_valid() {
const FRAME_SIZE: usize = 1024;
let mut output_container = vec![0.0; FRAME_SIZE];
let mut output_buffer = AudioBufferMut::try_from(&mut output_container[..]).unwrap();
let context = Context::default();
let audio_settings = AudioSettings::default();
let direct_effect_settings = DirectEffectSettings { num_channels: 1 };
let direct_effect =
DirectEffect::try_new(&context, &audio_settings, &direct_effect_settings).unwrap();
assert!(direct_effect.tail(&mut output_buffer).is_ok());
}
#[test]
fn test_invalid_output_num_channels() {
const FRAME_SIZE: usize = 1024;
let mut output_container = vec![0.0; 2 * FRAME_SIZE];
let mut output_buffer = AudioBufferMut::try_new(&mut output_container, 2).unwrap();
let context = Context::default();
let audio_settings = AudioSettings::default();
let direct_effect_settings = DirectEffectSettings { num_channels: 1 };
let direct_effect =
DirectEffect::try_new(&context, &audio_settings, &direct_effect_settings).unwrap();
assert_eq!(
direct_effect.tail(&mut output_buffer),
Err(EffectError::InvalidOutputChannels {
expected: ChannelRequirement::Exactly(1),
actual: 2,
})
);
}
}
mod clone {
use super::*;
#[test]
fn test_clone() {
let context = Context::default();
let audio_settings = AudioSettings::default();
let effect = DirectEffect::try_new(
&context,
&audio_settings,
&DirectEffectSettings { num_channels: 1 },
)
.unwrap();
let clone = effect.clone();
assert_eq!(effect.raw_ptr(), clone.raw_ptr());
drop(effect);
assert!(!clone.raw_ptr().is_null());
}
}
}