├── .github └── workflows │ ├── ci.yml │ └── publish.yaml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── UPGRADE.md ├── assets ├── README.md ├── RL.flac ├── RL.m4a ├── RL.mp3 ├── RL.ogg ├── RL.wav ├── audacity16bit.wav ├── audacity16bit_level5.flac ├── audacity24bit_level0.flac ├── audacity24bit_level5.flac ├── audacity24bit_level8.flac ├── audacity32bit.wav ├── audacity32bit_int.wav ├── beep.wav ├── beep2.wav ├── beep3.ogg ├── lmms16bit.wav ├── lmms24bit.wav ├── lmms32bit.wav ├── monkeys.mp4a ├── music.flac ├── music.m4a ├── music.mp3 ├── music.ogg └── music.wav ├── benches ├── conversions.rs ├── effects.rs ├── pipeline.rs ├── resampler.rs └── shared.rs ├── examples ├── automatic_gain_control.rs ├── basic.rs ├── callback_on_end.rs ├── custom_config.rs ├── distortion.rs ├── distortion_mp3.rs ├── distortion_wav.rs ├── distortion_wav_alternate.rs ├── error_callback.rs ├── into_file.rs ├── low_pass.rs ├── mix_multiple_sources.rs ├── music_flac.rs ├── music_m4a.rs ├── music_mp3.rs ├── music_ogg.rs ├── music_wav.rs ├── noise_generator.rs ├── reverb.rs ├── seek_mp3.rs ├── signal_generator.rs ├── spatial.rs └── stereo.rs ├── outreach ├── outreach_to_bevy_audio_discord_channel.txt ├── outreach_to_bevy_audio_working_group.txt └── v0.20_announcement_and_call_for_userstories.md ├── src ├── buffer.rs ├── common.rs ├── conversions │ ├── channels.rs │ ├── mod.rs │ ├── sample.rs │ └── sample_rate.rs ├── decoder │ ├── builder.rs │ ├── flac.rs │ ├── mod.rs │ ├── mp3.rs │ ├── read_seek_source.rs │ ├── symphonia.rs │ ├── vorbis.rs │ └── wav.rs ├── lib.rs ├── math.rs ├── mixer.rs ├── queue.rs ├── sink.rs ├── source │ ├── agc.rs │ ├── amplify.rs │ ├── blt.rs │ ├── buffered.rs │ ├── channel_volume.rs │ ├── chirp.rs │ ├── crossfade.rs │ ├── delay.rs │ ├── distortion.rs │ ├── done.rs │ ├── empty.rs │ ├── empty_callback.rs │ ├── fadein.rs │ ├── fadeout.rs │ ├── from_factory.rs │ ├── from_iter.rs │ ├── linear_ramp.rs │ ├── mix.rs │ ├── mod.rs │ ├── noise.rs │ ├── pausable.rs │ ├── periodic.rs │ ├── position.rs │ ├── repeat.rs │ ├── sawtooth.rs │ ├── signal_generator.rs │ ├── sine.rs │ ├── skip.rs │ ├── skippable.rs │ ├── spatial.rs │ ├── speed.rs │ ├── square.rs │ ├── stoppable.rs │ ├── take.rs │ ├── triangle.rs │ ├── uniform.rs │ └── zero.rs ├── spatial_sink.rs ├── static_buffer.rs ├── stream.rs └── wav_output.rs └── tests ├── flac_test.rs ├── mp4a_test.rs ├── seek.rs ├── total_duration.rs └── wav_test.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main, master] 7 | 8 | env: 9 | RUSTFLAGS: "-C debuginfo=0 -D warnings" 10 | CARGO_TERM_COLOR: always 11 | CARGO_INCREMENTAL: 0 12 | 13 | jobs: 14 | tests: 15 | name: Tests 16 | runs-on: ${{ matrix.os }} 17 | continue-on-error: ${{ matrix.toolchain == 'nightly' }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: [macos-latest, windows-latest, ubuntu-latest] 22 | toolchain: [stable, beta, nightly] 23 | include: 24 | - os: macos-latest 25 | MACOS: true 26 | - os: windows-latest 27 | - os: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - name: Install linux build requirements 32 | run: sudo apt install --yes --no-install-recommends libasound2-dev pkg-config 33 | if: contains(matrix.os, 'ubuntu') 34 | 35 | - name: install ${{ matrix.toolchain }} toolchain 36 | run: rustup toolchain install ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} 37 | 38 | - run: cargo clippy -- -D warnings 39 | if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest' 40 | 41 | - run: | 42 | rustup component add rustfmt 43 | cargo fmt --all -- --check 44 | if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest' 45 | 46 | - run: cargo test --all-targets 47 | - run: cargo test --lib --bins --tests --benches --features=experimental 48 | - run: cargo test --all-targets --features=symphonia-all 49 | # `cargo test` does not check benchmarks and `cargo test --all-targets` excludes 50 | # documentation tests. Therefore, we need an additional docs test command here. 51 | - run: cargo test --doc 52 | # Check minimal build. 53 | - run: cargo check --tests --lib --no-default-features 54 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | cargo-publish: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | with: 12 | ref: ${{ inputs.version_tag }} 13 | 14 | - name: Fetch tags 15 | run: git fetch --prune --unshallow --tags 16 | 17 | - name: Install linux build requirements 18 | run: sudo apt install --yes --no-install-recommends libasound2-dev pkg-config 19 | 20 | - name: Publish and tag 21 | run: | 22 | echo "Current git commit is $(git rev-list -n 1 HEAD)." 23 | 24 | VERSION="$(yq '.package.version' Cargo.toml)" 25 | echo "Project version from Cargo.toml is $VERSION" 26 | if ! (echo "$VERSION" | grep --quiet "^[0-9]\{1,2\}\.[0-9]\{1,3\}\(\.[0-9]\{1,3\}\)\?$"); then 27 | echo "The version format does not look like a release version, not publishing the crate." 28 | exit 1 29 | fi 30 | 31 | VERSION_TAG="v$VERSION" 32 | if git tag | grep --quiet "^$VERSION_TAG$"; then 33 | echo "Tag $VERSION_TAG already exists at $(git rev-list -n 1 $VERSION_TAG), not publishing the crate." 34 | exit 1 35 | fi 36 | 37 | cargo publish --token "${{ secrets.CRATESIO_TOKEN }}" 38 | 39 | echo "Tagging current version with $VERSION_TAG ..." 40 | # The bot name and email is taken from here https://github.com/actions/checkout/pull/1707 41 | # see also https://api.github.com/users/github-actions%5Bbot%5D 42 | git config user.name "github-actions[bot]" 43 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 44 | git tag --annotate "$VERSION_TAG" --message "Release version $VERSION_TAG" 45 | git push origin "$VERSION_TAG" 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | use_try_shorthand = true 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rodio" 3 | version = "0.20.1" 4 | license = "MIT OR Apache-2.0" 5 | description = "Audio playback library" 6 | keywords = ["audio", "playback", "gamedev"] 7 | repository = "https://github.com/RustAudio/rodio" 8 | documentation = "https://docs.rs/rodio" 9 | exclude = ["assets/**", "tests/**"] 10 | edition = "2021" 11 | 12 | [dependencies] 13 | cpal = { version = "0.15.3", optional = true } 14 | dasp_sample = "0.11.0" 15 | claxon = { version = "0.4.2", optional = true } 16 | hound = { version = "3.3.1", optional = true } 17 | lewton = { version = "0.10", optional = true } 18 | minimp3_fixed = { version = "0.5.4", optional = true } 19 | symphonia = { version = "0.5.4", optional = true, default-features = false } 20 | crossbeam-channel = { version = "0.5.15", optional = true } 21 | 22 | rand = { version = "0.9.0", features = [ 23 | "small_rng", 24 | "os_rng", 25 | ], optional = true } 26 | tracing = { version = "0.1.40", optional = true } 27 | 28 | atomic_float = { version = "1.1.0", optional = true } 29 | num-rational = "0.4.2" 30 | 31 | [features] 32 | default = ["playback", "flac", "vorbis", "wav", "mp3"] 33 | tracing = ["dep:tracing"] 34 | experimental = ["dep:atomic_float"] 35 | playback = ["dep:cpal"] 36 | 37 | flac = ["claxon"] 38 | vorbis = ["lewton"] 39 | wav = ["hound"] 40 | mp3 = ["symphonia-mp3"] 41 | minimp3 = ["dep:minimp3_fixed"] 42 | 43 | noise = ["rand"] 44 | 45 | wasm-bindgen = ["cpal/wasm-bindgen"] 46 | cpal-shared-stdcxx = ["cpal/oboe-shared-stdcxx"] 47 | 48 | symphonia-aac = ["symphonia/aac"] 49 | symphonia-all = [ 50 | "symphonia-aac", 51 | "symphonia-flac", 52 | "symphonia-isomp4", 53 | "symphonia-mp3", 54 | "symphonia-ogg", 55 | "symphonia-vorbis", 56 | "symphonia-wav", 57 | ] 58 | symphonia-flac = ["symphonia/flac"] 59 | symphonia-isomp4 = ["symphonia/isomp4"] 60 | symphonia-mp3 = ["symphonia/mp3"] 61 | symphonia-ogg = ["symphonia/ogg"] 62 | symphonia-vorbis = ["symphonia/vorbis"] 63 | symphonia-wav = ["symphonia/wav", "symphonia/pcm", "symphonia/adpcm"] 64 | symphonia-alac = ["symphonia/isomp4", "symphonia/alac"] 65 | symphonia-aiff = ["symphonia/aiff", "symphonia/pcm"] 66 | 67 | [dev-dependencies] 68 | quickcheck = "1" 69 | rstest = "0.18.2" 70 | rstest_reuse = "0.6.0" 71 | approx = "0.5.1" 72 | dasp_sample = "0.11.0" 73 | divan = "0.1.14" 74 | 75 | [[bench]] 76 | name = "effects" 77 | harness = false 78 | required-features = ["wav"] 79 | 80 | [[bench]] 81 | name = "conversions" 82 | harness = false 83 | required-features = ["wav"] 84 | 85 | [[bench]] 86 | name = "resampler" 87 | harness = false 88 | required-features = ["wav"] 89 | 90 | [[bench]] 91 | name = "pipeline" 92 | harness = false 93 | required-features = ["wav"] 94 | 95 | [[example]] 96 | name = "automatic_gain_control" 97 | required-features = ["playback", "flac"] 98 | 99 | [[example]] 100 | name = "basic" 101 | required-features = ["playback", "vorbis"] 102 | 103 | [[example]] 104 | name = "callback_on_end" 105 | required-features = ["playback", "wav"] 106 | 107 | [[example]] 108 | name = "custom_config" 109 | required-features = ["playback", "wav"] 110 | 111 | [[example]] 112 | name = "distortion" 113 | required-features = ["playback"] 114 | 115 | [[example]] 116 | name = "distortion_mp3" 117 | required-features = ["playback", "mp3"] 118 | 119 | [[example]] 120 | name = "distortion_wav" 121 | required-features = ["playback", "wav"] 122 | 123 | [[example]] 124 | name = "distortion_wav_alternate" 125 | required-features = ["playback", "wav"] 126 | 127 | [[example]] 128 | name = "error_callback" 129 | required-features = ["playback"] 130 | 131 | [[example]] 132 | name = "into_file" 133 | required-features = ["mp3", "wav"] 134 | 135 | [[example]] 136 | name = "low_pass" 137 | required-features = ["playback", "wav"] 138 | 139 | [[example]] 140 | name = "mix_multiple_sources" 141 | required-features = ["playback"] 142 | 143 | [[example]] 144 | name = "music_flac" 145 | required-features = ["playback", "flac"] 146 | 147 | [[example]] 148 | name = "music_m4a" 149 | required-features = ["playback", "symphonia-isomp4", "symphonia-aac"] 150 | 151 | [[example]] 152 | name = "music_mp3" 153 | required-features = ["playback", "mp3"] 154 | 155 | [[example]] 156 | name = "music_ogg" 157 | required-features = ["playback", "vorbis"] 158 | 159 | [[example]] 160 | name = "music_wav" 161 | required-features = ["playback", "wav"] 162 | 163 | [[example]] 164 | name = "noise_generator" 165 | required-features = ["playback", "noise"] 166 | 167 | [[example]] 168 | name = "reverb" 169 | required-features = ["playback", "vorbis"] 170 | 171 | [[example]] 172 | name = "seek_mp3" 173 | required-features = ["playback", "mp3"] 174 | 175 | [[example]] 176 | name = "signal_generator" 177 | required-features = ["playback"] 178 | 179 | [[example]] 180 | name = "spatial" 181 | required-features = ["playback", "vorbis"] 182 | 183 | [[example]] 184 | name = "stereo" 185 | required-features = ["playback", "vorbis"] 186 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Audio playback library 2 | 3 | [![Crates.io Version](https://img.shields.io/crates/v/rodio.svg)](https://crates.io/crates/rodio) 4 | [![Crates.io Downloads](https://img.shields.io/crates/d/rodio.svg)](https://crates.io/crates/rodio) 5 | [![Build Status](https://github.com/RustAudio/rodio/workflows/CI/badge.svg)](https://github.com/RustAudio/rodio/actions) 6 | 7 | Rust playback library. 8 | 9 | Playback is handled by [cpal](https://github.com/RustAudio/cpal). Format decoding can be handled either by [Symphonia](https://github.com/pdeljanov/Symphonia), or by format-specific decoders: 10 | 11 | - MP3 by [minimp3](https://github.com/lieff/minimp3) (but defaults to [Symphonia](https://github.com/pdeljanov/Symphonia)). 12 | - WAV by [hound](https://github.com/ruud-v-a/hound). 13 | - Vorbis by [lewton](https://github.com/est31/lewton). 14 | - FLAC by [claxon](https://github.com/ruuda/claxon). 15 | - MP4 and AAC (both disabled by default) are handled only by [Symphonia](https://github.com/pdeljanov/Symphonia). 16 | 17 | See [the docs](https://docs.rs/rodio/latest/rodio/#alternative-decoder-backends) for more details on backends. 18 | 19 | # [Documentation](http://docs.rs/rodio) 20 | 21 | [The documentation](http://docs.rs/rodio) contains an introduction to the library. 22 | 23 | # [Examples](https://github.com/RustAudio/rodio/tree/f1eaaa4a6346933fc8a58d5fd1ace170946b3a94/examples) 24 | 25 | We are currently making large improvements to rodio. This does mean the updated examples do not work with the current crates.io release. You will have to look at the examples from commit `f1eaaa4a`. They are available [on github](https://github.com/RustAudio/rodio/tree/f1eaaa4a6346933fc8a58d5fd1ace170946b3a94/examples). 26 | 27 | ## Requirements 28 | 29 | Rodio playback works in environments supported by [cpal](https://github.com/RustAudio/cpal) library. 30 | 31 | The CPU of the target system should have hardware support for 32-bit floating point (`f32`), and atomic operations that are at least 32 bit wide. Without these the CPU may not be fast enough to keep up with real-time. 32 | 33 | ## Dependencies (Linux only) 34 | 35 | Rodio uses `cpal` library to send audio to the OS for playback. ALSA development files are needed to build `cpal` on Linux. These are provided as part of the `libasound2-dev` package on Debian and Ubuntu distributions and `alsa-lib-devel` on Fedora. 36 | 37 | ### Minimal build 38 | 39 | It is possible to build `rodio` without support for audio playback. In this configuration `cpal` dependency and its requirements are excluded. This configuration may be useful, for example, for decoding and processing audio in environments when the audio output is not available (e.g. in case of Linux, when ALSA is not available). See `into_file` example that works with this build. 40 | 41 | In order to use `rodio` in this configuration disable default features and add the necessary ones. In this case the `Cargo.toml` dependency would look like: 42 | ```toml 43 | [dependencies] 44 | rodio = { version = "0.20.1", default-features = false, features = ["symphonia-all"] } 45 | ``` 46 | ### Cross compling aarch64/arm 47 | 48 | Through cpal rodio depends on the alsa library (libasound & libasound-dev), this can make crosscompiling hard. Cpal has some guides on crosscompling in their Readme (https://github.com/RustAudio/cpal). They are missing instructions on aarch64 (arm linux) so we have some here: 49 | 50 | #### aarch64/arm on Debian like (Ubuntu/pop) 51 | - Install crossbuild-essential-arm64: `sudo apt-get install crossbuild-essential-arm64 clang` 52 | - Add the aarch64 target for rust: `rustup target add aarch64-unknown-linux-gnu` 53 | - Add the architecture arm64 to apt using: `sudo dpkg --add-architecture arm64` 54 | - Install the [multi-arch](https://wiki.debian.org/Multiarch/HOWTO) version of libasound2-dev for arm64 using: `sudo apt install libasound2-dev:arm64` 55 | - Build with the pkg config sysroot set to /usr/aarch64-linux-gnu and aarch64-linux-gnu as linker: `PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc" cargo build --target aarch64-unknown-linux-gnu` 56 | 57 | This will work for other Linux targets too if you change the architecture in the 58 | command and if there are multi-arch packages available. 59 | 60 | You might want to look at [cross](https://github.com/cross-rs/cross) if you are 61 | running on a non debian system or want to make this more repeatable. 62 | 63 | # Contributing 64 | 65 | For information on how to contribute to this project, please see our [Contributing Guide](CONTRIBUTING.md). 66 | 67 | ## License 68 | [License]: #license 69 | 70 | Licensed under either of 71 | 72 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0), or 73 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 74 | 75 | at your option. 76 | 77 | ### License of your contributions 78 | 79 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 80 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | This guide will help you update your code when upgrading from older versions of rodio. 2 | 3 | # rodio 0.20.1 or earlier to current GitHub version 4 | 5 | ## Features 6 | - If you use disable the rodio features with `default_features = false` in 7 | `Cargo.toml` you need to add a new feature `playback`. 8 | 9 | ## Source implementations 10 | - Source had a required method `current_frame_len`. In the latest version of rodio *frame* has been renamed to *span*. You will need to change every occurrence of `current_frame_len` to `current_span_len`. 11 | 12 | ## OutputStream 13 | - The outputstream is now more configurable. Where you used `OutputStream::try_default()` you have a choice: 14 | - *(recommended)* Get an error when the default stream could not be opened: `OutputStreamBuilder::open_default_stream()?` 15 | - Stay close to the old behavior using: 16 | `OutputStreamBuilder::open_stream_or_fallback()`, which tries to open the 17 | default (audio) stream. If that fails it tries all other combinations of 18 | device and settings. The old behavior was only trying all settings of the 19 | default device. 20 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | ## License 2 | 3 | The `music.wav` and `music.ogg` files in this directory are under cc-by-sa. 4 | -------------------------------------------------------------------------------- /assets/RL.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/RL.flac -------------------------------------------------------------------------------- /assets/RL.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/RL.m4a -------------------------------------------------------------------------------- /assets/RL.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/RL.mp3 -------------------------------------------------------------------------------- /assets/RL.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/RL.ogg -------------------------------------------------------------------------------- /assets/RL.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/RL.wav -------------------------------------------------------------------------------- /assets/audacity16bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/audacity16bit.wav -------------------------------------------------------------------------------- /assets/audacity16bit_level5.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/audacity16bit_level5.flac -------------------------------------------------------------------------------- /assets/audacity24bit_level0.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/audacity24bit_level0.flac -------------------------------------------------------------------------------- /assets/audacity24bit_level5.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/audacity24bit_level5.flac -------------------------------------------------------------------------------- /assets/audacity24bit_level8.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/audacity24bit_level8.flac -------------------------------------------------------------------------------- /assets/audacity32bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/audacity32bit.wav -------------------------------------------------------------------------------- /assets/audacity32bit_int.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/audacity32bit_int.wav -------------------------------------------------------------------------------- /assets/beep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/beep.wav -------------------------------------------------------------------------------- /assets/beep2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/beep2.wav -------------------------------------------------------------------------------- /assets/beep3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/beep3.ogg -------------------------------------------------------------------------------- /assets/lmms16bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/lmms16bit.wav -------------------------------------------------------------------------------- /assets/lmms24bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/lmms24bit.wav -------------------------------------------------------------------------------- /assets/lmms32bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/lmms32bit.wav -------------------------------------------------------------------------------- /assets/monkeys.mp4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/monkeys.mp4a -------------------------------------------------------------------------------- /assets/music.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/music.flac -------------------------------------------------------------------------------- /assets/music.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/music.m4a -------------------------------------------------------------------------------- /assets/music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/music.mp3 -------------------------------------------------------------------------------- /assets/music.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/music.ogg -------------------------------------------------------------------------------- /assets/music.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/rodio/5df626a204566dbc8713b3c55bedc9124d21503a/assets/music.wav -------------------------------------------------------------------------------- /benches/conversions.rs: -------------------------------------------------------------------------------- 1 | use dasp_sample::{Duplex, Sample}; 2 | use divan::Bencher; 3 | use rodio::conversions::SampleTypeConverter; 4 | 5 | mod shared; 6 | 7 | fn main() { 8 | divan::main(); 9 | } 10 | 11 | #[divan::bench(types = [i16, u16, f32])] 12 | fn from_sample>(bencher: Bencher) { 13 | bencher 14 | .with_inputs(|| { 15 | shared::music_wav() 16 | .map(|s| s.to_sample::()) 17 | .collect::>() 18 | .into_iter() 19 | }) 20 | .bench_values(|source| { 21 | SampleTypeConverter::<_, rodio::Sample>::new(source).for_each(divan::black_box_drop) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /benches/effects.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use divan::Bencher; 4 | use rodio::Source; 5 | 6 | mod shared; 7 | use shared::music_wav; 8 | 9 | fn main() { 10 | divan::main(); 11 | } 12 | 13 | #[divan::bench] 14 | fn reverb(bencher: Bencher) { 15 | bencher.with_inputs(music_wav).bench_values(|source| { 16 | source 17 | .buffered() 18 | .reverb(Duration::from_secs_f32(0.05), 0.3) 19 | .for_each(divan::black_box_drop) 20 | }) 21 | } 22 | 23 | #[divan::bench] 24 | fn high_pass(bencher: Bencher) { 25 | bencher 26 | .with_inputs(music_wav) 27 | .bench_values(|source| source.high_pass(200).for_each(divan::black_box_drop)) 28 | } 29 | 30 | #[divan::bench] 31 | fn fade_out(bencher: Bencher) { 32 | bencher.with_inputs(music_wav).bench_values(|source| { 33 | source 34 | .fade_out(Duration::from_secs(5)) 35 | .for_each(divan::black_box_drop) 36 | }) 37 | } 38 | 39 | #[divan::bench] 40 | fn amplify(bencher: Bencher) { 41 | bencher 42 | .with_inputs(music_wav) 43 | .bench_values(|source| source.amplify(0.8).for_each(divan::black_box_drop)) 44 | } 45 | 46 | #[divan::bench] 47 | fn agc_enabled(bencher: Bencher) { 48 | bencher.with_inputs(music_wav).bench_values(|source| { 49 | source 50 | .automatic_gain_control( 51 | 1.0, // target_level 52 | 4.0, // attack_time (in seconds) 53 | 0.005, // release_time (in seconds) 54 | 5.0, // absolute_max_gain 55 | ) 56 | .for_each(divan::black_box_drop) 57 | }) 58 | } 59 | 60 | #[cfg(feature = "experimental")] 61 | #[divan::bench] 62 | fn agc_disabled(bencher: Bencher) { 63 | bencher.with_inputs(|| music_wav()).bench_values(|source| { 64 | // Create the AGC source 65 | let amplified_source = source.automatic_gain_control( 66 | 1.0, // target_level 67 | 4.0, // attack_time (in seconds) 68 | 0.005, // release_time (in seconds) 69 | 5.0, // absolute_max_gain 70 | ); 71 | 72 | // Get the control handle and disable AGC 73 | let agc_control = amplified_source.get_agc_control(); 74 | agc_control.store(false, std::sync::atomic::Ordering::Relaxed); 75 | 76 | // Process the audio stream with AGC disabled 77 | amplified_source.for_each(divan::black_box_drop) 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /benches/pipeline.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use divan::Bencher; 4 | use rodio::{source::UniformSourceIterator, Source}; 5 | 6 | mod shared; 7 | use shared::music_wav; 8 | 9 | fn main() { 10 | divan::main(); 11 | } 12 | 13 | #[divan::bench] 14 | fn long(bencher: Bencher) { 15 | bencher.with_inputs(music_wav).bench_values(|source| { 16 | let mut take_dur = source 17 | .high_pass(300) 18 | .amplify(1.2) 19 | .speed(0.9) 20 | .automatic_gain_control( 21 | 1.0, // target_level 22 | 4.0, // attack_time (in seconds) 23 | 0.005, // release_time (in seconds) 24 | 5.0, // absolute_max_gain 25 | ) 26 | .delay(Duration::from_secs_f32(0.5)) 27 | .fade_in(Duration::from_secs_f32(2.0)) 28 | .take_duration(Duration::from_secs(10)); 29 | take_dur.set_filter_fadeout(); 30 | let effects_applied = take_dur 31 | .buffered() 32 | .reverb(Duration::from_secs_f32(0.05), 0.3) 33 | .skippable(); 34 | let resampled = UniformSourceIterator::new(effects_applied, 2, 40_000); 35 | resampled.for_each(divan::black_box_drop) 36 | }) 37 | } 38 | 39 | #[divan::bench] 40 | fn short(bencher: Bencher) { 41 | bencher.with_inputs(music_wav).bench_values(|source| { 42 | source 43 | .amplify(1.2) 44 | .low_pass(200) 45 | .for_each(divan::black_box_drop) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /benches/resampler.rs: -------------------------------------------------------------------------------- 1 | use divan::Bencher; 2 | use rodio::source::UniformSourceIterator; 3 | 4 | mod shared; 5 | use shared::music_wav; 6 | 7 | use rodio::Source; 8 | 9 | fn main() { 10 | divan::main(); 11 | } 12 | 13 | #[divan::bench] 14 | fn no_resampling(bencher: Bencher) { 15 | bencher 16 | .with_inputs(|| { 17 | let source = music_wav(); 18 | (source.channels(), source.sample_rate(), source) 19 | }) 20 | .bench_values(|(channels, sample_rate, source)| { 21 | UniformSourceIterator::<_>::new(source, channels, sample_rate) 22 | .for_each(divan::black_box_drop) 23 | }) 24 | } 25 | 26 | // taken from: https://github.com/audiojs/sample-rate/readme.md commit: be31b67 27 | const COMMON_SAMPLE_RATES: [u32; 12] = [ 28 | 8_000, 11_025, 16_000, 22_050, 44_100, 48_000, 88_200, 96_000, 176_400, 192_000, 352_800, 29 | 384_000, 30 | ]; 31 | 32 | #[divan::bench(args = COMMON_SAMPLE_RATES)] 33 | fn resample_to(bencher: Bencher, target_sample_rate: u32) { 34 | bencher 35 | .with_inputs(|| { 36 | let source = music_wav(); 37 | (source.channels(), source) 38 | }) 39 | .bench_values(|(channels, source)| { 40 | UniformSourceIterator::<_>::new(source, channels, target_sample_rate) 41 | .for_each(divan::black_box_drop) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /benches/shared.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | use std::time::Duration; 3 | use std::vec; 4 | 5 | use rodio::{ChannelCount, Sample, SampleRate, Source}; 6 | 7 | pub struct TestSource { 8 | samples: vec::IntoIter, 9 | channels: u16, 10 | sample_rate: u32, 11 | total_duration: Duration, 12 | } 13 | 14 | impl Iterator for TestSource { 15 | type Item = Sample; 16 | 17 | #[inline] 18 | fn next(&mut self) -> Option { 19 | self.samples.next() 20 | } 21 | } 22 | 23 | impl ExactSizeIterator for TestSource { 24 | #[inline] 25 | fn len(&self) -> usize { 26 | self.samples.len() 27 | } 28 | } 29 | 30 | impl Source for TestSource { 31 | #[inline] 32 | fn current_span_len(&self) -> Option { 33 | None // forever 34 | } 35 | 36 | #[inline] 37 | fn channels(&self) -> ChannelCount { 38 | self.channels 39 | } 40 | 41 | #[inline] 42 | fn sample_rate(&self) -> SampleRate { 43 | self.sample_rate 44 | } 45 | 46 | #[inline] 47 | fn total_duration(&self) -> Option { 48 | Some(self.total_duration) 49 | } 50 | } 51 | 52 | pub fn music_wav() -> TestSource { 53 | let data = include_bytes!("../assets/music.wav"); 54 | let cursor = Cursor::new(data); 55 | 56 | let duration = Duration::from_secs(10); 57 | let sound = rodio::Decoder::new(cursor) 58 | .expect("music.wav is correctly encoded & wav is supported") 59 | .take_duration(duration); 60 | 61 | TestSource { 62 | channels: sound.channels(), 63 | sample_rate: sound.sample_rate(), 64 | total_duration: duration, 65 | samples: sound.into_iter().collect::>().into_iter(), 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/automatic_gain_control.rs: -------------------------------------------------------------------------------- 1 | use rodio::source::Source; 2 | use rodio::Decoder; 3 | use std::error::Error; 4 | use std::fs::File; 5 | use std::sync::atomic::{AtomicBool, Ordering}; 6 | use std::sync::Arc; 7 | use std::thread; 8 | use std::time::Duration; 9 | 10 | fn main() -> Result<(), Box> { 11 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 12 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 13 | 14 | // Decode the sound file into a source 15 | let file = File::open("assets/music.flac")?; 16 | let source = Decoder::try_from(file)?; 17 | 18 | // Apply automatic gain control to the source 19 | let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0); 20 | 21 | // Make it so that the source checks if automatic gain control should be 22 | // enabled or disabled every 5 milliseconds. We must clone `agc_enabled`, 23 | // or we would lose it when we move it into the periodic access. 24 | let agc_enabled = Arc::new(AtomicBool::new(true)); 25 | let agc_enabled_clone = agc_enabled.clone(); 26 | let controlled = agc_source.periodic_access(Duration::from_millis(5), move |agc_source| { 27 | agc_source.set_enabled(agc_enabled_clone.load(Ordering::Relaxed)); 28 | }); 29 | 30 | // Add the source now equipped with automatic gain control and controlled via 31 | // periodic_access to the sink for the playback. 32 | sink.append(controlled); 33 | 34 | // After 5 seconds of playback disable automatic gain control using the 35 | // shared AtomicBool `agc_enabled`. You could do this from another part 36 | // of the program since `agc_enabled` is of type Arc which 37 | // is freely clone-able and move-able. 38 | // 39 | // Note that disabling the AGC takes up to 5 millis because periodic_access 40 | // controls the source every 5 millis. 41 | thread::sleep(Duration::from_secs(5)); 42 | agc_enabled.store(false, Ordering::Relaxed); 43 | 44 | // Keep the program running until the playback is complete. 45 | sink.sleep_until_end(); 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | use rodio::source::SineWave; 2 | use rodio::Source; 3 | use std::error::Error; 4 | use std::io::BufReader; 5 | use std::thread; 6 | use std::time::Duration; 7 | 8 | fn main() -> Result<(), Box> { 9 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 10 | let mixer = stream_handle.mixer(); 11 | 12 | let beep1 = { 13 | // Play a WAV file. 14 | let file = std::fs::File::open("assets/beep.wav")?; 15 | let sink = rodio::play(mixer, BufReader::new(file))?; 16 | sink.set_volume(0.2); 17 | sink 18 | }; 19 | println!("Started beep1"); 20 | thread::sleep(Duration::from_millis(1500)); 21 | 22 | { 23 | // Generate sine wave. 24 | let wave = SineWave::new(740.0) 25 | .amplify(0.2) 26 | .take_duration(Duration::from_secs(3)); 27 | mixer.add(wave); 28 | } 29 | println!("Started beep2"); 30 | thread::sleep(Duration::from_millis(1500)); 31 | 32 | let beep3 = { 33 | // Play an OGG file. 34 | let file = std::fs::File::open("assets/beep3.ogg")?; 35 | let sink = rodio::play(mixer, BufReader::new(file))?; 36 | sink.set_volume(0.2); 37 | sink 38 | }; 39 | println!("Started beep3"); 40 | thread::sleep(Duration::from_millis(1500)); 41 | 42 | drop(beep1); 43 | println!("Stopped beep1"); 44 | 45 | thread::sleep(Duration::from_millis(1500)); 46 | drop(beep3); 47 | println!("Stopped beep3"); 48 | 49 | thread::sleep(Duration::from_millis(1500)); 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /examples/callback_on_end.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::sync::atomic::{AtomicU32, Ordering}; 3 | use std::sync::Arc; 4 | 5 | fn main() -> Result<(), Box> { 6 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 7 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 8 | 9 | let file = std::fs::File::open("assets/music.wav")?; 10 | sink.append(rodio::Decoder::try_from(file)?); 11 | 12 | // lets increment a number after `music.wav` has played. We are going to use atomics 13 | // however you could also use a `Mutex` or send a message through a `std::sync::mpsc`. 14 | let playlist_pos = Arc::new(AtomicU32::new(0)); 15 | 16 | // The closure needs to own everything it uses. We move a clone of 17 | // playlist_pos into the closure. That way we can still access playlist_pos 18 | // after appending the EmptyCallback. 19 | let playlist_pos_clone = playlist_pos.clone(); 20 | sink.append(rodio::source::EmptyCallback::new(Box::new(move || { 21 | println!("empty callback is now running"); 22 | playlist_pos_clone.fetch_add(1, Ordering::Relaxed); 23 | }))); 24 | 25 | assert_eq!(playlist_pos.load(Ordering::Relaxed), 0); 26 | println!( 27 | "playlist position is: {}", 28 | playlist_pos.load(Ordering::Relaxed) 29 | ); 30 | sink.sleep_until_end(); 31 | assert_eq!(playlist_pos.load(Ordering::Relaxed), 1); 32 | println!( 33 | "playlist position is: {}", 34 | playlist_pos.load(Ordering::Relaxed) 35 | ); 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /examples/custom_config.rs: -------------------------------------------------------------------------------- 1 | use cpal::traits::HostTrait; 2 | use cpal::{BufferSize, SampleFormat}; 3 | use rodio::source::SineWave; 4 | use rodio::Source; 5 | use std::error::Error; 6 | use std::thread; 7 | use std::time::Duration; 8 | 9 | fn main() -> Result<(), Box> { 10 | // You can use any other output device that can be queried from CPAL. 11 | let default_device = cpal::default_host() 12 | .default_output_device() 13 | .ok_or("No default audio output device is found.")?; 14 | let stream_handle = rodio::OutputStreamBuilder::from_device(default_device)? 15 | // No need to set all parameters explicitly here, 16 | // the defaults were set from the device's description. 17 | .with_buffer_size(BufferSize::Fixed(256)) 18 | .with_sample_rate(48_000) 19 | .with_sample_format(SampleFormat::F32) 20 | // Note that the function below still tries alternative configs if the specified one fails. 21 | // If you need to only use the exact specified configuration, 22 | // then use OutputStreamBuilder::open_stream() instead. 23 | .open_stream_or_fallback()?; 24 | let mixer = stream_handle.mixer(); 25 | 26 | let wave = SineWave::new(740.0) 27 | .amplify(0.1) 28 | .take_duration(Duration::from_secs(1)); 29 | mixer.add(wave); 30 | 31 | println!("Beep..."); 32 | thread::sleep(Duration::from_millis(1500)); 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /examples/distortion.rs: -------------------------------------------------------------------------------- 1 | use rodio::source::{SineWave, Source}; 2 | use std::error::Error; 3 | use std::thread; 4 | use std::time::Duration; 5 | 6 | fn main() -> Result<(), Box> { 7 | // Open the default output stream and get the mixer 8 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 9 | let mixer = stream_handle.mixer(); 10 | 11 | // Create a sine wave source and apply distortion 12 | let distorted = SineWave::new(440.0) 13 | .amplify(0.2) 14 | .distortion(4.0, 0.3) 15 | .take_duration(Duration::from_secs(3)); 16 | 17 | // Play the distorted sound 18 | mixer.add(distorted); 19 | 20 | println!("Playing distorted sine wave for 3 seconds..."); 21 | thread::sleep(Duration::from_secs(3)); 22 | println!("Done."); 23 | 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /examples/distortion_mp3.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use rodio::Source; 4 | 5 | fn main() -> Result<(), Box> { 6 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 7 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 8 | 9 | let file = std::fs::File::open("assets/music.mp3")?; 10 | // Apply distortion effect before appending to the sink 11 | let source = rodio::Decoder::try_from(file)?.distortion(4.0, 0.3); 12 | sink.append(source); 13 | 14 | sink.sleep_until_end(); 15 | 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /examples/distortion_wav.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use rodio::Source; 4 | 5 | fn main() -> Result<(), Box> { 6 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 7 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 8 | 9 | let file = std::fs::File::open("assets/music.wav")?; 10 | // Apply distortion effect before appending to the sink 11 | let source = rodio::Decoder::try_from(file)?.distortion(4.0, 0.3); 12 | sink.append(source); 13 | 14 | sink.sleep_until_end(); 15 | 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /examples/distortion_wav_alternate.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::sync::{ 3 | atomic::{AtomicBool, Ordering}, 4 | Arc, 5 | }; 6 | use std::thread; 7 | use std::time::Duration; 8 | 9 | use rodio::Source; 10 | 11 | fn main() -> Result<(), Box> { 12 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 13 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 14 | 15 | let file = std::fs::File::open("assets/music.wav")?; 16 | let source = rodio::Decoder::try_from(file)?; 17 | 18 | // Shared flag to enable/disable distortion 19 | let distortion_enabled = Arc::new(AtomicBool::new(true)); 20 | let distortion_enabled_clone = distortion_enabled.clone(); 21 | 22 | // Apply distortion and alternate the effect during playback 23 | let distorted = 24 | source 25 | .distortion(4.0, 0.3) 26 | .periodic_access(Duration::from_millis(250), move |src| { 27 | // src is &mut PeriodicAccess>> 28 | let enable = distortion_enabled_clone.load(Ordering::Relaxed); 29 | // Call the setters on the distortion filter inside the source 30 | src.set_gain(if enable { 4.0 } else { 1.0 }); 31 | src.set_threshold(if enable { 0.3 } else { 1.0 }); 32 | }); 33 | 34 | sink.append(distorted); 35 | 36 | println!("Playing music.wav with alternating distortion effect..."); 37 | // Alternate the distortion effect every second for 10 seconds 38 | for _ in 0..10 { 39 | thread::sleep(Duration::from_secs(1)); 40 | let prev = distortion_enabled.load(Ordering::Relaxed); 41 | distortion_enabled.store(!prev, Ordering::Relaxed); 42 | println!("Distortion {}", if !prev { "ON" } else { "OFF" }); 43 | } 44 | 45 | // Wait for playback to finish 46 | sink.sleep_until_end(); 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /examples/error_callback.rs: -------------------------------------------------------------------------------- 1 | use cpal::traits::HostTrait; 2 | use rodio::source::SineWave; 3 | use rodio::Source; 4 | use std::error::Error; 5 | use std::time::Duration; 6 | 7 | fn main() -> Result<(), Box> { 8 | // You can use any other output device that can be queried from CPAL. 9 | let default_device = cpal::default_host() 10 | .default_output_device() 11 | .ok_or("No default audio output device is found.")?; 12 | 13 | let (tx, rx) = std::sync::mpsc::channel(); 14 | 15 | let stream_handle = rodio::OutputStreamBuilder::from_device(default_device)? 16 | .with_error_callback(move |err| { 17 | // Filter for where err is a DeviceNotAvailable error. 18 | if let cpal::StreamError::DeviceNotAvailable = err { 19 | if let Err(e) = tx.send(err) { 20 | eprintln!("Error emitting StreamError: {}", e); 21 | } 22 | } 23 | }) 24 | .open_stream_or_fallback()?; 25 | 26 | let mixer = stream_handle.mixer(); 27 | 28 | let wave = SineWave::new(740.0) 29 | .amplify(0.1) 30 | .take_duration(Duration::from_secs(30)); 31 | mixer.add(wave); 32 | 33 | if let Ok(err) = rx.recv_timeout(Duration::from_secs(30)) { 34 | // Here we print the error that was emitted by the error callback. 35 | // but in a real application we may want to destroy the stream and 36 | // try to reopen it, either with the same device or a different one. 37 | eprintln!("Error with stream {}", err); 38 | } 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /examples/into_file.rs: -------------------------------------------------------------------------------- 1 | use rodio::{output_to_wav, Source}; 2 | use std::error::Error; 3 | 4 | /// Converts mp3 file to a wav file. 5 | /// This example does not use any audio devices 6 | /// and can be used in build configurations without `cpal` feature enabled. 7 | fn main() -> Result<(), Box> { 8 | let file = std::fs::File::open("assets/music.mp3")?; 9 | let mut audio = rodio::Decoder::try_from(file)? 10 | .automatic_gain_control(1.0, 4.0, 0.005, 3.0) 11 | .speed(0.8); 12 | 13 | let wav_path = "music_mp3_converted.wav"; 14 | println!("Storing converted audio into {}", wav_path); 15 | output_to_wav(&mut audio, wav_path)?; 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /examples/low_pass.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::io::BufReader; 3 | 4 | use rodio::Source; 5 | 6 | fn main() -> Result<(), Box> { 7 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 8 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 9 | 10 | let file = std::fs::File::open("assets/music.wav")?; 11 | let decoder = rodio::Decoder::new(BufReader::new(file))?; 12 | let source = decoder.low_pass(200); 13 | sink.append(source); 14 | 15 | sink.sleep_until_end(); 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /examples/mix_multiple_sources.rs: -------------------------------------------------------------------------------- 1 | use rodio::mixer; 2 | use rodio::source::{SineWave, Source}; 3 | use std::error::Error; 4 | use std::time::Duration; 5 | 6 | fn main() -> Result<(), Box> { 7 | // Construct a dynamic controller and mixer, stream_handle, and sink. 8 | let (controller, mixer) = mixer::mixer(2, 44_100); 9 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 10 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 11 | 12 | // Create four unique sources. The frequencies used here correspond 13 | // notes in the key of C and in octave 4: C4, or middle C on a piano, 14 | // E4, G4, and A4 respectively. 15 | let source_c = SineWave::new(261.63) 16 | .take_duration(Duration::from_secs_f32(1.)) 17 | .amplify(0.20); 18 | let source_e = SineWave::new(329.63) 19 | .take_duration(Duration::from_secs_f32(1.)) 20 | .amplify(0.20); 21 | let source_g = SineWave::new(392.0) 22 | .take_duration(Duration::from_secs_f32(1.)) 23 | .amplify(0.20); 24 | let source_a = SineWave::new(440.0) 25 | .take_duration(Duration::from_secs_f32(1.)) 26 | .amplify(0.20); 27 | 28 | // Add sources C, E, G, and A to the mixer controller. 29 | controller.add(source_c); 30 | controller.add(source_e); 31 | controller.add(source_g); 32 | controller.add(source_a); 33 | 34 | // Append the dynamic mixer to the sink to play a C major 6th chord. 35 | sink.append(mixer); 36 | 37 | // Sleep the thread until sink is empty. 38 | sink.sleep_until_end(); 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /examples/music_flac.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | fn main() -> Result<(), Box> { 4 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 5 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 6 | 7 | let file = std::fs::File::open("assets/music.flac")?; 8 | sink.append(rodio::Decoder::try_from(file)?); 9 | 10 | sink.sleep_until_end(); 11 | 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /examples/music_m4a.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | fn main() -> Result<(), Box> { 4 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 5 | let sink = rodio::Sink::connect_new(&stream_handle.mixer()); 6 | 7 | let file = std::fs::File::open("assets/music.m4a")?; 8 | sink.append(rodio::Decoder::try_from(file)?); 9 | 10 | sink.sleep_until_end(); 11 | 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /examples/music_mp3.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | fn main() -> Result<(), Box> { 4 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 5 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 6 | 7 | let file = std::fs::File::open("assets/music.mp3")?; 8 | sink.append(rodio::Decoder::try_from(file)?); 9 | 10 | sink.sleep_until_end(); 11 | 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /examples/music_ogg.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | fn main() -> Result<(), Box> { 4 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 5 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 6 | 7 | let file = std::fs::File::open("assets/music.ogg")?; 8 | sink.append(rodio::Decoder::try_from(file)?); 9 | 10 | sink.sleep_until_end(); 11 | 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /examples/music_wav.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | fn main() -> Result<(), Box> { 4 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 5 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 6 | 7 | let file = std::fs::File::open("assets/music.wav")?; 8 | sink.append(rodio::Decoder::try_from(file)?); 9 | 10 | sink.sleep_until_end(); 11 | 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /examples/noise_generator.rs: -------------------------------------------------------------------------------- 1 | //! Noise generator example. Use the "noise" feature to enable the noise generator sources. 2 | 3 | use std::error::Error; 4 | 5 | fn main() -> Result<(), Box> { 6 | use rodio::source::{pink, white, Source}; 7 | use std::thread; 8 | use std::time::Duration; 9 | 10 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 11 | 12 | let noise_duration = Duration::from_millis(1000); 13 | let interval_duration = Duration::from_millis(1500); 14 | 15 | stream_handle 16 | .mixer() 17 | .add(white(48000).amplify(0.1).take_duration(noise_duration)); 18 | println!("Playing white noise"); 19 | 20 | thread::sleep(interval_duration); 21 | 22 | stream_handle 23 | .mixer() 24 | .add(pink(48000).amplify(0.1).take_duration(noise_duration)); 25 | println!("Playing pink noise"); 26 | 27 | thread::sleep(interval_duration); 28 | 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /examples/reverb.rs: -------------------------------------------------------------------------------- 1 | use rodio::Source; 2 | use std::error::Error; 3 | use std::time::Duration; 4 | 5 | fn main() -> Result<(), Box> { 6 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 7 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 8 | 9 | let file = std::fs::File::open("assets/music.ogg")?; 10 | let source = rodio::Decoder::try_from(file)?; 11 | let with_reverb = source.buffered().reverb(Duration::from_millis(40), 0.7); 12 | sink.append(with_reverb); 13 | 14 | sink.sleep_until_end(); 15 | 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /examples/seek_mp3.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::time::Duration; 3 | 4 | fn main() -> Result<(), Box> { 5 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 6 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 7 | 8 | let file = std::fs::File::open("assets/music.mp3")?; 9 | sink.append(rodio::Decoder::try_from(file)?); 10 | 11 | std::thread::sleep(std::time::Duration::from_secs(2)); 12 | sink.try_seek(Duration::from_secs(0))?; 13 | 14 | std::thread::sleep(std::time::Duration::from_secs(2)); 15 | sink.try_seek(Duration::from_secs(4))?; 16 | 17 | sink.sleep_until_end(); 18 | 19 | // This doesn't do anything since the sound has ended already. 20 | sink.try_seek(Duration::from_secs(5))?; 21 | println!("seek example ended"); 22 | 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /examples/signal_generator.rs: -------------------------------------------------------------------------------- 1 | //! Test signal generator example. 2 | 3 | use std::error::Error; 4 | 5 | fn main() -> Result<(), Box> { 6 | use rodio::source::{chirp, Function, SignalGenerator, Source}; 7 | use std::thread; 8 | use std::time::Duration; 9 | 10 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 11 | 12 | let test_signal_duration = Duration::from_millis(1000); 13 | let interval_duration = Duration::from_millis(1500); 14 | let sample_rate = 48000; 15 | 16 | println!("Playing 1000 Hz tone"); 17 | stream_handle.mixer().add( 18 | SignalGenerator::new(sample_rate, 1000.0, Function::Sine) 19 | .amplify(0.1) 20 | .take_duration(test_signal_duration), 21 | ); 22 | 23 | thread::sleep(interval_duration); 24 | 25 | println!("Playing 10,000 Hz tone"); 26 | stream_handle.mixer().add( 27 | SignalGenerator::new(sample_rate, 10000.0, Function::Sine) 28 | .amplify(0.1) 29 | .take_duration(test_signal_duration), 30 | ); 31 | 32 | thread::sleep(interval_duration); 33 | 34 | println!("Playing 440 Hz Triangle Wave"); 35 | stream_handle.mixer().add( 36 | SignalGenerator::new(sample_rate, 440.0, Function::Triangle) 37 | .amplify(0.1) 38 | .take_duration(test_signal_duration), 39 | ); 40 | 41 | thread::sleep(interval_duration); 42 | 43 | println!("Playing 440 Hz Sawtooth Wave"); 44 | stream_handle.mixer().add( 45 | SignalGenerator::new(sample_rate, 440.0, Function::Sawtooth) 46 | .amplify(0.1) 47 | .take_duration(test_signal_duration), 48 | ); 49 | 50 | thread::sleep(interval_duration); 51 | 52 | println!("Playing 440 Hz Square Wave"); 53 | stream_handle.mixer().add( 54 | SignalGenerator::new(sample_rate, 440.0, Function::Square) 55 | .amplify(0.1) 56 | .take_duration(test_signal_duration), 57 | ); 58 | 59 | thread::sleep(interval_duration); 60 | 61 | println!("Playing 20-10000 Hz Sweep"); 62 | stream_handle.mixer().add( 63 | chirp(sample_rate, 20.0, 10000.0, Duration::from_secs(1)) 64 | .amplify(0.1) 65 | .take_duration(test_signal_duration), 66 | ); 67 | 68 | thread::sleep(interval_duration); 69 | 70 | Ok(()) 71 | } 72 | -------------------------------------------------------------------------------- /examples/spatial.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::thread; 3 | use std::time::Duration; 4 | 5 | use rodio::Source; 6 | 7 | fn main() -> Result<(), Box> { 8 | let iter_duration = Duration::from_secs(5); 9 | let iter_distance = 5.; 10 | 11 | let refresh_duration = Duration::from_millis(10); 12 | 13 | let num_steps = iter_duration.as_secs_f32() / refresh_duration.as_secs_f32(); 14 | let step_distance = iter_distance / num_steps; 15 | let num_steps = num_steps as u32; 16 | 17 | let repeats = 5; 18 | 19 | let total_duration = iter_duration * 2 * repeats; 20 | 21 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 22 | 23 | let mut positions = ([0., 0., 0.], [-1., 0., 0.], [1., 0., 0.]); 24 | let sink = rodio::SpatialSink::connect_new( 25 | stream_handle.mixer(), 26 | positions.0, 27 | positions.1, 28 | positions.2, 29 | ); 30 | 31 | let file = std::fs::File::open("assets/music.ogg")?; 32 | let source = rodio::Decoder::try_from(file)? 33 | .repeat_infinite() 34 | .take_duration(total_duration); 35 | sink.append(source); 36 | // A sound emitter playing the music starting at the centre gradually moves to the right 37 | // until it stops and begins traveling to the left, it will eventually pass through the 38 | // listener again and go to the far left. 39 | // This is repeated 5 times. 40 | for _ in 0..repeats { 41 | for _ in 0..num_steps { 42 | thread::sleep(refresh_duration); 43 | positions.0[0] += step_distance; 44 | sink.set_emitter_position(positions.0); 45 | } 46 | for _ in 0..(num_steps * 2) { 47 | thread::sleep(refresh_duration); 48 | positions.0[0] -= step_distance; 49 | sink.set_emitter_position(positions.0); 50 | } 51 | for _ in 0..num_steps { 52 | thread::sleep(refresh_duration); 53 | positions.0[0] += step_distance; 54 | sink.set_emitter_position(positions.0); 55 | } 56 | } 57 | sink.sleep_until_end(); 58 | 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /examples/stereo.rs: -------------------------------------------------------------------------------- 1 | //! Plays a tone alternating between right and left ears, with right being first. 2 | 3 | use rodio::Source; 4 | use std::error::Error; 5 | 6 | fn main() -> Result<(), Box> { 7 | let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; 8 | let sink = rodio::Sink::connect_new(stream_handle.mixer()); 9 | 10 | let file = std::fs::File::open("assets/RL.ogg")?; 11 | sink.append(rodio::Decoder::try_from(file)?.amplify(0.2)); 12 | 13 | sink.sleep_until_end(); 14 | 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /outreach/outreach_to_bevy_audio_discord_channel.txt: -------------------------------------------------------------------------------- 1 | Hi all, I am the current maintainer of Rodio. We recently discovered we need to make a rather large change to rodio to fix a group of bugs. We have a few options. We find it hard to make a choice since none of us know if and how this affects Bevy. 2 | 3 | I think it could make sense to talk to Bevy users and devs. It would help us improve Rodio and we might be able to offer advice on how to best use it in and with Bevy. 4 | 5 | If you have the time please look at https://github.com/RustAudio/rodio/issues/712 and let us know if such a setup would work for you. We would also love your input on whats missing here: https://github.com/RustAudio/rodio/issues/626. 6 | -------------------------------------------------------------------------------- /outreach/outreach_to_bevy_audio_working_group.txt: -------------------------------------------------------------------------------- 1 | Hi all, 2 | 3 | Rodio maintainer here, François was so kind to point this working group out to me. The Rodio team is making great steps in improving Rodio, and a lot has changed in the last year. We are reaching out because we are about to make a rather big change and we want to do that such that it makes Rodio better for game dev (and use from Bevy). We also think Rodio can do more in Bevy then it currenty is. 4 | 5 | I've read through the last few messages here, specifically the summary by @manokara was insightfull. There might be some inaccuracies in this working group knowledge of what rodio can do. That is an issue with Rodio, it lacks documentation regarding more advanced audio pipelines. I will not get into that now but I would like to quickly note a few things relevant to Bevy: 6 | 7 | - Rodio's goal is to allow you to make custom audio graph's (tree shaped). The `Sink` struct is meant for simple audo playback without the need of a graph. 8 | - The file decoders support streaming & seeking . 9 | - Within the tree you can use filters we have some ready made (for example high/low pass) and you can insert custom ones. No need to re-implement decoders. 10 | 11 | I think it could be really helpfull to Bevy and Rodio if I have a call with one of the audio working group members. We could help better integrate rodio in Bevy, discuss if Rodio can fullfill all Bevy's needs and what changes would be needed to do so. 12 | 13 | I look forward to hear from you all, 14 | Best regards, 15 | 16 | David 17 | 18 | Ps: the large change being considerd: https://github.com/RustAudio/rodio/issues/712 19 | Ps: discussion on controlling the graph: https://github.com/RustAudio/rodio/issues/658 20 | -------------------------------------------------------------------------------- /outreach/v0.20_announcement_and_call_for_userstories.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # Announcing rodio 0.20 and call for help 7 | 8 | 9 | Rodio is an audio playback library. It can decode audio files, synthesize new 10 | sounds, apply effects to sounds & mix them. Rodio has been part of the Rust 11 | ecosystem for 9 years now! 🎉. 12 | 13 | ## New release 14 | The rodio contributors have made many improvements in the last 5 months. Rodio can now: 15 | 16 | - Seek back and forth through sound efficiently 17 | - Track the playback position at sample accuracy! 18 | - Generate more signals such as chirps, white & pink noise and different 19 | wavesforms 20 | - Automatically adjust the gain to limit the peak volume and change in loudness 21 | 22 | This is ignoring the many fixes and smaller additions made by the many 23 | contributors who helped out expand rodio. 24 | 25 | ## Call for help 26 | 27 | In its 9 years of existence Rust has changed a lot. Further more Rodio is being 28 | used for applications beyond its original scope. To improve rodio we believe its 29 | time for larger (breaking) changes. 30 | 31 | ### User feedback 32 | To ensure we make the right changes we want 33 | to know what rodio is being used for and what you all would like to use it for. 34 | 35 | We can use any input you have but are especially looking for users who are: 36 | - using rodio and feel some part of the API is hard to use. 37 | - have experienced footguns/pain point 38 | - wanted to use rodio but could not make it fit their use-case (excluding complex 39 | game audio (best served by [kira](https://crates.io/crates/kira)) and advanced 40 | dsp). If you disagree and think rodio can server those excluded use-case too 41 | let us know! 42 | 43 | The best way to leave your feedback is a short user story on our issue 44 | [tracker](https://github.com/RustAudio/rodio/issues). If that is not your thing 45 | any other form posted there works too! 46 | 47 | ### Architecture & API 48 | We can use input on our planned [changes](https://github.com/RustAudio/rodio/issues/614) and how to best implement them. 49 | -------------------------------------------------------------------------------- /src/buffer.rs: -------------------------------------------------------------------------------- 1 | //! A simple source of samples coming from a buffer. 2 | //! 3 | //! The `SamplesBuffer` struct can be used to treat a list of values as a `Source`. 4 | //! 5 | //! # Example 6 | //! 7 | //! ``` 8 | //! use rodio::buffer::SamplesBuffer; 9 | //! let _ = SamplesBuffer::new(1, 44100, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); 10 | //! ``` 11 | //! 12 | 13 | use crate::common::{ChannelCount, SampleRate}; 14 | use crate::source::SeekError; 15 | use crate::{Sample, Source}; 16 | use std::sync::Arc; 17 | use std::time::Duration; 18 | 19 | /// A buffer of samples treated as a source. 20 | #[derive(Debug, Clone)] 21 | pub struct SamplesBuffer { 22 | data: Arc<[Sample]>, 23 | pos: usize, 24 | channels: ChannelCount, 25 | sample_rate: SampleRate, 26 | duration: Duration, 27 | } 28 | 29 | impl SamplesBuffer { 30 | /// Builds a new `SamplesBuffer`. 31 | /// 32 | /// # Panics 33 | /// 34 | /// - Panics if the number of channels is zero. 35 | /// - Panics if the samples rate is zero. 36 | /// - Panics if the length of the buffer is larger than approximately 16 billion elements. 37 | /// This is because the calculation of the duration would overflow. 38 | /// 39 | pub fn new(channels: ChannelCount, sample_rate: SampleRate, data: D) -> SamplesBuffer 40 | where 41 | D: Into>, 42 | { 43 | assert!(channels >= 1); 44 | assert!(sample_rate >= 1); 45 | 46 | let data: Arc<[f32]> = data.into().into(); 47 | let duration_ns = 1_000_000_000u64.checked_mul(data.len() as u64).unwrap() 48 | / sample_rate as u64 49 | / channels as u64; 50 | let duration = Duration::new( 51 | duration_ns / 1_000_000_000, 52 | (duration_ns % 1_000_000_000) as u32, 53 | ); 54 | 55 | SamplesBuffer { 56 | data, 57 | pos: 0, 58 | channels, 59 | sample_rate, 60 | duration, 61 | } 62 | } 63 | } 64 | 65 | impl Source for SamplesBuffer { 66 | #[inline] 67 | fn current_span_len(&self) -> Option { 68 | None 69 | } 70 | 71 | #[inline] 72 | fn channels(&self) -> ChannelCount { 73 | self.channels 74 | } 75 | 76 | #[inline] 77 | fn sample_rate(&self) -> SampleRate { 78 | self.sample_rate 79 | } 80 | 81 | #[inline] 82 | fn total_duration(&self) -> Option { 83 | Some(self.duration) 84 | } 85 | 86 | /// This jumps in memory till the sample for `pos`. 87 | #[inline] 88 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 89 | // This is fast because all the samples are in memory already 90 | // and due to the constant sample_rate we can jump to the right 91 | // sample directly. 92 | 93 | let curr_channel = self.pos % self.channels() as usize; 94 | let new_pos = pos.as_secs_f32() * self.sample_rate() as f32 * self.channels() as f32; 95 | // saturate pos at the end of the source 96 | let new_pos = new_pos as usize; 97 | let new_pos = new_pos.min(self.data.len()); 98 | 99 | // make sure the next sample is for the right channel 100 | let new_pos = new_pos.next_multiple_of(self.channels() as usize); 101 | let new_pos = new_pos - curr_channel; 102 | 103 | self.pos = new_pos; 104 | Ok(()) 105 | } 106 | } 107 | 108 | impl Iterator for SamplesBuffer { 109 | type Item = Sample; 110 | 111 | #[inline] 112 | fn next(&mut self) -> Option { 113 | let sample = self.data.get(self.pos)?; 114 | self.pos += 1; 115 | Some(*sample) 116 | } 117 | 118 | #[inline] 119 | fn size_hint(&self) -> (usize, Option) { 120 | (self.data.len(), Some(self.data.len())) 121 | } 122 | } 123 | 124 | #[cfg(test)] 125 | mod tests { 126 | use crate::buffer::SamplesBuffer; 127 | use crate::source::Source; 128 | 129 | #[test] 130 | fn basic() { 131 | let _ = SamplesBuffer::new(1, 44100, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); 132 | } 133 | 134 | #[test] 135 | #[should_panic] 136 | fn panic_if_zero_channels() { 137 | SamplesBuffer::new(0, 44100, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); 138 | } 139 | 140 | #[test] 141 | #[should_panic] 142 | fn panic_if_zero_sample_rate() { 143 | SamplesBuffer::new(1, 0, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); 144 | } 145 | 146 | #[test] 147 | fn duration_basic() { 148 | let buf = SamplesBuffer::new(2, 2, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); 149 | let dur = buf.total_duration().unwrap(); 150 | assert_eq!(dur.as_secs(), 1); 151 | assert_eq!(dur.subsec_nanos(), 500_000_000); 152 | } 153 | 154 | #[test] 155 | fn iteration() { 156 | let mut buf = SamplesBuffer::new(1, 44100, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); 157 | assert_eq!(buf.next(), Some(1.0)); 158 | assert_eq!(buf.next(), Some(2.0)); 159 | assert_eq!(buf.next(), Some(3.0)); 160 | assert_eq!(buf.next(), Some(4.0)); 161 | assert_eq!(buf.next(), Some(5.0)); 162 | assert_eq!(buf.next(), Some(6.0)); 163 | assert_eq!(buf.next(), None); 164 | } 165 | 166 | #[cfg(test)] 167 | mod try_seek { 168 | use super::*; 169 | use crate::common::{ChannelCount, SampleRate}; 170 | use crate::Sample; 171 | use std::time::Duration; 172 | 173 | #[test] 174 | fn channel_order_stays_correct() { 175 | const SAMPLE_RATE: SampleRate = 100; 176 | const CHANNELS: ChannelCount = 2; 177 | let mut buf = SamplesBuffer::new( 178 | CHANNELS, 179 | SAMPLE_RATE, 180 | (0..2000i16).map(|s| s as Sample).collect::>(), 181 | ); 182 | buf.try_seek(Duration::from_secs(5)).unwrap(); 183 | assert_eq!(buf.next(), Some(5.0 * SAMPLE_RATE as f32 * CHANNELS as f32)); 184 | 185 | assert!(buf.next().is_some_and(|s| s.trunc() as i32 % 2 == 1)); 186 | assert!(buf.next().is_some_and(|s| s.trunc() as i32 % 2 == 0)); 187 | 188 | buf.try_seek(Duration::from_secs(6)).unwrap(); 189 | assert!(buf.next().is_some_and(|s| s.trunc() as i32 % 2 == 1),); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | /// Stream sample rate (a frame rate or samples per second per channel). 2 | pub type SampleRate = u32; 3 | 4 | /// Number of channels in a stream. 5 | pub type ChannelCount = u16; 6 | 7 | /// Represents value of a single sample. 8 | /// Silence corresponds to the value `0.0`. The expected amplitude range is -1.0...1.0. 9 | /// Values below and above this range are clipped in conversion to other sample types. 10 | /// Use conversion traits from [dasp_sample] crate or [crate::conversions::SampleTypeConverter] 11 | /// to convert between sample types if necessary. 12 | pub type Sample = f32; 13 | -------------------------------------------------------------------------------- /src/conversions/channels.rs: -------------------------------------------------------------------------------- 1 | use crate::common::ChannelCount; 2 | use crate::Sample; 3 | 4 | /// Iterator that converts from a certain channel count to another. 5 | #[derive(Clone, Debug)] 6 | pub struct ChannelCountConverter 7 | where 8 | I: Iterator, 9 | { 10 | input: I, 11 | from: ChannelCount, 12 | to: ChannelCount, 13 | sample_repeat: Option, 14 | next_output_sample_pos: ChannelCount, 15 | } 16 | 17 | impl ChannelCountConverter 18 | where 19 | I: Iterator, 20 | { 21 | /// Initializes the iterator. 22 | /// 23 | /// # Panic 24 | /// 25 | /// Panics if `from` or `to` are equal to 0. 26 | /// 27 | #[inline] 28 | pub fn new(input: I, from: ChannelCount, to: ChannelCount) -> ChannelCountConverter { 29 | assert!(from >= 1); 30 | assert!(to >= 1); 31 | 32 | ChannelCountConverter { 33 | input, 34 | from, 35 | to, 36 | sample_repeat: None, 37 | next_output_sample_pos: 0, 38 | } 39 | } 40 | 41 | /// Destroys this iterator and returns the underlying iterator. 42 | #[inline] 43 | pub fn into_inner(self) -> I { 44 | self.input 45 | } 46 | 47 | /// Get mutable access to the iterator 48 | #[inline] 49 | pub fn inner_mut(&mut self) -> &mut I { 50 | &mut self.input 51 | } 52 | } 53 | 54 | impl Iterator for ChannelCountConverter 55 | where 56 | I: Iterator, 57 | { 58 | type Item = I::Item; 59 | 60 | fn next(&mut self) -> Option { 61 | let result = match self.next_output_sample_pos { 62 | 0 => { 63 | // save first sample for mono -> stereo conversion 64 | let value = self.input.next(); 65 | self.sample_repeat = value; 66 | value 67 | } 68 | x if x < self.from => self.input.next(), 69 | 1 => self.sample_repeat, 70 | _ => Some(0.0), 71 | }; 72 | 73 | if result.is_some() { 74 | self.next_output_sample_pos += 1; 75 | } 76 | 77 | if self.next_output_sample_pos == self.to { 78 | self.next_output_sample_pos = 0; 79 | 80 | if self.from > self.to { 81 | for _ in self.to..self.from { 82 | self.input.next(); // discarding extra input 83 | } 84 | } 85 | } 86 | 87 | result 88 | } 89 | 90 | #[inline] 91 | fn size_hint(&self) -> (usize, Option) { 92 | let (min, max) = self.input.size_hint(); 93 | 94 | let consumed = std::cmp::min(self.from, self.next_output_sample_pos) as usize; 95 | 96 | let min = ((min + consumed) / self.from as usize * self.to as usize) 97 | .saturating_sub(self.next_output_sample_pos as usize); 98 | 99 | let max = max.map(|max| { 100 | ((max + consumed) / self.from as usize * self.to as usize) 101 | .saturating_sub(self.next_output_sample_pos as usize) 102 | }); 103 | 104 | (min, max) 105 | } 106 | } 107 | 108 | impl ExactSizeIterator for ChannelCountConverter where I: ExactSizeIterator {} 109 | 110 | #[cfg(test)] 111 | mod test { 112 | use super::ChannelCountConverter; 113 | use crate::common::ChannelCount; 114 | use crate::Sample; 115 | 116 | #[test] 117 | fn remove_channels() { 118 | let input = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; 119 | let output = ChannelCountConverter::new(input.into_iter(), 3, 2).collect::>(); 120 | assert_eq!(output, [1.0, 2.0, 4.0, 5.0]); 121 | 122 | let input = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]; 123 | let output = ChannelCountConverter::new(input.into_iter(), 4, 1).collect::>(); 124 | assert_eq!(output, [1.0, 5.0]); 125 | } 126 | 127 | #[test] 128 | fn add_channels() { 129 | let input = vec![1.0, 2.0, 3.0, 4.0]; 130 | let output = ChannelCountConverter::new(input.into_iter(), 1, 2).collect::>(); 131 | assert_eq!(output, [1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0]); 132 | 133 | let input = vec![1.0, 2.0]; 134 | let output = ChannelCountConverter::new(input.into_iter(), 1, 4).collect::>(); 135 | assert_eq!(output, [1.0, 1.0, 0.0, 0.0, 2.0, 2.0, 0.0, 0.0]); 136 | 137 | let input = vec![1.0, 2.0, 3.0, 4.0]; 138 | let output = ChannelCountConverter::new(input.into_iter(), 2, 4).collect::>(); 139 | assert_eq!(output, [1.0, 2.0, 0.0, 0.0, 3.0, 4.0, 0.0, 0.0]); 140 | } 141 | 142 | #[test] 143 | fn size_hint() { 144 | fn test(input: &[Sample], from: ChannelCount, to: ChannelCount) { 145 | let mut converter = ChannelCountConverter::new(input.iter().copied(), from, to); 146 | let count = converter.clone().count(); 147 | for left_in_iter in (0..=count).rev() { 148 | println!("left_in_iter = {left_in_iter}"); 149 | assert_eq!(converter.size_hint(), (left_in_iter, Some(left_in_iter))); 150 | converter.next(); 151 | } 152 | assert_eq!(converter.size_hint(), (0, Some(0))); 153 | } 154 | 155 | test(&[1.0, 2.0, 3.0], 1, 2); 156 | test(&[1.0, 2.0, 3.0, 4.0], 2, 4); 157 | test(&[1.0, 2.0, 3.0, 4.0], 4, 2); 158 | test(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 3, 8); 159 | test(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], 4, 1); 160 | } 161 | 162 | #[test] 163 | fn len_more() { 164 | let input = vec![1.0, 2.0, 3.0, 4.0]; 165 | let output = ChannelCountConverter::new(input.into_iter(), 2, 3); 166 | assert_eq!(output.len(), 6); 167 | } 168 | 169 | #[test] 170 | fn len_less() { 171 | let input = vec![1.0, 2.0, 3.0, 4.0]; 172 | let output = ChannelCountConverter::new(input.into_iter(), 2, 1); 173 | assert_eq!(output.len(), 2); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/conversions/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This module contains functions that convert from one PCM format to another. 3 | 4 | This includes conversion between sample formats, channels or sample rates. 5 | */ 6 | 7 | pub use self::channels::ChannelCountConverter; 8 | pub use self::sample::SampleTypeConverter; 9 | pub use self::sample_rate::SampleRateConverter; 10 | 11 | mod channels; 12 | mod sample; 13 | mod sample_rate; 14 | -------------------------------------------------------------------------------- /src/conversions/sample.rs: -------------------------------------------------------------------------------- 1 | use dasp_sample::{FromSample, ToSample}; 2 | use std::marker::PhantomData; 3 | 4 | /// Converts the samples data type to `O`. 5 | #[derive(Clone, Debug)] 6 | pub struct SampleTypeConverter { 7 | input: I, 8 | marker: PhantomData, 9 | } 10 | 11 | impl SampleTypeConverter { 12 | /// Builds a new converter. 13 | #[inline] 14 | pub fn new(input: I) -> SampleTypeConverter { 15 | SampleTypeConverter { 16 | input, 17 | marker: PhantomData, 18 | } 19 | } 20 | 21 | /// Destroys this iterator and returns the underlying iterator. 22 | #[inline] 23 | pub fn into_inner(self) -> I { 24 | self.input 25 | } 26 | 27 | /// get mutable access to the iterator 28 | #[inline] 29 | pub fn inner_mut(&mut self) -> &mut I { 30 | &mut self.input 31 | } 32 | } 33 | 34 | impl Iterator for SampleTypeConverter 35 | where 36 | I: Iterator, 37 | I::Item: ToSample, 38 | { 39 | type Item = O; 40 | 41 | #[inline] 42 | fn next(&mut self) -> Option { 43 | self.input.next().map(|s| s.to_sample_()) 44 | } 45 | 46 | #[inline] 47 | fn size_hint(&self) -> (usize, Option) { 48 | self.input.size_hint() 49 | } 50 | } 51 | 52 | impl ExactSizeIterator for SampleTypeConverter 53 | where 54 | I: ExactSizeIterator, 55 | O: FromSample, 56 | { 57 | } 58 | -------------------------------------------------------------------------------- /src/decoder/flac.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Seek, SeekFrom}; 2 | use std::mem; 3 | use std::time::Duration; 4 | 5 | use crate::source::SeekError; 6 | use crate::Source; 7 | 8 | use crate::common::{ChannelCount, Sample, SampleRate}; 9 | 10 | use claxon::FlacReader; 11 | use dasp_sample::Sample as _; 12 | use dasp_sample::I24; 13 | 14 | /// Decoder for the FLAC format. 15 | pub struct FlacDecoder 16 | where 17 | R: Read + Seek, 18 | { 19 | reader: FlacReader, 20 | current_block: Vec, 21 | current_block_channel_len: usize, 22 | current_block_off: usize, 23 | bits_per_sample: u32, 24 | sample_rate: SampleRate, 25 | channels: ChannelCount, 26 | total_duration: Option, 27 | } 28 | 29 | impl FlacDecoder 30 | where 31 | R: Read + Seek, 32 | { 33 | /// Attempts to decode the data as FLAC. 34 | pub fn new(mut data: R) -> Result, R> { 35 | if !is_flac(data.by_ref()) { 36 | return Err(data); 37 | } 38 | 39 | let reader = FlacReader::new(data).expect("should still be flac"); 40 | 41 | let spec = reader.streaminfo(); 42 | let sample_rate = spec.sample_rate; 43 | 44 | // `samples` in FLAC means "inter-channel samples" aka frames 45 | // so we do not divide by `self.channels` here. 46 | let total_duration = spec.samples.map(|s| { 47 | // Calculate duration as (samples * 1_000_000) / sample_rate 48 | // but do the division first to avoid overflow 49 | let sample_rate = sample_rate as u64; 50 | let secs = s / sample_rate; 51 | let nanos = ((s % sample_rate) * 1_000_000_000) / sample_rate; 52 | Duration::new(secs, nanos as u32) 53 | }); 54 | 55 | Ok(FlacDecoder { 56 | reader, 57 | current_block: Vec::with_capacity( 58 | spec.max_block_size as usize * spec.channels as usize, 59 | ), 60 | current_block_channel_len: 1, 61 | current_block_off: 0, 62 | bits_per_sample: spec.bits_per_sample, 63 | sample_rate, 64 | channels: spec.channels as ChannelCount, 65 | total_duration, 66 | }) 67 | } 68 | 69 | #[inline] 70 | pub fn into_inner(self) -> R { 71 | self.reader.into_inner() 72 | } 73 | } 74 | 75 | impl Source for FlacDecoder 76 | where 77 | R: Read + Seek, 78 | { 79 | #[inline] 80 | fn current_span_len(&self) -> Option { 81 | None 82 | } 83 | 84 | #[inline] 85 | fn channels(&self) -> ChannelCount { 86 | self.channels 87 | } 88 | 89 | #[inline] 90 | fn sample_rate(&self) -> SampleRate { 91 | self.sample_rate 92 | } 93 | 94 | #[inline] 95 | fn total_duration(&self) -> Option { 96 | self.total_duration 97 | } 98 | 99 | #[inline] 100 | fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { 101 | Err(SeekError::NotSupported { 102 | underlying_source: std::any::type_name::(), 103 | }) 104 | } 105 | } 106 | 107 | impl Iterator for FlacDecoder 108 | where 109 | R: Read + Seek, 110 | { 111 | type Item = Sample; 112 | 113 | #[inline] 114 | fn next(&mut self) -> Option { 115 | loop { 116 | if self.current_block_off < self.current_block.len() { 117 | // Read from current block. 118 | let real_offset = (self.current_block_off % self.channels as usize) 119 | * self.current_block_channel_len 120 | + self.current_block_off / self.channels as usize; 121 | let raw_val = self.current_block[real_offset]; 122 | self.current_block_off += 1; 123 | let bits = self.bits_per_sample; 124 | let real_val = match bits { 125 | 8 => (raw_val as i8).to_sample(), 126 | 16 => (raw_val as i16).to_sample(), 127 | 24 => I24::new(raw_val) 128 | .unwrap_or(dasp_sample::Sample::EQUILIBRIUM) 129 | .to_sample(), 130 | 32 => raw_val.to_sample(), 131 | _ => { 132 | // FLAC also supports 12 and 20 bits per sample. We use bit 133 | // shifts to convert them to 32 bits, because: 134 | // - I12 does not exist as a type 135 | // - I20 exists but does not have `ToSample` implemented 136 | (raw_val << (32 - bits)).to_sample() 137 | } 138 | }; 139 | return Some(real_val); 140 | } 141 | 142 | // Load the next block. 143 | self.current_block_off = 0; 144 | let buffer = mem::take(&mut self.current_block); 145 | match self.reader.blocks().read_next_or_eof(buffer) { 146 | Ok(Some(block)) => { 147 | self.current_block_channel_len = (block.len() / block.channels()) as usize; 148 | self.current_block = block.into_buffer(); 149 | } 150 | _ => return None, 151 | } 152 | } 153 | } 154 | } 155 | 156 | /// Returns true if the stream contains FLAC data, then tries to rewind it to where it was. 157 | fn is_flac(mut data: R) -> bool 158 | where 159 | R: Read + Seek, 160 | { 161 | let stream_pos = data.stream_position().unwrap_or_default(); 162 | let result = FlacReader::new(data.by_ref()).is_ok(); 163 | let _ = data.seek(SeekFrom::Start(stream_pos)); 164 | result 165 | } 166 | -------------------------------------------------------------------------------- /src/decoder/mp3.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Seek, SeekFrom}; 2 | use std::time::Duration; 3 | 4 | use super::DecoderSample; 5 | use crate::common::{ChannelCount, SampleRate}; 6 | use crate::source::SeekError; 7 | use crate::Source; 8 | 9 | use minimp3::Decoder; 10 | use minimp3::Frame; 11 | use minimp3_fixed as minimp3; 12 | 13 | pub struct Mp3Decoder 14 | where 15 | R: Read + Seek, 16 | { 17 | // decoder: SeekDecoder, 18 | decoder: Decoder, 19 | // what minimp3 calls frames rodio calls spans 20 | current_span: Frame, 21 | current_span_offset: usize, 22 | } 23 | 24 | impl Mp3Decoder 25 | where 26 | R: Read + Seek, 27 | { 28 | pub fn new(mut data: R) -> Result { 29 | if !is_mp3(data.by_ref()) { 30 | return Err(data); 31 | } 32 | // let mut decoder = SeekDecoder::new(data) 33 | let mut decoder = Decoder::new(data); 34 | // parameters are correct and minimp3 is used correctly 35 | // thus if we crash here one of these invariants is broken: 36 | // .expect("should be able to allocate memory, perform IO"); 37 | // let current_span = decoder.decode_frame() 38 | let current_span = decoder.next_frame().expect("should still be mp3"); 39 | 40 | Ok(Mp3Decoder { 41 | decoder, 42 | current_span, 43 | current_span_offset: 0, 44 | }) 45 | } 46 | 47 | #[inline] 48 | pub fn into_inner(self) -> R { 49 | self.decoder.into_inner() 50 | } 51 | } 52 | 53 | impl Source for Mp3Decoder 54 | where 55 | R: Read + Seek, 56 | { 57 | #[inline] 58 | fn current_span_len(&self) -> Option { 59 | Some(self.current_span.data.len()) 60 | } 61 | 62 | #[inline] 63 | fn channels(&self) -> ChannelCount { 64 | self.current_span.channels as _ 65 | } 66 | 67 | #[inline] 68 | fn sample_rate(&self) -> SampleRate { 69 | self.current_span.sample_rate as _ 70 | } 71 | 72 | #[inline] 73 | fn total_duration(&self) -> Option { 74 | None 75 | } 76 | 77 | fn try_seek(&mut self, _pos: Duration) -> Result<(), SeekError> { 78 | // TODO waiting for PR in minimp3_fixed or minimp3 79 | 80 | // let pos = (pos.as_secs_f32() * self.sample_rate() as f32) as u64; 81 | // // do not trigger a sample_rate, channels and frame/span len update 82 | // // as the seek only takes effect after the current frame/span is done 83 | // self.decoder.seek_samples(pos)?; 84 | // Ok(()) 85 | 86 | Err(SeekError::NotSupported { 87 | underlying_source: std::any::type_name::(), 88 | }) 89 | } 90 | } 91 | 92 | impl Iterator for Mp3Decoder 93 | where 94 | R: Read + Seek, 95 | { 96 | type Item = DecoderSample; 97 | 98 | fn next(&mut self) -> Option { 99 | let current_span_len = self.current_span_len()?; 100 | if self.current_span_offset == current_span_len { 101 | if let Ok(span) = self.decoder.next_frame() { 102 | // if let Ok(span) = self.decoder.decode_frame() { 103 | self.current_span = span; 104 | self.current_span_offset = 0; 105 | } else { 106 | return None; 107 | } 108 | } 109 | 110 | let v = self.current_span.data[self.current_span_offset]; 111 | self.current_span_offset += 1; 112 | 113 | Some(v.to_sample()) 114 | } 115 | } 116 | 117 | /// Returns true if the stream contains mp3 data, then resets it to where it was. 118 | fn is_mp3(mut data: R) -> bool 119 | where 120 | R: Read + Seek, 121 | { 122 | let stream_pos = data.stream_position().unwrap_or_default(); 123 | let mut decoder = Decoder::new(data.by_ref()); 124 | let result = decoder.next_frame().is_ok(); 125 | let _ = data.seek(SeekFrom::Start(stream_pos)); 126 | result 127 | } 128 | -------------------------------------------------------------------------------- /src/decoder/read_seek_source.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Result, Seek, SeekFrom}; 2 | 3 | use symphonia::core::io::MediaSource; 4 | 5 | use super::Settings; 6 | 7 | /// A wrapper around a `Read + Seek` type that implements Symphonia's `MediaSource` trait. 8 | /// 9 | /// This type allows standard Rust I/O types to be used with Symphonia's media framework 10 | /// by implementing the required `MediaSource` trait. 11 | pub struct ReadSeekSource { 12 | /// The wrapped reader/seeker 13 | inner: T, 14 | /// Optional length of the media source in bytes. 15 | /// When known, this can help with seeking and duration calculations. 16 | byte_len: Option, 17 | /// Whether this media source reports as seekable. 18 | is_seekable: bool, 19 | } 20 | 21 | impl ReadSeekSource { 22 | /// Creates a new `ReadSeekSource` by wrapping a reader/seeker. 23 | /// 24 | /// # Arguments 25 | /// * `inner` - The reader/seeker to wrap 26 | /// * `settings` - Decoder settings for configuring the source 27 | #[inline] 28 | pub fn new(inner: T, settings: &Settings) -> Self { 29 | ReadSeekSource { 30 | inner, 31 | byte_len: settings.byte_len, 32 | is_seekable: settings.is_seekable, 33 | } 34 | } 35 | } 36 | 37 | impl MediaSource for ReadSeekSource { 38 | /// Returns whether this media source reports as seekable. 39 | #[inline] 40 | fn is_seekable(&self) -> bool { 41 | self.is_seekable 42 | } 43 | 44 | /// Returns the total length of the media source in bytes, if known. 45 | #[inline] 46 | fn byte_len(&self) -> Option { 47 | self.byte_len 48 | } 49 | } 50 | 51 | impl Read for ReadSeekSource { 52 | #[inline] 53 | /// Reads bytes from the underlying reader into the provided buffer. 54 | /// 55 | /// Delegates to the inner reader's implementation. 56 | fn read(&mut self, buf: &mut [u8]) -> Result { 57 | self.inner.read(buf) 58 | } 59 | } 60 | 61 | impl Seek for ReadSeekSource { 62 | /// Seeks to a position in the underlying reader. 63 | /// 64 | /// Delegates to the inner reader's implementation. 65 | #[inline] 66 | fn seek(&mut self, pos: SeekFrom) -> Result { 67 | self.inner.seek(pos) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/decoder/vorbis.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Seek, SeekFrom}; 2 | use std::time::Duration; 3 | 4 | use crate::source::SeekError; 5 | use crate::Source; 6 | 7 | use crate::common::{ChannelCount, Sample, SampleRate}; 8 | use lewton::inside_ogg::OggStreamReader; 9 | use lewton::samples::InterleavedSamples; 10 | 11 | /// Decoder for an OGG file that contains Vorbis sound format. 12 | pub struct VorbisDecoder 13 | where 14 | R: Read + Seek, 15 | { 16 | stream_reader: OggStreamReader, 17 | current_data: Vec, 18 | next: usize, 19 | } 20 | 21 | impl VorbisDecoder 22 | where 23 | R: Read + Seek, 24 | { 25 | /// Attempts to decode the data as ogg/vorbis. 26 | pub fn new(mut data: R) -> Result, R> { 27 | if !is_vorbis(data.by_ref()) { 28 | return Err(data); 29 | } 30 | 31 | let stream_reader = OggStreamReader::new(data).expect("should still be vorbis"); 32 | Ok(Self::from_stream_reader(stream_reader)) 33 | } 34 | pub fn from_stream_reader(mut stream_reader: OggStreamReader) -> Self { 35 | let mut data = match stream_reader.read_dec_packet_generic::>() { 36 | Ok(Some(d)) => d.samples, 37 | _ => Vec::new(), 38 | }; 39 | 40 | // The first packet is always empty, therefore 41 | // we need to read the second frame to get some data 42 | if let Ok(Some(mut d)) = 43 | stream_reader.read_dec_packet_generic::>() 44 | { 45 | data.append(&mut d.samples); 46 | } 47 | 48 | VorbisDecoder { 49 | stream_reader, 50 | current_data: data, 51 | next: 0, 52 | } 53 | } 54 | 55 | #[inline] 56 | pub fn into_inner(self) -> OggStreamReader { 57 | self.stream_reader 58 | } 59 | } 60 | 61 | impl Source for VorbisDecoder 62 | where 63 | R: Read + Seek, 64 | { 65 | #[inline] 66 | fn current_span_len(&self) -> Option { 67 | Some(self.current_data.len()) 68 | } 69 | 70 | #[inline] 71 | fn channels(&self) -> ChannelCount { 72 | self.stream_reader.ident_hdr.audio_channels as ChannelCount 73 | } 74 | 75 | #[inline] 76 | fn sample_rate(&self) -> SampleRate { 77 | self.stream_reader.ident_hdr.audio_sample_rate 78 | } 79 | 80 | #[inline] 81 | fn total_duration(&self) -> Option { 82 | None 83 | } 84 | 85 | /// seek is broken, https://github.com/RustAudio/lewton/issues/73. 86 | // We could work around it by: 87 | // - using unsafe to create an instance of Self 88 | // - use mem::swap to turn the &mut self into a mut self 89 | // - take out the underlying Read+Seek 90 | // - make a new self and seek 91 | // 92 | // If this issue is fixed use the implementation in 93 | // commit: 3bafe32388b4eb7a48c6701e6c65044dc8c555e6 94 | #[inline] 95 | fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { 96 | Err(SeekError::NotSupported { 97 | underlying_source: std::any::type_name::(), 98 | }) 99 | } 100 | } 101 | 102 | impl Iterator for VorbisDecoder 103 | where 104 | R: Read + Seek, 105 | { 106 | type Item = Sample; 107 | 108 | #[inline] 109 | fn next(&mut self) -> Option { 110 | if let Some(sample) = self.current_data.get(self.next).copied() { 111 | self.next += 1; 112 | if self.current_data.is_empty() { 113 | if let Ok(Some(data)) = self 114 | .stream_reader 115 | .read_dec_packet_generic::>() 116 | { 117 | self.current_data = data.samples; 118 | self.next = 0; 119 | } 120 | } 121 | Some(sample) 122 | } else { 123 | if let Ok(Some(data)) = self 124 | .stream_reader 125 | .read_dec_packet_generic::>() 126 | { 127 | self.current_data = data.samples; 128 | self.next = 0; 129 | } 130 | let sample = self.current_data.get(self.next).copied(); 131 | self.next += 1; 132 | sample 133 | } 134 | } 135 | 136 | #[inline] 137 | fn size_hint(&self) -> (usize, Option) { 138 | (self.current_data.len(), None) 139 | } 140 | } 141 | 142 | /// Returns true if the stream contains Vorbis data, then resets it to where it was. 143 | fn is_vorbis(mut data: R) -> bool 144 | where 145 | R: Read + Seek, 146 | { 147 | let stream_pos = data.stream_position().unwrap_or_default(); 148 | let result = OggStreamReader::new(data.by_ref()).is_ok(); 149 | let _ = data.seek(SeekFrom::Start(stream_pos)); 150 | result 151 | } 152 | -------------------------------------------------------------------------------- /src/decoder/wav.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Seek, SeekFrom}; 2 | use std::time::Duration; 3 | 4 | use crate::source::SeekError; 5 | use crate::{Sample, Source}; 6 | 7 | use crate::common::{ChannelCount, SampleRate}; 8 | 9 | use dasp_sample::Sample as _; 10 | use dasp_sample::I24; 11 | use hound::{SampleFormat, WavReader}; 12 | 13 | /// Decoder for the WAV format. 14 | pub struct WavDecoder 15 | where 16 | R: Read + Seek, 17 | { 18 | reader: SamplesIterator, 19 | total_duration: Duration, 20 | sample_rate: SampleRate, 21 | channels: ChannelCount, 22 | } 23 | 24 | impl WavDecoder 25 | where 26 | R: Read + Seek, 27 | { 28 | /// Attempts to decode the data as WAV. 29 | pub fn new(mut data: R) -> Result, R> { 30 | if !is_wave(data.by_ref()) { 31 | return Err(data); 32 | } 33 | 34 | let reader = WavReader::new(data).expect("should still be wav"); 35 | let spec = reader.spec(); 36 | let len = reader.len() as u64; 37 | let reader = SamplesIterator { 38 | reader, 39 | samples_read: 0, 40 | }; 41 | 42 | let sample_rate = spec.sample_rate; 43 | let channels = spec.channels; 44 | 45 | let total_duration = { 46 | let data_rate = sample_rate as u64 * channels as u64; 47 | let secs = len / data_rate; 48 | let nanos = ((len % data_rate) * 1_000_000_000) / data_rate; 49 | Duration::new(secs, nanos as u32) 50 | }; 51 | 52 | Ok(WavDecoder { 53 | reader, 54 | total_duration, 55 | sample_rate: sample_rate as SampleRate, 56 | channels: channels as ChannelCount, 57 | }) 58 | } 59 | 60 | #[inline] 61 | pub fn into_inner(self) -> R { 62 | self.reader.reader.into_inner() 63 | } 64 | } 65 | 66 | struct SamplesIterator 67 | where 68 | R: Read + Seek, 69 | { 70 | reader: WavReader, 71 | samples_read: u32, // wav header is u32 so this suffices 72 | } 73 | 74 | impl Iterator for SamplesIterator 75 | where 76 | R: Read + Seek, 77 | { 78 | type Item = Sample; 79 | 80 | #[inline] 81 | fn next(&mut self) -> Option { 82 | self.samples_read += 1; 83 | let spec = self.reader.spec(); 84 | let next_sample: Option = 85 | match (spec.sample_format, spec.bits_per_sample as u32) { 86 | (SampleFormat::Float, bits) => { 87 | if bits == 32 { 88 | let next_f32: Option> = self.reader.samples().next(); 89 | next_f32.and_then(|value| value.ok()) 90 | } else { 91 | #[cfg(feature = "tracing")] 92 | tracing::error!("Unsupported WAV float bit depth: {}", bits); 93 | #[cfg(not(feature = "tracing"))] 94 | eprintln!("Unsupported WAV float bit depth: {}", bits); 95 | None 96 | } 97 | } 98 | 99 | (SampleFormat::Int, 8) => { 100 | let next_i8: Option> = self.reader.samples().next(); 101 | next_i8.and_then(|value| value.ok().map(|value| value.to_sample())) 102 | } 103 | (SampleFormat::Int, 16) => { 104 | let next_i16: Option> = self.reader.samples().next(); 105 | next_i16.and_then(|value| value.ok().map(|value| value.to_sample())) 106 | } 107 | (SampleFormat::Int, 24) => { 108 | let next_i24_in_i32: Option> = self.reader.samples().next(); 109 | next_i24_in_i32.and_then(|value| { 110 | value.ok().and_then(I24::new).map(|value| value.to_sample()) 111 | }) 112 | } 113 | (SampleFormat::Int, 32) => { 114 | let next_i32: Option> = self.reader.samples().next(); 115 | next_i32.and_then(|value| value.ok().map(|value| value.to_sample())) 116 | } 117 | (SampleFormat::Int, bits) => { 118 | // Unofficial WAV integer bit depth, try to handle it anyway 119 | let next_i32: Option> = self.reader.samples().next(); 120 | if bits <= 32 { 121 | next_i32.and_then(|value| { 122 | value.ok().map(|value| (value << (32 - bits)).to_sample()) 123 | }) 124 | } else { 125 | #[cfg(feature = "tracing")] 126 | tracing::error!("Unsupported WAV integer bit depth: {}", bits); 127 | #[cfg(not(feature = "tracing"))] 128 | eprintln!("Unsupported WAV integer bit depth: {}", bits); 129 | None 130 | } 131 | } 132 | }; 133 | next_sample 134 | } 135 | 136 | #[inline] 137 | fn size_hint(&self) -> (usize, Option) { 138 | let len = (self.reader.len() - self.samples_read) as usize; 139 | (len, Some(len)) 140 | } 141 | } 142 | 143 | impl ExactSizeIterator for SamplesIterator where R: Read + Seek {} 144 | 145 | impl Source for WavDecoder 146 | where 147 | R: Read + Seek, 148 | { 149 | #[inline] 150 | fn current_span_len(&self) -> Option { 151 | None 152 | } 153 | 154 | #[inline] 155 | fn channels(&self) -> ChannelCount { 156 | self.channels 157 | } 158 | 159 | #[inline] 160 | fn sample_rate(&self) -> SampleRate { 161 | self.sample_rate 162 | } 163 | 164 | #[inline] 165 | fn total_duration(&self) -> Option { 166 | Some(self.total_duration) 167 | } 168 | 169 | #[inline] 170 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 171 | let file_len = self.reader.reader.duration(); 172 | 173 | let new_pos = pos.as_secs_f32() * self.sample_rate() as f32; 174 | let new_pos = new_pos as u32; 175 | let new_pos = new_pos.min(file_len); // saturate pos at the end of the source 176 | 177 | // make sure the next sample is for the right channel 178 | let to_skip = self.reader.samples_read % self.channels() as u32; 179 | 180 | self.reader 181 | .reader 182 | .seek(new_pos) 183 | .map_err(SeekError::HoundDecoder)?; 184 | self.reader.samples_read = new_pos * self.channels() as u32; 185 | 186 | for _ in 0..to_skip { 187 | self.next(); 188 | } 189 | 190 | Ok(()) 191 | } 192 | } 193 | 194 | impl Iterator for WavDecoder 195 | where 196 | R: Read + Seek, 197 | { 198 | type Item = Sample; 199 | 200 | #[inline] 201 | fn next(&mut self) -> Option { 202 | self.reader.next() 203 | } 204 | 205 | #[inline] 206 | fn size_hint(&self) -> (usize, Option) { 207 | self.reader.size_hint() 208 | } 209 | } 210 | 211 | impl ExactSizeIterator for WavDecoder where R: Read + Seek {} 212 | 213 | /// Returns true if the stream contains WAV data, then resets it to where it was. 214 | fn is_wave(mut data: R) -> bool 215 | where 216 | R: Read + Seek, 217 | { 218 | let stream_pos = data.stream_position().unwrap_or_default(); 219 | let result = WavReader::new(data.by_ref()).is_ok(); 220 | let _ = data.seek(SeekFrom::Start(stream_pos)); 221 | result 222 | } 223 | -------------------------------------------------------------------------------- /src/math.rs: -------------------------------------------------------------------------------- 1 | /// Linear interpolation between two samples. 2 | /// 3 | /// The result should be equivalent to 4 | /// `first * (1 - numerator / denominator) + second * numerator / denominator`. 5 | /// 6 | /// To avoid numeric overflows pick smaller numerator. 7 | // TODO (refactoring) Streamline this using coefficient instead of numerator and denominator. 8 | #[inline] 9 | pub fn lerp(first: &f32, second: &f32, numerator: u32, denominator: u32) -> f32 { 10 | first + (second - first) * numerator as f32 / denominator as f32 11 | } 12 | 13 | #[cfg(test)] 14 | mod test { 15 | use super::*; 16 | use num_rational::Ratio; 17 | use quickcheck::{quickcheck, TestResult}; 18 | 19 | quickcheck! { 20 | fn lerp_f32_random(first: u16, second: u16, numerator: u16, denominator: u16) -> TestResult { 21 | if denominator == 0 { return TestResult::discard(); } 22 | 23 | let (numerator, denominator) = Ratio::new(numerator, denominator).into_raw(); 24 | if numerator > 5000 { return TestResult::discard(); } 25 | 26 | let a = first as f64; 27 | let b = second as f64; 28 | let c = numerator as f64 / denominator as f64; 29 | if !(0.0..=1.0).contains(&c) { return TestResult::discard(); }; 30 | 31 | let reference = a * (1.0 - c) + b * c; 32 | let x = lerp(&(first as f32), &(second as f32), numerator as u32, denominator as u32) as f64; 33 | // TODO (review) It seems that the diff tolerance should be a lot lower. Why lerp so imprecise? 34 | TestResult::from_bool((x - reference).abs() < 0.01) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/source/amplify.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::Source; 6 | 7 | /// Internal function that builds a `Amplify` object. 8 | pub fn amplify(input: I, factor: f32) -> Amplify 9 | where 10 | I: Source, 11 | { 12 | Amplify { input, factor } 13 | } 14 | 15 | /// Internal function that converts decibels to linear 16 | pub(super) fn to_linear(decibels: f32) -> f32 { 17 | f32::powf(10f32, decibels * 0.05) 18 | } 19 | 20 | /// Filter that modifies each sample by a given value. 21 | #[derive(Clone, Debug)] 22 | pub struct Amplify { 23 | input: I, 24 | factor: f32, 25 | } 26 | 27 | impl Amplify { 28 | /// Modifies the amplification factor. 29 | #[inline] 30 | pub fn set_factor(&mut self, factor: f32) { 31 | self.factor = factor; 32 | } 33 | 34 | /// Modifies the amplification factor logarithmically. 35 | #[inline] 36 | pub fn set_log_factor(&mut self, factor: f32) { 37 | self.factor = to_linear(factor); 38 | } 39 | 40 | /// Returns a reference to the inner source. 41 | #[inline] 42 | pub fn inner(&self) -> &I { 43 | &self.input 44 | } 45 | 46 | /// Returns a mutable reference to the inner source. 47 | #[inline] 48 | pub fn inner_mut(&mut self) -> &mut I { 49 | &mut self.input 50 | } 51 | 52 | /// Returns the inner source. 53 | #[inline] 54 | pub fn into_inner(self) -> I { 55 | self.input 56 | } 57 | } 58 | 59 | impl Iterator for Amplify 60 | where 61 | I: Source, 62 | { 63 | type Item = I::Item; 64 | 65 | #[inline] 66 | fn next(&mut self) -> Option { 67 | self.input.next().map(|value| value * self.factor) 68 | } 69 | 70 | #[inline] 71 | fn size_hint(&self) -> (usize, Option) { 72 | self.input.size_hint() 73 | } 74 | } 75 | 76 | impl ExactSizeIterator for Amplify where I: Source + ExactSizeIterator {} 77 | 78 | impl Source for Amplify 79 | where 80 | I: Source, 81 | { 82 | #[inline] 83 | fn current_span_len(&self) -> Option { 84 | self.input.current_span_len() 85 | } 86 | 87 | #[inline] 88 | fn channels(&self) -> ChannelCount { 89 | self.input.channels() 90 | } 91 | 92 | #[inline] 93 | fn sample_rate(&self) -> SampleRate { 94 | self.input.sample_rate() 95 | } 96 | 97 | #[inline] 98 | fn total_duration(&self) -> Option { 99 | self.input.total_duration() 100 | } 101 | 102 | #[inline] 103 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 104 | self.input.try_seek(pos) 105 | } 106 | } 107 | 108 | #[cfg(test)] 109 | mod test { 110 | use super::*; 111 | /// Based on [Wikipedia's Decibel article]. 112 | /// 113 | /// [Wikipedia's Decibel article]: https://web.archive.org/web/20230810185300/https://en.wikipedia.org/wiki/Decibel 114 | const DECIBELS_LINEAR_TABLE: [(f32, f32); 27] = [ 115 | (100., 100000.), 116 | (90., 31623.), 117 | (80., 10000.), 118 | (70., 3162.), 119 | (60., 1000.), 120 | (50., 316.2), 121 | (40., 100.), 122 | (30., 31.62), 123 | (20., 10.), 124 | (10., 3.162), 125 | (5.998, 1.995), 126 | (3.003, 1.413), 127 | (1.002, 1.122), 128 | (0., 1.), 129 | (-1.002, 0.891), 130 | (-3.003, 0.708), 131 | (-5.998, 0.501), 132 | (-10., 0.3162), 133 | (-20., 0.1), 134 | (-30., 0.03162), 135 | (-40., 0.01), 136 | (-50., 0.003162), 137 | (-60., 0.001), 138 | (-70., 0.0003162), 139 | (-80., 0.0001), 140 | (-90., 0.00003162), 141 | (-100., 0.00001), 142 | ]; 143 | 144 | #[test] 145 | fn convert_decibels_to_linear() { 146 | for (db, linear) in DECIBELS_LINEAR_TABLE { 147 | const PRECISION: f32 = 5.066e3; 148 | let to_linear = to_linear(db); 149 | 150 | assert!( 151 | 2.0 * (to_linear - linear).abs() 152 | < PRECISION * f32::EPSILON * (to_linear.abs() + linear.abs()) 153 | ); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/source/blt.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{ChannelCount, SampleRate}; 2 | use crate::Source; 3 | use std::f32::consts::PI; 4 | use std::time::Duration; 5 | 6 | use super::SeekError; 7 | 8 | // Implemented following http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt 9 | 10 | /// Internal function that builds a `BltFilter` object. 11 | pub fn low_pass(input: I, freq: u32) -> BltFilter 12 | where 13 | I: Source, 14 | { 15 | low_pass_with_q(input, freq, 0.5) 16 | } 17 | 18 | pub fn high_pass(input: I, freq: u32) -> BltFilter 19 | where 20 | I: Source, 21 | { 22 | high_pass_with_q(input, freq, 0.5) 23 | } 24 | 25 | /// Same as low_pass but allows the q value (bandwidth) to be changed 26 | pub fn low_pass_with_q(input: I, freq: u32, q: f32) -> BltFilter 27 | where 28 | I: Source, 29 | { 30 | BltFilter { 31 | input, 32 | formula: BltFormula::LowPass { freq, q }, 33 | applier: None, 34 | x_n1: 0.0, 35 | x_n2: 0.0, 36 | y_n1: 0.0, 37 | y_n2: 0.0, 38 | } 39 | } 40 | 41 | /// Same as high_pass but allows the q value (bandwidth) to be changed 42 | pub fn high_pass_with_q(input: I, freq: u32, q: f32) -> BltFilter 43 | where 44 | I: Source, 45 | { 46 | BltFilter { 47 | input, 48 | formula: BltFormula::HighPass { freq, q }, 49 | applier: None, 50 | x_n1: 0.0, 51 | x_n2: 0.0, 52 | y_n1: 0.0, 53 | y_n2: 0.0, 54 | } 55 | } 56 | 57 | /// This applies an audio filter, it can be a high or low pass filter. 58 | #[derive(Clone, Debug)] 59 | pub struct BltFilter { 60 | input: I, 61 | formula: BltFormula, 62 | applier: Option, 63 | x_n1: f32, 64 | x_n2: f32, 65 | y_n1: f32, 66 | y_n2: f32, 67 | } 68 | 69 | impl BltFilter { 70 | /// Modifies this filter so that it becomes a low-pass filter. 71 | pub fn to_low_pass(&mut self, freq: u32) { 72 | self.to_low_pass_with_q(freq, 0.5); 73 | } 74 | 75 | /// Modifies this filter so that it becomes a high-pass filter 76 | pub fn to_high_pass(&mut self, freq: u32) { 77 | self.to_high_pass_with_q(freq, 0.5); 78 | } 79 | 80 | /// Same as to_low_pass but allows the q value (bandwidth) to be changed 81 | pub fn to_low_pass_with_q(&mut self, freq: u32, q: f32) { 82 | self.formula = BltFormula::LowPass { freq, q }; 83 | self.applier = None; 84 | } 85 | 86 | /// Same as to_high_pass but allows the q value (bandwidth) to be changed 87 | pub fn to_high_pass_with_q(&mut self, freq: u32, q: f32) { 88 | self.formula = BltFormula::HighPass { freq, q }; 89 | self.applier = None; 90 | } 91 | 92 | /// Returns a reference to the inner source. 93 | #[inline] 94 | pub fn inner(&self) -> &I { 95 | &self.input 96 | } 97 | 98 | /// Returns a mutable reference to the inner source. 99 | #[inline] 100 | pub fn inner_mut(&mut self) -> &mut I { 101 | &mut self.input 102 | } 103 | 104 | /// Returns the inner source. 105 | #[inline] 106 | pub fn into_inner(self) -> I { 107 | self.input 108 | } 109 | } 110 | 111 | impl Iterator for BltFilter 112 | where 113 | I: Source, 114 | { 115 | type Item = f32; 116 | 117 | #[inline] 118 | fn next(&mut self) -> Option { 119 | let last_in_span = self.input.current_span_len() == Some(1); 120 | 121 | if self.applier.is_none() { 122 | self.applier = Some(self.formula.to_applier(self.input.sample_rate())); 123 | } 124 | 125 | let sample = self.input.next()?; 126 | let result = self 127 | .applier 128 | .as_ref() 129 | .unwrap() 130 | .apply(sample, self.x_n1, self.x_n2, self.y_n1, self.y_n2); 131 | 132 | self.y_n2 = self.y_n1; 133 | self.x_n2 = self.x_n1; 134 | self.y_n1 = result; 135 | self.x_n1 = sample; 136 | 137 | if last_in_span { 138 | self.applier = None; 139 | } 140 | 141 | Some(result) 142 | } 143 | 144 | #[inline] 145 | fn size_hint(&self) -> (usize, Option) { 146 | self.input.size_hint() 147 | } 148 | } 149 | 150 | impl ExactSizeIterator for BltFilter where I: Source + ExactSizeIterator {} 151 | 152 | impl Source for BltFilter 153 | where 154 | I: Source, 155 | { 156 | #[inline] 157 | fn current_span_len(&self) -> Option { 158 | self.input.current_span_len() 159 | } 160 | 161 | #[inline] 162 | fn channels(&self) -> ChannelCount { 163 | self.input.channels() 164 | } 165 | 166 | #[inline] 167 | fn sample_rate(&self) -> SampleRate { 168 | self.input.sample_rate() 169 | } 170 | 171 | #[inline] 172 | fn total_duration(&self) -> Option { 173 | self.input.total_duration() 174 | } 175 | 176 | #[inline] 177 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 178 | self.input.try_seek(pos) 179 | } 180 | } 181 | 182 | #[derive(Clone, Debug)] 183 | enum BltFormula { 184 | LowPass { freq: u32, q: f32 }, 185 | HighPass { freq: u32, q: f32 }, 186 | } 187 | 188 | impl BltFormula { 189 | fn to_applier(&self, sampling_frequency: u32) -> BltApplier { 190 | match *self { 191 | BltFormula::LowPass { freq, q } => { 192 | let w0 = 2.0 * PI * freq as f32 / sampling_frequency as f32; 193 | 194 | let alpha = w0.sin() / (2.0 * q); 195 | let b1 = 1.0 - w0.cos(); 196 | let b0 = b1 / 2.0; 197 | let b2 = b0; 198 | let a0 = 1.0 + alpha; 199 | let a1 = -2.0 * w0.cos(); 200 | let a2 = 1.0 - alpha; 201 | 202 | BltApplier { 203 | b0: b0 / a0, 204 | b1: b1 / a0, 205 | b2: b2 / a0, 206 | a1: a1 / a0, 207 | a2: a2 / a0, 208 | } 209 | } 210 | BltFormula::HighPass { freq, q } => { 211 | let w0 = 2.0 * PI * freq as f32 / sampling_frequency as f32; 212 | let cos_w0 = w0.cos(); 213 | let alpha = w0.sin() / (2.0 * q); 214 | 215 | let b0 = (1.0 + cos_w0) / 2.0; 216 | let b1 = -1.0 - cos_w0; 217 | let b2 = b0; 218 | let a0 = 1.0 + alpha; 219 | let a1 = -2.0 * cos_w0; 220 | let a2 = 1.0 - alpha; 221 | 222 | BltApplier { 223 | b0: b0 / a0, 224 | b1: b1 / a0, 225 | b2: b2 / a0, 226 | a1: a1 / a0, 227 | a2: a2 / a0, 228 | } 229 | } 230 | } 231 | } 232 | } 233 | 234 | #[derive(Clone, Debug)] 235 | struct BltApplier { 236 | b0: f32, 237 | b1: f32, 238 | b2: f32, 239 | a1: f32, 240 | a2: f32, 241 | } 242 | 243 | impl BltApplier { 244 | #[inline] 245 | fn apply(&self, x_n: f32, x_n1: f32, x_n2: f32, y_n1: f32, y_n2: f32) -> f32 { 246 | self.b0 * x_n + self.b1 * x_n1 + self.b2 * x_n2 - self.a1 * y_n1 - self.a2 * y_n2 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/source/channel_volume.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::{Sample, Source}; 6 | 7 | /// Combines channels in input into a single mono source, then plays that mono sound 8 | /// to each channel at the volume given for that channel. 9 | #[derive(Clone, Debug)] 10 | pub struct ChannelVolume 11 | where 12 | I: Source, 13 | { 14 | input: I, 15 | channel_volumes: Vec, 16 | current_channel: usize, 17 | current_sample: Option, 18 | } 19 | 20 | impl ChannelVolume 21 | where 22 | I: Source, 23 | { 24 | /// Wrap the input source and make it mono. Play that mono sound to each 25 | /// channel at the volume set by the user. The volume can be changed using 26 | /// [`ChannelVolume::set_volume`]. 27 | pub fn new(input: I, channel_volumes: Vec) -> ChannelVolume 28 | where 29 | I: Source, 30 | { 31 | let channel_count = channel_volumes.len(); // See next() implementation. 32 | ChannelVolume { 33 | input, 34 | channel_volumes, 35 | current_channel: channel_count, 36 | current_sample: None, 37 | } 38 | } 39 | 40 | /// Sets the volume for a given channel number. Will panic if channel number 41 | /// is invalid. 42 | pub fn set_volume(&mut self, channel: usize, volume: f32) { 43 | self.channel_volumes[channel] = volume; 44 | } 45 | 46 | /// Returns a reference to the inner source. 47 | #[inline] 48 | pub fn inner(&self) -> &I { 49 | &self.input 50 | } 51 | 52 | /// Returns a mutable reference to the inner source. 53 | #[inline] 54 | pub fn inner_mut(&mut self) -> &mut I { 55 | &mut self.input 56 | } 57 | 58 | /// Returns the inner source. 59 | #[inline] 60 | pub fn into_inner(self) -> I { 61 | self.input 62 | } 63 | } 64 | 65 | impl Iterator for ChannelVolume 66 | where 67 | I: Source, 68 | { 69 | type Item = Sample; 70 | 71 | #[inline] 72 | fn next(&mut self) -> Option { 73 | // TODO Need a test for this 74 | if self.current_channel >= self.channel_volumes.len() { 75 | self.current_channel = 0; 76 | self.current_sample = None; 77 | let num_channels = self.input.channels(); 78 | for _ in 0..num_channels { 79 | if let Some(s) = self.input.next() { 80 | self.current_sample = Some(self.current_sample.unwrap_or(0.0) + s); 81 | } 82 | } 83 | self.current_sample.map(|s| s / num_channels as f32); 84 | } 85 | let result = self 86 | .current_sample 87 | .map(|s| s * self.channel_volumes[self.current_channel]); 88 | self.current_channel += 1; 89 | result 90 | } 91 | 92 | #[inline] 93 | fn size_hint(&self) -> (usize, Option) { 94 | self.input.size_hint() 95 | } 96 | } 97 | 98 | impl ExactSizeIterator for ChannelVolume where I: Source + ExactSizeIterator {} 99 | 100 | impl Source for ChannelVolume 101 | where 102 | I: Source, 103 | { 104 | #[inline] 105 | fn current_span_len(&self) -> Option { 106 | self.input.current_span_len() 107 | } 108 | 109 | #[inline] 110 | fn channels(&self) -> ChannelCount { 111 | self.channel_volumes.len() as ChannelCount 112 | } 113 | 114 | #[inline] 115 | fn sample_rate(&self) -> SampleRate { 116 | self.input.sample_rate() 117 | } 118 | 119 | #[inline] 120 | fn total_duration(&self) -> Option { 121 | self.input.total_duration() 122 | } 123 | 124 | #[inline] 125 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 126 | self.input.try_seek(pos) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/source/chirp.rs: -------------------------------------------------------------------------------- 1 | //! Chirp/sweep source. 2 | 3 | use crate::common::{ChannelCount, SampleRate}; 4 | use crate::Source; 5 | use std::{f32::consts::TAU, time::Duration}; 6 | 7 | /// Convenience function to create a new `Chirp` source. 8 | #[inline] 9 | pub fn chirp( 10 | sample_rate: SampleRate, 11 | start_frequency: f32, 12 | end_frequency: f32, 13 | duration: Duration, 14 | ) -> Chirp { 15 | Chirp::new(sample_rate, start_frequency, end_frequency, duration) 16 | } 17 | 18 | /// Generate a sine wave with an instantaneous frequency that changes/sweeps linearly over time. 19 | /// At the end of the chirp, once the `end_frequency` is reached, the source is exhausted. 20 | #[derive(Clone, Debug)] 21 | pub struct Chirp { 22 | start_frequency: f32, 23 | end_frequency: f32, 24 | sample_rate: SampleRate, 25 | total_samples: u64, 26 | elapsed_samples: u64, 27 | } 28 | 29 | impl Chirp { 30 | fn new( 31 | sample_rate: SampleRate, 32 | start_frequency: f32, 33 | end_frequency: f32, 34 | duration: Duration, 35 | ) -> Self { 36 | Self { 37 | sample_rate, 38 | start_frequency, 39 | end_frequency, 40 | total_samples: (duration.as_secs_f64() * (sample_rate as f64)) as u64, 41 | elapsed_samples: 0, 42 | } 43 | } 44 | } 45 | 46 | impl Iterator for Chirp { 47 | type Item = f32; 48 | 49 | fn next(&mut self) -> Option { 50 | let i = self.elapsed_samples; 51 | let ratio = self.elapsed_samples as f32 / self.total_samples as f32; 52 | self.elapsed_samples += 1; 53 | let freq = self.start_frequency * (1.0 - ratio) + self.end_frequency * ratio; 54 | let t = (i as f32 / self.sample_rate() as f32) * TAU * freq; 55 | Some(t.sin()) 56 | } 57 | } 58 | 59 | impl Source for Chirp { 60 | fn current_span_len(&self) -> Option { 61 | None 62 | } 63 | 64 | fn channels(&self) -> ChannelCount { 65 | 1 66 | } 67 | 68 | fn sample_rate(&self) -> SampleRate { 69 | self.sample_rate 70 | } 71 | 72 | fn total_duration(&self) -> Option { 73 | let secs: f64 = self.total_samples as f64 / self.sample_rate as f64; 74 | Some(Duration::new(1, 0).mul_f64(secs)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/source/crossfade.rs: -------------------------------------------------------------------------------- 1 | use crate::source::{FadeIn, Mix, TakeDuration}; 2 | use crate::Source; 3 | use std::time::Duration; 4 | 5 | /// Mixes one sound fading out with another sound fading in for the given 6 | /// duration. 7 | /// 8 | /// Only the crossfaded portion (beginning of fadeout, beginning of fadein) is 9 | /// returned. 10 | pub fn crossfade( 11 | input_fadeout: I1, 12 | input_fadein: I2, 13 | duration: Duration, 14 | ) -> Crossfade 15 | where 16 | I1: Source, 17 | I2: Source, 18 | { 19 | let mut input_fadeout = input_fadeout.take_duration(duration); 20 | input_fadeout.set_filter_fadeout(); 21 | let input_fadein = input_fadein.take_duration(duration).fade_in(duration); 22 | input_fadeout.mix(input_fadein) 23 | } 24 | 25 | /// Mixes one sound fading out with another sound fading in for the given 26 | /// duration. 27 | /// 28 | /// Only the crossfaded portion (beginning of fadeout, beginning of fadein) is 29 | /// covered. 30 | pub type Crossfade = Mix, FadeIn>>; 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | use crate::buffer::SamplesBuffer; 36 | use crate::source::Zero; 37 | 38 | fn dummy_source(length: u8) -> SamplesBuffer { 39 | let data: Vec = (1..=length).map(f32::from).collect(); 40 | SamplesBuffer::new(1, 1, data) 41 | } 42 | 43 | #[test] 44 | fn test_crossfade_with_self() { 45 | let source1 = dummy_source(10); 46 | let source2 = dummy_source(10); 47 | let mut mixed = crossfade( 48 | source1, 49 | source2, 50 | Duration::from_secs(5) + Duration::from_nanos(1), 51 | ); 52 | assert_eq!(mixed.next(), Some(1.0)); 53 | assert_eq!(mixed.next(), Some(2.0)); 54 | assert_eq!(mixed.next(), Some(3.0)); 55 | assert_eq!(mixed.next(), Some(4.0)); 56 | assert_eq!(mixed.next(), Some(5.0)); 57 | assert_eq!(mixed.next(), None); 58 | } 59 | 60 | #[test] 61 | fn test_crossfade() { 62 | let source1 = dummy_source(10); 63 | let source2 = Zero::new(1, 1); 64 | let mixed = crossfade( 65 | source1, 66 | source2, 67 | Duration::from_secs(5) + Duration::from_nanos(1), 68 | ); 69 | let result = mixed.collect::>(); 70 | assert_eq!(result.len(), 5); 71 | assert!(result 72 | .iter() 73 | .zip(vec![1.0, 2.0 * 0.8, 3.0 * 0.6, 4.0 * 0.4, 5.0 * 0.2]) 74 | .all(|(a, b)| (a - b).abs() < 1e-6)); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/source/delay.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::Source; 6 | 7 | fn remaining_samples( 8 | until_playback: Duration, 9 | sample_rate: SampleRate, 10 | channels: ChannelCount, 11 | ) -> usize { 12 | let ns = until_playback.as_secs() * 1_000_000_000 + until_playback.subsec_nanos() as u64; 13 | let samples = ns * channels as u64 * sample_rate as u64 / 1_000_000_000; 14 | samples as usize 15 | } 16 | 17 | /// Internal function that builds a `Delay` object. 18 | pub fn delay(input: I, duration: Duration) -> Delay 19 | where 20 | I: Source, 21 | { 22 | Delay { 23 | remaining_samples: remaining_samples(duration, input.sample_rate(), input.channels()), 24 | requested_duration: duration, 25 | input, 26 | } 27 | } 28 | 29 | /// A source that delays the given source by a certain amount. 30 | #[derive(Clone, Debug)] 31 | pub struct Delay { 32 | input: I, 33 | remaining_samples: usize, 34 | requested_duration: Duration, 35 | } 36 | 37 | impl Delay 38 | where 39 | I: Source, 40 | { 41 | /// Returns a reference to the inner source. 42 | #[inline] 43 | pub fn inner(&self) -> &I { 44 | &self.input 45 | } 46 | 47 | /// Returns a mutable reference to the inner source. 48 | #[inline] 49 | pub fn inner_mut(&mut self) -> &mut I { 50 | &mut self.input 51 | } 52 | 53 | /// Returns the inner source. 54 | #[inline] 55 | pub fn into_inner(self) -> I { 56 | self.input 57 | } 58 | } 59 | 60 | impl Iterator for Delay 61 | where 62 | I: Source, 63 | { 64 | type Item = ::Item; 65 | 66 | #[inline] 67 | fn next(&mut self) -> Option<::Item> { 68 | if self.remaining_samples >= 1 { 69 | self.remaining_samples -= 1; 70 | Some(0.0) 71 | } else { 72 | self.input.next() 73 | } 74 | } 75 | 76 | #[inline] 77 | fn size_hint(&self) -> (usize, Option) { 78 | let (min, max) = self.input.size_hint(); 79 | ( 80 | min + self.remaining_samples, 81 | max.map(|v| v + self.remaining_samples), 82 | ) 83 | } 84 | } 85 | 86 | impl Source for Delay 87 | where 88 | I: Iterator + Source, 89 | { 90 | #[inline] 91 | fn current_span_len(&self) -> Option { 92 | self.input 93 | .current_span_len() 94 | .map(|val| val + self.remaining_samples) 95 | } 96 | 97 | #[inline] 98 | fn channels(&self) -> ChannelCount { 99 | self.input.channels() 100 | } 101 | 102 | #[inline] 103 | fn sample_rate(&self) -> SampleRate { 104 | self.input.sample_rate() 105 | } 106 | 107 | #[inline] 108 | fn total_duration(&self) -> Option { 109 | self.input 110 | .total_duration() 111 | .map(|val| val + self.requested_duration) 112 | } 113 | 114 | /// Pos is seen from the perspective of the api user. 115 | /// 116 | /// # Example 117 | /// 118 | /// ```ignore 119 | /// use std::time::Duration; 120 | /// 121 | /// let mut source = inner_source.delay(Duration::from_secs(10)); 122 | /// source.try_seek(Duration::from_secs(15)); 123 | /// 124 | /// // inner_source is now at pos: Duration::from_secs(5); 125 | /// ``` 126 | /// 127 | #[inline] 128 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 129 | if pos < self.requested_duration { 130 | self.input.try_seek(Duration::ZERO)?; 131 | let until_playback = self.requested_duration - pos; 132 | self.remaining_samples = 133 | remaining_samples(until_playback, self.sample_rate(), self.channels()); 134 | } 135 | let compensated_for_delay = pos.saturating_sub(self.requested_duration); 136 | self.input.try_seek(compensated_for_delay) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/source/distortion.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::Source; 6 | 7 | /// Internal function that builds a `Distortion` object. 8 | pub(crate) fn distortion(input: I, gain: f32, threshold: f32) -> Distortion 9 | where 10 | I: Source, 11 | { 12 | Distortion { 13 | input, 14 | gain, 15 | threshold, 16 | } 17 | } 18 | 19 | /// Filter that applies a distortion effect to the source. 20 | #[derive(Clone, Debug)] 21 | pub struct Distortion { 22 | input: I, 23 | gain: f32, 24 | threshold: f32, 25 | } 26 | 27 | impl Distortion { 28 | /// Modifies the distortion gain. 29 | #[inline] 30 | pub fn set_gain(&mut self, gain: f32) { 31 | self.gain = gain; 32 | } 33 | 34 | /// Modifies the distortion threshold. 35 | #[inline] 36 | pub fn set_threshold(&mut self, threshold: f32) { 37 | self.threshold = threshold; 38 | } 39 | 40 | /// Returns a reference to the inner source. 41 | #[inline] 42 | pub fn inner(&self) -> &I { 43 | &self.input 44 | } 45 | 46 | /// Returns a mutable reference to the inner source. 47 | #[inline] 48 | pub fn inner_mut(&mut self) -> &mut I { 49 | &mut self.input 50 | } 51 | 52 | /// Returns the inner source. 53 | #[inline] 54 | pub fn into_inner(self) -> I { 55 | self.input 56 | } 57 | } 58 | 59 | impl Iterator for Distortion 60 | where 61 | I: Source, 62 | { 63 | type Item = I::Item; 64 | 65 | #[inline] 66 | fn next(&mut self) -> Option { 67 | self.input.next().map(|value| { 68 | let v = value * self.gain; 69 | let t = self.threshold; 70 | v.clamp(-t, t) 71 | }) 72 | } 73 | 74 | #[inline] 75 | fn size_hint(&self) -> (usize, Option) { 76 | self.input.size_hint() 77 | } 78 | } 79 | 80 | impl ExactSizeIterator for Distortion where I: Source + ExactSizeIterator {} 81 | 82 | impl Source for Distortion 83 | where 84 | I: Source, 85 | { 86 | #[inline] 87 | fn current_span_len(&self) -> Option { 88 | self.input.current_span_len() 89 | } 90 | 91 | #[inline] 92 | fn channels(&self) -> ChannelCount { 93 | self.input.channels() 94 | } 95 | 96 | #[inline] 97 | fn sample_rate(&self) -> SampleRate { 98 | self.input.sample_rate() 99 | } 100 | 101 | #[inline] 102 | fn total_duration(&self) -> Option { 103 | self.input.total_duration() 104 | } 105 | 106 | #[inline] 107 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 108 | self.input.try_seek(pos) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/source/done.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicUsize, Ordering}; 2 | use std::sync::Arc; 3 | use std::time::Duration; 4 | 5 | use super::SeekError; 6 | use crate::common::{ChannelCount, SampleRate}; 7 | use crate::Source; 8 | 9 | /// When the inner source is empty this decrements a `AtomicUsize`. 10 | #[derive(Debug, Clone)] 11 | pub struct Done { 12 | input: I, 13 | signal: Arc, 14 | signal_sent: bool, 15 | } 16 | 17 | impl Done { 18 | /// When the inner source is empty the AtomicUsize passed in is decremented. 19 | /// If it was zero it will overflow negatively. 20 | #[inline] 21 | pub fn new(input: I, signal: Arc) -> Done { 22 | Done { 23 | input, 24 | signal, 25 | signal_sent: false, 26 | } 27 | } 28 | 29 | /// Returns a reference to the inner source. 30 | #[inline] 31 | pub fn inner(&self) -> &I { 32 | &self.input 33 | } 34 | 35 | /// Returns a mutable reference to the inner source. 36 | #[inline] 37 | pub fn inner_mut(&mut self) -> &mut I { 38 | &mut self.input 39 | } 40 | 41 | /// Returns the inner source. 42 | #[inline] 43 | pub fn into_inner(self) -> I { 44 | self.input 45 | } 46 | } 47 | 48 | impl Iterator for Done 49 | where 50 | I: Source, 51 | { 52 | type Item = I::Item; 53 | 54 | #[inline] 55 | fn next(&mut self) -> Option { 56 | let next = self.input.next(); 57 | if !self.signal_sent && next.is_none() { 58 | self.signal.fetch_sub(1, Ordering::Relaxed); 59 | self.signal_sent = true; 60 | } 61 | next 62 | } 63 | 64 | fn size_hint(&self) -> (usize, Option) { 65 | self.input.size_hint() 66 | } 67 | } 68 | 69 | impl Source for Done 70 | where 71 | I: Source, 72 | { 73 | #[inline] 74 | fn current_span_len(&self) -> Option { 75 | self.input.current_span_len() 76 | } 77 | 78 | #[inline] 79 | fn channels(&self) -> ChannelCount { 80 | self.input.channels() 81 | } 82 | 83 | #[inline] 84 | fn sample_rate(&self) -> SampleRate { 85 | self.input.sample_rate() 86 | } 87 | 88 | #[inline] 89 | fn total_duration(&self) -> Option { 90 | self.input.total_duration() 91 | } 92 | 93 | #[inline] 94 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 95 | self.input.try_seek(pos) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/source/empty.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::{Sample, Source}; 6 | 7 | /// An empty source. 8 | #[derive(Debug, Copy, Clone)] 9 | pub struct Empty(); 10 | 11 | impl Default for Empty { 12 | #[inline] 13 | fn default() -> Self { 14 | Self::new() 15 | } 16 | } 17 | 18 | impl Empty { 19 | /// An empty source that immediately ends without ever returning a sample to 20 | /// play 21 | #[inline] 22 | pub fn new() -> Empty { 23 | Empty() 24 | } 25 | } 26 | 27 | impl Iterator for Empty { 28 | type Item = Sample; 29 | 30 | #[inline] 31 | fn next(&mut self) -> Option { 32 | None 33 | } 34 | } 35 | 36 | impl Source for Empty { 37 | #[inline] 38 | fn current_span_len(&self) -> Option { 39 | None 40 | } 41 | 42 | #[inline] 43 | fn channels(&self) -> ChannelCount { 44 | 1 45 | } 46 | 47 | #[inline] 48 | fn sample_rate(&self) -> SampleRate { 49 | 48000 50 | } 51 | 52 | #[inline] 53 | fn total_duration(&self) -> Option { 54 | Some(Duration::new(0, 0)) 55 | } 56 | 57 | #[inline] 58 | fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { 59 | Err(SeekError::NotSupported { 60 | underlying_source: std::any::type_name::(), 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/source/empty_callback.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::{Sample, Source}; 6 | 7 | /// An empty source that executes a callback function 8 | pub struct EmptyCallback { 9 | callback: Box, 10 | } 11 | 12 | impl EmptyCallback { 13 | #[inline] 14 | /// Create an empty source that executes a callback function. 15 | /// Example use-case: 16 | /// 17 | /// Detect and do something when the source before this one has ended. 18 | pub fn new(callback: Box) -> EmptyCallback { 19 | EmptyCallback { callback } 20 | } 21 | } 22 | 23 | impl Iterator for EmptyCallback { 24 | type Item = Sample; 25 | 26 | #[inline] 27 | fn next(&mut self) -> Option { 28 | (self.callback)(); 29 | None 30 | } 31 | } 32 | 33 | impl Source for EmptyCallback { 34 | #[inline] 35 | fn current_span_len(&self) -> Option { 36 | None 37 | } 38 | 39 | #[inline] 40 | fn channels(&self) -> ChannelCount { 41 | 1 42 | } 43 | 44 | #[inline] 45 | fn sample_rate(&self) -> SampleRate { 46 | 48000 47 | } 48 | 49 | #[inline] 50 | fn total_duration(&self) -> Option { 51 | Some(Duration::new(0, 0)) 52 | } 53 | 54 | #[inline] 55 | fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { 56 | Err(SeekError::NotSupported { 57 | underlying_source: std::any::type_name::(), 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/source/fadein.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::{linear_ramp::linear_gain_ramp, LinearGainRamp, SeekError}; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::Source; 6 | 7 | /// Internal function that builds a `FadeIn` object. 8 | pub fn fadein(input: I, duration: Duration) -> FadeIn 9 | where 10 | I: Source, 11 | { 12 | FadeIn { 13 | input: linear_gain_ramp(input, duration, 0.0f32, 1.0f32, false), 14 | } 15 | } 16 | 17 | /// Filter that modifies raises the volume from silence over a time period. 18 | #[derive(Clone, Debug)] 19 | pub struct FadeIn { 20 | input: LinearGainRamp, 21 | } 22 | 23 | impl FadeIn 24 | where 25 | I: Source, 26 | { 27 | /// Returns a reference to the inner source. 28 | #[inline] 29 | pub fn inner(&self) -> &I { 30 | self.input.inner() 31 | } 32 | 33 | /// Returns a mutable reference to the inner source. 34 | #[inline] 35 | pub fn inner_mut(&mut self) -> &mut I { 36 | self.input.inner_mut() 37 | } 38 | 39 | /// Returns the inner source. 40 | #[inline] 41 | pub fn into_inner(self) -> I { 42 | self.input.into_inner() 43 | } 44 | } 45 | 46 | impl Iterator for FadeIn 47 | where 48 | I: Source, 49 | { 50 | type Item = I::Item; 51 | 52 | #[inline] 53 | fn next(&mut self) -> Option { 54 | self.input.next() 55 | } 56 | 57 | #[inline] 58 | fn size_hint(&self) -> (usize, Option) { 59 | self.input.size_hint() 60 | } 61 | } 62 | 63 | impl ExactSizeIterator for FadeIn where I: Source + ExactSizeIterator {} 64 | 65 | impl Source for FadeIn 66 | where 67 | I: Source, 68 | { 69 | #[inline] 70 | fn current_span_len(&self) -> Option { 71 | self.inner().current_span_len() 72 | } 73 | 74 | #[inline] 75 | fn channels(&self) -> ChannelCount { 76 | self.inner().channels() 77 | } 78 | 79 | #[inline] 80 | fn sample_rate(&self) -> SampleRate { 81 | self.inner().sample_rate() 82 | } 83 | 84 | #[inline] 85 | fn total_duration(&self) -> Option { 86 | self.inner().total_duration() 87 | } 88 | 89 | #[inline] 90 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 91 | self.inner_mut().try_seek(pos) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/source/fadeout.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::{linear_ramp::linear_gain_ramp, LinearGainRamp, SeekError}; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::Source; 6 | 7 | /// Internal function that builds a `FadeOut` object. 8 | pub fn fadeout(input: I, duration: Duration) -> FadeOut 9 | where 10 | I: Source, 11 | { 12 | FadeOut { 13 | input: linear_gain_ramp(input, duration, 1.0f32, 0.0f32, true), 14 | } 15 | } 16 | 17 | /// Filter that modifies lowers the volume to silence over a time period. 18 | #[derive(Clone, Debug)] 19 | pub struct FadeOut { 20 | input: LinearGainRamp, 21 | } 22 | 23 | impl FadeOut 24 | where 25 | I: Source, 26 | { 27 | /// Returns a reference to the inner source. 28 | #[inline] 29 | pub fn inner(&self) -> &I { 30 | self.input.inner() 31 | } 32 | 33 | /// Returns a mutable reference to the inner source. 34 | #[inline] 35 | pub fn inner_mut(&mut self) -> &mut I { 36 | self.input.inner_mut() 37 | } 38 | 39 | /// Returns the inner source. 40 | #[inline] 41 | pub fn into_inner(self) -> I { 42 | self.input.into_inner() 43 | } 44 | } 45 | 46 | impl Iterator for FadeOut 47 | where 48 | I: Source, 49 | { 50 | type Item = I::Item; 51 | 52 | #[inline] 53 | fn next(&mut self) -> Option { 54 | self.input.next() 55 | } 56 | 57 | #[inline] 58 | fn size_hint(&self) -> (usize, Option) { 59 | self.input.size_hint() 60 | } 61 | } 62 | 63 | impl ExactSizeIterator for FadeOut where I: Source + ExactSizeIterator {} 64 | 65 | impl Source for FadeOut 66 | where 67 | I: Source, 68 | { 69 | #[inline] 70 | fn current_span_len(&self) -> Option { 71 | self.inner().current_span_len() 72 | } 73 | 74 | #[inline] 75 | fn channels(&self) -> ChannelCount { 76 | self.inner().channels() 77 | } 78 | 79 | #[inline] 80 | fn sample_rate(&self) -> SampleRate { 81 | self.inner().sample_rate() 82 | } 83 | 84 | #[inline] 85 | fn total_duration(&self) -> Option { 86 | self.inner().total_duration() 87 | } 88 | 89 | #[inline] 90 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 91 | self.inner_mut().try_seek(pos) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/source/from_factory.rs: -------------------------------------------------------------------------------- 1 | use crate::source::{from_iter, FromIter}; 2 | 3 | /// Builds a source that chains sources built from a factory. 4 | /// 5 | /// The `factory` parameter is a function that produces a source. The source is then played. 6 | /// Whenever the source ends, `factory` is called again in order to produce the source that is 7 | /// played next. 8 | /// 9 | /// If the `factory` closure returns `None`, then the sound ends. 10 | pub fn from_factory(factory: F) -> FromIter> 11 | where 12 | F: FnMut() -> Option, 13 | { 14 | from_iter(FromFactoryIter { factory }) 15 | } 16 | 17 | /// Internal type used by `from_factory`. 18 | pub struct FromFactoryIter { 19 | factory: F, 20 | } 21 | 22 | impl Iterator for FromFactoryIter 23 | where 24 | F: FnMut() -> Option, 25 | { 26 | type Item = S; 27 | 28 | #[inline] 29 | fn next(&mut self) -> Option { 30 | (self.factory)() 31 | } 32 | 33 | #[inline] 34 | fn size_hint(&self) -> (usize, Option) { 35 | (0, None) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/source/from_iter.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::Source; 6 | 7 | /// Builds a source that chains sources provided by an iterator. 8 | /// 9 | /// The `iterator` parameter is an iterator that produces a source. The source is then played. 10 | /// Whenever the source ends, the `iterator` is used again in order to produce the source that is 11 | /// played next. 12 | /// 13 | /// If the `iterator` produces `None`, then the sound ends. 14 | pub fn from_iter(iterator: I) -> FromIter 15 | where 16 | I: IntoIterator, 17 | { 18 | let mut iterator = iterator.into_iter(); 19 | let first_source = iterator.next(); 20 | 21 | FromIter { 22 | iterator, 23 | current_source: first_source, 24 | } 25 | } 26 | 27 | /// A source that chains sources provided by an iterator. 28 | #[derive(Clone)] 29 | pub struct FromIter 30 | where 31 | I: Iterator, 32 | { 33 | // The iterator that provides sources. 34 | iterator: I, 35 | // Is only ever `None` if the first element of the iterator is `None`. 36 | current_source: Option, 37 | } 38 | 39 | impl Iterator for FromIter 40 | where 41 | I: Iterator, 42 | I::Item: Iterator + Source, 43 | { 44 | type Item = ::Item; 45 | 46 | #[inline] 47 | fn next(&mut self) -> Option { 48 | loop { 49 | if let Some(src) = &mut self.current_source { 50 | if let Some(value) = src.next() { 51 | return Some(value); 52 | } 53 | } 54 | 55 | if let Some(src) = self.iterator.next() { 56 | self.current_source = Some(src); 57 | } else { 58 | return None; 59 | } 60 | } 61 | } 62 | 63 | #[inline] 64 | fn size_hint(&self) -> (usize, Option) { 65 | if let Some(cur) = &self.current_source { 66 | (cur.size_hint().0, None) 67 | } else { 68 | (0, None) 69 | } 70 | } 71 | } 72 | 73 | impl Source for FromIter 74 | where 75 | I: Iterator, 76 | I::Item: Iterator + Source, 77 | { 78 | #[inline] 79 | fn current_span_len(&self) -> Option { 80 | // This function is non-trivial because the boundary between the current source and the 81 | // next must be a span boundary as well. 82 | // 83 | // The current sound is free to return `None` for `current_span_len()`, in which case 84 | // we *should* return the number of samples remaining the current sound. 85 | // This can be estimated with `size_hint()`. 86 | // 87 | // If the `size_hint` is `None` as well, we are in the worst case scenario. To handle this 88 | // situation we force a span to have a maximum number of samples indicate by this 89 | // constant. 90 | const THRESHOLD: usize = 10240; 91 | 92 | // Try the current `current_span_len`. 93 | if let Some(src) = &self.current_source { 94 | if let Some(val) = src.current_span_len() { 95 | if val != 0 { 96 | return Some(val); 97 | } 98 | } 99 | } 100 | 101 | // Try the size hint. 102 | if let Some(src) = &self.current_source { 103 | if let Some(val) = src.size_hint().1 { 104 | if val < THRESHOLD && val != 0 { 105 | return Some(val); 106 | } 107 | } 108 | } 109 | 110 | // Otherwise we use the constant value. 111 | Some(THRESHOLD) 112 | } 113 | 114 | #[inline] 115 | fn channels(&self) -> ChannelCount { 116 | if let Some(src) = &self.current_source { 117 | src.channels() 118 | } else { 119 | // Dummy value that only happens if the iterator was empty. 120 | 2 121 | } 122 | } 123 | 124 | #[inline] 125 | fn sample_rate(&self) -> SampleRate { 126 | if let Some(src) = &self.current_source { 127 | src.sample_rate() 128 | } else { 129 | // Dummy value that only happens if the iterator was empty. 130 | 44100 131 | } 132 | } 133 | 134 | #[inline] 135 | fn total_duration(&self) -> Option { 136 | None 137 | } 138 | 139 | #[inline] 140 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 141 | if let Some(source) = self.current_source.as_mut() { 142 | source.try_seek(pos) 143 | } else { 144 | Ok(()) 145 | } 146 | } 147 | } 148 | 149 | #[cfg(test)] 150 | mod tests { 151 | use crate::buffer::SamplesBuffer; 152 | use crate::source::{from_iter, Source}; 153 | 154 | #[test] 155 | fn basic() { 156 | let mut rx = from_iter((0..2).map(|n| { 157 | if n == 0 { 158 | SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0]) 159 | } else if n == 1 { 160 | SamplesBuffer::new(2, 96000, vec![5.0, 5.0, 5.0, 5.0]) 161 | } else { 162 | unreachable!() 163 | } 164 | })); 165 | 166 | assert_eq!(rx.channels(), 1); 167 | assert_eq!(rx.sample_rate(), 48000); 168 | assert_eq!(rx.next(), Some(10.0)); 169 | assert_eq!(rx.next(), Some(-10.0)); 170 | assert_eq!(rx.next(), Some(10.0)); 171 | assert_eq!(rx.next(), Some(-10.0)); 172 | /*assert_eq!(rx.channels(), 2); 173 | assert_eq!(rx.sample_rate(), 96000);*/ 174 | // FIXME: not working 175 | assert_eq!(rx.next(), Some(5.0)); 176 | assert_eq!(rx.next(), Some(5.0)); 177 | assert_eq!(rx.next(), Some(5.0)); 178 | assert_eq!(rx.next(), Some(5.0)); 179 | assert_eq!(rx.next(), None); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/source/mix.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | use std::time::Duration; 3 | 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::source::uniform::UniformSourceIterator; 6 | use crate::source::SeekError; 7 | use crate::Source; 8 | 9 | /// Internal function that builds a `Mix` object. 10 | pub fn mix(input1: I1, input2: I2) -> Mix 11 | where 12 | I1: Source, 13 | I2: Source, 14 | { 15 | let channels = input1.channels(); 16 | let rate = input1.sample_rate(); 17 | 18 | Mix { 19 | input1: UniformSourceIterator::new(input1, channels, rate), 20 | input2: UniformSourceIterator::new(input2, channels, rate), 21 | } 22 | } 23 | 24 | /// Filter that modifies each sample by a given value. 25 | #[derive(Clone)] 26 | pub struct Mix 27 | where 28 | I1: Source, 29 | I2: Source, 30 | { 31 | input1: UniformSourceIterator, 32 | input2: UniformSourceIterator, 33 | } 34 | 35 | impl Iterator for Mix 36 | where 37 | I1: Source, 38 | I2: Source, 39 | { 40 | type Item = I1::Item; 41 | 42 | #[inline] 43 | fn next(&mut self) -> Option { 44 | let s1 = self.input1.next(); 45 | let s2 = self.input2.next(); 46 | 47 | match (s1, s2) { 48 | (Some(s1), Some(s2)) => Some(s1 + s2), 49 | (Some(s1), None) => Some(s1), 50 | (None, Some(s2)) => Some(s2), 51 | (None, None) => None, 52 | } 53 | } 54 | 55 | #[inline] 56 | fn size_hint(&self) -> (usize, Option) { 57 | let s1 = self.input1.size_hint(); 58 | let s2 = self.input2.size_hint(); 59 | 60 | let min = cmp::max(s1.0, s2.0); 61 | let max = match (s1.1, s2.1) { 62 | (Some(s1), Some(s2)) => Some(cmp::max(s1, s2)), 63 | _ => None, 64 | }; 65 | 66 | (min, max) 67 | } 68 | } 69 | 70 | impl ExactSizeIterator for Mix 71 | where 72 | I1: Source + ExactSizeIterator, 73 | I2: Source + ExactSizeIterator, 74 | { 75 | } 76 | 77 | impl Source for Mix 78 | where 79 | I1: Source, 80 | I2: Source, 81 | { 82 | #[inline] 83 | fn current_span_len(&self) -> Option { 84 | let f1 = self.input1.current_span_len(); 85 | let f2 = self.input2.current_span_len(); 86 | 87 | match (f1, f2) { 88 | (Some(f1), Some(f2)) => Some(cmp::min(f1, f2)), 89 | _ => None, 90 | } 91 | } 92 | 93 | #[inline] 94 | fn channels(&self) -> ChannelCount { 95 | self.input1.channels() 96 | } 97 | 98 | #[inline] 99 | fn sample_rate(&self) -> SampleRate { 100 | self.input1.sample_rate() 101 | } 102 | 103 | #[inline] 104 | fn total_duration(&self) -> Option { 105 | let f1 = self.input1.total_duration(); 106 | let f2 = self.input2.total_duration(); 107 | 108 | match (f1, f2) { 109 | (Some(f1), Some(f2)) => Some(cmp::max(f1, f2)), 110 | _ => None, 111 | } 112 | } 113 | 114 | /// Will only attempt a seek if both underlying sources support seek. 115 | #[inline] 116 | fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { 117 | Err(SeekError::NotSupported { 118 | underlying_source: std::any::type_name::(), 119 | }) 120 | 121 | // uncomment when #510 is implemented (query position of playback) 122 | // TODO use source_intact to check if rollback makes sense 123 | 124 | // let org_pos = self.input1.playback_pos(); 125 | // self.input1.try_seek(pos)?; 126 | // 127 | // let res = self.input2.try_seek(pos); 128 | // if res.is_err() { // rollback seek in input1 129 | // self.input1.try_seek(org_pos)?; 130 | // } 131 | // 132 | // res 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/source/noise.rs: -------------------------------------------------------------------------------- 1 | //! Noise sources. 2 | //! 3 | //! 4 | 5 | use crate::{ChannelCount, Source}; 6 | 7 | use super::SeekError; 8 | 9 | use crate::common::SampleRate; 10 | use rand::{rngs::SmallRng, RngCore, SeedableRng}; 11 | 12 | /// Convenience function to create a new `WhiteNoise` noise source. 13 | #[inline] 14 | pub fn white(sample_rate: SampleRate) -> WhiteNoise { 15 | WhiteNoise::new(sample_rate) 16 | } 17 | 18 | /// Convenience function to create a new `PinkNoise` noise source. 19 | #[inline] 20 | pub fn pink(sample_rate: SampleRate) -> PinkNoise { 21 | PinkNoise::new(sample_rate) 22 | } 23 | 24 | /// Generates an infinite stream of random samples in [-1.0, 1.0]. This source generates random 25 | /// samples as provided by the `rand::rngs::SmallRng` randomness source. 26 | #[derive(Clone, Debug)] 27 | pub struct WhiteNoise { 28 | sample_rate: SampleRate, 29 | rng: SmallRng, 30 | } 31 | 32 | impl WhiteNoise { 33 | /// Create a new white noise generator, seeding the RNG with `seed`. 34 | pub fn new_with_seed(sample_rate: SampleRate, seed: u64) -> Self { 35 | Self { 36 | sample_rate, 37 | rng: SmallRng::seed_from_u64(seed), 38 | } 39 | } 40 | 41 | /// Create a new white noise generator, seeding the RNG with system entropy. 42 | pub fn new(sample_rate: SampleRate) -> Self { 43 | Self { 44 | sample_rate, 45 | rng: SmallRng::from_os_rng(), 46 | } 47 | } 48 | } 49 | 50 | impl Iterator for WhiteNoise { 51 | type Item = f32; 52 | 53 | #[inline] 54 | fn next(&mut self) -> Option { 55 | let rand = self.rng.next_u32() as f32 / u32::MAX as f32; 56 | let scaled = rand * 2.0 - 1.0; 57 | Some(scaled) 58 | } 59 | } 60 | 61 | impl Source for WhiteNoise { 62 | #[inline] 63 | fn current_span_len(&self) -> Option { 64 | None 65 | } 66 | 67 | #[inline] 68 | fn channels(&self) -> ChannelCount { 69 | 1 70 | } 71 | 72 | #[inline] 73 | fn sample_rate(&self) -> SampleRate { 74 | self.sample_rate 75 | } 76 | 77 | #[inline] 78 | fn total_duration(&self) -> Option { 79 | None 80 | } 81 | 82 | #[inline] 83 | fn try_seek(&mut self, _: std::time::Duration) -> Result<(), SeekError> { 84 | // Does nothing, should do nothing 85 | Ok(()) 86 | } 87 | } 88 | 89 | /// Generates an infinite stream of pink noise samples in [-1.0, 1.0]. 90 | /// 91 | /// The output of the source is the result of taking the output of the `WhiteNoise` source and 92 | /// filtering it according to a weighted-sum of seven FIR filters after [Paul Kellett's 93 | /// method][pk_method] from *musicdsp.org*. 94 | /// 95 | /// [pk_method]: https://www.musicdsp.org/en/latest/Filters/76-pink-noise-filter.html 96 | pub struct PinkNoise { 97 | white_noise: WhiteNoise, 98 | b: [f32; 7], 99 | } 100 | 101 | impl PinkNoise { 102 | /// Create new pink noise source with given sample rate. 103 | pub fn new(sample_rate: SampleRate) -> Self { 104 | Self { 105 | white_noise: WhiteNoise::new(sample_rate), 106 | b: [0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32], 107 | } 108 | } 109 | } 110 | 111 | impl Iterator for PinkNoise { 112 | type Item = f32; 113 | 114 | fn next(&mut self) -> Option { 115 | let white = self.white_noise.next().unwrap(); 116 | self.b[0] = 0.99886 * self.b[0] + white * 0.0555179; 117 | self.b[1] = 0.99332 * self.b[1] + white * 0.0750759; 118 | self.b[2] = 0.969 * self.b[2] + white * 0.153852; 119 | self.b[3] = 0.8665 * self.b[3] + white * 0.3104856; 120 | self.b[4] = 0.550 * self.b[4] + white * 0.5329522; 121 | self.b[5] = -0.7616 * self.b[5] - white * 0.016898; 122 | 123 | let pink = self.b[0] 124 | + self.b[1] 125 | + self.b[2] 126 | + self.b[3] 127 | + self.b[4] 128 | + self.b[5] 129 | + self.b[6] 130 | + white * 0.5362; 131 | 132 | self.b[6] = white * 0.115926; 133 | 134 | Some(pink) 135 | } 136 | } 137 | 138 | impl Source for PinkNoise { 139 | fn current_span_len(&self) -> Option { 140 | None 141 | } 142 | 143 | fn channels(&self) -> ChannelCount { 144 | 1 145 | } 146 | 147 | fn sample_rate(&self) -> SampleRate { 148 | self.white_noise.sample_rate() 149 | } 150 | 151 | fn total_duration(&self) -> Option { 152 | None 153 | } 154 | 155 | #[inline] 156 | fn try_seek(&mut self, _: std::time::Duration) -> Result<(), SeekError> { 157 | // Does nothing, should do nothing 158 | Ok(()) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/source/pausable.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::Source; 6 | 7 | /// Builds a `Pausable` object. 8 | pub fn pausable(source: I, paused: bool) -> Pausable 9 | where 10 | I: Source, 11 | { 12 | let paused_channels = if paused { 13 | Some(source.channels()) 14 | } else { 15 | None 16 | }; 17 | Pausable { 18 | input: source, 19 | paused_channels, 20 | remaining_paused_samples: 0, 21 | } 22 | } 23 | 24 | /// Wraps a source and makes it pausable by calling [`Pausable::set_paused`] on 25 | /// this object. When the source is paused it returns zero value samples. 26 | /// 27 | /// You can usually still use this from another source wrapping this one by 28 | /// calling `inner_mut` on it. Similarly this provides [`Pausable::inner`] and 29 | /// mutable/destructing variants for accessing the underlying source. 30 | #[derive(Clone, Debug)] 31 | pub struct Pausable { 32 | input: I, 33 | paused_channels: Option, 34 | remaining_paused_samples: ChannelCount, 35 | } 36 | 37 | impl Pausable 38 | where 39 | I: Source, 40 | { 41 | /// Sets whether the filter applies. 42 | /// 43 | /// If set to true, the inner sound stops playing and no samples are processed from it. 44 | #[inline] 45 | pub fn set_paused(&mut self, paused: bool) { 46 | match (self.paused_channels, paused) { 47 | (None, true) => self.paused_channels = Some(self.input.channels()), 48 | (Some(_), false) => self.paused_channels = None, 49 | _ => (), 50 | } 51 | } 52 | 53 | /// Indicates if the data source is in a paused state. 54 | #[inline] 55 | pub fn is_paused(&self) -> bool { 56 | self.paused_channels.is_some() 57 | } 58 | 59 | /// Returns a reference to the inner source. 60 | #[inline] 61 | pub fn inner(&self) -> &I { 62 | &self.input 63 | } 64 | 65 | /// Returns a mutable reference to the inner source. 66 | #[inline] 67 | pub fn inner_mut(&mut self) -> &mut I { 68 | &mut self.input 69 | } 70 | 71 | /// Returns the inner source. 72 | #[inline] 73 | pub fn into_inner(self) -> I { 74 | self.input 75 | } 76 | } 77 | 78 | impl Iterator for Pausable 79 | where 80 | I: Source, 81 | { 82 | type Item = I::Item; 83 | 84 | #[inline] 85 | fn next(&mut self) -> Option { 86 | if self.remaining_paused_samples > 0 { 87 | self.remaining_paused_samples -= 1; 88 | return Some(0.0); 89 | } 90 | 91 | if let Some(paused_channels) = self.paused_channels { 92 | self.remaining_paused_samples = paused_channels - 1; 93 | return Some(0.0); 94 | } 95 | 96 | self.input.next() 97 | } 98 | 99 | #[inline] 100 | fn size_hint(&self) -> (usize, Option) { 101 | self.input.size_hint() 102 | } 103 | } 104 | 105 | impl Source for Pausable 106 | where 107 | I: Source, 108 | { 109 | #[inline] 110 | fn current_span_len(&self) -> Option { 111 | self.input.current_span_len() 112 | } 113 | 114 | #[inline] 115 | fn channels(&self) -> ChannelCount { 116 | self.input.channels() 117 | } 118 | 119 | #[inline] 120 | fn sample_rate(&self) -> SampleRate { 121 | self.input.sample_rate() 122 | } 123 | 124 | #[inline] 125 | fn total_duration(&self) -> Option { 126 | self.input.total_duration() 127 | } 128 | 129 | #[inline] 130 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 131 | self.input.try_seek(pos) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/source/periodic.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::Source; 6 | 7 | /// Internal function that builds a `PeriodicAccess` object. 8 | pub fn periodic(source: I, period: Duration, modifier: F) -> PeriodicAccess 9 | where 10 | I: Source, 11 | { 12 | // TODO: handle the fact that the samples rate can change 13 | // TODO: generally, just wrong 14 | let update_ms = period.as_secs() as u32 * 1_000 + period.subsec_millis(); 15 | let update_frequency = (update_ms * source.sample_rate()) / 1000 * source.channels() as u32; 16 | 17 | PeriodicAccess { 18 | input: source, 19 | modifier, 20 | // Can overflow when subtracting if this is 0 21 | update_frequency: if update_frequency == 0 { 22 | 1 23 | } else { 24 | update_frequency 25 | }, 26 | samples_until_update: 1, 27 | } 28 | } 29 | 30 | /// Calls a function on a source every time a period elapsed. 31 | #[derive(Clone, Debug)] 32 | pub struct PeriodicAccess { 33 | // The inner source. 34 | input: I, 35 | 36 | // Closure that gets access to `inner`. 37 | modifier: F, 38 | 39 | // The frequency with which local_volume should be updated by remote_volume 40 | update_frequency: u32, 41 | 42 | // How many samples remain until it is time to update local_volume with remote_volume. 43 | samples_until_update: u32, 44 | } 45 | 46 | impl PeriodicAccess 47 | where 48 | I: Source, 49 | 50 | F: FnMut(&mut I), 51 | { 52 | /// Returns a reference to the inner source. 53 | #[inline] 54 | pub fn inner(&self) -> &I { 55 | &self.input 56 | } 57 | 58 | /// Returns a mutable reference to the inner source. 59 | #[inline] 60 | pub fn inner_mut(&mut self) -> &mut I { 61 | &mut self.input 62 | } 63 | 64 | /// Returns the inner source. 65 | #[inline] 66 | pub fn into_inner(self) -> I { 67 | self.input 68 | } 69 | } 70 | 71 | impl Iterator for PeriodicAccess 72 | where 73 | I: Source, 74 | 75 | F: FnMut(&mut I), 76 | { 77 | type Item = I::Item; 78 | 79 | #[inline] 80 | fn next(&mut self) -> Option { 81 | self.samples_until_update -= 1; 82 | if self.samples_until_update == 0 { 83 | (self.modifier)(&mut self.input); 84 | self.samples_until_update = self.update_frequency; 85 | } 86 | 87 | self.input.next() 88 | } 89 | 90 | #[inline] 91 | fn size_hint(&self) -> (usize, Option) { 92 | self.input.size_hint() 93 | } 94 | } 95 | 96 | impl Source for PeriodicAccess 97 | where 98 | I: Source, 99 | 100 | F: FnMut(&mut I), 101 | { 102 | #[inline] 103 | fn current_span_len(&self) -> Option { 104 | self.input.current_span_len() 105 | } 106 | 107 | #[inline] 108 | fn channels(&self) -> ChannelCount { 109 | self.input.channels() 110 | } 111 | 112 | #[inline] 113 | fn sample_rate(&self) -> SampleRate { 114 | self.input.sample_rate() 115 | } 116 | 117 | #[inline] 118 | fn total_duration(&self) -> Option { 119 | self.input.total_duration() 120 | } 121 | 122 | #[inline] 123 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 124 | self.input.try_seek(pos) 125 | } 126 | } 127 | 128 | #[cfg(test)] 129 | mod tests { 130 | use std::cell::RefCell; 131 | use std::time::Duration; 132 | 133 | use crate::buffer::SamplesBuffer; 134 | use crate::source::Source; 135 | 136 | #[test] 137 | fn stereo_access() { 138 | // Stereo, 1Hz audio buffer 139 | let inner = SamplesBuffer::new(2, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); 140 | 141 | let cnt = RefCell::new(0); 142 | 143 | let mut source = inner.periodic_access(Duration::from_millis(1000), |_src| { 144 | *cnt.borrow_mut() += 1; 145 | }); 146 | 147 | assert_eq!(*cnt.borrow(), 0); 148 | // Always called on first access! 149 | assert_eq!(source.next(), Some(10.0)); 150 | assert_eq!(*cnt.borrow(), 1); 151 | // Called every 1 second afterwards 152 | assert_eq!(source.next(), Some(-10.0)); 153 | assert_eq!(*cnt.borrow(), 1); 154 | assert_eq!(source.next(), Some(10.0)); 155 | assert_eq!(*cnt.borrow(), 2); 156 | assert_eq!(source.next(), Some(-10.0)); 157 | assert_eq!(*cnt.borrow(), 2); 158 | assert_eq!(source.next(), Some(20.0)); 159 | assert_eq!(*cnt.borrow(), 3); 160 | assert_eq!(source.next(), Some(-20.0)); 161 | assert_eq!(*cnt.borrow(), 3); 162 | } 163 | 164 | #[test] 165 | fn fast_access_overflow() { 166 | // 1hz is lower than 0.5 samples per 5ms 167 | let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); 168 | let mut source = inner.periodic_access(Duration::from_millis(5), |_src| {}); 169 | 170 | source.next(); 171 | source.next(); // Would overflow here. 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/source/position.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::Source; 6 | 7 | /// Internal function that builds a `TrackPosition` object. See trait docs for 8 | /// details 9 | pub fn track_position(source: I) -> TrackPosition { 10 | TrackPosition { 11 | input: source, 12 | samples_counted: 0, 13 | offset_duration: 0.0, 14 | current_span_sample_rate: 0, 15 | current_span_channels: 0, 16 | current_span_len: None, 17 | } 18 | } 19 | 20 | /// Tracks the elapsed duration since the start of the underlying source. 21 | #[derive(Debug)] 22 | pub struct TrackPosition { 23 | input: I, 24 | samples_counted: usize, 25 | offset_duration: f64, 26 | current_span_sample_rate: SampleRate, 27 | current_span_channels: ChannelCount, 28 | current_span_len: Option, 29 | } 30 | 31 | impl TrackPosition { 32 | /// Returns a reference to the inner source. 33 | #[inline] 34 | pub fn inner(&self) -> &I { 35 | &self.input 36 | } 37 | 38 | /// Returns a mutable reference to the inner source. 39 | #[inline] 40 | pub fn inner_mut(&mut self) -> &mut I { 41 | &mut self.input 42 | } 43 | 44 | /// Returns the inner source. 45 | #[inline] 46 | pub fn into_inner(self) -> I { 47 | self.input 48 | } 49 | } 50 | 51 | impl TrackPosition 52 | where 53 | I: Source, 54 | { 55 | /// Returns the position of the underlying source relative to its start. 56 | /// 57 | /// If a speedup and or delay is applied after applying a 58 | /// [`Source::track_position`] it will not be reflected in the position 59 | /// returned by [`get_pos`](TrackPosition::get_pos). 60 | /// 61 | /// This can get confusing when using [`get_pos()`](TrackPosition::get_pos) 62 | /// together with [`Source::try_seek()`] as the the latter does take all 63 | /// speedup's and delay's into account. Its recommended therefore to apply 64 | /// track_position after speedup's and delay's. 65 | #[inline] 66 | pub fn get_pos(&self) -> Duration { 67 | let seconds = self.samples_counted as f64 68 | / self.input.sample_rate() as f64 69 | / self.input.channels() as f64 70 | + self.offset_duration; 71 | Duration::from_secs_f64(seconds) 72 | } 73 | 74 | #[inline] 75 | fn set_current_span(&mut self) { 76 | self.current_span_len = self.current_span_len(); 77 | self.current_span_sample_rate = self.sample_rate(); 78 | self.current_span_channels = self.channels(); 79 | } 80 | } 81 | 82 | impl Iterator for TrackPosition 83 | where 84 | I: Source, 85 | { 86 | type Item = I::Item; 87 | 88 | #[inline] 89 | fn next(&mut self) -> Option { 90 | // This should only be executed once at the first call to next. 91 | if self.current_span_len.is_none() { 92 | self.set_current_span(); 93 | } 94 | 95 | let item = self.input.next(); 96 | if item.is_some() { 97 | self.samples_counted += 1; 98 | 99 | // At the end of a span add the duration of this span to 100 | // offset_duration and start collecting samples again. 101 | if Some(self.samples_counted) == self.current_span_len() { 102 | self.offset_duration += self.samples_counted as f64 103 | / self.current_span_sample_rate as f64 104 | / self.current_span_channels as f64; 105 | 106 | // Reset. 107 | self.samples_counted = 0; 108 | self.set_current_span(); 109 | }; 110 | }; 111 | item 112 | } 113 | 114 | #[inline] 115 | fn size_hint(&self) -> (usize, Option) { 116 | self.input.size_hint() 117 | } 118 | } 119 | 120 | impl Source for TrackPosition 121 | where 122 | I: Source, 123 | { 124 | #[inline] 125 | fn current_span_len(&self) -> Option { 126 | self.input.current_span_len() 127 | } 128 | 129 | #[inline] 130 | fn channels(&self) -> ChannelCount { 131 | self.input.channels() 132 | } 133 | 134 | #[inline] 135 | fn sample_rate(&self) -> SampleRate { 136 | self.input.sample_rate() 137 | } 138 | 139 | #[inline] 140 | fn total_duration(&self) -> Option { 141 | self.input.total_duration() 142 | } 143 | 144 | #[inline] 145 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 146 | let result = self.input.try_seek(pos); 147 | if result.is_ok() { 148 | self.offset_duration = pos.as_secs_f64(); 149 | // This assumes that the seek implementation of the codec always 150 | // starts again at the beginning of a span. Which is the case with 151 | // symphonia. 152 | self.samples_counted = 0; 153 | } 154 | result 155 | } 156 | } 157 | 158 | #[cfg(test)] 159 | mod tests { 160 | use std::time::Duration; 161 | 162 | use crate::buffer::SamplesBuffer; 163 | use crate::source::Source; 164 | 165 | #[test] 166 | fn test_position() { 167 | let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); 168 | let mut source = inner.track_position(); 169 | 170 | assert_eq!(source.get_pos().as_secs_f32(), 0.0); 171 | source.next(); 172 | assert_eq!(source.get_pos().as_secs_f32(), 1.0); 173 | 174 | source.next(); 175 | assert_eq!(source.get_pos().as_secs_f32(), 2.0); 176 | 177 | assert!(source.try_seek(Duration::new(1, 0)).is_ok()); 178 | assert_eq!(source.get_pos().as_secs_f32(), 1.0); 179 | } 180 | 181 | #[test] 182 | fn test_position_in_presence_of_speedup() { 183 | let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); 184 | let mut source = inner.speed(2.0).track_position(); 185 | 186 | assert_eq!(source.get_pos().as_secs_f32(), 0.0); 187 | source.next(); 188 | assert_eq!(source.get_pos().as_secs_f32(), 0.5); 189 | 190 | source.next(); 191 | assert_eq!(source.get_pos().as_secs_f32(), 1.0); 192 | 193 | assert!(source.try_seek(Duration::new(1, 0)).is_ok()); 194 | assert_eq!(source.get_pos().as_secs_f32(), 1.0); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/source/repeat.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use crate::source::buffered::Buffered; 4 | 5 | use super::SeekError; 6 | use crate::common::{ChannelCount, SampleRate}; 7 | use crate::Source; 8 | 9 | /// Internal function that builds a `Repeat` object. 10 | pub fn repeat(input: I) -> Repeat 11 | where 12 | I: Source, 13 | { 14 | let input = input.buffered(); 15 | Repeat { 16 | inner: input.clone(), 17 | next: input, 18 | } 19 | } 20 | 21 | /// A source that repeats the given source. 22 | pub struct Repeat 23 | where 24 | I: Source, 25 | { 26 | inner: Buffered, 27 | next: Buffered, 28 | } 29 | 30 | impl Iterator for Repeat 31 | where 32 | I: Source, 33 | { 34 | type Item = ::Item; 35 | 36 | #[inline] 37 | fn next(&mut self) -> Option<::Item> { 38 | if let Some(value) = self.inner.next() { 39 | return Some(value); 40 | } 41 | 42 | self.inner = self.next.clone(); 43 | self.inner.next() 44 | } 45 | 46 | #[inline] 47 | fn size_hint(&self) -> (usize, Option) { 48 | // infinite 49 | (0, None) 50 | } 51 | } 52 | 53 | impl Source for Repeat 54 | where 55 | I: Iterator + Source, 56 | { 57 | #[inline] 58 | fn current_span_len(&self) -> Option { 59 | match self.inner.current_span_len() { 60 | Some(0) => self.next.current_span_len(), 61 | a => a, 62 | } 63 | } 64 | 65 | #[inline] 66 | fn channels(&self) -> ChannelCount { 67 | match self.inner.current_span_len() { 68 | Some(0) => self.next.channels(), 69 | _ => self.inner.channels(), 70 | } 71 | } 72 | 73 | #[inline] 74 | fn sample_rate(&self) -> SampleRate { 75 | match self.inner.current_span_len() { 76 | Some(0) => self.next.sample_rate(), 77 | _ => self.inner.sample_rate(), 78 | } 79 | } 80 | 81 | #[inline] 82 | fn total_duration(&self) -> Option { 83 | None 84 | } 85 | 86 | #[inline] 87 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 88 | self.inner.try_seek(pos) 89 | } 90 | } 91 | 92 | impl Clone for Repeat 93 | where 94 | I: Source, 95 | { 96 | #[inline] 97 | fn clone(&self) -> Repeat { 98 | Repeat { 99 | inner: self.inner.clone(), 100 | next: self.next.clone(), 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/source/sawtooth.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{ChannelCount, SampleRate}; 2 | use crate::source::{Function, SignalGenerator}; 3 | use crate::Source; 4 | use std::time::Duration; 5 | 6 | use super::SeekError; 7 | 8 | /// An infinite source that produces a sawtooth wave. 9 | /// 10 | /// Always has a sample rate of 48kHz and one channel. 11 | /// 12 | /// This source is a thin interface on top of `SignalGenerator` provided for 13 | /// your convenience. 14 | #[derive(Clone, Debug)] 15 | pub struct SawtoothWave { 16 | test_saw: SignalGenerator, 17 | } 18 | 19 | impl SawtoothWave { 20 | const SAMPLE_RATE: SampleRate = 48000; 21 | 22 | /// The frequency of the sine. 23 | #[inline] 24 | pub fn new(freq: f32) -> SawtoothWave { 25 | SawtoothWave { 26 | test_saw: SignalGenerator::new(Self::SAMPLE_RATE, freq, Function::Sawtooth), 27 | } 28 | } 29 | } 30 | 31 | impl Iterator for SawtoothWave { 32 | type Item = f32; 33 | 34 | #[inline] 35 | fn next(&mut self) -> Option { 36 | self.test_saw.next() 37 | } 38 | } 39 | 40 | impl Source for SawtoothWave { 41 | #[inline] 42 | fn current_span_len(&self) -> Option { 43 | None 44 | } 45 | 46 | #[inline] 47 | fn channels(&self) -> ChannelCount { 48 | 1 49 | } 50 | 51 | #[inline] 52 | fn sample_rate(&self) -> SampleRate { 53 | Self::SAMPLE_RATE 54 | } 55 | 56 | #[inline] 57 | fn total_duration(&self) -> Option { 58 | None 59 | } 60 | 61 | #[inline] 62 | fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { 63 | self.test_saw.try_seek(duration) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/source/sine.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{ChannelCount, SampleRate}; 2 | use crate::source::{Function, SignalGenerator}; 3 | use crate::Source; 4 | use std::time::Duration; 5 | 6 | use super::SeekError; 7 | 8 | /// An infinite source that produces a sine. 9 | /// 10 | /// Always has a sample rate of 48kHz and one channel. 11 | /// 12 | /// This source is a thin interface on top of `SignalGenerator` provided for 13 | /// your convenience. 14 | #[derive(Clone, Debug)] 15 | pub struct SineWave { 16 | test_sine: SignalGenerator, 17 | } 18 | 19 | impl SineWave { 20 | const SAMPLE_RATE: u32 = 48000; 21 | 22 | /// The frequency of the sine. 23 | #[inline] 24 | pub fn new(freq: f32) -> SineWave { 25 | SineWave { 26 | test_sine: SignalGenerator::new(Self::SAMPLE_RATE, freq, Function::Sine), 27 | } 28 | } 29 | } 30 | 31 | impl Iterator for SineWave { 32 | type Item = f32; 33 | 34 | #[inline] 35 | fn next(&mut self) -> Option { 36 | self.test_sine.next() 37 | } 38 | } 39 | 40 | impl Source for SineWave { 41 | #[inline] 42 | fn current_span_len(&self) -> Option { 43 | None 44 | } 45 | 46 | #[inline] 47 | fn channels(&self) -> ChannelCount { 48 | 1 49 | } 50 | 51 | #[inline] 52 | fn sample_rate(&self) -> SampleRate { 53 | Self::SAMPLE_RATE 54 | } 55 | 56 | #[inline] 57 | fn total_duration(&self) -> Option { 58 | None 59 | } 60 | 61 | #[inline] 62 | fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { 63 | self.test_sine.try_seek(duration) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/source/skippable.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{ChannelCount, SampleRate}; 2 | use crate::Source; 3 | use std::time::Duration; 4 | 5 | use super::SeekError; 6 | 7 | /// Wrap the source in a skippable. It allows ending the current source early by 8 | /// calling [`Skippable::skip`]. If this source is in a queue such as the Sink 9 | /// ending the source early is equal to skipping the source. 10 | pub fn skippable(source: I) -> Skippable { 11 | Skippable { 12 | input: source, 13 | do_skip: false, 14 | } 15 | } 16 | 17 | /// Wrap the source in a skippable. It allows ending the current source early by 18 | /// calling [`Skippable::skip`]. If this source is in a queue such as the Sink 19 | /// ending the source early is equal to skipping the source. 20 | #[derive(Clone, Debug)] 21 | pub struct Skippable { 22 | input: I, 23 | do_skip: bool, 24 | } 25 | 26 | impl Skippable { 27 | /// Skips the current source 28 | #[inline] 29 | pub fn skip(&mut self) { 30 | self.do_skip = true; 31 | } 32 | 33 | /// Returns a reference to the inner source. 34 | #[inline] 35 | pub fn inner(&self) -> &I { 36 | &self.input 37 | } 38 | 39 | /// Returns a mutable reference to the inner source. 40 | #[inline] 41 | pub fn inner_mut(&mut self) -> &mut I { 42 | &mut self.input 43 | } 44 | 45 | /// Returns the inner source. 46 | #[inline] 47 | pub fn into_inner(self) -> I { 48 | self.input 49 | } 50 | } 51 | 52 | impl Iterator for Skippable 53 | where 54 | I: Source, 55 | { 56 | type Item = I::Item; 57 | 58 | #[inline] 59 | fn next(&mut self) -> Option { 60 | if self.do_skip { 61 | None 62 | } else { 63 | self.input.next() 64 | } 65 | } 66 | 67 | #[inline] 68 | fn size_hint(&self) -> (usize, Option) { 69 | self.input.size_hint() 70 | } 71 | } 72 | 73 | impl Source for Skippable 74 | where 75 | I: Source, 76 | { 77 | #[inline] 78 | fn current_span_len(&self) -> Option { 79 | self.input.current_span_len() 80 | } 81 | 82 | #[inline] 83 | fn channels(&self) -> ChannelCount { 84 | self.input.channels() 85 | } 86 | 87 | #[inline] 88 | fn sample_rate(&self) -> SampleRate { 89 | self.input.sample_rate() 90 | } 91 | 92 | #[inline] 93 | fn total_duration(&self) -> Option { 94 | self.input.total_duration() 95 | } 96 | 97 | #[inline] 98 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 99 | self.input.try_seek(pos) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/source/spatial.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::source::ChannelVolume; 6 | use crate::Source; 7 | 8 | /// A simple spatial audio source. The underlying source is transformed to Mono 9 | /// and then played in stereo. The left and right channel's volume are amplified 10 | /// differently depending on the distance of the left and right ear to the source. 11 | #[derive(Clone)] 12 | pub struct Spatial 13 | where 14 | I: Source, 15 | { 16 | input: ChannelVolume, 17 | } 18 | 19 | fn dist_sq(a: [f32; 3], b: [f32; 3]) -> f32 { 20 | a.iter() 21 | .zip(b.iter()) 22 | .map(|(a, b)| (a - b) * (a - b)) 23 | .sum::() 24 | } 25 | 26 | impl Spatial 27 | where 28 | I: Source, 29 | { 30 | /// Builds a new `SpatialSink`, beginning playback on a stream. 31 | pub fn new( 32 | input: I, 33 | emitter_position: [f32; 3], 34 | left_ear: [f32; 3], 35 | right_ear: [f32; 3], 36 | ) -> Spatial 37 | where 38 | I: Source, 39 | { 40 | let mut ret = Spatial { 41 | input: ChannelVolume::new(input, vec![0.0, 0.0]), 42 | }; 43 | ret.set_positions(emitter_position, left_ear, right_ear); 44 | ret 45 | } 46 | 47 | /// Sets the position of the emitter and ears in the 3D world. 48 | pub fn set_positions( 49 | &mut self, 50 | emitter_pos: [f32; 3], 51 | left_ear: [f32; 3], 52 | right_ear: [f32; 3], 53 | ) { 54 | debug_assert!(left_ear != right_ear); 55 | let left_dist_sq = dist_sq(left_ear, emitter_pos); 56 | let right_dist_sq = dist_sq(right_ear, emitter_pos); 57 | let max_diff = dist_sq(left_ear, right_ear).sqrt(); 58 | let left_dist = left_dist_sq.sqrt(); 59 | let right_dist = right_dist_sq.sqrt(); 60 | let left_diff_modifier = (((left_dist - right_dist) / max_diff + 1.0) / 4.0 + 0.5).min(1.0); 61 | let right_diff_modifier = 62 | (((right_dist - left_dist) / max_diff + 1.0) / 4.0 + 0.5).min(1.0); 63 | let left_dist_modifier = (1.0 / left_dist_sq).min(1.0); 64 | let right_dist_modifier = (1.0 / right_dist_sq).min(1.0); 65 | self.input 66 | .set_volume(0, left_diff_modifier * left_dist_modifier); 67 | self.input 68 | .set_volume(1, right_diff_modifier * right_dist_modifier); 69 | } 70 | } 71 | 72 | impl Iterator for Spatial 73 | where 74 | I: Source, 75 | { 76 | type Item = I::Item; 77 | 78 | #[inline] 79 | fn next(&mut self) -> Option { 80 | self.input.next() 81 | } 82 | 83 | #[inline] 84 | fn size_hint(&self) -> (usize, Option) { 85 | self.input.size_hint() 86 | } 87 | } 88 | 89 | impl ExactSizeIterator for Spatial where I: Source + ExactSizeIterator {} 90 | 91 | impl Source for Spatial 92 | where 93 | I: Source, 94 | { 95 | #[inline] 96 | fn current_span_len(&self) -> Option { 97 | self.input.current_span_len() 98 | } 99 | 100 | #[inline] 101 | fn channels(&self) -> ChannelCount { 102 | self.input.channels() 103 | } 104 | 105 | #[inline] 106 | fn sample_rate(&self) -> SampleRate { 107 | self.input.sample_rate() 108 | } 109 | 110 | #[inline] 111 | fn total_duration(&self) -> Option { 112 | self.input.total_duration() 113 | } 114 | 115 | #[inline] 116 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 117 | self.input.try_seek(pos) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/source/speed.rs: -------------------------------------------------------------------------------- 1 | //! Playback Speed control Module. 2 | //! 3 | //! The main concept of this module is the [`Speed`] struct, which 4 | //! encapsulates playback speed controls of the current sink. 5 | //! 6 | //! In order to speed up a sink, the speed struct: 7 | //! - Increases the current sample rate by the given factor. 8 | //! - Updates the total duration function to cover for the new factor by dividing by the factor. 9 | //! - Updates the try_seek function by multiplying the audio position by the factor. 10 | //! 11 | //! To speed up a source from sink all you need to do is call the `set_speed(factor: f32)` function 12 | //! For example, here is how you speed up your sound by using sink or playing raw: 13 | //! 14 | #![cfg_attr(not(feature = "playback"), doc = "```ignore")] 15 | #![cfg_attr(feature = "playback", doc = "```no_run")] 16 | //!# use std::fs::File; 17 | //!# use rodio::{Decoder, Sink, OutputStream, source::{Source, SineWave}}; 18 | //! 19 | //! // Get an output stream handle to the default physical sound device. 20 | //! // Note that no sound will be played if the _stream is dropped. 21 | //! let stream_handle = rodio::OutputStreamBuilder::open_default_stream() 22 | //! .expect("open default audio stream"); 23 | //! // Load a sound from a file, using a path relative to `Cargo.toml` 24 | //! let file = File::open("examples/music.ogg").unwrap(); 25 | //! // Decode that sound file into a source 26 | //! let source = Decoder::try_from(file).unwrap(); 27 | //! // Play the sound directly on the device 2x faster 28 | //! stream_handle.mixer().add(source.speed(2.0)); 29 | //! std::thread::sleep(std::time::Duration::from_secs(5)); 30 | //! ``` 31 | //! Here is how you would do it using the sink: 32 | #![cfg_attr(not(feature = "playback"), doc = "```ignore")] 33 | #![cfg_attr(feature = "playback", doc = "```no_run")] 34 | //! use rodio::source::{Source, SineWave}; 35 | //! let source = SineWave::new(440.0) 36 | //! .take_duration(std::time::Duration::from_secs_f32(20.25)) 37 | //! .amplify(0.20); 38 | //! let stream_handle = rodio::OutputStreamBuilder::open_default_stream() 39 | //! .expect("open default audio stream"); 40 | //! let sink = rodio::Sink::connect_new(&stream_handle.mixer()); 41 | //! sink.set_speed(2.0); 42 | //! sink.append(source); 43 | //! std::thread::sleep(std::time::Duration::from_secs(5)); 44 | //! ``` 45 | //! Notice the increase in pitch as the factor increases 46 | //! 47 | //! Since the samples are played faster the audio wave get shorter increasing their frequencies 48 | 49 | use std::time::Duration; 50 | 51 | use super::SeekError; 52 | use crate::common::{ChannelCount, SampleRate}; 53 | use crate::Source; 54 | 55 | /// Internal function that builds a `Speed` object. 56 | pub fn speed(input: I, factor: f32) -> Speed { 57 | Speed { input, factor } 58 | } 59 | 60 | /// Filter that modifies each sample by a given value. 61 | #[derive(Clone, Debug)] 62 | pub struct Speed { 63 | input: I, 64 | factor: f32, 65 | } 66 | 67 | impl Speed 68 | where 69 | I: Source, 70 | { 71 | /// Modifies the speed factor. 72 | #[inline] 73 | pub fn set_factor(&mut self, factor: f32) { 74 | self.factor = factor; 75 | } 76 | 77 | /// Returns a reference to the inner source. 78 | #[inline] 79 | pub fn inner(&self) -> &I { 80 | &self.input 81 | } 82 | 83 | /// Returns a mutable reference to the inner source. 84 | #[inline] 85 | pub fn inner_mut(&mut self) -> &mut I { 86 | &mut self.input 87 | } 88 | 89 | /// Returns the inner source. 90 | #[inline] 91 | pub fn into_inner(self) -> I { 92 | self.input 93 | } 94 | } 95 | 96 | impl Iterator for Speed 97 | where 98 | I: Source, 99 | { 100 | type Item = I::Item; 101 | 102 | #[inline] 103 | fn next(&mut self) -> Option { 104 | self.input.next() 105 | } 106 | 107 | #[inline] 108 | fn size_hint(&self) -> (usize, Option) { 109 | self.input.size_hint() 110 | } 111 | } 112 | 113 | impl ExactSizeIterator for Speed where I: Source + ExactSizeIterator {} 114 | 115 | impl Source for Speed 116 | where 117 | I: Source, 118 | { 119 | #[inline] 120 | fn current_span_len(&self) -> Option { 121 | self.input.current_span_len() 122 | } 123 | 124 | #[inline] 125 | fn channels(&self) -> ChannelCount { 126 | self.input.channels() 127 | } 128 | 129 | #[inline] 130 | fn sample_rate(&self) -> SampleRate { 131 | (self.input.sample_rate() as f32 * self.factor) as u32 132 | } 133 | 134 | #[inline] 135 | fn total_duration(&self) -> Option { 136 | self.input.total_duration().map(|d| d.div_f32(self.factor)) 137 | } 138 | 139 | #[inline] 140 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 141 | let pos_accounting_for_speedup = pos.mul_f32(self.factor); 142 | self.input.try_seek(pos_accounting_for_speedup) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/source/square.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{ChannelCount, SampleRate}; 2 | use crate::source::{Function, SignalGenerator}; 3 | use crate::Source; 4 | use std::time::Duration; 5 | 6 | use super::SeekError; 7 | 8 | /// An infinite source that produces a square wave. 9 | /// 10 | /// Always has a sample rate of 48kHz and one channel. 11 | /// 12 | /// This source is a thin interface on top of `SignalGenerator` provided for 13 | /// your convenience. 14 | #[derive(Clone, Debug)] 15 | pub struct SquareWave { 16 | test_square: SignalGenerator, 17 | } 18 | 19 | impl SquareWave { 20 | const SAMPLE_RATE: u32 = 48000; 21 | 22 | /// The frequency of the sine. 23 | #[inline] 24 | pub fn new(freq: f32) -> SquareWave { 25 | SquareWave { 26 | test_square: SignalGenerator::new(Self::SAMPLE_RATE, freq, Function::Square), 27 | } 28 | } 29 | } 30 | 31 | impl Iterator for SquareWave { 32 | type Item = f32; 33 | 34 | #[inline] 35 | fn next(&mut self) -> Option { 36 | self.test_square.next() 37 | } 38 | } 39 | 40 | impl Source for SquareWave { 41 | #[inline] 42 | fn current_span_len(&self) -> Option { 43 | None 44 | } 45 | 46 | #[inline] 47 | fn channels(&self) -> ChannelCount { 48 | 1 49 | } 50 | 51 | #[inline] 52 | fn sample_rate(&self) -> SampleRate { 53 | Self::SAMPLE_RATE 54 | } 55 | 56 | #[inline] 57 | fn total_duration(&self) -> Option { 58 | None 59 | } 60 | 61 | #[inline] 62 | fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { 63 | self.test_square.try_seek(duration) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/source/stoppable.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::Source; 6 | 7 | /// This is the same as [`skippable`](crate::source::skippable) see its docs 8 | pub fn stoppable(source: I) -> Stoppable { 9 | Stoppable { 10 | input: source, 11 | stopped: false, 12 | } 13 | } 14 | 15 | /// This is the same as [`Skippable`](crate::source::Skippable) see its docs 16 | #[derive(Clone, Debug)] 17 | pub struct Stoppable { 18 | input: I, 19 | stopped: bool, 20 | } 21 | 22 | impl Stoppable { 23 | /// Stops the sound. 24 | #[inline] 25 | pub fn stop(&mut self) { 26 | self.stopped = true; 27 | } 28 | 29 | /// Returns a reference to the inner source. 30 | #[inline] 31 | pub fn inner(&self) -> &I { 32 | &self.input 33 | } 34 | 35 | /// Returns a mutable reference to the inner source. 36 | #[inline] 37 | pub fn inner_mut(&mut self) -> &mut I { 38 | &mut self.input 39 | } 40 | 41 | /// Returns the inner source. 42 | #[inline] 43 | pub fn into_inner(self) -> I { 44 | self.input 45 | } 46 | } 47 | 48 | impl Iterator for Stoppable 49 | where 50 | I: Source, 51 | { 52 | type Item = I::Item; 53 | 54 | #[inline] 55 | fn next(&mut self) -> Option { 56 | if self.stopped { 57 | None 58 | } else { 59 | self.input.next() 60 | } 61 | } 62 | 63 | #[inline] 64 | fn size_hint(&self) -> (usize, Option) { 65 | self.input.size_hint() 66 | } 67 | } 68 | 69 | impl Source for Stoppable 70 | where 71 | I: Source, 72 | { 73 | #[inline] 74 | fn current_span_len(&self) -> Option { 75 | self.input.current_span_len() 76 | } 77 | 78 | #[inline] 79 | fn channels(&self) -> ChannelCount { 80 | self.input.channels() 81 | } 82 | 83 | #[inline] 84 | fn sample_rate(&self) -> SampleRate { 85 | self.input.sample_rate() 86 | } 87 | 88 | #[inline] 89 | fn total_duration(&self) -> Option { 90 | self.input.total_duration() 91 | } 92 | 93 | #[inline] 94 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 95 | self.input.try_seek(pos) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/source/take.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::{Sample, Source}; 6 | 7 | /// Internal function that builds a `TakeDuration` object. 8 | pub fn take_duration(input: I, duration: Duration) -> TakeDuration 9 | where 10 | I: Source, 11 | { 12 | TakeDuration { 13 | current_span_len: input.current_span_len(), 14 | duration_per_sample: TakeDuration::get_duration_per_sample(&input), 15 | input, 16 | remaining_duration: duration, 17 | requested_duration: duration, 18 | filter: None, 19 | } 20 | } 21 | 22 | /// A filter that can be applied to a `TakeDuration`. 23 | #[derive(Clone, Debug)] 24 | enum DurationFilter { 25 | FadeOut, 26 | } 27 | impl DurationFilter { 28 | fn apply(&self, sample: Sample, parent: &TakeDuration) -> Sample { 29 | match self { 30 | DurationFilter::FadeOut => { 31 | let remaining = parent.remaining_duration.as_millis() as f32; 32 | let total = parent.requested_duration.as_millis() as f32; 33 | sample * remaining / total 34 | } 35 | } 36 | } 37 | } 38 | 39 | const NANOS_PER_SEC: u64 = 1_000_000_000; 40 | 41 | /// A source that truncates the given source to a certain duration. 42 | #[derive(Clone, Debug)] 43 | pub struct TakeDuration { 44 | input: I, 45 | remaining_duration: Duration, 46 | requested_duration: Duration, 47 | filter: Option, 48 | // Remaining samples in current span. 49 | current_span_len: Option, 50 | // Only updated when the current span len is exhausted. 51 | duration_per_sample: Duration, 52 | } 53 | 54 | impl TakeDuration 55 | where 56 | I: Source, 57 | { 58 | /// Returns the duration elapsed for each sample extracted. 59 | #[inline] 60 | fn get_duration_per_sample(input: &I) -> Duration { 61 | let ns = NANOS_PER_SEC / (input.sample_rate() as u64 * input.channels() as u64); 62 | // \|/ the maximum value of `ns` is one billion, so this can't fail 63 | Duration::new(0, ns as u32) 64 | } 65 | 66 | /// Returns a reference to the inner source. 67 | #[inline] 68 | pub fn inner(&self) -> &I { 69 | &self.input 70 | } 71 | 72 | /// Returns a mutable reference to the inner source. 73 | #[inline] 74 | pub fn inner_mut(&mut self) -> &mut I { 75 | &mut self.input 76 | } 77 | 78 | /// Returns the inner source. 79 | #[inline] 80 | pub fn into_inner(self) -> I { 81 | self.input 82 | } 83 | 84 | /// Make the truncated source end with a FadeOut. The fadeout covers the 85 | /// entire length of the take source. 86 | pub fn set_filter_fadeout(&mut self) { 87 | self.filter = Some(DurationFilter::FadeOut); 88 | } 89 | 90 | /// Remove any filter set. 91 | pub fn clear_filter(&mut self) { 92 | self.filter = None; 93 | } 94 | } 95 | 96 | impl Iterator for TakeDuration 97 | where 98 | I: Source, 99 | { 100 | type Item = ::Item; 101 | 102 | fn next(&mut self) -> Option<::Item> { 103 | if let Some(span_len) = self.current_span_len.take() { 104 | if span_len > 0 { 105 | self.current_span_len = Some(span_len - 1); 106 | } else { 107 | self.current_span_len = self.input.current_span_len(); 108 | // Sample rate might have changed 109 | self.duration_per_sample = Self::get_duration_per_sample(&self.input); 110 | } 111 | } 112 | 113 | if self.remaining_duration <= self.duration_per_sample { 114 | None 115 | } else if let Some(sample) = self.input.next() { 116 | let sample = match &self.filter { 117 | Some(filter) => filter.apply(sample, self), 118 | None => sample, 119 | }; 120 | 121 | self.remaining_duration -= self.duration_per_sample; 122 | 123 | Some(sample) 124 | } else { 125 | None 126 | } 127 | } 128 | 129 | // TODO: size_hint 130 | } 131 | 132 | impl Source for TakeDuration 133 | where 134 | I: Iterator + Source, 135 | { 136 | #[inline] 137 | fn current_span_len(&self) -> Option { 138 | let remaining_nanos = self.remaining_duration.as_secs() * NANOS_PER_SEC 139 | + self.remaining_duration.subsec_nanos() as u64; 140 | let nanos_per_sample = self.duration_per_sample.as_secs() * NANOS_PER_SEC 141 | + self.duration_per_sample.subsec_nanos() as u64; 142 | let remaining_samples = (remaining_nanos / nanos_per_sample) as usize; 143 | 144 | self.input 145 | .current_span_len() 146 | .filter(|value| *value < remaining_samples) 147 | .or(Some(remaining_samples)) 148 | } 149 | 150 | #[inline] 151 | fn channels(&self) -> ChannelCount { 152 | self.input.channels() 153 | } 154 | 155 | #[inline] 156 | fn sample_rate(&self) -> SampleRate { 157 | self.input.sample_rate() 158 | } 159 | 160 | #[inline] 161 | fn total_duration(&self) -> Option { 162 | if let Some(duration) = self.input.total_duration() { 163 | if duration < self.requested_duration { 164 | Some(duration) 165 | } else { 166 | Some(self.requested_duration) 167 | } 168 | } else { 169 | None 170 | } 171 | } 172 | 173 | #[inline] 174 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 175 | self.input.try_seek(pos) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/source/triangle.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{ChannelCount, SampleRate}; 2 | use crate::source::{Function, SignalGenerator}; 3 | use crate::Source; 4 | use std::time::Duration; 5 | 6 | use super::SeekError; 7 | 8 | /// An infinite source that produces a triangle wave. 9 | /// 10 | /// Always has a sample rate of 48kHz and one channel. 11 | /// 12 | /// This source is a thin interface on top of `SignalGenerator` provided for 13 | /// your convenience. 14 | #[derive(Clone, Debug)] 15 | pub struct TriangleWave { 16 | test_tri: SignalGenerator, 17 | } 18 | 19 | impl TriangleWave { 20 | const SAMPLE_RATE: SampleRate = 48000; 21 | 22 | /// The frequency of the sine. 23 | #[inline] 24 | pub fn new(freq: f32) -> TriangleWave { 25 | TriangleWave { 26 | test_tri: SignalGenerator::new(Self::SAMPLE_RATE, freq, Function::Triangle), 27 | } 28 | } 29 | } 30 | 31 | impl Iterator for TriangleWave { 32 | type Item = f32; 33 | 34 | #[inline] 35 | fn next(&mut self) -> Option { 36 | self.test_tri.next() 37 | } 38 | } 39 | 40 | impl Source for TriangleWave { 41 | #[inline] 42 | fn current_span_len(&self) -> Option { 43 | None 44 | } 45 | 46 | #[inline] 47 | fn channels(&self) -> ChannelCount { 48 | 1 49 | } 50 | 51 | #[inline] 52 | fn sample_rate(&self) -> SampleRate { 53 | Self::SAMPLE_RATE 54 | } 55 | 56 | #[inline] 57 | fn total_duration(&self) -> Option { 58 | None 59 | } 60 | 61 | #[inline] 62 | fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { 63 | self.test_tri.try_seek(duration) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/source/uniform.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | use std::time::Duration; 3 | 4 | use super::SeekError; 5 | use crate::common::{ChannelCount, SampleRate}; 6 | use crate::conversions::{ChannelCountConverter, SampleRateConverter}; 7 | use crate::Source; 8 | 9 | /// An iterator that reads from a `Source` and converts the samples to a 10 | /// specific type, sample-rate and channels count. 11 | /// 12 | /// It implements `Source` as well, but all the data is guaranteed to be in a 13 | /// single span whose channels and samples rate have been passed to `new`. 14 | #[derive(Clone)] 15 | pub struct UniformSourceIterator 16 | where 17 | I: Source, 18 | { 19 | inner: Option>>>, 20 | target_channels: ChannelCount, 21 | target_sample_rate: SampleRate, 22 | total_duration: Option, 23 | } 24 | 25 | impl UniformSourceIterator 26 | where 27 | I: Source, 28 | { 29 | /// Wrap a `Source` and lazily convert its samples to a specific type, 30 | /// sample-rate and channels count. 31 | #[inline] 32 | pub fn new( 33 | input: I, 34 | target_channels: ChannelCount, 35 | target_sample_rate: SampleRate, 36 | ) -> UniformSourceIterator { 37 | let total_duration = input.total_duration(); 38 | let input = UniformSourceIterator::bootstrap(input, target_channels, target_sample_rate); 39 | 40 | UniformSourceIterator { 41 | inner: Some(input), 42 | target_channels, 43 | target_sample_rate, 44 | total_duration, 45 | } 46 | } 47 | 48 | #[inline] 49 | fn bootstrap( 50 | input: I, 51 | target_channels: ChannelCount, 52 | target_sample_rate: SampleRate, 53 | ) -> ChannelCountConverter>> { 54 | // Limit the span length to something reasonable 55 | let span_len = input.current_span_len().map(|x| x.min(32768)); 56 | 57 | let from_channels = input.channels(); 58 | let from_sample_rate = input.sample_rate(); 59 | 60 | let input = Take { 61 | iter: input, 62 | n: span_len, 63 | }; 64 | let input = 65 | SampleRateConverter::new(input, from_sample_rate, target_sample_rate, from_channels); 66 | ChannelCountConverter::new(input, from_channels, target_channels) 67 | } 68 | } 69 | 70 | impl Iterator for UniformSourceIterator 71 | where 72 | I: Source, 73 | { 74 | type Item = I::Item; 75 | 76 | #[inline] 77 | fn next(&mut self) -> Option { 78 | if let Some(value) = self.inner.as_mut().unwrap().next() { 79 | return Some(value); 80 | } 81 | 82 | let input = self.inner.take().unwrap().into_inner().into_inner().iter; 83 | 84 | let mut input = 85 | UniformSourceIterator::bootstrap(input, self.target_channels, self.target_sample_rate); 86 | 87 | let value = input.next(); 88 | self.inner = Some(input); 89 | value 90 | } 91 | 92 | #[inline] 93 | fn size_hint(&self) -> (usize, Option) { 94 | (self.inner.as_ref().unwrap().size_hint().0, None) 95 | } 96 | } 97 | 98 | impl Source for UniformSourceIterator 99 | where 100 | I: Iterator + Source, 101 | { 102 | #[inline] 103 | fn current_span_len(&self) -> Option { 104 | None 105 | } 106 | 107 | #[inline] 108 | fn channels(&self) -> ChannelCount { 109 | self.target_channels 110 | } 111 | 112 | #[inline] 113 | fn sample_rate(&self) -> SampleRate { 114 | self.target_sample_rate 115 | } 116 | 117 | #[inline] 118 | fn total_duration(&self) -> Option { 119 | self.total_duration 120 | } 121 | 122 | #[inline] 123 | fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 124 | if let Some(input) = self.inner.as_mut() { 125 | input.inner_mut().inner_mut().inner_mut().try_seek(pos) 126 | } else { 127 | Ok(()) 128 | } 129 | } 130 | } 131 | 132 | #[derive(Clone, Debug)] 133 | struct Take { 134 | iter: I, 135 | n: Option, 136 | } 137 | 138 | impl Take { 139 | #[inline] 140 | pub fn inner_mut(&mut self) -> &mut I { 141 | &mut self.iter 142 | } 143 | } 144 | 145 | impl Iterator for Take 146 | where 147 | I: Iterator, 148 | { 149 | type Item = ::Item; 150 | 151 | #[inline] 152 | fn next(&mut self) -> Option<::Item> { 153 | if let Some(n) = &mut self.n { 154 | if *n != 0 { 155 | *n -= 1; 156 | self.iter.next() 157 | } else { 158 | None 159 | } 160 | } else { 161 | self.iter.next() 162 | } 163 | } 164 | 165 | #[inline] 166 | fn size_hint(&self) -> (usize, Option) { 167 | if let Some(n) = self.n { 168 | let (lower, upper) = self.iter.size_hint(); 169 | 170 | let lower = cmp::min(lower, n); 171 | 172 | let upper = match upper { 173 | Some(x) if x < n => Some(x), 174 | _ => Some(n), 175 | }; 176 | 177 | (lower, upper) 178 | } else { 179 | self.iter.size_hint() 180 | } 181 | } 182 | } 183 | 184 | impl ExactSizeIterator for Take where I: ExactSizeIterator {} 185 | -------------------------------------------------------------------------------- /src/source/zero.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::SeekError; 4 | use crate::common::{ChannelCount, SampleRate}; 5 | use crate::{Sample, Source}; 6 | 7 | /// An source that produces samples with value zero (silence). Depending on if 8 | /// it where created with [`Zero::new`] or [`Zero::new_samples`] it can be never 9 | /// ending or finite. 10 | #[derive(Clone, Debug)] 11 | pub struct Zero { 12 | channels: ChannelCount, 13 | sample_rate: SampleRate, 14 | num_samples: Option, 15 | } 16 | 17 | impl Zero { 18 | /// Create a new source that never ends and produces total silence. 19 | #[inline] 20 | pub fn new(channels: ChannelCount, sample_rate: SampleRate) -> Zero { 21 | Zero { 22 | channels, 23 | sample_rate, 24 | num_samples: None, 25 | } 26 | } 27 | /// Create a new source that never ends and produces total silence. 28 | #[inline] 29 | pub fn new_samples( 30 | channels: ChannelCount, 31 | sample_rate: SampleRate, 32 | num_samples: usize, 33 | ) -> Zero { 34 | Zero { 35 | channels, 36 | sample_rate, 37 | num_samples: Some(num_samples), 38 | } 39 | } 40 | } 41 | 42 | impl Iterator for Zero { 43 | type Item = Sample; 44 | 45 | #[inline] 46 | fn next(&mut self) -> Option { 47 | if let Some(num_samples) = self.num_samples { 48 | if num_samples > 0 { 49 | self.num_samples = Some(num_samples - 1); 50 | Some(0.0) 51 | } else { 52 | None 53 | } 54 | } else { 55 | Some(0.0) 56 | } 57 | } 58 | } 59 | 60 | impl Source for Zero { 61 | #[inline] 62 | fn current_span_len(&self) -> Option { 63 | self.num_samples 64 | } 65 | 66 | #[inline] 67 | fn channels(&self) -> ChannelCount { 68 | self.channels 69 | } 70 | 71 | #[inline] 72 | fn sample_rate(&self) -> SampleRate { 73 | self.sample_rate 74 | } 75 | 76 | #[inline] 77 | fn total_duration(&self) -> Option { 78 | None 79 | } 80 | 81 | #[inline] 82 | fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { 83 | Ok(()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/static_buffer.rs: -------------------------------------------------------------------------------- 1 | //! A simple source of samples coming from a static buffer. 2 | //! 3 | //! The `StaticSamplesBuffer` struct can be used to treat a list of values as a `Source`. 4 | //! 5 | //! # Example 6 | //! 7 | //! ``` 8 | //! use rodio::static_buffer::StaticSamplesBuffer; 9 | //! let _ = StaticSamplesBuffer::new(1, 44100, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); 10 | //! ``` 11 | //! 12 | 13 | use std::slice::Iter as SliceIter; 14 | use std::time::Duration; 15 | 16 | use crate::common::{ChannelCount, SampleRate}; 17 | use crate::source::SeekError; 18 | use crate::{Sample, Source}; 19 | 20 | /// A buffer of samples treated as a source. 21 | #[derive(Clone)] 22 | pub struct StaticSamplesBuffer { 23 | data: SliceIter<'static, Sample>, 24 | channels: ChannelCount, 25 | sample_rate: SampleRate, 26 | duration: Duration, 27 | } 28 | 29 | impl StaticSamplesBuffer { 30 | /// Builds a new `StaticSamplesBuffer`. 31 | /// 32 | /// # Panic 33 | /// 34 | /// - Panics if the number of channels is zero. 35 | /// - Panics if the samples rate is zero. 36 | /// - Panics if the length of the buffer is larger than approximately 16 billion elements. 37 | /// This is because the calculation of the duration would overflow. 38 | /// 39 | pub fn new( 40 | channels: ChannelCount, 41 | sample_rate: SampleRate, 42 | data: &'static [Sample], 43 | ) -> StaticSamplesBuffer { 44 | assert!(channels != 0); 45 | assert!(sample_rate != 0); 46 | 47 | let duration_ns = 1_000_000_000u64.checked_mul(data.len() as u64).unwrap() 48 | / sample_rate as u64 49 | / channels as u64; 50 | let duration = Duration::new( 51 | duration_ns / 1_000_000_000, 52 | (duration_ns % 1_000_000_000) as u32, 53 | ); 54 | 55 | StaticSamplesBuffer { 56 | data: data.iter(), 57 | channels, 58 | sample_rate, 59 | duration, 60 | } 61 | } 62 | } 63 | 64 | impl Source for StaticSamplesBuffer { 65 | #[inline] 66 | fn current_span_len(&self) -> Option { 67 | None 68 | } 69 | 70 | #[inline] 71 | fn channels(&self) -> ChannelCount { 72 | self.channels 73 | } 74 | 75 | #[inline] 76 | fn sample_rate(&self) -> SampleRate { 77 | self.sample_rate 78 | } 79 | 80 | #[inline] 81 | fn total_duration(&self) -> Option { 82 | Some(self.duration) 83 | } 84 | 85 | #[inline] 86 | fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { 87 | Err(SeekError::NotSupported { 88 | underlying_source: std::any::type_name::(), 89 | }) 90 | } 91 | } 92 | 93 | impl Iterator for StaticSamplesBuffer { 94 | type Item = Sample; 95 | 96 | #[inline] 97 | fn next(&mut self) -> Option { 98 | self.data.next().cloned() 99 | } 100 | 101 | #[inline] 102 | fn size_hint(&self) -> (usize, Option) { 103 | self.data.size_hint() 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use crate::source::Source; 110 | use crate::static_buffer::StaticSamplesBuffer; 111 | 112 | #[test] 113 | fn basic() { 114 | let _ = StaticSamplesBuffer::new(1, 44100, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); 115 | } 116 | 117 | #[test] 118 | #[should_panic] 119 | fn panic_if_zero_channels() { 120 | StaticSamplesBuffer::new(0, 44100, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); 121 | } 122 | 123 | #[test] 124 | #[should_panic] 125 | fn panic_if_zero_sample_rate() { 126 | StaticSamplesBuffer::new(1, 0, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); 127 | } 128 | 129 | #[test] 130 | fn duration_basic() { 131 | let buf = StaticSamplesBuffer::new(2, 2, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); 132 | let dur = buf.total_duration().unwrap(); 133 | assert_eq!(dur.as_secs(), 1); 134 | assert_eq!(dur.subsec_nanos(), 500_000_000); 135 | } 136 | 137 | #[test] 138 | fn iteration() { 139 | let mut buf = StaticSamplesBuffer::new(1, 44100, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); 140 | assert_eq!(buf.next(), Some(1.0)); 141 | assert_eq!(buf.next(), Some(2.0)); 142 | assert_eq!(buf.next(), Some(3.0)); 143 | assert_eq!(buf.next(), Some(4.0)); 144 | assert_eq!(buf.next(), Some(5.0)); 145 | assert_eq!(buf.next(), Some(6.0)); 146 | assert_eq!(buf.next(), None); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/wav_output.rs: -------------------------------------------------------------------------------- 1 | use crate::{ChannelCount, Source}; 2 | use hound::{SampleFormat, WavSpec}; 3 | use std::path; 4 | 5 | /// This procedure saves Source's output into a wav file. The output samples format is 32-bit float. 6 | /// This function is intended primarily for testing and diagnostics. It can be used to see 7 | /// the output without opening output stream to a real audio device. 8 | pub fn output_to_wav( 9 | source: &mut impl Source, 10 | wav_file: impl AsRef, 11 | ) -> Result<(), Box> { 12 | let format = WavSpec { 13 | channels: source.channels() as ChannelCount, 14 | sample_rate: source.sample_rate(), 15 | bits_per_sample: 32, 16 | sample_format: SampleFormat::Float, 17 | }; 18 | let mut writer = hound::WavWriter::create(wav_file, format)?; 19 | for sample in source { 20 | writer.write_sample(sample)?; 21 | } 22 | writer.finalize()?; 23 | Ok(()) 24 | } 25 | 26 | #[cfg(test)] 27 | mod test { 28 | use super::output_to_wav; 29 | use crate::common::ChannelCount; 30 | use crate::Source; 31 | use std::io::BufReader; 32 | use std::time::Duration; 33 | 34 | #[test] 35 | fn test_output_to_wav() { 36 | let make_source = || { 37 | crate::source::SineWave::new(745.0) 38 | .amplify(0.1) 39 | .take_duration(Duration::from_secs(1)) 40 | }; 41 | let wav_file_path = "target/tmp/save-to-wav-test.wav"; 42 | output_to_wav(&mut make_source(), wav_file_path).expect("output file can be written"); 43 | 44 | let file = std::fs::File::open(wav_file_path).expect("output file can be opened"); 45 | // Not using crate::Decoder bcause it is limited to i16 samples. 46 | let mut reader = 47 | hound::WavReader::new(BufReader::new(file)).expect("wav file can be read back"); 48 | let reference = make_source(); 49 | assert_eq!(reference.sample_rate(), reader.spec().sample_rate); 50 | assert_eq!(reference.channels(), reader.spec().channels as ChannelCount); 51 | 52 | let actual_samples: Vec = reader.samples::().map(|x| x.unwrap()).collect(); 53 | let expected_samples: Vec = reference.collect(); 54 | assert!( 55 | expected_samples == actual_samples, 56 | "wav samples do not match the source" 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/flac_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "flac", feature = "symphonia-flac"))] 2 | use rodio::Source; 3 | #[cfg(any(feature = "flac", feature = "symphonia-flac"))] 4 | use std::time::Duration; 5 | 6 | #[cfg(any(feature = "flac", feature = "symphonia-flac"))] 7 | #[test] 8 | fn test_flac_encodings() { 9 | // 16 bit FLAC file exported from Audacity (2 channels, compression level 5) 10 | let file = std::fs::File::open("assets/audacity16bit_level5.flac").unwrap(); 11 | let mut decoder = rodio::Decoder::try_from(file).unwrap(); 12 | 13 | // File is not just silence 14 | assert!(decoder.any(|x| x != 0.0)); 15 | assert_eq!(decoder.total_duration(), Some(Duration::from_secs(3))); // duration is calculated correctly 16 | 17 | // 24 bit FLAC file exported from Audacity (2 channels, various compression levels) 18 | for level in &[0, 5, 8] { 19 | let file = std::fs::File::open(format!("assets/audacity24bit_level{level}.flac")).unwrap(); 20 | let mut decoder = rodio::Decoder::try_from(file).unwrap(); 21 | assert!(!decoder.all(|x| x != 0.0)); 22 | assert_eq!(decoder.total_duration(), Some(Duration::from_secs(3))); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/mp4a_test.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "symphonia-aac", feature = "symphonia-isomp4"))] 2 | 3 | #[test] 4 | fn test_mp4a_encodings() { 5 | // mp4a codec downloaded from YouTube 6 | // "Monkeys Spinning Monkeys" 7 | // Kevin MacLeod (incompetech.com) 8 | // Licensed under Creative Commons: By Attribution 3.0 9 | // http://creativecommons.org/licenses/by/3.0/ 10 | let file = std::fs::File::open("assets/monkeys.mp4a").unwrap(); 11 | // Open with `new` instead of `try_from` to ensure it works even without is_seekable 12 | let mut decoder = rodio::Decoder::new(file).unwrap(); 13 | assert!(decoder.any(|x| x != 0.0)); // Assert not all zeros 14 | } 15 | -------------------------------------------------------------------------------- /tests/total_duration.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | 4 | use std::io::{Read, Seek}; 5 | use std::path::Path; 6 | use std::time::Duration; 7 | 8 | use rodio::{Decoder, Source}; 9 | 10 | use rstest::rstest; 11 | use rstest_reuse::{self, *}; 12 | 13 | #[cfg(any( 14 | feature = "flac", 15 | feature = "minimp3", 16 | feature = "symphonia-aac", 17 | feature = "symphonia-flac", 18 | feature = "symphonia-mp3", 19 | feature = "symphonia-isomp4", 20 | feature = "symphonia-ogg", 21 | feature = "symphonia-wav", 22 | feature = "wav", 23 | ))] 24 | #[template] 25 | #[rstest] 26 | #[cfg_attr( 27 | feature = "symphonia-vorbis", 28 | case("ogg", Duration::from_secs_f64(69.328979591), "symphonia") 29 | )] 30 | #[cfg_attr( 31 | all(feature = "minimp3", not(feature = "symphonia-mp3")), 32 | case("mp3", Duration::ZERO, "minimp3") 33 | )] 34 | #[cfg_attr( 35 | all(feature = "wav", not(feature = "symphonia-wav")), 36 | case("wav", Duration::from_secs_f64(10.143469387), "hound") 37 | )] 38 | #[cfg_attr( 39 | all(feature = "flac", not(feature = "symphonia-flac")), 40 | case("flac", Duration::from_secs_f64(10.152380952), "claxon") 41 | )] 42 | #[cfg_attr( 43 | feature = "symphonia-mp3", 44 | case("mp3", Duration::from_secs_f64(10.187755102), "symphonia mp3") 45 | )] 46 | #[cfg_attr( 47 | feature = "symphonia-isomp4", 48 | case("m4a", Duration::from_secs_f64(10.188662131), "symphonia m4a") 49 | )] 50 | #[cfg_attr( 51 | feature = "symphonia-wav", 52 | case("wav", Duration::from_secs_f64(10.143469387), "symphonia wav") 53 | )] 54 | #[cfg_attr( 55 | feature = "symphonia-flac", 56 | case("flac", Duration::from_secs_f64(10.152380952), "symphonia flac") 57 | )] 58 | fn all_decoders( 59 | #[case] format: &'static str, 60 | #[case] correct_duration: Duration, 61 | #[case] decoder_name: &'static str, 62 | ) { 63 | } 64 | 65 | fn get_music(format: &str) -> Decoder { 66 | let asset = Path::new("assets/music").with_extension(format); 67 | let file = std::fs::File::open(asset).unwrap(); 68 | let len = file.metadata().unwrap().len(); 69 | rodio::Decoder::builder() 70 | .with_data(file) 71 | .with_byte_len(len) 72 | .with_seekable(true) 73 | .with_gapless(false) 74 | .build() 75 | .unwrap() 76 | } 77 | 78 | #[cfg(any( 79 | feature = "flac", 80 | feature = "minimp3", 81 | feature = "symphonia-flac", 82 | feature = "symphonia-mp3", 83 | feature = "symphonia-isomp4", 84 | feature = "symphonia-ogg", 85 | feature = "symphonia-wav", 86 | feature = "wav", 87 | ))] 88 | #[apply(all_decoders)] 89 | #[trace] 90 | fn decoder_returns_total_duration( 91 | #[case] format: &'static str, 92 | #[case] correct_duration: Duration, 93 | #[case] decoder_name: &'static str, 94 | ) { 95 | eprintln!("decoder: {decoder_name}"); 96 | let decoder = get_music(format); 97 | let res = decoder 98 | .total_duration() 99 | .unwrap_or_else(|| panic!("did not return a total duration, decoder: {decoder_name}")) 100 | .as_secs_f64(); 101 | let correct_duration = correct_duration.as_secs_f64(); 102 | let abs_diff = (res - correct_duration).abs(); 103 | assert!( 104 | abs_diff < 0.0001, 105 | "decoder got {res}, correct is: {correct_duration}" 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /tests/wav_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "wav")] 2 | #[test] 3 | fn test_wav_encodings() { 4 | // 16 bit wav file exported from Audacity (1 channel) 5 | let file = std::fs::File::open("assets/audacity16bit.wav").unwrap(); 6 | let mut decoder = rodio::Decoder::try_from(file).unwrap(); 7 | assert!(decoder.any(|x| x != 0.0)); 8 | 9 | // 16 bit wav file exported from LMMS (2 channels) 10 | let file = std::fs::File::open("assets/lmms16bit.wav").unwrap(); 11 | let mut decoder = rodio::Decoder::try_from(file).unwrap(); 12 | assert!(decoder.any(|x| x != 0.0)); 13 | 14 | // 24 bit wav file exported from LMMS (2 channels) 15 | let file = std::fs::File::open("assets/lmms24bit.wav").unwrap(); 16 | let mut decoder = rodio::Decoder::try_from(file).unwrap(); 17 | assert!(decoder.any(|x| x != 0.0)); 18 | 19 | // 32 bit wav file exported from Audacity (1 channel) 20 | let file = std::fs::File::open("assets/audacity32bit.wav").unwrap(); 21 | let mut decoder = rodio::Decoder::try_from(file).unwrap(); 22 | assert!(decoder.any(|x| x != 0.0)); 23 | 24 | // 32 bit wav file exported from LMMS (2 channels) 25 | let file = std::fs::File::open("assets/lmms32bit.wav").unwrap(); 26 | let mut decoder = rodio::Decoder::try_from(file).unwrap(); 27 | assert!(decoder.any(|x| x != 0.0)); 28 | 29 | // 32 bit signed integer wav file exported from Audacity (1 channel). 30 | let file = std::fs::File::open("assets/audacity32bit_int.wav").unwrap(); 31 | let mut decoder = rodio::Decoder::try_from(file).unwrap(); 32 | assert!(decoder.any(|x| x != 0.0)); 33 | } 34 | --------------------------------------------------------------------------------