use crate::context::Context;
use crate::energy_field::EnergyField;
use crate::error::{to_option_error, SteamAudioError};
use crate::impulse_response::ImpulseResponse;
#[derive(Debug)]
pub struct Reconstructor {
inner: audionimbus_sys::IPLReconstructor,
max_duration: f32,
max_order: u32,
}
impl Reconstructor {
pub fn try_new(
context: &Context,
reconstructor_settings: &ReconstructorSettings,
) -> Result<Self, SteamAudioError> {
let mut reconstructor = Self {
inner: std::ptr::null_mut(),
max_duration: reconstructor_settings.max_duration,
max_order: reconstructor_settings.max_order,
};
let status = unsafe {
audionimbus_sys::iplReconstructorCreate(
context.raw_ptr(),
&mut audionimbus_sys::IPLReconstructorSettings::from(reconstructor_settings),
reconstructor.raw_ptr_mut(),
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
Ok(reconstructor)
}
pub fn reconstruct(
&self,
inputs: &[ReconstructorInputs],
shared_inputs: &ReconstructorSharedInputs,
outputs: &[ReconstructorOutputs],
) -> Result<(), ReconstructorError> {
if shared_inputs.duration > self.max_duration {
return Err(ReconstructorError::DurationExceedsMax {
duration: shared_inputs.duration,
max_duration: self.max_duration,
});
}
if shared_inputs.order > self.max_order {
return Err(ReconstructorError::OrderExceedsMax {
order: shared_inputs.order,
max_order: self.max_order,
});
}
if inputs.len() != outputs.len() {
return Err(ReconstructorError::InputOutputLengthMismatch {
inputs_len: inputs.len(),
outputs_len: outputs.len(),
});
}
let c_inputs: Vec<audionimbus_sys::IPLReconstructorInputs> = inputs
.iter()
.map(audionimbus_sys::IPLReconstructorInputs::from)
.collect();
let c_outputs: Vec<audionimbus_sys::IPLReconstructorOutputs> = outputs
.iter()
.map(audionimbus_sys::IPLReconstructorOutputs::from)
.collect();
let c_shared_inputs = audionimbus_sys::IPLReconstructorSharedInputs::from(shared_inputs);
unsafe {
audionimbus_sys::iplReconstructorReconstruct(
self.raw_ptr(),
inputs.len() as i32,
c_inputs.as_ptr().cast_mut(),
&c_shared_inputs as *const audionimbus_sys::IPLReconstructorSharedInputs
as *mut audionimbus_sys::IPLReconstructorSharedInputs,
c_outputs.as_ptr().cast_mut(),
);
}
Ok(())
}
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLReconstructor {
self.inner
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLReconstructor {
&mut self.inner
}
}
impl Drop for Reconstructor {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplReconstructorRelease(&raw mut self.inner) }
}
}
unsafe impl Send for Reconstructor {}
unsafe impl Sync for Reconstructor {}
impl Clone for Reconstructor {
fn clone(&self) -> Self {
Self {
inner: unsafe { audionimbus_sys::iplReconstructorRetain(self.inner) },
max_duration: self.max_duration,
max_order: self.max_order,
}
}
}
#[derive(Debug)]
pub struct ReconstructorSettings {
pub max_duration: f32,
pub max_order: u32,
pub sampling_rate: u32,
}
impl From<&ReconstructorSettings> for audionimbus_sys::IPLReconstructorSettings {
fn from(settings: &ReconstructorSettings) -> Self {
Self {
maxDuration: settings.max_duration,
maxOrder: settings.max_order as i32,
samplingRate: settings.sampling_rate as i32,
}
}
}
#[derive(Debug)]
pub struct ReconstructorSharedInputs {
pub duration: f32,
pub order: u32,
}
impl From<&ReconstructorSharedInputs> for audionimbus_sys::IPLReconstructorSharedInputs {
fn from(reconstructor_shared_inputs: &ReconstructorSharedInputs) -> Self {
Self {
duration: reconstructor_shared_inputs.duration,
order: reconstructor_shared_inputs.order as i32,
}
}
}
#[derive(Debug)]
pub struct ReconstructorInputs<'a> {
pub energy_field: &'a EnergyField,
}
impl From<&ReconstructorInputs<'_>> for audionimbus_sys::IPLReconstructorInputs {
fn from(reconstructor_inputs: &ReconstructorInputs) -> Self {
Self {
energyField: reconstructor_inputs.energy_field.raw_ptr(),
}
}
}
#[derive(Debug)]
pub struct ReconstructorOutputs<'a> {
pub impulse_response: &'a mut ImpulseResponse,
}
impl From<&ReconstructorOutputs<'_>> for audionimbus_sys::IPLReconstructorOutputs {
fn from(reconstructor_outputs: &ReconstructorOutputs) -> Self {
Self {
impulseResponse: reconstructor_outputs.impulse_response.raw_ptr(),
}
}
}
#[derive(Debug, PartialEq)]
pub enum ReconstructorError {
DurationExceedsMax { duration: f32, max_duration: f32 },
OrderExceedsMax { order: u32, max_order: u32 },
InputOutputLengthMismatch {
inputs_len: usize,
outputs_len: usize,
},
}
impl std::error::Error for ReconstructorError {}
impl std::fmt::Display for ReconstructorError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::DurationExceedsMax {
duration,
max_duration,
} => write!(
f,
"duration {duration} exceeds max duration {max_duration}"
),
Self::OrderExceedsMax { order, max_order } => {
write!(f, "order {order} exceeds max order {max_order}")
}
Self::InputOutputLengthMismatch {
inputs_len,
outputs_len,
} => write!(
f,
"inputs and outputs length mismatch: inputs_len={inputs_len}, outputs_len={outputs_len}"
),
}
}
}
#[cfg(test)]
mod tests {
use crate::*;
mod reconstructor {
use super::*;
const SAMPLING_RATE: u32 = 48_000;
const MAX_DURATION: f32 = 2.0;
const MAX_ORDER: u32 = 2;
const VALID_DURATION: f32 = 1.0;
const VALID_ORDER: u32 = 1;
#[test]
fn test_valid() {
let context = Context::default();
let reconstructor_settings = ReconstructorSettings {
max_duration: MAX_DURATION,
max_order: MAX_ORDER,
sampling_rate: SAMPLING_RATE,
};
let reconstructor = Reconstructor::try_new(&context, &reconstructor_settings).unwrap();
let shared_inputs = ReconstructorSharedInputs {
duration: VALID_DURATION,
order: VALID_ORDER,
};
let energy_field = EnergyField::try_new(
&context,
&EnergyFieldSettings {
duration: VALID_DURATION,
order: VALID_ORDER,
},
)
.unwrap();
let inputs = vec![ReconstructorInputs {
energy_field: &energy_field,
}];
let mut impulse_response = ImpulseResponse::try_new(
&context,
&ImpulseResponseSettings {
duration: VALID_DURATION,
order: VALID_ORDER,
sampling_rate: SAMPLING_RATE,
},
)
.unwrap();
let outputs = vec![ReconstructorOutputs {
impulse_response: &mut impulse_response,
}];
let result = reconstructor.reconstruct(&inputs, &shared_inputs, &outputs);
assert!(result.is_ok());
}
#[test]
fn test_duration_exceeds_max() {
let context = Context::default();
let reconstructor_settings = ReconstructorSettings {
max_duration: MAX_DURATION,
max_order: MAX_ORDER,
sampling_rate: SAMPLING_RATE,
};
let reconstructor = Reconstructor::try_new(&context, &reconstructor_settings).unwrap();
let invalid_duration = MAX_DURATION + 1.0;
let shared_inputs = ReconstructorSharedInputs {
duration: invalid_duration,
order: VALID_ORDER,
};
let energy_field = EnergyField::try_new(
&context,
&EnergyFieldSettings {
duration: VALID_DURATION,
order: VALID_ORDER,
},
)
.unwrap();
let inputs = vec![ReconstructorInputs {
energy_field: &energy_field,
}];
let mut impulse_response = ImpulseResponse::try_new(
&context,
&ImpulseResponseSettings {
duration: VALID_DURATION,
order: VALID_ORDER,
sampling_rate: SAMPLING_RATE,
},
)
.unwrap();
let outputs = vec![ReconstructorOutputs {
impulse_response: &mut impulse_response,
}];
assert_eq!(
reconstructor.reconstruct(&inputs, &shared_inputs, &outputs),
Err(ReconstructorError::DurationExceedsMax {
duration: invalid_duration,
max_duration: MAX_DURATION,
}),
);
}
#[test]
fn test_order_exceeds_max() {
let context = Context::default();
let reconstructor_settings = ReconstructorSettings {
max_duration: MAX_DURATION,
max_order: MAX_ORDER,
sampling_rate: SAMPLING_RATE,
};
let reconstructor = Reconstructor::try_new(&context, &reconstructor_settings).unwrap();
let invalid_order = MAX_ORDER + 1;
let shared_inputs = ReconstructorSharedInputs {
duration: MAX_DURATION,
order: invalid_order,
};
let energy_field = EnergyField::try_new(
&context,
&EnergyFieldSettings {
duration: VALID_DURATION,
order: VALID_ORDER,
},
)
.unwrap();
let inputs = vec![ReconstructorInputs {
energy_field: &energy_field,
}];
let mut impulse_response = ImpulseResponse::try_new(
&context,
&ImpulseResponseSettings {
duration: VALID_DURATION,
order: VALID_ORDER,
sampling_rate: SAMPLING_RATE,
},
)
.unwrap();
let outputs = vec![ReconstructorOutputs {
impulse_response: &mut impulse_response,
}];
assert_eq!(
reconstructor.reconstruct(&inputs, &shared_inputs, &outputs),
Err(ReconstructorError::OrderExceedsMax {
order: invalid_order,
max_order: MAX_ORDER,
}),
);
}
#[test]
fn test_input_output_length_mismatch() {
let context = Context::default();
let reconstructor_settings = ReconstructorSettings {
max_duration: MAX_DURATION,
max_order: MAX_ORDER,
sampling_rate: SAMPLING_RATE,
};
let reconstructor = Reconstructor::try_new(&context, &reconstructor_settings).unwrap();
let shared_inputs = ReconstructorSharedInputs {
duration: VALID_DURATION,
order: VALID_ORDER,
};
let energy_field = EnergyField::try_new(
&context,
&EnergyFieldSettings {
duration: VALID_DURATION,
order: VALID_ORDER,
},
)
.unwrap();
let inputs = vec![
ReconstructorInputs {
energy_field: &energy_field,
},
ReconstructorInputs {
energy_field: &energy_field,
},
];
let mut impulse_response = ImpulseResponse::try_new(
&context,
&ImpulseResponseSettings {
duration: VALID_DURATION,
order: VALID_ORDER,
sampling_rate: SAMPLING_RATE,
},
)
.unwrap();
let outputs = vec![ReconstructorOutputs {
impulse_response: &mut impulse_response,
}];
assert_eq!(
reconstructor.reconstruct(&inputs, &shared_inputs, &outputs),
Err(ReconstructorError::InputOutputLengthMismatch {
inputs_len: 2,
outputs_len: 1,
}),
);
}
#[test]
fn test_clone() {
let context = Context::default();
let reconstructor_settings = ReconstructorSettings {
max_duration: MAX_DURATION,
max_order: MAX_ORDER,
sampling_rate: SAMPLING_RATE,
};
let reconstructor = Reconstructor::try_new(&context, &reconstructor_settings).unwrap();
let clone = reconstructor.clone();
assert_eq!(reconstructor.raw_ptr(), clone.raw_ptr());
drop(reconstructor);
assert!(!clone.raw_ptr().is_null());
}
}
}