use crate::audio_buffer::Sample;
use crate::context::Context;
use crate::error::{to_option_error, SteamAudioError};
use crate::NUM_BANDS;
#[derive(Debug)]
pub struct EnergyField(pub(crate) audionimbus_sys::IPLEnergyField);
impl EnergyField {
pub fn try_new(
context: &Context,
energy_field_settings: &EnergyFieldSettings,
) -> Result<Self, SteamAudioError> {
let mut energy_field = Self(std::ptr::null_mut());
let status = unsafe {
audionimbus_sys::iplEnergyFieldCreate(
context.raw_ptr(),
&mut audionimbus_sys::IPLEnergyFieldSettings::from(energy_field_settings),
energy_field.raw_ptr_mut(),
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
Ok(energy_field)
}
pub fn num_channels(&self) -> u32 {
unsafe { audionimbus_sys::iplEnergyFieldGetNumChannels(self.raw_ptr()) as u32 }
}
pub fn num_bins(&self) -> u32 {
unsafe { audionimbus_sys::iplEnergyFieldGetNumBins(self.raw_ptr()) as u32 }
}
pub fn data(&self) -> &[Sample] {
let ptr = unsafe { audionimbus_sys::iplEnergyFieldGetData(self.raw_ptr()) };
let len = self.num_channels() * NUM_BANDS * self.num_bins();
unsafe { std::slice::from_raw_parts(ptr, len as usize) }
}
pub fn channel(&self, channel_index: u32) -> Result<&[Sample], EnergyFieldError> {
let num_channels = self.num_channels();
if channel_index >= num_channels {
return Err(EnergyFieldError::ChannelIndexOutOfBounds {
channel_index,
num_channels,
});
}
let ptr = unsafe {
audionimbus_sys::iplEnergyFieldGetChannel(self.raw_ptr(), channel_index as i32)
};
let len = NUM_BANDS * self.num_bins();
let data = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
Ok(data)
}
pub fn band(&self, channel_index: u32, band_index: u32) -> Result<&[Sample], EnergyFieldError> {
let num_channels = self.num_channels();
if channel_index >= num_channels {
return Err(EnergyFieldError::ChannelIndexOutOfBounds {
channel_index,
num_channels,
});
}
if band_index >= NUM_BANDS {
return Err(EnergyFieldError::BandIndexOutOfBounds {
band_index,
max_bands: NUM_BANDS,
});
}
let ptr = unsafe {
audionimbus_sys::iplEnergyFieldGetBand(
self.raw_ptr(),
channel_index as i32,
band_index as i32,
)
};
let len = self.num_bins();
let data = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
Ok(data)
}
pub fn reset(&mut self) {
unsafe { audionimbus_sys::iplEnergyFieldReset(self.raw_ptr()) }
}
pub fn copy_into(&self, dst: &mut Self) {
unsafe { audionimbus_sys::iplEnergyFieldCopy(self.raw_ptr(), dst.raw_ptr()) }
}
pub fn swap(&mut self, other: &mut Self) {
unsafe { audionimbus_sys::iplEnergyFieldSwap(self.raw_ptr(), other.raw_ptr()) }
}
pub fn add(&mut self, other: &Self) {
unsafe {
audionimbus_sys::iplEnergyFieldAdd(self.raw_ptr(), other.raw_ptr(), self.raw_ptr());
}
}
pub fn scale(&mut self, scalar: f32) {
unsafe { audionimbus_sys::iplEnergyFieldScale(self.raw_ptr(), scalar, self.raw_ptr()) }
}
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLEnergyField {
self.0
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLEnergyField {
&mut self.0
}
}
impl Drop for EnergyField {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplEnergyFieldRelease(&raw mut self.0) }
}
}
unsafe impl Send for EnergyField {}
unsafe impl Sync for EnergyField {}
impl Clone for EnergyField {
fn clone(&self) -> Self {
Self(unsafe { audionimbus_sys::iplEnergyFieldRetain(self.0) })
}
}
#[derive(Debug)]
pub struct EnergyFieldSettings {
pub duration: f32,
pub order: u32,
}
impl From<&EnergyFieldSettings> for audionimbus_sys::IPLEnergyFieldSettings {
fn from(settings: &EnergyFieldSettings) -> Self {
Self {
duration: settings.duration,
order: settings.order as i32,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum EnergyFieldError {
ChannelIndexOutOfBounds {
channel_index: u32,
num_channels: u32,
},
BandIndexOutOfBounds { band_index: u32, max_bands: u32 },
}
impl std::error::Error for EnergyFieldError {}
impl std::fmt::Display for EnergyFieldError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::ChannelIndexOutOfBounds {
channel_index,
num_channels,
} => write!(
f,
"channel index {channel_index} out of bounds (num_channels: {num_channels})"
),
Self::BandIndexOutOfBounds {
band_index,
max_bands,
} => write!(
f,
"band index {band_index} out of bounds (max_bands: {max_bands})"
),
}
}
}
pub fn add_energy_fields(in1: &EnergyField, in2: &EnergyField, out: &mut EnergyField) {
unsafe { audionimbus_sys::iplEnergyFieldAdd(in1.raw_ptr(), in2.raw_ptr(), out.raw_ptr()) }
}
pub fn scale_energy_field(energy_field: &EnergyField, scalar: f32, out: &mut EnergyField) {
unsafe { audionimbus_sys::iplEnergyFieldScale(energy_field.raw_ptr(), scalar, out.raw_ptr()) }
}
pub fn scale_accum_energy_field(energy_field: &EnergyField, scalar: f32, out: &mut EnergyField) {
unsafe {
audionimbus_sys::iplEnergyFieldScaleAccum(energy_field.raw_ptr(), scalar, out.raw_ptr());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
mod energy_field {
use super::*;
mod channel {
use super::*;
#[test]
fn test_valid() {
let context = Context::default();
let settings = EnergyFieldSettings {
duration: 1.0,
order: 1,
};
let energy_field = EnergyField::try_new(&context, &settings).unwrap();
let result = energy_field.channel(2);
assert!(result.is_ok());
let result = energy_field.channel(3);
assert!(result.is_ok());
}
#[test]
fn test_index_out_of_bounds() {
let context = Context::default();
let energy_field = EnergyField::try_new(
&context,
&EnergyFieldSettings {
duration: 1.0,
order: 1,
},
)
.unwrap();
assert_eq!(
energy_field.channel(5),
Err(EnergyFieldError::ChannelIndexOutOfBounds {
channel_index: 5,
num_channels: 4,
}),
);
}
}
mod band {
use super::*;
#[test]
fn test_valid() {
let context = Context::default();
let settings = EnergyFieldSettings {
duration: 1.0,
order: 1,
};
let energy_field = EnergyField::try_new(&context, &settings).unwrap();
assert!(energy_field.band(0, 0).is_ok());
assert!(energy_field.band(3, NUM_BANDS - 1).is_ok());
}
#[test]
fn test_band_index_out_of_bounds() {
let context = Context::default();
let settings = EnergyFieldSettings {
duration: 1.0,
order: 1,
};
let energy_field = EnergyField::try_new(&context, &settings).unwrap();
assert_eq!(
energy_field.band(0, 5),
Err(EnergyFieldError::BandIndexOutOfBounds {
band_index: 5,
max_bands: 3,
}),
);
}
#[test]
fn test_band_channel_index_out_of_bounds() {
let context = Context::default();
let settings = EnergyFieldSettings {
duration: 1.0,
order: 1,
};
let energy_field = EnergyField::try_new(&context, &settings).unwrap();
assert_eq!(
energy_field.band(10, 0),
Err(EnergyFieldError::ChannelIndexOutOfBounds {
channel_index: 10,
num_channels: 4,
}),
);
}
}
#[test]
fn test_clone() {
let context = Context::default();
let settings = EnergyFieldSettings {
duration: 1.0,
order: 1,
};
let energy_field = EnergyField::try_new(&context, &settings).unwrap();
let clone = energy_field.clone();
assert_eq!(energy_field.raw_ptr(), clone.raw_ptr());
drop(energy_field);
assert!(!clone.raw_ptr().is_null());
}
}
}