├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── audionimbus-sys ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs └── src │ ├── fmod.rs │ ├── lib.rs │ ├── phonon.rs │ └── wwise.rs └── audionimbus ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── demo ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── src ├── audio_buffer.rs ├── audio_settings.rs ├── callback.rs ├── context.rs ├── device │ ├── embree.rs │ ├── mod.rs │ ├── open_cl.rs │ ├── radeon_rays.rs │ └── true_audio_next.rs ├── effect │ ├── ambisonics │ │ ├── binaural.rs │ │ ├── decode.rs │ │ ├── encode.rs │ │ ├── mod.rs │ │ ├── panning.rs │ │ ├── rotation.rs │ │ ├── speaker_layout.rs │ │ └── type.rs │ ├── audio_effect_state.rs │ ├── binaural.rs │ ├── direct.rs │ ├── equalizer.rs │ ├── mod.rs │ ├── panning.rs │ ├── path.rs │ ├── reflection.rs │ └── virtual_surround.rs ├── error.rs ├── ffi_wrapper.rs ├── fmod.rs ├── geometry │ ├── coordinate_system.rs │ ├── direction.rs │ ├── instanced_mesh.rs │ ├── material.rs │ ├── matrix.rs │ ├── mod.rs │ ├── point.rs │ ├── scene.rs │ ├── sphere.rs │ ├── static_mesh.rs │ ├── triangle.rs │ └── vector3.rs ├── hrtf.rs ├── lib.rs ├── model │ ├── air_absorption.rs │ ├── directivity.rs │ ├── distance_attenuation.rs │ └── mod.rs ├── probe.rs ├── serialized_object.rs ├── simulation.rs ├── version.rs └── wwise.rs └── tests └── integration_tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "audionimbus-sys/steam-audio"] 2 | path = audionimbus-sys/steam-audio 3 | url = git@github.com:ValveSoftware/steam-audio.git 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "audionimbus", 5 | "audionimbus/demo", 6 | "audionimbus-sys" 7 | ] 8 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Maxence Maire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AudioNimbus 2 | 3 | A Rust wrapper around [Steam Audio](https://valvesoftware.github.io/steam-audio/), bringing powerful spatial audio capabilities to the Rust ecosystem. 4 | 5 | ## What is Steam Audio? 6 | 7 | Steam Audio is a toolkit for spatial audio, developed by Valve. It simulates realistic sound propagation, including effects like directionality, distance attenuation, and reflections. 8 | 9 | ## What is AudioNimbus? 10 | 11 | AudioNimbus provides a safe and ergonomic Rust interface to Steam Audio, enabling developers to integrate immersive spatial audio into their Rust projects. It consists of two crates: 12 | 13 | * [`audionimbus`](audionimbus): A high-level, safe wrapper around Steam Audio. 14 | * [`audionimbus-sys`](audionimbus-sys): Automatically generated raw bindings to the Steam Audio C API. 15 | 16 | Both can integrate with FMOD Studio and Wwise. 17 | 18 | ## Features 19 | 20 | AudioNimbus supports a variety of spatial audio effects, including: 21 | 22 | * **Head-Related Transfer Function (HRTF)**: Simulates how the listener’s ears, head, and shoulders shape sound perception, providing the acoustic cues the brain uses to infer direction and distance. 23 | * **Ambisonics and surround sound**: Uses multiple audio channels to create the sensation of sound coming from specific directions. 24 | * **Sound propagation**: Models how sound is affected as it travels through its environment, including effects like distance attenuation and interaction with physical obstacles of varying materials. 25 | * **Reflections**: Simulates how sound waves reflect off surrounding geometry, mimicking real-world acoustic behavior. 26 | 27 | For a demonstration of AudioNimbus' capabilities, watch [the walkthrough video](https://www.youtube.com/watch?v=zlhW1maG0Is). 28 | 29 | ## Get Started 30 | 31 | To get started using [`audionimbus`](audionimbus), check out the [`demo`](audionimbus/demo) for a practical example of how to integrate and use the library in your project. 32 | 33 | ## License 34 | 35 | This repository is dual-licensed under the [MIT License](LICENSE-MIT) and the [Apache-2.0 License](LICENSE-APACHE). 36 | You may choose either license when using the software. 37 | -------------------------------------------------------------------------------- /audionimbus-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "audionimbus-sys" 3 | description = "Rust bindings for Steam Audio." 4 | version = "4.6.2-fmodwwise.2" 5 | edition = "2021" 6 | authors = ["Maxence Maire "] 7 | license = "MIT OR Apache-2.0" 8 | keywords = ["gamedev", "game", "audio", "engine", "steam"] 9 | categories = ["game-development", "game-engines", "multimedia::audio", "simulation", "external-ffi-bindings"] 10 | repository = "https://github.com/MaxenceMaire/audionimbus" 11 | readme = "README.md" 12 | exclude = [ 13 | "steam-audio/**", 14 | "!steam-audio/core/src/core/**/*.h", 15 | "!steam-audio/fmod/src/**/*.h", 16 | "!steam-audio/fmod/include/**", 17 | "!steam-audio/wwise/src/**/*.h", 18 | "!steam-audio/wwise/include/**" 19 | ] 20 | 21 | [build-dependencies] 22 | bindgen = "0.71.1" 23 | 24 | [features] 25 | fmod = [] 26 | wwise = [] 27 | 28 | [package.metadata.docs.rs] 29 | features = ["fmod"] 30 | rustdoc-args = ["--cfg", "docsrs"] 31 | -------------------------------------------------------------------------------- /audionimbus-sys/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /audionimbus-sys/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /audionimbus-sys/README.md: -------------------------------------------------------------------------------- 1 | # audionimbus-sys 2 | 3 | Rust bindings to the [Steam Audio](https://valvesoftware.github.io/steam-audio/) library. 4 | This crate is not meant to be used directly; most users should use [`audionimbus`](../audionimbus), a safe wrapper built on top of `audionimbus-sys`. 5 | 6 | ## Overview 7 | 8 | `audionimbus-sys` exposes raw bindings to the [Steam Audio C library](steam-audio). 9 | It is inherently unsafe, as it interfaces with external C code; for a safe API, refer to [`audionimbus`](../audionimbus). 10 | 11 | `audionimbus-sys` can also integrate with FMOD studio and Wwise. 12 | 13 | ## Version compatibility 14 | 15 | `audionimbus-sys` mirrors the version of [Steam Audio](steam-audio). 16 | 17 | ## Installation 18 | 19 | Before installation, make sure that Clang 9.0 or later is installed on your system. 20 | 21 | `audionimbus-sys` requires linking against the Steam Audio library during compilation. 22 | 23 | To do so, download `steamaudio_4.6.1.zip` from the [release page](https://github.com/ValveSoftware/steam-audio/releases). 24 | 25 | Locate the relevant library for your target platform (`SDKROOT` refers to the directory in which you extracted the zip file): 26 | 27 | | Platform | Library Directory | Library To Link | 28 | | --- | --- | --- | 29 | | Windows 32-bit | `SDKROOT/lib/windows-x86` | `phonon.dll` | 30 | | Windows 64-bit | `SDKROOT/lib/windows-x64` | `phonon.dll` | 31 | | Linux 32-bit | `SDKROOT/lib/linux-x86` | `libphonon.so` | 32 | | Linux 64-bit | `SDKROOT/lib/linux-x64` | `libphonon.so` | 33 | | macOS | `SDKROOT/lib/osx` | `libphonon.dylib` | 34 | | Android ARMv7 | `SDKROOT/lib/android-armv7` | `libphonon.so` | 35 | | Android ARMv8/AArch64 | `SDKROOT/lib/android-armv8` | `libphonon.so` | 36 | | Android x86 | `SDKROOT/lib/android-x86` | `libphonon.so` | 37 | | Android x64 | `SDKROOT/lib/android-x64` | `libphonon.so` | 38 | | iOS ARMv8/AArch64 | `SDKROOT/lib/ios` | `libphonon.a` | 39 | 40 | Ensure the library is placed in a location listed in the [dynamic library search paths](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) (e.g., `/usr/local/lib`). 41 | 42 | Finally, add `audionimbus-sys` to your `Cargo.toml`: 43 | 44 | ```toml 45 | [dependencies] 46 | audionimbus-sys = "4.6.2-fmodwwise.2" 47 | ``` 48 | 49 | ## FMOD Studio Integration 50 | 51 | `audionimbus-sys` can be used to add spatial audio to an FMOD Studio project. 52 | 53 | It requires linking against both the Steam Audio library and the FMOD integration library during compilation: 54 | 55 | 1. Download `steamaudio_fmod_4.6.1.zip` from the [release page](https://github.com/ValveSoftware/steam-audio/releases). 56 | 57 | 2. Locate the two relevant libraries for your target platform (`SDKROOT` refers to the directory in which you extracted the zip file): 58 | 59 | | Platform | Library Directory | Library To Link | 60 | | --- | --- | --- | 61 | | Windows 32-bit | `SDKROOT/lib/windows-x86` | `phonon.dll`, `phonon_fmod.dll` | 62 | | Windows 64-bit | `SDKROOT/lib/windows-x64` | `phonon.dll`, `phonon_fmod.dll` | 63 | | Linux 32-bit | `SDKROOT/lib/linux-x86` | `libphonon.so`, `libphonon_fmod.so` | 64 | | Linux 64-bit | `SDKROOT/lib/linux-x64` | `libphonon.so`, `libphonon_fmod.so` | 65 | | macOS | `SDKROOT/lib/osx` | `libphonon.dylib`, `libphonon_fmod.dylib` | 66 | | Android ARMv7 | `SDKROOT/lib/android-armv7` | `libphonon.so`, `libphonon_fmod.so` | 67 | | Android ARMv8/AArch64 | `SDKROOT/lib/android-armv8` | `libphonon.so`, `libphonon_fmod.so` | 68 | | Android x86 | `SDKROOT/lib/android-x86` | `libphonon.so`, `libphonon_fmod.so` | 69 | | Android x64 | `SDKROOT/lib/android-x64` | `libphonon.so`, `libphonon_fmod.so` | 70 | | iOS ARMv8/AArch64 | `SDKROOT/lib/ios` | `libphonon.a`, `libphonon_fmod.a` | 71 | 72 | 3. Ensure the libraries are placed in a location listed in the [dynamic library search paths](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) (e.g., `/usr/local/lib`). 73 | 74 | 4. Finally, add `audionimbus-sys` with the `fmod` feature enabled to your `Cargo.toml`: 75 | 76 | ```toml 77 | [dependencies] 78 | audionimbus-sys = { version = "4.6.2-fmodwwise.2", features = ["fmod"] } 79 | ``` 80 | 81 | ## Wwise Integration 82 | 83 | `audionimbus-sys` can be used to add spatial audio to a Wwise project. 84 | 85 | It requires linking against both the Steam Audio library and the Wwise integration library during compilation: 86 | 87 | 1. Download `steamaudio_wwise_4.6.1.zip` from the [release page](https://github.com/ValveSoftware/steam-audio/releases). 88 | 89 | 2. Locate the two relevant libraries for your target platform and place them in a location listed in [dynamic library search paths](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) (e.g., `/usr/local/lib`). 90 | 91 | 3. Set the `WWISESDK` environment variable to the path of the Wwise SDK installed on your system (e.g. `export WWISESDK="/path/to/Audiokinetic/Wwise2024.1.3.8749/SDK"`). 92 | 93 | 4. Finally, add `audionimbus-sys` with the `wwise` feature enabled to your `Cargo.toml`: 94 | 95 | ```toml 96 | [dependencies] 97 | audionimbus-sys = { version = "4.6.2-fmodwwise.2", features = ["wwise"] } 98 | ``` 99 | 100 | ## Documentation 101 | 102 | Documentation is available at [docs.rs](https://docs.rs/audionimbus-sys/latest). 103 | 104 | Since this crate strictly follows Steam Audio’s C API, you can also refer to the [Steam Audio C API reference](https://valvesoftware.github.io/steam-audio/doc/capi/reference.html) for additional details. 105 | 106 | Note that because the Wwise integration depends on files that are local to your system, documentation for the `wwise` module is not available on docs.rs. 107 | However, it can be generated locally using `cargo doc --open --features wwise`. 108 | 109 | ## License 110 | 111 | `audionimbus-sys` is dual-licensed under the [MIT License](LICENSE-MIT) and the [Apache-2.0 License](LICENSE-APACHE). 112 | You may choose either license when using the software. 113 | -------------------------------------------------------------------------------- /audionimbus-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | const PHONON_HEADER_PATH: &str = "steam-audio/core/src/core/phonon.h"; 4 | 5 | fn main() { 6 | println!("cargo::rerun-if-changed=steam-audio"); 7 | 8 | let out_dir_path = std::env::var("OUT_DIR").unwrap(); 9 | let out_dir = Path::new(&out_dir_path); 10 | 11 | let version = version(); 12 | 13 | generate_bindings_phonon(&out_dir.join("phonon.rs"), &version, out_dir); 14 | 15 | #[cfg(feature = "fmod")] 16 | generate_bindings_phonon_fmod(&out_dir.join("phonon_fmod.rs"), &version, out_dir); 17 | 18 | #[cfg(feature = "wwise")] 19 | generate_bindings_phonon_wwise(&out_dir.join("phonon_wwise.rs"), &version, out_dir); 20 | } 21 | 22 | fn generate_bindings_phonon(output_path: &Path, version: &Version, tmp_dir: &Path) { 23 | println!("cargo:rustc-link-lib=phonon"); 24 | 25 | let _phonon_header_guard = 26 | temporary_version_header(&tmp_dir.join("phonon_version.h"), version, "STEAMAUDIO"); 27 | 28 | let bindings = bindgen::Builder::default() 29 | .header(PHONON_HEADER_PATH) 30 | .clang_arg(format!("-I{}", tmp_dir.display())) 31 | .clang_args(system_flags()) 32 | .rustified_enum(".*") 33 | .bitfield_enum(".*Flags") 34 | .generate() 35 | .unwrap(); 36 | 37 | bindings.write_to_file(output_path).unwrap(); 38 | } 39 | 40 | #[cfg(feature = "fmod")] 41 | fn generate_bindings_phonon_fmod(output_path: &Path, version: &Version, tmp_dir: &Path) { 42 | const PHONON_FMOD_HEADER_PATH: &str = "steam-audio/fmod/src/steamaudio_fmod.h"; 43 | 44 | println!("cargo:rustc-link-lib=phonon_fmod"); 45 | 46 | let _phonon_header_guard = 47 | temporary_version_header(&tmp_dir.join("phonon_version.h"), version, "STEAMAUDIO"); 48 | 49 | let _phonon_fmod_header_guard = temporary_version_header( 50 | &tmp_dir.join("steamaudio_fmod_version.h"), 51 | version, 52 | "STEAMAUDIO_FMOD", 53 | ); 54 | 55 | let phonon_header = Path::new(PHONON_HEADER_PATH); 56 | let phonon_header_dir = phonon_header.parent().unwrap(); 57 | 58 | let bindings = bindgen::Builder::default() 59 | .header(PHONON_FMOD_HEADER_PATH) 60 | .clang_args(&[ 61 | String::from("-xc++"), 62 | String::from("-std=c++14"), 63 | format!("-I{}", tmp_dir.display()), 64 | format!("-I{}", phonon_header_dir.display()), 65 | format!("-I{}", "steam-audio/fmod/include"), 66 | ]) 67 | .clang_args(system_flags()) 68 | .rustified_enum(".*") 69 | .bitfield_enum(".*Flags") 70 | .blocklist_type("_?IPL.*") 71 | .allowlist_function("iplFMOD.*") 72 | .generate() 73 | .unwrap(); 74 | 75 | bindings.write_to_file(output_path).unwrap(); 76 | } 77 | 78 | #[cfg(feature = "wwise")] 79 | fn generate_bindings_phonon_wwise(output_path: &Path, version: &Version, tmp_dir: &Path) { 80 | const PHONON_WWISE_HEADER_PATH: &str = 81 | "steam-audio/wwise/src/SoundEnginePlugin/SteamAudioCommon.h"; 82 | 83 | let wwise_sdk = std::env::var("WWISESDK").expect("env var WWISESDK not set"); 84 | let wwise_includes = Path::new(&wwise_sdk).join("include"); 85 | 86 | println!("cargo:rustc-link-lib=SteamAudioWwise"); 87 | 88 | let _phonon_header_guard = 89 | temporary_version_header(&tmp_dir.join("phonon_version.h"), version, "STEAMAUDIO"); 90 | 91 | let _phonon_wwise_header_guard = temporary_version_header( 92 | &tmp_dir.join("SteamAudioVersion.h"), 93 | version, 94 | "STEAMAUDIO_WWISE", 95 | ); 96 | 97 | let phonon_header = Path::new(PHONON_HEADER_PATH); 98 | let phonon_header_dir = phonon_header.parent().unwrap(); 99 | 100 | let bindings = bindgen::Builder::default() 101 | .header(PHONON_WWISE_HEADER_PATH) 102 | .clang_args(&[ 103 | String::from("-xc++"), 104 | String::from("-std=c++14"), 105 | format!("-I{}", tmp_dir.display()), 106 | format!("-I{}", phonon_header_dir.display()), 107 | format!("-I{}", wwise_includes.display()), 108 | ]) 109 | .clang_args(system_flags()) 110 | .rustified_enum(".*") 111 | .bitfield_enum(".*Flags") 112 | .allowlist_recursively(false) 113 | .allowlist_type("IPLWwise.*") 114 | .allowlist_type("AkGameObjectID") 115 | .allowlist_type("AkUInt64") 116 | .allowlist_function("iplWwise.*") 117 | .generate() 118 | .unwrap(); 119 | 120 | bindings.write_to_file(output_path).unwrap(); 121 | } 122 | 123 | struct Version { 124 | major: u32, 125 | minor: u32, 126 | patch: u32, 127 | } 128 | 129 | fn version() -> Version { 130 | let major = std::env::var("CARGO_PKG_VERSION_MAJOR") 131 | .unwrap() 132 | .parse::() 133 | .unwrap(); 134 | 135 | let minor = std::env::var("CARGO_PKG_VERSION_MINOR") 136 | .unwrap() 137 | .parse::() 138 | .unwrap(); 139 | 140 | let patch = std::env::var("CARGO_PKG_VERSION_PATCH") 141 | .unwrap() 142 | .parse::() 143 | .unwrap(); 144 | 145 | // TODO: remove statement upon release of Steam Audio v4.6.2. 146 | // The version of audionimbus-sys is temporarily ahead of Steam Audio's 147 | // to allow for the introduction of new features, so we need to explicitly 148 | // pin the version. 149 | let patch = 1; 150 | 151 | Version { 152 | major, 153 | minor, 154 | patch, 155 | } 156 | } 157 | 158 | fn temporary_version_header(path: &Path, version: &Version, prefix: &str) -> TemporaryFileGuard { 159 | let packed_version = (version.major << 16) | (version.minor << 8) | version.patch; 160 | let version_header = format!( 161 | r#" 162 | #ifndef IPL_PHONON_VERSION_H 163 | #define IPL_PHONON_VERSION_H 164 | 165 | #define {prefix}_VERSION_MAJOR {} 166 | #define {prefix}_VERSION_MINOR {} 167 | #define {prefix}_VERSION_PATCH {} 168 | #define {prefix}_VERSION {packed_version} 169 | 170 | #endif 171 | "#, 172 | version.major, version.minor, version.patch, 173 | ); 174 | std::fs::write(path, version_header).unwrap(); 175 | 176 | TemporaryFileGuard(path.to_path_buf()) 177 | } 178 | 179 | // The file this guard points to gets removed when the guard goes out of scope. 180 | struct TemporaryFileGuard(PathBuf); 181 | 182 | impl Drop for TemporaryFileGuard { 183 | fn drop(&mut self) { 184 | let _ = std::fs::remove_file(&self.0); 185 | } 186 | } 187 | 188 | fn system_flags() -> Vec { 189 | let mut flags = vec![]; 190 | 191 | if cfg!(target_os = "windows") { 192 | flags.push("-DIPL_OS_WINDOWS"); 193 | } else if cfg!(target_os = "linux") { 194 | flags.push("-DIPL_OS_LINUX"); 195 | } else if cfg!(target_os = "macos") { 196 | flags.push("-DIPL_OS_MACOSX"); 197 | } else if cfg!(target_os = "android") { 198 | flags.push("-DIPL_OS_ANDROID"); 199 | } else if cfg!(target_os = "ios") { 200 | flags.push("-DIPL_OS_IOS"); 201 | } else if cfg!(target_family = "wasm") { 202 | flags.push("-DIPL_OS_WASM"); 203 | } 204 | 205 | if cfg!(target_os = "windows") || cfg!(target_os = "linux") { 206 | if cfg!(target_pointer_width = "64") { 207 | flags.push("-DIPL_CPU_X64"); 208 | } else if cfg!(target_pointer_width = "32") { 209 | flags.push("-DIPL_CPU_X86"); 210 | } 211 | } else if cfg!(target_os = "macos") { 212 | } else if cfg!(target_os = "android") { 213 | if std::env::var("TARGET").unwrap().contains("armv8") { 214 | flags.push("-DIPL_CPU_ARMV8"); 215 | } else if cfg!(target_arch = "arm") { 216 | flags.push("-DIPL_CPU_ARMV7"); 217 | } else if cfg!(target_arch = "x86") { 218 | flags.push("-DIPL_CPU_X86"); 219 | } else if cfg!(target_arch = "x86_64") { 220 | flags.push("-DIPL_CPU_X64"); 221 | } 222 | } else if cfg!(target_os = "ios") { 223 | flags.push("-DIPL_CPU_ARMV8"); 224 | } else if cfg!(target_family = "wasm") { 225 | flags.push("-DIPL_CPU_ARMV7"); 226 | } 227 | 228 | flags.into_iter().map(|s| s.to_string()).collect() 229 | } 230 | -------------------------------------------------------------------------------- /audionimbus-sys/src/fmod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | FMOD Studio integration. 3 | */ 4 | 5 | #![allow( 6 | non_camel_case_types, 7 | non_snake_case, 8 | non_upper_case_globals, 9 | rustdoc::broken_intra_doc_links 10 | )] 11 | 12 | use crate::phonon::*; 13 | 14 | include!(concat!(env!("OUT_DIR"), "/phonon_fmod.rs")); 15 | -------------------------------------------------------------------------------- /audionimbus-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # audionimbus-sys 3 | 4 | Rust bindings to the [Steam Audio](https://valvesoftware.github.io/steam-audio/) library. 5 | This crate is not meant to be used directly; most users should use [`audionimbus`](https://github.com/MaxenceMaire/audionimbus/tree/master/audionimbus), a safe wrapper built on top of `audionimbus-sys`. 6 | 7 | ## Overview 8 | 9 | `audionimbus-sys` exposes raw bindings to the Steam Audio C library. 10 | It is inherently unsafe, as it interfaces with external C code; for a safe API, refer to [`audionimbus`](https://github.com/MaxenceMaire/audionimbus/tree/master/audionimbus). 11 | 12 | ## Version compatibility 13 | 14 | `audionimbus-sys` mirrors the version of Steam Audio. 15 | 16 | ## Installation 17 | 18 | Before installation, make sure that Clang 9.0 or later is installed on your system. 19 | 20 | `audionimbus-sys` requires linking against the Steam Audio library during compilation. 21 | 22 | To do so, download `steamaudio_4.6.1.zip` from the [release page](https://github.com/ValveSoftware/steam-audio/releases). 23 | 24 | Locate the relevant library for your target platform (`SDKROOT` refers to the directory in which you extracted the zip file): 25 | 26 | | Platform | Library Directory | Library To Link | 27 | | --- | --- | --- | 28 | | Windows 32-bit | `SDKROOT/lib/windows-x86` | `phonon.dll` | 29 | | Windows 64-bit | `SDKROOT/lib/windows-x64` | `phonon.dll` | 30 | | Linux 32-bit | `SDKROOT/lib/linux-x86` | `libphonon.so` | 31 | | Linux 64-bit | `SDKROOT/lib/linux-x64` | `libphonon.so` | 32 | | macOS | `SDKROOT/lib/osx` | `libphonon.dylib` | 33 | | Android ARMv7 | `SDKROOT/lib/android-armv7` | `libphonon.so` | 34 | | Android ARMv8/AArch64 | `SDKROOT/lib/android-armv8` | `libphonon.so` | 35 | | Android x86 | `SDKROOT/lib/android-x86` | `libphonon.so` | 36 | | Android x64 | `SDKROOT/lib/android-x64` | `libphonon.so` | 37 | | iOS ARMv8/AArch64 | `SDKROOT/lib/ios` | `libphonon.a` | 38 | 39 | Ensure the library is placed in a location listed in the [dynamic library search paths](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) (e.g., `/usr/local/lib`). 40 | 41 | Finally, add `audionimbus-sys` to your `Cargo.toml`: 42 | 43 | ```toml 44 | [dependencies] 45 | audionimbus-sys = "4.6.2-fmodwwise.2" 46 | ``` 47 | 48 | ## FMOD Studio Integration 49 | 50 | `audionimbus-sys` can be used to add spatial audio to an FMOD Studio project. 51 | 52 | It requires linking against both the Steam Audio library and the FMOD integration library during compilation: 53 | 54 | 1. Download `steamaudio_fmod_4.6.1.zip` from the [release page](https://github.com/ValveSoftware/steam-audio/releases). 55 | 56 | 2. Locate the two relevant libraries for your target platform (`SDKROOT` refers to the directory in which you extracted the zip file): 57 | 58 | | Platform | Library Directory | Library To Link | 59 | | --- | --- | --- | 60 | | Windows 32-bit | `SDKROOT/lib/windows-x86` | `phonon.dll`, `phonon_fmod.dll` | 61 | | Windows 64-bit | `SDKROOT/lib/windows-x64` | `phonon.dll`, `phonon_fmod.dll` | 62 | | Linux 32-bit | `SDKROOT/lib/linux-x86` | `libphonon.so`, `libphonon_fmod.so` | 63 | | Linux 64-bit | `SDKROOT/lib/linux-x64` | `libphonon.so`, `libphonon_fmod.so` | 64 | | macOS | `SDKROOT/lib/osx` | `libphonon.dylib`, `libphonon_fmod.dylib` | 65 | | Android ARMv7 | `SDKROOT/lib/android-armv7` | `libphonon.so`, `libphonon_fmod.so` | 66 | | Android ARMv8/AArch64 | `SDKROOT/lib/android-armv8` | `libphonon.so`, `libphonon_fmod.so` | 67 | | Android x86 | `SDKROOT/lib/android-x86` | `libphonon.so`, `libphonon_fmod.so` | 68 | | Android x64 | `SDKROOT/lib/android-x64` | `libphonon.so`, `libphonon_fmod.so` | 69 | | iOS ARMv8/AArch64 | `SDKROOT/lib/ios` | `libphonon.a`, `libphonon_fmod.a` | 70 | 71 | 3. Ensure the libraries are placed in a location listed in the [dynamic library search paths](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) (e.g., `/usr/local/lib`). 72 | 73 | 4. Finally, add `audionimbus-sys` with the `fmod` feature enabled to your `Cargo.toml`: 74 | 75 | ```toml 76 | [dependencies] 77 | audionimbus-sys = { version = "4.6.2-fmodwwise.2", features = ["fmod"] } 78 | ``` 79 | 80 | ## Wwise Integration 81 | 82 | `audionimbus-sys` can be used to add spatial audio to a Wwise project. 83 | 84 | It requires linking against both the Steam Audio library and the Wwise integration library during compilation: 85 | 86 | 1. Download `steamaudio_wwise_4.6.1.zip` from the [release page](https://github.com/ValveSoftware/steam-audio/releases). 87 | 88 | 2. Locate the two relevant libraries for your target platform and place them in a location listed in [dynamic library search paths](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) (e.g., `/usr/local/lib`). 89 | 90 | 3. Set the `WWISESDK` environment variable to the path of the Wwise SDK installed on your system (e.g. `export WWISESDK="/path/to/Audiokinetic/Wwise2024.1.3.8749/SDK"`). 91 | 92 | 4. Finally, add `audionimbus-sys` with the `wwise` feature enabled to your `Cargo.toml`: 93 | 94 | ```toml 95 | [dependencies] 96 | audionimbus-sys = { version = "4.6.2-fmodwwise.2", features = ["wwise"] } 97 | ``` 98 | 99 | ## Documentation 100 | 101 | Documentation is available at [docs.rs](https://docs.rs/audionimbus-sys/latest). 102 | 103 | Since this crate strictly follows Steam Audio’s C API, you can also refer to the [Steam Audio C API reference](https://valvesoftware.github.io/steam-audio/doc/capi/reference.html) for additional details. 104 | 105 | Note that because the Wwise integration depends on files that are local to your system, documentation for the `wwise` module is not available on docs.rs. 106 | However, it can be generated locally using `cargo doc --open --features wwise`. 107 | 108 | ## License 109 | 110 | `audionimbus-sys` is dual-licensed under the [MIT License](https://github.com/MaxenceMaire/audionimbus/blob/master/LICENSE-MIT) and the [Apache-2.0 License](https://github.com/MaxenceMaire/audionimbus/blob/master/LICENSE-APACHE). 111 | You may choose either license when using the software. 112 | */ 113 | 114 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 115 | 116 | mod phonon; 117 | pub use phonon::*; 118 | 119 | #[cfg(feature = "fmod")] 120 | pub mod fmod; 121 | #[cfg(feature = "fmod")] 122 | pub use fmod::*; 123 | 124 | #[cfg(feature = "wwise")] 125 | pub mod wwise; 126 | #[cfg(feature = "wwise")] 127 | pub use wwise::*; 128 | -------------------------------------------------------------------------------- /audionimbus-sys/src/phonon.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | non_camel_case_types, 3 | non_snake_case, 4 | non_upper_case_globals, 5 | rustdoc::broken_intra_doc_links 6 | )] 7 | include!(concat!(env!("OUT_DIR"), "/phonon.rs")); 8 | -------------------------------------------------------------------------------- /audionimbus-sys/src/wwise.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Wwise integration. 3 | */ 4 | 5 | #![allow( 6 | non_camel_case_types, 7 | non_snake_case, 8 | non_upper_case_globals, 9 | rustdoc::broken_intra_doc_links 10 | )] 11 | 12 | use crate::phonon::*; 13 | 14 | include!(concat!(env!("OUT_DIR"), "/phonon_wwise.rs")); 15 | -------------------------------------------------------------------------------- /audionimbus/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.6.4] - 2025-04-16 4 | 5 | ### Fixed 6 | 7 | - Dependency `audionimbus-sys` would fail to compile on some platforms when feature `fmod` was enabled, due to missing flags. It has been updated to version `4.6.2-fmodwwise.2`. 8 | 9 | ## [0.6.3] - 2025-04-15 10 | 11 | ### Added 12 | 13 | - Support for the Steam Audio Wwise integration. 14 | 15 | ## [0.6.2] - 2025-04-14 16 | 17 | ### Added 18 | 19 | - Documentation generation for feature `fmod`. 20 | 21 | ### Fixed 22 | 23 | - Version `4.6.2-fmod.2` of dependency `audionimbus-sys` would fail to compile when feature `fmod` was enabled. It has been updated to version `4.6.2-fmod.3`. 24 | 25 | ## [0.6.1] - 2025-04-13 26 | 27 | ### Fixed 28 | 29 | - Version `4.6.2-fmod` of dependency `audionimbus-sys` would prevent the documentation from being generated. It has been updated to version `4.6.2-fmod.1`. 30 | 31 | ## [0.6.0] - 2025-04-13 32 | 33 | ### Added 34 | 35 | - Support for the Steam Audio FMOD integration. 36 | 37 | ## [0.5.1] - 2025-04-12 38 | 39 | ### Added 40 | 41 | - Support for Steam Audio v.4.6.1 via `audionimbus-sys` v.4.6.1. 42 | 43 | ## [0.5.0] - 2025-04-10 44 | 45 | ### Fixed 46 | 47 | - Casting `u32` flags into `std::os::raw::c_uint` would fail on systems expecting `std::os::raw::c_int` IPL flags. 48 | 49 | ## [0.4.0] - 2025-04-01 50 | 51 | ### Added 52 | 53 | - Implement `mix`, `downmix`, `convert_ambisonics`, `convert_ambisonics_into` methods for `AudioBuffer`. 54 | - Implement `tail`, `tail_size` methods for `PanningEffect`, `PathEffect`, `AmbisonicsBinauralEffect`, `AmbisonicsDecodeEffect`, `AmbisonicsDecodeEffect`, `AmbisonicsPanningEffect`, `AmbisonicsRotationEffect`, `BinauralEffect`, `DirectEffect`, `DirectEffect`, `PathEffect`, `ReflectionEffect`, `VirtualSurroundEffect`. 55 | - Implement `tail_into_mixer` for `ReflectionEffect`. 56 | - Implement `save_obj` for `Scene`. 57 | 58 | ### Fixed 59 | 60 | - Using `i32` instead of `c_uint` in some places would fail to compile on some platforms. 61 | 62 | ## [0.3.0] - 2025-03-23 63 | 64 | ### Added 65 | 66 | - Implement `Send` trait for `Scene`. 67 | - Add assertion to enforce the number of channels in the input buffer when applying the reflection effect or the ambisonics encode effect. 68 | 69 | ### Changed 70 | 71 | - Refactor `SimulationInputs`, `SimulationSettings`. 72 | - Change signatures of `Scene::add_static_mesh`, `Scene::remove_static_mesh`, `Scene::add_instanced_mesh`, `Scene::remove_instanced_mesh`, `Scene::update_instanced_mesh_transform`, `Scene::commit` to take `&mut self` instead of `&self`. 73 | - Remove fields that can be inferred from `StaticMeshSettings`, borrow slices instead of owning data. 74 | 75 | ### Fixed 76 | 77 | - `SimulationOutputs` would remain zeroed. 78 | - `DirectEffectParams` fields would always be `None` since `IPLDirectEffectParams.flags` is not set when retrieving simulation results for a source. 79 | 80 | ## [0.2.0] - 2025-03-17 81 | 82 | ### Changed 83 | 84 | - Made inner fields of `OpenClDeviceList`, `AmbisonicsBinauralEffect`, `AmbisonicsDecodeEffect`, `AmbisonicsEncodeEffect`, `AmbisonicsPanningEffect`, `AmbisonicsRotationEffect`, `DirectEffect` private. 85 | 86 | ### Fixed 87 | 88 | - Order assertions of ambisonics effects. 89 | 90 | ## [0.1.0] - 2025-03-08 91 | 92 | ### Added 93 | 94 | - Initial version supporting Steam Audio 4.6.0. 95 | - First public release. 96 | -------------------------------------------------------------------------------- /audionimbus/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "audionimbus" 3 | description = "A safe wrapper around Steam Audio that provides spatial audio capabilities with realistic occlusion, reverb, and HRTF effects, accounting for physical attributes and scene geometry." 4 | version = "0.6.4" 5 | edition = "2021" 6 | authors = ["Maxence Maire "] 7 | license = "MIT OR Apache-2.0" 8 | keywords = ["gamedev", "game", "audio", "engine", "steam"] 9 | categories = ["game-development", "game-engines", "multimedia::audio", "simulation", "api-bindings"] 10 | repository = "https://github.com/MaxenceMaire/audionimbus" 11 | readme = "README.md" 12 | 13 | [dependencies] 14 | audionimbus-sys = { version = "4.6.2-fmodwwise.2", path = "../audionimbus-sys" } 15 | bitflags = "2.8.0" 16 | 17 | [features] 18 | fmod = ["audionimbus-sys/fmod"] 19 | wwise = ["audionimbus-sys/wwise"] 20 | 21 | [package.metadata.docs.rs] 22 | features = ["fmod"] 23 | rustdoc-args = ["--cfg", "docsrs"] 24 | -------------------------------------------------------------------------------- /audionimbus/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /audionimbus/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /audionimbus/README.md: -------------------------------------------------------------------------------- 1 | # audionimbus 2 | 3 | A Rust wrapper around [Steam Audio](https://valvesoftware.github.io/steam-audio/) that provides spatial audio capabilities with realistic occlusion, reverb, and HRTF effects, accounting for physical attributes and scene geometry. 4 | 5 | ## Overview 6 | 7 | `audionimbus` simplifies the integration of Steam Audio into Rust projects by offering a safe, idiomiatic API. 8 | 9 | It builds upon [`audionimbus-sys`](../audionimbus-sys), which provides raw bindings to the Steam Audio C API. 10 | 11 | To experience AudioNimbus in action, play the [interactive demo](https://github.com/MaxenceMaire/audionimbus-demo) or watch the [walkthrough video](https://www.youtube.com/watch?v=zlhW1maG0Is). 12 | 13 | `audionimbus` can also integrate with FMOD studio. 14 | 15 | ## Version compatibility 16 | 17 | `audionimbus` currently tracks Steam Audio 4.6.1. 18 | 19 | Unlike `audionimbus-sys`, which mirrors Steam Audio's versioning, `audionimbus` introduces its own abstractions and is subject to breaking changes. 20 | As a result, it uses independent versioning. 21 | 22 | ## Installation 23 | 24 | Before installation, make sure that Clang 9.0 or later is installed on your system. 25 | 26 | `audionimbus` requires linking against the Steam Audio library during compilation. 27 | 28 | To do so, download `steamaudio_4.6.1.zip` from the [release page](https://github.com/ValveSoftware/steam-audio/releases). 29 | 30 | Locate the relevant library for your target platform (`SDKROOT` refers to the directory in which you extracted the zip file): 31 | 32 | | Platform | Library Directory | Library To Link | 33 | | --- | --- | --- | 34 | | Windows 32-bit | `SDKROOT/lib/windows-x86` | `phonon.dll` | 35 | | Windows 64-bit | `SDKROOT/lib/windows-x64` | `phonon.dll` | 36 | | Linux 32-bit | `SDKROOT/lib/linux-x86` | `libphonon.so` | 37 | | Linux 64-bit | `SDKROOT/lib/linux-x64` | `libphonon.so` | 38 | | macOS | `SDKROOT/lib/osx` | `libphonon.dylib` | 39 | | Android ARMv7 | `SDKROOT/lib/android-armv7` | `libphonon.so` | 40 | | Android ARMv8/AArch64 | `SDKROOT/lib/android-armv8` | `libphonon.so` | 41 | | Android x86 | `SDKROOT/lib/android-x86` | `libphonon.so` | 42 | | Android x64 | `SDKROOT/lib/android-x64` | `libphonon.so` | 43 | | iOS ARMv8/AArch64 | `SDKROOT/lib/ios` | `libphonon.a` | 44 | 45 | Ensure the library is placed in a location listed in the [dynamic library search paths](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) (e.g., `/usr/local/lib`). 46 | 47 | Finally, add `audionimbus` to your `Cargo.toml`: 48 | 49 | ```toml 50 | [dependencies] 51 | audionimbus = "0.6.4" 52 | ``` 53 | 54 | ## Example 55 | 56 | This example demonstrates how to spatialize sound using the `audionimbus` library: 57 | 58 | ```rust 59 | use audionimbus::*; 60 | 61 | // Initialize the audio context. 62 | let context = Context::try_new(&ContextSettings::default()).unwrap(); 63 | 64 | let audio_settings = AudioSettings { 65 | sampling_rate: 48000, 66 | frame_size: 1024, 67 | }; 68 | 69 | // Set up HRTF for binaural rendering. 70 | let hrtf = Hrtf::try_new(&context, &audio_settings, &HrtfSettings::default()).unwrap(); 71 | 72 | // Create a binaural effect. 73 | let binaural_effect = BinauralEffect::try_new( 74 | &context, 75 | &audio_settings, 76 | &BinauralEffectSettings { hrtf: &hrtf }, 77 | ) 78 | .unwrap(); 79 | 80 | // Generate an input frame (in thise case, a single-channel sine wave). 81 | let input: Vec = (0..audio_settings.frame_size) 82 | .map(|i| { 83 | (i as f32 * 2.0 * std::f32::consts::PI * 440.0 / audio_settings.sampling_rate as f32) 84 | .sin() 85 | }) 86 | .collect(); 87 | // Create an audio buffer over the input data. 88 | let input_buffer = AudioBuffer::try_with_data(&input).unwrap(); 89 | 90 | let num_channels: usize = 2; // Stereo 91 | // Allocate memory to store processed samples. 92 | let mut output = vec![0.0; audio_settings.frame_size * num_channels]; 93 | // Create another audio buffer over the output container. 94 | let output_buffer = AudioBuffer::try_with_data_and_settings( 95 | &mut output, 96 | &AudioBufferSettings { 97 | num_channels: Some(num_channels), 98 | ..Default::default() 99 | }, 100 | ) 101 | .unwrap(); 102 | 103 | // Apply a binaural audio effect. 104 | let binaural_effect_params = BinauralEffectParams { 105 | direction: Direction::new( 106 | 1.0, // Right 107 | 0.0, // Up 108 | 0.0, // Behind 109 | ), 110 | interpolation: HrtfInterpolation::Nearest, 111 | spatial_blend: 1.0, 112 | hrtf: &hrtf, 113 | peak_delays: None, 114 | }; 115 | let _effect_state = 116 | binaural_effect.apply(&binaural_effect_params, &input_buffer, &output_buffer); 117 | 118 | // `output` now contains the processed samples in a deinterleaved format (i.e., left channel 119 | // samples followed by right channel samples). 120 | 121 | // Note: most audio engines expect interleaved audio (alternating samples for each channel). If 122 | // required, use the `AudioBuffer::interleave` method to convert the format. 123 | ``` 124 | 125 | To implement real-time audio processing and playback in your game, check out the [demo crate](./demo) for a basic example. 126 | 127 | For a complete demonstration featuring HRTF, Ambisonics, reflections and reverb in an interactive environment, see the [AudioNimbus Interactive Demo repository](https://github.com/MaxenceMaire/audionimbus-demo). 128 | 129 | For additional examples, you can explore the [tests](./tests), which closely follow [Steam Audio's Programmer's Guide](https://valvesoftware.github.io/steam-audio/doc/capi/guide.html). 130 | 131 | ## FMOD Studio Integration 132 | 133 | `audionimbus` can be used to add spatial audio to an FMOD Studio project. 134 | 135 | It requires linking against both the Steam Audio library and the FMOD integration library during compilation: 136 | 137 | 1. Download `steamaudio_fmod_4.6.1.zip` from the [release page](https://github.com/ValveSoftware/steam-audio/releases). 138 | 139 | 2. Locate the two relevant libraries for your target platform (`SDKROOT` refers to the directory in which you extracted the zip file): 140 | 141 | | Platform | Library Directory | Library To Link | 142 | | --- | --- | --- | 143 | | Windows 32-bit | `SDKROOT/lib/windows-x86` | `phonon.dll`, `phonon_fmod.dll` | 144 | | Windows 64-bit | `SDKROOT/lib/windows-x64` | `phonon.dll`, `phonon_fmod.dll` | 145 | | Linux 32-bit | `SDKROOT/lib/linux-x86` | `libphonon.so`, `libphonon_fmod.so` | 146 | | Linux 64-bit | `SDKROOT/lib/linux-x64` | `libphonon.so`, `libphonon_fmod.so` | 147 | | macOS | `SDKROOT/lib/osx` | `libphonon.dylib`, `libphonon_fmod.dylib` | 148 | | Android ARMv7 | `SDKROOT/lib/android-armv7` | `libphonon.so`, `libphonon_fmod.so` | 149 | | Android ARMv8/AArch64 | `SDKROOT/lib/android-armv8` | `libphonon.so`, `libphonon_fmod.so` | 150 | | Android x86 | `SDKROOT/lib/android-x86` | `libphonon.so`, `libphonon_fmod.so` | 151 | | Android x64 | `SDKROOT/lib/android-x64` | `libphonon.so`, `libphonon_fmod.so` | 152 | | iOS ARMv8/AArch64 | `SDKROOT/lib/ios` | `libphonon.a`, `libphonon_fmod.a` | 153 | 154 | 3. Ensure the libraries are placed in a location listed in the [dynamic library search paths](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) (e.g., `/usr/local/lib`). 155 | 156 | 4. Finally, add `audionimbus` with the `fmod` feature enabled to your `Cargo.toml`: 157 | 158 | ```toml 159 | [dependencies] 160 | audionimbus = { version = "0.6.4", features = ["fmod"] } 161 | ``` 162 | 163 | ## Wwise Integration 164 | 165 | `audionimbus` can be used to add spatial audio to a Wwise project. 166 | 167 | It requires linking against both the Steam Audio library and the Wwise integration library during compilation: 168 | 169 | 1. Download `steamaudio_wwise_4.6.1.zip` from the [release page](https://github.com/ValveSoftware/steam-audio/releases). 170 | 171 | 2. Locate the two relevant libraries for your target platform and place them in a location listed in [dynamic library search paths](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) (e.g., `/usr/local/lib`). 172 | 173 | 3. Set the `WWISESDK` environment variable to the path of the Wwise SDK installed on your system (e.g. `export WWISESDK="/path/to/Audiokinetic/Wwise2024.1.3.8749/SDK"`). 174 | 175 | 4. Finally, add `audionimbus` with the `wwise` feature enabled to your `Cargo.toml`: 176 | 177 | ```toml 178 | [dependencies] 179 | audionimbus = { version = "0.6.4", features = ["wwise"] } 180 | ``` 181 | 182 | ## Documentation 183 | 184 | Documentation is available at [doc.rs](https://docs.rs/audionimbus/latest). 185 | 186 | For more details on Steam Audio's concepts, see the [Steam Audio SDK documentation](https://valvesoftware.github.io/steam-audio/doc/capi/index.html). 187 | 188 | Note that because the Wwise integration depends on files that are local to your system, documentation for the `wwise` module is not available on docs.rs. 189 | However, it can be generated locally using `cargo doc --open --features wwise`. 190 | 191 | ## License 192 | 193 | `audionimbus` is dual-licensed under the [MIT License](LICENSE-MIT) and the [Apache-2.0 License](LICENSE-APACHE). 194 | You may choose either license when using the software. 195 | -------------------------------------------------------------------------------- /audionimbus/demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demo" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Maxence Maire "] 6 | 7 | [dependencies] 8 | audionimbus = { path = ".." } 9 | cpal = "0.15.3" 10 | -------------------------------------------------------------------------------- /audionimbus/demo/README.md: -------------------------------------------------------------------------------- 1 | # Audionimbus Demo 2 | 3 | This crate demonstrates how to get started with the [`audionimbus`](..) library, a Rust wrapper around Steam Audio. 4 | 5 | The demo generates a sine wave tone and simulates the sound source circling around the listener by applying a HRTF-based binaural effect in real-time. 6 | -------------------------------------------------------------------------------- /audionimbus/demo/src/main.rs: -------------------------------------------------------------------------------- 1 | use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; 2 | 3 | fn main() { 4 | // Initialize CPAL. 5 | let host = cpal::default_host(); 6 | let device = host 7 | .default_output_device() 8 | .expect("no output device available"); 9 | 10 | let frame_size: usize = 1024; 11 | let sample_rate: usize = 48000; 12 | let num_channels: usize = 2; 13 | 14 | let config = cpal::StreamConfig { 15 | buffer_size: cpal::BufferSize::Fixed(frame_size as u32), 16 | sample_rate: cpal::SampleRate(sample_rate as u32), 17 | channels: num_channels as u16, 18 | }; 19 | 20 | // Initialize the audio context. 21 | let context = audionimbus::Context::try_new(&audionimbus::ContextSettings::default()).unwrap(); 22 | 23 | let audio_settings = audionimbus::AudioSettings { 24 | sampling_rate: sample_rate, 25 | frame_size, 26 | }; 27 | 28 | // Set up HRTF for binaural rendering. 29 | let hrtf = audionimbus::Hrtf::try_new( 30 | &context, 31 | &audio_settings, 32 | &audionimbus::HrtfSettings::default(), 33 | ) 34 | .unwrap(); 35 | 36 | // Create a binaural effect. 37 | let binaural_effect = audionimbus::BinauralEffect::try_new( 38 | &context, 39 | &audio_settings, 40 | &audionimbus::BinauralEffectSettings { hrtf: &hrtf }, 41 | ) 42 | .unwrap(); 43 | 44 | // Parameters of the generated sine wave. 45 | let frequency = 440.0; // Frequency of the sine wave, in Hz. 46 | let amplitude = 0.5; // Amplitude of the sine wave. 47 | let mut phase: f32 = 0.0; // Phase of the sine wave. 48 | let phase_increment = 2.0 * std::f32::consts::PI * frequency / sample_rate as f32; 49 | let delta_time = frame_size as f32 / sample_rate as f32; // Duration of a frame, in seconds. 50 | let speed = 5.0; // Speed of the sound source, in m/s. 51 | let distance_traveled = speed * delta_time; // Distance traveled over a frame. 52 | let radius = 1.0; // Radius of the sound source's circular path, in meters. 53 | let mut angle = 0.0; // Direction angle of the sound source. 54 | 55 | let stream = device 56 | .build_output_stream( 57 | &config, 58 | move |output: &mut [audionimbus::Sample], _: &cpal::OutputCallbackInfo| { 59 | // Generate the sine wave for this frame. 60 | let sine_wave: Vec = (0..frame_size) 61 | .map(|_| { 62 | let sample = amplitude * phase.sin(); 63 | phase = (phase + phase_increment) % (2.0 * std::f32::consts::PI); 64 | sample 65 | }) 66 | .collect(); 67 | 68 | let input_buffer = audionimbus::AudioBuffer::try_with_data(&sine_wave).unwrap(); 69 | 70 | // Container the effect will write processed samples into. 71 | let mut staging_container = vec![0.0; frame_size * num_channels]; 72 | let staging_buffer = audionimbus::AudioBuffer::try_with_data_and_settings( 73 | &mut staging_container, 74 | &audionimbus::AudioBufferSettings { 75 | num_channels: Some(num_channels), 76 | ..Default::default() 77 | }, 78 | ) 79 | .unwrap(); 80 | 81 | // Update the direction of the sound source. 82 | angle = (angle + distance_traveled / radius).rem_euclid(std::f32::consts::TAU); 83 | let x = angle.cos() * radius; 84 | let z = angle.sin() * radius; 85 | let direction = audionimbus::Direction::new(x, 0.0, z); 86 | 87 | let binaural_effect_params = audionimbus::BinauralEffectParams { 88 | direction, 89 | interpolation: audionimbus::HrtfInterpolation::Nearest, 90 | spatial_blend: 1.0, 91 | hrtf: &hrtf, 92 | peak_delays: None, 93 | }; 94 | let _effect_state = 95 | binaural_effect.apply(&binaural_effect_params, &input_buffer, &staging_buffer); 96 | 97 | // Samples are currently deinterleaved (i.e., [L0, L1, ..., R0, R1, ...]), but CPAL 98 | // requires an interleaved format (i.e., [L0, R0, L1, R1, ...]) as output. 99 | staging_buffer.interleave(&context, output); 100 | }, 101 | move |err| eprintln!("an error occurred on the output audio stream: {}", err), 102 | None, 103 | ) 104 | .unwrap(); 105 | 106 | stream.play().unwrap(); 107 | 108 | // Keep the main thread alive. 109 | std::thread::park(); 110 | } 111 | -------------------------------------------------------------------------------- /audionimbus/src/audio_settings.rs: -------------------------------------------------------------------------------- 1 | /// Global settings for audio signal processing. 2 | #[derive(Debug)] 3 | pub struct AudioSettings { 4 | /// Sampling rate, in Hz. 5 | pub sampling_rate: usize, 6 | 7 | /// Frame size, in samples. 8 | /// Independent of number of channels. 9 | pub frame_size: usize, 10 | } 11 | 12 | impl Default for AudioSettings { 13 | fn default() -> Self { 14 | Self { 15 | sampling_rate: 48000, 16 | frame_size: 1024, 17 | } 18 | } 19 | } 20 | 21 | impl From<&AudioSettings> for audionimbus_sys::IPLAudioSettings { 22 | fn from(settings: &AudioSettings) -> Self { 23 | Self { 24 | samplingRate: settings.sampling_rate as i32, 25 | frameSize: settings.frame_size as i32, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /audionimbus/src/callback.rs: -------------------------------------------------------------------------------- 1 | /// A callback function along with associated user data. 2 | #[derive(Debug)] 3 | pub struct CallbackInformation { 4 | /// The callback function. 5 | pub callback: T, 6 | 7 | /// Pointer to arbitrary data that will be provided to the callback function whenever it is called. May be `NULL`. 8 | pub user_data: *mut std::ffi::c_void, 9 | } 10 | 11 | /// Callback for updating the application on the progress of a function. 12 | /// 13 | /// You can use this to provide the user with visual feedback, like a progress bar. 14 | /// 15 | /// # Arguments 16 | /// 17 | /// - `progress`: fraction of the function work that has been completed, between 0.0 and 1.0. 18 | /// - `user_data`: pointer to arbitrary user-specified data provided when calling the function that will call this callback. 19 | pub type ProgressCallback = unsafe extern "C" fn(progress: f32, user_data: *mut std::ffi::c_void); 20 | -------------------------------------------------------------------------------- /audionimbus/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{to_option_error, SteamAudioError}; 2 | use crate::version::SteamAudioVersion; 3 | 4 | /// A context object, which controls low-level operations of Steam Audio. 5 | /// 6 | /// Typically, a context is specified once during the execution of the client program, before calling any other API functions. 7 | #[derive(Debug)] 8 | pub struct Context(pub(crate) audionimbus_sys::IPLContext); 9 | 10 | impl Context { 11 | pub fn try_new(settings: &ContextSettings) -> Result { 12 | let mut context = Self(std::ptr::null_mut()); 13 | 14 | let status = unsafe { 15 | audionimbus_sys::iplContextCreate( 16 | &mut audionimbus_sys::IPLContextSettings::from(settings), 17 | context.raw_ptr_mut(), 18 | ) 19 | }; 20 | 21 | if let Some(error) = to_option_error(status) { 22 | return Err(error); 23 | } 24 | 25 | Ok(context) 26 | } 27 | 28 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLContext { 29 | self.0 30 | } 31 | 32 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLContext { 33 | &mut self.0 34 | } 35 | } 36 | 37 | impl Clone for Context { 38 | fn clone(&self) -> Self { 39 | unsafe { 40 | audionimbus_sys::iplContextRetain(self.0); 41 | } 42 | Self(self.0) 43 | } 44 | } 45 | 46 | impl Drop for Context { 47 | fn drop(&mut self) { 48 | unsafe { audionimbus_sys::iplContextRelease(&mut self.0) } 49 | } 50 | } 51 | 52 | unsafe impl Send for Context {} 53 | unsafe impl Sync for Context {} 54 | 55 | /// Settings used to create a [`Context`]. 56 | pub struct ContextSettings { 57 | /// The API version. 58 | /// 59 | /// Context creation will fail if `phonon.dll` does not implement a compatible version of the API. 60 | /// Typically, this should be set to [`SteamAudioVersion::default()`]. 61 | pub version: SteamAudioVersion, 62 | 63 | /// If `Some`, Steam Audio will call this function to record log messages generated by certain operations. 64 | pub log_callback: Option< 65 | unsafe extern "C" fn( 66 | level: audionimbus_sys::IPLLogLevel, 67 | message: *const std::os::raw::c_char, 68 | ), 69 | >, 70 | 71 | /// If `Some`, Steam Audio will call this function whenever it needs to allocate memory. 72 | pub allocate_callback: 73 | Option *mut std::ffi::c_void>, 74 | 75 | /// If `Some`, Steam Audio will call this function whenever it needs to free memory. 76 | pub free_callback: Option, 77 | 78 | /// The maximum SIMD instruction set level that Steam Audio should use. 79 | /// 80 | /// Steam Audio automatically chooses the best instruction set to use based on the user’s CPU, but you can prevent it from using certain newer instruction sets using this parameter. 81 | /// For example, with some workloads, AVX512 instructions consume enough power that the CPU clock speed will be throttled, resulting in lower performance than expected. 82 | /// If you observe this in your application, set this parameter to IPL_SIMDLEVEL_AVX2 or lower. 83 | pub simd_level: SimdLevel, 84 | 85 | /// Additional flags for modifying the behavior of the created context. 86 | pub flags: ContextFlags, 87 | } 88 | 89 | impl Default for ContextSettings { 90 | fn default() -> Self { 91 | Self { 92 | version: SteamAudioVersion::default(), 93 | log_callback: None, 94 | allocate_callback: None, 95 | free_callback: None, 96 | simd_level: SimdLevel::default(), 97 | flags: ContextFlags::empty(), 98 | } 99 | } 100 | } 101 | 102 | impl From<&ContextSettings> for audionimbus_sys::IPLContextSettings { 103 | fn from(settings: &ContextSettings) -> Self { 104 | Self { 105 | version: settings.version.into(), 106 | logCallback: settings.log_callback, 107 | allocateCallback: settings.allocate_callback, 108 | freeCallback: settings.free_callback, 109 | simdLevel: settings.simd_level.into(), 110 | flags: settings.flags.into(), 111 | } 112 | } 113 | } 114 | 115 | /// SIMD instruction sets that Steam Audio can attempt to use. 116 | #[derive(Debug, Copy, Clone)] 117 | pub enum SimdLevel { 118 | /// Intel Streaming SIMD Extensions 2. 119 | /// Up to 4 simultaneous floating-point operations. 120 | SSE2 = 0, 121 | 122 | /// Intel Streaming SIMD Extensions 4.2 or older. 123 | /// Up to 4 simultaneous floating-point operations. 124 | SSE4 = 1, 125 | 126 | /// Intel Advanced Vector Extensions or older. 127 | /// Up to 8 simultaneous floating-point operations. 128 | AVX = 2, 129 | 130 | /// Intel Advanced Vector Extensions 2 or older. 131 | /// Up to 8 simultaneous floating-point operations. 132 | AVX2 = 3, 133 | 134 | /// Intel Advanced Vector Extensions 512 or older. 135 | /// Up to 16 simultaneous floating-point operations. 136 | AVX512 = 4, 137 | } 138 | 139 | impl Default for SimdLevel { 140 | fn default() -> Self { 141 | Self::AVX512 142 | } 143 | } 144 | 145 | impl From for audionimbus_sys::IPLSIMDLevel { 146 | fn from(simd_level: SimdLevel) -> Self { 147 | match simd_level { 148 | SimdLevel::SSE2 => Self::IPL_SIMDLEVEL_SSE2, 149 | SimdLevel::SSE4 => Self::IPL_SIMDLEVEL_SSE4, 150 | SimdLevel::AVX => Self::IPL_SIMDLEVEL_AVX, 151 | SimdLevel::AVX2 => Self::IPL_SIMDLEVEL_AVX2, 152 | SimdLevel::AVX512 => Self::IPL_SIMDLEVEL_AVX512, 153 | } 154 | } 155 | } 156 | 157 | bitflags::bitflags! { 158 | /// Additional flags for modifying the behavior of a Steam Audio context. 159 | #[derive(Debug, Copy, Clone)] 160 | pub struct ContextFlags: u32 { 161 | /// All API functions perform extra validation checks. 162 | /// NOTE: This imposes a significant performance penalty. 163 | const VALIDATION = 1 << 0; 164 | 165 | /// Force this enum to be 32 bits in size. 166 | const FORCE_32BIT = 1 << 1; 167 | } 168 | } 169 | 170 | impl From for audionimbus_sys::IPLContextFlags { 171 | fn from(context_flags: ContextFlags) -> Self { 172 | Self(context_flags.bits() as _) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /audionimbus/src/device/embree.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::error::{to_option_error, SteamAudioError}; 3 | 4 | /// Application-wide state for the Embree ray tracer. 5 | /// 6 | /// An Embree device must be created before using any of Steam Audio’s Embree ray tracing functionality. 7 | #[derive(Debug)] 8 | pub struct EmbreeDevice(audionimbus_sys::IPLEmbreeDevice); 9 | 10 | impl EmbreeDevice { 11 | pub fn new(context: &Context) -> Result { 12 | let mut embree_device = Self(std::ptr::null_mut()); 13 | 14 | let embree_device_settings: *mut audionimbus_sys::IPLEmbreeDeviceSettings = 15 | std::ptr::null_mut(); 16 | 17 | let status = unsafe { 18 | audionimbus_sys::iplEmbreeDeviceCreate( 19 | context.raw_ptr(), 20 | embree_device_settings, 21 | embree_device.raw_ptr_mut(), 22 | ) 23 | }; 24 | 25 | if let Some(error) = to_option_error(status) { 26 | return Err(error); 27 | } 28 | 29 | Ok(embree_device) 30 | } 31 | 32 | pub fn null() -> Self { 33 | Self(std::ptr::null_mut()) 34 | } 35 | 36 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLEmbreeDevice { 37 | self.0 38 | } 39 | 40 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLEmbreeDevice { 41 | &mut self.0 42 | } 43 | } 44 | 45 | impl Clone for EmbreeDevice { 46 | fn clone(&self) -> Self { 47 | unsafe { 48 | audionimbus_sys::iplEmbreeDeviceRetain(self.0); 49 | } 50 | Self(self.0) 51 | } 52 | } 53 | 54 | impl Drop for EmbreeDevice { 55 | fn drop(&mut self) { 56 | unsafe { audionimbus_sys::iplEmbreeDeviceRelease(&mut self.0) } 57 | } 58 | } 59 | 60 | unsafe impl Send for EmbreeDevice {} 61 | unsafe impl Sync for EmbreeDevice {} 62 | -------------------------------------------------------------------------------- /audionimbus/src/device/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod open_cl; 2 | pub use open_cl::{OpenClDevice, OpenClDeviceList, OpenClDeviceSettings, OpenClDeviceType}; 3 | 4 | pub mod radeon_rays; 5 | pub use radeon_rays::RadeonRaysDevice; 6 | 7 | pub mod embree; 8 | pub use embree::EmbreeDevice; 9 | 10 | pub mod true_audio_next; 11 | pub use true_audio_next::TrueAudioNextDevice; 12 | -------------------------------------------------------------------------------- /audionimbus/src/device/open_cl.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::error::{to_option_error, SteamAudioError}; 3 | 4 | /// Application-wide state for OpenCL. 5 | /// 6 | /// An OpenCL device must be created before using any of Steam Audio’s Radeon Rays or TrueAudio Next functionality. 7 | #[derive(Debug)] 8 | pub struct OpenClDevice(audionimbus_sys::IPLOpenCLDevice); 9 | 10 | impl OpenClDevice { 11 | pub fn new( 12 | context: &Context, 13 | device_list: &OpenClDeviceList, 14 | index: usize, 15 | ) -> Result { 16 | let mut open_cl_device = Self(std::ptr::null_mut()); 17 | 18 | let status = unsafe { 19 | audionimbus_sys::iplOpenCLDeviceCreate( 20 | context.raw_ptr(), 21 | **device_list, 22 | index as i32, 23 | open_cl_device.raw_ptr_mut(), 24 | ) 25 | }; 26 | 27 | if let Some(error) = to_option_error(status) { 28 | return Err(error); 29 | } 30 | 31 | Ok(open_cl_device) 32 | } 33 | 34 | pub fn null() -> Self { 35 | Self(std::ptr::null_mut()) 36 | } 37 | 38 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLOpenCLDevice { 39 | self.0 40 | } 41 | 42 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLOpenCLDevice { 43 | &mut self.0 44 | } 45 | } 46 | 47 | impl Clone for OpenClDevice { 48 | fn clone(&self) -> Self { 49 | unsafe { 50 | audionimbus_sys::iplOpenCLDeviceRetain(self.0); 51 | } 52 | Self(self.0) 53 | } 54 | } 55 | 56 | impl Drop for OpenClDevice { 57 | fn drop(&mut self) { 58 | unsafe { audionimbus_sys::iplOpenCLDeviceRelease(&mut self.0) } 59 | } 60 | } 61 | 62 | unsafe impl Send for OpenClDevice {} 63 | unsafe impl Sync for OpenClDevice {} 64 | 65 | /// Provides a list of OpenCL devices available on the user’s system. 66 | /// 67 | /// Use this to enumerate the available OpenCL devices, inspect their capabilities, and select the most suitable one for your application’s needs. 68 | pub struct OpenClDeviceList(audionimbus_sys::IPLOpenCLDeviceList); 69 | 70 | impl OpenClDeviceList { 71 | pub fn new( 72 | context: &Context, 73 | open_cl_device_settings: &OpenClDeviceSettings, 74 | ) -> Result { 75 | let open_cl_device_list = unsafe { 76 | let open_cl_device_list: *mut audionimbus_sys::IPLOpenCLDeviceList = 77 | std::ptr::null_mut(); 78 | let status = audionimbus_sys::iplOpenCLDeviceListCreate( 79 | context.raw_ptr(), 80 | &mut audionimbus_sys::IPLOpenCLDeviceSettings::from(open_cl_device_settings), 81 | open_cl_device_list, 82 | ); 83 | 84 | if let Some(error) = to_option_error(status) { 85 | return Err(error); 86 | } 87 | 88 | *open_cl_device_list 89 | }; 90 | 91 | Ok(Self(open_cl_device_list)) 92 | } 93 | } 94 | 95 | impl std::ops::Deref for OpenClDeviceList { 96 | type Target = audionimbus_sys::IPLOpenCLDeviceList; 97 | 98 | fn deref(&self) -> &Self::Target { 99 | &self.0 100 | } 101 | } 102 | 103 | impl std::ops::DerefMut for OpenClDeviceList { 104 | fn deref_mut(&mut self) -> &mut Self::Target { 105 | &mut self.0 106 | } 107 | } 108 | 109 | impl Clone for OpenClDeviceList { 110 | fn clone(&self) -> Self { 111 | unsafe { 112 | audionimbus_sys::iplOpenCLDeviceListRetain(self.0); 113 | } 114 | Self(self.0) 115 | } 116 | } 117 | 118 | impl Drop for OpenClDeviceList { 119 | fn drop(&mut self) { 120 | unsafe { audionimbus_sys::iplOpenCLDeviceListRelease(&mut self.0) } 121 | } 122 | } 123 | 124 | unsafe impl Send for OpenClDeviceList {} 125 | unsafe impl Sync for OpenClDeviceList {} 126 | 127 | /// Specifies requirements that an OpenCL device must meet in order to be considered when listing OpenCL devices. 128 | #[derive(Debug)] 129 | pub struct OpenClDeviceSettings { 130 | pub device_type: OpenClDeviceType, 131 | 132 | /// The number of GPU compute units (CUs) that should be reserved for use by Steam Audio. 133 | /// 134 | /// If set to a non-zero value, then a GPU will be included in the device list only if it can reserve at least this many CUs. 135 | /// Set to 0 to indicate that Steam Audio can use the entire GPU, in which case all available GPUs will be considered. 136 | pub num_compute_units_to_reserve: i32, 137 | 138 | /// The fraction of reserved CUs that should be used for impulse response (IR) update. 139 | /// 140 | /// IR update includes: a) ray tracing using Radeon Rays to simulate sound propagation, and/or b) pre-transformation of IRs for convolution using TrueAudio Next. 141 | /// Steam Audio will only list GPU devices that are able to subdivide the reserved CUs as per this value. 142 | /// The value must be between 0.0 and 1.0. 143 | /// 144 | /// For example, if `num_compute_units_to_reserve` is 8, and `fraction_of_compute_units_for_impulse_response_update` is 0.5, then 4 CUs will be used for IR update and 4 CUs will be used for convolution. 145 | /// Below are typical scenarios: 146 | /// - Using only TrueAudio Next. Set `fraction_of_compute_units_for_impulse_response_update` to 0.5. This ensures that reserved CUs are available for IR update as well as convolution. 147 | /// - Using TrueAudio Next and Radeon Rays for real-time simulation and rendering. Choosing `fraction_of_compute_units_for_impulse_response_update` may require some experimentation to utilize reserved CUs optimally. You can start by setting `fraction_of_compute_units_for_impulse_response_update` to 0.5. However, if IR calculation has high latency with these settings, increase `fraction_of_compute_units_for_impulse_response_update` to use more CUs for ray tracing. 148 | /// - Using only Radeon Rays. Set `fraction_of_compute_units_for_impulse_response_update` to 1.0, to make sure all the reserved CUs are used for ray tracing. If using Steam Audio for preprocessing (e.g. baking reverb), then consider setting `fraction_of_compute_units_for_impulse_response_update` to 0.0 to use the entire GPU for accelerated ray tracing. 149 | /// 150 | /// Ignored if `num_compute_units_to_reserve` is 0. 151 | pub fraction_of_compute_units_for_impulse_response_update: f32, 152 | 153 | /// If `true`, then the GPU device must support TrueAudio Next. 154 | /// 155 | /// It is not necessary to set this to `true` if `num_compute_units_to_reserve` or `fraction_of_compute_units_for_impulse_response_update` are set to non-zero values. 156 | pub requires_true_audio_next: bool, 157 | } 158 | 159 | impl Default for OpenClDeviceSettings { 160 | fn default() -> Self { 161 | Self { 162 | device_type: OpenClDeviceType::Cpu, 163 | num_compute_units_to_reserve: 0, 164 | fraction_of_compute_units_for_impulse_response_update: 0.0, 165 | requires_true_audio_next: false, 166 | } 167 | } 168 | } 169 | 170 | impl From<&OpenClDeviceSettings> for audionimbus_sys::IPLOpenCLDeviceSettings { 171 | fn from(settings: &OpenClDeviceSettings) -> Self { 172 | audionimbus_sys::IPLOpenCLDeviceSettings { 173 | type_: settings.device_type.into(), 174 | numCUsToReserve: settings.num_compute_units_to_reserve, 175 | fractionCUsForIRUpdate: settings.fraction_of_compute_units_for_impulse_response_update, 176 | requiresTAN: if settings.requires_true_audio_next { 177 | audionimbus_sys::IPLbool::IPL_TRUE 178 | } else { 179 | audionimbus_sys::IPLbool::IPL_FALSE 180 | }, 181 | } 182 | } 183 | } 184 | 185 | /// The type of device. 186 | #[derive(Copy, Clone, Debug)] 187 | pub enum OpenClDeviceType { 188 | /// List both CPU and GPU devices. 189 | Any, 190 | 191 | /// Only list CPU devices. 192 | Cpu, 193 | 194 | /// Only list GPU devices. 195 | Gpu, 196 | } 197 | 198 | impl From for audionimbus_sys::IPLOpenCLDeviceType { 199 | fn from(device_type: OpenClDeviceType) -> Self { 200 | match device_type { 201 | OpenClDeviceType::Any => audionimbus_sys::IPLOpenCLDeviceType::IPL_OPENCLDEVICETYPE_ANY, 202 | OpenClDeviceType::Cpu => audionimbus_sys::IPLOpenCLDeviceType::IPL_OPENCLDEVICETYPE_CPU, 203 | OpenClDeviceType::Gpu => audionimbus_sys::IPLOpenCLDeviceType::IPL_OPENCLDEVICETYPE_GPU, 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /audionimbus/src/device/radeon_rays.rs: -------------------------------------------------------------------------------- 1 | use super::open_cl::OpenClDevice; 2 | use crate::error::{to_option_error, SteamAudioError}; 3 | 4 | /// Application-wide state for the Radeon Rays ray tracer. 5 | /// 6 | /// A Radeon Rays device must be created before using any of Steam Audio’s Radeon Rays ray tracing functionality. 7 | #[derive(Debug)] 8 | pub struct RadeonRaysDevice(audionimbus_sys::IPLRadeonRaysDevice); 9 | 10 | impl RadeonRaysDevice { 11 | pub fn new(open_cl_device: &OpenClDevice) -> Result { 12 | let mut radeon_rays_device = Self(std::ptr::null_mut()); 13 | 14 | let radeon_rays_device_settings: *mut audionimbus_sys::IPLRadeonRaysDeviceSettings = 15 | std::ptr::null_mut(); 16 | 17 | let status = unsafe { 18 | audionimbus_sys::iplRadeonRaysDeviceCreate( 19 | open_cl_device.raw_ptr(), 20 | radeon_rays_device_settings, 21 | radeon_rays_device.raw_ptr_mut(), 22 | ) 23 | }; 24 | 25 | if let Some(error) = to_option_error(status) { 26 | return Err(error); 27 | } 28 | 29 | Ok(radeon_rays_device) 30 | } 31 | 32 | pub fn null() -> Self { 33 | Self(std::ptr::null_mut()) 34 | } 35 | 36 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLRadeonRaysDevice { 37 | self.0 38 | } 39 | 40 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLRadeonRaysDevice { 41 | &mut self.0 42 | } 43 | } 44 | 45 | impl Clone for RadeonRaysDevice { 46 | fn clone(&self) -> Self { 47 | unsafe { 48 | audionimbus_sys::iplRadeonRaysDeviceRetain(self.0); 49 | } 50 | Self(self.0) 51 | } 52 | } 53 | 54 | impl Drop for RadeonRaysDevice { 55 | fn drop(&mut self) { 56 | unsafe { audionimbus_sys::iplRadeonRaysDeviceRelease(&mut self.0) } 57 | } 58 | } 59 | 60 | unsafe impl Send for RadeonRaysDevice {} 61 | unsafe impl Sync for RadeonRaysDevice {} 62 | -------------------------------------------------------------------------------- /audionimbus/src/device/true_audio_next.rs: -------------------------------------------------------------------------------- 1 | use super::open_cl::OpenClDevice; 2 | use crate::error::{to_option_error, SteamAudioError}; 3 | 4 | /// Application-wide state for the TrueAudio Next convolution engine. 5 | /// 6 | /// A TrueAudio Next device must be created before using any of Steam Audio’s TrueAudio Next convolution functionality. 7 | #[derive(Debug)] 8 | pub struct TrueAudioNextDevice(pub(crate) audionimbus_sys::IPLTrueAudioNextDevice); 9 | 10 | impl TrueAudioNextDevice { 11 | pub fn new( 12 | open_cl_device: &OpenClDevice, 13 | settings: &TrueAudioNextDeviceSettings, 14 | ) -> Result { 15 | let mut true_audio_next_device = Self(std::ptr::null_mut()); 16 | 17 | let status = unsafe { 18 | audionimbus_sys::iplTrueAudioNextDeviceCreate( 19 | open_cl_device.raw_ptr(), 20 | &mut audionimbus_sys::IPLTrueAudioNextDeviceSettings::from(settings), 21 | true_audio_next_device.raw_ptr_mut(), 22 | ) 23 | }; 24 | 25 | if let Some(error) = to_option_error(status) { 26 | return Err(error); 27 | } 28 | 29 | Ok(true_audio_next_device) 30 | } 31 | 32 | pub fn null() -> Self { 33 | Self(std::ptr::null_mut()) 34 | } 35 | 36 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLTrueAudioNextDevice { 37 | self.0 38 | } 39 | 40 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLTrueAudioNextDevice { 41 | &mut self.0 42 | } 43 | } 44 | 45 | impl Clone for TrueAudioNextDevice { 46 | fn clone(&self) -> Self { 47 | unsafe { 48 | audionimbus_sys::iplTrueAudioNextDeviceRetain(self.0); 49 | } 50 | Self(self.0) 51 | } 52 | } 53 | 54 | impl Drop for TrueAudioNextDevice { 55 | fn drop(&mut self) { 56 | unsafe { audionimbus_sys::iplTrueAudioNextDeviceRelease(&mut self.0) } 57 | } 58 | } 59 | 60 | unsafe impl Send for TrueAudioNextDevice {} 61 | unsafe impl Sync for TrueAudioNextDevice {} 62 | 63 | /// Settings used to create a TrueAudio Next device. 64 | #[derive(Debug)] 65 | pub struct TrueAudioNextDeviceSettings { 66 | /// The number of samples in an audio frame. 67 | pub frame_size: usize, 68 | 69 | /// The number of samples in the impulse responses that will be used for convolution. 70 | pub impulse_response_size: usize, 71 | 72 | /// The Ambisonic order of the impulse responses that will be used for convolution. 73 | pub order: usize, 74 | 75 | /// The maximum number of sources that will use TrueAudio Next for convolution. 76 | pub max_sources: usize, 77 | } 78 | 79 | impl From<&TrueAudioNextDeviceSettings> for audionimbus_sys::IPLTrueAudioNextDeviceSettings { 80 | fn from(settings: &TrueAudioNextDeviceSettings) -> Self { 81 | Self { 82 | frameSize: settings.frame_size as i32, 83 | irSize: settings.impulse_response_size as i32, 84 | order: settings.order as i32, 85 | maxSources: settings.max_sources as i32, 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /audionimbus/src/effect/ambisonics/binaural.rs: -------------------------------------------------------------------------------- 1 | use super::super::AudioEffectState; 2 | use crate::audio_buffer::{AudioBuffer, Sample}; 3 | use crate::audio_settings::AudioSettings; 4 | use crate::context::Context; 5 | use crate::error::{to_option_error, SteamAudioError}; 6 | use crate::ffi_wrapper::FFIWrapper; 7 | use crate::hrtf::Hrtf; 8 | 9 | /// Renders ambisonic audio using HRTF-based binaural rendering. 10 | /// 11 | /// This results in more immersive spatialization of the ambisonic audio as compared to using an ambisonics binaural effect, at the cost of slightly increased CPU usage. 12 | #[derive(Debug)] 13 | pub struct AmbisonicsBinauralEffect(audionimbus_sys::IPLAmbisonicsBinauralEffect); 14 | 15 | impl AmbisonicsBinauralEffect { 16 | pub fn try_new( 17 | context: &Context, 18 | audio_settings: &AudioSettings, 19 | ambisonics_binaural_effect_settings: &AmbisonicsBinauralEffectSettings, 20 | ) -> Result { 21 | let mut ambisonics_binaural_effect = Self(std::ptr::null_mut()); 22 | 23 | let status = unsafe { 24 | audionimbus_sys::iplAmbisonicsBinauralEffectCreate( 25 | context.raw_ptr(), 26 | &mut audionimbus_sys::IPLAudioSettings::from(audio_settings), 27 | &mut audionimbus_sys::IPLAmbisonicsBinauralEffectSettings::from( 28 | ambisonics_binaural_effect_settings, 29 | ), 30 | ambisonics_binaural_effect.raw_ptr_mut(), 31 | ) 32 | }; 33 | 34 | if let Some(error) = to_option_error(status) { 35 | return Err(error); 36 | } 37 | 38 | Ok(ambisonics_binaural_effect) 39 | } 40 | 41 | /// Applies an ambisonics binaural effect to an audio buffer. 42 | /// 43 | /// This effect CANNOT be applied in-place. 44 | pub fn apply( 45 | &self, 46 | ambisonics_binaural_effect_params: &AmbisonicsBinauralEffectParams, 47 | input_buffer: &AudioBuffer, 48 | output_buffer: &AudioBuffer, 49 | ) -> AudioEffectState 50 | where 51 | I: AsRef<[Sample]>, 52 | O: AsRef<[Sample]> + AsMut<[Sample]>, 53 | { 54 | let required_num_channels = (ambisonics_binaural_effect_params.order + 1).pow(2); 55 | assert_eq!( 56 | input_buffer.num_channels(), 57 | required_num_channels, 58 | "ambisonic order N = {} requires (N + 1)^2 = {} input channels", 59 | ambisonics_binaural_effect_params.order, 60 | required_num_channels 61 | ); 62 | 63 | assert_eq!( 64 | output_buffer.num_channels(), 65 | 2, 66 | "output audio buffer must have 2 channels", 67 | ); 68 | 69 | unsafe { 70 | audionimbus_sys::iplAmbisonicsBinauralEffectApply( 71 | self.raw_ptr(), 72 | &mut *ambisonics_binaural_effect_params.as_ffi(), 73 | &mut *input_buffer.as_ffi(), 74 | &mut *output_buffer.as_ffi(), 75 | ) 76 | } 77 | .into() 78 | } 79 | 80 | /// Retrieves a single frame of tail samples from an Ambisonics binaural effect’s internal buffers. 81 | /// 82 | /// After the input to the Ambisonics binaural effect has stopped, this function must be called instead of [`Self::apply`] until the return value indicates that no more tail samples remain. 83 | /// 84 | /// The output audio buffer must have 2 channels. 85 | pub fn tail(&self, output_buffer: &AudioBuffer) -> AudioEffectState 86 | where 87 | O: AsRef<[Sample]> + AsMut<[Sample]>, 88 | { 89 | assert_eq!( 90 | output_buffer.num_channels(), 91 | 2, 92 | "input buffer must have 2 channels", 93 | ); 94 | 95 | unsafe { 96 | audionimbus_sys::iplAmbisonicsBinauralEffectGetTail( 97 | self.raw_ptr(), 98 | &mut *output_buffer.as_ffi(), 99 | ) 100 | } 101 | .into() 102 | } 103 | 104 | /// Returns the number of tail samples remaining in an Ambisonics binaural effect’s internal buffers. 105 | /// 106 | /// Tail samples are audio samples that should be played even after the input to the effect has stopped playing and no further input samples are available. 107 | pub fn tail_size(&self) -> usize { 108 | unsafe { audionimbus_sys::iplAmbisonicsBinauralEffectGetTailSize(self.raw_ptr()) as usize } 109 | } 110 | 111 | /// Resets the internal processing state of an ambisonics binaural effect. 112 | pub fn reset(&mut self) { 113 | unsafe { audionimbus_sys::iplAmbisonicsBinauralEffectReset(self.raw_ptr()) }; 114 | } 115 | 116 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLAmbisonicsBinauralEffect { 117 | self.0 118 | } 119 | 120 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLAmbisonicsBinauralEffect { 121 | &mut self.0 122 | } 123 | } 124 | 125 | impl Clone for AmbisonicsBinauralEffect { 126 | fn clone(&self) -> Self { 127 | unsafe { 128 | audionimbus_sys::iplAmbisonicsBinauralEffectRetain(self.0); 129 | } 130 | Self(self.0) 131 | } 132 | } 133 | 134 | impl Drop for AmbisonicsBinauralEffect { 135 | fn drop(&mut self) { 136 | unsafe { audionimbus_sys::iplAmbisonicsBinauralEffectRelease(&mut self.0) } 137 | } 138 | } 139 | 140 | unsafe impl Send for AmbisonicsBinauralEffect {} 141 | unsafe impl Sync for AmbisonicsBinauralEffect {} 142 | 143 | /// Settings used to create an ambisonics binaural effect. 144 | #[derive(Debug)] 145 | pub struct AmbisonicsBinauralEffectSettings<'a> { 146 | /// The HRTF to use. 147 | pub hrtf: &'a Hrtf, 148 | 149 | /// The maximum ambisonics order that will be used by input audio buffers. 150 | pub max_order: usize, 151 | } 152 | 153 | impl From<&AmbisonicsBinauralEffectSettings<'_>> 154 | for audionimbus_sys::IPLAmbisonicsBinauralEffectSettings 155 | { 156 | fn from(settings: &AmbisonicsBinauralEffectSettings) -> Self { 157 | Self { 158 | hrtf: settings.hrtf.raw_ptr(), 159 | maxOrder: settings.max_order as i32, 160 | } 161 | } 162 | } 163 | 164 | /// Parameters for applying an ambisonics binaural effect to an audio buffer. 165 | #[derive(Debug)] 166 | pub struct AmbisonicsBinauralEffectParams<'a> { 167 | /// The HRTF to use. 168 | pub hrtf: &'a Hrtf, 169 | 170 | /// Ambisonic order of the input buffer. 171 | /// 172 | /// May be less than the `max_order` specified when creating the effect, in which case the effect will process fewer input channels, reducing CPU usage. 173 | pub order: usize, 174 | } 175 | 176 | impl AmbisonicsBinauralEffectParams<'_> { 177 | pub(crate) fn as_ffi( 178 | &self, 179 | ) -> FFIWrapper<'_, audionimbus_sys::IPLAmbisonicsBinauralEffectParams, Self> { 180 | let ambisonics_binaural_effect_params = 181 | audionimbus_sys::IPLAmbisonicsBinauralEffectParams { 182 | hrtf: self.hrtf.raw_ptr(), 183 | order: self.order as i32, 184 | }; 185 | 186 | FFIWrapper::new(ambisonics_binaural_effect_params) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /audionimbus/src/effect/ambisonics/decode.rs: -------------------------------------------------------------------------------- 1 | use super::super::AudioEffectState; 2 | use super::SpeakerLayout; 3 | use crate::audio_buffer::{AudioBuffer, Sample}; 4 | use crate::audio_settings::AudioSettings; 5 | use crate::context::Context; 6 | use crate::error::{to_option_error, SteamAudioError}; 7 | use crate::ffi_wrapper::FFIWrapper; 8 | use crate::geometry::CoordinateSystem; 9 | use crate::hrtf::Hrtf; 10 | 11 | /// Applies a rotation to an ambisonics audio buffer, then decodes it using panning or binaural rendering. 12 | /// 13 | /// This is essentially an ambisonics rotate effect followed by either an ambisonics panning effect or an ambisonics binaural effect. 14 | #[derive(Debug)] 15 | pub struct AmbisonicsDecodeEffect(audionimbus_sys::IPLAmbisonicsDecodeEffect); 16 | 17 | impl AmbisonicsDecodeEffect { 18 | pub fn try_new( 19 | context: &Context, 20 | audio_settings: &AudioSettings, 21 | ambisonics_decode_effect_settings: &AmbisonicsDecodeEffectSettings, 22 | ) -> Result { 23 | let mut ambisonics_decode_effect = Self(std::ptr::null_mut()); 24 | 25 | let status = unsafe { 26 | audionimbus_sys::iplAmbisonicsDecodeEffectCreate( 27 | context.raw_ptr(), 28 | &mut audionimbus_sys::IPLAudioSettings::from(audio_settings), 29 | &mut audionimbus_sys::IPLAmbisonicsDecodeEffectSettings::from( 30 | ambisonics_decode_effect_settings, 31 | ), 32 | ambisonics_decode_effect.raw_ptr_mut(), 33 | ) 34 | }; 35 | 36 | if let Some(error) = to_option_error(status) { 37 | return Err(error); 38 | } 39 | 40 | Ok(ambisonics_decode_effect) 41 | } 42 | 43 | /// Applies an ambisonics decode effect to an audio buffer. 44 | /// 45 | /// This effect CANNOT be applied in-place. 46 | pub fn apply( 47 | &self, 48 | ambisonics_decode_effect_params: &AmbisonicsDecodeEffectParams, 49 | input_buffer: &AudioBuffer, 50 | output_buffer: &AudioBuffer, 51 | ) -> AudioEffectState 52 | where 53 | I: AsRef<[Sample]>, 54 | O: AsRef<[Sample]> + AsMut<[Sample]>, 55 | { 56 | let required_num_channels = (ambisonics_decode_effect_params.order + 1).pow(2); 57 | assert_eq!( 58 | input_buffer.num_channels(), 59 | required_num_channels, 60 | "ambisonic order N = {} requires (N + 1)^2 = {} input channels", 61 | ambisonics_decode_effect_params.order, 62 | required_num_channels 63 | ); 64 | 65 | unsafe { 66 | audionimbus_sys::iplAmbisonicsDecodeEffectApply( 67 | self.raw_ptr(), 68 | &mut *ambisonics_decode_effect_params.as_ffi(), 69 | &mut *input_buffer.as_ffi(), 70 | &mut *output_buffer.as_ffi(), 71 | ) 72 | } 73 | .into() 74 | } 75 | 76 | /// Retrieves a single frame of tail samples from an Ambisonics decode effect’s internal buffers. 77 | /// 78 | /// After the input to the Ambisonics decode effect has stopped, this function must be called instead of [`Self::apply`] until the return value indicates that no more tail samples remain. 79 | /// 80 | /// The output audio buffer must have as many channels as needed for the speaker layout specified when creating the effect (if using panning) or 2 channels (if using binaural rendering). 81 | pub fn tail(&self, output_buffer: &AudioBuffer) -> AudioEffectState 82 | where 83 | O: AsRef<[Sample]> + AsMut<[Sample]>, 84 | { 85 | unsafe { 86 | audionimbus_sys::iplAmbisonicsDecodeEffectGetTail( 87 | self.raw_ptr(), 88 | &mut *output_buffer.as_ffi(), 89 | ) 90 | } 91 | .into() 92 | } 93 | 94 | /// Returns the number of tail samples remaining in an Ambisonics decode effect’s internal buffers. 95 | /// 96 | /// Tail samples are audio samples that should be played even after the input to the effect has stopped playing and no further input samples are available. 97 | pub fn tail_size(&self) -> usize { 98 | unsafe { audionimbus_sys::iplAmbisonicsDecodeEffectGetTailSize(self.raw_ptr()) as usize } 99 | } 100 | 101 | /// Resets the internal processing state of an ambisonics decode effect. 102 | pub fn reset(&mut self) { 103 | unsafe { audionimbus_sys::iplAmbisonicsDecodeEffectReset(self.raw_ptr()) }; 104 | } 105 | 106 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLAmbisonicsDecodeEffect { 107 | self.0 108 | } 109 | 110 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLAmbisonicsDecodeEffect { 111 | &mut self.0 112 | } 113 | } 114 | 115 | impl Clone for AmbisonicsDecodeEffect { 116 | fn clone(&self) -> Self { 117 | unsafe { 118 | audionimbus_sys::iplAmbisonicsDecodeEffectRetain(self.0); 119 | } 120 | Self(self.0) 121 | } 122 | } 123 | 124 | impl Drop for AmbisonicsDecodeEffect { 125 | fn drop(&mut self) { 126 | unsafe { audionimbus_sys::iplAmbisonicsDecodeEffectRelease(&mut self.0) } 127 | } 128 | } 129 | 130 | unsafe impl Send for AmbisonicsDecodeEffect {} 131 | unsafe impl Sync for AmbisonicsDecodeEffect {} 132 | 133 | /// Settings used to create an ambisonics decode effect. 134 | #[derive(Debug)] 135 | pub struct AmbisonicsDecodeEffectSettings<'a> { 136 | /// The speaker layout that will be used by output audio buffers. 137 | pub speaker_layout: SpeakerLayout, 138 | 139 | /// The HRTF to use. 140 | pub hrtf: &'a Hrtf, 141 | 142 | /// The maximum ambisonics order that will be used by input audio buffers. 143 | pub max_order: usize, 144 | } 145 | 146 | impl From<&AmbisonicsDecodeEffectSettings<'_>> 147 | for audionimbus_sys::IPLAmbisonicsDecodeEffectSettings 148 | { 149 | fn from(settings: &AmbisonicsDecodeEffectSettings) -> Self { 150 | Self { 151 | speakerLayout: audionimbus_sys::IPLSpeakerLayout::from(&settings.speaker_layout), 152 | hrtf: settings.hrtf.raw_ptr(), 153 | maxOrder: settings.max_order as i32, 154 | } 155 | } 156 | } 157 | 158 | /// Parameters for applying an ambisonics decode effect to an audio buffer. 159 | #[derive(Debug)] 160 | pub struct AmbisonicsDecodeEffectParams<'a> { 161 | /// Ambisonic order of the input buffer. 162 | /// 163 | /// May be less than the `max_order` specified when creating the effect, in which case the effect will process fewer input channels, reducing CPU usage. 164 | pub order: usize, 165 | 166 | /// The HRTF to use. 167 | pub hrtf: &'a Hrtf, 168 | 169 | /// The orientation of the listener. 170 | pub orientation: CoordinateSystem, 171 | 172 | /// Whether to use binaural rendering or panning. 173 | pub binaural: bool, 174 | } 175 | 176 | impl AmbisonicsDecodeEffectParams<'_> { 177 | pub(crate) fn as_ffi( 178 | &self, 179 | ) -> FFIWrapper<'_, audionimbus_sys::IPLAmbisonicsDecodeEffectParams, Self> { 180 | let ambisonics_decode_effect_params = audionimbus_sys::IPLAmbisonicsDecodeEffectParams { 181 | order: self.order as i32, 182 | hrtf: self.hrtf.raw_ptr(), 183 | orientation: self.orientation.into(), 184 | binaural: if self.binaural { 185 | audionimbus_sys::IPLbool::IPL_TRUE 186 | } else { 187 | audionimbus_sys::IPLbool::IPL_FALSE 188 | }, 189 | }; 190 | 191 | FFIWrapper::new(ambisonics_decode_effect_params) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /audionimbus/src/effect/ambisonics/encode.rs: -------------------------------------------------------------------------------- 1 | use super::super::AudioEffectState; 2 | use crate::audio_buffer::{AudioBuffer, Sample}; 3 | use crate::audio_settings::AudioSettings; 4 | use crate::context::Context; 5 | use crate::error::{to_option_error, SteamAudioError}; 6 | use crate::ffi_wrapper::FFIWrapper; 7 | use crate::geometry::Direction; 8 | 9 | /// Encodes a point source into ambisonics. 10 | /// 11 | /// Given a point source with some direction relative to the listener, this effect generates an Ambisonic audio buffer that approximates a point source in the given direction. 12 | /// This allows multiple point sources and ambiences to mixed to a single ambisonics buffer before being spatialized. 13 | #[derive(Debug)] 14 | pub struct AmbisonicsEncodeEffect(audionimbus_sys::IPLAmbisonicsEncodeEffect); 15 | 16 | impl AmbisonicsEncodeEffect { 17 | pub fn try_new( 18 | context: &Context, 19 | audio_settings: &AudioSettings, 20 | ambisonics_encode_effect_settings: &AmbisonicsEncodeEffectSettings, 21 | ) -> Result { 22 | let mut ambisonics_encode_effect = Self(std::ptr::null_mut()); 23 | 24 | let status = unsafe { 25 | audionimbus_sys::iplAmbisonicsEncodeEffectCreate( 26 | context.raw_ptr(), 27 | &mut audionimbus_sys::IPLAudioSettings::from(audio_settings), 28 | &mut audionimbus_sys::IPLAmbisonicsEncodeEffectSettings::from( 29 | ambisonics_encode_effect_settings, 30 | ), 31 | ambisonics_encode_effect.raw_ptr_mut(), 32 | ) 33 | }; 34 | 35 | if let Some(error) = to_option_error(status) { 36 | return Err(error); 37 | } 38 | 39 | Ok(ambisonics_encode_effect) 40 | } 41 | 42 | /// Applies an ambisonics encode effect to an audio buffer. 43 | /// 44 | /// This effect CANNOT be applied in-place. 45 | pub fn apply( 46 | &self, 47 | ambisonics_encode_effect_params: &AmbisonicsEncodeEffectParams, 48 | input_buffer: &AudioBuffer, 49 | output_buffer: &AudioBuffer, 50 | ) -> AudioEffectState 51 | where 52 | I: AsRef<[Sample]>, 53 | O: AsRef<[Sample]> + AsMut<[Sample]>, 54 | { 55 | assert_eq!( 56 | input_buffer.num_channels(), 57 | 1, 58 | "input buffer must have 1 channel", 59 | ); 60 | 61 | let required_num_channels = (ambisonics_encode_effect_params.order + 1).pow(2); 62 | assert_eq!( 63 | output_buffer.num_channels(), 64 | required_num_channels, 65 | "ambisonic order N = {} requires (N + 1)^2 = {} output channels", 66 | ambisonics_encode_effect_params.order, 67 | required_num_channels 68 | ); 69 | 70 | unsafe { 71 | audionimbus_sys::iplAmbisonicsEncodeEffectApply( 72 | self.raw_ptr(), 73 | &mut *ambisonics_encode_effect_params.as_ffi(), 74 | &mut *input_buffer.as_ffi(), 75 | &mut *output_buffer.as_ffi(), 76 | ) 77 | } 78 | .into() 79 | } 80 | 81 | /// Retrieves a single frame of tail samples from an Ambisonics encode effect’s internal buffers. 82 | /// 83 | /// After the input to the Ambisonics encode effect has stopped, this function must be called instead of [`Self::apply`] until the return value indicates that no more tail samples remain. 84 | /// 85 | /// The output audio buffer must have as many channels as needed for the Ambisonics order specified when creating the effect. 86 | pub fn tail(&self, output_buffer: &AudioBuffer) -> AudioEffectState 87 | where 88 | O: AsRef<[Sample]> + AsMut<[Sample]>, 89 | { 90 | unsafe { 91 | audionimbus_sys::iplAmbisonicsEncodeEffectGetTail( 92 | self.raw_ptr(), 93 | &mut *output_buffer.as_ffi(), 94 | ) 95 | } 96 | .into() 97 | } 98 | 99 | /// Returns the number of tail samples remaining in an Ambisonics encode effect’s internal buffers. 100 | /// 101 | /// Tail samples are audio samples that should be played even after the input to the effect has stopped playing and no further input samples are available. 102 | pub fn tail_size(&self) -> usize { 103 | unsafe { audionimbus_sys::iplAmbisonicsEncodeEffectGetTailSize(self.raw_ptr()) as usize } 104 | } 105 | 106 | /// Resets the internal processing state of an ambisonics encode effect. 107 | pub fn reset(&mut self) { 108 | unsafe { audionimbus_sys::iplAmbisonicsEncodeEffectReset(self.raw_ptr()) }; 109 | } 110 | 111 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLAmbisonicsEncodeEffect { 112 | self.0 113 | } 114 | 115 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLAmbisonicsEncodeEffect { 116 | &mut self.0 117 | } 118 | } 119 | 120 | impl Clone for AmbisonicsEncodeEffect { 121 | fn clone(&self) -> Self { 122 | unsafe { 123 | audionimbus_sys::iplAmbisonicsEncodeEffectRetain(self.0); 124 | } 125 | Self(self.0) 126 | } 127 | } 128 | 129 | impl Drop for AmbisonicsEncodeEffect { 130 | fn drop(&mut self) { 131 | unsafe { audionimbus_sys::iplAmbisonicsEncodeEffectRelease(&mut self.0) } 132 | } 133 | } 134 | 135 | unsafe impl Send for AmbisonicsEncodeEffect {} 136 | unsafe impl Sync for AmbisonicsEncodeEffect {} 137 | 138 | /// Settings used to create an ambisonics decode effect. 139 | #[derive(Debug)] 140 | pub struct AmbisonicsEncodeEffectSettings { 141 | /// The maximum ambisonics order that will be used by input audio buffers. 142 | /// Maximum ambisonics order to encode audio buffers to. 143 | pub max_order: usize, 144 | } 145 | 146 | impl From<&AmbisonicsEncodeEffectSettings> for audionimbus_sys::IPLAmbisonicsEncodeEffectSettings { 147 | fn from(settings: &AmbisonicsEncodeEffectSettings) -> Self { 148 | Self { 149 | maxOrder: settings.max_order as i32, 150 | } 151 | } 152 | } 153 | 154 | /// Parameters for applying an ambisonics encode effect to an audio buffer. 155 | #[derive(Debug)] 156 | pub struct AmbisonicsEncodeEffectParams { 157 | /// Vector pointing from the listener towards the source. 158 | /// 159 | /// Need not be normalized; Steam Audio will automatically normalize this vector. 160 | /// If a zero-length vector is passed, the output will be order 0 (omnidirectional). 161 | pub direction: Direction, 162 | 163 | /// Ambisonic order of the output buffer. 164 | /// 165 | /// May be less than the `max_order` specified when creating the effect, in which case the effect will generate fewer output channels, reducing CPU usage. 166 | pub order: usize, 167 | } 168 | 169 | impl AmbisonicsEncodeEffectParams { 170 | pub(crate) fn as_ffi( 171 | &self, 172 | ) -> FFIWrapper<'_, audionimbus_sys::IPLAmbisonicsEncodeEffectParams, Self> { 173 | let ambisonics_encode_effect_params = audionimbus_sys::IPLAmbisonicsEncodeEffectParams { 174 | direction: self.direction.into(), 175 | order: self.order as i32, 176 | }; 177 | 178 | FFIWrapper::new(ambisonics_encode_effect_params) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /audionimbus/src/effect/ambisonics/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod encode; 2 | pub use encode::*; 3 | 4 | pub mod decode; 5 | pub use decode::*; 6 | 7 | pub mod panning; 8 | pub use panning::*; 9 | 10 | pub mod binaural; 11 | pub use binaural::*; 12 | 13 | pub mod rotation; 14 | pub use rotation::*; 15 | 16 | mod speaker_layout; 17 | pub use speaker_layout::SpeakerLayout; 18 | 19 | mod r#type; 20 | pub use r#type::AmbisonicsType; 21 | -------------------------------------------------------------------------------- /audionimbus/src/effect/ambisonics/panning.rs: -------------------------------------------------------------------------------- 1 | use super::super::AudioEffectState; 2 | use super::super::SpeakerLayout; 3 | use crate::audio_buffer::{AudioBuffer, Sample}; 4 | use crate::audio_settings::AudioSettings; 5 | use crate::context::Context; 6 | use crate::error::{to_option_error, SteamAudioError}; 7 | use crate::ffi_wrapper::FFIWrapper; 8 | 9 | /// Renders Ambisonic audio by panning it to a standard speaker layout. 10 | /// 11 | /// This involves calculating signals to emit from each speaker so as to approximate the Ambisonic sound field. 12 | #[derive(Debug)] 13 | pub struct AmbisonicsPanningEffect(audionimbus_sys::IPLAmbisonicsPanningEffect); 14 | 15 | impl AmbisonicsPanningEffect { 16 | pub fn try_new( 17 | context: &Context, 18 | audio_settings: &AudioSettings, 19 | ambisonics_panning_effect_settings: &AmbisonicsPanningEffectSettings, 20 | ) -> Result { 21 | let mut ambisonics_panning_effect = Self(std::ptr::null_mut()); 22 | 23 | let status = unsafe { 24 | audionimbus_sys::iplAmbisonicsPanningEffectCreate( 25 | context.raw_ptr(), 26 | &mut audionimbus_sys::IPLAudioSettings::from(audio_settings), 27 | &mut audionimbus_sys::IPLAmbisonicsPanningEffectSettings::from( 28 | ambisonics_panning_effect_settings, 29 | ), 30 | ambisonics_panning_effect.raw_ptr_mut(), 31 | ) 32 | }; 33 | 34 | if let Some(error) = to_option_error(status) { 35 | return Err(error); 36 | } 37 | 38 | Ok(ambisonics_panning_effect) 39 | } 40 | 41 | /// Applies an ambisonics panning effect to an audio buffer. 42 | /// 43 | /// This effect CANNOT be applied in-place. 44 | pub fn apply( 45 | &self, 46 | ambisonics_panning_effect_params: &AmbisonicsPanningEffectParams, 47 | input_buffer: &AudioBuffer, 48 | output_buffer: &AudioBuffer, 49 | ) -> AudioEffectState 50 | where 51 | I: AsRef<[Sample]>, 52 | O: AsRef<[Sample]> + AsMut<[Sample]>, 53 | { 54 | let required_num_channels = (ambisonics_panning_effect_params.order + 1).pow(2); 55 | assert_eq!( 56 | input_buffer.num_channels(), 57 | required_num_channels, 58 | "ambisonic order N = {} requires (N + 1)^2 = {} input channels", 59 | ambisonics_panning_effect_params.order, 60 | required_num_channels 61 | ); 62 | 63 | unsafe { 64 | audionimbus_sys::iplAmbisonicsPanningEffectApply( 65 | self.raw_ptr(), 66 | &mut *ambisonics_panning_effect_params.as_ffi(), 67 | &mut *input_buffer.as_ffi(), 68 | &mut *output_buffer.as_ffi(), 69 | ) 70 | } 71 | .into() 72 | } 73 | 74 | /// Retrieves a single frame of tail samples from a Ambisonics panning effect’s internal buffers. 75 | /// 76 | /// After the input to the Ambisonics panning effect has stopped, this function must be called instead of [`Self::apply`] until the return value indicates that no more tail samples remain. 77 | /// 78 | /// The output audio buffer must have as many channels as needed for the speaker layout specified when creating the effect. 79 | pub fn tail(&self, output_buffer: &AudioBuffer) -> AudioEffectState 80 | where 81 | O: AsRef<[Sample]> + AsMut<[Sample]>, 82 | { 83 | unsafe { 84 | audionimbus_sys::iplAmbisonicsPanningEffectGetTail( 85 | self.raw_ptr(), 86 | &mut *output_buffer.as_ffi(), 87 | ) 88 | } 89 | .into() 90 | } 91 | 92 | /// Returns the number of tail samples remaining in an Ambisonics panning effect’s internal buffers. 93 | /// 94 | /// Tail samples are audio samples that should be played even after the input to the effect has stopped playing and no further input samples are available. 95 | pub fn tail_size(&self) -> usize { 96 | unsafe { audionimbus_sys::iplAmbisonicsPanningEffectGetTailSize(self.raw_ptr()) as usize } 97 | } 98 | 99 | /// Resets the internal processing state of an ambisonics panning effect. 100 | pub fn reset(&mut self) { 101 | unsafe { audionimbus_sys::iplAmbisonicsPanningEffectReset(self.raw_ptr()) }; 102 | } 103 | 104 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLAmbisonicsPanningEffect { 105 | self.0 106 | } 107 | 108 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLAmbisonicsPanningEffect { 109 | &mut self.0 110 | } 111 | } 112 | 113 | impl Clone for AmbisonicsPanningEffect { 114 | fn clone(&self) -> Self { 115 | unsafe { 116 | audionimbus_sys::iplAmbisonicsPanningEffectRetain(self.0); 117 | } 118 | Self(self.0) 119 | } 120 | } 121 | 122 | impl Drop for AmbisonicsPanningEffect { 123 | fn drop(&mut self) { 124 | unsafe { audionimbus_sys::iplAmbisonicsPanningEffectRelease(&mut self.0) } 125 | } 126 | } 127 | 128 | unsafe impl Send for AmbisonicsPanningEffect {} 129 | unsafe impl Sync for AmbisonicsPanningEffect {} 130 | 131 | /// Settings used to create an ambisonics panning effect. 132 | #[derive(Debug)] 133 | pub struct AmbisonicsPanningEffectSettings { 134 | /// The speaker layout that will be used by output audio buffers. 135 | pub speaker_layout: SpeakerLayout, 136 | 137 | /// The maximum ambisonics order that will be used by input audio buffers. 138 | pub max_order: usize, 139 | } 140 | 141 | impl From<&AmbisonicsPanningEffectSettings> 142 | for audionimbus_sys::IPLAmbisonicsPanningEffectSettings 143 | { 144 | fn from(settings: &AmbisonicsPanningEffectSettings) -> Self { 145 | Self { 146 | speakerLayout: (&settings.speaker_layout).into(), 147 | maxOrder: settings.max_order as i32, 148 | } 149 | } 150 | } 151 | 152 | /// Parameters for applying an ambisonics panning effect to an audio buffer. 153 | #[derive(Debug)] 154 | pub struct AmbisonicsPanningEffectParams { 155 | /// Ambisonic order of the input buffer. 156 | /// 157 | /// May be less than the `max_order` specified when creating the effect, in which case the effect will process fewer input channels, reducing CPU usage. 158 | pub order: usize, 159 | } 160 | 161 | impl AmbisonicsPanningEffectParams { 162 | pub(crate) fn as_ffi( 163 | &self, 164 | ) -> FFIWrapper<'_, audionimbus_sys::IPLAmbisonicsPanningEffectParams, Self> { 165 | let ambisonics_panning_effect_params = audionimbus_sys::IPLAmbisonicsPanningEffectParams { 166 | order: self.order as i32, 167 | }; 168 | 169 | FFIWrapper::new(ambisonics_panning_effect_params) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /audionimbus/src/effect/ambisonics/rotation.rs: -------------------------------------------------------------------------------- 1 | use super::super::AudioEffectState; 2 | use crate::audio_buffer::{AudioBuffer, Sample}; 3 | use crate::audio_settings::AudioSettings; 4 | use crate::context::Context; 5 | use crate::error::{to_option_error, SteamAudioError}; 6 | use crate::ffi_wrapper::FFIWrapper; 7 | use crate::geometry::CoordinateSystem; 8 | 9 | /// Applies a rotation to an ambisonics audio buffer. 10 | /// 11 | /// The input buffer is assumed to describe a sound field in "world space". 12 | /// The output buffer is then the same sound field, but expressed relative to the listener’s orientation. 13 | #[derive(Debug)] 14 | pub struct AmbisonicsRotationEffect(audionimbus_sys::IPLAmbisonicsRotationEffect); 15 | 16 | impl AmbisonicsRotationEffect { 17 | pub fn try_new( 18 | context: &Context, 19 | audio_settings: &AudioSettings, 20 | ambisonics_rotation_effect_settings: &AmbisonicsRotationEffectSettings, 21 | ) -> Result { 22 | let mut ambisonics_rotation_effect = Self(std::ptr::null_mut()); 23 | 24 | let status = unsafe { 25 | audionimbus_sys::iplAmbisonicsRotationEffectCreate( 26 | context.raw_ptr(), 27 | &mut audionimbus_sys::IPLAudioSettings::from(audio_settings), 28 | &mut audionimbus_sys::IPLAmbisonicsRotationEffectSettings::from( 29 | ambisonics_rotation_effect_settings, 30 | ), 31 | ambisonics_rotation_effect.raw_ptr_mut(), 32 | ) 33 | }; 34 | 35 | if let Some(error) = to_option_error(status) { 36 | return Err(error); 37 | } 38 | 39 | Ok(ambisonics_rotation_effect) 40 | } 41 | 42 | /// Applies an ambisonics rotation effect to an audio buffer. 43 | /// 44 | /// This effect CANNOT be applied in-place. 45 | pub fn apply( 46 | &self, 47 | ambisonics_rotation_effect_params: &AmbisonicsRotationEffectParams, 48 | input_buffer: &AudioBuffer, 49 | output_buffer: &AudioBuffer, 50 | ) -> AudioEffectState 51 | where 52 | I: AsRef<[Sample]>, 53 | O: AsRef<[Sample]> + AsMut<[Sample]>, 54 | { 55 | let required_num_channels = (ambisonics_rotation_effect_params.order + 1).pow(2); 56 | assert_eq!( 57 | input_buffer.num_channels(), 58 | required_num_channels, 59 | "ambisonic order N = {} requires (N + 1)^2 = {} input channels", 60 | ambisonics_rotation_effect_params.order, 61 | required_num_channels 62 | ); 63 | assert_eq!( 64 | output_buffer.num_channels(), 65 | required_num_channels, 66 | "ambisonic order N = {} requires (N + 1)^2 = {} output channels", 67 | ambisonics_rotation_effect_params.order, 68 | required_num_channels 69 | ); 70 | 71 | unsafe { 72 | audionimbus_sys::iplAmbisonicsRotationEffectApply( 73 | self.raw_ptr(), 74 | &mut *ambisonics_rotation_effect_params.as_ffi(), 75 | &mut *input_buffer.as_ffi(), 76 | &mut *output_buffer.as_ffi(), 77 | ) 78 | } 79 | .into() 80 | } 81 | 82 | /// Retrieves a single frame of tail samples from an Ambisonics rotation effect’s internal buffers. 83 | /// 84 | /// After the input to the Ambisonics rotation effect has stopped, this function must be called instead of [`Self::apply`] until the return value indicates that no more tail samples remain. 85 | /// 86 | /// The output audio buffer must have as many channels as needed for the Ambisonics order specified when creating the effect. 87 | pub fn tail(&self, output_buffer: &AudioBuffer) -> AudioEffectState 88 | where 89 | O: AsRef<[Sample]> + AsMut<[Sample]>, 90 | { 91 | unsafe { 92 | audionimbus_sys::iplAmbisonicsRotationEffectGetTail( 93 | self.raw_ptr(), 94 | &mut *output_buffer.as_ffi(), 95 | ) 96 | } 97 | .into() 98 | } 99 | 100 | /// Returns the number of tail samples remaining in an Ambisonics rotation effect’s internal buffers. 101 | /// 102 | /// Tail samples are audio samples that should be played even after the input to the effect has stopped playing and no further input samples are available. 103 | pub fn tail_size(&self) -> usize { 104 | unsafe { audionimbus_sys::iplAmbisonicsRotationEffectGetTailSize(self.raw_ptr()) as usize } 105 | } 106 | 107 | /// Resets the internal processing state of an ambisonics rotation effect. 108 | pub fn reset(&mut self) { 109 | unsafe { audionimbus_sys::iplAmbisonicsRotationEffectReset(self.raw_ptr()) }; 110 | } 111 | 112 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLAmbisonicsRotationEffect { 113 | self.0 114 | } 115 | 116 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLAmbisonicsRotationEffect { 117 | &mut self.0 118 | } 119 | } 120 | 121 | impl Clone for AmbisonicsRotationEffect { 122 | fn clone(&self) -> Self { 123 | unsafe { 124 | audionimbus_sys::iplAmbisonicsRotationEffectRetain(self.0); 125 | } 126 | Self(self.0) 127 | } 128 | } 129 | 130 | impl Drop for AmbisonicsRotationEffect { 131 | fn drop(&mut self) { 132 | unsafe { audionimbus_sys::iplAmbisonicsRotationEffectRelease(&mut self.0) } 133 | } 134 | } 135 | 136 | unsafe impl Send for AmbisonicsRotationEffect {} 137 | unsafe impl Sync for AmbisonicsRotationEffect {} 138 | 139 | /// Settings used to create an ambisonics rotation effect. 140 | #[derive(Debug)] 141 | pub struct AmbisonicsRotationEffectSettings { 142 | /// The maximum ambisonics order that will be used by input audio buffers. 143 | pub max_order: usize, 144 | } 145 | 146 | impl From<&AmbisonicsRotationEffectSettings> 147 | for audionimbus_sys::IPLAmbisonicsRotationEffectSettings 148 | { 149 | fn from(settings: &AmbisonicsRotationEffectSettings) -> Self { 150 | Self { 151 | maxOrder: settings.max_order as i32, 152 | } 153 | } 154 | } 155 | 156 | /// Parameters for applying an ambisonics rotation effect to an audio buffer. 157 | #[derive(Debug)] 158 | pub struct AmbisonicsRotationEffectParams { 159 | /// The orientation of the listener. 160 | pub orientation: CoordinateSystem, 161 | 162 | /// Ambisonic order of the input and output buffers. 163 | /// 164 | /// May be less than the `max_order` specified when creating the effect, in which case the effect will process fewer channels, reducing CPU usage. 165 | pub order: usize, 166 | } 167 | 168 | impl AmbisonicsRotationEffectParams { 169 | pub(crate) fn as_ffi( 170 | &self, 171 | ) -> FFIWrapper<'_, audionimbus_sys::IPLAmbisonicsRotationEffectParams, Self> { 172 | let ambisonics_rotation_effect_params = 173 | audionimbus_sys::IPLAmbisonicsRotationEffectParams { 174 | orientation: self.orientation.into(), 175 | order: self.order as i32, 176 | }; 177 | 178 | FFIWrapper::new(ambisonics_rotation_effect_params) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /audionimbus/src/effect/ambisonics/speaker_layout.rs: -------------------------------------------------------------------------------- 1 | use crate::geometry::Direction; 2 | 3 | /// Describes a standard or custom speaker layout. 4 | #[derive(Debug, Clone)] 5 | pub enum SpeakerLayout { 6 | /// Mono. 7 | Mono, 8 | 9 | /// Stereo (left, right). 10 | Stereo, 11 | 12 | /// Front left, front right, rear left, rear right. 13 | Quadraphonic, 14 | 15 | /// Front left, front right, front center, LFE, rear left, rear right. 16 | Surround5_1, 17 | 18 | /// Front left, front right, front center, LFE, rear left, rear right, side left, side right. 19 | Surround7_1, 20 | 21 | /// User-defined speaker layout. 22 | Custom { 23 | /// Unit-length direction for each speaker. 24 | speaker_directions: Vec, 25 | }, 26 | } 27 | 28 | impl From<&SpeakerLayout> for audionimbus_sys::IPLSpeakerLayout { 29 | fn from(speaker_layout: &SpeakerLayout) -> Self { 30 | let (type_, num_speakers, mut speaker_directions) = match speaker_layout { 31 | SpeakerLayout::Mono => ( 32 | audionimbus_sys::IPLSpeakerLayoutType::IPL_SPEAKERLAYOUTTYPE_MONO, 33 | i32::default(), 34 | vec![], 35 | ), 36 | SpeakerLayout::Stereo => ( 37 | audionimbus_sys::IPLSpeakerLayoutType::IPL_SPEAKERLAYOUTTYPE_STEREO, 38 | i32::default(), 39 | vec![], 40 | ), 41 | SpeakerLayout::Quadraphonic => ( 42 | audionimbus_sys::IPLSpeakerLayoutType::IPL_SPEAKERLAYOUTTYPE_QUADRAPHONIC, 43 | i32::default(), 44 | vec![], 45 | ), 46 | SpeakerLayout::Surround5_1 => ( 47 | audionimbus_sys::IPLSpeakerLayoutType::IPL_SPEAKERLAYOUTTYPE_SURROUND_5_1, 48 | i32::default(), 49 | vec![], 50 | ), 51 | SpeakerLayout::Surround7_1 => ( 52 | audionimbus_sys::IPLSpeakerLayoutType::IPL_SPEAKERLAYOUTTYPE_SURROUND_7_1, 53 | i32::default(), 54 | vec![], 55 | ), 56 | SpeakerLayout::Custom { speaker_directions } => ( 57 | audionimbus_sys::IPLSpeakerLayoutType::IPL_SPEAKERLAYOUTTYPE_CUSTOM, 58 | i32::default(), 59 | speaker_directions 60 | .clone() 61 | .into_iter() 62 | .map(|speaker_direction| audionimbus_sys::IPLVector3::from(speaker_direction)) 63 | .collect(), 64 | ), 65 | }; 66 | 67 | Self { 68 | type_, 69 | numSpeakers: num_speakers as i32, 70 | speakers: speaker_directions.as_mut_ptr(), 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /audionimbus/src/effect/ambisonics/type.rs: -------------------------------------------------------------------------------- 1 | /// Supported channel ordering and normalization schemes for Ambisonic audio. 2 | #[derive(Copy, Clone, Debug)] 3 | pub enum AmbisonicsType { 4 | /// ACN channel ordering, orthonormal spherical harmonics. 5 | N3D, 6 | 7 | /// ACN channel ordering, semi-normalized spherical harmonics. 8 | /// AmbiX format. 9 | SN3D, 10 | 11 | /// Furse-Malham (B-format). 12 | FUMA, 13 | } 14 | 15 | impl From for audionimbus_sys::IPLAmbisonicsType { 16 | fn from(ambisonics_type: AmbisonicsType) -> Self { 17 | match ambisonics_type { 18 | AmbisonicsType::N3D => audionimbus_sys::IPLAmbisonicsType::IPL_AMBISONICSTYPE_N3D, 19 | AmbisonicsType::SN3D => audionimbus_sys::IPLAmbisonicsType::IPL_AMBISONICSTYPE_SN3D, 20 | AmbisonicsType::FUMA => audionimbus_sys::IPLAmbisonicsType::IPL_AMBISONICSTYPE_FUMA, 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /audionimbus/src/effect/audio_effect_state.rs: -------------------------------------------------------------------------------- 1 | /// States that an audio effect can be left in after processing a frame of audio. 2 | #[derive(Eq, PartialEq, Debug)] 3 | pub enum AudioEffectState { 4 | /// One or more samples of tail remain in the effect’s internal buffers. 5 | TailRemaining, 6 | 7 | /// No tail remains in the effect’s internal buffers. 8 | TailComplete, 9 | } 10 | 11 | impl From for AudioEffectState { 12 | fn from(state: audionimbus_sys::IPLAudioEffectState) -> Self { 13 | match state { 14 | audionimbus_sys::IPLAudioEffectState::IPL_AUDIOEFFECTSTATE_TAILREMAINING => { 15 | Self::TailRemaining 16 | } 17 | audionimbus_sys::IPLAudioEffectState::IPL_AUDIOEFFECTSTATE_TAILCOMPLETE => { 18 | Self::TailComplete 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /audionimbus/src/effect/binaural.rs: -------------------------------------------------------------------------------- 1 | use super::audio_effect_state::AudioEffectState; 2 | use crate::audio_buffer::{AudioBuffer, Sample}; 3 | use crate::audio_settings::AudioSettings; 4 | use crate::context::Context; 5 | use crate::error::{to_option_error, SteamAudioError}; 6 | use crate::ffi_wrapper::FFIWrapper; 7 | use crate::geometry::Direction; 8 | use crate::hrtf::{Hrtf, HrtfInterpolation}; 9 | 10 | /// Spatializes a point source using an HRTF, based on the 3D position of the source relative to the listener. 11 | /// 12 | /// The source audio can be 1- or 2-channel; in either case all input channels are spatialized from the same position. 13 | #[derive(Debug)] 14 | pub struct BinauralEffect(audionimbus_sys::IPLBinauralEffect); 15 | 16 | impl BinauralEffect { 17 | pub fn try_new( 18 | context: &Context, 19 | audio_settings: &AudioSettings, 20 | binaural_effect_settings: &BinauralEffectSettings, 21 | ) -> Result { 22 | let mut binaural_effect = Self(std::ptr::null_mut()); 23 | 24 | let status = unsafe { 25 | audionimbus_sys::iplBinauralEffectCreate( 26 | context.raw_ptr(), 27 | &mut audionimbus_sys::IPLAudioSettings::from(audio_settings), 28 | &mut audionimbus_sys::IPLBinauralEffectSettings::from(binaural_effect_settings), 29 | binaural_effect.raw_ptr_mut(), 30 | ) 31 | }; 32 | 33 | if let Some(error) = to_option_error(status) { 34 | return Err(error); 35 | } 36 | 37 | Ok(binaural_effect) 38 | } 39 | 40 | /// Applies a binaural effect to an audio buffer. 41 | /// 42 | /// This effect CANNOT be applied in-place. 43 | pub fn apply( 44 | &self, 45 | binaural_effect_params: &BinauralEffectParams, 46 | input_buffer: &AudioBuffer, 47 | output_buffer: &AudioBuffer, 48 | ) -> AudioEffectState 49 | where 50 | I: AsRef<[Sample]>, 51 | O: AsRef<[Sample]> + AsMut<[Sample]>, 52 | { 53 | unsafe { 54 | audionimbus_sys::iplBinauralEffectApply( 55 | self.raw_ptr(), 56 | &mut *binaural_effect_params.as_ffi(), 57 | &mut *input_buffer.as_ffi(), 58 | &mut *output_buffer.as_ffi(), 59 | ) 60 | } 61 | .into() 62 | } 63 | 64 | /// Retrieves a single frame of tail samples from a binaural effect’s internal buffers. 65 | /// 66 | /// After the input to the binaural effect has stopped, this function must be called instead of [`Self::apply`] until the return value indicates that no more tail samples remain. 67 | /// 68 | /// The output audio buffer must be 2-channel. 69 | pub fn tail(&self, output_buffer: &AudioBuffer) -> AudioEffectState 70 | where 71 | O: AsRef<[Sample]> + AsMut<[Sample]>, 72 | { 73 | assert_eq!( 74 | output_buffer.num_channels(), 75 | 2, 76 | "input buffer must have 2 channels", 77 | ); 78 | 79 | unsafe { 80 | audionimbus_sys::iplBinauralEffectGetTail(self.raw_ptr(), &mut *output_buffer.as_ffi()) 81 | } 82 | .into() 83 | } 84 | 85 | /// Returns the number of tail samples remaining in a binaural effect’s internal buffers. 86 | /// 87 | /// Tail samples are audio samples that should be played even after the input to the effect has stopped playing and no further input samples are available. 88 | pub fn tail_size(&self) -> usize { 89 | unsafe { audionimbus_sys::iplBinauralEffectGetTailSize(self.raw_ptr()) as usize } 90 | } 91 | 92 | /// Resets the internal processing state of a binaural effect. 93 | pub fn reset(&mut self) { 94 | unsafe { audionimbus_sys::iplBinauralEffectReset(self.raw_ptr()) }; 95 | } 96 | 97 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLBinauralEffect { 98 | self.0 99 | } 100 | 101 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLBinauralEffect { 102 | &mut self.0 103 | } 104 | } 105 | 106 | impl Clone for BinauralEffect { 107 | fn clone(&self) -> Self { 108 | unsafe { 109 | audionimbus_sys::iplBinauralEffectRetain(self.0); 110 | } 111 | Self(self.0) 112 | } 113 | } 114 | 115 | impl Drop for BinauralEffect { 116 | fn drop(&mut self) { 117 | unsafe { audionimbus_sys::iplBinauralEffectRelease(&mut self.0) } 118 | } 119 | } 120 | 121 | unsafe impl Send for BinauralEffect {} 122 | unsafe impl Sync for BinauralEffect {} 123 | 124 | /// Settings used to create a binaural effect. 125 | #[derive(Debug)] 126 | pub struct BinauralEffectSettings<'a> { 127 | /// The HRTF to use. 128 | pub hrtf: &'a Hrtf, 129 | } 130 | 131 | impl From<&BinauralEffectSettings<'_>> for audionimbus_sys::IPLBinauralEffectSettings { 132 | fn from(settings: &BinauralEffectSettings) -> Self { 133 | Self { 134 | hrtf: settings.hrtf.raw_ptr(), 135 | } 136 | } 137 | } 138 | 139 | /// Parameters for applying an ambisonics binaural effect to an audio buffer. 140 | #[derive(Debug)] 141 | pub struct BinauralEffectParams<'a> { 142 | /// Unit vector pointing from the listener towards the source. 143 | pub direction: Direction, 144 | 145 | /// The interpolation technique to use. 146 | pub interpolation: HrtfInterpolation, 147 | 148 | /// Amount to blend input audio with spatialized audio. 149 | /// 150 | /// When set to 0.0, output audio is not spatialized at all and is close to input audio. 151 | /// If set to 1.0, output audio is fully spatialized. 152 | pub spatial_blend: f32, 153 | 154 | /// The HRTF to use. 155 | pub hrtf: &'a Hrtf, 156 | 157 | /// Optional left- and right-ear peak delays for the HRTF used to spatialize the input audio. 158 | /// Can be None, in which case peak delays will not be written. 159 | pub peak_delays: Option<[f32; 2]>, 160 | } 161 | 162 | impl BinauralEffectParams<'_> { 163 | pub(crate) fn as_ffi(&self) -> FFIWrapper<'_, audionimbus_sys::IPLBinauralEffectParams, Self> { 164 | let peak_delays_ptr = self 165 | .peak_delays 166 | .as_ref() 167 | .map(|peak_delays| peak_delays.as_ptr() as *mut f32) 168 | .unwrap_or(std::ptr::null_mut()); 169 | 170 | let binaural_effect_params = audionimbus_sys::IPLBinauralEffectParams { 171 | direction: self.direction.into(), 172 | interpolation: self.interpolation.into(), 173 | spatialBlend: self.spatial_blend, 174 | hrtf: self.hrtf.raw_ptr(), 175 | peakDelays: peak_delays_ptr, 176 | }; 177 | 178 | FFIWrapper::new(binaural_effect_params) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /audionimbus/src/effect/direct.rs: -------------------------------------------------------------------------------- 1 | use super::audio_effect_state::AudioEffectState; 2 | use super::Equalizer; 3 | use crate::audio_buffer::{AudioBuffer, Sample}; 4 | use crate::audio_settings::AudioSettings; 5 | use crate::context::Context; 6 | use crate::error::{to_option_error, SteamAudioError}; 7 | use crate::ffi_wrapper::FFIWrapper; 8 | 9 | /// Filters and attenuates an audio signal based on various properties of the direct path between a point source and the listener. 10 | #[derive(Debug)] 11 | pub struct DirectEffect(audionimbus_sys::IPLDirectEffect); 12 | 13 | impl DirectEffect { 14 | pub fn try_new( 15 | context: &Context, 16 | audio_settings: &AudioSettings, 17 | direct_effect_settings: &DirectEffectSettings, 18 | ) -> Result { 19 | let mut direct_effect = Self(std::ptr::null_mut()); 20 | 21 | let status = unsafe { 22 | audionimbus_sys::iplDirectEffectCreate( 23 | context.raw_ptr(), 24 | &mut audionimbus_sys::IPLAudioSettings::from(audio_settings), 25 | &mut audionimbus_sys::IPLDirectEffectSettings::from(direct_effect_settings), 26 | direct_effect.raw_ptr_mut(), 27 | ) 28 | }; 29 | 30 | if let Some(error) = to_option_error(status) { 31 | return Err(error); 32 | } 33 | 34 | Ok(direct_effect) 35 | } 36 | 37 | /// Applies a direct effect to an audio buffer. 38 | /// 39 | /// This effect CAN be applied in-place. 40 | pub fn apply( 41 | &self, 42 | direct_effect_params: &DirectEffectParams, 43 | input_buffer: &AudioBuffer, 44 | output_buffer: &AudioBuffer, 45 | ) -> AudioEffectState 46 | where 47 | I: AsRef<[Sample]>, 48 | O: AsRef<[Sample]> + AsMut<[Sample]>, 49 | { 50 | unsafe { 51 | audionimbus_sys::iplDirectEffectApply( 52 | self.raw_ptr(), 53 | &mut *direct_effect_params.as_ffi(), 54 | &mut *input_buffer.as_ffi(), 55 | &mut *output_buffer.as_ffi(), 56 | ) 57 | } 58 | .into() 59 | } 60 | 61 | /// Retrieves a single frame of tail samples from a direct effect’s internal buffers. 62 | /// 63 | /// After the input to the direct effect has stopped, this function must be called instead of [`Self::apply`] until the return value indicates that no more tail samples remain. 64 | /// 65 | /// The output audio buffer must have as many channels as specified when creating the effect. 66 | pub fn tail(&self, output_buffer: &AudioBuffer) -> AudioEffectState 67 | where 68 | O: AsRef<[Sample]> + AsMut<[Sample]>, 69 | { 70 | unsafe { 71 | audionimbus_sys::iplDirectEffectGetTail(self.raw_ptr(), &mut *output_buffer.as_ffi()) 72 | } 73 | .into() 74 | } 75 | 76 | /// Returns the number of tail samples remaining in a direct effect’s internal buffers. 77 | /// 78 | /// Tail samples are audio samples that should be played even after the input to the effect has stopped playing and no further input samples are available. 79 | pub fn tail_size(&self) -> usize { 80 | unsafe { audionimbus_sys::iplDirectEffectGetTailSize(self.raw_ptr()) as usize } 81 | } 82 | 83 | /// Resets the internal processing state of a direct effect. 84 | pub fn reset(&mut self) { 85 | unsafe { audionimbus_sys::iplDirectEffectReset(self.raw_ptr()) }; 86 | } 87 | 88 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLDirectEffect { 89 | self.0 90 | } 91 | 92 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLDirectEffect { 93 | &mut self.0 94 | } 95 | } 96 | 97 | impl Clone for DirectEffect { 98 | fn clone(&self) -> Self { 99 | unsafe { 100 | audionimbus_sys::iplDirectEffectRetain(self.0); 101 | } 102 | Self(self.0) 103 | } 104 | } 105 | 106 | impl Drop for DirectEffect { 107 | fn drop(&mut self) { 108 | unsafe { audionimbus_sys::iplDirectEffectRelease(&mut self.0) } 109 | } 110 | } 111 | 112 | unsafe impl Send for DirectEffect {} 113 | unsafe impl Sync for DirectEffect {} 114 | 115 | /// Settings used to create a direct effect. 116 | #[derive(Debug)] 117 | pub struct DirectEffectSettings { 118 | /// Number of channels that will be used by input and output buffers. 119 | pub num_channels: usize, 120 | } 121 | 122 | impl From<&DirectEffectSettings> for audionimbus_sys::IPLDirectEffectSettings { 123 | fn from(settings: &DirectEffectSettings) -> Self { 124 | Self { 125 | numChannels: settings.num_channels as i32, 126 | } 127 | } 128 | } 129 | 130 | /// Parameters for applying a direct effect to an audio buffer. 131 | #[derive(Default, Debug)] 132 | pub struct DirectEffectParams { 133 | /// Optional distance attenuation, with a value between 0.0 and 1.0. 134 | pub distance_attenuation: Option, 135 | 136 | /// Optional air absorption. 137 | pub air_absorption: Option>, 138 | 139 | /// Optional directivity term, with a value between 0.0 and 1.0. 140 | pub directivity: Option, 141 | 142 | /// Optional occlusion factor, with a value between 0.0 and 1.0. 143 | pub occlusion: Option, 144 | 145 | /// Optional transmission. 146 | pub transmission: Option, 147 | } 148 | 149 | impl From for DirectEffectParams { 150 | fn from(params: audionimbus_sys::IPLDirectEffectParams) -> Self { 151 | let distance_attenuation = Some(params.distanceAttenuation); 152 | let air_absorption = Some(Equalizer(params.airAbsorption)); 153 | let directivity = Some(params.directivity); 154 | let occlusion = Some(params.occlusion); 155 | let transmission = Some(match params.transmissionType { 156 | audionimbus_sys::IPLTransmissionType::IPL_TRANSMISSIONTYPE_FREQINDEPENDENT => { 157 | Transmission::FrequencyIndependent(Equalizer(params.transmission)) 158 | } 159 | audionimbus_sys::IPLTransmissionType::IPL_TRANSMISSIONTYPE_FREQDEPENDENT => { 160 | Transmission::FrequencyDependent(Equalizer(params.transmission)) 161 | } 162 | }); 163 | 164 | Self { 165 | distance_attenuation, 166 | air_absorption, 167 | directivity, 168 | occlusion, 169 | transmission, 170 | } 171 | } 172 | } 173 | 174 | impl DirectEffectParams { 175 | pub(crate) fn as_ffi(&self) -> FFIWrapper<'_, audionimbus_sys::IPLDirectEffectParams, Self> { 176 | let mut flags = audionimbus_sys::IPLDirectEffectFlags(<_>::default()); 177 | 178 | let distance_attenuation = self.distance_attenuation.unwrap_or_default(); 179 | if self.distance_attenuation.is_some() { 180 | flags |= audionimbus_sys::IPLDirectEffectFlags::IPL_DIRECTEFFECTFLAGS_APPLYDISTANCEATTENUATION; 181 | } 182 | 183 | let default_air_absorption = Default::default(); 184 | let air_absorption = self 185 | .air_absorption 186 | .as_ref() 187 | .unwrap_or(&default_air_absorption); 188 | if self.air_absorption.is_some() { 189 | flags |= 190 | audionimbus_sys::IPLDirectEffectFlags::IPL_DIRECTEFFECTFLAGS_APPLYAIRABSORPTION; 191 | } 192 | 193 | let directivity = self.directivity.unwrap_or_default(); 194 | if self.directivity.is_some() { 195 | flags |= audionimbus_sys::IPLDirectEffectFlags::IPL_DIRECTEFFECTFLAGS_APPLYDIRECTIVITY; 196 | } 197 | 198 | let occlusion = self.occlusion.unwrap_or_default(); 199 | if self.occlusion.is_some() { 200 | flags |= audionimbus_sys::IPLDirectEffectFlags::IPL_DIRECTEFFECTFLAGS_APPLYOCCLUSION; 201 | } 202 | 203 | let (transmission_type, transmission) = if let Some(transmission) = &self.transmission { 204 | flags |= audionimbus_sys::IPLDirectEffectFlags::IPL_DIRECTEFFECTFLAGS_APPLYTRANSMISSION; 205 | 206 | match transmission { 207 | Transmission::FrequencyIndependent(equalizer) => ( 208 | audionimbus_sys::IPLTransmissionType::IPL_TRANSMISSIONTYPE_FREQINDEPENDENT, 209 | equalizer, 210 | ), 211 | Transmission::FrequencyDependent(equalizer) => ( 212 | audionimbus_sys::IPLTransmissionType::IPL_TRANSMISSIONTYPE_FREQDEPENDENT, 213 | equalizer, 214 | ), 215 | } 216 | } else { 217 | ( 218 | audionimbus_sys::IPLTransmissionType::IPL_TRANSMISSIONTYPE_FREQINDEPENDENT, 219 | &Default::default(), 220 | ) 221 | }; 222 | 223 | let direct_effect_params = audionimbus_sys::IPLDirectEffectParams { 224 | flags, 225 | transmissionType: transmission_type, 226 | distanceAttenuation: distance_attenuation, 227 | airAbsorption: **air_absorption, 228 | directivity, 229 | occlusion, 230 | transmission: **transmission, 231 | }; 232 | 233 | FFIWrapper::new(direct_effect_params) 234 | } 235 | } 236 | 237 | /// Transmission parameters. 238 | #[derive(Debug)] 239 | pub enum Transmission { 240 | /// Frequency-independent transmission. 241 | FrequencyIndependent(Equalizer<3>), 242 | 243 | /// Frequency-dependent transmission. 244 | FrequencyDependent(Equalizer<3>), 245 | } 246 | -------------------------------------------------------------------------------- /audionimbus/src/effect/equalizer.rs: -------------------------------------------------------------------------------- 1 | /// An N-band equalizer, with band coefficients between 0.0 and 1.0. 2 | #[derive(Debug)] 3 | pub struct Equalizer(pub [f32; N]); 4 | 5 | impl Default for Equalizer { 6 | fn default() -> Self { 7 | Self([0.0; N]) 8 | } 9 | } 10 | 11 | impl std::ops::Deref for Equalizer { 12 | type Target = [f32; N]; 13 | 14 | fn deref(&self) -> &Self::Target { 15 | &self.0 16 | } 17 | } 18 | 19 | impl std::ops::DerefMut for Equalizer { 20 | fn deref_mut(&mut self) -> &mut Self::Target { 21 | &mut self.0 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /audionimbus/src/effect/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ambisonics; 2 | pub use ambisonics::*; 3 | 4 | pub mod binaural; 5 | pub use binaural::*; 6 | 7 | pub mod direct; 8 | pub use direct::*; 9 | 10 | pub mod reflection; 11 | pub use reflection::*; 12 | 13 | pub mod panning; 14 | pub use panning::*; 15 | 16 | pub mod path; 17 | pub use path::*; 18 | 19 | pub mod virtual_surround; 20 | pub use virtual_surround::*; 21 | 22 | mod equalizer; 23 | pub use equalizer::Equalizer; 24 | 25 | mod audio_effect_state; 26 | pub use audio_effect_state::AudioEffectState; 27 | -------------------------------------------------------------------------------- /audionimbus/src/effect/panning.rs: -------------------------------------------------------------------------------- 1 | use super::audio_effect_state::AudioEffectState; 2 | use super::SpeakerLayout; 3 | use crate::audio_buffer::{AudioBuffer, Sample}; 4 | use crate::audio_settings::AudioSettings; 5 | use crate::context::Context; 6 | use crate::error::{to_option_error, SteamAudioError}; 7 | use crate::ffi_wrapper::FFIWrapper; 8 | use crate::geometry::Direction; 9 | 10 | /// Pans a single-channel point source to a multi-channel speaker layout based on the 3D position of the source relative to the listener. 11 | #[derive(Debug)] 12 | pub struct PanningEffect(audionimbus_sys::IPLPanningEffect); 13 | 14 | impl PanningEffect { 15 | pub fn try_new( 16 | context: &Context, 17 | audio_settings: &AudioSettings, 18 | panning_effect_settings: &PanningEffectSettings, 19 | ) -> Result { 20 | let mut panning_effect = Self(std::ptr::null_mut()); 21 | 22 | let status = unsafe { 23 | audionimbus_sys::iplPanningEffectCreate( 24 | context.raw_ptr(), 25 | &mut audionimbus_sys::IPLAudioSettings::from(audio_settings), 26 | &mut audionimbus_sys::IPLPanningEffectSettings::from(panning_effect_settings), 27 | panning_effect.raw_ptr_mut(), 28 | ) 29 | }; 30 | 31 | if let Some(error) = to_option_error(status) { 32 | return Err(error); 33 | } 34 | 35 | Ok(panning_effect) 36 | } 37 | 38 | /// Applies a panning effect to an audio buffer. 39 | /// 40 | /// This effect CANNOT be applied in-place. 41 | pub fn apply( 42 | &self, 43 | panning_effect_params: &PanningEffectParams, 44 | input_buffer: &AudioBuffer, 45 | output_buffer: &AudioBuffer, 46 | ) -> AudioEffectState 47 | where 48 | I: AsRef<[Sample]>, 49 | O: AsRef<[Sample]> + AsMut<[Sample]>, 50 | { 51 | unsafe { 52 | audionimbus_sys::iplPanningEffectApply( 53 | self.raw_ptr(), 54 | &mut *panning_effect_params.as_ffi(), 55 | &mut *input_buffer.as_ffi(), 56 | &mut *output_buffer.as_ffi(), 57 | ) 58 | } 59 | .into() 60 | } 61 | 62 | /// Retrieves a single frame of tail samples from a panning effect’s internal buffers. 63 | /// 64 | /// After the input to the panning effect has stopped, this function must be called instead of [`Self::apply`] until the return value indicates that no more tail samples remain. 65 | /// 66 | /// The output audio buffer must have as many channels as needed for the speaker layout specified when creating the panning effect. 67 | pub fn tail(&self, output_buffer: &AudioBuffer) -> AudioEffectState 68 | where 69 | O: AsRef<[Sample]> + AsMut<[Sample]>, 70 | { 71 | unsafe { 72 | audionimbus_sys::iplPanningEffectGetTail(self.raw_ptr(), &mut *output_buffer.as_ffi()) 73 | } 74 | .into() 75 | } 76 | 77 | /// Returns the number of tail samples remaining in a panning effect’s internal buffers. 78 | /// 79 | /// Tail samples are audio samples that should be played even after the input to the effect has stopped playing and no further input samples are available. 80 | pub fn tail_size(&self) -> usize { 81 | unsafe { audionimbus_sys::iplPanningEffectGetTailSize(self.raw_ptr()) as usize } 82 | } 83 | 84 | /// Resets the internal processing state of a panning effect. 85 | pub fn reset(&mut self) { 86 | unsafe { audionimbus_sys::iplPanningEffectReset(self.raw_ptr()) }; 87 | } 88 | 89 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLPanningEffect { 90 | self.0 91 | } 92 | 93 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLPanningEffect { 94 | &mut self.0 95 | } 96 | } 97 | 98 | impl Clone for PanningEffect { 99 | fn clone(&self) -> Self { 100 | unsafe { 101 | audionimbus_sys::iplPanningEffectRetain(self.0); 102 | } 103 | Self(self.0) 104 | } 105 | } 106 | 107 | impl Drop for PanningEffect { 108 | fn drop(&mut self) { 109 | unsafe { audionimbus_sys::iplPanningEffectRelease(&mut self.0) } 110 | } 111 | } 112 | 113 | unsafe impl Send for PanningEffect {} 114 | unsafe impl Sync for PanningEffect {} 115 | 116 | /// Settings used to create a panning effect. 117 | #[derive(Debug)] 118 | pub struct PanningEffectSettings { 119 | /// The speaker layout to pan input audio to. 120 | pub speaker_layout: SpeakerLayout, 121 | } 122 | 123 | impl From<&PanningEffectSettings> for audionimbus_sys::IPLPanningEffectSettings { 124 | fn from(settings: &PanningEffectSettings) -> Self { 125 | Self { 126 | speakerLayout: (&settings.speaker_layout).into(), 127 | } 128 | } 129 | } 130 | 131 | /// Parameters for applying a panning effect to an audio buffer. 132 | #[derive(Debug)] 133 | pub struct PanningEffectParams { 134 | /// Unit vector pointing from the listener towards the source. 135 | pub direction: Direction, 136 | } 137 | 138 | impl From for PanningEffectParams { 139 | fn from(params: audionimbus_sys::IPLPanningEffectParams) -> Self { 140 | Self { 141 | direction: params.direction.into(), 142 | } 143 | } 144 | } 145 | 146 | impl PanningEffectParams { 147 | pub(crate) fn as_ffi(&self) -> FFIWrapper<'_, audionimbus_sys::IPLPanningEffectParams, Self> { 148 | let panning_effect_params = audionimbus_sys::IPLPanningEffectParams { 149 | direction: self.direction.into(), 150 | }; 151 | 152 | FFIWrapper::new(panning_effect_params) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /audionimbus/src/effect/virtual_surround.rs: -------------------------------------------------------------------------------- 1 | use super::audio_effect_state::AudioEffectState; 2 | use super::SpeakerLayout; 3 | use crate::audio_buffer::{AudioBuffer, Sample}; 4 | use crate::audio_settings::AudioSettings; 5 | use crate::context::Context; 6 | use crate::error::{to_option_error, SteamAudioError}; 7 | use crate::ffi_wrapper::FFIWrapper; 8 | use crate::Hrtf; 9 | 10 | /// Spatializes multi-channel speaker-based audio (e.g., stereo, quadraphonic, 5.1, or 7.1) using HRTF-based binaural rendering. 11 | /// 12 | /// The audio signal for each speaker is spatialized from a point in space corresponding to the speaker’s location. 13 | /// This allows users to experience a surround sound mix over regular stereo headphones. 14 | /// 15 | /// Virtual surround is also a fast way to get approximate binaural rendering. 16 | /// All sources can be panned to some surround format (say, 7.1). 17 | /// After the sources are mixed, the mix can be rendered using virtual surround. 18 | /// This can reduce CPU usage, at the cost of spatialization accuracy. 19 | #[derive(Debug)] 20 | pub struct VirtualSurroundEffect(audionimbus_sys::IPLVirtualSurroundEffect); 21 | 22 | impl VirtualSurroundEffect { 23 | pub fn try_new( 24 | context: &Context, 25 | audio_settings: &AudioSettings, 26 | virtual_surround_effect_settings: &VirtualSurroundEffectSettings, 27 | ) -> Result { 28 | let mut virtual_surround_effect = Self(std::ptr::null_mut()); 29 | 30 | let status = unsafe { 31 | audionimbus_sys::iplVirtualSurroundEffectCreate( 32 | context.raw_ptr(), 33 | &mut audionimbus_sys::IPLAudioSettings::from(audio_settings), 34 | &mut audionimbus_sys::IPLVirtualSurroundEffectSettings::from( 35 | virtual_surround_effect_settings, 36 | ), 37 | virtual_surround_effect.raw_ptr_mut(), 38 | ) 39 | }; 40 | 41 | if let Some(error) = to_option_error(status) { 42 | return Err(error); 43 | } 44 | 45 | Ok(virtual_surround_effect) 46 | } 47 | 48 | /// Applies a virtual surround effect to an audio buffer. 49 | /// 50 | /// This effect CANNOT be applied in-place. 51 | pub fn apply( 52 | &self, 53 | virtual_surround_effect_params: &VirtualSurroundEffectParams, 54 | input_buffer: &AudioBuffer, 55 | output_buffer: &AudioBuffer, 56 | ) -> AudioEffectState 57 | where 58 | I: AsRef<[Sample]>, 59 | O: AsRef<[Sample]> + AsMut<[Sample]>, 60 | { 61 | unsafe { 62 | audionimbus_sys::iplVirtualSurroundEffectApply( 63 | self.raw_ptr(), 64 | &mut *virtual_surround_effect_params.as_ffi(), 65 | &mut *input_buffer.as_ffi(), 66 | &mut *output_buffer.as_ffi(), 67 | ) 68 | } 69 | .into() 70 | } 71 | 72 | /// Retrieves a single frame of tail samples from a virtual surround effect’s internal buffers. 73 | /// 74 | /// After the input to the virtual surround effect has stopped, this function must be called instead of [`Self::apply`] until the return value indicates that no more tail samples remain. 75 | /// 76 | /// The output audio buffer must be 2-channel. 77 | pub fn tail(&self, output_buffer: &AudioBuffer) -> AudioEffectState 78 | where 79 | O: AsRef<[Sample]> + AsMut<[Sample]>, 80 | { 81 | assert_eq!( 82 | output_buffer.num_channels(), 83 | 2, 84 | "input buffer must have 2 channels", 85 | ); 86 | 87 | unsafe { 88 | audionimbus_sys::iplVirtualSurroundEffectGetTail( 89 | self.raw_ptr(), 90 | &mut *output_buffer.as_ffi(), 91 | ) 92 | } 93 | .into() 94 | } 95 | 96 | /// Returns the number of tail samples remaining in a virtual surround effect’s internal buffers. 97 | /// 98 | /// Tail samples are audio samples that should be played even after the input to the effect has stopped playing and no further input samples are available. 99 | pub fn tail_size(&self) -> usize { 100 | unsafe { audionimbus_sys::iplVirtualSurroundEffectGetTailSize(self.raw_ptr()) as usize } 101 | } 102 | 103 | /// Resets the internal processing state of a virtual surround effect. 104 | pub fn reset(&mut self) { 105 | unsafe { audionimbus_sys::iplVirtualSurroundEffectReset(self.raw_ptr()) }; 106 | } 107 | 108 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLVirtualSurroundEffect { 109 | self.0 110 | } 111 | 112 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLVirtualSurroundEffect { 113 | &mut self.0 114 | } 115 | } 116 | 117 | impl Clone for VirtualSurroundEffect { 118 | fn clone(&self) -> Self { 119 | unsafe { 120 | audionimbus_sys::iplVirtualSurroundEffectRetain(self.0); 121 | } 122 | Self(self.0) 123 | } 124 | } 125 | 126 | impl Drop for VirtualSurroundEffect { 127 | fn drop(&mut self) { 128 | unsafe { audionimbus_sys::iplVirtualSurroundEffectRelease(&mut self.0) } 129 | } 130 | } 131 | 132 | unsafe impl Send for VirtualSurroundEffect {} 133 | unsafe impl Sync for VirtualSurroundEffect {} 134 | 135 | /// Settings used to create a virtual surround effect. 136 | #[derive(Debug)] 137 | pub struct VirtualSurroundEffectSettings<'a> { 138 | /// The speaker layout that will be used by input audio buffers. 139 | pub speaker_layout: SpeakerLayout, 140 | 141 | /// The HRTF to use. 142 | pub hrtf: &'a Hrtf, 143 | } 144 | 145 | impl From<&VirtualSurroundEffectSettings<'_>> 146 | for audionimbus_sys::IPLVirtualSurroundEffectSettings 147 | { 148 | fn from(settings: &VirtualSurroundEffectSettings) -> Self { 149 | Self { 150 | speakerLayout: (&settings.speaker_layout).into(), 151 | hrtf: settings.hrtf.raw_ptr(), 152 | } 153 | } 154 | } 155 | 156 | /// Parameters for applying a virtual surround effect to an audio buffer. 157 | #[derive(Debug)] 158 | pub struct VirtualSurroundEffectParams<'a> { 159 | /// The HRTF to use. 160 | pub hrtf: &'a Hrtf, 161 | } 162 | 163 | impl VirtualSurroundEffectParams<'_> { 164 | pub(crate) fn as_ffi( 165 | &self, 166 | ) -> FFIWrapper<'_, audionimbus_sys::IPLVirtualSurroundEffectParams, Self> { 167 | let virtual_surround_effect_params = audionimbus_sys::IPLVirtualSurroundEffectParams { 168 | hrtf: self.hrtf.raw_ptr(), 169 | }; 170 | 171 | FFIWrapper::new(virtual_surround_effect_params) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /audionimbus/src/error.rs: -------------------------------------------------------------------------------- 1 | /// A Steam Audio error. 2 | #[derive(Debug)] 3 | pub enum SteamAudioError { 4 | /// An unspecified error occurred. 5 | Unspecified, 6 | 7 | /// The system ran out of memory. 8 | OutOfMemory, 9 | 10 | /// An error occurred while initializing an external dependency. 11 | Initialization, 12 | } 13 | 14 | impl std::error::Error for SteamAudioError {} 15 | 16 | impl std::fmt::Display for SteamAudioError { 17 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 18 | match &self { 19 | Self::Unspecified => write!(f, "unspecified error",), 20 | Self::OutOfMemory => write!(f, "out of memory"), 21 | Self::Initialization => write!(f, "error while initializing an external dependency"), 22 | } 23 | } 24 | } 25 | 26 | pub fn to_option_error(status: audionimbus_sys::IPLerror) -> Option { 27 | match status { 28 | audionimbus_sys::IPLerror::IPL_STATUS_SUCCESS => None, 29 | audionimbus_sys::IPLerror::IPL_STATUS_FAILURE => Some(SteamAudioError::Unspecified), 30 | audionimbus_sys::IPLerror::IPL_STATUS_OUTOFMEMORY => Some(SteamAudioError::OutOfMemory), 31 | audionimbus_sys::IPLerror::IPL_STATUS_INITIALIZATION => { 32 | Some(SteamAudioError::Initialization) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /audionimbus/src/ffi_wrapper.rs: -------------------------------------------------------------------------------- 1 | /// A generic wrapper that ties the lifetime of an FFI type (`T`) to the lifetime of a struct (`Owner`). 2 | #[derive(Debug)] 3 | pub struct FFIWrapper<'a, T, Owner> { 4 | pub ffi_object: T, 5 | _marker: std::marker::PhantomData<&'a Owner>, 6 | } 7 | 8 | impl FFIWrapper<'_, T, Owner> { 9 | pub fn new(ffi_object: T) -> Self { 10 | FFIWrapper { 11 | ffi_object, 12 | _marker: std::marker::PhantomData, 13 | } 14 | } 15 | } 16 | 17 | impl std::ops::Deref for FFIWrapper<'_, T, Owner> { 18 | type Target = T; 19 | 20 | fn deref(&self) -> &Self::Target { 21 | &self.ffi_object 22 | } 23 | } 24 | 25 | impl std::ops::DerefMut for FFIWrapper<'_, T, Owner> { 26 | fn deref_mut(&mut self) -> &mut Self::Target { 27 | &mut self.ffi_object 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /audionimbus/src/fmod.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::hrtf::Hrtf; 3 | use crate::simulation::{SimulationSettings, Source}; 4 | 5 | /// Initializes the FMOD Studio integration. 6 | /// 7 | /// This function must be called before creating any Steam Audio DSP effects. 8 | pub fn initialize(context: &Context) { 9 | unsafe { audionimbus_sys::fmod::iplFMODInitialize(context.raw_ptr()) } 10 | } 11 | 12 | /// Shuts down the FMOD Studio integration. 13 | /// 14 | /// This function must be called after all Steam Audio DSP effects have been destroyed. 15 | pub fn terminate() { 16 | unsafe { audionimbus_sys::fmod::iplFMODTerminate() } 17 | } 18 | 19 | /// Specifies the simulation settings used by the game engine for simulating direct and/or indirect sound propagation. 20 | /// 21 | /// This function must be called once during initialization, after [`initialize`]. 22 | pub fn set_simulation_settings(simulation_settings: SimulationSettings) { 23 | unsafe { 24 | audionimbus_sys::fmod::iplFMODSetSimulationSettings( 25 | audionimbus_sys::IPLSimulationSettings::from(simulation_settings), 26 | ) 27 | } 28 | } 29 | 30 | /// Specifies the HRTF to use for spatialization in subsequent audio frames. 31 | /// 32 | /// This function must be called once during initialization, after [`initialize`]. 33 | /// It should also be called whenever the game engine needs to change the HRTF. 34 | pub fn set_hrtf(hrtf: &Hrtf) { 35 | unsafe { audionimbus_sys::fmod::iplFMODSetHRTF(hrtf.raw_ptr()) } 36 | } 37 | 38 | /// Enables or disables HRTF. 39 | pub fn set_hrtf_disabled(disabled: bool) { 40 | unsafe { audionimbus_sys::fmod::iplFMODSetHRTFDisabled(disabled) } 41 | } 42 | 43 | /// A handle to a [`Source`] that can be used in C# scripts. 44 | pub type SourceHandle = i32; 45 | 46 | /// Registers a source, and returns the corresponding handle. 47 | pub fn add_source(source: &Source) -> SourceHandle { 48 | unsafe { audionimbus_sys::fmod::iplFMODAddSource(source.raw_ptr()) } 49 | } 50 | 51 | /// Unregisters a [`Source`] associated with the given handle. 52 | pub fn remove_source(handle: SourceHandle) { 53 | unsafe { audionimbus_sys::fmod::iplFMODRemoveSource(handle as audionimbus_sys::IPLint32) } 54 | } 55 | 56 | /// Specifies the [`Source`] used by the game engine for simulating reverb. 57 | /// 58 | /// Typically, listener-centric reverb is simulated by creating a [`Source`] with the same position as the listener, and simulating reflections. 59 | /// To render this simulated reverb, call this function and pass it the [`Source`] used. 60 | pub fn set_reverb_source(source: &Source) { 61 | unsafe { audionimbus_sys::fmod::iplFMODSetReverbSource(source.raw_ptr()) } 62 | } 63 | 64 | /// Returns the version of the FMOD Studio integration being used. 65 | pub fn version() -> FmodStudioIntegrationVersion { 66 | use std::os::raw::c_uint; 67 | 68 | let mut major: c_uint = 0; 69 | let mut minor: c_uint = 0; 70 | let mut patch: c_uint = 0; 71 | 72 | unsafe { 73 | audionimbus_sys::fmod::iplFMODGetVersion(&mut major, &mut minor, &mut patch); 74 | } 75 | 76 | FmodStudioIntegrationVersion { 77 | major: major as usize, 78 | minor: minor as usize, 79 | patch: patch as usize, 80 | } 81 | } 82 | 83 | /// The version of the FMOD Studio integration. 84 | #[derive(Copy, Clone, Debug)] 85 | pub struct FmodStudioIntegrationVersion { 86 | pub major: usize, 87 | pub minor: usize, 88 | pub patch: usize, 89 | } 90 | -------------------------------------------------------------------------------- /audionimbus/src/geometry/coordinate_system.rs: -------------------------------------------------------------------------------- 1 | use super::{Point, Vector3}; 2 | 3 | /// A 3D coordinate system, expressed relative to a canonical coordinate system. 4 | #[derive(Copy, Clone, Debug)] 5 | pub struct CoordinateSystem { 6 | /// Unit vector pointing to the right (local +x axis). 7 | pub right: Vector3, 8 | 9 | /// Unit vector pointing upwards (local +y axis). 10 | pub up: Vector3, 11 | 12 | /// Unit vector pointing forwards (local -z axis). 13 | pub ahead: Vector3, 14 | 15 | /// The origin, relative to the canonical coordinate system. 16 | pub origin: Point, 17 | } 18 | 19 | impl Default for CoordinateSystem { 20 | fn default() -> Self { 21 | Self { 22 | right: Vector3::new(1.0, 0.0, 0.0), 23 | up: Vector3::new(0.0, 1.0, 0.0), 24 | ahead: Vector3::new(0.0, 0.0, 1.0), 25 | origin: Vector3::new(0.0, 0.0, 0.0), 26 | } 27 | } 28 | } 29 | 30 | impl From for audionimbus_sys::IPLCoordinateSpace3 { 31 | fn from(coordinate_system: CoordinateSystem) -> Self { 32 | Self { 33 | right: coordinate_system.right.into(), 34 | up: coordinate_system.up.into(), 35 | ahead: coordinate_system.ahead.into(), 36 | origin: coordinate_system.origin.into(), 37 | } 38 | } 39 | } 40 | 41 | impl From for CoordinateSystem { 42 | fn from(coordinate_system: audionimbus_sys::IPLCoordinateSpace3) -> Self { 43 | Self { 44 | right: coordinate_system.right.into(), 45 | up: coordinate_system.up.into(), 46 | ahead: coordinate_system.ahead.into(), 47 | origin: coordinate_system.origin.into(), 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /audionimbus/src/geometry/direction.rs: -------------------------------------------------------------------------------- 1 | use super::Vector3; 2 | 3 | /// A direction in 3D space. 4 | pub type Direction = Vector3; 5 | -------------------------------------------------------------------------------- /audionimbus/src/geometry/instanced_mesh.rs: -------------------------------------------------------------------------------- 1 | use super::{Matrix, Scene}; 2 | use crate::error::{to_option_error, SteamAudioError}; 3 | 4 | /// A triangle mesh that can be moved (translated), rotated, or scaled, but cannot deform. 5 | /// 6 | /// Portions of a scene that undergo rigid-body motion can be represented as instanced meshes. 7 | /// An instanced mesh is essentially a scene (called the “sub-scene”) with a transform applied to it. 8 | /// Adding an instanced mesh to a scene places the sub-scene into the scene with the transform applied. 9 | /// For example, the sub-scene may be a prefab door, and the transform can be used to place it in a doorway and animate it as it opens or closes. 10 | #[derive(Debug)] 11 | pub struct InstancedMesh(audionimbus_sys::IPLInstancedMesh); 12 | 13 | impl InstancedMesh { 14 | pub fn try_new( 15 | scene: &Scene, 16 | settings: &InstancedMeshSettings, 17 | ) -> Result { 18 | let mut instanced_mesh = Self(std::ptr::null_mut()); 19 | 20 | let mut instanced_mesh_settings_ffi = audionimbus_sys::IPLInstancedMeshSettings { 21 | subScene: settings.sub_scene.raw_ptr(), 22 | transform: settings.transform.into(), 23 | }; 24 | 25 | let status = unsafe { 26 | audionimbus_sys::iplInstancedMeshCreate( 27 | scene.raw_ptr(), 28 | &mut instanced_mesh_settings_ffi, 29 | instanced_mesh.raw_ptr_mut(), 30 | ) 31 | }; 32 | 33 | if let Some(error) = to_option_error(status) { 34 | return Err(error); 35 | } 36 | 37 | Ok(instanced_mesh) 38 | } 39 | 40 | /// Updates the local-to-world transform of the instanced mesh within its parent scene. 41 | /// 42 | /// This function allows the instanced mesh to be moved, rotated, and scaled dynamically. 43 | /// 44 | /// After calling this function, [`Scene::commit`] must be called for the changes to take effect. 45 | pub fn update_transform(&mut self, scene: &Scene, new_transform: &Matrix) { 46 | unsafe { 47 | audionimbus_sys::iplInstancedMeshUpdateTransform( 48 | self.raw_ptr(), 49 | scene.raw_ptr(), 50 | new_transform.into(), 51 | ); 52 | } 53 | } 54 | 55 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLInstancedMesh { 56 | self.0 57 | } 58 | 59 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLInstancedMesh { 60 | &mut self.0 61 | } 62 | } 63 | 64 | impl Clone for InstancedMesh { 65 | fn clone(&self) -> Self { 66 | unsafe { 67 | audionimbus_sys::iplInstancedMeshRetain(self.0); 68 | } 69 | Self(self.0) 70 | } 71 | } 72 | 73 | impl Drop for InstancedMesh { 74 | fn drop(&mut self) { 75 | unsafe { audionimbus_sys::iplInstancedMeshRelease(&mut self.0) } 76 | } 77 | } 78 | 79 | unsafe impl Send for InstancedMesh {} 80 | unsafe impl Sync for InstancedMesh {} 81 | 82 | /// Settings used to create an instanced mesh. 83 | #[derive(Debug)] 84 | pub struct InstancedMeshSettings<'a> { 85 | /// Handle to the scene to be instantiated. 86 | pub sub_scene: &'a Scene, 87 | 88 | /// Local-to-world transform that places the instance within the parent scene. 89 | pub transform: &'a Matrix, 90 | } 91 | -------------------------------------------------------------------------------- /audionimbus/src/geometry/material.rs: -------------------------------------------------------------------------------- 1 | /// The acoustic properties of a surface. 2 | /// 3 | /// You can specify the acoustic material properties of each triangle, although typically many triangles will share a common material. 4 | /// 5 | /// The acoustic material properties are specified for three frequency bands with center frequencies of 400 Hz, 2.5 KHz, and 15 KHz. 6 | #[derive(Copy, Clone, Debug)] 7 | pub struct Material { 8 | /// Fraction of sound energy absorbed at low, middle, high frequencies. 9 | /// 10 | /// Between 0.0 and 1.0. 11 | pub absorption: [f32; 3], 12 | 13 | /// Fraction of sound energy scattered in a random direction on reflection. 14 | /// 15 | /// Between 0.0 (pure specular) and 1.0 (pure diffuse). 16 | pub scattering: f32, 17 | 18 | /// Fraction of sound energy transmitted through at low, middle, high frequencies. 19 | /// 20 | /// Between 0.0 and 1.0. Only used for direct occlusion calculations. 21 | pub transmission: [f32; 3], 22 | } 23 | 24 | impl Material { 25 | pub const GENERIC: Self = Self { 26 | absorption: [0.10, 0.20, 0.30], 27 | scattering: 0.05, 28 | transmission: [0.100, 0.050, 0.030], 29 | }; 30 | 31 | pub const BRICK: Self = Self { 32 | absorption: [0.03, 0.04, 0.07], 33 | scattering: 0.05, 34 | transmission: [0.015, 0.015, 0.015], 35 | }; 36 | 37 | pub const CONCRETE: Self = Self { 38 | absorption: [0.05, 0.07, 0.08], 39 | scattering: 0.05, 40 | transmission: [0.015, 0.002, 0.001], 41 | }; 42 | 43 | pub const CERAMIC: Self = Self { 44 | absorption: [0.01, 0.02, 0.02], 45 | scattering: 0.05, 46 | transmission: [0.060, 0.044, 0.011], 47 | }; 48 | 49 | pub const GRAVEL: Self = Self { 50 | absorption: [0.60, 0.70, 0.80], 51 | scattering: 0.05, 52 | transmission: [0.031, 0.012, 0.008], 53 | }; 54 | 55 | pub const CARPET: Self = Self { 56 | absorption: [0.24, 0.69, 0.73], 57 | scattering: 0.05, 58 | transmission: [0.020, 0.005, 0.003], 59 | }; 60 | 61 | pub const GLASS: Self = Self { 62 | absorption: [0.06, 0.03, 0.02], 63 | scattering: 0.05, 64 | transmission: [0.060, 0.044, 0.011], 65 | }; 66 | 67 | pub const PLASTER: Self = Self { 68 | absorption: [0.12, 0.06, 0.04], 69 | scattering: 0.05, 70 | transmission: [0.056, 0.056, 0.004], 71 | }; 72 | 73 | pub const WOOD: Self = Self { 74 | absorption: [0.11, 0.07, 0.06], 75 | scattering: 0.05, 76 | transmission: [0.070, 0.014, 0.005], 77 | }; 78 | 79 | pub const METAL: Self = Self { 80 | absorption: [0.20, 0.07, 0.06], 81 | scattering: 0.05, 82 | transmission: [0.200, 0.025, 0.010], 83 | }; 84 | 85 | pub const ROCK: Self = Self { 86 | absorption: [0.13, 0.20, 0.24], 87 | scattering: 0.05, 88 | transmission: [0.015, 0.002, 0.001], 89 | }; 90 | } 91 | 92 | impl From for audionimbus_sys::IPLMaterial { 93 | fn from(material: Material) -> Self { 94 | audionimbus_sys::IPLMaterial { 95 | absorption: material.absorption, 96 | scattering: material.scattering, 97 | transmission: material.transmission, 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /audionimbus/src/geometry/matrix.rs: -------------------------------------------------------------------------------- 1 | /// A ROWSxCOLS matrix of type T elements. 2 | #[derive(Debug, Copy, Clone)] 3 | pub struct Matrix { 4 | /// Matrix elements, in row-major order. 5 | pub elements: [[T; COLS]; ROWS], 6 | } 7 | 8 | impl Matrix { 9 | pub fn new(elements: [[T; COLS]; ROWS]) -> Self { 10 | Self { elements } 11 | } 12 | } 13 | 14 | impl From> for audionimbus_sys::IPLMatrix4x4 { 15 | fn from(matrix: Matrix) -> Self { 16 | audionimbus_sys::IPLMatrix4x4 { 17 | elements: matrix.elements, 18 | } 19 | } 20 | } 21 | 22 | impl From<&Matrix> for audionimbus_sys::IPLMatrix4x4 { 23 | fn from(matrix: &Matrix) -> Self { 24 | audionimbus_sys::IPLMatrix4x4 { 25 | elements: matrix.elements, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /audionimbus/src/geometry/mod.rs: -------------------------------------------------------------------------------- 1 | mod vector3; 2 | pub use vector3::Vector3; 3 | 4 | mod point; 5 | pub use point::Point; 6 | 7 | mod direction; 8 | pub use direction::Direction; 9 | 10 | mod coordinate_system; 11 | pub use coordinate_system::CoordinateSystem; 12 | 13 | mod matrix; 14 | pub use matrix::Matrix; 15 | 16 | mod triangle; 17 | pub use triangle::Triangle; 18 | 19 | mod material; 20 | pub use material::Material; 21 | 22 | mod scene; 23 | pub use scene::{Scene, SceneParams, SceneSettings}; 24 | 25 | mod static_mesh; 26 | pub use static_mesh::{StaticMesh, StaticMeshSettings}; 27 | 28 | mod instanced_mesh; 29 | pub use instanced_mesh::{InstancedMesh, InstancedMeshSettings}; 30 | 31 | mod sphere; 32 | pub use sphere::Sphere; 33 | -------------------------------------------------------------------------------- /audionimbus/src/geometry/point.rs: -------------------------------------------------------------------------------- 1 | use super::Vector3; 2 | 3 | /// A point in 3D space. 4 | pub type Point = Vector3; 5 | -------------------------------------------------------------------------------- /audionimbus/src/geometry/sphere.rs: -------------------------------------------------------------------------------- 1 | use super::Point; 2 | 3 | /// A sphere. 4 | /// Spheres are used to define a region of influence around a point. 5 | #[derive(Copy, Clone, Debug)] 6 | pub struct Sphere { 7 | /// The center. 8 | pub center: Point, 9 | 10 | /// The radius. 11 | pub radius: f32, 12 | } 13 | 14 | impl Default for Sphere { 15 | fn default() -> Self { 16 | Self { 17 | center: Point::default(), 18 | radius: f32::default(), 19 | } 20 | } 21 | } 22 | 23 | impl From for audionimbus_sys::IPLSphere { 24 | fn from(sphere: Sphere) -> Self { 25 | Self { 26 | center: sphere.center.into(), 27 | radius: sphere.radius.into(), 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /audionimbus/src/geometry/static_mesh.rs: -------------------------------------------------------------------------------- 1 | use super::{Material, Point, Scene, Triangle}; 2 | use crate::callback::{CallbackInformation, ProgressCallback}; 3 | use crate::error::{to_option_error, SteamAudioError}; 4 | use crate::serialized_object::SerializedObject; 5 | 6 | #[cfg(doc)] 7 | use crate::SceneSettings; 8 | 9 | /// A triangle mesh that doesn’t move or deform in any way. 10 | /// 11 | /// The unchanging portions of a scene should typically be collected into a single static mesh object. 12 | /// In addition to the geometry, a static mesh also contains acoustic material information for each triangle. 13 | #[derive(Debug)] 14 | pub struct StaticMesh(audionimbus_sys::IPLStaticMesh); 15 | 16 | impl StaticMesh { 17 | pub fn try_new(scene: &Scene, settings: &StaticMeshSettings) -> Result { 18 | let mut static_mesh = Self(std::ptr::null_mut()); 19 | 20 | let mut vertices: Vec = settings 21 | .vertices 22 | .iter() 23 | .map(|v| audionimbus_sys::IPLVector3::from(*v)) 24 | .collect(); 25 | 26 | let mut triangles: Vec = settings 27 | .triangles 28 | .iter() 29 | .map(|t| audionimbus_sys::IPLTriangle::from(*t)) 30 | .collect(); 31 | 32 | let mut material_indices: Vec = settings 33 | .material_indices 34 | .iter() 35 | .map(|&i| i as i32) 36 | .collect(); 37 | 38 | let mut materials: Vec = settings 39 | .materials 40 | .iter() 41 | .map(|m| audionimbus_sys::IPLMaterial::from(*m)) 42 | .collect(); 43 | 44 | let mut static_mesh_settings_ffi = audionimbus_sys::IPLStaticMeshSettings { 45 | numVertices: vertices.len() as i32, 46 | numTriangles: triangles.len() as i32, 47 | numMaterials: materials.len() as i32, 48 | vertices: vertices.as_mut_ptr(), 49 | triangles: triangles.as_mut_ptr(), 50 | materialIndices: material_indices.as_mut_ptr(), 51 | materials: materials.as_mut_ptr(), 52 | }; 53 | 54 | let status = unsafe { 55 | audionimbus_sys::iplStaticMeshCreate( 56 | scene.raw_ptr(), 57 | &mut static_mesh_settings_ffi, 58 | static_mesh.raw_ptr_mut(), 59 | ) 60 | }; 61 | 62 | if let Some(error) = to_option_error(status) { 63 | return Err(error); 64 | } 65 | 66 | Ok(static_mesh) 67 | } 68 | 69 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLStaticMesh { 70 | self.0 71 | } 72 | 73 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLStaticMesh { 74 | &mut self.0 75 | } 76 | 77 | /// Saves a static mesh to a serialized object. 78 | /// 79 | /// Typically, the serialized object will then be saved to disk. 80 | /// 81 | /// This function can only be called on a static mesh that is part of a scene created with [`SceneSettings::Default`]. 82 | pub fn save(&self, serialized_object: &mut SerializedObject) { 83 | unsafe { 84 | audionimbus_sys::iplStaticMeshSave(self.raw_ptr(), serialized_object.raw_ptr()); 85 | } 86 | } 87 | 88 | /// Loads a static mesh from a serialized object. 89 | /// 90 | /// Typically, the serialized object will be created from a byte array loaded from disk or over the network. 91 | pub fn load( 92 | scene: &Scene, 93 | serialized_object: &mut SerializedObject, 94 | ) -> Result { 95 | Self::load_with_optional_progress_callback(scene, serialized_object, None) 96 | } 97 | 98 | /// Loads a static mesh from a serialized object, with a progress callback. 99 | /// 100 | /// Typically, the serialized object will be created from a byte array loaded from disk or over the network. 101 | pub fn load_with_progress_callback( 102 | scene: &Scene, 103 | serialized_object: &SerializedObject, 104 | progress_callback_information: CallbackInformation, 105 | ) -> Result { 106 | Self::load_with_optional_progress_callback( 107 | scene, 108 | serialized_object, 109 | Some(progress_callback_information), 110 | ) 111 | } 112 | 113 | /// Loads a static mesh from a serialized object, with an optional progress callback. 114 | /// 115 | /// Typically, the serialized object will be created from a byte array loaded from disk or over the network. 116 | fn load_with_optional_progress_callback( 117 | scene: &Scene, 118 | serialized_object: &SerializedObject, 119 | progress_callback_information: Option>, 120 | ) -> Result { 121 | let (progress_callback, progress_callback_user_data) = if let Some(CallbackInformation { 122 | callback, 123 | user_data, 124 | }) = progress_callback_information 125 | { 126 | (Some(callback), user_data) 127 | } else { 128 | (None, std::ptr::null_mut()) 129 | }; 130 | 131 | let mut static_mesh = Self(std::ptr::null_mut()); 132 | 133 | let status = unsafe { 134 | audionimbus_sys::iplStaticMeshLoad( 135 | scene.raw_ptr(), 136 | serialized_object.raw_ptr(), 137 | progress_callback, 138 | progress_callback_user_data, 139 | static_mesh.raw_ptr_mut(), 140 | ) 141 | }; 142 | 143 | if let Some(error) = to_option_error(status) { 144 | return Err(error); 145 | } 146 | 147 | Ok(static_mesh) 148 | } 149 | } 150 | 151 | impl Clone for StaticMesh { 152 | fn clone(&self) -> Self { 153 | unsafe { 154 | audionimbus_sys::iplStaticMeshRetain(self.0); 155 | } 156 | Self(self.0) 157 | } 158 | } 159 | 160 | impl Drop for StaticMesh { 161 | fn drop(&mut self) { 162 | unsafe { audionimbus_sys::iplStaticMeshRelease(&mut self.0) } 163 | } 164 | } 165 | 166 | unsafe impl Send for StaticMesh {} 167 | unsafe impl Sync for StaticMesh {} 168 | 169 | /// Settings used to create a static mesh. 170 | #[derive(Default, Debug)] 171 | pub struct StaticMeshSettings<'a> { 172 | /// Array containing vertices. 173 | pub vertices: &'a [Point], 174 | 175 | /// Array containing (indexed) triangles. 176 | pub triangles: &'a [Triangle], 177 | 178 | /// Array containing, for each triangle, the index of the associated material. 179 | pub material_indices: &'a [usize], 180 | 181 | /// Array of materials. 182 | pub materials: &'a [Material], 183 | } 184 | -------------------------------------------------------------------------------- /audionimbus/src/geometry/triangle.rs: -------------------------------------------------------------------------------- 1 | /// A triangle in 3D space. 2 | /// 3 | /// Triangles are specified by their three vertices, which are in turn specified using indices into a vertex array. 4 | /// 5 | /// Steam Audio uses a counter-clockwise winding order. 6 | /// This means that when looking at the triangle such that the normal is pointing towards you, the vertices are specified in counter-clockwise order. 7 | /// 8 | /// Each triangle must be specified using three vertices; triangle strip or fan representations are not supported. 9 | #[derive(Copy, Clone, Debug)] 10 | pub struct Triangle { 11 | /// Indices of the three vertices of this triangle. 12 | pub indices: [i32; 3], 13 | } 14 | 15 | impl Triangle { 16 | pub fn new(vertex_index_0: i32, vertex_index_1: i32, vertex_index_2: i32) -> Self { 17 | Self { 18 | indices: [vertex_index_0, vertex_index_1, vertex_index_2], 19 | } 20 | } 21 | } 22 | 23 | impl From for audionimbus_sys::IPLTriangle { 24 | fn from(triangle: Triangle) -> Self { 25 | audionimbus_sys::IPLTriangle { 26 | indices: triangle.indices, 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /audionimbus/src/geometry/vector3.rs: -------------------------------------------------------------------------------- 1 | /// A point or vector in 3D space. 2 | /// 3 | /// Steam Audio uses a right-handed coordinate system, with the positive x-axis pointing right, the positive y-axis pointing up, and the negative z-axis pointing ahead. 4 | /// Position and direction data obtained from a game engine or audio engine must be properly transformed before being passed to any Steam Audio API function. 5 | #[derive(Copy, Clone, Debug)] 6 | pub struct Vector3 { 7 | /// The x-coordinate. 8 | pub x: f32, 9 | 10 | /// The y-coordinate. 11 | pub y: f32, 12 | 13 | /// The z-coordinate. 14 | pub z: f32, 15 | } 16 | 17 | impl Vector3 { 18 | pub fn new(x: f32, y: f32, z: f32) -> Self { 19 | Self { x, y, z } 20 | } 21 | } 22 | 23 | impl Default for Vector3 { 24 | fn default() -> Self { 25 | Self { 26 | x: 0.0, 27 | y: 0.0, 28 | z: 0.0, 29 | } 30 | } 31 | } 32 | 33 | impl From<[f32; 3]> for Vector3 { 34 | fn from(vector: [f32; 3]) -> Self { 35 | Self { 36 | x: vector[0], 37 | y: vector[1], 38 | z: vector[2], 39 | } 40 | } 41 | } 42 | 43 | impl From for audionimbus_sys::IPLVector3 { 44 | fn from(vector: Vector3) -> Self { 45 | Self { 46 | x: vector.x, 47 | y: vector.y, 48 | z: vector.z, 49 | } 50 | } 51 | } 52 | 53 | impl From for Vector3 { 54 | fn from(vector: audionimbus_sys::IPLVector3) -> Self { 55 | Self { 56 | x: vector.x, 57 | y: vector.y, 58 | z: vector.z, 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /audionimbus/src/hrtf.rs: -------------------------------------------------------------------------------- 1 | use crate::audio_settings::AudioSettings; 2 | use crate::context::Context; 3 | use crate::error::{to_option_error, SteamAudioError}; 4 | 5 | /// A Head-Related Transfer Function (HRTF). 6 | /// 7 | /// HRTFs describe how sound from different directions is perceived by a each of a listener’s ears, and are a crucial component of spatial audio. 8 | /// Steam Audio includes a built-in HRTF, while also allowing developers and users to import their own custom HRTFs. 9 | #[derive(Debug)] 10 | pub struct Hrtf(pub(crate) audionimbus_sys::IPLHRTF); 11 | 12 | impl Hrtf { 13 | pub fn try_new( 14 | context: &Context, 15 | audio_settings: &AudioSettings, 16 | hrtf_settings: &HrtfSettings, 17 | ) -> Result { 18 | let mut hrtf = Self(std::ptr::null_mut()); 19 | 20 | let status = unsafe { 21 | audionimbus_sys::iplHRTFCreate( 22 | context.raw_ptr(), 23 | &mut audionimbus_sys::IPLAudioSettings::from(audio_settings), 24 | &mut audionimbus_sys::IPLHRTFSettings::from(hrtf_settings), 25 | hrtf.raw_ptr_mut(), 26 | ) 27 | }; 28 | 29 | if let Some(error) = to_option_error(status) { 30 | return Err(error); 31 | } 32 | 33 | Ok(hrtf) 34 | } 35 | 36 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLHRTF { 37 | self.0 38 | } 39 | 40 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLHRTF { 41 | &mut self.0 42 | } 43 | } 44 | 45 | impl Clone for Hrtf { 46 | fn clone(&self) -> Self { 47 | unsafe { 48 | audionimbus_sys::iplHRTFRetain(self.0); 49 | } 50 | Self(self.0) 51 | } 52 | } 53 | 54 | impl Drop for Hrtf { 55 | fn drop(&mut self) { 56 | unsafe { audionimbus_sys::iplHRTFRelease(&mut self.0) } 57 | } 58 | } 59 | 60 | unsafe impl Send for Hrtf {} 61 | unsafe impl Sync for Hrtf {} 62 | 63 | /// Settings used to create an [`Hrtf`]. 64 | #[derive(Debug)] 65 | pub struct HrtfSettings { 66 | /// Volume correction factor to apply to the loaded HRTF data. 67 | /// 68 | /// A value of 1.0 means the HRTF data will be used without any change. 69 | pub volume: f32, 70 | 71 | /// Optional SOFA information to be used to load HRTF data. 72 | pub sofa_information: Option, 73 | 74 | /// Volume normalization setting. 75 | pub volume_normalization: VolumeNormalization, 76 | } 77 | 78 | impl Default for HrtfSettings { 79 | fn default() -> Self { 80 | Self { 81 | volume: 1.0, 82 | sofa_information: None, 83 | volume_normalization: VolumeNormalization::None, 84 | } 85 | } 86 | } 87 | 88 | impl From<&HrtfSettings> for audionimbus_sys::IPLHRTFSettings { 89 | fn from(settings: &HrtfSettings) -> Self { 90 | let (type_, sofa_filename, sofa_data, sofa_data_size): ( 91 | audionimbus_sys::IPLHRTFType, 92 | *const std::os::raw::c_char, 93 | *const u8, 94 | _, 95 | ) = if let Some(information) = &settings.sofa_information { 96 | match information { 97 | Sofa::Filename(filename) => { 98 | let c_string = std::ffi::CString::new(filename.clone()).unwrap(); 99 | ( 100 | audionimbus_sys::IPLHRTFType::IPL_HRTFTYPE_SOFA, 101 | c_string.as_ptr() as *const std::os::raw::c_char, 102 | std::ptr::null(), 103 | 0, 104 | ) 105 | } 106 | Sofa::Buffer(buffer) => ( 107 | audionimbus_sys::IPLHRTFType::IPL_HRTFTYPE_SOFA, 108 | std::ptr::null(), 109 | buffer.as_ptr(), 110 | buffer.len() as i32, 111 | ), 112 | } 113 | } else { 114 | ( 115 | audionimbus_sys::IPLHRTFType::IPL_HRTFTYPE_DEFAULT, 116 | std::ptr::null(), 117 | std::ptr::null(), 118 | 0, 119 | ) 120 | }; 121 | 122 | Self { 123 | type_, 124 | sofaFileName: sofa_filename, 125 | sofaData: sofa_data, 126 | sofaDataSize: sofa_data_size, 127 | volume: settings.volume, 128 | normType: settings.volume_normalization.into(), 129 | } 130 | } 131 | } 132 | 133 | /// Whether to load SOFA data from a filename or a buffer. 134 | #[derive(Debug)] 135 | pub enum Sofa { 136 | /// SOFA file from which to load HRTF data. 137 | Filename(String), 138 | 139 | /// Buffer containing SOFA file data from which to load HRTF data. 140 | Buffer(Vec), 141 | } 142 | 143 | /// HRTF volume normalization setting. 144 | #[derive(Debug, Copy, Clone)] 145 | pub enum VolumeNormalization { 146 | /// No normalization. 147 | None, 148 | 149 | /// Root-mean squared normalization. 150 | /// 151 | /// Normalize HRTF volume to ensure similar volume from all directions based on root-mean-square value of each HRTF. 152 | RootMeanSquared, 153 | } 154 | 155 | impl From for audionimbus_sys::IPLHRTFNormType { 156 | fn from(volume_normalization: VolumeNormalization) -> Self { 157 | match volume_normalization { 158 | VolumeNormalization::None => audionimbus_sys::IPLHRTFNormType::IPL_HRTFNORMTYPE_NONE, 159 | VolumeNormalization::RootMeanSquared => { 160 | audionimbus_sys::IPLHRTFNormType::IPL_HRTFNORMTYPE_RMS 161 | } 162 | } 163 | } 164 | } 165 | 166 | /// Techniques for interpolating HRTF data. 167 | /// 168 | /// This is used when rendering a point source whose position relative to the listener is not contained in the measured HRTF data. 169 | #[derive(Copy, Clone, Debug)] 170 | pub enum HrtfInterpolation { 171 | /// Nearest-neighbor filtering, i.e., no interpolation. 172 | /// 173 | /// Selects the measurement location that is closest to the source’s actual location. 174 | Nearest, 175 | 176 | /// Bilinear filtering. 177 | /// 178 | /// Incurs a relatively high CPU overhead as compared to nearest-neighbor filtering, so use this for sounds where it has a significant benefit. 179 | /// Typically, bilinear filtering is most useful for wide-band noise-like sounds, such as radio static, mechanical noise, fire, etc. 180 | Bilinear, 181 | } 182 | 183 | impl From for audionimbus_sys::IPLHRTFInterpolation { 184 | fn from(hrtf_interpolation: HrtfInterpolation) -> Self { 185 | match hrtf_interpolation { 186 | HrtfInterpolation::Nearest => { 187 | audionimbus_sys::IPLHRTFInterpolation::IPL_HRTFINTERPOLATION_NEAREST 188 | } 189 | HrtfInterpolation::Bilinear => { 190 | audionimbus_sys::IPLHRTFInterpolation::IPL_HRTFINTERPOLATION_BILINEAR 191 | } 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /audionimbus/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # audionimbus 3 | 4 | A Rust wrapper around [Steam Audio](https://valvesoftware.github.io/steam-audio/) that provides spatial audio capabilities with realistic occlusion, reverb, and HRTF effects, accounting for physical attributes and scene geometry. 5 | 6 | ## Overview 7 | 8 | `audionimbus` simplifies the integration of Steam Audio into Rust projects by offering a safe, idiomiatic API. 9 | 10 | It builds upon [`audionimbus-sys`](https://github.com/MaxenceMaire/audionimbus/tree/master/audionimbus-sys), which provides raw bindings to the Steam Audio C API. 11 | 12 | ## Version compatibility 13 | 14 | `audionimbus` currently tracks Steam Audio 4.6.1. 15 | 16 | Unlike `audionimbus-sys`, which mirrors Steam Audio's versioning, `audionimbus` introduces its own abstractions and is subject to breaking changes. 17 | As a result, it uses independent versioning. 18 | 19 | ## Installation 20 | 21 | Before installation, make sure that Clang 9.0 or later is installed on your system. 22 | 23 | `audionimbus` requires linking against the Steam Audio library during compilation. 24 | 25 | To do so, download `steamaudio_4.6.1.zip` from the [release page](https://github.com/ValveSoftware/steam-audio/releases). 26 | 27 | Locate the relevant library for your target platform (`SDKROOT` refers to the directory in which you extracted the zip file): 28 | 29 | | Platform | Library Directory | Library To Link | 30 | | --- | --- | --- | 31 | | Windows 32-bit | `SDKROOT/lib/windows-x86` | `phonon.dll` | 32 | | Windows 64-bit | `SDKROOT/lib/windows-x64` | `phonon.dll` | 33 | | Linux 32-bit | `SDKROOT/lib/linux-x86` | `libphonon.so` | 34 | | Linux 64-bit | `SDKROOT/lib/linux-x64` | `libphonon.so` | 35 | | macOS | `SDKROOT/lib/osx` | `libphonon.dylib` | 36 | | Android ARMv7 | `SDKROOT/lib/android-armv7` | `libphonon.so` | 37 | | Android ARMv8/AArch64 | `SDKROOT/lib/android-armv8` | `libphonon.so` | 38 | | Android x86 | `SDKROOT/lib/android-x86` | `libphonon.so` | 39 | | Android x64 | `SDKROOT/lib/android-x64` | `libphonon.so` | 40 | | iOS ARMv8/AArch64 | `SDKROOT/lib/ios` | `libphonon.a` | 41 | 42 | Ensure the library is placed in a location listed in the [dynamic library search paths](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) (e.g., `/usr/local/lib`). 43 | 44 | Finally, add `audionimbus` to your `Cargo.toml`: 45 | 46 | ```toml 47 | [dependencies] 48 | audionimbus = "0.6.4" 49 | ``` 50 | 51 | ## Example 52 | 53 | This example demonstrates how to spatialize sound using the `audionimbus` library: 54 | 55 | ```rust 56 | use audionimbus::*; 57 | 58 | // Initialize the audio context. 59 | let context = Context::try_new(&ContextSettings::default()).unwrap(); 60 | 61 | let audio_settings = AudioSettings { 62 | sampling_rate: 48000, 63 | frame_size: 1024, 64 | }; 65 | 66 | // Set up HRTF for binaural rendering. 67 | let hrtf = Hrtf::try_new(&context, &audio_settings, &HrtfSettings::default()).unwrap(); 68 | 69 | // Create a binaural effect. 70 | let binaural_effect = BinauralEffect::try_new( 71 | &context, 72 | &audio_settings, 73 | &BinauralEffectSettings { hrtf: &hrtf }, 74 | ) 75 | .unwrap(); 76 | 77 | // Generate an input frame (in thise case, a single-channel sine wave). 78 | let input: Vec = (0..audio_settings.frame_size) 79 | .map(|i| { 80 | (i as f32 * 2.0 * std::f32::consts::PI * 440.0 / audio_settings.sampling_rate as f32) 81 | .sin() 82 | }) 83 | .collect(); 84 | // Create an audio buffer over the input data. 85 | let input_buffer = AudioBuffer::try_with_data(&input).unwrap(); 86 | 87 | let num_channels: usize = 2; // Stereo 88 | // Allocate memory to store processed samples. 89 | let mut output = vec![0.0; audio_settings.frame_size * num_channels]; 90 | // Create another audio buffer over the output container. 91 | let output_buffer = AudioBuffer::try_with_data_and_settings( 92 | &mut output, 93 | &AudioBufferSettings { 94 | num_channels: Some(num_channels), 95 | ..Default::default() 96 | }, 97 | ) 98 | .unwrap(); 99 | 100 | // Apply a binaural audio effect. 101 | let binaural_effect_params = BinauralEffectParams { 102 | direction: Direction::new( 103 | 1.0, // Right 104 | 0.0, // Up 105 | 0.0, // Behind 106 | ), 107 | interpolation: HrtfInterpolation::Nearest, 108 | spatial_blend: 1.0, 109 | hrtf: &hrtf, 110 | peak_delays: None, 111 | }; 112 | let _effect_state = 113 | binaural_effect.apply(&binaural_effect_params, &input_buffer, &output_buffer); 114 | 115 | // `output` now contains the processed samples in a deinterleaved format (i.e., left channel 116 | // samples followed by right channel samples). 117 | 118 | // Note: most audio engines expect interleaved audio (alternating samples for each channel). If 119 | // required, use the `AudioBuffer::interleave` method to convert the format. 120 | ``` 121 | 122 | To implement real-time audio processing and playback in your game, check out the [demo crate](https://github.com/MaxenceMaire/audionimbus/tree/master/audionimbus/demo) for a more comprehensive example. 123 | 124 | For additional examples, you can explore the [tests](https://github.com/MaxenceMaire/audionimbus/tree/master/audionimbus/tests), which closely follow [Steam Audio's Programmer's Guide](https://valvesoftware.github.io/steam-audio/doc/capi/guide.html). 125 | 126 | ## FMOD Studio Integration 127 | 128 | `audionimbus` can be used to add spatial audio to an FMOD Studio project. 129 | 130 | It requires linking against both the Steam Audio library and the FMOD integration library during compilation: 131 | 132 | 1. Download `steamaudio_fmod_4.6.1.zip` from the [release page](https://github.com/ValveSoftware/steam-audio/releases). 133 | 134 | 2. Locate the two relevant libraries for your target platform (`SDKROOT` refers to the directory in which you extracted the zip file): 135 | 136 | | Platform | Library Directory | Library To Link | 137 | | --- | --- | --- | 138 | | Windows 32-bit | `SDKROOT/lib/windows-x86` | `phonon.dll`, `phonon_fmod.dll` | 139 | | Windows 64-bit | `SDKROOT/lib/windows-x64` | `phonon.dll`, `phonon_fmod.dll` | 140 | | Linux 32-bit | `SDKROOT/lib/linux-x86` | `libphonon.so`, `libphonon_fmod.so` | 141 | | Linux 64-bit | `SDKROOT/lib/linux-x64` | `libphonon.so`, `libphonon_fmod.so` | 142 | | macOS | `SDKROOT/lib/osx` | `libphonon.dylib`, `libphonon_fmod.dylib` | 143 | | Android ARMv7 | `SDKROOT/lib/android-armv7` | `libphonon.so`, `libphonon_fmod.so` | 144 | | Android ARMv8/AArch64 | `SDKROOT/lib/android-armv8` | `libphonon.so`, `libphonon_fmod.so` | 145 | | Android x86 | `SDKROOT/lib/android-x86` | `libphonon.so`, `libphonon_fmod.so` | 146 | | Android x64 | `SDKROOT/lib/android-x64` | `libphonon.so`, `libphonon_fmod.so` | 147 | | iOS ARMv8/AArch64 | `SDKROOT/lib/ios` | `libphonon.a`, `libphonon_fmod.a` | 148 | 149 | 3. Ensure the libraries are placed in a location listed in the [dynamic library search paths](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) (e.g., `/usr/local/lib`). 150 | 151 | 4. Finally, add `audionimbus` with the `fmod` feature enabled to your `Cargo.toml`: 152 | 153 | ```toml 154 | [dependencies] 155 | audionimbus = { version = "0.6.4", features = ["fmod"] } 156 | ``` 157 | 158 | ## Wwise Integration 159 | 160 | `audionimbus` can be used to add spatial audio to a Wwise project. 161 | 162 | It requires linking against both the Steam Audio library and the Wwise integration library during compilation: 163 | 164 | 1. Download `steamaudio_wwise_4.6.1.zip` from the [release page](https://github.com/ValveSoftware/steam-audio/releases). 165 | 166 | 2. Locate the two relevant libraries for your target platform and place them in a location listed in [dynamic library search paths](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) (e.g., `/usr/local/lib`). 167 | 168 | 3. Set the `WWISESDK` environment variable to the path of the Wwise SDK installed on your system (e.g. `export WWISESDK="/path/to/Audiokinetic/Wwise2024.1.3.8749/SDK"`). 169 | 170 | 4. Finally, add `audionimbus` with the `wwise` feature enabled to your `Cargo.toml`: 171 | 172 | ```toml 173 | [dependencies] 174 | audionimbus = { version = "0.6.4", features = ["wwise"] } 175 | ``` 176 | 177 | ## Documentation 178 | 179 | Documentation is available at [doc.rs](https://docs.rs/audionimbus/latest). 180 | 181 | For more details on Steam Audio's concepts, see the [Steam Audio SDK documentation](https://valvesoftware.github.io/steam-audio/doc/capi/index.html). 182 | 183 | Note that because the Wwise integration depends on files that are local to your system, documentation for the `wwise` module is not available on docs.rs. 184 | However, it can be generated locally using `cargo doc --open --features wwise`. 185 | 186 | ## License 187 | 188 | `audionimbus` is dual-licensed under the [MIT License](https://github.com/MaxenceMaire/audionimbus/blob/master/LICENSE-MIT) and the [Apache-2.0 License](https://github.com/MaxenceMaire/audionimbus/blob/master/LICENSE-APACHE). 189 | You may choose either license when using the software. 190 | */ 191 | 192 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 193 | 194 | pub mod audio_buffer; 195 | pub use audio_buffer::*; 196 | 197 | pub mod audio_settings; 198 | pub use audio_settings::*; 199 | 200 | pub mod callback; 201 | pub use callback::*; 202 | 203 | pub mod context; 204 | pub use context::*; 205 | 206 | pub mod device; 207 | pub use device::*; 208 | 209 | pub mod effect; 210 | pub use effect::*; 211 | 212 | mod error; 213 | pub use error::SteamAudioError; 214 | 215 | mod ffi_wrapper; 216 | 217 | pub mod geometry; 218 | pub use geometry::*; 219 | 220 | pub mod hrtf; 221 | pub use hrtf::*; 222 | 223 | pub mod model; 224 | pub use model::*; 225 | 226 | pub mod probe; 227 | pub use probe::*; 228 | 229 | mod serialized_object; 230 | pub use serialized_object::SerializedObject; 231 | 232 | pub mod simulation; 233 | pub use simulation::*; 234 | 235 | pub mod version; 236 | pub use version::*; 237 | 238 | #[cfg(feature = "fmod")] 239 | pub mod fmod; 240 | 241 | #[cfg(feature = "wwise")] 242 | pub mod wwise; 243 | -------------------------------------------------------------------------------- /audionimbus/src/model/air_absorption.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::geometry; 3 | 4 | /// An air absorption model that can be used for modeling frequency-dependent attenuation of sound over distance. 5 | #[derive(Debug, Copy, Clone)] 6 | pub enum AirAbsorptionModel { 7 | /// The default air absorption model. 8 | /// This is an exponential falloff, with decay rates derived from physical properties of air. 9 | Default, 10 | 11 | /// An exponential falloff. 12 | /// You can configure the decay rates for each frequency band. 13 | Exponential { 14 | /// The exponential falloff coefficients to use. 15 | coefficients: [f32; 3], 16 | }, 17 | 18 | /// An arbitrary air absorption model, defined by a callback function. 19 | Callback { 20 | /// Callback for calculating how much air absorption should be applied to a sound based on its distance from the listener. 21 | /// 22 | /// # Arguments 23 | /// 24 | /// - `distance`: the distance (in meters) between the source and the listener. 25 | /// - `band`: index of the frequency band for which to calculate air absorption. 0.0 = low frequencies, 1.0 = middle frequencies, 2.0 = high frequencies. 26 | /// - `user_data`: pointer to the arbitrary data specified. 27 | /// 28 | /// # Returns 29 | /// 30 | /// The air absorption to apply, between 0.0 and 1.0. 31 | /// 0.0 = sound in the frequency band `band` is not audible, 1.0 = sound in the frequency band `band` is not attenuated. 32 | callback: 33 | unsafe extern "C" fn(distance: f32, band: i32, user_data: *mut std::ffi::c_void) -> f32, 34 | 35 | /// Pointer to arbitrary data that will be provided to the callback function whenever it is called. May be `NULL`. 36 | user_data: *mut std::ffi::c_void, 37 | 38 | /// Set to `true` to indicate that the air absorption model defined by the callback function has changed since the last time simulation was run. 39 | /// For example, the callback may be evaluating a set of curves defined in a GUI. 40 | /// If the user is editing the curves in real-time, set this to `true` whenever the curves change, so Steam Audio can update simulation results to match. 41 | dirty: bool, 42 | }, 43 | } 44 | 45 | impl Default for AirAbsorptionModel { 46 | fn default() -> Self { 47 | Self::Default 48 | } 49 | } 50 | 51 | impl From<&AirAbsorptionModel> for audionimbus_sys::IPLAirAbsorptionModel { 52 | fn from(air_absorption_model: &AirAbsorptionModel) -> Self { 53 | let (type_, coefficients, callback, user_data, dirty) = match air_absorption_model { 54 | AirAbsorptionModel::Default => ( 55 | audionimbus_sys::IPLAirAbsorptionModelType::IPL_AIRABSORPTIONTYPE_DEFAULT, 56 | <[f32; 3]>::default(), 57 | None, 58 | std::ptr::null_mut(), 59 | bool::default(), 60 | ), 61 | AirAbsorptionModel::Exponential { coefficients } => ( 62 | audionimbus_sys::IPLAirAbsorptionModelType::IPL_AIRABSORPTIONTYPE_EXPONENTIAL, 63 | *coefficients, 64 | None, 65 | std::ptr::null_mut(), 66 | bool::default(), 67 | ), 68 | AirAbsorptionModel::Callback { 69 | callback, 70 | user_data, 71 | dirty, 72 | } => ( 73 | audionimbus_sys::IPLAirAbsorptionModelType::IPL_AIRABSORPTIONTYPE_CALLBACK, 74 | <[f32; 3]>::default(), 75 | Some(*callback), 76 | *user_data, 77 | *dirty, 78 | ), 79 | }; 80 | 81 | Self { 82 | type_, 83 | coefficients, 84 | callback, 85 | userData: user_data, 86 | dirty: if dirty { 87 | audionimbus_sys::IPLbool::IPL_TRUE 88 | } else { 89 | audionimbus_sys::IPLbool::IPL_FALSE 90 | }, 91 | } 92 | } 93 | } 94 | 95 | /// Calculates the air absorption coefficients between a source and a listener. 96 | pub fn air_absorption( 97 | context: &Context, 98 | source: &geometry::Point, 99 | listener: &geometry::Point, 100 | model: &AirAbsorptionModel, 101 | ) -> [f32; 3] { 102 | let mut air_absorption = [0.0; 3]; 103 | 104 | unsafe { 105 | audionimbus_sys::iplAirAbsorptionCalculate( 106 | context.raw_ptr(), 107 | (*source).into(), 108 | (*listener).into(), 109 | &mut model.into(), 110 | air_absorption.as_mut_ptr(), 111 | ); 112 | } 113 | 114 | air_absorption 115 | } 116 | -------------------------------------------------------------------------------- /audionimbus/src/model/directivity.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::geometry; 3 | 4 | /// A directivity pattern that can be used to model changes in sound intensity as a function of the source’s orientation. 5 | /// Can be used with both direct and indirect sound propagation. 6 | #[derive(Debug, Copy, Clone)] 7 | pub enum Directivity { 8 | /// The default directivity model is a weighted dipole. 9 | /// This is a linear blend between an omnidirectional source (which emits sound with equal intensity in all directions), and a dipole oriented along the z-axis in the source’s coordinate system (which focuses sound along the +z and -z axes). 10 | WeightedDipole { 11 | /// How much of the dipole to blend into the directivity pattern. 12 | /// 0.0 = pure omnidirectional, 1.0 = pure dipole. 13 | /// 0.5 results in a cardioid directivity pattern. 14 | weight: f32, 15 | 16 | /// How “sharp” the dipole is. 17 | /// Higher values result in sound being focused within a narrower range of directions. 18 | power: f32, 19 | }, 20 | 21 | /// A callback function to implement any other arbitrary directivity pattern. 22 | Callback { 23 | /// Callback for calculating how much to attenuate a sound based on its directivity pattern and orientation in world space. 24 | /// 25 | /// # Arguments 26 | /// 27 | /// - `direction`: unit vector (in world space) pointing forwards from the source. This is the direction that the source is “pointing towards”. 28 | /// - `user_data`: pointer to the arbitrary data specified. 29 | /// 30 | /// # Returns 31 | /// 32 | /// The directivity value to apply, between 0.0 and 1.0. 33 | /// 0.0 = the sound is not audible, 1.0 = the sound is as loud as it would be if it had a uniform (omnidirectional) directivity pattern. 34 | callback: unsafe extern "C" fn( 35 | direction: audionimbus_sys::IPLVector3, 36 | user_data: *mut std::ffi::c_void, 37 | ) -> f32, 38 | 39 | /// Pointer to arbitrary data that will be provided to the callback function whenever it is called. May be `NULL`. 40 | user_data: *mut std::ffi::c_void, 41 | }, 42 | } 43 | 44 | impl Default for Directivity { 45 | fn default() -> Self { 46 | Self::WeightedDipole { 47 | weight: 0.5, 48 | power: 0.5, 49 | } 50 | } 51 | } 52 | 53 | impl From<&Directivity> for audionimbus_sys::IPLDirectivity { 54 | fn from(directivity: &Directivity) -> Self { 55 | let (dipole_weight, dipole_power, callback, user_data) = match directivity { 56 | Directivity::WeightedDipole { weight, power } => { 57 | (*weight, *power, None, std::ptr::null_mut()) 58 | } 59 | Directivity::Callback { 60 | callback, 61 | user_data, 62 | } => (f32::default(), f32::default(), Some(*callback), *user_data), 63 | }; 64 | 65 | Self { 66 | dipoleWeight: dipole_weight, 67 | dipolePower: dipole_power, 68 | callback, 69 | userData: user_data, 70 | } 71 | } 72 | } 73 | 74 | /// Calculates the attenuation of a source due to its directivity pattern and orientation relative to a listener. 75 | pub fn directivity_attenuation( 76 | context: &Context, 77 | source: &geometry::CoordinateSystem, 78 | listener: &geometry::Point, 79 | directivity: &Directivity, 80 | ) -> f32 { 81 | unsafe { 82 | audionimbus_sys::iplDirectivityCalculate( 83 | context.raw_ptr(), 84 | (*source).into(), 85 | (*listener).into(), 86 | &mut directivity.into(), 87 | ) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /audionimbus/src/model/distance_attenuation.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::geometry; 3 | 4 | /// A distance attenuation model that can be used for modeling attenuation of sound over distance. 5 | /// Can be used with both direct and indirect sound propagation. 6 | #[derive(Debug, Copy, Clone)] 7 | pub enum DistanceAttenuationModel { 8 | /// The default distance attenuation model. 9 | /// This is an inverse distance falloff, with all sounds within 1 meter of the listener rendered without distance attenuation. 10 | Default, 11 | 12 | /// An inverse distance falloff. 13 | /// You can configure the minimum distance, within which distance attenuation is not applied. 14 | InverseDistance { 15 | /// No distance attenuation is applied to any sound whose distance from the listener is less than this value. 16 | min_distance: f32, 17 | }, 18 | 19 | /// An arbitrary distance falloff function, defined by a callback function. 20 | Callback { 21 | /// Callback for calculating how much attenuation should be applied to a sound based on its distance from the listener. 22 | /// 23 | /// # Arguments 24 | /// 25 | /// - `distance`: the distance (in meters) between the source and the listener. 26 | /// - `user_data`: pointer to the arbitrary data specified. 27 | /// 28 | /// # Returns 29 | /// 30 | /// The distance attenuation to apply, between 0.0 and 1.0. 31 | /// 0.0 = the sound is not audible, 1.0 = the sound is as loud as it would be if it were emitted from the listener’s position. 32 | callback: unsafe extern "C" fn(distance: f32, user_data: *mut std::ffi::c_void) -> f32, 33 | 34 | /// Pointer to arbitrary data that will be provided to the callback function whenever it is called. May be `NULL`. 35 | user_data: *mut std::ffi::c_void, 36 | 37 | /// Set to `true` to indicate that the distance attenuation model defined by the callback function has changed since the last time simulation was run. 38 | /// For example, the callback may be evaluating a curve defined in a GUI. 39 | /// If the user is editing the curve in real-time, set this to `true` whenever the curve changes, so Steam Audio can update simulation results to match. 40 | dirty: bool, 41 | }, 42 | } 43 | 44 | impl Default for DistanceAttenuationModel { 45 | fn default() -> Self { 46 | Self::Default 47 | } 48 | } 49 | 50 | impl From<&DistanceAttenuationModel> for audionimbus_sys::IPLDistanceAttenuationModel { 51 | fn from(distance_attenuation_model: &DistanceAttenuationModel) -> Self { 52 | let (type_, min_distance, callback, user_data, dirty) = match distance_attenuation_model { 53 | DistanceAttenuationModel::Default => ( 54 | audionimbus_sys::IPLDistanceAttenuationModelType::IPL_DISTANCEATTENUATIONTYPE_DEFAULT, 55 | f32::default(), 56 | None, 57 | std::ptr::null_mut(), 58 | bool::default() 59 | ), 60 | DistanceAttenuationModel::InverseDistance { min_distance } => ( 61 | audionimbus_sys::IPLDistanceAttenuationModelType::IPL_DISTANCEATTENUATIONTYPE_INVERSEDISTANCE, 62 | *min_distance, 63 | None, 64 | std::ptr::null_mut(), 65 | bool::default() 66 | ), 67 | DistanceAttenuationModel::Callback { callback, user_data, dirty } => ( 68 | audionimbus_sys::IPLDistanceAttenuationModelType::IPL_DISTANCEATTENUATIONTYPE_CALLBACK, 69 | f32::default(), 70 | Some(*callback), 71 | *user_data, 72 | *dirty 73 | ) 74 | }; 75 | 76 | Self { 77 | type_, 78 | minDistance: min_distance, 79 | callback, 80 | userData: user_data, 81 | dirty: if dirty { 82 | audionimbus_sys::IPLbool::IPL_TRUE 83 | } else { 84 | audionimbus_sys::IPLbool::IPL_FALSE 85 | }, 86 | } 87 | } 88 | } 89 | 90 | /// Calculates the distance attenuation between a source and a listener. 91 | pub fn distance_attenuation( 92 | context: &Context, 93 | source: &geometry::Point, 94 | listener: &geometry::Point, 95 | model: &DistanceAttenuationModel, 96 | ) -> f32 { 97 | unsafe { 98 | audionimbus_sys::iplDistanceAttenuationCalculate( 99 | context.raw_ptr(), 100 | (*source).into(), 101 | (*listener).into(), 102 | &mut model.into(), 103 | ) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /audionimbus/src/model/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod distance_attenuation; 2 | pub use distance_attenuation::*; 3 | 4 | pub mod air_absorption; 5 | pub use air_absorption::*; 6 | 7 | pub mod directivity; 8 | pub use directivity::*; 9 | -------------------------------------------------------------------------------- /audionimbus/src/probe.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::error::{to_option_error, SteamAudioError}; 3 | use crate::geometry::{Matrix, Scene, Sphere}; 4 | use crate::serialized_object::SerializedObject; 5 | 6 | /// An array of sound probes. 7 | /// 8 | /// Each probe has a position and a radius of influence. 9 | #[derive(Debug)] 10 | pub struct ProbeArray(audionimbus_sys::IPLProbeArray); 11 | 12 | impl ProbeArray { 13 | pub fn try_new(context: &Context) -> Result { 14 | let mut probe_array = Self(std::ptr::null_mut()); 15 | 16 | let status = unsafe { 17 | audionimbus_sys::iplProbeArrayCreate(context.raw_ptr(), probe_array.raw_ptr_mut()) 18 | }; 19 | 20 | if let Some(error) = to_option_error(status) { 21 | return Err(error); 22 | } 23 | 24 | Ok(probe_array) 25 | } 26 | 27 | /// Generates probes and adds them to the probe array. 28 | pub fn generate_probes(&mut self, scene: &Scene, probe_params: &ProbeGenerationParams) { 29 | unsafe { 30 | audionimbus_sys::iplProbeArrayGenerateProbes( 31 | self.raw_ptr(), 32 | scene.raw_ptr(), 33 | &mut audionimbus_sys::IPLProbeGenerationParams::from(*probe_params), 34 | ); 35 | } 36 | } 37 | 38 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLProbeArray { 39 | self.0 40 | } 41 | 42 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLProbeArray { 43 | &mut self.0 44 | } 45 | } 46 | 47 | impl Clone for ProbeArray { 48 | fn clone(&self) -> Self { 49 | unsafe { 50 | audionimbus_sys::iplProbeArrayRetain(self.0); 51 | } 52 | Self(self.0) 53 | } 54 | } 55 | 56 | impl Drop for ProbeArray { 57 | fn drop(&mut self) { 58 | unsafe { audionimbus_sys::iplProbeArrayRelease(&mut self.0) } 59 | } 60 | } 61 | 62 | unsafe impl Send for ProbeArray {} 63 | unsafe impl Sync for ProbeArray {} 64 | 65 | /// Settings used to generate probes. 66 | #[derive(Copy, Clone, Debug)] 67 | pub enum ProbeGenerationParams { 68 | /// Generates a single probe at the center of the specified box. 69 | Centroid { 70 | /// A transformation matrix that transforms an axis-aligned unit cube, with minimum and maximum vertices at (0.0, 0.0, 0.0) and (1.0, 1.0, 1.0), into a parallelopiped volume. 71 | /// Probes will be generated within this volume. 72 | transform: Matrix, 73 | }, 74 | 75 | /// Generates probes that are uniformly-spaced, at a fixed height above solid geometry. 76 | /// A probe will never be generated above another probe unless there is a solid object between them. 77 | /// The goal is to model floors or terrain, and generate probes that are a fixed height above the floor or terrain, and uniformly-spaced along the horizontal plane. 78 | /// This algorithm is not suitable for scenarios where the listener may fly into a region with no probes; if this happens, the listener will not be influenced by any of the baked data. 79 | UniformFloor { 80 | /// Spacing (in meters) between two neighboring probes. 81 | spacing: f32, 82 | 83 | /// Height (in meters) above the floor at which probes will be generated. 84 | height: f32, 85 | 86 | /// A transformation matrix that transforms an axis-aligned unit cube, with minimum and maximum vertices at (0.0, 0.0, 0.0) and (1.0, 1.0, 1.0), into a parallelopiped volume. 87 | /// Probes will be generated within this volume. 88 | transform: Matrix, 89 | }, 90 | } 91 | 92 | impl From for audionimbus_sys::IPLProbeGenerationParams { 93 | fn from(probe_generation_params: ProbeGenerationParams) -> Self { 94 | let (type_, spacing, height, transform) = match probe_generation_params { 95 | ProbeGenerationParams::Centroid { transform } => ( 96 | audionimbus_sys::IPLProbeGenerationType::IPL_PROBEGENERATIONTYPE_CENTROID, 97 | f32::default(), 98 | f32::default(), 99 | transform, 100 | ), 101 | ProbeGenerationParams::UniformFloor { 102 | spacing, 103 | height, 104 | transform, 105 | } => ( 106 | audionimbus_sys::IPLProbeGenerationType::IPL_PROBEGENERATIONTYPE_UNIFORMFLOOR, 107 | spacing, 108 | height, 109 | transform, 110 | ), 111 | }; 112 | 113 | Self { 114 | type_, 115 | spacing, 116 | height, 117 | transform: transform.into(), 118 | } 119 | } 120 | } 121 | 122 | /// A batch of sound probes, along with associated data. 123 | /// 124 | /// The associated data may include reverb, reflections from a static source position, pathing, and more. 125 | /// This data is loaded and unloaded as a unit, either from disk or over the network. 126 | #[derive(Debug)] 127 | pub struct ProbeBatch(audionimbus_sys::IPLProbeBatch); 128 | 129 | impl ProbeBatch { 130 | pub fn try_new(context: &Context) -> Result { 131 | let mut probe_batch = Self(std::ptr::null_mut()); 132 | 133 | let status = unsafe { 134 | audionimbus_sys::iplProbeBatchCreate(context.raw_ptr(), probe_batch.raw_ptr_mut()) 135 | }; 136 | 137 | if let Some(error) = to_option_error(status) { 138 | return Err(error); 139 | } 140 | 141 | Ok(probe_batch) 142 | } 143 | 144 | /// Adds a probe to a batch. 145 | /// The new probe will be added as the last probe in the batch. 146 | pub fn add_probe(&mut self, probe: &Sphere) { 147 | unsafe { 148 | audionimbus_sys::iplProbeBatchAddProbe( 149 | self.raw_ptr(), 150 | audionimbus_sys::IPLSphere::from(*probe), 151 | ); 152 | } 153 | } 154 | 155 | /// Adds every probe in an array to a batch. 156 | /// The new probes will be added, in order, at the end of the batch. 157 | pub fn add_probe_array(&mut self, probe_array: &ProbeArray) { 158 | unsafe { 159 | audionimbus_sys::iplProbeBatchAddProbeArray(self.raw_ptr(), probe_array.raw_ptr()); 160 | } 161 | } 162 | 163 | /// Commits all changes made to a probe batch since this function was last called (or since the probe batch was first created, if this function was never called). 164 | /// This function must be called after adding, removing, or updating any probes in the batch, for the changes to take effect. 165 | pub fn commit(&self) { 166 | unsafe { audionimbus_sys::iplProbeBatchCommit(self.raw_ptr()) } 167 | } 168 | 169 | /// Saves a probe batch to a serialized object. 170 | /// Typically, the serialized object will then be saved to disk. 171 | pub fn save(&self, serialized_object: &mut SerializedObject) { 172 | unsafe { 173 | audionimbus_sys::iplProbeBatchSave(self.raw_ptr(), serialized_object.raw_ptr()); 174 | } 175 | } 176 | 177 | /// Loads a probe batch from a serialized object. 178 | /// Typically, the serialized object will be created from a byte array loaded from disk or over the network. 179 | pub fn load( 180 | context: &Context, 181 | serialized_object: &mut SerializedObject, 182 | ) -> Result { 183 | let mut probe_batch = Self(std::ptr::null_mut()); 184 | 185 | let status = unsafe { 186 | audionimbus_sys::iplProbeBatchLoad( 187 | context.raw_ptr(), 188 | serialized_object.raw_ptr(), 189 | probe_batch.raw_ptr_mut(), 190 | ) 191 | }; 192 | 193 | if let Some(error) = to_option_error(status) { 194 | return Err(error); 195 | } 196 | 197 | Ok(probe_batch) 198 | } 199 | 200 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLProbeBatch { 201 | self.0 202 | } 203 | 204 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLProbeBatch { 205 | &mut self.0 206 | } 207 | } 208 | 209 | impl Clone for ProbeBatch { 210 | fn clone(&self) -> Self { 211 | unsafe { 212 | audionimbus_sys::iplProbeBatchRetain(self.0); 213 | } 214 | Self(self.0) 215 | } 216 | } 217 | 218 | impl Drop for ProbeBatch { 219 | fn drop(&mut self) { 220 | unsafe { audionimbus_sys::iplProbeBatchRelease(&mut self.0) } 221 | } 222 | } 223 | 224 | unsafe impl Send for ProbeBatch {} 225 | unsafe impl Sync for ProbeBatch {} 226 | -------------------------------------------------------------------------------- /audionimbus/src/serialized_object.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::error::{to_option_error, SteamAudioError}; 3 | 4 | #[cfg(doc)] 5 | use crate::geometry::Scene; 6 | #[cfg(doc)] 7 | use crate::probe::ProbeBatch; 8 | 9 | /// A serialized representation of an API object, like a [`Scene`] or [`ProbeBatch`]. 10 | /// 11 | /// Create an empty serialized object if you want to serialize an existing object to a byte array, or create a serialized object that wraps an existing byte array if you want to deserialize it. 12 | #[derive(Debug)] 13 | pub struct SerializedObject(pub(crate) audionimbus_sys::IPLSerializedObject); 14 | 15 | impl SerializedObject { 16 | pub fn try_new(context: &Context) -> Result { 17 | let serialized_object_settings = audionimbus_sys::IPLSerializedObjectSettings { 18 | data: std::ptr::null_mut(), 19 | size: 0, 20 | }; 21 | 22 | Self::try_with_settings(context, serialized_object_settings) 23 | } 24 | 25 | pub fn try_with_buffer( 26 | context: &Context, 27 | buffer: &mut Vec, 28 | ) -> Result { 29 | let serialized_object_settings = audionimbus_sys::IPLSerializedObjectSettings { 30 | data: buffer.as_mut_ptr() as *mut audionimbus_sys::IPLbyte, 31 | size: buffer.len(), 32 | }; 33 | 34 | Self::try_with_settings(context, serialized_object_settings) 35 | } 36 | 37 | fn try_with_settings( 38 | context: &Context, 39 | mut serialized_object_settings: audionimbus_sys::IPLSerializedObjectSettings, 40 | ) -> Result { 41 | let mut serialized_object = Self(std::ptr::null_mut()); 42 | 43 | let status = unsafe { 44 | audionimbus_sys::iplSerializedObjectCreate( 45 | context.raw_ptr(), 46 | &mut serialized_object_settings, 47 | serialized_object.raw_ptr_mut(), 48 | ) 49 | }; 50 | 51 | if let Some(error) = to_option_error(status) { 52 | return Err(error); 53 | } 54 | 55 | Ok(serialized_object) 56 | } 57 | 58 | pub fn raw_ptr(&self) -> audionimbus_sys::IPLSerializedObject { 59 | self.0 60 | } 61 | 62 | pub fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLSerializedObject { 63 | &mut self.0 64 | } 65 | 66 | pub fn to_vec(&self) -> Vec { 67 | let raw_ptr = self.raw_ptr(); 68 | 69 | let data_ptr = unsafe { audionimbus_sys::iplSerializedObjectGetData(raw_ptr) }; 70 | 71 | let size = unsafe { audionimbus_sys::iplSerializedObjectGetSize(raw_ptr) } as usize; 72 | 73 | let data_slice = unsafe { std::slice::from_raw_parts(data_ptr, size) }; 74 | 75 | data_slice.to_vec() 76 | } 77 | } 78 | 79 | impl Clone for SerializedObject { 80 | fn clone(&self) -> Self { 81 | unsafe { 82 | audionimbus_sys::iplSerializedObjectRetain(self.0); 83 | } 84 | Self(self.0) 85 | } 86 | } 87 | 88 | impl Drop for SerializedObject { 89 | fn drop(&mut self) { 90 | unsafe { audionimbus_sys::iplSerializedObjectRelease(&mut self.0) } 91 | } 92 | } 93 | 94 | unsafe impl Send for SerializedObject {} 95 | unsafe impl Sync for SerializedObject {} 96 | -------------------------------------------------------------------------------- /audionimbus/src/version.rs: -------------------------------------------------------------------------------- 1 | pub const STEAMAUDIO_VERSION: usize = audionimbus_sys::STEAMAUDIO_VERSION as usize; 2 | pub const STEAMAUDIO_VERSION_MAJOR: usize = audionimbus_sys::STEAMAUDIO_VERSION_MAJOR as usize; 3 | pub const STEAMAUDIO_VERSION_MINOR: usize = audionimbus_sys::STEAMAUDIO_VERSION_MINOR as usize; 4 | pub const STEAMAUDIO_VERSION_PATCH: usize = audionimbus_sys::STEAMAUDIO_VERSION_PATCH as usize; 5 | 6 | /// The version of the Steam Audio library. 7 | #[derive(Copy, Clone, Debug)] 8 | pub struct SteamAudioVersion { 9 | pub major: usize, 10 | pub minor: usize, 11 | pub patch: usize, 12 | } 13 | 14 | impl From for u32 { 15 | fn from(version: SteamAudioVersion) -> Self { 16 | ((version.major << 16) + (version.minor << 8) + version.patch) as u32 17 | } 18 | } 19 | 20 | impl Default for SteamAudioVersion { 21 | fn default() -> Self { 22 | Self { 23 | major: STEAMAUDIO_VERSION_MAJOR, 24 | minor: STEAMAUDIO_VERSION_MINOR, 25 | patch: STEAMAUDIO_VERSION_PATCH, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /audionimbus/src/wwise.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::hrtf::Hrtf; 3 | use crate::simulation::{SimulationSettings, Source}; 4 | 5 | #[derive(Debug, Copy, Clone)] 6 | /// Settings used for initializing the Steam Audio Wwise integration. 7 | pub struct WwiseSettings { 8 | /// Scaling factor to apply when converting from game engine units to Steam Audio units (which are in meters). 9 | pub meters_per_unit: f32, 10 | } 11 | 12 | /// Initializes the Wwise integration. 13 | /// 14 | /// This function must be called before creating any Steam Audio DSP effects. 15 | pub fn initialize(context: &Context, settings: Option) { 16 | let mut ffi_settings = settings.map(|s| audionimbus_sys::IPLWwiseSettings { 17 | metersPerUnit: s.meters_per_unit, 18 | }); 19 | 20 | let ipl_settings = ffi_settings 21 | .as_mut() 22 | .map(|s| s as *mut _) 23 | .unwrap_or(std::ptr::null_mut()); 24 | 25 | unsafe { audionimbus_sys::wwise::iplWwiseInitialize(context.raw_ptr(), ipl_settings) } 26 | } 27 | 28 | /// Shuts down the Wwise integration. 29 | /// 30 | /// This function must be called after all Steam Audio DSP effects have been destroyed. 31 | pub fn terminate() { 32 | unsafe { audionimbus_sys::wwise::iplWwiseTerminate() } 33 | } 34 | 35 | /// Specifies the simulation settings used by the game engine for simulating direct and/or indirect sound propagation. 36 | /// 37 | /// This function must be called once during initialization, after [`initialize`]. 38 | pub fn set_simulation_settings(simulation_settings: SimulationSettings) { 39 | unsafe { 40 | audionimbus_sys::wwise::iplWwiseSetSimulationSettings( 41 | audionimbus_sys::IPLSimulationSettings::from(simulation_settings), 42 | ) 43 | } 44 | } 45 | 46 | /// Specifies the HRTF to use for spatialization in subsequent audio frames. 47 | /// 48 | /// This function must be called once during initialization, after [`initialize`]. 49 | /// It should also be called whenever the game engine needs to change the HRTF. 50 | pub fn set_hrtf(hrtf: &Hrtf) { 51 | unsafe { audionimbus_sys::wwise::iplWwiseSetHRTF(hrtf.raw_ptr()) } 52 | } 53 | 54 | /// Wwise game object ID. 55 | pub type WwiseGameObjectId = u64; 56 | 57 | /// Specifies the [`Source`] used by the game engine for simulating occlusion, reflections, etc. for the given Wwise game object (identified by its AkGameObjectID). 58 | pub fn add_source(game_object_id: WwiseGameObjectId, source: &Source) { 59 | unsafe { audionimbus_sys::wwise::iplWwiseAddSource(game_object_id, source.raw_ptr()) } 60 | } 61 | 62 | /// Remove any [`Source`] associated the given Wwise game object ID. 63 | /// 64 | /// This should be called when the game engine no longer needs to render occlusion, reflections, etc. for the given game object. 65 | pub fn remove_source(game_object_id: WwiseGameObjectId) { 66 | unsafe { audionimbus_sys::wwise::iplWwiseRemoveSource(game_object_id) } 67 | } 68 | 69 | /// Specifies the [`Source`] used by the game engine for simulating reverb. 70 | /// 71 | /// Typically, listener-centric reverb is simulated by creating a [`Source`] with the same position as the listener, and simulating reflections. 72 | /// To render this simulated reverb, call this function and pass it the [`Source`] used. 73 | pub fn set_reverb_source(source: &Source) { 74 | unsafe { audionimbus_sys::wwise::iplWwiseSetReverbSource(source.raw_ptr()) } 75 | } 76 | 77 | /// Returns the version of the Wwise integration being used. 78 | pub fn version() -> WwiseIntegrationVersion { 79 | use std::os::raw::c_uint; 80 | 81 | let mut major: c_uint = 0; 82 | let mut minor: c_uint = 0; 83 | let mut patch: c_uint = 0; 84 | 85 | unsafe { 86 | audionimbus_sys::wwise::iplWwiseGetVersion(&mut major, &mut minor, &mut patch); 87 | } 88 | 89 | WwiseIntegrationVersion { 90 | major: major as usize, 91 | minor: minor as usize, 92 | patch: patch as usize, 93 | } 94 | } 95 | 96 | /// The version of the Wwise integration. 97 | #[derive(Copy, Clone, Debug)] 98 | pub struct WwiseIntegrationVersion { 99 | pub major: usize, 100 | pub minor: usize, 101 | pub patch: usize, 102 | } 103 | --------------------------------------------------------------------------------