├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── audio-core ├── Cargo.toml ├── README.md └── src │ ├── buf.rs │ ├── buf │ ├── limit.rs │ ├── macros.rs │ ├── skip.rs │ └── tail.rs │ ├── buf_mut.rs │ ├── channel.rs │ ├── channel_mut.rs │ ├── exact_size_buf.rs │ ├── frame.rs │ ├── frame_mut.rs │ ├── interleaved_buf.rs │ ├── interleaved_buf_mut.rs │ ├── lib.rs │ ├── linear_channel.rs │ ├── linear_channel_mut.rs │ ├── read_buf.rs │ ├── resizable_buf.rs │ ├── sample.rs │ ├── translate.rs │ ├── translate │ └── tests.rs │ ├── uniform_buf.rs │ └── write_buf.rs ├── audio-device-alsa-sys ├── Cargo.toml ├── README.md ├── build.rs └── src │ ├── bindings.rs │ └── lib.rs ├── audio-device-pipewire-sys ├── Cargo.toml ├── README.md ├── build.rs └── src │ ├── bindings.rs │ └── lib.rs ├── audio-device-pulse-sys ├── Cargo.toml ├── README.md ├── build.rs └── src │ ├── bindings.rs │ └── lib.rs ├── audio-device ├── Cargo.toml ├── README.md ├── examples │ ├── alsa-async.rs │ ├── alsa-list.rs │ ├── alsa.rs │ ├── events.rs │ ├── poll.rs │ ├── pulse.rs │ ├── wasapi-async.rs │ └── wasapi.rs └── src │ ├── alsa │ ├── access_mask.rs │ ├── async_writer.rs │ ├── card.rs │ ├── cell.rs │ ├── channel_area.rs │ ├── configurator.rs │ ├── control.rs │ ├── enums.rs │ ├── format_mask.rs │ ├── hardware_parameters.rs │ ├── mod.rs │ ├── pcm.rs │ ├── sample.rs │ ├── software_parameters.rs │ └── writer.rs │ ├── error.rs │ ├── lib.rs │ ├── libc.rs │ ├── loom │ ├── mod.rs │ └── sync.rs │ ├── macros.rs │ ├── pipewire │ ├── main_loop.rs │ ├── mod.rs │ └── property_list.rs │ ├── pulse │ ├── context.rs │ ├── enums.rs │ ├── error.rs │ ├── main_loop.rs │ ├── mod.rs │ └── property_list.rs │ ├── runtime │ ├── atomic_waker.rs │ ├── events.rs │ ├── mod.rs │ └── poll.rs │ ├── unix │ ├── mod.rs │ └── poll.rs │ ├── wasapi │ ├── buffer_mut.rs │ ├── client.rs │ ├── initialized_client.rs │ ├── mod.rs │ ├── render_client.rs │ └── sample.rs │ └── windows │ ├── event.rs │ └── mod.rs ├── audio-generator ├── Cargo.toml ├── README.md └── src │ ├── generator.rs │ ├── generator │ ├── amplitude.rs │ └── iter.rs │ ├── lib.rs │ └── sine.rs ├── audio ├── Cargo.toml ├── README.md └── src │ ├── buf.rs │ ├── buf │ ├── dynamic.rs │ ├── dynamic │ │ └── iter.rs │ ├── interleaved.rs │ ├── interleaved │ │ ├── buf.rs │ │ └── iter.rs │ ├── sequential.rs │ └── sequential │ │ ├── buf.rs │ │ └── iter.rs │ ├── channel.rs │ ├── channel │ ├── interleaved.rs │ ├── interleaved │ │ ├── macros.rs │ │ └── tests.rs │ ├── linear.rs │ └── linear │ │ ├── iter.rs │ │ └── macros.rs │ ├── frame.rs │ ├── frame │ ├── interleaved.rs │ └── sequential.rs │ ├── io.rs │ ├── io │ ├── macros.rs │ ├── read.rs │ ├── read_write.rs │ ├── utils.rs │ └── write.rs │ ├── lib.rs │ ├── macros.rs │ ├── slice.rs │ ├── tests │ ├── byte_arrays.rs │ ├── copy_channel.rs │ ├── dynamic.rs │ ├── interleaved.rs │ ├── io.rs │ ├── mod.rs │ └── sequential.rs │ ├── utils.rs │ ├── wrap.rs │ └── wrap │ ├── dynamic.rs │ ├── interleaved.rs │ └── sequential.rs ├── examples ├── Cargo.toml └── src │ └── bin │ └── play-mp3.rs ├── generate ├── Cargo.toml ├── README.md ├── alsa.h ├── pipewire.h ├── pulse.h └── src │ └── bin │ ├── generate-alsa.rs │ ├── generate-pipewire.rs │ └── generate-pulse.rs ├── ste ├── Cargo.toml ├── README.md ├── benches │ └── benches.rs ├── examples │ ├── recover_from_panic.rs │ ├── simple_async.rs │ ├── submit.rs │ ├── submit_async.rs │ └── submit_async_threaded.rs └── src │ ├── lib.rs │ ├── linked_list.rs │ ├── loom.rs │ ├── misc.rs │ ├── parker.rs │ ├── tag.rs │ ├── tests.rs │ ├── wait_future.rs │ └── worker.rs └── tools └── readmes.rn /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - main 8 | schedule: 9 | - cron: '36 13 * * 4' 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | msrv: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: dtolnay/rust-toolchain@1.70 21 | - uses: Swatinem/rust-cache@v2 22 | - run: cargo build --all-features -p audio -p audio-core -p audio-generator -p ste 23 | 24 | test: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: dtolnay/rust-toolchain@stable 29 | - uses: Swatinem/rust-cache@v2 30 | - run: cargo test --all-features --all-targets -p audio -p audio-core -p audio-generator -p ste 31 | - run: cargo test --no-default-features --all-targets -p audio -p audio-core -p audio-generator 32 | - run: cargo test --all-features --doc -p audio -p audio-core -p audio-generator -p ste 33 | 34 | test-wasapi: 35 | runs-on: windows-latest 36 | steps: 37 | - uses: actions/checkout@v4 38 | - uses: dtolnay/rust-toolchain@stable 39 | - uses: Swatinem/rust-cache@v2 40 | - run: cargo test --all-targets -F wasapi -p audio-device 41 | - run: cargo test --doc -F wasapi -p audio-device 42 | 43 | test-alsa: 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: dtolnay/rust-toolchain@stable 48 | - uses: Swatinem/rust-cache@v2 49 | - run: sudo apt install libasound2-dev 50 | - run: cargo test --all-targets -p audio-device -F alsa 51 | - run: cargo test --doc -p audio-device -F alsa 52 | 53 | test-pulse: 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v4 57 | - uses: dtolnay/rust-toolchain@stable 58 | - uses: Swatinem/rust-cache@v2 59 | - run: sudo apt install libpulse-dev 60 | - run: cargo test --all-targets -p audio-device -F pulse 61 | - run: cargo test --doc -p audio-device -F pulse 62 | 63 | clippy: 64 | runs-on: ubuntu-latest 65 | steps: 66 | - uses: actions/checkout@v4 67 | - uses: dtolnay/rust-toolchain@stable 68 | with: 69 | components: clippy 70 | - run: cargo clippy --all-targets -p audio -p audio-core -p audio-generator -p ste -- -D warnings 71 | 72 | rustfmt: 73 | runs-on: ubuntu-latest 74 | steps: 75 | - uses: actions/checkout@v4 76 | - uses: dtolnay/rust-toolchain@stable 77 | with: 78 | components: rustfmt 79 | - run: cargo fmt --all --check 80 | 81 | docs: 82 | runs-on: ubuntu-latest 83 | steps: 84 | - uses: actions/checkout@v4 85 | - uses: dtolnay/rust-toolchain@stable 86 | - uses: Swatinem/rust-cache@v2 87 | - run: cargo doc -p audio -p audio-core --all-features 88 | env: 89 | RUSTFLAGS: --cfg docsrs 90 | RUSTDOCFLAGS: --cfg docsrs 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "examples", 6 | "audio", 7 | "audio-core", 8 | "audio-device", 9 | "audio-generator", 10 | "generate", 11 | "audio-device-alsa-sys", 12 | "audio-device-pulse-sys", 13 | "audio-device-pipewire-sys", 14 | "ste", 15 | ] 16 | 17 | default-members = [ 18 | "audio", 19 | "audio-core", 20 | "audio-device", 21 | "audio-generator", 22 | "generate", 23 | "ste", 24 | ] 25 | 26 | [patch.'https://github.com/udoprog/audio'] 27 | audio = { path = "audio" } 28 | audio-core = { path = "audio-core" } 29 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /audio-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "audio-core" 3 | version = "0.2.0" 4 | authors = ["John-John Tedro "] 5 | edition = "2018" 6 | rust-version = "1.70" 7 | description = "The core audio traits" 8 | documentation = "https://docs.rs/audio" 9 | readme = "README.md" 10 | homepage = "https://github.com/udoprog/audio" 11 | repository = "https://github.com/udoprog/audio" 12 | license = "MIT OR Apache-2.0" 13 | keywords = ["audio", "buffer", "dsp"] 14 | categories = ["multimedia::audio"] 15 | 16 | [features] 17 | default = ["std"] 18 | std = [] 19 | 20 | [dev-dependencies] 21 | audio = { version = "0.2.0", path = "../audio" } 22 | -------------------------------------------------------------------------------- /audio-core/README.md: -------------------------------------------------------------------------------- 1 | # audio-core 2 | 3 | [github](https://github.com/udoprog/audio) 4 | [crates.io](https://crates.io/crates/audio-core) 5 | [docs.rs](https://docs.rs/audio-core) 6 | [build status](https://github.com/udoprog/audio/actions?query=branch%3Amain) 7 | 8 | The core [audio] traits. 9 | 10 | If you want to build an audio component that is completely agnostic to the 11 | shape of any one given audio buffer you can add a dependency directly to 12 | these traits instead of depending on all of the [audio] crate. 13 | 14 | [audio]: https://docs.rs/audio 15 | -------------------------------------------------------------------------------- /audio-core/src/buf/limit.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buf, BufMut, Channel, ChannelMut, ExactSizeBuf, ReadBuf}; 2 | 3 | /// A buffer that has been limited. 4 | /// 5 | /// See [Buf::limit]. 6 | pub struct Limit { 7 | buf: B, 8 | limit: usize, 9 | } 10 | 11 | impl Limit { 12 | /// Construct a new limited buffer. 13 | pub(crate) fn new(buf: B, limit: usize) -> Self { 14 | Self { buf, limit } 15 | } 16 | } 17 | 18 | impl Buf for Limit 19 | where 20 | B: Buf, 21 | { 22 | type Sample = B::Sample; 23 | 24 | type Channel<'this> 25 | = B::Channel<'this> 26 | where 27 | Self: 'this; 28 | 29 | type IterChannels<'this> 30 | = IterChannels> 31 | where 32 | Self: 'this; 33 | 34 | fn frames_hint(&self) -> Option { 35 | let frames = self.buf.frames_hint()?; 36 | Some(usize::min(frames, self.limit)) 37 | } 38 | 39 | fn channels(&self) -> usize { 40 | self.buf.channels() 41 | } 42 | 43 | fn get_channel(&self, channel: usize) -> Option> { 44 | Some(self.buf.get_channel(channel)?.limit(self.limit)) 45 | } 46 | 47 | fn iter_channels(&self) -> Self::IterChannels<'_> { 48 | IterChannels { 49 | iter: self.buf.iter_channels(), 50 | limit: self.limit, 51 | } 52 | } 53 | } 54 | 55 | impl BufMut for Limit 56 | where 57 | B: BufMut, 58 | { 59 | type ChannelMut<'this> 60 | = B::ChannelMut<'this> 61 | where 62 | Self: 'this; 63 | 64 | type IterChannelsMut<'this> 65 | = IterChannelsMut> 66 | where 67 | Self: 'this; 68 | 69 | fn get_channel_mut(&mut self, channel: usize) -> Option> { 70 | Some(self.buf.get_channel_mut(channel)?.limit(self.limit)) 71 | } 72 | 73 | fn copy_channel(&mut self, from: usize, to: usize) 74 | where 75 | Self::Sample: Copy, 76 | { 77 | self.buf.copy_channel(from, to); 78 | } 79 | 80 | fn iter_channels_mut(&mut self) -> Self::IterChannelsMut<'_> { 81 | IterChannelsMut { 82 | iter: self.buf.iter_channels_mut(), 83 | limit: self.limit, 84 | } 85 | } 86 | } 87 | 88 | /// [Limit] adjusts the implementation of [ExactSizeBuf] to take the frame 89 | /// limiting into account. 90 | /// 91 | /// ``` 92 | /// use audio::{Buf, ExactSizeBuf}; 93 | /// 94 | /// let buf = audio::interleaved![[0; 4]; 2]; 95 | /// 96 | /// assert_eq!((&buf).limit(0).frames(), 0); 97 | /// assert_eq!((&buf).limit(1).frames(), 1); 98 | /// assert_eq!((&buf).limit(5).frames(), 4); 99 | /// ``` 100 | impl ExactSizeBuf for Limit 101 | where 102 | B: ExactSizeBuf, 103 | { 104 | fn frames(&self) -> usize { 105 | usize::min(self.buf.frames(), self.limit) 106 | } 107 | } 108 | 109 | impl ReadBuf for Limit 110 | where 111 | B: ReadBuf, 112 | { 113 | fn remaining(&self) -> usize { 114 | usize::min(self.buf.remaining(), self.limit) 115 | } 116 | 117 | fn advance(&mut self, n: usize) { 118 | self.buf.advance(usize::min(n, self.limit)); 119 | } 120 | } 121 | 122 | iterators!(limit: usize => self.limit(limit)); 123 | -------------------------------------------------------------------------------- /audio-core/src/buf/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! iterators { 2 | ( 3 | $($field:ident : $field_ty:ty),* $(,)? 4 | => 5 | $self:ident . $fn:ident ($($arg:ident),* $(,)?) 6 | ) => { 7 | pub struct IterChannels { 8 | iter: I, 9 | $($field: $field_ty,)* 10 | } 11 | 12 | impl Iterator for IterChannels 13 | where 14 | I: Iterator, 15 | I::Item: Channel, 16 | { 17 | type Item = I::Item; 18 | 19 | fn next(&mut $self) -> Option { 20 | Some($self.iter.next()?.$fn($($self.$arg),*)) 21 | } 22 | 23 | fn nth(&mut $self, n: usize) -> Option { 24 | Some($self.iter.nth(n)?.$fn($($self.$arg),*)) 25 | } 26 | } 27 | 28 | impl DoubleEndedIterator for IterChannels 29 | where 30 | I: DoubleEndedIterator, 31 | I::Item: Channel, 32 | { 33 | fn next_back(&mut $self) -> Option { 34 | Some($self.iter.next_back()?.$fn($($self.$arg),*)) 35 | } 36 | 37 | fn nth_back(&mut $self, n: usize) -> Option { 38 | Some($self.iter.nth_back(n)?.$fn($($self.$arg),*)) 39 | } 40 | } 41 | 42 | impl ExactSizeIterator for IterChannels 43 | where 44 | I: ExactSizeIterator, 45 | I::Item: ChannelMut, 46 | { 47 | fn len(&$self) -> usize { 48 | $self.iter.len() 49 | } 50 | } 51 | 52 | pub struct IterChannelsMut { 53 | iter: I, 54 | $($field: $field_ty,)* 55 | } 56 | 57 | impl Iterator for IterChannelsMut 58 | where 59 | I: Iterator, 60 | I::Item: ChannelMut, 61 | { 62 | type Item = I::Item; 63 | 64 | fn next(&mut $self) -> Option { 65 | Some($self.iter.next()?.$fn($($self . $arg),*)) 66 | } 67 | 68 | fn nth(&mut $self, n: usize) -> Option { 69 | Some($self.iter.nth(n)?.$fn($($self . $arg),*)) 70 | } 71 | } 72 | 73 | impl DoubleEndedIterator for IterChannelsMut 74 | where 75 | I: DoubleEndedIterator, 76 | I::Item: ChannelMut, 77 | { 78 | fn next_back(&mut $self) -> Option { 79 | Some($self.iter.next_back()?.$fn($($self . $arg),*)) 80 | } 81 | 82 | fn nth_back(&mut $self, n: usize) -> Option { 83 | Some($self.iter.nth_back(n)?.$fn($($self . $arg),*)) 84 | } 85 | } 86 | 87 | impl ExactSizeIterator for IterChannelsMut 88 | where 89 | I: ExactSizeIterator, 90 | I::Item: ChannelMut, 91 | { 92 | fn len(&$self) -> usize { 93 | $self.iter.len() 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /audio-core/src/buf/skip.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buf, BufMut, Channel, ChannelMut, ExactSizeBuf, ReadBuf}; 2 | 3 | /// A buffer where a number of frames have been skipped over. 4 | /// 5 | /// See [Buf::skip]. 6 | pub struct Skip { 7 | buf: B, 8 | n: usize, 9 | } 10 | 11 | impl Skip { 12 | /// Construct a new buffer skip. 13 | pub(crate) fn new(buf: B, n: usize) -> Self { 14 | Self { buf, n } 15 | } 16 | } 17 | 18 | /// [Skip] adjusts the implementation of [Buf]. 19 | /// 20 | /// ``` 21 | /// use audio::{Buf, ExactSizeBuf}; 22 | /// 23 | /// let buf = audio::interleaved![[0; 4]; 2]; 24 | /// 25 | /// assert_eq!((&buf).skip(0).channels(), 2); 26 | /// assert_eq!((&buf).skip(0).frames_hint(), Some(4)); 27 | /// 28 | /// assert_eq!((&buf).skip(1).channels(), 2); 29 | /// assert_eq!((&buf).skip(1).frames_hint(), Some(3)); 30 | /// 31 | /// assert_eq!((&buf).skip(5).channels(), 2); 32 | /// assert_eq!((&buf).skip(5).frames_hint(), Some(0)); 33 | /// ``` 34 | impl Buf for Skip 35 | where 36 | B: Buf, 37 | { 38 | type Sample = B::Sample; 39 | 40 | type Channel<'this> 41 | = B::Channel<'this> 42 | where 43 | Self: 'this; 44 | 45 | type IterChannels<'this> 46 | = IterChannels> 47 | where 48 | Self: 'this; 49 | 50 | fn frames_hint(&self) -> Option { 51 | let frames = self.buf.frames_hint()?; 52 | Some(frames.saturating_sub(self.n)) 53 | } 54 | 55 | fn channels(&self) -> usize { 56 | self.buf.channels() 57 | } 58 | 59 | fn get_channel(&self, channel: usize) -> Option> { 60 | Some(self.buf.get_channel(channel)?.skip(self.n)) 61 | } 62 | 63 | fn iter_channels(&self) -> Self::IterChannels<'_> { 64 | IterChannels { 65 | iter: self.buf.iter_channels(), 66 | n: self.n, 67 | } 68 | } 69 | } 70 | 71 | impl BufMut for Skip 72 | where 73 | B: BufMut, 74 | { 75 | type ChannelMut<'a> 76 | = B::ChannelMut<'a> 77 | where 78 | Self: 'a; 79 | 80 | type IterChannelsMut<'a> 81 | = IterChannelsMut> 82 | where 83 | Self: 'a; 84 | 85 | fn get_channel_mut(&mut self, channel: usize) -> Option> { 86 | Some(self.buf.get_channel_mut(channel)?.skip(self.n)) 87 | } 88 | 89 | fn copy_channel(&mut self, from: usize, to: usize) 90 | where 91 | Self::Sample: Copy, 92 | { 93 | self.buf.copy_channel(from, to); 94 | } 95 | 96 | fn iter_channels_mut(&mut self) -> Self::IterChannelsMut<'_> { 97 | IterChannelsMut { 98 | iter: self.buf.iter_channels_mut(), 99 | n: self.n, 100 | } 101 | } 102 | } 103 | 104 | /// [Skip] adjusts the implementation of [ExactSizeBuf]. 105 | /// 106 | /// ``` 107 | /// use audio::{Buf, ExactSizeBuf}; 108 | /// 109 | /// let buf = audio::interleaved![[0; 4]; 2]; 110 | /// 111 | /// assert_eq!((&buf).skip(0).frames(), 4); 112 | /// assert_eq!((&buf).skip(1).frames(), 3); 113 | /// assert_eq!((&buf).skip(5).frames(), 0); 114 | /// ``` 115 | impl ExactSizeBuf for Skip 116 | where 117 | B: ExactSizeBuf, 118 | { 119 | fn frames(&self) -> usize { 120 | self.buf.frames().saturating_sub(self.n) 121 | } 122 | } 123 | 124 | impl ReadBuf for Skip 125 | where 126 | B: ReadBuf, 127 | { 128 | fn remaining(&self) -> usize { 129 | self.buf.remaining().saturating_sub(self.n) 130 | } 131 | 132 | fn advance(&mut self, n: usize) { 133 | self.buf.advance(self.n.saturating_add(n)); 134 | } 135 | } 136 | 137 | iterators!(n: usize => self.skip(n)); 138 | -------------------------------------------------------------------------------- /audio-core/src/buf/tail.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buf, BufMut, Channel, ChannelMut, ExactSizeBuf, ReadBuf}; 2 | 3 | /// The tail of a buffer. 4 | /// 5 | /// See [Buf::tail]. 6 | pub struct Tail { 7 | buf: B, 8 | n: usize, 9 | } 10 | 11 | impl Tail { 12 | /// Construct a new buffer tail. 13 | pub(crate) fn new(buf: B, n: usize) -> Self { 14 | Self { buf, n } 15 | } 16 | } 17 | 18 | impl Buf for Tail 19 | where 20 | B: Buf, 21 | { 22 | type Sample = B::Sample; 23 | 24 | type Channel<'this> 25 | = B::Channel<'this> 26 | where 27 | Self: 'this; 28 | 29 | type IterChannels<'this> 30 | = IterChannels> 31 | where 32 | Self: 'this; 33 | 34 | fn frames_hint(&self) -> Option { 35 | let frames = self.buf.frames_hint()?; 36 | Some(usize::min(frames, self.n)) 37 | } 38 | 39 | fn channels(&self) -> usize { 40 | self.buf.channels() 41 | } 42 | 43 | fn get_channel(&self, channel: usize) -> Option> { 44 | Some(self.buf.get_channel(channel)?.tail(self.n)) 45 | } 46 | 47 | fn iter_channels(&self) -> Self::IterChannels<'_> { 48 | IterChannels { 49 | iter: self.buf.iter_channels(), 50 | n: self.n, 51 | } 52 | } 53 | } 54 | 55 | impl BufMut for Tail 56 | where 57 | B: BufMut, 58 | { 59 | type ChannelMut<'a> 60 | = B::ChannelMut<'a> 61 | where 62 | Self: 'a; 63 | 64 | type IterChannelsMut<'a> 65 | = IterChannelsMut> 66 | where 67 | Self: 'a; 68 | 69 | fn get_channel_mut(&mut self, channel: usize) -> Option> { 70 | Some(self.buf.get_channel_mut(channel)?.tail(self.n)) 71 | } 72 | 73 | fn copy_channel(&mut self, from: usize, to: usize) 74 | where 75 | Self::Sample: Copy, 76 | { 77 | self.buf.copy_channel(from, to); 78 | } 79 | 80 | fn iter_channels_mut(&mut self) -> Self::IterChannelsMut<'_> { 81 | IterChannelsMut { 82 | iter: self.buf.iter_channels_mut(), 83 | n: self.n, 84 | } 85 | } 86 | } 87 | 88 | /// [Tail] adjusts the implementation of [ExactSizeBuf]. 89 | /// 90 | /// ``` 91 | /// use audio::{Buf, ExactSizeBuf}; 92 | /// 93 | /// let buf = audio::interleaved![[0; 4]; 2]; 94 | /// 95 | /// assert_eq!((&buf).tail(0).frames(), 0); 96 | /// assert_eq!((&buf).tail(1).frames(), 1); 97 | /// assert_eq!((&buf).tail(5).frames(), 4); 98 | /// ``` 99 | impl ExactSizeBuf for Tail 100 | where 101 | B: ExactSizeBuf, 102 | { 103 | fn frames(&self) -> usize { 104 | usize::min(self.buf.frames(), self.n) 105 | } 106 | } 107 | 108 | impl ReadBuf for Tail 109 | where 110 | B: ReadBuf, 111 | { 112 | fn remaining(&self) -> usize { 113 | usize::min(self.buf.remaining(), self.n) 114 | } 115 | 116 | fn advance(&mut self, n: usize) { 117 | let n = self 118 | .buf 119 | .remaining() 120 | .saturating_sub(self.n) 121 | .saturating_add(n); 122 | 123 | self.buf.advance(n); 124 | } 125 | } 126 | 127 | iterators!(n: usize => self.tail(n)); 128 | -------------------------------------------------------------------------------- /audio-core/src/buf_mut.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buf, ChannelMut}; 2 | 3 | /// A trait describing a mutable audio buffer. 4 | pub trait BufMut: Buf { 5 | /// The type of the mutable channel container. 6 | type ChannelMut<'this>: ChannelMut 7 | where 8 | Self: 'this; 9 | 10 | /// A mutable iterator over available channels. 11 | type IterChannelsMut<'this>: Iterator> 12 | where 13 | Self: 'this; 14 | 15 | /// Construct a mutable iterator over available channels. 16 | /// 17 | /// # Examples 18 | /// 19 | /// ``` 20 | /// use audio::{BufMut, ChannelMut}; 21 | /// 22 | /// fn test(mut buf: impl BufMut) { 23 | /// for (n, mut chan) in buf.iter_channels_mut().enumerate() { 24 | /// for f in chan.iter_mut() { 25 | /// *f += n as i32 + 1; 26 | /// } 27 | /// } 28 | /// } 29 | /// 30 | /// let mut buf = audio::dynamic![[0; 4]; 2]; 31 | /// test(&mut buf); 32 | /// assert_eq!( 33 | /// buf.iter_channels().collect::>(), 34 | /// vec![[1, 1, 1, 1], [2, 2, 2, 2]], 35 | /// ); 36 | /// 37 | /// let mut buf = audio::interleaved![[0; 4]; 2]; 38 | /// test(&mut buf); 39 | /// assert_eq!( 40 | /// buf.iter_channels().collect::>(), 41 | /// vec![[1, 1, 1, 1], [2, 2, 2, 2]], 42 | /// ); 43 | /// ``` 44 | fn iter_channels_mut(&mut self) -> Self::IterChannelsMut<'_>; 45 | 46 | /// Return a mutable handler to the buffer associated with the channel. 47 | /// 48 | /// # Examples 49 | /// 50 | /// ``` 51 | /// use audio::{BufMut, ChannelMut}; 52 | /// 53 | /// fn test(mut buf: impl BufMut) { 54 | /// if let Some(mut chan) = buf.get_channel_mut(1) { 55 | /// for f in chan.iter_mut() { 56 | /// *f += 1; 57 | /// } 58 | /// } 59 | /// } 60 | /// 61 | /// let mut buf = audio::dynamic![[0; 4]; 2]; 62 | /// test(&mut buf); 63 | /// assert_eq!( 64 | /// buf.iter_channels().collect::>(), 65 | /// vec![[0, 0, 0, 0], [1, 1, 1, 1]], 66 | /// ); 67 | /// ``` 68 | fn get_channel_mut(&mut self, channel: usize) -> Option>; 69 | 70 | /// Copy one channel into another. 71 | /// 72 | /// If the channels have different sizes, the minimul difference between 73 | /// them will be copied. 74 | /// 75 | /// # Panics 76 | /// 77 | /// Panics if one of the channels being tried to copy from or to is out of 78 | /// bounds as reported by [Buf::channels]. 79 | /// 80 | /// # Examples 81 | /// 82 | /// ``` 83 | /// use audio::{Buf, BufMut}; 84 | /// 85 | /// let mut buf = audio::dynamic![[1, 2, 3, 4], [0, 0, 0, 0]]; 86 | /// buf.copy_channel(0, 1); 87 | /// assert_eq!(buf.get_channel(1), buf.get_channel(0)); 88 | /// ``` 89 | fn copy_channel(&mut self, from: usize, to: usize) 90 | where 91 | Self::Sample: Copy; 92 | 93 | /// Fill the entire buffer with the specified value 94 | /// # Example 95 | /// 96 | /// ``` 97 | /// use audio::BufMut; 98 | /// 99 | /// let mut buf = audio::sequential![[0; 2]; 2]; 100 | /// buf.fill(1); 101 | /// assert_eq!(buf.as_slice(), &[1, 1, 1, 1]); 102 | /// ``` 103 | fn fill(&mut self, value: Self::Sample) 104 | where 105 | Self::Sample: Copy, 106 | { 107 | for mut channel in self.iter_channels_mut() { 108 | channel.fill(value); 109 | } 110 | } 111 | } 112 | 113 | impl BufMut for &mut B 114 | where 115 | B: ?Sized + BufMut, 116 | { 117 | type ChannelMut<'this> 118 | = B::ChannelMut<'this> 119 | where 120 | Self: 'this; 121 | 122 | type IterChannelsMut<'this> 123 | = B::IterChannelsMut<'this> 124 | where 125 | Self: 'this; 126 | 127 | #[inline] 128 | fn get_channel_mut(&mut self, channel: usize) -> Option> { 129 | (**self).get_channel_mut(channel) 130 | } 131 | 132 | #[inline] 133 | fn copy_channel(&mut self, from: usize, to: usize) 134 | where 135 | Self::Sample: Copy, 136 | { 137 | (**self).copy_channel(from, to); 138 | } 139 | 140 | #[inline] 141 | fn iter_channels_mut(&mut self) -> Self::IterChannelsMut<'_> { 142 | (**self).iter_channels_mut() 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /audio-core/src/channel_mut.rs: -------------------------------------------------------------------------------- 1 | use crate::Channel; 2 | 3 | /// One channel of audio samples, usually one of several channels in a 4 | /// multichannel buffer 5 | /// 6 | /// This trait provides read and write access. 7 | /// 8 | /// See [BufMut::get_channel_mut][crate::BufMut::get_channel_mut]. 9 | pub trait ChannelMut: Channel { 10 | /// A reborrowed mutable channel. 11 | type ChannelMut<'this>: ChannelMut 12 | where 13 | Self: 'this; 14 | 15 | /// A mutable iterator over a channel. 16 | type IterMut<'this>: Iterator 17 | where 18 | Self: 'this; 19 | 20 | /// Reborrow the channel mutably. 21 | fn as_channel_mut(&mut self) -> Self::ChannelMut<'_>; 22 | 23 | /// Construct a mutable iterator over the channel 24 | fn iter_mut(&mut self) -> Self::IterMut<'_>; 25 | 26 | /// Get the frame at the given offset in the channel. 27 | /// 28 | /// # Examples 29 | /// 30 | /// ``` 31 | /// use audio::{BufMut, ChannelMut}; 32 | /// 33 | /// fn test(mut buf: impl BufMut) { 34 | /// for mut chan in buf.iter_channels_mut() { 35 | /// if let Some(f) = chan.get_mut(2) { 36 | /// *f = 1; 37 | /// } 38 | /// } 39 | /// } 40 | /// 41 | /// test(&mut audio::dynamic![[0; 16]; 2]); 42 | /// test(&mut audio::sequential![[0; 16]; 2]); 43 | /// test(&mut audio::interleaved![[0; 16]; 2]); 44 | /// ``` 45 | fn get_mut(&mut self, n: usize) -> Option<&mut Self::Sample>; 46 | 47 | /// Try to access the current channel as a mutable linear buffer. 48 | /// 49 | /// This is available because it could permit for some optimizations. 50 | /// 51 | /// # Examples 52 | /// 53 | /// ``` 54 | /// use audio::{BufMut, Channel, ChannelMut}; 55 | /// 56 | /// fn test(buf: &mut impl BufMut) { 57 | /// let is_linear = if let Some(linear) = buf.get_channel_mut(0).unwrap().try_as_linear_mut() { 58 | /// linear[2] = 1.0; 59 | /// true 60 | /// } else { 61 | /// false 62 | /// }; 63 | /// 64 | /// if is_linear { 65 | /// assert_eq!(buf.get_channel(0).and_then(|c| c.get(2)), Some(1.0)); 66 | /// } 67 | /// } 68 | /// 69 | /// test(&mut audio::dynamic![[0.0; 8]; 2]); 70 | /// test(&mut audio::sequential![[0.0; 8]; 2]); 71 | /// test(&mut audio::interleaved![[0.0; 8]; 2]); 72 | /// ``` 73 | fn try_as_linear_mut(&mut self) -> Option<&mut [Self::Sample]>; 74 | 75 | /// Replace all samples in the channel with the specified value 76 | /// 77 | /// # Example 78 | /// 79 | /// ``` 80 | /// use audio::ChannelMut; 81 | /// 82 | /// let mut buf = audio::sequential![[0; 2]; 2]; 83 | /// for mut channel in buf.iter_channels_mut() { 84 | /// channel.fill(1); 85 | /// } 86 | /// assert_eq!(buf.get_channel(0).unwrap().as_ref(), &[1, 1]); 87 | /// assert_eq!(buf.get_channel(1).unwrap().as_ref(), &[1, 1]); 88 | /// ``` 89 | fn fill(&mut self, value: Self::Sample) { 90 | for sample in self.iter_mut() { 91 | *sample = value; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /audio-core/src/exact_size_buf.rs: -------------------------------------------------------------------------------- 1 | use crate::Buf; 2 | 3 | /// Trait used to describe a buffer that knows exactly how many frames it has 4 | /// regardless of if it's sized or not. 5 | /// 6 | /// # Examples 7 | /// 8 | /// ``` 9 | /// use audio::ExactSizeBuf; 10 | /// 11 | /// fn test(buf: T) where T: ExactSizeBuf { 12 | /// assert_eq!(buf.frames(), 4); 13 | /// } 14 | /// 15 | /// test(audio::interleaved![[0i16; 4]; 4]); 16 | /// test(audio::sequential![[0i16; 4]; 4]); 17 | /// test(audio::dynamic![[0i16; 4]; 4]); 18 | /// test(audio::wrap::interleaved([0i16; 16], 4)); 19 | /// test(audio::wrap::sequential([0i16; 16], 4)); 20 | /// ``` 21 | pub trait ExactSizeBuf: Buf { 22 | /// The number of frames in a buffer. 23 | /// 24 | /// # Examples 25 | /// 26 | /// ``` 27 | /// use audio::ExactSizeBuf; 28 | /// 29 | /// fn test(buf: T) where T: ExactSizeBuf { 30 | /// assert_eq!(buf.frames(), 4); 31 | /// } 32 | /// 33 | /// test(audio::interleaved![[0i16; 4]; 4]); 34 | /// test(audio::sequential![[0i16; 4]; 4]); 35 | /// test(audio::dynamic![[0i16; 4]; 4]); 36 | /// test(audio::wrap::interleaved([0i16; 16], 4)); 37 | /// test(audio::wrap::sequential([0i16; 16], 4)); 38 | /// ``` 39 | fn frames(&self) -> usize; 40 | } 41 | 42 | impl ExactSizeBuf for &B 43 | where 44 | B: ?Sized + ExactSizeBuf, 45 | { 46 | #[inline] 47 | fn frames(&self) -> usize { 48 | (**self).frames() 49 | } 50 | } 51 | 52 | impl ExactSizeBuf for &mut B 53 | where 54 | B: ?Sized + ExactSizeBuf, 55 | { 56 | #[inline] 57 | fn frames(&self) -> usize { 58 | (**self).frames() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /audio-core/src/frame.rs: -------------------------------------------------------------------------------- 1 | //! A frame buffer as created through 2 | //! [UniformBuf::get_frame][crate::UniformBuf::get_frame]. 3 | 4 | /// The buffer of a single frame. 5 | pub trait Frame { 6 | /// The sample of a channel. 7 | type Sample: Copy; 8 | 9 | /// The type the frame assumes when coerced into a reference. 10 | type Frame<'this>: Frame 11 | where 12 | Self: 'this; 13 | 14 | /// A borrowing iterator over the channel. 15 | type Iter<'this>: Iterator 16 | where 17 | Self: 'this; 18 | 19 | /// Reborrow the current frame as a reference. 20 | fn as_frame(&self) -> Self::Frame<'_>; 21 | 22 | /// Get the length which indicates number of samples in the current frame. 23 | fn len(&self) -> usize; 24 | 25 | /// Test if the current frame is empty. 26 | fn is_empty(&self) -> bool { 27 | self.len() == 0 28 | } 29 | 30 | /// Get the sample at the given channel in the frame. 31 | fn get(&self, channel: usize) -> Option; 32 | 33 | /// Construct an iterator over the frame. 34 | fn iter(&self) -> Self::Iter<'_>; 35 | } 36 | -------------------------------------------------------------------------------- /audio-core/src/frame_mut.rs: -------------------------------------------------------------------------------- 1 | //! A frame buffer as created through 2 | //! [UniformBuf::get_frame][crate::UniformBuf::get_frame]. 3 | 4 | use crate::frame::Frame; 5 | 6 | /// The buffer of a single frame. 7 | pub trait FrameMut: Frame { 8 | /// The type the frame assumes when coerced into a reference. 9 | type FrameMut<'this>: FrameMut 10 | where 11 | Self: 'this; 12 | 13 | /// A borrowing iterator over the channel. 14 | type IterMut<'this>: Iterator 15 | where 16 | Self: 'this; 17 | 18 | /// Reborrow the current frame as a reference. 19 | fn as_sample_mut(&self) -> Self::FrameMut<'_>; 20 | 21 | /// Get the sample mutable at the given channel in the frame. 22 | fn get_mut(&self, channel: usize) -> Option<&mut Self::Sample>; 23 | 24 | /// Construct a mutable iterator over the frame. 25 | fn iter_mut(&self) -> Self::IterMut<'_>; 26 | } 27 | -------------------------------------------------------------------------------- /audio-core/src/interleaved_buf.rs: -------------------------------------------------------------------------------- 1 | /// A trait describing a buffer that is interleaved. 2 | /// 3 | /// This allows for accessing the raw underlying interleaved buffer. 4 | pub trait InterleavedBuf { 5 | /// The type of a single sample. 6 | type Sample; 7 | 8 | /// Access the underlying raw interleaved buffer. 9 | /// 10 | /// # Examples 11 | /// 12 | /// ``` 13 | /// use audio::InterleavedBuf; 14 | /// 15 | /// fn test(buf: impl InterleavedBuf) { 16 | /// assert_eq!(buf.as_interleaved(), &[1, 1, 2, 2, 3, 3, 4, 4]); 17 | /// } 18 | /// 19 | /// test(audio::interleaved![[1, 2, 3, 4]; 2]); 20 | /// ``` 21 | fn as_interleaved(&self) -> &[Self::Sample]; 22 | } 23 | 24 | impl InterleavedBuf for &B 25 | where 26 | B: ?Sized + InterleavedBuf, 27 | { 28 | type Sample = B::Sample; 29 | 30 | #[inline] 31 | fn as_interleaved(&self) -> &[Self::Sample] { 32 | (**self).as_interleaved() 33 | } 34 | } 35 | 36 | impl InterleavedBuf for &mut B 37 | where 38 | B: ?Sized + InterleavedBuf, 39 | { 40 | type Sample = B::Sample; 41 | 42 | #[inline] 43 | fn as_interleaved(&self) -> &[Self::Sample] { 44 | (**self).as_interleaved() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /audio-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [github](https://github.com/udoprog/audio) 2 | //! [crates.io](https://crates.io/crates/audio-core) 3 | //! [docs.rs](https://docs.rs/audio-core) 4 | //! 5 | //! The core [audio] traits. 6 | //! 7 | //! If you want to build an audio component that is completely agnostic to the 8 | //! shape of any one given audio buffer you can add a dependency directly to 9 | //! these traits instead of depending on all of the [audio] crate. 10 | //! 11 | //! [audio]: https://docs.rs/audio 12 | 13 | #![cfg_attr(not(feature = "std"), no_std)] 14 | #![deny(missing_docs, rustdoc::broken_intra_doc_links)] 15 | #![allow(clippy::should_implement_trait)] 16 | 17 | pub mod buf; 18 | pub use self::buf::Buf; 19 | 20 | mod buf_mut; 21 | pub use self::buf_mut::BufMut; 22 | 23 | mod channel; 24 | pub use self::channel::Channel; 25 | 26 | mod channel_mut; 27 | pub use self::channel_mut::ChannelMut; 28 | 29 | mod frame; 30 | pub use self::frame::Frame; 31 | 32 | mod frame_mut; 33 | pub use self::frame_mut::FrameMut; 34 | 35 | pub mod translate; 36 | pub use self::translate::Translate; 37 | 38 | mod sample; 39 | pub use self::sample::Sample; 40 | 41 | mod read_buf; 42 | pub use self::read_buf::ReadBuf; 43 | 44 | mod write_buf; 45 | pub use self::write_buf::WriteBuf; 46 | 47 | mod exact_size_buf; 48 | pub use self::exact_size_buf::ExactSizeBuf; 49 | 50 | mod resizable_buf; 51 | pub use self::resizable_buf::ResizableBuf; 52 | 53 | mod interleaved_buf; 54 | pub use self::interleaved_buf::InterleavedBuf; 55 | 56 | mod interleaved_buf_mut; 57 | pub use self::interleaved_buf_mut::InterleavedBufMut; 58 | 59 | mod linear_channel; 60 | pub use self::linear_channel::LinearChannel; 61 | 62 | mod linear_channel_mut; 63 | pub use self::linear_channel_mut::LinearChannelMut; 64 | 65 | mod uniform_buf; 66 | pub use self::uniform_buf::UniformBuf; 67 | -------------------------------------------------------------------------------- /audio-core/src/linear_channel.rs: -------------------------------------------------------------------------------- 1 | use crate::Channel; 2 | 3 | /// Traits for linear channels. 4 | pub trait LinearChannel: Channel { 5 | /// Access the linear channel. 6 | fn as_linear_channel(&self) -> &[Self::Sample]; 7 | } 8 | -------------------------------------------------------------------------------- /audio-core/src/linear_channel_mut.rs: -------------------------------------------------------------------------------- 1 | use crate::ChannelMut; 2 | 3 | /// Trait for linear mutable channels. 4 | pub trait LinearChannelMut: ChannelMut { 5 | /// Access the linear channel mutably. 6 | fn as_linear_channel_mut(&mut self) -> &mut [Self::Sample]; 7 | } 8 | -------------------------------------------------------------------------------- /audio-core/src/read_buf.rs: -------------------------------------------------------------------------------- 1 | /// Trait used to govern sequential reading of an audio buffer. 2 | /// 3 | /// This is the "in" part of "buffered I/O". It allows for buffers to govern 4 | /// which slice of frames in them has been read so that operations can be 5 | /// performed in multiple stages. 6 | /// 7 | /// This can be accomplished manually using available buffer combinators such as 8 | /// [Buf::tail][crate::Buf::tail]. But buffered I/O allows us to do this in a 9 | /// much more structured fashion. 10 | /// 11 | /// # Examples 12 | /// 13 | /// ``` 14 | /// use audio::ReadBuf; 15 | /// use audio::{io, wrap}; 16 | /// # fn send_data(buf: &mut [i16]) {} 17 | /// 18 | /// // A simple mutable buffer we want to write to. Fits 2 channels with 64 19 | /// // frames each. 20 | /// let mut to = [0i16; 128]; 21 | /// 22 | /// // A buffer we want to read from. 2 channels with 512 frames each. 23 | /// let from = audio::interleaved![[0i16; 512]; 2]; 24 | /// let mut from = io::Read::new(from); 25 | /// 26 | /// let mut steps = 0; 27 | /// 28 | /// while from.has_remaining() { 29 | /// // Wrap the output buffer according to format so it can be written to 30 | /// // correctly. 31 | /// io::copy_remaining(&mut from, wrap::interleaved(&mut to[..], 2)); 32 | /// 33 | /// send_data(&mut to[..]); 34 | /// 35 | /// steps += 1; 36 | /// } 37 | /// 38 | /// // We needed to write 8 times to copy our entire buffer. 39 | /// assert_eq!(steps, 8); 40 | /// ``` 41 | pub trait ReadBuf { 42 | /// Test if there are any remaining frames to read. 43 | /// 44 | /// # Examples 45 | /// 46 | /// ``` 47 | /// use audio::ReadBuf; 48 | /// 49 | /// let mut buf = audio::wrap::interleaved(&[0, 1, 2, 3, 4, 5, 6, 7][..], 2); 50 | /// 51 | /// assert!(buf.has_remaining()); 52 | /// assert_eq!(buf.remaining(), 4); 53 | /// buf.advance(4); 54 | /// assert_eq!(buf.remaining(), 0); 55 | /// ``` 56 | fn has_remaining(&self) -> bool { 57 | self.remaining() > 0 58 | } 59 | 60 | /// Get the number of frames remaining that can be read from the buffer. 61 | /// 62 | /// # Examples 63 | /// 64 | /// ``` 65 | /// use audio::ReadBuf; 66 | /// 67 | /// let buf = audio::wrap::interleaved(&[0, 1, 2, 3, 4, 5, 6, 7][..], 2); 68 | /// assert_eq!(buf.remaining(), 4); 69 | /// ``` 70 | fn remaining(&self) -> usize; 71 | 72 | /// Advance the read number of frames by `n`. 73 | /// 74 | /// # Examples 75 | /// 76 | /// ``` 77 | /// use audio::ReadBuf; 78 | /// 79 | /// let mut buf = audio::wrap::interleaved(&[0, 1, 2, 3, 4, 5, 6, 7][..], 2); 80 | /// 81 | /// assert_eq!(buf.remaining(), 4); 82 | /// buf.advance(2); 83 | /// assert_eq!(buf.remaining(), 2); 84 | /// ``` 85 | fn advance(&mut self, n: usize); 86 | } 87 | 88 | impl ReadBuf for &mut B 89 | where 90 | B: ReadBuf, 91 | { 92 | #[inline] 93 | fn has_remaining(&self) -> bool { 94 | (**self).has_remaining() 95 | } 96 | 97 | #[inline] 98 | fn remaining(&self) -> usize { 99 | (**self).remaining() 100 | } 101 | 102 | #[inline] 103 | fn advance(&mut self, n: usize) { 104 | (**self).advance(n); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /audio-core/src/resizable_buf.rs: -------------------------------------------------------------------------------- 1 | /// Trait implemented for buffers that can be resized. 2 | pub trait ResizableBuf { 3 | /// Ensure that the audio buffer has space for at least the given `capacity` 4 | /// of contiguous memory. The `capacity` is specified in number of 5 | /// [Samples][crate::Buf::Sample]. 6 | /// 7 | /// This is a no-op unless the underlying buffer is contiguous in memory 8 | /// which can be ensured by requiring traits such as 9 | /// [InterleavedBufMut][crate::InterleavedBufMut]. 10 | /// 11 | /// This returns a boolean indicating if we could successfully reserve the 12 | /// given amount of memory. The caller can only assume that a buffer is 13 | /// present up to the given number of samples if it returns `true`. This is 14 | /// important to observe if the code you're working on has safety 15 | /// implications. 16 | /// 17 | /// A typical approach in case a reservation fails is to either panic or 18 | /// return an error indicating that the provided buffer is not supported. 19 | fn try_reserve(&mut self, capacity: usize) -> bool; 20 | 21 | /// Resize the number of per-channel frames in the buffer. 22 | /// 23 | /// # Examples 24 | /// 25 | /// ``` 26 | /// use audio::{Buf, ExactSizeBuf, ResizableBuf}; 27 | /// 28 | /// fn test(mut buffer: impl ResizableBuf) { 29 | /// buffer.resize_frames(4); 30 | /// } 31 | /// 32 | /// let mut buf = audio::interleaved![[0; 0]; 2]; 33 | /// 34 | /// assert_eq!(buf.channels(), 2); 35 | /// assert_eq!(buf.frames(), 0); 36 | /// 37 | /// test(&mut buf); 38 | /// 39 | /// assert_eq!(buf.channels(), 2); 40 | /// assert_eq!(buf.frames(), 4); 41 | /// ``` 42 | fn resize_frames(&mut self, frames: usize); 43 | 44 | /// Resize the buffer to match the given topology. 45 | /// 46 | /// # Examples 47 | /// 48 | /// ``` 49 | /// use audio::ResizableBuf; 50 | /// 51 | /// fn test(mut buf: impl ResizableBuf) { 52 | /// buf.resize_topology(2, 4); 53 | /// } 54 | /// 55 | /// let mut buf = audio::interleaved![[0; 0]; 4]; 56 | /// 57 | /// test(&mut buf); 58 | /// 59 | /// assert_eq!(buf.channels(), 2); 60 | /// assert_eq!(buf.frames(), 4); 61 | /// ``` 62 | fn resize_topology(&mut self, channels: usize, frames: usize); 63 | } 64 | 65 | impl ResizableBuf for &mut B 66 | where 67 | B: ?Sized + ResizableBuf, 68 | { 69 | fn try_reserve(&mut self, capacity: usize) -> bool { 70 | (**self).try_reserve(capacity) 71 | } 72 | 73 | fn resize_frames(&mut self, frames: usize) { 74 | (**self).resize_frames(frames); 75 | } 76 | 77 | fn resize_topology(&mut self, channels: usize, frames: usize) { 78 | (**self).resize_topology(channels, frames); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /audio-core/src/sample.rs: -------------------------------------------------------------------------------- 1 | /// A sample that can be stored in an audio buffer. Types implementing this are 2 | /// known as being sample apt. 3 | /// 4 | /// Sample apt types have the following gurantees: 5 | /// 6 | /// * The type does not need to be dropped (by implementing [Copy]). 7 | /// * The type can safely be initialized with the all-zeros bit pattern. 8 | /// 9 | /// # Safety 10 | /// 11 | /// Implementor must make sure that a bit-pattern of all-zeros is a legal 12 | /// bit-pattern for the implemented type. 13 | pub unsafe trait Sample: Copy { 14 | /// The zero pattern for the sample. 15 | const ZERO: Self; 16 | } 17 | 18 | /// The bit-pattern of all zeros is a legal bit-pattern for floats. 19 | /// 20 | /// See for example: 21 | /// . 22 | /// 23 | /// Proof: 24 | /// 25 | /// ``` 26 | /// use audio::Sample; 27 | /// 28 | /// assert_eq!((f64::ZERO).to_bits(), 0u64); 29 | /// ``` 30 | unsafe impl Sample for f32 { 31 | const ZERO: Self = 0.0; 32 | } 33 | 34 | /// The bit-pattern of all zeros is a legal bit-pattern for floats. 35 | /// 36 | /// See for example: 37 | /// . 38 | /// 39 | /// Proof: 40 | /// 41 | /// ``` 42 | /// use audio::Sample; 43 | /// 44 | /// assert_eq!((f64::ZERO).to_bits(), 0u64); 45 | /// ``` 46 | unsafe impl Sample for f64 { 47 | const ZERO: Self = 0.0; 48 | } 49 | 50 | // Helper macro to implement [Sample] for integer types. 51 | macro_rules! impl_int { 52 | ($ty:ty) => { 53 | unsafe impl Sample for $ty { 54 | const ZERO: Self = 0; 55 | } 56 | }; 57 | } 58 | 59 | // Note: trivial integer implementations. 60 | impl_int!(u8); 61 | impl_int!(u16); 62 | impl_int!(u32); 63 | impl_int!(u64); 64 | impl_int!(u128); 65 | impl_int!(i8); 66 | impl_int!(i16); 67 | impl_int!(i32); 68 | impl_int!(i64); 69 | impl_int!(i128); 70 | impl_int!(usize); 71 | impl_int!(isize); 72 | 73 | // Implement for byte arrays of any length 74 | unsafe impl Sample for [u8; N] { 75 | const ZERO: Self = [0; N]; 76 | } 77 | -------------------------------------------------------------------------------- /audio-core/src/uniform_buf.rs: -------------------------------------------------------------------------------- 1 | use crate::buf::Buf; 2 | use crate::frame::Frame; 3 | 4 | /// A buffer which has a unifom channel size. 5 | pub trait UniformBuf: Buf { 6 | /// The type the channel assumes when coerced into a reference. 7 | type Frame<'this>: Frame 8 | where 9 | Self: 'this; 10 | 11 | /// A borrowing iterator over the channel. 12 | type IterFrames<'this>: Iterator> 13 | where 14 | Self: 'this; 15 | 16 | /// Get a single frame at the given offset. 17 | /// 18 | /// # Examples 19 | /// 20 | /// ``` 21 | /// use audio::{Frame, UniformBuf}; 22 | /// 23 | /// fn test(buf: B) 24 | /// where 25 | /// B: UniformBuf, 26 | /// { 27 | /// let frame = buf.get_frame(0).unwrap(); 28 | /// assert_eq!(frame.get(1), Some(5)); 29 | /// assert_eq!(frame.iter().collect::>(), [1, 5]); 30 | /// 31 | /// let frame = buf.get_frame(2).unwrap(); 32 | /// assert_eq!(frame.get(1), Some(7)); 33 | /// assert_eq!(frame.iter().collect::>(), [3, 7]); 34 | /// 35 | /// assert!(buf.get_frame(4).is_none()); 36 | /// } 37 | /// 38 | /// test(audio::sequential![[1, 2, 3, 4], [5, 6, 7, 8]]); 39 | /// test(audio::wrap::sequential([1, 2, 3, 4, 5, 6, 7, 8], 2)); 40 | /// 41 | /// test(audio::interleaved![[1, 2, 3, 4], [5, 6, 7, 8]]); 42 | /// test(audio::wrap::interleaved([1, 5, 2, 6, 3, 7, 4, 8], 2)); 43 | /// ``` 44 | fn get_frame(&self, frame: usize) -> Option>; 45 | 46 | /// Construct an iterator over all the frames in the audio buffer. 47 | /// 48 | /// # Examples 49 | /// 50 | /// ``` 51 | /// use audio::{Frame, UniformBuf}; 52 | /// 53 | /// fn test(buf: B) 54 | /// where 55 | /// B: UniformBuf, 56 | /// { 57 | /// let mut it = buf.iter_frames(); 58 | /// 59 | /// let frame = it.next().unwrap(); 60 | /// assert_eq!(frame.get(1), Some(5)); 61 | /// assert_eq!(frame.iter().collect::>(), [1, 5]); 62 | /// 63 | /// assert!(it.next().is_some()); 64 | /// 65 | /// let frame = it.next().unwrap(); 66 | /// assert_eq!(frame.get(1), Some(7)); 67 | /// assert_eq!(frame.iter().collect::>(), [3, 7]); 68 | /// 69 | /// assert!(it.next().is_some()); 70 | /// assert!(it.next().is_none()); 71 | /// } 72 | /// 73 | /// test(audio::sequential![[1, 2, 3, 4], [5, 6, 7, 8]]); 74 | /// test(audio::wrap::interleaved([1, 5, 2, 6, 3, 7, 4, 8], 2)); 75 | /// 76 | /// test(audio::interleaved![[1, 2, 3, 4], [5, 6, 7, 8]]); 77 | /// test(audio::wrap::sequential([1, 2, 3, 4, 5, 6, 7, 8], 2)); 78 | /// ``` 79 | fn iter_frames(&self) -> Self::IterFrames<'_>; 80 | } 81 | -------------------------------------------------------------------------------- /audio-core/src/write_buf.rs: -------------------------------------------------------------------------------- 1 | /// Trait used to govern sequential writing to an audio buffer. 2 | /// 3 | /// This is the "out" part of "buffered I/O". It allows for buffers to govern 4 | /// which slice of frames in them has been read so that operations can be 5 | /// performed in multiple stages. 6 | /// 7 | /// This can be accomplished manually using available buffer combinators such as 8 | /// [Buf::tail][crate::Buf::tail]. But buffered I/O allows us to do this in a 9 | /// much more structured fashion. 10 | /// 11 | /// # Examples 12 | /// 13 | /// ``` 14 | /// use audio::WriteBuf; 15 | /// use audio::{io, wrap}; 16 | /// # fn recv_data(buf: &mut [i16]) {} 17 | /// 18 | /// // A simple buffer we want to read from to. Fits 2 channels with 64 19 | /// // frames each. 20 | /// let mut from = [0i16; 128]; 21 | /// 22 | /// // A buffer we want to write to. 2 channels with 512 frames each. 23 | /// let to = audio::interleaved![[0i16; 512]; 2]; 24 | /// let mut to = io::Write::new(to); 25 | /// 26 | /// let mut steps = 0; 27 | /// 28 | /// while to.has_remaining_mut() { 29 | /// // Fill the buffer with something interesting. 30 | /// recv_data(&mut from[..]); 31 | /// 32 | /// // Wrap the filled buffer according to format so it can be written to 33 | /// // correctly. 34 | /// io::copy_remaining(wrap::interleaved(&mut from[..], 2), &mut to); 35 | /// 36 | /// steps += 1; 37 | /// } 38 | /// 39 | /// // We needed to write 8 times to fill our entire buffer. 40 | /// assert_eq!(steps, 8); 41 | /// ``` 42 | pub trait WriteBuf { 43 | /// Test if this buffer has remaining mutable frames that can be written. 44 | /// 45 | /// # Examples 46 | /// 47 | /// ``` 48 | /// use audio::WriteBuf; 49 | /// 50 | /// let mut buf = [0, 1, 2, 3, 4, 5, 6, 7]; 51 | /// let mut buf = audio::wrap::interleaved(&mut buf[..], 2); 52 | /// 53 | /// assert!(buf.has_remaining_mut()); 54 | /// assert_eq!(buf.remaining_mut(), 4); 55 | /// buf.advance_mut(4); 56 | /// assert_eq!(buf.remaining_mut(), 0); 57 | /// ``` 58 | fn has_remaining_mut(&self) -> bool { 59 | self.remaining_mut() > 0 60 | } 61 | 62 | /// Remaining number of frames that can be written. 63 | /// 64 | /// # Examples 65 | /// 66 | /// ``` 67 | /// use audio::WriteBuf; 68 | /// 69 | /// let mut buf = [0, 1, 2, 3, 4, 5, 6, 7]; 70 | /// let buf = audio::wrap::interleaved(&mut buf[..], 2); 71 | /// 72 | /// assert_eq!(buf.remaining_mut(), 4); 73 | /// ``` 74 | fn remaining_mut(&self) -> usize; 75 | 76 | /// Advance the number of frames that have been written. 77 | /// 78 | /// # Examples 79 | /// 80 | /// ``` 81 | /// use audio::WriteBuf; 82 | /// 83 | /// let mut buf = [0, 1, 2, 3, 4, 5, 6, 7]; 84 | /// let mut buf = audio::wrap::interleaved(&mut buf[..], 2); 85 | /// 86 | /// assert_eq!(buf.remaining_mut(), 4); 87 | /// buf.advance_mut(2); 88 | /// assert_eq!(buf.remaining_mut(), 2); 89 | /// ``` 90 | fn advance_mut(&mut self, n: usize); 91 | } 92 | 93 | impl WriteBuf for &mut B 94 | where 95 | B: WriteBuf, 96 | { 97 | fn has_remaining_mut(&self) -> bool { 98 | (**self).has_remaining_mut() 99 | } 100 | 101 | fn remaining_mut(&self) -> usize { 102 | (**self).remaining_mut() 103 | } 104 | 105 | fn advance_mut(&mut self, n: usize) { 106 | (**self).advance_mut(n); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /audio-device-alsa-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "audio-device-alsa-sys" 3 | version = "0.1.0-alpha.1" 4 | authors = ["John-John Tedro "] 5 | edition = "2018" 6 | rust-version = "1.70" 7 | description = "audio-device system bindings for ALSA" 8 | documentation = "https://docs.rs/audio" 9 | readme = "README.md" 10 | homepage = "https://github.com/udoprog/audio" 11 | repository = "https://github.com/udoprog/audio" 12 | license = "MIT OR Apache-2.0" 13 | keywords = ["audio", "buffer", "dsp"] 14 | categories = ["multimedia::audio"] 15 | 16 | [dependencies] 17 | libc = "0.2.125" 18 | 19 | [build-dependencies] 20 | anyhow = "1.0.57" 21 | pkg-config = "0.3.25" 22 | -------------------------------------------------------------------------------- /audio-device-alsa-sys/README.md: -------------------------------------------------------------------------------- 1 | # audio-device-alsa-sys 2 | 3 | [github](https://github.com/udoprog/audio) 4 | [crates.io](https://crates.io/crates/audio-device-alsa-sys) 5 | [docs.rs](https://docs.rs/audio-device-alsa-sys) 6 | [build status](https://github.com/udoprog/audio/actions?query=branch%3Amain) 7 | 8 | [audio-device] system bindings for ALSA. 9 | 10 | These bindings are generated with: 11 | 12 | ```sh 13 | cargo run --package generate --bin generate-alsa 14 | ``` 15 | 16 | [audio-device]: https://docs.rs/audio-device 17 | -------------------------------------------------------------------------------- /audio-device-alsa-sys/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> anyhow::Result<()> { 2 | pkg_config::Config::new().statik(false).probe("alsa")?; 3 | Ok(()) 4 | } 5 | -------------------------------------------------------------------------------- /audio-device-alsa-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [github](https://github.com/udoprog/audio) 2 | //! [crates.io](https://crates.io/crates/audio-device-alsa-sys) 3 | //! [docs.rs](https://docs.rs/audio-device-alsa-sys) 4 | //! 5 | //! [audio-device] system bindings for ALSA. 6 | //! 7 | //! These bindings are generated with: 8 | //! 9 | //! ```sh 10 | //! cargo run --package generate --bin generate-alsa 11 | //! ``` 12 | //! 13 | //! [audio-device]: https://docs.rs/audio-device 14 | 15 | #![allow(non_camel_case_types)] 16 | 17 | use libc::{pid_t, pollfd, timespec, timeval, FILE}; 18 | 19 | pub const SND_PCM_NONBLOCK: ::std::os::raw::c_int = 0x1; 20 | pub const SND_PCM_ASYNC: ::std::os::raw::c_int = 0x2; 21 | 22 | pub const SND_SEQ_OPEN_OUTPUT: i32 = 1; 23 | pub const SND_SEQ_OPEN_INPUT: i32 = 2; 24 | pub const SND_SEQ_OPEN_DUPLEX: i32 = SND_SEQ_OPEN_OUTPUT | SND_SEQ_OPEN_INPUT; 25 | pub const SND_SEQ_NONBLOCK: i32 = 0x0001; 26 | pub const SND_SEQ_ADDRESS_BROADCAST: u8 = 255; 27 | pub const SND_SEQ_ADDRESS_SUBSCRIBERS: u8 = 254; 28 | pub const SND_SEQ_ADDRESS_UNKNOWN: u8 = 253; 29 | pub const SND_SEQ_QUEUE_DIRECT: u8 = 253; 30 | pub const SND_SEQ_TIME_MODE_MASK: u8 = 1 << 1; 31 | pub const SND_SEQ_TIME_STAMP_MASK: u8 = 1 << 0; 32 | pub const SND_SEQ_TIME_MODE_REL: u8 = 1 << 1; 33 | pub const SND_SEQ_TIME_STAMP_REAL: u8 = 1 << 0; 34 | pub const SND_SEQ_TIME_STAMP_TICK: u8 = 0 << 0; 35 | pub const SND_SEQ_TIME_MODE_ABS: u8 = 0 << 1; 36 | pub const SND_SEQ_CLIENT_SYSTEM: u8 = 0; 37 | pub const SND_SEQ_PORT_SYSTEM_TIMER: u8 = 0; 38 | pub const SND_SEQ_PORT_SYSTEM_ANNOUNCE: u8 = 1; 39 | pub const SND_SEQ_PRIORITY_HIGH: u8 = 1 << 4; 40 | pub const SND_SEQ_EVENT_LENGTH_FIXED: u8 = 0 << 2; 41 | pub const SND_SEQ_EVENT_LENGTH_MASK: u8 = 3 << 2; 42 | pub const SND_SEQ_EVENT_LENGTH_VARIABLE: u8 = 1 << 2; 43 | pub const SND_SEQ_EVENT_LENGTH_VARUSR: u8 = 2 << 2; 44 | 45 | include!("bindings.rs"); 46 | -------------------------------------------------------------------------------- /audio-device-pipewire-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "audio-device-pipewire-sys" 3 | version = "0.1.0-alpha.1" 4 | authors = ["John-John Tedro "] 5 | edition = "2018" 6 | rust-version = "1.70" 7 | description = "audio-device system bindings for PipeWire" 8 | documentation = "https://docs.rs/audio" 9 | readme = "README.md" 10 | homepage = "https://github.com/udoprog/audio" 11 | repository = "https://github.com/udoprog/audio" 12 | license = "MIT OR Apache-2.0" 13 | keywords = ["audio", "buffer", "dsp"] 14 | categories = ["multimedia::audio"] 15 | 16 | [dependencies] 17 | libc = "0.2.125" 18 | 19 | [build-dependencies] 20 | anyhow = "1.0.57" 21 | pkg-config = "0.3.25" 22 | -------------------------------------------------------------------------------- /audio-device-pipewire-sys/README.md: -------------------------------------------------------------------------------- 1 | # audio-device-pipewire-sys 2 | 3 | [github](https://github.com/udoprog/audio) 4 | [crates.io](https://crates.io/crates/audio-device-pipewire-sys) 5 | [docs.rs](https://docs.rs/audio-device-pipewire-sys) 6 | [build status](https://github.com/udoprog/audio/actions?query=branch%3Amain) 7 | 8 | [audio-device] system bindings for PulseAudio. 9 | 10 | These bindings are generated with: 11 | 12 | ```sh 13 | cargo run --package generate --bin generate-pulse 14 | ``` 15 | 16 | [audio-device]: https://docs.rs/audio-device 17 | -------------------------------------------------------------------------------- /audio-device-pipewire-sys/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> anyhow::Result<()> { 2 | pkg_config::Config::new() 3 | .statik(false) 4 | .probe("libpipewire-0.3")?; 5 | Ok(()) 6 | } 7 | -------------------------------------------------------------------------------- /audio-device-pipewire-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [github](https://github.com/udoprog/audio) 2 | //! [crates.io](https://crates.io/crates/audio-device-pipewire-sys) 3 | //! [docs.rs](https://docs.rs/audio-device-pipewire-sys) 4 | //! 5 | //! [audio-device] system bindings for PulseAudio. 6 | //! 7 | //! These bindings are generated with: 8 | //! 9 | //! ```sh 10 | //! cargo run --package generate --bin generate-pulse 11 | //! ``` 12 | //! 13 | //! [audio-device]: https://docs.rs/audio-device 14 | 15 | #![allow(non_camel_case_types, non_upper_case_globals)] 16 | 17 | use libc::{itimerspec, timespec}; 18 | 19 | include!("bindings.rs"); 20 | -------------------------------------------------------------------------------- /audio-device-pulse-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "audio-device-pulse-sys" 3 | version = "0.1.0-alpha.1" 4 | authors = ["John-John Tedro "] 5 | edition = "2018" 6 | rust-version = "1.70" 7 | description = "audio-device system bindings for PulseAudio" 8 | documentation = "https://docs.rs/audio" 9 | readme = "README.md" 10 | homepage = "https://github.com/udoprog/audio" 11 | repository = "https://github.com/udoprog/audio" 12 | license = "MIT OR Apache-2.0" 13 | keywords = ["audio", "buffer", "dsp"] 14 | categories = ["multimedia::audio"] 15 | 16 | [dependencies] 17 | libc = "0.2.125" 18 | 19 | [build-dependencies] 20 | anyhow = "1.0.57" 21 | pkg-config = "0.3.25" 22 | -------------------------------------------------------------------------------- /audio-device-pulse-sys/README.md: -------------------------------------------------------------------------------- 1 | # audio-device-pulse-sys 2 | 3 | [github](https://github.com/udoprog/audio) 4 | [crates.io](https://crates.io/crates/audio-device-pulse-sys) 5 | [docs.rs](https://docs.rs/audio-device-pulse-sys) 6 | [build status](https://github.com/udoprog/audio/actions?query=branch%3Amain) 7 | 8 | [audio-device] system bindings for PulseAudio. 9 | 10 | These bindings are generated with: 11 | 12 | ```sh 13 | cargo run --package generate --bin generate-pulse 14 | ``` 15 | 16 | [audio-device]: https://docs.rs/audio-device 17 | -------------------------------------------------------------------------------- /audio-device-pulse-sys/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> anyhow::Result<()> { 2 | pkg_config::Config::new().statik(false).probe("libpulse")?; 3 | Ok(()) 4 | } 5 | -------------------------------------------------------------------------------- /audio-device-pulse-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [github](https://github.com/udoprog/audio) 2 | //! [crates.io](https://crates.io/crates/audio-device-pulse-sys) 3 | //! [docs.rs](https://docs.rs/audio-device-pulse-sys) 4 | //! 5 | //! [audio-device] system bindings for PulseAudio. 6 | //! 7 | //! These bindings are generated with: 8 | //! 9 | //! ```sh 10 | //! cargo run --package generate --bin generate-pulse 11 | //! ``` 12 | //! 13 | //! [audio-device]: https://docs.rs/audio-device 14 | 15 | #![allow(non_camel_case_types)] 16 | 17 | use libc::{pollfd, timeval}; 18 | 19 | include!("bindings.rs"); 20 | -------------------------------------------------------------------------------- /audio-device/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "audio-device" 3 | version = "0.1.0-alpha.6" 4 | authors = ["John-John Tedro "] 5 | edition = "2018" 6 | rust-version = "1.70" 7 | description = "A library for interacting with audio devices" 8 | documentation = "https://docs.rs/audio" 9 | readme = "README.md" 10 | homepage = "https://github.com/udoprog/audio" 11 | repository = "https://github.com/udoprog/audio" 12 | license = "MIT OR Apache-2.0" 13 | keywords = ["audio", "buffer", "dsp"] 14 | categories = ["multimedia::audio"] 15 | 16 | autoexamples = false 17 | 18 | [package.metadata.docs.rs] 19 | all-features = true 20 | rustdoc-args = ["--cfg", "docsrs"] 21 | 22 | [features] 23 | default = [] 24 | alsa = ["alsa-sys", "poll-driver"] 25 | pulse = ["pulse-sys", "libc", "unix"] 26 | pipewire = ["pipewire-sys", "libc"] 27 | events-driver = ["windows"] 28 | poll-driver = ["unix"] 29 | unix = ["libc"] 30 | wasapi = [ 31 | "windows", 32 | "events-driver", 33 | "windows?/Win32_System_Threading", 34 | "windows?/Win32_Foundation", 35 | "windows?/Win32_System_Com_StructuredStorage", 36 | "windows?/Win32_Security", 37 | "windows?/Win32_System_WindowsProgramming", 38 | "windows?/Win32_System_Com", 39 | "windows?/Win32_Media_Audio", 40 | "windows?/Win32_Media_KernelStreaming", 41 | "windows?/Win32_Media_Multimedia", 42 | ] 43 | 44 | [dependencies] 45 | tracing = "0.1.36" 46 | audio-core = { version = "0.2.0", path = "../audio-core" } 47 | thiserror = "1.0.31" 48 | rand = "0.8.5" 49 | ste = { version = "0.1.0-alpha.11", path = "../ste" } 50 | 51 | pulse-sys = { package = "audio-device-pulse-sys", version = "0.1.0-alpha.1", path = "../audio-device-pulse-sys", optional = true } 52 | pipewire-sys = { package = "audio-device-pipewire-sys", version = "0.1.0-alpha.1", path = "../audio-device-pipewire-sys", optional = true } 53 | 54 | # unix 55 | alsa-sys = { package = "audio-device-alsa-sys", version = "0.1.0-alpha.1", path = "../audio-device-alsa-sys", optional = true } 56 | libc = { version = "0.2.125", optional = true } 57 | 58 | [dev-dependencies] 59 | audio = { version = "0.2.0", path = "../audio" } 60 | audio-generator = { version = "0.1.0-alpha.2", path = "../audio-generator" } 61 | anyhow = "1.0.57" 62 | tokio = { version = "1.18.1", features = ["full"] } 63 | tracing-subscriber = { version = "0.3.3", features = ["env-filter"] } 64 | 65 | [dependencies.windows] 66 | version = "0.40.0" 67 | optional = true 68 | 69 | [[example]] 70 | name = "alsa-list" 71 | required-features = ["alsa"] 72 | 73 | [[example]] 74 | name = "alsa" 75 | required-features = ["alsa"] 76 | 77 | [[example]] 78 | name = "pulse" 79 | required-features = ["pulse"] 80 | 81 | [[example]] 82 | name = "alsa-async" 83 | required-features = ["alsa", "poll-driver"] 84 | 85 | [[example]] 86 | name = "wasapi" 87 | required-features = ["wasapi"] 88 | 89 | [[example]] 90 | name = "wasapi-async" 91 | required-features = ["wasapi"] 92 | 93 | [[example]] 94 | name = "events" 95 | required-features = ["events-driver"] 96 | 97 | [[example]] 98 | name = "poll" 99 | required-features = ["poll-driver"] 100 | -------------------------------------------------------------------------------- /audio-device/README.md: -------------------------------------------------------------------------------- 1 | # audio-device 2 | 3 | [github](https://github.com/udoprog/audio) 4 | [crates.io](https://crates.io/crates/audio-device) 5 | [docs.rs](https://docs.rs/audio-device) 6 | [build status](https://github.com/udoprog/audio/actions?query=branch%3Amain) 7 | 8 | A library for interacting with audio devices. 9 | 10 | The sole aim of this crate is to provide idiomatic *low level* audio 11 | interface drivers that can be used independently. If all you need is WASAPI 12 | or ALSA, then that is all you pay for and you should have a decent 13 | Rust-idiomatic programming experience. 14 | 15 | This is part of the [audio ecosystem] and makes use of core traits provided 16 | by the [audio-core] crate. 17 | 18 |
19 | 20 | ## Examples 21 | 22 | * [ALSA blocking playback][alsa-blocking]. 23 | * [ALSA async playback][alsa-async]. 24 | * [WASAPI blocking playback][wasapi-blocking]. 25 | * [WASAPI async playback][wasapi-async]. 26 | 27 |
28 | 29 | ## Support 30 | 31 | Supported tier 1 platforms and systems are the following: 32 | 33 | | Platform | System | Blocking | Async | 34 | |----------|--------|----------|---------| 35 | | Windows | WASAPI | **wip** | **wip** | 36 | | Linux | ALSA | **wip** | **wip** | 37 | 38 | [audio ecosystem]: https://docs.rs/audio 39 | [alsa-blocking]: https://github.com/udoprog/audio/blob/main/audio-device/examples/alsa.rs 40 | [alsa-async]: https://github.com/udoprog/audio/blob/main/audio-device/examples/alsa-async.rs 41 | [audio-core]: https://docs.rs/audio-core 42 | [wasapi-async]: https://github.com/udoprog/audio/blob/main/audio-device/examples/wasapi-async.rs 43 | [wasapi-blocking]: https://github.com/udoprog/audio/blob/main/audio-device/examples/wasapi.rs 44 | -------------------------------------------------------------------------------- /audio-device/examples/alsa-async.rs: -------------------------------------------------------------------------------- 1 | use audio_core::ReadBuf; 2 | use audio_core::Translate as _; 3 | use audio_device::alsa; 4 | use audio_generator::{self as gen, Generator as _}; 5 | 6 | async fn generate_audio() -> anyhow::Result<()> { 7 | let mut pcm = alsa::Pcm::open_default_nonblocking(alsa::Stream::Playback)?; 8 | 9 | let config = pcm.configure::().install()?; 10 | let mut writer = pcm.async_writer::()?; 11 | dbg!(config); 12 | 13 | let sample_rate = config.rate as f32; 14 | let channels = config.channels as usize; 15 | 16 | let mut a = gen::Sine::new(261.63, sample_rate); 17 | let mut b = gen::Sine::new(329.63, sample_rate); 18 | let mut c = gen::Sine::new(440.00, sample_rate); 19 | let mut buf = [0i16; 16 * 1024]; 20 | 21 | loop { 22 | for o in (0..buf.len()).step_by(channels) { 23 | let s = i16::translate((a.sample() + b.sample() + c.sample()) * 0.01); 24 | 25 | for c in 0..channels { 26 | buf[o + c] = s; 27 | } 28 | } 29 | 30 | let mut buf = audio::wrap::interleaved(&buf[..], channels); 31 | 32 | while buf.has_remaining() { 33 | writer.write_interleaved(&mut buf).await?; 34 | } 35 | } 36 | } 37 | 38 | #[tokio::main] 39 | async fn main() -> anyhow::Result<()> { 40 | let runtime = audio_device::runtime::Runtime::new()?; 41 | let bg = ste::Builder::new().build()?; 42 | 43 | bg.submit_async(runtime.wrap(generate_audio())).await?; 44 | 45 | bg.join(); 46 | runtime.join(); 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /audio-device/examples/alsa-list.rs: -------------------------------------------------------------------------------- 1 | use audio_device::alsa; 2 | use std::ffi::CString; 3 | 4 | fn main() -> anyhow::Result<()> { 5 | let thread = ste::spawn(); 6 | 7 | thread.submit(|| { 8 | for card in alsa::cards() { 9 | let card = card?; 10 | let name = CString::new(format!("hw:{}", card.index()))?; 11 | let control = alsa::Control::open(&name)?; 12 | 13 | println!( 14 | "{} ({})", 15 | card.long_name()?.to_str()?, 16 | card.name()?.to_str()? 17 | ); 18 | 19 | println!("control: {}", control.name().to_str()?); 20 | 21 | for (n, element) in control.element_list()?.iter().enumerate() { 22 | println!( 23 | "{}: {} ({}) (index: {})", 24 | n, 25 | element.interface(), 26 | element.name().to_str()?, 27 | element.index(), 28 | ); 29 | } 30 | } 31 | 32 | Ok::<_, anyhow::Error>(()) 33 | })?; 34 | 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /audio-device/examples/alsa.rs: -------------------------------------------------------------------------------- 1 | use audio_core::ReadBuf; 2 | use audio_core::Translate as _; 3 | use audio_device::alsa; 4 | use audio_generator::{self as gen, Generator as _}; 5 | 6 | fn generate_audio() -> anyhow::Result<()> { 7 | let mut pcm = alsa::Pcm::open_default(alsa::Stream::Playback)?; 8 | 9 | let config = pcm.configure::().install()?; 10 | let mut writer = pcm.writer::()?; 11 | dbg!(config); 12 | 13 | let sample_rate = config.rate as f32; 14 | let channels = config.channels as usize; 15 | 16 | let mut a = gen::Sine::new(261.63, sample_rate); 17 | let mut b = gen::Sine::new(329.63, sample_rate); 18 | let mut c = gen::Sine::new(440.00, sample_rate); 19 | let mut buf = [0i16; 16 * 1024]; 20 | 21 | loop { 22 | for o in (0..buf.len()).step_by(channels) { 23 | let s = i16::translate((a.sample() + b.sample() + c.sample()) * 0.01); 24 | 25 | for c in 0..channels { 26 | buf[o + c] = s; 27 | } 28 | } 29 | 30 | let mut buf = audio::wrap::interleaved(&buf[..], channels); 31 | 32 | while buf.has_remaining() { 33 | writer.write_interleaved(&mut buf)?; 34 | } 35 | } 36 | } 37 | 38 | fn main() -> anyhow::Result<()> { 39 | let bg = ste::spawn(); 40 | bg.submit(generate_audio)?; 41 | bg.join(); 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /audio-device/examples/events.rs: -------------------------------------------------------------------------------- 1 | use audio_device::windows::AsyncEvent; 2 | use std::sync::Arc; 3 | 4 | #[tokio::main] 5 | async fn main() -> anyhow::Result<()> { 6 | let runtime = audio_device::runtime::Runtime::new()?; 7 | let guard = runtime.enter(); 8 | let event = Arc::new(AsyncEvent::new(false)?); 9 | let event2 = event.clone(); 10 | 11 | tokio::spawn(async move { 12 | tokio::time::sleep(std::time::Duration::from_secs(5)).await; 13 | event2.set(); 14 | }); 15 | 16 | println!("waiting for event..."); 17 | event.wait().await; 18 | println!("event woken up"); 19 | 20 | drop(guard); 21 | runtime.join(); 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /audio-device/examples/poll.rs: -------------------------------------------------------------------------------- 1 | use audio_device::runtime::Runtime; 2 | use audio_device::unix::AsyncPoll; 3 | use std::fs::OpenOptions; 4 | use std::io; 5 | use std::io::Read; 6 | use std::os::unix::fs::OpenOptionsExt; 7 | use std::os::unix::io::AsRawFd; 8 | 9 | /// Read the data from the reader `R` asynchronously using the specified poll 10 | /// handle. 11 | async fn read_to_vec(handle: &AsyncPoll, mut read: R) -> io::Result> 12 | where 13 | R: Read, 14 | { 15 | let mut data = Vec::::new(); 16 | let mut buf = [0u8; 1024]; 17 | 18 | 'outer: loop { 19 | let guard = handle.returned_events().await; 20 | let events = guard.events(); 21 | 22 | if events & libc::POLLIN != 0 { 23 | loop { 24 | match read.read(&mut buf[..]) { 25 | Ok(0) => break 'outer, 26 | Ok(n) => { 27 | data.extend(&buf[..n]); 28 | } 29 | // Note: This will most likely never happen, because 30 | // nonblocking I/O on Linux file descriptors are never 31 | // signalled as blocking. 32 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => { 33 | continue 'outer; 34 | } 35 | Err(e) => { 36 | return Err(e.into()); 37 | } 38 | } 39 | } 40 | } else { 41 | panic!("did not grok the correct error kind here: {:?}", events); 42 | } 43 | } 44 | 45 | Ok(data) 46 | } 47 | 48 | #[tokio::main] 49 | async fn main() -> anyhow::Result<()> { 50 | let runtime = Runtime::new()?; 51 | let _guard = runtime.enter(); 52 | 53 | let mut file = OpenOptions::new() 54 | .read(true) 55 | .custom_flags(libc::O_NONBLOCK) 56 | .open("Cargo.toml")?; 57 | 58 | let pollfd = libc::pollfd { 59 | fd: file.as_raw_fd(), 60 | events: libc::POLLIN, 61 | revents: 0, 62 | }; 63 | 64 | let handle = unsafe { AsyncPoll::new(pollfd)? }; 65 | let data = read_to_vec(&handle, &mut file).await?; 66 | 67 | let contents = std::str::from_utf8(&data)?; 68 | println!("contents:\n{}", contents); 69 | Ok(()) 70 | } 71 | -------------------------------------------------------------------------------- /audio-device/examples/pulse.rs: -------------------------------------------------------------------------------- 1 | use audio_device::pulse; 2 | use std::ffi::CString; 3 | 4 | fn generate_audio() -> anyhow::Result<()> { 5 | let name = CString::new("Hello World")?; 6 | 7 | let mut main = pulse::MainLoop::new(); 8 | let mut context = main.context(&name); 9 | 10 | context.set_callback(|c| { 11 | println!("state changed: {}", c.state()?); 12 | Err(pulse::Error::User("hello".into())) 13 | })?; 14 | 15 | context.connect()?; 16 | std::thread::sleep(std::time::Duration::from_secs(10)); 17 | Ok(()) 18 | } 19 | 20 | fn main() -> anyhow::Result<()> { 21 | let bg = ste::spawn(); 22 | bg.submit(generate_audio)?; 23 | bg.join(); 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /audio-device/examples/wasapi-async.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use audio_device::wasapi; 3 | use audio_generator::{self as gen, Generator as _}; 4 | 5 | async fn run_output(client: wasapi::Client, mut config: wasapi::ClientConfig) -> Result<()> 6 | where 7 | T: Copy + wasapi::Sample + audio_core::Translate, 8 | [T]: rand::Fill, 9 | { 10 | config.sample_rate = 120000; 11 | 12 | let initialized = client.initialize_async::(config)?; 13 | let mut render_client = initialized.render_client()?; 14 | 15 | client.start()?; 16 | 17 | let config = initialized.config(); 18 | let sample_rate = config.sample_rate as f32; 19 | 20 | dbg!(config); 21 | 22 | let mut a = gen::Sine::new(261.63, sample_rate); 23 | let mut b = gen::Sine::new(329.63, sample_rate); 24 | let mut c = gen::Sine::new(440.00, sample_rate); 25 | 26 | loop { 27 | let mut data = render_client.buffer_mut_async().await?; 28 | 29 | for n in (0..data.len()).step_by(config.channels as usize) { 30 | let f = T::translate((a.sample() + b.sample() + c.sample()) * 0.01); 31 | 32 | for c in 0..config.channels as usize { 33 | data[n + c] = f; 34 | } 35 | } 36 | 37 | data.release()?; 38 | } 39 | } 40 | 41 | async fn generate_audio() -> Result<()> { 42 | let output = 43 | wasapi::default_output_client()?.ok_or_else(|| anyhow!("no default device found"))?; 44 | let config = output.default_client_config()?; 45 | 46 | match config.sample_format { 47 | wasapi::SampleFormat::I16 => run_output::(output, config).await, 48 | wasapi::SampleFormat::F32 => run_output::(output, config).await, 49 | } 50 | } 51 | 52 | #[tokio::main] 53 | pub async fn main() -> Result<()> { 54 | tracing_subscriber::fmt() 55 | .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) 56 | .init(); 57 | 58 | println!("WARNING: This program will generate audio and we do our best to avoid them being too loud."); 59 | println!("Please make sure your volume is turned down!"); 60 | println!(); 61 | println!("Press [enter] to continue..."); 62 | 63 | let mut line = String::new(); 64 | std::io::stdin().read_line(&mut line)?; 65 | 66 | let runtime = audio_device::runtime::Runtime::new()?; 67 | let bg = ste::Builder::new().prelude(wasapi::audio_prelude).build()?; 68 | 69 | bg.submit_async(runtime.wrap(generate_audio())).await?; 70 | 71 | bg.join(); 72 | runtime.join(); 73 | Ok(()) 74 | } 75 | -------------------------------------------------------------------------------- /audio-device/examples/wasapi.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use audio_device::wasapi; 3 | use audio_generator::{self as gen, Generator as _}; 4 | 5 | fn run_output(client: wasapi::Client, mut config: wasapi::ClientConfig) -> Result<()> 6 | where 7 | T: Copy + wasapi::Sample + audio_core::Translate, 8 | [T]: rand::Fill, 9 | { 10 | config.sample_rate = 120000; 11 | 12 | let initialized = client.initialize::(config)?; 13 | let mut render_client = initialized.render_client()?; 14 | 15 | client.start()?; 16 | 17 | let config = initialized.config(); 18 | let sample_rate = config.sample_rate as f32; 19 | 20 | dbg!(config); 21 | 22 | let mut a = gen::Sine::new(261.63, sample_rate); 23 | let mut b = gen::Sine::new(329.63, sample_rate); 24 | let mut c = gen::Sine::new(440.00, sample_rate); 25 | 26 | loop { 27 | let mut data = render_client.buffer_mut()?; 28 | 29 | for n in (0..data.len()).step_by(config.channels as usize) { 30 | let f = T::translate((a.sample() + b.sample() + c.sample()) * 0.01); 31 | 32 | for c in 0..config.channels as usize { 33 | data[n + c] = f; 34 | } 35 | } 36 | 37 | data.release()?; 38 | } 39 | } 40 | 41 | fn generate_audio() -> Result<()> { 42 | let output = 43 | wasapi::default_output_client()?.ok_or_else(|| anyhow!("no default device found"))?; 44 | let config = output.default_client_config()?; 45 | 46 | match config.sample_format { 47 | wasapi::SampleFormat::I16 => run_output::(output, config), 48 | wasapi::SampleFormat::F32 => run_output::(output, config), 49 | } 50 | } 51 | 52 | pub fn main() -> Result<()> { 53 | tracing_subscriber::fmt() 54 | .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) 55 | .init(); 56 | 57 | println!("WARNING: This program will generate audio and we do our best to avoid them being too loud."); 58 | println!("Please make sure your volume is turned down!"); 59 | println!(); 60 | println!("Press [enter] to continue..."); 61 | 62 | let mut line = String::new(); 63 | std::io::stdin().read_line(&mut line)?; 64 | 65 | let bg = ste::Builder::new().prelude(wasapi::audio_prelude).build()?; 66 | bg.submit(generate_audio)?; 67 | bg.join(); 68 | Ok(()) 69 | } 70 | -------------------------------------------------------------------------------- /audio-device/src/alsa/access_mask.rs: -------------------------------------------------------------------------------- 1 | use crate::alsa::{Access, Result}; 2 | use crate::libc as c; 3 | use alsa_sys as alsa; 4 | use std::mem; 5 | use std::ptr; 6 | 7 | /// Access mask used in combination with hardware parameters. 8 | /// 9 | /// # Examples 10 | /// 11 | /// ``` 12 | /// use audio_device::alsa; 13 | /// 14 | /// # fn main() -> anyhow::Result<()> { 15 | /// let mut mask = alsa::AccessMask::new()?; 16 | /// assert!(!mask.test(alsa::Access::MmapInterleaved)); 17 | /// assert!(mask.is_empty()); 18 | /// 19 | /// mask.set(alsa::Access::MmapInterleaved); 20 | /// assert!(!mask.is_empty()); 21 | /// assert!(mask.test(alsa::Access::MmapInterleaved)); 22 | /// 23 | /// mask.reset(alsa::Access::MmapInterleaved); 24 | /// assert!(!mask.test(alsa::Access::MmapInterleaved)); 25 | /// assert!(mask.is_empty()); 26 | /// # Ok(()) } 27 | /// ``` 28 | pub struct AccessMask { 29 | pub(super) handle: ptr::NonNull, 30 | } 31 | 32 | impl AccessMask { 33 | /// Construct a new empty access mask. 34 | /// 35 | /// # Examples 36 | /// 37 | /// ``` 38 | /// use audio_device::alsa; 39 | /// 40 | /// # fn main() -> anyhow::Result<()> { 41 | /// let mut mask = alsa::AccessMask::new()?; 42 | /// assert!(!mask.test(alsa::Access::MmapInterleaved)); 43 | /// # Ok(()) } 44 | /// ``` 45 | pub fn new() -> Result { 46 | unsafe { 47 | let mut mask = Self::allocate()?; 48 | alsa::snd_pcm_access_mask_none(mask.handle.as_mut()); 49 | Ok(mask) 50 | } 51 | } 52 | 53 | /// Allocate a new access mask. The state of it will be uninitialized. 54 | pub(super) unsafe fn allocate() -> Result { 55 | let mut handle = mem::MaybeUninit::uninit(); 56 | errno!(alsa::snd_pcm_access_mask_malloc(handle.as_mut_ptr()))?; 57 | let handle = ptr::NonNull::new_unchecked(handle.assume_init()); 58 | Ok(Self { handle }) 59 | } 60 | 61 | /// Test if mask is empty. 62 | /// 63 | /// See [AccessMask] documentation. 64 | pub fn is_empty(&self) -> bool { 65 | unsafe { alsa::snd_pcm_access_mask_empty(self.handle.as_ptr()) == 1 } 66 | } 67 | 68 | /// Set all bits. 69 | /// 70 | /// See [AccessMask] documentation. 71 | pub fn any(&mut self) { 72 | unsafe { 73 | alsa::snd_pcm_access_mask_any(self.handle.as_mut()); 74 | } 75 | } 76 | 77 | /// Reset all bits. 78 | /// 79 | /// See [AccessMask] documentation. 80 | pub fn none(&mut self) { 81 | unsafe { 82 | alsa::snd_pcm_access_mask_none(self.handle.as_mut()); 83 | } 84 | } 85 | 86 | /// Make an access type present. 87 | /// 88 | /// See [AccessMask] documentation. 89 | pub fn set(&mut self, access: Access) { 90 | unsafe { 91 | alsa::snd_pcm_access_mask_set(self.handle.as_mut(), access as c::c_uint); 92 | } 93 | } 94 | 95 | /// Make an access type missing. 96 | /// 97 | /// See [AccessMask] documentation. 98 | pub fn reset(&mut self, access: Access) { 99 | unsafe { 100 | alsa::snd_pcm_access_mask_reset(self.handle.as_mut(), access as c::c_uint); 101 | } 102 | } 103 | 104 | /// Test the presence of an access type. 105 | /// 106 | /// See [AccessMask] documentation. 107 | pub fn test(&mut self, access: Access) -> bool { 108 | unsafe { alsa::snd_pcm_access_mask_test(self.handle.as_mut(), access as c::c_uint) == 1 } 109 | } 110 | 111 | /// Copy one mask to another. 112 | pub fn copy(&mut self, other: &Self) { 113 | unsafe { 114 | alsa::snd_pcm_access_mask_copy(self.handle.as_mut(), other.handle.as_ptr()); 115 | } 116 | } 117 | } 118 | 119 | impl Drop for AccessMask { 120 | fn drop(&mut self) { 121 | unsafe { 122 | let _ = alsa::snd_pcm_access_mask_free(self.handle.as_mut()); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /audio-device/src/alsa/async_writer.rs: -------------------------------------------------------------------------------- 1 | use core::marker; 2 | 3 | use crate::alsa::{Error, Pcm, Result}; 4 | use crate::libc as c; 5 | use crate::unix::{Errno, PollFlags}; 6 | use crate::unix::AsyncPoll; 7 | 8 | /// An interleaved type-checked async PCM writer. 9 | /// 10 | /// See [Pcm::async_writer]. 11 | pub struct AsyncWriter<'a, T> { 12 | pcm: &'a mut Pcm, 13 | poll_handle: AsyncPoll, 14 | pollfd: c::pollfd, 15 | channels: usize, 16 | _marker: marker::PhantomData, 17 | } 18 | 19 | impl<'a, T> AsyncWriter<'a, T> { 20 | /// Construct a new writer surrounding the given PCM. 21 | /// 22 | /// # Safety 23 | /// 24 | /// This constructor assumes that the caller has checked that type `T` is 25 | /// appropriate for writing to the given PCM. 26 | pub(super) unsafe fn new(pcm: &'a mut Pcm, pollfd: c::pollfd, channels: usize) -> Result { 27 | Ok(Self { 28 | pcm, 29 | poll_handle: AsyncPoll::new(pollfd)?, 30 | pollfd, 31 | channels, 32 | _marker: marker::PhantomData, 33 | }) 34 | } 35 | 36 | /// Write an interleaved buffer. 37 | pub async fn write_interleaved(&mut self, mut buf: B) -> Result<()> 38 | where 39 | B: audio_core::Buf + audio_core::ReadBuf + audio_core::ExactSizeBuf + audio_core::InterleavedBuf, 40 | { 41 | if buf.channels() != self.channels { 42 | return Err(Error::ChannelsMismatch { 43 | actual: buf.channels(), 44 | expected: self.channels, 45 | }); 46 | } 47 | 48 | while buf.has_remaining() { 49 | self.pcm.tag.ensure_on_thread(); 50 | let frames = buf.frames() as usize; 51 | 52 | unsafe { 53 | let result = { 54 | let ptr = buf.as_interleaved().as_ptr() as *const c::c_void; 55 | self.pcm.write_interleaved_unchecked(ptr, frames as u64) 56 | }; 57 | 58 | let written = match result { 59 | Ok(written) => written as usize, 60 | Err(Error::Sys(Errno::EWOULDBLOCK)) => { 61 | loop { 62 | let guard = self.poll_handle.returned_events().await; 63 | self.pollfd.revents = guard.events(); 64 | 65 | let mut fds = [self.pollfd]; 66 | let flags = self.pcm.poll_descriptors_revents(&mut fds)?; 67 | 68 | if flags.test(PollFlags::POLLOUT) { 69 | break; 70 | } 71 | 72 | drop(guard); 73 | } 74 | 75 | continue; 76 | } 77 | Err(e) => return Err(e), 78 | }; 79 | 80 | buf.advance(written); 81 | } 82 | } 83 | 84 | Ok(()) 85 | } 86 | } 87 | 88 | // Safety: [Pcm] is tagged with the thread its created it and is ensured not to 89 | // leave it. 90 | unsafe impl Send for AsyncWriter<'_, T> {} 91 | -------------------------------------------------------------------------------- /audio-device/src/alsa/card.rs: -------------------------------------------------------------------------------- 1 | use crate::alsa::{CString, Result}; 2 | use crate::libc as c; 3 | use alsa_sys as alsa; 4 | use std::ffi::CStr; 5 | use std::mem; 6 | 7 | /// Construct an iterator over sounds cards. 8 | /// 9 | /// # Examples 10 | /// 11 | /// ```no_run 12 | /// use audio_device::alsa; 13 | /// 14 | /// # fn main() -> anyhow::Result<()> { 15 | /// for card in alsa::cards() { 16 | /// let card = card?; 17 | /// println!("{}", card.name()?.to_str()?); 18 | /// } 19 | /// # Ok(()) } 20 | /// ``` 21 | pub fn cards() -> Cards { 22 | Cards { index: -1 } 23 | } 24 | 25 | /// An iterator over available cards. 26 | /// 27 | /// See [cards]. 28 | pub struct Cards { 29 | index: c::c_int, 30 | } 31 | 32 | impl Iterator for Cards { 33 | type Item = Result; 34 | 35 | fn next(&mut self) -> Option { 36 | Some( 37 | if let Err(e) = errno!(unsafe { alsa::snd_card_next(&mut self.index) }) { 38 | Err(e.into()) 39 | } else { 40 | if self.index == -1 { 41 | return None; 42 | } 43 | 44 | Ok(Card { index: self.index }) 45 | }, 46 | ) 47 | } 48 | } 49 | 50 | /// A reference to a card. 51 | pub struct Card { 52 | index: c::c_int, 53 | } 54 | 55 | impl Card { 56 | /// Open the given pcm device identified by name. 57 | /// 58 | /// # Examples 59 | /// 60 | /// ```no_run 61 | /// use audio_device::alsa; 62 | /// use std::ffi::CStr; 63 | /// 64 | /// # fn main() -> anyhow::Result<()> { 65 | /// let name = CStr::from_bytes_with_nul(b"hw:0\0")?; 66 | /// 67 | /// let pcm = alsa::Card::open(name)?; 68 | /// # Ok(()) } 69 | /// ``` 70 | pub fn open(name: &CStr) -> Result { 71 | unsafe { 72 | let index = errno!(alsa::snd_card_get_index( 73 | name.to_bytes().as_ptr() as *const i8 74 | ))?; 75 | Ok(Self { index }) 76 | } 77 | } 78 | 79 | /// Get the index of the card. 80 | /// 81 | /// # Examples 82 | /// 83 | /// ```no_run 84 | /// use audio_device::alsa; 85 | /// 86 | /// # fn main() -> anyhow::Result<()> { 87 | /// for card in alsa::cards() { 88 | /// let card = card?; 89 | /// println!("{}", card.index()); 90 | /// } 91 | /// # Ok(()) } 92 | /// ``` 93 | pub fn index(&self) -> c::c_int { 94 | self.index 95 | } 96 | 97 | /// Get the name of the card. 98 | /// 99 | /// # Examples 100 | /// 101 | /// ```no_run 102 | /// use audio_device::alsa; 103 | /// 104 | /// # fn main() -> anyhow::Result<()> { 105 | /// for card in alsa::cards() { 106 | /// let card = card?; 107 | /// println!("{}", card.name()?.to_str()?); 108 | /// } 109 | /// # Ok(()) } 110 | /// ``` 111 | pub fn name(&self) -> Result { 112 | unsafe { 113 | let mut ptr = mem::MaybeUninit::uninit(); 114 | errno!(alsa::snd_card_get_name(self.index, ptr.as_mut_ptr()))?; 115 | let ptr = ptr.assume_init(); 116 | Ok(CString::from_raw(ptr)) 117 | } 118 | } 119 | 120 | /// Get the long name of the card. 121 | /// 122 | /// # Examples 123 | /// 124 | /// ```no_run 125 | /// use audio_device::alsa; 126 | /// 127 | /// # fn main() -> anyhow::Result<()> { 128 | /// for card in alsa::cards() { 129 | /// let card = card?; 130 | /// println!("{}", card.long_name()?.to_str()?); 131 | /// } 132 | /// # Ok(()) } 133 | /// ``` 134 | pub fn long_name(&self) -> Result { 135 | unsafe { 136 | let mut ptr = mem::MaybeUninit::uninit(); 137 | errno!(alsa::snd_card_get_longname(self.index, ptr.as_mut_ptr()))?; 138 | let ptr = ptr.assume_init(); 139 | Ok(CString::from_raw(ptr)) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /audio-device/src/alsa/cell.rs: -------------------------------------------------------------------------------- 1 | /// A runtime-checked container for the given type `T`. 2 | struct Cell { 3 | inner: std::cell::RefCell, 4 | } 5 | 6 | impl Cell { 7 | /// Construct a new cell. 8 | fn new(value: T) -> Self { 9 | Self { 10 | inner: std::cell::RefCell::new(value), 11 | } 12 | } 13 | 14 | /// Acquire the cell for reading. 15 | fn read(&self) -> Result> { 16 | match self.inner.try_borrow() { 17 | Ok(borrow) => Ok(borrow), 18 | Err(..) => Err(Error::BusyShared), 19 | } 20 | } 21 | 22 | /// Acquire the cell for writing. 23 | fn write(&self) -> Result> { 24 | match self.inner.try_borrow_mut() { 25 | Ok(borrow) => Ok(borrow), 26 | Err(..) => Err(Error::BusyShared), 27 | } 28 | } 29 | 30 | /// Get the mutable inner value without checking. 31 | /// 32 | /// This is permitted, because mutable access implies exclusive access. 33 | fn get_mut(&mut self) -> &mut T { 34 | self.inner.get_mut() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /audio-device/src/alsa/channel_area.rs: -------------------------------------------------------------------------------- 1 | // component is work in progress 2 | #![allow(unused)] 3 | 4 | use crate::libc as c; 5 | use alsa_sys as alsa; 6 | use std::ptr; 7 | 8 | /// A memory-mapped channel area. 9 | pub struct ChannelArea<'a> { 10 | pub(super) pcm: &'a mut ptr::NonNull, 11 | pub(super) area: *const alsa::snd_pcm_channel_area_t, 12 | pub(super) offset: c::c_ulong, 13 | pub(super) frames: c::c_ulong, 14 | } 15 | -------------------------------------------------------------------------------- /audio-device/src/alsa/format_mask.rs: -------------------------------------------------------------------------------- 1 | use crate::alsa::{Format, Result}; 2 | use crate::libc as c; 3 | use alsa_sys as alsa; 4 | use std::mem; 5 | use std::ptr; 6 | 7 | /// Format mask used in combination with hardware parameters. 8 | /// 9 | /// # Examples 10 | /// 11 | /// ``` 12 | /// use audio_device::alsa; 13 | /// 14 | /// # fn main() -> anyhow::Result<()> { 15 | /// let mut mask = alsa::FormatMask::new()?; 16 | /// assert!(!mask.test(alsa::Format::S8)); 17 | /// assert!(mask.is_empty()); 18 | /// 19 | /// mask.set(alsa::Format::S8); 20 | /// assert!(!mask.is_empty()); 21 | /// assert!(mask.test(alsa::Format::S8)); 22 | /// 23 | /// mask.reset(alsa::Format::S8); 24 | /// assert!(!mask.test(alsa::Format::S8)); 25 | /// assert!(mask.is_empty()); 26 | /// # Ok(()) } 27 | /// ``` 28 | pub struct FormatMask { 29 | pub(super) handle: ptr::NonNull, 30 | } 31 | 32 | impl FormatMask { 33 | /// Construct a new empty access mask. 34 | /// 35 | /// # Examples 36 | /// 37 | /// ``` 38 | /// use audio_device::alsa; 39 | /// 40 | /// # fn main() -> anyhow::Result<()> { 41 | /// let mut mask = alsa::FormatMask::new()?; 42 | /// assert!(!mask.test(alsa::Format::S8)); 43 | /// # Ok(()) } 44 | /// ``` 45 | pub fn new() -> Result { 46 | unsafe { 47 | let mut mask = Self::allocate()?; 48 | mask.none(); 49 | Ok(mask) 50 | } 51 | } 52 | 53 | /// Allocate a new access mask. The state of it will be uninitialized. 54 | pub(super) unsafe fn allocate() -> Result { 55 | let mut handle = mem::MaybeUninit::uninit(); 56 | errno!(alsa::snd_pcm_format_mask_malloc(handle.as_mut_ptr()))?; 57 | let handle = ptr::NonNull::new_unchecked(handle.assume_init()); 58 | Ok(Self { handle }) 59 | } 60 | 61 | /// Test if mask is empty. 62 | /// 63 | /// See [FormatMask] documentation. 64 | pub fn is_empty(&self) -> bool { 65 | unsafe { alsa::snd_pcm_format_mask_empty(self.handle.as_ptr()) == 1 } 66 | } 67 | 68 | /// Set all bits. 69 | /// 70 | /// See [FormatMask] documentation. 71 | pub fn any(&mut self) { 72 | unsafe { 73 | alsa::snd_pcm_format_mask_any(self.handle.as_mut()); 74 | } 75 | } 76 | 77 | /// Reset all bits. 78 | /// 79 | /// See [FormatMask] documentation. 80 | pub fn none(&mut self) { 81 | unsafe { 82 | alsa::snd_pcm_format_mask_none(self.handle.as_mut()); 83 | } 84 | } 85 | 86 | /// Make a format present. 87 | /// 88 | /// See [FormatMask] documentation. 89 | pub fn set(&mut self, format: Format) { 90 | unsafe { 91 | alsa::snd_pcm_format_mask_set(self.handle.as_mut(), format as c::c_int); 92 | } 93 | } 94 | 95 | /// Make a format missing. 96 | /// 97 | /// See [FormatMask] documentation. 98 | pub fn reset(&mut self, format: Format) { 99 | unsafe { 100 | alsa::snd_pcm_format_mask_reset(self.handle.as_mut(), format as c::c_int); 101 | } 102 | } 103 | 104 | /// Test the presence of a format. 105 | /// 106 | /// See [FormatMask] documentation. 107 | pub fn test(&mut self, format: Format) -> bool { 108 | unsafe { alsa::snd_pcm_format_mask_test(self.handle.as_mut(), format as c::c_int) == 1 } 109 | } 110 | 111 | /// Copy one mask to another. 112 | pub fn copy(&mut self, other: &Self) { 113 | unsafe { 114 | alsa::snd_pcm_format_mask_copy(self.handle.as_mut(), other.handle.as_ptr()); 115 | } 116 | } 117 | } 118 | 119 | impl Drop for FormatMask { 120 | fn drop(&mut self) { 121 | unsafe { 122 | let _ = alsa::snd_pcm_format_mask_free(self.handle.as_mut()); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /audio-device/src/alsa/sample.rs: -------------------------------------------------------------------------------- 1 | use crate::alsa::Format; 2 | 3 | /// Trait used to designate types which are sample-appropriate for 4 | /// [Pcm][super::Pcm]. 5 | /// 6 | /// # Safety 7 | /// 8 | /// This trait is unsafe to implement, because an incorrectly implemented format 9 | /// test might have safety implications. 10 | pub unsafe trait Sample { 11 | /// The default format to use for this sample. 12 | const DEFAULT_FORMAT: Format; 13 | 14 | /// Test if the given format is appropriate for this sample type. 15 | fn test(format: Format) -> bool; 16 | 17 | /// A static description of the sample type. 18 | fn describe() -> &'static str; 19 | } 20 | 21 | macro_rules! implement { 22 | ($ty:ty, $le:ident, $be:ident) => { 23 | unsafe impl Sample for $ty { 24 | #[cfg(target_endian = "little")] 25 | const DEFAULT_FORMAT: Format = Format::$le; 26 | #[cfg(target_endian = "big")] 27 | const DEFAULT_FORMAT: Format = Format::$be; 28 | 29 | fn test(format: Format) -> bool { 30 | match format { 31 | #[cfg(target_endian = "little")] 32 | Format::$le => true, 33 | #[cfg(target_endian = "big")] 34 | Format::$be => true, 35 | _ => false, 36 | } 37 | } 38 | 39 | #[cfg(target_endian = "little")] 40 | fn describe() -> &'static str { 41 | concat!(stringify!($ty), " (little endian)") 42 | } 43 | 44 | #[cfg(target_endian = "big")] 45 | fn describe() -> &'static str { 46 | concat!(stringify!($ty), " (big endian)") 47 | } 48 | } 49 | }; 50 | } 51 | 52 | unsafe impl Sample for u8 { 53 | const DEFAULT_FORMAT: Format = Format::U8; 54 | 55 | fn test(format: Format) -> bool { 56 | matches!(format, Format::U8) 57 | } 58 | 59 | #[cfg(target_endian = "little")] 60 | fn describe() -> &'static str { 61 | "u8 (little endian)" 62 | } 63 | 64 | #[cfg(target_endian = "big")] 65 | fn describe() -> &'static str { 66 | "u8 (big endian)" 67 | } 68 | } 69 | 70 | unsafe impl Sample for i8 { 71 | const DEFAULT_FORMAT: Format = Format::S8; 72 | 73 | fn test(format: Format) -> bool { 74 | matches!(format, Format::S8) 75 | } 76 | 77 | #[cfg(target_endian = "little")] 78 | fn describe() -> &'static str { 79 | "i8 (little endian)" 80 | } 81 | 82 | #[cfg(target_endian = "big")] 83 | fn describe() -> &'static str { 84 | "i8 (big endian)" 85 | } 86 | } 87 | 88 | implement!(i16, S16LE, S16BE); 89 | implement!(u16, U16LE, U16BE); 90 | implement!(i32, S32LE, S32BE); 91 | implement!(u32, U32LE, U32BE); 92 | implement!(f32, FloatLE, FloatBE); 93 | implement!(f64, Float64LE, Float64BE); 94 | -------------------------------------------------------------------------------- /audio-device/src/alsa/writer.rs: -------------------------------------------------------------------------------- 1 | use core::marker; 2 | 3 | use crate::alsa::{Error, Pcm, Result}; 4 | use crate::libc as c; 5 | 6 | /// A interleaved type-checked PCM writer. 7 | /// 8 | /// See [Pcm::writer]. 9 | pub struct Writer<'a, T> { 10 | pcm: &'a mut Pcm, 11 | channels: usize, 12 | _marker: marker::PhantomData, 13 | } 14 | 15 | impl<'a, T> Writer<'a, T> { 16 | /// Construct a new writer surrounding the given PCM. 17 | /// 18 | /// # Safety 19 | /// 20 | /// This constructor assumes that the caller has checked that type `T` is 21 | /// appropriate for writing to the given PCM. 22 | pub(super) unsafe fn new(pcm: &'a mut Pcm, channels: usize) -> Self { 23 | Self { 24 | pcm, 25 | channels, 26 | _marker: marker::PhantomData, 27 | } 28 | } 29 | 30 | /// Write an interleaved buffer. 31 | pub fn write_interleaved(&mut self, mut buf: B) -> Result<()> 32 | where 33 | B: audio_core::Buf + audio_core::ReadBuf + audio_core::ExactSizeBuf + audio_core::InterleavedBuf, 34 | { 35 | if buf.channels() != self.channels { 36 | return Err(Error::ChannelsMismatch { 37 | actual: buf.channels(), 38 | expected: self.channels, 39 | }); 40 | } 41 | 42 | let frames = buf.frames() as usize; 43 | 44 | unsafe { 45 | let ptr = buf.as_interleaved().as_ptr() as *const c::c_void; 46 | let written = self.pcm.write_interleaved_unchecked(ptr, frames as u64)?; 47 | buf.advance(written as usize); 48 | } 49 | 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /audio-device/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | /// Audio runtime errors. 4 | #[derive(Debug, Error)] 5 | pub enum Error { 6 | #[cfg(feature = "unix")] 7 | #[error("system error: {0}")] 8 | /// A unix system error. 9 | Unix(#[from] crate::unix::Errno), 10 | #[cfg(feature = "windows")] 11 | #[error("system error: {0}")] 12 | /// A windows system error. 13 | Windows( 14 | #[from] 15 | #[source] 16 | windows::core::Error, 17 | ), 18 | } 19 | 20 | /// The re-exported error type. 21 | pub type Result = ::std::result::Result; 22 | -------------------------------------------------------------------------------- /audio-device/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [github](https://github.com/udoprog/audio) 2 | //! [crates.io](https://crates.io/crates/audio-device) 3 | //! [docs.rs](https://docs.rs/audio-device) 4 | //! 5 | //! A library for interacting with audio devices. 6 | //! 7 | //! The sole aim of this crate is to provide idiomatic *low level* audio 8 | //! interface drivers that can be used independently. If all you need is WASAPI 9 | //! or ALSA, then that is all you pay for and you should have a decent 10 | //! Rust-idiomatic programming experience. 11 | //! 12 | //! This is part of the [audio ecosystem] and makes use of core traits provided 13 | //! by the [audio-core] crate. 14 | //! 15 | //!
16 | //! 17 | //! ## Examples 18 | //! 19 | //! * [ALSA blocking playback][alsa-blocking]. 20 | //! * [ALSA async playback][alsa-async]. 21 | //! * [WASAPI blocking playback][wasapi-blocking]. 22 | //! * [WASAPI async playback][wasapi-async]. 23 | //! 24 | //!
25 | //! 26 | //! ## Support 27 | //! 28 | //! Supported tier 1 platforms and systems are the following: 29 | //! 30 | //! | Platform | System | Blocking | Async | 31 | //! |----------|--------|----------|---------| 32 | //! | Windows | WASAPI | **wip** | **wip** | 33 | //! | Linux | ALSA | **wip** | **wip** | 34 | //! 35 | //! [audio ecosystem]: https://docs.rs/audio 36 | //! [alsa-blocking]: https://github.com/udoprog/audio/blob/main/audio-device/examples/alsa.rs 37 | //! [alsa-async]: https://github.com/udoprog/audio/blob/main/audio-device/examples/alsa-async.rs 38 | //! [audio-core]: https://docs.rs/audio-core 39 | //! [wasapi-async]: https://github.com/udoprog/audio/blob/main/audio-device/examples/wasapi-async.rs 40 | //! [wasapi-blocking]: https://github.com/udoprog/audio/blob/main/audio-device/examples/wasapi.rs 41 | 42 | #![warn(missing_docs)] 43 | #![cfg_attr(docsrs, feature(doc_cfg))] 44 | 45 | pub(crate) mod loom; 46 | 47 | #[macro_use] 48 | #[doc(hidden)] 49 | mod macros; 50 | 51 | cfg_unix! { 52 | #[macro_use] 53 | pub mod unix; 54 | } 55 | 56 | cfg_wasapi! { 57 | pub mod wasapi; 58 | } 59 | 60 | cfg_windows! { 61 | pub mod windows; 62 | } 63 | 64 | cfg_libc! { 65 | pub mod libc; 66 | } 67 | 68 | cfg_alsa! { 69 | pub mod alsa; 70 | } 71 | 72 | cfg_pulse! { 73 | pub mod pulse; 74 | } 75 | 76 | cfg_pipewire! { 77 | pub mod pipewire; 78 | } 79 | 80 | pub mod runtime; 81 | 82 | mod error; 83 | pub use self::error::{Error, Result}; 84 | -------------------------------------------------------------------------------- /audio-device/src/libc.rs: -------------------------------------------------------------------------------- 1 | //! libc specifics 2 | //! 3 | //! These are all re-exports from the [libc crate] and are intended for local 4 | //! use w/ APIs that uses a C-like ABI, like [ALSA][crate::alsa]. 5 | //! 6 | //! [libc crate]: https://crates.io/crates/libc 7 | 8 | pub use ::libc::eventfd; 9 | pub use ::libc::free; 10 | pub use ::libc::nfds_t; 11 | pub use ::libc::{EFD_NONBLOCK, EWOULDBLOCK}; 12 | pub use ::libc::{c_char, c_int, c_long, c_short, c_uint, c_ulong, c_void}; 13 | pub use ::libc::{poll, pollfd, POLLIN, POLLOUT}; 14 | pub use ::libc::{read, write}; 15 | -------------------------------------------------------------------------------- /audio-device/src/loom/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | pub(crate) use ::std::cell; 4 | pub(crate) use ::std::thread; 5 | pub(crate) mod sync; 6 | -------------------------------------------------------------------------------- /audio-device/src/loom/sync.rs: -------------------------------------------------------------------------------- 1 | pub(crate) use ::std::sync::atomic; 2 | pub(crate) use ::std::sync::Arc; 3 | use std::ops::{Deref, DerefMut}; 4 | 5 | pub(crate) struct MutexGuard<'a, T> { 6 | inner: ::std::sync::MutexGuard<'a, T>, 7 | } 8 | 9 | impl Deref for MutexGuard<'_, T> { 10 | type Target = T; 11 | 12 | fn deref(&self) -> &T { 13 | self.inner.deref() 14 | } 15 | } 16 | 17 | impl DerefMut for MutexGuard<'_, T> { 18 | fn deref_mut(&mut self) -> &mut T { 19 | self.inner.deref_mut() 20 | } 21 | } 22 | 23 | pub(crate) struct Mutex { 24 | inner: ::std::sync::Mutex, 25 | } 26 | 27 | impl Mutex { 28 | pub(crate) fn new(value: T) -> Self { 29 | Self { 30 | inner: ::std::sync::Mutex::new(value), 31 | } 32 | } 33 | 34 | pub(crate) fn lock(&self) -> MutexGuard<'_, T> { 35 | MutexGuard { 36 | inner: self.inner.lock().unwrap(), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /audio-device/src/macros.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | /// Macro to use for modules constrained to any windows-specific audio drivers. 4 | macro_rules! cfg_windows { 5 | ($($item:item)*) => { 6 | $( 7 | #[cfg(any(feature = "windows"))] 8 | #[cfg_attr(docsrs, doc( 9 | cfg(any(feature = "windows"))) 10 | )] 11 | $item 12 | )* 13 | } 14 | } 15 | 16 | /// Macro to use for modules constrained to any unix-specific audio drivers. 17 | macro_rules! cfg_unix { 18 | ($($item:item)*) => { 19 | $( 20 | #[cfg(feature = "unix")] 21 | #[cfg_attr(docsrs, doc( 22 | cfg(feature = "unix") 23 | ))] 24 | $item 25 | )* 26 | } 27 | } 28 | 29 | macro_rules! cfg_libc { 30 | ($($item:item)*) => { 31 | $( 32 | #[cfg(feature = "libc")] 33 | #[cfg_attr(docsrs, doc( 34 | cfg(feature = "libc") 35 | ))] 36 | $item 37 | )* 38 | } 39 | } 40 | 41 | macro_rules! cfg_events_driver { 42 | ($($item:item)*) => { 43 | $( 44 | #[cfg(feature = "events-driver")] 45 | #[cfg_attr(docsrs, doc( 46 | cfg(feature = "events-driver") 47 | ))] 48 | $item 49 | )* 50 | } 51 | } 52 | 53 | macro_rules! cfg_poll_driver { 54 | ($($item:item)*) => { 55 | $( 56 | #[cfg(feature = "poll-driver")] 57 | #[cfg_attr(docsrs, doc( 58 | cfg(feature = "poll-driver") 59 | ))] 60 | $item 61 | )* 62 | } 63 | } 64 | 65 | macro_rules! cfg_wasapi { 66 | ($($item:item)*) => { 67 | $( 68 | #[cfg(feature = "wasapi")] 69 | #[cfg_attr(docsrs, doc( 70 | cfg(feature = "wasapi") 71 | ))] 72 | $item 73 | )* 74 | } 75 | } 76 | 77 | macro_rules! cfg_alsa { 78 | ($($item:item)*) => { 79 | $( 80 | #[cfg(feature = "alsa")] 81 | #[cfg_attr(docsrs, doc( 82 | cfg(feature = "alsa") 83 | ))] 84 | $item 85 | )* 86 | } 87 | } 88 | 89 | macro_rules! cfg_pulse { 90 | ($($item:item)*) => { 91 | $( 92 | #[cfg(feature = "pulse")] 93 | #[cfg_attr(docsrs, doc( 94 | cfg(feature = "pulse") 95 | ))] 96 | $item 97 | )* 98 | } 99 | } 100 | 101 | macro_rules! cfg_pipewire { 102 | ($($item:item)*) => { 103 | $( 104 | #[cfg(feature = "pipewire")] 105 | #[cfg_attr(docsrs, doc( 106 | cfg(feature = "pipewire") 107 | ))] 108 | $item 109 | )* 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /audio-device/src/pipewire/main_loop.rs: -------------------------------------------------------------------------------- 1 | use pipewire_sys as pw; 2 | use std::ptr; 3 | 4 | /// A PipeWire main loop. 5 | /// 6 | /// See [MainLoop::new]. 7 | pub struct MainLoop { 8 | handle: ptr::NonNull, 9 | } 10 | 11 | impl MainLoop { 12 | /// Construct a new main loop object. 13 | /// 14 | /// # Examples 15 | /// 16 | /// ```no_run 17 | /// use audio_device::pipewire; 18 | /// 19 | /// # fn main() -> anyhow::Result<()> { 20 | /// let m = pipewire::MainLoop::new(); 21 | /// # Ok(()) } 22 | /// ``` 23 | pub fn new() -> Self { 24 | unsafe { 25 | let mut handle = ptr::NonNull::new_unchecked(pw::pw_main_loop_new(ptr::null())); 26 | 27 | Self { handle } 28 | } 29 | } 30 | } 31 | 32 | impl Drop for MainLoop { 33 | fn drop(&mut self) { 34 | unsafe { 35 | pw::pw_main_loop_destroy(self.handle.as_mut()); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /audio-device/src/pipewire/mod.rs: -------------------------------------------------------------------------------- 1 | //! An idiomatic Rust PipeWire interface. 2 | // Documentation: https://docs.pipewire.org/ 3 | 4 | mod main_loop; 5 | pub use self::main_loop::MainLoop; 6 | 7 | mod property_list; 8 | pub use self::property_list::PropertyList; 9 | -------------------------------------------------------------------------------- /audio-device/src/pipewire/property_list.rs: -------------------------------------------------------------------------------- 1 | use pipewire_sys as pw; 2 | use std::ptr; 3 | 4 | /// A property list object. 5 | /// 6 | /// See [PropertyList::new]. 7 | pub struct PropertyList { 8 | pub(super) handle: ptr::NonNull, 9 | } 10 | 11 | impl PropertyList { 12 | /// Construct a property list. 13 | /// 14 | /// # Examples 15 | /// 16 | /// ``` 17 | /// use audio_device::pipewire; 18 | /// 19 | /// # fn main() -> anyhow::Result<()> { 20 | /// let props = pipewire::PropertyList::new(); 21 | /// # Ok(()) } 22 | /// ``` 23 | pub fn new() -> Self { 24 | unsafe { 25 | Self { 26 | handle: ptr::NonNull::new_unchecked(pw::pw_properties_new(ptr::null())), 27 | } 28 | } 29 | } 30 | } 31 | 32 | impl Drop for PropertyList { 33 | fn drop(&mut self) { 34 | unsafe { 35 | pw::pw_properties_free(self.handle.as_mut()); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /audio-device/src/pulse/context.rs: -------------------------------------------------------------------------------- 1 | use crate::libc as c; 2 | use crate::pulse::{error, ContextState, Error, Result}; 3 | use pulse_sys as pulse; 4 | use std::ptr; 5 | 6 | /// Structure holding onto a registered callback. 7 | pub struct Callback { 8 | data: *mut (), 9 | drop: unsafe fn(*mut ()), 10 | } 11 | 12 | impl Drop for Callback { 13 | fn drop(&mut self) { 14 | unsafe { 15 | (self.drop)(self.data); 16 | } 17 | } 18 | } 19 | 20 | /// An opaque connection context to a daemon. 21 | /// 22 | /// See [MainLoop::context][super::MainLoop::context]. 23 | pub struct Context { 24 | pub(super) handle: ptr::NonNull, 25 | pub(super) callbacks: Vec, 26 | } 27 | 28 | impl Context { 29 | /// Set a callback function that is called whenever the context status 30 | /// changes. 31 | pub fn set_callback(&mut self, cb: C) -> Result<()> 32 | where 33 | C: 'static + FnMut(&mut Context) -> Result<()>, 34 | { 35 | let cx = self as *mut _; 36 | 37 | let cb = Box::into_raw(Box::new(Wrapper { cx, cb })); 38 | 39 | self.callbacks.push(Callback { 40 | data: cb as *mut (), 41 | drop: drop_impl::>, 42 | }); 43 | 44 | unsafe { 45 | return ffi_error!(pulse::pa_context_set_state_callback( 46 | self.handle.as_mut(), 47 | Some(callback::), 48 | cb as *mut c::c_void 49 | )); 50 | } 51 | 52 | extern "C" fn callback(cx: *mut pulse::pa_context, cb: *mut c::c_void) 53 | where 54 | C: 'static + FnMut(&mut Context) -> Result<()>, 55 | { 56 | unsafe { 57 | let cb = &mut *(cb as *mut Wrapper); 58 | debug_assert!(ptr::eq(cx, (*cb.cx).handle.as_ptr())); 59 | cb.call(); 60 | } 61 | } 62 | 63 | // Wire up the type `C` to be dropped once the context is dropped. 64 | unsafe fn drop_impl(data: *mut ()) { 65 | ptr::drop_in_place(data as *mut C); 66 | } 67 | 68 | struct Wrapper { 69 | cx: *mut Context, 70 | cb: C, 71 | } 72 | 73 | impl Wrapper 74 | where 75 | C: 'static + FnMut(&mut Context) -> Result<()>, 76 | { 77 | unsafe fn call(&mut self) { 78 | let cx = &mut (*self.cx); 79 | error::capture(|| (self.cb)(cx)); 80 | } 81 | } 82 | } 83 | 84 | /// Connect the context to the specified server. 85 | pub fn connect(&mut self) -> Result<()> { 86 | unsafe { 87 | error!( 88 | self, 89 | pulse::pa_context_connect(self.handle.as_mut(), ptr::null(), 0, ptr::null()) 90 | )?; 91 | Ok(()) 92 | } 93 | } 94 | 95 | /// Get the current state of the context. 96 | pub fn state(&self) -> Result { 97 | unsafe { 98 | let state = pulse::pa_context_get_state(self.handle.as_ptr()); 99 | ContextState::from_value(state).ok_or_else(|| Error::BadContextState(state)) 100 | } 101 | } 102 | } 103 | 104 | impl Drop for Context { 105 | fn drop(&mut self) { 106 | unsafe { 107 | pulse::pa_context_unref(self.handle.as_mut()); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /audio-device/src/pulse/enums.rs: -------------------------------------------------------------------------------- 1 | use pulse_sys as pulse; 2 | use std::fmt; 3 | 4 | macro_rules! decl_enum { 5 | ( 6 | $(#[doc = $doc:literal])* 7 | #[repr($ty:ident)] 8 | $vis:vis enum $name:ident { 9 | $( 10 | $(#[$m:meta])* 11 | $a:ident = $b:ident 12 | ),* $(,)? 13 | } 14 | ) => { 15 | $(#[doc = $doc])* 16 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 17 | #[non_exhaustive] 18 | #[repr($ty)] 19 | $vis enum $name { 20 | $( 21 | $(#[$m])* 22 | #[allow(missing_docs)] 23 | $a = pulse::$b, 24 | )* 25 | } 26 | 27 | impl $name { 28 | /// Parse the given enum from a value. 29 | $vis fn from_value(value: $ty) -> Option { 30 | Some(match value { 31 | $(pulse::$b => Self::$a,)* 32 | _ => return None, 33 | }) 34 | } 35 | } 36 | 37 | impl fmt::Display for $name { 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 39 | let id = match self { 40 | $(Self::$a => stringify!($a),)* 41 | }; 42 | 43 | f.write_str(id) 44 | } 45 | } 46 | } 47 | } 48 | 49 | decl_enum! { 50 | /// The state of a connection context. 51 | #[repr(u32)] 52 | pub enum ContextState { 53 | /// The context hasn't been connected yet. 54 | Unconnected = PA_CONTEXT_UNCONNECTED, 55 | /// A connection is being established. 56 | Connecting = PA_CONTEXT_CONNECTING, 57 | /// The client is authorizing itself to the daemon. 58 | Authorizing = PA_CONTEXT_AUTHORIZING, 59 | /// The client is passing its application name to the daemon. 60 | SettingName = PA_CONTEXT_SETTING_NAME, 61 | /// The connection is established, the context is ready to execute operations. 62 | Ready = PA_CONTEXT_READY, 63 | /// The connection failed or was disconnected. 64 | Failed = PA_CONTEXT_FAILED, 65 | /// The connection was terminated cleanly. 66 | Terminated = PA_CONTEXT_TERMINATED, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /audio-device/src/pulse/error.rs: -------------------------------------------------------------------------------- 1 | use crate::libc as c; 2 | use crate::unix::Errno; 3 | use std::cell::Cell; 4 | use std::ptr; 5 | use thiserror::Error; 6 | 7 | macro_rules! error { 8 | ($s:expr, $expr:expr) => {{ 9 | let result = $expr; 10 | 11 | if result < 0 { 12 | let errno = { pulse::pa_context_errno($s.handle.as_ptr()) }; 13 | 14 | Err(crate::pulse::Error::Sys( 15 | crate::unix::Errno::new(errno), 16 | )) 17 | } else { 18 | ffi_error!(result) 19 | } 20 | }}; 21 | } 22 | 23 | macro_rules! ffi_error { 24 | ($expr:expr) => {{ 25 | let result = $expr; 26 | 27 | if let Some(e) = $crate::pulse::error::last_error() { 28 | Err(e) 29 | } else { 30 | Ok(result) 31 | } 32 | }}; 33 | } 34 | 35 | thread_local! { 36 | /// The last error encountered on this thread. 37 | /// 38 | /// This is set by callbacks so transfer errors across their FFI boundary. 39 | static LAST_ERROR: Cell<*mut Error> = Cell::new(ptr::null_mut()); 40 | } 41 | 42 | /// Take the last error encountered on this thread. 43 | pub(super) fn last_error() -> Option { 44 | LAST_ERROR.with(|e| { 45 | let e = e.replace(ptr::null_mut()); 46 | 47 | if e.is_null() { 48 | None 49 | } else { 50 | // Safety: fully managed within this module. 51 | Some(unsafe { *Box::from_raw(e) }) 52 | } 53 | }) 54 | } 55 | 56 | /// Run the given closure and capture any errors raised. 57 | /// 58 | /// Also abort on panics. 59 | pub(super) fn capture(f: F) 60 | where 61 | F: FnOnce() -> Result<()>, 62 | { 63 | if let Err(e) = f() { 64 | let new = Box::into_raw(Box::new(e)); 65 | 66 | LAST_ERROR.with(|e| { 67 | let e = e.replace(new); 68 | 69 | if !e.is_null() { 70 | // Safety: fully managed within this module. 71 | let _ = unsafe { Box::from_raw(e) }; 72 | } 73 | }); 74 | } 75 | } 76 | 77 | /// Errors that can be raised by the PulseAudio layer. 78 | #[derive(Debug, Error)] 79 | pub enum Error { 80 | /// System error. 81 | #[error("system error: {0}")] 82 | Sys(#[from] Errno), 83 | /// Tried to decode bad context state. 84 | #[error("bad context state identifier `{0}`")] 85 | BadContextState(c::c_uint), 86 | /// A custom user error. 87 | #[error("error: {0}")] 88 | User(#[source] Box), 89 | } 90 | 91 | /// Helper result wrapper. 92 | pub type Result = ::std::result::Result; 93 | -------------------------------------------------------------------------------- /audio-device/src/pulse/main_loop.rs: -------------------------------------------------------------------------------- 1 | use crate::pulse::Context; 2 | use pulse_sys as pulse; 3 | use std::ffi::CStr; 4 | use std::ptr; 5 | 6 | /// A Pulseaudio main loop. 7 | /// 8 | /// See [MainLoop::new]. 9 | pub struct MainLoop { 10 | handle: ptr::NonNull, 11 | api: ptr::NonNull, 12 | } 13 | 14 | impl MainLoop { 15 | /// Construct a new main loop object. 16 | /// 17 | /// # Examples 18 | /// 19 | /// ```no_run 20 | /// use audio_device::pulse; 21 | /// 22 | /// # fn main() -> anyhow::Result<()> { 23 | /// let m = pulse::MainLoop::new(); 24 | /// # Ok(()) } 25 | /// ``` 26 | pub fn new() -> Self { 27 | unsafe { 28 | let mut handle = ptr::NonNull::new_unchecked(pulse::pa_mainloop_new()); 29 | let api = ptr::NonNull::new_unchecked(pulse::pa_mainloop_get_api(handle.as_mut())); 30 | 31 | Self { handle, api } 32 | } 33 | } 34 | 35 | /// Instantiate a new connection context with an abstract mainloop API and 36 | /// an application name. 37 | /// 38 | /// # Examples 39 | /// 40 | /// ```no_run 41 | /// use audio_device::pulse; 42 | /// use std::ffi::CString; 43 | /// 44 | /// # fn main() -> anyhow::Result<()> { 45 | /// let mut m = pulse::MainLoop::new(); 46 | /// let ctx = m.context(&CString::new("My Application")?); 47 | /// # Ok(()) } 48 | /// ``` 49 | pub fn context(&mut self, name: &CStr) -> Context { 50 | unsafe { 51 | let handle = pulse::pa_context_new(self.api.as_mut(), name.as_ptr()); 52 | assert!(!handle.is_null(), "pa_context_new: returned NULL"); 53 | 54 | Context { 55 | handle: ptr::NonNull::new_unchecked(handle), 56 | callbacks: Vec::new(), 57 | } 58 | } 59 | } 60 | } 61 | 62 | impl Drop for MainLoop { 63 | fn drop(&mut self) { 64 | unsafe { 65 | pulse::pa_mainloop_free(self.handle.as_mut()); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /audio-device/src/pulse/mod.rs: -------------------------------------------------------------------------------- 1 | //! An idiomatic Rust PulseAudio interface. 2 | // Documentation: https://freedesktop.org/software/pulseaudio/doxygen/ 3 | // Ref: https://gist.github.com/toroidal-code/8798775 4 | 5 | #[macro_use] 6 | mod error; 7 | pub use self::error::{Error, Result}; 8 | 9 | mod enums; 10 | pub use self::enums::ContextState; 11 | 12 | mod main_loop; 13 | pub use self::main_loop::MainLoop; 14 | 15 | mod property_list; 16 | pub use self::property_list::PropertyList; 17 | 18 | mod context; 19 | pub use self::context::Context; 20 | -------------------------------------------------------------------------------- /audio-device/src/pulse/property_list.rs: -------------------------------------------------------------------------------- 1 | use crate::libc as c; 2 | use pulse_sys as pulse; 3 | use std::ptr; 4 | 5 | /// A property list object. 6 | /// 7 | /// Basically a dictionary with ASCII strings as keys and arbitrary data as values. 8 | /// 9 | /// See [PropertyList::new]. 10 | pub struct PropertyList { 11 | handle: ptr::NonNull, 12 | } 13 | 14 | impl PropertyList { 15 | /// Construct a property list. 16 | /// 17 | /// # Examples 18 | /// 19 | /// ``` 20 | /// use audio_device::pulse; 21 | /// 22 | /// # fn main() -> anyhow::Result<()> { 23 | /// let props = pulse::PropertyList::new(); 24 | /// # Ok(()) } 25 | /// ``` 26 | pub fn new() -> Self { 27 | unsafe { 28 | Self { 29 | handle: ptr::NonNull::new_unchecked(pulse::pa_proplist_new()), 30 | } 31 | } 32 | } 33 | 34 | /// Return the number of entries in the property list. 35 | /// 36 | /// # Examples 37 | /// 38 | /// ``` 39 | /// use audio_device::pulse; 40 | /// 41 | /// # fn main() -> anyhow::Result<()> { 42 | /// let props = pulse::PropertyList::new(); 43 | /// assert_eq!(props.len(), 0); 44 | /// # Ok(()) } 45 | /// ``` 46 | pub fn len(&self) -> c::c_uint { 47 | unsafe { pulse::pa_proplist_size(self.handle.as_ref()) } 48 | } 49 | 50 | /// Return the number of entries in the property list. 51 | /// 52 | /// # Examples 53 | /// 54 | /// ``` 55 | /// use audio_device::pulse; 56 | /// 57 | /// # fn main() -> anyhow::Result<()> { 58 | /// let props = pulse::PropertyList::new(); 59 | /// assert!(props.is_empty()); 60 | /// # Ok(()) } 61 | /// ``` 62 | pub fn is_empty(&self) -> bool { 63 | unsafe { dbg!(pulse::pa_proplist_isempty(self.handle.as_ref())) == 1 } 64 | } 65 | } 66 | 67 | impl Drop for PropertyList { 68 | fn drop(&mut self) { 69 | unsafe { 70 | pulse::pa_proplist_free(self.handle.as_ptr()); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /audio-device/src/unix/mod.rs: -------------------------------------------------------------------------------- 1 | //! Unix-specific types and definitions. 2 | 3 | use std::{fmt, error}; 4 | 5 | /// A unix error number. 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 7 | #[repr(transparent)] 8 | pub struct Errno(i32); 9 | 10 | impl Errno { 11 | pub(crate) const EWOULDBLOCK: Self = Self(libc::EWOULDBLOCK); 12 | 13 | pub(crate) fn new(value: i32) -> Self { 14 | Self(value) 15 | } 16 | } 17 | 18 | impl fmt::Display for Errno { 19 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 20 | match *self { 21 | Self::EWOULDBLOCK => { 22 | write!(f, "EWOULDBLOCK") 23 | } 24 | errno => { 25 | write!(f, "({})", errno) 26 | } 27 | } 28 | } 29 | } 30 | 31 | impl error::Error for Errno { 32 | } 33 | 34 | cfg_poll_driver! { 35 | /// Poll flags. 36 | #[derive(Debug, Clone, Copy)] 37 | #[repr(transparent)] 38 | pub struct PollFlags(libc::c_short); 39 | 40 | impl PollFlags { 41 | pub(crate) const POLLOUT: Self = Self(crate::libc::POLLOUT); 42 | 43 | pub(crate) fn from_bits_truncate(bits: libc::c_short) -> Self { 44 | Self(bits) 45 | } 46 | 47 | pub(crate) fn test(self, bits: PollFlags) -> bool { 48 | (self.0 & bits.0) != 0 49 | } 50 | } 51 | 52 | pub use crate::runtime::poll::{AsyncPoll, PollEventsGuard}; 53 | } 54 | -------------------------------------------------------------------------------- /audio-device/src/unix/poll.rs: -------------------------------------------------------------------------------- 1 | //! Unix-related types needed to deal with polling. 2 | 3 | #[doc(inline)] 4 | pub use ::nix::poll::PollFlags; 5 | -------------------------------------------------------------------------------- /audio-device/src/wasapi/buffer_mut.rs: -------------------------------------------------------------------------------- 1 | use crate::wasapi::Error; 2 | use std::marker; 3 | use std::ops; 4 | use std::slice; 5 | use windows::Win32::Media::Audio as audio; 6 | 7 | /// A typed mutable data buffer. 8 | pub struct BufferMut<'a, T> { 9 | pub(super) tag: ste::Tag, 10 | pub(super) render_client: &'a mut audio::IAudioRenderClient, 11 | pub(super) data: *mut T, 12 | pub(super) frames: u32, 13 | pub(super) len: usize, 14 | pub(super) in_use: bool, 15 | pub(super) _marker: marker::PhantomData<&'a mut [T]>, 16 | } 17 | 18 | impl<'a, T> BufferMut<'a, T> { 19 | /// Release the buffer allowing the audio device to consume it. 20 | pub fn release(mut self) -> Result<(), Error> { 21 | self.tag.ensure_on_thread(); 22 | 23 | if std::mem::take(&mut self.in_use) { 24 | unsafe { 25 | self.render_client.ReleaseBuffer(self.frames, 0)?; 26 | } 27 | } 28 | 29 | Ok(()) 30 | } 31 | } 32 | 33 | impl<'a, T> Drop for BufferMut<'a, T> { 34 | fn drop(&mut self) { 35 | self.tag.ensure_on_thread(); 36 | 37 | if std::mem::take(&mut self.in_use) { 38 | unsafe { 39 | self.render_client 40 | .ReleaseBuffer(self.frames, 0) 41 | .ok() 42 | .unwrap(); 43 | } 44 | } 45 | } 46 | } 47 | 48 | impl<'a, T> ops::Deref for BufferMut<'a, T> { 49 | type Target = [T]; 50 | 51 | fn deref(&self) -> &Self::Target { 52 | self.tag.ensure_on_thread(); 53 | debug_assert!(self.in_use); 54 | unsafe { slice::from_raw_parts(self.data, self.len) } 55 | } 56 | } 57 | 58 | impl<'a, T> ops::DerefMut for BufferMut<'a, T> { 59 | fn deref_mut(&mut self) -> &mut Self::Target { 60 | self.tag.ensure_on_thread(); 61 | debug_assert!(self.in_use); 62 | unsafe { slice::from_raw_parts_mut(self.data, self.len) } 63 | } 64 | } 65 | 66 | // Safety: thread safety is ensured through tagging with ste::Tag. 67 | unsafe impl Send for BufferMut<'_, T> {} 68 | -------------------------------------------------------------------------------- /audio-device/src/wasapi/initialized_client.rs: -------------------------------------------------------------------------------- 1 | use crate::loom::sync::Arc; 2 | use crate::wasapi::{ClientConfig, Error, RenderClient, Sample}; 3 | use std::marker; 4 | use windows::Win32::Media::Audio as audio; 5 | 6 | /// A client that has been initialized with the given type `T`. 7 | /// 8 | /// The type must implement the [Sample] trait to make sure it's appropriate for 9 | /// use with WASAPI. 10 | pub struct InitializedClient { 11 | pub(super) tag: ste::Tag, 12 | pub(super) audio_client: audio::IAudioClient, 13 | pub(super) config: ClientConfig, 14 | pub(super) buffer_size: u32, 15 | pub(super) event: Arc, 16 | pub(super) _marker: marker::PhantomData, 17 | } 18 | 19 | impl InitializedClient 20 | where 21 | T: Sample, 22 | { 23 | /// Get the initialized client configuration. 24 | pub fn config(&self) -> ClientConfig { 25 | self.config 26 | } 27 | 28 | /// Construct a render client used for writing output into. 29 | #[tracing::instrument(skip_all)] 30 | pub fn render_client(&self) -> Result, Error> { 31 | tracing::trace!("initializing render client"); 32 | 33 | self.tag.ensure_on_thread(); 34 | 35 | let render_client: audio::IAudioRenderClient = unsafe { 36 | self.audio_client.GetService()? 37 | }; 38 | 39 | Ok(RenderClient { 40 | tag: self.tag, 41 | audio_client: self.audio_client.clone(), 42 | render_client, 43 | buffer_size: self.buffer_size, 44 | channels: self.config.channels as usize, 45 | event: self.event.clone(), 46 | _marker: marker::PhantomData, 47 | }) 48 | } 49 | } 50 | 51 | // Safety: thread safety is ensured through tagging with ste::Tag. 52 | unsafe impl Send for InitializedClient {} 53 | -------------------------------------------------------------------------------- /audio-device/src/wasapi/mod.rs: -------------------------------------------------------------------------------- 1 | //! An idiomatic Rust WASAPI interface. 2 | 3 | use std::ptr; 4 | 5 | use thiserror::Error; 6 | use windows::Win32::System::Com as com; 7 | use windows::Win32::Media::Audio as audio; 8 | 9 | mod initialized_client; 10 | pub use self::initialized_client::InitializedClient; 11 | 12 | mod client; 13 | pub use self::client::Client; 14 | 15 | mod render_client; 16 | pub use self::render_client::RenderClient; 17 | 18 | mod buffer_mut; 19 | pub use self::buffer_mut::BufferMut; 20 | 21 | mod sample; 22 | pub use self::sample::Sample; 23 | 24 | /// WASAPI-specific errors. 25 | #[derive(Debug, Error)] 26 | pub enum Error { 27 | /// A system error. 28 | #[error("system error: {0}")] 29 | Sys( 30 | #[from] 31 | #[source] 32 | windows::core::Error, 33 | ), 34 | /// Trying to use a mix format which is not supported by the device. 35 | #[error("Device doesn't support a compatible mix format")] 36 | UnsupportedMixFormat, 37 | } 38 | 39 | /// The audio prelude to use for wasapi. 40 | pub fn audio_prelude() { 41 | unsafe { 42 | if let Err(e) = com::CoInitializeEx(ptr::null_mut(), com::COINIT_MULTITHREADED) { 43 | panic!("failed to initialize multithreaded apartment: {}", e); 44 | } 45 | } 46 | } 47 | 48 | /// The sample format detected for the device. 49 | #[derive(Debug, Clone, Copy)] 50 | pub enum SampleFormat { 51 | /// A 16-bit sample format. 52 | I16, 53 | /// A 32-bit floating point sample format. 54 | F32, 55 | } 56 | 57 | /// A client configuration. 58 | /// 59 | /// Constructed through [Client::default_client_config]. 60 | #[derive(Debug, Clone, Copy)] 61 | pub struct ClientConfig { 62 | _tag: ste::Tag, 63 | /// The number of channels in use. 64 | pub channels: u16, 65 | /// The sample rate in use. 66 | pub sample_rate: u32, 67 | /// The sample format in use. 68 | pub sample_format: SampleFormat, 69 | } 70 | 71 | /// Open the default output device for WASAPI. 72 | #[tracing::instrument(skip_all)] 73 | pub fn default_output_client() -> Result, Error> { 74 | let tag = ste::Tag::current_thread(); 75 | 76 | let enumerator: audio::IMMDeviceEnumerator = unsafe { 77 | com::CoCreateInstance(&audio::MMDeviceEnumerator, None, com::CLSCTX_ALL)? 78 | }; 79 | 80 | unsafe { 81 | let device = enumerator 82 | .GetDefaultAudioEndpoint(audio::eRender, audio::eConsole); 83 | 84 | let device = match device { 85 | Ok(device) => device, 86 | Err(..) => return Ok(None), 87 | }; 88 | 89 | tracing::trace!("got default audio endpoint"); 90 | let audio_client: audio::IAudioClient = device.Activate(com::CLSCTX_ALL, None)?; 91 | tracing::trace!("got audio client"); 92 | Ok(Some(Client { tag, audio_client })) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /audio-device/src/wasapi/render_client.rs: -------------------------------------------------------------------------------- 1 | use crate::loom::sync::Arc; 2 | use crate::wasapi::{BufferMut, Error}; 3 | use crate::windows::{Event, RawEvent}; 4 | use std::marker; 5 | use windows::Win32::Media::Audio as audio; 6 | use windows::Win32::System::Threading as th; 7 | use windows::Win32::System::WindowsProgramming as wp; 8 | use windows::Win32::Foundation as f; 9 | 10 | /// A typed render client. 11 | pub struct RenderClient { 12 | pub(super) tag: ste::Tag, 13 | pub(super) audio_client: audio::IAudioClient, 14 | pub(super) render_client: audio::IAudioRenderClient, 15 | pub(super) buffer_size: u32, 16 | pub(super) channels: usize, 17 | pub(super) event: Arc, 18 | pub(super) _marker: marker::PhantomData, 19 | } 20 | 21 | impl RenderClient { 22 | fn get_current_padding(&self) -> Result { 23 | unsafe { 24 | let padding = self.audio_client 25 | .GetCurrentPadding()?; 26 | Ok(padding) 27 | } 28 | } 29 | 30 | /// Get the buffer associated with the render client. 31 | fn get_buffer(&self, frames: u32) -> Result<*mut T, Error> { 32 | unsafe { 33 | let data = self.render_client 34 | .GetBuffer(frames)?; 35 | 36 | Ok(data as *mut T) 37 | } 38 | } 39 | } 40 | 41 | impl RenderClient { 42 | /// Get access to the raw mutable buffer. 43 | /// 44 | /// This will block until it is appropriate to submit a buffer. 45 | pub fn buffer_mut(&mut self) -> Result, Error> { 46 | self.tag.ensure_on_thread(); 47 | 48 | unsafe { 49 | loop { 50 | match th::WaitForSingleObject(self.event.raw_event(), wp::INFINITE) { 51 | f::WAIT_OBJECT_0 => (), 52 | _ => { 53 | return Err(Error::from(windows::core::Error::from_win32())); 54 | } 55 | } 56 | 57 | let padding = self.get_current_padding()?; 58 | let frames = self.buffer_size.saturating_sub(padding); 59 | 60 | if frames == 0 { 61 | continue; 62 | } 63 | 64 | let data = self.get_buffer(frames)?; 65 | 66 | return Ok(BufferMut { 67 | tag: self.tag, 68 | render_client: &mut self.render_client, 69 | data, 70 | frames, 71 | len: frames as usize * self.channels, 72 | in_use: true, 73 | _marker: marker::PhantomData, 74 | }); 75 | } 76 | } 77 | } 78 | } 79 | 80 | cfg_events_driver! { 81 | use crate::windows::AsyncEvent; 82 | 83 | impl RenderClient { 84 | /// Get access to the raw mutable buffer. 85 | /// 86 | /// This will block until it is appropriate to submit a buffer. 87 | pub async fn buffer_mut_async(&mut self) -> Result, Error> { 88 | loop { 89 | self.event.wait().await; 90 | self.tag.ensure_on_thread(); 91 | 92 | let padding = self.get_current_padding()?; 93 | let frames = self.buffer_size.saturating_sub(padding); 94 | 95 | if frames == 0 { 96 | continue; 97 | } 98 | 99 | let data = self.get_buffer(frames)?; 100 | 101 | return Ok(BufferMut { 102 | tag: self.tag, 103 | render_client: &mut self.render_client, 104 | data, 105 | frames, 106 | len: frames as usize * self.channels, 107 | in_use: true, 108 | _marker: marker::PhantomData, 109 | }); 110 | } 111 | } 112 | } 113 | } 114 | 115 | // Safety: thread safety is ensured through tagging with ste::Tag. 116 | unsafe impl Send for RenderClient {} 117 | -------------------------------------------------------------------------------- /audio-device/src/wasapi/sample.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | use windows::Win32::Media::Audio as audio; 3 | use windows::Win32::Media::KernelStreaming as ks; 4 | use windows::Win32::Media::Multimedia as mm; 5 | 6 | use super::ClientConfig; 7 | 8 | /// Trait implemented for types which can be used to feed the output device. 9 | pub unsafe trait Sample: Copy { 10 | /// The mid level (silent) level of a sample. 11 | const MID: Self; 12 | 13 | /// Construct a wave format specification compatible with the current sample 14 | /// type. 15 | fn mix_format(config: ClientConfig) -> audio::WAVEFORMATEXTENSIBLE; 16 | 17 | /// Test if the current sample type is compatible. 18 | unsafe fn is_compatible_with(mix_format: *const audio::WAVEFORMATEX) -> bool; 19 | } 20 | 21 | unsafe impl Sample for i16 { 22 | const MID: Self = 0; 23 | 24 | fn mix_format(config: ClientConfig) -> audio::WAVEFORMATEXTENSIBLE { 25 | let avg_bytes_per_sec = 26 | config.channels as u32 * config.sample_rate * mem::size_of::() as u32; 27 | let bits_per_sample = mem::size_of::() as u16 * 8; 28 | let block_align = config.channels as u16 * mem::size_of::() as u16; 29 | 30 | audio::WAVEFORMATEXTENSIBLE { 31 | Format: audio::WAVEFORMATEX { 32 | wFormatTag: audio::WAVE_FORMAT_PCM as u16, 33 | nChannels: config.channels, 34 | nSamplesPerSec: config.sample_rate, 35 | nAvgBytesPerSec: avg_bytes_per_sec, 36 | nBlockAlign: block_align, 37 | wBitsPerSample: bits_per_sample, 38 | cbSize: 0, 39 | }, 40 | Samples: audio::WAVEFORMATEXTENSIBLE_0 { 41 | wValidBitsPerSample: 0, 42 | }, 43 | dwChannelMask: 0, 44 | SubFormat: windows::core::GUID::zeroed(), 45 | } 46 | } 47 | 48 | unsafe fn is_compatible_with(mix_format: *const audio::WAVEFORMATEX) -> bool { 49 | let bits_per_sample = (*mix_format).wBitsPerSample; 50 | 51 | match (*mix_format).wFormatTag as u32 { 52 | audio::WAVE_FORMAT_PCM => { 53 | assert!((*mix_format).cbSize == 0); 54 | bits_per_sample == 16 55 | } 56 | _ => false, 57 | } 58 | } 59 | } 60 | 61 | unsafe impl Sample for f32 { 62 | const MID: Self = 0.0; 63 | 64 | fn mix_format(config: ClientConfig) -> audio::WAVEFORMATEXTENSIBLE { 65 | let avg_bytes_per_sec = 66 | config.channels as u32 * config.sample_rate * mem::size_of::() as u32; 67 | let bits_per_sample = mem::size_of::() as u16 * 8; 68 | let block_align = config.channels as u16 * mem::size_of::() as u16; 69 | let cb_size = mem::size_of::() as u16 70 | - mem::size_of::() as u16; 71 | 72 | audio::WAVEFORMATEXTENSIBLE { 73 | Format: audio::WAVEFORMATEX { 74 | wFormatTag: ks::WAVE_FORMAT_EXTENSIBLE as u16, 75 | nChannels: config.channels, 76 | nSamplesPerSec: config.sample_rate, 77 | nAvgBytesPerSec: avg_bytes_per_sec, 78 | nBlockAlign: block_align, 79 | wBitsPerSample: bits_per_sample, 80 | cbSize: cb_size, 81 | }, 82 | Samples: audio::WAVEFORMATEXTENSIBLE_0 { 83 | wValidBitsPerSample: bits_per_sample, 84 | }, 85 | dwChannelMask: 0, 86 | SubFormat: mm::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 87 | } 88 | } 89 | 90 | unsafe fn is_compatible_with(mix_format: *const audio::WAVEFORMATEX) -> bool { 91 | let bits_per_sample = (*mix_format).wBitsPerSample; 92 | 93 | match (*mix_format).wFormatTag as u32 { 94 | ks::WAVE_FORMAT_EXTENSIBLE => { 95 | debug_assert_eq! { 96 | (*mix_format).cbSize as usize, 97 | mem::size_of::() - mem::size_of::() 98 | }; 99 | 100 | let mix_format = mix_format as *const audio::WAVEFORMATEXTENSIBLE; 101 | bits_per_sample == 32 102 | && matches!((*mix_format).SubFormat, mm::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) 103 | } 104 | _ => false, 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /audio-device/src/windows/event.rs: -------------------------------------------------------------------------------- 1 | use windows::Win32::System::Threading as th; 2 | use windows::Win32::Foundation as f; 3 | use windows::core::PCSTR; 4 | 5 | use crate::windows::RawEvent; 6 | 7 | /// A managed ewvent object. 8 | #[repr(transparent)] 9 | pub struct Event { 10 | handle: f::HANDLE, 11 | } 12 | 13 | impl Event { 14 | pub(crate) fn new(manual_reset: bool, initial_state: bool) -> windows::core::Result { 15 | let handle = unsafe { 16 | th::CreateEventA( 17 | None, 18 | manual_reset, 19 | initial_state, 20 | PCSTR::null(), 21 | )? 22 | }; 23 | 24 | Ok(Self { handle }) 25 | } 26 | 27 | /// Set the event. 28 | pub fn set(&self) { 29 | unsafe { 30 | th::SetEvent(self.handle); 31 | } 32 | } 33 | 34 | /// Reset the event. 35 | pub fn reset(&self) { 36 | unsafe { 37 | th::ResetEvent(self.handle); 38 | } 39 | } 40 | } 41 | 42 | impl RawEvent for Event { 43 | unsafe fn raw_event(&self) -> f::HANDLE { 44 | self.handle 45 | } 46 | } 47 | 48 | impl Drop for Event { 49 | fn drop(&mut self) { 50 | unsafe { 51 | // NB: We intentionally ignore errors here. 52 | let _ = f::CloseHandle(self.handle).ok(); 53 | } 54 | } 55 | } 56 | 57 | unsafe impl Send for Event {} 58 | unsafe impl Sync for Event {} 59 | -------------------------------------------------------------------------------- /audio-device/src/windows/mod.rs: -------------------------------------------------------------------------------- 1 | //! Shared helpers for windows programming. 2 | 3 | use windows::Win32::Foundation as f; 4 | mod event; 5 | pub use self::event::Event; 6 | 7 | cfg_events_driver! { 8 | pub use crate::runtime::events::AsyncEvent; 9 | } 10 | 11 | /// Trait that indicates a type that encapsulates an event. 12 | pub trait RawEvent { 13 | /// Access the underlying raw handle for the event. 14 | /// 15 | /// # Safety 16 | /// 17 | /// Caller must ensure that the raw handle stays alive for the duration of 18 | /// whatever its being associated with. 19 | unsafe fn raw_event(&self) -> f::HANDLE; 20 | } 21 | -------------------------------------------------------------------------------- /audio-generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "audio-generator" 3 | version = "0.1.0-alpha.2" 4 | authors = ["John-John Tedro "] 5 | edition = "2018" 6 | rust-version = "1.70" 7 | description = "Audio generator APIs" 8 | documentation = "https://docs.rs/audio" 9 | readme = "README.md" 10 | homepage = "https://github.com/udoprog/audio" 11 | repository = "https://github.com/udoprog/audio" 12 | license = "MIT OR Apache-2.0" 13 | keywords = ["audio", "buffer", "dsp"] 14 | categories = ["multimedia::audio"] 15 | 16 | [dependencies] 17 | audio-core = { version = "0.2.0", path = "../audio-core" } 18 | -------------------------------------------------------------------------------- /audio-generator/README.md: -------------------------------------------------------------------------------- 1 | # audio-generator 2 | 3 | [github](https://github.com/udoprog/audio) 4 | [crates.io](https://crates.io/crates/audio-generator) 5 | [docs.rs](https://docs.rs/audio-generator) 6 | [build status](https://github.com/udoprog/audio/actions?query=branch%3Amain) 7 | 8 | Audio generators. 9 | 10 | This provides audio generators which implements the [Generator] trait. 11 | 12 | It is part of the [audio ecosystem] of crates. 13 | 14 |
15 | 16 | ## Examples 17 | 18 | ```rust 19 | use audio_generator::{Generator, Sine}; 20 | 21 | let mut g = Sine::new(440.0, 44100.0); 22 | assert_eq!(g.sample(), 0.0); 23 | assert!(g.sample() > 0.0); 24 | ``` 25 | 26 | [audio ecosystem]: https://docs.rs/audio 27 | -------------------------------------------------------------------------------- /audio-generator/src/generator.rs: -------------------------------------------------------------------------------- 1 | mod amplitude; 2 | pub use self::amplitude::Amplitude; 3 | 4 | mod iter; 5 | pub use self::iter::Iter; 6 | 7 | /// The trait for an audio generator. 8 | pub trait Generator { 9 | /// The sample that is generated. 10 | type Sample; 11 | 12 | /// Generate the next sample from the generator. Advances the underlying 13 | /// generator to the next sample. 14 | /// 15 | /// # Examples 16 | /// 17 | /// ``` 18 | /// use audio_generator::{Generator, Sine}; 19 | /// 20 | /// let mut g = Sine::new(440.0, 44100.0); 21 | /// assert_eq!(g.sample(), 0.0); 22 | /// assert!(g.sample() > 0.0); 23 | /// ``` 24 | fn sample(&mut self) -> Self::Sample; 25 | 26 | /// Construct an iterator from this generator. 27 | /// 28 | /// ``` 29 | /// use audio_generator::{Generator, Sine}; 30 | /// 31 | /// let mut g = Sine::new(440.0, 44100.0).amplitude(0.5); 32 | /// let samples = g.iter().take(10).collect::>(); 33 | /// 34 | /// assert_eq!(samples.len(), 10); 35 | /// ``` 36 | fn iter(self) -> Iter 37 | where 38 | Self: Sized, 39 | { 40 | Iter::new(self) 41 | } 42 | 43 | /// Modify the amplitude of the sample. 44 | /// 45 | /// # Examples 46 | /// 47 | /// ``` 48 | /// use audio_generator::{Generator, Sine}; 49 | /// 50 | /// let mut a = Sine::new(440.0, 44100.0).amplitude(0.1); 51 | /// let mut b = Sine::new(440.0, 44100.0).amplitude(0.1); 52 | /// 53 | /// for _ in 0..100 { 54 | /// assert!(a.sample().abs() <= b.sample().abs()); 55 | /// } 56 | /// ``` 57 | fn amplitude(self, amplitude: f32) -> Amplitude 58 | where 59 | Self: Sized, 60 | { 61 | Amplitude::new(self, amplitude) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /audio-generator/src/generator/amplitude.rs: -------------------------------------------------------------------------------- 1 | use crate::generator::Generator; 2 | use audio_core::Translate; 3 | use std::ops; 4 | 5 | /// A generator combinator that adjusts the amplitude of the generated value. 6 | /// 7 | /// See [Generator::amplitude]. 8 | pub struct Amplitude { 9 | generator: G, 10 | amplitude: f32, 11 | } 12 | 13 | impl Amplitude { 14 | pub(super) fn new(generator: G, amplitude: f32) -> Self { 15 | Self { 16 | generator, 17 | amplitude, 18 | } 19 | } 20 | } 21 | 22 | impl Generator for Amplitude 23 | where 24 | G: Generator, 25 | G::Sample: Translate, 26 | G::Sample: ops::Mul, 27 | { 28 | type Sample = G::Sample; 29 | 30 | fn sample(&mut self) -> G::Sample { 31 | self.generator.sample() * Self::Sample::translate(self.amplitude) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /audio-generator/src/generator/iter.rs: -------------------------------------------------------------------------------- 1 | use crate::generator::Generator; 2 | 3 | /// An iterator constructed from a [Generator]. 4 | /// 5 | /// See [Generator::iter]. 6 | pub struct Iter { 7 | generator: G, 8 | } 9 | 10 | impl Iter { 11 | pub(super) fn new(generator: G) -> Self { 12 | Self { generator } 13 | } 14 | } 15 | 16 | impl Iterator for Iter 17 | where 18 | G: Generator, 19 | { 20 | type Item = G::Sample; 21 | 22 | fn next(&mut self) -> Option { 23 | Some(self.generator.sample()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /audio-generator/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [github](https://github.com/udoprog/audio) 2 | //! [crates.io](https://crates.io/crates/audio-generator) 3 | //! [docs.rs](https://docs.rs/audio-generator) 4 | //! 5 | //! Audio generators. 6 | //! 7 | //! This provides audio generators which implements the [Generator] trait. 8 | //! 9 | //! It is part of the [audio ecosystem] of crates. 10 | //! 11 | //!
12 | //! 13 | //! ## Examples 14 | //! 15 | //! ```rust 16 | //! use audio_generator::{Generator, Sine}; 17 | //! 18 | //! let mut g = Sine::new(440.0, 44100.0); 19 | //! assert_eq!(g.sample(), 0.0); 20 | //! assert!(g.sample() > 0.0); 21 | //! ``` 22 | //! 23 | //! [audio ecosystem]: https://docs.rs/audio 24 | 25 | mod generator; 26 | pub use self::generator::Generator; 27 | 28 | mod sine; 29 | pub use self::sine::Sine; 30 | -------------------------------------------------------------------------------- /audio-generator/src/sine.rs: -------------------------------------------------------------------------------- 1 | use crate::generator::Generator; 2 | 3 | /// A sine tone generator. 4 | /// 5 | /// # Examples 6 | /// 7 | /// ``` 8 | /// use audio_generator::{Generator, Sine}; 9 | /// 10 | /// let mut g = Sine::new(440.0, 44100.0); 11 | /// assert_eq!(g.sample(), 0.0); 12 | /// assert!(g.sample() > 0.0); 13 | /// ``` 14 | pub struct Sine { 15 | at: f32, 16 | step: f32, 17 | round_at: f32, 18 | } 19 | 20 | impl Sine { 21 | /// Construct a new sine tone generator. The generated tone has the given 22 | /// `rate` adjusted for the provided `sample_rate`. 23 | /// 24 | /// # Examples 25 | /// 26 | /// ``` 27 | /// use audio_generator::{Generator, Sine}; 28 | /// 29 | /// let mut g = Sine::new(440.0, 44100.0); 30 | /// assert_eq!(g.sample(), 0.0); 31 | /// assert!(g.sample() > 0.0); 32 | /// ``` 33 | pub fn new(rate: f32, sample_rate: f32) -> Self { 34 | let freq = rate / sample_rate / 2.0; 35 | let step = 2.0 * std::f32::consts::PI * freq; 36 | 37 | Self { 38 | at: 0.0, 39 | step, 40 | round_at: step / freq, 41 | } 42 | } 43 | } 44 | 45 | impl Iterator for Sine { 46 | type Item = f32; 47 | 48 | fn next(&mut self) -> Option { 49 | Some(self.sample()) 50 | } 51 | } 52 | 53 | impl Generator for Sine { 54 | type Sample = f32; 55 | 56 | fn sample(&mut self) -> Self::Sample { 57 | let f = self.at; 58 | self.at += self.step; 59 | 60 | if self.at > self.round_at { 61 | self.at -= self.round_at; 62 | } 63 | 64 | f 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /audio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "audio" 3 | version = "0.2.0" 4 | authors = ["John-John Tedro "] 5 | edition = "2018" 6 | rust-version = "1.70" 7 | description = "A crate for working with audio in Rust" 8 | documentation = "https://docs.rs/audio" 9 | readme = "README.md" 10 | homepage = "https://github.com/udoprog/audio" 11 | repository = "https://github.com/udoprog/audio" 12 | license = "MIT OR Apache-2.0" 13 | keywords = ["audio", "buffer", "dsp"] 14 | categories = ["multimedia::audio"] 15 | 16 | [features] 17 | default = ["std"] 18 | std = ["audio-core/std"] 19 | 20 | [dependencies] 21 | audio-core = { version = "0.2.0", path = "../audio-core" } 22 | 23 | [dev-dependencies] 24 | rand = "0.8.5" 25 | bittle = "0.2.1" 26 | -------------------------------------------------------------------------------- /audio/src/buf.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for working with audio buffers. 2 | 3 | use audio_core::{Buf, BufMut, Channel, ChannelMut, Translate}; 4 | 5 | #[cfg(feature = "std")] 6 | pub mod dynamic; 7 | #[cfg(feature = "std")] 8 | pub use self::dynamic::Dynamic; 9 | 10 | pub mod interleaved; 11 | #[cfg(feature = "std")] 12 | pub use self::interleaved::Interleaved; 13 | 14 | pub mod sequential; 15 | #[cfg(feature = "std")] 16 | pub use self::sequential::Sequential; 17 | 18 | /// Copy from the buffer specified by `from` into the buffer specified by `to`. 19 | /// 20 | /// Only the common count of channels will be copied. 21 | pub fn copy(from: I, mut to: O) 22 | where 23 | I: Buf, 24 | O: BufMut, 25 | I::Sample: Copy, 26 | { 27 | for (from, to) in from.iter_channels().zip(to.iter_channels_mut()) { 28 | crate::channel::copy(from, to); 29 | } 30 | } 31 | 32 | /// Translate the content of one buffer `from` into the buffer specified by `to`. 33 | /// 34 | /// Only the common count of channels will be copied. 35 | pub fn translate(from: I, mut to: O) 36 | where 37 | I: Buf, 38 | O: BufMut, 39 | O::Sample: Translate, 40 | I::Sample: Copy, 41 | { 42 | for (mut to, from) in to.iter_channels_mut().zip(from.iter_channels()) { 43 | for (t, f) in to.iter_mut().zip(from.iter()) { 44 | *t = O::Sample::translate(f); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /audio/src/buf/dynamic/iter.rs: -------------------------------------------------------------------------------- 1 | use crate::buf::dynamic::RawSlice; 2 | use crate::channel::{LinearChannel, LinearChannelMut}; 3 | use std::slice; 4 | 5 | // Helper to forward slice-optimized iterator functions. 6 | macro_rules! forward { 7 | ($as:ident) => { 8 | #[inline] 9 | fn next(&mut self) -> Option { 10 | let buf = self.iter.next()?; 11 | Some(unsafe { buf.$as(self.len) }) 12 | } 13 | 14 | #[inline] 15 | fn nth(&mut self, n: usize) -> Option { 16 | let buf = self.iter.nth(n)?; 17 | Some(unsafe { buf.$as(self.len) }) 18 | } 19 | 20 | #[inline] 21 | fn last(self) -> Option { 22 | let buf = self.iter.last()?; 23 | Some(unsafe { buf.$as(self.len) }) 24 | } 25 | 26 | #[inline] 27 | fn size_hint(&self) -> (usize, Option) { 28 | self.iter.size_hint() 29 | } 30 | 31 | #[inline] 32 | fn count(self) -> usize { 33 | self.iter.count() 34 | } 35 | }; 36 | } 37 | 38 | /// An iterator over the channels in the buffer. 39 | /// 40 | /// Created with [Dynamic::iter_channels][crate::buf::Dynamic::iter_channels]. 41 | pub struct IterChannels<'a, T> { 42 | iter: slice::Iter<'a, RawSlice>, 43 | len: usize, 44 | } 45 | 46 | impl<'a, T> IterChannels<'a, T> { 47 | /// Construct a new iterator. 48 | /// 49 | /// # Safety 50 | /// 51 | /// The provided `len` must match the lengths of all provided slices. 52 | #[inline] 53 | pub(super) unsafe fn new(data: &'a [RawSlice], len: usize) -> Self { 54 | Self { 55 | iter: data.iter(), 56 | len, 57 | } 58 | } 59 | } 60 | 61 | impl<'a, T> Iterator for IterChannels<'a, T> { 62 | type Item = LinearChannel<'a, T>; 63 | 64 | forward!(as_linear_channel); 65 | } 66 | 67 | impl DoubleEndedIterator for IterChannels<'_, T> { 68 | #[inline] 69 | fn next_back(&mut self) -> Option { 70 | let buf = self.iter.next_back()?; 71 | Some(unsafe { buf.as_linear_channel(self.len) }) 72 | } 73 | 74 | #[inline] 75 | fn nth_back(&mut self, n: usize) -> Option { 76 | let buf = self.iter.nth_back(n)?; 77 | Some(unsafe { buf.as_linear_channel(self.len) }) 78 | } 79 | } 80 | 81 | impl ExactSizeIterator for IterChannels<'_, T> { 82 | fn len(&self) -> usize { 83 | self.iter.len() 84 | } 85 | } 86 | 87 | /// A mutable iterator over the channels in the buffer. 88 | /// 89 | /// Created with [Dynamic::iter_channels_mut][crate::buf::Dynamic::iter_channels_mut]. 90 | pub struct IterChannelsMut<'a, T> { 91 | iter: slice::IterMut<'a, RawSlice>, 92 | len: usize, 93 | } 94 | 95 | impl<'a, T> IterChannelsMut<'a, T> { 96 | /// Construct a new iterator. 97 | /// 98 | /// # Safety 99 | /// 100 | /// The provided `len` must match the lengths of all provided slices. 101 | #[inline] 102 | pub(super) unsafe fn new(data: &'a mut [RawSlice], len: usize) -> Self { 103 | Self { 104 | iter: data.iter_mut(), 105 | len, 106 | } 107 | } 108 | } 109 | 110 | impl<'a, T> Iterator for IterChannelsMut<'a, T> { 111 | type Item = LinearChannelMut<'a, T>; 112 | 113 | forward!(as_linear_channel_mut); 114 | } 115 | 116 | impl DoubleEndedIterator for IterChannelsMut<'_, T> { 117 | #[inline] 118 | fn next_back(&mut self) -> Option { 119 | let buf = self.iter.next_back()?; 120 | Some(unsafe { buf.as_linear_channel_mut(self.len) }) 121 | } 122 | 123 | #[inline] 124 | fn nth_back(&mut self, n: usize) -> Option { 125 | let buf = self.iter.nth_back(n)?; 126 | Some(unsafe { buf.as_linear_channel_mut(self.len) }) 127 | } 128 | } 129 | 130 | impl ExactSizeIterator for IterChannelsMut<'_, T> { 131 | fn len(&self) -> usize { 132 | self.iter.len() 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /audio/src/buf/interleaved.rs: -------------------------------------------------------------------------------- 1 | //! A dynamically sized, multi-channel interleaved audio buffer. 2 | 3 | mod iter; 4 | pub use self::iter::{IterChannels, IterChannelsMut}; 5 | 6 | #[cfg(feature = "std")] 7 | mod buf; 8 | #[cfg(feature = "std")] 9 | pub use self::buf::Interleaved; 10 | -------------------------------------------------------------------------------- /audio/src/buf/interleaved/iter.rs: -------------------------------------------------------------------------------- 1 | use core::marker; 2 | use core::ptr; 3 | 4 | use crate::channel::{InterleavedChannel, InterleavedChannelMut}; 5 | 6 | /// An immutable iterator over an interleaved buffer. 7 | pub struct IterChannels<'a, T> { 8 | ptr: ptr::NonNull, 9 | len: usize, 10 | channel: usize, 11 | channels: usize, 12 | _marker: marker::PhantomData<&'a [T]>, 13 | } 14 | 15 | impl IterChannels<'_, T> { 16 | /// Construct a new unchecked iterator. 17 | /// 18 | /// # Safety 19 | /// 20 | /// The caller must ensure that the pointed to buffer is a valid immutable 21 | /// interleaved region of data. 22 | pub(crate) unsafe fn new_unchecked(ptr: ptr::NonNull, len: usize, channels: usize) -> Self { 23 | Self { 24 | ptr, 25 | len, 26 | channel: 0, 27 | channels, 28 | _marker: marker::PhantomData, 29 | } 30 | } 31 | } 32 | 33 | impl<'a, T> Iterator for IterChannels<'a, T> 34 | where 35 | T: Copy, 36 | { 37 | type Item = InterleavedChannel<'a, T>; 38 | 39 | fn next(&mut self) -> Option { 40 | if self.channel == self.channels { 41 | return None; 42 | } 43 | 44 | let channel = self.channel; 45 | self.channel += 1; 46 | 47 | unsafe { 48 | Some(InterleavedChannel::new_unchecked( 49 | self.ptr, 50 | self.len, 51 | channel, 52 | self.channels, 53 | )) 54 | } 55 | } 56 | } 57 | 58 | /// An mutable iterator over an interleaved buffer. 59 | pub struct IterChannelsMut<'a, T> { 60 | ptr: ptr::NonNull, 61 | len: usize, 62 | channel: usize, 63 | channels: usize, 64 | _marker: marker::PhantomData<&'a mut [T]>, 65 | } 66 | 67 | impl IterChannelsMut<'_, T> { 68 | /// Construct a new unchecked iterator. 69 | /// 70 | /// # Safety 71 | /// 72 | /// The caller must ensure that the pointed to buffer is a valid mutable 73 | /// interleaved region of data. 74 | pub(crate) unsafe fn new_unchecked(ptr: ptr::NonNull, len: usize, channels: usize) -> Self { 75 | Self { 76 | ptr, 77 | len, 78 | channel: 0, 79 | channels, 80 | _marker: marker::PhantomData, 81 | } 82 | } 83 | } 84 | 85 | impl<'a, T> Iterator for IterChannelsMut<'a, T> 86 | where 87 | T: Copy, 88 | { 89 | type Item = InterleavedChannelMut<'a, T>; 90 | 91 | fn next(&mut self) -> Option { 92 | if self.channel == self.channels { 93 | return None; 94 | } 95 | 96 | let channel = self.channel; 97 | self.channel += 1; 98 | 99 | unsafe { 100 | Some(InterleavedChannelMut::new_unchecked( 101 | self.ptr, 102 | self.len, 103 | channel, 104 | self.channels, 105 | )) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /audio/src/buf/sequential.rs: -------------------------------------------------------------------------------- 1 | //! A dynamically sized, multi-channel sequential audio buffer. 2 | 3 | mod iter; 4 | pub use self::iter::{IterChannels, IterChannelsMut}; 5 | 6 | #[cfg(feature = "std")] 7 | mod buf; 8 | #[cfg(feature = "std")] 9 | pub use self::buf::Sequential; 10 | -------------------------------------------------------------------------------- /audio/src/buf/sequential/iter.rs: -------------------------------------------------------------------------------- 1 | use core::slice; 2 | 3 | use crate::channel::{LinearChannel, LinearChannelMut}; 4 | 5 | // Helper to forward slice-optimized iterator functions. 6 | macro_rules! forward { 7 | ($channel:ident) => { 8 | #[inline] 9 | fn next(&mut self) -> Option { 10 | Some($channel::new(self.iter.next()?)) 11 | } 12 | 13 | #[inline] 14 | fn nth(&mut self, n: usize) -> Option { 15 | Some($channel::new(self.iter.nth(n)?)) 16 | } 17 | 18 | #[inline] 19 | fn last(self) -> Option { 20 | Some($channel::new(self.iter.last()?)) 21 | } 22 | 23 | #[inline] 24 | fn size_hint(&self) -> (usize, Option) { 25 | self.iter.size_hint() 26 | } 27 | 28 | #[inline] 29 | fn count(self) -> usize { 30 | self.iter.count() 31 | } 32 | }; 33 | } 34 | 35 | /// An iterator over the channels in the buffer. 36 | /// 37 | /// Created with [Sequential::iter_channels][super::Sequential::iter_channels]. 38 | pub struct IterChannels<'a, T> { 39 | iter: slice::ChunksExact<'a, T>, 40 | } 41 | 42 | impl<'a, T> IterChannels<'a, T> { 43 | #[inline] 44 | pub(crate) fn new(data: &'a [T], frames: usize) -> Self { 45 | Self { 46 | iter: data.chunks_exact(frames), 47 | } 48 | } 49 | } 50 | 51 | impl<'a, T> Iterator for IterChannels<'a, T> { 52 | type Item = LinearChannel<'a, T>; 53 | 54 | forward!(LinearChannel); 55 | } 56 | 57 | impl DoubleEndedIterator for IterChannels<'_, T> { 58 | #[inline] 59 | fn next_back(&mut self) -> Option { 60 | Some(LinearChannel::new(self.iter.next_back()?)) 61 | } 62 | 63 | #[inline] 64 | fn nth_back(&mut self, n: usize) -> Option { 65 | Some(LinearChannel::new(self.iter.nth_back(n)?)) 66 | } 67 | } 68 | 69 | impl ExactSizeIterator for IterChannels<'_, T> { 70 | fn len(&self) -> usize { 71 | self.iter.len() 72 | } 73 | } 74 | 75 | /// A mutable iterator over the channels in the buffer. 76 | /// 77 | /// Created with [Sequential::iter_channels_mut][super::Sequential::iter_channels_mut]. 78 | pub struct IterChannelsMut<'a, T> { 79 | iter: slice::ChunksExactMut<'a, T>, 80 | } 81 | 82 | impl<'a, T> IterChannelsMut<'a, T> { 83 | #[inline] 84 | pub(crate) fn new(data: &'a mut [T], frames: usize) -> Self { 85 | Self { 86 | iter: data.chunks_exact_mut(frames), 87 | } 88 | } 89 | } 90 | 91 | impl<'a, T> Iterator for IterChannelsMut<'a, T> { 92 | type Item = LinearChannelMut<'a, T>; 93 | 94 | forward!(LinearChannelMut); 95 | } 96 | 97 | impl DoubleEndedIterator for IterChannelsMut<'_, T> { 98 | #[inline] 99 | fn next_back(&mut self) -> Option { 100 | Some(LinearChannelMut::new(self.iter.next_back()?)) 101 | } 102 | 103 | #[inline] 104 | fn nth_back(&mut self, n: usize) -> Option { 105 | Some(LinearChannelMut::new(self.iter.nth_back(n)?)) 106 | } 107 | } 108 | 109 | impl ExactSizeIterator for IterChannelsMut<'_, T> { 110 | fn len(&self) -> usize { 111 | self.iter.len() 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /audio/src/channel.rs: -------------------------------------------------------------------------------- 1 | //! Structs for accessing a single channel of audio within a multichannel buffer 2 | //! 3 | //! * [LinearChannelMut] and [LinearChannel] wraps a mutable and immutable *linear* channel 4 | //! buffer respectively. 5 | //! * [InterleavedChannelMut] and [InterleavedChannel] wraps mutable and immutable 6 | //! *interleaved* channel buffers respectively. 7 | 8 | use audio_core::{Channel, ChannelMut}; 9 | 10 | pub mod linear; 11 | pub use self::linear::{LinearChannel, LinearChannelMut}; 12 | 13 | pub mod interleaved; 14 | pub use self::interleaved::{InterleavedChannel, InterleavedChannelMut}; 15 | 16 | /// Copy the content of one channel to another. 17 | /// 18 | /// # Examples 19 | /// 20 | /// ``` 21 | /// use audio::{Buf, BufMut, ChannelMut}; 22 | /// 23 | /// let from = audio::interleaved![[1i16; 4]; 2]; 24 | /// let mut to = audio::buf::Interleaved::::with_topology(2, 4); 25 | /// 26 | /// audio::channel::copy(from.limit(2).get_channel(0).unwrap(), to.get_mut(0).unwrap()); 27 | /// assert_eq!(to.as_slice(), &[1, 0, 1, 0, 0, 0, 0, 0]); 28 | /// ``` 29 | pub fn copy(from: I, mut to: O) 30 | where 31 | I: Channel, 32 | O: ChannelMut, 33 | I::Sample: Copy, 34 | { 35 | match (from.try_as_linear(), to.try_as_linear_mut()) { 36 | (Some(from), Some(to)) => { 37 | let len = usize::min(to.len(), from.len()); 38 | to[..len].copy_from_slice(&from[..len]); 39 | } 40 | _ => { 41 | for (t, f) in to.iter_mut().zip(from.iter()) { 42 | *t = f; 43 | } 44 | } 45 | } 46 | } 47 | 48 | /// Copy an iterator into a channel. 49 | /// 50 | /// # Examples 51 | /// 52 | /// ``` 53 | /// use audio::ChannelMut; 54 | /// 55 | /// let mut to = audio::buf::Interleaved::::with_topology(2, 4); 56 | /// 57 | /// audio::channel::copy_iter(0i16.., to.get_mut(0).unwrap()); 58 | /// assert_eq!(to.as_slice(), &[0, 0, 1, 0, 2, 0, 3, 0]); 59 | /// ``` 60 | pub fn copy_iter(from: I, mut to: O) 61 | where 62 | I: IntoIterator, 63 | O: ChannelMut, 64 | I::Item: Copy, 65 | { 66 | for (t, f) in to.iter_mut().zip(from) { 67 | *t = f; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /audio/src/channel/linear/iter.rs: -------------------------------------------------------------------------------- 1 | use core::slice; 2 | 3 | /// An iterator over the frames in a linear channel. 4 | /// 5 | /// Created with [LinearChannel::iter][super::LinearChannel::iter]. 6 | pub struct Iter<'a, T> { 7 | iter: slice::Iter<'a, T>, 8 | } 9 | 10 | impl<'a, T> Iter<'a, T> { 11 | #[inline] 12 | pub(crate) fn new(data: &'a [T]) -> Self { 13 | Self { iter: data.iter() } 14 | } 15 | 16 | /// Views the underlying data as a subslice of the original data. 17 | #[inline] 18 | pub fn as_slice(&self) -> &'a [T] { 19 | self.iter.as_slice() 20 | } 21 | } 22 | 23 | impl Iterator for Iter<'_, T> 24 | where 25 | T: Copy, 26 | { 27 | type Item = T; 28 | 29 | #[inline] 30 | fn next(&mut self) -> Option { 31 | Some(*self.iter.next()?) 32 | } 33 | 34 | #[inline] 35 | fn nth(&mut self, n: usize) -> Option { 36 | Some(*self.iter.nth(n)?) 37 | } 38 | 39 | #[inline] 40 | fn last(self) -> Option { 41 | Some(*self.iter.last()?) 42 | } 43 | 44 | #[inline] 45 | fn size_hint(&self) -> (usize, Option) { 46 | self.iter.size_hint() 47 | } 48 | 49 | #[inline] 50 | fn count(self) -> usize { 51 | self.iter.count() 52 | } 53 | } 54 | 55 | impl DoubleEndedIterator for Iter<'_, T> 56 | where 57 | T: Copy, 58 | { 59 | #[inline] 60 | fn next_back(&mut self) -> Option { 61 | Some(*self.iter.next_back()?) 62 | } 63 | 64 | #[inline] 65 | fn nth_back(&mut self, n: usize) -> Option { 66 | Some(*self.iter.nth_back(n)?) 67 | } 68 | } 69 | 70 | impl ExactSizeIterator for Iter<'_, T> 71 | where 72 | T: Copy, 73 | { 74 | fn len(&self) -> usize { 75 | self.iter.len() 76 | } 77 | } 78 | 79 | /// A mutable iterator over the frames in a linear channel. 80 | /// 81 | /// Created with [LinearChannelMut::iter_mut][super::LinearChannelMut::iter_mut]. 82 | pub struct IterMut<'a, T> { 83 | iter: slice::IterMut<'a, T>, 84 | } 85 | 86 | impl<'a, T> IterMut<'a, T> { 87 | #[inline] 88 | pub(crate) fn new(data: &'a mut [T]) -> Self { 89 | Self { 90 | iter: data.iter_mut(), 91 | } 92 | } 93 | 94 | /// Views the underlying data as a subslice of the original data. 95 | /// 96 | /// To avoid creating `&mut` references that alias, this is forced to 97 | /// consume the iterator. 98 | pub fn into_slice(self) -> &'a [T] { 99 | self.iter.into_slice() 100 | } 101 | 102 | /// Views the underlying data as a subslice of the original data. 103 | pub fn as_slice(&self) -> &[T] { 104 | self.iter.as_slice() 105 | } 106 | } 107 | 108 | impl<'a, T> Iterator for IterMut<'a, T> { 109 | type Item = &'a mut T; 110 | 111 | #[inline] 112 | fn next(&mut self) -> Option { 113 | self.iter.next() 114 | } 115 | 116 | #[inline] 117 | fn nth(&mut self, n: usize) -> Option { 118 | self.iter.nth(n) 119 | } 120 | 121 | #[inline] 122 | fn last(self) -> Option { 123 | self.iter.last() 124 | } 125 | 126 | #[inline] 127 | fn size_hint(&self) -> (usize, Option) { 128 | self.iter.size_hint() 129 | } 130 | 131 | #[inline] 132 | fn count(self) -> usize { 133 | self.iter.count() 134 | } 135 | } 136 | 137 | impl DoubleEndedIterator for IterMut<'_, T> { 138 | #[inline] 139 | fn next_back(&mut self) -> Option { 140 | self.iter.next_back() 141 | } 142 | 143 | #[inline] 144 | fn nth_back(&mut self, n: usize) -> Option { 145 | self.iter.nth_back(n) 146 | } 147 | } 148 | 149 | impl ExactSizeIterator for IterMut<'_, T> { 150 | fn len(&self) -> usize { 151 | self.iter.len() 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /audio/src/channel/linear/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! slice_comparisons { 2 | ($(#[$meta:meta])* {$($gen:tt)*}, $a:ty, $b:ty) => { 3 | $(#[$meta])* 4 | impl<$($gen)*> cmp::PartialEq<$b> for $a where T: Copy, T: cmp::PartialEq { 5 | fn eq(&self, b: &$b) -> bool { 6 | <[T]>::as_ref(self.buf).eq(<[T]>::as_ref(b)) 7 | } 8 | } 9 | 10 | $(#[$meta])* 11 | impl<$($gen)*> cmp::PartialOrd<$b> for $a where T: Copy, T: cmp::PartialOrd { 12 | fn partial_cmp(&self, b: &$b) -> Option { 13 | <[T]>::as_ref(self.buf).partial_cmp(<[T]>::as_ref(b)) 14 | } 15 | } 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /audio/src/frame.rs: -------------------------------------------------------------------------------- 1 | //! Common helpers for frame abstractions. 2 | 3 | mod interleaved; 4 | pub(crate) use self::interleaved::RawInterleaved; 5 | pub use self::interleaved::{InterleavedFrame, InterleavedFramesIter}; 6 | 7 | mod sequential; 8 | pub(crate) use self::sequential::RawSequential; 9 | pub use self::sequential::{SequentialFrame, SequentialFramesIter}; 10 | -------------------------------------------------------------------------------- /audio/src/io.rs: -------------------------------------------------------------------------------- 1 | //! Reading and writing sequentially from buffers. 2 | //! 3 | //! This is called buffered I/O, and allow buffers to support sequential reading 4 | //! and writing to and from buffer. 5 | //! 6 | //! The primary traits that govern this is [ReadBuf] and [WriteBuf]. 7 | 8 | pub use audio_core::{ReadBuf, WriteBuf}; 9 | 10 | #[macro_use] 11 | mod macros; 12 | 13 | mod utils; 14 | pub use self::utils::{copy_remaining, translate_remaining}; 15 | 16 | mod read; 17 | pub use self::read::Read; 18 | 19 | mod write; 20 | pub use self::write::Write; 21 | 22 | mod read_write; 23 | pub use self::read_write::ReadWrite; 24 | -------------------------------------------------------------------------------- /audio/src/io/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! iter { 2 | ( 3 | $($field:ident : $field_ty:ty),* $(,)? 4 | => 5 | $self:ident $(. $fn:ident ($($arg:ident),* $(,)?))* 6 | ) => { 7 | pub struct Iter<'a, B> 8 | where 9 | B: 'a + Buf, 10 | { 11 | iter: B::IterChannels<'a>, 12 | $($field: $field_ty,)* 13 | } 14 | 15 | impl<'a, B> Iterator for Iter<'a, B> 16 | where 17 | B: 'a + Buf, 18 | { 19 | type Item = B::Channel<'a>; 20 | 21 | #[inline] 22 | fn next(&mut $self) -> Option { 23 | let channel = $self.iter.next()?; 24 | Some(channel $(. $fn ($($self . $arg),*))*) 25 | } 26 | } 27 | } 28 | } 29 | 30 | macro_rules! iter_mut { 31 | ( 32 | $($field:ident : $field_ty:ty),* $(,)? 33 | => 34 | $self:ident $(. $fn:ident ($($arg:ident),* $(,)?))* 35 | ) => { 36 | pub struct IterMut<'a, B> 37 | where 38 | B: 'a + BufMut, 39 | { 40 | iter: B::IterChannelsMut<'a>, 41 | $($field: $field_ty,)* 42 | } 43 | 44 | impl<'a, B> Iterator for IterMut<'a, B> 45 | where 46 | B: 'a + BufMut, 47 | { 48 | type Item = B::ChannelMut<'a>; 49 | 50 | #[inline] 51 | fn next(&mut $self) -> Option { 52 | let channel = $self.iter.next()?; 53 | Some(channel $(. $fn ($($self . $arg),*))*) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /audio/src/io/utils.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for manipulating audio buffers. 2 | 3 | use audio_core::{Buf, BufMut, ReadBuf, Translate, WriteBuf}; 4 | 5 | /// Copy the shared remaining frames from `from` into `to`. 6 | /// 7 | /// This will copy the minimum number of frames between [ReadBuf::remaining] and 8 | /// [WriteBuf::remaining_mut], and advance the provided buffers appropriately 9 | /// using [ReadBuf::advance] and [WriteBuf::advance_mut]. 10 | pub fn copy_remaining(mut from: I, mut to: O) 11 | where 12 | I: ReadBuf + Buf, 13 | O: WriteBuf + BufMut, 14 | I::Sample: Copy, 15 | { 16 | let len = usize::min(from.remaining(), to.remaining_mut()); 17 | crate::buf::copy(&from, &mut to); 18 | from.advance(len); 19 | to.advance_mut(len); 20 | } 21 | 22 | /// Translate the shared remaining frames from `from` into `to`. 23 | /// 24 | /// Samples will be translated through the [Translate] trait. 25 | /// 26 | /// This will translate the minimum number of frames between 27 | /// [ReadBuf::remaining] and [WriteBuf::remaining_mut], and advance the provided 28 | /// buffers appropriately using [ReadBuf::advance] and [WriteBuf::advance_mut]. 29 | pub fn translate_remaining(mut from: I, mut to: O) 30 | where 31 | I: ReadBuf + Buf, 32 | O: WriteBuf + BufMut, 33 | O::Sample: Translate, 34 | I::Sample: Copy, 35 | { 36 | let len = usize::min(from.remaining(), to.remaining_mut()); 37 | crate::buf::translate(&from, &mut to); 38 | from.advance(len); 39 | to.advance_mut(len); 40 | } 41 | -------------------------------------------------------------------------------- /audio/src/tests/byte_arrays.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn test_byte_array() { 3 | let buf: crate::buf::Interleaved<[u8; 2]> = 4 | crate::interleaved![[[1, 2], [3, 4]], [[5, 6], [7, 8]]]; 5 | 6 | assert_eq!(buf.channels(), 2); 7 | assert_eq!(buf.sample(0, 0).unwrap(), [1, 2]); 8 | assert_eq!(buf.sample(0, 1).unwrap(), [3, 4]); 9 | assert_eq!(buf.sample(1, 0).unwrap(), [5, 6]); 10 | assert_eq!(buf.sample(1, 1).unwrap(), [7, 8]); 11 | } 12 | -------------------------------------------------------------------------------- /audio/src/tests/copy_channel.rs: -------------------------------------------------------------------------------- 1 | // Miri: copying channels internally in a buffer intrinsically requires a bit of 2 | // tongue in cheek pointer mangling. These tests are added here so that they can 3 | // be run through miri to test that at least a base level of sanity is 4 | // maintained. 5 | 6 | use crate::{buf, wrap}; 7 | use audio_core::{Buf, BufMut, Channel}; 8 | 9 | #[test] 10 | fn test_copy_channels_dynamic() { 11 | let mut buf: buf::Dynamic = crate::dynamic![[1, 2, 3, 4], [0, 0, 0, 0]]; 12 | buf.copy_channel(0, 1); 13 | 14 | assert_eq!(buf.get_channel(1), buf.get_channel(0)); 15 | } 16 | 17 | #[test] 18 | fn test_copy_channels_sequential() { 19 | let mut buf: buf::Sequential = crate::sequential![[1, 2, 3, 4], [0, 0, 0, 0]]; 20 | buf.copy_channel(0, 1); 21 | 22 | assert_eq!(buf.get_channel(1), buf.get_channel(0)); 23 | assert_eq!(buf.as_slice(), &[1, 2, 3, 4, 1, 2, 3, 4]); 24 | } 25 | 26 | #[test] 27 | fn test_copy_channels_wrap_sequential() { 28 | let mut data = [1, 2, 3, 4, 0, 0, 0, 0]; 29 | let data = &mut data[..]; 30 | let mut buf: wrap::Sequential<&mut [i16]> = wrap::sequential(data, 2); 31 | buf.copy_channel(0, 1); 32 | 33 | assert_eq!(buf.get_channel(1), buf.get_channel(0)); 34 | assert_eq!(data, &[1, 2, 3, 4, 1, 2, 3, 4]); 35 | } 36 | 37 | #[test] 38 | fn test_copy_channels_interleaved() { 39 | let mut buf: buf::Interleaved = crate::interleaved![[1, 2, 3, 4], [0, 0, 0, 0]]; 40 | buf.copy_channel(0, 1); 41 | 42 | assert_eq!(buf.get_channel(1), buf.get_channel(0)); 43 | assert_eq!(buf.as_slice(), &[1, 1, 2, 2, 3, 3, 4, 4]); 44 | } 45 | 46 | #[test] 47 | fn test_copy_channels_wrap_interleaved() { 48 | let mut data = [1, 0, 2, 0, 3, 0, 4, 0]; 49 | let mut buf: wrap::Interleaved<&mut [i16]> = wrap::interleaved(&mut data[..], 2); 50 | buf.copy_channel(0, 1); 51 | 52 | assert_eq!(buf.get_channel(1), buf.get_channel(0)); 53 | assert_eq!(&data[..], &[1, 1, 2, 2, 3, 3, 4, 4]); 54 | } 55 | 56 | #[test] 57 | fn test_copy_channels_vec_of_vecs() { 58 | let mut buf = crate::wrap::dynamic(vec![vec![1, 2, 3, 4], vec![0, 0]]); 59 | buf.copy_channel(0, 1); 60 | 61 | assert_eq!( 62 | buf.get_channel(1).unwrap(), 63 | buf.get_channel(0).unwrap().limit(2) 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /audio/src/tests/io.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn test_read_write() { 3 | use crate::io::{Read, ReadWrite, Write}; 4 | use audio_core::{Buf, ReadBuf, WriteBuf}; 5 | 6 | let from = crate::interleaved![[1.0f32, 2.0f32, 3.0f32, 4.0f32]; 2]; 7 | let to = crate::interleaved![[0.0f32; 4]; 2]; 8 | 9 | // Make `to` into a ReadWrite adapter. 10 | let mut to = ReadWrite::empty(to); 11 | 12 | crate::io::copy_remaining(Read::new((&from).skip(2).limit(1)), &mut to); 13 | assert_eq!(to.remaining(), 1); 14 | 15 | crate::io::copy_remaining(Read::new((&from).limit(1)), &mut to); 16 | assert_eq!(to.remaining(), 2); 17 | 18 | assert_eq! { 19 | to.as_ref().as_slice(), 20 | &[3.0, 3.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0], 21 | }; 22 | 23 | // Note: 4 channels, 2 frames each. 24 | let mut read_out = Write::new(crate::buf::Interleaved::with_topology(4, 2)); 25 | 26 | assert_eq!(read_out.remaining_mut(), 2); 27 | assert!(read_out.has_remaining_mut()); 28 | 29 | assert_eq!(to.remaining(), 2); 30 | assert!(to.has_remaining()); 31 | 32 | crate::io::copy_remaining(&mut to, &mut read_out); 33 | 34 | assert_eq!(read_out.remaining_mut(), 0); 35 | assert!(!read_out.has_remaining_mut()); 36 | 37 | assert_eq!(to.remaining(), 0); 38 | assert!(!to.has_remaining()); 39 | 40 | assert_eq! { 41 | read_out.as_ref().as_slice(), 42 | &[3.0, 3.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0], 43 | } 44 | } 45 | 46 | #[test] 47 | fn test_simple_io() { 48 | use crate::io::ReadWrite; 49 | 50 | let buf: crate::buf::Interleaved = crate::interleaved![[1, 2, 3, 4]; 4]; 51 | let mut buf = ReadWrite::new(buf); 52 | 53 | let from = crate::wrap::interleaved(&[1i16, 2i16, 3i16, 4i16][..], 2); 54 | 55 | crate::io::translate_remaining(from, &mut buf); 56 | 57 | let buf = buf.into_inner(); 58 | 59 | assert_eq!(buf.channels(), 4); 60 | } 61 | -------------------------------------------------------------------------------- /audio/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod byte_arrays; 2 | mod copy_channel; 3 | mod dynamic; 4 | mod interleaved; 5 | mod io; 6 | mod sequential; 7 | -------------------------------------------------------------------------------- /audio/src/utils.rs: -------------------------------------------------------------------------------- 1 | use core::ptr; 2 | 3 | /// Utility functions to copy a channel in-place in a sequential audio buffer 4 | /// from one place to another. 5 | /// 6 | /// # Safety 7 | /// 8 | /// Caller has to ensure that the `data` pointer points to sequential memory 9 | /// with the correct topology and initialized frame count. 10 | pub(crate) unsafe fn copy_channels_sequential( 11 | data: ptr::NonNull, 12 | channels: usize, 13 | frames: usize, 14 | from: usize, 15 | to: usize, 16 | ) where 17 | T: Copy, 18 | { 19 | assert! { 20 | from < channels, 21 | "copy from channel {} is out of bounds 0-{}", 22 | from, 23 | channels 24 | }; 25 | assert! { 26 | to < channels, 27 | "copy to channel {} which is out of bounds 0-{}", 28 | to, 29 | channels 30 | }; 31 | 32 | if from != to { 33 | let from = data.as_ptr().add(from * frames); 34 | let to = data.as_ptr().add(to * frames); 35 | ptr::copy_nonoverlapping(from, to, frames); 36 | } 37 | } 38 | 39 | /// Utility functions to copy a channel in-place in an interleaved audio buffer 40 | /// from one place to another. 41 | /// 42 | /// # Safety 43 | /// 44 | /// Caller has to ensure that the `data` pointer points to interleaved memory 45 | /// with the correct topology and initialized frame count. 46 | pub(crate) unsafe fn copy_channels_interleaved( 47 | data: ptr::NonNull, 48 | channels: usize, 49 | frames: usize, 50 | from: usize, 51 | to: usize, 52 | ) where 53 | T: Copy, 54 | { 55 | assert! { 56 | from < channels, 57 | "copy from channel {} is out of bounds 0-{}", 58 | from, 59 | channels 60 | }; 61 | assert! { 62 | to < channels, 63 | "copy to channel {} which is out of bounds 0-{}", 64 | to, 65 | channels 66 | }; 67 | 68 | if from != to { 69 | // Safety: We're making sure not to access any mutable buffers which 70 | // are not initialized. 71 | for n in 0..frames { 72 | let from = data.as_ptr().add(from + channels * n) as *const _; 73 | let to = data.as_ptr().add(to + channels * n); 74 | let f = ptr::read(from); 75 | ptr::write(to, f); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /audio/src/wrap.rs: -------------------------------------------------------------------------------- 1 | //! This module provides wrappers to pass audio data from crates which use 2 | //! different buffer formats into functions that take [Buf][crate::Buf] or 3 | //! [BufMut][crate::BufMut] without needing to copy data into an intermediate 4 | //! buffer. They may also be useful for incrementally introducing this crate 5 | //! into a codebase that uses a different buffer format. 6 | 7 | use crate::slice::Slice; 8 | 9 | mod interleaved; 10 | pub use self::interleaved::Interleaved; 11 | 12 | mod sequential; 13 | pub use self::sequential::Sequential; 14 | 15 | #[cfg(feature = "std")] 16 | mod dynamic; 17 | #[cfg(feature = "std")] 18 | pub use self::dynamic::Dynamic; 19 | 20 | /// Wrap a slice as an interleaved buffer with the given number of channels. 21 | /// 22 | /// Certain interleaved buffers can be used conveniently as implementors of 23 | /// [ReadBuf][crate::ReadBuf] and [WriteBuf][crate::WriteBuf], due to the 24 | /// convenient nature of the buffer living linearly in memory. 25 | /// 26 | /// * `&[T]` - implements [ReadBuf][crate::ReadBuf]. 27 | /// * `&mut [T]` - implements [WriteBuf][crate::WriteBuf]. 28 | /// 29 | /// # Example using a buffer for linear I/O 30 | /// 31 | /// ``` 32 | /// use audio::{wrap, io}; 33 | /// use audio::ReadBuf as _; 34 | /// 35 | /// let mut read_from = wrap::interleaved(&[0, 1, 2, 4, 5, 6, 7, 8][..], 2); 36 | /// let mut write_to = io::Write::new(audio::sequential![[0i16; 4]; 2]); 37 | /// 38 | /// assert!(read_from.has_remaining()); 39 | /// io::copy_remaining(&mut read_from, &mut write_to); 40 | /// assert!(!read_from.has_remaining()); 41 | /// 42 | /// assert_eq! { 43 | /// write_to.as_ref().as_slice(), 44 | /// &[0, 2, 5, 7, 1, 4, 6, 8], 45 | /// }; 46 | /// ``` 47 | /// 48 | /// Or with a mutable slice for writing. 49 | /// 50 | /// ``` 51 | /// use audio::{wrap, io}; 52 | /// use audio::WriteBuf as _; 53 | /// 54 | /// let mut vec = vec![0, 1, 2, 4, 5, 6, 7, 8]; 55 | /// 56 | /// let mut read_from = io::Read::new(audio::sequential![[0i16, 1i16, 2i16, 3i16]; 2]); 57 | /// let mut write_to = wrap::interleaved(&mut vec[..], 2); 58 | /// 59 | /// assert!(write_to.has_remaining_mut()); 60 | /// io::copy_remaining(&mut read_from, &mut write_to); 61 | /// assert!(!write_to.has_remaining_mut()); 62 | /// 63 | /// assert_eq! { 64 | /// &vec[..], 65 | /// &[0, 0, 1, 1, 2, 2, 3, 3], 66 | /// }; 67 | /// ``` 68 | pub fn interleaved(value: T, channels: usize) -> Interleaved 69 | where 70 | T: Slice, 71 | { 72 | Interleaved::new(value, channels) 73 | } 74 | 75 | /// Wrap a slice as a sequential buffer with the given number of frames. The 76 | /// length of the buffer determines the number of channels it has. 77 | /// 78 | /// Unlike [interleaved][interleaved()], wrapped sequential buffers cannot be 79 | /// used as implementations of [ReadBuf][crate::ReadBuf] or 80 | /// [WriteBuf][crate::WriteBuf]. 81 | /// 82 | /// You can instead use the [Read][crate::io::Read] or [Write][crate::io::Write] 83 | /// adapters available to accomplish this. 84 | pub fn sequential(value: T, channels: usize) -> Sequential 85 | where 86 | T: Slice, 87 | { 88 | Sequential::new(value, channels) 89 | } 90 | 91 | /// Wrap a [Vec] of Vecs or slice of Vecs where each inner Vec is a channel. 92 | /// The channels do not need to be equally sized. 93 | /// 94 | /// This should only be used for external types which you have no control over 95 | /// or if you legitimately need a buffer which doesn't have uniformly sized 96 | /// channels. Otherwise you should prefer to use [Dynamic][crate::buf::Dynamic] 97 | /// instead since it's more memory efficient. 98 | /// 99 | /// # Example 100 | /// 101 | /// ``` 102 | /// use audio::Buf; 103 | /// 104 | /// let buf = vec![vec![1, 2, 3, 4], vec![5, 6]]; 105 | /// let buf = audio::wrap::dynamic(&buf[..]); 106 | /// assert_eq!(buf.channels(), 2); 107 | /// assert_eq!(buf.frames_hint(), Some(4)); 108 | /// ``` 109 | #[cfg(feature = "std")] 110 | pub fn dynamic(value: T) -> Dynamic { 111 | Dynamic::new(value) 112 | } 113 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | version = "0.0.0" 4 | authors = ["John-John Tedro "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | audio = { path = "../audio" } 10 | bittle = "0.2.1" 11 | cpal = "0.14.0" 12 | minimp3 = { git = "https://github.com/udoprog/minimp3-rs", branch = "next" } 13 | rubato = { git = "https://github.com/udoprog/rubato", branch = "next" } 14 | anyhow = "1.0.57" 15 | tracing = "0.1.36" 16 | tracing-subscriber = { version = "0.3.3", features = ["env-filter"] } 17 | -------------------------------------------------------------------------------- /generate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generate" 3 | version = "0.0.0" 4 | authors = ["John-John Tedro "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | anyhow = "1.0.57" 10 | windows_macros = "0.31.0" 11 | windows_gen = "0.31.0" 12 | windows_reader = "0.31.0" 13 | bindgen = "0.59.2" 14 | pkg-config = "0.3.25" 15 | -------------------------------------------------------------------------------- /generate/README.md: -------------------------------------------------------------------------------- 1 | # audio-device-bindings 2 | -------------------------------------------------------------------------------- /generate/alsa.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /generate/pipewire.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /generate/pulse.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /generate/src/bin/generate-alsa.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use std::env; 3 | use std::path::PathBuf; 4 | 5 | fn main() -> anyhow::Result<()> { 6 | let lib = pkg_config::Config::new() 7 | .statik(false) 8 | .cargo_metadata(false) 9 | .probe("alsa")?; 10 | generate_bindings(&lib)?; 11 | Ok(()) 12 | } 13 | 14 | fn generate_bindings(lib: &pkg_config::Library) -> anyhow::Result<()> { 15 | let include_args = lib.include_paths.iter().map(|include_path| { 16 | format!( 17 | "-I{}", 18 | include_path.to_str().expect("include path was not UTF-8") 19 | ) 20 | }); 21 | 22 | let mut config = bindgen::CodegenConfig::empty(); 23 | config.insert(bindgen::CodegenConfig::FUNCTIONS); 24 | config.insert(bindgen::CodegenConfig::TYPES); 25 | 26 | let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?); 27 | let output = root 28 | .join("..") 29 | .join("audio-device-alsa-sys") 30 | .join("src") 31 | .join("bindings.rs"); 32 | 33 | let builder = bindgen::Builder::default() 34 | .size_t_is_usize(true) 35 | .allowlist_recursively(false) 36 | .prepend_enum_name(false) 37 | .layout_tests(false) 38 | .allowlist_function("snd_.*") 39 | .allowlist_type("_?snd_.*") 40 | .allowlist_type(".*va_list.*") 41 | .with_codegen_config(config) 42 | .clang_args(include_args) 43 | .header(root.join("alsa.h").display().to_string()); 44 | 45 | let bindings = builder 46 | .generate() 47 | .map_err(|()| anyhow!("Unable to generate bindings"))?; 48 | 49 | bindings.write_to_file(output)?; 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /generate/src/bin/generate-pipewire.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use std::env; 3 | use std::path::PathBuf; 4 | 5 | fn main() -> anyhow::Result<()> { 6 | let lib = pkg_config::Config::new() 7 | .statik(false) 8 | .cargo_metadata(false) 9 | .probe("libpipewire-0.3")?; 10 | generate_bindings(&lib)?; 11 | Ok(()) 12 | } 13 | 14 | fn generate_bindings(lib: &pkg_config::Library) -> anyhow::Result<()> { 15 | let include_args = lib.include_paths.iter().map(|include_path| { 16 | format!( 17 | "-I{}", 18 | include_path.to_str().expect("include path was not UTF-8") 19 | ) 20 | }); 21 | 22 | let mut config = bindgen::CodegenConfig::empty(); 23 | config.insert(bindgen::CodegenConfig::FUNCTIONS); 24 | config.insert(bindgen::CodegenConfig::TYPES); 25 | 26 | let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?); 27 | let output = root 28 | .join("..") 29 | .join("audio-device-pipewire-sys") 30 | .join("src") 31 | .join("bindings.rs"); 32 | 33 | let builder = bindgen::Builder::default() 34 | .size_t_is_usize(true) 35 | .allowlist_recursively(false) 36 | .prepend_enum_name(false) 37 | .layout_tests(false) 38 | .allowlist_function("pw_.*") 39 | .allowlist_type("pw_.*") 40 | .allowlist_function("spa_.*") 41 | .allowlist_type("spa_.*") 42 | .allowlist_type("__va_list_.*") 43 | .with_codegen_config(config) 44 | .clang_args(include_args) 45 | .header(root.join("pipewire.h").display().to_string()); 46 | 47 | let bindings = builder 48 | .generate() 49 | .map_err(|()| anyhow!("Unable to generate bindings"))?; 50 | 51 | bindings.write_to_file(output)?; 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /generate/src/bin/generate-pulse.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use std::env; 3 | use std::path::PathBuf; 4 | 5 | fn main() -> anyhow::Result<()> { 6 | let lib = pkg_config::Config::new() 7 | .statik(false) 8 | .cargo_metadata(false) 9 | .probe("libpulse")?; 10 | generate_bindings(&lib)?; 11 | Ok(()) 12 | } 13 | 14 | fn generate_bindings(lib: &pkg_config::Library) -> anyhow::Result<()> { 15 | let include_args = lib.include_paths.iter().map(|include_path| { 16 | format!( 17 | "-I{}", 18 | include_path.to_str().expect("include path was not UTF-8") 19 | ) 20 | }); 21 | 22 | let mut config = bindgen::CodegenConfig::empty(); 23 | config.insert(bindgen::CodegenConfig::FUNCTIONS); 24 | config.insert(bindgen::CodegenConfig::TYPES); 25 | 26 | let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?); 27 | let output = root 28 | .join("..") 29 | .join("audio-device-pulse-sys") 30 | .join("src") 31 | .join("bindings.rs"); 32 | 33 | let builder = bindgen::Builder::default() 34 | .size_t_is_usize(true) 35 | .allowlist_recursively(false) 36 | .prepend_enum_name(false) 37 | .layout_tests(false) 38 | .allowlist_function("pa_.*") 39 | .allowlist_type("pa_.*") 40 | .with_codegen_config(config) 41 | .clang_args(include_args) 42 | .header(root.join("pulse.h").display().to_string()); 43 | 44 | let bindings = builder 45 | .generate() 46 | .map_err(|()| anyhow!("Unable to generate bindings"))?; 47 | 48 | bindings.write_to_file(output)?; 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /ste/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ste" 3 | version = "0.1.0-alpha.11" 4 | authors = ["John-John Tedro "] 5 | edition = "2018" 6 | rust-version = "1.70" 7 | description = "A single-threaded executor with some tricks up its sleeve." 8 | documentation = "https://docs.rs/audio" 9 | readme = "README.md" 10 | homepage = "https://github.com/udoprog/audio" 11 | repository = "https://github.com/udoprog/audio" 12 | license = "MIT OR Apache-2.0" 13 | keywords = ["executor", "thread"] 14 | categories = ["concurrency"] 15 | 16 | [lints.rust] 17 | unexpected_cfgs = { level = "deny", check-cfg = ['cfg(loom)'] } 18 | 19 | [features] 20 | default = ["tokio"] 21 | 22 | [dependencies] 23 | tokio = { version = "1.18.1", features = ["rt"], optional = true } 24 | 25 | [dev-dependencies] 26 | anyhow = "1.0.57" 27 | criterion = "0.4.0" 28 | tokio = { version = "1.18.1", features = ["rt", "macros", "sync", "time"] } 29 | futures = "0.3.21" 30 | 31 | [[bench]] 32 | name = "benches" 33 | harness = false 34 | 35 | [target.'cfg(loom)'.dependencies] 36 | loom = "0.5.4" 37 | -------------------------------------------------------------------------------- /ste/benches/benches.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | fn test_on_ste(b: &mut Criterion) { 4 | b.bench_function("test_on_ste", |b| { 5 | b.iter(|| { 6 | let thread = ste::spawn(); 7 | let mut result = 0; 8 | 9 | for n in 0..100 { 10 | result += thread.submit(move || n); 11 | } 12 | 13 | thread.join(); 14 | result 15 | }); 16 | }); 17 | } 18 | 19 | fn count_to_1000_ste(b: &mut Criterion) { 20 | b.bench_function("test_on_ste", |b| { 21 | b.iter(|| { 22 | let thread = ste::spawn(); 23 | let mut total = 0u32; 24 | 25 | for n in 0..1000u32 { 26 | total += thread.submit(move || n + 1); 27 | } 28 | 29 | thread.join(); 30 | assert_eq!(total, 500500); 31 | total 32 | }); 33 | }); 34 | } 35 | 36 | fn count_to_1000_mpsc(b: &mut Criterion) { 37 | use std::sync::mpsc; 38 | use std::thread; 39 | 40 | b.bench_function("count_to_1000_mpsc", |b| { 41 | b.iter(|| { 42 | let mut total = 0u32; 43 | 44 | let t = { 45 | let (tx, rx) = mpsc::sync_channel(0); 46 | let (out_tx, out_rx) = mpsc::sync_channel(0); 47 | 48 | let t = thread::spawn(move || { 49 | while let Ok(task) = rx.recv() { 50 | out_tx.send(task + 1).unwrap(); 51 | } 52 | }); 53 | 54 | for n in 0..1000u32 { 55 | tx.send(n).unwrap(); 56 | total += out_rx.recv().unwrap(); 57 | } 58 | 59 | t 60 | }; 61 | 62 | t.join().unwrap(); 63 | assert_eq!(total, 500500); 64 | total 65 | }); 66 | }); 67 | } 68 | 69 | criterion_group!(benches, test_on_ste, count_to_1000_ste, count_to_1000_mpsc); 70 | criterion_main!(benches); 71 | -------------------------------------------------------------------------------- /ste/examples/recover_from_panic.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use std::sync::Arc; 3 | use std::thread; 4 | 5 | fn main() -> anyhow::Result<()> { 6 | for _ in 0..100 { 7 | let thread = Arc::new(ste::spawn()); 8 | 9 | let mut threads = Vec::new(); 10 | 11 | for _ in 0..2 { 12 | let thread = thread.clone(); 13 | 14 | let t = thread::spawn(move || { 15 | thread.submit(|| { 16 | thread::sleep(std::time::Duration::from_millis(10)); 17 | panic!("trigger"); 18 | }) 19 | }); 20 | 21 | threads.push(t); 22 | } 23 | 24 | for t in threads { 25 | assert!(t.join().is_err()); 26 | } 27 | 28 | let thread = Arc::try_unwrap(thread).map_err(|_| anyhow!("unwrap failed"))?; 29 | thread.join(); 30 | } 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /ste/examples/simple_async.rs: -------------------------------------------------------------------------------- 1 | #[tokio::main(flavor = "current_thread")] 2 | async fn main() -> anyhow::Result<()> { 3 | let thread = ste::Builder::new().with_tokio().build()?; 4 | 5 | let mut result = 0u32; 6 | 7 | thread 8 | .submit_async(async { 9 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 10 | result += 1 11 | }) 12 | .await; 13 | 14 | assert_eq!(result, 1u32); 15 | 16 | thread.join(); 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /ste/examples/submit.rs: -------------------------------------------------------------------------------- 1 | fn main() -> anyhow::Result<()> { 2 | for _ in 0..10 { 3 | let thread = ste::spawn(); 4 | let mut result = 0; 5 | 6 | for n in 0..100 { 7 | result += thread.submit(move || n); 8 | } 9 | 10 | assert_eq!(result, 4950); 11 | thread.join(); 12 | } 13 | 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /ste/examples/submit_async.rs: -------------------------------------------------------------------------------- 1 | #[tokio::main(flavor = "current_thread")] 2 | async fn main() -> anyhow::Result<()> { 3 | for _ in 0..10u32 { 4 | let thread = ste::spawn(); 5 | let mut result = 0u32; 6 | 7 | for n in 0..100 { 8 | thread 9 | .submit_async(async { 10 | result += n; 11 | }) 12 | .await; 13 | } 14 | 15 | assert_eq!(result, 4950); 16 | thread.join(); 17 | } 18 | 19 | let thread = ste::spawn(); 20 | thread.submit_async(async move { panic!("woops") }).await; 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /ste/examples/submit_async_threaded.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::thread; 3 | 4 | #[tokio::main(flavor = "current_thread")] 5 | async fn main() -> anyhow::Result<()> { 6 | for _ in 0..10 { 7 | let mut threads = Vec::new(); 8 | 9 | let thread = Arc::new(ste::spawn()); 10 | 11 | for n in 0..100 { 12 | let thread = thread.clone(); 13 | 14 | threads.push(thread::spawn(move || { 15 | let mut result = 0u32; 16 | 17 | let future = thread.submit_async(async { 18 | result += n; 19 | }); 20 | 21 | futures::executor::block_on(future); 22 | result 23 | })); 24 | } 25 | 26 | let mut result = 0; 27 | 28 | for t in threads { 29 | result += t.join().unwrap(); 30 | } 31 | 32 | assert_eq!(result, 4950); 33 | 34 | let thread = Arc::try_unwrap(thread).map_err(|_| "not unique").unwrap(); 35 | thread.join(); 36 | } 37 | 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /ste/src/loom.rs: -------------------------------------------------------------------------------- 1 | #[cfg(loom)] 2 | pub use loom::sync; 3 | #[cfg(loom)] 4 | pub use loom::thread; 5 | 6 | #[cfg(not(loom))] 7 | pub use ::std::sync; 8 | #[cfg(not(loom))] 9 | pub use ::std::thread; 10 | -------------------------------------------------------------------------------- /ste/src/misc.rs: -------------------------------------------------------------------------------- 1 | use std::ptr; 2 | 3 | /// Small helper for sending pointers which are not send. 4 | #[repr(transparent)] 5 | pub(crate) struct RawSend(pub(crate) ptr::NonNull) 6 | where 7 | T: ?Sized; 8 | 9 | // Safety: this is limited to the module and guaranteed to be correct. 10 | unsafe impl Send for RawSend where T: ?Sized {} 11 | -------------------------------------------------------------------------------- /ste/src/tests.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use std::sync::Arc; 3 | use std::thread; 4 | 5 | #[test] 6 | fn test_recover_from_panic() -> anyhow::Result<()> { 7 | for _ in 0..100 { 8 | let thread = Arc::new(crate::spawn()); 9 | 10 | let mut threads = Vec::new(); 11 | 12 | for _ in 0..10 { 13 | let thread = thread.clone(); 14 | 15 | let t = thread::spawn(move || { 16 | thread.submit(|| { 17 | thread::sleep(std::time::Duration::from_millis(10)); 18 | panic!("trigger"); 19 | }) 20 | }); 21 | 22 | threads.push(t); 23 | } 24 | 25 | for t in threads { 26 | assert!(t.join().is_err()); 27 | } 28 | 29 | let thread = Arc::try_unwrap(thread).map_err(|_| anyhow!("unwrap failed"))?; 30 | thread.join(); 31 | } 32 | 33 | Ok(()) 34 | } 35 | 36 | #[test] 37 | fn test_threading() -> anyhow::Result<()> { 38 | for _ in 0..100 { 39 | let thread = Arc::new(crate::spawn()); 40 | 41 | let mut threads = Vec::new(); 42 | 43 | for n in 0..10 { 44 | let thread = thread.clone(); 45 | let t = thread::spawn(move || { 46 | thread.submit(|| { 47 | std::thread::sleep(std::time::Duration::from_millis(10)); 48 | n 49 | }) 50 | }); 51 | threads.push(t); 52 | } 53 | 54 | let mut result = 0; 55 | 56 | for t in threads { 57 | result += t.join().unwrap(); 58 | } 59 | 60 | assert_eq!(result, 45); 61 | 62 | let thread = Arc::try_unwrap(thread).map_err(|_| anyhow!("unwrap failed"))?; 63 | thread.join(); 64 | } 65 | 66 | Ok(()) 67 | } 68 | -------------------------------------------------------------------------------- /ste/src/wait_future.rs: -------------------------------------------------------------------------------- 1 | use crate::misc::RawSend; 2 | use crate::parker::Parker; 3 | use crate::tag::{with_tag, Tag}; 4 | use crate::worker::{Entry, Shared}; 5 | use std::future::Future; 6 | use std::pin::Pin; 7 | use std::ptr; 8 | use std::task::{Context, Poll, Waker}; 9 | 10 | pub(super) struct WaitFuture<'a, F> 11 | where 12 | F: Future, 13 | { 14 | /// The future being polled. 15 | pub(super) future: ptr::NonNull, 16 | /// Where to store output. 17 | pub(super) output: ptr::NonNull>, 18 | pub(super) parker: ptr::NonNull, 19 | pub(super) complete: bool, 20 | pub(super) shared: &'a Shared, 21 | } 22 | 23 | impl Future for WaitFuture<'_, F> 24 | where 25 | F: Future, 26 | { 27 | type Output = F::Output; 28 | 29 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 30 | unsafe { 31 | let this = Pin::get_unchecked_mut(self.as_mut()); 32 | 33 | if this.complete { 34 | panic!("task already completed"); 35 | } 36 | 37 | let mut task = into_task( 38 | RawSend((&mut this.complete).into()), 39 | RawSend(this.future), 40 | RawSend(this.output), 41 | RawSend(cx.waker().into()), 42 | ); 43 | let entry = Entry::new(&mut task, this.parker); 44 | 45 | this.shared.schedule_in_place(this.parker, entry); 46 | 47 | if this.complete { 48 | panic!("background thread panicked"); 49 | } 50 | 51 | if let Some(output) = this.output.as_mut().take() { 52 | this.complete = true; 53 | Poll::Ready(output) 54 | } else { 55 | Poll::Pending 56 | } 57 | } 58 | } 59 | } 60 | 61 | unsafe impl Send for WaitFuture<'_, F> where F: Future {} 62 | 63 | fn into_task( 64 | mut complete: RawSend, 65 | mut future: RawSend, 66 | mut output: RawSend>, 67 | waker: RawSend, 68 | ) -> impl FnMut(Tag) + Send 69 | where 70 | F: Future, 71 | { 72 | use std::panic; 73 | 74 | move |tag| { 75 | unsafe { 76 | // Safety: At this point, we know the waker has been 77 | // replaced by the polling task and can safely deref it into 78 | // the underlying waker. 79 | let waker = waker.0.as_ref(); 80 | 81 | let mut cx = Context::from_waker(waker); 82 | let future = Pin::new_unchecked(future.0.as_mut()); 83 | 84 | let result = panic::catch_unwind(panic::AssertUnwindSafe(|| { 85 | if let Poll::Ready(ready) = with_tag(tag, || future.poll(&mut cx)) { 86 | *output.0.as_mut() = Some(ready); 87 | } 88 | })); 89 | 90 | if result.is_err() { 91 | *complete.0.as_mut() = true; 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tools/readmes.rn: -------------------------------------------------------------------------------- 1 | use process::Command; 2 | 3 | async fn update_readme(project, output) { 4 | let cargo = Command::new("cargo"); 5 | cargo.args(["readme", "-r", project, "-o", output]); 6 | Ok(cargo.spawn()?.wait_with_output().await?.status) 7 | } 8 | 9 | pub async fn main() { 10 | let cargo_toml = fs::read_to_string("Cargo.toml").await?; 11 | let cargo_toml = toml::from_string(cargo_toml)?; 12 | let projects = cargo_toml.workspace.members; 13 | 14 | for project in projects { 15 | let cargo_toml = fs::read_to_string(`${project}/Cargo.toml`).await?; 16 | let cargo_toml = toml::from_string(cargo_toml)?; 17 | 18 | if let Some(publish) = cargo_toml.package.get("publish") { 19 | if !publish { 20 | println(`${project}: skipping (publish = false)`); 21 | continue; 22 | } 23 | } 24 | 25 | let status = update_readme(project, "README.md").await?; 26 | println(`${project}: ${status}`); 27 | } 28 | 29 | let status = update_readme("audio", "../README.md").await?; 30 | println(`.: ${status}`); 31 | } 32 | --------------------------------------------------------------------------------