audionimbus/simulation.rs
1use crate::air_absorption::AirAbsorptionModel;
2use crate::callback::CallbackInformation;
3use crate::context::Context;
4use crate::deviation::DeviationModel;
5use crate::device::open_cl::OpenClDevice;
6use crate::device::radeon_rays::RadeonRaysDevice;
7use crate::device::true_audio_next::TrueAudioNextDevice;
8use crate::directivity::Directivity;
9use crate::distance_attenuation::DistanceAttenuationModel;
10use crate::effect::{DirectEffectParams, PathEffectParams, ReflectionEffectParams};
11use crate::error::{to_option_error, SteamAudioError};
12use crate::ffi_wrapper::FFIWrapper;
13use crate::geometry;
14use crate::geometry::{Scene, SceneParams};
15use crate::probe::ProbeBatch;
16use std::marker::PhantomData;
17
18// Marker types for capabilities.
19#[derive(Debug)]
20pub struct Direct;
21#[derive(Debug)]
22pub struct Reflections;
23#[derive(Debug)]
24pub struct Pathing;
25
26/// Builder for creating a [`Simulator`].
27#[derive(Debug)]
28pub struct SimulatorBuilder<D = (), R = (), P = ()> {
29 settings: SimulationSettings<'static>,
30 _direct: PhantomData<D>,
31 _reflections: PhantomData<R>,
32 _pathing: PhantomData<P>,
33}
34
35/// Manages direct and indirect sound propagation simulation for multiple sources.
36///
37/// Your application will typically create one simulator object and use it to run simulations with different source and listener parameters between consecutive simulation runs.
38/// The simulator can also be reused across scene changes.
39#[derive(Debug)]
40pub struct Simulator<D = (), R = (), P = ()> {
41 inner: audionimbus_sys::IPLSimulator,
42 _direct: PhantomData<D>,
43 _reflections: PhantomData<R>,
44 _pathing: PhantomData<P>,
45}
46
47impl Simulator<(), (), ()> {
48 /// Creates a new simulator builder with required parameters.
49 pub fn builder(
50 scene_params: SceneParams<'static>,
51 sampling_rate: usize,
52 frame_size: usize,
53 ) -> SimulatorBuilder<(), (), ()> {
54 SimulatorBuilder {
55 settings: SimulationSettings {
56 scene_params,
57 sampling_rate,
58 frame_size,
59 direct_simulation: None,
60 reflections_simulation: None,
61 pathing_simulation: None,
62 },
63 _direct: PhantomData,
64 _reflections: PhantomData,
65 _pathing: PhantomData,
66 }
67 }
68}
69
70impl<D, R, P> SimulatorBuilder<D, R, P> {
71 /// Enables direct simulation.
72 pub fn with_direct(
73 self,
74 direct_settings: DirectSimulationSettings,
75 ) -> SimulatorBuilder<Direct, R, P> {
76 let SimulatorBuilder {
77 mut settings,
78 _reflections,
79 _pathing,
80 ..
81 } = self;
82
83 settings.direct_simulation = Some(direct_settings);
84
85 SimulatorBuilder {
86 settings,
87 _direct: PhantomData,
88 _reflections,
89 _pathing,
90 }
91 }
92
93 /// Enables reflections simulation.
94 pub fn with_reflections(
95 self,
96 reflections_settings: ReflectionsSimulationSettings<'static>,
97 ) -> SimulatorBuilder<D, Reflections, P> {
98 let SimulatorBuilder {
99 mut settings,
100 _direct,
101 _pathing,
102 ..
103 } = self;
104
105 settings.reflections_simulation = Some(reflections_settings);
106
107 SimulatorBuilder {
108 settings,
109 _direct,
110 _reflections: PhantomData,
111 _pathing,
112 }
113 }
114
115 /// Enables pathing simulation.
116 pub fn with_pathing(
117 self,
118 pathing_settings: PathingSimulationSettings,
119 ) -> SimulatorBuilder<D, R, Pathing> {
120 let SimulatorBuilder {
121 mut settings,
122 _direct,
123 _reflections,
124 ..
125 } = self;
126
127 settings.pathing_simulation = Some(pathing_settings);
128
129 SimulatorBuilder {
130 settings,
131 _direct,
132 _reflections,
133 _pathing: PhantomData,
134 }
135 }
136
137 pub fn try_build(self, context: &Context) -> Result<Simulator<D, R, P>, SteamAudioError> {
138 let mut simulator = Simulator {
139 inner: std::ptr::null_mut(),
140 _direct: PhantomData,
141 _reflections: PhantomData,
142 _pathing: PhantomData,
143 };
144
145 let status = unsafe {
146 audionimbus_sys::iplSimulatorCreate(
147 context.raw_ptr(),
148 &mut audionimbus_sys::IPLSimulationSettings::from(self.settings),
149 simulator.raw_ptr_mut(),
150 )
151 };
152
153 if let Some(error) = to_option_error(status) {
154 return Err(error);
155 }
156
157 Ok(simulator)
158 }
159}
160
161impl<D, R, P> Simulator<D, R, P> {
162 /// Specifies the scene within which all subsequent simulations should be run.
163 ///
164 /// Call [`Self::commit`] after calling this function for the changes to take effect.
165 ///
166 /// This function cannot be called while any simulation is running.
167 pub fn set_scene(&mut self, scene: &Scene) {
168 unsafe { audionimbus_sys::iplSimulatorSetScene(self.raw_ptr(), scene.raw_ptr()) }
169 }
170
171 /// Adds a probe batch for use in subsequent simulations.
172 /// Sources that require baked data can then use the data contained in the specified probe batch.
173 ///
174 /// Call [`Self::commit`] after calling this function for the changes to take effect.
175 ///
176 /// This function cannot be called while any simulation is running.
177 pub fn add_probe_batch(&mut self, probe: &ProbeBatch) {
178 unsafe {
179 audionimbus_sys::iplSimulatorAddProbeBatch(self.raw_ptr(), probe.raw_ptr());
180 }
181 }
182
183 /// Removes a probe batch from use in subsequent simulations.
184 /// Sources that require baked data will then stop using the data contained in the specified probe batch.
185 ///
186 /// Call [`Self::commit`] after calling this function for the changes to take effect.
187 ///
188 /// This function cannot be called while any simulation is running.
189 pub fn remove_probe_batch(&mut self, probe: &ProbeBatch) {
190 unsafe {
191 audionimbus_sys::iplSimulatorRemoveProbeBatch(self.raw_ptr(), probe.raw_ptr());
192 }
193 }
194
195 /// Adds a source to the set of sources processed by a simulator in subsequent simulations.
196 ///
197 /// Call [`Self::commit`] after calling this function for the changes to take effect.
198 pub fn add_source(&mut self, source: &Source) {
199 unsafe {
200 audionimbus_sys::iplSourceAdd(source.raw_ptr(), self.raw_ptr());
201 }
202 }
203
204 /// Removes a source from the set of sources processed by a simulator in subsequent simulations.
205 ///
206 /// Call [`Self::commit`] after calling this function for the changes to take effect.
207 pub fn remove_source(&mut self, source: &Source) {
208 unsafe {
209 audionimbus_sys::iplSourceRemove(source.raw_ptr(), self.raw_ptr());
210 }
211 }
212
213 /// Commits changes to the scene or probe batches used for simulation.
214 ///
215 /// Call this function after calling [`Self::set_scene`], [`Self::add_probe_batch`], or [`Self::remove_probe_batch`] for the changes to take effect.
216 ///
217 /// This function cannot be called while any simulation is running.
218 pub fn commit(&mut self) {
219 unsafe { audionimbus_sys::iplSimulatorCommit(self.raw_ptr()) }
220 }
221
222 /// Specifies simulation parameters that are not associated with any particular source.
223 ///
224 /// # Arguments
225 ///
226 /// - `flags`: the types of simulation for which to specify shared inputs. If, for example, direct and reflections simulations are being run on separate threads, you can call this function on the direct simulation thread with [`SimulationFlags::DIRECT`], and on the reflections simulation thread with [`SimulationFlags::REFLECTIONS`], without requiring any synchronization between the calls.
227 /// - `shared_inputs`: the shared input parameters to set.
228 pub fn set_shared_inputs(
229 &mut self,
230 simulation_flags: SimulationFlags,
231 shared_inputs: &SimulationSharedInputs,
232 ) {
233 unsafe {
234 audionimbus_sys::iplSimulatorSetSharedInputs(
235 self.raw_ptr(),
236 simulation_flags.into(),
237 &mut audionimbus_sys::IPLSimulationSharedInputs::from(shared_inputs),
238 );
239 }
240 }
241
242 pub fn raw_ptr(&self) -> audionimbus_sys::IPLSimulator {
243 self.inner
244 }
245
246 pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLSimulator {
247 &mut self.inner
248 }
249}
250
251impl<R, P> Simulator<Direct, R, P> {
252 /// Runs a direct simulation for all sources added to the simulator.
253 /// This may include distance attenuation, air absorption, directivity, occlusion, and transmission.
254 ///
255 /// This function should not be called from the audio processing thread if occlusion and/or transmission are enabled.
256 pub fn run_direct(&self) {
257 unsafe {
258 audionimbus_sys::iplSimulatorRunDirect(self.raw_ptr());
259 }
260 }
261}
262
263impl<D, P> Simulator<D, Reflections, P> {
264 /// Runs a reflections simulation for all sources added to the simulator.
265 ///
266 /// This function can be CPU intensive, and should be called from a separate thread in order to not block either the audio processing thread or the game’s main update thread.
267 pub fn run_reflections(&self) {
268 unsafe {
269 audionimbus_sys::iplSimulatorRunReflections(self.raw_ptr());
270 }
271 }
272}
273
274impl<D, R> Simulator<D, R, Pathing> {
275 /// Runs a pathing simulation for all sources added to the simulator.
276 ///
277 /// This function can be CPU intensive, and should be called from a separate thread in order to not block either the audio processing thread or the game’s main update thread.
278 pub fn run_pathing(&self) {
279 unsafe {
280 audionimbus_sys::iplSimulatorRunPathing(self.raw_ptr());
281 }
282 }
283}
284
285impl<D, R, P> Clone for Simulator<D, R, P> {
286 fn clone(&self) -> Self {
287 unsafe {
288 audionimbus_sys::iplSimulatorRetain(self.inner);
289 }
290
291 Self {
292 inner: self.inner,
293 _direct: PhantomData,
294 _reflections: PhantomData,
295 _pathing: PhantomData,
296 }
297 }
298}
299
300impl<D, R, P> Drop for Simulator<D, R, P> {
301 fn drop(&mut self) {
302 unsafe { audionimbus_sys::iplSimulatorRelease(&mut self.inner) }
303 }
304}
305
306unsafe impl<D, R, P> Send for Simulator<D, R, P> {}
307unsafe impl<D, R, P> Sync for Simulator<D, R, P> {}
308
309/// Settings used to create a simulator.
310#[derive(Debug, Copy, Clone)]
311pub struct SimulationSettings<'a> {
312 /// The scene parameters that will be used for simulations.
313 /// The scene parameters cannot change during the lifetime of a simulator object.
314 pub scene_params: SceneParams<'a>,
315
316 /// If `Some`, this simulator will be used for direct path simulation.
317 pub direct_simulation: Option<DirectSimulationSettings>,
318
319 /// If `Some`, this simulator will be used for reflections simulation.
320 /// The reflections effect type cannot change during the lifetime of a simulator object.
321 pub reflections_simulation: Option<ReflectionsSimulationSettings<'a>>,
322
323 /// If `Some`, this simulator will be used for pathing.
324 pub pathing_simulation: Option<PathingSimulationSettings>,
325
326 /// The sampling rate (in Hz) used for audio processing.
327 pub sampling_rate: usize,
328
329 /// The size (in samples) of the audio buffers used for audio processing.
330 pub frame_size: usize,
331}
332
333/// Settings used for direct path simulation.
334#[derive(Debug, Copy, Clone)]
335pub struct DirectSimulationSettings {
336 /// The maximum number of point samples to consider when calculating occlusion using the volumetric occlusion algorithm.
337 /// Different sources can use different numbers of samples, and the number of samples can change between simulation runs, but this is the maximum value.
338 /// Increasing this value results in smoother occlusion transitions, at the cost of increased CPU usage.
339 pub max_num_occlusion_samples: usize,
340}
341
342/// Settings used for reflections simulation.
343#[derive(Debug, Copy, Clone)]
344pub enum ReflectionsSimulationSettings<'a> {
345 /// Multi-channel convolution reverb.
346 Convolution {
347 /// The maximum number of rays to trace from the listener when simulating reflections.
348 /// You can use different numbers of rays between simulation runs, but this is the maximum value.
349 /// Increasing this value results in more accurate reflections, at the cost of increased CPU usage.
350 max_num_rays: usize,
351
352 /// The number of directions to sample when generating diffusely reflected rays.
353 /// Increasing this value may increase the accuracy of diffuse reflections.
354 num_diffuse_samples: usize,
355
356 /// The maximum length (in seconds) of impulse responses generated by reflection simulations.
357 /// You can change this value betweeen simulation runs, but this is the maximum value.
358 /// Increasing this value results in longer, more accurate reverb tails, at the cost of increased CPU and memory usage.
359 max_duration: f32,
360
361 /// The maximum Ambisonic order of impulse responses generated by reflection simulations.
362 /// You can change this value between simulation runs, but this is the maximum value.
363 /// Increasing this value results in more accurate directional variations in the impulse responses, at the cost of increased CPU and memory usage.
364 max_order: usize,
365
366 /// The maximum number of sources for which reflection simulations will be run at any given time.
367 max_num_sources: usize,
368
369 /// The number of threads used for real-time reflection simulations.
370 num_threads: usize,
371 },
372
373 /// Parametric (or artificial) reverb, using feedback delay networks.
374 Parametric {
375 /// The maximum number of rays to trace from the listener when simulating reflections.
376 /// You can use different numbers of rays between simulation runs, but this is the maximum value.
377 /// Increasing this value results in more accurate reflections, at the cost of increased CPU usage.
378 max_num_rays: usize,
379
380 /// The number of directions to sample when generating diffusely reflected rays.
381 /// Increasing this value may increase the accuracy of diffuse reflections.
382 num_diffuse_samples: usize,
383
384 /// The maximum length (in seconds) of impulse responses generated by reflection simulations.
385 /// You can change this value betweeen simulation runs, but this is the maximum value.
386 /// Increasing this value results in longer, more accurate reverb tails, at the cost of increased CPU and memory usage.
387 max_duration: f32,
388
389 /// The maximum Ambisonic order of impulse responses generated by reflection simulations.
390 /// You can change this value between simulation runs, but this is the maximum value.
391 /// Increasing this value results in more accurate directional variations in the impulse responses, at the cost of increased CPU and memory usage.
392 max_order: usize,
393
394 /// The maximum number of sources for which reflection simulations will be run at any given time.
395 max_num_sources: usize,
396
397 /// The number of threads used for real-time reflection simulations.
398 num_threads: usize,
399 },
400
401 /// A hybrid of convolution and parametric reverb.
402 Hybrid {
403 /// The maximum number of rays to trace from the listener when simulating reflections.
404 /// You can use different numbers of rays between simulation runs, but this is the maximum value.
405 /// Increasing this value results in more accurate reflections, at the cost of increased CPU usage.
406 max_num_rays: usize,
407
408 /// The number of directions to sample when generating diffusely reflected rays.
409 /// Increasing this value may increase the accuracy of diffuse reflections.
410 num_diffuse_samples: usize,
411
412 /// The maximum length (in seconds) of impulse responses generated by reflection simulations.
413 /// You can change this value betweeen simulation runs, but this is the maximum value.
414 /// Increasing this value results in longer, more accurate reverb tails, at the cost of increased CPU and memory usage.
415 max_duration: f32,
416
417 /// The maximum Ambisonic order of impulse responses generated by reflection simulations.
418 /// You can change this value between simulation runs, but this is the maximum value.
419 /// Increasing this value results in more accurate directional variations in the impulse responses, at the cost of increased CPU and memory usage.
420 max_order: usize,
421
422 /// The maximum number of sources for which reflection simulations will be run at any given time.
423 max_num_sources: usize,
424
425 /// The number of threads used for real-time reflection simulations.
426 num_threads: usize,
427 },
428
429 /// Multi-channel convolution reverb, using AMD TrueAudio Next for GPU acceleration.
430 TrueAudioNext {
431 /// The maximum number of rays to trace from the listener when simulating reflections.
432 /// You can use different numbers of rays between simulation runs, but this is the maximum value.
433 /// Increasing this value results in more accurate reflections, at the cost of increased CPU usage.
434 max_num_rays: usize,
435
436 /// The number of directions to sample when generating diffusely reflected rays.
437 /// Increasing this value may increase the accuracy of diffuse reflections.
438 num_diffuse_samples: usize,
439
440 /// The maximum length (in seconds) of impulse responses generated by reflection simulations.
441 /// You can change this value betweeen simulation runs, but this is the maximum value.
442 /// Increasing this value results in longer, more accurate reverb tails, at the cost of increased CPU and memory usage.
443 max_duration: f32,
444
445 /// The maximum Ambisonic order of impulse responses generated by reflection simulations.
446 /// You can change this value between simulation runs, but this is the maximum value.
447 /// Increasing this value results in more accurate directional variations in the impulse responses, at the cost of increased CPU and memory usage.
448 max_order: usize,
449
450 /// The maximum number of sources for which reflection simulations will be run at any given time.
451 max_num_sources: usize,
452
453 /// The number of threads used for real-time reflection simulations.
454 num_threads: usize,
455
456 /// The OpenCL device being used.
457 open_cl_device: &'a OpenClDevice,
458
459 /// The TrueAudio Next device being used.
460 true_audio_next_device: &'a TrueAudioNextDevice,
461 },
462}
463
464/// Settings used for pathing simulation.
465#[derive(Debug, Copy, Clone)]
466pub struct PathingSimulationSettings {
467 /// The number of point samples to consider when calculating probe-to-probe visibility for pathing simulations.
468 /// Baked paths may end up being occluded by dynamic objects, in which case you can configure the simulator to look for alternate paths in real time.
469 /// This process will involve checking visibility between probes.
470 pub num_visibility_samples: usize,
471}
472
473impl From<SimulationSettings<'_>> for audionimbus_sys::IPLSimulationSettings {
474 fn from(settings: SimulationSettings) -> Self {
475 let SimulationSettings {
476 scene_params,
477 direct_simulation,
478 reflections_simulation,
479 pathing_simulation,
480 sampling_rate,
481 frame_size,
482 } = settings;
483
484 let mut ray_batch_size = usize::default();
485 let mut open_cl_device = &OpenClDevice::null();
486 let mut radeon_rays_device = &RadeonRaysDevice::null();
487 let scene_type = match scene_params {
488 SceneParams::Default => audionimbus_sys::IPLSceneType::IPL_SCENETYPE_DEFAULT,
489 SceneParams::Embree => audionimbus_sys::IPLSceneType::IPL_SCENETYPE_EMBREE,
490 SceneParams::RadeonRays {
491 open_cl_device: ocl_device,
492 radeon_rays_device: rr_device,
493 } => {
494 open_cl_device = ocl_device;
495 radeon_rays_device = rr_device;
496 audionimbus_sys::IPLSceneType::IPL_SCENETYPE_RADEONRAYS
497 }
498 SceneParams::Custom {
499 ray_batch_size: rb_size,
500 } => {
501 ray_batch_size = rb_size;
502 audionimbus_sys::IPLSceneType::IPL_SCENETYPE_CUSTOM
503 }
504 };
505
506 let mut flags = audionimbus_sys::IPLSimulationFlags(0);
507
508 let mut max_num_occlusion_samples = usize::default();
509 if let Some(direct_simulation_settings) = direct_simulation {
510 flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_DIRECT;
511 max_num_occlusion_samples = direct_simulation_settings.max_num_occlusion_samples;
512 }
513
514 let mut reflection_type =
515 audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_CONVOLUTION;
516 let mut max_num_rays = usize::default();
517 let mut num_diffuse_samples = usize::default();
518 let mut max_duration = f32::default();
519 let mut max_order = usize::default();
520 let mut max_num_sources = usize::default();
521 let mut num_threads = usize::default();
522 let mut true_audio_next_device = &TrueAudioNextDevice::null();
523 if let Some(reflections_simulation_settings) = reflections_simulation {
524 flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_REFLECTIONS;
525
526 (
527 reflection_type,
528 max_num_rays,
529 num_diffuse_samples,
530 max_duration,
531 max_order,
532 max_num_sources,
533 num_threads,
534 ) = match reflections_simulation_settings {
535 ReflectionsSimulationSettings::Convolution {
536 max_num_rays,
537 num_diffuse_samples,
538 max_duration,
539 max_order,
540 max_num_sources,
541 num_threads,
542 } => (
543 audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_CONVOLUTION,
544 max_num_rays,
545 num_diffuse_samples,
546 max_duration,
547 max_order,
548 max_num_sources,
549 num_threads,
550 ),
551 ReflectionsSimulationSettings::Parametric {
552 max_num_rays,
553 num_diffuse_samples,
554 max_duration,
555 max_order,
556 max_num_sources,
557 num_threads,
558 } => (
559 audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_PARAMETRIC,
560 max_num_rays,
561 num_diffuse_samples,
562 max_duration,
563 max_order,
564 max_num_sources,
565 num_threads,
566 ),
567 ReflectionsSimulationSettings::Hybrid {
568 max_num_rays,
569 num_diffuse_samples,
570 max_duration,
571 max_order,
572 max_num_sources,
573 num_threads,
574 } => (
575 audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_HYBRID,
576 max_num_rays,
577 num_diffuse_samples,
578 max_duration,
579 max_order,
580 max_num_sources,
581 num_threads,
582 ),
583 ReflectionsSimulationSettings::TrueAudioNext {
584 max_num_rays,
585 num_diffuse_samples,
586 max_duration,
587 max_order,
588 max_num_sources,
589 num_threads,
590 open_cl_device: ocl_device,
591 true_audio_next_device: tan_device,
592 } => {
593 open_cl_device = ocl_device;
594 true_audio_next_device = tan_device;
595
596 (
597 audionimbus_sys::IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_TAN,
598 max_num_rays,
599 num_diffuse_samples,
600 max_duration,
601 max_order,
602 max_num_sources,
603 num_threads,
604 )
605 }
606 };
607 }
608
609 let mut num_visibility_samples = usize::default();
610 if let Some(pathing_simulation_settings) = pathing_simulation {
611 flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_PATHING;
612 num_visibility_samples = pathing_simulation_settings.num_visibility_samples;
613 }
614
615 Self {
616 flags,
617 sceneType: scene_type,
618 reflectionType: reflection_type,
619 maxNumOcclusionSamples: max_num_occlusion_samples as i32,
620 maxNumRays: max_num_rays as i32,
621 numDiffuseSamples: num_diffuse_samples as i32,
622 maxDuration: max_duration,
623 maxOrder: max_order as i32,
624 maxNumSources: max_num_sources as i32,
625 numThreads: num_threads as i32,
626 rayBatchSize: ray_batch_size as i32,
627 numVisSamples: num_visibility_samples as i32,
628 samplingRate: sampling_rate as i32,
629 frameSize: frame_size as i32,
630 openCLDevice: open_cl_device.raw_ptr(),
631 radeonRaysDevice: radeon_rays_device.raw_ptr(),
632 tanDevice: true_audio_next_device.raw_ptr(),
633 }
634 }
635}
636
637bitflags::bitflags! {
638 /// Flags indicating which types of simulation should be enabled.
639 #[derive(Copy, Clone, Debug)]
640 pub struct SimulationFlags: u32 {
641 /// Enable direct simulation.
642 /// This includes distance attenuation, air absorption, directivity, occlusion, and transmission.
643 const DIRECT = 1 << 0;
644
645 /// Enable reflections simulation.
646 /// This includes both real-time and baked simulation.
647 const REFLECTIONS = 1 << 1;
648
649 /// Enable pathing simulation.
650 const PATHING = 1 << 2;
651 }
652}
653
654impl From<SimulationFlags> for audionimbus_sys::IPLSimulationFlags {
655 fn from(simulation_flags: SimulationFlags) -> Self {
656 Self(simulation_flags.bits() as _)
657 }
658}
659
660/// A sound source, for the purposes of simulation.
661///
662/// This object is used to specify various parameters for direct and indirect sound propagation simulation, and to retrieve the simulation results.
663#[derive(Debug)]
664pub struct Source(audionimbus_sys::IPLSource);
665
666impl Source {
667 pub fn try_new<D, R, P>(
668 simulator: &Simulator<D, R, P>,
669 source_settings: &SourceSettings,
670 ) -> Result<Self, SteamAudioError> {
671 let mut source = Self(std::ptr::null_mut());
672
673 let status = unsafe {
674 audionimbus_sys::iplSourceCreate(
675 simulator.raw_ptr(),
676 &mut audionimbus_sys::IPLSourceSettings::from(source_settings),
677 source.raw_ptr_mut(),
678 )
679 };
680
681 if let Some(error) = to_option_error(status) {
682 return Err(error);
683 }
684
685 Ok(source)
686 }
687
688 /// Specifies simulation parameters for a source.
689 ///
690 /// # Arguments
691 ///
692 /// - `flags`: the types of simulation for which to specify inputs. If, for example, direct and reflections simulations are being run on separate threads, you can call this function on the direct simulation thread with [`SimulationFlags::DIRECT`], and on the reflections simulation thread with [`SimulationFlags::REFLECTIONS`], without requiring any synchronization between the calls.
693 /// - `inputs`: the input parameters to set.
694 pub fn set_inputs(&mut self, simulation_flags: SimulationFlags, inputs: SimulationInputs) {
695 unsafe {
696 audionimbus_sys::iplSourceSetInputs(
697 self.raw_ptr(),
698 simulation_flags.into(),
699 &mut inputs.into(),
700 );
701 }
702 }
703
704 /// Retrieves simulation results for a source.
705 ///
706 /// # Arguments
707 ///
708 /// - `flags`: the types of simulation for which to retrieve results.
709 pub fn get_outputs(&self, simulation_flags: SimulationFlags) -> SimulationOutputs {
710 let simulation_outputs = SimulationOutputs::default();
711
712 unsafe {
713 audionimbus_sys::iplSourceGetOutputs(
714 self.raw_ptr(),
715 simulation_flags.into(),
716 simulation_outputs.raw_ptr(),
717 );
718 }
719
720 simulation_outputs
721 }
722
723 pub fn raw_ptr(&self) -> audionimbus_sys::IPLSource {
724 self.0
725 }
726
727 pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLSource {
728 &mut self.0
729 }
730}
731
732impl Clone for Source {
733 fn clone(&self) -> Self {
734 unsafe {
735 audionimbus_sys::iplSourceRetain(self.0);
736 }
737 Self(self.0)
738 }
739}
740
741impl Drop for Source {
742 fn drop(&mut self) {
743 unsafe { audionimbus_sys::iplSourceRelease(&mut self.0) }
744 }
745}
746
747unsafe impl Send for Source {}
748unsafe impl Sync for Source {}
749
750/// Settings used to create a source.
751#[derive(Debug)]
752pub struct SourceSettings {
753 /// The types of simulation that may be run for this source.
754 pub flags: SimulationFlags,
755}
756
757impl From<&SourceSettings> for audionimbus_sys::IPLSourceSettings {
758 fn from(settings: &SourceSettings) -> Self {
759 Self {
760 flags: settings.flags.into(),
761 }
762 }
763}
764
765/// Simulation parameters for a source.
766#[derive(Debug, Copy, Clone)]
767pub struct SimulationInputs<'a> {
768 /// The position and orientation of this source.
769 pub source: geometry::CoordinateSystem,
770
771 /// If `Some`, enables direct simulation. This includes distance attenuation, air absorption, directivity, occlusion, and transmission.
772 pub direct_simulation: Option<DirectSimulationParameters>,
773
774 /// If `Some`, enables reflections simulation. This includes both real-time and baked simulation.
775 pub reflections_simulation: Option<ReflectionsSimulationParameters>,
776
777 /// If `Some`, enables pathing simulation.
778 pub pathing_simulation: Option<PathingSimulationParameters<'a>>,
779}
780
781/// Direct simulation parameters for a source.
782#[derive(Debug, Copy, Clone)]
783pub struct DirectSimulationParameters {
784 /// If `Some`, enables distance attenuation calculations with the specified model.
785 pub distance_attenuation: Option<DistanceAttenuationModel>,
786
787 /// If `Some`, enables air absorption calculations with the specified model.
788 pub air_absorption: Option<AirAbsorptionModel>,
789
790 /// If `Some`, enables directivity calculations with the specified directivity pattern.
791 pub directivity: Option<Directivity>,
792
793 /// If `Some`, enables occlusion simulation.
794 pub occlusion: Option<Occlusion>,
795}
796
797/// Occlusion parameters.
798#[derive(Debug, Copy, Clone)]
799pub struct Occlusion {
800 /// If `Some`, enables transmission simulation.
801 pub transmission: Option<TransmissionParameters>,
802
803 /// The occlusion algorithm to use.
804 pub algorithm: OcclusionAlgorithm,
805}
806
807/// Transmission parameters.
808#[derive(Debug, Copy, Clone)]
809pub struct TransmissionParameters {
810 /// If simulating transmission, this is the maximum number of surfaces, starting from the closest surface to the listener, whose transmission coefficients will be considered when calculating the total amount of sound transmitted.
811 /// Increasing this value will result in more accurate results when multiple surfaces lie between the source and the listener, at the cost of increased CPU usage.
812 pub num_transmission_rays: usize,
813}
814
815/// Reflections simulation parameters for a source.
816#[derive(Debug, Copy, Clone)]
817pub enum ReflectionsSimulationParameters {
818 /// Multi-channel convolution reverb.
819 Convolution {
820 /// The optional identifier used to specify which layer of baked data to use for simulating reflections for this source.
821 baked_data_identifier: Option<BakedDataIdentifier>,
822 },
823
824 /// Parametric (or artificial) reverb, using feedback delay networks.
825 Parametric {
826 /// The reverb decay times for each frequency band are scaled by these values.
827 /// Set to `[1.0, 1.0, 1.0]` to use the simulated values without modification.
828 reverb_scale: [f32; 3],
829
830 /// The optional identifier used to specify which layer of baked data to use for simulating reflections for this source.
831 baked_data_identifier: Option<BakedDataIdentifier>,
832 },
833
834 /// A hybrid of convolution and parametric reverb.
835 Hybrid {
836 /// The reverb decay times for each frequency band are scaled by these values.
837 /// Set to `[1.0, 1.0, 1.0]` to use the simulated values without modification.
838 reverb_scale: [f32; 3],
839
840 /// This is the length (in seconds) of impulse response to use for convolution reverb.
841 /// The rest of the impulse response will be used for parametric reverb estimation only.
842 /// Increasing this value results in more accurate reflections, at the cost of increased CPU usage.
843 hybrid_reverb_transition_time: f32,
844
845 /// This is the amount of overlap between the convolution and parametric parts.
846 /// To ensure smooth transitions from the early convolution part to the late parametric part, the two are cross-faded towards the end of the convolution part.
847 /// For example, if `hybrid_reverb_transition_time` is 1.0, and `hybrid_reverb_overlap_percent` is 0.25, then the first 0.75 seconds are pure convolution, the next 0.25 seconds are a blend between convolution and parametric, and the portion of the tail beyond 1.0 second is pure parametric.
848 hybrid_reverb_overlap_percent: f32,
849
850 /// The optional identifier used to specify which layer of baked data to use for simulating reflections for this source.
851 baked_data_identifier: Option<BakedDataIdentifier>,
852 },
853
854 /// Multi-channel convolution reverb, using AMD TrueAudio Next for GPU acceleration.
855 TrueAudioNext {
856 /// The optional identifier used to specify which layer of baked data to use for simulating reflections for this source.
857 baked_data_identifier: Option<BakedDataIdentifier>,
858 },
859}
860
861/// Pathing simulation parameters for a source.
862#[derive(Debug, Copy, Clone)]
863pub struct PathingSimulationParameters<'a> {
864 /// The probe batch within which to find paths from this source to the listener.
865 pub pathing_probes: &'a ProbeBatch,
866
867 /// When testing for mutual visibility between a pair of probes, each probe is treated as a sphere of this radius (in meters), and point samples are generated within this sphere.
868 pub visibility_radius: f32,
869
870 /// When tracing rays to test for mutual visibility between a pair of probes, the fraction of rays that are unoccluded must be greater than this threshold for the pair of probes to be considered mutually visible.
871 pub visibility_threshold: f32,
872
873 /// If the distance between two probes is greater than this value, the probes are not considered mutually visible.
874 /// Increasing this value can result in simpler paths, at the cost of increased CPU usage.
875 pub visibility_range: f32,
876
877 /// The Ambisonic order used for representing path directionality.
878 /// Higher values result in more precise spatialization of paths, at the cost of increased CPU usage.
879 pub pathing_order: usize,
880
881 /// If `true`, baked paths are tested for visibility.
882 /// This is useful if your scene has dynamic objects that might occlude baked paths.
883 pub enable_validation: bool,
884
885 /// If `true`, and [`Self::enable_validation`] is `true`, then if a baked path is occluded by dynamic geometry, path finding is re-run in real-time to find alternate paths that take into account the dynamic geometry.
886 pub find_alternate_paths: bool,
887
888 /// The deviation model to use for this source.
889 pub deviation: DeviationModel,
890}
891
892impl From<SimulationInputs<'_>> for audionimbus_sys::IPLSimulationInputs {
893 fn from(simulation_inputs: SimulationInputs) -> Self {
894 let SimulationInputs {
895 source,
896 direct_simulation,
897 reflections_simulation,
898 pathing_simulation,
899 } = simulation_inputs;
900
901 let mut flags = audionimbus_sys::IPLSimulationFlags(0);
902
903 let (
904 direct_flags,
905 distance_attenuation_model,
906 air_absorption_model,
907 directivity,
908 occlusion_type,
909 occlusion_radius,
910 num_occlusion_samples,
911 num_transmission_rays,
912 ) = if let Some(direct_simulation_parameters) = direct_simulation {
913 flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_DIRECT;
914
915 let DirectSimulationParameters {
916 distance_attenuation,
917 air_absorption,
918 directivity,
919 occlusion,
920 } = direct_simulation_parameters;
921
922 let mut direct_flags = audionimbus_sys::IPLDirectSimulationFlags(0);
923
924 let distance_attenuation_model = if let Some(distance_attenuation_model) =
925 distance_attenuation
926 {
927 direct_flags |= audionimbus_sys::IPLDirectSimulationFlags::IPL_DIRECTSIMULATIONFLAGS_DISTANCEATTENUATION;
928 distance_attenuation_model
929 } else {
930 DistanceAttenuationModel::default()
931 };
932
933 let air_absorption_model = if let Some(air_absorption_model) = air_absorption {
934 direct_flags |= audionimbus_sys::IPLDirectSimulationFlags::IPL_DIRECTSIMULATIONFLAGS_AIRABSORPTION;
935 air_absorption_model
936 } else {
937 AirAbsorptionModel::default()
938 };
939
940 let directivity = if let Some(directivity) = directivity {
941 direct_flags |= audionimbus_sys::IPLDirectSimulationFlags::IPL_DIRECTSIMULATIONFLAGS_DIRECTIVITY;
942 directivity
943 } else {
944 Directivity::default()
945 };
946
947 let (occlusion_type, occlusion_radius, num_occlusion_samples, num_transmission_rays) =
948 if let Some(occlusion) = occlusion {
949 direct_flags |=
950 audionimbus_sys::IPLDirectSimulationFlags::IPL_DIRECTSIMULATIONFLAGS_OCCLUSION;
951
952 let (occlusion_type, occlusion_radius, num_occlusion_samples) =
953 match occlusion.algorithm {
954 OcclusionAlgorithm::Raycast => (
955 audionimbus_sys::IPLOcclusionType::IPL_OCCLUSIONTYPE_RAYCAST,
956 f32::default(),
957 usize::default(),
958 ),
959 OcclusionAlgorithm::Volumetric {
960 radius,
961 num_occlusion_samples,
962 } => (
963 audionimbus_sys::IPLOcclusionType::IPL_OCCLUSIONTYPE_VOLUMETRIC,
964 radius,
965 num_occlusion_samples,
966 ),
967 };
968
969 let num_transmission_rays = if let Some(transmission_parameters) =
970 occlusion.transmission
971 {
972 direct_flags |= audionimbus_sys::IPLDirectSimulationFlags::IPL_DIRECTSIMULATIONFLAGS_TRANSMISSION;
973 transmission_parameters.num_transmission_rays
974 } else {
975 usize::default()
976 };
977
978 (
979 occlusion_type,
980 occlusion_radius,
981 num_occlusion_samples,
982 num_transmission_rays,
983 )
984 } else {
985 (
986 audionimbus_sys::IPLOcclusionType::IPL_OCCLUSIONTYPE_RAYCAST,
987 f32::default(),
988 usize::default(),
989 usize::default(),
990 )
991 };
992
993 (
994 direct_flags,
995 distance_attenuation_model,
996 air_absorption_model,
997 directivity,
998 occlusion_type,
999 occlusion_radius,
1000 num_occlusion_samples,
1001 num_transmission_rays,
1002 )
1003 } else {
1004 (
1005 audionimbus_sys::IPLDirectSimulationFlags(0),
1006 DistanceAttenuationModel::default(),
1007 AirAbsorptionModel::default(),
1008 Directivity::default(),
1009 audionimbus_sys::IPLOcclusionType::IPL_OCCLUSIONTYPE_RAYCAST,
1010 f32::default(),
1011 usize::default(),
1012 usize::default(),
1013 )
1014 };
1015
1016 let (
1017 baked,
1018 baked_data_identifier,
1019 reverb_scale,
1020 hybrid_reverb_transition_time,
1021 hybrid_reverb_overlap_percent,
1022 ) = if let Some(reflections_simulation_parameters) = reflections_simulation {
1023 flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_REFLECTIONS;
1024
1025 let (
1026 baked_data_identifier,
1027 reverb_scale,
1028 hybrid_reverb_transition_time,
1029 hybrid_reverb_overlap_percent,
1030 ) = match reflections_simulation_parameters {
1031 ReflectionsSimulationParameters::Convolution {
1032 baked_data_identifier,
1033 } => (
1034 baked_data_identifier,
1035 <[f32; 3]>::default(),
1036 f32::default(),
1037 f32::default(),
1038 ),
1039 ReflectionsSimulationParameters::Parametric {
1040 reverb_scale,
1041 baked_data_identifier,
1042 } => (
1043 baked_data_identifier,
1044 reverb_scale,
1045 f32::default(),
1046 f32::default(),
1047 ),
1048 ReflectionsSimulationParameters::Hybrid {
1049 reverb_scale,
1050 hybrid_reverb_transition_time,
1051 hybrid_reverb_overlap_percent,
1052 baked_data_identifier,
1053 } => (
1054 baked_data_identifier,
1055 reverb_scale,
1056 hybrid_reverb_transition_time,
1057 hybrid_reverb_overlap_percent,
1058 ),
1059 ReflectionsSimulationParameters::TrueAudioNext {
1060 baked_data_identifier,
1061 } => (
1062 baked_data_identifier,
1063 <[f32; 3]>::default(),
1064 f32::default(),
1065 f32::default(),
1066 ),
1067 };
1068
1069 let (baked, baked_data_identifier) =
1070 if let Some(baked_data_identifier) = baked_data_identifier {
1071 (audionimbus_sys::IPLbool::IPL_TRUE, baked_data_identifier)
1072 } else {
1073 (
1074 audionimbus_sys::IPLbool::IPL_FALSE,
1075 BakedDataIdentifier::Reflections {
1076 variation: BakedDataVariation::Reverb,
1077 },
1078 )
1079 };
1080
1081 (
1082 baked,
1083 baked_data_identifier,
1084 reverb_scale,
1085 hybrid_reverb_transition_time,
1086 hybrid_reverb_overlap_percent,
1087 )
1088 } else {
1089 (
1090 audionimbus_sys::IPLbool::IPL_FALSE,
1091 BakedDataIdentifier::Reflections {
1092 variation: BakedDataVariation::Reverb,
1093 },
1094 <[f32; 3]>::default(),
1095 f32::default(),
1096 f32::default(),
1097 )
1098 };
1099
1100 let (
1101 pathing_probes,
1102 visibility_radius,
1103 visibility_threshold,
1104 visibility_range,
1105 pathing_order,
1106 enable_validation,
1107 find_alternate_paths,
1108 deviation_model,
1109 ) = if let Some(pathing_simulation_parameters) = pathing_simulation {
1110 flags |= audionimbus_sys::IPLSimulationFlags::IPL_SIMULATIONFLAGS_PATHING;
1111
1112 (
1113 pathing_simulation_parameters.pathing_probes.raw_ptr(),
1114 pathing_simulation_parameters.visibility_radius,
1115 pathing_simulation_parameters.visibility_threshold,
1116 pathing_simulation_parameters.visibility_range,
1117 pathing_simulation_parameters.pathing_order,
1118 pathing_simulation_parameters.enable_validation,
1119 pathing_simulation_parameters.find_alternate_paths,
1120 // FIXME: Potential memory leak: this prevents dangling pointers, but there is no guarantee it will be freed by the C library.
1121 Box::into_raw(Box::new((&pathing_simulation_parameters.deviation).into())),
1122 )
1123 } else {
1124 (
1125 std::ptr::null_mut(),
1126 f32::default(),
1127 f32::default(),
1128 f32::default(),
1129 usize::default(),
1130 bool::default(),
1131 bool::default(),
1132 std::ptr::null_mut(),
1133 )
1134 };
1135
1136 Self {
1137 flags,
1138 directFlags: direct_flags,
1139 source: source.into(),
1140 distanceAttenuationModel: (&distance_attenuation_model).into(),
1141 airAbsorptionModel: (&air_absorption_model).into(),
1142 directivity: (&directivity).into(),
1143 occlusionType: occlusion_type,
1144 occlusionRadius: occlusion_radius,
1145 numOcclusionSamples: num_occlusion_samples as i32,
1146 reverbScale: reverb_scale,
1147 hybridReverbTransitionTime: hybrid_reverb_transition_time,
1148 hybridReverbOverlapPercent: hybrid_reverb_overlap_percent,
1149 baked,
1150 bakedDataIdentifier: baked_data_identifier.into(),
1151 pathingProbes: pathing_probes,
1152 visRadius: visibility_radius,
1153 visThreshold: visibility_threshold,
1154 visRange: visibility_range,
1155 pathingOrder: pathing_order as i32,
1156 enableValidation: if enable_validation {
1157 audionimbus_sys::IPLbool::IPL_TRUE
1158 } else {
1159 audionimbus_sys::IPLbool::IPL_FALSE
1160 },
1161 findAlternatePaths: if find_alternate_paths {
1162 audionimbus_sys::IPLbool::IPL_TRUE
1163 } else {
1164 audionimbus_sys::IPLbool::IPL_FALSE
1165 },
1166 numTransmissionRays: num_transmission_rays as i32,
1167 deviationModel: deviation_model,
1168 }
1169 }
1170}
1171
1172/// The different algorithms for simulating occlusion.
1173#[derive(Copy, Clone, Debug)]
1174pub enum OcclusionAlgorithm {
1175 /// Raycast occlusion.
1176 /// A single ray is traced from the listener to the source.
1177 /// If the ray hits a solid object before it reaches the source, the source is considered occluded.
1178 Raycast,
1179
1180 /// A volumetric occlusion algorithm that can model partial occlusion.
1181 /// The source is modeled as a sphere with a configurable radius.
1182 /// Multiple points are sampled within the volume of this sphere.
1183 /// Rays are then traced from each sample point to both the source and the listener.
1184 /// A sample point is considered occluded if either of these two rays is occluded.
1185 /// The occlusion value for the source is calculated as the fraction of sample points that are unoccluded.
1186 /// This algorithm allows for smoother transitions in and out of occlusion.
1187 Volumetric {
1188 /// The radius of the sphere the source is modeled as.
1189 radius: f32,
1190
1191 /// The number of point samples to consider when tracing rays.
1192 /// This value can change between simulation runs.
1193 num_occlusion_samples: usize,
1194 },
1195}
1196
1197/// Identifies a “layer” of data stored in a probe batch.
1198/// Each probe batch may store multiple layers of data, such as reverb, static source reflections, or pathing.
1199/// Each layer can be accessed using an identifier.
1200#[derive(Copy, Clone, Debug)]
1201pub enum BakedDataIdentifier {
1202 /// Reflections.
1203 /// The source and listener positions used to compute the reflections data stored at each probe depends on the \c IPLBakedDataVariation selected.
1204 Reflections {
1205 /// The way in which source and listener positions depend on probe position.
1206 variation: BakedDataVariation,
1207 },
1208
1209 /// Pathing.
1210 /// The probe batch stores data about the shortest paths between any pair of probes in the batch.
1211 Pathing {
1212 /// The way in which source and listener positions depend on probe position.
1213 variation: BakedDataVariation,
1214 },
1215}
1216
1217impl From<BakedDataIdentifier> for audionimbus_sys::IPLBakedDataIdentifier {
1218 fn from(baked_data_identifier: BakedDataIdentifier) -> Self {
1219 let (type_, variation) = match baked_data_identifier {
1220 BakedDataIdentifier::Reflections { variation } => (
1221 audionimbus_sys::IPLBakedDataType::IPL_BAKEDDATATYPE_REFLECTIONS,
1222 variation,
1223 ),
1224 BakedDataIdentifier::Pathing { variation } => (
1225 audionimbus_sys::IPLBakedDataType::IPL_BAKEDDATATYPE_PATHING,
1226 variation,
1227 ),
1228 };
1229
1230 let (variation, endpoint_influence) = match variation {
1231 BakedDataVariation::Reverb => (
1232 audionimbus_sys::IPLBakedDataVariation::IPL_BAKEDDATAVARIATION_REVERB,
1233 geometry::Sphere::default().into(),
1234 ),
1235 BakedDataVariation::StaticSource { endpoint_influence } => (
1236 audionimbus_sys::IPLBakedDataVariation::IPL_BAKEDDATAVARIATION_STATICSOURCE,
1237 endpoint_influence.into(),
1238 ),
1239 BakedDataVariation::StaticListener { endpoint_influence } => (
1240 audionimbus_sys::IPLBakedDataVariation::IPL_BAKEDDATAVARIATION_STATICLISTENER,
1241 endpoint_influence.into(),
1242 ),
1243 BakedDataVariation::Dynamic => (
1244 audionimbus_sys::IPLBakedDataVariation::IPL_BAKEDDATAVARIATION_DYNAMIC,
1245 geometry::Sphere::default().into(),
1246 ),
1247 };
1248
1249 Self {
1250 type_,
1251 variation,
1252 endpointInfluence: endpoint_influence,
1253 }
1254 }
1255}
1256
1257/// The different ways in which the source and listener positions used to generate baked data can vary as a function of probe position.
1258#[derive(Copy, Clone, Debug)]
1259pub enum BakedDataVariation {
1260 /// At each probe, baked data is calculated with both the source and the listener at the probe position.
1261 /// This is useful for modeling traditional reverbs, which depend only on the listener’s position (or only on the source’s position).
1262 Reverb,
1263
1264 /// At each probe, baked data is calculated with the source at some fixed position (specified separately), and the listener at the probe position.
1265 /// This is used for modeling reflections from a static source to any point within the probe batch.
1266 StaticSource {
1267 /// The static source used to generate baked data.
1268 /// Baked data is only stored for probes that lie within the radius of this sphere.
1269 endpoint_influence: geometry::Sphere,
1270 },
1271
1272 /// At each probe, baked data is calculated with the source at the probe position, and the listener at some fixed position (specified separately).
1273 /// This is used for modeling reflections from a moving source to a static listener.
1274 StaticListener {
1275 /// The static listener used to generate baked data.
1276 /// Baked data is only stored for probes that lie within the radius of this sphere.
1277 endpoint_influence: geometry::Sphere,
1278 },
1279
1280 /// Baked data is calculated for each pair of probes.
1281 /// For example, this is used for calculating paths between every pair of probes in a batch.
1282 Dynamic,
1283}
1284
1285/// Simulation parameters that are not specific to any source.
1286#[derive(Debug)]
1287pub struct SimulationSharedInputs {
1288 /// The position and orientation of the listener.
1289 pub listener: geometry::CoordinateSystem,
1290
1291 /// The number of rays to trace from the listener.
1292 /// Increasing this value results in more accurate reflections, at the cost of increased CPU usage.
1293 pub num_rays: usize,
1294
1295 /// The number of times each ray traced from the listener is reflected when it encounters a solid object.
1296 /// Increasing this value results in longer, more accurate reverb tails, at the cost of increased CPU usage during simulation.
1297 pub num_bounces: usize,
1298
1299 /// The duration (in seconds) of the impulse responses generated when simulating reflections.
1300 /// Increasing this value results in longer, more accurate reverb tails, at the cost of increased CPU usage during audio processing.
1301 pub duration: f32,
1302
1303 /// The Ambisonic order of the impulse responses generated when simulating reflections.
1304 /// Increasing this value results in more accurate directional variation of reflected sound, at the cost of increased CPU usage during audio processing.
1305 pub order: usize,
1306
1307 /// When calculating how much sound energy reaches a surface directly from a source, any source that is closer than [`Self::irradiance_min_distance`] to the surface is assumed to be at a distance of [`Self::irradiance_min_distance`], for the purposes of energy calculations.
1308 pub irradiance_min_distance: f32,
1309
1310 /// Optional callback for visualizing valid path segments during call to [`Simulator::run_pathing`].
1311 pub pathing_visualization_callback: Option<CallbackInformation<PathingVisualizationCallback>>,
1312}
1313
1314impl From<&SimulationSharedInputs> for audionimbus_sys::IPLSimulationSharedInputs {
1315 fn from(simulation_shared_inputs: &SimulationSharedInputs) -> Self {
1316 let (pathing_visualization_callback, pathing_user_data) =
1317 if let Some(callback_information) =
1318 &simulation_shared_inputs.pathing_visualization_callback
1319 {
1320 (
1321 Some(callback_information.callback),
1322 callback_information.user_data,
1323 )
1324 } else {
1325 (None, std::ptr::null_mut())
1326 };
1327
1328 Self {
1329 listener: simulation_shared_inputs.listener.into(),
1330 numRays: simulation_shared_inputs.num_rays as i32,
1331 numBounces: simulation_shared_inputs.num_bounces as i32,
1332 duration: simulation_shared_inputs.duration,
1333 order: simulation_shared_inputs.order as i32,
1334 irradianceMinDistance: simulation_shared_inputs.irradiance_min_distance,
1335 pathingVisCallback: pathing_visualization_callback,
1336 pathingUserData: pathing_user_data,
1337 }
1338 }
1339}
1340
1341/// Callback for visualizing valid path segments during the call to [`Simulator::run_pathing`].
1342///
1343/// You can use this to provide the user with visual feedback, like drawing each segment of a path.
1344///
1345/// # Arguments
1346///
1347/// - `from`: position of starting probe.
1348/// - `to`: position of ending probe.
1349/// - `occluded`: occlusion status of ray segment between `from` to `to`.
1350/// - `user_data`: pointer to arbitrary user-specified data provided when calling the function that will call this callback.
1351pub type PathingVisualizationCallback = unsafe extern "C" fn(
1352 from: audionimbus_sys::IPLVector3,
1353 to: audionimbus_sys::IPLVector3,
1354 occluded: audionimbus_sys::IPLbool,
1355 user_data: *mut std::ffi::c_void,
1356);
1357
1358/// Simulation results for a source.
1359#[derive(Debug)]
1360pub struct SimulationOutputs(*mut audionimbus_sys::IPLSimulationOutputs);
1361
1362impl SimulationOutputs {
1363 pub fn direct(&self) -> FFIWrapper<'_, DirectEffectParams, Self> {
1364 unsafe { FFIWrapper::new((*self.0).direct.into()) }
1365 }
1366
1367 pub fn reflections(&self) -> FFIWrapper<'_, ReflectionEffectParams, Self> {
1368 unsafe { FFIWrapper::new((*self.0).reflections.into()) }
1369 }
1370
1371 pub fn pathing(&self) -> FFIWrapper<'_, PathEffectParams, Self> {
1372 unsafe { FFIWrapper::new((*self.0).pathing.into()) }
1373 }
1374
1375 pub fn raw_ptr(&self) -> *mut audionimbus_sys::IPLSimulationOutputs {
1376 self.0
1377 }
1378
1379 pub fn raw_ptr_mut(&mut self) -> &mut *mut audionimbus_sys::IPLSimulationOutputs {
1380 &mut self.0
1381 }
1382}
1383
1384impl Default for SimulationOutputs {
1385 fn default() -> Self {
1386 let ptr = unsafe {
1387 let layout = std::alloc::Layout::new::<audionimbus_sys::IPLSimulationOutputs>();
1388 let ptr = std::alloc::alloc(layout) as *mut audionimbus_sys::IPLSimulationOutputs;
1389 if ptr.is_null() {
1390 panic!("failed to allocate memory for IPLSimulationOutputs");
1391 }
1392 std::ptr::write(ptr, std::mem::zeroed());
1393 ptr
1394 };
1395
1396 SimulationOutputs(ptr)
1397 }
1398}
1399
1400impl Drop for SimulationOutputs {
1401 fn drop(&mut self) {
1402 unsafe {
1403 let layout = std::alloc::Layout::new::<audionimbus_sys::IPLSimulationOutputs>();
1404 std::alloc::dealloc(self.0 as *mut u8, layout);
1405 }
1406 }
1407}