perf(rodio): reduce control polling during playback
This commit is contained in:
1 parent
3c1c598e34
commit
1e6a8b1aee
70 files changed
+9378
No files matched your search
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"v":1}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "27f2b424060ff4a977285ff83ecd6c076205c121"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main, master]
|
||||
|
||||
env:
|
||||
RUSTFLAGS: "-C debuginfo=0 -D warnings"
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.toolchain == 'nightly' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
toolchain: [stable, beta, nightly]
|
||||
include:
|
||||
- os: macos-latest
|
||||
MACOS: true
|
||||
- os: windows-latest
|
||||
- os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: install linux deps
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y --no-install-recommends libasound2-dev pkg-config
|
||||
if: contains(matrix.os, 'ubuntu')
|
||||
|
||||
- name: install ${{ matrix.toolchain }} toolchain
|
||||
id: install_toolchain
|
||||
run: rustup toolchain install ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
|
||||
|
||||
- run: cargo clippy -- -D warnings
|
||||
if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest'
|
||||
|
||||
- run: |
|
||||
rustup component add rustfmt
|
||||
cargo fmt --all -- --check
|
||||
if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest'
|
||||
|
||||
- run: cargo test --all-targets
|
||||
- run: cargo test --features=symphonia-all --all-targets
|
||||
cargo-publish:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
env:
|
||||
CRATESIO_TOKEN: ${{ secrets.CRATESIO_TOKEN }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Update apt
|
||||
run: sudo apt update
|
||||
- name: Install alsa
|
||||
run: sudo apt install -y --no-install-recommends libasound2-dev pkg-config
|
||||
- name: Run cargo publish for rodio
|
||||
continue-on-error: true
|
||||
run: |
|
||||
RODIO_TMP=$(mktemp /tmp/rodioXXX.txt) || echo "::error::mktemp error"
|
||||
echo "RODIO_TMP=$RODIO_TMP" >> $GITHUB_ENV
|
||||
cargo publish --token $CRATESIO_TOKEN 2> $RODIO_TMP
|
||||
- name: Check if rodio is already published
|
||||
run: |
|
||||
empty=0
|
||||
RODIO_TMP="${{ env.RODIO_TMP }}"
|
||||
grep -q '[^[:space:]]' < $RODIO_TMP || empty=1
|
||||
[ $empty -eq 0 ] && cat $RODIO_TMP
|
||||
[ $empty -eq 1 ] || grep -q "is already uploaded" < $RODIO_TMP
|
||||
@@ -0,0 +1,2 @@
|
||||
target
|
||||
Cargo.lock
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
reorder_imports = true
|
||||
use_try_shorthand = true
|
||||
Vendored
+137
@@ -0,0 +1,137 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
# Version 0.19.0 (2024-06-29)
|
||||
|
||||
### Added
|
||||
- Adds a new source `track_position`. It keeps track of duration since the
|
||||
beginning of the underlying source.
|
||||
|
||||
### Fixed
|
||||
- Mp4a with decodable tracks after undecodable tracks now play. This matches
|
||||
VLC's behaviour.
|
||||
|
||||
# Version 0.18.1 (2024-05-23)
|
||||
|
||||
### Fixed
|
||||
- Seek no longer hangs if the sink is empty.
|
||||
|
||||
# Version 0.18.0 (2024-05-05)
|
||||
|
||||
### Changed
|
||||
- `Source` trait is now also implemented for `Box<dyn Source>` and `&mut Source`
|
||||
- `fn new_vorbis` is now also available when the `symphonia-vorbis` feature is enabled
|
||||
|
||||
### Added
|
||||
- Adds a new method `try_seek` to all sources. It returns either an error or
|
||||
seeks to the given position. A few sources are "unsupported" they return the
|
||||
error `Unsupported`.
|
||||
- Adds `SpatialSink::clear()` bringing it in line with `Sink`
|
||||
|
||||
### Fixed
|
||||
- channel upscaling now follows the 'WAVEFORMATEXTENSIBLE' format and no longer
|
||||
repeats the last source channel on all extra output channels.
|
||||
Stereo content playing on a 5.1 speaker set will now only use the front left
|
||||
and front right speaker instead of repeating the right sample on all speakers
|
||||
except the front left one.
|
||||
- `mp3::is_mp3()` no longer changes the position in the stream when the stream
|
||||
is mp3
|
||||
|
||||
# Version 0.17.3 (2023-10-23)
|
||||
|
||||
- Build fix for `minimp3` backend.
|
||||
|
||||
# Version 0.17.2 (2023-10-17)
|
||||
|
||||
- Add `EmptyCallback` source.
|
||||
- Fix index out of bounds bug.
|
||||
- Use non-vulnerable `minimp3` fork.
|
||||
- Add filter functions with additional q parameter.
|
||||
|
||||
# Version 0.17.1 (2023-02-25)
|
||||
|
||||
- Disable `symphonia`'s default features.
|
||||
|
||||
# Version 0.17.0 (2023-02-17)
|
||||
|
||||
- Update `cpal` to [0.15](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md#version-0150-2022-01-29).
|
||||
- Default to `symphonia` for mp3 decoding.
|
||||
|
||||
# Version 0.16.0 (2022-09-14)
|
||||
|
||||
- Update `cpal` to [0.14](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md#version-0140-2022-08-22).
|
||||
- Update `symphonia` to [0.5](https://github.com/pdeljanov/Symphonia/releases/tag/v0.5.1).
|
||||
|
||||
# Version 0.15.0 (2022-01-23)
|
||||
|
||||
- Remove requirement that the argument `Decoder::new` and `LoopedDecoder::new` implement `Send`.
|
||||
- Add optional symphonia backend.
|
||||
- `WavDecoder`'s `total_duration` now returns the total duration of the sound rather than the remaining duration.
|
||||
- Add 32-bit signed in WAV decoding.
|
||||
- `SineWave::new()` now takes a `f32` instead of a `u32`.
|
||||
- Add `len()` method to `SpatialSink`.
|
||||
|
||||
# Version 0.14.0 (2021-05-21)
|
||||
|
||||
- Re-export `cpal` in full.
|
||||
- Replace panics when calling `OutputStream::try_default`, `OutputStream::try_from_device` with new
|
||||
`StreamError` variants.
|
||||
- `OutputStream::try_default` will now fallback to non-default output devices if an `OutputStream`
|
||||
cannot be created from the default device.
|
||||
|
||||
# Version 0.13.1 (2021-03-28)
|
||||
|
||||
- Fix panic when no `pulseaudio-alsa` was installed.
|
||||
|
||||
# Version 0.13.0 (2020-11-03)
|
||||
|
||||
- Update `cpal` to [0.13](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md#version-0130-2020-10-28).
|
||||
- Add Android support.
|
||||
|
||||
# Version 0.12.0 (2020-10-05)
|
||||
|
||||
- Breaking: Update `cpal` to [0.12](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md#version-0120-2020-07-09).
|
||||
- Breaking: Rework API removing global "rodio audio processing" thread & adapting to the upstream cpal API changes.
|
||||
- Add new_X format specific methods to Decoder.
|
||||
- Fix resampler dependency on internal `Vec::capacity` behaviour.
|
||||
|
||||
# Version 0.11.0 (2020-03-16)
|
||||
|
||||
- Update `lewton` to [0.10](https://github.com/RustAudio/lewton/blob/master/CHANGELOG.md#release-0100---january-30-2020).
|
||||
- Breaking: Update `cpal` to [0.11](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md#version-0110-2019-12-11)
|
||||
|
||||
# Version 0.10.0 (2019-11-16)
|
||||
|
||||
- Removal of nalgebra in favour of own code.
|
||||
- Fix a bug that switched channels when resuming after having paused.
|
||||
- Attempt all supported output formats if the default format fails in `Sink::new`.
|
||||
- Breaking: Update `cpal` to [0.10](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md#version-0100-2019-07-05).
|
||||
|
||||
# Version 0.9.0 (2019-06-08)
|
||||
|
||||
- Remove exclusive `&mut` borrow requirements in `Sink` & `SpatialSink` setters.
|
||||
- Use `nalgebra` instead of `cgmath` for `Spatial` source.
|
||||
|
||||
# Version 0.8.1 (2018-09-18)
|
||||
|
||||
- Update `lewton` dependency to [0.9](https://github.com/RustAudio/lewton/blob/master/CHANGELOG.md#release-090---august-16-2018)
|
||||
- Change license from `Apache-2.0` only to `Apache-2.0 OR MIT`
|
||||
|
||||
# Version 0.8.0 (2018-06-22)
|
||||
|
||||
- Add mp3 decoding capabilities via `minimp3`
|
||||
|
||||
# Version 0.7.0 (2018-04-19)
|
||||
|
||||
- Update `cpal` dependency to 0.8, and adopt the new naming convention
|
||||
- BREAKING CHANGES:
|
||||
- renamed `Endpoint` to `Device`
|
||||
- split `default_endpoint()` into `default_output_device()` and `default_input_device()`
|
||||
- renamed `endpoints()` to `devices()`
|
||||
- introduced `output_devices()` and `input_devices()`
|
||||
Vendored
+109
@@ -0,0 +1,109 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "rodio"
|
||||
version = "0.19.0"
|
||||
exclude = [
|
||||
"assets/**",
|
||||
"tests/**",
|
||||
]
|
||||
description = "Audio playback library"
|
||||
documentation = "https://docs.rs/rodio"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"audio",
|
||||
"playback",
|
||||
"gamedev",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/RustAudio/rodio"
|
||||
|
||||
[[example]]
|
||||
name = "music_m4a"
|
||||
required-features = [
|
||||
"symphonia-isomp4",
|
||||
"symphonia-aac",
|
||||
]
|
||||
|
||||
[dependencies.claxon]
|
||||
version = "0.4.2"
|
||||
optional = true
|
||||
|
||||
[dependencies.cpal]
|
||||
version = "0.15.3"
|
||||
|
||||
[dependencies.crossbeam-channel]
|
||||
version = "0.5.8"
|
||||
optional = true
|
||||
|
||||
[dependencies.hound]
|
||||
version = "3.3.1"
|
||||
optional = true
|
||||
|
||||
[dependencies.lewton]
|
||||
version = "0.10"
|
||||
optional = true
|
||||
|
||||
[dependencies.minimp3_fixed]
|
||||
version = "0.5.4"
|
||||
optional = true
|
||||
|
||||
[dependencies.symphonia]
|
||||
version = "0.5.4"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.thiserror]
|
||||
version = "1.0.49"
|
||||
|
||||
[dev-dependencies.quickcheck]
|
||||
version = "0.9.2"
|
||||
|
||||
[dev-dependencies.rstest]
|
||||
version = "0.18.2"
|
||||
|
||||
[dev-dependencies.rstest_reuse]
|
||||
version = "0.6.0"
|
||||
|
||||
[features]
|
||||
cpal-shared-stdcxx = ["cpal/oboe-shared-stdcxx"]
|
||||
default = [
|
||||
"flac",
|
||||
"vorbis",
|
||||
"wav",
|
||||
"mp3",
|
||||
]
|
||||
flac = ["claxon"]
|
||||
minimp3 = ["dep:minimp3_fixed"]
|
||||
mp3 = ["symphonia-mp3"]
|
||||
symphonia-aac = ["symphonia/aac"]
|
||||
symphonia-all = [
|
||||
"symphonia-aac",
|
||||
"symphonia-flac",
|
||||
"symphonia-isomp4",
|
||||
"symphonia-mp3",
|
||||
"symphonia-vorbis",
|
||||
"symphonia-wav",
|
||||
]
|
||||
symphonia-flac = ["symphonia/flac"]
|
||||
symphonia-isomp4 = ["symphonia/isomp4"]
|
||||
symphonia-mp3 = ["symphonia/mp3"]
|
||||
symphonia-vorbis = ["symphonia/vorbis"]
|
||||
symphonia-wav = [
|
||||
"symphonia/wav",
|
||||
"symphonia/pcm",
|
||||
"symphonia/adpcm",
|
||||
]
|
||||
vorbis = ["lewton"]
|
||||
wasm-bindgen = ["cpal/wasm-bindgen"]
|
||||
wav = ["hound"]
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
[package]
|
||||
name = "rodio"
|
||||
version = "0.19.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Audio playback library"
|
||||
keywords = ["audio", "playback", "gamedev"]
|
||||
repository = "https://github.com/RustAudio/rodio"
|
||||
documentation = "https://docs.rs/rodio"
|
||||
exclude = ["assets/**", "tests/**"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cpal = "0.15.3"
|
||||
claxon = { version = "0.4.2", optional = true }
|
||||
hound = { version = "3.3.1", optional = true }
|
||||
lewton = { version = "0.10", optional = true }
|
||||
minimp3_fixed = { version = "0.5.4", optional = true}
|
||||
symphonia = { version = "0.5.4", optional = true, default-features = false }
|
||||
crossbeam-channel = { version = "0.5.8", optional = true }
|
||||
thiserror = "1.0.49"
|
||||
|
||||
[features]
|
||||
default = ["flac", "vorbis", "wav", "mp3"]
|
||||
|
||||
flac = ["claxon"]
|
||||
vorbis = ["lewton"]
|
||||
wav = ["hound"]
|
||||
mp3 = ["symphonia-mp3"]
|
||||
minimp3 = ["dep:minimp3_fixed"]
|
||||
wasm-bindgen = ["cpal/wasm-bindgen"]
|
||||
cpal-shared-stdcxx = ["cpal/oboe-shared-stdcxx"]
|
||||
symphonia-aac = ["symphonia/aac"]
|
||||
symphonia-all = ["symphonia-aac", "symphonia-flac", "symphonia-isomp4", "symphonia-mp3", "symphonia-vorbis", "symphonia-wav"]
|
||||
symphonia-flac = ["symphonia/flac"]
|
||||
symphonia-isomp4 = ["symphonia/isomp4"]
|
||||
symphonia-mp3 = ["symphonia/mp3"]
|
||||
symphonia-vorbis = ["symphonia/vorbis"]
|
||||
symphonia-wav = ["symphonia/wav", "symphonia/pcm", "symphonia/adpcm"]
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9.2"
|
||||
rstest = "0.18.2"
|
||||
rstest_reuse = "0.6.0"
|
||||
|
||||
[[example]]
|
||||
name = "music_m4a"
|
||||
required-features = ["symphonia-isomp4", "symphonia-aac"]
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2015 Pierre Krieger
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
# Audio playback library
|
||||
|
||||
[](https://crates.io/crates/rodio)
|
||||
[](https://crates.io/crates/rodio)
|
||||
[](https://github.com/RustAudio/rodio/actions)
|
||||
|
||||
Rust playback library.
|
||||
|
||||
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:
|
||||
|
||||
- MP3 by [minimp3](https://github.com/lieff/minimp3) (but defaults to [Symphonia](https://github.com/pdeljanov/Symphonia)).
|
||||
- WAV by [hound](https://github.com/ruud-v-a/hound).
|
||||
- Vorbis by [lewton](https://github.com/est31/lewton).
|
||||
- FLAC by [claxon](https://github.com/ruuda/claxon).
|
||||
- MP4 and AAC (both disabled by default) are handled only by [Symphonia](https://github.com/pdeljanov/Symphonia).
|
||||
|
||||
See [the docs](https://docs.rs/rodio/latest/rodio/#alternative-decoder-backends) for more details on backends.
|
||||
|
||||
# [Documentation](http://docs.rs/rodio)
|
||||
|
||||
[The documentation](http://docs.rs/rodio) contains an introduction to the library.
|
||||
|
||||
## License
|
||||
[License]: #license
|
||||
|
||||
Licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0), or
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
### License of your contributions
|
||||
|
||||
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.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
use std::io::BufReader;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
|
||||
|
||||
let file = std::fs::File::open("assets/beep.wav").unwrap();
|
||||
let beep1 = stream_handle.play_once(BufReader::new(file)).unwrap();
|
||||
beep1.set_volume(0.2);
|
||||
println!("Started beep1");
|
||||
|
||||
thread::sleep(Duration::from_millis(1500));
|
||||
|
||||
let file = std::fs::File::open("assets/beep2.wav").unwrap();
|
||||
let beep2 = stream_handle.play_once(BufReader::new(file)).unwrap();
|
||||
beep2.set_volume(0.3);
|
||||
beep2.detach();
|
||||
println!("Started beep2");
|
||||
|
||||
thread::sleep(Duration::from_millis(1500));
|
||||
let file = std::fs::File::open("assets/beep3.ogg").unwrap();
|
||||
let beep3 = stream_handle.play_once(file).unwrap();
|
||||
beep3.set_volume(0.2);
|
||||
println!("Started beep3");
|
||||
|
||||
thread::sleep(Duration::from_millis(1500));
|
||||
drop(beep1);
|
||||
println!("Stopped beep1");
|
||||
|
||||
thread::sleep(Duration::from_millis(1500));
|
||||
drop(beep3);
|
||||
println!("Stopped beep3");
|
||||
|
||||
thread::sleep(Duration::from_millis(1500));
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use rodio::source::{SineWave, Source};
|
||||
use rodio::{dynamic_mixer, OutputStream, Sink};
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
// Construct a dynamic controller and mixer, stream_handle, and sink.
|
||||
let (controller, mixer) = dynamic_mixer::mixer::<f32>(2, 44_100);
|
||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
let sink = Sink::try_new(&stream_handle).unwrap();
|
||||
|
||||
// Create four unique sources. The frequencies used here correspond
|
||||
// notes in the key of C and in octave 4: C4, or middle C on a piano,
|
||||
// E4, G4, and A4 respectively.
|
||||
let source_c = SineWave::new(261.63)
|
||||
.take_duration(Duration::from_secs_f32(1.))
|
||||
.amplify(0.20);
|
||||
let source_e = SineWave::new(329.63)
|
||||
.take_duration(Duration::from_secs_f32(1.))
|
||||
.amplify(0.20);
|
||||
let source_g = SineWave::new(392.0)
|
||||
.take_duration(Duration::from_secs_f32(1.))
|
||||
.amplify(0.20);
|
||||
let source_a = SineWave::new(440.0)
|
||||
.take_duration(Duration::from_secs_f32(1.))
|
||||
.amplify(0.20);
|
||||
|
||||
// Add sources C, E, G, and A to the mixer controller.
|
||||
controller.add(source_c);
|
||||
controller.add(source_e);
|
||||
controller.add(source_g);
|
||||
controller.add(source_a);
|
||||
|
||||
// Append the dynamic mixer to the sink to play a C major 6th chord.
|
||||
sink.append(mixer);
|
||||
|
||||
// Sleep the thread until sink is empty.
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
use std::io::BufReader;
|
||||
|
||||
fn main() {
|
||||
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
|
||||
let sink = rodio::Sink::try_new(&handle).unwrap();
|
||||
|
||||
let file = std::fs::File::open("assets/music.flac").unwrap();
|
||||
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
|
||||
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
use std::io::BufReader;
|
||||
|
||||
fn main() {
|
||||
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
|
||||
let sink = rodio::Sink::try_new(&handle).unwrap();
|
||||
|
||||
let file = std::fs::File::open("assets/music.m4a").unwrap();
|
||||
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
|
||||
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
use std::io::BufReader;
|
||||
|
||||
fn main() {
|
||||
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
|
||||
let sink = rodio::Sink::try_new(&handle).unwrap();
|
||||
|
||||
let file = std::fs::File::open("assets/music.mp3").unwrap();
|
||||
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
|
||||
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
use std::io::BufReader;
|
||||
|
||||
fn main() {
|
||||
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
|
||||
let sink = rodio::Sink::try_new(&handle).unwrap();
|
||||
|
||||
let file = std::fs::File::open("assets/music.ogg").unwrap();
|
||||
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
|
||||
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
use std::io::BufReader;
|
||||
|
||||
fn main() {
|
||||
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
|
||||
let sink = rodio::Sink::try_new(&handle).unwrap();
|
||||
|
||||
let file = std::fs::File::open("assets/music.wav").unwrap();
|
||||
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
|
||||
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
use rodio::Source;
|
||||
use std::io::BufReader;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
|
||||
let sink = rodio::Sink::try_new(&handle).unwrap();
|
||||
|
||||
let file = std::fs::File::open("assets/music.ogg").unwrap();
|
||||
let source = rodio::Decoder::new(BufReader::new(file)).unwrap();
|
||||
let with_reverb = source.buffered().reverb(Duration::from_millis(40), 0.7);
|
||||
sink.append(with_reverb);
|
||||
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
use std::io::BufReader;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
|
||||
let sink = rodio::Sink::try_new(&handle).unwrap();
|
||||
|
||||
let file = std::fs::File::open("assets/music.mp3").unwrap();
|
||||
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
sink.try_seek(Duration::from_secs(0)).unwrap();
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
sink.try_seek(Duration::from_secs(4)).unwrap();
|
||||
|
||||
sink.sleep_until_end();
|
||||
|
||||
// wont do anything since the sound has ended already
|
||||
sink.try_seek(Duration::from_secs(5)).unwrap();
|
||||
println!("seek example ended");
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
use std::io::BufReader;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use rodio::Source;
|
||||
|
||||
fn main() {
|
||||
let iter_duration = Duration::from_secs(5);
|
||||
let iter_distance = 5.;
|
||||
|
||||
let refresh_duration = Duration::from_millis(10);
|
||||
|
||||
let num_steps = iter_duration.as_secs_f32() / refresh_duration.as_secs_f32();
|
||||
let step_distance = iter_distance / num_steps;
|
||||
let num_steps = num_steps as u32;
|
||||
|
||||
let repeats = 5;
|
||||
|
||||
let total_duration = iter_duration * 2 * repeats;
|
||||
|
||||
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
|
||||
let mut positions = ([0., 0., 0.], [-1., 0., 0.], [1., 0., 0.]);
|
||||
let sink = rodio::SpatialSink::try_new(&handle, positions.0, positions.1, positions.2).unwrap();
|
||||
|
||||
let file = std::fs::File::open("assets/music.ogg").unwrap();
|
||||
let source = rodio::Decoder::new(BufReader::new(file))
|
||||
.unwrap()
|
||||
.repeat_infinite()
|
||||
.take_duration(total_duration);
|
||||
sink.append(source);
|
||||
// A sound emitter playing the music starting at the centre gradually moves to the right
|
||||
// until it stops and begins traveling to the left, it will eventually pass through the
|
||||
// listener again and go to the far left.
|
||||
// This is repeated 5 times.
|
||||
for _ in 0..repeats {
|
||||
for _ in 0..num_steps {
|
||||
thread::sleep(refresh_duration);
|
||||
positions.0[0] += step_distance;
|
||||
sink.set_emitter_position(positions.0);
|
||||
}
|
||||
for _ in 0..(num_steps * 2) {
|
||||
thread::sleep(refresh_duration);
|
||||
positions.0[0] -= step_distance;
|
||||
sink.set_emitter_position(positions.0);
|
||||
}
|
||||
for _ in 0..num_steps {
|
||||
thread::sleep(refresh_duration);
|
||||
positions.0[0] += step_distance;
|
||||
sink.set_emitter_position(positions.0);
|
||||
}
|
||||
}
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
//! Plays a tone alternating between right and left ears, with right being first.
|
||||
use std::io::BufReader;
|
||||
|
||||
fn main() {
|
||||
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
|
||||
let sink = rodio::Sink::try_new(&handle).unwrap();
|
||||
|
||||
let file = std::fs::File::open("assets/RL.ogg").unwrap();
|
||||
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
|
||||
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
Vendored
+200
@@ -0,0 +1,200 @@
|
||||
//! A simple source of samples coming from a buffer.
|
||||
//!
|
||||
//! The `SamplesBuffer` struct can be used to treat a list of values as a `Source`.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! use rodio::buffer::SamplesBuffer;
|
||||
//! let _ = SamplesBuffer::new(1, 44100, vec![1i16, 2, 3, 4, 5, 6]);
|
||||
//! ```
|
||||
//!
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::SeekError;
|
||||
use crate::{Sample, Source};
|
||||
|
||||
/// A buffer of samples treated as a source.
|
||||
pub struct SamplesBuffer<S> {
|
||||
data: Vec<S>,
|
||||
pos: usize,
|
||||
channels: u16,
|
||||
sample_rate: u32,
|
||||
duration: Duration,
|
||||
}
|
||||
|
||||
impl<S> SamplesBuffer<S>
|
||||
where
|
||||
S: Sample,
|
||||
{
|
||||
/// Builds a new `SamplesBuffer`.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// - Panics if the number of channels is zero.
|
||||
/// - Panics if the samples rate is zero.
|
||||
/// - Panics if the length of the buffer is larger than approximately 16 billion elements.
|
||||
/// This is because the calculation of the duration would overflow.
|
||||
///
|
||||
pub fn new<D>(channels: u16, sample_rate: u32, data: D) -> SamplesBuffer<S>
|
||||
where
|
||||
D: Into<Vec<S>>,
|
||||
{
|
||||
assert!(channels != 0);
|
||||
assert!(sample_rate != 0);
|
||||
|
||||
let data = data.into();
|
||||
let duration_ns = 1_000_000_000u64.checked_mul(data.len() as u64).unwrap()
|
||||
/ sample_rate as u64
|
||||
/ channels as u64;
|
||||
let duration = Duration::new(
|
||||
duration_ns / 1_000_000_000,
|
||||
(duration_ns % 1_000_000_000) as u32,
|
||||
);
|
||||
|
||||
SamplesBuffer {
|
||||
data,
|
||||
pos: 0,
|
||||
channels,
|
||||
sample_rate,
|
||||
duration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Source for SamplesBuffer<S>
|
||||
where
|
||||
S: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.channels
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
Some(self.duration)
|
||||
}
|
||||
|
||||
// this is fast because all the samples are in memory already
|
||||
// and due to the constant sample_rate we can jump to the right
|
||||
// sample directly
|
||||
//
|
||||
/// This jumps in memory till the sample for `pos`.
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
let curr_channel = self.pos % self.channels() as usize;
|
||||
let new_pos = pos.as_secs_f32() * self.sample_rate() as f32 * self.channels() as f32;
|
||||
// saturate pos at the end of the source
|
||||
let new_pos = new_pos as usize;
|
||||
let new_pos = new_pos.min(self.data.len());
|
||||
|
||||
// make sure the next sample is for the right channel
|
||||
let new_pos = new_pos.next_multiple_of(self.channels() as usize);
|
||||
let new_pos = new_pos - curr_channel;
|
||||
|
||||
self.pos = new_pos;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Iterator for SamplesBuffer<S>
|
||||
where
|
||||
S: Sample,
|
||||
{
|
||||
type Item = S;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<S> {
|
||||
let sample = self.data.get(self.pos)?;
|
||||
self.pos += 1;
|
||||
Some(*sample)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(self.data.len(), Some(self.data.len()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::buffer::SamplesBuffer;
|
||||
use crate::source::Source;
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let _ = SamplesBuffer::new(1, 44100, vec![0i16, 0, 0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panic_if_zero_channels() {
|
||||
SamplesBuffer::new(0, 44100, vec![0i16, 0, 0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panic_if_zero_sample_rate() {
|
||||
SamplesBuffer::new(1, 0, vec![0i16, 0, 0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_basic() {
|
||||
let buf = SamplesBuffer::new(2, 2, vec![0i16, 0, 0, 0, 0, 0]);
|
||||
let dur = buf.total_duration().unwrap();
|
||||
assert_eq!(dur.as_secs(), 1);
|
||||
assert_eq!(dur.subsec_nanos(), 500_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iteration() {
|
||||
let mut buf = SamplesBuffer::new(1, 44100, vec![1i16, 2, 3, 4, 5, 6]);
|
||||
assert_eq!(buf.next(), Some(1));
|
||||
assert_eq!(buf.next(), Some(2));
|
||||
assert_eq!(buf.next(), Some(3));
|
||||
assert_eq!(buf.next(), Some(4));
|
||||
assert_eq!(buf.next(), Some(5));
|
||||
assert_eq!(buf.next(), Some(6));
|
||||
assert_eq!(buf.next(), None);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod try_seek {
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn channel_order_stays_correct() {
|
||||
const SAMPLE_RATE: u32 = 100;
|
||||
const CHANNELS: u16 = 2;
|
||||
let mut buf = SamplesBuffer::new(
|
||||
CHANNELS,
|
||||
SAMPLE_RATE,
|
||||
(0..2000i16).into_iter().collect::<Vec<_>>(),
|
||||
);
|
||||
buf.try_seek(Duration::from_secs(5)).unwrap();
|
||||
assert_eq!(
|
||||
buf.next(),
|
||||
Some(5i16 * SAMPLE_RATE as i16 * CHANNELS as i16)
|
||||
);
|
||||
|
||||
assert!(buf.next().is_some_and(|s| s % 2 == 1));
|
||||
assert!(buf.next().is_some_and(|s| s % 2 == 0));
|
||||
|
||||
buf.try_seek(Duration::from_secs(6)).unwrap();
|
||||
assert!(buf.next().is_some_and(|s| s % 2 == 1),);
|
||||
}
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
use cpal::Sample;
|
||||
|
||||
/// Iterator that converts from a certain channel count to another.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ChannelCountConverter<I>
|
||||
where
|
||||
I: Iterator,
|
||||
{
|
||||
input: I,
|
||||
from: cpal::ChannelCount,
|
||||
to: cpal::ChannelCount,
|
||||
sample_repeat: Option<I::Item>,
|
||||
next_output_sample_pos: cpal::ChannelCount,
|
||||
}
|
||||
|
||||
impl<I> ChannelCountConverter<I>
|
||||
where
|
||||
I: Iterator,
|
||||
{
|
||||
/// Initializes the iterator.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// Panics if `from` or `to` are equal to 0.
|
||||
///
|
||||
#[inline]
|
||||
pub fn new(
|
||||
input: I,
|
||||
from: cpal::ChannelCount,
|
||||
to: cpal::ChannelCount,
|
||||
) -> ChannelCountConverter<I> {
|
||||
assert!(from >= 1);
|
||||
assert!(to >= 1);
|
||||
|
||||
ChannelCountConverter {
|
||||
input,
|
||||
from,
|
||||
to,
|
||||
sample_repeat: None,
|
||||
next_output_sample_pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroys this iterator and returns the underlying iterator.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
|
||||
/// Get mutable access to the iterator
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for ChannelCountConverter<I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
let result = match self.next_output_sample_pos {
|
||||
0 => {
|
||||
// save first sample for mono -> stereo conversion
|
||||
let value = self.input.next();
|
||||
self.sample_repeat = value;
|
||||
value
|
||||
}
|
||||
x if x < self.from => self.input.next(),
|
||||
1 => self.sample_repeat,
|
||||
_ => Some(I::Item::EQUILIBRIUM),
|
||||
};
|
||||
|
||||
if result.is_some() {
|
||||
self.next_output_sample_pos += 1;
|
||||
}
|
||||
|
||||
if self.next_output_sample_pos == self.to {
|
||||
self.next_output_sample_pos = 0;
|
||||
|
||||
if self.from > self.to {
|
||||
for _ in self.to..self.from {
|
||||
self.input.next(); // discarding extra input
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let (min, max) = self.input.size_hint();
|
||||
|
||||
let consumed = std::cmp::min(self.from, self.next_output_sample_pos) as usize;
|
||||
let calculate = |size| {
|
||||
(size + consumed) / self.from as usize * self.to as usize
|
||||
- self.next_output_sample_pos as usize
|
||||
};
|
||||
|
||||
let min = calculate(min);
|
||||
let max = max.map(calculate);
|
||||
|
||||
(min, max)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ExactSizeIterator for ChannelCountConverter<I>
|
||||
where
|
||||
I: ExactSizeIterator,
|
||||
I::Item: Sample,
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::ChannelCountConverter;
|
||||
|
||||
#[test]
|
||||
fn remove_channels() {
|
||||
let input = vec![1u16, 2, 3, 4, 5, 6];
|
||||
let output = ChannelCountConverter::new(input.into_iter(), 3, 2).collect::<Vec<_>>();
|
||||
assert_eq!(output, [1, 2, 4, 5]);
|
||||
|
||||
let input = vec![1u16, 2, 3, 4, 5, 6, 7, 8];
|
||||
let output = ChannelCountConverter::new(input.into_iter(), 4, 1).collect::<Vec<_>>();
|
||||
assert_eq!(output, [1, 5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_channels() {
|
||||
let input = vec![1i16, 2, 3, 4];
|
||||
let output = ChannelCountConverter::new(input.into_iter(), 1, 2).collect::<Vec<_>>();
|
||||
assert_eq!(output, [1, 1, 2, 2, 3, 3, 4, 4]);
|
||||
|
||||
let input = vec![1i16, 2];
|
||||
let output = ChannelCountConverter::new(input.into_iter(), 1, 4).collect::<Vec<_>>();
|
||||
assert_eq!(output, [1, 1, 0, 0, 2, 2, 0, 0]);
|
||||
|
||||
let input = vec![1i16, 2, 3, 4];
|
||||
let output = ChannelCountConverter::new(input.into_iter(), 2, 4).collect::<Vec<_>>();
|
||||
assert_eq!(output, [1, 2, 0, 0, 3, 4, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size_hint() {
|
||||
fn test(input: &[i16], from: cpal::ChannelCount, to: cpal::ChannelCount) {
|
||||
let mut converter = ChannelCountConverter::new(input.iter().copied(), from, to);
|
||||
let count = converter.clone().count();
|
||||
for left_in_iter in (0..=count).rev() {
|
||||
println!("left_in_iter = {}", left_in_iter);
|
||||
assert_eq!(converter.size_hint(), (left_in_iter, Some(left_in_iter)));
|
||||
converter.next();
|
||||
}
|
||||
assert_eq!(converter.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
test(&[1i16, 2, 3], 1, 2);
|
||||
test(&[1i16, 2, 3, 4], 2, 4);
|
||||
test(&[1i16, 2, 3, 4], 4, 2);
|
||||
test(&[1i16, 2, 3, 4, 5, 6], 3, 8);
|
||||
test(&[1i16, 2, 3, 4, 5, 6, 7, 8], 4, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_more() {
|
||||
let input = vec![1i16, 2, 3, 4];
|
||||
let output = ChannelCountConverter::new(input.into_iter(), 2, 3);
|
||||
assert_eq!(output.len(), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_less() {
|
||||
let input = vec![1i16, 2, 3, 4];
|
||||
let output = ChannelCountConverter::new(input.into_iter(), 2, 1);
|
||||
assert_eq!(output.len(), 2);
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*!
|
||||
This module contains function that will convert from one PCM format to another.
|
||||
|
||||
This includes conversion between sample formats, channels or sample rates.
|
||||
|
||||
*/
|
||||
|
||||
pub use self::channels::ChannelCountConverter;
|
||||
pub use self::sample::DataConverter;
|
||||
pub use self::sample::Sample;
|
||||
pub use self::sample_rate::SampleRateConverter;
|
||||
|
||||
mod channels;
|
||||
// TODO: < shouldn't be public ; there's a bug in Rust 1.4 and below that makes This
|
||||
// `pub` mandatory
|
||||
pub mod sample;
|
||||
mod sample_rate;
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
use cpal::{FromSample, Sample as CpalSample};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Converts the samples data type to `O`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DataConverter<I, O> {
|
||||
input: I,
|
||||
marker: PhantomData<O>,
|
||||
}
|
||||
|
||||
impl<I, O> DataConverter<I, O> {
|
||||
/// Builds a new converter.
|
||||
#[inline]
|
||||
pub fn new(input: I) -> DataConverter<I, O> {
|
||||
DataConverter {
|
||||
input,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroys this iterator and returns the underlying iterator.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
|
||||
/// get mutable access to the iterator
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> Iterator for DataConverter<I, O>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Sample,
|
||||
O: FromSample<I::Item> + Sample,
|
||||
{
|
||||
type Item = O;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<O> {
|
||||
self.input.next().map(|s| CpalSample::from_sample(s))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> ExactSizeIterator for DataConverter<I, O>
|
||||
where
|
||||
I: ExactSizeIterator,
|
||||
I::Item: Sample,
|
||||
O: FromSample<I::Item> + Sample,
|
||||
{
|
||||
}
|
||||
|
||||
/// Represents a value of a single sample.
|
||||
///
|
||||
/// This trait is implemented by default on three types: `i16`, `u16` and `f32`.
|
||||
///
|
||||
/// - For `i16`, silence corresponds to the value `0`. The minimum and maximum amplitudes are
|
||||
/// represented by `i16::min_value()` and `i16::max_value()` respectively.
|
||||
/// - For `u16`, silence corresponds to the value `u16::max_value() / 2`. The minimum and maximum
|
||||
/// amplitudes are represented by `0` and `u16::max_value()` respectively.
|
||||
/// - For `f32`, silence corresponds to the value `0.0`. The minimum and maximum amplitudes are
|
||||
/// represented by `-1.0` and `1.0` respectively.
|
||||
///
|
||||
/// You can implement this trait on your own type as well if you wish so.
|
||||
///
|
||||
pub trait Sample: CpalSample {
|
||||
/// Linear interpolation between two samples.
|
||||
///
|
||||
/// The result should be equal to
|
||||
/// `first * numerator / denominator + second * (1 - numerator / denominator)`.
|
||||
fn lerp(first: Self, second: Self, numerator: u32, denominator: u32) -> Self;
|
||||
/// Multiplies the value of this sample by the given amount.
|
||||
fn amplify(self, value: f32) -> Self;
|
||||
|
||||
/// Calls `saturating_add` on the sample.
|
||||
fn saturating_add(self, other: Self) -> Self;
|
||||
|
||||
/// Returns the value corresponding to the absence of sound.
|
||||
fn zero_value() -> Self;
|
||||
}
|
||||
|
||||
impl Sample for u16 {
|
||||
#[inline]
|
||||
fn lerp(first: u16, second: u16, numerator: u32, denominator: u32) -> u16 {
|
||||
let a = first as i32;
|
||||
let b = second as i32;
|
||||
let n = numerator as i32;
|
||||
let d = denominator as i32;
|
||||
(a + (b - a) * n / d) as u16
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn amplify(self, value: f32) -> u16 {
|
||||
((self as f32) * value) as u16
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn saturating_add(self, other: u16) -> u16 {
|
||||
self.saturating_add(other)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn zero_value() -> u16 {
|
||||
32768
|
||||
}
|
||||
}
|
||||
|
||||
impl Sample for i16 {
|
||||
#[inline]
|
||||
fn lerp(first: i16, second: i16, numerator: u32, denominator: u32) -> i16 {
|
||||
(first as i32 + (second as i32 - first as i32) * numerator as i32 / denominator as i32)
|
||||
as i16
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn amplify(self, value: f32) -> i16 {
|
||||
((self as f32) * value) as i16
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn saturating_add(self, other: i16) -> i16 {
|
||||
self.saturating_add(other)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn zero_value() -> i16 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl Sample for f32 {
|
||||
#[inline]
|
||||
fn lerp(first: f32, second: f32, numerator: u32, denominator: u32) -> f32 {
|
||||
first + (second - first) * numerator as f32 / denominator as f32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn amplify(self, value: f32) -> f32 {
|
||||
self * value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn saturating_add(self, other: f32) -> f32 {
|
||||
self + other
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn zero_value() -> f32 {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
use crate::conversions::Sample;
|
||||
|
||||
use std::mem;
|
||||
|
||||
/// Iterator that converts from a certain sample rate to another.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SampleRateConverter<I>
|
||||
where
|
||||
I: Iterator,
|
||||
{
|
||||
/// The iterator that gives us samples.
|
||||
input: I,
|
||||
/// We convert chunks of `from` samples into chunks of `to` samples.
|
||||
from: u32,
|
||||
/// We convert chunks of `from` samples into chunks of `to` samples.
|
||||
to: u32,
|
||||
/// Number of channels in the stream
|
||||
channels: cpal::ChannelCount,
|
||||
/// One sample per channel, extracted from `input`.
|
||||
current_frame: Vec<I::Item>,
|
||||
/// Position of `current_sample` modulo `from`.
|
||||
current_frame_pos_in_chunk: u32,
|
||||
/// The samples right after `current_sample` (one per channel), extracted from `input`.
|
||||
next_frame: Vec<I::Item>,
|
||||
/// The position of the next sample that the iterator should return, modulo `to`.
|
||||
/// This counter is incremented (modulo `to`) every time the iterator is called.
|
||||
next_output_frame_pos_in_chunk: u32,
|
||||
/// The buffer containing the samples waiting to be output.
|
||||
output_buffer: Vec<I::Item>,
|
||||
}
|
||||
|
||||
impl<I> SampleRateConverter<I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Sample,
|
||||
{
|
||||
///
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// Panics if `from` or `to` are equal to 0.
|
||||
///
|
||||
#[inline]
|
||||
pub fn new(
|
||||
mut input: I,
|
||||
from: cpal::SampleRate,
|
||||
to: cpal::SampleRate,
|
||||
num_channels: cpal::ChannelCount,
|
||||
) -> SampleRateConverter<I> {
|
||||
let from = from.0;
|
||||
let to = to.0;
|
||||
|
||||
assert!(from >= 1);
|
||||
assert!(to >= 1);
|
||||
|
||||
// finding greatest common divisor
|
||||
let gcd = {
|
||||
#[inline]
|
||||
fn gcd(a: u32, b: u32) -> u32 {
|
||||
if b == 0 {
|
||||
a
|
||||
} else {
|
||||
gcd(b, a % b)
|
||||
}
|
||||
}
|
||||
|
||||
gcd(from, to)
|
||||
};
|
||||
|
||||
let (first_samples, next_samples) = if from == to {
|
||||
// if `from` == `to` == 1, then we just pass through
|
||||
debug_assert_eq!(from, gcd);
|
||||
(Vec::new(), Vec::new())
|
||||
} else {
|
||||
let first = input
|
||||
.by_ref()
|
||||
.take(num_channels as usize)
|
||||
.collect::<Vec<_>>();
|
||||
let next = input
|
||||
.by_ref()
|
||||
.take(num_channels as usize)
|
||||
.collect::<Vec<_>>();
|
||||
(first, next)
|
||||
};
|
||||
|
||||
SampleRateConverter {
|
||||
input,
|
||||
from: from / gcd,
|
||||
to: to / gcd,
|
||||
channels: num_channels,
|
||||
current_frame_pos_in_chunk: 0,
|
||||
next_output_frame_pos_in_chunk: 0,
|
||||
current_frame: first_samples,
|
||||
next_frame: next_samples,
|
||||
output_buffer: Vec::with_capacity(num_channels as usize - 1),
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroys this iterator and returns the underlying iterator.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
|
||||
/// get mutable access to the iterator
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
fn next_input_frame(&mut self) {
|
||||
self.current_frame_pos_in_chunk += 1;
|
||||
|
||||
mem::swap(&mut self.current_frame, &mut self.next_frame);
|
||||
self.next_frame.clear();
|
||||
for _ in 0..self.channels {
|
||||
if let Some(i) = self.input.next() {
|
||||
self.next_frame.push(i);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for SampleRateConverter<I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Sample + Clone,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
// the algorithm below doesn't work if `self.from == self.to`
|
||||
if self.from == self.to {
|
||||
debug_assert_eq!(self.from, 1);
|
||||
return self.input.next();
|
||||
}
|
||||
|
||||
// Short circuit if there are some samples waiting.
|
||||
if !self.output_buffer.is_empty() {
|
||||
return Some(self.output_buffer.remove(0));
|
||||
}
|
||||
|
||||
// The frame we are going to return from this function will be a linear interpolation
|
||||
// between `self.current_frame` and `self.next_frame`.
|
||||
|
||||
if self.next_output_frame_pos_in_chunk == self.to {
|
||||
// If we jump to the next frame, we reset the whole state.
|
||||
self.next_output_frame_pos_in_chunk = 0;
|
||||
|
||||
self.next_input_frame();
|
||||
while self.current_frame_pos_in_chunk != self.from {
|
||||
self.next_input_frame();
|
||||
}
|
||||
self.current_frame_pos_in_chunk = 0;
|
||||
} else {
|
||||
// Finding the position of the first sample of the linear interpolation.
|
||||
let req_left_sample =
|
||||
(self.from * self.next_output_frame_pos_in_chunk / self.to) % self.from;
|
||||
|
||||
// Advancing `self.current_frame`, `self.next_frame` and
|
||||
// `self.current_frame_pos_in_chunk` until the latter variable
|
||||
// matches `req_left_sample`.
|
||||
while self.current_frame_pos_in_chunk != req_left_sample {
|
||||
self.next_input_frame();
|
||||
debug_assert!(self.current_frame_pos_in_chunk < self.from);
|
||||
}
|
||||
}
|
||||
|
||||
// Merging `self.current_frame` and `self.next_frame` into `self.output_buffer`.
|
||||
// Note that `self.output_buffer` can be truncated if there is not enough data in
|
||||
// `self.next_frame`.
|
||||
let mut result = None;
|
||||
let numerator = (self.from * self.next_output_frame_pos_in_chunk) % self.to;
|
||||
for (off, (cur, next)) in self
|
||||
.current_frame
|
||||
.iter()
|
||||
.zip(self.next_frame.iter())
|
||||
.enumerate()
|
||||
{
|
||||
let sample = Sample::lerp(*cur, *next, numerator, self.to);
|
||||
|
||||
if off == 0 {
|
||||
result = Some(sample);
|
||||
} else {
|
||||
self.output_buffer.push(sample);
|
||||
}
|
||||
}
|
||||
|
||||
// Incrementing the counter for the next iteration.
|
||||
self.next_output_frame_pos_in_chunk += 1;
|
||||
|
||||
if result.is_some() {
|
||||
result
|
||||
} else {
|
||||
// draining `self.current_frame`
|
||||
if !self.current_frame.is_empty() {
|
||||
let r = Some(self.current_frame.remove(0));
|
||||
mem::swap(&mut self.output_buffer, &mut self.current_frame);
|
||||
self.current_frame.clear();
|
||||
r
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let apply = |samples: usize| {
|
||||
// `samples_after_chunk` will contain the number of samples remaining after the chunk
|
||||
// currently being processed
|
||||
let samples_after_chunk = samples;
|
||||
// adding the samples of the next chunk that may have already been read
|
||||
let samples_after_chunk = if self.current_frame_pos_in_chunk == self.from - 1 {
|
||||
samples_after_chunk + self.next_frame.len()
|
||||
} else {
|
||||
samples_after_chunk
|
||||
};
|
||||
// removing the samples of the current chunk that have not yet been read
|
||||
let samples_after_chunk = samples_after_chunk.saturating_sub(
|
||||
self.from
|
||||
.saturating_sub(self.current_frame_pos_in_chunk + 2) as usize
|
||||
* usize::from(self.channels),
|
||||
);
|
||||
// calculating the number of samples after the transformation
|
||||
// TODO: this is wrong here \|/
|
||||
let samples_after_chunk = samples_after_chunk * self.to as usize / self.from as usize;
|
||||
|
||||
// `samples_current_chunk` will contain the number of samples remaining to be output
|
||||
// for the chunk currently being processed
|
||||
let samples_current_chunk = (self.to - self.next_output_frame_pos_in_chunk) as usize
|
||||
* usize::from(self.channels);
|
||||
|
||||
samples_current_chunk + samples_after_chunk + self.output_buffer.len()
|
||||
};
|
||||
|
||||
if self.from == self.to {
|
||||
self.input.size_hint()
|
||||
} else {
|
||||
let (min, max) = self.input.size_hint();
|
||||
(apply(min), max.map(apply))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ExactSizeIterator for SampleRateConverter<I>
|
||||
where
|
||||
I: ExactSizeIterator,
|
||||
I::Item: Sample + Clone,
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::SampleRateConverter;
|
||||
use core::time::Duration;
|
||||
use cpal::SampleRate;
|
||||
use quickcheck::quickcheck;
|
||||
|
||||
// TODO: Remove once cpal 0.12.2 is released and the dependency is updated
|
||||
// (cpal#483 implemented ops::Mul on SampleRate)
|
||||
const fn multiply_rate(r: SampleRate, k: u32) -> SampleRate {
|
||||
SampleRate(k * r.0)
|
||||
}
|
||||
|
||||
quickcheck! {
|
||||
/// Check that resampling an empty input produces no output.
|
||||
fn empty(from: u32, to: u32, n: u16) -> () {
|
||||
let from = if from == 0 { return; } else { SampleRate(from) };
|
||||
let to = if to == 0 { return; } else { SampleRate(to) };
|
||||
if n == 0 { return; }
|
||||
|
||||
let input: Vec<u16> = Vec::new();
|
||||
let output =
|
||||
SampleRateConverter::new(input.into_iter(), from, to, n)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output, []);
|
||||
}
|
||||
|
||||
/// Check that resampling to the same rate does not change the signal.
|
||||
fn identity(from: u32, n: u16, input: Vec<u16>) -> () {
|
||||
let from = if from == 0 { return; } else { SampleRate(from) };
|
||||
if n == 0 { return; }
|
||||
|
||||
let output =
|
||||
SampleRateConverter::new(input.clone().into_iter(), from, from, n)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(input, output);
|
||||
}
|
||||
|
||||
/// Check that dividing the sample rate by k (integer) is the same as
|
||||
/// dropping a sample from each channel.
|
||||
fn divide_sample_rate(to: u32, k: u32, input: Vec<u16>, n: u16) -> () {
|
||||
let to = if to == 0 { return; } else { SampleRate(to) };
|
||||
let from = multiply_rate(to, k);
|
||||
if k == 0 || n == 0 { return; }
|
||||
|
||||
// Truncate the input, so it contains an integer number of frames.
|
||||
let input = {
|
||||
let ns = n as usize;
|
||||
let mut i = input;
|
||||
i.truncate(ns * (i.len() / ns));
|
||||
i
|
||||
};
|
||||
|
||||
let output =
|
||||
SampleRateConverter::new(input.clone().into_iter(), from, to, n)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(input.chunks_exact(n.into())
|
||||
.step_by(k as usize).collect::<Vec<_>>().concat(),
|
||||
output)
|
||||
}
|
||||
|
||||
/// Check that, after multiplying the sample rate by k, every k-th
|
||||
/// sample in the output matches exactly with the input.
|
||||
fn multiply_sample_rate(from: u32, k: u32, input: Vec<u16>, n: u16) -> () {
|
||||
let from = if from == 0 { return; } else { SampleRate(from) };
|
||||
let to = multiply_rate(from, k);
|
||||
if k == 0 || n == 0 { return; }
|
||||
|
||||
// Truncate the input, so it contains an integer number of frames.
|
||||
let input = {
|
||||
let ns = n as usize;
|
||||
let mut i = input;
|
||||
i.truncate(ns * (i.len() / ns));
|
||||
i
|
||||
};
|
||||
|
||||
let output =
|
||||
SampleRateConverter::new(input.clone().into_iter(), from, to, n)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(input,
|
||||
output.chunks_exact(n.into())
|
||||
.step_by(k as usize).collect::<Vec<_>>().concat()
|
||||
)
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
/// Check that resampling does not change the audio duration,
|
||||
/// except by a negligible amount (± 1ms). Reproduces #316.
|
||||
/// Ignored, pending a bug fix.
|
||||
fn preserve_durations(d: Duration, freq: f32, to: u32) -> () {
|
||||
use crate::source::{SineWave, Source};
|
||||
|
||||
let to = if to == 0 { return; } else { SampleRate(to) };
|
||||
let source = SineWave::new(freq).take_duration(d);
|
||||
let from = SampleRate(source.sample_rate());
|
||||
|
||||
let resampled =
|
||||
SampleRateConverter::new(source, from, to, 1);
|
||||
let duration =
|
||||
Duration::from_secs_f32(resampled.count() as f32 / to.0 as f32);
|
||||
|
||||
let delta = if d < duration { duration - d } else { d - duration };
|
||||
assert!(delta < Duration::from_millis(1),
|
||||
"Resampled duration ({:?}) is not close to original ({:?}); Δ = {:?}",
|
||||
duration, d, delta);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upsample() {
|
||||
let input = vec![2u16, 16, 4, 18, 6, 20, 8, 22];
|
||||
let output =
|
||||
SampleRateConverter::new(input.into_iter(), SampleRate(2000), SampleRate(3000), 2);
|
||||
assert_eq!(output.len(), 12);
|
||||
|
||||
let output = output.collect::<Vec<_>>();
|
||||
assert_eq!(output, [2, 16, 3, 17, 4, 18, 6, 20, 7, 21, 8, 22]);
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::mem;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::SeekError;
|
||||
use crate::Source;
|
||||
|
||||
use claxon::FlacReader;
|
||||
|
||||
/// Decoder for the Flac format.
|
||||
pub struct FlacDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
reader: FlacReader<R>,
|
||||
current_block: Vec<i32>,
|
||||
current_block_channel_len: usize,
|
||||
current_block_off: usize,
|
||||
bits_per_sample: u32,
|
||||
sample_rate: u32,
|
||||
channels: u16,
|
||||
samples: Option<u64>,
|
||||
}
|
||||
|
||||
impl<R> FlacDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
/// Attempts to decode the data as Flac.
|
||||
pub fn new(mut data: R) -> Result<FlacDecoder<R>, R> {
|
||||
if !is_flac(data.by_ref()) {
|
||||
return Err(data);
|
||||
}
|
||||
|
||||
let reader = FlacReader::new(data).unwrap();
|
||||
let spec = reader.streaminfo();
|
||||
|
||||
Ok(FlacDecoder {
|
||||
reader,
|
||||
current_block: Vec::with_capacity(
|
||||
spec.max_block_size as usize * spec.channels as usize,
|
||||
),
|
||||
current_block_channel_len: 1,
|
||||
current_block_off: 0,
|
||||
bits_per_sample: spec.bits_per_sample,
|
||||
sample_rate: spec.sample_rate,
|
||||
channels: spec.channels as u16,
|
||||
samples: spec.samples,
|
||||
})
|
||||
}
|
||||
pub fn into_inner(self) -> R {
|
||||
self.reader.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Source for FlacDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.channels
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
// `samples` in FLAC means "inter-channel samples" aka frames
|
||||
// so we do not divide by `self.channels` here.
|
||||
self.samples
|
||||
.map(|s| Duration::from_micros(s * 1_000_000 / self.sample_rate as u64))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
|
||||
Err(SeekError::NotSupported {
|
||||
underlying_source: std::any::type_name::<Self>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Iterator for FlacDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
type Item = i16;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<i16> {
|
||||
loop {
|
||||
if self.current_block_off < self.current_block.len() {
|
||||
// Read from current block.
|
||||
let real_offset = (self.current_block_off % self.channels as usize)
|
||||
* self.current_block_channel_len
|
||||
+ self.current_block_off / self.channels as usize;
|
||||
let raw_val = self.current_block[real_offset];
|
||||
self.current_block_off += 1;
|
||||
let real_val = match self.bits_per_sample.cmp(&16) {
|
||||
Ordering::Less => (raw_val << (16 - self.bits_per_sample)) as i16,
|
||||
Ordering::Equal => raw_val as i16,
|
||||
Ordering::Greater => (raw_val >> (self.bits_per_sample - 16)) as i16,
|
||||
};
|
||||
return Some(real_val);
|
||||
}
|
||||
|
||||
// Load the next block.
|
||||
self.current_block_off = 0;
|
||||
let buffer = mem::take(&mut self.current_block);
|
||||
match self.reader.blocks().read_next_or_eof(buffer) {
|
||||
Ok(Some(block)) => {
|
||||
self.current_block_channel_len = (block.len() / block.channels()) as usize;
|
||||
self.current_block = block.into_buffer();
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the stream contains Flac data, then resets it to where it was.
|
||||
fn is_flac<R>(mut data: R) -> bool
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let stream_pos = data.stream_position().unwrap();
|
||||
|
||||
if FlacReader::new(data.by_ref()).is_err() {
|
||||
data.seek(SeekFrom::Start(stream_pos)).unwrap();
|
||||
return false;
|
||||
}
|
||||
|
||||
data.seek(SeekFrom::Start(stream_pos)).unwrap();
|
||||
true
|
||||
}
|
||||
+575
@@ -0,0 +1,575 @@
|
||||
//! Decodes samples from an audio file.
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
#[allow(unused_imports)]
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::mem;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::SeekError;
|
||||
use crate::Source;
|
||||
|
||||
#[cfg(feature = "symphonia")]
|
||||
use self::read_seek_source::ReadSeekSource;
|
||||
#[cfg(feature = "symphonia")]
|
||||
use ::symphonia::core::io::{MediaSource, MediaSourceStream};
|
||||
|
||||
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
||||
mod flac;
|
||||
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
||||
mod mp3;
|
||||
#[cfg(feature = "symphonia")]
|
||||
mod read_seek_source;
|
||||
#[cfg(feature = "symphonia")]
|
||||
pub mod symphonia;
|
||||
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
||||
mod vorbis;
|
||||
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
||||
mod wav;
|
||||
|
||||
/// Source of audio samples from decoding a file.
|
||||
///
|
||||
/// Supports MP3, WAV, Vorbis and Flac.
|
||||
pub struct Decoder<R>(DecoderImpl<R>)
|
||||
where
|
||||
R: Read + Seek;
|
||||
|
||||
pub struct LoopedDecoder<R>(DecoderImpl<R>)
|
||||
where
|
||||
R: Read + Seek;
|
||||
|
||||
// can not really reduce the size of the VorbisDecoder. There are not any
|
||||
// arrays just a lot of struct fields.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum DecoderImpl<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
||||
Wav(wav::WavDecoder<R>),
|
||||
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
||||
Vorbis(vorbis::VorbisDecoder<R>),
|
||||
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
||||
Flac(flac::FlacDecoder<R>),
|
||||
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
||||
Mp3(mp3::Mp3Decoder<R>),
|
||||
#[cfg(feature = "symphonia")]
|
||||
Symphonia(symphonia::SymphoniaDecoder),
|
||||
None(::std::marker::PhantomData<R>),
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> DecoderImpl<R> {
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<i16> {
|
||||
match self {
|
||||
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
||||
DecoderImpl::Wav(source) => source.next(),
|
||||
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
||||
DecoderImpl::Vorbis(source) => source.next(),
|
||||
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
||||
DecoderImpl::Flac(source) => source.next(),
|
||||
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
||||
DecoderImpl::Mp3(source) => source.next(),
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderImpl::Symphonia(source) => source.next(),
|
||||
DecoderImpl::None(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self {
|
||||
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
||||
DecoderImpl::Wav(source) => source.size_hint(),
|
||||
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
||||
DecoderImpl::Vorbis(source) => source.size_hint(),
|
||||
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
||||
DecoderImpl::Flac(source) => source.size_hint(),
|
||||
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
||||
DecoderImpl::Mp3(source) => source.size_hint(),
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderImpl::Symphonia(source) => source.size_hint(),
|
||||
DecoderImpl::None(_) => (0, None),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
match self {
|
||||
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
||||
DecoderImpl::Wav(source) => source.current_frame_len(),
|
||||
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
||||
DecoderImpl::Vorbis(source) => source.current_frame_len(),
|
||||
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
||||
DecoderImpl::Flac(source) => source.current_frame_len(),
|
||||
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
||||
DecoderImpl::Mp3(source) => source.current_frame_len(),
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderImpl::Symphonia(source) => source.current_frame_len(),
|
||||
DecoderImpl::None(_) => Some(0),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
match self {
|
||||
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
||||
DecoderImpl::Wav(source) => source.channels(),
|
||||
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
||||
DecoderImpl::Vorbis(source) => source.channels(),
|
||||
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
||||
DecoderImpl::Flac(source) => source.channels(),
|
||||
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
||||
DecoderImpl::Mp3(source) => source.channels(),
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderImpl::Symphonia(source) => source.channels(),
|
||||
DecoderImpl::None(_) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
match self {
|
||||
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
||||
DecoderImpl::Wav(source) => source.sample_rate(),
|
||||
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
||||
DecoderImpl::Vorbis(source) => source.sample_rate(),
|
||||
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
||||
DecoderImpl::Flac(source) => source.sample_rate(),
|
||||
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
||||
DecoderImpl::Mp3(source) => source.sample_rate(),
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderImpl::Symphonia(source) => source.sample_rate(),
|
||||
DecoderImpl::None(_) => 1,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
match self {
|
||||
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
||||
DecoderImpl::Wav(source) => source.total_duration(),
|
||||
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
||||
DecoderImpl::Vorbis(source) => source.total_duration(),
|
||||
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
||||
DecoderImpl::Flac(source) => source.total_duration(),
|
||||
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
||||
DecoderImpl::Mp3(source) => source.total_duration(),
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderImpl::Symphonia(source) => source.total_duration(),
|
||||
DecoderImpl::None(_) => Some(Duration::default()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
match self {
|
||||
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
||||
DecoderImpl::Wav(source) => source.try_seek(pos),
|
||||
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
||||
DecoderImpl::Vorbis(source) => source.try_seek(pos),
|
||||
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
||||
DecoderImpl::Flac(source) => source.try_seek(pos),
|
||||
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
||||
DecoderImpl::Mp3(source) => source.try_seek(pos),
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderImpl::Symphonia(source) => source.try_seek(pos),
|
||||
DecoderImpl::None(_) => Err(SeekError::NotSupported {
|
||||
underlying_source: "DecoderImpl::None",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Decoder<R>
|
||||
where
|
||||
R: Read + Seek + Send + Sync + 'static,
|
||||
{
|
||||
/// Builds a new decoder.
|
||||
///
|
||||
/// Attempts to automatically detect the format of the source of data.
|
||||
#[allow(unused_variables)]
|
||||
pub fn new(data: R) -> Result<Decoder<R>, DecoderError> {
|
||||
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
||||
let data = match wav::WavDecoder::new(data) {
|
||||
Err(data) => data,
|
||||
Ok(decoder) => {
|
||||
return Ok(Decoder(DecoderImpl::Wav(decoder)));
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
||||
let data = match flac::FlacDecoder::new(data) {
|
||||
Err(data) => data,
|
||||
Ok(decoder) => {
|
||||
return Ok(Decoder(DecoderImpl::Flac(decoder)));
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
||||
let data = match vorbis::VorbisDecoder::new(data) {
|
||||
Err(data) => data,
|
||||
Ok(decoder) => {
|
||||
return Ok(Decoder(DecoderImpl::Vorbis(decoder)));
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
||||
let data = match mp3::Mp3Decoder::new(data) {
|
||||
Err(data) => data,
|
||||
Ok(decoder) => {
|
||||
return Ok(Decoder(DecoderImpl::Mp3(decoder)));
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "symphonia")]
|
||||
{
|
||||
let mss = MediaSourceStream::new(
|
||||
Box::new(ReadSeekSource::new(data)) as Box<dyn MediaSource>,
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
match symphonia::SymphoniaDecoder::new(mss, None) {
|
||||
Err(e) => Err(e),
|
||||
Ok(decoder) => Ok(Decoder(DecoderImpl::Symphonia(decoder))),
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "symphonia"))]
|
||||
Err(DecoderError::UnrecognizedFormat)
|
||||
}
|
||||
pub fn new_looped(data: R) -> Result<LoopedDecoder<R>, DecoderError> {
|
||||
Self::new(data).map(LoopedDecoder::new)
|
||||
}
|
||||
|
||||
/// Builds a new decoder from wav data.
|
||||
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
||||
pub fn new_wav(data: R) -> Result<Decoder<R>, DecoderError> {
|
||||
match wav::WavDecoder::new(data) {
|
||||
Err(_) => Err(DecoderError::UnrecognizedFormat),
|
||||
Ok(decoder) => Ok(Decoder(DecoderImpl::Wav(decoder))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new decoder from wav data.
|
||||
#[cfg(feature = "symphonia-wav")]
|
||||
pub fn new_wav(data: R) -> Result<Decoder<R>, DecoderError> {
|
||||
Decoder::new_symphonia(data, "wav")
|
||||
}
|
||||
|
||||
/// Builds a new decoder from flac data.
|
||||
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
||||
pub fn new_flac(data: R) -> Result<Decoder<R>, DecoderError> {
|
||||
match flac::FlacDecoder::new(data) {
|
||||
Err(_) => Err(DecoderError::UnrecognizedFormat),
|
||||
Ok(decoder) => Ok(Decoder(DecoderImpl::Flac(decoder))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new decoder from flac data.
|
||||
#[cfg(feature = "symphonia-flac")]
|
||||
pub fn new_flac(data: R) -> Result<Decoder<R>, DecoderError> {
|
||||
Decoder::new_symphonia(data, "flac")
|
||||
}
|
||||
|
||||
/// Builds a new decoder from vorbis data.
|
||||
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
||||
pub fn new_vorbis(data: R) -> Result<Decoder<R>, DecoderError> {
|
||||
match vorbis::VorbisDecoder::new(data) {
|
||||
Err(_) => Err(DecoderError::UnrecognizedFormat),
|
||||
Ok(decoder) => Ok(Decoder(DecoderImpl::Vorbis(decoder))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new decoder from vorbis data.
|
||||
#[cfg(feature = "symphonia-vorbis")]
|
||||
pub fn new_vorbis(data: R) -> Result<Decoder<R>, DecoderError> {
|
||||
Decoder::new_symphonia(data, "ogg")
|
||||
}
|
||||
|
||||
/// Builds a new decoder from mp3 data.
|
||||
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
||||
pub fn new_mp3(data: R) -> Result<Decoder<R>, DecoderError> {
|
||||
match mp3::Mp3Decoder::new(data) {
|
||||
Err(_) => Err(DecoderError::UnrecognizedFormat),
|
||||
Ok(decoder) => Ok(Decoder(DecoderImpl::Mp3(decoder))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new decoder from mp3 data.
|
||||
#[cfg(feature = "symphonia-mp3")]
|
||||
pub fn new_mp3(data: R) -> Result<Decoder<R>, DecoderError> {
|
||||
Decoder::new_symphonia(data, "mp3")
|
||||
}
|
||||
|
||||
/// Builds a new decoder from aac data.
|
||||
#[cfg(feature = "symphonia-aac")]
|
||||
pub fn new_aac(data: R) -> Result<Decoder<R>, DecoderError> {
|
||||
Decoder::new_symphonia(data, "aac")
|
||||
}
|
||||
|
||||
/// Builds a new decoder from mp4 data.
|
||||
#[cfg(feature = "symphonia-isomp4")]
|
||||
pub fn new_mp4(data: R, hint: Mp4Type) -> Result<Decoder<R>, DecoderError> {
|
||||
Decoder::new_symphonia(data, &hint.to_string())
|
||||
}
|
||||
|
||||
#[cfg(feature = "symphonia")]
|
||||
fn new_symphonia(data: R, hint: &str) -> Result<Decoder<R>, DecoderError> {
|
||||
let mss = MediaSourceStream::new(
|
||||
Box::new(ReadSeekSource::new(data)) as Box<dyn MediaSource>,
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
match symphonia::SymphoniaDecoder::new(mss, Some(hint)) {
|
||||
Err(e) => Err(e),
|
||||
Ok(decoder) => Ok(Decoder(DecoderImpl::Symphonia(decoder))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Mp4Type {
|
||||
Mp4,
|
||||
M4a,
|
||||
M4p,
|
||||
M4b,
|
||||
M4r,
|
||||
M4v,
|
||||
Mov,
|
||||
}
|
||||
|
||||
impl FromStr for Mp4Type {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(input: &str) -> Result<Mp4Type, Self::Err> {
|
||||
match &input.to_lowercase()[..] {
|
||||
"mp4" => Ok(Mp4Type::Mp4),
|
||||
"m4a" => Ok(Mp4Type::M4a),
|
||||
"m4p" => Ok(Mp4Type::M4p),
|
||||
"m4b" => Ok(Mp4Type::M4b),
|
||||
"m4r" => Ok(Mp4Type::M4r),
|
||||
"m4v" => Ok(Mp4Type::M4v),
|
||||
"mov" => Ok(Mp4Type::Mov),
|
||||
_ => Err(format!("{} is not a valid mp4 extension", input)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Mp4Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let text = match self {
|
||||
Mp4Type::Mp4 => "mp4",
|
||||
Mp4Type::M4a => "m4a",
|
||||
Mp4Type::M4p => "m4p",
|
||||
Mp4Type::M4b => "m4b",
|
||||
Mp4Type::M4r => "m4r",
|
||||
Mp4Type::M4v => "m4v",
|
||||
Mp4Type::Mov => "mov",
|
||||
};
|
||||
write!(f, "{}", text)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> LoopedDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
fn new(decoder: Decoder<R>) -> LoopedDecoder<R> {
|
||||
Self(decoder.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Iterator for Decoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
type Item = i16;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<i16> {
|
||||
self.0.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.0.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Source for Decoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.0.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.0.channels()
|
||||
}
|
||||
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.0.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.0.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.0.try_seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Iterator for LoopedDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
type Item = i16;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<i16> {
|
||||
if let Some(sample) = self.0.next() {
|
||||
Some(sample)
|
||||
} else {
|
||||
let decoder = mem::replace(&mut self.0, DecoderImpl::None(Default::default()));
|
||||
let (decoder, sample) = match decoder {
|
||||
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
||||
DecoderImpl::Wav(source) => {
|
||||
let mut reader = source.into_inner();
|
||||
reader.seek(SeekFrom::Start(0)).ok()?;
|
||||
let mut source = wav::WavDecoder::new(reader).ok()?;
|
||||
let sample = source.next();
|
||||
(DecoderImpl::Wav(source), sample)
|
||||
}
|
||||
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
||||
DecoderImpl::Vorbis(source) => {
|
||||
use lewton::inside_ogg::OggStreamReader;
|
||||
let mut reader = source.into_inner().into_inner();
|
||||
reader.seek_bytes(SeekFrom::Start(0)).ok()?;
|
||||
let mut source = vorbis::VorbisDecoder::from_stream_reader(
|
||||
OggStreamReader::from_ogg_reader(reader).ok()?,
|
||||
);
|
||||
let sample = source.next();
|
||||
(DecoderImpl::Vorbis(source), sample)
|
||||
}
|
||||
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
||||
DecoderImpl::Flac(source) => {
|
||||
let mut reader = source.into_inner();
|
||||
reader.seek(SeekFrom::Start(0)).ok()?;
|
||||
let mut source = flac::FlacDecoder::new(reader).ok()?;
|
||||
let sample = source.next();
|
||||
(DecoderImpl::Flac(source), sample)
|
||||
}
|
||||
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
||||
DecoderImpl::Mp3(source) => {
|
||||
let mut reader = source.into_inner();
|
||||
reader.seek(SeekFrom::Start(0)).ok()?;
|
||||
let mut source = mp3::Mp3Decoder::new(reader).ok()?;
|
||||
let sample = source.next();
|
||||
(DecoderImpl::Mp3(source), sample)
|
||||
}
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderImpl::Symphonia(source) => {
|
||||
let mut reader = source.into_inner();
|
||||
reader.seek(SeekFrom::Start(0)).ok()?;
|
||||
let mut source = symphonia::SymphoniaDecoder::new(reader, None).ok()?;
|
||||
let sample = source.next();
|
||||
(DecoderImpl::Symphonia(source), sample)
|
||||
}
|
||||
none @ DecoderImpl::None(_) => (none, None),
|
||||
};
|
||||
self.0 = decoder;
|
||||
sample
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.0.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Source for LoopedDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.0.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.0.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.0.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.0.try_seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that can happen when creating a decoder.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DecoderError {
|
||||
/// The format of the data has not been recognized.
|
||||
UnrecognizedFormat,
|
||||
|
||||
/// An IO error occurred while reading, writing, or seeking the stream.
|
||||
#[cfg(feature = "symphonia")]
|
||||
IoError(String),
|
||||
|
||||
/// The stream contained malformed data and could not be decoded or demuxed.
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecodeError(&'static str),
|
||||
|
||||
/// A default or user-defined limit was reached while decoding or demuxing the stream. Limits
|
||||
/// are used to prevent denial-of-service attacks from malicious streams.
|
||||
#[cfg(feature = "symphonia")]
|
||||
LimitError(&'static str),
|
||||
|
||||
/// The demuxer or decoder needs to be reset before continuing.
|
||||
#[cfg(feature = "symphonia")]
|
||||
ResetRequired,
|
||||
|
||||
/// No streams were found by the decoder
|
||||
#[cfg(feature = "symphonia")]
|
||||
NoStreams,
|
||||
}
|
||||
|
||||
impl fmt::Display for DecoderError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let text = match self {
|
||||
DecoderError::UnrecognizedFormat => "Unrecognized format",
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderError::IoError(msg) => &msg[..],
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderError::DecodeError(msg) => msg,
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderError::LimitError(msg) => msg,
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderError::ResetRequired => "Reset required",
|
||||
#[cfg(feature = "symphonia")]
|
||||
DecoderError::NoStreams => "No streams",
|
||||
};
|
||||
write!(f, "{}", text)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for DecoderError {}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::SeekError;
|
||||
use crate::Source;
|
||||
|
||||
use minimp3::Decoder;
|
||||
use minimp3::Frame;
|
||||
use minimp3_fixed as minimp3;
|
||||
|
||||
pub struct Mp3Decoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
// decoder: SeekDecoder<R>,
|
||||
decoder: Decoder<R>,
|
||||
current_frame: Frame,
|
||||
current_frame_offset: usize,
|
||||
}
|
||||
|
||||
impl<R> Mp3Decoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
pub fn new(mut data: R) -> Result<Self, R> {
|
||||
if !is_mp3(data.by_ref()) {
|
||||
return Err(data);
|
||||
}
|
||||
// let mut decoder = SeekDecoder::new(data)
|
||||
let mut decoder = Decoder::new(data);
|
||||
// parameters are correct and minimp3 is used correctly
|
||||
// thus if we crash here one of these invariants is broken:
|
||||
// .expect("should be able to allocate memory, perform IO");
|
||||
// let current_frame = decoder.decode_frame()
|
||||
let current_frame = decoder.next_frame()
|
||||
// the reader makes enough data available therefore
|
||||
// if we crash here the invariant broken is:
|
||||
.expect("data should not corrupt");
|
||||
|
||||
Ok(Mp3Decoder {
|
||||
decoder,
|
||||
current_frame,
|
||||
current_frame_offset: 0,
|
||||
})
|
||||
}
|
||||
pub fn into_inner(self) -> R {
|
||||
self.decoder.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Source for Mp3Decoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
Some(self.current_frame.data.len())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.current_frame.channels as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.current_frame.sample_rate as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
// TODO waiting for PR in minimp3_fixed or minimp3
|
||||
|
||||
// let pos = (pos.as_secs_f32() * self.sample_rate() as f32) as u64;
|
||||
// // do not trigger a sample_rate, channels and frame len update
|
||||
// // as the seek only takes effect after the current frame is done
|
||||
// self.decoder.seek_samples(pos)?;
|
||||
// Ok(())
|
||||
|
||||
Err(SeekError::NotSupported {
|
||||
underlying_source: std::any::type_name::<Self>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Iterator for Mp3Decoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
type Item = i16;
|
||||
|
||||
fn next(&mut self) -> Option<i16> {
|
||||
if self.current_frame_offset == self.current_frame_len().unwrap() {
|
||||
if let Ok(frame) = self.decoder.next_frame() {
|
||||
// if let Ok(frame) = self.decoder.decode_frame() {
|
||||
self.current_frame = frame;
|
||||
self.current_frame_offset = 0;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let v = self.current_frame.data[self.current_frame_offset];
|
||||
self.current_frame_offset += 1;
|
||||
|
||||
Some(v)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the stream contains mp3 data, then resets it to where it was.
|
||||
fn is_mp3<R>(mut data: R) -> bool
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let stream_pos = data.seek(SeekFrom::Current(0)).unwrap();
|
||||
let mut decoder = Decoder::new(data.by_ref());
|
||||
let ok = decoder.next_frame().is_ok();
|
||||
data.seek(SeekFrom::Start(stream_pos)).unwrap();
|
||||
|
||||
ok
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use std::io::{Read, Result, Seek, SeekFrom};
|
||||
|
||||
use symphonia::core::io::MediaSource;
|
||||
|
||||
pub struct ReadSeekSource<T: Read + Seek + Send + Sync> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: Read + Seek + Send + Sync> ReadSeekSource<T> {
|
||||
/// Instantiates a new `ReadSeekSource<T>` by taking ownership and wrapping the provided
|
||||
/// `Read + Seek`er.
|
||||
pub fn new(inner: T) -> Self {
|
||||
ReadSeekSource { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read + Seek + Send + Sync> MediaSource for ReadSeekSource<T> {
|
||||
fn is_seekable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn byte_len(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read + Seek + Send + Sync> Read for ReadSeekSource<T> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||
self.inner.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read + Seek + Send + Sync> Seek for ReadSeekSource<T> {
|
||||
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
|
||||
self.inner.seek(pos)
|
||||
}
|
||||
}
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
use std::time::Duration;
|
||||
use symphonia::{
|
||||
core::{
|
||||
audio::{AudioBufferRef, SampleBuffer, SignalSpec},
|
||||
codecs::{Decoder, DecoderOptions, CODEC_TYPE_NULL},
|
||||
errors::Error,
|
||||
formats::{FormatOptions, FormatReader, SeekedTo},
|
||||
io::MediaSourceStream,
|
||||
meta::MetadataOptions,
|
||||
probe::Hint,
|
||||
units::{self, Time},
|
||||
},
|
||||
default::get_probe,
|
||||
};
|
||||
|
||||
use crate::{source, Source};
|
||||
|
||||
use super::DecoderError;
|
||||
|
||||
// Decoder errors are not considered fatal.
|
||||
// The correct action is to just get a new packet and try again.
|
||||
// But a decode error in more than 3 consecutive packets is fatal.
|
||||
const MAX_DECODE_RETRIES: usize = 3;
|
||||
|
||||
pub(crate) struct SymphoniaDecoder {
|
||||
decoder: Box<dyn Decoder>,
|
||||
current_frame_offset: usize,
|
||||
format: Box<dyn FormatReader>,
|
||||
total_duration: Option<Time>,
|
||||
buffer: SampleBuffer<i16>,
|
||||
spec: SignalSpec,
|
||||
}
|
||||
|
||||
impl SymphoniaDecoder {
|
||||
pub(crate) fn new(
|
||||
mss: MediaSourceStream,
|
||||
extension: Option<&str>,
|
||||
) -> Result<Self, DecoderError> {
|
||||
match SymphoniaDecoder::init(mss, extension) {
|
||||
Err(e) => match e {
|
||||
Error::IoError(e) => Err(DecoderError::IoError(e.to_string())),
|
||||
Error::DecodeError(e) => Err(DecoderError::DecodeError(e)),
|
||||
Error::SeekError(_) => {
|
||||
unreachable!("Seek errors should not occur during initialization")
|
||||
}
|
||||
Error::Unsupported(_) => Err(DecoderError::UnrecognizedFormat),
|
||||
Error::LimitError(e) => Err(DecoderError::LimitError(e)),
|
||||
Error::ResetRequired => Err(DecoderError::ResetRequired),
|
||||
},
|
||||
Ok(Some(decoder)) => Ok(decoder),
|
||||
Ok(None) => Err(DecoderError::NoStreams),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_inner(self) -> MediaSourceStream {
|
||||
self.format.into_inner()
|
||||
}
|
||||
|
||||
fn init(
|
||||
mss: MediaSourceStream,
|
||||
extension: Option<&str>,
|
||||
) -> symphonia::core::errors::Result<Option<SymphoniaDecoder>> {
|
||||
let mut hint = Hint::new();
|
||||
if let Some(ext) = extension {
|
||||
hint.with_extension(ext);
|
||||
}
|
||||
let format_opts: FormatOptions = FormatOptions {
|
||||
enable_gapless: true,
|
||||
..Default::default()
|
||||
};
|
||||
let metadata_opts: MetadataOptions = Default::default();
|
||||
let mut probed = get_probe().format(&hint, mss, &format_opts, &metadata_opts)?;
|
||||
|
||||
let stream = match probed.format.default_track() {
|
||||
Some(stream) => stream,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
// Select the first supported track
|
||||
let track_id = probed
|
||||
.format
|
||||
.tracks()
|
||||
.iter()
|
||||
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
|
||||
.ok_or(symphonia::core::errors::Error::Unsupported(
|
||||
"No track with supported codec",
|
||||
))?
|
||||
.id;
|
||||
|
||||
let track = probed
|
||||
.format
|
||||
.tracks()
|
||||
.iter()
|
||||
.find(|track| track.id == track_id)
|
||||
.unwrap();
|
||||
|
||||
let mut decoder = symphonia::default::get_codecs()
|
||||
.make(&track.codec_params, &DecoderOptions::default())?;
|
||||
let total_duration = stream
|
||||
.codec_params
|
||||
.time_base
|
||||
.zip(stream.codec_params.n_frames)
|
||||
.map(|(base, frames)| base.calc_time(frames));
|
||||
|
||||
let mut decode_errors: usize = 0;
|
||||
let decoded = loop {
|
||||
let current_frame = match probed.format.next_packet() {
|
||||
Ok(packet) => packet,
|
||||
Err(Error::IoError(_)) => break decoder.last_decoded(),
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
// If the packet does not belong to the selected track, skip over it
|
||||
if current_frame.track_id() != track_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
match decoder.decode(¤t_frame) {
|
||||
Ok(decoded) => break decoded,
|
||||
Err(e) => match e {
|
||||
Error::DecodeError(_) => {
|
||||
decode_errors += 1;
|
||||
if decode_errors > MAX_DECODE_RETRIES {
|
||||
return Err(e);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
};
|
||||
let spec = decoded.spec().to_owned();
|
||||
let buffer = SymphoniaDecoder::get_buffer(decoded, &spec);
|
||||
Ok(Some(SymphoniaDecoder {
|
||||
decoder,
|
||||
current_frame_offset: 0,
|
||||
format: probed.format,
|
||||
total_duration,
|
||||
buffer,
|
||||
spec,
|
||||
}))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_buffer(decoded: AudioBufferRef, spec: &SignalSpec) -> SampleBuffer<i16> {
|
||||
let duration = units::Duration::from(decoded.capacity() as u64);
|
||||
let mut buffer = SampleBuffer::<i16>::new(duration, *spec);
|
||||
buffer.copy_interleaved_ref(decoded);
|
||||
buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for SymphoniaDecoder {
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
Some(self.buffer.samples().len())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.spec.channels.count() as u16
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.spec.rate
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.total_duration
|
||||
.map(|Time { seconds, frac }| Duration::new(seconds, (1f64 / frac) as u32))
|
||||
}
|
||||
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), source::SeekError> {
|
||||
use symphonia::core::formats::{SeekMode, SeekTo};
|
||||
|
||||
let seek_beyond_end = self
|
||||
.total_duration()
|
||||
.is_some_and(|dur| dur.saturating_sub(pos).as_millis() < 1);
|
||||
|
||||
let time = if seek_beyond_end {
|
||||
let time = self.total_duration.expect("if guarantees this is Some");
|
||||
skip_back_a_tiny_bit(time) // some decoders can only seek to just before the end
|
||||
} else {
|
||||
pos.as_secs_f64().into()
|
||||
};
|
||||
|
||||
// make sure the next sample is for the right channel
|
||||
let to_skip = self.current_frame_offset % self.channels() as usize;
|
||||
|
||||
let seek_res = self
|
||||
.format
|
||||
.seek(
|
||||
SeekMode::Accurate,
|
||||
SeekTo::Time {
|
||||
time,
|
||||
track_id: None,
|
||||
},
|
||||
)
|
||||
.map_err(SeekError::BaseSeek)?;
|
||||
|
||||
self.refine_position(seek_res)?;
|
||||
self.current_frame_offset += to_skip;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SeekError {
|
||||
#[error("Could not get next packet while refining seek position: {0:?}")]
|
||||
Refining(symphonia::core::errors::Error),
|
||||
#[error("Format reader failed to seek: {0:?}")]
|
||||
BaseSeek(symphonia::core::errors::Error),
|
||||
#[error("Decoding failed retrying on the next packet failed: {0:?}")]
|
||||
Retrying(symphonia::core::errors::Error),
|
||||
#[error("Decoding failed on multiple consecutive packets: {0:?}")]
|
||||
Decoding(symphonia::core::errors::Error),
|
||||
}
|
||||
|
||||
impl SymphoniaDecoder {
|
||||
/// note frame offset must be set after
|
||||
fn refine_position(&mut self, seek_res: SeekedTo) -> Result<(), source::SeekError> {
|
||||
let mut samples_to_pass = seek_res.required_ts - seek_res.actual_ts;
|
||||
let packet = loop {
|
||||
let candidate = self.format.next_packet().map_err(SeekError::Refining)?;
|
||||
if candidate.dur() > samples_to_pass {
|
||||
break candidate;
|
||||
} else {
|
||||
samples_to_pass -= candidate.dur();
|
||||
}
|
||||
};
|
||||
|
||||
let mut decoded = self.decoder.decode(&packet);
|
||||
for _ in 0..MAX_DECODE_RETRIES {
|
||||
if decoded.is_err() {
|
||||
let packet = self.format.next_packet().map_err(SeekError::Retrying)?;
|
||||
decoded = self.decoder.decode(&packet);
|
||||
}
|
||||
}
|
||||
|
||||
let decoded = decoded.map_err(SeekError::Decoding)?;
|
||||
decoded.spec().clone_into(&mut self.spec);
|
||||
self.buffer = SymphoniaDecoder::get_buffer(decoded, &self.spec);
|
||||
self.current_frame_offset = samples_to_pass as usize * self.channels() as usize;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_back_a_tiny_bit(
|
||||
Time {
|
||||
mut seconds,
|
||||
mut frac,
|
||||
}: Time,
|
||||
) -> Time {
|
||||
frac -= 0.0001;
|
||||
if frac < 0.0 {
|
||||
seconds = seconds.saturating_sub(1);
|
||||
frac = 1.0 - frac;
|
||||
}
|
||||
Time { seconds, frac }
|
||||
}
|
||||
|
||||
impl Iterator for SymphoniaDecoder {
|
||||
type Item = i16;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<i16> {
|
||||
if self.current_frame_offset >= self.buffer.len() {
|
||||
let packet = self.format.next_packet().ok()?;
|
||||
let mut decoded = self.decoder.decode(&packet);
|
||||
for _ in 0..MAX_DECODE_RETRIES {
|
||||
if decoded.is_err() {
|
||||
let packet = self.format.next_packet().ok()?;
|
||||
decoded = self.decoder.decode(&packet);
|
||||
}
|
||||
}
|
||||
let decoded = decoded.ok()?;
|
||||
decoded.spec().clone_into(&mut self.spec);
|
||||
self.buffer = SymphoniaDecoder::get_buffer(decoded, &self.spec);
|
||||
self.current_frame_offset = 0;
|
||||
}
|
||||
|
||||
let sample = *self.buffer.samples().get(self.current_frame_offset)?;
|
||||
self.current_frame_offset += 1;
|
||||
|
||||
Some(sample)
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::SeekError;
|
||||
use crate::Source;
|
||||
|
||||
use lewton::inside_ogg::OggStreamReader;
|
||||
|
||||
/// Decoder for an OGG file that contains Vorbis sound format.
|
||||
pub struct VorbisDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
stream_reader: OggStreamReader<R>,
|
||||
current_data: Vec<i16>,
|
||||
next: usize,
|
||||
}
|
||||
|
||||
impl<R> VorbisDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
/// Attempts to decode the data as ogg/vorbis.
|
||||
pub fn new(mut data: R) -> Result<VorbisDecoder<R>, R> {
|
||||
if !is_vorbis(data.by_ref()) {
|
||||
return Err(data);
|
||||
}
|
||||
|
||||
let stream_reader = OggStreamReader::new(data).unwrap();
|
||||
Ok(Self::from_stream_reader(stream_reader))
|
||||
}
|
||||
pub fn from_stream_reader(mut stream_reader: OggStreamReader<R>) -> Self {
|
||||
let mut data = match stream_reader.read_dec_packet_itl() {
|
||||
Ok(Some(d)) => d,
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
// The first packet is always empty, therefore
|
||||
// we need to read the second frame to get some data
|
||||
if let Ok(Some(mut d)) = stream_reader.read_dec_packet_itl() {
|
||||
data.append(&mut d);
|
||||
}
|
||||
|
||||
VorbisDecoder {
|
||||
stream_reader,
|
||||
current_data: data,
|
||||
next: 0,
|
||||
}
|
||||
}
|
||||
pub fn into_inner(self) -> OggStreamReader<R> {
|
||||
self.stream_reader
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Source for VorbisDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
Some(self.current_data.len())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.stream_reader.ident_hdr.audio_channels as u16
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.stream_reader.ident_hdr.audio_sample_rate
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None
|
||||
}
|
||||
|
||||
/// seek is broken, https://github.com/RustAudio/lewton/issues/73.
|
||||
// We could work around it by:
|
||||
// - using unsafe to create an instance of Self
|
||||
// - use mem::swap to turn the &mut self into a mut self
|
||||
// - take out the underlying Read+Seek
|
||||
// - make a new self and seek
|
||||
//
|
||||
// If this issue is fixed use the implementation in
|
||||
// commit: 3bafe32388b4eb7a48c6701e6c65044dc8c555e6
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
|
||||
Err(SeekError::NotSupported {
|
||||
underlying_source: std::any::type_name::<Self>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Iterator for VorbisDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
type Item = i16;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<i16> {
|
||||
if let Some(sample) = self.current_data.get(self.next).copied() {
|
||||
self.next += 1;
|
||||
if self.current_data.is_empty() {
|
||||
if let Ok(Some(data)) = self.stream_reader.read_dec_packet_itl() {
|
||||
self.current_data = data;
|
||||
self.next = 0;
|
||||
}
|
||||
}
|
||||
Some(sample)
|
||||
} else {
|
||||
if let Ok(Some(data)) = self.stream_reader.read_dec_packet_itl() {
|
||||
self.current_data = data;
|
||||
self.next = 0;
|
||||
}
|
||||
let sample = self.current_data.get(self.next).copied();
|
||||
self.next += 1;
|
||||
sample
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(self.current_data.len(), None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the stream contains Vorbis data, then resets it to where it was.
|
||||
fn is_vorbis<R>(mut data: R) -> bool
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let stream_pos = data.stream_position().unwrap();
|
||||
|
||||
if OggStreamReader::new(data.by_ref()).is_err() {
|
||||
data.seek(SeekFrom::Start(stream_pos)).unwrap();
|
||||
return false;
|
||||
}
|
||||
|
||||
data.seek(SeekFrom::Start(stream_pos)).unwrap();
|
||||
true
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::SeekError;
|
||||
use crate::Source;
|
||||
|
||||
use hound::{SampleFormat, WavReader};
|
||||
|
||||
/// Decoder for the WAV format.
|
||||
pub struct WavDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
reader: SamplesIterator<R>,
|
||||
total_duration: Duration,
|
||||
sample_rate: u32,
|
||||
channels: u16,
|
||||
}
|
||||
|
||||
impl<R> WavDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
/// Attempts to decode the data as WAV.
|
||||
pub fn new(mut data: R) -> Result<WavDecoder<R>, R> {
|
||||
if !is_wave(data.by_ref()) {
|
||||
return Err(data);
|
||||
}
|
||||
|
||||
let reader = WavReader::new(data).unwrap();
|
||||
let spec = reader.spec();
|
||||
let len = reader.len() as u64;
|
||||
let reader = SamplesIterator {
|
||||
reader,
|
||||
samples_read: 0,
|
||||
};
|
||||
|
||||
let sample_rate = spec.sample_rate;
|
||||
let channels = spec.channels;
|
||||
let total_duration =
|
||||
Duration::from_micros((1_000_000 * len) / (sample_rate as u64 * channels as u64));
|
||||
|
||||
Ok(WavDecoder {
|
||||
reader,
|
||||
total_duration,
|
||||
sample_rate,
|
||||
channels,
|
||||
})
|
||||
}
|
||||
pub fn into_inner(self) -> R {
|
||||
self.reader.reader.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
struct SamplesIterator<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
reader: WavReader<R>,
|
||||
samples_read: u32, // wav header is u32 so this suffices
|
||||
}
|
||||
|
||||
impl<R> Iterator for SamplesIterator<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
type Item = i16;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<i16> {
|
||||
let spec = self.reader.spec();
|
||||
match (spec.sample_format, spec.bits_per_sample) {
|
||||
(SampleFormat::Float, 32) => self.reader.samples().next().map(|value| {
|
||||
self.samples_read += 1;
|
||||
f32_to_i16(value.unwrap_or(0.0))
|
||||
}),
|
||||
(SampleFormat::Int, 8) => self.reader.samples().next().map(|value| {
|
||||
self.samples_read += 1;
|
||||
i8_to_i16(value.unwrap_or(0))
|
||||
}),
|
||||
(SampleFormat::Int, 16) => self.reader.samples().next().map(|value| {
|
||||
self.samples_read += 1;
|
||||
value.unwrap_or(0)
|
||||
}),
|
||||
(SampleFormat::Int, 24) => self.reader.samples().next().map(|value| {
|
||||
self.samples_read += 1;
|
||||
i24_to_i16(value.unwrap_or(0))
|
||||
}),
|
||||
(SampleFormat::Int, 32) => self.reader.samples().next().map(|value| {
|
||||
self.samples_read += 1;
|
||||
i32_to_i16(value.unwrap_or(0))
|
||||
}),
|
||||
(sample_format, bits_per_sample) => panic!(
|
||||
"Unimplemented wav spec: {:?}, {}",
|
||||
sample_format, bits_per_sample
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let len = (self.reader.len() - self.samples_read) as usize;
|
||||
(len, Some(len))
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> ExactSizeIterator for SamplesIterator<R> where R: Read + Seek {}
|
||||
|
||||
impl<R> Source for WavDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.channels
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
Some(self.total_duration)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
let file_len = self.reader.reader.duration();
|
||||
|
||||
let new_pos = pos.as_secs_f32() * self.sample_rate() as f32;
|
||||
let new_pos = new_pos as u32;
|
||||
let new_pos = new_pos.min(file_len); // saturate pos at the end of the source
|
||||
|
||||
// make sure the next sample is for the right channel
|
||||
let to_skip = self.reader.samples_read % self.channels() as u32;
|
||||
|
||||
self.reader
|
||||
.reader
|
||||
.seek(new_pos)
|
||||
.map_err(SeekError::HoundDecoder)?;
|
||||
self.reader.samples_read = new_pos * self.channels() as u32;
|
||||
|
||||
for _ in 0..to_skip {
|
||||
self.next();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Iterator for WavDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
type Item = i16;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<i16> {
|
||||
self.reader.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.reader.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> ExactSizeIterator for WavDecoder<R> where R: Read + Seek {}
|
||||
|
||||
/// Returns true if the stream contains WAV data, then resets it to where it was.
|
||||
fn is_wave<R>(mut data: R) -> bool
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let stream_pos = data.stream_position().unwrap();
|
||||
|
||||
if WavReader::new(data.by_ref()).is_err() {
|
||||
data.seek(SeekFrom::Start(stream_pos)).unwrap();
|
||||
return false;
|
||||
}
|
||||
|
||||
data.seek(SeekFrom::Start(stream_pos)).unwrap();
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns a 32 bit WAV float as an i16. WAV floats are typically in the range of
|
||||
/// [-1.0, 1.0] while i16s are in the range [-32768, 32767]. Note that this
|
||||
/// function definitely causes precision loss but hopefully this isn't too
|
||||
/// audiable when actually playing?
|
||||
fn f32_to_i16(f: f32) -> i16 {
|
||||
// prefer to clip the input rather than be excessively loud.
|
||||
(f.clamp(-1.0, 1.0) * i16::MAX as f32) as i16
|
||||
}
|
||||
|
||||
/// Returns an 8-bit WAV int as an i16. This scales the sample value by a factor
|
||||
/// of 256.
|
||||
fn i8_to_i16(i: i8) -> i16 {
|
||||
i as i16 * 256
|
||||
}
|
||||
|
||||
/// Returns a 24 bit WAV int as an i16. Note that this is a 24 bit integer, not a
|
||||
/// 32 bit one. 24 bit ints are in the range [−8,388,608, 8,388,607] while i16s
|
||||
/// are in the range [-32768, 32767]. Note that this function definitely causes
|
||||
/// precision loss but hopefully this isn't too audiable when actually playing?
|
||||
fn i24_to_i16(i: i32) -> i16 {
|
||||
(i >> 8) as i16
|
||||
}
|
||||
|
||||
/// Returns a 32 bit WAV int as an i16. 32 bit ints are in the range
|
||||
/// [-2,147,483,648, 2,147,483,647] while i16s are in the range [-32768, 32767].
|
||||
/// Note that this function definitely causes precision loss but hopefully this
|
||||
/// isn't too audiable when actually playing?
|
||||
fn i32_to_i16(i: i32) -> i16 {
|
||||
(i >> 16) as i16
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
//! Mixer that plays multiple sounds at the same time.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::{SeekError, Source, UniformSourceIterator};
|
||||
use crate::Sample;
|
||||
|
||||
/// Builds a new mixer.
|
||||
///
|
||||
/// You can choose the characteristics of the output thanks to this constructor. All the sounds
|
||||
/// added to the mixer will be converted to these values.
|
||||
///
|
||||
/// After creating a mixer, you can add new sounds with the controller.
|
||||
pub fn mixer<S>(
|
||||
channels: u16,
|
||||
sample_rate: u32,
|
||||
) -> (Arc<DynamicMixerController<S>>, DynamicMixer<S>)
|
||||
where
|
||||
S: Sample + Send + 'static,
|
||||
{
|
||||
let input = Arc::new(DynamicMixerController {
|
||||
has_pending: AtomicBool::new(false),
|
||||
pending_sources: Mutex::new(Vec::new()),
|
||||
channels,
|
||||
sample_rate,
|
||||
});
|
||||
|
||||
let output = DynamicMixer {
|
||||
current_sources: Vec::with_capacity(16),
|
||||
input: input.clone(),
|
||||
sample_count: 0,
|
||||
still_pending: vec![],
|
||||
still_current: vec![],
|
||||
};
|
||||
|
||||
(input, output)
|
||||
}
|
||||
|
||||
/// The input of the mixer.
|
||||
pub struct DynamicMixerController<S> {
|
||||
has_pending: AtomicBool,
|
||||
pending_sources: Mutex<Vec<Box<dyn Source<Item = S> + Send>>>,
|
||||
channels: u16,
|
||||
sample_rate: u32,
|
||||
}
|
||||
|
||||
impl<S> DynamicMixerController<S>
|
||||
where
|
||||
S: Sample + Send + 'static,
|
||||
{
|
||||
/// Adds a new source to mix to the existing ones.
|
||||
#[inline]
|
||||
pub fn add<T>(&self, source: T)
|
||||
where
|
||||
T: Source<Item = S> + Send + 'static,
|
||||
{
|
||||
let uniform_source = UniformSourceIterator::new(source, self.channels, self.sample_rate);
|
||||
self.pending_sources
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(Box::new(uniform_source) as Box<_>);
|
||||
self.has_pending.store(true, Ordering::SeqCst); // TODO: can we relax this ordering?
|
||||
}
|
||||
}
|
||||
|
||||
/// The output of the mixer. Implements `Source`.
|
||||
pub struct DynamicMixer<S> {
|
||||
// The current iterator that produces samples.
|
||||
current_sources: Vec<Box<dyn Source<Item = S> + Send>>,
|
||||
|
||||
// The pending sounds.
|
||||
input: Arc<DynamicMixerController<S>>,
|
||||
|
||||
// The number of samples produced so far.
|
||||
sample_count: usize,
|
||||
|
||||
// A temporary vec used in start_pending_sources.
|
||||
still_pending: Vec<Box<dyn Source<Item = S> + Send>>,
|
||||
|
||||
// A temporary vec used in sum_current_sources.
|
||||
still_current: Vec<Box<dyn Source<Item = S> + Send>>,
|
||||
}
|
||||
|
||||
impl<S> Source for DynamicMixer<S>
|
||||
where
|
||||
S: Sample + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
|
||||
Err(SeekError::NotSupported {
|
||||
underlying_source: std::any::type_name::<Self>(),
|
||||
})
|
||||
|
||||
// uncomment when #510 is implemented (query position of playback)
|
||||
|
||||
// let mut org_positions = Vec::with_capacity(self.current_sources.len());
|
||||
// let mut encounterd_err = None;
|
||||
//
|
||||
// for source in &mut self.current_sources {
|
||||
// let pos = /* source.playback_pos() */ todo!();
|
||||
// if let Err(e) = source.try_seek(pos) {
|
||||
// encounterd_err = Some(e);
|
||||
// break;
|
||||
// } else {
|
||||
// // store pos in case we need to roll back
|
||||
// org_positions.push(pos);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if let Some(e) = encounterd_err {
|
||||
// // rollback seeks that happend before err
|
||||
// for (pos, source) in org_positions
|
||||
// .into_iter()
|
||||
// .zip(self.current_sources.iter_mut())
|
||||
// {
|
||||
// source.try_seek(pos)?;
|
||||
// }
|
||||
// Err(e)
|
||||
// } else {
|
||||
// Ok(())
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Iterator for DynamicMixer<S>
|
||||
where
|
||||
S: Sample + Send + 'static,
|
||||
{
|
||||
type Item = S;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<S> {
|
||||
if self.input.has_pending.load(Ordering::SeqCst) {
|
||||
self.start_pending_sources();
|
||||
}
|
||||
|
||||
self.sample_count += 1;
|
||||
|
||||
let sum = self.sum_current_sources();
|
||||
|
||||
if self.current_sources.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(sum)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(0, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DynamicMixer<S>
|
||||
where
|
||||
S: Sample + Send + 'static,
|
||||
{
|
||||
// Samples from the #next() function are interlaced for each of the channels.
|
||||
// We need to ensure we start playing sources so that their samples are
|
||||
// in-step with the modulo of the samples produced so far. Otherwise, the
|
||||
// sound will play on the wrong channels, e.g. left / right will be reversed.
|
||||
fn start_pending_sources(&mut self) {
|
||||
let mut pending = self.input.pending_sources.lock().unwrap(); // TODO: relax ordering?
|
||||
|
||||
for source in pending.drain(..) {
|
||||
let in_step = self.sample_count % source.channels() as usize == 0;
|
||||
|
||||
if in_step {
|
||||
self.current_sources.push(source);
|
||||
} else {
|
||||
self.still_pending.push(source);
|
||||
}
|
||||
}
|
||||
std::mem::swap(&mut self.still_pending, &mut pending);
|
||||
|
||||
let has_pending = !pending.is_empty();
|
||||
self.input.has_pending.store(has_pending, Ordering::SeqCst); // TODO: relax ordering?
|
||||
}
|
||||
|
||||
fn sum_current_sources(&mut self) -> S {
|
||||
let mut sum = S::zero_value();
|
||||
|
||||
for mut source in self.current_sources.drain(..) {
|
||||
if let Some(value) = source.next() {
|
||||
sum = sum.saturating_add(value);
|
||||
self.still_current.push(source);
|
||||
}
|
||||
}
|
||||
std::mem::swap(&mut self.still_current, &mut self.current_sources);
|
||||
|
||||
sum
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::buffer::SamplesBuffer;
|
||||
use crate::dynamic_mixer;
|
||||
use crate::source::Source;
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let (tx, mut rx) = dynamic_mixer::mixer(1, 48000);
|
||||
|
||||
tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
|
||||
tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 5, 5]));
|
||||
|
||||
assert_eq!(rx.channels(), 1);
|
||||
assert_eq!(rx.sample_rate(), 48000);
|
||||
assert_eq!(rx.next(), Some(15));
|
||||
assert_eq!(rx.next(), Some(-5));
|
||||
assert_eq!(rx.next(), Some(15));
|
||||
assert_eq!(rx.next(), Some(-5));
|
||||
assert_eq!(rx.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn channels_conv() {
|
||||
let (tx, mut rx) = dynamic_mixer::mixer(2, 48000);
|
||||
|
||||
tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
|
||||
tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 5, 5]));
|
||||
|
||||
assert_eq!(rx.channels(), 2);
|
||||
assert_eq!(rx.sample_rate(), 48000);
|
||||
assert_eq!(rx.next(), Some(15));
|
||||
assert_eq!(rx.next(), Some(15));
|
||||
assert_eq!(rx.next(), Some(-5));
|
||||
assert_eq!(rx.next(), Some(-5));
|
||||
assert_eq!(rx.next(), Some(15));
|
||||
assert_eq!(rx.next(), Some(15));
|
||||
assert_eq!(rx.next(), Some(-5));
|
||||
assert_eq!(rx.next(), Some(-5));
|
||||
assert_eq!(rx.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rate_conv() {
|
||||
let (tx, mut rx) = dynamic_mixer::mixer(1, 96000);
|
||||
|
||||
tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
|
||||
tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 5, 5]));
|
||||
|
||||
assert_eq!(rx.channels(), 1);
|
||||
assert_eq!(rx.sample_rate(), 96000);
|
||||
assert_eq!(rx.next(), Some(15));
|
||||
assert_eq!(rx.next(), Some(5));
|
||||
assert_eq!(rx.next(), Some(-5));
|
||||
assert_eq!(rx.next(), Some(5));
|
||||
assert_eq!(rx.next(), Some(15));
|
||||
assert_eq!(rx.next(), Some(5));
|
||||
assert_eq!(rx.next(), Some(-5));
|
||||
assert_eq!(rx.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_afterwards() {
|
||||
let (tx, mut rx) = dynamic_mixer::mixer(1, 48000);
|
||||
|
||||
tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
|
||||
|
||||
assert_eq!(rx.next(), Some(10));
|
||||
assert_eq!(rx.next(), Some(-10));
|
||||
|
||||
tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 6, 6, 7, 7, 7]));
|
||||
|
||||
assert_eq!(rx.next(), Some(15));
|
||||
assert_eq!(rx.next(), Some(-5));
|
||||
|
||||
assert_eq!(rx.next(), Some(6));
|
||||
assert_eq!(rx.next(), Some(6));
|
||||
|
||||
tx.add(SamplesBuffer::new(1, 48000, vec![2i16]));
|
||||
|
||||
assert_eq!(rx.next(), Some(9));
|
||||
assert_eq!(rx.next(), Some(7));
|
||||
assert_eq!(rx.next(), Some(7));
|
||||
|
||||
assert_eq!(rx.next(), None);
|
||||
}
|
||||
}
|
||||
Vendored
+137
@@ -0,0 +1,137 @@
|
||||
//! Audio playback library.
|
||||
//!
|
||||
//! The main concept of this library is the [`Source`] trait, which
|
||||
//! represents a sound (streaming or not). In order to play a sound, there are three steps:
|
||||
//!
|
||||
//! - Create an object that represents the streaming sound. It can be a sine wave, a buffer, a
|
||||
//! [`decoder`], etc. or even your own type that implements the [`Source`] trait.
|
||||
//! - Get an output stream handle to a physical device. For example, get a stream to the system's
|
||||
//! default sound device with [`OutputStream::try_default()`]
|
||||
//! - Call [`.play_raw(source)`](OutputStreamHandle::play_raw) on the output stream handle.
|
||||
//!
|
||||
//! The [`play_raw`](OutputStreamHandle::play_raw) function expects the source to produce [`f32`]s,
|
||||
//! which may not be the case. If you get a compilation error, try calling
|
||||
//! [`.convert_samples()`](Source::convert_samples) on the source to fix it.
|
||||
//!
|
||||
//! For example, here is how you would play an audio file:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use std::fs::File;
|
||||
//! use std::io::BufReader;
|
||||
//! use rodio::{Decoder, OutputStream, source::Source};
|
||||
//!
|
||||
//! // Get an output stream handle to the default physical sound device
|
||||
//! let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
//! // Load a sound from a file, using a path relative to Cargo.toml
|
||||
//! let file = BufReader::new(File::open("examples/music.ogg").unwrap());
|
||||
//! // Decode that sound file into a source
|
||||
//! let source = Decoder::new(file).unwrap();
|
||||
//! // Play the sound directly on the device
|
||||
//! stream_handle.play_raw(source.convert_samples());
|
||||
//!
|
||||
//! // The sound plays in a separate audio thread,
|
||||
//! // so we need to keep the main thread alive while it's playing.
|
||||
//! std::thread::sleep(std::time::Duration::from_secs(5));
|
||||
//! ```
|
||||
//!
|
||||
//! ## Sink
|
||||
//!
|
||||
//! In order to make it easier to control the playback, the rodio library also provides a type
|
||||
//! named [`Sink`] which represents an audio track.
|
||||
//!
|
||||
//! Instead of playing the sound with [`play_raw`](OutputStreamHandle::play_raw), you can add it to
|
||||
//! a [`Sink`] instead.
|
||||
//!
|
||||
//! - Get a [`Sink`] to the output stream, and [`.append()`](Sink::append) your sound to it.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use std::fs::File;
|
||||
//! use std::io::BufReader;
|
||||
//! use std::time::Duration;
|
||||
//! use rodio::{Decoder, OutputStream, Sink};
|
||||
//! use rodio::source::{SineWave, Source};
|
||||
//!
|
||||
//! // _stream must live as long as the sink
|
||||
//! let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
//! let sink = Sink::try_new(&stream_handle).unwrap();
|
||||
//!
|
||||
//! // Add a dummy source of the sake of the example.
|
||||
//! let source = SineWave::new(440.0).take_duration(Duration::from_secs_f32(0.25)).amplify(0.20);
|
||||
//! sink.append(source);
|
||||
//!
|
||||
//! // The sound plays in a separate thread. This call will block the current thread until the sink
|
||||
//! // has finished playing all its queued sounds.
|
||||
//! sink.sleep_until_end();
|
||||
//! ```
|
||||
//!
|
||||
//! The [`append`](Sink::append) method will add the sound at the end of the
|
||||
//! sink. It will be played when all the previous sounds have been played. If you want multiple
|
||||
//! sounds to play simultaneously, you should create multiple [`Sink`]s.
|
||||
//!
|
||||
//! The [`Sink`] type also provides utilities such as playing/pausing or controlling the volume.
|
||||
//!
|
||||
//! **Please note that the [`Sink`] requires the [`OutputStream`], make sure that the outputstream is not dropped before the sink.**
|
||||
//!
|
||||
//! ## Filters
|
||||
//!
|
||||
//! The [`Source`] trait provides various filters, similar to the standard [`Iterator`] trait.
|
||||
//!
|
||||
//! Example:
|
||||
//!
|
||||
//! ```
|
||||
//! use rodio::Source;
|
||||
//! use std::time::Duration;
|
||||
//!
|
||||
//! // Repeats the first five seconds of the sound forever.
|
||||
//! # let source = rodio::source::SineWave::new(440.0);
|
||||
//! let source = source.take_duration(Duration::from_secs(5)).repeat_infinite();
|
||||
//! ```
|
||||
//!
|
||||
//! ## Alternative Decoder Backends
|
||||
//!
|
||||
//! [Symphonia](https://github.com/pdeljanov/Symphonia) is an alternative decoder library that can be used in place
|
||||
//! of many of the default backends.
|
||||
//! Currently, the main benefit is that Symphonia is the only backend that supports M4A and AAC,
|
||||
//! but it may be used to implement additional optional functionality in the future.
|
||||
//!
|
||||
//! To use, enable either the `symphonia-all` feature to enable all Symphonia codecs
|
||||
//! or enable specific codecs using one of the `symphonia-{codec name}` features.
|
||||
//! If you enable one or more of the Symphonia codecs, you may want to set `default-features = false` in order
|
||||
//! to avoid adding extra crates to your binary.
|
||||
//! See the [available feature flags](https://docs.rs/crate/rodio/latest/features) for all options.
|
||||
//!
|
||||
//! ## How it works under the hood
|
||||
//!
|
||||
//! Rodio spawns a background thread that is dedicated to reading from the sources and sending
|
||||
//! the output to the device. Whenever you give up ownership of a [`Source`] in order to play it,
|
||||
//! it is sent to this background thread where it will be read by rodio.
|
||||
//!
|
||||
//! All the sounds are mixed together by rodio before being sent to the operating system or the
|
||||
//! hardware. Therefore there is no restriction on the number of sounds that play simultaneously or
|
||||
//! the number of sinks that can be created (except for the fact that creating too many will slow
|
||||
//! down your program).
|
||||
//!
|
||||
#![cfg_attr(test, deny(missing_docs))]
|
||||
pub use cpal::{
|
||||
self, traits::DeviceTrait, Device, Devices, DevicesError, InputDevices, OutputDevices,
|
||||
SupportedStreamConfig,
|
||||
};
|
||||
|
||||
mod conversions;
|
||||
mod sink;
|
||||
mod spatial_sink;
|
||||
mod stream;
|
||||
|
||||
pub mod buffer;
|
||||
pub mod decoder;
|
||||
pub mod dynamic_mixer;
|
||||
pub mod queue;
|
||||
pub mod source;
|
||||
pub mod static_buffer;
|
||||
|
||||
pub use crate::conversions::Sample;
|
||||
pub use crate::decoder::Decoder;
|
||||
pub use crate::sink::Sink;
|
||||
pub use crate::source::Source;
|
||||
pub use crate::spatial_sink::SpatialSink;
|
||||
pub use crate::stream::{OutputStream, OutputStreamHandle, PlayError, StreamError};
|
||||
Vendored
+319
@@ -0,0 +1,319 @@
|
||||
//! Queue that plays sounds one after the other.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::{Empty, SeekError, Source, Zero};
|
||||
use crate::Sample;
|
||||
|
||||
#[cfg(feature = "crossbeam-channel")]
|
||||
use crossbeam_channel::{unbounded as channel, Receiver, Sender};
|
||||
#[cfg(not(feature = "crossbeam-channel"))]
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
|
||||
/// Builds a new queue. It consists of an input and an output.
|
||||
///
|
||||
/// The input can be used to add sounds to the end of the queue, while the output implements
|
||||
/// `Source` and plays the sounds.
|
||||
///
|
||||
/// The parameter indicates how the queue should behave if the queue becomes empty:
|
||||
///
|
||||
/// - If you pass `true`, then the queue is infinite and will play a silence instead until you add
|
||||
/// a new sound.
|
||||
/// - If you pass `false`, then the queue will report that it has finished playing.
|
||||
///
|
||||
pub fn queue<S>(keep_alive_if_empty: bool) -> (Arc<SourcesQueueInput<S>>, SourcesQueueOutput<S>)
|
||||
where
|
||||
S: Sample + Send + 'static,
|
||||
{
|
||||
let input = Arc::new(SourcesQueueInput {
|
||||
next_sounds: Mutex::new(Vec::new()),
|
||||
keep_alive_if_empty: AtomicBool::new(keep_alive_if_empty),
|
||||
});
|
||||
|
||||
let output = SourcesQueueOutput {
|
||||
current: Box::new(Empty::<S>::new()) as Box<_>,
|
||||
signal_after_end: None,
|
||||
input: input.clone(),
|
||||
};
|
||||
|
||||
(input, output)
|
||||
}
|
||||
|
||||
// TODO: consider reimplementing this with `from_factory`
|
||||
|
||||
type Sound<S> = Box<dyn Source<Item = S> + Send>;
|
||||
type SignalDone = Option<Sender<()>>;
|
||||
|
||||
/// The input of the queue.
|
||||
pub struct SourcesQueueInput<S> {
|
||||
next_sounds: Mutex<Vec<(Sound<S>, SignalDone)>>,
|
||||
|
||||
// See constructor.
|
||||
keep_alive_if_empty: AtomicBool,
|
||||
}
|
||||
|
||||
impl<S> SourcesQueueInput<S>
|
||||
where
|
||||
S: Sample + Send + 'static,
|
||||
{
|
||||
/// Adds a new source to the end of the queue.
|
||||
#[inline]
|
||||
pub fn append<T>(&self, source: T)
|
||||
where
|
||||
T: Source<Item = S> + Send + 'static,
|
||||
{
|
||||
self.next_sounds
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push((Box::new(source) as Box<_>, None));
|
||||
}
|
||||
|
||||
/// Adds a new source to the end of the queue.
|
||||
///
|
||||
/// The `Receiver` will be signalled when the sound has finished playing.
|
||||
///
|
||||
/// Enable the feature flag `crossbeam-channel` in rodio to use a `crossbeam_channel::Receiver` instead.
|
||||
#[inline]
|
||||
pub fn append_with_signal<T>(&self, source: T) -> Receiver<()>
|
||||
where
|
||||
T: Source<Item = S> + Send + 'static,
|
||||
{
|
||||
let (tx, rx) = channel();
|
||||
self.next_sounds
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push((Box::new(source) as Box<_>, Some(tx)));
|
||||
rx
|
||||
}
|
||||
|
||||
/// Sets whether the queue stays alive if there's no more sound to play.
|
||||
///
|
||||
/// See also the constructor.
|
||||
pub fn set_keep_alive_if_empty(&self, keep_alive_if_empty: bool) {
|
||||
self.keep_alive_if_empty
|
||||
.store(keep_alive_if_empty, Ordering::Release);
|
||||
}
|
||||
|
||||
/// Removes all the sounds from the queue. Returns the number of sounds cleared.
|
||||
pub fn clear(&self) -> usize {
|
||||
let mut sounds = self.next_sounds.lock().unwrap();
|
||||
let len = sounds.len();
|
||||
sounds.clear();
|
||||
len
|
||||
}
|
||||
}
|
||||
/// The output of the queue. Implements `Source`.
|
||||
pub struct SourcesQueueOutput<S> {
|
||||
// The current iterator that produces samples.
|
||||
current: Box<dyn Source<Item = S> + Send>,
|
||||
|
||||
// Signal this sender before picking from `next`.
|
||||
signal_after_end: Option<Sender<()>>,
|
||||
|
||||
// The next sounds.
|
||||
input: Arc<SourcesQueueInput<S>>,
|
||||
}
|
||||
|
||||
const THRESHOLD: usize = 512;
|
||||
impl<S> Source for SourcesQueueOutput<S>
|
||||
where
|
||||
S: Sample + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
// This function is non-trivial because the boundary between two sounds in the queue should
|
||||
// be a frame boundary as well.
|
||||
//
|
||||
// The current sound is free to return `None` for `current_frame_len()`, in which case
|
||||
// we *should* return the number of samples remaining the current sound.
|
||||
// This can be estimated with `size_hint()`.
|
||||
//
|
||||
// If the `size_hint` is `None` as well, we are in the worst case scenario. To handle this
|
||||
// situation we force a frame to have a maximum number of samples indicate by this
|
||||
// constant.
|
||||
|
||||
// Try the current `current_frame_len`.
|
||||
if let Some(val) = self.current.current_frame_len() {
|
||||
if val != 0 {
|
||||
return Some(val);
|
||||
} else if self.input.keep_alive_if_empty.load(Ordering::Acquire)
|
||||
&& self.input.next_sounds.lock().unwrap().is_empty()
|
||||
{
|
||||
// The next source will be a filler silence which will have the length of `THRESHOLD`
|
||||
return Some(THRESHOLD);
|
||||
}
|
||||
}
|
||||
|
||||
// Try the size hint.
|
||||
let (lower_bound, _) = self.current.size_hint();
|
||||
// The iterator default implementation just returns 0.
|
||||
// That's a problematic value, so skip it.
|
||||
if lower_bound > 0 {
|
||||
return Some(lower_bound);
|
||||
}
|
||||
|
||||
// Otherwise we use the constant value.
|
||||
Some(THRESHOLD)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.current.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.current.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Only seeks within the current source.
|
||||
// We can not go back to previous sources. We could implement seek such
|
||||
// that it advances the queue if the position is beyond the current song.
|
||||
//
|
||||
// We would then however need to enable seeking backwards across sources too.
|
||||
// That no longer seems in line with the queue behaviour.
|
||||
//
|
||||
// A final pain point is that we would need the total duration for the
|
||||
// next few songs.
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.current.try_seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Iterator for SourcesQueueOutput<S>
|
||||
where
|
||||
S: Sample + Send + 'static,
|
||||
{
|
||||
type Item = S;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<S> {
|
||||
loop {
|
||||
// Basic situation that will happen most of the time.
|
||||
if let Some(sample) = self.current.next() {
|
||||
return Some(sample);
|
||||
}
|
||||
|
||||
// Since `self.current` has finished, we need to pick the next sound.
|
||||
// In order to avoid inlining this expensive operation, the code is in another function.
|
||||
if self.go_next().is_err() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(self.current.size_hint().0, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> SourcesQueueOutput<S>
|
||||
where
|
||||
S: Sample + Send + 'static,
|
||||
{
|
||||
// Called when `current` is empty and we must jump to the next element.
|
||||
// Returns `Ok` if the sound should continue playing, or an error if it should stop.
|
||||
//
|
||||
// This method is separate so that it is not inlined.
|
||||
fn go_next(&mut self) -> Result<(), ()> {
|
||||
if let Some(signal_after_end) = self.signal_after_end.take() {
|
||||
let _ = signal_after_end.send(());
|
||||
}
|
||||
|
||||
let (next, signal_after_end) = {
|
||||
let mut next = self.input.next_sounds.lock().unwrap();
|
||||
|
||||
if next.len() == 0 {
|
||||
let silence = Box::new(Zero::<S>::new_samples(1, 44100, THRESHOLD)) as Box<_>;
|
||||
if self.input.keep_alive_if_empty.load(Ordering::Acquire) {
|
||||
// Play a short silence in order to avoid spinlocking.
|
||||
(silence, None)
|
||||
} else {
|
||||
return Err(());
|
||||
}
|
||||
} else {
|
||||
next.remove(0)
|
||||
}
|
||||
};
|
||||
|
||||
self.current = next;
|
||||
self.signal_after_end = signal_after_end;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::buffer::SamplesBuffer;
|
||||
use crate::queue;
|
||||
use crate::source::Source;
|
||||
|
||||
#[test]
|
||||
#[ignore] // FIXME: samples rate and channel not updated immediately after transition
|
||||
fn basic() {
|
||||
let (tx, mut rx) = queue::queue(false);
|
||||
|
||||
tx.append(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
|
||||
tx.append(SamplesBuffer::new(2, 96000, vec![5i16, 5, 5, 5]));
|
||||
|
||||
assert_eq!(rx.channels(), 1);
|
||||
assert_eq!(rx.sample_rate(), 48000);
|
||||
assert_eq!(rx.next(), Some(10));
|
||||
assert_eq!(rx.next(), Some(-10));
|
||||
assert_eq!(rx.next(), Some(10));
|
||||
assert_eq!(rx.next(), Some(-10));
|
||||
assert_eq!(rx.channels(), 2);
|
||||
assert_eq!(rx.sample_rate(), 96000);
|
||||
assert_eq!(rx.next(), Some(5));
|
||||
assert_eq!(rx.next(), Some(5));
|
||||
assert_eq!(rx.next(), Some(5));
|
||||
assert_eq!(rx.next(), Some(5));
|
||||
assert_eq!(rx.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn immediate_end() {
|
||||
let (_, mut rx) = queue::queue::<i16>(false);
|
||||
assert_eq!(rx.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keep_alive() {
|
||||
let (tx, mut rx) = queue::queue(true);
|
||||
tx.append(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
|
||||
|
||||
assert_eq!(rx.next(), Some(10));
|
||||
assert_eq!(rx.next(), Some(-10));
|
||||
assert_eq!(rx.next(), Some(10));
|
||||
assert_eq!(rx.next(), Some(-10));
|
||||
|
||||
for _ in 0..100000 {
|
||||
assert_eq!(rx.next(), Some(0));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // TODO: not yet implemented
|
||||
fn no_delay_when_added() {
|
||||
let (tx, mut rx) = queue::queue(true);
|
||||
|
||||
for _ in 0..500 {
|
||||
assert_eq!(rx.next(), Some(0));
|
||||
}
|
||||
|
||||
tx.append(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
|
||||
assert_eq!(rx.next(), Some(10));
|
||||
assert_eq!(rx.next(), Some(-10));
|
||||
assert_eq!(rx.next(), Some(10));
|
||||
assert_eq!(rx.next(), Some(-10));
|
||||
}
|
||||
}
|
||||
Vendored
+428
@@ -0,0 +1,428 @@
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(feature = "crossbeam-channel")]
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
#[cfg(not(feature = "crossbeam-channel"))]
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
use crate::source::SeekError;
|
||||
use crate::stream::{OutputStreamHandle, PlayError};
|
||||
use crate::{queue, source::Done, Sample, Source};
|
||||
use cpal::FromSample;
|
||||
|
||||
/// Handle to a device that outputs sounds.
|
||||
///
|
||||
/// Dropping the `Sink` stops all sounds. You can use `detach` if you want the sounds to continue
|
||||
/// playing.
|
||||
pub struct Sink {
|
||||
queue_tx: Arc<queue::SourcesQueueInput<f32>>,
|
||||
sleep_until_end: Mutex<Option<Receiver<()>>>,
|
||||
|
||||
controls: Arc<Controls>,
|
||||
sound_count: Arc<AtomicUsize>,
|
||||
|
||||
detached: bool,
|
||||
}
|
||||
|
||||
struct SeekOrder {
|
||||
pos: Duration,
|
||||
feedback: Sender<Result<(), SeekError>>,
|
||||
}
|
||||
|
||||
impl SeekOrder {
|
||||
fn new(pos: Duration) -> (Self, Receiver<Result<(), SeekError>>) {
|
||||
#[cfg(not(feature = "crossbeam-channel"))]
|
||||
let (tx, rx) = {
|
||||
use std::sync::mpsc;
|
||||
mpsc::channel()
|
||||
};
|
||||
|
||||
#[cfg(feature = "crossbeam-channel")]
|
||||
let (tx, rx) = {
|
||||
use crossbeam_channel::bounded;
|
||||
bounded(1)
|
||||
};
|
||||
(Self { pos, feedback: tx }, rx)
|
||||
}
|
||||
|
||||
fn attempt<S>(self, maybe_seekable: &mut S)
|
||||
where
|
||||
S: Source,
|
||||
S::Item: Sample + Send,
|
||||
{
|
||||
let res = maybe_seekable.try_seek(self.pos);
|
||||
let _ignore_receiver_dropped = self.feedback.send(res);
|
||||
}
|
||||
}
|
||||
|
||||
struct Controls {
|
||||
pause: AtomicBool,
|
||||
volume: Mutex<f32>,
|
||||
stopped: AtomicBool,
|
||||
speed: Mutex<f32>,
|
||||
to_clear: Mutex<u32>,
|
||||
seek: Mutex<Option<SeekOrder>>,
|
||||
position: Mutex<Duration>,
|
||||
}
|
||||
|
||||
impl Sink {
|
||||
/// Builds a new `Sink`, beginning playback on a stream.
|
||||
#[inline]
|
||||
pub fn try_new(stream: &OutputStreamHandle) -> Result<Sink, PlayError> {
|
||||
let (sink, queue_rx) = Sink::new_idle();
|
||||
stream.play_raw(queue_rx)?;
|
||||
Ok(sink)
|
||||
}
|
||||
|
||||
/// Builds a new `Sink`.
|
||||
#[inline]
|
||||
pub fn new_idle() -> (Sink, queue::SourcesQueueOutput<f32>) {
|
||||
let (queue_tx, queue_rx) = queue::queue(true);
|
||||
|
||||
let sink = Sink {
|
||||
queue_tx,
|
||||
sleep_until_end: Mutex::new(None),
|
||||
controls: Arc::new(Controls {
|
||||
pause: AtomicBool::new(false),
|
||||
volume: Mutex::new(1.0),
|
||||
stopped: AtomicBool::new(false),
|
||||
speed: Mutex::new(1.0),
|
||||
to_clear: Mutex::new(0),
|
||||
seek: Mutex::new(None),
|
||||
position: Mutex::new(Duration::ZERO),
|
||||
}),
|
||||
sound_count: Arc::new(AtomicUsize::new(0)),
|
||||
detached: false,
|
||||
};
|
||||
(sink, queue_rx)
|
||||
}
|
||||
|
||||
/// Appends a sound to the queue of sounds to play.
|
||||
#[inline]
|
||||
pub fn append<S>(&self, source: S)
|
||||
where
|
||||
S: Source + Send + 'static,
|
||||
f32: FromSample<S::Item>,
|
||||
S::Item: Sample + Send,
|
||||
{
|
||||
// Wait for queue to flush then resume stopped playback
|
||||
if self.controls.stopped.load(Ordering::SeqCst) {
|
||||
if self.sound_count.load(Ordering::SeqCst) > 0 {
|
||||
self.sleep_until_end();
|
||||
}
|
||||
self.controls.stopped.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
let controls = self.controls.clone();
|
||||
|
||||
let start_played = AtomicBool::new(false);
|
||||
|
||||
let source = source
|
||||
.speed(1.0)
|
||||
// must be placed before pausable but after speed & delay
|
||||
.track_position()
|
||||
.pausable(false)
|
||||
.amplify(1.0)
|
||||
.skippable()
|
||||
.stoppable()
|
||||
// if you change the duration update the docs for try_seek!
|
||||
.periodic_access(Duration::from_millis(20), move |src| {
|
||||
if controls.stopped.load(Ordering::SeqCst) {
|
||||
src.stop();
|
||||
*controls.position.lock().unwrap() = Duration::ZERO;
|
||||
}
|
||||
{
|
||||
let mut to_clear = controls.to_clear.lock().unwrap();
|
||||
if *to_clear > 0 {
|
||||
src.inner_mut().skip();
|
||||
*to_clear -= 1;
|
||||
*controls.position.lock().unwrap() = Duration::ZERO;
|
||||
} else {
|
||||
*controls.position.lock().unwrap() = src.inner().inner().inner().inner().get_pos();
|
||||
}
|
||||
}
|
||||
let amp = src.inner_mut().inner_mut();
|
||||
amp.set_factor(*controls.volume.lock().unwrap());
|
||||
amp.inner_mut()
|
||||
.set_paused(controls.pause.load(Ordering::SeqCst));
|
||||
amp.inner_mut()
|
||||
.inner_mut()
|
||||
.inner_mut()
|
||||
.set_factor(*controls.speed.lock().unwrap());
|
||||
if let Some(seek) = controls.seek.lock().unwrap().take() {
|
||||
seek.attempt(amp)
|
||||
}
|
||||
start_played.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.convert_samples();
|
||||
self.sound_count.fetch_add(1, Ordering::Relaxed);
|
||||
let source = Done::new(source, self.sound_count.clone());
|
||||
*self.sleep_until_end.lock().unwrap() = Some(self.queue_tx.append_with_signal(source));
|
||||
}
|
||||
|
||||
/// Gets the volume of the sound.
|
||||
///
|
||||
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than 1.0 will
|
||||
/// multiply each sample by this value.
|
||||
#[inline]
|
||||
pub fn volume(&self) -> f32 {
|
||||
*self.controls.volume.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Changes the volume of the sound.
|
||||
///
|
||||
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0` will
|
||||
/// multiply each sample by this value.
|
||||
#[inline]
|
||||
pub fn set_volume(&self, value: f32) {
|
||||
*self.controls.volume.lock().unwrap() = value;
|
||||
}
|
||||
|
||||
/// Gets the speed of the sound.
|
||||
///
|
||||
/// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0` will
|
||||
/// change the play speed of the sound.
|
||||
#[inline]
|
||||
pub fn speed(&self) -> f32 {
|
||||
*self.controls.speed.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Changes the speed of the sound.
|
||||
///
|
||||
/// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0` will
|
||||
/// change the play speed of the sound.
|
||||
#[inline]
|
||||
pub fn set_speed(&self, value: f32) {
|
||||
*self.controls.speed.lock().unwrap() = value;
|
||||
}
|
||||
|
||||
/// Resumes playback of a paused sink.
|
||||
///
|
||||
/// No effect if not paused.
|
||||
#[inline]
|
||||
pub fn play(&self) {
|
||||
self.controls.pause.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
// There is no `can_seek()` method as it is impossible to use correctly. Between
|
||||
// checking if a source supports seeking and actually seeking the sink can
|
||||
// switch to a new source.
|
||||
|
||||
/// Attempts to seek to a given position in the current source.
|
||||
///
|
||||
/// This blocks between 0 and ~5 milliseconds.
|
||||
///
|
||||
/// As long as the duration of the source is known, seek is guaranteed to saturate
|
||||
/// at the end of the source. For example given a source that reports a total duration
|
||||
/// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to
|
||||
/// 42 seconds.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function will return [`SeekError::NotSupported`] if one of the underlying
|
||||
/// sources does not support seeking.
|
||||
///
|
||||
/// It will return an error if an implementation ran
|
||||
/// into one during the seek.
|
||||
///
|
||||
/// When seeking beyond the end of a source this
|
||||
/// function might return an error if the duration of the source is not known.
|
||||
pub fn try_seek(&self, pos: Duration) -> Result<(), SeekError> {
|
||||
let (order, feedback) = SeekOrder::new(pos);
|
||||
*self.controls.seek.lock().unwrap() = Some(order);
|
||||
|
||||
if self.sound_count.load(Ordering::Acquire) == 0 {
|
||||
// No sound is playing, seek will not be performed
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match feedback.recv() {
|
||||
Ok(seek_res) => seek_res,
|
||||
// The feedback channel closed. Probably another seekorder was set
|
||||
// invalidating this one and closing the feedback channel
|
||||
// ... or the audio thread panicked.
|
||||
Err(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Pauses playback of this sink.
|
||||
///
|
||||
/// No effect if already paused.
|
||||
///
|
||||
/// A paused sink can be resumed with `play()`.
|
||||
pub fn pause(&self) {
|
||||
self.controls.pause.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Gets if a sink is paused
|
||||
///
|
||||
/// Sinks can be paused and resumed using `pause()` and `play()`. This returns `true` if the
|
||||
/// sink is paused.
|
||||
pub fn is_paused(&self) -> bool {
|
||||
self.controls.pause.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Removes all currently loaded `Source`s from the `Sink`, and pauses it.
|
||||
///
|
||||
/// See `pause()` for information about pausing a `Sink`.
|
||||
pub fn clear(&self) {
|
||||
let len = self.sound_count.load(Ordering::SeqCst) as u32;
|
||||
*self.controls.to_clear.lock().unwrap() = len;
|
||||
self.sleep_until_end();
|
||||
self.pause();
|
||||
}
|
||||
|
||||
/// Skips to the next `Source` in the `Sink`
|
||||
///
|
||||
/// If there are more `Source`s appended to the `Sink` at the time,
|
||||
/// it will play the next one. Otherwise, the `Sink` will finish as if
|
||||
/// it had finished playing a `Source` all the way through.
|
||||
pub fn skip_one(&self) {
|
||||
let len = self.sound_count.load(Ordering::SeqCst) as u32;
|
||||
let mut to_clear = self.controls.to_clear.lock().unwrap();
|
||||
if len > *to_clear {
|
||||
*to_clear += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops the sink by emptying the queue.
|
||||
#[inline]
|
||||
pub fn stop(&self) {
|
||||
self.controls.stopped.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Destroys the sink without stopping the sounds that are still playing.
|
||||
#[inline]
|
||||
pub fn detach(mut self) {
|
||||
self.detached = true;
|
||||
}
|
||||
|
||||
/// Sleeps the current thread until the sound ends.
|
||||
#[inline]
|
||||
pub fn sleep_until_end(&self) {
|
||||
if let Some(sleep_until_end) = self.sleep_until_end.lock().unwrap().take() {
|
||||
let _ = sleep_until_end.recv();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this sink has no more sounds to play.
|
||||
#[inline]
|
||||
pub fn empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Returns the number of sounds currently in the queue.
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.sound_count.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Returns the position of the sound that's being played.
|
||||
///
|
||||
/// This takes into account any speedup or delay applied.
|
||||
///
|
||||
/// Example: if you apply a speedup of *2* to an mp3 decoder source and
|
||||
/// [`get_pos()`](Sink::get_pos) returns *5s* then the position in the mp3
|
||||
/// recording is *10s* from its start.
|
||||
#[inline]
|
||||
pub fn get_pos(&self) -> Duration {
|
||||
*self.controls.position.lock().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Sink {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
self.queue_tx.set_keep_alive_if_empty(false);
|
||||
|
||||
if !self.detached {
|
||||
self.controls.stopped.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::buffer::SamplesBuffer;
|
||||
use crate::{Sink, Source};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[test]
|
||||
fn test_pause_and_stop() {
|
||||
let (sink, mut queue_rx) = Sink::new_idle();
|
||||
|
||||
// assert_eq!(queue_rx.next(), Some(0.0));
|
||||
|
||||
let v = vec![10i16, -10, 20, -20, 30, -30];
|
||||
|
||||
// Low rate to ensure immediate control.
|
||||
sink.append(SamplesBuffer::new(1, 1, v.clone()));
|
||||
let mut src = SamplesBuffer::new(1, 1, v).convert_samples();
|
||||
|
||||
assert_eq!(queue_rx.next(), src.next());
|
||||
assert_eq!(queue_rx.next(), src.next());
|
||||
|
||||
sink.pause();
|
||||
|
||||
assert_eq!(queue_rx.next(), Some(0.0));
|
||||
|
||||
sink.play();
|
||||
|
||||
assert_eq!(queue_rx.next(), src.next());
|
||||
assert_eq!(queue_rx.next(), src.next());
|
||||
|
||||
sink.stop();
|
||||
|
||||
assert_eq!(queue_rx.next(), Some(0.0));
|
||||
|
||||
assert_eq!(sink.empty(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stop_and_start() {
|
||||
let (sink, mut queue_rx) = Sink::new_idle();
|
||||
|
||||
let v = vec![10i16, -10, 20, -20, 30, -30];
|
||||
|
||||
sink.append(SamplesBuffer::new(1, 1, v.clone()));
|
||||
let mut src = SamplesBuffer::new(1, 1, v.clone()).convert_samples();
|
||||
|
||||
assert_eq!(queue_rx.next(), src.next());
|
||||
assert_eq!(queue_rx.next(), src.next());
|
||||
|
||||
sink.stop();
|
||||
|
||||
assert!(sink.controls.stopped.load(Ordering::SeqCst));
|
||||
assert_eq!(queue_rx.next(), Some(0.0));
|
||||
|
||||
src = SamplesBuffer::new(1, 1, v.clone()).convert_samples();
|
||||
sink.append(SamplesBuffer::new(1, 1, v));
|
||||
|
||||
assert!(!sink.controls.stopped.load(Ordering::SeqCst));
|
||||
// Flush silence
|
||||
let mut queue_rx = queue_rx.skip_while(|v| *v == 0.0);
|
||||
|
||||
assert_eq!(queue_rx.next(), src.next());
|
||||
assert_eq!(queue_rx.next(), src.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume() {
|
||||
let (sink, mut queue_rx) = Sink::new_idle();
|
||||
|
||||
let v = vec![10i16, -10, 20, -20, 30, -30];
|
||||
|
||||
// High rate to avoid immediate control.
|
||||
sink.append(SamplesBuffer::new(2, 44100, v.clone()));
|
||||
let src = SamplesBuffer::new(2, 44100, v.clone()).convert_samples();
|
||||
|
||||
let mut src = src.amplify(0.5);
|
||||
sink.set_volume(0.5);
|
||||
|
||||
for _ in 0..v.len() {
|
||||
assert_eq!(queue_rx.next(), src.next());
|
||||
}
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Internal function that builds a `Amplify` object.
|
||||
pub fn amplify<I>(input: I, factor: f32) -> Amplify<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
Amplify { input, factor }
|
||||
}
|
||||
|
||||
/// Filter that modifies each sample by a given value.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Amplify<I> {
|
||||
input: I,
|
||||
factor: f32,
|
||||
}
|
||||
|
||||
impl<I> Amplify<I> {
|
||||
/// Modifies the amplification factor.
|
||||
#[inline]
|
||||
pub fn set_factor(&mut self, factor: f32) {
|
||||
self.factor = factor;
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for Amplify<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
self.input.next().map(|value| value.amplify(self.factor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ExactSizeIterator for Amplify<I>
|
||||
where
|
||||
I: Source + ExactSizeIterator,
|
||||
I::Item: Sample,
|
||||
{
|
||||
}
|
||||
|
||||
impl<I> Source for Amplify<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.input.try_seek(pos)
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
use std::f32::consts::PI;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::Source;
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
// Implemented following http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
|
||||
|
||||
/// Internal function that builds a `BltFilter` object.
|
||||
pub fn low_pass<I>(input: I, freq: u32) -> BltFilter<I>
|
||||
where
|
||||
I: Source<Item = f32>,
|
||||
{
|
||||
low_pass_with_q(input, freq, 0.5)
|
||||
}
|
||||
|
||||
pub fn high_pass<I>(input: I, freq: u32) -> BltFilter<I>
|
||||
where
|
||||
I: Source<Item = f32>,
|
||||
{
|
||||
high_pass_with_q(input, freq, 0.5)
|
||||
}
|
||||
|
||||
/// Same as low_pass but allows the q value (bandwidth) to be changed
|
||||
pub fn low_pass_with_q<I>(input: I, freq: u32, q: f32) -> BltFilter<I>
|
||||
where
|
||||
I: Source<Item = f32>,
|
||||
{
|
||||
BltFilter {
|
||||
input,
|
||||
formula: BltFormula::LowPass { freq, q },
|
||||
applier: None,
|
||||
x_n1: 0.0,
|
||||
x_n2: 0.0,
|
||||
y_n1: 0.0,
|
||||
y_n2: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as high_pass but allows the q value (bandwidth) to be changed
|
||||
pub fn high_pass_with_q<I>(input: I, freq: u32, q: f32) -> BltFilter<I>
|
||||
where
|
||||
I: Source<Item = f32>,
|
||||
{
|
||||
BltFilter {
|
||||
input,
|
||||
formula: BltFormula::HighPass { freq, q },
|
||||
applier: None,
|
||||
x_n1: 0.0,
|
||||
x_n2: 0.0,
|
||||
y_n1: 0.0,
|
||||
y_n2: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BltFilter<I> {
|
||||
input: I,
|
||||
formula: BltFormula,
|
||||
applier: Option<BltApplier>,
|
||||
x_n1: f32,
|
||||
x_n2: f32,
|
||||
y_n1: f32,
|
||||
y_n2: f32,
|
||||
}
|
||||
|
||||
impl<I> BltFilter<I> {
|
||||
/// Modifies this filter so that it becomes a low-pass filter.
|
||||
pub fn to_low_pass(&mut self, freq: u32) {
|
||||
self.to_low_pass_with_q(freq, 0.5);
|
||||
}
|
||||
|
||||
/// Modifies this filter so that it becomes a high-pass filter
|
||||
pub fn to_high_pass(&mut self, freq: u32) {
|
||||
self.to_high_pass_with_q(freq, 0.5);
|
||||
}
|
||||
|
||||
/// Same as to_low_pass but allows the q value (bandwidth) to be changed
|
||||
pub fn to_low_pass_with_q(&mut self, freq: u32, q: f32) {
|
||||
self.formula = BltFormula::LowPass { freq, q };
|
||||
self.applier = None;
|
||||
}
|
||||
|
||||
/// Same as to_high_pass but allows the q value (bandwidth) to be changed
|
||||
pub fn to_high_pass_with_q(&mut self, freq: u32, q: f32) {
|
||||
self.formula = BltFormula::HighPass { freq, q };
|
||||
self.applier = None;
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for BltFilter<I>
|
||||
where
|
||||
I: Source<Item = f32>,
|
||||
{
|
||||
type Item = f32;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<f32> {
|
||||
let last_in_frame = self.input.current_frame_len() == Some(1);
|
||||
|
||||
if self.applier.is_none() {
|
||||
self.applier = Some(self.formula.to_applier(self.input.sample_rate()));
|
||||
}
|
||||
|
||||
let sample = match self.input.next() {
|
||||
None => return None,
|
||||
Some(s) => s,
|
||||
};
|
||||
|
||||
let result = self
|
||||
.applier
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.apply(sample, self.x_n1, self.x_n2, self.y_n1, self.y_n2);
|
||||
|
||||
self.y_n2 = self.y_n1;
|
||||
self.x_n2 = self.x_n1;
|
||||
self.y_n1 = result;
|
||||
self.x_n1 = sample;
|
||||
|
||||
if last_in_frame {
|
||||
self.applier = None;
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ExactSizeIterator for BltFilter<I> where I: Source<Item = f32> + ExactSizeIterator {}
|
||||
|
||||
impl<I> Source for BltFilter<I>
|
||||
where
|
||||
I: Source<Item = f32>,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.input.try_seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum BltFormula {
|
||||
LowPass { freq: u32, q: f32 },
|
||||
HighPass { freq: u32, q: f32 },
|
||||
}
|
||||
|
||||
impl BltFormula {
|
||||
fn to_applier(&self, sampling_frequency: u32) -> BltApplier {
|
||||
match *self {
|
||||
BltFormula::LowPass { freq, q } => {
|
||||
let w0 = 2.0 * PI * freq as f32 / sampling_frequency as f32;
|
||||
|
||||
let alpha = w0.sin() / (2.0 * q);
|
||||
let b1 = 1.0 - w0.cos();
|
||||
let b0 = b1 / 2.0;
|
||||
let b2 = b0;
|
||||
let a0 = 1.0 + alpha;
|
||||
let a1 = -2.0 * w0.cos();
|
||||
let a2 = 1.0 - alpha;
|
||||
|
||||
BltApplier {
|
||||
b0: b0 / a0,
|
||||
b1: b1 / a0,
|
||||
b2: b2 / a0,
|
||||
a1: a1 / a0,
|
||||
a2: a2 / a0,
|
||||
}
|
||||
}
|
||||
BltFormula::HighPass { freq, q } => {
|
||||
let w0 = 2.0 * PI * freq as f32 / sampling_frequency as f32;
|
||||
let cos_w0 = w0.cos();
|
||||
let alpha = w0.sin() / (2.0 * q);
|
||||
|
||||
let b0 = (1.0 + cos_w0) / 2.0;
|
||||
let b1 = -1.0 - cos_w0;
|
||||
let b2 = b0;
|
||||
let a0 = 1.0 + alpha;
|
||||
let a1 = -2.0 * cos_w0;
|
||||
let a2 = 1.0 - alpha;
|
||||
|
||||
BltApplier {
|
||||
b0: b0 / a0,
|
||||
b1: b1 / a0,
|
||||
b2: b2 / a0,
|
||||
a1: a1 / a0,
|
||||
a2: a2 / a0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct BltApplier {
|
||||
b0: f32,
|
||||
b1: f32,
|
||||
b2: f32,
|
||||
a1: f32,
|
||||
a2: f32,
|
||||
}
|
||||
|
||||
impl BltApplier {
|
||||
#[inline]
|
||||
fn apply(&self, x_n: f32, x_n1: f32, x_n2: f32, y_n1: f32, y_n2: f32) -> f32 {
|
||||
self.b0 * x_n + self.b1 * x_n1 + self.b2 * x_n2 - self.a1 * y_n1 - self.a2 * y_n2
|
||||
}
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
use std::cmp;
|
||||
use std::mem;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Internal function that builds a `Buffered` object.
|
||||
#[inline]
|
||||
pub fn buffered<I>(input: I) -> Buffered<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
let total_duration = input.total_duration();
|
||||
let first_frame = extract(input);
|
||||
|
||||
Buffered {
|
||||
current_frame: first_frame,
|
||||
position_in_frame: 0,
|
||||
total_duration,
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator that at the same time extracts data from the iterator and stores it in a buffer.
|
||||
pub struct Buffered<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
/// Immutable reference to the next frame of data. Cannot be `Frame::Input`.
|
||||
current_frame: Arc<Frame<I>>,
|
||||
|
||||
/// The position in number of samples of this iterator inside `current_frame`.
|
||||
position_in_frame: usize,
|
||||
|
||||
/// Obtained once at creation and never modified again.
|
||||
total_duration: Option<Duration>,
|
||||
}
|
||||
|
||||
enum Frame<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
/// Data that has already been extracted from the iterator. Also contains a pointer to the
|
||||
/// next frame.
|
||||
Data(FrameData<I>),
|
||||
|
||||
/// No more data.
|
||||
End,
|
||||
|
||||
/// Unextracted data. The `Option` should never be `None` and is only here for easier data
|
||||
/// processing.
|
||||
Input(Mutex<Option<I>>),
|
||||
}
|
||||
|
||||
struct FrameData<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
data: Vec<I::Item>,
|
||||
channels: u16,
|
||||
rate: u32,
|
||||
next: Mutex<Arc<Frame<I>>>,
|
||||
}
|
||||
|
||||
impl<I> Drop for FrameData<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
// This is necessary to prevent stack overflows deallocating long chains of the mutually
|
||||
// recursive `Frame` and `FrameData` types. This iteratively traverses as much of the
|
||||
// chain as needs to be deallocated, and repeatedly "pops" the head off the list. This
|
||||
// solves the problem, as when the time comes to actually deallocate the `FrameData`,
|
||||
// the `next` field will contain a `Frame::End`, or an `Arc` with additional references,
|
||||
// so the depth of recursive drops will be bounded.
|
||||
while let Ok(arc_next) = self.next.get_mut() {
|
||||
if let Some(next_ref) = Arc::get_mut(arc_next) {
|
||||
// This allows us to own the next Frame.
|
||||
let next = mem::replace(next_ref, Frame::End);
|
||||
if let Frame::Data(next_data) = next {
|
||||
// Swap the current FrameData with the next one, allowing the current one
|
||||
// to go out of scope.
|
||||
*self = next_data;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a frame from the input iterator.
|
||||
fn extract<I>(mut input: I) -> Arc<Frame<I>>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
let frame_len = input.current_frame_len();
|
||||
|
||||
if frame_len == Some(0) {
|
||||
return Arc::new(Frame::End);
|
||||
}
|
||||
|
||||
let channels = input.channels();
|
||||
let rate = input.sample_rate();
|
||||
let data: Vec<I::Item> = input
|
||||
.by_ref()
|
||||
.take(cmp::min(frame_len.unwrap_or(32768), 32768))
|
||||
.collect();
|
||||
|
||||
if data.is_empty() {
|
||||
return Arc::new(Frame::End);
|
||||
}
|
||||
|
||||
Arc::new(Frame::Data(FrameData {
|
||||
data,
|
||||
channels,
|
||||
rate,
|
||||
next: Mutex::new(Arc::new(Frame::Input(Mutex::new(Some(input))))),
|
||||
}))
|
||||
}
|
||||
|
||||
impl<I> Buffered<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
/// Advances to the next frame.
|
||||
fn next_frame(&mut self) {
|
||||
let next_frame = {
|
||||
let mut next_frame_ptr = match &*self.current_frame {
|
||||
Frame::Data(FrameData { next, .. }) => next.lock().unwrap(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let next_frame = match &**next_frame_ptr {
|
||||
Frame::Data(_) => next_frame_ptr.clone(),
|
||||
Frame::End => next_frame_ptr.clone(),
|
||||
Frame::Input(input) => {
|
||||
let input = input.lock().unwrap().take().unwrap();
|
||||
extract(input)
|
||||
}
|
||||
};
|
||||
|
||||
*next_frame_ptr = next_frame.clone();
|
||||
next_frame
|
||||
};
|
||||
|
||||
self.current_frame = next_frame;
|
||||
self.position_in_frame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for Buffered<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
let current_sample;
|
||||
let advance_frame;
|
||||
|
||||
match &*self.current_frame {
|
||||
Frame::Data(FrameData { data, .. }) => {
|
||||
current_sample = Some(data[self.position_in_frame]);
|
||||
self.position_in_frame += 1;
|
||||
advance_frame = self.position_in_frame >= data.len();
|
||||
}
|
||||
|
||||
Frame::End => {
|
||||
current_sample = None;
|
||||
advance_frame = false;
|
||||
}
|
||||
|
||||
Frame::Input(_) => unreachable!(),
|
||||
};
|
||||
|
||||
if advance_frame {
|
||||
self.next_frame();
|
||||
}
|
||||
|
||||
current_sample
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
// TODO:
|
||||
(0, None)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: uncomment when `size_hint` is fixed
|
||||
/*impl<I> ExactSizeIterator for Amplify<I> where I: Source + ExactSizeIterator, I::Item: Sample {
|
||||
}*/
|
||||
|
||||
impl<I> Source for Buffered<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
match &*self.current_frame {
|
||||
Frame::Data(FrameData { data, .. }) => Some(data.len() - self.position_in_frame),
|
||||
Frame::End => Some(0),
|
||||
Frame::Input(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
match *self.current_frame {
|
||||
Frame::Data(FrameData { channels, .. }) => channels,
|
||||
Frame::End => 1,
|
||||
Frame::Input(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
match *self.current_frame {
|
||||
Frame::Data(FrameData { rate, .. }) => rate,
|
||||
Frame::End => 44100,
|
||||
Frame::Input(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.total_duration
|
||||
}
|
||||
|
||||
/// Can not support seek, in the end state we lose the underlying source
|
||||
/// which makes seeking back impossible.
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
|
||||
Err(SeekError::NotSupported {
|
||||
underlying_source: std::any::type_name::<Self>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Clone for Buffered<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn clone(&self) -> Buffered<I> {
|
||||
Buffered {
|
||||
current_frame: self.current_frame.clone(),
|
||||
position_in_frame: self.position_in_frame,
|
||||
total_duration: self.total_duration,
|
||||
}
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Combines channels in input into a single mono source, then plays that mono sound
|
||||
/// to each channel at the volume given for that channel.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ChannelVolume<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
input: I,
|
||||
// Channel number is used as index for amplification value.
|
||||
channel_volumes: Vec<f32>,
|
||||
// Current listener being processed.
|
||||
current_channel: usize,
|
||||
current_sample: Option<I::Item>,
|
||||
}
|
||||
|
||||
impl<I> ChannelVolume<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
pub fn new(mut input: I, channel_volumes: Vec<f32>) -> ChannelVolume<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
let mut sample = None;
|
||||
for _ in 0..input.channels() {
|
||||
if let Some(s) = input.next() {
|
||||
sample = Some(
|
||||
sample
|
||||
.get_or_insert_with(I::Item::zero_value)
|
||||
.saturating_add(s),
|
||||
);
|
||||
}
|
||||
}
|
||||
ChannelVolume {
|
||||
input,
|
||||
channel_volumes,
|
||||
current_channel: 0,
|
||||
current_sample: sample,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the volume for a given channel number. Will panic if channel number
|
||||
/// was invalid.
|
||||
pub fn set_volume(&mut self, channel: usize, volume: f32) {
|
||||
self.channel_volumes[channel] = volume;
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for ChannelVolume<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
// return value
|
||||
let ret = self
|
||||
.current_sample
|
||||
.map(|sample| sample.amplify(self.channel_volumes[self.current_channel]));
|
||||
self.current_channel += 1;
|
||||
if self.current_channel >= self.channel_volumes.len() {
|
||||
self.current_channel = 0;
|
||||
self.current_sample = None;
|
||||
for _ in 0..self.input.channels() {
|
||||
if let Some(s) = self.input.next() {
|
||||
self.current_sample = Some(
|
||||
self.current_sample
|
||||
.get_or_insert_with(I::Item::zero_value)
|
||||
.saturating_add(s),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ExactSizeIterator for ChannelVolume<I>
|
||||
where
|
||||
I: Source + ExactSizeIterator,
|
||||
I::Item: Sample,
|
||||
{
|
||||
}
|
||||
|
||||
impl<I> Source for ChannelVolume<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.channel_volumes.len() as u16
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.input.try_seek(pos)
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use cpal::FromSample;
|
||||
|
||||
use crate::source::{FadeIn, Mix, TakeDuration};
|
||||
use crate::{Sample, Source};
|
||||
|
||||
/// Mixes one sound fading out with another sound fading in for the given duration.
|
||||
///
|
||||
/// Only the crossfaded portion (beginning of fadeout, beginning of fadein) is returned.
|
||||
pub fn crossfade<I1, I2>(
|
||||
input_fadeout: I1,
|
||||
input_fadein: I2,
|
||||
duration: Duration,
|
||||
) -> Crossfade<I1, I2>
|
||||
where
|
||||
I1: Source,
|
||||
I2: Source,
|
||||
I1::Item: FromSample<I2::Item> + Sample,
|
||||
I2::Item: Sample,
|
||||
{
|
||||
let mut input_fadeout = input_fadeout.take_duration(duration);
|
||||
input_fadeout.set_filter_fadeout();
|
||||
let input_fadein = input_fadein.take_duration(duration).fade_in(duration);
|
||||
input_fadeout.mix(input_fadein)
|
||||
}
|
||||
|
||||
pub type Crossfade<I1, I2> = Mix<TakeDuration<I1>, FadeIn<TakeDuration<I2>>>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::buffer::SamplesBuffer;
|
||||
fn dummysource(length: u8) -> SamplesBuffer<f32> {
|
||||
let data: Vec<f32> = (1..=length).map(f32::from).collect();
|
||||
SamplesBuffer::new(1, 1, data)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_crossfade() {
|
||||
let source1 = dummysource(10);
|
||||
let source2 = dummysource(10);
|
||||
let mut mixed = crossfade(
|
||||
source1,
|
||||
source2,
|
||||
Duration::from_secs(5) + Duration::from_nanos(1),
|
||||
);
|
||||
assert_eq!(mixed.next(), Some(1.0));
|
||||
assert_eq!(mixed.next(), Some(2.0));
|
||||
assert_eq!(mixed.next(), Some(3.0));
|
||||
assert_eq!(mixed.next(), Some(4.0));
|
||||
assert_eq!(mixed.next(), Some(5.0));
|
||||
assert_eq!(mixed.next(), None);
|
||||
|
||||
let source1 = dummysource(10);
|
||||
let source2 = dummysource(10).amplify(0.0);
|
||||
let mut mixed = crossfade(
|
||||
source1,
|
||||
source2,
|
||||
Duration::from_secs(5) + Duration::from_nanos(1),
|
||||
);
|
||||
assert_eq!(mixed.next(), Some(1.0 * 1.0));
|
||||
assert_eq!(mixed.next(), Some(2.0 * 0.8));
|
||||
assert_eq!(mixed.next(), Some(3.0 * 0.6));
|
||||
assert_eq!(mixed.next(), Some(4.0 * 0.4));
|
||||
assert_eq!(mixed.next(), Some(5.0 * 0.2));
|
||||
assert_eq!(mixed.next(), None);
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
fn remaining_samples(until_playback: Duration, sample_rate: u32, channels: u16) -> usize {
|
||||
let ns = until_playback.as_secs() * 1_000_000_000 + until_playback.subsec_nanos() as u64;
|
||||
let samples = ns * channels as u64 * sample_rate as u64 / 1_000_000_000;
|
||||
samples as usize
|
||||
}
|
||||
|
||||
/// Internal function that builds a `Delay` object.
|
||||
pub fn delay<I>(input: I, duration: Duration) -> Delay<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
Delay {
|
||||
remaining_samples: remaining_samples(duration, input.sample_rate(), input.channels()),
|
||||
requested_duration: duration,
|
||||
input,
|
||||
}
|
||||
}
|
||||
|
||||
/// A source that delays the given source by a certain amount.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Delay<I> {
|
||||
input: I,
|
||||
remaining_samples: usize,
|
||||
requested_duration: Duration,
|
||||
}
|
||||
|
||||
impl<I> Delay<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for Delay<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = <I as Iterator>::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<<I as Iterator>::Item> {
|
||||
if self.remaining_samples >= 1 {
|
||||
self.remaining_samples -= 1;
|
||||
Some(Sample::zero_value())
|
||||
} else {
|
||||
self.input.next()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let (min, max) = self.input.size_hint();
|
||||
(
|
||||
min + self.remaining_samples,
|
||||
max.map(|v| v + self.remaining_samples),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Source for Delay<I>
|
||||
where
|
||||
I: Iterator + Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input
|
||||
.current_frame_len()
|
||||
.map(|val| val + self.remaining_samples)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input
|
||||
.total_duration()
|
||||
.map(|val| val + self.requested_duration)
|
||||
}
|
||||
|
||||
/// Pos is seen from the perspective of the api user.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let mut source = inner_source.delay(Duration::from_secs(10));
|
||||
/// source.try_seek(Duration::from_secs(15));
|
||||
///
|
||||
/// // inner_source is now at pos: Duration::from_secs(5);
|
||||
/// ```
|
||||
///
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
if pos < self.requested_duration {
|
||||
self.input.try_seek(Duration::ZERO)?;
|
||||
let until_playback = self.requested_duration - pos;
|
||||
self.remaining_samples =
|
||||
remaining_samples(until_playback, self.sample_rate(), self.channels());
|
||||
}
|
||||
let compensated_for_delay = pos.saturating_sub(self.requested_duration);
|
||||
self.input.try_seek(compensated_for_delay)
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// When the inner source is empty this decrements an `AtomicUsize`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Done<I> {
|
||||
input: I,
|
||||
signal: Arc<AtomicUsize>,
|
||||
signal_sent: bool,
|
||||
}
|
||||
|
||||
impl<I> Done<I> {
|
||||
#[inline]
|
||||
pub fn new(input: I, signal: Arc<AtomicUsize>) -> Done<I> {
|
||||
Done {
|
||||
input,
|
||||
signal,
|
||||
signal_sent: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Source> Iterator for Done<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
let next = self.input.next();
|
||||
if !self.signal_sent && next.is_none() {
|
||||
self.signal.fetch_sub(1, Ordering::Relaxed);
|
||||
self.signal_sent = true;
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Source for Done<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.input.try_seek(pos)
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// An empty source.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Empty<S>(PhantomData<S>);
|
||||
|
||||
impl<S> Default for Empty<S> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Empty<S> {
|
||||
#[inline]
|
||||
pub fn new() -> Empty<S> {
|
||||
Empty(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Iterator for Empty<S> {
|
||||
type Item = S;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<S> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Source for Empty<S>
|
||||
where
|
||||
S: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
48000
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
Some(Duration::new(0, 0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
|
||||
Err(SeekError::NotSupported {
|
||||
underlying_source: std::any::type_name::<Self>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// An empty source which executes a callback function
|
||||
pub struct EmptyCallback<S> {
|
||||
pub phantom_data: PhantomData<S>,
|
||||
pub callback: Box<dyn Send + Fn()>,
|
||||
}
|
||||
|
||||
impl<S> EmptyCallback<S> {
|
||||
#[inline]
|
||||
pub fn new(callback: Box<dyn Send + Fn()>) -> EmptyCallback<S> {
|
||||
EmptyCallback {
|
||||
phantom_data: PhantomData,
|
||||
callback,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Iterator for EmptyCallback<S> {
|
||||
type Item = S;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<S> {
|
||||
(self.callback)();
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Source for EmptyCallback<S>
|
||||
where
|
||||
S: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
48000
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
Some(Duration::new(0, 0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
|
||||
Err(SeekError::NotSupported {
|
||||
underlying_source: std::any::type_name::<Self>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Internal function that builds a `FadeIn` object.
|
||||
pub fn fadein<I>(input: I, duration: Duration) -> FadeIn<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
let duration = duration.as_secs() * 1000000000 + duration.subsec_nanos() as u64;
|
||||
|
||||
FadeIn {
|
||||
input,
|
||||
remaining_ns: duration as f32,
|
||||
total_ns: duration as f32,
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter that modifies raises the volume from silence over a time period.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FadeIn<I> {
|
||||
input: I,
|
||||
remaining_ns: f32,
|
||||
total_ns: f32,
|
||||
}
|
||||
|
||||
impl<I> FadeIn<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for FadeIn<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
if self.remaining_ns <= 0.0 {
|
||||
return self.input.next();
|
||||
}
|
||||
|
||||
let factor = 1.0 - self.remaining_ns / self.total_ns;
|
||||
self.remaining_ns -=
|
||||
1000000000.0 / (self.input.sample_rate() as f32 * self.channels() as f32);
|
||||
self.input.next().map(|value| value.amplify(factor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ExactSizeIterator for FadeIn<I>
|
||||
where
|
||||
I: Source + ExactSizeIterator,
|
||||
I::Item: Sample,
|
||||
{
|
||||
}
|
||||
|
||||
impl<I> Source for FadeIn<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.input.try_seek(pos)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use crate::source::{from_iter, FromIter};
|
||||
|
||||
/// Builds a source that chains sources built from a factory.
|
||||
///
|
||||
/// The `factory` parameter is a function that produces a source. The source is then played.
|
||||
/// Whenever the source ends, `factory` is called again in order to produce the source that is
|
||||
/// played next.
|
||||
///
|
||||
/// If the `factory` closure returns `None`, then the sound ends.
|
||||
pub fn from_factory<F, S>(factory: F) -> FromIter<FromFactoryIter<F>>
|
||||
where
|
||||
F: FnMut() -> Option<S>,
|
||||
{
|
||||
from_iter(FromFactoryIter { factory })
|
||||
}
|
||||
|
||||
/// Internal type used by `from_factory`.
|
||||
pub struct FromFactoryIter<F> {
|
||||
factory: F,
|
||||
}
|
||||
|
||||
impl<F, S> Iterator for FromFactoryIter<F>
|
||||
where
|
||||
F: FnMut() -> Option<S>,
|
||||
{
|
||||
type Item = S;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<S> {
|
||||
(self.factory)()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(0, None)
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Builds a source that chains sources provided by an iterator.
|
||||
///
|
||||
/// The `iterator` parameter is an iterator that produces a source. The source is then played.
|
||||
/// Whenever the source ends, the `iterator` is used again in order to produce the source that is
|
||||
/// played next.
|
||||
///
|
||||
/// If the `iterator` produces `None`, then the sound ends.
|
||||
pub fn from_iter<I>(iterator: I) -> FromIter<I::IntoIter>
|
||||
where
|
||||
I: IntoIterator,
|
||||
{
|
||||
let mut iterator = iterator.into_iter();
|
||||
let first_source = iterator.next();
|
||||
|
||||
FromIter {
|
||||
iterator,
|
||||
current_source: first_source,
|
||||
}
|
||||
}
|
||||
|
||||
/// A source that chains sources provided by an iterator.
|
||||
#[derive(Clone)]
|
||||
pub struct FromIter<I>
|
||||
where
|
||||
I: Iterator,
|
||||
{
|
||||
// The iterator that provides sources.
|
||||
iterator: I,
|
||||
// Is only ever `None` if the first element of the iterator is `None`.
|
||||
current_source: Option<I::Item>,
|
||||
}
|
||||
|
||||
impl<I> Iterator for FromIter<I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Iterator + Source,
|
||||
<I::Item as Iterator>::Item: Sample,
|
||||
{
|
||||
type Item = <I::Item as Iterator>::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<<I::Item as Iterator>::Item> {
|
||||
loop {
|
||||
if let Some(src) = &mut self.current_source {
|
||||
if let Some(value) = src.next() {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(src) = self.iterator.next() {
|
||||
self.current_source = Some(src);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if let Some(cur) = &self.current_source {
|
||||
(cur.size_hint().0, None)
|
||||
} else {
|
||||
(0, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Source for FromIter<I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Iterator + Source,
|
||||
<I::Item as Iterator>::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
// This function is non-trivial because the boundary between the current source and the
|
||||
// next must be a frame boundary as well.
|
||||
//
|
||||
// The current sound is free to return `None` for `current_frame_len()`, in which case
|
||||
// we *should* return the number of samples remaining the current sound.
|
||||
// This can be estimated with `size_hint()`.
|
||||
//
|
||||
// If the `size_hint` is `None` as well, we are in the worst case scenario. To handle this
|
||||
// situation we force a frame to have a maximum number of samples indicate by this
|
||||
// constant.
|
||||
const THRESHOLD: usize = 10240;
|
||||
|
||||
// Try the current `current_frame_len`.
|
||||
if let Some(src) = &self.current_source {
|
||||
if let Some(val) = src.current_frame_len() {
|
||||
if val != 0 {
|
||||
return Some(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try the size hint.
|
||||
if let Some(src) = &self.current_source {
|
||||
if let Some(val) = src.size_hint().1 {
|
||||
if val < THRESHOLD && val != 0 {
|
||||
return Some(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise we use the constant value.
|
||||
Some(THRESHOLD)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
if let Some(src) = &self.current_source {
|
||||
src.channels()
|
||||
} else {
|
||||
// Dummy value that only happens if the iterator was empty.
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
if let Some(src) = &self.current_source {
|
||||
src.sample_rate()
|
||||
} else {
|
||||
// Dummy value that only happens if the iterator was empty.
|
||||
44100
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
if let Some(source) = self.current_source.as_mut() {
|
||||
source.try_seek(pos)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::buffer::SamplesBuffer;
|
||||
use crate::source::{from_iter, Source};
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let mut rx = from_iter((0..2).map(|n| {
|
||||
if n == 0 {
|
||||
SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10])
|
||||
} else if n == 1 {
|
||||
SamplesBuffer::new(2, 96000, vec![5i16, 5, 5, 5])
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}));
|
||||
|
||||
assert_eq!(rx.channels(), 1);
|
||||
assert_eq!(rx.sample_rate(), 48000);
|
||||
assert_eq!(rx.next(), Some(10));
|
||||
assert_eq!(rx.next(), Some(-10));
|
||||
assert_eq!(rx.next(), Some(10));
|
||||
assert_eq!(rx.next(), Some(-10));
|
||||
/*assert_eq!(rx.channels(), 2);
|
||||
assert_eq!(rx.sample_rate(), 96000);*/
|
||||
// FIXME: not working
|
||||
assert_eq!(rx.next(), Some(5));
|
||||
assert_eq!(rx.next(), Some(5));
|
||||
assert_eq!(rx.next(), Some(5));
|
||||
assert_eq!(rx.next(), Some(5));
|
||||
assert_eq!(rx.next(), None);
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
use std::cmp;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::uniform::UniformSourceIterator;
|
||||
use crate::source::SeekError;
|
||||
use crate::{Sample, Source};
|
||||
use cpal::{FromSample, Sample as CpalSample};
|
||||
|
||||
/// Internal function that builds a `Mix` object.
|
||||
pub fn mix<I1, I2>(input1: I1, input2: I2) -> Mix<I1, I2>
|
||||
where
|
||||
I1: Source,
|
||||
I1::Item: FromSample<I2::Item> + Sample,
|
||||
I2: Source,
|
||||
I2::Item: Sample,
|
||||
{
|
||||
let channels = input1.channels();
|
||||
let rate = input1.sample_rate();
|
||||
|
||||
Mix {
|
||||
input1: UniformSourceIterator::new(input1, channels, rate),
|
||||
input2: UniformSourceIterator::new(input2, channels, rate),
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter that modifies each sample by a given value.
|
||||
#[derive(Clone)]
|
||||
pub struct Mix<I1, I2>
|
||||
where
|
||||
I1: Source,
|
||||
I1::Item: FromSample<I2::Item> + Sample,
|
||||
I2: Source,
|
||||
I2::Item: Sample,
|
||||
{
|
||||
input1: UniformSourceIterator<I1, I1::Item>,
|
||||
input2: UniformSourceIterator<I2, I2::Item>,
|
||||
}
|
||||
|
||||
impl<I1, I2> Iterator for Mix<I1, I2>
|
||||
where
|
||||
I1: Source,
|
||||
I1::Item: FromSample<I2::Item> + Sample,
|
||||
I2: Source,
|
||||
I2::Item: Sample,
|
||||
{
|
||||
type Item = I1::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I1::Item> {
|
||||
let s1 = self.input1.next();
|
||||
let s2 = self.input2.next();
|
||||
|
||||
match (s1, s2) {
|
||||
(Some(s1), Some(s2)) => Some(s1.saturating_add(CpalSample::from_sample(s2))),
|
||||
(Some(s1), None) => Some(s1),
|
||||
(None, Some(s2)) => Some(CpalSample::from_sample(s2)),
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let s1 = self.input1.size_hint();
|
||||
let s2 = self.input2.size_hint();
|
||||
|
||||
let min = cmp::max(s1.0, s2.0);
|
||||
let max = match (s1.1, s2.1) {
|
||||
(Some(s1), Some(s2)) => Some(cmp::max(s1, s2)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
(min, max)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I1, I2> ExactSizeIterator for Mix<I1, I2>
|
||||
where
|
||||
I1: Source + ExactSizeIterator,
|
||||
I1::Item: FromSample<I2::Item> + Sample,
|
||||
I2: Source + ExactSizeIterator,
|
||||
I2::Item: Sample,
|
||||
{
|
||||
}
|
||||
|
||||
impl<I1, I2> Source for Mix<I1, I2>
|
||||
where
|
||||
I1: Source,
|
||||
I1::Item: FromSample<I2::Item> + Sample,
|
||||
I2: Source,
|
||||
I2::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
let f1 = self.input1.current_frame_len();
|
||||
let f2 = self.input2.current_frame_len();
|
||||
|
||||
match (f1, f2) {
|
||||
(Some(f1), Some(f2)) => Some(cmp::min(f1, f2)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input1.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input1.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
let f1 = self.input1.total_duration();
|
||||
let f2 = self.input2.total_duration();
|
||||
|
||||
match (f1, f2) {
|
||||
(Some(f1), Some(f2)) => Some(cmp::max(f1, f2)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Will only attempt a seek if both underlying sources support seek.
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
|
||||
Err(SeekError::NotSupported {
|
||||
underlying_source: std::any::type_name::<Self>(),
|
||||
})
|
||||
|
||||
// uncomment when #510 is implemented (query position of playback)
|
||||
// TODO use source_intact to check if rollback makes sense
|
||||
|
||||
// let org_pos = self.input1.playback_pos();
|
||||
// self.input1.try_seek(pos)?;
|
||||
//
|
||||
// let res = self.input2.try_seek(pos);
|
||||
// if res.is_err() { // rollback seek in input1
|
||||
// self.input1.try_seek(org_pos)?;
|
||||
// }
|
||||
//
|
||||
// res
|
||||
}
|
||||
}
|
||||
+491
@@ -0,0 +1,491 @@
|
||||
//! Sources of sound and various filters.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use cpal::FromSample;
|
||||
|
||||
use crate::Sample;
|
||||
|
||||
pub use self::amplify::Amplify;
|
||||
pub use self::blt::BltFilter;
|
||||
pub use self::buffered::Buffered;
|
||||
pub use self::channel_volume::ChannelVolume;
|
||||
pub use self::crossfade::Crossfade;
|
||||
pub use self::delay::Delay;
|
||||
pub use self::done::Done;
|
||||
pub use self::empty::Empty;
|
||||
pub use self::empty_callback::EmptyCallback;
|
||||
pub use self::fadein::FadeIn;
|
||||
pub use self::from_factory::{from_factory, FromFactoryIter};
|
||||
pub use self::from_iter::{from_iter, FromIter};
|
||||
pub use self::mix::Mix;
|
||||
pub use self::pausable::Pausable;
|
||||
pub use self::periodic::PeriodicAccess;
|
||||
pub use self::position::TrackPosition;
|
||||
pub use self::repeat::Repeat;
|
||||
pub use self::samples_converter::SamplesConverter;
|
||||
pub use self::sine::SineWave;
|
||||
pub use self::skip::SkipDuration;
|
||||
pub use self::skippable::Skippable;
|
||||
pub use self::spatial::Spatial;
|
||||
pub use self::speed::Speed;
|
||||
pub use self::stoppable::Stoppable;
|
||||
pub use self::take::TakeDuration;
|
||||
pub use self::uniform::UniformSourceIterator;
|
||||
pub use self::zero::Zero;
|
||||
|
||||
mod amplify;
|
||||
mod blt;
|
||||
mod buffered;
|
||||
mod channel_volume;
|
||||
mod crossfade;
|
||||
mod delay;
|
||||
mod done;
|
||||
mod empty;
|
||||
mod empty_callback;
|
||||
mod fadein;
|
||||
mod from_factory;
|
||||
mod from_iter;
|
||||
mod mix;
|
||||
mod pausable;
|
||||
mod periodic;
|
||||
mod position;
|
||||
mod repeat;
|
||||
mod samples_converter;
|
||||
mod sine;
|
||||
mod skip;
|
||||
mod skippable;
|
||||
mod spatial;
|
||||
mod speed;
|
||||
mod stoppable;
|
||||
mod take;
|
||||
mod uniform;
|
||||
mod zero;
|
||||
|
||||
/// A source of samples.
|
||||
///
|
||||
/// # A quick lesson about sounds
|
||||
///
|
||||
/// ## Sampling
|
||||
///
|
||||
/// A sound is a vibration that propagates through air and reaches your ears. This vibration can
|
||||
/// be represented as an analog signal.
|
||||
///
|
||||
/// In order to store this signal in the computer's memory or on the disk, we perform what is
|
||||
/// called *sampling*. The consists in choosing an interval of time (for example 20µs) and reading
|
||||
/// the amplitude of the signal at each interval (for example, if the interval is 20µs we read the
|
||||
/// amplitude every 20µs). By doing so we obtain a list of numerical values, each value being
|
||||
/// called a *sample*.
|
||||
///
|
||||
/// Therefore a sound can be represented in memory by a frequency and a list of samples. The
|
||||
/// frequency is expressed in hertz and corresponds to the number of samples that have been
|
||||
/// read per second. For example if we read one sample every 20µs, the frequency would be
|
||||
/// 50000 Hz. In reality, common values for the frequency are 44100, 48000 and 96000.
|
||||
///
|
||||
/// ## Channels
|
||||
///
|
||||
/// But a frequency and a list of values only represent one signal. When you listen to a sound,
|
||||
/// your left and right ears don't receive exactly the same signal. In order to handle this,
|
||||
/// we usually record not one but two different signals: one for the left ear and one for the right
|
||||
/// ear. We say that such a sound has two *channels*.
|
||||
///
|
||||
/// Sometimes sounds even have five or six channels, each corresponding to a location around the
|
||||
/// head of the listener.
|
||||
///
|
||||
/// The standard in audio manipulation is to *interleave* the multiple channels. In other words,
|
||||
/// in a sound with two channels the list of samples contains the first sample of the first
|
||||
/// channel, then the first sample of the second channel, then the second sample of the first
|
||||
/// channel, then the second sample of the second channel, and so on. The same applies if you have
|
||||
/// more than two channels. The rodio library only supports this schema.
|
||||
///
|
||||
/// Therefore in order to represent a sound in memory in fact we need three characteristics: the
|
||||
/// frequency, the number of channels, and the list of samples.
|
||||
///
|
||||
/// ## The `Source` trait
|
||||
///
|
||||
/// A Rust object that represents a sound should implement the `Source` trait.
|
||||
///
|
||||
/// The three characteristics that describe a sound are provided through this trait:
|
||||
///
|
||||
/// - The number of channels can be retrieved with `channels`.
|
||||
/// - The frequency can be retrieved with `sample_rate`.
|
||||
/// - The list of values can be retrieved by iterating on the source. The `Source` trait requires
|
||||
/// that the `Iterator` trait be implemented as well.
|
||||
///
|
||||
/// # Frames
|
||||
///
|
||||
/// The samples rate and number of channels of some sound sources can change by itself from time
|
||||
/// to time.
|
||||
///
|
||||
/// > **Note**: As a basic example, if you play two audio files one after the other and treat the
|
||||
/// > whole as a single source, then the channels and samples rate of that source may change at the
|
||||
/// > transition between the two files.
|
||||
///
|
||||
/// However, for optimization purposes rodio supposes that the number of channels and the frequency
|
||||
/// stay the same for long periods of time and avoids calling `channels()` and
|
||||
/// `sample_rate` too frequently.
|
||||
///
|
||||
/// In order to properly handle this situation, the `current_frame_len()` method should return
|
||||
/// the number of samples that remain in the iterator before the samples rate and number of
|
||||
/// channels can potentially change.
|
||||
///
|
||||
pub trait Source: Iterator
|
||||
where
|
||||
Self::Item: Sample,
|
||||
{
|
||||
/// Returns the number of samples before the current frame ends. `None` means "infinite" or
|
||||
/// "until the sound ends".
|
||||
/// Should never return 0 unless there's no more data.
|
||||
///
|
||||
/// After the engine has finished reading the specified number of samples, it will check
|
||||
/// whether the value of `channels()` and/or `sample_rate()` have changed.
|
||||
fn current_frame_len(&self) -> Option<usize>;
|
||||
|
||||
/// Returns the number of channels. Channels are always interleaved.
|
||||
fn channels(&self) -> u16;
|
||||
|
||||
/// Returns the rate at which the source should be played. In number of samples per second.
|
||||
fn sample_rate(&self) -> u32;
|
||||
|
||||
/// Returns the total duration of this source, if known.
|
||||
///
|
||||
/// `None` indicates at the same time "infinite" or "unknown".
|
||||
fn total_duration(&self) -> Option<Duration>;
|
||||
|
||||
/// Stores the source in a buffer in addition to returning it. This iterator can be cloned.
|
||||
|
||||
#[inline]
|
||||
fn buffered(self) -> Buffered<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
buffered::buffered(self)
|
||||
}
|
||||
|
||||
/// Mixes this source with another one.
|
||||
#[inline]
|
||||
fn mix<S>(self, other: S) -> Mix<Self, S>
|
||||
where
|
||||
Self: Sized,
|
||||
Self::Item: FromSample<S::Item>,
|
||||
S: Source,
|
||||
S::Item: Sample,
|
||||
{
|
||||
mix::mix(self, other)
|
||||
}
|
||||
|
||||
/// Repeats this source forever.
|
||||
///
|
||||
/// Note that this works by storing the data in a buffer, so the amount of memory used is
|
||||
/// proportional to the size of the sound.
|
||||
#[inline]
|
||||
fn repeat_infinite(self) -> Repeat<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
repeat::repeat(self)
|
||||
}
|
||||
|
||||
/// Takes a certain duration of this source and then stops.
|
||||
#[inline]
|
||||
fn take_duration(self, duration: Duration) -> TakeDuration<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
take::take_duration(self, duration)
|
||||
}
|
||||
|
||||
/// Delays the sound by a certain duration.
|
||||
///
|
||||
/// The rate and channels of the silence will use the same format as the first frame of the
|
||||
/// source.
|
||||
#[inline]
|
||||
fn delay(self, duration: Duration) -> Delay<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
delay::delay(self, duration)
|
||||
}
|
||||
|
||||
/// Immediately skips a certain duration of this source.
|
||||
///
|
||||
/// If the specified duration is longer than the source itself, `skip_duration` will skip to the end of the source.
|
||||
#[inline]
|
||||
fn skip_duration(self, duration: Duration) -> SkipDuration<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
skip::skip_duration(self, duration)
|
||||
}
|
||||
|
||||
/// Amplifies the sound by the given value.
|
||||
#[inline]
|
||||
fn amplify(self, value: f32) -> Amplify<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
amplify::amplify(self, value)
|
||||
}
|
||||
|
||||
/// Mixes this sound fading out with another sound fading in for the given duration.
|
||||
///
|
||||
/// Only the crossfaded portion (beginning of self, beginning of other) is returned.
|
||||
#[inline]
|
||||
fn take_crossfade_with<S: Source>(self, other: S, duration: Duration) -> Crossfade<Self, S>
|
||||
where
|
||||
Self: Sized,
|
||||
Self::Item: FromSample<S::Item>,
|
||||
<S as Iterator>::Item: Sample,
|
||||
{
|
||||
crossfade::crossfade(self, other, duration)
|
||||
}
|
||||
|
||||
/// Fades in the sound.
|
||||
#[inline]
|
||||
fn fade_in(self, duration: Duration) -> FadeIn<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fadein::fadein(self, duration)
|
||||
}
|
||||
|
||||
/// Calls the `access` closure on `Self` the first time the source is iterated and every
|
||||
/// time `period` elapses.
|
||||
///
|
||||
/// Later changes in either `sample_rate()` or `channels_count()` won't be reflected in
|
||||
/// the rate of access.
|
||||
///
|
||||
/// The rate is based on playback speed, so both the following will call `access` when the
|
||||
/// same samples are reached:
|
||||
/// `periodic_access(Duration::from_secs(1), ...).speed(2.0)`
|
||||
/// `speed(2.0).periodic_access(Duration::from_secs(2), ...)`
|
||||
#[inline]
|
||||
fn periodic_access<F>(self, period: Duration, access: F) -> PeriodicAccess<Self, F>
|
||||
where
|
||||
Self: Sized,
|
||||
F: FnMut(&mut Self),
|
||||
{
|
||||
periodic::periodic(self, period, access)
|
||||
}
|
||||
|
||||
/// Changes the play speed of the sound. Does not adjust the samples, only the play speed.
|
||||
#[inline]
|
||||
fn speed(self, ratio: f32) -> Speed<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
speed::speed(self, ratio)
|
||||
}
|
||||
|
||||
/// Adds a basic reverb effect.
|
||||
///
|
||||
/// This function requires the source to implement `Clone`. This can be done by using
|
||||
/// `buffered()`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let source = source.buffered().reverb(Duration::from_millis(100), 0.7);
|
||||
/// ```
|
||||
#[inline]
|
||||
fn reverb(self, duration: Duration, amplitude: f32) -> Mix<Self, Delay<Amplify<Self>>>
|
||||
where
|
||||
Self: Sized + Clone,
|
||||
{
|
||||
let echo = self.clone().amplify(amplitude).delay(duration);
|
||||
self.mix(echo)
|
||||
}
|
||||
|
||||
/// Converts the samples of this source to another type.
|
||||
#[inline]
|
||||
fn convert_samples<D>(self) -> SamplesConverter<Self, D>
|
||||
where
|
||||
Self: Sized,
|
||||
D: Sample,
|
||||
{
|
||||
SamplesConverter::new(self)
|
||||
}
|
||||
|
||||
/// Makes the sound pausable.
|
||||
// TODO: add example
|
||||
#[inline]
|
||||
fn pausable(self, initially_paused: bool) -> Pausable<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
pausable::pausable(self, initially_paused)
|
||||
}
|
||||
|
||||
/// Makes the sound stoppable.
|
||||
// TODO: add example
|
||||
#[inline]
|
||||
fn stoppable(self) -> Stoppable<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
stoppable::stoppable(self)
|
||||
}
|
||||
|
||||
fn skippable(self) -> Skippable<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
skippable::skippable(self)
|
||||
}
|
||||
|
||||
/// Start tracking the elapsed duration since the start of the underlying
|
||||
/// source.
|
||||
///
|
||||
/// If a speedup and or delay is applied after this that will not be reflected
|
||||
/// in the position returned by [`get_pos`](TrackPosition::get_pos).
|
||||
///
|
||||
/// This can get confusing when using [`get_pos()`](TrackPosition::get_pos)
|
||||
/// together with [`Source::try_seek()`] as the the latter does take all
|
||||
/// speedup's and delay's into account. Its recommended therefore to apply
|
||||
/// track_position after speedup's and delay's.
|
||||
fn track_position(self) -> TrackPosition<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
position::track_position(self)
|
||||
}
|
||||
|
||||
/// Applies a low-pass filter to the source.
|
||||
/// **Warning**: Probably buggy.
|
||||
#[inline]
|
||||
fn low_pass(self, freq: u32) -> BltFilter<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
Self: Source<Item = f32>,
|
||||
{
|
||||
blt::low_pass(self, freq)
|
||||
}
|
||||
|
||||
/// Applies a high-pass filter to the source.
|
||||
#[inline]
|
||||
fn high_pass(self, freq: u32) -> BltFilter<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
Self: Source<Item = f32>,
|
||||
{
|
||||
blt::high_pass(self, freq)
|
||||
}
|
||||
|
||||
/// Applies a low-pass filter to the source while allowing the q (bandwidth) to be changed.
|
||||
#[inline]
|
||||
fn low_pass_with_q(self, freq: u32, q: f32) -> BltFilter<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
Self: Source<Item = f32>,
|
||||
{
|
||||
blt::low_pass_with_q(self, freq, q)
|
||||
}
|
||||
|
||||
/// Applies a high-pass filter to the source while allowing the q (bandwidth) to be changed.
|
||||
#[inline]
|
||||
fn high_pass_with_q(self, freq: u32, q: f32) -> BltFilter<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
Self: Source<Item = f32>,
|
||||
{
|
||||
blt::high_pass_with_q(self, freq, q)
|
||||
}
|
||||
|
||||
// There is no `can_seek()` method as it is impossible to use correctly. Between
|
||||
// checking if a source supports seeking and actually seeking the sink can
|
||||
// switch to a new source.
|
||||
|
||||
/// Attempts to seek to a given position in the current source.
|
||||
///
|
||||
/// As long as the duration of the source is known seek is guaranteed to saturate
|
||||
/// at the end of the source. For example given a source that reports a total duration
|
||||
/// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to
|
||||
/// 42 seconds.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function will return [`SeekError::NotSupported`] if one of the underlying
|
||||
/// sources does not support seeking.
|
||||
///
|
||||
/// It will return an error if an implementation ran
|
||||
/// into one during the seek.
|
||||
///
|
||||
/// Seeking beyond the end of a source might return an error if the total duration of
|
||||
/// the source is not known.
|
||||
#[allow(unused_variables)]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
Err(SeekError::NotSupported {
|
||||
underlying_source: std::any::type_name::<Self>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// We might add decoders requiring new error types, without non_exhaustive
|
||||
// this would break users builds
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SeekError {
|
||||
#[error("Streaming is not supported by source: {underlying_source}")]
|
||||
NotSupported { underlying_source: &'static str },
|
||||
#[cfg(feature = "symphonia")]
|
||||
#[error("Error seeking: {0}")]
|
||||
SymphoniaDecoder(#[from] crate::decoder::symphonia::SeekError),
|
||||
#[cfg(feature = "wav")]
|
||||
#[error("Error seeking in wav source: {0}")]
|
||||
HoundDecoder(std::io::Error),
|
||||
#[error("An error occurred")]
|
||||
Other(Box<dyn std::error::Error + Send>),
|
||||
}
|
||||
|
||||
impl SeekError {
|
||||
pub fn source_intact(&self) -> bool {
|
||||
match self {
|
||||
SeekError::NotSupported { .. } => true,
|
||||
#[cfg(feature = "symphonia")]
|
||||
SeekError::SymphoniaDecoder(_) => false,
|
||||
#[cfg(feature = "wav")]
|
||||
SeekError::HoundDecoder(_) => false,
|
||||
SeekError::Other(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! source_pointer_impl {
|
||||
($($sig:tt)+) => {
|
||||
impl $($sig)+ {
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
(**self).current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
(**self).channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
(**self).sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
(**self).total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
(**self).try_seek(pos)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
source_pointer_impl!(<S> Source for Box<dyn Source<Item = S>> where S: Sample,);
|
||||
|
||||
source_pointer_impl!(<S> Source for Box<dyn Source<Item = S> + Send> where S: Sample,);
|
||||
|
||||
source_pointer_impl!(<S> Source for Box<dyn Source<Item = S> + Send + Sync> where S: Sample,);
|
||||
|
||||
source_pointer_impl!(<'a, S, C> Source for &'a mut C where S: Sample, C: Source<Item = S>,);
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Internal function that builds a `Pausable` object.
|
||||
pub fn pausable<I>(source: I, paused: bool) -> Pausable<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
let paused_channels = if paused {
|
||||
Some(source.channels())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Pausable {
|
||||
input: source,
|
||||
paused_channels,
|
||||
remaining_paused_samples: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Pausable<I> {
|
||||
input: I,
|
||||
paused_channels: Option<u16>,
|
||||
remaining_paused_samples: u16,
|
||||
}
|
||||
|
||||
impl<I> Pausable<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
/// Sets whether the filter applies.
|
||||
///
|
||||
/// If set to true, the inner sound stops playing and no samples are processed from it.
|
||||
#[inline]
|
||||
pub fn set_paused(&mut self, paused: bool) {
|
||||
match (self.paused_channels, paused) {
|
||||
(None, true) => self.paused_channels = Some(self.input.channels()),
|
||||
(Some(_), false) => self.paused_channels = None,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for Pausable<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
if self.remaining_paused_samples > 0 {
|
||||
self.remaining_paused_samples -= 1;
|
||||
return Some(I::Item::zero_value());
|
||||
}
|
||||
|
||||
if let Some(paused_channels) = self.paused_channels {
|
||||
self.remaining_paused_samples = paused_channels - 1;
|
||||
return Some(I::Item::zero_value());
|
||||
}
|
||||
|
||||
self.input.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Source for Pausable<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.input.try_seek(pos)
|
||||
}
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Internal function that builds a `PeriodicAccess` object.
|
||||
pub fn periodic<I, F>(source: I, period: Duration, modifier: F) -> PeriodicAccess<I, F>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
// TODO: handle the fact that the samples rate can change
|
||||
// TODO: generally, just wrong
|
||||
let update_ms = period.as_secs() as u32 * 1_000 + period.subsec_millis();
|
||||
let update_frequency = (update_ms * source.sample_rate()) / 1000 * source.channels() as u32;
|
||||
|
||||
PeriodicAccess {
|
||||
input: source,
|
||||
modifier,
|
||||
// Can overflow when subtracting if this is 0
|
||||
update_frequency: if update_frequency == 0 {
|
||||
1
|
||||
} else {
|
||||
update_frequency
|
||||
},
|
||||
samples_until_update: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls a function on a source every time a period elapsed.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PeriodicAccess<I, F> {
|
||||
// The inner source.
|
||||
input: I,
|
||||
|
||||
// Closure that gets access to `inner`.
|
||||
modifier: F,
|
||||
|
||||
// The frequency with which local_volume should be updated by remote_volume
|
||||
update_frequency: u32,
|
||||
|
||||
// How many samples remain until it is time to update local_volume with remote_volume.
|
||||
samples_until_update: u32,
|
||||
}
|
||||
|
||||
impl<I, F> PeriodicAccess<I, F>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
F: FnMut(&mut I),
|
||||
{
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, F> Iterator for PeriodicAccess<I, F>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
F: FnMut(&mut I),
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
self.samples_until_update -= 1;
|
||||
if self.samples_until_update == 0 {
|
||||
(self.modifier)(&mut self.input);
|
||||
self.samples_until_update = self.update_frequency;
|
||||
}
|
||||
|
||||
self.input.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, F> Source for PeriodicAccess<I, F>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
F: FnMut(&mut I),
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.input.try_seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::cell::RefCell;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::buffer::SamplesBuffer;
|
||||
use crate::source::Source;
|
||||
|
||||
#[test]
|
||||
fn stereo_access() {
|
||||
// Stereo, 1Hz audio buffer
|
||||
let inner = SamplesBuffer::new(2, 1, vec![10i16, -10, 10, -10, 20, -20]);
|
||||
|
||||
let cnt = RefCell::new(0);
|
||||
|
||||
let mut source = inner.periodic_access(Duration::from_millis(1000), |_src| {
|
||||
*cnt.borrow_mut() += 1;
|
||||
});
|
||||
|
||||
assert_eq!(*cnt.borrow(), 0);
|
||||
// Always called on first access!
|
||||
assert_eq!(source.next(), Some(10));
|
||||
assert_eq!(*cnt.borrow(), 1);
|
||||
// Called every 1 second afterwards
|
||||
assert_eq!(source.next(), Some(-10));
|
||||
assert_eq!(*cnt.borrow(), 1);
|
||||
assert_eq!(source.next(), Some(10));
|
||||
assert_eq!(*cnt.borrow(), 2);
|
||||
assert_eq!(source.next(), Some(-10));
|
||||
assert_eq!(*cnt.borrow(), 2);
|
||||
assert_eq!(source.next(), Some(20));
|
||||
assert_eq!(*cnt.borrow(), 3);
|
||||
assert_eq!(source.next(), Some(-20));
|
||||
assert_eq!(*cnt.borrow(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fast_access_overflow() {
|
||||
// 1hz is lower than 0.5 samples per 5ms
|
||||
let inner = SamplesBuffer::new(1, 1, vec![10i16, -10, 10, -10, 20, -20]);
|
||||
let mut source = inner.periodic_access(Duration::from_millis(5), |_src| {});
|
||||
|
||||
source.next();
|
||||
source.next(); // Would overflow here.
|
||||
}
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Internal function that builds a `TrackPosition` object. See trait docs for
|
||||
/// details
|
||||
pub fn track_position<I>(source: I) -> TrackPosition<I> {
|
||||
TrackPosition {
|
||||
input: source,
|
||||
samples_counted: 0,
|
||||
offset_duration: 0.0,
|
||||
current_frame_sample_rate: 0,
|
||||
current_frame_channels: 0,
|
||||
current_frame_len: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrackPosition<I> {
|
||||
input: I,
|
||||
samples_counted: usize,
|
||||
offset_duration: f64,
|
||||
current_frame_sample_rate: u32,
|
||||
current_frame_channels: u16,
|
||||
current_frame_len: Option<usize>,
|
||||
}
|
||||
|
||||
impl<I> TrackPosition<I> {
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> TrackPosition<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
/// Returns the position of the underlying source relative to its start.
|
||||
///
|
||||
/// If a speedup and or delay is applied after applying a
|
||||
/// [`Source::track_position`] it will not be reflected in the position
|
||||
/// returned by [`get_pos`](TrackPosition::get_pos).
|
||||
///
|
||||
/// This can get confusing when using [`get_pos()`](TrackPosition::get_pos)
|
||||
/// together with [`Source::try_seek()`] as the the latter does take all
|
||||
/// speedup's and delay's into account. Its recommended therefore to apply
|
||||
/// track_position after speedup's and delay's.
|
||||
#[inline]
|
||||
pub fn get_pos(&self) -> Duration {
|
||||
let seconds = self.samples_counted as f64
|
||||
/ self.input.sample_rate() as f64
|
||||
/ self.input.channels() as f64
|
||||
+ self.offset_duration;
|
||||
Duration::from_secs_f64(seconds)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_current_frame(&mut self) {
|
||||
self.current_frame_len = self.current_frame_len();
|
||||
self.current_frame_sample_rate = self.sample_rate();
|
||||
self.current_frame_channels = self.channels();
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for TrackPosition<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
// This should only be executed once at the first call to next.
|
||||
if self.current_frame_len.is_none() {
|
||||
self.set_current_frame();
|
||||
}
|
||||
|
||||
let item = self.input.next();
|
||||
if item.is_some() {
|
||||
self.samples_counted += 1;
|
||||
|
||||
// At the end of a frame add the duration of this frame to
|
||||
// offset_duration and start collecting samples again.
|
||||
if Some(self.samples_counted) == self.current_frame_len() {
|
||||
self.offset_duration += self.samples_counted as f64
|
||||
/ self.current_frame_sample_rate as f64
|
||||
/ self.current_frame_channels as f64;
|
||||
|
||||
// Reset.
|
||||
self.samples_counted = 0;
|
||||
self.set_current_frame();
|
||||
};
|
||||
};
|
||||
item
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Source for TrackPosition<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
let result = self.input.try_seek(pos);
|
||||
if result.is_ok() {
|
||||
self.offset_duration = pos.as_secs_f64();
|
||||
// This assumes that the seek implementation of the codec always
|
||||
// starts again at the beginning of a frame. Which is the case with
|
||||
// symphonia.
|
||||
self.samples_counted = 0;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::buffer::SamplesBuffer;
|
||||
use crate::source::Source;
|
||||
|
||||
#[test]
|
||||
fn test_position() {
|
||||
let inner = SamplesBuffer::new(1, 1, vec![10i16, -10, 10, -10, 20, -20]);
|
||||
let mut source = inner.track_position();
|
||||
|
||||
assert_eq!(source.get_pos().as_secs_f32(), 0.0);
|
||||
source.next();
|
||||
assert_eq!(source.get_pos().as_secs_f32(), 1.0);
|
||||
|
||||
source.next();
|
||||
assert_eq!(source.get_pos().as_secs_f32(), 2.0);
|
||||
|
||||
assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true);
|
||||
assert_eq!(source.get_pos().as_secs_f32(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_position_in_presence_of_speedup() {
|
||||
let inner = SamplesBuffer::new(1, 1, vec![10i16, -10, 10, -10, 20, -20]);
|
||||
let mut source = inner.speed(2.0).track_position();
|
||||
|
||||
assert_eq!(source.get_pos().as_secs_f32(), 0.0);
|
||||
source.next();
|
||||
assert_eq!(source.get_pos().as_secs_f32(), 0.5);
|
||||
|
||||
source.next();
|
||||
assert_eq!(source.get_pos().as_secs_f32(), 1.0);
|
||||
|
||||
assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true);
|
||||
assert_eq!(source.get_pos().as_secs_f32(), 1.0);
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::buffered::Buffered;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Internal function that builds a `Repeat` object.
|
||||
pub fn repeat<I>(input: I) -> Repeat<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
let input = input.buffered();
|
||||
Repeat {
|
||||
inner: input.clone(),
|
||||
next: input,
|
||||
}
|
||||
}
|
||||
|
||||
/// A source that repeats the given source.
|
||||
pub struct Repeat<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
inner: Buffered<I>,
|
||||
next: Buffered<I>,
|
||||
}
|
||||
|
||||
impl<I> Iterator for Repeat<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = <I as Iterator>::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<<I as Iterator>::Item> {
|
||||
if let Some(value) = self.inner.next() {
|
||||
return Some(value);
|
||||
}
|
||||
|
||||
self.inner = self.next.clone();
|
||||
self.inner.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
// infinite
|
||||
(0, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Source for Repeat<I>
|
||||
where
|
||||
I: Iterator + Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
match self.inner.current_frame_len() {
|
||||
Some(0) => self.next.current_frame_len(),
|
||||
a => a,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
match self.inner.current_frame_len() {
|
||||
Some(0) => self.next.channels(),
|
||||
_ => self.inner.channels(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
match self.inner.current_frame_len() {
|
||||
Some(0) => self.next.sample_rate(),
|
||||
_ => self.inner.sample_rate(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.inner.try_seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Clone for Repeat<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn clone(&self) -> Repeat<I> {
|
||||
Repeat {
|
||||
inner: self.inner.clone(),
|
||||
next: self.next.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
use cpal::{FromSample, Sample as CpalSample};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// An iterator that reads from a `Source` and converts the samples to a specific rate and
|
||||
/// channels count.
|
||||
///
|
||||
/// It implements `Source` as well, but all the data is guaranteed to be in a single frame whose
|
||||
/// channels and samples rate have been passed to `new`.
|
||||
#[derive(Clone)]
|
||||
pub struct SamplesConverter<I, D> {
|
||||
inner: I,
|
||||
dest: PhantomData<D>,
|
||||
}
|
||||
|
||||
impl<I, D> SamplesConverter<I, D> {
|
||||
#[inline]
|
||||
pub fn new(input: I) -> SamplesConverter<I, D> {
|
||||
SamplesConverter {
|
||||
inner: input,
|
||||
dest: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.inner
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, D> Iterator for SamplesConverter<I, D>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
D: FromSample<I::Item> + Sample,
|
||||
{
|
||||
type Item = D;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<D> {
|
||||
self.inner.next().map(|s| CpalSample::from_sample(s))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, D> ExactSizeIterator for SamplesConverter<I, D>
|
||||
where
|
||||
I: Source + ExactSizeIterator,
|
||||
I::Item: Sample,
|
||||
D: FromSample<I::Item> + Sample,
|
||||
{
|
||||
}
|
||||
|
||||
impl<I, D> Source for SamplesConverter<I, D>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
D: FromSample<I::Item> + Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.inner.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.inner.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.inner.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.inner.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.inner.try_seek(pos)
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
use std::f32::consts::PI;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::Source;
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// An infinite source that produces a sine.
|
||||
///
|
||||
/// Always has a rate of 48kHz and one channel.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SineWave {
|
||||
freq: f32,
|
||||
num_sample: usize,
|
||||
}
|
||||
|
||||
impl SineWave {
|
||||
/// The frequency of the sine.
|
||||
#[inline]
|
||||
pub fn new(freq: f32) -> SineWave {
|
||||
SineWave {
|
||||
freq,
|
||||
num_sample: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SineWave {
|
||||
type Item = f32;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<f32> {
|
||||
self.num_sample = self.num_sample.wrapping_add(1);
|
||||
|
||||
let value = 2.0 * PI * self.freq * self.num_sample as f32 / 48000.0;
|
||||
Some(value.sin())
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for SineWave {
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
48000
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
|
||||
// This is a constant sound, normal seeking would not have any effect.
|
||||
// While changing the phase of the sine wave could change how it sounds in
|
||||
// combination with another sound (beating) such precision is not the intend
|
||||
// of seeking
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
const NS_PER_SECOND: u128 = 1_000_000_000;
|
||||
|
||||
/// Internal function that builds a `SkipDuration` object.
|
||||
pub fn skip_duration<I>(mut input: I, duration: Duration) -> SkipDuration<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
do_skip_duration(&mut input, duration);
|
||||
SkipDuration {
|
||||
input,
|
||||
skipped_duration: duration,
|
||||
}
|
||||
}
|
||||
|
||||
/// Skips specified `duration` of the given `input` source from it's current position.
|
||||
fn do_skip_duration<I>(input: &mut I, mut duration: Duration)
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
while duration > Duration::new(0, 0) {
|
||||
if input.current_frame_len().is_none() {
|
||||
// Sample rate and the amount of channels will be the same till the end.
|
||||
do_skip_duration_unchecked(input, duration);
|
||||
return;
|
||||
}
|
||||
|
||||
// .unwrap() safety: if `current_frame_len()` is None, the body of the `if` statement
|
||||
// above returns before we get here.
|
||||
let frame_len: usize = input.current_frame_len().unwrap();
|
||||
// If frame_len is zero, then there is no more data to skip. Instead
|
||||
// just bail out.
|
||||
if frame_len == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let ns_per_sample: u128 =
|
||||
NS_PER_SECOND / input.sample_rate() as u128 / input.channels() as u128;
|
||||
|
||||
// Check if we need to skip only part of the current frame.
|
||||
if frame_len as u128 * ns_per_sample > duration.as_nanos() {
|
||||
skip_samples(input, (duration.as_nanos() / ns_per_sample) as usize);
|
||||
return;
|
||||
}
|
||||
|
||||
skip_samples(input, frame_len);
|
||||
|
||||
duration -= Duration::from_nanos((frame_len * ns_per_sample as usize) as u64);
|
||||
}
|
||||
}
|
||||
|
||||
/// Skips specified `duration` from the `input` source assuming that sample rate
|
||||
/// and amount of channels are not changing.
|
||||
fn do_skip_duration_unchecked<I>(input: &mut I, duration: Duration)
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
let samples_per_channel: u128 =
|
||||
duration.as_nanos() * input.sample_rate() as u128 / NS_PER_SECOND;
|
||||
let samples_to_skip: u128 = samples_per_channel * input.channels() as u128;
|
||||
|
||||
skip_samples(input, samples_to_skip as usize);
|
||||
}
|
||||
|
||||
/// Skips `n` samples from the given `input` source.
|
||||
fn skip_samples<I>(input: &mut I, n: usize)
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
for _ in 0..n {
|
||||
if input.next().is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A source that skips specified duration of the given source from it's current position.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SkipDuration<I> {
|
||||
input: I,
|
||||
skipped_duration: Duration,
|
||||
}
|
||||
|
||||
impl<I> SkipDuration<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for SkipDuration<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = <I as Iterator>::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.input.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Source for SkipDuration<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration().map(|val| {
|
||||
val.checked_sub(self.skipped_duration)
|
||||
.unwrap_or_else(|| Duration::from_secs(0))
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.input.try_seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::buffer::SamplesBuffer;
|
||||
use crate::source::Source;
|
||||
|
||||
fn test_skip_duration_samples_left(
|
||||
channels: u16,
|
||||
sample_rate: u32,
|
||||
seconds: u32,
|
||||
seconds_to_skip: u32,
|
||||
) {
|
||||
let data: Vec<f32> = (1..=(sample_rate * channels as u32 * seconds))
|
||||
.map(|_| 0f32)
|
||||
.collect();
|
||||
let test_buffer = SamplesBuffer::new(channels, sample_rate, data);
|
||||
let seconds_left = seconds.saturating_sub(seconds_to_skip);
|
||||
|
||||
let samples_left_expected = (sample_rate * channels as u32 * seconds_left) as usize;
|
||||
let samples_left = test_buffer
|
||||
.skip_duration(Duration::from_secs(seconds_to_skip as u64))
|
||||
.count();
|
||||
|
||||
assert_eq!(samples_left, samples_left_expected);
|
||||
}
|
||||
|
||||
macro_rules! skip_duration_test_block {
|
||||
($(channels: $ch:expr, sample rate: $sr:expr, seconds: $sec:expr, seconds to skip: $sec_to_skip:expr;)+) => {
|
||||
$(
|
||||
test_skip_duration_samples_left($ch, $sr, $sec, $sec_to_skip);
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_duration_shorter_than_source() {
|
||||
skip_duration_test_block! {
|
||||
channels: 1, sample rate: 44100, seconds: 5, seconds to skip: 3;
|
||||
channels: 1, sample rate: 96000, seconds: 5, seconds to skip: 3;
|
||||
|
||||
channels: 2, sample rate: 44100, seconds: 5, seconds to skip: 3;
|
||||
channels: 2, sample rate: 96000, seconds: 5, seconds to skip: 3;
|
||||
|
||||
channels: 4, sample rate: 44100, seconds: 5, seconds to skip: 3;
|
||||
channels: 4, sample rate: 96000, seconds: 5, seconds to skip: 3;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_duration_zero_duration() {
|
||||
skip_duration_test_block! {
|
||||
channels: 1, sample rate: 44100, seconds: 5, seconds to skip: 0;
|
||||
channels: 1, sample rate: 96000, seconds: 5, seconds to skip: 0;
|
||||
|
||||
channels: 2, sample rate: 44100, seconds: 5, seconds to skip: 0;
|
||||
channels: 2, sample rate: 96000, seconds: 5, seconds to skip: 0;
|
||||
|
||||
channels: 4, sample rate: 44100, seconds: 5, seconds to skip: 0;
|
||||
channels: 4, sample rate: 96000, seconds: 5, seconds to skip: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_duration_longer_than_source() {
|
||||
skip_duration_test_block! {
|
||||
channels: 1, sample rate: 44100, seconds: 1, seconds to skip: 5;
|
||||
channels: 1, sample rate: 96000, seconds: 10, seconds to skip: 11;
|
||||
|
||||
channels: 2, sample rate: 44100, seconds: 1, seconds to skip: 5;
|
||||
channels: 2, sample rate: 96000, seconds: 10, seconds to skip: 11;
|
||||
|
||||
channels: 4, sample rate: 44100, seconds: 1, seconds to skip: 5;
|
||||
channels: 4, sample rate: 96000, seconds: 10, seconds to skip: 11;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_duration_equal_to_source_length() {
|
||||
skip_duration_test_block! {
|
||||
channels: 1, sample rate: 44100, seconds: 1, seconds to skip: 1;
|
||||
channels: 1, sample rate: 96000, seconds: 10, seconds to skip: 10;
|
||||
|
||||
channels: 2, sample rate: 44100, seconds: 1, seconds to skip: 1;
|
||||
channels: 2, sample rate: 96000, seconds: 10, seconds to skip: 10;
|
||||
|
||||
channels: 4, sample rate: 44100, seconds: 1, seconds to skip: 1;
|
||||
channels: 4, sample rate: 96000, seconds: 10, seconds to skip: 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::Sample;
|
||||
use crate::Source;
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Internal function that builds a `Skippable` object.
|
||||
pub fn skippable<I>(source: I) -> Skippable<I> {
|
||||
Skippable {
|
||||
input: source,
|
||||
do_skip: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Skippable<I> {
|
||||
input: I,
|
||||
do_skip: bool,
|
||||
}
|
||||
|
||||
impl<I> Skippable<I> {
|
||||
/// Skips the current source
|
||||
#[inline]
|
||||
pub fn skip(&mut self) {
|
||||
self.do_skip = true;
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for Skippable<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
if self.do_skip {
|
||||
None
|
||||
} else {
|
||||
self.input.next()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Source for Skippable<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.input.try_seek(pos)
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::ChannelVolume;
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Combines channels in input into a single mono source, then plays that mono sound
|
||||
/// to each channel at the volume given for that channel.
|
||||
#[derive(Clone)]
|
||||
pub struct Spatial<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
input: ChannelVolume<I>,
|
||||
}
|
||||
|
||||
fn dist_sq(a: [f32; 3], b: [f32; 3]) -> f32 {
|
||||
a.iter()
|
||||
.zip(b.iter())
|
||||
.map(|(a, b)| (a - b) * (a - b))
|
||||
.sum::<f32>()
|
||||
}
|
||||
|
||||
impl<I> Spatial<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
pub fn new(
|
||||
input: I,
|
||||
emitter_position: [f32; 3],
|
||||
left_ear: [f32; 3],
|
||||
right_ear: [f32; 3],
|
||||
) -> Spatial<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
let mut ret = Spatial {
|
||||
input: ChannelVolume::new(input, vec![0.0, 0.0]),
|
||||
};
|
||||
ret.set_positions(emitter_position, left_ear, right_ear);
|
||||
ret
|
||||
}
|
||||
|
||||
/// Sets the position of the emitter and ears in the 3D world.
|
||||
pub fn set_positions(
|
||||
&mut self,
|
||||
emitter_pos: [f32; 3],
|
||||
left_ear: [f32; 3],
|
||||
right_ear: [f32; 3],
|
||||
) {
|
||||
debug_assert!(left_ear != right_ear);
|
||||
let left_dist_sq = dist_sq(left_ear, emitter_pos);
|
||||
let right_dist_sq = dist_sq(right_ear, emitter_pos);
|
||||
let max_diff = dist_sq(left_ear, right_ear).sqrt();
|
||||
let left_dist = left_dist_sq.sqrt();
|
||||
let right_dist = right_dist_sq.sqrt();
|
||||
let left_diff_modifier = (((left_dist - right_dist) / max_diff + 1.0) / 4.0 + 0.5).min(1.0);
|
||||
let right_diff_modifier =
|
||||
(((right_dist - left_dist) / max_diff + 1.0) / 4.0 + 0.5).min(1.0);
|
||||
let left_dist_modifier = (1.0 / left_dist_sq).min(1.0);
|
||||
let right_dist_modifier = (1.0 / right_dist_sq).min(1.0);
|
||||
self.input
|
||||
.set_volume(0, left_diff_modifier * left_dist_modifier);
|
||||
self.input
|
||||
.set_volume(1, right_diff_modifier * right_dist_modifier);
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for Spatial<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
self.input.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ExactSizeIterator for Spatial<I>
|
||||
where
|
||||
I: Source + ExactSizeIterator,
|
||||
I::Item: Sample,
|
||||
{
|
||||
}
|
||||
|
||||
impl<I> Source for Spatial<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.input.try_seek(pos)
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Internal function that builds a `Speed` object.
|
||||
pub fn speed<I>(input: I, factor: f32) -> Speed<I> {
|
||||
Speed { input, factor }
|
||||
}
|
||||
|
||||
/// Filter that modifies each sample by a given value.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Speed<I> {
|
||||
input: I,
|
||||
factor: f32,
|
||||
}
|
||||
|
||||
impl<I> Speed<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
/// Modifies the speed factor.
|
||||
#[inline]
|
||||
pub fn set_factor(&mut self, factor: f32) {
|
||||
self.factor = factor;
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for Speed<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
self.input.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ExactSizeIterator for Speed<I>
|
||||
where
|
||||
I: Source + ExactSizeIterator,
|
||||
I::Item: Sample,
|
||||
{
|
||||
}
|
||||
|
||||
impl<I> Source for Speed<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
(self.input.sample_rate() as f32 * self.factor) as u32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration().map(|d| d.mul_f32(self.factor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
/* TODO: This might be wrong, I do not know how speed achieves its speedup
|
||||
* so I can not reason about the correctness.
|
||||
* <dvdsk noreply@davidsk.dev> */
|
||||
|
||||
// even after 24 hours of playback f32 has enough precision
|
||||
let pos_accounting_for_speedup = pos.mul_f32(self.factor);
|
||||
self.input.try_seek(pos_accounting_for_speedup)
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Internal function that builds a `Stoppable` object.
|
||||
pub fn stoppable<I>(source: I) -> Stoppable<I> {
|
||||
Stoppable {
|
||||
input: source,
|
||||
stopped: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Stoppable<I> {
|
||||
input: I,
|
||||
stopped: bool,
|
||||
}
|
||||
|
||||
impl<I> Stoppable<I> {
|
||||
/// Stops the sound.
|
||||
#[inline]
|
||||
pub fn stop(&mut self) {
|
||||
self.stopped = true;
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for Stoppable<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<I::Item> {
|
||||
if self.stopped {
|
||||
None
|
||||
} else {
|
||||
self.input.next()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.input.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Source for Stoppable<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.input.current_frame_len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.input.total_duration()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.input.try_seek(pos)
|
||||
}
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// Internal function that builds a `TakeDuration` object.
|
||||
pub fn take_duration<I>(input: I, duration: Duration) -> TakeDuration<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
TakeDuration {
|
||||
current_frame_len: input.current_frame_len(),
|
||||
duration_per_sample: TakeDuration::get_duration_per_sample(&input),
|
||||
input,
|
||||
remaining_duration: duration,
|
||||
requested_duration: duration,
|
||||
filter: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// A filter that can be applied to a `TakeDuration`.
|
||||
#[derive(Clone, Debug)]
|
||||
enum DurationFilter {
|
||||
FadeOut,
|
||||
}
|
||||
impl DurationFilter {
|
||||
fn apply<I: Iterator>(
|
||||
&self,
|
||||
sample: <I as Iterator>::Item,
|
||||
parent: &TakeDuration<I>,
|
||||
) -> <I as Iterator>::Item
|
||||
where
|
||||
I::Item: Sample,
|
||||
{
|
||||
use self::DurationFilter::*;
|
||||
match self {
|
||||
FadeOut => {
|
||||
let remaining = parent.remaining_duration.as_millis() as f32;
|
||||
let total = parent.requested_duration.as_millis() as f32;
|
||||
sample.amplify(remaining / total)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const NANOS_PER_SEC: u64 = 1_000_000_000;
|
||||
|
||||
/// A source that truncates the given source to a certain duration.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TakeDuration<I> {
|
||||
input: I,
|
||||
remaining_duration: Duration,
|
||||
requested_duration: Duration,
|
||||
filter: Option<DurationFilter>,
|
||||
// Remaining samples in current frame.
|
||||
current_frame_len: Option<usize>,
|
||||
// Only updated when the current frame len is exhausted.
|
||||
duration_per_sample: Duration,
|
||||
}
|
||||
|
||||
impl<I> TakeDuration<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
/// Returns the duration elapsed for each sample extracted.
|
||||
#[inline]
|
||||
fn get_duration_per_sample(input: &I) -> Duration {
|
||||
let ns = NANOS_PER_SEC / (input.sample_rate() as u64 * input.channels() as u64);
|
||||
// \|/ the maximum value of `ns` is one billion, so this can't fail
|
||||
Duration::new(0, ns as u32)
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &I {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner source.
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Returns the inner source.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> I {
|
||||
self.input
|
||||
}
|
||||
|
||||
pub fn set_filter_fadeout(&mut self) {
|
||||
self.filter = Some(DurationFilter::FadeOut);
|
||||
}
|
||||
|
||||
pub fn clear_filter(&mut self) {
|
||||
self.filter = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for TakeDuration<I>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
type Item = <I as Iterator>::Item;
|
||||
|
||||
fn next(&mut self) -> Option<<I as Iterator>::Item> {
|
||||
if let Some(frame_len) = self.current_frame_len.take() {
|
||||
if frame_len > 0 {
|
||||
self.current_frame_len = Some(frame_len - 1);
|
||||
} else {
|
||||
self.current_frame_len = self.input.current_frame_len();
|
||||
// Sample rate might have changed
|
||||
self.duration_per_sample = Self::get_duration_per_sample(&self.input);
|
||||
}
|
||||
}
|
||||
|
||||
if self.remaining_duration <= self.duration_per_sample {
|
||||
None
|
||||
} else if let Some(sample) = self.input.next() {
|
||||
let sample = match &self.filter {
|
||||
Some(filter) => filter.apply(sample, self),
|
||||
None => sample,
|
||||
};
|
||||
|
||||
self.remaining_duration -= self.duration_per_sample;
|
||||
|
||||
Some(sample)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: size_hint
|
||||
}
|
||||
|
||||
impl<I> Source for TakeDuration<I>
|
||||
where
|
||||
I: Iterator + Source,
|
||||
I::Item: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
let remaining_nanos = self.remaining_duration.as_secs() * NANOS_PER_SEC
|
||||
+ self.remaining_duration.subsec_nanos() as u64;
|
||||
let nanos_per_sample = self.duration_per_sample.as_secs() * NANOS_PER_SEC
|
||||
+ self.duration_per_sample.subsec_nanos() as u64;
|
||||
let remaining_samples = (remaining_nanos / nanos_per_sample) as usize;
|
||||
|
||||
self.input
|
||||
.current_frame_len()
|
||||
.filter(|value| *value < remaining_samples)
|
||||
.or(Some(remaining_samples))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.input.channels()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.input.sample_rate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
if let Some(duration) = self.input.total_duration() {
|
||||
if duration < self.requested_duration {
|
||||
Some(duration)
|
||||
} else {
|
||||
Some(self.requested_duration)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.input.try_seek(pos)
|
||||
}
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
use std::cmp;
|
||||
use std::time::Duration;
|
||||
|
||||
use cpal::FromSample;
|
||||
|
||||
use crate::conversions::{ChannelCountConverter, DataConverter, SampleRateConverter};
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// An iterator that reads from a `Source` and converts the samples to a specific rate and
|
||||
/// channels count.
|
||||
///
|
||||
/// It implements `Source` as well, but all the data is guaranteed to be in a single frame whose
|
||||
/// channels and samples rate have been passed to `new`.
|
||||
#[derive(Clone)]
|
||||
pub struct UniformSourceIterator<I, D>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
D: Sample,
|
||||
{
|
||||
inner: Option<DataConverter<ChannelCountConverter<SampleRateConverter<Take<I>>>, D>>,
|
||||
target_channels: u16,
|
||||
target_sample_rate: u32,
|
||||
total_duration: Option<Duration>,
|
||||
}
|
||||
|
||||
impl<I, D> UniformSourceIterator<I, D>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
D: Sample,
|
||||
{
|
||||
#[inline]
|
||||
pub fn new(
|
||||
input: I,
|
||||
target_channels: u16,
|
||||
target_sample_rate: u32,
|
||||
) -> UniformSourceIterator<I, D> {
|
||||
let total_duration = input.total_duration();
|
||||
let input = UniformSourceIterator::bootstrap(input, target_channels, target_sample_rate);
|
||||
|
||||
UniformSourceIterator {
|
||||
inner: Some(input),
|
||||
target_channels,
|
||||
target_sample_rate,
|
||||
total_duration,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bootstrap(
|
||||
input: I,
|
||||
target_channels: u16,
|
||||
target_sample_rate: u32,
|
||||
) -> DataConverter<ChannelCountConverter<SampleRateConverter<Take<I>>>, D> {
|
||||
// Limit the frame length to something reasonable
|
||||
let frame_len = input.current_frame_len().map(|x| x.min(32768));
|
||||
|
||||
let from_channels = input.channels();
|
||||
let from_sample_rate = input.sample_rate();
|
||||
|
||||
let input = Take {
|
||||
iter: input,
|
||||
n: frame_len,
|
||||
};
|
||||
let input = SampleRateConverter::new(
|
||||
input,
|
||||
cpal::SampleRate(from_sample_rate),
|
||||
cpal::SampleRate(target_sample_rate),
|
||||
from_channels,
|
||||
);
|
||||
let input = ChannelCountConverter::new(input, from_channels, target_channels);
|
||||
|
||||
DataConverter::new(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, D> Iterator for UniformSourceIterator<I, D>
|
||||
where
|
||||
I: Source,
|
||||
I::Item: Sample,
|
||||
D: FromSample<I::Item> + Sample,
|
||||
{
|
||||
type Item = D;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<D> {
|
||||
if let Some(value) = self.inner.as_mut().unwrap().next() {
|
||||
return Some(value);
|
||||
}
|
||||
|
||||
let input = self
|
||||
.inner
|
||||
.take()
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
.into_inner()
|
||||
.into_inner()
|
||||
.iter;
|
||||
|
||||
let mut input =
|
||||
UniformSourceIterator::bootstrap(input, self.target_channels, self.target_sample_rate);
|
||||
|
||||
let value = input.next();
|
||||
self.inner = Some(input);
|
||||
value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(self.inner.as_ref().unwrap().size_hint().0, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, D> Source for UniformSourceIterator<I, D>
|
||||
where
|
||||
I: Iterator + Source,
|
||||
I::Item: Sample,
|
||||
D: FromSample<I::Item> + Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.target_channels
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.target_sample_rate
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.total_duration
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||
if let Some(input) = self.inner.as_mut() {
|
||||
input
|
||||
.inner_mut()
|
||||
.inner_mut()
|
||||
.inner_mut()
|
||||
.inner_mut()
|
||||
.try_seek(pos)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Take<I> {
|
||||
iter: I,
|
||||
n: Option<usize>,
|
||||
}
|
||||
|
||||
impl<I> Take<I> {
|
||||
#[inline]
|
||||
pub fn inner_mut(&mut self) -> &mut I {
|
||||
&mut self.iter
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for Take<I>
|
||||
where
|
||||
I: Iterator,
|
||||
{
|
||||
type Item = <I as Iterator>::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<<I as Iterator>::Item> {
|
||||
if let Some(n) = &mut self.n {
|
||||
if *n != 0 {
|
||||
*n -= 1;
|
||||
self.iter.next()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
self.iter.next()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if let Some(n) = self.n {
|
||||
let (lower, upper) = self.iter.size_hint();
|
||||
|
||||
let lower = cmp::min(lower, n);
|
||||
|
||||
let upper = match upper {
|
||||
Some(x) if x < n => Some(x),
|
||||
_ => Some(n),
|
||||
};
|
||||
|
||||
(lower, upper)
|
||||
} else {
|
||||
self.iter.size_hint()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ExactSizeIterator for Take<I> where I: ExactSizeIterator {}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{Sample, Source};
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
/// An infinite source that produces zero.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Zero<S> {
|
||||
channels: u16,
|
||||
sample_rate: u32,
|
||||
num_samples: Option<usize>,
|
||||
marker: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S> Zero<S> {
|
||||
#[inline]
|
||||
pub fn new(channels: u16, sample_rate: u32) -> Zero<S> {
|
||||
Zero {
|
||||
channels,
|
||||
sample_rate,
|
||||
num_samples: None,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn new_samples(channels: u16, sample_rate: u32, num_samples: usize) -> Zero<S> {
|
||||
Zero {
|
||||
channels,
|
||||
sample_rate,
|
||||
num_samples: Some(num_samples),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Iterator for Zero<S>
|
||||
where
|
||||
S: Sample,
|
||||
{
|
||||
type Item = S;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<S> {
|
||||
if let Some(num_samples) = self.num_samples {
|
||||
if num_samples > 0 {
|
||||
self.num_samples = Some(num_samples - 1);
|
||||
Some(S::zero_value())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(S::zero_value())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Source for Zero<S>
|
||||
where
|
||||
S: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
self.num_samples
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.channels
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
use std::f32;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use cpal::FromSample;
|
||||
|
||||
use crate::source::{SeekError, Spatial};
|
||||
use crate::stream::{OutputStreamHandle, PlayError};
|
||||
use crate::{Sample, Sink, Source};
|
||||
|
||||
pub struct SpatialSink {
|
||||
sink: Sink,
|
||||
positions: Arc<Mutex<SoundPositions>>,
|
||||
}
|
||||
|
||||
struct SoundPositions {
|
||||
emitter_position: [f32; 3],
|
||||
left_ear: [f32; 3],
|
||||
right_ear: [f32; 3],
|
||||
}
|
||||
|
||||
impl SpatialSink {
|
||||
/// Builds a new `SpatialSink`.
|
||||
pub fn try_new(
|
||||
stream: &OutputStreamHandle,
|
||||
emitter_position: [f32; 3],
|
||||
left_ear: [f32; 3],
|
||||
right_ear: [f32; 3],
|
||||
) -> Result<SpatialSink, PlayError> {
|
||||
Ok(SpatialSink {
|
||||
sink: Sink::try_new(stream)?,
|
||||
positions: Arc::new(Mutex::new(SoundPositions {
|
||||
emitter_position,
|
||||
left_ear,
|
||||
right_ear,
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the position of the sound emitter in 3 dimensional space.
|
||||
pub fn set_emitter_position(&self, pos: [f32; 3]) {
|
||||
self.positions.lock().unwrap().emitter_position = pos;
|
||||
}
|
||||
|
||||
/// Sets the position of the left ear in 3 dimensional space.
|
||||
pub fn set_left_ear_position(&self, pos: [f32; 3]) {
|
||||
self.positions.lock().unwrap().left_ear = pos;
|
||||
}
|
||||
|
||||
/// Sets the position of the right ear in 3 dimensional space.
|
||||
pub fn set_right_ear_position(&self, pos: [f32; 3]) {
|
||||
self.positions.lock().unwrap().right_ear = pos;
|
||||
}
|
||||
|
||||
/// Appends a sound to the queue of sounds to play.
|
||||
#[inline]
|
||||
pub fn append<S>(&self, source: S)
|
||||
where
|
||||
S: Source + Send + 'static,
|
||||
f32: FromSample<S::Item>,
|
||||
S::Item: Sample + Send,
|
||||
{
|
||||
let positions = self.positions.clone();
|
||||
let pos_lock = self.positions.lock().unwrap();
|
||||
let source = Spatial::new(
|
||||
source,
|
||||
pos_lock.emitter_position,
|
||||
pos_lock.left_ear,
|
||||
pos_lock.right_ear,
|
||||
)
|
||||
.periodic_access(Duration::from_millis(10), move |i| {
|
||||
let pos = positions.lock().unwrap();
|
||||
i.set_positions(pos.emitter_position, pos.left_ear, pos.right_ear);
|
||||
});
|
||||
self.sink.append(source);
|
||||
}
|
||||
|
||||
// Gets the volume of the sound.
|
||||
///
|
||||
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than 1.0 will
|
||||
/// multiply each sample by this value.
|
||||
#[inline]
|
||||
pub fn volume(&self) -> f32 {
|
||||
self.sink.volume()
|
||||
}
|
||||
|
||||
/// Changes the volume of the sound.
|
||||
///
|
||||
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than 1.0 will
|
||||
/// multiply each sample by this value.
|
||||
#[inline]
|
||||
pub fn set_volume(&self, value: f32) {
|
||||
self.sink.set_volume(value);
|
||||
}
|
||||
|
||||
/// Gets the speed of the sound.
|
||||
///
|
||||
/// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0` will
|
||||
/// change the play speed of the sound.
|
||||
#[inline]
|
||||
pub fn speed(&self) -> f32 {
|
||||
self.sink.speed()
|
||||
}
|
||||
|
||||
/// Changes the speed of the sound.
|
||||
///
|
||||
/// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0` will
|
||||
/// change the play speed of the sound.
|
||||
#[inline]
|
||||
pub fn set_speed(&self, value: f32) {
|
||||
self.sink.set_speed(value)
|
||||
}
|
||||
|
||||
/// Resumes playback of a paused sound.
|
||||
///
|
||||
/// No effect if not paused.
|
||||
#[inline]
|
||||
pub fn play(&self) {
|
||||
self.sink.play();
|
||||
}
|
||||
|
||||
/// Pauses playback of this sink.
|
||||
///
|
||||
/// No effect if already paused.
|
||||
///
|
||||
/// A paused sound can be resumed with `play()`.
|
||||
pub fn pause(&self) {
|
||||
self.sink.pause();
|
||||
}
|
||||
|
||||
/// Gets if a sound is paused
|
||||
///
|
||||
/// Sounds can be paused and resumed using pause() and play(). This gets if a sound is paused.
|
||||
pub fn is_paused(&self) -> bool {
|
||||
self.sink.is_paused()
|
||||
}
|
||||
|
||||
/// Removes all currently loaded `Source`s from the `SpatialSink` and pauses it.
|
||||
///
|
||||
/// See `pause()` for information about pausing a `Sink`.
|
||||
#[inline]
|
||||
pub fn clear(&self) {
|
||||
self.sink.clear();
|
||||
}
|
||||
|
||||
/// Stops the sink by emptying the queue.
|
||||
#[inline]
|
||||
pub fn stop(&self) {
|
||||
self.sink.stop()
|
||||
}
|
||||
|
||||
/// Destroys the sink without stopping the sounds that are still playing.
|
||||
#[inline]
|
||||
pub fn detach(self) {
|
||||
self.sink.detach();
|
||||
}
|
||||
|
||||
/// Sleeps the current thread until the sound ends.
|
||||
#[inline]
|
||||
pub fn sleep_until_end(&self) {
|
||||
self.sink.sleep_until_end();
|
||||
}
|
||||
|
||||
/// Returns true if this sink has no more sounds to play.
|
||||
#[inline]
|
||||
pub fn empty(&self) -> bool {
|
||||
self.sink.empty()
|
||||
}
|
||||
|
||||
/// Returns the number of sounds currently in the queue.
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.sink.len()
|
||||
}
|
||||
|
||||
/// Attempts to seek to a given position in the current source.
|
||||
///
|
||||
/// This blocks between 0 and ~5 milliseconds.
|
||||
///
|
||||
/// As long as the duration of the source is known seek is guaranteed to saturate
|
||||
/// at the end of the source. For example given a source that reports a total duration
|
||||
/// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to
|
||||
/// 42 seconds.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function will return [`SeekError::NotSupported`] if one of the underlying
|
||||
/// sources does not support seeking.
|
||||
///
|
||||
/// It will return an error if an implementation ran
|
||||
/// into one during the seek.
|
||||
///
|
||||
/// When seeking beyond the end of a source this
|
||||
/// function might return an error if the duration of the source is not known.
|
||||
pub fn try_seek(&self, pos: Duration) -> Result<(), SeekError> {
|
||||
self.sink.try_seek(pos)
|
||||
}
|
||||
|
||||
/// Returns the position of the sound that's being played.
|
||||
///
|
||||
/// This takes into account any speedup or delay applied.
|
||||
///
|
||||
/// Example: if you apply a speedup of *2* to an mp3 decoder source and
|
||||
/// [`get_pos()`](Sink::get_pos) returns *5s* then the position in the mp3
|
||||
/// recording is *10s* from its start.
|
||||
#[inline]
|
||||
pub fn get_pos(&self) -> Duration {
|
||||
self.sink.get_pos()
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
//! A simple source of samples coming from a static buffer.
|
||||
//!
|
||||
//! The `StaticSamplesBuffer` struct can be used to treat a list of values as a `Source`.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! use rodio::static_buffer::StaticSamplesBuffer;
|
||||
//! let _ = StaticSamplesBuffer::new(1, 44100, &[1i16, 2, 3, 4, 5, 6]);
|
||||
//! ```
|
||||
//!
|
||||
|
||||
use std::slice::Iter as SliceIter;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::SeekError;
|
||||
use crate::{Sample, Source};
|
||||
|
||||
/// A buffer of samples treated as a source.
|
||||
#[derive(Clone)]
|
||||
pub struct StaticSamplesBuffer<S>
|
||||
where
|
||||
S: 'static,
|
||||
{
|
||||
data: SliceIter<'static, S>,
|
||||
channels: u16,
|
||||
sample_rate: u32,
|
||||
duration: Duration,
|
||||
}
|
||||
|
||||
impl<S> StaticSamplesBuffer<S>
|
||||
where
|
||||
S: Sample,
|
||||
{
|
||||
/// Builds a new `StaticSamplesBuffer`.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// - Panics if the number of channels is zero.
|
||||
/// - Panics if the samples rate is zero.
|
||||
/// - Panics if the length of the buffer is larger than approximately 16 billion elements.
|
||||
/// This is because the calculation of the duration would overflow.
|
||||
///
|
||||
pub fn new(channels: u16, sample_rate: u32, data: &'static [S]) -> StaticSamplesBuffer<S> {
|
||||
assert!(channels != 0);
|
||||
assert!(sample_rate != 0);
|
||||
|
||||
let duration_ns = 1_000_000_000u64.checked_mul(data.len() as u64).unwrap()
|
||||
/ sample_rate as u64
|
||||
/ channels as u64;
|
||||
let duration = Duration::new(
|
||||
duration_ns / 1_000_000_000,
|
||||
(duration_ns % 1_000_000_000) as u32,
|
||||
);
|
||||
|
||||
StaticSamplesBuffer {
|
||||
data: data.iter(),
|
||||
channels,
|
||||
sample_rate,
|
||||
duration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Source for StaticSamplesBuffer<S>
|
||||
where
|
||||
S: Sample,
|
||||
{
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
self.channels
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
Some(self.duration)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
|
||||
Err(SeekError::NotSupported {
|
||||
underlying_source: std::any::type_name::<Self>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Iterator for StaticSamplesBuffer<S>
|
||||
where
|
||||
S: Sample + Clone,
|
||||
{
|
||||
type Item = S;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<S> {
|
||||
self.data.next().cloned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.data.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::source::Source;
|
||||
use crate::static_buffer::StaticSamplesBuffer;
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let _ = StaticSamplesBuffer::new(1, 44100, &[0i16, 0, 0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panic_if_zero_channels() {
|
||||
StaticSamplesBuffer::new(0, 44100, &[0i16, 0, 0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panic_if_zero_sample_rate() {
|
||||
StaticSamplesBuffer::new(1, 0, &[0i16, 0, 0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_basic() {
|
||||
let buf = StaticSamplesBuffer::new(2, 2, &[0i16, 0, 0, 0, 0, 0]);
|
||||
let dur = buf.total_duration().unwrap();
|
||||
assert_eq!(dur.as_secs(), 1);
|
||||
assert_eq!(dur.subsec_nanos(), 500_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iteration() {
|
||||
let mut buf = StaticSamplesBuffer::new(1, 44100, &[1i16, 2, 3, 4, 5, 6]);
|
||||
assert_eq!(buf.next(), Some(1));
|
||||
assert_eq!(buf.next(), Some(2));
|
||||
assert_eq!(buf.next(), Some(3));
|
||||
assert_eq!(buf.next(), Some(4));
|
||||
assert_eq!(buf.next(), Some(5));
|
||||
assert_eq!(buf.next(), Some(6));
|
||||
assert_eq!(buf.next(), None);
|
||||
}
|
||||
}
|
||||
Vendored
+358
@@ -0,0 +1,358 @@
|
||||
use std::io::{Read, Seek};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::{error, fmt};
|
||||
|
||||
use crate::decoder;
|
||||
use crate::dynamic_mixer::{self, DynamicMixerController};
|
||||
use crate::sink::Sink;
|
||||
use crate::source::Source;
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use cpal::{Sample, SupportedStreamConfig};
|
||||
|
||||
/// `cpal::Stream` container. Also see the more useful `OutputStreamHandle`.
|
||||
///
|
||||
/// If this is dropped playback will end & attached `OutputStreamHandle`s will no longer work.
|
||||
pub struct OutputStream {
|
||||
mixer: Arc<DynamicMixerController<f32>>,
|
||||
_stream: cpal::Stream,
|
||||
}
|
||||
|
||||
/// More flexible handle to a `OutputStream` that provides playback.
|
||||
#[derive(Clone)]
|
||||
pub struct OutputStreamHandle {
|
||||
mixer: Weak<DynamicMixerController<f32>>,
|
||||
}
|
||||
|
||||
impl OutputStream {
|
||||
/// Returns a new stream & handle using the given output device and the default output
|
||||
/// configuration.
|
||||
pub fn try_from_device(
|
||||
device: &cpal::Device,
|
||||
) -> Result<(Self, OutputStreamHandle), StreamError> {
|
||||
let default_config = device.default_output_config()?;
|
||||
OutputStream::try_from_device_config(device, default_config)
|
||||
}
|
||||
|
||||
/// Returns a new stream & handle using the given device and stream config.
|
||||
///
|
||||
/// If the supplied `SupportedStreamConfig` is invalid for the device this function will
|
||||
/// fail to create an output stream and instead return a `StreamError`
|
||||
pub fn try_from_device_config(
|
||||
device: &cpal::Device,
|
||||
config: SupportedStreamConfig,
|
||||
) -> Result<(Self, OutputStreamHandle), StreamError> {
|
||||
let (mixer, _stream) = device.try_new_output_stream_config(config)?;
|
||||
_stream.play()?;
|
||||
let out = Self { mixer, _stream };
|
||||
let handle = OutputStreamHandle {
|
||||
mixer: Arc::downgrade(&out.mixer),
|
||||
};
|
||||
Ok((out, handle))
|
||||
}
|
||||
|
||||
/// Return a new stream & handle using the default output device.
|
||||
///
|
||||
/// On failure will fallback to trying any non-default output devices.
|
||||
pub fn try_default() -> Result<(Self, OutputStreamHandle), StreamError> {
|
||||
let default_device = cpal::default_host()
|
||||
.default_output_device()
|
||||
.ok_or(StreamError::NoDevice)?;
|
||||
|
||||
let default_stream = Self::try_from_device(&default_device);
|
||||
|
||||
default_stream.or_else(|original_err| {
|
||||
// default device didn't work, try other ones
|
||||
let mut devices = match cpal::default_host().output_devices() {
|
||||
Ok(d) => d,
|
||||
Err(_) => return Err(original_err),
|
||||
};
|
||||
|
||||
devices
|
||||
.find_map(|d| Self::try_from_device(&d).ok())
|
||||
.ok_or(original_err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputStreamHandle {
|
||||
/// Plays a source with a device until it ends.
|
||||
pub fn play_raw<S>(&self, source: S) -> Result<(), PlayError>
|
||||
where
|
||||
S: Source<Item = f32> + Send + 'static,
|
||||
{
|
||||
let mixer = self.mixer.upgrade().ok_or(PlayError::NoDevice)?;
|
||||
mixer.add(source);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Plays a sound once. Returns a `Sink` that can be used to control the sound.
|
||||
pub fn play_once<R>(&self, input: R) -> Result<Sink, PlayError>
|
||||
where
|
||||
R: Read + Seek + Send + Sync + 'static,
|
||||
{
|
||||
let input = decoder::Decoder::new(input)?;
|
||||
let sink = Sink::try_new(self)?;
|
||||
sink.append(input);
|
||||
Ok(sink)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error occurred while attempting to play a sound.
|
||||
#[derive(Debug)]
|
||||
pub enum PlayError {
|
||||
/// Attempting to decode the audio failed.
|
||||
DecoderError(decoder::DecoderError),
|
||||
/// The output device was lost.
|
||||
NoDevice,
|
||||
}
|
||||
|
||||
impl From<decoder::DecoderError> for PlayError {
|
||||
fn from(err: decoder::DecoderError) -> Self {
|
||||
Self::DecoderError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PlayError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::DecoderError(e) => e.fmt(f),
|
||||
Self::NoDevice => write!(f, "NoDevice"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for PlayError {
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match self {
|
||||
Self::DecoderError(e) => Some(e),
|
||||
Self::NoDevice => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StreamError {
|
||||
PlayStreamError(cpal::PlayStreamError),
|
||||
DefaultStreamConfigError(cpal::DefaultStreamConfigError),
|
||||
BuildStreamError(cpal::BuildStreamError),
|
||||
SupportedStreamConfigsError(cpal::SupportedStreamConfigsError),
|
||||
NoDevice,
|
||||
}
|
||||
|
||||
impl From<cpal::DefaultStreamConfigError> for StreamError {
|
||||
fn from(err: cpal::DefaultStreamConfigError) -> Self {
|
||||
Self::DefaultStreamConfigError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cpal::SupportedStreamConfigsError> for StreamError {
|
||||
fn from(err: cpal::SupportedStreamConfigsError) -> Self {
|
||||
Self::SupportedStreamConfigsError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cpal::BuildStreamError> for StreamError {
|
||||
fn from(err: cpal::BuildStreamError) -> Self {
|
||||
Self::BuildStreamError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cpal::PlayStreamError> for StreamError {
|
||||
fn from(err: cpal::PlayStreamError) -> Self {
|
||||
Self::PlayStreamError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for StreamError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::PlayStreamError(e) => e.fmt(f),
|
||||
Self::BuildStreamError(e) => e.fmt(f),
|
||||
Self::DefaultStreamConfigError(e) => e.fmt(f),
|
||||
Self::SupportedStreamConfigsError(e) => e.fmt(f),
|
||||
Self::NoDevice => write!(f, "NoDevice"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for StreamError {
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match self {
|
||||
Self::PlayStreamError(e) => Some(e),
|
||||
Self::BuildStreamError(e) => Some(e),
|
||||
Self::DefaultStreamConfigError(e) => Some(e),
|
||||
Self::SupportedStreamConfigsError(e) => Some(e),
|
||||
Self::NoDevice => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extensions to `cpal::Device`
|
||||
pub(crate) trait CpalDeviceExt {
|
||||
fn new_output_stream_with_format(
|
||||
&self,
|
||||
format: cpal::SupportedStreamConfig,
|
||||
) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), cpal::BuildStreamError>;
|
||||
|
||||
fn try_new_output_stream_config(
|
||||
&self,
|
||||
config: cpal::SupportedStreamConfig,
|
||||
) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), StreamError>;
|
||||
}
|
||||
|
||||
impl CpalDeviceExt for cpal::Device {
|
||||
fn new_output_stream_with_format(
|
||||
&self,
|
||||
format: cpal::SupportedStreamConfig,
|
||||
) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), cpal::BuildStreamError> {
|
||||
let (mixer_tx, mut mixer_rx) =
|
||||
dynamic_mixer::mixer::<f32>(format.channels(), format.sample_rate().0);
|
||||
|
||||
let error_callback = |err| eprintln!("an error occurred on output stream: {}", err);
|
||||
|
||||
match format.sample_format() {
|
||||
cpal::SampleFormat::F32 => self.build_output_stream::<f32, _, _>(
|
||||
&format.config(),
|
||||
move |data, _| {
|
||||
data.iter_mut()
|
||||
.for_each(|d| *d = mixer_rx.next().unwrap_or(0f32))
|
||||
},
|
||||
error_callback,
|
||||
None,
|
||||
),
|
||||
cpal::SampleFormat::F64 => self.build_output_stream::<f64, _, _>(
|
||||
&format.config(),
|
||||
move |data, _| {
|
||||
data.iter_mut()
|
||||
.for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0f64))
|
||||
},
|
||||
error_callback,
|
||||
None,
|
||||
),
|
||||
cpal::SampleFormat::I8 => self.build_output_stream::<i8, _, _>(
|
||||
&format.config(),
|
||||
move |data, _| {
|
||||
data.iter_mut()
|
||||
.for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i8))
|
||||
},
|
||||
error_callback,
|
||||
None,
|
||||
),
|
||||
cpal::SampleFormat::I16 => self.build_output_stream::<i16, _, _>(
|
||||
&format.config(),
|
||||
move |data, _| {
|
||||
data.iter_mut()
|
||||
.for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i16))
|
||||
},
|
||||
error_callback,
|
||||
None,
|
||||
),
|
||||
cpal::SampleFormat::I32 => self.build_output_stream::<i32, _, _>(
|
||||
&format.config(),
|
||||
move |data, _| {
|
||||
data.iter_mut()
|
||||
.for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i32))
|
||||
},
|
||||
error_callback,
|
||||
None,
|
||||
),
|
||||
cpal::SampleFormat::I64 => self.build_output_stream::<i64, _, _>(
|
||||
&format.config(),
|
||||
move |data, _| {
|
||||
data.iter_mut()
|
||||
.for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i64))
|
||||
},
|
||||
error_callback,
|
||||
None,
|
||||
),
|
||||
cpal::SampleFormat::U8 => self.build_output_stream::<u8, _, _>(
|
||||
&format.config(),
|
||||
move |data, _| {
|
||||
data.iter_mut().for_each(|d| {
|
||||
*d = mixer_rx
|
||||
.next()
|
||||
.map(Sample::from_sample)
|
||||
.unwrap_or(u8::MAX / 2)
|
||||
})
|
||||
},
|
||||
error_callback,
|
||||
None,
|
||||
),
|
||||
cpal::SampleFormat::U16 => self.build_output_stream::<u16, _, _>(
|
||||
&format.config(),
|
||||
move |data, _| {
|
||||
data.iter_mut().for_each(|d| {
|
||||
*d = mixer_rx
|
||||
.next()
|
||||
.map(Sample::from_sample)
|
||||
.unwrap_or(u16::MAX / 2)
|
||||
})
|
||||
},
|
||||
error_callback,
|
||||
None,
|
||||
),
|
||||
cpal::SampleFormat::U32 => self.build_output_stream::<u32, _, _>(
|
||||
&format.config(),
|
||||
move |data, _| {
|
||||
data.iter_mut().for_each(|d| {
|
||||
*d = mixer_rx
|
||||
.next()
|
||||
.map(Sample::from_sample)
|
||||
.unwrap_or(u32::MAX / 2)
|
||||
})
|
||||
},
|
||||
error_callback,
|
||||
None,
|
||||
),
|
||||
cpal::SampleFormat::U64 => self.build_output_stream::<u64, _, _>(
|
||||
&format.config(),
|
||||
move |data, _| {
|
||||
data.iter_mut().for_each(|d| {
|
||||
*d = mixer_rx
|
||||
.next()
|
||||
.map(Sample::from_sample)
|
||||
.unwrap_or(u64::MAX / 2)
|
||||
})
|
||||
},
|
||||
error_callback,
|
||||
None,
|
||||
),
|
||||
_ => return Err(cpal::BuildStreamError::StreamConfigNotSupported),
|
||||
}
|
||||
.map(|stream| (mixer_tx, stream))
|
||||
}
|
||||
|
||||
fn try_new_output_stream_config(
|
||||
&self,
|
||||
config: SupportedStreamConfig,
|
||||
) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), StreamError> {
|
||||
self.new_output_stream_with_format(config).or_else(|err| {
|
||||
// look through all supported formats to see if another works
|
||||
supported_output_formats(self)?
|
||||
.find_map(|format| self.new_output_stream_with_format(format).ok())
|
||||
// return original error if nothing works
|
||||
.ok_or(StreamError::BuildStreamError(err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// All the supported output formats with sample rates
|
||||
fn supported_output_formats(
|
||||
device: &cpal::Device,
|
||||
) -> Result<impl Iterator<Item = cpal::SupportedStreamConfig>, StreamError> {
|
||||
const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100);
|
||||
|
||||
let mut supported: Vec<_> = device.supported_output_configs()?.collect();
|
||||
supported.sort_by(|a, b| b.cmp_default_heuristics(a));
|
||||
|
||||
Ok(supported.into_iter().flat_map(|sf| {
|
||||
let max_rate = sf.max_sample_rate();
|
||||
let min_rate = sf.min_sample_rate();
|
||||
let mut formats = vec![sf.with_max_sample_rate()];
|
||||
if HZ_44100 < max_rate && HZ_44100 > min_rate {
|
||||
formats.push(sf.with_sample_rate(HZ_44100))
|
||||
}
|
||||
formats.push(sf.with_sample_rate(min_rate));
|
||||
formats
|
||||
}))
|
||||
}
|
||||
Reference in new issue
Block a user