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::{DirectEffectParams, PathEffectParams, ReflectionEffectParams};
use crate::error::{to_option_error, SteamAudioError};
use crate::ffi_wrapper::FFIWrapper;
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 crate::Sealed;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::{Arc, Mutex, MutexGuard};
#[derive(Debug)]
pub struct Direct;
#[derive(Debug)]
pub struct Reflections;
#[derive(Debug)]
pub struct Pathing;
#[derive(Debug)]
pub struct Simulator<'a, T: RayTracer, D = (), R = (), P = ()> {
inner: audionimbus_sys::IPLSimulator,
shared: Arc<Mutex<SimulatorShared>>,
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<&'a OpenClDevice>,
_radeon_rays_device: Option<&'a RadeonRaysDevice>,
_true_audio_next_device: Option<&'a TrueAudioNextDevice>,
_ray_tracer: PhantomData<T>,
_direct: PhantomData<D>,
_reflections: PhantomData<R>,
_pathing: PhantomData<P>,
_lifetime: PhantomData<&'a ()>,
}
#[derive(Default, Debug)]
struct SimulatorShared {
committed_num_probes: usize,
pending_probe_batches: HashMap<audionimbus_sys::IPLProbeBatch, usize>,
has_committed_scene: bool,
has_pending_scene: bool,
}
impl<'a, T, D, R, P> Simulator<'a, T, D, R, P>
where
T: RayTracer,
D: 'static,
R: 'static,
P: 'static,
{
pub fn try_new(
context: &Context,
settings: &SimulationSettings<'a, T, D, R, P>,
) -> 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 simulator = Self {
inner: std::ptr::null_mut(),
#[allow(clippy::arc_with_non_send_sync)]
shared: Arc::new(Mutex::new(SimulatorShared::default())),
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,
_radeon_rays_device: settings._radeon_rays_device,
_true_audio_next_device: settings._true_audio_next_device,
_ray_tracer: PhantomData,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
_lifetime: PhantomData,
};
let status = unsafe {
audionimbus_sys::iplSimulatorCreate(
context.raw_ptr(),
&mut settings.to_ffi(),
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>) {
unsafe { audionimbus_sys::iplSimulatorSetScene(self.raw_ptr(), scene.raw_ptr()) }
let mut shared = self.shared.lock().unwrap();
shared.has_pending_scene = true;
}
pub fn add_probe_batch(&mut self, probe_batch: &ProbeBatch) {
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 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>(&self, source: &Source<'a, SrcD, SrcR, SrcP>)
where
SrcD: DirectCompatible<D> + 'static,
SrcR: ReflectionsCompatible<R> + 'static,
SrcP: PathingCompatible<P> + 'static,
{
unsafe {
audionimbus_sys::iplSourceAdd(source.raw_ptr(), self.raw_ptr());
}
}
pub fn remove_source<SrcD, SrcR, SrcP>(&self, source: &Source<'a, SrcD, SrcR, SrcP>)
where
SrcD: DirectCompatible<D> + 'static,
SrcR: ReflectionsCompatible<R> + 'static,
SrcP: PathingCompatible<P> + 'static,
{
unsafe {
audionimbus_sys::iplSourceRemove(source.raw_ptr(), self.raw_ptr());
}
}
pub fn commit(&mut self) {
let _guards: Vec<_> = [
self.direct_lock.as_ref(),
self.reflections_lock.as_ref(),
self.pathing_lock.as_ref(),
]
.iter()
.filter_map(|lock| lock.as_ref().map(|l| l.lock().unwrap()))
.collect();
unsafe { audionimbus_sys::iplSimulatorCommit(self.raw_ptr()) }
let mut shared = self.shared.lock().unwrap();
shared.committed_num_probes = shared.pending_probe_batches.values().sum();
if shared.has_pending_scene {
shared.has_committed_scene = true;
shared.has_pending_scene = false;
}
}
pub fn set_shared_inputs<_D, _P>(
&self,
simulation_flags: SimulationFlags,
shared_inputs: &SimulationSharedInputs<_D, R, _P>,
) -> Result<(), ParameterValidationError> {
self.validate_shared_inputs(shared_inputs)?;
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 guards = Vec::new();
if flags.contains(SimulationFlags::DIRECT) {
if let Some(lock) = &self.direct_lock {
guards.push(lock.lock().unwrap());
}
}
if flags.contains(SimulationFlags::REFLECTIONS) {
if let Some(lock) = &self.reflections_lock {
guards.push(lock.lock().unwrap());
}
}
if flags.contains(SimulationFlags::PATHING) {
if let Some(lock) = &self.pathing_lock {
guards.push(lock.lock().unwrap());
}
}
guards
}
fn validate_shared_inputs<_D, _P>(
&self,
shared_inputs: &SimulationSharedInputs<_D, R, _P>,
) -> Result<(), ParameterValidationError> {
let Some(reflections_inputs) = &shared_inputs.reflections_shared_inputs else {
return Ok(());
};
if let Some(max) = self.max_num_rays {
if reflections_inputs.num_rays > max {
return Err(ParameterValidationError::NumRaysExceedsMax {
requested: reflections_inputs.num_rays,
max,
});
}
}
if let Some(max) = self.max_duration {
if 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, R, P> Simulator<'_, T, Direct, R, P>
where
T: RayTracer,
R: 'static,
P: 'static,
{
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> Simulator<'_, T, D, Reflections, P>
where
T: RayTracer,
D: 'static,
P: 'static,
{
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.has_committed_scene {
return Err(SimulationError::ReflectionsWithoutScene);
}
unsafe {
audionimbus_sys::iplSimulatorRunReflections(self.raw_ptr());
}
Ok(())
}
}
impl<T, D, R> Simulator<'_, T, D, R, Pathing>
where
T: RayTracer,
D: 'static,
R: 'static,
{
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> Drop for Simulator<'_, T, D, R, P> {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplSimulatorRelease(&raw mut self.inner) }
}
}
unsafe impl<T: RayTracer, D, R, P> Send for Simulator<'_, T, D, R, P> {}
unsafe impl<T: RayTracer, D, R, P> Sync for Simulator<'_, T, D, R, P> {}
impl<'a, T, D, R, P> Clone for Simulator<'a, T, D, R, P>
where
T: RayTracer,
D: 'static,
R: 'static,
P: 'static,
{
fn clone(&self) -> Self {
Self {
inner: unsafe { audionimbus_sys::iplSimulatorRetain(self.inner) },
shared: Arc::clone(&self.shared),
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,
_radeon_rays_device: self._radeon_rays_device,
_true_audio_next_device: self._true_audio_next_device,
_ray_tracer: PhantomData,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
_lifetime: PhantomData,
}
}
}
#[derive(Debug)]
pub struct SimulationSettings<'a, T: RayTracer, D = (), R = (), P = ()> {
settings: audionimbus_sys::IPLSimulationSettings,
_open_cl_device: Option<&'a OpenClDevice>,
_radeon_rays_device: Option<&'a RadeonRaysDevice>,
_true_audio_next_device: Option<&'a TrueAudioNextDevice>,
_ray_tracer: PhantomData<T>,
_direct: PhantomData<D>,
_reflections: PhantomData<R>,
_pathing: PhantomData<P>,
_lifetime: PhantomData<&'a ()>,
}
impl SimulationSettings<'_, DefaultRayTracer, (), (), ()> {
pub const fn new(sampling_rate: u32, frame_size: u32, max_order: u32) -> 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: max_order as i32,
maxNumSources: 0,
numThreads: 0,
rayBatchSize: 0,
numVisSamples: 0,
samplingRate: sampling_rate as i32,
frameSize: 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,
_lifetime: PhantomData,
}
}
}
impl<'a, D, R, P> SimulationSettings<'a, DefaultRayTracer, D, R, P> {
pub fn with_embree(self) -> SimulationSettings<'a, Embree, (), (), ()> {
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,
_lifetime: PhantomData,
}
}
pub fn with_radeon_rays(
self,
open_cl_device: &'a OpenClDevice,
radeon_rays_device: &'a RadeonRaysDevice,
) -> SimulationSettings<'a, RadeonRays, (), (), ()> {
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,
_lifetime: PhantomData,
}
}
pub fn with_custom_ray_tracer(
self,
ray_batch_size: u32,
) -> SimulationSettings<'a, CustomRayTracer, (), (), ()> {
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,
_lifetime: PhantomData,
}
}
}
impl<'a, T: RayTracer, D, R, P> SimulationSettings<'a, T, D, R, P> {
pub fn with_direct(
self,
direct_settings: DirectSimulationSettings,
) -> SimulationSettings<'a, T, Direct, R, P> {
let Self {
mut settings,
_open_cl_device,
_radeon_rays_device,
_true_audio_next_device,
_ray_tracer,
_reflections,
_pathing,
_lifetime,
..
} = 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,
_lifetime,
}
}
pub fn with_reflections(
self,
reflections_settings: ReflectionsSimulationSettings<'static>,
) -> SimulationSettings<'a, T, D, Reflections, P> {
let Self {
mut settings,
mut _open_cl_device,
_radeon_rays_device,
mut _true_audio_next_device,
_ray_tracer,
_direct,
_pathing,
_lifetime,
..
} = self;
settings.flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_REFLECTIONS;
let (
reflection_effect_type,
max_num_rays,
num_diffuse_samples,
max_duration,
max_num_sources,
num_threads,
) = match reflections_settings {
ReflectionsSimulationSettings::Convolution {
max_num_rays,
num_diffuse_samples,
max_duration,
max_num_sources,
num_threads,
} => (
audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_CONVOLUTION,
max_num_rays,
num_diffuse_samples,
max_duration,
max_num_sources,
num_threads,
),
ReflectionsSimulationSettings::Parametric {
max_num_rays,
num_diffuse_samples,
max_duration,
max_num_sources,
num_threads,
} => (
audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_PARAMETRIC,
max_num_rays,
num_diffuse_samples,
max_duration,
max_num_sources,
num_threads,
),
ReflectionsSimulationSettings::Hybrid {
max_num_rays,
num_diffuse_samples,
max_duration,
max_num_sources,
num_threads,
} => (
audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_HYBRID,
max_num_rays,
num_diffuse_samples,
max_duration,
max_num_sources,
num_threads,
),
ReflectionsSimulationSettings::TrueAudioNext {
max_num_rays,
num_diffuse_samples,
max_duration,
max_num_sources,
num_threads,
open_cl_device,
true_audio_next_device,
} => {
settings.openCLDevice = open_cl_device.raw_ptr();
settings.tanDevice = true_audio_next_device.raw_ptr();
_open_cl_device = Some(open_cl_device);
_true_audio_next_device = Some(true_audio_next_device);
(
audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_TAN,
max_num_rays,
num_diffuse_samples,
max_duration,
max_num_sources,
num_threads,
)
}
};
settings.reflectionType = reflection_effect_type;
settings.maxNumRays = max_num_rays as i32;
settings.numDiffuseSamples = num_diffuse_samples as i32;
settings.maxDuration = max_duration;
settings.maxNumSources = max_num_sources as i32;
settings.numThreads = num_threads as i32;
SimulationSettings {
settings,
_open_cl_device,
_radeon_rays_device,
_true_audio_next_device,
_ray_tracer,
_direct,
_reflections: PhantomData,
_pathing,
_lifetime,
}
}
pub fn with_pathing(
self,
pathing_settings: PathingSimulationSettings,
) -> SimulationSettings<'a, T, D, R, Pathing> {
let Self {
mut settings,
_open_cl_device,
_radeon_rays_device,
_true_audio_next_device,
_ray_tracer,
_direct,
_reflections,
_lifetime,
..
} = 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,
_lifetime,
}
}
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
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct DirectSimulationSettings {
pub max_num_occlusion_samples: u32,
}
#[derive(Debug, Copy, Clone)]
pub enum ReflectionsSimulationSettings<'a> {
Convolution {
max_num_rays: u32,
num_diffuse_samples: u32,
max_duration: f32,
max_num_sources: u32,
num_threads: u32,
},
Parametric {
max_num_rays: u32,
num_diffuse_samples: u32,
max_duration: f32,
max_num_sources: u32,
num_threads: u32,
},
Hybrid {
max_num_rays: u32,
num_diffuse_samples: u32,
max_duration: f32,
max_num_sources: u32,
num_threads: u32,
},
TrueAudioNext {
max_num_rays: u32,
num_diffuse_samples: u32,
max_duration: f32,
max_num_sources: u32,
num_threads: u32,
open_cl_device: &'a OpenClDevice,
true_audio_next_device: &'a TrueAudioNextDevice,
},
}
#[derive(Debug, Copy, Clone)]
pub struct PathingSimulationSettings {
pub num_visibility_samples: u32,
}
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 () {}
#[derive(Debug)]
pub struct Source<'a, D = (), R = (), P = ()> {
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>,
_lifetime: PhantomData<&'a ()>,
}
#[derive(Default, Debug)]
struct SourceShared {
deviation_model: Option<DeviationModel>,
}
impl<'a, D, R, P> Source<'a, D, R, P>
where
D: 'static,
R: 'static,
P: 'static,
{
pub fn try_new<T, SimD, SimR, SimP>(
simulator: &Simulator<'a, T, SimD, SimR, SimP>,
source_settings: &SourceSettings,
) -> Result<Self, SteamAudioError>
where
T: RayTracer,
SimD: 'static,
SimR: 'static,
SimP: 'static,
D: DirectCompatible<SimD> + 'static,
R: ReflectionsCompatible<SimR> + 'static,
P: PathingCompatible<SimP> + 'static,
{
let mut inner = std::ptr::null_mut();
let status = unsafe {
audionimbus_sys::iplSourceCreate(
simulator.raw_ptr(),
&mut audionimbus_sys::IPLSourceSettings::from(source_settings),
&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,
_lifetime: PhantomData,
};
Ok(source)
}
pub fn set_inputs(
&mut self,
simulation_flags: SimulationFlags,
inputs: SimulationInputs<'_, D, R, P>,
) -> Result<(), ParameterValidationError> {
Self::validate_flags(simulation_flags);
self.validate_inputs(&inputs)?;
let mut ffi_inputs = inputs.to_ffi();
if let Some(pathing_params) = inputs.pathing_simulation {
self.shared.lock().unwrap().deviation_model = Some(pathing_params.deviation);
}
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(
&mut self,
simulation_flags: SimulationFlags,
) -> Result<SimulationOutputs<'_, D, R, P>, SteamAudioError> {
Self::validate_flags(simulation_flags);
let _guards = self.acquire_locks_for_flags(simulation_flags);
let simulation_outputs = SimulationOutputs::try_allocate()?;
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) {
if let Some(lock) = &self.direct_lock {
guards.push(lock.lock().unwrap());
}
}
if flags.contains(SimulationFlags::REFLECTIONS) {
if let Some(lock) = &self.reflections_lock {
guards.push(lock.lock().unwrap());
}
}
if flags.contains(SimulationFlags::PATHING) {
if let Some(lock) = &self.pathing_lock {
guards.push(lock.lock().unwrap());
}
}
guards
}
fn validate_flags(flags: SimulationFlags) {
let mut valid_flags = SimulationFlags::empty();
if std::any::TypeId::of::<D>() == std::any::TypeId::of::<Direct>() {
valid_flags |= SimulationFlags::DIRECT;
}
if std::any::TypeId::of::<R>() == std::any::TypeId::of::<Reflections>() {
valid_flags |= SimulationFlags::REFLECTIONS;
}
if std::any::TypeId::of::<P>() == std::any::TypeId::of::<Pathing>() {
valid_flags |= SimulationFlags::PATHING;
}
assert!(
valid_flags.contains(flags),
"requested simulation flags {:?} are not enabled on this source (valid flags: {:?})",
flags,
valid_flags
);
}
fn validate_inputs(
&self,
inputs: &SimulationInputs<'_, D, R, P>,
) -> Result<(), ParameterValidationError> {
let Some(direct_params) = &inputs.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(())
}
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<D, R, P> Drop for Source<'_, D, R, P> {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplSourceRelease(&raw mut self.inner) }
}
}
unsafe impl<D, R, P> Send for Source<'_, D, R, P> {}
unsafe impl<D, R, P> Sync for Source<'_, D, R, P> {}
impl<'a, D, R, P> Clone for Source<'a, D, R, P>
where
D: 'static,
R: 'static,
P: 'static,
{
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,
_lifetime: PhantomData,
}
}
}
#[derive(Debug)]
pub struct SourceSettings {
pub flags: SimulationFlags,
}
impl From<&SourceSettings> for audionimbus_sys::IPLSourceSettings {
fn from(settings: &SourceSettings) -> Self {
Self {
flags: settings.flags.into(),
}
}
}
#[derive(Debug)]
pub struct SimulationInputs<'a, D = (), R = (), P = ()> {
source: CoordinateSystem,
direct_simulation: Option<DirectSimulationParameters>,
reflections_simulation: Option<ReflectionsSimulationParameters>,
pathing_simulation: Option<PathingSimulationParameters<'a>>,
_direct: PhantomData<D>,
_reflections: PhantomData<R>,
_pathing: PhantomData<P>,
}
impl SimulationInputs<'_> {
pub fn new(source: CoordinateSystem) -> Self {
Self {
source,
direct_simulation: None,
reflections_simulation: None,
pathing_simulation: None,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
}
}
}
impl<'a, D, R, P> SimulationInputs<'a, D, R, P> {
pub fn with_direct(
self,
params: DirectSimulationParameters,
) -> SimulationInputs<'a, Direct, R, P> {
let Self {
source,
reflections_simulation,
pathing_simulation,
_reflections,
_pathing,
..
} = self;
SimulationInputs {
source,
direct_simulation: Some(params),
reflections_simulation,
pathing_simulation,
_direct: PhantomData,
_reflections,
_pathing,
}
}
pub fn with_reflections(
self,
params: ReflectionsSimulationParameters,
) -> SimulationInputs<'a, D, Reflections, P> {
let Self {
source,
direct_simulation,
pathing_simulation,
_direct,
_pathing,
..
} = self;
SimulationInputs {
source,
direct_simulation,
reflections_simulation: Some(params),
pathing_simulation,
_direct,
_reflections: PhantomData,
_pathing,
}
}
pub fn with_pathing(
self,
params: PathingSimulationParameters<'a>,
) -> SimulationInputs<'a, D, R, Pathing> {
let Self {
source,
direct_simulation,
reflections_simulation,
_direct,
_reflections,
..
} = self;
SimulationInputs {
source,
direct_simulation,
reflections_simulation,
pathing_simulation: Some(params),
_direct,
_reflections,
_pathing: PhantomData,
}
}
fn to_ffi(&self) -> audionimbus_sys::IPLSimulationInputs {
let mut flags = audionimbus_sys::IPLSimulationFlags(0);
let source = self.source.into();
if self.direct_simulation.is_some() {
flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_DIRECT;
}
let direct_data = DirectSimulationData::from_params(&self.direct_simulation);
if self.reflections_simulation.is_some() {
flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_REFLECTIONS;
}
let reflections_data = ReflectionsSimulationData::from_params(self.reflections_simulation);
if self.pathing_simulation.is_some() {
flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_PATHING;
}
let pathing_data = PathingSimulationData::from_params(self.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(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,
}
#[derive(Debug, Copy, Clone)]
pub enum ReflectionsSimulationParameters {
Convolution {
baked_data_identifier: Option<BakedDataIdentifier>,
},
Parametric {
reverb_scale: [f32; 3],
baked_data_identifier: Option<BakedDataIdentifier>,
},
Hybrid {
reverb_scale: [f32; 3],
hybrid_reverb_transition_time: f32,
hybrid_reverb_overlap_percent: f32,
baked_data_identifier: Option<BakedDataIdentifier>,
},
TrueAudioNext {
baked_data_identifier: Option<BakedDataIdentifier>,
},
}
#[derive(Debug)]
pub struct PathingSimulationParameters<'a> {
pub pathing_probes: &'a 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,
}
}
}
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 ReflectionsSimulationData {
fn from_params(params: Option<ReflectionsSimulationParameters>) -> Self {
let Some(params) = params else {
return Self::default();
};
let (baked_data_id_opt, reverb_scale, transition_time, overlap_percent) = match params {
ReflectionsSimulationParameters::Convolution {
baked_data_identifier,
}
| ReflectionsSimulationParameters::TrueAudioNext {
baked_data_identifier,
} => (baked_data_identifier, [0.0; 3], 0.0, 0.0),
ReflectionsSimulationParameters::Parametric {
reverb_scale,
baked_data_identifier,
} => (baked_data_identifier, reverb_scale, 0.0, 0.0),
ReflectionsSimulationParameters::Hybrid {
reverb_scale,
hybrid_reverb_transition_time,
hybrid_reverb_overlap_percent,
baked_data_identifier,
} => (
baked_data_identifier,
reverb_scale,
hybrid_reverb_transition_time,
hybrid_reverb_overlap_percent,
),
};
let (baked, baked_data_identifier) = baked_data_id_opt.map_or(
(
audionimbus_sys::IPLbool::IPL_FALSE,
BakedDataIdentifier::Reflections {
variation: BakedDataVariation::Reverb,
},
),
|id| (audionimbus_sys::IPLbool::IPL_TRUE, id),
);
Self {
baked,
baked_data_identifier,
reverb_scale,
hybrid_reverb_transition_time: transition_time,
hybrid_reverb_overlap_percent: overlap_percent,
}
}
}
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(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,
}
}
}
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<'src, D, R, P> {
inner: *mut audionimbus_sys::IPLSimulationOutputs,
_direct: PhantomData<D>,
_reflections: PhantomData<R>,
_pathing: PhantomData<P>,
_marker: PhantomData<&'src ()>,
}
impl<'src, D, R, P> SimulationOutputs<'src, D, R, P> {
fn try_allocate() -> Result<Self, SteamAudioError> {
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
};
Ok(Self {
inner: ptr,
_direct: PhantomData,
_reflections: PhantomData,
_pathing: PhantomData,
_marker: 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<'src, R, P> SimulationOutputs<'src, Direct, R, P> {
pub fn direct(&self) -> FFIWrapper<'_, DirectEffectParams, Self> {
unsafe { FFIWrapper::new((*self.inner).direct.into()) }
}
}
impl<'src, D, P> SimulationOutputs<'src, D, Reflections, P> {
pub fn reflections<'a, T: ReflectionEffectType>(
&'a self,
) -> FFIWrapper<'a, ReflectionEffectParams<'a, T>, Self> {
unsafe {
FFIWrapper::new(ReflectionEffectParams::from_ffi_unchecked(
(*self.inner).reflections,
))
}
}
}
impl<'src, D, R> SimulationOutputs<'src, D, R, Pathing> {
pub fn pathing(&self) -> FFIWrapper<'_, PathEffectParams, Self> {
unsafe { FFIWrapper::new((*self.inner).pathing.into()) }
}
}
unsafe impl<D, R, P> Send for SimulationOutputs<'_, D, R, P> {}
impl<D, R, P> Drop for SimulationOutputs<'_, D, R, P> {
fn drop(&mut self) {
unsafe {
let layout = std::alloc::Layout::new::<audionimbus_sys::IPLSimulationOutputs>();
std::alloc::dealloc(self.inner.cast::<u8>(), layout);
}
}
}
#[derive(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, 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 set_inputs {
use super::*;
#[test]
#[should_panic(
expected = "requested simulation flags SimulationFlags(REFLECTIONS) are not enabled on this source (valid flags: SimulationFlags(DIRECT))"
)]
fn panic_on_invalid_flags() {
let context = Context::default();
const SAMPLING_RATE: u32 = 48_000;
const FRAME_SIZE: u32 = 1024;
const MAX_ORDER: u32 = 1;
let simulation_settings =
SimulationSettings::new(SAMPLING_RATE, FRAME_SIZE, MAX_ORDER).with_direct(
DirectSimulationSettings {
max_num_occlusion_samples: 4,
},
);
let mut simulator = Simulator::try_new(&context, &simulation_settings).unwrap();
let scene = Scene::try_new(&context).unwrap();
simulator.set_scene(&scene);
let source_settings = SourceSettings {
flags: SimulationFlags::DIRECT | SimulationFlags::REFLECTIONS,
};
let mut source =
Source::<Direct, (), ()>::try_new(&simulator, &source_settings).unwrap();
let simulation_inputs = SimulationInputs::new(CoordinateSystem {
right: Vector3::new(1.0, 0.0, 0.0),
up: Vector3::new(0.0, 1.0, 0.0),
ahead: Vector3::new(0.0, 0.0, 1.0),
origin: Vector3::new(0.0, 0.0, 0.0),
})
.with_direct(
DirectSimulationParameters::new()
.with_distance_attenuation(DistanceAttenuationModel::default())
.with_air_absorption(AirAbsorptionModel::default())
.with_directivity(Directivity::default())
.with_occlusion(
Occlusion::new(OcclusionAlgorithm::Raycast).with_transmission(
TransmissionParameters {
num_transmission_rays: 1,
},
),
),
);
source
.set_inputs(SimulationFlags::REFLECTIONS, simulation_inputs)
.unwrap();
}
}
mod get_outputs {
use super::*;
#[test]
#[should_panic(
expected = "requested simulation flags SimulationFlags(REFLECTIONS) are not enabled on this source (valid flags: SimulationFlags(DIRECT))"
)]
fn panic_on_invalid_flags() {
let context = Context::default();
const SAMPLING_RATE: u32 = 48_000;
const FRAME_SIZE: u32 = 1024;
const MAX_ORDER: u32 = 1;
let simulation_settings =
SimulationSettings::new(SAMPLING_RATE, FRAME_SIZE, MAX_ORDER).with_direct(
DirectSimulationSettings {
max_num_occlusion_samples: 4,
},
);
let mut simulator = Simulator::try_new(&context, &simulation_settings).unwrap();
let scene = Scene::try_new(&context).unwrap();
simulator.set_scene(&scene);
let source_settings = SourceSettings {
flags: SimulationFlags::DIRECT | SimulationFlags::REFLECTIONS,
};
let mut source = Source::try_new(&simulator, &source_settings).unwrap();
let simulation_inputs = SimulationInputs::new(CoordinateSystem {
right: Vector3::new(1.0, 0.0, 0.0),
up: Vector3::new(0.0, 1.0, 0.0),
ahead: Vector3::new(0.0, 0.0, 1.0),
origin: Vector3::new(0.0, 0.0, 0.0),
})
.with_direct(
DirectSimulationParameters::new()
.with_distance_attenuation(DistanceAttenuationModel::default())
.with_air_absorption(AirAbsorptionModel::default())
.with_directivity(Directivity::default())
.with_occlusion(
Occlusion::new(OcclusionAlgorithm::Raycast).with_transmission(
TransmissionParameters {
num_transmission_rays: 1,
},
),
),
);
source
.set_inputs(SimulationFlags::DIRECT, simulation_inputs)
.unwrap();
simulator.run_direct();
let _ = source.get_outputs(SimulationFlags::REFLECTIONS).unwrap();
}
}
mod clone {
use super::*;
#[test]
fn test_clone() {
let context = Context::default();
let simulator_settings = SimulationSettings::new(48_000, 1024, 1);
let simulator = Simulator::try_new(&context, &simulator_settings).unwrap();
let source_settings = SourceSettings {
flags: SimulationFlags::empty(),
};
let source = Source::<(), (), ()>::try_new(&simulator, &source_settings).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 settings = SimulationSettings::new(48_000, 1024, 1);
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());
}
}
}