├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── adapt.rs ├── offline.rs ├── realtime.rs ├── simple.rs ├── wav.rs └── wav │ └── stereo-test.wav └── src ├── adapt.rs ├── constant.rs ├── cycle.rs ├── downmix.rs ├── fader.rs ├── frame.rs ├── frames.rs ├── gain.rs ├── lib.rs ├── math ├── libm.rs ├── mod.rs └── std.rs ├── mixer.rs ├── reinhard.rs ├── ring.rs ├── set.rs ├── signal.rs ├── sine.rs ├── smooth.rs ├── spatial.rs ├── speed.rs ├── spsc.rs ├── stream.rs ├── swap.rs └── tanh.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | pull_request: 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | rust: 14 | - stable 15 | - beta 16 | 17 | steps: 18 | - name: Install alsa dev package 19 | run: sudo apt-get install libasound2-dev 20 | 21 | - uses: actions/checkout@v1 22 | 23 | - uses: actions-rs/toolchain@v1 24 | with: 25 | profile: minimal 26 | toolchain: ${{ matrix.rust }} 27 | override: true 28 | 29 | - uses: actions-rs/cargo@v1 30 | with: 31 | command: build 32 | 33 | - uses: actions-rs/cargo@v1 34 | with: 35 | command: test 36 | 37 | - uses: actions-rs/cargo@v1 38 | with: 39 | command: build 40 | args: --all-features 41 | 42 | - uses: actions-rs/cargo@v1 43 | with: 44 | command: test 45 | args: --all-features 46 | 47 | lint: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v1 51 | 52 | - uses: actions-rs/toolchain@v1 53 | with: 54 | profile: minimal 55 | toolchain: stable 56 | override: true 57 | components: rustfmt, clippy 58 | 59 | - uses: actions-rs/cargo@v1 60 | with: 61 | command: fmt 62 | args: --all -- --check 63 | 64 | - name: doc 65 | run: cargo doc --no-deps 66 | env: 67 | RUSTDOCFLAGS: -Dwarnings 68 | 69 | - uses: actions-rs/cargo@v1 70 | if: always() 71 | with: 72 | command: clippy 73 | args: -- -D warnings 74 | 75 | miri: 76 | runs-on: ubuntu-latest 77 | steps: 78 | - name: Install alsa dev package 79 | run: sudo apt-get install libasound2-dev 80 | 81 | - uses: actions/checkout@v1 82 | 83 | - uses: actions-rs/toolchain@v1 84 | with: 85 | profile: minimal 86 | toolchain: nightly 87 | override: true 88 | components: miri, rust-src 89 | 90 | - uses: actions-rs/cargo@v1 91 | with: 92 | command: miri 93 | args: setup 94 | 95 | - uses: actions-rs/cargo@v1 96 | with: 97 | command: miri 98 | args: test 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oddio" 3 | version = "0.7.4" 4 | authors = ["Benjamin Saunders "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/Ralith/oddio" 8 | description = "Lightweight game audio library" 9 | categories = ["multimedia::audio", "game-development"] 10 | 11 | [badges] 12 | maintenance = { status = "actively-developed" } 13 | 14 | [features] 15 | no_std = ["libm"] 16 | 17 | [dependencies] 18 | mint = "0.5.5" 19 | libm = { version = "0.2.1", optional = true } 20 | 21 | [dev-dependencies] 22 | cpal = "0.13.1" 23 | hound = "3.4" 24 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 The oddio Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Oddio 2 | 3 | [![Documentation](https://docs.rs/oddio/badge.svg)](https://docs.rs/oddio/) 4 | [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE-APACHE) 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE-MIT) 6 | 7 | Oddio is a game-oriented audio library that is: 8 | 9 | - **Lightweight**: Fast compilation, few dependencies, and a simple interface 10 | - **Sans I/O**: Send output wherever you like 11 | - **Real-time**: Audio output is efficient and wait-free: no glitches until you run out of CPU 12 | - **3D**: Spatialization with doppler effects and propagation delay available out of the box 13 | - **Extensible**: Implement `Signal` for custom streaming synthesis and filtering 14 | - **Composable**: `Signal`s can be transformed without obstructing the inner `Signal`'s controls 15 | 16 | ### Example 17 | 18 | ```rust 19 | let (mut scene_handle, mut scene) = oddio::SpatialScene::new(); 20 | 21 | // In audio callback: 22 | let out_frames = oddio::frame_stereo(data); 23 | oddio::run(&mut scene, output_sample_rate, out_frames); 24 | 25 | // In game logic: 26 | let frames = oddio::FramesSignal::from(oddio::Frames::from_slice(sample_rate, &frames)); 27 | let mut handle = scene_handle 28 | .play(frames, oddio::SpatialOptions { position, velocity, ..Default::default() }); 29 | 30 | // When position/velocity changes: 31 | handle.set_motion(position, velocity, false); 32 | ``` 33 | 34 | ## License 35 | 36 | Licensed under either of 37 | 38 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 39 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 40 | 41 | at your option. 42 | 43 | ### Contribution 44 | 45 | Unless you explicitly state otherwise, any contribution intentionally 46 | submitted for inclusion in the work by you, as defined in the 47 | Apache-2.0 license, shall be dual licensed as above, without any 48 | additional terms or conditions. 49 | -------------------------------------------------------------------------------- /examples/adapt.rs: -------------------------------------------------------------------------------- 1 | const DURATION_SECS: u32 = 2; 2 | const RATE: u32 = 44100; 3 | const BLOCK_SIZE: usize = 512; 4 | 5 | fn main() { 6 | let (mut mixer, signal) = oddio::Mixer::new(); 7 | let mut signal = oddio::Adapt::new( 8 | signal, 9 | 1e-3 / 2.0f32.sqrt(), 10 | oddio::AdaptOptions { 11 | tau: 0.1, 12 | max_gain: 1e6, 13 | low: 0.1 / 2.0f32.sqrt(), 14 | high: 0.5 / 2.0f32.sqrt(), 15 | }, 16 | ); 17 | 18 | let spec = hound::WavSpec { 19 | channels: 1, 20 | sample_rate: RATE, 21 | bits_per_sample: 16, 22 | sample_format: hound::SampleFormat::Int, 23 | }; 24 | let mut writer = hound::WavWriter::create("adapt.wav", spec).unwrap(); 25 | 26 | let mut drive = || { 27 | for _ in 0..(RATE * DURATION_SECS / BLOCK_SIZE as u32) { 28 | let mut block = [0.0; BLOCK_SIZE]; 29 | oddio::run(&mut signal, RATE, &mut block); 30 | for &sample in &block { 31 | writer 32 | .write_sample((sample * i16::MAX as f32) as i16) 33 | .unwrap(); 34 | } 35 | } 36 | }; 37 | 38 | let quiet = oddio::FixedGain::new(oddio::Sine::new(0.0, 5e2), -60.0); 39 | let loud = oddio::FixedGain::new(oddio::Sine::new(0.0, 4e2), -2.0); 40 | 41 | mixer.play(quiet); 42 | drive(); 43 | let mut handle = mixer.play(loud); 44 | drive(); 45 | handle.stop(); 46 | drive(); 47 | 48 | writer.finalize().unwrap(); 49 | } 50 | -------------------------------------------------------------------------------- /examples/offline.rs: -------------------------------------------------------------------------------- 1 | const DURATION_SECS: u32 = 3; 2 | const RATE: u32 = 44100; 3 | const BLOCK_SIZE: usize = 512; 4 | const SPEED: f32 = 50.0; 5 | 6 | fn main() { 7 | let boop = oddio::Frames::from_iter( 8 | RATE, 9 | // Generate a simple sine wave 10 | (0..RATE * DURATION_SECS).map(|i| { 11 | let t = i as f32 / RATE as f32; 12 | (t * 500.0 * 2.0 * core::f32::consts::PI).sin() * 80.0 13 | }), 14 | ); 15 | let (mut scene_handle, mut scene) = oddio::SpatialScene::new(); 16 | scene_handle.play( 17 | oddio::FramesSignal::from(boop), 18 | oddio::SpatialOptions { 19 | position: [-SPEED, 10.0, 0.0].into(), 20 | velocity: [SPEED, 0.0, 0.0].into(), 21 | radius: 0.1, 22 | }, 23 | ); 24 | 25 | let spec = hound::WavSpec { 26 | channels: 2, 27 | sample_rate: RATE, 28 | bits_per_sample: 16, 29 | sample_format: hound::SampleFormat::Int, 30 | }; 31 | let mut writer = hound::WavWriter::create("offline.wav", spec).unwrap(); 32 | 33 | for _ in 0..(RATE * DURATION_SECS / BLOCK_SIZE as u32) { 34 | let mut block = [[0.0; 2]; BLOCK_SIZE]; 35 | oddio::run(&mut scene, RATE, &mut block); 36 | for &frame in &block { 37 | for &sample in &frame { 38 | writer 39 | .write_sample((sample * i16::MAX as f32) as i16) 40 | .unwrap(); 41 | } 42 | } 43 | } 44 | 45 | writer.finalize().unwrap(); 46 | } 47 | -------------------------------------------------------------------------------- /examples/realtime.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | thread, 3 | time::{Duration, Instant}, 4 | }; 5 | 6 | use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; 7 | 8 | const DURATION_SECS: u32 = 6; 9 | 10 | fn main() { 11 | let host = cpal::default_host(); 12 | let device = host 13 | .default_output_device() 14 | .expect("no output device available"); 15 | let sample_rate = device.default_output_config().unwrap().sample_rate(); 16 | let config = cpal::StreamConfig { 17 | channels: 2, 18 | sample_rate, 19 | buffer_size: cpal::BufferSize::Default, 20 | }; 21 | 22 | // create our oddio handles for a `SpatialScene`. We could also use a `Mixer`, 23 | // which doesn't spatialize signals. 24 | let (mut scene_handle, mut scene) = oddio::SpatialScene::new(); 25 | 26 | // We send `scene` into this closure, where changes to `scene_handle` are reflected. 27 | // `scene_handle` is how we add new sounds and modify the scene live. 28 | let stream = device 29 | .build_output_stream( 30 | &config, 31 | move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { 32 | let frames = oddio::frame_stereo(data); 33 | oddio::run(&mut scene, sample_rate.0, frames); 34 | }, 35 | move |err| { 36 | eprintln!("{}", err); 37 | }, 38 | ) 39 | .unwrap(); 40 | stream.play().unwrap(); 41 | 42 | // Let's make some audio. 43 | // Here, we're manually constructing a sound, which might otherwise be e.g. decoded from an mp3. 44 | // in `oddio`, a sound like this is called `Frames` (each frame consisting of one sample per channel). 45 | let boop = oddio::Frames::from_iter( 46 | sample_rate.0, 47 | // Generate a simple sine wave 48 | (0..sample_rate.0 * DURATION_SECS).map(|i| { 49 | let t = i as f32 / sample_rate.0 as f32; 50 | (t * 500.0 * 2.0 * core::f32::consts::PI).sin() * 80.0 51 | }), 52 | ); 53 | 54 | // We need to create a `FramesSignal`. This is the basic type we need to play a `Frames`. 55 | // We can create the most basic `FramesSignal` like this: 56 | let basic_signal: oddio::FramesSignal<_> = oddio::FramesSignal::from(boop); 57 | // or we could start 5 seconds in like this: 58 | // let basic_signal = oddio::FramesSignal::new(boop, 5.0); 59 | 60 | // We can also add filters around our `FramesSignal` to make our sound more controllable. 61 | // A common one is `Gain`, which lets us modulate the gain of the `Signal` (how loud it is) 62 | let (mut gain_control, gain) = oddio::Gain::new(basic_signal); 63 | 64 | // the speed at which we'll be moving around 65 | const SPEED: f32 = 50.0; 66 | // `play_buffered` is used because the dynamically adjustable `Gain` filter makes sample values 67 | // non-deterministic. For immutable signals like a bare `FramesSignal`, the regular `play` is 68 | // more efficient. 69 | let mut spatial_control = scene_handle.play_buffered( 70 | gain, 71 | oddio::SpatialOptions { 72 | position: [-SPEED, 10.0, 0.0].into(), 73 | velocity: [SPEED, 0.0, 0.0].into(), 74 | radius: 0.1, 75 | }, 76 | 1000.0, 77 | sample_rate.0, 78 | 0.1, 79 | ); 80 | 81 | let start = Instant::now(); 82 | 83 | loop { 84 | thread::sleep(Duration::from_millis(50)); 85 | let dt = start.elapsed(); 86 | if dt >= Duration::from_secs(DURATION_SECS as u64) { 87 | break; 88 | } 89 | 90 | // This has no noticable effect because it matches the initial velocity, but serves to 91 | // demonstrate that `Spatial` can smooth over the inevitable small timing inconsistencies 92 | // between the main thread and the audio thread without glitching. 93 | spatial_control.set_motion( 94 | [-SPEED + SPEED * dt.as_secs_f32(), 10.0, 0.0].into(), 95 | [SPEED, 0.0, 0.0].into(), 96 | false, 97 | ); 98 | 99 | // We also could adjust the Gain here in the same way: 100 | gain_control.set_gain(1.0); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | //! A minimal example of a dynamic real-time mixer; a good place to start. 2 | 3 | use std::{thread, time::Duration}; 4 | 5 | use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; 6 | 7 | fn main() { 8 | // Configure cpal 9 | let host = cpal::default_host(); 10 | let device = host 11 | .default_output_device() 12 | .expect("no output device available"); 13 | let sample_rate = device.default_output_config().unwrap().sample_rate(); 14 | let config = cpal::StreamConfig { 15 | channels: 2, 16 | sample_rate, 17 | buffer_size: cpal::BufferSize::Default, 18 | }; 19 | 20 | // Create the root mixer, and divide it into two parts: a handle that we can use to add new 21 | // signals to play, and an object we can pass to `oddio::run` in cpal's callback to generate 22 | // output frames. 23 | let (mut mixer_handle, mut mixer) = oddio::Mixer::new(); 24 | 25 | // Start cpal, taking care not to drop its stream early 26 | let stream = device 27 | .build_output_stream( 28 | &config, 29 | move |out_flat: &mut [f32], _: &cpal::OutputCallbackInfo| { 30 | let out_stereo: &mut [[f32; 2]] = oddio::frame_stereo(out_flat); 31 | oddio::run(&mut mixer, sample_rate.0, out_stereo); 32 | }, 33 | move |err| { 34 | eprintln!("{}", err); 35 | }, 36 | ) 37 | .unwrap(); 38 | stream.play().unwrap(); 39 | 40 | // Start a 200Hz sine wave. We can do this as many times as we like, whenever we like, with 41 | // different types of signals as needed. 42 | mixer_handle.play(oddio::MonoToStereo::new(oddio::Sine::new(0.0, 400.0))); 43 | 44 | // Wait a bit before exiting 45 | thread::sleep(Duration::from_secs(3)); 46 | } 47 | -------------------------------------------------------------------------------- /examples/wav.rs: -------------------------------------------------------------------------------- 1 | use std::{thread, time::Duration}; 2 | 3 | use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; 4 | 5 | fn main() { 6 | // get device's sample rate 7 | let host = cpal::default_host(); 8 | let device = host 9 | .default_output_device() 10 | .expect("no output device available"); 11 | let device_sample_rate = device.default_output_config().unwrap().sample_rate().0; 12 | 13 | // get metadata from the WAV file 14 | // note that this wav file has a low sample rate so the sound quality is bad 15 | let mut reader = hound::WavReader::new(include_bytes!("wav/stereo-test.wav").as_ref()) 16 | .expect("Failed to read WAV file"); 17 | let hound::WavSpec { 18 | sample_rate: source_sample_rate, 19 | sample_format, 20 | bits_per_sample, 21 | channels, 22 | .. 23 | } = reader.spec(); 24 | let length_samples = reader.duration(); 25 | let length_seconds = length_samples as f32 / source_sample_rate as f32; 26 | 27 | // this example assumes the sound has two channels 28 | assert_eq!(channels, 2); 29 | 30 | // convert the WAV data to floating point samples 31 | // e.g. i8 data is converted from [-128, 127] to [-1.0, 1.0] 32 | let samples_result: Result, _> = match sample_format { 33 | hound::SampleFormat::Int => { 34 | let max_value = 2_u32.pow(bits_per_sample as u32 - 1) - 1; 35 | reader 36 | .samples::() 37 | .map(|sample| sample.map(|sample| sample as f32 / max_value as f32)) 38 | .collect() 39 | } 40 | hound::SampleFormat::Float => reader.samples::().collect(), 41 | }; 42 | let mut samples = samples_result.unwrap(); 43 | 44 | // channels are interleaved, so we put them together in stereo 45 | let samples_stereo = oddio::frame_stereo(&mut samples); 46 | let sound_frames = oddio::Frames::from_slice(source_sample_rate, samples_stereo); 47 | 48 | let (mut mixer_handle, mut mixer) = oddio::Mixer::new(); 49 | 50 | let config = cpal::StreamConfig { 51 | channels: 2, 52 | sample_rate: cpal::SampleRate(device_sample_rate), 53 | buffer_size: cpal::BufferSize::Default, 54 | }; 55 | 56 | let stream = device 57 | .build_output_stream( 58 | &config, 59 | move |out_flat: &mut [f32], _: &cpal::OutputCallbackInfo| { 60 | let out_stereo = oddio::frame_stereo(out_flat); 61 | oddio::run(&mut mixer, device_sample_rate, out_stereo); 62 | }, 63 | move |err| { 64 | eprintln!("{}", err); 65 | }, 66 | ) 67 | .unwrap(); 68 | stream.play().unwrap(); 69 | 70 | mixer_handle.play(oddio::FramesSignal::from(sound_frames)); 71 | 72 | thread::sleep(Duration::from_secs_f32(length_seconds)); 73 | } 74 | -------------------------------------------------------------------------------- /examples/wav/stereo-test.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ralith/oddio/85ad0723b460bc8c641198ef02bf114ef04f04f9/examples/wav/stereo-test.wav -------------------------------------------------------------------------------- /src/adapt.rs: -------------------------------------------------------------------------------- 1 | use crate::{math::Float, Frame, Signal}; 2 | 3 | /// Smoothly adjusts gain over time to keep average (RMS) signal level within a target range 4 | /// 5 | /// Useful for allowing both quiet and loud sounds to be heard without severe distortion. 6 | /// 7 | /// Rapid changes in input amplitude can cause the output to rise above 1. If a hard limit on output 8 | /// is required, a stateless compressor like [`Reinhard`](crate::Reinhard) should be chained 9 | /// afterwards. 10 | /// 11 | /// This filter is configured in terms of signal root mean square values. For reference, the RMS 12 | /// value of a sine wave is `amplitude / 2.0f32.sqrt()`. Note that these are linear units, whereas 13 | /// perception of loudness is logarithmic. 14 | pub struct Adapt { 15 | options: AdaptOptions, 16 | avg_squared: f32, 17 | inner: T, 18 | } 19 | 20 | impl Adapt { 21 | /// Apply adaptation to `signal` 22 | /// 23 | /// Initialized as if an infinite signal with root mean squared level `initial_rms` had been 24 | /// processed. 25 | pub fn new(signal: T, initial_rms: f32, options: AdaptOptions) -> Self { 26 | Self { 27 | options, 28 | avg_squared: initial_rms * initial_rms, 29 | inner: signal, 30 | } 31 | } 32 | } 33 | 34 | /// Configuration for an [`Adapt`] filter, passed to [`Adapt::new`] 35 | #[derive(Debug, Copy, Clone)] 36 | pub struct AdaptOptions { 37 | /// How smoothly the filter should respond. Smaller values reduce time spent outside the target 38 | /// range, at the cost of lower perceived dynamic range. 0.1 is a good place to start. 39 | pub tau: f32, 40 | /// Maximum linear gain to apply regardless of input signal 41 | pub max_gain: f32, 42 | /// When the average RMS level is below this, the gain will increase over time, up to at most 43 | /// `max_gain` 44 | pub low: f32, 45 | /// When the average RMS level is above this, the gain will decrease over time 46 | /// 47 | /// This should usually be set lower than your desired maximum peak output to avoid clipping of 48 | /// transient spikes. 49 | pub high: f32, 50 | } 51 | 52 | impl Default for AdaptOptions { 53 | fn default() -> Self { 54 | Self { 55 | tau: 0.1, 56 | max_gain: f32::INFINITY, 57 | low: 0.1 / 2.0f32.sqrt(), 58 | high: 0.5 / 2.0f32.sqrt(), 59 | } 60 | } 61 | } 62 | 63 | impl Signal for Adapt 64 | where 65 | T::Frame: Frame, 66 | { 67 | type Frame = T::Frame; 68 | 69 | fn sample(&mut self, interval: f32, out: &mut [T::Frame]) { 70 | let alpha = 1.0 - (-interval / self.options.tau).exp(); 71 | self.inner.sample(interval, out); 72 | for x in out { 73 | let sample = x.channels().iter().sum::(); 74 | self.avg_squared = sample * sample * alpha + self.avg_squared * (1.0 - alpha); 75 | let avg_peak = self.avg_squared.sqrt() * 2.0f32.sqrt(); 76 | let gain = if avg_peak < self.options.low { 77 | (self.options.low / avg_peak).min(self.options.max_gain) 78 | } else if avg_peak > self.options.high { 79 | self.options.high / avg_peak 80 | } else { 81 | 1.0 82 | }; 83 | for s in x.channels_mut() { 84 | *s *= gain; 85 | } 86 | } 87 | } 88 | 89 | fn is_finished(&self) -> bool { 90 | self.inner.is_finished() 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use super::*; 97 | use crate::Constant; 98 | 99 | #[test] 100 | fn smoke() { 101 | const LOW: f32 = 0.1; 102 | const HIGH: f32 = 1.0; 103 | const MAX_GAIN: f32 = 10.0; 104 | let mut adapt = Adapt::new( 105 | Constant::new(0.0), 106 | 0.0, 107 | AdaptOptions { 108 | tau: 0.5, 109 | low: LOW, 110 | high: HIGH, 111 | max_gain: MAX_GAIN, 112 | }, 113 | ); 114 | 115 | let mut out = [0.0]; 116 | // Silence isn't modified 117 | for _ in 0..10 { 118 | adapt.sample(0.1, &mut out); 119 | assert_eq!(out[0], 0.0); 120 | } 121 | 122 | // Suddenly loud! 123 | adapt.inner.0 = 10.0; 124 | let mut out = [0.0; 10]; 125 | adapt.sample(0.1, &mut out); 126 | assert!(out[0] > 0.0 && out[0] < 10.0); 127 | for w in out.windows(2) { 128 | assert!(w[0] > w[1]); 129 | } 130 | 131 | // Back to quiet. 132 | adapt.inner.0 = 0.01; 133 | adapt.sample(0.1, &mut out); 134 | assert!(out[0] > 0.0); 135 | for w in out.windows(2) { 136 | assert!(w[0] < w[1]); 137 | } 138 | 139 | // SUPER quiet. 140 | adapt.inner.0 = 1e-6; 141 | for _ in 0..100 { 142 | adapt.sample(0.1, &mut out); 143 | for &x in &out { 144 | assert!(x <= adapt.inner.0 * MAX_GAIN); 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/constant.rs: -------------------------------------------------------------------------------- 1 | use crate::{Seek, Signal}; 2 | 3 | /// A constant signal, useful for testing 4 | pub struct Constant(pub T); 5 | 6 | impl Constant { 7 | /// Construct a signal that always emits `frame` 8 | pub fn new(frame: T) -> Self { 9 | Self(frame) 10 | } 11 | } 12 | 13 | impl Signal for Constant { 14 | type Frame = T; 15 | 16 | fn sample(&mut self, _interval: f32, out: &mut [T]) { 17 | out.fill(self.0.clone()); 18 | } 19 | } 20 | 21 | impl Seek for Constant { 22 | fn seek(&mut self, _: f32) {} 23 | } 24 | -------------------------------------------------------------------------------- /src/cycle.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | 3 | use crate::{frame, math::Float, Frame, Frames, Seek, Signal}; 4 | 5 | /// Loops [`Frames`] end-to-end to construct a repeating signal 6 | pub struct Cycle { 7 | /// Current playback time, in samples 8 | cursor: f64, 9 | frames: Arc>, 10 | } 11 | 12 | impl Cycle { 13 | /// Construct cycle from `frames` 14 | // TODO: Crossfade 15 | pub fn new(frames: Arc>) -> Self { 16 | Self { 17 | cursor: 0.0, 18 | frames, 19 | } 20 | } 21 | } 22 | 23 | impl Signal for Cycle { 24 | type Frame = T; 25 | 26 | fn sample(&mut self, interval: f32, out: &mut [T]) { 27 | let ds = interval * self.frames.rate() as f32; 28 | let mut base = self.cursor as usize; 29 | let mut offset = (self.cursor - base as f64) as f32; 30 | for o in out { 31 | let trunc = unsafe { offset.to_int_unchecked::() }; 32 | let fract = offset - trunc as f32; 33 | let x = base + trunc; 34 | let (a, b) = if x < self.frames.len() - 1 { 35 | (self.frames[x], self.frames[x + 1]) 36 | } else if x < self.frames.len() { 37 | (self.frames[x], self.frames[0]) 38 | } else { 39 | base = 0; 40 | offset = (x % self.frames.len()) as f32 + fract; 41 | let x = unsafe { offset.to_int_unchecked::() }; 42 | if x < self.frames.len() - 1 { 43 | (self.frames[x], self.frames[x + 1]) 44 | } else { 45 | (self.frames[x], self.frames[0]) 46 | } 47 | }; 48 | 49 | *o = frame::lerp(&a, &b, fract); 50 | offset += ds; 51 | } 52 | self.cursor = base as f64 + offset as f64; 53 | } 54 | } 55 | 56 | impl Seek for Cycle { 57 | fn seek(&mut self, seconds: f32) { 58 | self.cursor = (self.cursor + f64::from(seconds) * self.frames.rate() as f64) 59 | .rem_euclid(self.frames.len() as f64); 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::*; 66 | 67 | const FRAMES: &[f32] = &[1.0, 2.0, 3.0]; 68 | 69 | #[test] 70 | fn wrap_single() { 71 | let mut s = Cycle::new(Frames::from_slice(1, FRAMES)); 72 | let mut buf = [0.0; 5]; 73 | s.sample(1.0, &mut buf); 74 | assert_eq!(buf, [1.0, 2.0, 3.0, 1.0, 2.0]); 75 | } 76 | 77 | #[test] 78 | fn wrap_multi() { 79 | let mut s = Cycle::new(Frames::from_slice(1, FRAMES)); 80 | let mut buf = [0.0; 5]; 81 | s.sample(1.0, &mut buf[..2]); 82 | s.sample(1.0, &mut buf[2..]); 83 | assert_eq!(buf, [1.0, 2.0, 3.0, 1.0, 2.0]); 84 | } 85 | 86 | #[test] 87 | fn wrap_fract() { 88 | let mut s = Cycle::new(Frames::from_slice(1, FRAMES)); 89 | let mut buf = [0.0; 8]; 90 | s.sample(0.5, &mut buf[..2]); 91 | s.sample(0.5, &mut buf[2..]); 92 | assert_eq!(buf, [1.0, 1.5, 2.0, 2.5, 3.0, 2.0, 1.0, 1.5]); 93 | } 94 | 95 | #[test] 96 | fn wrap_fract_offset() { 97 | let mut s = Cycle::new(Frames::from_slice(1, FRAMES)); 98 | s.seek(0.25); 99 | let mut buf = [0.0; 7]; 100 | s.sample(0.5, &mut buf[..2]); 101 | s.sample(0.5, &mut buf[2..]); 102 | assert_eq!(buf, [1.25, 1.75, 2.25, 2.75, 2.5, 1.5, 1.25]); 103 | } 104 | 105 | #[test] 106 | fn wrap_single_frame() { 107 | let mut s = Cycle::new(Frames::from_slice(1, &[1.0])); 108 | s.seek(0.25); 109 | let mut buf = [0.0; 3]; 110 | s.sample(1.0, &mut buf[..2]); 111 | s.sample(1.0, &mut buf[2..]); 112 | assert_eq!(buf, [1.0, 1.0, 1.0]); 113 | } 114 | 115 | #[test] 116 | fn wrap_large_interval() { 117 | let mut s = Cycle::new(Frames::from_slice(1, FRAMES)); 118 | let mut buf = [0.0; 3]; 119 | s.sample(10.0, &mut buf[..2]); 120 | s.sample(10.0, &mut buf[2..]); 121 | assert_eq!(buf, [1.0, 2.0, 3.0]); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/downmix.rs: -------------------------------------------------------------------------------- 1 | use crate::{Frame, Sample, Seek, Signal}; 2 | 3 | /// Sums all channels together 4 | /// 5 | /// Beware that downmixing produces a maximum amplitude equal to the sum of the maximum amplitudes 6 | /// of its inputs. However, scaling the mixed signal back down by that proportion will usually 7 | /// produce a quieter signal than the inputs. 8 | pub struct Downmix(T); 9 | 10 | impl Downmix { 11 | /// Sum together `signal`'s channels 12 | pub fn new(signal: T) -> Self { 13 | Self(signal) 14 | } 15 | } 16 | 17 | impl Signal for Downmix 18 | where 19 | T::Frame: Frame, 20 | { 21 | type Frame = Sample; 22 | 23 | fn sample(&mut self, interval: f32, out: &mut [Sample]) { 24 | const CHUNK_SIZE: usize = 256; 25 | 26 | let mut buf = [Frame::ZERO; CHUNK_SIZE]; 27 | for chunk in out.chunks_mut(CHUNK_SIZE) { 28 | self.0.sample(interval, &mut buf); 29 | for (i, o) in buf.iter_mut().zip(chunk) { 30 | *o = i.channels().iter().copied().sum(); 31 | } 32 | } 33 | } 34 | 35 | fn is_finished(&self) -> bool { 36 | self.0.is_finished() 37 | } 38 | } 39 | 40 | impl Seek for Downmix 41 | where 42 | T::Frame: Frame, 43 | { 44 | fn seek(&mut self, seconds: f32) { 45 | self.0.seek(seconds); 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use super::*; 52 | use crate::Constant; 53 | 54 | #[test] 55 | fn smoke() { 56 | let mut signal = Downmix::new(Constant::new([1.0, 2.0])); 57 | let mut out = [0.0; 384]; 58 | signal.sample(1.0, &mut out); 59 | assert_eq!(out, [3.0; 384]); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/fader.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | use core::mem; 3 | 4 | use crate::{frame, math::Float, swap, Frame, Signal}; 5 | 6 | /// Cross-fades smoothly between dynamically-supplied signals 7 | /// 8 | /// Uses constant-power fading, suitable for blending uncorrelated signals without distorting 9 | /// perceived loudness 10 | pub struct Fader { 11 | progress: f32, 12 | next: swap::Receiver>>, 13 | inner: T, 14 | } 15 | 16 | impl Fader { 17 | /// Create a fader initially wrapping `inner` 18 | pub fn new(inner: T) -> (FaderControl, Self) { 19 | let (send, recv) = swap::swap(|| None); 20 | let signal = Self { 21 | progress: 1.0, 22 | next: recv, 23 | inner, 24 | }; 25 | let control = FaderControl(send); 26 | (control, signal) 27 | } 28 | } 29 | 30 | impl Signal for Fader 31 | where 32 | T::Frame: Frame, 33 | { 34 | type Frame = T::Frame; 35 | 36 | #[allow(clippy::float_cmp)] 37 | fn sample(&mut self, interval: f32, mut out: &mut [T::Frame]) { 38 | if self.progress >= 1.0 { 39 | // A fade must complete before a new one begins 40 | if self.next.refresh() { 41 | self.progress = 0.0; 42 | } else { 43 | // Fast path 44 | self.inner.sample(interval, out); 45 | return; 46 | } 47 | } 48 | 49 | let next = (*self.next.received()).as_mut().unwrap(); 50 | let increment = interval / next.duration; 51 | while !out.is_empty() { 52 | let mut buffer = [(); 1024].map(|()| T::Frame::ZERO); 53 | let n = buffer.len().min(out.len()); 54 | self.inner.sample(interval, &mut buffer); 55 | next.fade_to.sample(interval, out); 56 | 57 | for (o, x) in out.iter_mut().zip(&buffer) { 58 | let fade_out = (1.0 - self.progress).sqrt(); 59 | let fade_in = self.progress.sqrt(); 60 | *o = frame::mix(&frame::scale(x, fade_out), &frame::scale(o, fade_in)); 61 | self.progress = (self.progress + increment).min(1.0); 62 | } 63 | out = &mut out[n..]; 64 | } 65 | 66 | if self.progress >= 1.0 { 67 | // We've finished fading; move the new signal into `self`, and stash the old one back in 68 | // `next` to be dropped by a future `fade_to` call. 69 | mem::swap(&mut self.inner, &mut next.fade_to); 70 | } 71 | } 72 | 73 | #[inline] 74 | fn is_finished(&self) -> bool { 75 | false 76 | } 77 | } 78 | 79 | /// Thread-safe control for a [`Fader`] filter 80 | pub struct FaderControl(swap::Sender>>); 81 | 82 | impl FaderControl { 83 | /// Crossfade to `signal` over `duration`. If a fade is already in progress, it will complete 84 | /// before a fading to the new signal begins. If another signal is already waiting for a current 85 | /// fade to complete, the waiting signal is replaced. 86 | pub fn fade_to(&mut self, signal: T, duration: f32) { 87 | *self.0.pending() = Some(Command { 88 | fade_to: signal, 89 | duration, 90 | }); 91 | self.0.flush() 92 | } 93 | } 94 | 95 | struct Command { 96 | fade_to: T, 97 | duration: f32, 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use crate::Constant; 103 | 104 | use super::*; 105 | 106 | #[test] 107 | fn smoke() { 108 | let (mut c, mut s) = Fader::new(Constant(1.0)); 109 | let mut buf = [42.0; 12]; 110 | s.sample(0.1, &mut buf); 111 | assert_eq!(buf, [1.0; 12]); 112 | c.fade_to(Constant(0.0), 1.0); 113 | s.sample(0.1, &mut buf); 114 | assert_eq!(buf[0], 1.0); 115 | assert_eq!(buf[11], 0.0); 116 | assert!((buf[5] - 0.5f32.sqrt()).abs() < 1e-6); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/frame.rs: -------------------------------------------------------------------------------- 1 | use crate::Sample; 2 | 3 | /// A single frame of audio data, encoding one sample for each channel 4 | pub trait Frame { 5 | /// A frame with zeroes in every channel 6 | const ZERO: Self; 7 | 8 | /// Access the frame's channels 9 | fn channels(&self) -> &[Sample]; 10 | 11 | /// Mutably access the frame's channels 12 | fn channels_mut(&mut self) -> &mut [Sample]; 13 | } 14 | 15 | #[inline] 16 | fn map(x: &T, mut f: impl FnMut(Sample) -> Sample) -> T { 17 | let mut out = T::ZERO; 18 | for (&x, o) in x.channels().iter().zip(out.channels_mut()) { 19 | *o = f(x); 20 | } 21 | out 22 | } 23 | 24 | #[inline] 25 | fn bimap(x: &T, y: &T, mut f: impl FnMut(Sample, Sample) -> Sample) -> T { 26 | let mut out = T::ZERO; 27 | for ((&x, &y), o) in x 28 | .channels() 29 | .iter() 30 | .zip(y.channels()) 31 | .zip(out.channels_mut()) 32 | { 33 | *o = f(x, y); 34 | } 35 | out 36 | } 37 | 38 | #[inline] 39 | pub(crate) fn lerp(a: &T, b: &T, t: f32) -> T { 40 | bimap(a, b, |a, b| a + t * (b - a)) 41 | } 42 | 43 | #[inline] 44 | pub(crate) fn mix(a: &T, b: &T) -> T { 45 | bimap(a, b, |a, b| a + b) 46 | } 47 | 48 | #[inline] 49 | pub(crate) fn scale(x: &T, factor: f32) -> T { 50 | map(x, |x| x * factor) 51 | } 52 | 53 | impl Frame for Sample { 54 | const ZERO: Sample = 0.0; 55 | 56 | #[inline] 57 | fn channels(&self) -> &[Sample] { 58 | core::slice::from_ref(self) 59 | } 60 | 61 | #[inline] 62 | fn channels_mut(&mut self) -> &mut [Sample] { 63 | core::slice::from_mut(self) 64 | } 65 | } 66 | 67 | impl Frame for [Sample; N] { 68 | const ZERO: Self = [0.0; N]; 69 | 70 | #[inline] 71 | fn channels(&self) -> &[Sample] { 72 | self.as_ref() 73 | } 74 | 75 | #[inline] 76 | fn channels_mut(&mut self) -> &mut [Sample] { 77 | self.as_mut() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/frames.rs: -------------------------------------------------------------------------------- 1 | use crate::alloc::{alloc, boxed::Box, sync::Arc}; 2 | use core::{ 3 | convert::TryFrom, 4 | mem, 5 | ops::{Deref, DerefMut}, 6 | ptr, 7 | sync::atomic::{AtomicIsize, Ordering}, 8 | }; 9 | 10 | use crate::{frame, math::Float, Frame, Seek, Signal}; 11 | 12 | /// A sequence of static audio frames at a particular sample rate 13 | /// 14 | /// Used to store e.g. sound effects decoded from files on disk. 15 | /// 16 | /// Dynamically sized type. Typically stored inside an `Arc`, allowing efficient simultaneous use by 17 | /// multiple signals. 18 | #[derive(Debug)] 19 | pub struct Frames { 20 | rate: f64, 21 | samples: [T], 22 | } 23 | 24 | impl Frames { 25 | /// Construct samples from existing memory 26 | pub fn from_slice(rate: u32, samples: &[T]) -> Arc 27 | where 28 | T: Copy, 29 | { 30 | let header_layout = alloc::Layout::new::(); 31 | let (layout, payload_offset) = header_layout 32 | .extend( 33 | alloc::Layout::from_size_align(mem::size_of_val(samples), mem::align_of::()) 34 | .unwrap(), 35 | ) 36 | .unwrap(); 37 | let layout = layout.pad_to_align(); 38 | unsafe { 39 | let mem = alloc::alloc(layout); 40 | mem.cast::().write(rate.into()); 41 | let payload = mem.add(payload_offset).cast::(); 42 | for (i, &x) in samples.iter().enumerate() { 43 | payload.add(i).write(x); 44 | } 45 | Box::from_raw(ptr::slice_from_raw_parts_mut(mem, samples.len()) as *mut Self).into() 46 | } 47 | } 48 | 49 | /// Generate samples from an iterator 50 | pub fn from_iter(rate: u32, iter: I) -> Arc 51 | where 52 | I: IntoIterator, 53 | I::IntoIter: ExactSizeIterator, 54 | { 55 | let iter = iter.into_iter(); 56 | let len = iter.len(); 57 | let header_layout = alloc::Layout::new::(); 58 | let (layout, payload_offset) = header_layout 59 | .extend( 60 | alloc::Layout::from_size_align(mem::size_of::() * len, mem::align_of::()) 61 | .unwrap(), 62 | ) 63 | .unwrap(); 64 | let layout = layout.pad_to_align(); 65 | unsafe { 66 | let mem = alloc::alloc(layout); 67 | mem.cast::().write(rate.into()); 68 | let payload = mem.add(payload_offset).cast::(); 69 | let mut n = 0; 70 | for (i, x) in iter.enumerate() { 71 | payload.add(i).write(x); 72 | n += 1; 73 | } 74 | assert_eq!(n, len, "iterator returned incorrect length"); 75 | Box::from_raw(ptr::slice_from_raw_parts_mut(mem, len) as *mut Self).into() 76 | } 77 | } 78 | 79 | /// Number of samples per second 80 | pub fn rate(&self) -> u32 { 81 | self.rate as u32 82 | } 83 | 84 | /// The runtime in seconds 85 | pub fn runtime(&self) -> f64 { 86 | self.samples.len() as f64 / self.rate 87 | } 88 | 89 | /// Interpolate a frame for position `s` 90 | /// 91 | /// Note that `s` is in samples, not seconds. Whole numbers are always an exact sample, and 92 | /// out-of-range positions yield 0. 93 | #[inline] 94 | pub fn interpolate(&self, s: f64) -> T 95 | where 96 | T: Frame + Copy, 97 | { 98 | let x0 = s as isize; 99 | let fract = (s - x0 as f64) as f32; 100 | let (a, b) = self.get_pair(x0); 101 | frame::lerp(&a, &b, fract) 102 | } 103 | 104 | #[inline] 105 | fn get_pair(&self, sample: isize) -> (T, T) 106 | where 107 | T: Frame + Copy, 108 | { 109 | if sample >= 0 { 110 | let sample = sample as usize; 111 | if sample < self.samples.len() - 1 { 112 | (self.samples[sample], self.samples[sample + 1]) 113 | } else if sample < self.samples.len() { 114 | (self.samples[sample], T::ZERO) 115 | } else { 116 | (T::ZERO, T::ZERO) 117 | } 118 | } else if sample < -1 { 119 | (T::ZERO, T::ZERO) 120 | } else { 121 | (T::ZERO, self.samples[0]) 122 | } 123 | } 124 | } 125 | 126 | impl Deref for Frames { 127 | type Target = [T]; 128 | fn deref(&self) -> &[T] { 129 | &self.samples 130 | } 131 | } 132 | 133 | impl DerefMut for Frames { 134 | fn deref_mut(&mut self) -> &mut [T] { 135 | &mut self.samples 136 | } 137 | } 138 | 139 | /// An audio signal backed by a static sequence of samples 140 | #[derive(Debug)] 141 | pub struct FramesSignal { 142 | /// Frames to play 143 | data: Arc>, 144 | /// Playback position in seconds 145 | t: f64, 146 | /// Approximation of t in samples, for reading from the control. We could store t's bits in an 147 | /// AtomicU64 here, but that would sacrifice portability to platforms that don't have it, 148 | /// e.g. mips32. 149 | sample_t: Arc, 150 | } 151 | 152 | impl FramesSignal { 153 | /// Create an audio signal from some samples 154 | /// 155 | /// `start_seconds` adjusts the initial playback position, and may be negative. 156 | pub fn new(data: Arc>, start_seconds: f64) -> (FramesSignalControl, Self) { 157 | let samples = data.len(); 158 | let signal = Self { 159 | t: start_seconds, 160 | sample_t: Arc::new(AtomicIsize::new((start_seconds * data.rate) as isize)), 161 | data, 162 | }; 163 | let control = FramesSignalControl { 164 | samples, 165 | sample_position: signal.sample_t.clone(), 166 | rate: signal.data.rate, 167 | }; 168 | (control, signal) 169 | } 170 | } 171 | 172 | impl Signal for FramesSignal { 173 | type Frame = T; 174 | 175 | #[inline] 176 | fn sample(&mut self, interval: f32, out: &mut [T]) { 177 | let s0 = self.t * self.data.rate; 178 | let ds = interval * self.data.rate as f32; 179 | let base = s0 as isize; 180 | if (ds - 1.0).abs() <= f32::EPSILON { 181 | // This fast-path is important for Spatial::play_buffered where we sample the signal 182 | // into the Ring with the interval = 1 / rate. 183 | let fract = (s0 - base as f64) as f32; 184 | for (i, o) in out.iter_mut().enumerate() { 185 | let (a, b) = self.data.get_pair(base + i as isize); 186 | *o = frame::lerp(&a, &b, fract); 187 | } 188 | } else { 189 | let mut offset = (s0 - base as f64) as f32; 190 | for o in out.iter_mut() { 191 | let trunc = unsafe { offset.to_int_unchecked::() }; 192 | let (a, b) = self.data.get_pair(base + trunc); 193 | let fract = offset - trunc as f32; 194 | *o = frame::lerp(&a, &b, fract); 195 | offset += ds; 196 | } 197 | } 198 | self.t += f64::from(interval) * out.len() as f64; 199 | self.sample_t 200 | .store((self.t * self.data.rate) as isize, Ordering::Relaxed); 201 | } 202 | 203 | #[inline] 204 | fn is_finished(&self) -> bool { 205 | self.t >= (self.data.samples.len() - 1) as f64 / self.data.rate 206 | } 207 | } 208 | 209 | impl Seek for FramesSignal { 210 | #[inline] 211 | fn seek(&mut self, seconds: f32) { 212 | self.t += f64::from(seconds); 213 | } 214 | } 215 | 216 | impl From>> for FramesSignal { 217 | fn from(samples: Arc>) -> Self { 218 | Self::new(samples, 0.0).1 219 | } 220 | } 221 | 222 | /// Thread-safe control for a [`FramesSignal`], giving access to current playback location. 223 | pub struct FramesSignalControl { 224 | samples: usize, 225 | sample_position: Arc, 226 | rate: f64, 227 | } 228 | 229 | impl FramesSignalControl { 230 | /// Get the current playback position. 231 | /// 232 | /// This number may be negative if the starting time was negative, 233 | /// and it may be longer than the duration of the sample as well. 234 | /// 235 | /// Right now, we don't support a method to *set* the playback_position, 236 | /// as naively setting this variable causes audible distortions. 237 | #[inline] 238 | pub fn playback_position(&self) -> f64 { 239 | self.sample_position.load(Ordering::Relaxed) as f64 / self.rate 240 | } 241 | 242 | /// Whether the signal has finished playing 243 | #[inline] 244 | pub fn is_finished(&self) -> bool { 245 | usize::try_from(self.sample_position.load(Ordering::Relaxed)) 246 | .map_or(false, |x| x >= self.samples) 247 | } 248 | } 249 | 250 | #[cfg(test)] 251 | mod tests { 252 | use super::*; 253 | use ::alloc::vec; 254 | 255 | fn assert_out(stream: &mut FramesSignal, interval: f32, expected: &[f32]) { 256 | let mut output = vec![0.0; expected.len()]; 257 | stream.sample(interval, &mut output); 258 | assert_eq!(&output, expected); 259 | } 260 | 261 | #[test] 262 | fn from_slice() { 263 | const DATA: &[f32] = &[1.0, 2.0, 3.0]; 264 | let frames = Frames::from_slice(1, DATA); 265 | assert_eq!(&frames[..], DATA); 266 | } 267 | 268 | #[test] 269 | fn sample() { 270 | let (_, mut signal) = FramesSignal::new(Frames::from_slice(1, &[1.0, 2.0, 3.0, 4.0]), -2.0); 271 | 272 | assert_out(&mut signal, 0.25, &[0.0, 0.0, 0.0, 0.0]); 273 | assert_out(&mut signal, 0.5, &[0.0, 0.5, 1.0]); 274 | assert_out(&mut signal, 1.0, &[1.5, 2.5, 3.5, 2.0, 0.0]); 275 | } 276 | 277 | #[test] 278 | fn playback_position() { 279 | let (control, mut signal) = 280 | FramesSignal::new(Frames::from_slice(1, &[1.0, 2.0, 3.0]), -2.0); 281 | 282 | // negatives are fine 283 | let init = control.playback_position(); 284 | assert_eq!(init, -2.0); 285 | assert!(!control.is_finished()); 286 | 287 | let mut buf = [0.0; 10]; 288 | 289 | // get back to positive 290 | signal.sample(0.2, &mut buf); 291 | assert_eq!(0.0, control.playback_position()); 292 | assert!(!control.is_finished()); 293 | 294 | signal.sample(0.1, &mut buf); 295 | assert_eq!(1.0, control.playback_position()); 296 | signal.sample(0.1, &mut buf); 297 | assert_eq!(2.0, control.playback_position()); 298 | signal.sample(0.2, &mut buf); 299 | assert!(control.is_finished()); 300 | assert_eq!(4.0, control.playback_position()); 301 | signal.sample(0.5, &mut buf); 302 | assert_eq!(9.0, control.playback_position()); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/gain.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | use core::sync::atomic::{AtomicU32, Ordering}; 3 | 4 | use crate::{frame, math::Float, Frame, Seek, Signal, Smoothed}; 5 | 6 | /// Amplifies a signal by a constant amount 7 | /// 8 | /// Unlike [`Gain`], this can implement [`Seek`]. 9 | pub struct FixedGain { 10 | gain: f32, 11 | inner: T, 12 | } 13 | 14 | impl FixedGain { 15 | /// Amplify `signal` by `db` decibels 16 | /// 17 | /// Decibels are perceptually linear. Negative values make the signal quieter. 18 | pub fn new(signal: T, db: f32) -> Self { 19 | Self { 20 | gain: 10.0f32.powf(db / 20.0), 21 | inner: signal, 22 | } 23 | } 24 | } 25 | 26 | impl Signal for FixedGain 27 | where 28 | T::Frame: Frame, 29 | { 30 | type Frame = T::Frame; 31 | 32 | fn sample(&mut self, interval: f32, out: &mut [T::Frame]) { 33 | self.inner.sample(interval, out); 34 | for x in out { 35 | *x = frame::scale(x, self.gain); 36 | } 37 | } 38 | 39 | fn is_finished(&self) -> bool { 40 | self.inner.is_finished() 41 | } 42 | } 43 | 44 | impl Seek for FixedGain 45 | where 46 | T::Frame: Frame, 47 | { 48 | fn seek(&mut self, seconds: f32) { 49 | self.inner.seek(seconds) 50 | } 51 | } 52 | 53 | /// Amplifies a signal dynamically 54 | /// 55 | /// To implement a volume control, place a gain combinator near the end of your pipeline where the 56 | /// input amplitude is initially in the range [0, 1] and pass decibels to [`GainControl::set_gain`], 57 | /// mapping the maximum volume to 0 decibels, and the minimum to e.g. -60. 58 | pub struct Gain { 59 | shared: Arc, 60 | gain: Smoothed, 61 | inner: T, 62 | } 63 | 64 | impl Gain { 65 | /// Apply dynamic amplification to `signal` 66 | pub fn new(signal: T) -> (GainControl, Self) { 67 | let signal = Gain { 68 | shared: Arc::new(AtomicU32::new(1.0f32.to_bits())), 69 | gain: Smoothed::new(1.0), 70 | inner: signal, 71 | }; 72 | let handle = GainControl(signal.shared.clone()); 73 | (handle, signal) 74 | } 75 | 76 | /// Set the initial amplification to `db` decibels 77 | /// 78 | /// Perceptually linear. Negative values make the signal quieter. 79 | /// 80 | /// Equivalent to `self.set_amplitude_ratio(10.0f32.powf(db / 20.0))`. 81 | pub fn set_gain(&mut self, db: f32) { 82 | self.set_amplitude_ratio(10.0f32.powf(db / 20.0)); 83 | } 84 | 85 | /// Set the initial amplitude scaling of the signal directly 86 | /// 87 | /// This is nonlinear in terms of both perception and power. Most users should prefer 88 | /// `set_gain`. Unlike `set_gain`, this method allows a signal to be completely zeroed out if 89 | /// needed, or even have its phase inverted with a negative factor. 90 | pub fn set_amplitude_ratio(&mut self, factor: f32) { 91 | self.shared.store(factor.to_bits(), Ordering::Relaxed); 92 | self.gain = Smoothed::new(factor); 93 | } 94 | } 95 | 96 | impl Signal for Gain 97 | where 98 | T::Frame: Frame, 99 | { 100 | type Frame = T::Frame; 101 | 102 | #[allow(clippy::float_cmp)] 103 | fn sample(&mut self, interval: f32, out: &mut [T::Frame]) { 104 | self.inner.sample(interval, out); 105 | let shared = f32::from_bits(self.shared.load(Ordering::Relaxed)); 106 | if self.gain.target() != &shared { 107 | self.gain.set(shared); 108 | } 109 | if self.gain.progress() == 1.0 { 110 | let g = self.gain.get(); 111 | if g != 1.0 { 112 | for x in out { 113 | *x = frame::scale(x, g); 114 | } 115 | } 116 | return; 117 | } 118 | for x in out { 119 | *x = frame::scale(x, self.gain.get()); 120 | self.gain.advance(interval / SMOOTHING_PERIOD); 121 | } 122 | } 123 | 124 | fn is_finished(&self) -> bool { 125 | self.inner.is_finished() 126 | } 127 | } 128 | 129 | /// Thread-safe control for a [`Gain`] filter 130 | pub struct GainControl(Arc); 131 | 132 | impl GainControl { 133 | /// Get the current amplification in decibels 134 | pub fn gain(&self) -> f32 { 135 | 20.0 * self.amplitude_ratio().log10() 136 | } 137 | 138 | /// Amplify the signal by `db` decibels 139 | /// 140 | /// Perceptually linear. Negative values make the signal quieter. 141 | /// 142 | /// Equivalent to `self.set_amplitude_ratio(10.0f32.powf(db / 20.0))`. 143 | pub fn set_gain(&mut self, db: f32) { 144 | self.set_amplitude_ratio(10.0f32.powf(db / 20.0)); 145 | } 146 | 147 | /// Get the current amplitude scaling factor 148 | pub fn amplitude_ratio(&self) -> f32 { 149 | f32::from_bits(self.0.load(Ordering::Relaxed)) 150 | } 151 | 152 | /// Scale the amplitude of the signal directly 153 | /// 154 | /// This is nonlinear in terms of both perception and power. Most users should prefer 155 | /// `set_gain`. Unlike `set_gain`, this method allows a signal to be completely zeroed out if 156 | /// needed, or even have its phase inverted with a negative factor. 157 | pub fn set_amplitude_ratio(&mut self, factor: f32) { 158 | self.0.store(factor.to_bits(), Ordering::Relaxed); 159 | } 160 | } 161 | 162 | /// Number of seconds over which to smooth a change in gain 163 | const SMOOTHING_PERIOD: f32 = 0.1; 164 | 165 | #[cfg(test)] 166 | mod tests { 167 | use super::*; 168 | use crate::Constant; 169 | 170 | #[test] 171 | fn smoothing() { 172 | let (mut c, mut s) = Gain::new(Constant(1.0)); 173 | let mut buf = [0.0; 6]; 174 | c.set_amplitude_ratio(5.0); 175 | s.sample(0.025, &mut buf); 176 | assert_eq!(buf, [1.0, 2.0, 3.0, 4.0, 5.0, 5.0]); 177 | s.sample(0.025, &mut buf); 178 | assert_eq!(buf, [5.0; 6]); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Lightweight game audio 2 | //! 3 | //! ```no_run 4 | //! let (mut scene_handle, mut scene) = oddio::SpatialScene::new(); 5 | //! 6 | //! // In audio callback: 7 | //! # let data = &mut [][..]; 8 | //! # let output_sample_rate = 44100; 9 | //! let out_frames = oddio::frame_stereo(data); 10 | //! oddio::run(&mut scene, output_sample_rate, out_frames); 11 | //! 12 | //! // In game logic: 13 | //! # let frames = []; 14 | //! # let sample_rate = 44100; 15 | //! # let position = [0.0, 0.0, 0.0].into(); 16 | //! # let velocity = [0.0, 0.0, 0.0].into(); 17 | //! let frames = oddio::FramesSignal::from(oddio::Frames::from_slice(sample_rate, &frames)); 18 | //! let mut handle = scene_handle 19 | //! .play(frames, oddio::SpatialOptions { position, velocity, ..Default::default() }); 20 | //! 21 | //! // When position/velocity changes: 22 | //! handle.set_motion(position, velocity, false); 23 | //! ``` 24 | //! 25 | //! To get started, review [the `examples` 26 | //! subdirectory](https://github.com/Ralith/oddio/tree/main/examples) in the crate source. 27 | //! 28 | //! Key primitives: 29 | //! - [`Frames`] stores static audio data, which can be played with a [`FramesSignal`] 30 | //! - [`Mixer`] allows multiple signals to be played concurrently and controlled during playback 31 | //! - [`SpatialScene`] is a mixer that spatializes its signals 32 | //! - [`run`] writes frames from a [`Signal`] into an output buffer 33 | 34 | #![allow(unused_imports)] 35 | #![warn(missing_docs)] 36 | #![no_std] 37 | 38 | extern crate alloc; 39 | #[cfg(not(feature = "no_std"))] 40 | extern crate std; 41 | 42 | mod adapt; 43 | mod constant; 44 | mod cycle; 45 | mod downmix; 46 | mod fader; 47 | mod frame; 48 | mod frames; 49 | mod gain; 50 | mod math; 51 | mod mixer; 52 | mod reinhard; 53 | mod ring; 54 | mod set; 55 | mod signal; 56 | mod sine; 57 | mod smooth; 58 | mod spatial; 59 | mod speed; 60 | mod spsc; 61 | mod stream; 62 | mod swap; 63 | mod tanh; 64 | 65 | pub use adapt::{Adapt, AdaptOptions}; 66 | pub use constant::Constant; 67 | pub use cycle::Cycle; 68 | pub use downmix::Downmix; 69 | pub use fader::{Fader, FaderControl}; 70 | pub use frame::Frame; 71 | pub use frames::*; 72 | pub use gain::{FixedGain, Gain, GainControl}; 73 | pub use mixer::*; 74 | pub use reinhard::Reinhard; 75 | use set::*; 76 | pub use signal::*; 77 | pub use sine::*; 78 | pub use smooth::{Interpolate, Smoothed}; 79 | pub use spatial::*; 80 | pub use speed::{Speed, SpeedControl}; 81 | pub use stream::{Stream, StreamControl}; 82 | pub use tanh::Tanh; 83 | 84 | /// Unitless instantaneous sound wave amplitude measurement 85 | pub type Sample = f32; 86 | 87 | /// Populate `out` with frames from `signal` at `sample_rate` 88 | /// 89 | /// Convenience wrapper for [`Signal::sample`]. 90 | pub fn run(signal: &mut S, sample_rate: u32, out: &mut [S::Frame]) { 91 | let interval = 1.0 / sample_rate as f32; 92 | signal.sample(interval, out); 93 | } 94 | 95 | /// Convert a slice of interleaved stereo data into a slice of stereo frames 96 | /// 97 | /// Useful for adapting output buffers obtained externally. 98 | pub fn frame_stereo(xs: &mut [Sample]) -> &mut [[Sample; 2]] { 99 | unsafe { core::slice::from_raw_parts_mut(xs.as_mut_ptr() as _, xs.len() / 2) } 100 | } 101 | 102 | fn flatten_stereo(xs: &mut [[Sample; 2]]) -> &mut [Sample] { 103 | unsafe { core::slice::from_raw_parts_mut(xs.as_mut_ptr() as _, xs.len() * 2) } 104 | } 105 | -------------------------------------------------------------------------------- /src/math/libm.rs: -------------------------------------------------------------------------------- 1 | use crate::math::Float; 2 | 3 | impl Float for f32 { 4 | fn abs(self) -> Self { 5 | libm::fabsf(self) 6 | } 7 | 8 | fn sqrt(self) -> Self { 9 | libm::sqrtf(self) 10 | } 11 | 12 | fn exp(self) -> Self { 13 | libm::expf(self) 14 | } 15 | 16 | fn ceil(self) -> Self { 17 | libm::ceilf(self) 18 | } 19 | 20 | fn trunc(self) -> Self { 21 | libm::truncf(self) 22 | } 23 | 24 | fn fract(self) -> Self { 25 | self - self.trunc() 26 | } 27 | 28 | fn log10(self) -> Self { 29 | libm::log10f(self) 30 | } 31 | 32 | fn powf(self, n: Self) -> Self { 33 | libm::powf(self, n) 34 | } 35 | 36 | fn powi(mut self, mut rhs: i32) -> Self { 37 | let mut r = 1.0; 38 | let invert = if rhs < 0 { 39 | rhs *= -1; 40 | true 41 | } else { 42 | false 43 | }; 44 | loop { 45 | if rhs % 2 == 1 { 46 | r *= self; 47 | } 48 | rhs /= 2; 49 | if rhs == 0 { 50 | break; 51 | } 52 | self *= self; 53 | } 54 | if invert { 55 | 1.0 / r 56 | } else { 57 | r 58 | } 59 | } 60 | 61 | fn sin(self) -> Self { 62 | libm::sinf(self) 63 | } 64 | 65 | fn rem_euclid(self, rhs: Self) -> Self { 66 | let r = self % rhs; 67 | if r < 0.0 { 68 | r + rhs.abs() 69 | } else { 70 | r 71 | } 72 | } 73 | 74 | fn tanh(self) -> Self { 75 | libm::tanhf(self) 76 | } 77 | } 78 | 79 | impl Float for f64 { 80 | fn abs(self) -> Self { 81 | libm::fabs(self) 82 | } 83 | 84 | fn sqrt(self) -> Self { 85 | libm::sqrt(self) 86 | } 87 | 88 | fn exp(self) -> Self { 89 | libm::exp(self) 90 | } 91 | 92 | fn ceil(self) -> Self { 93 | libm::ceil(self) 94 | } 95 | 96 | fn trunc(self) -> Self { 97 | libm::trunc(self) 98 | } 99 | 100 | fn fract(self) -> Self { 101 | self - self.trunc() 102 | } 103 | 104 | fn log10(self) -> Self { 105 | libm::log10(self) 106 | } 107 | 108 | fn powf(self, n: Self) -> Self { 109 | libm::pow(self, n) 110 | } 111 | 112 | fn powi(mut self, mut rhs: i32) -> Self { 113 | let mut r = 1.0; 114 | let invert = if rhs < 0 { 115 | rhs *= -1; 116 | true 117 | } else { 118 | false 119 | }; 120 | loop { 121 | if rhs % 2 == 1 { 122 | r *= self; 123 | } 124 | rhs /= 2; 125 | if rhs == 0 { 126 | break; 127 | } 128 | self *= self; 129 | } 130 | if invert { 131 | 1.0 / r 132 | } else { 133 | r 134 | } 135 | } 136 | 137 | fn sin(self) -> Self { 138 | libm::sin(self) 139 | } 140 | 141 | fn rem_euclid(self, rhs: Self) -> Self { 142 | let r = self % rhs; 143 | if r < 0.0 { 144 | r + rhs.abs() 145 | } else { 146 | r 147 | } 148 | } 149 | 150 | fn tanh(self) -> Self { 151 | libm::tanh(self) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/math/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "no_std"))] 2 | mod std; 3 | 4 | #[cfg(feature = "no_std")] 5 | mod libm; 6 | 7 | pub(crate) trait Float { 8 | fn abs(self) -> Self; 9 | 10 | fn sqrt(self) -> Self; 11 | 12 | fn exp(self) -> Self; 13 | 14 | fn ceil(self) -> Self; 15 | 16 | fn trunc(self) -> Self; 17 | 18 | fn fract(self) -> Self; 19 | 20 | fn log10(self) -> Self; 21 | 22 | fn powf(self, n: Self) -> Self; 23 | 24 | fn powi(self, n: i32) -> Self; 25 | 26 | fn sin(self) -> Self; 27 | 28 | fn rem_euclid(self, rhs: Self) -> Self; 29 | 30 | fn tanh(self) -> Self; 31 | } 32 | 33 | pub fn norm(x: mint::Vector3) -> f32 { 34 | x.as_ref().iter().map(|&x| x.powi(2)).sum::().sqrt() 35 | } 36 | 37 | pub fn dot(x: mint::Vector3, y: mint::Vector3) -> f32 { 38 | x.as_ref() 39 | .iter() 40 | .zip(y.as_ref().iter()) 41 | .map(|(&x, &y)| x * y) 42 | .sum::() 43 | } 44 | 45 | pub fn scale(v: mint::Vector3, f: f32) -> mint::Vector3 { 46 | [v.x * f, v.y * f, v.z * f].into() 47 | } 48 | 49 | pub fn sub(a: mint::Point3, b: mint::Point3) -> mint::Vector3 { 50 | [a.x - b.x, a.y - b.y, a.z - b.z].into() 51 | } 52 | 53 | pub fn add(a: mint::Point3, b: mint::Vector3) -> mint::Point3 { 54 | [a.x + b.x, a.y + b.y, a.z + b.z].into() 55 | } 56 | 57 | pub fn mix(a: mint::Point3, b: mint::Point3, r: f32) -> mint::Point3 { 58 | let ir = 1.0 - r; 59 | [ir * a.x + r * b.x, ir * a.y + r * b.y, ir * a.z + r * b.z].into() 60 | } 61 | 62 | pub fn invert_quat(q: &mint::Quaternion) -> mint::Quaternion { 63 | mint::Quaternion { 64 | s: q.s, 65 | v: [-q.v.x, -q.v.y, -q.v.z].into(), 66 | } 67 | } 68 | 69 | fn quat_mul(q: &mint::Quaternion, r: &mint::Quaternion) -> mint::Quaternion { 70 | mint::Quaternion { 71 | s: q.s * r.s - q.v.x * r.v.x - q.v.y * r.v.y - q.v.z * r.v.z, 72 | v: [ 73 | q.s * r.v.x + q.v.x * r.s + q.v.y * r.v.z - q.v.z * r.v.y, 74 | q.s * r.v.y - q.v.x * r.v.z + q.v.y * r.s + q.v.z * r.v.x, 75 | q.s * r.v.z + q.v.x * r.v.y - q.v.y * r.v.x + q.v.z * r.s, 76 | ] 77 | .into(), 78 | } 79 | } 80 | 81 | pub fn rotate(rot: &mint::Quaternion, p: &mint::Point3) -> mint::Point3 { 82 | quat_mul( 83 | rot, 84 | &quat_mul( 85 | &mint::Quaternion { 86 | s: 0.0, 87 | v: (*p).into(), 88 | }, 89 | &invert_quat(rot), 90 | ), 91 | ) 92 | .v 93 | .into() 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::*; 99 | use core::f32::consts::PI; 100 | 101 | #[test] 102 | fn rotate_x() { 103 | let p = mint::Point3::from([0.0, 0.0, -1.0]); 104 | let q = axis_angle([1.0, 0.0, 0.0].into(), PI / 2.0); 105 | let r = rotate(&q, &p); 106 | assert_eq!(r.x, 0.0); 107 | assert!((r.y - 1.0).abs() < 1e-3); 108 | assert_eq!(r.z, 0.0); 109 | } 110 | 111 | #[test] 112 | fn rotate_y() { 113 | let p = mint::Point3::from([1.0, 0.0, 0.0]); 114 | let q = axis_angle([0.0, 1.0, 0.0].into(), PI / 2.0); 115 | let r = rotate(&q, &p); 116 | assert_eq!(r.x, 0.0); 117 | assert_eq!(r.y, 0.0); 118 | assert!((r.z + 1.0).abs() < 1e-3); 119 | } 120 | 121 | #[test] 122 | fn rotate_z() { 123 | let p = mint::Point3::from([0.0, 1.0, 0.0]); 124 | let q = axis_angle([0.0, 0.0, 1.0].into(), PI / 2.0); 125 | let r = rotate(&q, &p); 126 | assert_eq!(r.y, 0.0); 127 | assert!((r.x + 1.0).abs() < 1e-3); 128 | assert_eq!(r.z, 0.0); 129 | } 130 | 131 | fn axis_angle(axis: mint::Vector3, angle: f32) -> mint::Quaternion { 132 | let half = angle * 0.5; 133 | mint::Quaternion { 134 | s: half.cos(), 135 | v: [ 136 | axis.x * half.sin(), 137 | axis.y * half.sin(), 138 | axis.z * half.sin(), 139 | ] 140 | .into(), 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/math/std.rs: -------------------------------------------------------------------------------- 1 | use crate::math::Float; 2 | 3 | impl Float for f32 { 4 | fn abs(self) -> Self { 5 | Self::abs(self) 6 | } 7 | 8 | fn sqrt(self) -> Self { 9 | Self::sqrt(self) 10 | } 11 | 12 | fn exp(self) -> Self { 13 | Self::exp(self) 14 | } 15 | 16 | fn ceil(self) -> Self { 17 | Self::ceil(self) 18 | } 19 | 20 | fn trunc(self) -> Self { 21 | Self::trunc(self) 22 | } 23 | 24 | fn fract(self) -> Self { 25 | Self::fract(self) 26 | } 27 | 28 | fn log10(self) -> Self { 29 | Self::log10(self) 30 | } 31 | 32 | fn powf(self, n: Self) -> Self { 33 | Self::powf(self, n) 34 | } 35 | 36 | fn powi(self, n: i32) -> Self { 37 | Self::powi(self, n) 38 | } 39 | 40 | fn sin(self) -> Self { 41 | Self::sin(self) 42 | } 43 | 44 | fn rem_euclid(self, rhs: Self) -> Self { 45 | Self::rem_euclid(self, rhs) 46 | } 47 | 48 | fn tanh(self) -> Self { 49 | Self::tanh(self) 50 | } 51 | } 52 | 53 | impl Float for f64 { 54 | fn abs(self) -> Self { 55 | Self::abs(self) 56 | } 57 | 58 | fn sqrt(self) -> Self { 59 | Self::sqrt(self) 60 | } 61 | 62 | fn exp(self) -> Self { 63 | Self::exp(self) 64 | } 65 | 66 | fn ceil(self) -> Self { 67 | Self::ceil(self) 68 | } 69 | 70 | fn trunc(self) -> Self { 71 | Self::trunc(self) 72 | } 73 | 74 | fn fract(self) -> Self { 75 | Self::fract(self) 76 | } 77 | 78 | fn log10(self) -> Self { 79 | Self::log10(self) 80 | } 81 | 82 | fn powf(self, n: Self) -> Self { 83 | Self::powf(self, n) 84 | } 85 | 86 | fn powi(self, n: i32) -> Self { 87 | Self::powi(self, n) 88 | } 89 | 90 | fn sin(self) -> Self { 91 | Self::sin(self) 92 | } 93 | 94 | fn rem_euclid(self, rhs: Self) -> Self { 95 | Self::rem_euclid(self, rhs) 96 | } 97 | 98 | fn tanh(self) -> Self { 99 | Self::tanh(self) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/mixer.rs: -------------------------------------------------------------------------------- 1 | use alloc::{boxed::Box, sync::Arc, vec}; 2 | use core::sync::atomic::{AtomicBool, Ordering}; 3 | 4 | use crate::{frame, set, Frame, Set, SetHandle, Signal}; 5 | 6 | /// Handle for controlling a [`Mixer`] from another thread 7 | pub struct MixerControl(SetHandle>); 8 | 9 | impl MixerControl { 10 | /// Begin playing `signal`, returning a handle that can be used to pause or stop it and access 11 | /// other controls 12 | /// 13 | /// Finished signals are automatically stopped, and their storage reused for future `play` 14 | /// calls. 15 | /// 16 | /// The type of signal given determines what additional controls can be used. See the 17 | /// examples for a detailed guide. 18 | pub fn play(&mut self, signal: S) -> Mixed 19 | where 20 | S: Signal + Send + 'static, 21 | { 22 | let signal = Box::new(MixedSignal::new(signal)); 23 | let control = Mixed(signal.stop.clone()); 24 | self.0.insert(signal); 25 | control 26 | } 27 | } 28 | 29 | /// Handle to a signal playing in a [`Mixer`] 30 | pub struct Mixed(Arc); 31 | 32 | impl Mixed { 33 | /// Immediately halt playback of the associated signal by its [`Mixer`] 34 | pub fn stop(&mut self) { 35 | self.0.store(true, Ordering::Relaxed); 36 | } 37 | 38 | /// Whether the signal's playback 39 | /// 40 | /// Set by both `is_stopped` and signals naturally finishing. 41 | pub fn is_stopped(&self) -> bool { 42 | self.0.load(Ordering::Relaxed) 43 | } 44 | } 45 | 46 | struct MixedSignal { 47 | stop: Arc, 48 | inner: T, 49 | } 50 | 51 | impl MixedSignal { 52 | fn new(signal: T) -> Self { 53 | Self { 54 | stop: Arc::new(AtomicBool::new(false)), 55 | inner: signal, 56 | } 57 | } 58 | } 59 | 60 | /// A [`Signal`] that mixes a dynamic set of [`Signal`]s 61 | pub struct Mixer { 62 | recv: Inner, 63 | } 64 | 65 | impl Mixer 66 | where 67 | T: Frame + Clone, 68 | { 69 | /// Construct a new mixer 70 | pub fn new() -> (MixerControl, Self) { 71 | let (handle, set) = set(); 72 | ( 73 | MixerControl(handle), 74 | Self { 75 | recv: Inner { 76 | set, 77 | buffer: vec![T::ZERO; 1024].into(), 78 | }, 79 | }, 80 | ) 81 | } 82 | } 83 | 84 | struct Inner { 85 | set: Set>, 86 | buffer: Box<[T]>, 87 | } 88 | 89 | impl Signal for Mixer { 90 | type Frame = T; 91 | 92 | fn sample(&mut self, interval: f32, out: &mut [T]) { 93 | let this = &mut self.recv; 94 | this.set.update(); 95 | 96 | for o in out.iter_mut() { 97 | *o = T::ZERO; 98 | } 99 | 100 | for i in (0..this.set.len()).rev() { 101 | let signal = &mut this.set[i]; 102 | if signal.stop.load(Ordering::Relaxed) || signal.inner.is_finished() { 103 | signal.stop.store(true, Ordering::Relaxed); 104 | this.set.remove(i); 105 | continue; 106 | } 107 | 108 | // Sample into `buffer`, then mix into `out` 109 | let mut iter = out.iter_mut(); 110 | while iter.len() > 0 { 111 | let n = iter.len().min(this.buffer.len()); 112 | let staging = &mut this.buffer[..n]; 113 | signal.inner.sample(interval, staging); 114 | for (staged, o) in staging.iter().zip(&mut iter) { 115 | *o = frame::mix(o, staged); 116 | } 117 | } 118 | } 119 | } 120 | } 121 | 122 | type ErasedSignal = Box>>; 123 | 124 | #[cfg(test)] 125 | mod tests { 126 | use super::*; 127 | use crate::{Frames, FramesSignal}; 128 | 129 | #[test] 130 | fn is_stopped() { 131 | let (mut mixer_control, mut mixer) = Mixer::new(); 132 | let (_, signal) = FramesSignal::new(Frames::from_slice(1, &[0.0, 0.0]), 0.0); 133 | let handle = mixer_control.play(signal); 134 | assert!(!handle.is_stopped()); 135 | 136 | let mut out = [0.0]; 137 | 138 | mixer.sample(0.6, &mut out); 139 | assert!(!handle.is_stopped()); 140 | 141 | mixer.sample(0.6, &mut out); 142 | // Signal is finished, but we won't actually notice until the next scan 143 | assert!(!handle.is_stopped()); 144 | 145 | mixer.sample(0.0, &mut out); 146 | assert!(handle.is_stopped()); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/reinhard.rs: -------------------------------------------------------------------------------- 1 | use crate::{math::Float, Frame, Seek, Signal}; 2 | 3 | /// Smoothly maps a signal of any range into (-1, 1) 4 | /// 5 | /// For each input sample `x`, outputs `x / (1 + |x|)`. 6 | /// 7 | /// When many signals are combined with a [`Mixer`](crate::Mixer) or [`Spatial`](crate::Spatial), or 8 | /// when spatial signals are very near by, audio can get arbitrarily loud. Because surprisingly loud 9 | /// audio can be disruptive and even damaging, it can be useful to limit the output range, but 10 | /// simple clamping introduces audible artifacts. 11 | /// 12 | /// See also [`Tanh`](crate::Tanh), which distorts quiet sounds less, and loud sounds more. 13 | pub struct Reinhard(T); 14 | 15 | impl Reinhard { 16 | /// Apply the Reinhard operator to `signal` 17 | pub fn new(signal: T) -> Self { 18 | Self(signal) 19 | } 20 | } 21 | 22 | impl Signal for Reinhard 23 | where 24 | T::Frame: Frame, 25 | { 26 | type Frame = T::Frame; 27 | 28 | fn sample(&mut self, interval: f32, out: &mut [T::Frame]) { 29 | self.0.sample(interval, out); 30 | for x in out { 31 | for channel in x.channels_mut() { 32 | *channel /= 1.0 + channel.abs(); 33 | } 34 | } 35 | } 36 | 37 | fn is_finished(&self) -> bool { 38 | self.0.is_finished() 39 | } 40 | } 41 | 42 | impl Seek for Reinhard 43 | where 44 | T: Signal + Seek, 45 | T::Frame: Frame, 46 | { 47 | fn seek(&mut self, seconds: f32) { 48 | self.0.seek(seconds); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/ring.rs: -------------------------------------------------------------------------------- 1 | use crate::{frame, math::Float, Sample, Signal}; 2 | use alloc::{boxed::Box, vec}; 3 | 4 | pub struct Ring { 5 | buffer: Box<[Sample]>, 6 | write: f32, 7 | } 8 | 9 | impl Ring { 10 | pub fn new(capacity: usize) -> Self { 11 | Self { 12 | buffer: vec![0.0; capacity].into(), 13 | write: 0.0, 14 | } 15 | } 16 | 17 | /// Fill buffer from `signal` 18 | pub fn write + ?Sized>( 19 | &mut self, 20 | signal: &mut S, 21 | rate: u32, 22 | dt: f32, 23 | ) { 24 | debug_assert!( 25 | dt * rate as f32 <= self.buffer.len() as f32, 26 | "output range exceeds capacity" 27 | ); 28 | let end = (self.write + dt * rate as f32) % self.buffer.len() as f32; 29 | 30 | let start_idx = self.write.ceil() as usize; 31 | let end_idx = end.ceil() as usize; 32 | let interval = 1.0 / rate as f32; 33 | if end_idx > start_idx { 34 | signal.sample(interval, &mut self.buffer[start_idx..end_idx]); 35 | } else { 36 | signal.sample(interval, &mut self.buffer[start_idx..]); 37 | signal.sample(interval, &mut self.buffer[..end_idx]); 38 | } 39 | 40 | self.write = end; 41 | } 42 | 43 | /// Advance write cursor by `dt` given internal sample rate `rate`, as if writing a `Signal` 44 | /// that produces only zeroes 45 | pub fn delay(&mut self, rate: u32, dt: f32) { 46 | self.write = (self.write + rate as f32 * dt) % self.buffer.len() as f32; 47 | } 48 | 49 | /// Get the recorded signal at a certain range, relative to the *write* cursor. `t` must be 50 | /// negative. 51 | pub fn sample(&self, rate: u32, t: f32, interval: f32, out: &mut [Sample]) { 52 | debug_assert!(t < 0.0, "samples must lie in the past"); 53 | debug_assert!( 54 | ((t * rate as f32).abs().ceil() as usize) < self.buffer.len(), 55 | "samples must lie less than a buffer period in the past" 56 | ); 57 | let mut offset = (self.write + t * rate as f32).rem_euclid(self.buffer.len() as f32); 58 | let ds = interval * rate as f32; 59 | for o in out.iter_mut() { 60 | let trunc = unsafe { offset.to_int_unchecked::() }; 61 | let fract = offset - trunc as f32; 62 | let x = trunc; 63 | let (a, b) = if x < self.buffer.len() - 1 { 64 | (self.buffer[x], self.buffer[x + 1]) 65 | } else if x < self.buffer.len() { 66 | (self.buffer[x], self.buffer[0]) 67 | } else { 68 | let x = x % self.buffer.len(); 69 | offset = x as f32 + fract; 70 | if x < self.buffer.len() - 1 { 71 | (self.buffer[x], self.buffer[x + 1]) 72 | } else { 73 | (self.buffer[x], self.buffer[0]) 74 | } 75 | }; 76 | *o = frame::lerp(&a, &b, fract); 77 | offset += ds; 78 | } 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use super::*; 85 | 86 | struct TimeSignal(f32); 87 | 88 | impl Signal for TimeSignal { 89 | type Frame = Sample; 90 | fn sample(&mut self, interval: f32, out: &mut [Sample]) { 91 | for x in out { 92 | let t = self.0; 93 | *x = t as f32; 94 | self.0 = t + interval; 95 | } 96 | } 97 | } 98 | 99 | fn assert_out(r: &mut Ring, rate: u32, t: f32, interval: f32, expected: &[f32]) { 100 | let mut output = vec![0.0; expected.len()]; 101 | r.sample(rate, t, interval, &mut output); 102 | assert_eq!(&output, expected); 103 | } 104 | 105 | #[test] 106 | fn fill() { 107 | let mut r = Ring::new(4); 108 | let mut s = TimeSignal(1.0); 109 | 110 | r.write(&mut s, 1, 1.0); 111 | assert_eq!(r.write, 1.0); 112 | assert_eq!(r.buffer[..], [1.0, 0.0, 0.0, 0.0]); 113 | 114 | r.write(&mut s, 1, 2.0); 115 | assert_eq!(r.write, 3.0); 116 | assert_eq!(r.buffer[..], [1.0, 2.0, 3.0, 0.0]); 117 | 118 | assert_out(&mut r, 1, -1.5, 1.0, &[2.5, 1.5]); 119 | assert_out(&mut r, 1, -1.5, 0.25, &[2.5, 2.75, 3.0, 2.25]); 120 | } 121 | 122 | #[test] 123 | fn wrap() { 124 | let mut r = Ring::new(4); 125 | let mut s = TimeSignal(1.0); 126 | 127 | r.write(&mut s, 1, 3.0); 128 | assert_eq!(r.buffer[..], [1.0, 2.0, 3.0, 0.0]); 129 | 130 | r.write(&mut s, 1, 3.0); 131 | assert_eq!(r.buffer[..], [5.0, 6.0, 3.0, 4.0]); 132 | 133 | assert_out(&mut r, 1, -2.75, 0.5, &[4.25, 4.75, 5.25, 5.75, 5.25, 3.75]); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/set.rs: -------------------------------------------------------------------------------- 1 | use alloc::{collections::vec_deque::VecDeque, vec::Vec}; 2 | use core::{ 3 | cell::UnsafeCell, 4 | mem, 5 | ops::{Deref, DerefMut}, 6 | }; 7 | 8 | use crate::spsc; 9 | 10 | /// Build a set 11 | pub fn set() -> (SetHandle, Set) { 12 | let (msg_send, msg_recv) = spsc::channel(INITIAL_CHANNEL_CAPACITY); 13 | let (free_send, free_recv) = spsc::channel(INITIAL_SIGNALS_CAPACITY); 14 | let remote = SetHandle { 15 | sender: msg_send, 16 | free: free_recv, 17 | next_free: VecDeque::new(), 18 | old_senders: VecDeque::new(), 19 | signal_capacity: INITIAL_SIGNALS_CAPACITY, 20 | active_signals: 0, 21 | }; 22 | let mixer = Set(UnsafeCell::new(SetInner { 23 | recv: msg_recv, 24 | free: free_send, 25 | signals: SignalTable::with_capacity(remote.signal_capacity), 26 | })); 27 | (remote, mixer) 28 | } 29 | 30 | #[cfg(not(miri))] 31 | const INITIAL_CHANNEL_CAPACITY: usize = 127; // because the ring buffer wastes a slot 32 | #[cfg(not(miri))] 33 | const INITIAL_SIGNALS_CAPACITY: usize = 128; 34 | 35 | // Smaller versions for the sake of runtime 36 | #[cfg(miri)] 37 | const INITIAL_CHANNEL_CAPACITY: usize = 3; 38 | #[cfg(miri)] 39 | const INITIAL_SIGNALS_CAPACITY: usize = 4; 40 | 41 | /// Handle for adding signals to a [`Set`] from another thread 42 | /// 43 | /// Constructed by calling [`set`]. 44 | pub struct SetHandle { 45 | sender: spsc::Sender>, 46 | free: spsc::Receiver>, 47 | next_free: VecDeque>>, 48 | old_senders: VecDeque>>, 49 | signal_capacity: usize, 50 | active_signals: usize, 51 | } 52 | 53 | impl SetHandle { 54 | /// Add `signal` to the set 55 | pub fn insert(&mut self, signal: T) { 56 | self.gc(); 57 | if self.active_signals == self.signal_capacity { 58 | self.signal_capacity *= 2; 59 | let signals = SignalTable::with_capacity(self.signal_capacity); 60 | let (free_send, free_recv) = spsc::channel(self.signal_capacity + 1); // save a slot for table free msg 61 | self.send(Msg::ReallocSignals(signals, free_send)); 62 | self.next_free.push_back(free_recv); 63 | } 64 | self.send(Msg::Insert(signal)); 65 | self.active_signals += 1; 66 | } 67 | 68 | /// Send a message, allocating more storage to do so if necessary 69 | fn send(&mut self, msg: Msg) { 70 | if let Err(msg) = self.sender.send(msg, 1) { 71 | // Channel would become full; allocate a new one 72 | let (mut send, recv) = spsc::channel(2 * self.sender.capacity() + 1); 73 | self.sender 74 | .send(Msg::ReallocChannel(recv), 0) 75 | .unwrap_or_else(|_| unreachable!("a space was reserved for this message")); 76 | send.send(msg, 0) 77 | .unwrap_or_else(|_| unreachable!("newly allocated nonzero-capacity queue")); 78 | let old = mem::replace(&mut self.sender, send); 79 | self.old_senders.push_back(old); 80 | } 81 | } 82 | 83 | // Free old signals 84 | fn gc(&mut self) { 85 | while self 86 | .old_senders 87 | .front_mut() 88 | .map_or(false, |x| x.is_closed()) 89 | { 90 | self.old_senders.pop_front(); 91 | } 92 | loop { 93 | self.gc_inner(); 94 | if !self.free.is_closed() || self.sender.is_closed() { 95 | // If the free queue isn't closed, it may get more data in the future. If the 96 | // message queue is closed, then the mixer's gone and none of this 97 | // matters. Otherwise, we must be switching to a new free queue. 98 | break; 99 | } 100 | // Drain the queue again to guard against data added between the first run and the 101 | // channel becoming closed 102 | self.gc_inner(); 103 | self.free = self 104 | .next_free 105 | .pop_front() 106 | .expect("free channel closed without replacement"); 107 | } 108 | } 109 | 110 | fn gc_inner(&mut self) { 111 | self.free.update(); 112 | for x in self.free.drain() { 113 | match x { 114 | Free::Signal(_) => { 115 | self.active_signals -= 1; 116 | } 117 | Free::Table(x) => { 118 | debug_assert_eq!(x.len(), 0, "signals were transferred to new table"); 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | unsafe impl Send for SetHandle {} 126 | unsafe impl Sync for SetHandle {} 127 | 128 | /// A collection of heterogeneous [`Signal`]s, controlled from another thread by a [`SetHandle`] 129 | /// 130 | /// Constructed by calling [`set`]. A useful primitive for building aggregate [`Signal`]s like 131 | /// [`Mixer`](crate::Mixer). 132 | pub struct Set(UnsafeCell>); 133 | 134 | struct SetInner { 135 | recv: spsc::Receiver>, 136 | free: spsc::Sender>, 137 | signals: SignalTable, 138 | } 139 | 140 | impl SetInner { 141 | fn drain_msgs(&mut self) { 142 | self.recv.update(); 143 | while let Some(msg) = self.recv.pop() { 144 | use Msg::*; 145 | match msg { 146 | ReallocChannel(recv) => { 147 | self.recv = recv; 148 | self.recv.update(); 149 | } 150 | ReallocSignals(signals, free) => { 151 | // Move all existing slots into the new storage 152 | let mut old = mem::replace(&mut self.signals, signals); 153 | self.signals.append(&mut old); 154 | self.free = free; 155 | self.free 156 | .send(Free::Table(old), 0) 157 | .unwrap_or_else(|_| unreachable!("fresh channel must have capacity")); 158 | } 159 | Insert(signal) => { 160 | assert!( 161 | self.signals.len() < self.signals.capacity(), 162 | "mixer never does its own realloc" 163 | ); 164 | self.signals.push(signal); 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | unsafe impl Send for Set {} 172 | 173 | impl Set { 174 | /// Process changes to the set 175 | pub fn update(&mut self) { 176 | let this = unsafe { &mut (*self.0.get()) }; 177 | this.drain_msgs(); 178 | } 179 | 180 | /// Remove `index` from the set 181 | /// 182 | /// The last element in the set replaces it, as in `Vec::swap_remove`. 183 | pub fn remove(&mut self, index: usize) { 184 | let this = unsafe { &mut (*self.0.get()) }; 185 | this.free 186 | .send(Free::Signal(this.signals.swap_remove(index)), 0) 187 | .unwrap_or_else(|_| unreachable!("free queue has capacity for every signal")); 188 | } 189 | } 190 | 191 | impl Deref for Set { 192 | type Target = [T]; 193 | fn deref(&self) -> &[T] { 194 | let this = unsafe { &mut (*self.0.get()) }; 195 | &this.signals 196 | } 197 | } 198 | 199 | impl DerefMut for Set { 200 | fn deref_mut(&mut self) -> &mut [T] { 201 | let this = unsafe { &mut (*self.0.get()) }; 202 | &mut this.signals 203 | } 204 | } 205 | 206 | type SignalTable = Vec; 207 | 208 | enum Msg { 209 | ReallocChannel(spsc::Receiver>), 210 | ReallocSignals(SignalTable, spsc::Sender>), 211 | Insert(T), 212 | } 213 | 214 | enum Free { 215 | Table(Vec), 216 | Signal(T), 217 | } 218 | 219 | #[cfg(test)] 220 | mod tests { 221 | use super::*; 222 | use crate::{Frames, FramesSignal}; 223 | 224 | const RATE: u32 = 10; 225 | 226 | #[test] 227 | fn realloc_signals() { 228 | let (mut remote, mut s) = set(); 229 | let frames = Frames::from_slice(RATE, &[[0.0; 2]; RATE as usize]); 230 | for i in 1..=(INITIAL_SIGNALS_CAPACITY + 2) { 231 | remote.insert(FramesSignal::from(frames.clone())); 232 | s.update(); 233 | assert_eq!(unsafe { (*s.0.get()).signals.len() }, i); 234 | } 235 | } 236 | 237 | #[test] 238 | fn realloc_channel() { 239 | let (mut remote, mut s) = set(); 240 | let frames = Frames::from_slice(RATE, &[[0.0; 2]; RATE as usize]); 241 | for _ in 0..(INITIAL_CHANNEL_CAPACITY + 2) { 242 | remote.insert(FramesSignal::from(frames.clone())); 243 | } 244 | assert_eq!(remote.sender.capacity(), 1 + 2 * INITIAL_CHANNEL_CAPACITY); 245 | assert_eq!(unsafe { (*s.0.get()).signals.len() }, 0); 246 | s.update(); 247 | assert_eq!( 248 | unsafe { (*s.0.get()).signals.len() }, 249 | INITIAL_CHANNEL_CAPACITY + 2 250 | ); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/signal.rs: -------------------------------------------------------------------------------- 1 | use crate::{flatten_stereo, Sample}; 2 | 3 | /// An audio signal 4 | /// 5 | /// This interface is intended for use only from the code actually generating an audio signal for 6 | /// output. For example, in a real-time application, `Signal`s will typically be owned by the 7 | /// real-time audio thread and not directly accessible from elsewhere. Access to an active signal 8 | /// for other purposes (e.g. to adjust parameters) is generally through a control handle returned by 9 | /// its constructor. 10 | /// 11 | /// To ensure glitch-free audio, none of these methods should perform any operation that may 12 | /// wait. This includes locks, memory allocation or freeing, and even unbounded compare-and-swap 13 | /// loops. 14 | pub trait Signal { 15 | /// Type of frames yielded by `sample`, e.g. `[Sample; 2]` for stereo 16 | type Frame; 17 | 18 | /// Sample frames separated by `interval` seconds each 19 | fn sample(&mut self, interval: f32, out: &mut [Self::Frame]); 20 | 21 | /// Whether future calls to `sample` with a nonnegative `interval` will only produce zeroes 22 | /// 23 | /// Commonly used to determine when a `Signal` can be discarded. 24 | #[inline] 25 | fn is_finished(&self) -> bool { 26 | false 27 | } 28 | } 29 | 30 | impl Signal for alloc::boxed::Box { 31 | type Frame = T::Frame; 32 | 33 | fn sample(&mut self, interval: f32, out: &mut [T::Frame]) { 34 | (**self).sample(interval, out); 35 | } 36 | 37 | #[inline] 38 | fn is_finished(&self) -> bool { 39 | (**self).is_finished() 40 | } 41 | } 42 | 43 | /// Audio signals which support seeking 44 | /// 45 | /// Should only be implemented for signals which are defined deterministically in terms of absolute 46 | /// sample time. Nondeterministic or stateful behavior may produce audible glitches in downstream 47 | /// code. 48 | pub trait Seek: Signal { 49 | /// Shift the starting point of the next `sample` call by `seconds` 50 | fn seek(&mut self, seconds: f32); 51 | } 52 | 53 | impl Seek for alloc::boxed::Box { 54 | #[inline] 55 | fn seek(&mut self, seconds: f32) { 56 | (**self).seek(seconds); 57 | } 58 | } 59 | 60 | /// Adapts a mono signal to output stereo by duplicating its output 61 | pub struct MonoToStereo(T); 62 | 63 | impl MonoToStereo { 64 | /// Adapt `signal` from mono to stereo 65 | pub fn new(signal: T) -> Self { 66 | Self(signal) 67 | } 68 | } 69 | 70 | impl> Signal for MonoToStereo { 71 | type Frame = [Sample; 2]; 72 | 73 | fn sample(&mut self, interval: f32, out: &mut [[Sample; 2]]) { 74 | let n = out.len(); 75 | let buf = flatten_stereo(out); 76 | self.0.sample(interval, &mut buf[..n]); 77 | for i in (0..buf.len()).rev() { 78 | buf[i] = buf[i / 2]; 79 | } 80 | } 81 | 82 | fn is_finished(&self) -> bool { 83 | self.0.is_finished() 84 | } 85 | } 86 | 87 | impl> Seek for MonoToStereo { 88 | fn seek(&mut self, seconds: f32) { 89 | self.0.seek(seconds) 90 | } 91 | } 92 | 93 | #[cfg(test)] 94 | mod tests { 95 | use super::*; 96 | 97 | struct CountingSignal(u32); 98 | 99 | impl Signal for CountingSignal { 100 | type Frame = Sample; 101 | fn sample(&mut self, _: f32, out: &mut [Sample]) { 102 | for x in out { 103 | let i = self.0; 104 | *x = i as f32; 105 | self.0 = i + 1; 106 | } 107 | } 108 | } 109 | 110 | #[test] 111 | fn mono_to_stereo() { 112 | let mut signal = MonoToStereo::new(CountingSignal(0)); 113 | let mut buf = [[0.0; 2]; 4]; 114 | signal.sample(1.0, (&mut buf[..]).into()); 115 | assert_eq!(buf, [[0.0, 0.0], [1.0, 1.0], [2.0, 2.0], [3.0, 3.0]]); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/sine.rs: -------------------------------------------------------------------------------- 1 | use core::f32::consts::TAU; 2 | 3 | use crate::{math::Float, Sample, Seek, Signal}; 4 | 5 | /// A trivial [`Signal`] that produces a sine wave of a particular frequency, forever 6 | pub struct Sine { 7 | phase: f32, 8 | /// Radians per second 9 | frequency: f32, 10 | } 11 | 12 | impl Sine { 13 | /// Construct a sine wave that begins at `phase` radians and cycles `frequency_hz` times per 14 | /// second 15 | /// 16 | /// `phase` doesn't impact the sound of a single sine wave, but multiple concurrent sound waves 17 | /// may produce weird interference effects if their phases align. 18 | pub fn new(phase: f32, frequency_hz: f32) -> Self { 19 | Self { 20 | phase, 21 | frequency: frequency_hz * TAU, 22 | } 23 | } 24 | 25 | fn seek_to(&mut self, t: f32) { 26 | // Advance time, but wrap for numerical stability no matter how long we play for 27 | self.phase = (self.phase + t * self.frequency) % TAU; 28 | } 29 | } 30 | 31 | impl Signal for Sine { 32 | type Frame = Sample; 33 | 34 | fn sample(&mut self, interval: f32, out: &mut [Sample]) { 35 | for (i, x) in out.iter_mut().enumerate() { 36 | let t = interval * i as f32; 37 | *x = (t * self.frequency + self.phase).sin(); 38 | } 39 | self.seek_to(interval * out.len() as f32); 40 | } 41 | } 42 | 43 | impl Seek for Sine { 44 | fn seek(&mut self, seconds: f32) { 45 | self.seek_to(seconds); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/smooth.rs: -------------------------------------------------------------------------------- 1 | /// Helper to linearly ramp a parameter towards a target value 2 | /// 3 | /// Useful for implementing filters like [`Gain`](crate::Gain) which have dynamic parameters, where 4 | /// applying changes to parameters directly would cause unpleasant artifacts such as popping. 5 | /// 6 | /// # Example 7 | /// ``` 8 | /// let mut value = oddio::Smoothed::new(0.0); 9 | /// assert_eq!(value.get(), 0.0); 10 | /// // Changes only take effect after time passes 11 | /// value.set(1.0); 12 | /// assert_eq!(value.get(), 0.0); 13 | /// value.advance(0.5); 14 | /// assert_eq!(value.get(), 0.5); 15 | /// // A new value can be supplied mid-interpolation without causing a discontinuity 16 | /// value.set(1.5); 17 | /// value.advance(0.5); 18 | /// assert_eq!(value.get(), 1.0); 19 | /// value.advance(0.5); 20 | /// assert_eq!(value.get(), 1.5); 21 | /// // Interpolation halts once the target value is reached 22 | /// value.advance(0.5); 23 | /// assert_eq!(value.get(), 1.5); 24 | /// ``` 25 | #[derive(Copy, Clone, Default)] 26 | pub struct Smoothed { 27 | prev: T, 28 | next: T, 29 | progress: f32, 30 | } 31 | 32 | impl Smoothed { 33 | /// Create with initial value `x` 34 | pub fn new(x: T) -> Self 35 | where 36 | T: Clone, 37 | { 38 | Self { 39 | prev: x.clone(), 40 | next: x, 41 | progress: 1.0, 42 | } 43 | } 44 | 45 | /// Advance interpolation by `proportion`. For example, to advance at a fixed sample rate over a 46 | /// particular smoothing period, pass `sample_interval / smoothing_period`. 47 | pub fn advance(&mut self, proportion: f32) { 48 | self.progress = (self.progress + proportion).min(1.0); 49 | } 50 | 51 | /// Progress from the previous towards the next value 52 | pub fn progress(&self) -> f32 { 53 | self.progress 54 | } 55 | 56 | /// Set the next value to `x` 57 | pub fn set(&mut self, value: T) 58 | where 59 | T: Interpolate, 60 | { 61 | self.prev = self.get(); 62 | self.next = value; 63 | self.progress = 0.0; 64 | } 65 | 66 | /// Get the current value 67 | pub fn get(&self) -> T 68 | where 69 | T: Interpolate, 70 | { 71 | self.prev.interpolate(&self.next, self.progress) 72 | } 73 | 74 | /// Get the value most recently passed to `set` 75 | pub fn target(&self) -> &T { 76 | &self.next 77 | } 78 | } 79 | 80 | /// Types that can be linearly interpolated, for use with [`Smoothed`] 81 | pub trait Interpolate { 82 | /// Interpolate between `self` and `other` by `t`, which should be in [0, 1] 83 | fn interpolate(&self, other: &Self, t: f32) -> Self; 84 | } 85 | 86 | impl Interpolate for f32 { 87 | fn interpolate(&self, other: &Self, t: f32) -> Self { 88 | let diff = other - self; 89 | self + t * diff 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/spatial.rs: -------------------------------------------------------------------------------- 1 | use alloc::{boxed::Box, sync::Arc}; 2 | use core::{ 3 | ops::{Index, IndexMut}, 4 | sync::atomic::{AtomicBool, Ordering}, 5 | }; 6 | 7 | use crate::{ 8 | math::{add, dot, invert_quat, mix, norm, rotate, scale, sub, Float}, 9 | ring::Ring, 10 | set::{set, Set, SetHandle}, 11 | swap, Sample, Seek, Signal, 12 | }; 13 | 14 | type ErasedSpatialBuffered = Box + Send>>; 15 | type ErasedSpatial = Box + Send>>; 16 | 17 | /// An individual buffered spatialized signal 18 | struct SpatialSignalBuffered { 19 | rate: u32, 20 | max_delay: f32, 21 | common: Common, 22 | /// Delay queue of sound propagating through the medium 23 | /// 24 | /// Accounts only for the source's velocity. Listener velocity and attenuation are handled at 25 | /// output time. 26 | queue: Ring, 27 | inner: T, 28 | } 29 | 30 | impl SpatialSignalBuffered { 31 | fn new( 32 | rate: u32, 33 | inner: T, 34 | position: mint::Point3, 35 | velocity: mint::Vector3, 36 | max_delay: f32, 37 | radius: f32, 38 | ) -> (swap::Sender, Arc, Self) { 39 | let mut queue = Ring::new((max_delay * rate as f32).ceil() as usize + 1); 40 | queue.delay( 41 | rate, 42 | (norm(position.into()) / SPEED_OF_SOUND).min(max_delay), 43 | ); 44 | let (send, finished, recv) = Common::new(radius, position, velocity); 45 | ( 46 | send, 47 | finished, 48 | Self { 49 | rate, 50 | max_delay, 51 | common: recv, 52 | queue, 53 | inner, 54 | }, 55 | ) 56 | } 57 | } 58 | 59 | /// An individual seekable spatialized signal 60 | struct SpatialSignal { 61 | common: Common, 62 | inner: T, 63 | } 64 | 65 | impl SpatialSignal { 66 | fn new( 67 | inner: T, 68 | position: mint::Point3, 69 | velocity: mint::Vector3, 70 | radius: f32, 71 | ) -> (swap::Sender, Arc, Self) { 72 | let (send, finished, recv) = Common::new(radius, position, velocity); 73 | ( 74 | send, 75 | finished, 76 | Self { 77 | common: recv, 78 | inner, 79 | }, 80 | ) 81 | } 82 | } 83 | 84 | struct Common { 85 | radius: f32, 86 | motion: swap::Receiver, 87 | state: State, 88 | /// How long ago the signal finished, if it did 89 | finished_for: Option, 90 | stopped: Arc, 91 | } 92 | 93 | impl Common { 94 | fn new( 95 | radius: f32, 96 | position: mint::Point3, 97 | velocity: mint::Vector3, 98 | ) -> (swap::Sender, Arc, Self) { 99 | let finished = Arc::new(AtomicBool::new(false)); 100 | let (send, recv) = swap::swap(|| Motion { 101 | position, 102 | velocity, 103 | discontinuity: false, 104 | }); 105 | ( 106 | send, 107 | finished.clone(), 108 | Self { 109 | radius, 110 | motion: recv, 111 | state: State::new(position), 112 | finished_for: None, 113 | stopped: finished, 114 | }, 115 | ) 116 | } 117 | } 118 | 119 | /// Control for updating the motion of a spatial signal 120 | pub struct Spatial { 121 | motion: swap::Sender, 122 | finished: Arc, 123 | } 124 | 125 | impl Spatial { 126 | /// Update the position and velocity of the signal 127 | /// 128 | /// Coordinates should be in world space, translated such that the listener is at the origin, 129 | /// but not rotated, with velocity relative to the listener. Units are meters and meters per 130 | /// second. 131 | /// 132 | /// Set `discontinuity` when the signal or listener has teleported. This prevents inference of a 133 | /// very high velocity, with associated intense Doppler effects. 134 | /// 135 | /// If your sounds seem to be lagging behind their intended position by about half a second, 136 | /// make sure you're providing an accurate `velocity`! 137 | pub fn set_motion( 138 | &mut self, 139 | position: mint::Point3, 140 | velocity: mint::Vector3, 141 | discontinuity: bool, 142 | ) { 143 | *self.motion.pending() = Motion { 144 | position, 145 | velocity, 146 | discontinuity, 147 | }; 148 | self.motion.flush(); 149 | } 150 | 151 | /// Whether the signal has completed and can no longer be heard 152 | /// 153 | /// Accounts for signals still audible due to propagation delay. 154 | pub fn is_finished(&self) -> bool { 155 | self.finished.load(Ordering::Relaxed) 156 | } 157 | } 158 | 159 | /// [`Signal`] for stereo output from a spatial scene 160 | pub struct SpatialScene { 161 | rot: swap::Receiver>, 162 | recv_buffered: Set, 163 | recv: Set, 164 | } 165 | 166 | impl SpatialScene { 167 | /// Create a [`Signal`] for spatializing mono signals for stereo output 168 | /// 169 | /// Samples its component signals at `rate`. 170 | pub fn new() -> (SpatialSceneControl, Self) { 171 | let (seek_handle, seek_set) = set(); 172 | let (buffered_handle, buffered_set) = set(); 173 | let (rot_send, rot_recv) = swap::swap(|| mint::Quaternion { 174 | s: 1.0, 175 | v: [0.0; 3].into(), 176 | }); 177 | let control = SpatialSceneControl { 178 | rot: rot_send, 179 | seek: seek_handle, 180 | buffered: buffered_handle, 181 | }; 182 | let signal = SpatialScene { 183 | rot: rot_recv, 184 | recv_buffered: buffered_set, 185 | recv: seek_set, 186 | }; 187 | (control, signal) 188 | } 189 | } 190 | 191 | fn walk_set( 192 | set: &mut Set>, 193 | get_common: impl Fn(&mut T) -> &mut Common, 194 | get_inner: impl Fn(&T) -> &U, 195 | prev_rot: &mint::Quaternion, 196 | rot: &mint::Quaternion, 197 | elapsed: f32, 198 | mut mix_signal: impl FnMut(&mut T, mint::Point3, mint::Point3), 199 | ) where 200 | T: ?Sized, 201 | U: Signal + ?Sized, 202 | { 203 | set.update(); 204 | for i in (0..set.len()).rev() { 205 | let signal = &mut set[i]; 206 | let common = get_common(signal); 207 | 208 | let prev_position; 209 | let next_position; 210 | { 211 | // Compute the signal's smoothed start/end positions over the sampled period 212 | // TODO: Use historical positions 213 | let state = &mut common.state; 214 | 215 | // Update motion 216 | let orig_next = *common.motion.received(); 217 | if common.motion.refresh() { 218 | state.prev_position = if common.motion.received().discontinuity { 219 | common.motion.received().position 220 | } else { 221 | state.smoothed_position(0.0, &orig_next) 222 | }; 223 | state.dt = 0.0; 224 | } else { 225 | debug_assert_eq!(orig_next.position, common.motion.received().position); 226 | } 227 | 228 | prev_position = rotate( 229 | prev_rot, 230 | &state.smoothed_position(0.0, common.motion.received()), 231 | ); 232 | next_position = rotate( 233 | rot, 234 | &state.smoothed_position(elapsed, common.motion.received()), 235 | ); 236 | 237 | // Set up for next time 238 | state.dt += elapsed; 239 | } 240 | 241 | // Discard finished sources. If a source is moving away faster than the speed of sound, you 242 | // might get a pop. 243 | let distance = norm(prev_position.into()); 244 | match common.finished_for { 245 | Some(t) => { 246 | if t > distance / SPEED_OF_SOUND { 247 | common.stopped.store(true, Ordering::Relaxed); 248 | } else { 249 | common.finished_for = Some(t + elapsed); 250 | } 251 | } 252 | None => { 253 | if get_inner(signal).is_finished() { 254 | get_common(signal).finished_for = Some(elapsed); 255 | } 256 | } 257 | } 258 | if get_common(signal).stopped.load(Ordering::Relaxed) { 259 | set.remove(i); 260 | continue; 261 | } 262 | 263 | mix_signal(signal, prev_position, next_position); 264 | } 265 | } 266 | 267 | /// Control for modifying a [`SpatialScene`] 268 | pub struct SpatialSceneControl { 269 | rot: swap::Sender>, 270 | seek: SetHandle, 271 | buffered: SetHandle, 272 | } 273 | 274 | impl SpatialSceneControl { 275 | /// Begin playing `signal` 276 | /// 277 | /// Note that `signal` must be single-channel. Signals in a spatial scene are modeled as 278 | /// isotropic point sources, and cannot sensibly emit multichannel audio. 279 | /// 280 | /// Coordinates should be in world space, translated such that the listener is at the origin, 281 | /// but not rotated, with velocity relative to the listener. Units are meters and meters per 282 | /// second. 283 | /// 284 | /// Returns a handle that can be used to adjust the signal's movement in the future, pause or 285 | /// stop it, and access other controls. 286 | /// 287 | /// The type of signal given determines what additional controls can be used. See the 288 | /// examples for a detailed guide. 289 | pub fn play(&mut self, signal: S, options: SpatialOptions) -> Spatial 290 | where 291 | S: Seek + Send + 'static, 292 | { 293 | let (send, finished, recv) = 294 | SpatialSignal::new(signal, options.position, options.velocity, options.radius); 295 | let signal = Box::new(recv); 296 | let handle = Spatial { 297 | motion: send, 298 | finished, 299 | }; 300 | self.seek.insert(signal); 301 | handle 302 | } 303 | 304 | /// Like [`play`](Self::play), but supports propagation delay for sources which do not implement `Seek` by 305 | /// buffering. 306 | /// 307 | /// `max_distance` dictates the amount of propagation delay to allocate a buffer for; larger 308 | /// values consume more memory. To avoid glitching, the signal should be inaudible at 309 | /// `max_distance`. `signal` is sampled at `rate` before resampling based on motion. 310 | /// 311 | /// Sampling the scene for more than `buffer_duration` seconds at once may produce audible 312 | /// glitches when the signal exceeds `max_distance` from the listener. If in doubt, 0.1 is a 313 | /// reasonable guess. 314 | pub fn play_buffered( 315 | &mut self, 316 | signal: S, 317 | options: SpatialOptions, 318 | max_distance: f32, 319 | rate: u32, 320 | buffer_duration: f32, 321 | ) -> Spatial 322 | where 323 | S: Signal + Send + 'static, 324 | { 325 | let (send, finished, recv) = SpatialSignalBuffered::new( 326 | rate, 327 | signal, 328 | options.position, 329 | options.velocity, 330 | max_distance / SPEED_OF_SOUND + buffer_duration, 331 | options.radius, 332 | ); 333 | let signal = Box::new(recv); 334 | let handle = Spatial { 335 | motion: send, 336 | finished, 337 | }; 338 | self.buffered.insert(signal); 339 | handle 340 | } 341 | 342 | /// Set the listener's rotation 343 | /// 344 | /// An unrotated listener faces -Z, with +X to the right and +Y up. 345 | pub fn set_listener_rotation(&mut self, rotation: mint::Quaternion) { 346 | let signal_rotation = invert_quat(&rotation); 347 | *self.rot.pending() = signal_rotation; 348 | self.rot.flush(); 349 | } 350 | } 351 | 352 | /// Passed to [`SpatialSceneControl::play`] 353 | #[derive(Debug, Copy, Clone)] 354 | pub struct SpatialOptions { 355 | /// Initial position 356 | pub position: mint::Point3, 357 | /// Initial velocity 358 | pub velocity: mint::Vector3, 359 | /// Distance of zero attenuation. Approaching closer does not increase volume. 360 | pub radius: f32, 361 | } 362 | 363 | impl Default for SpatialOptions { 364 | fn default() -> Self { 365 | Self { 366 | position: [0.0; 3].into(), 367 | velocity: [0.0; 3].into(), 368 | radius: 0.1, 369 | } 370 | } 371 | } 372 | 373 | impl Signal for SpatialScene { 374 | type Frame = [Sample; 2]; 375 | 376 | fn sample(&mut self, interval: f32, out: &mut [[Sample; 2]]) { 377 | let set = &mut self.recv_buffered; 378 | // Update set contents 379 | set.update(); 380 | 381 | // Update listener rotation 382 | let (prev_rot, rot) = { 383 | let prev = *self.rot.received(); 384 | self.rot.refresh(); 385 | (prev, *self.rot.received()) 386 | }; 387 | 388 | // Zero output in preparation for mixing 389 | for frame in &mut *out { 390 | *frame = [0.0; 2]; 391 | } 392 | 393 | let mut buf = [0.0; 256]; 394 | let elapsed = interval * out.len() as f32; 395 | walk_set( 396 | set, 397 | |signal| &mut signal.common, 398 | |signal| &signal.inner, 399 | &prev_rot, 400 | &rot, 401 | elapsed, 402 | |signal, prev_position, next_position| { 403 | debug_assert!(signal.max_delay >= elapsed); 404 | 405 | // Extend delay queue with new data 406 | signal.queue.write(&mut signal.inner, signal.rate, elapsed); 407 | 408 | // Mix into output 409 | for &ear in &[Ear::Left, Ear::Right] { 410 | let prev_state = EarState::new(prev_position, ear, signal.common.radius); 411 | let next_state = EarState::new(next_position, ear, signal.common.radius); 412 | 413 | // Clamp into the max length of the delay queue 414 | let prev_offset = (prev_state.offset - elapsed).max(-signal.max_delay); 415 | let next_offset = next_state.offset.max(-signal.max_delay); 416 | 417 | let dt = (next_offset - prev_offset) / out.len() as f32; 418 | let d_gain = (next_state.gain - prev_state.gain) / out.len() as f32; 419 | 420 | let mut i = 0; 421 | let queue = &mut signal.queue; 422 | for chunk in out.chunks_mut(buf.len()) { 423 | let t = prev_offset + i as f32 * dt; 424 | queue.sample(signal.rate, t, dt, &mut buf[..chunk.len()]); 425 | for (s, o) in buf.iter().copied().zip(chunk) { 426 | let gain = prev_state.gain + i as f32 * d_gain; 427 | o[ear as usize] += s * gain; 428 | i += 1; 429 | } 430 | } 431 | } 432 | }, 433 | ); 434 | 435 | let set = &mut self.recv; 436 | // Update set contents 437 | set.update(); 438 | walk_set( 439 | set, 440 | |signal| &mut signal.common, 441 | |signal| &signal.inner, 442 | &prev_rot, 443 | &rot, 444 | elapsed, 445 | |signal, prev_position, next_position| { 446 | for &ear in &[Ear::Left, Ear::Right] { 447 | let prev_state = EarState::new(prev_position, ear, signal.common.radius); 448 | let next_state = EarState::new(next_position, ear, signal.common.radius); 449 | signal.inner.seek(prev_state.offset); // Initial real time -> Initial delayed 450 | 451 | let effective_elapsed = (elapsed + next_state.offset) - prev_state.offset; 452 | let dt = effective_elapsed / out.len() as f32; 453 | let d_gain = (next_state.gain - prev_state.gain) / out.len() as f32; 454 | 455 | let mut i = 0; 456 | for chunk in out.chunks_mut(buf.len()) { 457 | signal.inner.sample(dt, &mut buf[..chunk.len()]); 458 | for (s, o) in buf.iter().copied().zip(chunk) { 459 | let gain = prev_state.gain + i as f32 * d_gain; 460 | o[ear as usize] += s * gain; 461 | i += 1; 462 | } 463 | } 464 | // Final delayed -> Initial real time 465 | signal.inner.seek(-effective_elapsed - prev_state.offset); 466 | } 467 | // Initial real time -> Final real time 468 | signal.inner.seek(elapsed); 469 | }, 470 | ); 471 | } 472 | 473 | #[inline] 474 | fn is_finished(&self) -> bool { 475 | false 476 | } 477 | } 478 | 479 | #[derive(Copy, Clone)] 480 | struct Motion { 481 | position: mint::Point3, 482 | velocity: mint::Vector3, 483 | discontinuity: bool, 484 | } 485 | 486 | struct State { 487 | /// Smoothed position estimate when position/vel were updated 488 | prev_position: mint::Point3, 489 | /// Seconds since position/vel were updated 490 | dt: f32, 491 | } 492 | 493 | impl State { 494 | fn new(position: mint::Point3) -> Self { 495 | Self { 496 | prev_position: position, 497 | dt: 0.0, 498 | } 499 | } 500 | 501 | fn smoothed_position(&self, dt: f32, next: &Motion) -> mint::Point3 { 502 | let dt = self.dt + dt; 503 | let position_change = scale(next.velocity, dt); 504 | let naive_position = add(self.prev_position, position_change); 505 | let intended_position = add(next.position, position_change); 506 | mix( 507 | naive_position, 508 | intended_position, 509 | (dt / POSITION_SMOOTHING_PERIOD).min(1.0), 510 | ) 511 | } 512 | } 513 | 514 | /// Seconds over which to smooth position discontinuities 515 | /// 516 | /// Discontinuities arise because we only process commands at discrete intervals, and because the 517 | /// caller probably isn't running at perfectly even intervals either. If smoothed over too short a 518 | /// period, discontinuities will cause abrupt changes in effective velocity, which are distinctively 519 | /// audible due to the doppler effect. 520 | const POSITION_SMOOTHING_PERIOD: f32 = 0.5; 521 | 522 | #[derive(Debug, Clone)] 523 | struct EarState { 524 | /// Time offset at which this sound was most recently sampled 525 | offset: f32, 526 | /// Gain most recently applied 527 | gain: f32, 528 | } 529 | 530 | impl EarState { 531 | fn new(position_wrt_listener: mint::Point3, ear: Ear, radius: f32) -> Self { 532 | let distance = norm(sub(position_wrt_listener, ear.pos())); 533 | let offset = distance * (-1.0 / SPEED_OF_SOUND); 534 | let distance_gain = radius / distance.max(radius); 535 | // 1.0 when ear faces source directly; 0.5 when perpendicular; 0 when opposite 536 | let stereo_gain = 0.5 537 | + if distance < 1e-3 { 538 | 0.5 539 | } else { 540 | dot( 541 | ear.dir(), 542 | scale(position_wrt_listener.into(), 0.5 / distance), 543 | ) 544 | }; 545 | Self { 546 | offset, 547 | gain: stereo_gain * distance_gain, 548 | } 549 | } 550 | } 551 | 552 | #[derive(Debug, Copy, Clone)] 553 | enum Ear { 554 | Left, 555 | Right, 556 | } 557 | 558 | impl Index for [T] { 559 | type Output = T; 560 | fn index(&self, x: Ear) -> &T { 561 | &self[x as usize] 562 | } 563 | } 564 | 565 | impl IndexMut for [T] { 566 | fn index_mut(&mut self, x: Ear) -> &mut T { 567 | &mut self[x as usize] 568 | } 569 | } 570 | 571 | impl Ear { 572 | /// Location of the ear wrt a head facing -Z 573 | fn pos(self) -> mint::Point3 { 574 | [ 575 | match self { 576 | Ear::Left => -HEAD_RADIUS, 577 | Ear::Right => HEAD_RADIUS, 578 | }, 579 | 0.0, 580 | 0.0, 581 | ] 582 | .into() 583 | } 584 | 585 | /// Unit vector along which sound is least attenuated 586 | fn dir(self) -> mint::Vector3 { 587 | // [+-4, 0, -1] normalized 588 | [ 589 | match self { 590 | Ear::Left => -1.0, 591 | Ear::Right => 1.0, 592 | } * 4.0 593 | / 17.0f32.sqrt(), 594 | 0.0, 595 | -1.0 / 17.0f32.sqrt(), 596 | ] 597 | .into() 598 | } 599 | } 600 | 601 | /// Rate sound travels from signals to listeners (m/s) 602 | const SPEED_OF_SOUND: f32 = 343.0; 603 | 604 | /// Distance from center of head to an ear (m) 605 | const HEAD_RADIUS: f32 = 0.1075; 606 | 607 | #[cfg(test)] 608 | mod tests { 609 | use super::*; 610 | 611 | struct FinishedSignal; 612 | 613 | impl Signal for FinishedSignal { 614 | type Frame = f32; 615 | 616 | fn sample(&mut self, _: f32, out: &mut [Self::Frame]) { 617 | out.fill(0.0); 618 | } 619 | 620 | fn is_finished(&self) -> bool { 621 | true 622 | } 623 | } 624 | 625 | impl Seek for FinishedSignal { 626 | fn seek(&mut self, _: f32) {} 627 | } 628 | 629 | /// Verify that a signal is dropped only after accounting for propagation delay 630 | #[test] 631 | fn signal_finished() { 632 | let (mut control, mut scene) = SpatialScene::new(); 633 | control.play( 634 | FinishedSignal, 635 | SpatialOptions { 636 | // Exactly one second of propagation delay 637 | position: [SPEED_OF_SOUND, 0.0, 0.0].into(), 638 | ..SpatialOptions::default() 639 | }, 640 | ); 641 | scene.sample(0.0, &mut []); 642 | assert_eq!( 643 | scene.recv.len(), 644 | 1, 645 | "signal remains after no time has passed" 646 | ); 647 | scene.sample(0.6, &mut [[0.0; 2]]); 648 | assert_eq!( 649 | scene.recv.len(), 650 | 1, 651 | "signal remains partway through propagation" 652 | ); 653 | scene.sample(0.6, &mut [[0.0; 2]]); 654 | assert_eq!( 655 | scene.recv.len(), 656 | 1, 657 | "signal remains immediately after propagation delay expires" 658 | ); 659 | scene.sample(0.0, &mut []); 660 | assert_eq!( 661 | scene.recv.len(), 662 | 0, 663 | "signal dropped on first past after propagation delay expires" 664 | ); 665 | } 666 | } 667 | -------------------------------------------------------------------------------- /src/speed.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | use core::sync::atomic::{AtomicU32, Ordering}; 3 | 4 | use crate::{Frame, Signal}; 5 | 6 | /// Scales rate of playback by a dynamically-adjustable factor 7 | /// 8 | /// Higher/lower speeds will naturally result in higher/lower pitched sound respectively. 9 | pub struct Speed { 10 | speed: Arc, 11 | inner: T, 12 | } 13 | 14 | impl Speed { 15 | /// Apply dynamic speed to `signal` 16 | pub fn new(signal: T) -> (SpeedControl, Self) { 17 | let signal = Self { 18 | speed: Arc::new(AtomicU32::new(1.0f32.to_bits())), 19 | inner: signal, 20 | }; 21 | let control = SpeedControl(signal.speed.clone()); 22 | (control, signal) 23 | } 24 | } 25 | 26 | impl Signal for Speed 27 | where 28 | T::Frame: Frame, 29 | { 30 | type Frame = T::Frame; 31 | 32 | fn sample(&mut self, interval: f32, out: &mut [T::Frame]) { 33 | let speed = f32::from_bits(self.speed.load(Ordering::Relaxed)); 34 | self.inner.sample(interval * speed, out); 35 | } 36 | 37 | fn is_finished(&self) -> bool { 38 | self.inner.is_finished() 39 | } 40 | } 41 | 42 | /// Thread-safe control for a [`Speed`] filter 43 | pub struct SpeedControl(Arc); 44 | 45 | impl SpeedControl { 46 | /// Get the current speed 47 | pub fn speed(&self) -> f32 { 48 | f32::from_bits(self.0.load(Ordering::Relaxed)) 49 | } 50 | 51 | /// Adjust the speed 52 | pub fn set_speed(&mut self, factor: f32) { 53 | self.0.store(factor.to_bits(), Ordering::Relaxed); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/spsc.rs: -------------------------------------------------------------------------------- 1 | use crate::alloc::{alloc, boxed::Box, sync::Arc}; 2 | use core::{ 3 | cell::UnsafeCell, 4 | fmt, mem, 5 | mem::MaybeUninit, 6 | ops::Index, 7 | ptr, slice, 8 | sync::atomic::{AtomicUsize, Ordering}, 9 | }; 10 | 11 | pub fn channel(capacity: usize) -> (Sender, Receiver) { 12 | let shared = Shared::new(capacity + 1); 13 | ( 14 | Sender { 15 | shared: shared.clone(), 16 | }, 17 | Receiver { shared, len: 0 }, 18 | ) 19 | } 20 | 21 | pub struct Sender { 22 | shared: Arc>, 23 | } 24 | 25 | impl Sender { 26 | /// Append a prefix of `data` to the channel 27 | /// 28 | /// Returns the number of items sent. 29 | pub fn send_from_slice(&mut self, data: &[T]) -> usize 30 | where 31 | T: Copy, 32 | { 33 | let write = self.shared.header.write.load(Ordering::Relaxed); 34 | let read = self.shared.header.read.load(Ordering::Relaxed); 35 | let size = self.shared.data.len(); 36 | unsafe { 37 | let base = self.shared.data.as_ptr() as *mut T; 38 | let free = if write < read { 39 | ( 40 | slice::from_raw_parts_mut(base.add(write), read - write - 1), 41 | &mut [][..], 42 | ) 43 | } else if let Some(max) = read.checked_sub(1) { 44 | ( 45 | slice::from_raw_parts_mut(base.add(write), size - write), 46 | slice::from_raw_parts_mut(base, max), 47 | ) 48 | } else { 49 | ( 50 | slice::from_raw_parts_mut(base.add(write), size - write - 1), 51 | &mut [][..], 52 | ) 53 | }; 54 | let n1 = free.0.len().min(data.len()); 55 | ptr::copy_nonoverlapping(data.as_ptr(), free.0.as_mut_ptr(), n1); 56 | let mut n2 = 0; 57 | if let Some(rest) = data.len().checked_sub(n1) { 58 | n2 = free.1.len().min(rest); 59 | ptr::copy_nonoverlapping(data.as_ptr().add(n1), free.1.as_mut_ptr(), n2); 60 | } 61 | let n = n1 + n2; 62 | self.shared 63 | .header 64 | .write 65 | .store((write + n) % size, Ordering::Release); 66 | n 67 | } 68 | } 69 | 70 | pub fn capacity(&self) -> usize { 71 | self.shared.data.len() - 1 72 | } 73 | 74 | /// Lower bound on the number of items can be sent immediately 75 | pub fn free(&self) -> usize { 76 | let write = self.shared.header.write.load(Ordering::Relaxed); 77 | let read = self.shared.header.read.load(Ordering::Relaxed); 78 | let size = self.shared.data.len(); 79 | if write < read { 80 | read - write - 1 81 | } else if let Some(max) = read.checked_sub(1) { 82 | size - write + max 83 | } else { 84 | size - write - 1 85 | } 86 | } 87 | 88 | /// Append a single item, leaving at least `reserve_slots` for future use 89 | pub fn send(&mut self, data: T, reserve_slots: usize) -> Result<(), T> { 90 | let write = self.shared.header.write.load(Ordering::Relaxed); 91 | let read = self.shared.header.read.load(Ordering::Relaxed); 92 | let size = self.shared.data.len(); 93 | if ((write + reserve_slots + 1) % size).wrapping_sub(read) < reserve_slots + 1 { 94 | return Err(data); 95 | } 96 | unsafe { 97 | *self.shared.data[write].get() = MaybeUninit::new(data); 98 | } 99 | self.shared 100 | .header 101 | .write 102 | .store((write + 1) % size, Ordering::Release); 103 | Ok(()) 104 | } 105 | 106 | /// Whether the receiver has been dropped 107 | // Could be `&self` since we don't allow new references to be created, but :effort: 108 | #[allow(clippy::wrong_self_convention)] 109 | pub fn is_closed(&mut self) -> bool { 110 | Arc::get_mut(&mut self.shared).is_some() 111 | } 112 | } 113 | 114 | pub struct Receiver { 115 | shared: Arc>, 116 | len: usize, 117 | } 118 | 119 | impl Receiver { 120 | /// Number of elements available to read 121 | pub fn len(&self) -> usize { 122 | self.len 123 | } 124 | 125 | /// Extend with newly sent items 126 | pub fn update(&mut self) { 127 | let old_len = self.len; 128 | self.len = self.shared.readable_len(); 129 | debug_assert!(self.len >= old_len); 130 | } 131 | 132 | /// Release the first `n` elements for reuse by the sender 133 | pub fn release(&mut self, n: usize) { 134 | debug_assert!(n <= self.len); 135 | let n = self.len.min(n); 136 | unsafe { 137 | self.shared.release(n); 138 | } 139 | self.len -= n; 140 | } 141 | 142 | pub fn drain(&mut self) -> Drain<'_, T> { 143 | Drain { recv: self } 144 | } 145 | 146 | /// Whether the sender has been dropped 147 | pub fn is_closed(&self) -> bool { 148 | Arc::strong_count(&self.shared) == 1 149 | } 150 | 151 | pub fn pop(&mut self) -> Option { 152 | if self.len == 0 { 153 | return None; 154 | } 155 | let read = self.shared.header.read.load(Ordering::Relaxed); 156 | let value = unsafe { (*self.shared.data[read].get()).as_ptr().read() }; 157 | self.shared 158 | .header 159 | .read 160 | .store((read + 1) % self.shared.data.len(), Ordering::Relaxed); 161 | self.len -= 1; 162 | Some(value) 163 | } 164 | } 165 | 166 | impl Index for Receiver { 167 | type Output = T; 168 | fn index(&self, i: usize) -> &T { 169 | assert!(i < self.len); 170 | let read = self.shared.header.read.load(Ordering::Relaxed); 171 | unsafe { &*(*self.shared.data[(read + i) % self.shared.data.len()].get()).as_ptr() } 172 | } 173 | } 174 | 175 | impl fmt::Debug for Receiver { 176 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 177 | let mut entries = f.debug_list(); 178 | for i in 0..self.len() { 179 | entries.entry(&self[i]); 180 | } 181 | entries.finish() 182 | } 183 | } 184 | 185 | #[repr(C)] 186 | struct Shared { 187 | header: Header, 188 | data: [UnsafeCell>], 189 | } 190 | 191 | unsafe impl Sync for Shared {} 192 | 193 | impl Shared { 194 | fn new(capacity: usize) -> Arc { 195 | let header_layout = alloc::Layout::new::
(); 196 | let layout = header_layout 197 | .extend( 198 | alloc::Layout::from_size_align( 199 | mem::size_of::() * capacity, 200 | mem::align_of::(), 201 | ) 202 | .unwrap(), 203 | ) 204 | .unwrap() 205 | .0 206 | .pad_to_align(); 207 | unsafe { 208 | let mem = alloc::alloc(layout); 209 | mem.cast::
().write(Header { 210 | read: AtomicUsize::new(0), 211 | write: AtomicUsize::new(0), 212 | }); 213 | Box::from_raw(ptr::slice_from_raw_parts_mut(mem, capacity) as *mut Self).into() 214 | } 215 | } 216 | 217 | fn readable_len(&self) -> usize { 218 | let read = self.header.read.load(Ordering::Relaxed); 219 | let write = self.header.write.load(Ordering::Acquire); 220 | if write >= read { 221 | write - read 222 | } else { 223 | write + self.data.len() - read 224 | } 225 | } 226 | 227 | unsafe fn release(&self, n: usize) { 228 | let read = self.header.read.load(Ordering::Relaxed); 229 | for i in 0..n { 230 | ptr::drop_in_place((*self.data[(read + i) % self.data.len()].get()).as_mut_ptr()); 231 | } 232 | self.header 233 | .read 234 | .store((read + n) % self.data.len(), Ordering::Relaxed); 235 | } 236 | } 237 | 238 | impl Drop for Shared { 239 | fn drop(&mut self) { 240 | unsafe { self.release(self.readable_len()) } 241 | } 242 | } 243 | 244 | struct Header { 245 | read: AtomicUsize, 246 | write: AtomicUsize, 247 | } 248 | 249 | pub struct Drain<'a, T> { 250 | recv: &'a mut Receiver, 251 | } 252 | 253 | impl<'a, T> Iterator for Drain<'a, T> { 254 | type Item = T; 255 | fn next(&mut self) -> Option { 256 | self.recv.pop() 257 | } 258 | 259 | fn size_hint(&self) -> (usize, Option) { 260 | (self.recv.len, Some(self.recv.len)) 261 | } 262 | } 263 | 264 | impl<'a, T> ExactSizeIterator for Drain<'a, T> { 265 | fn len(&self) -> usize { 266 | self.recv.len 267 | } 268 | } 269 | 270 | #[cfg(test)] 271 | mod tests { 272 | use super::*; 273 | 274 | #[test] 275 | fn recv_empty() { 276 | let (send, mut recv) = channel::(4); 277 | assert_eq!(send.free(), 4); 278 | recv.update(); 279 | assert_eq!(recv.len(), 0); 280 | } 281 | 282 | #[test] 283 | fn send_recv() { 284 | let (mut send, mut recv) = channel::(4); 285 | send.send_from_slice(&[1, 2, 3]); 286 | assert_eq!(send.free(), 1); 287 | recv.update(); 288 | assert_eq!(recv.len(), 3); 289 | assert_eq!(recv[0], 1); 290 | assert_eq!(recv[1], 2); 291 | assert_eq!(recv[2], 3); 292 | } 293 | 294 | #[test] 295 | fn wrap() { 296 | let (mut send, mut recv) = channel::(4); 297 | send.send_from_slice(&[1, 2, 3]); 298 | assert_eq!(send.free(), 1); 299 | recv.update(); 300 | recv.release(2); 301 | assert_eq!(send.free(), 3); 302 | assert_eq!(recv.len(), 1); 303 | assert_eq!(recv[0], 3); 304 | send.send_from_slice(&[4, 5]); 305 | assert_eq!(send.free(), 1); 306 | recv.update(); 307 | assert_eq!(recv.len(), 3); 308 | assert_eq!(recv[0], 3); 309 | assert_eq!(recv[1], 4); 310 | assert_eq!(recv[2], 5); 311 | } 312 | 313 | #[test] 314 | fn send_excess() { 315 | let (mut send, mut recv) = channel::(4); 316 | assert_eq!(send.send_from_slice(&[1, 2, 3, 4, 5]), 4); 317 | recv.update(); 318 | assert_eq!(recv.len(), 4); 319 | assert_eq!(recv[0], 1); 320 | assert_eq!(recv[1], 2); 321 | assert_eq!(recv[2], 3); 322 | assert_eq!(recv[3], 4); 323 | } 324 | 325 | #[test] 326 | fn fill_release_fill() { 327 | let (mut send, mut recv) = channel::(4); 328 | assert_eq!(send.send_from_slice(&[1, 2, 3, 4]), 4); 329 | assert_eq!(send.free(), 0); 330 | recv.update(); 331 | recv.release(2); 332 | assert_eq!(send.free(), 2); 333 | assert_eq!(send.send_from_slice(&[5, 6, 7]), 2); 334 | assert_eq!(send.free(), 0); 335 | assert_eq!(send.send_from_slice(&[7]), 0); 336 | assert_eq!(send.free(), 0); 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/stream.rs: -------------------------------------------------------------------------------- 1 | //! Streaming audio support 2 | 3 | use crate::{frame, math::Float, spsc, Frame, Signal}; 4 | 5 | /// Dynamic audio from an external source 6 | pub struct Stream { 7 | rate: u32, 8 | inner: spsc::Receiver, 9 | /// Offset of t=0 from the start of the buffer, in frames 10 | t: f32, 11 | /// Whether `inner` will receive no further updates 12 | stopping: bool, 13 | } 14 | 15 | impl Stream { 16 | /// Construct a stream of dynamic audio 17 | /// 18 | /// Samples can be appended to the stream through its [`StreamControl`]. This allows the 19 | /// business of obtaining streaming audio, e.g. from a streaming decoder or the network, to take 20 | /// place without interfering with the low-latency requirements of audio output. 21 | /// 22 | /// - `rate` is the stream's sample rate 23 | /// - `size` dictates the maximum number of buffered frames 24 | pub fn new(rate: u32, size: usize) -> (StreamControl, Self) { 25 | let (send, recv) = spsc::channel(size); 26 | let signal = Self { 27 | rate, 28 | inner: recv, 29 | t: 0.0, 30 | stopping: false, 31 | }; 32 | let control = StreamControl(send); 33 | (control, signal) 34 | } 35 | 36 | #[inline] 37 | fn get(&self, sample: isize) -> T 38 | where 39 | T: Frame + Copy, 40 | { 41 | if sample < 0 { 42 | return T::ZERO; 43 | } 44 | let sample = sample as usize; 45 | if sample >= self.inner.len() { 46 | return T::ZERO; 47 | } 48 | self.inner[sample] 49 | } 50 | 51 | fn sample_single(&self, s: f32) -> T 52 | where 53 | T: Frame + Copy, 54 | { 55 | let x0 = s.trunc() as isize; 56 | let fract = s.fract(); 57 | let x1 = x0 + 1; 58 | let a = self.get(x0); 59 | let b = self.get(x1); 60 | frame::lerp(&a, &b, fract) 61 | } 62 | 63 | fn advance(&mut self, dt: f32) { 64 | let next = self.t + dt * self.rate as f32; 65 | let t = next.min(self.inner.len() as f32); 66 | self.inner.release(t as usize); 67 | self.t = t.fract(); 68 | } 69 | } 70 | 71 | impl Signal for Stream { 72 | type Frame = T; 73 | 74 | fn sample(&mut self, interval: f32, out: &mut [T]) { 75 | self.inner.update(); 76 | if self.inner.is_closed() { 77 | self.stopping = true; 78 | } 79 | let s0 = self.t; 80 | let ds = interval * self.rate as f32; 81 | 82 | for (i, o) in out.iter_mut().enumerate() { 83 | *o = self.sample_single(s0 + ds * i as f32); 84 | } 85 | self.advance(interval * out.len() as f32); 86 | } 87 | 88 | #[allow(clippy::float_cmp)] 89 | fn is_finished(&self) -> bool { 90 | self.stopping && self.t == self.inner.len() as f32 91 | } 92 | } 93 | 94 | /// Thread-safe control for a [`Stream`] 95 | pub struct StreamControl(spsc::Sender); 96 | 97 | impl StreamControl { 98 | /// Lower bound to the number of samples that the next `write` call will successfully consume 99 | pub fn free(&mut self) -> usize { 100 | self.0.free() 101 | } 102 | 103 | /// Add more samples. Returns the number of samples consumed. Remaining samples should be passed 104 | /// in again in a future call. 105 | pub fn write(&mut self, samples: &[T]) -> usize 106 | where 107 | T: Copy, 108 | { 109 | self.0.send_from_slice(samples) 110 | } 111 | } 112 | 113 | #[cfg(test)] 114 | mod tests { 115 | use super::*; 116 | use alloc::vec; 117 | 118 | fn assert_out(stream: &mut Stream, expected: &[f32]) { 119 | let mut output = vec![0.0; expected.len()]; 120 | stream.sample(1.0, &mut output); 121 | assert_eq!(output, expected); 122 | } 123 | 124 | #[test] 125 | fn smoke() { 126 | let (mut c, mut s) = Stream::::new(1, 3); 127 | assert_eq!(c.write(&[1.0, 2.0]), 2); 128 | assert_eq!(c.write(&[3.0, 4.0]), 1); 129 | assert_out(&mut s, &[1.0, 2.0, 3.0, 0.0, 0.0]); 130 | assert_eq!(c.write(&[5.0, 6.0, 7.0, 8.0]), 3); 131 | assert_out(&mut s, &[5.0]); 132 | assert_out(&mut s, &[6.0, 7.0, 0.0, 0.0]); 133 | assert_out(&mut s, &[0.0, 0.0]); 134 | } 135 | 136 | #[test] 137 | fn cleanup() { 138 | let (mut c, mut s) = Stream::::new(1, 4); 139 | assert_eq!(c.write(&[1.0, 2.0]), 2); 140 | assert!(!s.is_finished()); 141 | drop(c); 142 | assert!(!s.is_finished()); 143 | s.sample(1.0, &mut [0.0]); 144 | assert!(!s.is_finished()); 145 | s.sample(1.0, &mut [0.0]); 146 | assert!(s.is_finished()); 147 | s.sample(1.0, &mut [0.0]); 148 | assert!(s.is_finished()); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/swap.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | cell::{Cell, UnsafeCell}, 3 | sync::atomic::{AtomicUsize, Ordering}, 4 | }; 5 | 6 | use alloc::sync::Arc; 7 | 8 | /// SPSC queue that only retains the last element sent 9 | /// 10 | /// Useful for custom controllable signals. 11 | pub fn swap(mut init: impl FnMut() -> T) -> (Sender, Receiver) { 12 | let shared = Arc::new(Shared { 13 | slots: [ 14 | UnsafeCell::new(init()), 15 | UnsafeCell::new(init()), 16 | UnsafeCell::new(init()), 17 | ], 18 | index: AtomicUsize::new(1), 19 | }); 20 | ( 21 | Sender { 22 | index: 0, 23 | shared: shared.clone(), 24 | }, 25 | Receiver { index: 2, shared }, 26 | ) 27 | } 28 | 29 | pub struct Sender { 30 | index: usize, 31 | shared: Arc>, 32 | } 33 | 34 | impl Sender { 35 | /// Access the value that will be sent next 36 | pub fn pending(&mut self) -> &mut T { 37 | unsafe { &mut *self.shared.slots[self.index].get() } 38 | } 39 | 40 | /// Send the value from `pending` 41 | pub fn flush(&mut self) { 42 | self.index = self 43 | .shared 44 | .index 45 | .swap(self.index | FRESH_BIT, Ordering::AcqRel) 46 | & INDEX_MASK; 47 | } 48 | } 49 | 50 | pub struct Receiver { 51 | index: usize, 52 | shared: Arc>, 53 | } 54 | 55 | impl Receiver { 56 | /// Update the value exposed by `recv`. Returns whether new data was obtained. Consumer only. 57 | pub fn refresh(&mut self) -> bool { 58 | if self.shared.index.load(Ordering::Relaxed) & FRESH_BIT == 0 { 59 | return false; 60 | } 61 | self.index = self.shared.index.swap(self.index, Ordering::AcqRel) & INDEX_MASK; 62 | true 63 | } 64 | 65 | /// Access the most recent data as of the last `refresh` call. Consumer only. 66 | pub fn received(&mut self) -> &mut T { 67 | unsafe { &mut *self.shared.slots[self.index].get() } 68 | } 69 | } 70 | 71 | struct Shared { 72 | slots: [UnsafeCell; 3], 73 | index: AtomicUsize, 74 | } 75 | 76 | unsafe impl Send for Shared {} 77 | unsafe impl Sync for Shared {} 78 | 79 | const FRESH_BIT: usize = 0b100; 80 | const INDEX_MASK: usize = 0b011; 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use super::*; 85 | 86 | #[test] 87 | fn smoke() { 88 | let (mut s, mut r) = swap(|| 0); 89 | *s.pending() = 1; 90 | assert_eq!(*r.received(), 0); 91 | s.flush(); 92 | assert_eq!(*r.received(), 0); 93 | assert!(r.refresh()); 94 | assert_eq!(*r.received(), 1); 95 | assert!(!r.refresh()); 96 | assert_eq!(*r.received(), 1); 97 | *s.pending() = 2; 98 | assert!(!r.refresh()); 99 | assert_eq!(*r.received(), 1); 100 | s.flush(); 101 | assert_eq!(*r.received(), 1); 102 | assert!(r.refresh()); 103 | assert_eq!(*r.received(), 2); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/tanh.rs: -------------------------------------------------------------------------------- 1 | use crate::{math::Float, Frame, Seek, Signal}; 2 | 3 | /// Smoothly maps a signal of any range into (-1, 1) 4 | /// 5 | /// For each input sample `x`, outputs `x.tanh()`. Similar to [`Reinhard`](crate::Reinhard), but 6 | /// distorts quiet sounds less, and loud sounds more. 7 | pub struct Tanh(T); 8 | 9 | impl Tanh { 10 | /// Apply the hypberbolic tangent operator to `signal` 11 | pub fn new(signal: T) -> Self { 12 | Self(signal) 13 | } 14 | } 15 | 16 | impl Signal for Tanh 17 | where 18 | T::Frame: Frame, 19 | { 20 | type Frame = T::Frame; 21 | 22 | fn sample(&mut self, interval: f32, out: &mut [T::Frame]) { 23 | self.0.sample(interval, out); 24 | for x in out { 25 | for channel in x.channels_mut() { 26 | *channel = channel.tanh(); 27 | } 28 | } 29 | } 30 | 31 | fn is_finished(&self) -> bool { 32 | self.0.is_finished() 33 | } 34 | } 35 | 36 | impl Seek for Tanh 37 | where 38 | T: Signal + Seek, 39 | T::Frame: Frame, 40 | { 41 | fn seek(&mut self, seconds: f32) { 42 | self.0.seek(seconds); 43 | } 44 | } 45 | --------------------------------------------------------------------------------