diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 7450e888..219160bf 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -790,8 +790,6 @@ dependencies = [
[[package]]
name = "cpal"
version = "0.15.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779"
dependencies = [
"alsa",
"core-foundation-sys",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index f68a27af..f0e68173 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -73,3 +73,13 @@ windows = { version = "0.58", features = [
# - Gracefully skips malformed trak atoms (e.g. MJPEG cover-art streams) instead
# of failing the entire probe
symphonia-format-isomp4 = { path = "patches/symphonia-format-isomp4" }
+
+# Local patch for cpal's CoreAudio backend on macOS:
+# - Forces IOType::DefaultOutput for all output streams so that macOS never
+# triggers the TCC microphone-permission prompt. Upstream cpal uses
+# IOType::HalOutput when the user pins a non-default device, which routes
+# through AUHAL — that component is flagged as input-capable and triggers
+# the mic consent dialog on first launch / after each update.
+# - Tradeoff: output-device selection is a no-op on macOS (stream follows
+# system default). Surfaced in the Settings UI.
+cpal = { path = "patches/cpal-0.15.3" }
diff --git a/src-tauri/Entitlements.plist b/src-tauri/Entitlements.plist
index 6f9fde60..90bbb72e 100644
--- a/src-tauri/Entitlements.plist
+++ b/src-tauri/Entitlements.plist
@@ -12,11 +12,5 @@
Respected by ad-hoc signatures on some macOS versions. -->
com.apple.security.network.client
-
-
- com.apple.security.device.audio-input
-
diff --git a/src-tauri/patches/cpal-0.15.3/.cargo_vcs_info.json b/src-tauri/patches/cpal-0.15.3/.cargo_vcs_info.json
new file mode 100644
index 00000000..18d730c1
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "ac6cbb2ba55e61665a35ab88ae136a83380d1354"
+ },
+ "path_in_vcs": ""
+}
\ No newline at end of file
diff --git a/src-tauri/patches/cpal-0.15.3/.github/workflows/cpal.yml b/src-tauri/patches/cpal-0.15.3/.github/workflows/cpal.yml
new file mode 100644
index 00000000..c2061eba
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/.github/workflows/cpal.yml
@@ -0,0 +1,265 @@
+name: cpal
+
+on: [push, pull_request]
+
+jobs:
+
+ clippy-test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Update apt
+ run: sudo apt update
+ - name: Install alsa
+ run: sudo apt-get install libasound2-dev
+ - name: Install libjack
+ run: sudo apt-get install libjack-jackd2-dev libjack-jackd2-0
+ - name: Install stable
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ components: clippy
+ target: armv7-linux-androideabi
+ - name: Run clippy
+ run: cargo clippy --all --all-features
+ - name: Run clippy for Android target
+ run: cargo clippy --all --features asio --features oboe/fetch-prebuilt --target armv7-linux-androideabi
+
+ rustfmt-check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install stable
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ components: rustfmt
+ - name: Run rustfmt
+ run: cargo fmt --all -- --check
+
+ cargo-publish:
+ if: github.event_name == 'push' && github.ref == 'refs/heads/master'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install rust
+ uses: dtolnay/rust-toolchain@stable
+ - name: Update apt
+ run: sudo apt update
+ - name: Install alsa
+ run: sudo apt-get install libasound2-dev
+ - name: Verify publish crate
+ uses: katyo/publish-crates@v2
+ with:
+ dry-run: true
+ ignore-unpublished-changes: true
+ - name: Publish crate
+ uses: katyo/publish-crates@v2
+ with:
+ ignore-unpublished-changes: true
+ registry-token: ${{ secrets.CRATESIO_TOKEN }}
+
+ ubuntu-test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Update apt
+ run: sudo apt update
+ - name: Install alsa
+ run: sudo apt-get install libasound2-dev
+ - name: Install libjack
+ run: sudo apt-get install libjack-jackd2-dev libjack-jackd2-0
+ - name: Install stable
+ uses: dtolnay/rust-toolchain@stable
+ - name: Run without features
+ run: cargo test --all --no-default-features --verbose
+ - name: Run all features
+ run: cargo test --all --all-features --verbose
+
+ linux-check-and-test-armv7:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v4
+
+ - name: Install stable toolchain
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ target: armv7-unknown-linux-gnueabihf
+
+ - name: Build image
+ run: docker build -t cross/cpal_armv7:v1 ./
+
+ - name: Install cross
+ run: cargo install cross
+
+ - name: Check without features for armv7
+ run: cross check --target armv7-unknown-linux-gnueabihf --workspace --no-default-features --verbose
+
+ - name: Test without features for armv7
+ run: cross test --target armv7-unknown-linux-gnueabihf --workspace --no-default-features --verbose
+
+ - name: Check all features for armv7
+ run: cross check --target armv7-unknown-linux-gnueabihf --workspace --all-features --verbose
+
+ - name: Test all features for armv7
+ run: cross test --target armv7-unknown-linux-gnueabihf --workspace --all-features --verbose
+
+ asmjs-wasm32-test:
+ strategy:
+ matrix:
+ target: [wasm32-unknown-emscripten]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup Emscripten toolchain
+ uses: mymindstorm/setup-emsdk@v14
+ - name: Install stable
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ target: ${{ matrix.target }}
+ - name: Build beep example
+ run: cargo build --example beep --release --target ${{ matrix.target }}
+
+ wasm32-bindgen-test:
+
+ strategy:
+ matrix:
+ target: [wasm32-unknown-unknown]
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install stable
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ target: ${{ matrix.target }}
+ - name: Build beep example
+ run: cargo build --example beep --target ${{ matrix.target }} --features=wasm-bindgen
+
+ wasm32-wasi-test:
+
+ strategy:
+ matrix:
+ target: [wasm32-wasi]
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install stable
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ target: ${{ matrix.target }}
+ - name: Build beep example
+ run: cargo build --example beep --target ${{ matrix.target }}
+
+ windows-test:
+ strategy:
+ matrix:
+ version: [x86_64, i686]
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install ASIO SDK
+ env:
+ LINK: https://www.steinberg.net/asiosdk
+ run: |
+ curl -L -o asio.zip $env:LINK
+ 7z x -oasio asio.zip
+ move asio\*\* asio\
+ - name: Install ASIO4ALL
+ run: choco install asio4all
+ - name: Install llvm and clang
+ run: choco install llvm
+ - name: Install stable
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ target: ${{ matrix.version }}-pc-windows-msvc
+ - name: Run without features
+ run: cargo test --all --no-default-features --verbose
+ - name: Run all features
+ run: |
+ $Env:CPAL_ASIO_DIR = "$Env:GITHUB_WORKSPACE\asio"
+ cargo test --all --all-features --verbose
+
+ macos-test:
+ runs-on: macOS-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install llvm and clang
+ run: brew install llvm
+ - name: Install stable
+ uses: dtolnay/rust-toolchain@stable
+ - name: Build beep example
+ run: cargo build --example beep
+ - name: Run without features
+ run: cargo test --all --no-default-features --verbose
+ - name: Run all features
+ run: cargo test --all --all-features --verbose
+
+ android-check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install stable (Android target)
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ target: armv7-linux-androideabi
+ - name: Check android
+ run: cargo check --example android --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
+ - name: Check beep
+ run: cargo check --example beep --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
+ - name: Check enumerate
+ run: cargo check --example enumerate --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
+ - name: Check feedback
+ run: cargo check --example feedback --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
+ - name: Check record_wav
+ run: cargo check --example record_wav --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
+
+ android-apk-build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install stable (Android targets)
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: armv7-linux-androideabi,aarch64-linux-android,i686-linux-android,x86_64-linux-android
+ - name: Set Up Android tools
+ run: |
+ ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_SDK_ROOT --install "platforms;android-30"
+ - name: Install Cargo APK
+ run: cargo install cargo-apk
+ - name: Build APK
+ run: cargo apk build --example android
+
+ ios-build:
+ runs-on: macOS-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install llvm and clang
+ run: brew install llvm
+ - name: Install stable (iOS targets)
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: aarch64-apple-ios,x86_64-apple-ios
+ - name: Install cargo lipo
+ run: cargo install cargo-lipo
+ - name: Build iphonesimulator feedback example
+ run: cd examples/ios-feedback && xcodebuild -scheme cpal-ios-example -configuration Debug -derivedDataPath build -sdk iphonesimulator
+
+ wasm-beep-build:
+ # this only confirms that the Rust source builds
+ # and checks to prevent regressions like #721.
+ #
+ # It does not test the javascript/web integration
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install stable (wasm32 target)
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: wasm32-unknown-unknown
+ - name: Cargo Build
+ working-directory: ./examples/wasm-beep
+ run: cargo build --target wasm32-unknown-unknown
+
diff --git a/src-tauri/patches/cpal-0.15.3/.gitignore b/src-tauri/patches/cpal-0.15.3/.gitignore
new file mode 100644
index 00000000..0289afe1
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/.gitignore
@@ -0,0 +1,6 @@
+/target
+/Cargo.lock
+.cargo/
+.DS_Store
+recorded.wav
+rls*.log
diff --git a/src-tauri/patches/cpal-0.15.3/CHANGELOG.md b/src-tauri/patches/cpal-0.15.3/CHANGELOG.md
new file mode 100644
index 00000000..d82ce046
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/CHANGELOG.md
@@ -0,0 +1,196 @@
+# Unreleased
+
+# Version 0.15.3 (2024-03-04)
+
+- Add `try_with_sample_rate`, a non-panicking variant of `with_sample_rate`.
+- struct `platform::Stream` is now #[must_use].
+- enum `SupportedBufferSize` and struct `SupportedStreamConfigRange` are now `Copy`.
+- `platform::Device` is now `Clone`.
+- Remove `parking_lot` dependency in favor of the std library.
+- Fix crash on web/wasm when `atomics` flag is enabled.
+- Improve Examples: Migrate wasm example to `trunk`, Improve syth-thones example.
+- Improve CI: Update actions, Use Android 30 API level in CI, Remove `asmjs-unknown-emscripten` target.
+- Update `windows` dependency to v0.54
+- Update `jni` dependency to 0.21
+- Update `alsa` dependency to 0.9
+- Update `oboe` dependency to 0.6
+- Update `ndk` dependency to 0.8 and disable `default-features`.
+- Update `wasm-bindgen` to 0.2.89
+
+# Version 0.15.2 (2023-03-30)
+
+- webaudio: support multichannel output streams
+- Update `windows` dependency
+- wasapi: fix some thread panics
+
+# Version 0.15.1 (2023-03-14)
+
+- Add feature `oboe-shared-stdcxx` to enable `shared-stdcxx` on `oboe` for Android support
+- Remove `thiserror` dependency
+- Swith `mach` dependency to `mach2`
+
+# Version 0.15.0 (2023-01-29)
+
+- Update `windows-rs`, `jack`, `coreaudio-sys`, `oboe`, `alsa` dependencies
+- Switch to the `dasp_sample` crate for the sample trait
+- Switch to `web-sys` on the emscripten target
+- Adopt edition 2021
+- Add disconnection detection on Mac OS
+
+# Version 0.14.1 (2022-10-23)
+
+- Support the 0.6.1 release of `alsa-rs`
+- Fix `asio` feature broken in 0.14.0
+- `NetBSD` support
+- CI improvements
+
+# Version 0.14.0 (2022-08-22)
+
+- Switch to `windows-rs` crate
+- Turn `ndk-glue` into a dev-dependency and use `ndk-context` instead
+- Update dependencies (ndk, ndk-glue, parking_lot, once_cell, jack)
+
+# Version 0.13.5 (2022-01-28)
+
+- Faster sample format conversion
+- Update dependencies (ndk, oboe, ndk-glue, jack, alsa, nix)
+
+# Version 0.13.4 (2021-08-08)
+
+- wasapi: Allow both threading models and switch the default to STA
+- Update dependencies (core-foundation-sys, jni, rust-jack)
+- Alsa: improve stream setup parameters
+
+# Version 0.13.3 (2021-03-29)
+
+- Give each thread a unique name
+- Fix distortion regression on some alsa configs
+
+# Version 0.13.2 (2021-03-16)
+
+- Update dependencies (ndk, nix, oboe, jni, etc)
+
+# Version 0.13.1 (2020-11-08)
+
+- Don't panic when device is plugged out on Windows
+- Update `parking_lot` dependency
+
+# Version 0.13.0 (2020-10-28)
+
+- Add Android support via `oboe-rs`.
+- Add Android APK build an CI job.
+
+# Version 0.12.1 (2020-07-23)
+
+- Bugfix release to get the asio feature working again.
+
+# Version 0.12.0 (2020-07-09)
+
+- Large refactor removing the blocking EventLoop API.
+- Rename many `Format` types to `StreamConfig`:
+ - `Format` type's `data_type` field renamed to `sample_format`.
+ - `Shape` -> `StreamConfig` - The configuration input required to build a stream.
+ - `Format` -> `SupportedStreamConfig` - Describes a single supported stream configuration.
+ - `SupportedFormat` -> `SupportedStreamConfigRange` - Describes a range of supported configurations.
+ - `Device::default_input/output_format` -> `Device::default_input/output_config`.
+ - `Device::supported_input/output_formats` -> `Device::supported_input/output_configs`.
+ - `Device::SupportedInput/OutputFormats` -> `Device::SupportedInput/OutputConfigs`.
+ - `SupportedFormatsError` -> `SupportedStreamConfigsError`
+ - `DefaultFormatError` -> `DefaultStreamConfigError`
+ - `BuildStreamError::FormatNotSupported` -> `BuildStreamError::StreamConfigNotSupported`
+- Address deprecated use of `mem::uninitialized` in WASAPI.
+- Removed `UnknownTypeBuffer` in favour of specifying sample type.
+- Added `build_input/output_stream_raw` methods allowing for dynamically
+ handling sample format type.
+- Added support for DragonFly platform.
+- Add `InputCallbackInfo` and `OutputCallbackInfo` types and update expected
+ user data callback function signature to provide these.
+
+# Version 0.11.0 (2019-12-11)
+
+- Fix some underruns that could occur in ALSA.
+- Add name to `HostId`.
+- Use `snd_pcm_hw_params_set_buffer_time_near` rather than `set_buffer_time_max`
+ in ALSA backend.
+- Remove many uses of `std::mem::uninitialized`.
+- Fix WASAPI capture logic.
+- Panic on stream ID overflow rather than returning an error.
+- Use `ringbuffer` crate in feedback example.
+- Move errors into a separate module.
+- Switch from `failure` to `thiserror` for error handling.
+- Add `winbase` winapi feature to solve windows compile error issues.
+- Lots of CI improvements.
+
+# Version 0.10.0 (2019-07-05)
+
+- core-foundation-sys and coreaudio-rs version bumps.
+- Add an ASIO host, available under Windows.
+- Introduce a new Host API, adding support for alternative audio APIs.
+- Remove sleep loop on macOS in favour of using a `Condvar`.
+- Allow users to handle stream callback errors with a new `StreamEvent` type.
+- Overhaul error handling throughout the crate.
+- Remove unnecessary Mutex from ALSA and WASAPI backends in favour of channels.
+- Remove `panic!` from OutputBuffer Deref impl as it is no longer necessary.
+
+# Version 0.9.0 (2019-06-06)
+
+- Better buffer handling
+- Fix logic error in frame/sample size
+- Added error handling for unknown ALSA device errors
+- Fix resuming a paused stream on Windows (wasapi).
+- Implement `default_output_format` for emscripten backend.
+
+# Version 0.8.1 (2018-03-18)
+
+- Fix the handling of non-default sample rates for coreaudio input streams.
+
+# Version 0.8.0 (2018-02-15)
+
+- Add `record_wav.rs` example. Records 3 seconds to
+ `$CARGO_MANIFEST_DIR/recorded.wav` using default input device.
+- Update `enumerate.rs` example to display default input/output devices and
+ formats.
+- Add input stream support to coreaudio, alsa and windows backends.
+- Introduce `StreamData` type for handling either input or output streams in
+ `EventLoop::run` callback.
+- Add `Device::supported_{input/output}_formats` methods.
+- Add `Device::default_{input/output}_format` methods.
+- Add `default_{input/output}_device` functions.
+- Replace usage of `Voice` with `Stream` throughout the crate.
+- Remove `Endpoint` in favour of `Device` for supporting both input and output
+ streams.
+
+# Version 0.7.0 (2018-02-04)
+
+- Rename `ChannelsCount` to `ChannelCount`.
+- Rename `SamplesRate` to `SampleRate`.
+- Rename the `min_samples_rate` field of `SupportedFormat` to `min_sample_rate`
+- Rename the `with_max_samples_rate()` method of`SupportedFormat` to `with_max_sample_rate()`
+- Rename the `samples_rate` field of `Format` to `sample_rate`
+- Changed the type of the `channels` field of the `SupportedFormat` struct from `Vec` to `ChannelCount` (an alias to `u16`)
+- Remove unused ChannelPosition API.
+- Implement `Endpoint` and `Format` Enumeration for macOS.
+- Implement format handling for macos `build_voice` method.
+
+# Version 0.6.0 (2017-12-11)
+
+- Changed the emscripten backend to consume less CPU.
+- Added improvements to the crate documentation.
+- Implement `pause` and `play` for ALSA backend.
+- Reduced the number of allocations in the CoreAudio backend.
+- Fixes for macOS build (#186, #189).
+
+# Version 0.5.1 (2017-10-21)
+
+- Added `Sample::to_i16()`, `Sample::to_u16()` and `Sample::from`.
+
+# Version 0.5.0 (2017-10-21)
+
+- Removed the dependency on the `futures` library.
+- Removed the `Voice` and `SamplesStream` types.
+- Added `EventLoop::build_voice`, `EventLoop::destroy_voice`, `EventLoop::play`,
+ and `EventLoop::pause` that can be used to create, destroy, play and pause voices.
+- Added a `VoiceId` struct that is now used to identify a voice owned by an `EventLoop`.
+- Changed `EventLoop::run()` to take a callback that is called whenever a voice requires sound data.
+- Changed `supported_formats()` to produce a list of `SupportedFormat` instead of `Format`. A
+ `SupportedFormat` must then be turned into a `Format` in order to build a voice.
diff --git a/src-tauri/patches/cpal-0.15.3/Cargo.toml b/src-tauri/patches/cpal-0.15.3/Cargo.toml
new file mode 100644
index 00000000..8df046c1
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/Cargo.toml
@@ -0,0 +1,184 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.70"
+name = "cpal"
+version = "0.15.3"
+description = "Low-level cross-platform audio I/O library in pure Rust."
+documentation = "https://docs.rs/cpal"
+readme = "README.md"
+keywords = [
+ "audio",
+ "sound",
+]
+license = "Apache-2.0"
+repository = "https://github.com/rustaudio/cpal"
+
+[[example]]
+name = "android"
+crate-type = ["cdylib"]
+path = "examples/android.rs"
+
+[[example]]
+name = "beep"
+
+[[example]]
+name = "enumerate"
+
+[[example]]
+name = "feedback"
+
+[[example]]
+name = "record_wav"
+
+[[example]]
+name = "synth_tones"
+
+[dependencies.dasp_sample]
+version = "0.11"
+
+[dev-dependencies.anyhow]
+version = "1.0"
+
+[dev-dependencies.clap]
+version = "4.0"
+features = ["derive"]
+
+[dev-dependencies.hound]
+version = "3.5"
+
+[dev-dependencies.ringbuf]
+version = "0.3"
+
+[features]
+asio = [
+ "asio-sys",
+ "num-traits",
+]
+oboe-shared-stdcxx = ["oboe/shared-stdcxx"]
+
+[target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.js-sys]
+version = "0.3.35"
+
+[target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.wasm-bindgen]
+version = "0.2.58"
+optional = true
+
+[target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.web-sys]
+version = "0.3.35"
+features = [
+ "AudioContext",
+ "AudioContextOptions",
+ "AudioBuffer",
+ "AudioBufferSourceNode",
+ "AudioNode",
+ "AudioDestinationNode",
+ "Window",
+ "AudioContextState",
+]
+
+[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"netbsd\"))".dependencies.alsa]
+version = "0.9"
+
+[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"netbsd\"))".dependencies.jack]
+version = "0.11"
+optional = true
+
+[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"netbsd\"))".dependencies.libc]
+version = "0.2"
+
+[target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.core-foundation-sys]
+version = "0.8.2"
+
+[target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.mach2]
+version = "0.4"
+
+[target."cfg(target_os = \"android\")".dependencies.jni]
+version = "0.21"
+
+[target."cfg(target_os = \"android\")".dependencies.ndk]
+version = "0.8"
+default-features = false
+
+[target."cfg(target_os = \"android\")".dependencies.ndk-context]
+version = "0.1"
+
+[target."cfg(target_os = \"android\")".dependencies.oboe]
+version = "0.6"
+features = ["java-interface"]
+
+[target."cfg(target_os = \"android\")".dev-dependencies.ndk-glue]
+version = "0.7"
+
+[target."cfg(target_os = \"emscripten\")".dependencies.js-sys]
+version = "0.3.35"
+
+[target."cfg(target_os = \"emscripten\")".dependencies.wasm-bindgen]
+version = "0.2.89"
+
+[target."cfg(target_os = \"emscripten\")".dependencies.wasm-bindgen-futures]
+version = "0.4.33"
+
+[target."cfg(target_os = \"emscripten\")".dependencies.web-sys]
+version = "0.3.35"
+features = [
+ "AudioContext",
+ "AudioContextOptions",
+ "AudioBuffer",
+ "AudioBufferSourceNode",
+ "AudioNode",
+ "AudioDestinationNode",
+ "Window",
+ "AudioContextState",
+]
+
+[target."cfg(target_os = \"ios\")".dependencies.coreaudio-rs]
+version = "0.11"
+features = [
+ "audio_unit",
+ "core_audio",
+ "audio_toolbox",
+]
+default-features = false
+
+[target."cfg(target_os = \"macos\")".dependencies.coreaudio-rs]
+version = "0.11"
+features = [
+ "audio_unit",
+ "core_audio",
+]
+default-features = false
+
+[target."cfg(target_os = \"windows\")".dependencies.asio-sys]
+version = "0.2"
+optional = true
+
+[target."cfg(target_os = \"windows\")".dependencies.num-traits]
+version = "0.2.6"
+optional = true
+
+[target."cfg(target_os = \"windows\")".dependencies.windows]
+version = "0.54.0"
+features = [
+ "Win32_Media_Audio",
+ "Win32_Foundation",
+ "Win32_Devices_Properties",
+ "Win32_Media_KernelStreaming",
+ "Win32_System_Com_StructuredStorage",
+ "Win32_System_Threading",
+ "Win32_Security",
+ "Win32_System_SystemServices",
+ "Win32_System_Variant",
+ "Win32_Media_Multimedia",
+ "Win32_UI_Shell_PropertiesSystem",
+]
diff --git a/src-tauri/patches/cpal-0.15.3/Cargo.toml.orig b/src-tauri/patches/cpal-0.15.3/Cargo.toml.orig
new file mode 100644
index 00000000..7b00ce90
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/Cargo.toml.orig
@@ -0,0 +1,95 @@
+[package]
+name = "cpal"
+version = "0.15.3"
+description = "Low-level cross-platform audio I/O library in pure Rust."
+repository = "https://github.com/rustaudio/cpal"
+documentation = "https://docs.rs/cpal"
+license = "Apache-2.0"
+keywords = ["audio", "sound"]
+edition = "2021"
+rust-version = "1.70"
+
+[features]
+asio = ["asio-sys", "num-traits"] # Only available on Windows. See README for setup instructions.
+oboe-shared-stdcxx = ["oboe/shared-stdcxx"] # Only available on Android. See README for what it does.
+
+[dependencies]
+dasp_sample = "0.11"
+
+[dev-dependencies]
+anyhow = "1.0"
+hound = "3.5"
+ringbuf = "0.3"
+clap = { version = "4.0", features = ["derive"] }
+
+[target.'cfg(target_os = "android")'.dev-dependencies]
+ndk-glue = "0.7"
+
+[target.'cfg(target_os = "windows")'.dependencies]
+windows = { version = "0.54.0", features = [
+ "Win32_Media_Audio",
+ "Win32_Foundation",
+ "Win32_Devices_Properties",
+ "Win32_Media_KernelStreaming",
+ "Win32_System_Com_StructuredStorage",
+ "Win32_System_Threading",
+ "Win32_Security",
+ "Win32_System_SystemServices",
+ "Win32_System_Variant",
+ "Win32_Media_Multimedia",
+ "Win32_UI_Shell_PropertiesSystem"
+]}
+asio-sys = { version = "0.2", path = "asio-sys", optional = true }
+num-traits = { version = "0.2.6", optional = true }
+
+[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd"))'.dependencies]
+alsa = "0.9"
+libc = "0.2"
+jack = { version = "0.11", optional = true }
+
+[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
+core-foundation-sys = "0.8.2" # For linking to CoreFoundation.framework and handling device name `CFString`s.
+mach2 = "0.4" # For access to mach_timebase type.
+
+[target.'cfg(target_os = "macos")'.dependencies]
+coreaudio-rs = { version = "0.11", default-features = false, features = ["audio_unit", "core_audio"] }
+
+[target.'cfg(target_os = "ios")'.dependencies]
+coreaudio-rs = { version = "0.11", default-features = false, features = ["audio_unit", "core_audio", "audio_toolbox"] }
+
+[target.'cfg(target_os = "emscripten")'.dependencies]
+wasm-bindgen = { version = "0.2.89" }
+wasm-bindgen-futures = "0.4.33"
+js-sys = { version = "0.3.35" }
+web-sys = { version = "0.3.35", features = [ "AudioContext", "AudioContextOptions", "AudioBuffer", "AudioBufferSourceNode", "AudioNode", "AudioDestinationNode", "Window", "AudioContextState"] }
+
+[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
+wasm-bindgen = { version = "0.2.58", optional = true }
+js-sys = { version = "0.3.35" }
+web-sys = { version = "0.3.35", features = [ "AudioContext", "AudioContextOptions", "AudioBuffer", "AudioBufferSourceNode", "AudioNode", "AudioDestinationNode", "Window", "AudioContextState"] }
+
+[target.'cfg(target_os = "android")'.dependencies]
+oboe = { version = "0.6", features = [ "java-interface" ] }
+ndk = { version = "0.8", default-features = false }
+ndk-context = "0.1"
+jni = "0.21"
+
+[[example]]
+name = "android"
+path = "examples/android.rs"
+crate-type = ["cdylib"]
+
+[[example]]
+name = "beep"
+
+[[example]]
+name = "enumerate"
+
+[[example]]
+name = "feedback"
+
+[[example]]
+name = "record_wav"
+
+[[example]]
+name = "synth_tones"
diff --git a/src-tauri/patches/cpal-0.15.3/Cross.toml b/src-tauri/patches/cpal-0.15.3/Cross.toml
new file mode 100644
index 00000000..39abb817
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/Cross.toml
@@ -0,0 +1,7 @@
+[target.armv7-unknown-linux-gnueabihf]
+image = "cross/cpal_armv7:v1"
+
+[target.armv7-unknown-linux-gnueabihf.env]
+passthrough = [
+ "RUSTFLAGS",
+]
\ No newline at end of file
diff --git a/src-tauri/patches/cpal-0.15.3/Dockerfile b/src-tauri/patches/cpal-0.15.3/Dockerfile
new file mode 100644
index 00000000..05045c34
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/Dockerfile
@@ -0,0 +1,9 @@
+FROM rustembedded/cross:armv7-unknown-linux-gnueabihf
+
+ENV PKG_CONFIG_ALLOW_CROSS 1
+ENV PKG_CONFIG_PATH /usr/lib/arm-linux-gnueabihf/pkgconfig/
+
+RUN dpkg --add-architecture armhf && \
+ apt-get update && \
+ apt-get install libasound2-dev:armhf -y && \
+ apt-get install libjack-jackd2-dev:armhf libjack-jackd2-0:armhf -y \
\ No newline at end of file
diff --git a/src-tauri/patches/cpal-0.15.3/LICENSE b/src-tauri/patches/cpal-0.15.3/LICENSE
new file mode 100644
index 00000000..261eeb9e
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/src-tauri/patches/cpal-0.15.3/README.md b/src-tauri/patches/cpal-0.15.3/README.md
new file mode 100644
index 00000000..b5ac6709
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/README.md
@@ -0,0 +1,142 @@
+# CPAL - Cross-Platform Audio Library
+
+[](https://github.com/RustAudio/cpal/actions)
+[](https://crates.io/crates/cpal) [](https://docs.rs/cpal/)
+
+Low-level library for audio input and output in pure Rust.
+
+This library currently supports the following:
+
+- Enumerate supported audio hosts.
+- Enumerate all available audio devices.
+- Get the current default input and output devices.
+- Enumerate known supported input and output stream formats for a device.
+- Get the current default input and output stream formats for a device.
+- Build and run input and output PCM streams on a chosen device with a given stream format.
+
+Currently, supported hosts include:
+
+- Linux (via ALSA or JACK)
+- Windows (via WASAPI by default, see ASIO instructions below)
+- macOS (via CoreAudio)
+- iOS (via CoreAudio)
+- Android (via Oboe)
+- Emscripten
+
+Note that on Linux, the ALSA development files are required. These are provided
+as part of the `libasound2-dev` package on Debian and Ubuntu distributions and
+`alsa-lib-devel` on Fedora.
+
+## Compiling for Web Assembly
+
+If you are interested in using CPAL with WASM, please see [this guide](https://github.com/RustAudio/cpal/wiki/Setting-up-a-new-CPAL-WASM-project) in our Wiki which walks through setting up a new project from scratch.
+
+## Feature flags for audio backends
+
+Some audio backends are optional and will only be compiled with a [feature flag](https://doc.rust-lang.org/cargo/reference/features.html).
+
+- JACK (on Linux): `jack`
+- ASIO (on Windows): `asio`
+
+Oboe can either use a shared or static runtime. The static runtime is used by default, but activating the
+`oboe-shared-stdcxx` feature makes it use the shared runtime, which requires `libc++_shared.so` from the Android NDK to
+be present during execution.
+
+## ASIO on Windows
+
+[ASIO](https://en.wikipedia.org/wiki/Audio_Stream_Input/Output) is an audio
+driver protocol by Steinberg. While it is available on multiple operating
+systems, it is most commonly used on Windows to work around limitations of
+WASAPI including access to large numbers of channels and lower-latency audio
+processing.
+
+CPAL allows for using the ASIO SDK as the audio host on Windows instead of
+WASAPI.
+
+### Locating the ASIO SDK
+
+The location of ASIO SDK is exposed to CPAL by setting the `CPAL_ASIO_DIR` environment variable.
+
+The build script will try to find the ASIO SDK by following these steps in order:
+
+1. Check if `CPAL_ASIO_DIR` is set and if so use the path to point to the SDK.
+2. Check if the ASIO SDK is already installed in the temporary directory, if so use that and set the path of `CPAL_ASIO_DIR` to the output of `std::env::temp_dir().join("asio_sdk")`.
+3. If the ASIO SDK is not already installed, download it from and install it in the temporary directory. The path of `CPAL_ASIO_DIR` will be set to the output of `std::env::temp_dir().join("asio_sdk")`.
+
+In an ideal situation you don't need to worry about this step.
+
+### Preparing the build environment
+
+1. `bindgen`, the library used to generate bindings to the C++ SDK, requires
+ clang. **Download and install LLVM** from
+ [here](http://releases.llvm.org/download.html) under the "Pre-Built Binaries"
+ section. The version as of writing this is 17.0.1.
+2. Add the LLVM `bin` directory to a `LIBCLANG_PATH` environment variable. If
+ you installed LLVM to the default directory, this should work in the command
+ prompt:
+ ```
+ setx LIBCLANG_PATH "C:\Program Files\LLVM\bin"
+ ```
+3. If you don't have any ASIO devices or drivers available, you can [**download
+ and install ASIO4ALL**](http://www.asio4all.org/). Be sure to enable the
+ "offline" feature during installation despite what the installer says about
+ it being useless.
+4. Our build script assumes that Microsoft Visual Studio is installed if the host OS for compilation is Windows. The script will try to find `vcvarsall.bat`
+ and execute it with the right host and target machine architecture regardless of the Microsoft Visual Studio version.
+ If there are any errors encountered in this process which is unlikely,
+ you may find the `vcvarsall.bat` manually and execute it with your machine architecture as an argument.
+ The script will detect this and skip the step.
+
+ A manually executed command example for 64 bit machines:
+
+ ```
+ "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
+ ```
+
+ For more information please refer to the documentation of [`vcvarsall.bat``](https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-160#vcvarsall-syntax).
+
+5. Select the ASIO host at the start of our program with the following code:
+
+ ```rust
+ let host;
+ #[cfg(target_os = "windows")]
+ {
+ host = cpal::host_from_id(cpal::HostId::Asio).expect("failed to initialise ASIO host");
+ }
+ ```
+
+ If you run into compilations errors produced by `asio-sys` or `bindgen`, make
+ sure that `CPAL_ASIO_DIR` is set correctly and try `cargo clean`.
+
+6. Make sure to enable the `asio` feature when building CPAL:
+
+ ```
+ cargo build --features "asio"
+ ```
+
+ or if you are using CPAL as a dependency in a downstream project, enable the
+ feature like this:
+
+ ```toml
+ cpal = { version = "*", features = ["asio"] }
+ ```
+
+_Updated as of ASIO version 2.3.3._
+
+### Cross compilation
+
+When Windows is the host and the target OS, the build script of `asio-sys` supports all cross compilation targets
+which are supported by the MSVC compiler. An exhaustive list of combinations could be found [here](https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-160#vcvarsall-syntax) with the addition of undocumented `arm64`, `arm64_x86`, `arm64_amd64` and `arm64_arm` targets. (5.11.2023)
+
+It is also possible to compile Windows applications with ASIO support on Linux and macOS.
+
+For both platforms the common way to do this is to use the [MinGW-w64](https://www.mingw-w64.org/) toolchain.
+
+Make sure that you have included the `MinGW-w64` include directory in your `CPLUS_INCLUDE_PATH` environment variable.
+Make sure that LLVM is installed and include directory is also included in your `CPLUS_INCLUDE_PATH` environment variable.
+
+Example for macOS for the target of `x86_64-pc-windows-gnu` where `mingw-w64` is installed via brew:
+
+```
+export CPLUS_INCLUDE_PATH="$CPLUS_INCLUDE_PATH:/opt/homebrew/Cellar/mingw-w64/11.0.1/toolchain-x86_64/x86_64-w64-mingw32/include"
+```
diff --git a/src-tauri/patches/cpal-0.15.3/build.rs b/src-tauri/patches/cpal-0.15.3/build.rs
new file mode 100644
index 00000000..ae97f68f
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/build.rs
@@ -0,0 +1,14 @@
+use std::env;
+
+const CPAL_ASIO_DIR: &str = "CPAL_ASIO_DIR";
+
+fn main() {
+ println!("cargo:rerun-if-env-changed={}", CPAL_ASIO_DIR);
+
+ // If ASIO directory isn't set silently return early
+ // otherwise set the asio config flag
+ match env::var(CPAL_ASIO_DIR) {
+ Err(_) => {}
+ Ok(_) => println!("cargo:rustc-cfg=asio"),
+ };
+}
diff --git a/src-tauri/patches/cpal-0.15.3/examples/android.rs b/src-tauri/patches/cpal-0.15.3/examples/android.rs
new file mode 100644
index 00000000..b58731db
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/examples/android.rs
@@ -0,0 +1,82 @@
+#![allow(dead_code)]
+
+extern crate anyhow;
+extern crate cpal;
+
+use cpal::{
+ traits::{DeviceTrait, HostTrait, StreamTrait},
+ SizedSample,
+};
+use cpal::{FromSample, Sample};
+
+#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "full"))]
+fn main() {
+ let host = cpal::default_host();
+
+ let device = host
+ .default_output_device()
+ .expect("failed to find output device");
+
+ let config = device.default_output_config().unwrap();
+
+ match config.sample_format() {
+ cpal::SampleFormat::I8 => run::(&device, &config.into()).unwrap(),
+ cpal::SampleFormat::I16 => run::(&device, &config.into()).unwrap(),
+ // cpal::SampleFormat::I24 => run::(&device, &config.into()).unwrap(),
+ cpal::SampleFormat::I32 => run::(&device, &config.into()).unwrap(),
+ // cpal::SampleFormat::I48 => run::(&device, &config.into()).unwrap(),
+ cpal::SampleFormat::I64 => run::(&device, &config.into()).unwrap(),
+ cpal::SampleFormat::U8 => run::(&device, &config.into()).unwrap(),
+ cpal::SampleFormat::U16 => run::(&device, &config.into()).unwrap(),
+ // cpal::SampleFormat::U24 => run::(&device, &config.into()).unwrap(),
+ cpal::SampleFormat::U32 => run::(&device, &config.into()).unwrap(),
+ // cpal::SampleFormat::U48 => run::(&device, &config.into()).unwrap(),
+ cpal::SampleFormat::U64 => run::(&device, &config.into()).unwrap(),
+ cpal::SampleFormat::F32 => run::(&device, &config.into()).unwrap(),
+ cpal::SampleFormat::F64 => run::(&device, &config.into()).unwrap(),
+ sample_format => panic!("Unsupported sample format '{sample_format}'"),
+ }
+}
+
+fn run(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error>
+where
+ T: SizedSample + FromSample,
+{
+ let sample_rate = config.sample_rate.0 as f32;
+ let channels = config.channels as usize;
+
+ // Produce a sinusoid of maximum amplitude.
+ let mut sample_clock = 0f32;
+ let mut next_value = move || {
+ sample_clock = (sample_clock + 1.0) % sample_rate;
+ (sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin()
+ };
+
+ let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
+
+ let stream = device.build_output_stream(
+ config,
+ move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
+ write_data(data, channels, &mut next_value)
+ },
+ err_fn,
+ None,
+ )?;
+ stream.play()?;
+
+ std::thread::sleep(std::time::Duration::from_millis(1000));
+
+ Ok(())
+}
+
+fn write_data(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32)
+where
+ T: Sample + FromSample,
+{
+ for frame in output.chunks_mut(channels) {
+ let value: T = T::from_sample(next_sample());
+ for sample in frame.iter_mut() {
+ *sample = value;
+ }
+ }
+}
diff --git a/src-tauri/patches/cpal-0.15.3/examples/beep.rs b/src-tauri/patches/cpal-0.15.3/examples/beep.rs
new file mode 100644
index 00000000..7d3b23d8
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/examples/beep.rs
@@ -0,0 +1,138 @@
+use clap::Parser;
+use cpal::{
+ traits::{DeviceTrait, HostTrait, StreamTrait},
+ FromSample, Sample, SizedSample,
+};
+
+#[derive(Parser, Debug)]
+#[command(version, about = "CPAL beep example", long_about = None)]
+struct Opt {
+ /// The audio device to use
+ #[arg(short, long, default_value_t = String::from("default"))]
+ device: String,
+
+ /// Use the JACK host
+ #[cfg(all(
+ any(
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd"
+ ),
+ feature = "jack"
+ ))]
+ #[arg(short, long)]
+ #[allow(dead_code)]
+ jack: bool,
+}
+
+fn main() -> anyhow::Result<()> {
+ let opt = Opt::parse();
+
+ // Conditionally compile with jack if the feature is specified.
+ #[cfg(all(
+ any(
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd"
+ ),
+ feature = "jack"
+ ))]
+ // Manually check for flags. Can be passed through cargo with -- e.g.
+ // cargo run --release --example beep --features jack -- --jack
+ let host = if opt.jack {
+ cpal::host_from_id(cpal::available_hosts()
+ .into_iter()
+ .find(|id| *id == cpal::HostId::Jack)
+ .expect(
+ "make sure --features jack is specified. only works on OSes where jack is available",
+ )).expect("jack host unavailable")
+ } else {
+ cpal::default_host()
+ };
+
+ #[cfg(any(
+ not(any(
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd"
+ )),
+ not(feature = "jack")
+ ))]
+ let host = cpal::default_host();
+
+ let device = if opt.device == "default" {
+ host.default_output_device()
+ } else {
+ host.output_devices()?
+ .find(|x| x.name().map(|y| y == opt.device).unwrap_or(false))
+ }
+ .expect("failed to find output device");
+ println!("Output device: {}", device.name()?);
+
+ let config = device.default_output_config().unwrap();
+ println!("Default output config: {:?}", config);
+
+ match config.sample_format() {
+ cpal::SampleFormat::I8 => run::(&device, &config.into()),
+ cpal::SampleFormat::I16 => run::(&device, &config.into()),
+ // cpal::SampleFormat::I24 => run::(&device, &config.into()),
+ cpal::SampleFormat::I32 => run::(&device, &config.into()),
+ // cpal::SampleFormat::I48 => run::(&device, &config.into()),
+ cpal::SampleFormat::I64 => run::(&device, &config.into()),
+ cpal::SampleFormat::U8 => run::(&device, &config.into()),
+ cpal::SampleFormat::U16 => run::(&device, &config.into()),
+ // cpal::SampleFormat::U24 => run::(&device, &config.into()),
+ cpal::SampleFormat::U32 => run::(&device, &config.into()),
+ // cpal::SampleFormat::U48 => run::(&device, &config.into()),
+ cpal::SampleFormat::U64 => run::(&device, &config.into()),
+ cpal::SampleFormat::F32 => run::(&device, &config.into()),
+ cpal::SampleFormat::F64 => run::(&device, &config.into()),
+ sample_format => panic!("Unsupported sample format '{sample_format}'"),
+ }
+}
+
+pub fn run(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error>
+where
+ T: SizedSample + FromSample,
+{
+ let sample_rate = config.sample_rate.0 as f32;
+ let channels = config.channels as usize;
+
+ // Produce a sinusoid of maximum amplitude.
+ let mut sample_clock = 0f32;
+ let mut next_value = move || {
+ sample_clock = (sample_clock + 1.0) % sample_rate;
+ (sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin()
+ };
+
+ let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
+
+ let stream = device.build_output_stream(
+ config,
+ move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
+ write_data(data, channels, &mut next_value)
+ },
+ err_fn,
+ None,
+ )?;
+ stream.play()?;
+
+ std::thread::sleep(std::time::Duration::from_millis(1000));
+
+ Ok(())
+}
+
+fn write_data(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32)
+where
+ T: Sample + FromSample,
+{
+ for frame in output.chunks_mut(channels) {
+ let value: T = T::from_sample(next_sample());
+ for sample in frame.iter_mut() {
+ *sample = value;
+ }
+ }
+}
diff --git a/src-tauri/patches/cpal-0.15.3/examples/enumerate.rs b/src-tauri/patches/cpal-0.15.3/examples/enumerate.rs
new file mode 100644
index 00000000..ccba0eb6
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/examples/enumerate.rs
@@ -0,0 +1,74 @@
+extern crate anyhow;
+extern crate cpal;
+
+use cpal::traits::{DeviceTrait, HostTrait};
+
+fn main() -> Result<(), anyhow::Error> {
+ println!("Supported hosts:\n {:?}", cpal::ALL_HOSTS);
+ let available_hosts = cpal::available_hosts();
+ println!("Available hosts:\n {:?}", available_hosts);
+
+ for host_id in available_hosts {
+ println!("{}", host_id.name());
+ let host = cpal::host_from_id(host_id)?;
+
+ let default_in = host.default_input_device().map(|e| e.name().unwrap());
+ let default_out = host.default_output_device().map(|e| e.name().unwrap());
+ println!(" Default Input Device:\n {:?}", default_in);
+ println!(" Default Output Device:\n {:?}", default_out);
+
+ let devices = host.devices()?;
+ println!(" Devices: ");
+ for (device_index, device) in devices.enumerate() {
+ println!(" {}. \"{}\"", device_index + 1, device.name()?);
+
+ // Input configs
+ if let Ok(conf) = device.default_input_config() {
+ println!(" Default input stream config:\n {:?}", conf);
+ }
+ let input_configs = match device.supported_input_configs() {
+ Ok(f) => f.collect(),
+ Err(e) => {
+ println!(" Error getting supported input configs: {:?}", e);
+ Vec::new()
+ }
+ };
+ if !input_configs.is_empty() {
+ println!(" All supported input stream configs:");
+ for (config_index, config) in input_configs.into_iter().enumerate() {
+ println!(
+ " {}.{}. {:?}",
+ device_index + 1,
+ config_index + 1,
+ config
+ );
+ }
+ }
+
+ // Output configs
+ if let Ok(conf) = device.default_output_config() {
+ println!(" Default output stream config:\n {:?}", conf);
+ }
+ let output_configs = match device.supported_output_configs() {
+ Ok(f) => f.collect(),
+ Err(e) => {
+ println!(" Error getting supported output configs: {:?}", e);
+ Vec::new()
+ }
+ };
+ if !output_configs.is_empty() {
+ println!(" All supported output stream configs:");
+ for (config_index, config) in output_configs.into_iter().enumerate() {
+ println!(
+ " {}.{}. {:?}",
+ device_index + 1,
+ config_index + 1,
+ config
+ );
+ }
+ }
+ }
+ }
+
+ Ok(())
+}
diff --git a/src-tauri/patches/cpal-0.15.3/examples/feedback.rs b/src-tauri/patches/cpal-0.15.3/examples/feedback.rs
new file mode 100644
index 00000000..eae9fa2e
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/examples/feedback.rs
@@ -0,0 +1,174 @@
+//! Feeds back the input stream directly into the output stream.
+//!
+//! Assumes that the input and output devices can use the same stream configuration and that they
+//! support the f32 sample format.
+//!
+//! Uses a delay of `LATENCY_MS` milliseconds in case the default input and output streams are not
+//! precisely synchronised.
+
+use clap::Parser;
+use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
+use ringbuf::HeapRb;
+
+#[derive(Parser, Debug)]
+#[command(version, about = "CPAL feedback example", long_about = None)]
+struct Opt {
+ /// The input audio device to use
+ #[arg(short, long, value_name = "IN", default_value_t = String::from("default"))]
+ input_device: String,
+
+ /// The output audio device to use
+ #[arg(short, long, value_name = "OUT", default_value_t = String::from("default"))]
+ output_device: String,
+
+ /// Specify the delay between input and output
+ #[arg(short, long, value_name = "DELAY_MS", default_value_t = 150.0)]
+ latency: f32,
+
+ /// Use the JACK host
+ #[cfg(all(
+ any(
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd"
+ ),
+ feature = "jack"
+ ))]
+ #[arg(short, long)]
+ #[allow(dead_code)]
+ jack: bool,
+}
+
+fn main() -> anyhow::Result<()> {
+ let opt = Opt::parse();
+
+ // Conditionally compile with jack if the feature is specified.
+ #[cfg(all(
+ any(
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd"
+ ),
+ feature = "jack"
+ ))]
+ // Manually check for flags. Can be passed through cargo with -- e.g.
+ // cargo run --release --example beep --features jack -- --jack
+ let host = if opt.jack {
+ cpal::host_from_id(cpal::available_hosts()
+ .into_iter()
+ .find(|id| *id == cpal::HostId::Jack)
+ .expect(
+ "make sure --features jack is specified. only works on OSes where jack is available",
+ )).expect("jack host unavailable")
+ } else {
+ cpal::default_host()
+ };
+
+ #[cfg(any(
+ not(any(
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd"
+ )),
+ not(feature = "jack")
+ ))]
+ let host = cpal::default_host();
+
+ // Find devices.
+ let input_device = if opt.input_device == "default" {
+ host.default_input_device()
+ } else {
+ host.input_devices()?
+ .find(|x| x.name().map(|y| y == opt.input_device).unwrap_or(false))
+ }
+ .expect("failed to find input device");
+
+ let output_device = if opt.output_device == "default" {
+ host.default_output_device()
+ } else {
+ host.output_devices()?
+ .find(|x| x.name().map(|y| y == opt.output_device).unwrap_or(false))
+ }
+ .expect("failed to find output device");
+
+ println!("Using input device: \"{}\"", input_device.name()?);
+ println!("Using output device: \"{}\"", output_device.name()?);
+
+ // We'll try and use the same configuration between streams to keep it simple.
+ let config: cpal::StreamConfig = input_device.default_input_config()?.into();
+
+ // Create a delay in case the input and output devices aren't synced.
+ let latency_frames = (opt.latency / 1_000.0) * config.sample_rate.0 as f32;
+ let latency_samples = latency_frames as usize * config.channels as usize;
+
+ // The buffer to share samples
+ let ring = HeapRb::::new(latency_samples * 2);
+ let (mut producer, mut consumer) = ring.split();
+
+ // Fill the samples with 0.0 equal to the length of the delay.
+ for _ in 0..latency_samples {
+ // The ring buffer has twice as much space as necessary to add latency here,
+ // so this should never fail
+ producer.push(0.0).unwrap();
+ }
+
+ let input_data_fn = move |data: &[f32], _: &cpal::InputCallbackInfo| {
+ let mut output_fell_behind = false;
+ for &sample in data {
+ if producer.push(sample).is_err() {
+ output_fell_behind = true;
+ }
+ }
+ if output_fell_behind {
+ eprintln!("output stream fell behind: try increasing latency");
+ }
+ };
+
+ let output_data_fn = move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
+ let mut input_fell_behind = false;
+ for sample in data {
+ *sample = match consumer.pop() {
+ Some(s) => s,
+ None => {
+ input_fell_behind = true;
+ 0.0
+ }
+ };
+ }
+ if input_fell_behind {
+ eprintln!("input stream fell behind: try increasing latency");
+ }
+ };
+
+ // Build streams.
+ println!(
+ "Attempting to build both streams with f32 samples and `{:?}`.",
+ config
+ );
+ let input_stream = input_device.build_input_stream(&config, input_data_fn, err_fn, None)?;
+ let output_stream = output_device.build_output_stream(&config, output_data_fn, err_fn, None)?;
+ println!("Successfully built streams.");
+
+ // Play the streams.
+ println!(
+ "Starting the input and output streams with `{}` milliseconds of latency.",
+ opt.latency
+ );
+ input_stream.play()?;
+ output_stream.play()?;
+
+ // Run for 3 seconds before closing.
+ println!("Playing for 3 seconds... ");
+ std::thread::sleep(std::time::Duration::from_secs(3));
+ drop(input_stream);
+ drop(output_stream);
+ println!("Done!");
+ Ok(())
+}
+
+fn err_fn(err: cpal::StreamError) {
+ eprintln!("an error occurred on stream: {}", err);
+}
diff --git a/src-tauri/patches/cpal-0.15.3/examples/record_wav.rs b/src-tauri/patches/cpal-0.15.3/examples/record_wav.rs
new file mode 100644
index 00000000..4841bfa0
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/examples/record_wav.rs
@@ -0,0 +1,177 @@
+//! Records a WAV file (roughly 3 seconds long) using the default input device and config.
+//!
+//! The input data is recorded to "$CARGO_MANIFEST_DIR/recorded.wav".
+
+use clap::Parser;
+use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
+use cpal::{FromSample, Sample};
+use std::fs::File;
+use std::io::BufWriter;
+use std::sync::{Arc, Mutex};
+
+#[derive(Parser, Debug)]
+#[command(version, about = "CPAL record_wav example", long_about = None)]
+struct Opt {
+ /// The audio device to use
+ #[arg(short, long, default_value_t = String::from("default"))]
+ device: String,
+
+ /// Use the JACK host
+ #[cfg(all(
+ any(
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd"
+ ),
+ feature = "jack"
+ ))]
+ #[arg(short, long)]
+ #[allow(dead_code)]
+ jack: bool,
+}
+
+fn main() -> Result<(), anyhow::Error> {
+ let opt = Opt::parse();
+
+ // Conditionally compile with jack if the feature is specified.
+ #[cfg(all(
+ any(
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd"
+ ),
+ feature = "jack"
+ ))]
+ // Manually check for flags. Can be passed through cargo with -- e.g.
+ // cargo run --release --example beep --features jack -- --jack
+ let host = if opt.jack {
+ cpal::host_from_id(cpal::available_hosts()
+ .into_iter()
+ .find(|id| *id == cpal::HostId::Jack)
+ .expect(
+ "make sure --features jack is specified. only works on OSes where jack is available",
+ )).expect("jack host unavailable")
+ } else {
+ cpal::default_host()
+ };
+
+ #[cfg(any(
+ not(any(
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd"
+ )),
+ not(feature = "jack")
+ ))]
+ let host = cpal::default_host();
+
+ // Set up the input device and stream with the default input config.
+ let device = if opt.device == "default" {
+ host.default_input_device()
+ } else {
+ host.input_devices()?
+ .find(|x| x.name().map(|y| y == opt.device).unwrap_or(false))
+ }
+ .expect("failed to find input device");
+
+ println!("Input device: {}", device.name()?);
+
+ let config = device
+ .default_input_config()
+ .expect("Failed to get default input config");
+ println!("Default input config: {:?}", config);
+
+ // The WAV file we're recording to.
+ const PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav");
+ let spec = wav_spec_from_config(&config);
+ let writer = hound::WavWriter::create(PATH, spec)?;
+ let writer = Arc::new(Mutex::new(Some(writer)));
+
+ // A flag to indicate that recording is in progress.
+ println!("Begin recording...");
+
+ // Run the input stream on a separate thread.
+ let writer_2 = writer.clone();
+
+ let err_fn = move |err| {
+ eprintln!("an error occurred on stream: {}", err);
+ };
+
+ let stream = match config.sample_format() {
+ cpal::SampleFormat::I8 => device.build_input_stream(
+ &config.into(),
+ move |data, _: &_| write_input_data::(data, &writer_2),
+ err_fn,
+ None,
+ )?,
+ cpal::SampleFormat::I16 => device.build_input_stream(
+ &config.into(),
+ move |data, _: &_| write_input_data::(data, &writer_2),
+ err_fn,
+ None,
+ )?,
+ cpal::SampleFormat::I32 => device.build_input_stream(
+ &config.into(),
+ move |data, _: &_| write_input_data::(data, &writer_2),
+ err_fn,
+ None,
+ )?,
+ cpal::SampleFormat::F32 => device.build_input_stream(
+ &config.into(),
+ move |data, _: &_| write_input_data::(data, &writer_2),
+ err_fn,
+ None,
+ )?,
+ sample_format => {
+ return Err(anyhow::Error::msg(format!(
+ "Unsupported sample format '{sample_format}'"
+ )))
+ }
+ };
+
+ stream.play()?;
+
+ // Let recording go for roughly three seconds.
+ std::thread::sleep(std::time::Duration::from_secs(3));
+ drop(stream);
+ writer.lock().unwrap().take().unwrap().finalize()?;
+ println!("Recording {} complete!", PATH);
+ Ok(())
+}
+
+fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat {
+ if format.is_float() {
+ hound::SampleFormat::Float
+ } else {
+ hound::SampleFormat::Int
+ }
+}
+
+fn wav_spec_from_config(config: &cpal::SupportedStreamConfig) -> hound::WavSpec {
+ hound::WavSpec {
+ channels: config.channels() as _,
+ sample_rate: config.sample_rate().0 as _,
+ bits_per_sample: (config.sample_format().sample_size() * 8) as _,
+ sample_format: sample_format(config.sample_format()),
+ }
+}
+
+type WavWriterHandle = Arc>>>>;
+
+fn write_input_data(input: &[T], writer: &WavWriterHandle)
+where
+ T: Sample,
+ U: Sample + hound::Sample + FromSample,
+{
+ if let Ok(mut guard) = writer.try_lock() {
+ if let Some(writer) = guard.as_mut() {
+ for &sample in input.iter() {
+ let sample: U = U::from_sample(sample);
+ writer.write_sample(sample).ok();
+ }
+ }
+ }
+}
diff --git a/src-tauri/patches/cpal-0.15.3/examples/synth_tones.rs b/src-tauri/patches/cpal-0.15.3/examples/synth_tones.rs
new file mode 100644
index 00000000..c83f816d
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/examples/synth_tones.rs
@@ -0,0 +1,191 @@
+/* This example expose parameter to pass generator of sample.
+Good starting point for integration of cpal into your application.
+*/
+
+extern crate anyhow;
+extern crate clap;
+extern crate cpal;
+
+use cpal::{
+ traits::{DeviceTrait, HostTrait, StreamTrait},
+ SizedSample,
+};
+use cpal::{FromSample, Sample};
+
+fn main() -> anyhow::Result<()> {
+ let stream = stream_setup_for()?;
+ stream.play()?;
+ std::thread::sleep(std::time::Duration::from_millis(4000));
+ Ok(())
+}
+
+pub enum Waveform {
+ Sine,
+ Square,
+ Saw,
+ Triangle,
+}
+
+pub struct Oscillator {
+ pub sample_rate: f32,
+ pub waveform: Waveform,
+ pub current_sample_index: f32,
+ pub frequency_hz: f32,
+}
+
+impl Oscillator {
+ fn advance_sample(&mut self) {
+ self.current_sample_index = (self.current_sample_index + 1.0) % self.sample_rate;
+ }
+
+ fn set_waveform(&mut self, waveform: Waveform) {
+ self.waveform = waveform;
+ }
+
+ fn calculate_sine_output_from_freq(&self, freq: f32) -> f32 {
+ let two_pi = 2.0 * std::f32::consts::PI;
+ (self.current_sample_index * freq * two_pi / self.sample_rate).sin()
+ }
+
+ fn is_multiple_of_freq_above_nyquist(&self, multiple: f32) -> bool {
+ self.frequency_hz * multiple > self.sample_rate / 2.0
+ }
+
+ fn sine_wave(&mut self) -> f32 {
+ self.advance_sample();
+ self.calculate_sine_output_from_freq(self.frequency_hz)
+ }
+
+ fn generative_waveform(&mut self, harmonic_index_increment: i32, gain_exponent: f32) -> f32 {
+ self.advance_sample();
+ let mut output = 0.0;
+ let mut i = 1;
+ while !self.is_multiple_of_freq_above_nyquist(i as f32) {
+ let gain = 1.0 / (i as f32).powf(gain_exponent);
+ output += gain * self.calculate_sine_output_from_freq(self.frequency_hz * i as f32);
+ i += harmonic_index_increment;
+ }
+ output
+ }
+
+ fn square_wave(&mut self) -> f32 {
+ self.generative_waveform(2, 1.0)
+ }
+
+ fn saw_wave(&mut self) -> f32 {
+ self.generative_waveform(1, 1.0)
+ }
+
+ fn triangle_wave(&mut self) -> f32 {
+ self.generative_waveform(2, 2.0)
+ }
+
+ fn tick(&mut self) -> f32 {
+ match self.waveform {
+ Waveform::Sine => self.sine_wave(),
+ Waveform::Square => self.square_wave(),
+ Waveform::Saw => self.saw_wave(),
+ Waveform::Triangle => self.triangle_wave(),
+ }
+ }
+}
+
+pub fn stream_setup_for() -> Result
+where
+{
+ let (_host, device, config) = host_device_setup()?;
+
+ match config.sample_format() {
+ cpal::SampleFormat::I8 => make_stream::(&device, &config.into()),
+ cpal::SampleFormat::I16 => make_stream::(&device, &config.into()),
+ cpal::SampleFormat::I32 => make_stream::(&device, &config.into()),
+ cpal::SampleFormat::I64 => make_stream::(&device, &config.into()),
+ cpal::SampleFormat::U8 => make_stream::(&device, &config.into()),
+ cpal::SampleFormat::U16 => make_stream::(&device, &config.into()),
+ cpal::SampleFormat::U32 => make_stream::(&device, &config.into()),
+ cpal::SampleFormat::U64 => make_stream::(&device, &config.into()),
+ cpal::SampleFormat::F32 => make_stream::(&device, &config.into()),
+ cpal::SampleFormat::F64 => make_stream::(&device, &config.into()),
+ sample_format => Err(anyhow::Error::msg(format!(
+ "Unsupported sample format '{sample_format}'"
+ ))),
+ }
+}
+
+pub fn host_device_setup(
+) -> Result<(cpal::Host, cpal::Device, cpal::SupportedStreamConfig), anyhow::Error> {
+ let host = cpal::default_host();
+
+ let device = host
+ .default_output_device()
+ .ok_or_else(|| anyhow::Error::msg("Default output device is not available"))?;
+ println!("Output device : {}", device.name()?);
+
+ let config = device.default_output_config()?;
+ println!("Default output config : {:?}", config);
+
+ Ok((host, device, config))
+}
+
+pub fn make_stream(
+ device: &cpal::Device,
+ config: &cpal::StreamConfig,
+) -> Result
+where
+ T: SizedSample + FromSample,
+{
+ let num_channels = config.channels as usize;
+ let mut oscillator = Oscillator {
+ waveform: Waveform::Sine,
+ sample_rate: config.sample_rate.0 as f32,
+ current_sample_index: 0.0,
+ frequency_hz: 440.0,
+ };
+ let err_fn = |err| eprintln!("Error building output sound stream: {}", err);
+
+ let time_at_start = std::time::Instant::now();
+ println!("Time at start: {:?}", time_at_start);
+
+ let stream = device.build_output_stream(
+ config,
+ move |output: &mut [T], _: &cpal::OutputCallbackInfo| {
+ // for 0-1s play sine, 1-2s play square, 2-3s play saw, 3-4s play triangle_wave
+ let time_since_start = std::time::Instant::now()
+ .duration_since(time_at_start)
+ .as_secs_f32();
+ if time_since_start < 1.0 {
+ oscillator.set_waveform(Waveform::Sine);
+ } else if time_since_start < 2.0 {
+ oscillator.set_waveform(Waveform::Triangle);
+ } else if time_since_start < 3.0 {
+ oscillator.set_waveform(Waveform::Square);
+ } else if time_since_start < 4.0 {
+ oscillator.set_waveform(Waveform::Saw);
+ } else {
+ oscillator.set_waveform(Waveform::Sine);
+ }
+ process_frame(output, &mut oscillator, num_channels)
+ },
+ err_fn,
+ None,
+ )?;
+
+ Ok(stream)
+}
+
+fn process_frame(
+ output: &mut [SampleType],
+ oscillator: &mut Oscillator,
+ num_channels: usize,
+) where
+ SampleType: Sample + FromSample,
+{
+ for frame in output.chunks_mut(num_channels) {
+ let value: SampleType = SampleType::from_sample(oscillator.tick());
+
+ // copy the same value to all channels
+ for sample in frame.iter_mut() {
+ *sample = value;
+ }
+ }
+}
diff --git a/src-tauri/patches/cpal-0.15.3/src/error.rs b/src-tauri/patches/cpal-0.15.3/src/error.rs
new file mode 100644
index 00000000..e92ff4a5
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/src/error.rs
@@ -0,0 +1,292 @@
+use std::error::Error;
+use std::fmt::{Display, Formatter};
+
+/// The requested host, although supported on this platform, is unavailable.
+#[derive(Copy, Clone, Debug)]
+pub struct HostUnavailable;
+
+impl Display for HostUnavailable {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.write_str("the requested host is unavailable")
+ }
+}
+
+impl Error for HostUnavailable {}
+
+/// Some error has occurred that is specific to the backend from which it was produced.
+///
+/// This error is often used as a catch-all in cases where:
+///
+/// - It is unclear exactly what error might be produced by the backend API.
+/// - It does not make sense to add a variant to the enclosing error type.
+/// - No error was expected to occur at all, but we return an error to avoid the possibility of a
+/// `panic!` caused by some unforeseen or unknown reason.
+///
+/// **Note:** If you notice a `BackendSpecificError` that you believe could be better handled in a
+/// cross-platform manner, please create an issue or submit a pull request with a patch that adds
+/// the necessary error variant to the appropriate error enum.
+#[derive(Clone, Debug)]
+pub struct BackendSpecificError {
+ pub description: String,
+}
+
+impl Display for BackendSpecificError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "A backend-specific error has occurred: {}",
+ self.description
+ )
+ }
+}
+
+impl Error for BackendSpecificError {}
+
+/// An error that might occur while attempting to enumerate the available devices on a system.
+#[derive(Clone, Debug)]
+pub enum DevicesError {
+ /// See the [`BackendSpecificError`] docs for more information about this error variant.
+ BackendSpecific { err: BackendSpecificError },
+}
+
+impl Display for DevicesError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::BackendSpecific { err } => err.fmt(f),
+ }
+ }
+}
+
+impl Error for DevicesError {}
+
+impl From for DevicesError {
+ fn from(err: BackendSpecificError) -> Self {
+ Self::BackendSpecific { err }
+ }
+}
+
+/// An error that may occur while attempting to retrieve a device name.
+#[derive(Clone, Debug)]
+pub enum DeviceNameError {
+ /// See the [`BackendSpecificError`] docs for more information about this error variant.
+ BackendSpecific { err: BackendSpecificError },
+}
+
+impl Display for DeviceNameError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::BackendSpecific { err } => err.fmt(f),
+ }
+ }
+}
+
+impl Error for DeviceNameError {}
+
+impl From for DeviceNameError {
+ fn from(err: BackendSpecificError) -> Self {
+ Self::BackendSpecific { err }
+ }
+}
+
+/// Error that can happen when enumerating the list of supported formats.
+#[derive(Debug)]
+pub enum SupportedStreamConfigsError {
+ /// The device no longer exists. This can happen if the device is disconnected while the
+ /// program is running.
+ DeviceNotAvailable,
+ /// We called something the C-Layer did not understand
+ InvalidArgument,
+ /// See the [`BackendSpecificError`] docs for more information about this error variant.
+ BackendSpecific { err: BackendSpecificError },
+}
+
+impl Display for SupportedStreamConfigsError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::BackendSpecific { err } => err.fmt(f),
+ Self::DeviceNotAvailable => f.write_str("The requested device is no longer available. For example, it has been unplugged."),
+ Self::InvalidArgument => f.write_str("Invalid argument passed to the backend. For example, this happens when trying to read capture capabilities when the device does not support it.")
+ }
+ }
+}
+
+impl Error for SupportedStreamConfigsError {}
+
+impl From for SupportedStreamConfigsError {
+ fn from(err: BackendSpecificError) -> Self {
+ Self::BackendSpecific { err }
+ }
+}
+
+/// May occur when attempting to request the default input or output stream format from a [`Device`](crate::Device).
+#[derive(Debug)]
+pub enum DefaultStreamConfigError {
+ /// The device no longer exists. This can happen if the device is disconnected while the
+ /// program is running.
+ DeviceNotAvailable,
+ /// Returned if e.g. the default input format was requested on an output-only audio device.
+ StreamTypeNotSupported,
+ /// See the [`BackendSpecificError`] docs for more information about this error variant.
+ BackendSpecific { err: BackendSpecificError },
+}
+
+impl Display for DefaultStreamConfigError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::BackendSpecific { err } => err.fmt(f),
+ DefaultStreamConfigError::DeviceNotAvailable => f.write_str(
+ "The requested device is no longer available. For example, it has been unplugged.",
+ ),
+ DefaultStreamConfigError::StreamTypeNotSupported => {
+ f.write_str("The requested stream type is not supported by the device.")
+ }
+ }
+ }
+}
+
+impl Error for DefaultStreamConfigError {}
+
+impl From for DefaultStreamConfigError {
+ fn from(err: BackendSpecificError) -> Self {
+ Self::BackendSpecific { err }
+ }
+}
+/// Error that can happen when creating a [`Stream`](crate::Stream).
+#[derive(Debug)]
+pub enum BuildStreamError {
+ /// The device no longer exists. This can happen if the device is disconnected while the
+ /// program is running.
+ DeviceNotAvailable,
+ /// The specified stream configuration is not supported.
+ StreamConfigNotSupported,
+ /// We called something the C-Layer did not understand
+ ///
+ /// On ALSA device functions called with a feature they do not support will yield this. E.g.
+ /// Trying to use capture capabilities on an output only format yields this.
+ InvalidArgument,
+ /// Occurs if adding a new Stream ID would cause an integer overflow.
+ StreamIdOverflow,
+ /// See the [`BackendSpecificError`] docs for more information about this error variant.
+ BackendSpecific { err: BackendSpecificError },
+}
+
+impl Display for BuildStreamError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::BackendSpecific { err } => err.fmt(f),
+ BuildStreamError::DeviceNotAvailable => f.write_str(
+ "The requested device is no longer available. For example, it has been unplugged.",
+ ),
+ BuildStreamError::StreamConfigNotSupported => {
+ f.write_str("The requested stream configuration is not supported by the device.")
+ }
+ BuildStreamError::InvalidArgument => f.write_str(
+ "The requested device does not support this capability (invalid argument)",
+ ),
+ BuildStreamError::StreamIdOverflow => {
+ f.write_str("Adding a new stream ID would cause an overflow")
+ }
+ }
+ }
+}
+
+impl Error for BuildStreamError {}
+
+impl From for BuildStreamError {
+ fn from(err: BackendSpecificError) -> Self {
+ Self::BackendSpecific { err }
+ }
+}
+
+/// Errors that might occur when calling [`Stream::play()`](crate::traits::StreamTrait::play).
+///
+/// As of writing this, only macOS may immediately return an error while calling this method. This
+/// is because both the alsa and wasapi backends only enqueue these commands and do not process
+/// them immediately.
+#[derive(Debug)]
+pub enum PlayStreamError {
+ /// The device associated with the stream is no longer available.
+ DeviceNotAvailable,
+ /// See the [`BackendSpecificError`] docs for more information about this error variant.
+ BackendSpecific { err: BackendSpecificError },
+}
+
+impl Display for PlayStreamError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::BackendSpecific { err } => err.fmt(f),
+ PlayStreamError::DeviceNotAvailable => {
+ f.write_str("the device associated with the stream is no longer available")
+ }
+ }
+ }
+}
+
+impl Error for PlayStreamError {}
+
+impl From for PlayStreamError {
+ fn from(err: BackendSpecificError) -> Self {
+ Self::BackendSpecific { err }
+ }
+}
+
+/// Errors that might occur when calling [`Stream::pause()`](crate::traits::StreamTrait::pause).
+///
+/// As of writing this, only macOS may immediately return an error while calling this method. This
+/// is because both the alsa and wasapi backends only enqueue these commands and do not process
+/// them immediately.
+#[derive(Debug)]
+pub enum PauseStreamError {
+ /// The device associated with the stream is no longer available.
+ DeviceNotAvailable,
+ /// See the [`BackendSpecificError`] docs for more information about this error variant.
+ BackendSpecific { err: BackendSpecificError },
+}
+
+impl Display for PauseStreamError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::BackendSpecific { err } => err.fmt(f),
+ PauseStreamError::DeviceNotAvailable => {
+ f.write_str("the device associated with the stream is no longer available")
+ }
+ }
+ }
+}
+
+impl Error for PauseStreamError {}
+
+impl From for PauseStreamError {
+ fn from(err: BackendSpecificError) -> Self {
+ Self::BackendSpecific { err }
+ }
+}
+
+/// Errors that might occur while a stream is running.
+#[derive(Debug)]
+pub enum StreamError {
+ /// The device no longer exists. This can happen if the device is disconnected while the
+ /// program is running.
+ DeviceNotAvailable,
+ /// See the [`BackendSpecificError`] docs for more information about this error variant.
+ BackendSpecific { err: BackendSpecificError },
+}
+
+impl Display for StreamError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::BackendSpecific { err } => err.fmt(f),
+ StreamError::DeviceNotAvailable => f.write_str(
+ "The requested device is no longer available. For example, it has been unplugged.",
+ ),
+ }
+ }
+}
+
+impl Error for StreamError {}
+
+impl From for StreamError {
+ fn from(err: BackendSpecificError) -> Self {
+ Self::BackendSpecific { err }
+ }
+}
diff --git a/src-tauri/patches/cpal-0.15.3/src/host/alsa/enumerate.rs b/src-tauri/patches/cpal-0.15.3/src/host/alsa/enumerate.rs
new file mode 100644
index 00000000..3032861c
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/src/host/alsa/enumerate.rs
@@ -0,0 +1,70 @@
+use super::alsa;
+use super::{Device, DeviceHandles};
+use crate::{BackendSpecificError, DevicesError};
+use std::sync::{Arc, Mutex};
+
+/// ALSA's implementation for `Devices`.
+pub struct Devices {
+ hint_iter: alsa::device_name::HintIter,
+}
+
+impl Devices {
+ pub fn new() -> Result {
+ Ok(Devices {
+ hint_iter: alsa::device_name::HintIter::new_str(None, "pcm")?,
+ })
+ }
+}
+
+unsafe impl Send for Devices {}
+unsafe impl Sync for Devices {}
+
+impl Iterator for Devices {
+ type Item = Device;
+
+ fn next(&mut self) -> Option {
+ loop {
+ match self.hint_iter.next() {
+ None => return None,
+ Some(hint) => {
+ let name = match hint.name {
+ None => continue,
+ // Ignoring the `null` device.
+ Some(name) if name == "null" => continue,
+ Some(name) => name,
+ };
+
+ if let Ok(handles) = DeviceHandles::open(&name) {
+ return Some(Device {
+ name,
+ handles: Arc::new(Mutex::new(handles)),
+ });
+ }
+ }
+ }
+ }
+ }
+}
+
+#[inline]
+pub fn default_input_device() -> Option {
+ Some(Device {
+ name: "default".to_owned(),
+ handles: Arc::new(Mutex::new(Default::default())),
+ })
+}
+
+#[inline]
+pub fn default_output_device() -> Option {
+ Some(Device {
+ name: "default".to_owned(),
+ handles: Arc::new(Mutex::new(Default::default())),
+ })
+}
+
+impl From for DevicesError {
+ fn from(err: alsa::Error) -> Self {
+ let err: BackendSpecificError = err.into();
+ err.into()
+ }
+}
diff --git a/src-tauri/patches/cpal-0.15.3/src/host/alsa/mod.rs b/src-tauri/patches/cpal-0.15.3/src/host/alsa/mod.rs
new file mode 100644
index 00000000..009ddef9
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/src/host/alsa/mod.rs
@@ -0,0 +1,1163 @@
+extern crate alsa;
+extern crate libc;
+
+use self::alsa::poll::Descriptors;
+use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
+use crate::{
+ BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data,
+ DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo,
+ PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError,
+ SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
+ SupportedStreamConfigsError,
+};
+use std::cmp;
+use std::convert::TryInto;
+use std::sync::{Arc, Mutex};
+use std::thread::{self, JoinHandle};
+use std::time::Duration;
+use std::vec::IntoIter as VecIntoIter;
+
+pub use self::enumerate::{default_input_device, default_output_device, Devices};
+
+pub type SupportedInputConfigs = VecIntoIter;
+pub type SupportedOutputConfigs = VecIntoIter;
+
+mod enumerate;
+
+/// The default linux, dragonfly, freebsd and netbsd host type.
+#[derive(Debug)]
+pub struct Host;
+
+impl Host {
+ pub fn new() -> Result {
+ Ok(Host)
+ }
+}
+
+impl HostTrait for Host {
+ type Devices = Devices;
+ type Device = Device;
+
+ fn is_available() -> bool {
+ // Assume ALSA is always available on linux/dragonfly/freebsd/netbsd.
+ true
+ }
+
+ fn devices(&self) -> Result {
+ Devices::new()
+ }
+
+ fn default_input_device(&self) -> Option {
+ default_input_device()
+ }
+
+ fn default_output_device(&self) -> Option {
+ default_output_device()
+ }
+}
+
+impl DeviceTrait for Device {
+ type SupportedInputConfigs = SupportedInputConfigs;
+ type SupportedOutputConfigs = SupportedOutputConfigs;
+ type Stream = Stream;
+
+ fn name(&self) -> Result {
+ Device::name(self)
+ }
+
+ fn supported_input_configs(
+ &self,
+ ) -> Result {
+ Device::supported_input_configs(self)
+ }
+
+ fn supported_output_configs(
+ &self,
+ ) -> Result {
+ Device::supported_output_configs(self)
+ }
+
+ fn default_input_config(&self) -> Result {
+ Device::default_input_config(self)
+ }
+
+ fn default_output_config(&self) -> Result {
+ Device::default_output_config(self)
+ }
+
+ fn build_input_stream_raw(
+ &self,
+ conf: &StreamConfig,
+ sample_format: SampleFormat,
+ data_callback: D,
+ error_callback: E,
+ timeout: Option,
+ ) -> Result
+ where
+ D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
+ E: FnMut(StreamError) + Send + 'static,
+ {
+ let stream_inner =
+ self.build_stream_inner(conf, sample_format, alsa::Direction::Capture)?;
+ let stream = Stream::new_input(
+ Arc::new(stream_inner),
+ data_callback,
+ error_callback,
+ timeout,
+ );
+ Ok(stream)
+ }
+
+ fn build_output_stream_raw(
+ &self,
+ conf: &StreamConfig,
+ sample_format: SampleFormat,
+ data_callback: D,
+ error_callback: E,
+ timeout: Option,
+ ) -> Result
+ where
+ D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
+ E: FnMut(StreamError) + Send + 'static,
+ {
+ let stream_inner =
+ self.build_stream_inner(conf, sample_format, alsa::Direction::Playback)?;
+ let stream = Stream::new_output(
+ Arc::new(stream_inner),
+ data_callback,
+ error_callback,
+ timeout,
+ );
+ Ok(stream)
+ }
+}
+
+struct TriggerSender(libc::c_int);
+
+struct TriggerReceiver(libc::c_int);
+
+impl TriggerSender {
+ fn wakeup(&self) {
+ let buf = 1u64;
+ let ret = unsafe { libc::write(self.0, &buf as *const u64 as *const _, 8) };
+ assert_eq!(ret, 8);
+ }
+}
+
+impl TriggerReceiver {
+ fn clear_pipe(&self) {
+ let mut out = 0u64;
+ let ret = unsafe { libc::read(self.0, &mut out as *mut u64 as *mut _, 8) };
+ assert_eq!(ret, 8);
+ }
+}
+
+fn trigger() -> (TriggerSender, TriggerReceiver) {
+ let mut fds = [0, 0];
+ match unsafe { libc::pipe(fds.as_mut_ptr()) } {
+ 0 => (TriggerSender(fds[1]), TriggerReceiver(fds[0])),
+ _ => panic!("Could not create pipe"),
+ }
+}
+
+impl Drop for TriggerSender {
+ fn drop(&mut self) {
+ unsafe {
+ libc::close(self.0);
+ }
+ }
+}
+
+impl Drop for TriggerReceiver {
+ fn drop(&mut self) {
+ unsafe {
+ libc::close(self.0);
+ }
+ }
+}
+
+#[derive(Default)]
+struct DeviceHandles {
+ playback: Option,
+ capture: Option,
+}
+
+impl DeviceHandles {
+ /// Create `DeviceHandles` for `name` and try to open a handle for both
+ /// directions. Returns `Ok` if either direction is opened successfully.
+ fn open(name: &str) -> Result {
+ let mut handles = Self::default();
+ let playback_err = handles.try_open(name, alsa::Direction::Playback).err();
+ let capture_err = handles.try_open(name, alsa::Direction::Capture).err();
+ if let Some(err) = capture_err.and(playback_err) {
+ Err(err)
+ } else {
+ Ok(handles)
+ }
+ }
+
+ /// Get a mutable reference to the `Option` for a specific `stream_type`.
+ /// If the `Option` is `None`, the `alsa::PCM` will be opened and placed in
+ /// the `Option` before returning. If `handle_mut()` returns `Ok` the contained
+ /// `Option` is guaranteed to be `Some(..)`.
+ fn try_open(
+ &mut self,
+ name: &str,
+ stream_type: alsa::Direction,
+ ) -> Result<&mut Option, alsa::Error> {
+ let handle = match stream_type {
+ alsa::Direction::Playback => &mut self.playback,
+ alsa::Direction::Capture => &mut self.capture,
+ };
+
+ if handle.is_none() {
+ *handle = Some(alsa::pcm::PCM::new(name, stream_type, true)?);
+ }
+
+ Ok(handle)
+ }
+
+ /// Get a mutable reference to the `alsa::PCM` handle for a specific `stream_type`.
+ /// If the handle is not yet opened, it will be opened and stored in `self`.
+ fn get_mut(
+ &mut self,
+ name: &str,
+ stream_type: alsa::Direction,
+ ) -> Result<&mut alsa::PCM, alsa::Error> {
+ Ok(self.try_open(name, stream_type)?.as_mut().unwrap())
+ }
+
+ /// Take ownership of the `alsa::PCM` handle for a specific `stream_type`.
+ /// If the handle is not yet opened, it will be opened and returned.
+ fn take(&mut self, name: &str, stream_type: alsa::Direction) -> Result {
+ Ok(self.try_open(name, stream_type)?.take().unwrap())
+ }
+}
+
+#[derive(Clone)]
+pub struct Device {
+ name: String,
+ handles: Arc>,
+}
+
+impl Device {
+ fn build_stream_inner(
+ &self,
+ conf: &StreamConfig,
+ sample_format: SampleFormat,
+ stream_type: alsa::Direction,
+ ) -> Result {
+ let handle_result = self
+ .handles
+ .lock()
+ .unwrap()
+ .take(&self.name, stream_type)
+ .map_err(|e| (e, e.errno()));
+
+ let handle = match handle_result {
+ Err((_, libc::EBUSY)) => return Err(BuildStreamError::DeviceNotAvailable),
+ Err((_, libc::EINVAL)) => return Err(BuildStreamError::InvalidArgument),
+ Err((e, _)) => return Err(e.into()),
+ Ok(handle) => handle,
+ };
+ let can_pause = set_hw_params_from_format(&handle, conf, sample_format)?;
+ let period_len = set_sw_params_from_format(&handle, conf, stream_type)?;
+
+ handle.prepare()?;
+
+ let num_descriptors = handle.count();
+ if num_descriptors == 0 {
+ let description = "poll descriptor count for stream was 0".to_string();
+ let err = BackendSpecificError { description };
+ return Err(err.into());
+ }
+
+ // Check to see if we can retrieve valid timestamps from the device.
+ // Related: https://bugs.freedesktop.org/show_bug.cgi?id=88503
+ let ts = handle.status()?.get_htstamp();
+ let creation_instant = match (ts.tv_sec, ts.tv_nsec) {
+ (0, 0) => Some(std::time::Instant::now()),
+ _ => None,
+ };
+
+ if let alsa::Direction::Capture = stream_type {
+ handle.start()?;
+ }
+
+ let stream_inner = StreamInner {
+ channel: handle,
+ sample_format,
+ num_descriptors,
+ conf: conf.clone(),
+ period_len,
+ can_pause,
+ creation_instant,
+ };
+
+ Ok(stream_inner)
+ }
+
+ #[inline]
+ fn name(&self) -> Result {
+ Ok(self.name.clone())
+ }
+
+ fn supported_configs(
+ &self,
+ stream_t: alsa::Direction,
+ ) -> Result, SupportedStreamConfigsError> {
+ let mut guard = self.handles.lock().unwrap();
+ let handle_result = guard
+ .get_mut(&self.name, stream_t)
+ .map_err(|e| (e, e.errno()));
+
+ let handle = match handle_result {
+ Err((_, libc::ENOENT)) | Err((_, libc::EBUSY)) => {
+ return Err(SupportedStreamConfigsError::DeviceNotAvailable)
+ }
+ Err((_, libc::EINVAL)) => return Err(SupportedStreamConfigsError::InvalidArgument),
+ Err((e, _)) => return Err(e.into()),
+ Ok(handle) => handle,
+ };
+
+ let hw_params = alsa::pcm::HwParams::any(handle)?;
+
+ // TODO: check endianness
+ const FORMATS: [(SampleFormat, alsa::pcm::Format); 8] = [
+ (SampleFormat::I8, alsa::pcm::Format::S8),
+ (SampleFormat::U8, alsa::pcm::Format::U8),
+ (SampleFormat::I16, alsa::pcm::Format::S16LE),
+ //SND_PCM_FORMAT_S16_BE,
+ (SampleFormat::U16, alsa::pcm::Format::U16LE),
+ //SND_PCM_FORMAT_U16_BE,
+ //SND_PCM_FORMAT_S24_LE,
+ //SND_PCM_FORMAT_S24_BE,
+ //SND_PCM_FORMAT_U24_LE,
+ //SND_PCM_FORMAT_U24_BE,
+ (SampleFormat::I32, alsa::pcm::Format::S32LE),
+ //SND_PCM_FORMAT_S32_BE,
+ (SampleFormat::U32, alsa::pcm::Format::U32LE),
+ //SND_PCM_FORMAT_U32_BE,
+ (SampleFormat::F32, alsa::pcm::Format::FloatLE),
+ //SND_PCM_FORMAT_FLOAT_BE,
+ (SampleFormat::F64, alsa::pcm::Format::Float64LE),
+ //SND_PCM_FORMAT_FLOAT64_BE,
+ //SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
+ //SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
+ //SND_PCM_FORMAT_MU_LAW,
+ //SND_PCM_FORMAT_A_LAW,
+ //SND_PCM_FORMAT_IMA_ADPCM,
+ //SND_PCM_FORMAT_MPEG,
+ //SND_PCM_FORMAT_GSM,
+ //SND_PCM_FORMAT_SPECIAL,
+ //SND_PCM_FORMAT_S24_3LE,
+ //SND_PCM_FORMAT_S24_3BE,
+ //SND_PCM_FORMAT_U24_3LE,
+ //SND_PCM_FORMAT_U24_3BE,
+ //SND_PCM_FORMAT_S20_3LE,
+ //SND_PCM_FORMAT_S20_3BE,
+ //SND_PCM_FORMAT_U20_3LE,
+ //SND_PCM_FORMAT_U20_3BE,
+ //SND_PCM_FORMAT_S18_3LE,
+ //SND_PCM_FORMAT_S18_3BE,
+ //SND_PCM_FORMAT_U18_3LE,
+ //SND_PCM_FORMAT_U18_3BE,
+ ];
+
+ let mut supported_formats = Vec::new();
+ for &(sample_format, alsa_format) in FORMATS.iter() {
+ if hw_params.test_format(alsa_format).is_ok() {
+ supported_formats.push(sample_format);
+ }
+ }
+
+ let min_rate = hw_params.get_rate_min()?;
+ let max_rate = hw_params.get_rate_max()?;
+
+ let sample_rates = if min_rate == max_rate || hw_params.test_rate(min_rate + 1).is_ok() {
+ vec![(min_rate, max_rate)]
+ } else {
+ const RATES: [libc::c_uint; 13] = [
+ 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, 176400,
+ 192000,
+ ];
+
+ let mut rates = Vec::new();
+ for &rate in RATES.iter() {
+ if hw_params.test_rate(rate).is_ok() {
+ rates.push((rate, rate));
+ }
+ }
+
+ if rates.is_empty() {
+ vec![(min_rate, max_rate)]
+ } else {
+ rates
+ }
+ };
+
+ let min_channels = hw_params.get_channels_min()?;
+ let max_channels = hw_params.get_channels_max()?;
+
+ let max_channels = cmp::min(max_channels, 32); // TODO: limiting to 32 channels or too much stuff is returned
+ let supported_channels = (min_channels..max_channels + 1)
+ .filter_map(|num| {
+ if hw_params.test_channels(num).is_ok() {
+ Some(num as ChannelCount)
+ } else {
+ None
+ }
+ })
+ .collect::>();
+
+ let min_buffer_size = hw_params.get_buffer_size_min()?;
+ let max_buffer_size = hw_params.get_buffer_size_max()?;
+
+ let buffer_size_range = SupportedBufferSize::Range {
+ min: min_buffer_size as u32,
+ max: max_buffer_size as u32,
+ };
+
+ let mut output = Vec::with_capacity(
+ supported_formats.len() * supported_channels.len() * sample_rates.len(),
+ );
+ for &sample_format in supported_formats.iter() {
+ for &channels in supported_channels.iter() {
+ for &(min_rate, max_rate) in sample_rates.iter() {
+ output.push(SupportedStreamConfigRange {
+ channels,
+ min_sample_rate: SampleRate(min_rate as u32),
+ max_sample_rate: SampleRate(max_rate as u32),
+ buffer_size: buffer_size_range.clone(),
+ sample_format,
+ });
+ }
+ }
+ }
+
+ Ok(output.into_iter())
+ }
+
+ fn supported_input_configs(
+ &self,
+ ) -> Result {
+ self.supported_configs(alsa::Direction::Capture)
+ }
+
+ fn supported_output_configs(
+ &self,
+ ) -> Result {
+ self.supported_configs(alsa::Direction::Playback)
+ }
+
+ // ALSA does not offer default stream formats, so instead we compare all supported formats by
+ // the `SupportedStreamConfigRange::cmp_default_heuristics` order and select the greatest.
+ fn default_config(
+ &self,
+ stream_t: alsa::Direction,
+ ) -> Result {
+ let mut formats: Vec<_> = {
+ match self.supported_configs(stream_t) {
+ Err(SupportedStreamConfigsError::DeviceNotAvailable) => {
+ return Err(DefaultStreamConfigError::DeviceNotAvailable);
+ }
+ Err(SupportedStreamConfigsError::InvalidArgument) => {
+ // this happens sometimes when querying for input and output capabilities, but
+ // the device supports only one
+ return Err(DefaultStreamConfigError::StreamTypeNotSupported);
+ }
+ Err(SupportedStreamConfigsError::BackendSpecific { err }) => {
+ return Err(err.into());
+ }
+ Ok(fmts) => fmts.collect(),
+ }
+ };
+
+ formats.sort_by(|a, b| a.cmp_default_heuristics(b));
+
+ match formats.into_iter().last() {
+ Some(f) => {
+ let min_r = f.min_sample_rate;
+ let max_r = f.max_sample_rate;
+ let mut format = f.with_max_sample_rate();
+ const HZ_44100: SampleRate = SampleRate(44_100);
+ if min_r <= HZ_44100 && HZ_44100 <= max_r {
+ format.sample_rate = HZ_44100;
+ }
+ Ok(format)
+ }
+ None => Err(DefaultStreamConfigError::StreamTypeNotSupported),
+ }
+ }
+
+ fn default_input_config(&self) -> Result {
+ self.default_config(alsa::Direction::Capture)
+ }
+
+ fn default_output_config(&self) -> Result {
+ self.default_config(alsa::Direction::Playback)
+ }
+}
+
+struct StreamInner {
+ // The ALSA channel.
+ channel: alsa::pcm::PCM,
+
+ // When converting between file descriptors and `snd_pcm_t`, this is the number of
+ // file descriptors that this `snd_pcm_t` uses.
+ num_descriptors: usize,
+
+ // Format of the samples.
+ sample_format: SampleFormat,
+
+ // The configuration used to open this stream.
+ conf: StreamConfig,
+
+ // Minimum number of samples to put in the buffer.
+ period_len: usize,
+
+ #[allow(dead_code)]
+ // Whether or not the hardware supports pausing the stream.
+ // TODO: We need an API to expose this. See #197, #284.
+ can_pause: bool,
+
+ // In the case that the device does not return valid timestamps via `get_htstamp`, this field
+ // will be `Some` and will contain an `Instant` representing the moment the stream was created.
+ //
+ // If this field is `Some`, then the stream will use the duration since this instant as a
+ // source for timestamps.
+ //
+ // If this field is `None` then the elapsed duration between `get_trigger_htstamp` and
+ // `get_htstamp` is used.
+ creation_instant: Option,
+}
+
+// Assume that the ALSA library is built with thread safe option.
+unsafe impl Sync for StreamInner {}
+
+#[derive(Debug, Eq, PartialEq)]
+enum StreamType {
+ Input,
+ Output,
+}
+
+pub struct Stream {
+ /// The high-priority audio processing thread calling callbacks.
+ /// Option used for moving out in destructor.
+ thread: Option>,
+
+ /// Handle to the underlying stream for playback controls.
+ inner: Arc,
+
+ /// Used to signal to stop processing.
+ trigger: TriggerSender,
+}
+
+struct StreamWorkerContext {
+ descriptors: Vec,
+ buffer: Vec,
+ poll_timeout: i32,
+}
+
+impl StreamWorkerContext {
+ fn new(poll_timeout: &Option) -> Self {
+ let poll_timeout: i32 = if let Some(d) = poll_timeout {
+ d.as_millis().try_into().unwrap()
+ } else {
+ -1
+ };
+
+ Self {
+ descriptors: Vec::new(),
+ buffer: Vec::new(),
+ poll_timeout,
+ }
+ }
+}
+
+fn input_stream_worker(
+ rx: TriggerReceiver,
+ stream: &StreamInner,
+ data_callback: &mut (dyn FnMut(&Data, &InputCallbackInfo) + Send + 'static),
+ error_callback: &mut (dyn FnMut(StreamError) + Send + 'static),
+ timeout: Option,
+) {
+ let mut ctxt = StreamWorkerContext::new(&timeout);
+ loop {
+ let flow =
+ poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt).unwrap_or_else(|err| {
+ error_callback(err.into());
+ PollDescriptorsFlow::Continue
+ });
+
+ match flow {
+ PollDescriptorsFlow::Continue => {
+ continue;
+ }
+ PollDescriptorsFlow::XRun => {
+ if let Err(err) = stream.channel.prepare() {
+ error_callback(err.into());
+ }
+ continue;
+ }
+ PollDescriptorsFlow::Return => return,
+ PollDescriptorsFlow::Ready {
+ status,
+ avail_frames: _,
+ delay_frames,
+ stream_type,
+ } => {
+ assert_eq!(
+ stream_type,
+ StreamType::Input,
+ "expected input stream, but polling descriptors indicated output",
+ );
+ if let Err(err) = process_input(
+ stream,
+ &mut ctxt.buffer,
+ status,
+ delay_frames,
+ data_callback,
+ ) {
+ error_callback(err.into());
+ }
+ }
+ }
+ }
+}
+
+fn output_stream_worker(
+ rx: TriggerReceiver,
+ stream: &StreamInner,
+ data_callback: &mut (dyn FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static),
+ error_callback: &mut (dyn FnMut(StreamError) + Send + 'static),
+ timeout: Option,
+) {
+ let mut ctxt = StreamWorkerContext::new(&timeout);
+ loop {
+ let flow =
+ poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt).unwrap_or_else(|err| {
+ error_callback(err.into());
+ PollDescriptorsFlow::Continue
+ });
+
+ match flow {
+ PollDescriptorsFlow::Continue => continue,
+ PollDescriptorsFlow::XRun => {
+ if let Err(err) = stream.channel.prepare() {
+ error_callback(err.into());
+ }
+ continue;
+ }
+ PollDescriptorsFlow::Return => return,
+ PollDescriptorsFlow::Ready {
+ status,
+ avail_frames,
+ delay_frames,
+ stream_type,
+ } => {
+ assert_eq!(
+ stream_type,
+ StreamType::Output,
+ "expected output stream, but polling descriptors indicated input",
+ );
+ if let Err(err) = process_output(
+ stream,
+ &mut ctxt.buffer,
+ status,
+ avail_frames,
+ delay_frames,
+ data_callback,
+ error_callback,
+ ) {
+ error_callback(err.into());
+ }
+ }
+ }
+ }
+}
+
+enum PollDescriptorsFlow {
+ Continue,
+ Return,
+ Ready {
+ stream_type: StreamType,
+ status: alsa::pcm::Status,
+ avail_frames: usize,
+ delay_frames: usize,
+ },
+ XRun,
+}
+
+// This block is shared between both input and output stream worker functions.
+fn poll_descriptors_and_prepare_buffer(
+ rx: &TriggerReceiver,
+ stream: &StreamInner,
+ ctxt: &mut StreamWorkerContext,
+) -> Result {
+ let StreamWorkerContext {
+ ref mut descriptors,
+ ref mut buffer,
+ ref poll_timeout,
+ } = *ctxt;
+
+ descriptors.clear();
+
+ // Add the self-pipe for signaling termination.
+ descriptors.push(libc::pollfd {
+ fd: rx.0,
+ events: libc::POLLIN,
+ revents: 0,
+ });
+
+ // Add ALSA polling fds.
+ let len = descriptors.len();
+ descriptors.resize(
+ stream.num_descriptors + len,
+ libc::pollfd {
+ fd: 0,
+ events: 0,
+ revents: 0,
+ },
+ );
+ let filled = stream.channel.fill(&mut descriptors[len..])?;
+ debug_assert_eq!(filled, stream.num_descriptors);
+
+ // Don't timeout, wait forever.
+ let res = alsa::poll::poll(descriptors, *poll_timeout)?;
+ if res == 0 {
+ let description = String::from("`alsa::poll()` spuriously returned");
+ return Err(BackendSpecificError { description });
+ }
+
+ if descriptors[0].revents != 0 {
+ // The stream has been requested to be destroyed.
+ rx.clear_pipe();
+ return Ok(PollDescriptorsFlow::Return);
+ }
+
+ let revents = stream.channel.revents(&descriptors[1..])?;
+ if revents.contains(alsa::poll::Flags::ERR) {
+ let description = String::from("`alsa::poll()` returned POLLERR");
+ return Err(BackendSpecificError { description });
+ }
+ let stream_type = match revents {
+ alsa::poll::Flags::OUT => StreamType::Output,
+ alsa::poll::Flags::IN => StreamType::Input,
+ _ => {
+ // Nothing to process, poll again
+ return Ok(PollDescriptorsFlow::Continue);
+ }
+ };
+
+ let status = stream.channel.status()?;
+ let avail_frames = match stream.channel.avail() {
+ Err(err) if err.errno() == libc::EPIPE => return Ok(PollDescriptorsFlow::XRun),
+ res => res,
+ }? as usize;
+ let delay_frames = match status.get_delay() {
+ // Buffer underrun. TODO: Notify the user.
+ d if d < 0 => 0,
+ d => d as usize,
+ };
+ let available_samples = avail_frames * stream.conf.channels as usize;
+
+ // Only go on if there is at least `stream.period_len` samples.
+ if available_samples < stream.period_len {
+ return Ok(PollDescriptorsFlow::Continue);
+ }
+
+ // Prepare the data buffer.
+ let buffer_size = stream.sample_format.sample_size() * available_samples;
+ buffer.resize(buffer_size, 0u8);
+
+ Ok(PollDescriptorsFlow::Ready {
+ stream_type,
+ status,
+ avail_frames,
+ delay_frames,
+ })
+}
+
+// Read input data from ALSA and deliver it to the user.
+fn process_input(
+ stream: &StreamInner,
+ buffer: &mut [u8],
+ status: alsa::pcm::Status,
+ delay_frames: usize,
+ data_callback: &mut (dyn FnMut(&Data, &InputCallbackInfo) + Send + 'static),
+) -> Result<(), BackendSpecificError> {
+ stream.channel.io_bytes().readi(buffer)?;
+ let sample_format = stream.sample_format;
+ let data = buffer.as_mut_ptr() as *mut ();
+ let len = buffer.len() / sample_format.sample_size();
+ let data = unsafe { Data::from_parts(data, len, sample_format) };
+ let callback = stream_timestamp(&status, stream.creation_instant)?;
+ let delay_duration = frames_to_duration(delay_frames, stream.conf.sample_rate);
+ let capture = callback
+ .sub(delay_duration)
+ .expect("`capture` is earlier than representation supported by `StreamInstant`");
+ let timestamp = crate::InputStreamTimestamp { callback, capture };
+ let info = crate::InputCallbackInfo { timestamp };
+ data_callback(&data, &info);
+
+ Ok(())
+}
+
+// Request data from the user's function and write it via ALSA.
+//
+// Returns `true`
+fn process_output(
+ stream: &StreamInner,
+ buffer: &mut [u8],
+ status: alsa::pcm::Status,
+ available_frames: usize,
+ delay_frames: usize,
+ data_callback: &mut (dyn FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static),
+ error_callback: &mut dyn FnMut(StreamError),
+) -> Result<(), BackendSpecificError> {
+ {
+ // We're now sure that we're ready to write data.
+ let sample_format = stream.sample_format;
+ let data = buffer.as_mut_ptr() as *mut ();
+ let len = buffer.len() / sample_format.sample_size();
+ let mut data = unsafe { Data::from_parts(data, len, sample_format) };
+ let callback = stream_timestamp(&status, stream.creation_instant)?;
+ let delay_duration = frames_to_duration(delay_frames, stream.conf.sample_rate);
+ let playback = callback
+ .add(delay_duration)
+ .expect("`playback` occurs beyond representation supported by `StreamInstant`");
+ let timestamp = crate::OutputStreamTimestamp { callback, playback };
+ let info = crate::OutputCallbackInfo { timestamp };
+ data_callback(&mut data, &info);
+ }
+ loop {
+ match stream.channel.io_bytes().writei(buffer) {
+ Err(err) if err.errno() == libc::EPIPE => {
+ // buffer underrun
+ // TODO: Notify the user of this.
+ let _ = stream.channel.try_recover(err, false);
+ }
+ Err(err) => {
+ error_callback(err.into());
+ continue;
+ }
+ Ok(result) if result != available_frames => {
+ let description = format!(
+ "unexpected number of frames written: expected {}, \
+ result {} (this should never happen)",
+ available_frames, result,
+ );
+ error_callback(BackendSpecificError { description }.into());
+ continue;
+ }
+ _ => {
+ break;
+ }
+ }
+ }
+ Ok(())
+}
+
+// Use the elapsed duration since the start of the stream.
+//
+// This ensures positive values that are compatible with our `StreamInstant` representation.
+fn stream_timestamp(
+ status: &alsa::pcm::Status,
+ creation_instant: Option,
+) -> Result {
+ match creation_instant {
+ None => {
+ let trigger_ts = status.get_trigger_htstamp();
+ let ts = status.get_htstamp();
+ let nanos = timespec_diff_nanos(ts, trigger_ts);
+ if nanos < 0 {
+ panic!(
+ "get_htstamp `{}.{}` was earlier than get_trigger_htstamp `{}.{}`",
+ ts.tv_sec, ts.tv_nsec, trigger_ts.tv_sec, trigger_ts.tv_nsec
+ );
+ }
+ Ok(crate::StreamInstant::from_nanos(nanos))
+ }
+ Some(creation) => {
+ let now = std::time::Instant::now();
+ let duration = now.duration_since(creation);
+ let instant = crate::StreamInstant::from_nanos_i128(duration.as_nanos() as i128)
+ .expect("stream duration has exceeded `StreamInstant` representation");
+ Ok(instant)
+ }
+ }
+}
+
+// Adapted from `timestamp2ns` here:
+// https://fossies.org/linux/alsa-lib/test/audio_time.c
+fn timespec_to_nanos(ts: libc::timespec) -> i64 {
+ ts.tv_sec as i64 * 1_000_000_000 + ts.tv_nsec as i64
+}
+
+// Adapted from `timediff` here:
+// https://fossies.org/linux/alsa-lib/test/audio_time.c
+fn timespec_diff_nanos(a: libc::timespec, b: libc::timespec) -> i64 {
+ timespec_to_nanos(a) - timespec_to_nanos(b)
+}
+
+// Convert the given duration in frames at the given sample rate to a `std::time::Duration`.
+fn frames_to_duration(frames: usize, rate: crate::SampleRate) -> std::time::Duration {
+ let secsf = frames as f64 / rate.0 as f64;
+ let secs = secsf as u64;
+ let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32;
+ std::time::Duration::new(secs, nanos)
+}
+
+impl Stream {
+ fn new_input(
+ inner: Arc,
+ mut data_callback: D,
+ mut error_callback: E,
+ timeout: Option,
+ ) -> Stream
+ where
+ D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
+ E: FnMut(StreamError) + Send + 'static,
+ {
+ let (tx, rx) = trigger();
+ // Clone the handle for passing into worker thread.
+ let stream = inner.clone();
+ let thread = thread::Builder::new()
+ .name("cpal_alsa_in".to_owned())
+ .spawn(move || {
+ input_stream_worker(
+ rx,
+ &stream,
+ &mut data_callback,
+ &mut error_callback,
+ timeout,
+ );
+ })
+ .unwrap();
+ Stream {
+ thread: Some(thread),
+ inner,
+ trigger: tx,
+ }
+ }
+
+ fn new_output(
+ inner: Arc,
+ mut data_callback: D,
+ mut error_callback: E,
+ timeout: Option,
+ ) -> Stream
+ where
+ D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
+ E: FnMut(StreamError) + Send + 'static,
+ {
+ let (tx, rx) = trigger();
+ // Clone the handle for passing into worker thread.
+ let stream = inner.clone();
+ let thread = thread::Builder::new()
+ .name("cpal_alsa_out".to_owned())
+ .spawn(move || {
+ output_stream_worker(
+ rx,
+ &stream,
+ &mut data_callback,
+ &mut error_callback,
+ timeout,
+ );
+ })
+ .unwrap();
+ Stream {
+ thread: Some(thread),
+ inner,
+ trigger: tx,
+ }
+ }
+}
+
+impl Drop for Stream {
+ fn drop(&mut self) {
+ self.trigger.wakeup();
+ self.thread.take().unwrap().join().unwrap();
+ }
+}
+
+impl StreamTrait for Stream {
+ fn play(&self) -> Result<(), PlayStreamError> {
+ self.inner.channel.pause(false).ok();
+ Ok(())
+ }
+ fn pause(&self) -> Result<(), PauseStreamError> {
+ self.inner.channel.pause(true).ok();
+ Ok(())
+ }
+}
+
+fn set_hw_params_from_format(
+ pcm_handle: &alsa::pcm::PCM,
+ config: &StreamConfig,
+ sample_format: SampleFormat,
+) -> Result {
+ let hw_params = alsa::pcm::HwParams::any(pcm_handle)?;
+ hw_params.set_access(alsa::pcm::Access::RWInterleaved)?;
+
+ let sample_format = if cfg!(target_endian = "big") {
+ match sample_format {
+ SampleFormat::I8 => alsa::pcm::Format::S8,
+ SampleFormat::I16 => alsa::pcm::Format::S16BE,
+ // SampleFormat::I24 => alsa::pcm::Format::S24BE,
+ SampleFormat::I32 => alsa::pcm::Format::S32BE,
+ // SampleFormat::I48 => alsa::pcm::Format::S48BE,
+ // SampleFormat::I64 => alsa::pcm::Format::S64BE,
+ SampleFormat::U8 => alsa::pcm::Format::U8,
+ SampleFormat::U16 => alsa::pcm::Format::U16BE,
+ // SampleFormat::U24 => alsa::pcm::Format::U24BE,
+ SampleFormat::U32 => alsa::pcm::Format::U32BE,
+ // SampleFormat::U48 => alsa::pcm::Format::U48BE,
+ // SampleFormat::U64 => alsa::pcm::Format::U64BE,
+ SampleFormat::F32 => alsa::pcm::Format::FloatBE,
+ SampleFormat::F64 => alsa::pcm::Format::Float64BE,
+ sample_format => {
+ return Err(BackendSpecificError {
+ description: format!(
+ "Sample format '{}' is not supported by this backend",
+ sample_format
+ ),
+ })
+ }
+ }
+ } else {
+ match sample_format {
+ SampleFormat::I8 => alsa::pcm::Format::S8,
+ SampleFormat::I16 => alsa::pcm::Format::S16LE,
+ // SampleFormat::I24 => alsa::pcm::Format::S24LE,
+ SampleFormat::I32 => alsa::pcm::Format::S32LE,
+ // SampleFormat::I48 => alsa::pcm::Format::S48LE,
+ // SampleFormat::I64 => alsa::pcm::Format::S64LE,
+ SampleFormat::U8 => alsa::pcm::Format::U8,
+ SampleFormat::U16 => alsa::pcm::Format::U16LE,
+ // SampleFormat::U24 => alsa::pcm::Format::U24LE,
+ SampleFormat::U32 => alsa::pcm::Format::U32LE,
+ // SampleFormat::U48 => alsa::pcm::Format::U48LE,
+ // SampleFormat::U64 => alsa::pcm::Format::U64LE,
+ SampleFormat::F32 => alsa::pcm::Format::FloatLE,
+ SampleFormat::F64 => alsa::pcm::Format::Float64LE,
+ sample_format => {
+ return Err(BackendSpecificError {
+ description: format!(
+ "Sample format '{}' is not supported by this backend",
+ sample_format
+ ),
+ })
+ }
+ }
+ };
+
+ hw_params.set_format(sample_format)?;
+ hw_params.set_rate(config.sample_rate.0, alsa::ValueOr::Nearest)?;
+ hw_params.set_channels(config.channels as u32)?;
+
+ match config.buffer_size {
+ BufferSize::Fixed(v) => {
+ hw_params.set_period_size_near((v / 4) as alsa::pcm::Frames, alsa::ValueOr::Nearest)?;
+ hw_params.set_buffer_size(v as alsa::pcm::Frames)?;
+ }
+ BufferSize::Default => {
+ // These values together represent a moderate latency and wakeup interval.
+ // Without them, we are at the mercy of the device
+ hw_params.set_period_time_near(25_000, alsa::ValueOr::Nearest)?;
+ hw_params.set_buffer_time_near(100_000, alsa::ValueOr::Nearest)?;
+ }
+ }
+
+ pcm_handle.hw_params(&hw_params)?;
+
+ Ok(hw_params.can_pause())
+}
+
+fn set_sw_params_from_format(
+ pcm_handle: &alsa::pcm::PCM,
+ config: &StreamConfig,
+ stream_type: alsa::Direction,
+) -> Result {
+ let sw_params = pcm_handle.sw_params_current()?;
+
+ let period_len = {
+ let (buffer, period) = pcm_handle.get_params()?;
+ if buffer == 0 {
+ return Err(BackendSpecificError {
+ description: "initialization resulted in a null buffer".to_string(),
+ });
+ }
+ sw_params.set_avail_min(period as alsa::pcm::Frames)?;
+
+ let start_threshold = match stream_type {
+ alsa::Direction::Playback => buffer - period,
+
+ // For capture streams, the start threshold is irrelevant and ignored,
+ // because build_stream_inner() starts the stream before process_input()
+ // reads from it. Set it anyway I guess, since it's better than leaving
+ // it at an unspecified default value.
+ alsa::Direction::Capture => 1,
+ };
+ sw_params.set_start_threshold(start_threshold.try_into().unwrap())?;
+
+ period as usize * config.channels as usize
+ };
+
+ sw_params.set_tstamp_mode(true)?;
+ sw_params.set_tstamp_type(alsa::pcm::TstampType::MonotonicRaw)?;
+
+ // tstamp_type param cannot be changed after the device is opened.
+ // The default tstamp_type value on most Linux systems is "monotonic",
+ // let's try to use it if setting the tstamp_type fails.
+ if pcm_handle.sw_params(&sw_params).is_err() {
+ sw_params.set_tstamp_type(alsa::pcm::TstampType::Monotonic)?;
+ pcm_handle.sw_params(&sw_params)?;
+ }
+
+ Ok(period_len)
+}
+
+impl From for BackendSpecificError {
+ fn from(err: alsa::Error) -> Self {
+ BackendSpecificError {
+ description: err.to_string(),
+ }
+ }
+}
+
+impl From for BuildStreamError {
+ fn from(err: alsa::Error) -> Self {
+ let err: BackendSpecificError = err.into();
+ err.into()
+ }
+}
+
+impl From for SupportedStreamConfigsError {
+ fn from(err: alsa::Error) -> Self {
+ let err: BackendSpecificError = err.into();
+ err.into()
+ }
+}
+
+impl From for PlayStreamError {
+ fn from(err: alsa::Error) -> Self {
+ let err: BackendSpecificError = err.into();
+ err.into()
+ }
+}
+
+impl From for PauseStreamError {
+ fn from(err: alsa::Error) -> Self {
+ let err: BackendSpecificError = err.into();
+ err.into()
+ }
+}
+
+impl From for StreamError {
+ fn from(err: alsa::Error) -> Self {
+ let err: BackendSpecificError = err.into();
+ err.into()
+ }
+}
diff --git a/src-tauri/patches/cpal-0.15.3/src/host/asio/device.rs b/src-tauri/patches/cpal-0.15.3/src/host/asio/device.rs
new file mode 100644
index 00000000..f503158e
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/src/host/asio/device.rs
@@ -0,0 +1,232 @@
+pub type SupportedInputConfigs = std::vec::IntoIter;
+pub type SupportedOutputConfigs = std::vec::IntoIter;
+
+use super::sys;
+use crate::BackendSpecificError;
+use crate::DefaultStreamConfigError;
+use crate::DeviceNameError;
+use crate::DevicesError;
+use crate::SampleFormat;
+use crate::SampleRate;
+use crate::SupportedBufferSize;
+use crate::SupportedStreamConfig;
+use crate::SupportedStreamConfigRange;
+use crate::SupportedStreamConfigsError;
+use std::hash::{Hash, Hasher};
+use std::sync::atomic::AtomicI32;
+use std::sync::{Arc, Mutex};
+
+/// A ASIO Device
+#[derive(Clone)]
+pub struct Device {
+ /// The driver represented by this device.
+ pub driver: Arc,
+
+ // Input and/or Output stream.
+ // A driver can only have one of each.
+ // They need to be created at the same time.
+ pub asio_streams: Arc>,
+ pub current_buffer_index: Arc,
+}
+
+/// All available devices.
+pub struct Devices {
+ asio: Arc,
+ drivers: std::vec::IntoIter,
+}
+
+impl PartialEq for Device {
+ fn eq(&self, other: &Self) -> bool {
+ self.driver.name() == other.driver.name()
+ }
+}
+
+impl Eq for Device {}
+
+impl Hash for Device {
+ fn hash(&self, state: &mut H) {
+ self.driver.name().hash(state);
+ }
+}
+
+impl Device {
+ pub fn name(&self) -> Result {
+ Ok(self.driver.name().to_string())
+ }
+
+ /// Gets the supported input configs.
+ /// TODO currently only supports the default.
+ /// Need to find all possible configs.
+ pub fn supported_input_configs(
+ &self,
+ ) -> Result {
+ // Retrieve the default config for the total supported channels and supported sample
+ // format.
+ let f = match self.default_input_config() {
+ Err(_) => return Err(SupportedStreamConfigsError::DeviceNotAvailable),
+ Ok(f) => f,
+ };
+
+ // Collect a config for every combination of supported sample rate and number of channels.
+ let mut supported_configs = vec![];
+ for &rate in crate::COMMON_SAMPLE_RATES {
+ if !self
+ .driver
+ .can_sample_rate(rate.0.into())
+ .ok()
+ .unwrap_or(false)
+ {
+ continue;
+ }
+ for channels in 1..f.channels + 1 {
+ supported_configs.push(SupportedStreamConfigRange {
+ channels,
+ min_sample_rate: rate,
+ max_sample_rate: rate,
+ buffer_size: f.buffer_size,
+ sample_format: f.sample_format,
+ })
+ }
+ }
+ Ok(supported_configs.into_iter())
+ }
+
+ /// Gets the supported output configs.
+ /// TODO currently only supports the default.
+ /// Need to find all possible configs.
+ pub fn supported_output_configs(
+ &self,
+ ) -> Result {
+ // Retrieve the default config for the total supported channels and supported sample
+ // format.
+ let f = match self.default_output_config() {
+ Err(_) => return Err(SupportedStreamConfigsError::DeviceNotAvailable),
+ Ok(f) => f,
+ };
+
+ // Collect a config for every combination of supported sample rate and number of channels.
+ let mut supported_configs = vec![];
+ for &rate in crate::COMMON_SAMPLE_RATES {
+ if !self
+ .driver
+ .can_sample_rate(rate.0.into())
+ .ok()
+ .unwrap_or(false)
+ {
+ continue;
+ }
+ for channels in 1..f.channels + 1 {
+ supported_configs.push(SupportedStreamConfigRange {
+ channels,
+ min_sample_rate: rate,
+ max_sample_rate: rate,
+ buffer_size: f.buffer_size,
+ sample_format: f.sample_format,
+ })
+ }
+ }
+ Ok(supported_configs.into_iter())
+ }
+
+ /// Returns the default input config
+ pub fn default_input_config(&self) -> Result {
+ let channels = self.driver.channels().map_err(default_config_err)?.ins as u16;
+ let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_config_err)? as _);
+ let (min, max) = self.driver.buffersize_range().map_err(default_config_err)?;
+ let buffer_size = SupportedBufferSize::Range {
+ min: min as u32,
+ max: max as u32,
+ };
+ // Map th ASIO sample type to a CPAL sample type
+ let data_type = self.driver.input_data_type().map_err(default_config_err)?;
+ let sample_format = convert_data_type(&data_type)
+ .ok_or(DefaultStreamConfigError::StreamTypeNotSupported)?;
+ Ok(SupportedStreamConfig {
+ channels,
+ sample_rate,
+ buffer_size,
+ sample_format,
+ })
+ }
+
+ /// Returns the default output config
+ pub fn default_output_config(&self) -> Result {
+ let channels = self.driver.channels().map_err(default_config_err)?.outs as u16;
+ let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_config_err)? as _);
+ let (min, max) = self.driver.buffersize_range().map_err(default_config_err)?;
+ let buffer_size = SupportedBufferSize::Range {
+ min: min as u32,
+ max: max as u32,
+ };
+ let data_type = self.driver.output_data_type().map_err(default_config_err)?;
+ let sample_format = convert_data_type(&data_type)
+ .ok_or(DefaultStreamConfigError::StreamTypeNotSupported)?;
+ Ok(SupportedStreamConfig {
+ channels,
+ sample_rate,
+ buffer_size,
+ sample_format,
+ })
+ }
+}
+
+impl Devices {
+ pub fn new(asio: Arc) -> Result {
+ let drivers = asio.driver_names().into_iter();
+ Ok(Devices { asio, drivers })
+ }
+}
+
+impl Iterator for Devices {
+ type Item = Device;
+
+ /// Load drivers and return device
+ fn next(&mut self) -> Option {
+ loop {
+ match self.drivers.next() {
+ Some(name) => match self.asio.load_driver(&name) {
+ Ok(driver) => {
+ let driver = Arc::new(driver);
+ let asio_streams = Arc::new(Mutex::new(sys::AsioStreams {
+ input: None,
+ output: None,
+ }));
+ return Some(Device {
+ driver,
+ asio_streams,
+ current_buffer_index: Arc::new(AtomicI32::new(-1)),
+ });
+ }
+ Err(_) => continue,
+ },
+ None => return None,
+ }
+ }
+ }
+}
+
+pub(crate) fn convert_data_type(ty: &sys::AsioSampleType) -> Option {
+ let fmt = match *ty {
+ sys::AsioSampleType::ASIOSTInt16MSB => SampleFormat::I16,
+ sys::AsioSampleType::ASIOSTInt16LSB => SampleFormat::I16,
+ sys::AsioSampleType::ASIOSTFloat32MSB => SampleFormat::F32,
+ sys::AsioSampleType::ASIOSTFloat32LSB => SampleFormat::F32,
+ sys::AsioSampleType::ASIOSTInt32MSB => SampleFormat::I32,
+ sys::AsioSampleType::ASIOSTInt32LSB => SampleFormat::I32,
+ _ => return None,
+ };
+ Some(fmt)
+}
+
+fn default_config_err(e: sys::AsioError) -> DefaultStreamConfigError {
+ match e {
+ sys::AsioError::NoDrivers | sys::AsioError::HardwareMalfunction => {
+ DefaultStreamConfigError::DeviceNotAvailable
+ }
+ sys::AsioError::NoRate => DefaultStreamConfigError::StreamTypeNotSupported,
+ err => {
+ let description = format!("{}", err);
+ BackendSpecificError { description }.into()
+ }
+ }
+}
diff --git a/src-tauri/patches/cpal-0.15.3/src/host/asio/mod.rs b/src-tauri/patches/cpal-0.15.3/src/host/asio/mod.rs
new file mode 100644
index 00000000..73d936d9
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/src/host/asio/mod.rs
@@ -0,0 +1,138 @@
+extern crate asio_sys as sys;
+
+use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
+use crate::{
+ BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError,
+ InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat,
+ StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigsError,
+};
+
+pub use self::device::{Device, Devices, SupportedInputConfigs, SupportedOutputConfigs};
+pub use self::stream::Stream;
+use std::sync::Arc;
+use std::time::Duration;
+
+mod device;
+mod stream;
+
+/// The host for ASIO.
+#[derive(Debug)]
+pub struct Host {
+ asio: Arc,
+}
+
+impl Host {
+ pub fn new() -> Result {
+ let asio = Arc::new(sys::Asio::new());
+ let host = Host { asio };
+ Ok(host)
+ }
+}
+
+impl HostTrait for Host {
+ type Devices = Devices;
+ type Device = Device;
+
+ fn is_available() -> bool {
+ true
+ //unimplemented!("check how to do this using asio-sys")
+ }
+
+ fn devices(&self) -> Result {
+ Devices::new(self.asio.clone())
+ }
+
+ fn default_input_device(&self) -> Option {
+ // ASIO has no concept of a default device, so just use the first.
+ self.input_devices().ok().and_then(|mut ds| ds.next())
+ }
+
+ fn default_output_device(&self) -> Option {
+ // ASIO has no concept of a default device, so just use the first.
+ self.output_devices().ok().and_then(|mut ds| ds.next())
+ }
+}
+
+impl DeviceTrait for Device {
+ type SupportedInputConfigs = SupportedInputConfigs;
+ type SupportedOutputConfigs = SupportedOutputConfigs;
+ type Stream = Stream;
+
+ fn name(&self) -> Result {
+ Device::name(self)
+ }
+
+ fn supported_input_configs(
+ &self,
+ ) -> Result {
+ Device::supported_input_configs(self)
+ }
+
+ fn supported_output_configs(
+ &self,
+ ) -> Result {
+ Device::supported_output_configs(self)
+ }
+
+ fn default_input_config(&self) -> Result {
+ Device::default_input_config(self)
+ }
+
+ fn default_output_config(&self) -> Result {
+ Device::default_output_config(self)
+ }
+
+ fn build_input_stream_raw(
+ &self,
+ config: &StreamConfig,
+ sample_format: SampleFormat,
+ data_callback: D,
+ error_callback: E,
+ timeout: Option,
+ ) -> Result
+ where
+ D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
+ E: FnMut(StreamError) + Send + 'static,
+ {
+ Device::build_input_stream_raw(
+ self,
+ config,
+ sample_format,
+ data_callback,
+ error_callback,
+ timeout,
+ )
+ }
+
+ fn build_output_stream_raw(
+ &self,
+ config: &StreamConfig,
+ sample_format: SampleFormat,
+ data_callback: D,
+ error_callback: E,
+ timeout: Option,
+ ) -> Result
+ where
+ D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
+ E: FnMut(StreamError) + Send + 'static,
+ {
+ Device::build_output_stream_raw(
+ self,
+ config,
+ sample_format,
+ data_callback,
+ error_callback,
+ timeout,
+ )
+ }
+}
+
+impl StreamTrait for Stream {
+ fn play(&self) -> Result<(), PlayStreamError> {
+ Stream::play(self)
+ }
+
+ fn pause(&self) -> Result<(), PauseStreamError> {
+ Stream::pause(self)
+ }
+}
diff --git a/src-tauri/patches/cpal-0.15.3/src/host/asio/stream.rs b/src-tauri/patches/cpal-0.15.3/src/host/asio/stream.rs
new file mode 100644
index 00000000..c9070005
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/src/host/asio/stream.rs
@@ -0,0 +1,718 @@
+extern crate asio_sys as sys;
+extern crate num_traits;
+
+use self::num_traits::PrimInt;
+use super::Device;
+use crate::{
+ BackendSpecificError, BufferSize, BuildStreamError, Data, InputCallbackInfo,
+ OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError,
+};
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Arc, Mutex};
+use std::time::Duration;
+
+pub struct Stream {
+ playing: Arc,
+ // Ensure the `Driver` does not terminate until the last stream is dropped.
+ driver: Arc,
+ #[allow(dead_code)]
+ asio_streams: Arc>,
+ callback_id: sys::CallbackId,
+}
+
+impl Stream {
+ pub fn play(&self) -> Result<(), PlayStreamError> {
+ self.playing.store(true, Ordering::SeqCst);
+ Ok(())
+ }
+
+ pub fn pause(&self) -> Result<(), PauseStreamError> {
+ self.playing.store(false, Ordering::SeqCst);
+ Ok(())
+ }
+}
+
+impl Device {
+ pub fn build_input_stream_raw(
+ &self,
+ config: &StreamConfig,
+ sample_format: SampleFormat,
+ mut data_callback: D,
+ _error_callback: E,
+ _timeout: Option,
+ ) -> Result
+ where
+ D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
+ E: FnMut(StreamError) + Send + 'static,
+ {
+ let stream_type = self.driver.input_data_type().map_err(build_stream_err)?;
+
+ // Ensure that the desired sample type is supported.
+ let expected_sample_format = super::device::convert_data_type(&stream_type)
+ .ok_or(BuildStreamError::StreamConfigNotSupported)?;
+ if sample_format != expected_sample_format {
+ return Err(BuildStreamError::StreamConfigNotSupported);
+ }
+
+ let num_channels = config.channels;
+ let buffer_size = self.get_or_create_input_stream(config, sample_format)?;
+ let cpal_num_samples = buffer_size * num_channels as usize;
+
+ // Create the buffer depending on the size of the data type.
+ let len_bytes = cpal_num_samples * sample_format.sample_size();
+ let mut interleaved = vec![0u8; len_bytes];
+
+ let stream_playing = Arc::new(AtomicBool::new(false));
+ let playing = Arc::clone(&stream_playing);
+ let asio_streams = self.asio_streams.clone();
+
+ // Set the input callback.
+ // This is most performance critical part of the ASIO bindings.
+ let config = config.clone();
+ let callback_id = self.driver.add_callback(move |callback_info| unsafe {
+ // If not playing return early.
+ if !playing.load(Ordering::SeqCst) {
+ return;
+ }
+
+ // There is 0% chance of lock contention the host only locks when recreating streams.
+ let stream_lock = asio_streams.lock().unwrap();
+ let asio_stream = match stream_lock.input {
+ Some(ref asio_stream) => asio_stream,
+ None => return,
+ };
+
+ /// 1. Write from the ASIO buffer to the interleaved CPAL buffer.
+ /// 2. Deliver the CPAL buffer to the user callback.
+ unsafe fn process_input_callback(
+ data_callback: &mut D,
+ interleaved: &mut [u8],
+ asio_stream: &sys::AsioStream,
+ asio_info: &sys::CallbackInfo,
+ sample_rate: crate::SampleRate,
+ format: SampleFormat,
+ from_endianness: F,
+ ) where
+ A: Copy,
+ D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
+ F: Fn(A) -> A,
+ {
+ // 1. Write the ASIO channels to the CPAL buffer.
+ let interleaved: &mut [A] = cast_slice_mut(interleaved);
+ let n_frames = asio_stream.buffer_size as usize;
+ let n_channels = interleaved.len() / n_frames;
+ let buffer_index = asio_info.buffer_index as usize;
+ for ch_ix in 0..n_channels {
+ let asio_channel = asio_channel_slice::(asio_stream, buffer_index, ch_ix);
+ for (frame, s_asio) in interleaved.chunks_mut(n_channels).zip(asio_channel) {
+ frame[ch_ix] = from_endianness(*s_asio);
+ }
+ }
+
+ // 2. Deliver the interleaved buffer to the callback.
+ let data = interleaved.as_mut_ptr() as *mut ();
+ let len = interleaved.len();
+ let data = Data::from_parts(data, len, format);
+ let callback = system_time_to_stream_instant(asio_info.system_time);
+ let delay = frames_to_duration(n_frames, sample_rate);
+ let capture = callback
+ .sub(delay)
+ .expect("`capture` occurs before origin of alsa `StreamInstant`");
+ let timestamp = crate::InputStreamTimestamp { callback, capture };
+ let info = InputCallbackInfo { timestamp };
+ data_callback(&data, &info);
+ }
+
+ match (&stream_type, sample_format) {
+ (&sys::AsioSampleType::ASIOSTInt16LSB, SampleFormat::I16) => {
+ process_input_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::I16,
+ from_le,
+ );
+ }
+ (&sys::AsioSampleType::ASIOSTInt16MSB, SampleFormat::I16) => {
+ process_input_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::I16,
+ from_be,
+ );
+ }
+
+ (&sys::AsioSampleType::ASIOSTFloat32LSB, SampleFormat::F32) => {
+ process_input_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::F32,
+ from_le,
+ );
+ }
+ (&sys::AsioSampleType::ASIOSTFloat32MSB, SampleFormat::F32) => {
+ process_input_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::F32,
+ from_be,
+ );
+ }
+
+ (&sys::AsioSampleType::ASIOSTInt32LSB, SampleFormat::I32) => {
+ process_input_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::I32,
+ from_le,
+ );
+ }
+ (&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I32) => {
+ process_input_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::I32,
+ from_be,
+ );
+ }
+
+ (&sys::AsioSampleType::ASIOSTFloat64LSB, SampleFormat::F64) => {
+ process_input_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::F64,
+ from_le,
+ );
+ }
+ (&sys::AsioSampleType::ASIOSTFloat64MSB, SampleFormat::F64) => {
+ process_input_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::F64,
+ from_be,
+ );
+ }
+
+ unsupported_format_pair => unreachable!(
+ "`build_input_stream_raw` should have returned with unsupported \
+ format {:?}",
+ unsupported_format_pair
+ ),
+ }
+ });
+
+ let driver = self.driver.clone();
+ let asio_streams = self.asio_streams.clone();
+
+ // Immediately start the device?
+ self.driver.start().map_err(build_stream_err)?;
+
+ Ok(Stream {
+ playing: stream_playing,
+ driver,
+ asio_streams,
+ callback_id,
+ })
+ }
+
+ pub fn build_output_stream_raw(
+ &self,
+ config: &StreamConfig,
+ sample_format: SampleFormat,
+ mut data_callback: D,
+ _error_callback: E,
+ _timeout: Option,
+ ) -> Result
+ where
+ D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
+ E: FnMut(StreamError) + Send + 'static,
+ {
+ let stream_type = self.driver.output_data_type().map_err(build_stream_err)?;
+
+ // Ensure that the desired sample type is supported.
+ let expected_sample_format = super::device::convert_data_type(&stream_type)
+ .ok_or(BuildStreamError::StreamConfigNotSupported)?;
+ if sample_format != expected_sample_format {
+ return Err(BuildStreamError::StreamConfigNotSupported);
+ }
+
+ let num_channels = config.channels;
+ let buffer_size = self.get_or_create_output_stream(config, sample_format)?;
+ let cpal_num_samples = buffer_size * num_channels as usize;
+
+ // Create buffers depending on data type.
+ let len_bytes = cpal_num_samples * sample_format.sample_size();
+ let mut interleaved = vec![0u8; len_bytes];
+ let current_buffer_index = self.current_buffer_index.clone();
+
+ let stream_playing = Arc::new(AtomicBool::new(false));
+ let playing = Arc::clone(&stream_playing);
+ let asio_streams = self.asio_streams.clone();
+
+ let config = config.clone();
+ let callback_id = self.driver.add_callback(move |callback_info| unsafe {
+ // If not playing, return early.
+ if !playing.load(Ordering::SeqCst) {
+ return;
+ }
+
+ // There is 0% chance of lock contention the host only locks when recreating streams.
+ let mut stream_lock = asio_streams.lock().unwrap();
+ let asio_stream = match stream_lock.output {
+ Some(ref mut asio_stream) => asio_stream,
+ None => return,
+ };
+
+ // Silence the ASIO buffer that is about to be used.
+ //
+ // This checks if any other callbacks have already silenced the buffer associated with
+ // the current `buffer_index`.
+ let silence =
+ current_buffer_index.load(Ordering::Acquire) != callback_info.buffer_index;
+
+ if silence {
+ current_buffer_index.store(callback_info.buffer_index, Ordering::Release);
+ }
+
+ /// 1. Render the given callback to the given buffer of interleaved samples.
+ /// 2. If required, silence the ASIO buffer.
+ /// 3. Finally, write the interleaved data to the non-interleaved ASIO buffer,
+ /// performing endianness conversions as necessary.
+ unsafe fn process_output_callback(
+ data_callback: &mut D,
+ interleaved: &mut [u8],
+ silence_asio_buffer: bool,
+ asio_stream: &mut sys::AsioStream,
+ asio_info: &sys::CallbackInfo,
+ sample_rate: crate::SampleRate,
+ format: SampleFormat,
+ mix_samples: F,
+ ) where
+ A: Copy,
+ D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
+ F: Fn(A, A) -> A,
+ {
+ // 1. Render interleaved buffer from callback.
+ let interleaved: &mut [A] = cast_slice_mut(interleaved);
+ let data = interleaved.as_mut_ptr() as *mut ();
+ let len = interleaved.len();
+ let mut data = Data::from_parts(data, len, format);
+ let callback = system_time_to_stream_instant(asio_info.system_time);
+ let n_frames = asio_stream.buffer_size as usize;
+ let delay = frames_to_duration(n_frames, sample_rate);
+ let playback = callback
+ .add(delay)
+ .expect("`playback` occurs beyond representation supported by `StreamInstant`");
+ let timestamp = crate::OutputStreamTimestamp { callback, playback };
+ let info = OutputCallbackInfo { timestamp };
+ data_callback(&mut data, &info);
+
+ // 2. Silence ASIO channels if necessary.
+ let n_channels = interleaved.len() / n_frames;
+ let buffer_index = asio_info.buffer_index as usize;
+ if silence_asio_buffer {
+ for ch_ix in 0..n_channels {
+ let asio_channel =
+ asio_channel_slice_mut::(asio_stream, buffer_index, ch_ix);
+ asio_channel.align_to_mut::().1.fill(0);
+ }
+ }
+
+ // 3. Write interleaved samples to ASIO channels, one channel at a time.
+ for ch_ix in 0..n_channels {
+ let asio_channel =
+ asio_channel_slice_mut::(asio_stream, buffer_index, ch_ix);
+ for (frame, s_asio) in interleaved.chunks(n_channels).zip(asio_channel) {
+ *s_asio = mix_samples(*s_asio, frame[ch_ix]);
+ }
+ }
+ }
+
+ match (sample_format, &stream_type) {
+ (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16LSB) => {
+ process_output_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ silence,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::I16,
+ |old_sample, new_sample| {
+ from_le(old_sample).saturating_add(new_sample).to_le()
+ },
+ );
+ }
+ (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16MSB) => {
+ process_output_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ silence,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::I16,
+ |old_sample, new_sample| {
+ from_be(old_sample).saturating_add(new_sample).to_be()
+ },
+ );
+ }
+ (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32LSB) => {
+ process_output_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ silence,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::F32,
+ |old_sample, new_sample| {
+ (f32::from_bits(from_le(old_sample)) + f32::from_bits(new_sample))
+ .to_bits()
+ .to_le()
+ },
+ );
+ }
+
+ (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32MSB) => {
+ process_output_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ silence,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::F32,
+ |old_sample, new_sample| {
+ (f32::from_bits(from_be(old_sample)) + f32::from_bits(new_sample))
+ .to_bits()
+ .to_be()
+ },
+ );
+ }
+
+ (SampleFormat::I32, &sys::AsioSampleType::ASIOSTInt32LSB) => {
+ process_output_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ silence,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::I32,
+ |old_sample, new_sample| {
+ from_le(old_sample).saturating_add(new_sample).to_le()
+ },
+ );
+ }
+ (SampleFormat::I32, &sys::AsioSampleType::ASIOSTInt32MSB) => {
+ process_output_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ silence,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::I32,
+ |old_sample, new_sample| {
+ from_be(old_sample).saturating_add(new_sample).to_be()
+ },
+ );
+ }
+
+ (SampleFormat::F64, &sys::AsioSampleType::ASIOSTFloat64LSB) => {
+ process_output_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ silence,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::F64,
+ |old_sample, new_sample| {
+ (f64::from_bits(from_le(old_sample)) + f64::from_bits(new_sample))
+ .to_bits()
+ .to_le()
+ },
+ );
+ }
+
+ (SampleFormat::F64, &sys::AsioSampleType::ASIOSTFloat64MSB) => {
+ process_output_callback::(
+ &mut data_callback,
+ &mut interleaved,
+ silence,
+ asio_stream,
+ callback_info,
+ config.sample_rate,
+ SampleFormat::F64,
+ |old_sample, new_sample| {
+ (f64::from_bits(from_be(old_sample)) + f64::from_bits(new_sample))
+ .to_bits()
+ .to_be()
+ },
+ );
+ }
+
+ unsupported_format_pair => unreachable!(
+ "`build_output_stream_raw` should have returned with unsupported \
+ format {:?}",
+ unsupported_format_pair
+ ),
+ }
+ });
+
+ let driver = self.driver.clone();
+ let asio_streams = self.asio_streams.clone();
+
+ // Immediately start the device?
+ self.driver.start().map_err(build_stream_err)?;
+
+ Ok(Stream {
+ playing: stream_playing,
+ driver,
+ asio_streams,
+ callback_id,
+ })
+ }
+
+ /// Create a new CPAL Input Stream.
+ ///
+ /// If there is no existing ASIO Input Stream it will be created.
+ ///
+ /// On success, the buffer size of the stream is returned.
+ fn get_or_create_input_stream(
+ &self,
+ config: &StreamConfig,
+ sample_format: SampleFormat,
+ ) -> Result {
+ match self.default_input_config() {
+ Ok(f) => {
+ let num_asio_channels = f.channels;
+ check_config(&self.driver, config, sample_format, num_asio_channels)
+ }
+ Err(_) => Err(BuildStreamError::StreamConfigNotSupported),
+ }?;
+ let num_channels = config.channels as usize;
+ let mut streams = self.asio_streams.lock().unwrap();
+
+ let buffer_size = match config.buffer_size {
+ BufferSize::Fixed(v) => Some(v as i32),
+ BufferSize::Default => None,
+ };
+
+ // Either create a stream if thers none or had back the
+ // size of the current one.
+ match streams.input {
+ Some(ref input) => Ok(input.buffer_size as usize),
+ None => {
+ let output = streams.output.take();
+ self.driver
+ .prepare_input_stream(output, num_channels, buffer_size)
+ .map(|new_streams| {
+ let bs = match new_streams.input {
+ Some(ref inp) => inp.buffer_size as usize,
+ None => unreachable!(),
+ };
+ *streams = new_streams;
+ bs
+ })
+ .map_err(|ref e| {
+ println!("Error preparing stream: {}", e);
+ BuildStreamError::DeviceNotAvailable
+ })
+ }
+ }
+ }
+
+ /// Create a new CPAL Output Stream.
+ ///
+ /// If there is no existing ASIO Output Stream it will be created.
+ fn get_or_create_output_stream(
+ &self,
+ config: &StreamConfig,
+ sample_format: SampleFormat,
+ ) -> Result {
+ match self.default_output_config() {
+ Ok(f) => {
+ let num_asio_channels = f.channels;
+ check_config(&self.driver, config, sample_format, num_asio_channels)
+ }
+ Err(_) => Err(BuildStreamError::StreamConfigNotSupported),
+ }?;
+ let num_channels = config.channels as usize;
+ let mut streams = self.asio_streams.lock().unwrap();
+
+ let buffer_size = match config.buffer_size {
+ BufferSize::Fixed(v) => Some(v as i32),
+ BufferSize::Default => None,
+ };
+
+ // Either create a stream if thers none or had back the
+ // size of the current one.
+ match streams.output {
+ Some(ref output) => Ok(output.buffer_size as usize),
+ None => {
+ let input = streams.input.take();
+ self.driver
+ .prepare_output_stream(input, num_channels, buffer_size)
+ .map(|new_streams| {
+ let bs = match new_streams.output {
+ Some(ref out) => out.buffer_size as usize,
+ None => unreachable!(),
+ };
+ *streams = new_streams;
+ bs
+ })
+ .map_err(|ref e| {
+ println!("Error preparing stream: {}", e);
+ BuildStreamError::DeviceNotAvailable
+ })
+ }
+ }
+ }
+}
+
+impl Drop for Stream {
+ fn drop(&mut self) {
+ self.driver.remove_callback(self.callback_id);
+ }
+}
+
+fn asio_ns_to_double(val: sys::bindings::asio_import::ASIOTimeStamp) -> f64 {
+ let two_raised_to_32 = 4294967296.0;
+ val.lo as f64 + val.hi as f64 * two_raised_to_32
+}
+
+/// Asio retrieves system time via `timeGetTime` which returns the time in milliseconds.
+fn system_time_to_stream_instant(
+ system_time: sys::bindings::asio_import::ASIOTimeStamp,
+) -> crate::StreamInstant {
+ let systime_ns = asio_ns_to_double(system_time);
+ let secs = systime_ns as i64 / 1_000_000_000;
+ let nanos = (systime_ns as i64 - secs * 1_000_000_000) as u32;
+ crate::StreamInstant::new(secs, nanos)
+}
+
+/// Convert the given duration in frames at the given sample rate to a `std::time::Duration`.
+fn frames_to_duration(frames: usize, rate: crate::SampleRate) -> std::time::Duration {
+ let secsf = frames as f64 / rate.0 as f64;
+ let secs = secsf as u64;
+ let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32;
+ std::time::Duration::new(secs, nanos)
+}
+
+/// Check whether or not the desired config is supported by the stream.
+///
+/// Checks sample rate, data type and then finally the number of channels.
+fn check_config(
+ driver: &sys::Driver,
+ config: &StreamConfig,
+ sample_format: SampleFormat,
+ num_asio_channels: u16,
+) -> Result<(), BuildStreamError> {
+ let StreamConfig {
+ channels,
+ sample_rate,
+ buffer_size: _,
+ } = config;
+ // Try and set the sample rate to what the user selected.
+ let sample_rate = sample_rate.0.into();
+ if sample_rate != driver.sample_rate().map_err(build_stream_err)? {
+ if driver
+ .can_sample_rate(sample_rate)
+ .map_err(build_stream_err)?
+ {
+ driver
+ .set_sample_rate(sample_rate)
+ .map_err(build_stream_err)?;
+ } else {
+ return Err(BuildStreamError::StreamConfigNotSupported);
+ }
+ }
+ // unsigned formats are not supported by asio
+ match sample_format {
+ SampleFormat::I16 | SampleFormat::I32 | SampleFormat::F32 => (),
+ _ => return Err(BuildStreamError::StreamConfigNotSupported),
+ }
+ if *channels > num_asio_channels {
+ return Err(BuildStreamError::StreamConfigNotSupported);
+ }
+ Ok(())
+}
+
+/// Cast a byte slice into a mutable slice of desired type.
+///
+/// Safety: it's up to the caller to ensure that the input slice has valid bit representations.
+unsafe fn cast_slice_mut(v: &mut [u8]) -> &mut [T] {
+ debug_assert!(v.len() % std::mem::size_of::() == 0);
+ std::slice::from_raw_parts_mut(v.as_mut_ptr() as *mut T, v.len() / std::mem::size_of::())
+}
+
+/// Helper function to convert from little endianness.
+fn from_le(t: T) -> T {
+ T::from_le(t)
+}
+
+/// Helper function to convert from little endianness.
+fn from_be(t: T) -> T {
+ T::from_be(t)
+}
+
+/// Shorthand for retrieving the asio buffer slice associated with a channel.
+unsafe fn asio_channel_slice(
+ asio_stream: &sys::AsioStream,
+ buffer_index: usize,
+ channel_index: usize,
+) -> &[T] {
+ let buff_ptr: *const T =
+ asio_stream.buffer_infos[channel_index].buffers[buffer_index as usize] as *const _;
+ std::slice::from_raw_parts(buff_ptr, asio_stream.buffer_size as usize)
+}
+
+/// Shorthand for retrieving the asio buffer slice associated with a channel.
+unsafe fn asio_channel_slice_mut(
+ asio_stream: &mut sys::AsioStream,
+ buffer_index: usize,
+ channel_index: usize,
+) -> &mut [T] {
+ let buff_ptr: *mut T =
+ asio_stream.buffer_infos[channel_index].buffers[buffer_index as usize] as *mut _;
+ std::slice::from_raw_parts_mut(buff_ptr, asio_stream.buffer_size as usize)
+}
+
+fn build_stream_err(e: sys::AsioError) -> BuildStreamError {
+ match e {
+ sys::AsioError::NoDrivers | sys::AsioError::HardwareMalfunction => {
+ BuildStreamError::DeviceNotAvailable
+ }
+ sys::AsioError::InvalidInput | sys::AsioError::BadMode => BuildStreamError::InvalidArgument,
+ err => {
+ let description = format!("{}", err);
+ BackendSpecificError { description }.into()
+ }
+ }
+}
diff --git a/src-tauri/patches/cpal-0.15.3/src/host/coreaudio/ios/enumerate.rs b/src-tauri/patches/cpal-0.15.3/src/host/coreaudio/ios/enumerate.rs
new file mode 100644
index 00000000..44479297
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/src/host/coreaudio/ios/enumerate.rs
@@ -0,0 +1,43 @@
+use std::vec::IntoIter as VecIntoIter;
+
+use crate::DevicesError;
+use crate::SupportedStreamConfigRange;
+
+use super::Device;
+
+pub type SupportedInputConfigs = ::std::vec::IntoIter;
+pub type SupportedOutputConfigs = ::std::vec::IntoIter;
+
+// TODO: Support enumerating earpiece vs headset vs speaker etc?
+pub struct Devices(VecIntoIter);
+
+impl Devices {
+ pub fn new() -> Result {
+ Ok(Self::default())
+ }
+}
+
+impl Default for Devices {
+ fn default() -> Devices {
+ Devices(vec![Device].into_iter())
+ }
+}
+
+impl Iterator for Devices {
+ type Item = Device;
+
+ #[inline]
+ fn next(&mut self) -> Option {
+ self.0.next()
+ }
+}
+
+#[inline]
+pub fn default_input_device() -> Option {
+ Some(Device)
+}
+
+#[inline]
+pub fn default_output_device() -> Option {
+ Some(Device)
+}
diff --git a/src-tauri/patches/cpal-0.15.3/src/host/coreaudio/ios/mod.rs b/src-tauri/patches/cpal-0.15.3/src/host/coreaudio/ios/mod.rs
new file mode 100644
index 00000000..8d7176c6
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/src/host/coreaudio/ios/mod.rs
@@ -0,0 +1,434 @@
+//!
+//! coreaudio on iOS looks a bit different from macOS. A lot of configuration needs to use
+//! the AVAudioSession objc API which doesn't exist on macOS.
+//!
+//! TODO:
+//! - Use AVAudioSession to enumerate buffer size / sample rate / number of channels and set
+//! buffer size.
+//!
+
+extern crate core_foundation_sys;
+extern crate coreaudio;
+
+use std::cell::RefCell;
+
+use self::coreaudio::audio_unit::render_callback::data;
+use self::coreaudio::audio_unit::{render_callback, AudioUnit, Element, Scope};
+use self::coreaudio::sys::{
+ kAudioOutputUnitProperty_EnableIO, kAudioUnitProperty_StreamFormat, AudioBuffer,
+ AudioStreamBasicDescription,
+};
+
+use super::{asbd_from_config, frames_to_duration, host_time_to_stream_instant};
+use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
+
+use crate::{
+ BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError,
+ DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError,
+ PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize,
+ SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError,
+};
+
+use self::enumerate::{
+ default_input_device, default_output_device, Devices, SupportedInputConfigs,
+ SupportedOutputConfigs,
+};
+use std::slice;
+use std::time::Duration;
+
+pub mod enumerate;
+
+// These days the default of iOS is now F32 and no longer I16
+const SUPPORTED_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Device;
+
+pub struct Host;
+
+impl Host {
+ pub fn new() -> Result {
+ Ok(Host)
+ }
+}
+
+impl HostTrait for Host {
+ type Devices = Devices;
+ type Device = Device;
+
+ fn is_available() -> bool {
+ true
+ }
+
+ fn devices(&self) -> Result {
+ Devices::new()
+ }
+
+ fn default_input_device(&self) -> Option {
+ default_input_device()
+ }
+
+ fn default_output_device(&self) -> Option {
+ default_output_device()
+ }
+}
+
+impl Device {
+ #[inline]
+ fn name(&self) -> Result {
+ Ok("Default Device".to_owned())
+ }
+
+ #[inline]
+ fn supported_input_configs(
+ &self,
+ ) -> Result {
+ // TODO: query AVAudioSession for parameters, some values like sample rate and buffer size
+ // probably need to actually be set to see if it works, but channels can be enumerated.
+
+ let asbd: AudioStreamBasicDescription = default_input_asbd()?;
+ let stream_config = stream_config_from_asbd(asbd);
+ Ok(vec![SupportedStreamConfigRange {
+ channels: stream_config.channels,
+ min_sample_rate: stream_config.sample_rate,
+ max_sample_rate: stream_config.sample_rate,
+ buffer_size: stream_config.buffer_size.clone(),
+ sample_format: SUPPORTED_SAMPLE_FORMAT,
+ }]
+ .into_iter())
+ }
+
+ #[inline]
+ fn supported_output_configs(
+ &self,
+ ) -> Result {
+ // TODO: query AVAudioSession for parameters, some values like sample rate and buffer size
+ // probably need to actually be set to see if it works, but channels can be enumerated.
+
+ let asbd: AudioStreamBasicDescription = default_output_asbd()?;
+ let stream_config = stream_config_from_asbd(asbd);
+
+ let configs: Vec<_> = (1..=asbd.mChannelsPerFrame as u16)
+ .map(|channels| SupportedStreamConfigRange {
+ channels,
+ min_sample_rate: stream_config.sample_rate,
+ max_sample_rate: stream_config.sample_rate,
+ buffer_size: stream_config.buffer_size.clone(),
+ sample_format: SUPPORTED_SAMPLE_FORMAT,
+ })
+ .collect();
+ Ok(configs.into_iter())
+ }
+
+ #[inline]
+ fn default_input_config(&self) -> Result {
+ let asbd: AudioStreamBasicDescription = default_input_asbd()?;
+ let stream_config = stream_config_from_asbd(asbd);
+ Ok(stream_config)
+ }
+
+ #[inline]
+ fn default_output_config(&self) -> Result {
+ let asbd: AudioStreamBasicDescription = default_output_asbd()?;
+ let stream_config = stream_config_from_asbd(asbd);
+ Ok(stream_config)
+ }
+}
+
+impl DeviceTrait for Device {
+ type SupportedInputConfigs = SupportedInputConfigs;
+ type SupportedOutputConfigs = SupportedOutputConfigs;
+ type Stream = Stream;
+
+ #[inline]
+ fn name(&self) -> Result {
+ Device::name(self)
+ }
+
+ #[inline]
+ fn supported_input_configs(
+ &self,
+ ) -> Result {
+ Device::supported_input_configs(self)
+ }
+
+ #[inline]
+ fn supported_output_configs(
+ &self,
+ ) -> Result {
+ Device::supported_output_configs(self)
+ }
+
+ #[inline]
+ fn default_input_config(&self) -> Result {
+ Device::default_input_config(self)
+ }
+
+ #[inline]
+ fn default_output_config(&self) -> Result {
+ Device::default_output_config(self)
+ }
+
+ fn build_input_stream_raw(
+ &self,
+ config: &StreamConfig,
+ sample_format: SampleFormat,
+ mut data_callback: D,
+ mut error_callback: E,
+ _timeout: Option,
+ ) -> Result
+ where
+ D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
+ E: FnMut(StreamError) + Send + 'static,
+ {
+ // The scope and element for working with a device's input stream.
+ let scope = Scope::Output;
+ let element = Element::Input;
+
+ let mut audio_unit = create_audio_unit()?;
+ audio_unit.uninitialize()?;
+ configure_for_recording(&mut audio_unit)?;
+ audio_unit.initialize()?;
+
+ // Set the stream in interleaved mode.
+ let asbd = asbd_from_config(config, sample_format);
+ audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?;
+
+ // Set the buffersize
+ match config.buffer_size {
+ BufferSize::Fixed(_) => {
+ return Err(BuildStreamError::StreamConfigNotSupported);
+ }
+ BufferSize::Default => (),
+ }
+
+ // Register the callback that is being called by coreaudio whenever it needs data to be
+ // fed to the audio buffer.
+ let bytes_per_channel = sample_format.sample_size();
+ let sample_rate = config.sample_rate;
+ type Args = render_callback::Args;
+ audio_unit.set_input_callback(move |args: Args| unsafe {
+ let ptr = (*args.data.data).mBuffers.as_ptr() as *const AudioBuffer;
+ let len = (*args.data.data).mNumberBuffers as usize;
+ let buffers: &[AudioBuffer] = slice::from_raw_parts(ptr, len);
+
+ // There is only 1 buffer when using interleaved channels
+ let AudioBuffer {
+ mNumberChannels: channels,
+ mDataByteSize: data_byte_size,
+ mData: data,
+ } = buffers[0];
+
+ let data = data as *mut ();
+ let len = (data_byte_size as usize / bytes_per_channel) as usize;
+ let data = Data::from_parts(data, len, sample_format);
+
+ // TODO: Need a better way to get delay, for now we assume a double-buffer offset.
+ let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
+ Err(err) => {
+ error_callback(err.into());
+ return Err(());
+ }
+ Ok(cb) => cb,
+ };
+ let buffer_frames = len / channels as usize;
+ let delay = frames_to_duration(buffer_frames, sample_rate);
+ let capture = callback
+ .sub(delay)
+ .expect("`capture` occurs before origin of alsa `StreamInstant`");
+ let timestamp = crate::InputStreamTimestamp { callback, capture };
+
+ let info = InputCallbackInfo { timestamp };
+ data_callback(&data, &info);
+ Ok(())
+ })?;
+
+ audio_unit.start()?;
+
+ Ok(Stream::new(StreamInner {
+ playing: true,
+ audio_unit,
+ }))
+ }
+
+ /// Create an output stream.
+ fn build_output_stream_raw(
+ &self,
+ config: &StreamConfig,
+ sample_format: SampleFormat,
+ mut data_callback: D,
+ mut error_callback: E,
+ _timeout: Option,
+ ) -> Result
+ where
+ D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
+ E: FnMut(StreamError) + Send + 'static,
+ {
+ match config.buffer_size {
+ BufferSize::Fixed(_) => {
+ return Err(BuildStreamError::StreamConfigNotSupported);
+ }
+ BufferSize::Default => (),
+ };
+
+ let mut audio_unit = create_audio_unit()?;
+
+ // The scope and element for working with a device's output stream.
+ let scope = Scope::Input;
+ let element = Element::Output;
+
+ // Set the stream in interleaved mode.
+ let asbd = asbd_from_config(config, sample_format);
+ audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?;
+
+ // Register the callback that is being called by coreaudio whenever it needs data to be
+ // fed to the audio buffer.
+ let bytes_per_channel = sample_format.sample_size();
+ let sample_rate = config.sample_rate;
+ type Args = render_callback::Args;
+ audio_unit.set_render_callback(move |args: Args| unsafe {
+ // If `run()` is currently running, then a callback will be available from this list.
+ // Otherwise, we just fill the buffer with zeroes and return.
+
+ let AudioBuffer {
+ mNumberChannels: channels,
+ mDataByteSize: data_byte_size,
+ mData: data,
+ } = (*args.data.data).mBuffers[0];
+
+ let data = data as *mut ();
+ let len = (data_byte_size as usize / bytes_per_channel) as usize;
+ let mut data = Data::from_parts(data, len, sample_format);
+
+ let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
+ Err(err) => {
+ error_callback(err.into());
+ return Err(());
+ }
+ Ok(cb) => cb,
+ };
+ // TODO: Need a better way to get delay, for now we assume a double-buffer offset.
+ let buffer_frames = len / channels as usize;
+ let delay = frames_to_duration(buffer_frames, sample_rate);
+ let playback = callback
+ .add(delay)
+ .expect("`playback` occurs beyond representation supported by `StreamInstant`");
+ let timestamp = crate::OutputStreamTimestamp { callback, playback };
+
+ let info = OutputCallbackInfo { timestamp };
+ data_callback(&mut data, &info);
+ Ok(())
+ })?;
+
+ audio_unit.start()?;
+
+ Ok(Stream::new(StreamInner {
+ playing: true,
+ audio_unit,
+ }))
+ }
+}
+
+pub struct Stream {
+ inner: RefCell,
+}
+
+impl Stream {
+ fn new(inner: StreamInner) -> Self {
+ Self {
+ inner: RefCell::new(inner),
+ }
+ }
+}
+
+impl StreamTrait for Stream {
+ fn play(&self) -> Result<(), PlayStreamError> {
+ let mut stream = self.inner.borrow_mut();
+
+ if !stream.playing {
+ if let Err(e) = stream.audio_unit.start() {
+ let description = format!("{}", e);
+ let err = BackendSpecificError { description };
+ return Err(err.into());
+ }
+ stream.playing = true;
+ }
+ Ok(())
+ }
+
+ fn pause(&self) -> Result<(), PauseStreamError> {
+ let mut stream = self.inner.borrow_mut();
+
+ if stream.playing {
+ if let Err(e) = stream.audio_unit.stop() {
+ let description = format!("{}", e);
+ let err = BackendSpecificError { description };
+ return Err(err.into());
+ }
+
+ stream.playing = false;
+ }
+ Ok(())
+ }
+}
+
+struct StreamInner {
+ playing: bool,
+ audio_unit: AudioUnit,
+}
+
+fn create_audio_unit() -> Result {
+ AudioUnit::new(coreaudio::audio_unit::IOType::RemoteIO)
+}
+
+fn configure_for_recording(audio_unit: &mut AudioUnit) -> Result<(), coreaudio::Error> {
+ // Enable mic recording
+ let enable_input = 1u32;
+ audio_unit.set_property(
+ kAudioOutputUnitProperty_EnableIO,
+ Scope::Input,
+ Element::Input,
+ Some(&enable_input),
+ )?;
+
+ // Disable output
+ let disable_output = 0u32;
+ audio_unit.set_property(
+ kAudioOutputUnitProperty_EnableIO,
+ Scope::Output,
+ Element::Output,
+ Some(&disable_output),
+ )?;
+
+ Ok(())
+}
+
+fn default_output_asbd() -> Result {
+ let audio_unit = create_audio_unit()?;
+ let id = kAudioUnitProperty_StreamFormat;
+ let asbd: AudioStreamBasicDescription =
+ audio_unit.get_property(id, Scope::Output, Element::Output)?;
+ Ok(asbd)
+}
+
+fn default_input_asbd() -> Result {
+ let mut audio_unit = create_audio_unit()?;
+ audio_unit.uninitialize()?;
+ configure_for_recording(&mut audio_unit)?;
+ audio_unit.initialize()?;
+
+ let id = kAudioUnitProperty_StreamFormat;
+ let asbd: AudioStreamBasicDescription =
+ audio_unit.get_property(id, Scope::Input, Element::Input)?;
+ Ok(asbd)
+}
+
+fn stream_config_from_asbd(asbd: AudioStreamBasicDescription) -> SupportedStreamConfig {
+ let buffer_size = SupportedBufferSize::Range { min: 0, max: 0 };
+ SupportedStreamConfig {
+ channels: asbd.mChannelsPerFrame as u16,
+ sample_rate: SampleRate(asbd.mSampleRate as u32),
+ buffer_size: buffer_size.clone(),
+ sample_format: SUPPORTED_SAMPLE_FORMAT,
+ }
+}
diff --git a/src-tauri/patches/cpal-0.15.3/src/host/coreaudio/macos/enumerate.rs b/src-tauri/patches/cpal-0.15.3/src/host/coreaudio/macos/enumerate.rs
new file mode 100644
index 00000000..8d15f18e
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/src/host/coreaudio/macos/enumerate.rs
@@ -0,0 +1,152 @@
+extern crate coreaudio;
+
+use self::coreaudio::sys::{
+ kAudioHardwareNoError, kAudioHardwarePropertyDefaultInputDevice,
+ kAudioHardwarePropertyDefaultOutputDevice, kAudioHardwarePropertyDevices,
+ kAudioObjectPropertyElementMaster, kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject,
+ AudioDeviceID, AudioObjectGetPropertyData, AudioObjectGetPropertyDataSize,
+ AudioObjectPropertyAddress, OSStatus,
+};
+use super::Device;
+use crate::{BackendSpecificError, DevicesError, SupportedStreamConfigRange};
+use std::mem;
+use std::ptr::null;
+use std::vec::IntoIter as VecIntoIter;
+
+unsafe fn audio_devices() -> Result, OSStatus> {
+ let property_address = AudioObjectPropertyAddress {
+ mSelector: kAudioHardwarePropertyDevices,
+ mScope: kAudioObjectPropertyScopeGlobal,
+ mElement: kAudioObjectPropertyElementMaster,
+ };
+
+ macro_rules! try_status_or_return {
+ ($status:expr) => {
+ if $status != kAudioHardwareNoError as i32 {
+ return Err($status);
+ }
+ };
+ }
+
+ let data_size = 0u32;
+ let status = AudioObjectGetPropertyDataSize(
+ kAudioObjectSystemObject,
+ &property_address as *const _,
+ 0,
+ null(),
+ &data_size as *const _ as *mut _,
+ );
+ try_status_or_return!(status);
+
+ let device_count = data_size / mem::size_of::() as u32;
+ let mut audio_devices = vec![];
+ audio_devices.reserve_exact(device_count as usize);
+
+ let status = AudioObjectGetPropertyData(
+ kAudioObjectSystemObject,
+ &property_address as *const _,
+ 0,
+ null(),
+ &data_size as *const _ as *mut _,
+ audio_devices.as_mut_ptr() as *mut _,
+ );
+ try_status_or_return!(status);
+
+ audio_devices.set_len(device_count as usize);
+
+ Ok(audio_devices)
+}
+
+pub struct Devices(VecIntoIter);
+
+impl Devices {
+ pub fn new() -> Result {
+ let devices = unsafe {
+ match audio_devices() {
+ Ok(devices) => devices,
+ Err(os_status) => {
+ let description = format!("{}", os_status);
+ let err = BackendSpecificError { description };
+ return Err(err.into());
+ }
+ }
+ };
+ Ok(Devices(devices.into_iter()))
+ }
+}
+
+unsafe impl Send for Devices {}
+unsafe impl Sync for Devices {}
+
+impl Iterator for Devices {
+ type Item = Device;
+ fn next(&mut self) -> Option {
+ self.0.next().map(|id| Device {
+ audio_device_id: id,
+ is_default: false,
+ })
+ }
+}
+
+pub fn default_input_device() -> Option {
+ let property_address = AudioObjectPropertyAddress {
+ mSelector: kAudioHardwarePropertyDefaultInputDevice,
+ mScope: kAudioObjectPropertyScopeGlobal,
+ mElement: kAudioObjectPropertyElementMaster,
+ };
+
+ let audio_device_id: AudioDeviceID = 0;
+ let data_size = mem::size_of::();
+ let status = unsafe {
+ AudioObjectGetPropertyData(
+ kAudioObjectSystemObject,
+ &property_address as *const _,
+ 0,
+ null(),
+ &data_size as *const _ as *mut _,
+ &audio_device_id as *const _ as *mut _,
+ )
+ };
+ if status != kAudioHardwareNoError as i32 {
+ return None;
+ }
+
+ let device = Device {
+ audio_device_id,
+ is_default: true,
+ };
+ Some(device)
+}
+
+pub fn default_output_device() -> Option {
+ let property_address = AudioObjectPropertyAddress {
+ mSelector: kAudioHardwarePropertyDefaultOutputDevice,
+ mScope: kAudioObjectPropertyScopeGlobal,
+ mElement: kAudioObjectPropertyElementMaster,
+ };
+
+ let audio_device_id: AudioDeviceID = 0;
+ let data_size = mem::size_of::();
+ let status = unsafe {
+ AudioObjectGetPropertyData(
+ kAudioObjectSystemObject,
+ &property_address as *const _,
+ 0,
+ null(),
+ &data_size as *const _ as *mut _,
+ &audio_device_id as *const _ as *mut _,
+ )
+ };
+ if status != kAudioHardwareNoError as i32 {
+ return None;
+ }
+
+ let device = Device {
+ audio_device_id,
+ is_default: true,
+ };
+ Some(device)
+}
+
+pub type SupportedInputConfigs = VecIntoIter;
+pub type SupportedOutputConfigs = VecIntoIter;
diff --git a/src-tauri/patches/cpal-0.15.3/src/host/coreaudio/macos/mod.rs b/src-tauri/patches/cpal-0.15.3/src/host/coreaudio/macos/mod.rs
new file mode 100644
index 00000000..2ba61124
--- /dev/null
+++ b/src-tauri/patches/cpal-0.15.3/src/host/coreaudio/macos/mod.rs
@@ -0,0 +1,949 @@
+extern crate core_foundation_sys;
+extern crate coreaudio;
+
+use super::{asbd_from_config, check_os_status, frames_to_duration, host_time_to_stream_instant};
+
+use self::core_foundation_sys::string::{CFStringGetCString, CFStringGetCStringPtr, CFStringRef};
+use self::coreaudio::audio_unit::render_callback::{self, data};
+use self::coreaudio::audio_unit::{AudioUnit, Element, Scope};
+use self::coreaudio::sys::{
+ kAudioDevicePropertyAvailableNominalSampleRates, kAudioDevicePropertyBufferFrameSize,
+ kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyDeviceIsAlive,
+ kAudioDevicePropertyDeviceNameCFString, kAudioDevicePropertyNominalSampleRate,
+ kAudioDevicePropertyScopeOutput, kAudioDevicePropertyStreamConfiguration,
+ kAudioDevicePropertyStreamFormat, kAudioObjectPropertyElementMaster,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput,
+ kAudioObjectPropertyScopeOutput, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioOutputUnitProperty_EnableIO, kAudioUnitProperty_StreamFormat, kCFStringEncodingUTF8,
+ AudioBuffer, AudioBufferList, AudioDeviceID, AudioObjectGetPropertyData,
+ AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress,
+ AudioObjectPropertyScope, AudioObjectSetPropertyData, AudioStreamBasicDescription,
+ AudioValueRange, OSStatus,
+};
+use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
+use crate::{
+ BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data,
+ DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo,
+ PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError,
+ SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
+ SupportedStreamConfigsError,
+};
+use std::ffi::CStr;
+use std::fmt;
+use std::mem;
+use std::os::raw::c_char;
+use std::ptr::null;
+use std::rc::Rc;
+use std::slice;
+use std::sync::mpsc::{channel, RecvTimeoutError};
+use std::sync::{Arc, Mutex};
+use std::time::{Duration, Instant};
+
+pub use self::enumerate::{
+ default_input_device, default_output_device, Devices, SupportedInputConfigs,
+ SupportedOutputConfigs,
+};
+
+use property_listener::AudioObjectPropertyListener;
+
+pub mod enumerate;
+mod property_listener;
+
+/// Coreaudio host, the default host on macOS.
+#[derive(Debug)]
+pub struct Host;
+
+impl Host {
+ pub fn new() -> Result {
+ Ok(Host)
+ }
+}
+
+impl HostTrait for Host {
+ type Devices = Devices;
+ type Device = Device;
+
+ fn is_available() -> bool {
+ // Assume coreaudio is always available
+ true
+ }
+
+ fn devices(&self) -> Result {
+ Devices::new()
+ }
+
+ fn default_input_device(&self) -> Option {
+ default_input_device()
+ }
+
+ fn default_output_device(&self) -> Option {
+ default_output_device()
+ }
+}
+
+impl DeviceTrait for Device {
+ type SupportedInputConfigs = SupportedInputConfigs;
+ type SupportedOutputConfigs = SupportedOutputConfigs;
+ type Stream = Stream;
+
+ fn name(&self) -> Result {
+ Device::name(self)
+ }
+
+ fn supported_input_configs(
+ &self,
+ ) -> Result {
+ Device::supported_input_configs(self)
+ }
+
+ fn supported_output_configs(
+ &self,
+ ) -> Result {
+ Device::supported_output_configs(self)
+ }
+
+ fn default_input_config(&self) -> Result