use crate::Sealed;
use crate::audio_settings::AudioSettings;
use crate::baking::{BakedDataIdentifier, BakedDataVariation};
pub use crate::callback::PathingVisualizationCallback;
use crate::context::Context;
use crate::device::open_cl::OpenClDevice;
use crate::device::radeon_rays::RadeonRaysDevice;
use crate::device::true_audio_next::TrueAudioNextDevice;
use crate::effect::reflections::ReflectionEffectType;
use crate::effect::{
Convolution, DirectEffectParams, Hybrid, Parametric, PathEffectParams, ReflectionEffectParams,
TrueAudioNext,
};
use crate::error::{SteamAudioError, to_option_error};
use crate::geometry::{CoordinateSystem, Scene};
use crate::model::air_absorption::AirAbsorptionModel;
use crate::model::deviation::DeviationModel;
use crate::model::directivity::Directivity;
use crate::model::distance_attenuation::DistanceAttenuationModel;
use crate::probe::ProbeBatch;
use crate::ray_tracing::{CustomRayTracer, DefaultRayTracer, Embree, RadeonRays, RayTracer};
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::sync::{Arc, Mutex, MutexGuard};
#[derive(Default, Copy, Clone, Debug)]
pub struct Direct;
#[derive(Default, Copy, Clone, Debug)]
pub struct Reflections;
#[derive(Default, Copy, Clone, Debug)]
pub struct Pathing;
pub trait SimulationFlagsProvider {
fn flags() -> SimulationFlags;
}
impl SimulationFlagsProvider for Direct {
fn flags() -> SimulationFlags {
SimulationFlags::DIRECT
}
}
impl SimulationFlagsProvider for Reflections {
fn flags() -> SimulationFlags {
SimulationFlags::REFLECTIONS
}
}
impl SimulationFlagsProvider for Pathing {
fn flags() -> SimulationFlags {
SimulationFlags::PATHING
}
}
impl SimulationFlagsProvider for () {
fn flags() -> SimulationFlags {
SimulationFlags::empty()
}
}
#[derive(Debug)]
pub struct Simulator<T: RayTracer, D = (), R = (), P = (), RE = ()> {
inner: audionimbus_sys::IPLSimulator,
shared: Arc<Mutex<SimulatorShared<T>>>,
audio_settings: AudioSettings,
max_num_occlusion_samples: Option<u32>,
max_num_rays: Option<u32>,
max_duration: Option<f32>,
direct_lock: Option<Arc<Mutex<()>>>,
reflections_lock: Option<Arc<Mutex<()>>>,
pathing_lock: Option<Arc<Mutex<()>>>,
_open_cl_device: Option<OpenClDevice>,
_radeon_rays_device: Option<RadeonRaysDevice>,
_true_audio_next_device: Option<TrueAudioNextDevice>,
_ray_tracer: PhantomData<T>,
_direct: PhantomData<D>,
_reflections: PhantomData<R>,
_pathing: PhantomData<P>,
_reflection_effect: PhantomData<RE>,
}
#[derive(Debug)]
struct SimulatorShared<T: RayTracer> {
committed_num_probes: usize,
pending_probe_batches: HashMap<audionimbus_sys::IPLProbeBatch, usize>,
committed_scene: Option<Scene<T>>,
pending_scene: Option<Scene<T>>,
}
impl<T: RayTracer> Default for SimulatorShared<T> {
fn default() -> Self {
Self {
committed_num_probes: 0,
pending_probe_batches: HashMap::new(),
committed_scene: None,
pending_scene: None,
}
}
}
impl<T, D, R, P, RE> Simulator<T, D, R, P, RE>
where
T: RayTracer,
D: 'static,
R: 'static,
P: 'static,
RE: 'static,
{
pub fn try_new(
context: &Context,
settings: &SimulationSettings<T, D, R, P, RE>,
) -> Result<Self, SteamAudioError> {
let direct_lock = if std::any::TypeId::of::<D>() == std::any::TypeId::of::<Direct>() {
Some(Arc::new(Mutex::new(())))
} else {
None
};
let reflections_lock =
if std::any::TypeId::of::<R>() == std::any::TypeId::of::<Reflections>() {
Some(Arc::new(Mutex::new(())))
} else {
None
};
let pathing_lock = if std::any::TypeId::of::<P>() == std::any::TypeId::of::<Pathing>() {
Some(Arc::new(Mutex::new(())))
} else {
None
};
let mut ffi_settings = settings.to_ffi();
let mut simulator = Self {
inner: std::ptr::null_mut(),
#[allow(clippy::arc_with_non_send_sync)]
shared: Arc::new(Mutex::new(SimulatorShared::default())),
audio_settings: settings.audio_settings(),
max_num_occlusion_samples: settings.max_num_occlusion_samples(),
max_num_rays: settings.max_num_rays(),
max_duration: settings.max_duration(),
direct_lock,
reflections_lock,
pathing_lock,
_open_cl_device: settings.open_cl_device.clone(),
_radeon_rays_device: settings.radeon_rays_device.clone(),
_true_audio_next_device: settings.true_audio_next_device.clone(),
_ray_tracer: PhantomData,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
_reflection_effect: PhantomData,
};
let status = unsafe {
audionimbus_sys::iplSimulatorCreate(
context.raw_ptr(),
&mut ffi_settings,
simulator.raw_ptr_mut(),
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
Ok(simulator)
}
pub fn set_scene(&mut self, scene: &Scene<T>) {
let previous_pending_scene = {
let mut shared = self.shared.lock().unwrap();
let is_noop = shared.pending_scene.as_ref() == Some(scene)
|| (shared.pending_scene.is_none()
&& shared.committed_scene.as_ref() == Some(scene));
if is_noop {
return;
}
shared.pending_scene.replace(scene.clone())
};
let _guards = self.acquire_all_locks();
let simulator = self.raw_ptr();
let lock_handles = self.lock_handles();
if let Some(previous_pending_scene) = previous_pending_scene {
previous_pending_scene.unregister_simulator(simulator, 1);
}
scene.register_simulator(simulator, &lock_handles, 1);
unsafe { audionimbus_sys::iplSimulatorSetScene(self.raw_ptr(), scene.raw_ptr()) }
}
pub fn add_probe_batch(&mut self, probe_batch: &ProbeBatch) {
let _guards = self.acquire_all_locks();
let raw_ptr = probe_batch.raw_ptr();
unsafe {
audionimbus_sys::iplSimulatorAddProbeBatch(self.raw_ptr(), probe_batch.raw_ptr());
}
let mut shared = self.shared.lock().unwrap();
shared
.pending_probe_batches
.insert(raw_ptr, probe_batch.committed_num_probes());
}
pub fn remove_probe_batch(&mut self, probe_batch: &ProbeBatch) {
let _guards = self.acquire_all_locks();
let raw_ptr = probe_batch.raw_ptr();
unsafe {
audionimbus_sys::iplSimulatorRemoveProbeBatch(self.raw_ptr(), probe_batch.raw_ptr());
}
let mut shared = self.shared.lock().unwrap();
shared.pending_probe_batches.remove(&raw_ptr);
}
pub fn add_source<SrcD, SrcR, SrcP, SrcRE>(&self, source: &Source<SrcD, SrcR, SrcP, SrcRE>)
where
SrcD: DirectCompatible<D> + 'static,
SrcR: ReflectionsCompatible<R> + 'static,
SrcP: PathingCompatible<P> + 'static,
SrcRE: ReflectionEffectCompatible<SrcR, RE> + 'static,
{
unsafe {
audionimbus_sys::iplSourceAdd(source.raw_ptr(), self.raw_ptr());
}
}
pub fn remove_source<SrcD, SrcR, SrcP, SrcRE>(&self, source: &Source<SrcD, SrcR, SrcP, SrcRE>)
where
SrcD: DirectCompatible<D> + 'static,
SrcR: ReflectionsCompatible<R> + 'static,
SrcP: PathingCompatible<P> + 'static,
SrcRE: ReflectionEffectCompatible<SrcR, RE> + 'static,
{
unsafe {
audionimbus_sys::iplSourceRemove(source.raw_ptr(), self.raw_ptr());
}
}
pub fn commit(&self) {
let _guards = self.acquire_all_locks();
let simulator = self.raw_ptr();
unsafe { audionimbus_sys::iplSimulatorCommit(self.raw_ptr()) }
let previous_committed_scene = {
let mut shared = self.shared.lock().unwrap();
shared.committed_num_probes = shared.pending_probe_batches.values().sum();
if let Some(pending_scene) = shared.pending_scene.take() {
shared.committed_scene.replace(pending_scene)
} else {
None
}
};
if let Some(previous_committed_scene) = previous_committed_scene {
previous_committed_scene.unregister_simulator(simulator, 1);
}
}
pub fn set_shared_inputs<InD, InR, InP>(
&self,
shared_inputs: &SimulationSharedInputs<InD, InR, InP>,
) -> Result<(), ParameterValidationError>
where
D: DirectCompatible<D> + SimulationFlagsProvider,
R: ReflectionsCompatible<R> + ReflectionsCompatible<InR> + SimulationFlagsProvider,
P: PathingCompatible<P> + SimulationFlagsProvider,
InD: DirectCompatible<D> + SimulationFlagsProvider,
InR: ReflectionsCompatible<R> + SimulationFlagsProvider,
InP: PathingCompatible<P> + SimulationFlagsProvider,
{
self.set_shared_inputs_subset::<D, R, P, InD, InR, InP>(shared_inputs)
}
pub fn set_shared_inputs_subset<SubD, SubR, SubP, InD, InR, InP>(
&self,
shared_inputs: &SimulationSharedInputs<InD, InR, InP>,
) -> Result<(), ParameterValidationError>
where
SubD: DirectCompatible<D> + SimulationFlagsProvider,
SubR: ReflectionsCompatible<R> + ReflectionsCompatible<InR> + SimulationFlagsProvider,
SubP: PathingCompatible<P> + SimulationFlagsProvider,
InD: DirectCompatible<D> + SimulationFlagsProvider,
InR: ReflectionsCompatible<R> + SimulationFlagsProvider,
InP: PathingCompatible<P> + SimulationFlagsProvider,
{
self.validate_shared_inputs(shared_inputs)?;
let simulation_flags = SubD::flags() | SubR::flags() | SubP::flags();
let _guards = self.acquire_locks_for_flags(simulation_flags);
unsafe {
audionimbus_sys::iplSimulatorSetSharedInputs(
self.raw_ptr(),
simulation_flags.into(),
&mut audionimbus_sys::IPLSimulationSharedInputs::from(shared_inputs),
);
}
Ok(())
}
fn acquire_locks_for_flags(&self, flags: SimulationFlags) -> Vec<MutexGuard<'_, ()>> {
let mut locks = Vec::new();
if flags.contains(SimulationFlags::DIRECT)
&& let Some(lock) = &self.direct_lock
{
locks.push(lock);
}
if flags.contains(SimulationFlags::REFLECTIONS)
&& let Some(lock) = &self.reflections_lock
{
locks.push(lock);
}
if flags.contains(SimulationFlags::PATHING)
&& let Some(lock) = &self.pathing_lock
{
locks.push(lock);
}
locks.sort_unstable_by_key(|lock| Arc::as_ptr(lock) as usize);
locks.into_iter().map(|lock| lock.lock().unwrap()).collect()
}
fn acquire_all_locks(&self) -> Vec<MutexGuard<'_, ()>> {
let mut locks: Vec<_> = [
self.direct_lock.as_ref(),
self.reflections_lock.as_ref(),
self.pathing_lock.as_ref(),
]
.into_iter()
.flatten()
.collect();
locks.sort_unstable_by_key(|lock| Arc::as_ptr(lock) as usize);
locks.into_iter().map(|lock| lock.lock().unwrap()).collect()
}
fn lock_handles(&self) -> Vec<Arc<Mutex<()>>> {
let mut locks: Vec<_> = [
self.direct_lock.as_ref(),
self.reflections_lock.as_ref(),
self.pathing_lock.as_ref(),
]
.into_iter()
.flatten()
.cloned()
.collect();
locks.sort_unstable_by_key(|lock| Arc::as_ptr(lock) as usize);
locks
}
fn validate_shared_inputs<InD, InR, InP>(
&self,
shared_inputs: &SimulationSharedInputs<InD, InR, InP>,
) -> Result<(), ParameterValidationError>
where
InD: DirectCompatible<D>,
InR: ReflectionsCompatible<R>,
InP: PathingCompatible<P>,
{
let Some(reflections_inputs) = &shared_inputs.reflections_shared_inputs else {
return Ok(());
};
if let Some(max) = self.max_num_rays
&& reflections_inputs.num_rays > max
{
return Err(ParameterValidationError::NumRaysExceedsMax {
requested: reflections_inputs.num_rays,
max,
});
}
if let Some(max) = self.max_duration
&& reflections_inputs.duration > max
{
return Err(ParameterValidationError::DurationExceedsMax {
requested: reflections_inputs.duration,
max,
});
}
Ok(())
}
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLSimulator {
self.inner
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLSimulator {
&mut self.inner
}
}
impl<T: RayTracer, D, R, P, RE> Simulator<T, D, R, P, RE> {
pub const fn audio_settings(&self) -> AudioSettings {
self.audio_settings
}
}
impl<T, R, P, RE> Simulator<T, Direct, R, P, RE>
where
T: RayTracer,
R: 'static,
P: 'static,
RE: 'static,
{
pub fn set_shared_direct_inputs<InD, InR, InP>(
&self,
shared_inputs: &SimulationSharedInputs<InD, InR, InP>,
) -> Result<(), ParameterValidationError>
where
InD: DirectCompatible<Direct> + SimulationFlagsProvider,
InR: ReflectionsCompatible<R> + SimulationFlagsProvider,
InP: PathingCompatible<P> + SimulationFlagsProvider,
(): ReflectionsCompatible<R> + ReflectionsCompatible<InR>,
(): PathingCompatible<P> + PathingCompatible<InP>,
{
self.set_shared_inputs_subset::<Direct, (), (), InD, InR, InP>(shared_inputs)
}
pub fn run_direct(&self) {
let _guard = self
.direct_lock
.as_ref()
.expect("direct_lock must exist when direct simulation is enabled")
.lock()
.unwrap();
unsafe {
audionimbus_sys::iplSimulatorRunDirect(self.raw_ptr());
}
}
}
impl<T, D, P, RE> Simulator<T, D, Reflections, P, RE>
where
T: RayTracer,
D: 'static,
P: 'static,
RE: 'static,
{
pub fn set_shared_reflections_inputs<InD, InP>(
&self,
shared_inputs: &SimulationSharedInputs<InD, Reflections, InP>,
) -> Result<(), ParameterValidationError>
where
InD: DirectCompatible<D> + SimulationFlagsProvider,
InP: PathingCompatible<P> + SimulationFlagsProvider,
(): DirectCompatible<D> + DirectCompatible<InD>,
(): PathingCompatible<P> + PathingCompatible<InP>,
{
self.set_shared_inputs_subset::<(), Reflections, (), InD, Reflections, InP>(shared_inputs)
}
pub fn run_reflections(&self) -> Result<(), SimulationError> {
let _guard = self
.reflections_lock
.as_ref()
.expect("reflections_lock must exist when reflections simulation is enabled")
.lock()
.unwrap();
let shared = self.shared.lock().unwrap();
if shared.committed_scene.is_none() {
return Err(SimulationError::ReflectionsWithoutScene);
}
unsafe {
audionimbus_sys::iplSimulatorRunReflections(self.raw_ptr());
}
Ok(())
}
}
impl<T, D, R, RE> Simulator<T, D, R, Pathing, RE>
where
T: RayTracer,
D: 'static,
R: 'static,
RE: 'static,
{
pub fn set_shared_pathing_inputs<InD, InR, InP>(
&self,
shared_inputs: &SimulationSharedInputs<InD, InR, InP>,
) -> Result<(), ParameterValidationError>
where
InD: DirectCompatible<D> + SimulationFlagsProvider,
InR: ReflectionsCompatible<R> + SimulationFlagsProvider,
InP: PathingCompatible<Pathing> + SimulationFlagsProvider,
(): DirectCompatible<D> + DirectCompatible<InD>,
(): ReflectionsCompatible<R> + ReflectionsCompatible<InR>,
{
self.set_shared_inputs_subset::<(), (), Pathing, InD, InR, InP>(shared_inputs)
}
pub fn run_pathing(&self) -> Result<(), SimulationError> {
let _guard = self
.pathing_lock
.as_ref()
.expect("pathing_lock must exist when pathing simulation is enabled")
.lock()
.unwrap();
let shared = self.shared.lock().unwrap();
if shared.committed_num_probes == 0 {
return Err(SimulationError::PathingWithoutProbes);
}
unsafe {
audionimbus_sys::iplSimulatorRunPathing(self.raw_ptr());
}
Ok(())
}
}
impl<T: RayTracer, D, R, P, RE> Drop for Simulator<T, D, R, P, RE> {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplSimulatorRelease(&raw mut self.inner) }
}
}
unsafe impl<T: RayTracer, D, R, P, RE> Send for Simulator<T, D, R, P, RE> {}
unsafe impl<T: RayTracer, D, R, P, RE> Sync for Simulator<T, D, R, P, RE> {}
impl<T, D, R, P, RE> Clone for Simulator<T, D, R, P, RE>
where
T: RayTracer,
D: 'static,
R: 'static,
P: 'static,
RE: 'static,
{
fn clone(&self) -> Self {
Self {
inner: unsafe { audionimbus_sys::iplSimulatorRetain(self.inner) },
shared: Arc::clone(&self.shared),
audio_settings: self.audio_settings,
max_num_occlusion_samples: self.max_num_occlusion_samples,
max_num_rays: self.max_num_rays,
max_duration: self.max_duration,
direct_lock: self.direct_lock.clone(),
reflections_lock: self.reflections_lock.clone(),
pathing_lock: self.pathing_lock.clone(),
_open_cl_device: self._open_cl_device.clone(),
_radeon_rays_device: self._radeon_rays_device.clone(),
_true_audio_next_device: self._true_audio_next_device.clone(),
_ray_tracer: PhantomData,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
_reflection_effect: PhantomData,
}
}
}
impl<T, D, R, P, RE> PartialEq for Simulator<T, D, R, P, RE>
where
T: RayTracer,
D: 'static,
R: 'static,
P: 'static,
RE: 'static,
{
fn eq(&self, other: &Self) -> bool {
self.raw_ptr() == other.raw_ptr()
}
}
impl<T, D, R, P, RE> Eq for Simulator<T, D, R, P, RE>
where
T: RayTracer,
D: 'static,
R: 'static,
P: 'static,
RE: 'static,
{
}
impl<T, D, R, P, RE> Hash for Simulator<T, D, R, P, RE>
where
T: RayTracer,
D: 'static,
R: 'static,
P: 'static,
RE: 'static,
{
fn hash<H: Hasher>(&self, state: &mut H) {
std::ptr::hash(self.raw_ptr(), state);
}
}
#[derive(Clone, Debug)]
pub struct SimulationSettings<T: RayTracer, D = (), R = (), P = (), RE = ()> {
settings: audionimbus_sys::IPLSimulationSettings,
open_cl_device: Option<OpenClDevice>,
radeon_rays_device: Option<RadeonRaysDevice>,
true_audio_next_device: Option<TrueAudioNextDevice>,
_ray_tracer: PhantomData<T>,
_direct: PhantomData<D>,
_reflections: PhantomData<R>,
_pathing: PhantomData<P>,
_reflection_effect: PhantomData<RE>,
}
impl SimulationSettings<DefaultRayTracer, (), (), (), ()> {
pub fn new(audio_settings: &AudioSettings) -> Self {
let settings = audionimbus_sys::IPLSimulationSettings {
flags: audionimbus_sys::IPLSimulationFlags(0),
sceneType: audionimbus_sys::IPLSceneType::IPL_SCENETYPE_DEFAULT,
reflectionType:
audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_CONVOLUTION,
maxNumOcclusionSamples: 0,
maxNumRays: 0,
numDiffuseSamples: 0,
maxDuration: 0.0,
maxOrder: 0,
maxNumSources: 0,
numThreads: 0,
rayBatchSize: 0,
numVisSamples: 0,
samplingRate: audio_settings.sampling_rate as i32,
frameSize: audio_settings.frame_size as i32,
openCLDevice: std::ptr::null_mut(),
radeonRaysDevice: std::ptr::null_mut(),
tanDevice: std::ptr::null_mut(),
};
Self {
settings,
open_cl_device: None,
radeon_rays_device: None,
true_audio_next_device: None,
_ray_tracer: PhantomData,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
_reflection_effect: PhantomData,
}
}
}
impl<D, R, P, RE> SimulationSettings<DefaultRayTracer, D, R, P, RE> {
pub fn with_embree(self) -> SimulationSettings<Embree, D, R, P, RE> {
let Self {
mut settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
..
} = self;
settings.sceneType = Embree::scene_type();
SimulationSettings {
settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
_ray_tracer: PhantomData,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
_reflection_effect: PhantomData,
}
}
pub fn with_radeon_rays(
self,
open_cl_device: OpenClDevice,
radeon_rays_device: RadeonRaysDevice,
) -> SimulationSettings<RadeonRays, D, R, P, RE> {
let Self {
mut settings,
true_audio_next_device,
..
} = self;
settings.sceneType = RadeonRays::scene_type();
settings.openCLDevice = open_cl_device.raw_ptr();
settings.radeonRaysDevice = radeon_rays_device.raw_ptr();
SimulationSettings {
settings,
open_cl_device: Some(open_cl_device),
radeon_rays_device: Some(radeon_rays_device),
true_audio_next_device,
_ray_tracer: PhantomData,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
_reflection_effect: PhantomData,
}
}
pub fn with_custom_ray_tracer(
self,
ray_batch_size: u32,
) -> SimulationSettings<CustomRayTracer, D, R, P, RE> {
let Self {
mut settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
..
} = self;
settings.sceneType = CustomRayTracer::scene_type();
settings.rayBatchSize = ray_batch_size as i32;
SimulationSettings {
settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
_ray_tracer: PhantomData,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
_reflection_effect: PhantomData,
}
}
}
impl<T: RayTracer, D, R, P, RE> SimulationSettings<T, D, R, P, RE> {
pub fn with_direct(
self,
direct_settings: DirectSimulationSettings,
) -> SimulationSettings<T, Direct, R, P, RE> {
let Self {
mut settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
_ray_tracer,
_reflections,
_pathing,
_reflection_effect,
..
} = self;
settings.flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_DIRECT;
settings.maxNumOcclusionSamples = direct_settings.max_num_occlusion_samples as i32;
SimulationSettings {
settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
_ray_tracer,
_direct: PhantomData,
_reflections,
_pathing,
_reflection_effect,
}
}
pub fn with_reflections<A: ReflectionsAlgorithm>(
self,
algorithm_settings: A,
) -> SimulationSettings<T, D, Reflections, P, A::EffectType> {
algorithm_settings.apply(self)
}
pub fn with_pathing(
self,
pathing_settings: PathingSimulationSettings,
) -> SimulationSettings<T, D, R, Pathing, RE> {
let Self {
mut settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
_ray_tracer,
_direct,
_reflections,
_reflection_effect,
..
} = self;
settings.flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_PATHING;
settings.numVisSamples = pathing_settings.num_visibility_samples as i32;
SimulationSettings {
settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
_ray_tracer,
_direct,
_reflections,
_pathing: PhantomData,
_reflection_effect,
}
}
pub const fn audio_settings(&self) -> AudioSettings {
AudioSettings {
sampling_rate: self.settings.samplingRate as u32,
frame_size: self.settings.frameSize as u32,
}
}
pub const fn to_ffi(&self) -> audionimbus_sys::IPLSimulationSettings {
self.settings
}
const fn max_num_occlusion_samples(&self) -> Option<u32> {
if self.settings.flags.0 & audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_DIRECT.0
!= 0
{
Some(self.settings.maxNumOcclusionSamples as u32)
} else {
None
}
}
const fn max_num_rays(&self) -> Option<u32> {
if self.settings.flags.0
& audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_REFLECTIONS.0
!= 0
{
Some(self.settings.maxNumRays as u32)
} else {
None
}
}
const fn max_duration(&self) -> Option<f32> {
if self.settings.flags.0
& audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_REFLECTIONS.0
!= 0
{
Some(self.settings.maxDuration)
} else {
None
}
}
}
unsafe impl<T: RayTracer, D, R, P, RE> Send for SimulationSettings<T, D, R, P, RE> {}
unsafe impl<T: RayTracer, D, R, P, RE> Sync for SimulationSettings<T, D, R, P, RE> {}
#[derive(Debug, Copy, Clone)]
pub struct DirectSimulationSettings {
pub max_num_occlusion_samples: u32,
}
impl Default for DirectSimulationSettings {
fn default() -> Self {
Self {
max_num_occlusion_samples: 32,
}
}
}
pub trait ReflectionsAlgorithm: Sealed {
type EffectType: ReflectionEffectType;
fn apply<T: RayTracer, D, R, P, RE>(
self,
settings: SimulationSettings<T, D, R, P, RE>,
) -> SimulationSettings<T, D, Reflections, P, Self::EffectType>;
}
#[derive(Debug, Copy, Clone)]
pub struct ConvolutionSettings {
pub max_num_rays: u32,
pub num_diffuse_samples: u32,
pub max_duration: f32,
pub max_num_sources: u32,
pub num_threads: u32,
pub max_order: u32,
}
impl ReflectionsAlgorithm for ConvolutionSettings {
type EffectType = Convolution;
fn apply<T: RayTracer, D, R, P, RE>(
self,
simulation_settings: SimulationSettings<T, D, R, P, RE>,
) -> SimulationSettings<T, D, Reflections, P, Self::EffectType> {
let SimulationSettings {
mut settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
_ray_tracer,
_direct,
_pathing,
..
} = simulation_settings;
settings.flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_REFLECTIONS;
settings.reflectionType =
audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_CONVOLUTION;
settings.maxNumRays = self.max_num_rays as i32;
settings.numDiffuseSamples = self.num_diffuse_samples as i32;
settings.maxDuration = self.max_duration;
settings.maxNumSources = self.max_num_sources as i32;
settings.numThreads = self.num_threads as i32;
settings.maxOrder = self.max_order as i32;
SimulationSettings {
settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
_ray_tracer,
_direct,
_reflections: PhantomData,
_pathing,
_reflection_effect: PhantomData,
}
}
}
impl Default for ConvolutionSettings {
fn default() -> Self {
Self {
max_num_rays: 256,
num_diffuse_samples: 256,
max_duration: 2.0,
max_num_sources: 64,
num_threads: 4,
max_order: 2,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct ParametricSettings {
pub max_num_rays: u32,
pub num_diffuse_samples: u32,
pub max_duration: f32,
pub max_num_sources: u32,
pub num_threads: u32,
pub max_order: u32,
}
impl ReflectionsAlgorithm for ParametricSettings {
type EffectType = Parametric;
fn apply<T: RayTracer, D, R, P, RE>(
self,
simulation_settings: SimulationSettings<T, D, R, P, RE>,
) -> SimulationSettings<T, D, Reflections, P, Self::EffectType> {
let SimulationSettings {
mut settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
_ray_tracer,
_direct,
_pathing,
..
} = simulation_settings;
settings.flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_REFLECTIONS;
settings.reflectionType =
audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_PARAMETRIC;
settings.maxNumRays = self.max_num_rays as i32;
settings.numDiffuseSamples = self.num_diffuse_samples as i32;
settings.maxDuration = self.max_duration;
settings.maxNumSources = self.max_num_sources as i32;
settings.numThreads = self.num_threads as i32;
settings.maxOrder = self.max_order as i32;
SimulationSettings {
settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
_ray_tracer,
_direct,
_reflections: PhantomData,
_pathing,
_reflection_effect: PhantomData,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct HybridSettings {
pub max_num_rays: u32,
pub num_diffuse_samples: u32,
pub max_duration: f32,
pub max_num_sources: u32,
pub num_threads: u32,
pub max_order: u32,
}
impl ReflectionsAlgorithm for HybridSettings {
type EffectType = Hybrid;
fn apply<T: RayTracer, D, R, P, RE>(
self,
simulation_settings: SimulationSettings<T, D, R, P, RE>,
) -> SimulationSettings<T, D, Reflections, P, Self::EffectType> {
let SimulationSettings {
mut settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
_ray_tracer,
_direct,
_pathing,
..
} = simulation_settings;
settings.flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_REFLECTIONS;
settings.reflectionType =
audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_HYBRID;
settings.maxNumRays = self.max_num_rays as i32;
settings.numDiffuseSamples = self.num_diffuse_samples as i32;
settings.maxDuration = self.max_duration;
settings.maxNumSources = self.max_num_sources as i32;
settings.numThreads = self.num_threads as i32;
settings.maxOrder = self.max_order as i32;
SimulationSettings {
settings,
open_cl_device,
radeon_rays_device,
true_audio_next_device,
_ray_tracer,
_direct,
_reflections: PhantomData,
_pathing,
_reflection_effect: PhantomData,
}
}
}
#[derive(Debug, Clone)]
pub struct TrueAudioNextSettings {
pub max_num_rays: u32,
pub num_diffuse_samples: u32,
pub max_duration: f32,
pub max_num_sources: u32,
pub num_threads: u32,
pub open_cl_device: OpenClDevice,
pub true_audio_next_device: TrueAudioNextDevice,
pub max_order: u32,
}
impl ReflectionsAlgorithm for TrueAudioNextSettings {
type EffectType = TrueAudioNext;
fn apply<T: RayTracer, D, R, P, RE>(
self,
simulation_settings: SimulationSettings<T, D, R, P, RE>,
) -> SimulationSettings<T, D, Reflections, P, Self::EffectType> {
let SimulationSettings {
mut settings,
radeon_rays_device,
_ray_tracer,
_direct,
_pathing,
..
} = simulation_settings;
settings.flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_REFLECTIONS;
settings.reflectionType =
audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_TAN;
settings.openCLDevice = self.open_cl_device.raw_ptr();
settings.tanDevice = self.true_audio_next_device.raw_ptr();
settings.maxNumRays = self.max_num_rays as i32;
settings.numDiffuseSamples = self.num_diffuse_samples as i32;
settings.maxDuration = self.max_duration;
settings.maxNumSources = self.max_num_sources as i32;
settings.numThreads = self.num_threads as i32;
settings.maxOrder = self.max_order as i32;
SimulationSettings {
settings,
open_cl_device: Some(self.open_cl_device),
radeon_rays_device,
true_audio_next_device: Some(self.true_audio_next_device),
_ray_tracer,
_direct,
_reflections: PhantomData,
_pathing,
_reflection_effect: PhantomData,
}
}
}
impl Sealed for ConvolutionSettings {}
impl Sealed for ParametricSettings {}
impl Sealed for HybridSettings {}
impl Sealed for TrueAudioNextSettings {}
#[derive(Debug, Copy, Clone)]
pub struct PathingSimulationSettings {
pub num_visibility_samples: u32,
}
impl Default for PathingSimulationSettings {
fn default() -> Self {
Self {
num_visibility_samples: 16,
}
}
}
bitflags::bitflags! {
#[derive(Copy, Clone, Debug)]
pub struct SimulationFlags: u32 {
const DIRECT = 1 << 0;
const REFLECTIONS = 1 << 1;
const PATHING = 1 << 2;
}
}
impl From<SimulationFlags> for audionimbus_sys::IPLSimulationFlags {
fn from(simulation_flags: SimulationFlags) -> Self {
Self(simulation_flags.bits() as _)
}
}
pub trait SimulationType: Sealed {
const FLAG: SimulationFlags;
}
impl Sealed for Direct {}
impl SimulationType for Direct {
const FLAG: SimulationFlags = SimulationFlags::DIRECT;
}
impl Sealed for Reflections {}
impl SimulationType for Reflections {
const FLAG: SimulationFlags = SimulationFlags::REFLECTIONS;
}
impl Sealed for Pathing {}
impl SimulationType for Pathing {
const FLAG: SimulationFlags = SimulationFlags::PATHING;
}
impl Sealed for () {}
impl SimulationType for () {
const FLAG: SimulationFlags = SimulationFlags::empty();
}
pub trait DirectCompatible<SimD> {}
impl DirectCompatible<Direct> for Direct {}
impl DirectCompatible<Direct> for () {}
impl DirectCompatible<()> for () {}
pub trait ReflectionsCompatible<SimR> {}
impl ReflectionsCompatible<Reflections> for Reflections {}
impl ReflectionsCompatible<Reflections> for () {}
impl ReflectionsCompatible<()> for () {}
pub trait PathingCompatible<SimP> {}
impl PathingCompatible<Pathing> for Pathing {}
impl PathingCompatible<Pathing> for () {}
impl PathingCompatible<()> for () {}
pub trait ReflectionEffectCompatible<SrcR, SimRE> {}
impl<SrcRE, SimRE> ReflectionEffectCompatible<(), SimRE> for SrcRE {}
impl<SimRE> ReflectionEffectCompatible<Reflections, SimRE> for SimRE {}
#[derive(Debug)]
pub struct Source<D = (), R = (), P = (), RE = ()> {
inner: audionimbus_sys::IPLSource,
shared: Arc<Mutex<SourceShared>>,
max_num_occlusion_samples: Option<u32>,
direct_lock: Option<Arc<Mutex<()>>>,
reflections_lock: Option<Arc<Mutex<()>>>,
pathing_lock: Option<Arc<Mutex<()>>>,
_direct: PhantomData<D>,
_reflections: PhantomData<R>,
_pathing: PhantomData<P>,
_reflection_effect: PhantomData<RE>,
}
#[derive(Default, Debug)]
struct SourceShared {
deviation_model: Option<DeviationModel>,
_pathing_probes: Option<ProbeBatch>,
}
impl<D, R, P, RE> Source<D, R, P, RE>
where
D: 'static,
R: 'static,
P: 'static,
RE: 'static,
{
pub fn try_new<T>(simulator: &Simulator<T, D, R, P, RE>) -> Result<Self, SteamAudioError>
where
T: RayTracer,
D: 'static + DirectCompatible<D> + SimulationFlagsProvider,
R: 'static + ReflectionsCompatible<R> + SimulationFlagsProvider,
P: 'static + PathingCompatible<P> + SimulationFlagsProvider,
RE: 'static + ReflectionEffectCompatible<R, RE>,
{
Self::try_new_subset::<T, D, R, P, RE>(simulator)
}
pub fn try_new_subset<T, SimD, SimR, SimP, SimRE>(
simulator: &Simulator<T, SimD, SimR, SimP, SimRE>,
) -> Result<Self, SteamAudioError>
where
T: RayTracer,
SimD: 'static,
SimR: 'static,
SimP: 'static,
SimRE: 'static,
D: 'static + DirectCompatible<SimD> + SimulationFlagsProvider,
R: 'static + ReflectionsCompatible<SimR> + SimulationFlagsProvider,
P: 'static + PathingCompatible<SimP> + SimulationFlagsProvider,
RE: 'static + ReflectionEffectCompatible<R, SimRE>,
{
let mut inner = std::ptr::null_mut();
let simulation_flags = D::flags() | R::flags() | P::flags();
let status = unsafe {
audionimbus_sys::iplSourceCreate(
simulator.raw_ptr(),
&mut audionimbus_sys::IPLSourceSettings {
flags: simulation_flags.into(),
},
&mut inner,
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
let direct_lock = simulator.direct_lock.clone();
let reflections_lock = simulator.reflections_lock.clone();
let pathing_lock = simulator.pathing_lock.clone();
let source = Self {
inner,
shared: Arc::new(Mutex::new(SourceShared::default())),
max_num_occlusion_samples: simulator.max_num_occlusion_samples,
direct_lock,
reflections_lock,
pathing_lock,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
_reflection_effect: PhantomData,
};
Ok(source)
}
pub fn set_inputs<InD, InR, InP>(
&self,
inputs: &SimulationInputs<InD, InR, InP>,
) -> Result<(), ParameterValidationError>
where
D: DirectCompatible<D> + DirectCompatible<InD> + SimulationFlagsProvider,
R: ReflectionsCompatible<R> + ReflectionsCompatible<InR> + SimulationFlagsProvider,
P: PathingCompatible<P> + PathingCompatible<InP> + SimulationFlagsProvider,
InD: DirectCompatible<D> + SimulationFlagsProvider,
InR: ReflectionsCompatible<R> + SimulationFlagsProvider,
InP: PathingCompatible<P> + SimulationFlagsProvider,
{
self.set_inputs_subset::<D, R, P, InD, InR, InP>(inputs)
}
pub fn set_inputs_subset<SubD, SubR, SubP, InD, InR, InP>(
&self,
inputs: &SimulationInputs<InD, InR, InP>,
) -> Result<(), ParameterValidationError>
where
SubD: DirectCompatible<D> + DirectCompatible<InD> + SimulationFlagsProvider,
SubR: ReflectionsCompatible<R> + ReflectionsCompatible<InR> + SimulationFlagsProvider,
SubP: PathingCompatible<P> + PathingCompatible<InP> + SimulationFlagsProvider,
InD: DirectCompatible<D> + SimulationFlagsProvider,
InR: ReflectionsCompatible<R> + SimulationFlagsProvider,
InP: PathingCompatible<P> + SimulationFlagsProvider,
{
self.validate_inputs(inputs)?;
let simulation_flags = SubD::flags() | SubR::flags() | SubP::flags();
let mut ffi_inputs = inputs.to_ffi();
let mut shared = self.shared.lock().unwrap();
(shared.deviation_model, shared._pathing_probes) = inputs
.parameters
.pathing_simulation
.as_ref()
.map(|p| (Some(p.deviation.clone()), Some(p.pathing_probes.clone())))
.unwrap_or_default();
let _guards = self.acquire_locks_for_flags(simulation_flags);
unsafe {
audionimbus_sys::iplSourceSetInputs(
self.raw_ptr(),
simulation_flags.into(),
&mut ffi_inputs,
);
}
Ok(())
}
pub fn get_outputs(&self) -> Result<SimulationOutputs<D, R, P, RE>, SteamAudioError>
where
D: DirectCompatible<D> + SimulationFlagsProvider,
R: ReflectionsCompatible<R> + SimulationFlagsProvider,
P: PathingCompatible<P> + SimulationFlagsProvider,
RE: ReflectionEffectCompatible<R, RE>,
{
self.get_outputs_subset::<D, R, P>()
}
pub fn get_outputs_subset<OutD, OutR, OutP>(
&self,
) -> Result<SimulationOutputs<OutD, OutR, OutP, RE>, SteamAudioError>
where
OutD: DirectCompatible<D> + SimulationFlagsProvider,
OutR: ReflectionsCompatible<R> + SimulationFlagsProvider,
OutP: PathingCompatible<P> + SimulationFlagsProvider,
RE: ReflectionEffectCompatible<OutR, RE>,
{
let simulation_flags = OutD::flags() | OutR::flags() | OutP::flags();
let _guards = self.acquire_locks_for_flags(simulation_flags);
let simulation_outputs = SimulationOutputs::try_allocate(self)?;
unsafe {
audionimbus_sys::iplSourceGetOutputs(
self.raw_ptr(),
simulation_flags.into(),
simulation_outputs.raw_ptr(),
);
}
Ok(simulation_outputs)
}
fn acquire_locks_for_flags(&self, flags: SimulationFlags) -> Vec<MutexGuard<'_, ()>> {
let mut guards = Vec::new();
if flags.contains(SimulationFlags::DIRECT)
&& let Some(lock) = &self.direct_lock
{
guards.push(lock.lock().unwrap());
}
if flags.contains(SimulationFlags::REFLECTIONS)
&& let Some(lock) = &self.reflections_lock
{
guards.push(lock.lock().unwrap());
}
if flags.contains(SimulationFlags::PATHING)
&& let Some(lock) = &self.pathing_lock
{
guards.push(lock.lock().unwrap());
}
guards
}
fn validate_inputs<InD, InR, InP>(
&self,
inputs: &SimulationInputs<InD, InR, InP>,
) -> Result<(), ParameterValidationError>
where
InD: DirectCompatible<D>,
InR: ReflectionsCompatible<R>,
InP: PathingCompatible<P>,
{
let Some(direct_params) = &inputs.parameters.direct_simulation else {
return Ok(());
};
let Some(occlusion) = &direct_params.occlusion else {
return Ok(());
};
let OcclusionAlgorithm::Volumetric {
num_occlusion_samples,
..
} = occlusion.algorithm
else {
return Ok(());
};
let Some(max) = self.max_num_occlusion_samples else {
return Ok(());
};
if num_occlusion_samples > max {
return Err(ParameterValidationError::OcclusionSamplesExceedsMax {
requested: num_occlusion_samples,
max,
});
}
Ok(())
}
}
impl<D, R, P, RE> Source<D, R, P, RE> {
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLSource {
self.inner
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLSource {
&mut self.inner
}
}
impl<R, P, RE> Source<Direct, R, P, RE>
where
R: 'static,
P: 'static,
RE: 'static + ReflectionEffectCompatible<R, RE>,
(): ReflectionsCompatible<R>,
(): PathingCompatible<P>,
{
pub fn set_direct_inputs<InR, InP>(
&self,
inputs: &SimulationInputs<Direct, InR, InP>,
) -> Result<(), ParameterValidationError>
where
InR: ReflectionsCompatible<R> + SimulationFlagsProvider,
InP: PathingCompatible<P> + SimulationFlagsProvider,
(): ReflectionsCompatible<InR>,
(): PathingCompatible<InP>,
{
self.set_inputs_subset::<Direct, (), (), Direct, InR, InP>(inputs)
}
pub fn get_direct_outputs(&self) -> Result<DirectEffectParams, SteamAudioError> {
self.get_outputs_subset::<Direct, (), ()>()
.map(|outputs| outputs.direct())
}
}
impl<D, P, RE> Source<D, Reflections, P, RE>
where
D: 'static,
P: 'static,
RE: 'static + ReflectionEffectType + ReflectionEffectCompatible<Reflections, RE>,
(): DirectCompatible<D>,
(): PathingCompatible<P>,
{
pub fn set_reflections_inputs<InD, InP>(
&self,
inputs: &SimulationInputs<InD, Reflections, InP>,
) -> Result<(), ParameterValidationError>
where
InD: DirectCompatible<D> + SimulationFlagsProvider,
InP: PathingCompatible<P> + SimulationFlagsProvider,
(): DirectCompatible<InD>,
(): PathingCompatible<InP>,
{
self.set_inputs_subset::<(), Reflections, (), InD, Reflections, InP>(inputs)
}
pub fn get_reflections_outputs(&self) -> Result<ReflectionEffectParams<RE>, SteamAudioError> {
self.get_outputs_subset::<(), Reflections, ()>()
.map(|outputs| outputs.reflections())
}
}
impl<D, R, RE> Source<D, R, Pathing, RE>
where
D: 'static,
R: 'static,
RE: 'static + ReflectionEffectCompatible<R, RE>,
(): DirectCompatible<D>,
(): ReflectionsCompatible<R>,
{
pub fn set_pathing_inputs<InD, InR>(
&self,
inputs: &SimulationInputs<InD, InR, Pathing>,
) -> Result<(), ParameterValidationError>
where
InD: DirectCompatible<D> + SimulationFlagsProvider,
InR: ReflectionsCompatible<R> + SimulationFlagsProvider,
(): DirectCompatible<InD>,
(): ReflectionsCompatible<InR>,
{
self.set_inputs_subset::<(), (), Pathing, InD, InR, Pathing>(inputs)
}
pub fn get_pathing_outputs(&self) -> Result<PathEffectParams, SteamAudioError> {
self.get_outputs_subset::<(), (), Pathing>()
.map(|outputs| outputs.pathing())
}
}
impl<D, R, P, RE> Drop for Source<D, R, P, RE> {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplSourceRelease(&raw mut self.inner) }
}
}
unsafe impl<D, R, P, RE> Send for Source<D, R, P, RE> {}
unsafe impl<D, R, P, RE> Sync for Source<D, R, P, RE> {}
impl<D, R, P, RE> Clone for Source<D, R, P, RE> {
fn clone(&self) -> Self {
Self {
inner: unsafe { audionimbus_sys::iplSourceRetain(self.inner) },
max_num_occlusion_samples: self.max_num_occlusion_samples,
shared: Arc::clone(&self.shared),
direct_lock: self.direct_lock.clone(),
reflections_lock: self.reflections_lock.clone(),
pathing_lock: self.pathing_lock.clone(),
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
_reflection_effect: PhantomData,
}
}
}
impl<D, R, P, RE> PartialEq for Source<D, R, P, RE> {
fn eq(&self, other: &Self) -> bool {
self.raw_ptr() == other.raw_ptr()
}
}
impl<D, R, P, RE> Eq for Source<D, R, P, RE> {}
impl<D, R, P, RE> Hash for Source<D, R, P, RE> {
fn hash<H: Hasher>(&self, state: &mut H) {
std::ptr::hash(self.raw_ptr(), state);
}
}
#[derive(Clone, Default, Debug)]
pub struct SimulationInputs<D = (), R = (), P = ()> {
pub source: CoordinateSystem,
pub parameters: SimulationParameters<D, R, P>,
}
impl<D, R, P> SimulationInputs<D, R, P> {
fn to_ffi(&self) -> audionimbus_sys::IPLSimulationInputs {
let mut flags = audionimbus_sys::IPLSimulationFlags(0);
let source = self.source.into();
if self.parameters.direct_simulation.is_some() {
flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_DIRECT;
}
let direct_data = DirectSimulationData::from_params(&self.parameters.direct_simulation);
if self.parameters.reflections_simulation.is_some() {
flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_REFLECTIONS;
}
let reflections_data = self.parameters.reflections_simulation.unwrap_or_default();
if self.parameters.pathing_simulation.is_some() {
flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_PATHING;
}
let pathing_data =
PathingSimulationData::from_params(self.parameters.pathing_simulation.as_ref());
audionimbus_sys::IPLSimulationInputs {
flags,
directFlags: direct_data.flags,
source,
distanceAttenuationModel: direct_data.distance_attenuation_model,
airAbsorptionModel: direct_data.air_absorption_model,
directivity: direct_data.directivity,
occlusionType: direct_data.occlusion_type,
occlusionRadius: direct_data.occlusion_radius,
numOcclusionSamples: direct_data.num_occlusion_samples,
numTransmissionRays: direct_data.num_transmission_rays,
reverbScale: reflections_data.reverb_scale,
hybridReverbTransitionTime: reflections_data.hybrid_reverb_transition_time,
hybridReverbOverlapPercent: reflections_data.hybrid_reverb_overlap_percent,
baked: reflections_data.baked,
bakedDataIdentifier: reflections_data.baked_data_identifier.into(),
pathingProbes: pathing_data.pathing_probes,
visRadius: pathing_data.visibility_radius,
visThreshold: pathing_data.visibility_threshold,
visRange: pathing_data.visibility_range,
pathingOrder: pathing_data.pathing_order,
enableValidation: pathing_data.enable_validation,
findAlternatePaths: pathing_data.find_alternate_paths,
deviationModel: &pathing_data.deviation_model as *const _ as *mut _,
}
}
}
#[derive(Clone, Default, Debug)]
pub struct SimulationParameters<D = (), R = (), P = ()> {
direct_simulation: Option<DirectSimulationParameters>,
reflections_simulation: Option<ReflectionsSimulationData>,
pathing_simulation: Option<PathingSimulationParameters>,
_direct: PhantomData<D>,
_reflections: PhantomData<R>,
_pathing: PhantomData<P>,
}
impl SimulationParameters {
pub fn new() -> Self {
Self {
direct_simulation: None,
reflections_simulation: None,
pathing_simulation: None,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
}
}
}
impl<D, R, P> SimulationParameters<D, R, P> {
pub fn direct_simulation_parameters(&self) -> Option<&DirectSimulationParameters> {
self.direct_simulation.as_ref()
}
pub fn with_direct(
self,
params: DirectSimulationParameters,
) -> SimulationParameters<Direct, R, P> {
let Self {
reflections_simulation,
pathing_simulation,
_reflections,
_pathing,
..
} = self;
SimulationParameters {
direct_simulation: Some(params),
reflections_simulation,
pathing_simulation,
_direct: PhantomData,
_reflections,
_pathing,
}
}
pub fn with_reflections<Params: ReflectionsSimulationParameters>(
self,
params: Params,
) -> SimulationParameters<D, Reflections, P> {
let Self {
direct_simulation,
pathing_simulation,
_direct,
_pathing,
..
} = self;
SimulationParameters {
direct_simulation,
reflections_simulation: Some(params.into_data()),
pathing_simulation,
_direct,
_reflections: PhantomData,
_pathing,
}
}
pub fn with_pathing(
self,
params: PathingSimulationParameters,
) -> SimulationParameters<D, R, Pathing> {
let Self {
direct_simulation,
reflections_simulation,
_direct,
_reflections,
..
} = self;
SimulationParameters {
direct_simulation,
reflections_simulation,
pathing_simulation: Some(params),
_direct,
_reflections,
_pathing: PhantomData,
}
}
}
impl<R, P> SimulationParameters<Direct, R, P> {
pub fn set_direct_simulation_parameters(&mut self, params: DirectSimulationParameters) {
self.direct_simulation.replace(params);
}
}
impl<D, P> SimulationParameters<D, Reflections, P> {
pub fn set_reflections_simulation_parameters<Params: ReflectionsSimulationParameters>(
&mut self,
params: Params,
) {
self.reflections_simulation.replace(params.into_data());
}
}
impl<D, R> SimulationParameters<D, R, Pathing> {
pub fn set_pathing_simulation_parameters(&mut self, params: PathingSimulationParameters) {
self.pathing_simulation.replace(params);
}
}
#[derive(Clone, Default, Debug)]
pub struct DirectSimulationParameters {
pub distance_attenuation: Option<DistanceAttenuationModel>,
pub air_absorption: Option<AirAbsorptionModel>,
pub directivity: Option<Directivity>,
pub occlusion: Option<Occlusion>,
}
impl DirectSimulationParameters {
pub fn new() -> Self {
Self {
distance_attenuation: None,
air_absorption: None,
directivity: None,
occlusion: None,
}
}
pub fn with_distance_attenuation(mut self, model: DistanceAttenuationModel) -> Self {
self.distance_attenuation = Some(model);
self
}
pub fn with_air_absorption(mut self, model: AirAbsorptionModel) -> Self {
self.air_absorption = Some(model);
self
}
pub fn with_directivity(mut self, directivity: Directivity) -> Self {
self.directivity = Some(directivity);
self
}
pub fn with_occlusion(mut self, occlusion: Occlusion) -> Self {
self.occlusion = Some(occlusion);
self
}
}
#[derive(Debug, Copy, Clone)]
pub struct Occlusion {
pub transmission: Option<TransmissionParameters>,
pub algorithm: OcclusionAlgorithm,
}
impl Occlusion {
pub fn new(algorithm: OcclusionAlgorithm) -> Self {
Self {
transmission: None,
algorithm,
}
}
pub fn with_transmission(mut self, params: TransmissionParameters) -> Self {
self.transmission = Some(params);
self
}
}
#[derive(Debug, Copy, Clone)]
pub struct TransmissionParameters {
pub num_transmission_rays: u32,
}
#[allow(private_bounds)]
pub trait ReflectionsSimulationParameters: Sealed + IntoSimulationData {}
trait IntoSimulationData {
fn into_data(self) -> ReflectionsSimulationData;
}
#[derive(Default, Clone, Debug)]
pub struct ConvolutionParameters {
pub baked_data_identifier: Option<BakedDataIdentifier>,
}
impl Sealed for ConvolutionParameters {}
impl ReflectionsSimulationParameters for ConvolutionParameters {}
impl IntoSimulationData for ConvolutionParameters {
fn into_data(self) -> ReflectionsSimulationData {
let (baked, baked_data_identifier) = self.baked_data_identifier.map_or(
(
audionimbus_sys::IPLbool::IPL_FALSE,
BakedDataIdentifier::Reflections {
variation: BakedDataVariation::Reverb,
},
),
|id| (audionimbus_sys::IPLbool::IPL_TRUE, id),
);
ReflectionsSimulationData {
baked,
baked_data_identifier,
reverb_scale: [0.0; 3],
hybrid_reverb_transition_time: 0.0,
hybrid_reverb_overlap_percent: 0.0,
}
}
}
#[derive(Clone, Debug)]
pub struct ParametricParameters {
pub reverb_scale: [f32; 3],
pub baked_data_identifier: Option<BakedDataIdentifier>,
}
impl Sealed for ParametricParameters {}
impl ReflectionsSimulationParameters for ParametricParameters {}
impl IntoSimulationData for ParametricParameters {
fn into_data(self) -> ReflectionsSimulationData {
let (baked, baked_data_identifier) = self.baked_data_identifier.map_or(
(
audionimbus_sys::IPLbool::IPL_FALSE,
BakedDataIdentifier::Reflections {
variation: BakedDataVariation::Reverb,
},
),
|id| (audionimbus_sys::IPLbool::IPL_TRUE, id),
);
ReflectionsSimulationData {
baked,
baked_data_identifier,
reverb_scale: self.reverb_scale,
hybrid_reverb_transition_time: 0.0,
hybrid_reverb_overlap_percent: 0.0,
}
}
}
impl Default for ParametricParameters {
fn default() -> Self {
Self {
reverb_scale: [1.0, 1.0, 1.0],
baked_data_identifier: None,
}
}
}
#[derive(Clone, Debug)]
pub struct HybridParameters {
pub reverb_scale: [f32; 3],
pub hybrid_reverb_transition_time: f32,
pub hybrid_reverb_overlap_percent: f32,
pub baked_data_identifier: Option<BakedDataIdentifier>,
}
impl Sealed for HybridParameters {}
impl ReflectionsSimulationParameters for HybridParameters {}
impl IntoSimulationData for HybridParameters {
fn into_data(self) -> ReflectionsSimulationData {
let (baked, baked_data_identifier) = self.baked_data_identifier.map_or(
(
audionimbus_sys::IPLbool::IPL_FALSE,
BakedDataIdentifier::Reflections {
variation: BakedDataVariation::Reverb,
},
),
|id| (audionimbus_sys::IPLbool::IPL_TRUE, id),
);
ReflectionsSimulationData {
baked,
baked_data_identifier,
reverb_scale: self.reverb_scale,
hybrid_reverb_transition_time: self.hybrid_reverb_transition_time,
hybrid_reverb_overlap_percent: self.hybrid_reverb_overlap_percent,
}
}
}
impl Default for HybridParameters {
fn default() -> Self {
Self {
reverb_scale: [1.0, 1.0, 1.0],
hybrid_reverb_transition_time: 1.0,
hybrid_reverb_overlap_percent: 0.25,
baked_data_identifier: None,
}
}
}
#[derive(Default, Clone, Debug)]
pub struct TrueAudioNextParameters {
pub baked_data_identifier: Option<BakedDataIdentifier>,
}
impl Sealed for TrueAudioNextParameters {}
impl ReflectionsSimulationParameters for TrueAudioNextParameters {}
impl IntoSimulationData for TrueAudioNextParameters {
fn into_data(self) -> ReflectionsSimulationData {
let (baked, baked_data_identifier) = self.baked_data_identifier.map_or(
(
audionimbus_sys::IPLbool::IPL_FALSE,
BakedDataIdentifier::Reflections {
variation: BakedDataVariation::Reverb,
},
),
|id| (audionimbus_sys::IPLbool::IPL_TRUE, id),
);
ReflectionsSimulationData {
baked,
baked_data_identifier,
reverb_scale: [0.0; 3],
hybrid_reverb_transition_time: 0.0,
hybrid_reverb_overlap_percent: 0.0,
}
}
}
#[derive(Clone, Debug)]
pub struct PathingSimulationParameters {
pub pathing_probes: ProbeBatch,
pub visibility_radius: f32,
pub visibility_threshold: f32,
pub visibility_range: f32,
pub pathing_order: u32,
pub enable_validation: bool,
pub find_alternate_paths: bool,
pub deviation: DeviationModel,
}
struct DirectSimulationData {
flags: audionimbus_sys::IPLDirectSimulationFlags,
distance_attenuation_model: audionimbus_sys::IPLDistanceAttenuationModel,
air_absorption_model: audionimbus_sys::IPLAirAbsorptionModel,
directivity: audionimbus_sys::IPLDirectivity,
occlusion_type: audionimbus_sys::IPLOcclusionType,
occlusion_radius: f32,
num_occlusion_samples: i32,
num_transmission_rays: i32,
}
impl DirectSimulationData {
fn from_params(params: &Option<DirectSimulationParameters>) -> Self {
let Some(params) = params else {
return Self::default();
};
let mut flags = audionimbus_sys::IPLDirectSimulationFlags(0);
let distance_attenuation_model =
Self::process_distance_attenuation(¶ms.distance_attenuation, &mut flags);
let air_absorption_model = Self::process_air_absorption(¶ms.air_absorption, &mut flags);
let directivity = Self::process_directivity(¶ms.directivity, &mut flags);
let (occlusion_type, occlusion_radius, num_occlusion_samples, num_transmission_rays) =
Self::process_occlusion(params.occlusion, &mut flags);
Self {
flags,
distance_attenuation_model,
air_absorption_model,
directivity,
occlusion_type,
occlusion_radius,
num_occlusion_samples,
num_transmission_rays,
}
}
fn process_distance_attenuation(
distance_attenuation: &Option<DistanceAttenuationModel>,
flags: &mut audionimbus_sys::IPLDirectSimulationFlags,
) -> audionimbus_sys::IPLDistanceAttenuationModel {
if let Some(model) = distance_attenuation {
*flags |= audionimbus_sys::IPLDirectSimulationFlags::IPL_DIRECTSIMULATIONFLAGS_DISTANCEATTENUATION;
model.into()
} else {
(&DistanceAttenuationModel::default()).into()
}
}
fn process_air_absorption(
air_absorption: &Option<AirAbsorptionModel>,
flags: &mut audionimbus_sys::IPLDirectSimulationFlags,
) -> audionimbus_sys::IPLAirAbsorptionModel {
if let Some(model) = air_absorption {
*flags |=
audionimbus_sys::IPLDirectSimulationFlags::IPL_DIRECTSIMULATIONFLAGS_AIRABSORPTION;
model.into()
} else {
(&AirAbsorptionModel::default()).into()
}
}
fn process_directivity(
directivity: &Option<Directivity>,
flags: &mut audionimbus_sys::IPLDirectSimulationFlags,
) -> audionimbus_sys::IPLDirectivity {
if let Some(directivity) = directivity {
*flags |=
audionimbus_sys::IPLDirectSimulationFlags::IPL_DIRECTSIMULATIONFLAGS_DIRECTIVITY;
directivity.into()
} else {
(&Directivity::default()).into()
}
}
fn process_occlusion(
occlusion: Option<Occlusion>,
flags: &mut audionimbus_sys::IPLDirectSimulationFlags,
) -> (audionimbus_sys::IPLOcclusionType, f32, i32, i32) {
let Some(occlusion) = occlusion else {
return (
audionimbus_sys::IPLOcclusionType::IPL_OCCLUSIONTYPE_RAYCAST,
0.0,
0,
0,
);
};
*flags |= audionimbus_sys::IPLDirectSimulationFlags::IPL_DIRECTSIMULATIONFLAGS_OCCLUSION;
let (occlusion_type, occlusion_radius, num_occlusion_samples) = match occlusion.algorithm {
OcclusionAlgorithm::Raycast => (
audionimbus_sys::IPLOcclusionType::IPL_OCCLUSIONTYPE_RAYCAST,
0.0,
0,
),
OcclusionAlgorithm::Volumetric {
radius,
num_occlusion_samples,
} => (
audionimbus_sys::IPLOcclusionType::IPL_OCCLUSIONTYPE_VOLUMETRIC,
radius,
num_occlusion_samples,
),
};
let num_transmission_rays = if let Some(transmission) = occlusion.transmission {
*flags |=
audionimbus_sys::IPLDirectSimulationFlags::IPL_DIRECTSIMULATIONFLAGS_TRANSMISSION;
transmission.num_transmission_rays
} else {
0
};
(
occlusion_type,
occlusion_radius,
num_occlusion_samples as i32,
num_transmission_rays as i32,
)
}
}
impl Default for DirectSimulationData {
fn default() -> Self {
Self {
flags: audionimbus_sys::IPLDirectSimulationFlags(0),
distance_attenuation_model: (&DistanceAttenuationModel::default()).into(),
air_absorption_model: (&AirAbsorptionModel::default()).into(),
directivity: (&Directivity::default()).into(),
occlusion_type: audionimbus_sys::IPLOcclusionType::IPL_OCCLUSIONTYPE_RAYCAST,
occlusion_radius: 0.0,
num_occlusion_samples: 0,
num_transmission_rays: 0,
}
}
}
#[derive(Copy, Clone, Debug)]
struct ReflectionsSimulationData {
baked: audionimbus_sys::IPLbool,
baked_data_identifier: BakedDataIdentifier,
reverb_scale: [f32; 3],
hybrid_reverb_transition_time: f32,
hybrid_reverb_overlap_percent: f32,
}
impl Default for ReflectionsSimulationData {
fn default() -> Self {
Self {
baked: audionimbus_sys::IPLbool::IPL_FALSE,
baked_data_identifier: BakedDataIdentifier::Reflections {
variation: BakedDataVariation::Reverb,
},
reverb_scale: [0.0; 3],
hybrid_reverb_transition_time: 0.0,
hybrid_reverb_overlap_percent: 0.0,
}
}
}
struct PathingSimulationData {
pathing_probes: audionimbus_sys::IPLProbeBatch,
visibility_radius: f32,
visibility_threshold: f32,
visibility_range: f32,
pathing_order: i32,
enable_validation: audionimbus_sys::IPLbool,
find_alternate_paths: audionimbus_sys::IPLbool,
deviation_model: audionimbus_sys::IPLDeviationModel,
}
impl PathingSimulationData {
fn from_params(params: Option<&PathingSimulationParameters>) -> Self {
let Some(params) = params else {
return Self::default();
};
Self {
pathing_probes: params.pathing_probes.raw_ptr(),
visibility_radius: params.visibility_radius,
visibility_threshold: params.visibility_threshold,
visibility_range: params.visibility_range,
pathing_order: params.pathing_order as i32,
enable_validation: if params.enable_validation {
audionimbus_sys::IPLbool::IPL_TRUE
} else {
audionimbus_sys::IPLbool::IPL_FALSE
},
find_alternate_paths: if params.find_alternate_paths {
audionimbus_sys::IPLbool::IPL_TRUE
} else {
audionimbus_sys::IPLbool::IPL_FALSE
},
deviation_model: (¶ms.deviation).into(),
}
}
}
impl Default for PathingSimulationData {
fn default() -> Self {
Self {
pathing_probes: std::ptr::null_mut(),
visibility_radius: 0.0,
visibility_threshold: 0.0,
visibility_range: 0.0,
pathing_order: 0,
enable_validation: audionimbus_sys::IPLbool::IPL_FALSE,
find_alternate_paths: audionimbus_sys::IPLbool::IPL_FALSE,
deviation_model: (&DeviationModel::Default).into(),
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum OcclusionAlgorithm {
Raycast,
Volumetric {
radius: f32,
num_occlusion_samples: u32,
},
}
#[derive(Default, Clone, Debug)]
pub struct SimulationSharedInputs<D = (), R = (), P = ()> {
listener: CoordinateSystem,
reflections_shared_inputs: Option<ReflectionsSharedInputs>,
pathing_visualization_callback: Option<PathingVisualizationCallback>,
_direct: PhantomData<D>,
_reflections: PhantomData<R>,
_pathing: PhantomData<P>,
}
impl SimulationSharedInputs {
pub fn new(listener: CoordinateSystem) -> Self {
Self {
listener,
reflections_shared_inputs: None,
pathing_visualization_callback: None,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
}
}
}
impl<D, R, P> SimulationSharedInputs<D, R, P> {
pub fn with_direct(self) -> SimulationSharedInputs<Direct, R, P> {
let Self {
listener,
reflections_shared_inputs,
pathing_visualization_callback,
_reflections,
_pathing,
..
} = self;
SimulationSharedInputs {
listener,
reflections_shared_inputs,
pathing_visualization_callback,
_direct: PhantomData,
_reflections,
_pathing,
}
}
pub fn with_reflections(
self,
inputs: ReflectionsSharedInputs,
) -> SimulationSharedInputs<D, Reflections, P> {
let Self {
listener,
pathing_visualization_callback,
_direct,
_pathing,
..
} = self;
SimulationSharedInputs {
listener,
reflections_shared_inputs: Some(inputs),
pathing_visualization_callback,
_direct,
_reflections: PhantomData,
_pathing,
}
}
pub fn with_pathing(self) -> SimulationSharedInputs<D, R, Pathing> {
let Self {
listener,
reflections_shared_inputs,
pathing_visualization_callback,
_direct,
_reflections,
..
} = self;
SimulationSharedInputs {
listener,
reflections_shared_inputs,
pathing_visualization_callback,
_direct,
_reflections,
_pathing: PhantomData,
}
}
pub fn with_pathing_visualization_callback(
self,
pathing_visualization_callback: PathingVisualizationCallback,
) -> SimulationSharedInputs<D, R, Pathing> {
let Self {
listener,
reflections_shared_inputs,
_direct,
_reflections,
..
} = self;
SimulationSharedInputs {
listener,
reflections_shared_inputs,
pathing_visualization_callback: Some(pathing_visualization_callback),
_direct,
_reflections,
_pathing: PhantomData,
}
}
pub const fn set_listener(&mut self, listener: CoordinateSystem) {
self.listener = listener;
}
}
impl<D, P> SimulationSharedInputs<D, Reflections, P> {
pub fn set_reflections_shared_inputs(&mut self, shared_inputs: ReflectionsSharedInputs) {
self.reflections_shared_inputs.replace(shared_inputs);
}
}
impl<D, R> SimulationSharedInputs<D, R, Pathing> {
pub fn set_pathing_visualization_callback(
&mut self,
visualization_callback: PathingVisualizationCallback,
) {
self.pathing_visualization_callback
.replace(visualization_callback);
}
}
impl<D, R, P> From<&SimulationSharedInputs<D, R, P>>
for audionimbus_sys::IPLSimulationSharedInputs
{
fn from(simulation_shared_inputs: &SimulationSharedInputs<D, R, P>) -> Self {
let (pathing_visualization_callback, pathing_user_data) = simulation_shared_inputs
.pathing_visualization_callback
.as_ref()
.map_or((None, std::ptr::null_mut()), |callback| {
let (callback_fn, user_data) = callback.as_raw_parts();
(Some(callback_fn), user_data)
});
let (num_rays, num_bounces, duration, order, irradiance_min_distance) =
simulation_shared_inputs.reflections_shared_inputs.map_or(
(0, 0, 0.0, 0, 0.0),
|inputs| {
(
inputs.num_rays as i32,
inputs.num_bounces as i32,
inputs.duration,
inputs.order as i32,
inputs.irradiance_min_distance,
)
},
);
Self {
listener: simulation_shared_inputs.listener.into(),
numRays: num_rays,
numBounces: num_bounces,
duration,
order,
irradianceMinDistance: irradiance_min_distance,
pathingVisCallback: pathing_visualization_callback,
pathingUserData: pathing_user_data,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct ReflectionsSharedInputs {
pub num_rays: u32,
pub num_bounces: u32,
pub duration: f32,
pub order: u32,
pub irradiance_min_distance: f32,
}
#[derive(Debug)]
pub struct SimulationOutputs<D, R, P, RE = ()> {
inner: *mut audionimbus_sys::IPLSimulationOutputs,
_source: audionimbus_sys::IPLSource,
_direct: PhantomData<D>,
_reflections: PhantomData<R>,
_pathing: PhantomData<P>,
_reflection_effect: PhantomData<RE>,
}
impl<D, R, P, RE> SimulationOutputs<D, R, P, RE> {
fn try_allocate<SourceD, SourceR, SourceP, SourceRE>(
source: &Source<SourceD, SourceR, SourceP, SourceRE>,
) -> Result<Self, SteamAudioError>
where
D: DirectCompatible<SourceD>,
R: ReflectionsCompatible<SourceR>,
P: PathingCompatible<SourceP>,
RE: ReflectionEffectCompatible<R, SourceRE>,
SourceD: 'static,
SourceR: 'static,
SourceP: 'static,
SourceRE: 'static,
{
let ptr = unsafe {
let layout = std::alloc::Layout::new::<audionimbus_sys::IPLSimulationOutputs>();
let ptr = std::alloc::alloc(layout).cast::<audionimbus_sys::IPLSimulationOutputs>();
if ptr.is_null() {
return Err(SteamAudioError::OutOfMemory);
}
std::ptr::write(ptr, std::mem::zeroed());
ptr
};
let source = unsafe { audionimbus_sys::iplSourceRetain(source.raw_ptr()) };
Ok(Self {
inner: ptr,
_source: source,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
_reflection_effect: PhantomData,
})
}
pub const fn raw_ptr(&self) -> *mut audionimbus_sys::IPLSimulationOutputs {
self.inner
}
pub const fn raw_ptr_mut(&mut self) -> &mut *mut audionimbus_sys::IPLSimulationOutputs {
&mut self.inner
}
}
impl<R, P, RE> SimulationOutputs<Direct, R, P, RE> {
pub fn direct(&self) -> DirectEffectParams {
unsafe { (*self.inner).direct.into() }
}
}
impl<D, P, RE> SimulationOutputs<D, Reflections, P, RE>
where
RE: ReflectionEffectType,
{
pub fn reflections(&self) -> ReflectionEffectParams<RE> {
unsafe {
ReflectionEffectParams::from_ffi_unchecked((*self.inner).reflections, self._source)
}
}
}
impl<D, R, RE> SimulationOutputs<D, R, Pathing, RE> {
pub fn pathing(&self) -> PathEffectParams {
unsafe { (*self.inner).pathing.into() }
}
}
unsafe impl<D, R, P, RE> Send for SimulationOutputs<D, R, P, RE> {}
impl<D, R, P, RE> Drop for SimulationOutputs<D, R, P, RE> {
fn drop(&mut self) {
unsafe {
let layout = std::alloc::Layout::new::<audionimbus_sys::IPLSimulationOutputs>();
std::alloc::dealloc(self.inner.cast::<u8>(), layout);
audionimbus_sys::iplSourceRelease(&mut self._source);
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum SimulationError {
PathingWithoutProbes,
ReflectionsWithoutScene,
}
impl std::error::Error for SimulationError {}
impl std::fmt::Display for SimulationError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match &self {
Self::PathingWithoutProbes => {
write!(f, "running pathing on a simulator with no probes")
}
Self::ReflectionsWithoutScene => {
write!(f, "running reflections on a simulator with no scene set")
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ParameterValidationError {
OcclusionSamplesExceedsMax {
requested: u32,
max: u32,
},
NumRaysExceedsMax {
requested: u32,
max: u32,
},
DurationExceedsMax {
requested: f32,
max: f32,
},
}
impl std::error::Error for ParameterValidationError {}
impl std::fmt::Display for ParameterValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::OcclusionSamplesExceedsMax { requested, max } => {
write!(
f,
"requested {} occlusion samples, but maximum is {} (set during simulator initialization)",
requested, max
)
}
Self::NumRaysExceedsMax { requested, max } => {
write!(
f,
"requested {} rays, but maximum is {} (set during simulator initialization)",
requested, max
)
}
Self::DurationExceedsMax { requested, max } => {
write!(
f,
"requested duration of {}s, but maximum is {}s (set during simulator initialization)",
requested, max
)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::*;
mod source {
use super::*;
mod clone {
use super::*;
#[test]
fn test_clone() {
let context = Context::default();
let audio_settings = AudioSettings::default();
let simulator_settings = SimulationSettings::new(&audio_settings);
let simulator = Simulator::try_new(&context, &simulator_settings).unwrap();
let source = Source::try_new(&simulator).unwrap();
let clone = source.clone();
assert_eq!(source.raw_ptr(), clone.raw_ptr());
drop(source);
assert!(!clone.raw_ptr().is_null());
}
}
}
mod simulator {
use super::*;
#[test]
fn test_simulator_clone() {
let context = Context::default();
let audio_settings = AudioSettings::default();
let settings = SimulationSettings::new(&audio_settings);
let simulator = Simulator::try_new(&context, &settings).unwrap();
let clone = simulator.clone();
assert_eq!(simulator.raw_ptr(), clone.raw_ptr());
drop(simulator);
assert!(!clone.raw_ptr().is_null());
}
#[test]
fn test_set_scene_is_noop_when_scene_is_already_committed() {
let context = Context::default();
let audio_settings = AudioSettings::default();
let settings = SimulationSettings::new(&audio_settings);
let mut simulator = Simulator::try_new(&context, &settings).unwrap();
let scene = Scene::try_new(&context).unwrap();
simulator.set_scene(&scene);
simulator.commit();
{
let shared = simulator.shared.lock().unwrap();
assert!(shared.pending_scene.is_none());
assert_eq!(shared.committed_scene.as_ref(), Some(&scene));
}
simulator.set_scene(&scene);
let shared = simulator.shared.lock().unwrap();
assert!(shared.pending_scene.is_none());
assert_eq!(shared.committed_scene.as_ref(), Some(&scene));
}
}
}