├── .cargo └── config.toml ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── bridge ├── .clang-format ├── CMakeLists.txt ├── cxx_juce.cpp ├── cxx_juce.h ├── cxx_juce_audio_basics │ ├── cxx_juce_iir_filter.cpp │ └── cxx_juce_iir_filter.h ├── cxx_juce_audio_devices │ ├── cxx_juce_audio_callback_wrapper.cpp │ ├── cxx_juce_audio_callback_wrapper.h │ ├── cxx_juce_audio_device_manager.cpp │ ├── cxx_juce_audio_device_manager.h │ ├── cxx_juce_audio_device_setup.cpp │ ├── cxx_juce_audio_device_setup.h │ ├── cxx_juce_audio_io_device.cpp │ ├── cxx_juce_audio_io_device.h │ ├── cxx_juce_audio_io_device_type.cpp │ ├── cxx_juce_audio_io_device_type.h │ ├── cxx_juce_system_audio_volume.cpp │ └── cxx_juce_system_audio_volume.h ├── cxx_juce_bindings.cpp └── cxx_juce_bindings.h ├── build.rs ├── examples ├── audio_callback.rs ├── devices.rs ├── system_audio_volume.rs └── test_tone.rs ├── src ├── juce_audio_basics.rs ├── juce_audio_devices.rs └── lib.rs └── tests └── juce_audio_devices.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | RUST_TEST_THREADS = "1" 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [macos-latest, windows-latest, ubuntu-latest] 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Switch from default to more recent Xcode version 23 | if: matrix.os == 'macos-latest' 24 | run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer 25 | - name: Install Linux dependencies 26 | if: matrix.os == 'ubuntu-latest' 27 | run: | 28 | sudo apt update 29 | sudo apt install \ 30 | libasound2-dev libjack-jackd2-dev \ 31 | libfreetype-dev libfontconfig1-dev \ 32 | libx11-dev libxcomposite-dev libxcursor-dev libxcursor-dev libxext-dev libxinerama-dev libxrandr-dev libxrender-dev 33 | - name: Build 34 | run: cargo build --verbose 35 | - name: Run tests 36 | run: cargo test --verbose 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | cmake-build-* 4 | target -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cxx-juce" 3 | description = "Rust bindings for JUCE using cxx." 4 | version = "0.8.0" 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/JamesHallowell/cxx-juce" 8 | documentation = "https://docs.rs/cxx-juce" 9 | 10 | [features] 11 | asio = [] 12 | 13 | [dependencies] 14 | cxx = "1.0.124" 15 | slotmap = "1.0.7" 16 | 17 | [build-dependencies] 18 | cmake = "0.1.50" 19 | cxx-build = "1.0.124" 20 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cxx-juce 🧃 2 | 3 | [![Build Status](https://github.com/JamesHallowell/cxx-juce/actions/workflows/ci.yml/badge.svg)](https://github.com/JamesHallowell/cxx-juce/actions/workflows/ci.yml) 4 | [![Crates.io](https://img.shields.io/crates/v/cxx-juce.svg)](https://crates.io/crates/cxx-juce) 5 | [![Docs.rs](https://docs.rs/cxx-juce/badge.svg)](https://docs.rs/cxx-juce) 6 | 7 | **Rust bindings for [JUCE](https://juce.com/) using [cxx](https://github.com/dtolnay/cxx).** 8 | 9 | ## Overview 10 | 11 | JUCE is something of an industry standard for audio applications, so it would be nice to be able to make use of it from 12 | Rust. 13 | 14 | Providing bindings for the entirety of JUCE would be a huge undertaking, and much of it would be duplicating 15 | functionality already available to Rust in the standard library or via crates.io. 16 | 17 | The goal for this crate is to provide bindings for a subset of JUCE, in particular the mature and thoroughly 18 | battle-tested audio modules. 19 | 20 | ## Usage 21 | 22 | Add this to your `Cargo.toml`: 23 | 24 | ```toml 25 | [dependencies] 26 | cxx-juce = "0.8" 27 | ``` 28 | 29 | ## Dependencies 30 | 31 | Refer to the [JUCE documentation](https://github.com/juce-framework/JUCE#building-juce-projects) for the dependencies 32 | required to build JUCE on your platform. 33 | 34 | ## Crate Features 35 | 36 | #### `asio` 37 | 38 | To build with ASIO support: 39 | 40 | 1. Agree to Steinberg's licensing terms and download the ASIO SDK. 41 | 2. Enable the `asio` feature for this crate. 42 | 3. Set the `CXX_JUCE_ASIO_SDK_DIR` environment variable to the path of the extracted ASIO SDK. 43 | 44 | ## License 45 | 46 | Licensed under either of 47 | 48 | * Apache License, Version 2.0 49 | ([LICENSE-APACHE](LICENSE-APACHE)) 50 | * MIT license 51 | ([LICENSE-MIT](LICENSE-MIT)) 52 | 53 | at your option. 54 | 55 | The juce_audio_basics, juce_audio_devices, juce_core and juce_events modules are permissively licensed under the terms 56 | of the [ISC license](https://www.isc.org/licenses/). 57 | 58 | ## Contribution 59 | 60 | Unless you explicitly state otherwise, any contribution intentionally submitted 61 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 62 | dual licensed as above, without any additional terms or conditions. -------------------------------------------------------------------------------- /bridge/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AccessModifierOffset: -4 3 | AlignAfterOpenBracket: Align 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveDeclarations: false 6 | AlignEscapedNewlines: Left 7 | AlignOperands: Align 8 | AlignTrailingComments: false 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: Never 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: Never 14 | AllowShortLoopsOnASingleLine: false 15 | AlwaysBreakAfterDefinitionReturnType: None 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: false 18 | AlwaysBreakTemplateDeclarations: Yes 19 | BinPackArguments: false 20 | BinPackParameters: false 21 | BreakAfterJavaFieldAnnotations: false 22 | BreakBeforeBinaryOperators: NonAssignment 23 | BreakBeforeBraces: Allman 24 | BreakBeforeTernaryOperators: true 25 | BreakConstructorInitializersBeforeComma: true 26 | BreakStringLiterals: false 27 | ColumnLimit: 0 28 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 29 | ConstructorInitializerIndentWidth: 4 30 | ContinuationIndentWidth: 4 31 | Cpp11BracedListStyle: false 32 | DerivePointerAlignment: false 33 | DisableFormat: false 34 | ExperimentalAutoDetectBinPacking: false 35 | IndentCaseLabels: true 36 | IndentWidth: 4 37 | IndentWrappedFunctionNames: true 38 | KeepEmptyLinesAtTheStartOfBlocks: false 39 | Language: Cpp 40 | MaxEmptyLinesToKeep: 1 41 | NamespaceIndentation: Inner 42 | PointerAlignment: Left 43 | ReflowComments: false 44 | SortIncludes: true 45 | SpaceAfterCStyleCast: true 46 | SpaceAfterLogicalNot: true 47 | SpaceBeforeAssignmentOperators: true 48 | SpaceBeforeCpp11BracedList: true 49 | SpaceBeforeParens: NonEmptyParentheses 50 | SpaceInEmptyParentheses: false 51 | SpaceBeforeInheritanceColon: true 52 | SpacesInAngles: false 53 | SpacesInCStyleCastParentheses: false 54 | SpacesInContainerLiterals: true 55 | SpacesInParentheses: false 56 | SpacesInSquareBrackets: false 57 | Standard: "c++17" 58 | TabWidth: 4 59 | UseTab: Never 60 | --- 61 | Language: ObjC 62 | BasedOnStyle: Chromium 63 | AlignTrailingComments: true 64 | BreakBeforeBraces: Allman 65 | ColumnLimit: 0 66 | IndentWidth: 4 67 | KeepEmptyLinesAtTheStartOfBlocks: false 68 | ObjCSpaceAfterProperty: true 69 | ObjCSpaceBeforeProtocolList: true 70 | PointerAlignment: Left 71 | SpacesBeforeTrailingComments: 1 72 | TabWidth: 4 73 | UseTab: Never -------------------------------------------------------------------------------- /bridge/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(cxx-juce VERSION 0.1) 4 | 5 | set(CXX_JUCE_BINDINGS_DIR "" CACHE PATH "Path to the bindings directory") 6 | set(CXX_JUCE_USE_ASIO OFF CACHE BOOL "Use ASIO") 7 | set(CXX_JUCE_ASIO_SDK_DIR "" CACHE PATH "Path to the ASIO SDK directory") 8 | set(CXX_JUCE_VERSION_OF_JUCE 7.0.12) 9 | 10 | set(JUCE_MODULES_ONLY ON) 11 | 12 | include(FetchContent) 13 | set(JUCE_URL https://github.com/juce-framework/JUCE/archive/refs/tags/${CXX_JUCE_VERSION_OF_JUCE}.tar.gz) 14 | FetchContent_Declare( 15 | JUCE 16 | URL ${JUCE_URL} 17 | URL_HASH SHA256=94e3b35e1990cd67f59736bb5e1eff1cfd9590b16fea82696f309a8f59452434 18 | ) 19 | message(STATUS "Fetching JUCE ${CXX_JUCE_VERSION_OF_JUCE}...") 20 | FetchContent_MakeAvailable(JUCE) 21 | 22 | add_library(cxx-juce STATIC) 23 | 24 | target_compile_features(cxx-juce 25 | PUBLIC 26 | cxx_std_17 27 | ) 28 | 29 | target_compile_definitions(cxx-juce 30 | PUBLIC 31 | JUCE_USE_CURL=0 32 | ) 33 | 34 | message(STATUS "Using bindings at ${CXX_JUCE_BINDINGS_DIR}") 35 | 36 | target_include_directories(cxx-juce 37 | PRIVATE 38 | ${CMAKE_CURRENT_SOURCE_DIR} 39 | ${CXX_JUCE_BINDINGS_DIR}/crate 40 | ${CXX_JUCE_BINDINGS_DIR}/include 41 | ${CXX_JUCE_BINDINGS_DIR}/sources 42 | ) 43 | 44 | target_sources(cxx-juce 45 | PRIVATE 46 | cxx_juce.h 47 | cxx_juce.cpp 48 | cxx_juce_bindings.h 49 | cxx_juce_bindings.cpp 50 | cxx_juce_audio_basics/cxx_juce_iir_filter.h 51 | cxx_juce_audio_basics/cxx_juce_iir_filter.cpp 52 | cxx_juce_audio_devices/cxx_juce_audio_callback_wrapper.h 53 | cxx_juce_audio_devices/cxx_juce_audio_callback_wrapper.cpp 54 | cxx_juce_audio_devices/cxx_juce_audio_device_setup.h 55 | cxx_juce_audio_devices/cxx_juce_audio_device_setup.cpp 56 | cxx_juce_audio_devices/cxx_juce_audio_device_manager.h 57 | cxx_juce_audio_devices/cxx_juce_audio_device_manager.cpp 58 | cxx_juce_audio_devices/cxx_juce_system_audio_volume.h 59 | cxx_juce_audio_devices/cxx_juce_system_audio_volume.cpp 60 | cxx_juce_audio_devices/cxx_juce_audio_io_device_type.h 61 | cxx_juce_audio_devices/cxx_juce_audio_io_device_type.cpp 62 | cxx_juce_audio_devices/cxx_juce_audio_io_device.h 63 | cxx_juce_audio_devices/cxx_juce_audio_io_device.cpp 64 | ) 65 | 66 | target_link_libraries(cxx-juce 67 | PUBLIC 68 | juce::juce_audio_basics 69 | juce::juce_audio_devices 70 | juce::juce_core 71 | juce::juce_events 72 | juce::juce_recommended_config_flags 73 | juce::juce_recommended_warning_flags 74 | ) 75 | 76 | if (CXX_JUCE_USE_ASIO) 77 | message(STATUS "Using ASIO SDK at ${CXX_JUCE_ASIO_SDK_DIR}") 78 | 79 | target_compile_definitions(cxx-juce 80 | PUBLIC 81 | JUCE_ASIO=1 82 | ) 83 | 84 | target_include_directories(cxx-juce 85 | PRIVATE 86 | ${CXX_JUCE_ASIO_SDK_DIR}/common 87 | ) 88 | endif() 89 | -------------------------------------------------------------------------------- /bridge/cxx_juce.cpp: -------------------------------------------------------------------------------- 1 | #include "cxx_juce_bindings.h" 2 | 3 | namespace cxx_juce 4 | { 5 | rust::String juceVersion() 6 | { 7 | return juce::SystemStats::getJUCEVersion().toStdString(); 8 | } 9 | 10 | void initialiseJuce() 11 | { 12 | juce::initialiseJuce_GUI(); 13 | } 14 | 15 | void shutdownJuce() 16 | { 17 | juce::shutdownJuce_GUI(); 18 | } 19 | 20 | rust::Str toStr (const juce::String& string) 21 | { 22 | return { string.toRawUTF8(), string.getNumBytesAsUTF8() }; 23 | } 24 | } // namespace cxx_juce -------------------------------------------------------------------------------- /bridge/cxx_juce.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cxx_juce_audio_basics/cxx_juce_iir_filter.h" 4 | #include "cxx_juce_audio_devices/cxx_juce_audio_callback_wrapper.h" 5 | #include "cxx_juce_audio_devices/cxx_juce_audio_device_manager.h" 6 | #include "cxx_juce_audio_devices/cxx_juce_audio_device_setup.h" 7 | #include "cxx_juce_audio_devices/cxx_juce_audio_io_device.h" 8 | #include "cxx_juce_audio_devices/cxx_juce_audio_io_device_type.h" 9 | #include "cxx_juce_audio_devices/cxx_juce_system_audio_volume.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace juce 17 | { 18 | using AudioIODeviceTypeArray = OwnedArray; 19 | void initialiseNSApplication(); 20 | } // namespace juce 21 | 22 | namespace cxx_juce 23 | { 24 | struct BoxedAudioIODeviceCallback; 25 | struct BoxedAudioIODeviceType; 26 | 27 | rust::String juceVersion(); 28 | 29 | void initialiseJuce(); 30 | void shutdownJuce(); 31 | 32 | rust::Str toStr (const juce::String& string); 33 | 34 | } // namespace cxx_juce 35 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_basics/cxx_juce_iir_filter.cpp: -------------------------------------------------------------------------------- 1 | #include "cxx_juce_iir_filter.h" 2 | 3 | namespace cxx_juce::iir_filter 4 | { 5 | std::unique_ptr createIIRFilter (std::array coefficients) 6 | { 7 | auto filter = std::make_unique(); 8 | 9 | juce::IIRCoefficients coeffs; 10 | coeffs.coefficients[0] = coefficients[0]; 11 | coeffs.coefficients[1] = coefficients[1]; 12 | coeffs.coefficients[2] = coefficients[2]; 13 | coeffs.coefficients[3] = coefficients[3]; 14 | coeffs.coefficients[4] = coefficients[4]; 15 | 16 | filter->setCoefficients (coeffs); 17 | 18 | return filter; 19 | } 20 | 21 | std::array makeLowPass (double sampleRate, 22 | double cutoffFrequency, 23 | double q) 24 | { 25 | const auto coefficients = juce::IIRCoefficients::makeLowPass ( 26 | sampleRate, 27 | cutoffFrequency, 28 | q); 29 | 30 | return { coefficients.coefficients[0], 31 | coefficients.coefficients[1], 32 | coefficients.coefficients[2], 33 | coefficients.coefficients[3], 34 | coefficients.coefficients[4] }; 35 | } 36 | 37 | std::array makeHighPass (double sampleRate, 38 | double cutoffFrequency, 39 | double q) 40 | { 41 | const auto coefficients = juce::IIRCoefficients::makeHighPass ( 42 | sampleRate, 43 | cutoffFrequency, 44 | q); 45 | 46 | return { coefficients.coefficients[0], 47 | coefficients.coefficients[1], 48 | coefficients.coefficients[2], 49 | coefficients.coefficients[3], 50 | coefficients.coefficients[4] }; 51 | } 52 | 53 | std::array makeNotchFilter (double sampleRate, 54 | double cutoffFrequency, 55 | double q) 56 | { 57 | const auto coefficients = juce::IIRCoefficients::makeNotchFilter ( 58 | sampleRate, 59 | cutoffFrequency, 60 | q); 61 | 62 | return { coefficients.coefficients[0], 63 | coefficients.coefficients[1], 64 | coefficients.coefficients[2], 65 | coefficients.coefficients[3], 66 | coefficients.coefficients[4] }; 67 | } 68 | } // namespace cxx_juce::iir_filter 69 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_basics/cxx_juce_iir_filter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cxx_juce::iir_filter 7 | { 8 | std::unique_ptr createIIRFilter (std::array coefficients); 9 | std::array makeLowPass (double sampleRate, double cutoffFrequency, double q); 10 | std::array makeHighPass (double sampleRate, double cutoffFrequency, double q); 11 | std::array makeNotchFilter (double sampleRate, double cutoffFrequency, double q); 12 | } // namespace cxx_juce::iir_filter 13 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_devices/cxx_juce_audio_callback_wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cxx_juce 4 | { 5 | AudioCallbackWrapper::AudioCallbackWrapper (rust::Box callback) 6 | : _callback (std::move (callback)) 7 | { 8 | } 9 | 10 | void AudioCallbackWrapper::audioDeviceIOCallbackWithContext ( 11 | const float* const* inputChannelData, 12 | int numInputChannels, 13 | float* const* outputChannelData, 14 | int numOutputChannels, 15 | int numSamples, 16 | const juce::AudioIODeviceCallbackContext&) 17 | { 18 | juce::AudioSampleBuffer inputBuffer; 19 | if (inputChannelData) 20 | { 21 | inputBuffer.setDataToReferTo (const_cast (inputChannelData), 22 | numInputChannels, 23 | numSamples); 24 | } 25 | 26 | juce::AudioSampleBuffer outputBuffer; 27 | if (outputChannelData) 28 | { 29 | outputBuffer.setDataToReferTo (outputChannelData, 30 | numOutputChannels, 31 | numSamples); 32 | } 33 | 34 | audio_io_device_callback::processBlock (*_callback, 35 | inputBuffer, 36 | outputBuffer); 37 | } 38 | 39 | void AudioCallbackWrapper::audioDeviceAboutToStart (juce::AudioIODevice* device) 40 | { 41 | if (! device) 42 | { 43 | return; 44 | } 45 | 46 | audio_io_device_callback::aboutToStart (*_callback, 47 | *device); 48 | } 49 | 50 | void AudioCallbackWrapper::audioDeviceStopped() 51 | { 52 | audio_io_device_callback::stopped (*_callback); 53 | } 54 | 55 | std::unique_ptr wrapAudioCallback (rust::Box callback) 56 | { 57 | return std::make_unique (std::move (callback)); 58 | } 59 | 60 | } // namespace cxx_juce 61 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_devices/cxx_juce_audio_callback_wrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cxx_juce 7 | { 8 | 9 | struct BoxedAudioIODeviceCallback; 10 | 11 | class AudioCallbackWrapper : public juce::AudioIODeviceCallback 12 | { 13 | public: 14 | explicit AudioCallbackWrapper (rust::Box callback); 15 | 16 | void audioDeviceAboutToStart (juce::AudioIODevice* device) override; 17 | void audioDeviceIOCallbackWithContext (const float* const* inputChannelData, 18 | int numInputChannels, 19 | float* const* outputChannelData, 20 | int numOutputChannels, 21 | int numSamples, 22 | const juce::AudioIODeviceCallbackContext& context) override; 23 | void audioDeviceStopped() override; 24 | 25 | private: 26 | rust::Box _callback; 27 | }; 28 | 29 | std::unique_ptr wrapAudioCallback (rust::Box callback); 30 | 31 | } // namespace cxx_juce 32 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_devices/cxx_juce_audio_device_manager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cxx_juce 4 | { 5 | void AudioDeviceManager::initialiseWithDefaultDevices (rust::i32 inputChannels, 6 | rust::i32 outputChannels) 7 | { 8 | const auto result = _audioDeviceManager.initialiseWithDefaultDevices (inputChannels, outputChannels); 9 | if (result.isNotEmpty()) 10 | { 11 | throw std::runtime_error (result.toStdString()); 12 | } 13 | } 14 | 15 | [[nodiscard]] std::unique_ptr AudioDeviceManager::getAudioDeviceSetup() const 16 | { 17 | return std::make_unique (_audioDeviceManager.getAudioDeviceSetup()); 18 | } 19 | 20 | void AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& setup) 21 | { 22 | _audioDeviceManager.setAudioDeviceSetup (setup._audioDeviceSetup, true); 23 | } 24 | 25 | void AudioDeviceManager::addAudioCallback (const std::unique_ptr& callback) 26 | { 27 | _audioDeviceManager.addAudioCallback (callback.get()); 28 | } 29 | 30 | void AudioDeviceManager::removeAudioCallback (const std::unique_ptr& callback) 31 | { 32 | _audioDeviceManager.removeAudioCallback (callback.get()); 33 | } 34 | 35 | void AudioDeviceManager::addAudioDeviceType (rust::Box audioIODeviceType) 36 | { 37 | struct RustAudioIODeviceType : juce::AudioIODeviceType 38 | { 39 | explicit RustAudioIODeviceType (rust::Box audioIODeviceType) 40 | : juce::AudioIODeviceType ( 41 | static_cast (::audio_io_device_type::name (*audioIODeviceType))) 42 | , _audioIODeviceType (std::move (audioIODeviceType)) 43 | { 44 | } 45 | 46 | void scanForDevices() override 47 | { 48 | ::audio_io_device_type::scanForDevices (*_audioIODeviceType); 49 | } 50 | 51 | [[nodiscard]] juce::StringArray getDeviceNames (bool wantInputNames) const override 52 | { 53 | const auto names = ::audio_io_device_type::getDeviceNames (*_audioIODeviceType, wantInputNames); 54 | 55 | juce::StringArray stringArray; 56 | for (const auto& name : names) 57 | { 58 | stringArray.add (static_cast (name)); 59 | } 60 | return stringArray; 61 | } 62 | 63 | [[nodiscard]] int getDefaultDeviceIndex (bool /*forInput*/) const override 64 | { 65 | return 0; 66 | } 67 | 68 | int getIndexOfDevice (juce::AudioIODevice* device, 69 | bool asInput) const override 70 | { 71 | return getDeviceNames (asInput).indexOf (device->getName()); 72 | } 73 | 74 | [[nodiscard]] bool hasSeparateInputsAndOutputs() const override 75 | { 76 | return true; 77 | } 78 | 79 | juce::AudioIODevice* createDevice (const juce::String& outputDeviceName, 80 | const juce::String& inputDeviceName) override 81 | { 82 | struct RustAudioIODevice : juce::AudioIODevice 83 | { 84 | explicit RustAudioIODevice (BoxedAudioIODevice* device) 85 | : juce::AudioIODevice ( 86 | static_cast (::audio_io_device::deviceName (*device)), 87 | static_cast (::audio_io_device::typeName (*device))) 88 | , _device (device) 89 | { 90 | } 91 | 92 | ~RustAudioIODevice() override 93 | { 94 | ::audio_io_device_type::destroyDevice (_device); 95 | } 96 | 97 | juce::StringArray getOutputChannelNames() override 98 | { 99 | return {}; 100 | } 101 | 102 | juce::StringArray getInputChannelNames() override 103 | { 104 | return {}; 105 | } 106 | 107 | juce::Array getAvailableSampleRates() override 108 | { 109 | juce::Array sampleRates; 110 | for (auto sampleRate : ::audio_io_device::availableSampleRates (*_device)) 111 | { 112 | sampleRates.add (sampleRate); 113 | } 114 | return sampleRates; 115 | } 116 | 117 | juce::Array getAvailableBufferSizes() override 118 | { 119 | juce::Array bufferSizes; 120 | for (auto bufferSize : ::audio_io_device::availableBufferSizes (*_device)) 121 | { 122 | bufferSizes.add (static_cast (bufferSize)); 123 | } 124 | return bufferSizes; 125 | } 126 | 127 | int getDefaultBufferSize() override 128 | { 129 | return 0; 130 | } 131 | 132 | juce::String open (const juce::BigInteger& /*inputChannels*/, 133 | const juce::BigInteger& /*outputChannels*/, 134 | double sampleRate, 135 | int bufferSize) override 136 | { 137 | const auto result = ::audio_io_device::open (*_device, 138 | sampleRate, 139 | static_cast (bufferSize)); 140 | return static_cast (result); 141 | } 142 | 143 | void close() override 144 | { 145 | ::audio_io_device::close (*_device); 146 | } 147 | 148 | bool isOpen() override 149 | { 150 | return false; 151 | } 152 | 153 | void start (juce::AudioIODeviceCallback* /*callback*/) override 154 | { 155 | } 156 | 157 | void stop() override 158 | { 159 | } 160 | 161 | bool isPlaying() override 162 | { 163 | return false; 164 | } 165 | 166 | juce::String getLastError() override 167 | { 168 | return {}; 169 | } 170 | 171 | int getCurrentBufferSizeSamples() override 172 | { 173 | return static_cast (::audio_io_device::bufferSize (*_device)); 174 | } 175 | 176 | double getCurrentSampleRate() override 177 | { 178 | return ::audio_io_device::sampleRate (*_device); 179 | } 180 | 181 | int getCurrentBitDepth() override 182 | { 183 | return 0; 184 | } 185 | 186 | [[nodiscard]] juce::BigInteger getActiveOutputChannels() const override 187 | { 188 | return {}; 189 | } 190 | 191 | [[nodiscard]] juce::BigInteger getActiveInputChannels() const override 192 | { 193 | return {}; 194 | } 195 | 196 | int getOutputLatencyInSamples() override 197 | { 198 | return 0; 199 | } 200 | 201 | int getInputLatencyInSamples() override 202 | { 203 | return 0; 204 | } 205 | 206 | [[nodiscard]] bool hasControlPanel() const override 207 | { 208 | return false; 209 | } 210 | 211 | bool showControlPanel() override 212 | { 213 | return false; 214 | } 215 | 216 | bool setAudioPreprocessingEnabled (bool) override 217 | { 218 | return false; 219 | } 220 | 221 | [[nodiscard]] int getXRunCount() const noexcept override 222 | { 223 | return 0; 224 | } 225 | 226 | BoxedAudioIODevice* _device { nullptr }; 227 | }; 228 | 229 | const auto device = ::audio_io_device_type::createDevice ( 230 | *_audioIODeviceType, 231 | outputDeviceName.toStdString(), 232 | inputDeviceName.toStdString()); 233 | 234 | if (! device) 235 | { 236 | return nullptr; 237 | } 238 | 239 | return std::make_unique (device).release(); 240 | } 241 | 242 | rust::Box _audioIODeviceType; 243 | }; 244 | 245 | _audioDeviceManager.addAudioDeviceType (std::make_unique (std::move (audioIODeviceType))); 246 | } 247 | 248 | void AudioDeviceManager::setCurrentAudioDeviceType (rust::Str audioDeviceTypeName) 249 | { 250 | _audioDeviceManager.setCurrentAudioDeviceType (static_cast (audioDeviceTypeName), true); 251 | } 252 | 253 | void AudioDeviceManager::playTestSound() 254 | { 255 | _audioDeviceManager.playTestSound(); 256 | } 257 | 258 | juce::AudioIODevice* AudioDeviceManager::getCurrentAudioDevice() 259 | { 260 | return _audioDeviceManager.getCurrentAudioDevice(); 261 | } 262 | 263 | const juce::OwnedArray& AudioDeviceManager::getAvailableDeviceTypes() 264 | { 265 | return _audioDeviceManager.getAvailableDeviceTypes(); 266 | } 267 | 268 | juce::AudioIODeviceType* AudioDeviceManager::getCurrentDeviceTypeObject() const 269 | { 270 | return _audioDeviceManager.getCurrentDeviceTypeObject(); 271 | } 272 | 273 | std::unique_ptr createAudioDeviceManager() 274 | { 275 | jassert (juce::MessageManager::getInstanceWithoutCreating()); 276 | return std::make_unique(); 277 | } 278 | 279 | } // namespace cxx_juce 280 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_devices/cxx_juce_audio_device_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cxx_juce_audio_callback_wrapper.h" 4 | #include "cxx_juce_audio_device_setup.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace cxx_juce 10 | { 11 | 12 | struct BoxedAudioIODeviceType; 13 | 14 | struct AudioDeviceManager 15 | { 16 | void initialiseWithDefaultDevices (rust::i32 inputChannels, 17 | rust::i32 outputChannels); 18 | [[nodiscard]] std::unique_ptr getAudioDeviceSetup() const; 19 | void setAudioDeviceSetup (const AudioDeviceSetup& setup); 20 | void addAudioCallback (const std::unique_ptr& callback); 21 | void removeAudioCallback (const std::unique_ptr& callback); 22 | void addAudioDeviceType (rust::Box audioIODeviceType); 23 | void setCurrentAudioDeviceType (rust::Str audioDeviceTypeName); 24 | void playTestSound(); 25 | juce::AudioIODevice* getCurrentAudioDevice(); 26 | const juce::OwnedArray& getAvailableDeviceTypes(); 27 | juce::AudioIODeviceType* getCurrentDeviceTypeObject() const; 28 | 29 | juce::AudioDeviceManager _audioDeviceManager; 30 | }; 31 | 32 | std::unique_ptr createAudioDeviceManager(); 33 | 34 | } // namespace cxx_juce 35 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_devices/cxx_juce_audio_device_setup.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cxx_juce 4 | { 5 | std::unique_ptr createAudioDeviceSetup() 6 | { 7 | return std::make_unique(); 8 | } 9 | 10 | AudioDeviceSetup::AudioDeviceSetup (juce::AudioDeviceManager::AudioDeviceSetup audioDeviceSetup) 11 | : _audioDeviceSetup (std::move (audioDeviceSetup)) 12 | { 13 | } 14 | 15 | rust::Str AudioDeviceSetup::outputDeviceName() const 16 | { 17 | return toStr (_audioDeviceSetup.outputDeviceName); 18 | } 19 | 20 | void AudioDeviceSetup::setOutputDeviceName (rust::Str outputDeviceName) 21 | { 22 | _audioDeviceSetup.outputDeviceName = static_cast (outputDeviceName); 23 | } 24 | 25 | rust::Str AudioDeviceSetup::inputDeviceName() const 26 | { 27 | return toStr (_audioDeviceSetup.inputDeviceName); 28 | } 29 | 30 | void AudioDeviceSetup::setInputDeviceName (rust::Str inputDeviceName) 31 | { 32 | _audioDeviceSetup.inputDeviceName = static_cast (inputDeviceName); 33 | } 34 | 35 | rust::f64 AudioDeviceSetup::sampleRate() const 36 | { 37 | return _audioDeviceSetup.sampleRate; 38 | } 39 | 40 | void AudioDeviceSetup::setSampleRate (rust::f64 sampleRate) 41 | { 42 | _audioDeviceSetup.sampleRate = sampleRate; 43 | } 44 | 45 | rust::i32 AudioDeviceSetup::bufferSize() const 46 | { 47 | return _audioDeviceSetup.bufferSize; 48 | } 49 | 50 | void AudioDeviceSetup::setBufferSize (rust::i32 bufferSize) 51 | { 52 | _audioDeviceSetup.bufferSize = bufferSize; 53 | } 54 | 55 | rust::i32 AudioDeviceSetup::numberOfInputChannels() const 56 | { 57 | return _audioDeviceSetup.inputChannels.countNumberOfSetBits(); 58 | } 59 | 60 | void AudioDeviceSetup::setNumberOfInputChannels (rust::i32 numberOfInputChannels) 61 | { 62 | _audioDeviceSetup.inputChannels.clear(); 63 | _audioDeviceSetup.inputChannels.setRange (0, numberOfInputChannels, true); 64 | } 65 | 66 | void AudioDeviceSetup::useDefaultInputChannels (bool useDefaultInputChannels) 67 | { 68 | _audioDeviceSetup.useDefaultInputChannels = useDefaultInputChannels; 69 | } 70 | 71 | bool AudioDeviceSetup::usingDefaultInputChannels() const 72 | { 73 | return _audioDeviceSetup.useDefaultInputChannels; 74 | } 75 | 76 | rust::i32 AudioDeviceSetup::numberOfOutputChannels() const 77 | { 78 | return _audioDeviceSetup.outputChannels.countNumberOfSetBits(); 79 | } 80 | 81 | void AudioDeviceSetup::setNumberOfOutputChannels (rust::i32 numberOfOutputChannels) 82 | { 83 | _audioDeviceSetup.outputChannels.clear(); 84 | _audioDeviceSetup.outputChannels.setRange (0, numberOfOutputChannels, true); 85 | } 86 | 87 | void AudioDeviceSetup::useDefaultOutputChannels (bool useDefaultOutputChannels) 88 | { 89 | _audioDeviceSetup.useDefaultOutputChannels = useDefaultOutputChannels; 90 | } 91 | 92 | bool AudioDeviceSetup::usingDefaultOutputChannels() const 93 | { 94 | return _audioDeviceSetup.useDefaultOutputChannels; 95 | } 96 | } // namespace cxx_juce 97 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_devices/cxx_juce_audio_device_setup.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace cxx_juce 9 | { 10 | 11 | struct AudioDeviceSetup 12 | { 13 | AudioDeviceSetup() = default; 14 | explicit AudioDeviceSetup (juce::AudioDeviceManager::AudioDeviceSetup audioDeviceSetup); 15 | 16 | [[nodiscard]] rust::Str outputDeviceName() const; 17 | void setOutputDeviceName (rust::Str outputDeviceName); 18 | [[nodiscard]] rust::Str inputDeviceName() const; 19 | void setInputDeviceName (rust::Str inputDeviceName); 20 | [[nodiscard]] rust::f64 sampleRate() const; 21 | void setSampleRate (rust::f64 sampleRate); 22 | [[nodiscard]] rust::i32 bufferSize() const; 23 | void setBufferSize (rust::i32 bufferSize); 24 | [[nodiscard]] rust::i32 numberOfInputChannels() const; 25 | void setNumberOfInputChannels (rust::i32 numberOfInputChannels); 26 | void useDefaultInputChannels (bool useDefaultInputChannels); 27 | [[nodiscard]] bool usingDefaultInputChannels() const; 28 | [[nodiscard]] rust::i32 numberOfOutputChannels() const; 29 | void setNumberOfOutputChannels (rust::i32 numberOfOutputChannels); 30 | void useDefaultOutputChannels (bool useDefaultOutputChannels); 31 | [[nodiscard]] bool usingDefaultOutputChannels() const; 32 | 33 | juce::AudioDeviceManager::AudioDeviceSetup _audioDeviceSetup; 34 | }; 35 | 36 | std::unique_ptr createAudioDeviceSetup(); 37 | 38 | } // namespace cxx_juce 39 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_devices/cxx_juce_audio_io_device.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cxx_juce::audio_io_device 4 | { 5 | rust::Str getDeviceName (const juce::AudioIODevice& audioIoDevice) 6 | { 7 | return toStr (audioIoDevice.getName()); 8 | } 9 | 10 | rust::Str getDeviceTypeName (const juce::AudioIODevice& audioIoDevice) 11 | { 12 | return toStr (audioIoDevice.getTypeName()); 13 | } 14 | 15 | rust::Vec getAvailableSampleRates (juce::AudioIODevice& audioIoDevice) 16 | { 17 | const auto sampleRates = audioIoDevice.getAvailableSampleRates(); 18 | 19 | rust::Vec result; 20 | result.reserve (static_cast (sampleRates.size())); 21 | std::copy ( 22 | std::begin (sampleRates), 23 | std::end (sampleRates), 24 | std::back_inserter (result)); 25 | return result; 26 | } 27 | 28 | rust::Vec getAvailableBufferSizes (juce::AudioIODevice& audioIoDevice) 29 | { 30 | const auto bufferSizes = audioIoDevice.getAvailableBufferSizes(); 31 | 32 | rust::Vec result; 33 | result.reserve (static_cast (bufferSizes.size())); 34 | std::copy ( 35 | std::begin (bufferSizes), 36 | std::end (bufferSizes), 37 | std::back_inserter (result)); 38 | return result; 39 | } 40 | 41 | void open (juce::AudioIODevice& audioIoDevice, 42 | double sampleRate, 43 | size_t bufferSize) 44 | { 45 | audioIoDevice.open (juce::BigInteger {}, 46 | juce::BigInteger {}, 47 | sampleRate, 48 | static_cast (bufferSize)); 49 | } 50 | 51 | rust::i32 countActiveInputChannels (const juce::AudioIODevice& audioIoDevice) 52 | { 53 | return audioIoDevice.getActiveInputChannels().countNumberOfSetBits(); 54 | } 55 | 56 | rust::i32 countActiveOutputChannels (const juce::AudioIODevice& audioIoDevice) 57 | { 58 | return audioIoDevice.getActiveOutputChannels().countNumberOfSetBits(); 59 | } 60 | } // namespace cxx_juce::audio_io_device 61 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_devices/cxx_juce_audio_io_device.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cxx_juce::audio_io_device 7 | { 8 | 9 | rust::Str getDeviceName (const juce::AudioIODevice& audioIoDevice); 10 | rust::Str getDeviceTypeName (const juce::AudioIODevice& audioIoDevice); 11 | rust::Vec getAvailableSampleRates (juce::AudioIODevice& audioIoDevice); 12 | rust::Vec getAvailableBufferSizes (juce::AudioIODevice& audioIoDevice); 13 | void open (juce::AudioIODevice& audioIoDevice, double sampleRate, size_t bufferSize); 14 | rust::i32 countActiveInputChannels (const juce::AudioIODevice& audioIoDevice); 15 | rust::i32 countActiveOutputChannels (const juce::AudioIODevice& audioIoDevice); 16 | 17 | } // namespace cxx_juce::audio_io_device 18 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_devices/cxx_juce_audio_io_device_type.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cxx_juce::audio_io_device_type 4 | { 5 | rust::String getTypeName (const juce::AudioIODeviceType& audioIoDeviceType) 6 | { 7 | return audioIoDeviceType.getTypeName().toStdString(); 8 | } 9 | 10 | rust::Vec getInputDeviceNames ( 11 | const juce::AudioIODeviceType& audioIoDeviceType) 12 | { 13 | const auto deviceNames = audioIoDeviceType.getDeviceNames (true); 14 | 15 | rust::Vec result; 16 | std::transform ( 17 | std::begin (deviceNames), 18 | std::end (deviceNames), 19 | std::back_inserter (result), 20 | [] (const auto& deviceName) 21 | { return deviceName.toStdString(); }); 22 | return result; 23 | } 24 | 25 | rust::Vec getOutputDeviceNames ( 26 | const juce::AudioIODeviceType& audioIoDeviceType) 27 | { 28 | const auto deviceNames = audioIoDeviceType.getDeviceNames (false); 29 | 30 | rust::Vec result; 31 | result.reserve (static_cast (deviceNames.size())); 32 | std::transform ( 33 | std::begin (deviceNames), 34 | std::end (deviceNames), 35 | std::back_inserter (result), 36 | [] (const auto& deviceName) 37 | { return deviceName.toStdString(); }); 38 | return result; 39 | } 40 | 41 | std::unique_ptr createDevice ( 42 | juce::AudioIODeviceType& audioIoDeviceType, 43 | rust::Str inputDeviceName, 44 | rust::Str outputDeviceName) 45 | { 46 | if (auto* device = audioIoDeviceType.createDevice ( 47 | static_cast (inputDeviceName), 48 | static_cast (outputDeviceName))) 49 | { 50 | return std::unique_ptr (device); 51 | } 52 | 53 | return nullptr; 54 | } 55 | } // namespace cxx_juce::audio_io_device_type 56 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_devices/cxx_juce_audio_io_device_type.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cxx_juce::audio_io_device_type 7 | { 8 | 9 | rust::String getTypeName (const juce::AudioIODeviceType& audioIoDeviceType); 10 | rust::Vec getInputDeviceNames (const juce::AudioIODeviceType& audioIoDeviceType); 11 | rust::Vec getOutputDeviceNames (const juce::AudioIODeviceType& audioIoDeviceType); 12 | std::unique_ptr createDevice (juce::AudioIODeviceType& audioIoDeviceType, rust::Str inputDeviceName, rust::Str outputDeviceName); 13 | 14 | } // namespace cxx_juce::audio_io_device_type 15 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_devices/cxx_juce_system_audio_volume.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cxx_juce::system_audio_volume 4 | { 5 | void setMuted (bool muted) 6 | { 7 | juce::SystemAudioVolume::setMuted (muted); 8 | } 9 | 10 | bool isMuted() 11 | { 12 | return juce::SystemAudioVolume::isMuted(); 13 | } 14 | 15 | void setGain (rust::f32 gain) 16 | { 17 | juce::SystemAudioVolume::setGain (gain); 18 | } 19 | 20 | rust::f32 getGain() 21 | { 22 | return juce::SystemAudioVolume::getGain(); 23 | } 24 | } // namespace cxx_juce::system_audio_volume 25 | -------------------------------------------------------------------------------- /bridge/cxx_juce_audio_devices/cxx_juce_system_audio_volume.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cxx_juce::system_audio_volume 6 | { 7 | 8 | void setMuted (bool muted); 9 | bool isMuted(); 10 | void setGain (rust::f32 gain); 11 | rust::f32 getGain(); 12 | 13 | } // namespace cxx_juce::system_audio_volume 14 | -------------------------------------------------------------------------------- /bridge/cxx_juce_bindings.cpp: -------------------------------------------------------------------------------- 1 | #if __clang__ 2 | #pragma clang diagnostic push 3 | #pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" 4 | #pragma clang diagnostic ignored "-Wmissing-prototypes" 5 | #pragma clang diagnostic ignored "-Wshadow-field-in-constructor" 6 | #pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" 7 | #endif 8 | 9 | #include "cxx-juce/src/lib.rs.cc" 10 | 11 | #if __clang__ 12 | #pragma clang diagnostic pop 13 | #endif -------------------------------------------------------------------------------- /bridge/cxx_juce_bindings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if __clang__ 4 | #pragma clang diagnostic push 5 | #pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" 6 | #pragma clang diagnostic ignored "-Wmissing-prototypes" 7 | #pragma clang diagnostic ignored "-Wshadow-field-in-constructor" 8 | #pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" 9 | #endif 10 | 11 | #include "cxx-juce/src/lib.rs.h" 12 | 13 | #if __clang__ 14 | #pragma clang diagnostic pop 15 | #endif -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, path::Path}; 2 | 3 | fn main() { 4 | if env::var("DOCS_RS").is_ok() { 5 | return; 6 | } 7 | 8 | let _ = cxx_build::bridge("src/lib.rs"); 9 | 10 | let mut cmake = cmake::Config::new("bridge"); 11 | cmake.build_target("cxx-juce"); 12 | 13 | let out_dir = env::var("OUT_DIR").unwrap(); 14 | cmake.define("CXX_JUCE_BINDINGS_DIR", format!("{out_dir}/cxxbridge")); 15 | 16 | if cfg!(feature = "asio") { 17 | cmake.define("CXX_JUCE_USE_ASIO", "ON"); 18 | 19 | if let Ok(path) = env::var("CXX_JUCE_ASIO_SDK_DIR") { 20 | if Path::new(&path).join("common").join("iasiodrv.h").exists() { 21 | cmake.define("CXX_JUCE_ASIO_SDK_DIR", path); 22 | } else { 23 | panic!("CXX_JUCE_ASIO_SDK_DIR is set to '{path}' which is not a valid path to the ASIO SDK"); 24 | } 25 | } else { 26 | panic!("CXX_JUCE_ASIO_SDK_DIR is not set"); 27 | } 28 | } else { 29 | cmake.define("CXX_JUCE_USE_ASIO", "OFF"); 30 | } 31 | 32 | if cfg!(target_os = "windows") && cmake.get_profile() == "Debug" { 33 | cmake.profile("RelWithDebInfo"); 34 | } 35 | 36 | let destination = cmake.build(); 37 | 38 | println!("cargo:rerun-if-changed=src/lib.rs"); 39 | println!("cargo:rerun-if-changed=bridge"); 40 | println!("cargo:rerun-if-env-changed=CXX_JUCE_ASIO_SDK_DIR"); 41 | 42 | if cfg!(target_os = "windows") { 43 | println!( 44 | "cargo:rustc-link-search=native={}/build/{}", 45 | destination.display(), 46 | cmake.get_profile() 47 | ); 48 | } else { 49 | println!( 50 | "cargo:rustc-link-search=native={}/build", 51 | destination.display() 52 | ); 53 | }; 54 | 55 | println!("cargo:rustc-link-lib=static=cxx-juce"); 56 | 57 | if cfg!(target_os = "macos") { 58 | println!("cargo:rustc-link-lib=c++"); 59 | println!("cargo:rustc-link-lib=framework=Accelerate"); 60 | println!("cargo:rustc-link-lib=framework=AudioToolbox"); 61 | println!("cargo:rustc-link-lib=framework=Cocoa"); 62 | println!("cargo:rustc-link-lib=framework=CoreAudio"); 63 | println!("cargo:rustc-link-lib=framework=CoreFoundation"); 64 | println!("cargo:rustc-link-lib=framework=CoreMIDI"); 65 | println!("cargo:rustc-link-lib=framework=IOKit"); 66 | } 67 | 68 | if cfg!(target_os = "windows") { 69 | println!("cargo:rustc-link-lib=dylib=shell32"); 70 | println!("cargo:rustc-link-lib=dylib=ole32"); 71 | } 72 | 73 | if cfg!(target_os = "linux") { 74 | println!("cargo:rustc-link-lib=asound"); 75 | println!("cargo:rustc-link-lib=jack"); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /examples/audio_callback.rs: -------------------------------------------------------------------------------- 1 | use { 2 | cxx_juce::{ 3 | juce_audio_devices::{ 4 | AudioDeviceManager, AudioIODevice, AudioIODeviceCallback, InputAudioSampleBuffer, 5 | OutputAudioSampleBuffer, 6 | }, 7 | Result, JUCE, 8 | }, 9 | std::{iter::successors, thread::sleep, time::Duration}, 10 | }; 11 | 12 | #[derive(Debug, Default, Copy, Clone)] 13 | struct ToneGenerator { 14 | amplitude: f64, 15 | frequency: f64, 16 | phase: f64, 17 | increment: f64, 18 | } 19 | 20 | impl Iterator for ToneGenerator { 21 | type Item = f64; 22 | 23 | fn next(&mut self) -> Option { 24 | let sample = self.phase.sin() * self.amplitude; 25 | self.phase += self.increment; 26 | Some(sample) 27 | } 28 | } 29 | 30 | #[derive(Default)] 31 | struct AudioCallback { 32 | tones: Vec, 33 | } 34 | 35 | impl AudioIODeviceCallback for AudioCallback { 36 | fn about_to_start(&mut self, device: &mut dyn AudioIODevice) { 37 | const STARTING_FREQUENCY: f64 = 1024.0; 38 | 39 | let output_channels = device.output_channels() as usize; 40 | 41 | self.tones = successors( 42 | Some(ToneGenerator { 43 | amplitude: 0.25, 44 | frequency: STARTING_FREQUENCY, 45 | phase: 0.0, 46 | increment: STARTING_FREQUENCY / device.sample_rate(), 47 | }), 48 | |prev| { 49 | let frequency = prev.frequency * 2.5; 50 | Some(ToneGenerator { 51 | frequency, 52 | increment: frequency / device.sample_rate(), 53 | ..*prev 54 | }) 55 | }, 56 | ) 57 | .take(output_channels) 58 | .collect(); 59 | } 60 | 61 | fn process_block( 62 | &mut self, 63 | _input: &InputAudioSampleBuffer<'_>, 64 | output: &mut OutputAudioSampleBuffer<'_>, 65 | ) { 66 | for channel in 0..output.channels() { 67 | let samples = &mut output[channel]; 68 | let tone = &mut self.tones[channel]; 69 | 70 | for (sample, tone) in samples.iter_mut().zip(tone) { 71 | *sample = tone as f32; 72 | } 73 | } 74 | } 75 | 76 | fn stopped(&mut self) {} 77 | } 78 | 79 | fn main() -> Result<()> { 80 | let juce = JUCE::initialise(); 81 | let mut device_manager = AudioDeviceManager::new(&juce); 82 | device_manager.initialise(0, 2)?; 83 | 84 | let handle = device_manager.add_audio_callback(AudioCallback::default()); 85 | 86 | sleep(Duration::from_secs(2)); 87 | 88 | device_manager.remove_audio_callback(handle); 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /examples/devices.rs: -------------------------------------------------------------------------------- 1 | use cxx_juce::{ 2 | juce_audio_devices::{AudioDeviceManager, AudioIODeviceType}, 3 | Result, JUCE, 4 | }; 5 | 6 | fn main() -> Result<()> { 7 | let juce = JUCE::initialise(); 8 | let mut audio_device_manager = AudioDeviceManager::new(&juce); 9 | audio_device_manager.initialise(2, 2)?; 10 | 11 | let device_type = audio_device_manager.current_device_type().unwrap(); 12 | 13 | println!("Inputs:"); 14 | for input in device_type.input_devices() { 15 | println!(" {}", input); 16 | } 17 | 18 | println!("Outputs:"); 19 | for output in device_type.output_devices() { 20 | println!(" {}", output); 21 | } 22 | 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /examples/system_audio_volume.rs: -------------------------------------------------------------------------------- 1 | use { 2 | cxx_juce::juce_audio_devices::SystemAudioVolume, 3 | std::{thread::sleep, time::Duration}, 4 | }; 5 | 6 | fn delay() { 7 | sleep(Duration::from_secs(1)); 8 | } 9 | 10 | fn main() { 11 | let original_gain = SystemAudioVolume::get_gain(); 12 | println!("System gain is currently set at {original_gain}"); 13 | 14 | delay(); 15 | 16 | println!("Halving the gain 🤫"); 17 | SystemAudioVolume::set_gain(original_gain / 2.0); 18 | 19 | delay(); 20 | 21 | println!("Muting 🔇"); 22 | SystemAudioVolume::mute(); 23 | 24 | delay(); 25 | 26 | println!("Unmuting 🔊"); 27 | SystemAudioVolume::unmute(); 28 | 29 | delay(); 30 | 31 | print!("Putting it back to how you had it 🧹"); 32 | SystemAudioVolume::set_gain(original_gain); 33 | } 34 | -------------------------------------------------------------------------------- /examples/test_tone.rs: -------------------------------------------------------------------------------- 1 | use { 2 | cxx_juce::{ 3 | juce_audio_devices::{AudioDeviceManager, AudioIODevice}, 4 | Result, JUCE, 5 | }, 6 | std::{thread::sleep, time::Duration}, 7 | }; 8 | 9 | fn main() -> Result<()> { 10 | let juce = JUCE::initialise(); 11 | let mut audio_device_manager = AudioDeviceManager::new(&juce); 12 | audio_device_manager.initialise(0, 2)?; 13 | 14 | { 15 | let mut device = audio_device_manager 16 | .current_device() 17 | .expect("default device not found"); 18 | 19 | println!("Name: {}", device.name()); 20 | println!("Type: {}", device.type_name()); 21 | println!("Sample rate: {}", device.sample_rate()); 22 | println!("Buffer size: {}", device.buffer_size()); 23 | println!( 24 | "Available sample rates: {:?}", 25 | device.available_sample_rates() 26 | ); 27 | println!( 28 | "Available buffer sizes: {:?}", 29 | device.available_buffer_sizes() 30 | ); 31 | } 32 | 33 | audio_device_manager.play_test_sound(); 34 | sleep(Duration::from_secs(1)); 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /src/juce_audio_basics.rs: -------------------------------------------------------------------------------- 1 | //! Classes for audio buffer manipulation, midi message handling, synthesis, etc. 2 | 3 | use { 4 | std::f64::consts::FRAC_1_SQRT_2, 5 | {crate::juce, cxx::UniquePtr}, 6 | }; 7 | 8 | /// An infinite impulse response (IIR) filter. 9 | pub struct IIRFilter(UniquePtr); 10 | 11 | unsafe impl Send for IIRFilter {} 12 | 13 | /// The quality (Q) factor of a filter. 14 | #[derive(Debug, Copy, Clone)] 15 | pub struct Q(pub f64); 16 | 17 | impl Default for Q { 18 | fn default() -> Self { 19 | Self(FRAC_1_SQRT_2) 20 | } 21 | } 22 | 23 | impl IIRFilter { 24 | /// Create a low-pass filter. 25 | pub fn low_pass(sample_rate: f64, frequency: f64, Q(q): Q) -> Self { 26 | Self(juce::create_iir_filter(juce::make_low_pass( 27 | sample_rate, 28 | frequency, 29 | q, 30 | ))) 31 | } 32 | 33 | /// Create a high-pass filter. 34 | pub fn high_pass(sample_rate: f64, frequency: f64, Q(q): Q) -> Self { 35 | Self(juce::create_iir_filter(juce::make_high_pass( 36 | sample_rate, 37 | frequency, 38 | q, 39 | ))) 40 | } 41 | 42 | /// Create a notch filter. 43 | pub fn notch(sample_rate: f64, frequency: f64, Q(q): Q) -> Self { 44 | Self(juce::create_iir_filter(juce::make_notch_filter( 45 | sample_rate, 46 | frequency, 47 | q, 48 | ))) 49 | } 50 | 51 | /// Filter the given samples. 52 | pub fn process(&mut self, samples: &mut [f32]) { 53 | unsafe { 54 | self.0 55 | .pin_mut() 56 | .process_samples(samples.as_mut_ptr(), samples.len() as i32) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/juce_audio_devices.rs: -------------------------------------------------------------------------------- 1 | //! Play and record from audio and MIDI I/O devices. 2 | 3 | use { 4 | crate::{juce, Result, JUCE}, 5 | slotmap::SlotMap, 6 | std::{ 7 | ops::{Index, IndexMut}, 8 | pin::Pin, 9 | }, 10 | }; 11 | 12 | /// A multi-channel buffer of read-only audio samples. 13 | pub struct InputAudioSampleBuffer<'a> { 14 | buffer: &'a juce::AudioSampleBuffer, 15 | } 16 | 17 | impl<'a> InputAudioSampleBuffer<'a> { 18 | pub(crate) fn new(buffer: &'a juce::AudioSampleBuffer) -> Self { 19 | Self { buffer } 20 | } 21 | 22 | /// Returns the numbers of channels in the buffer. 23 | pub fn channels(&self) -> usize { 24 | self.buffer.get_num_channels() as usize 25 | } 26 | 27 | /// Returns the number of samples for each channel. 28 | pub fn samples(&self) -> usize { 29 | self.buffer.get_num_samples() as usize 30 | } 31 | } 32 | 33 | impl Index for InputAudioSampleBuffer<'_> { 34 | type Output = [f32]; 35 | 36 | fn index(&self, channel: usize) -> &Self::Output { 37 | if self.channels() < channel { 38 | panic!("channel out of bounds"); 39 | } 40 | 41 | let ptr = self.buffer.get_read_pointer(channel as i32); 42 | let len = self.samples(); 43 | 44 | unsafe { std::slice::from_raw_parts(ptr, len) } 45 | } 46 | } 47 | 48 | /// A multi-channel buffer of read-write audio samples. 49 | pub struct OutputAudioSampleBuffer<'a> { 50 | buffer: Pin<&'a mut juce::AudioSampleBuffer>, 51 | } 52 | 53 | impl<'a> OutputAudioSampleBuffer<'a> { 54 | pub(crate) fn new(buffer: Pin<&'a mut juce::AudioSampleBuffer>) -> Self { 55 | Self { buffer } 56 | } 57 | 58 | /// Returns the numbers of channels in the buffer. 59 | pub fn channels(&self) -> usize { 60 | self.buffer.get_num_channels() as usize 61 | } 62 | 63 | /// Returns the number of samples for each channel. 64 | pub fn samples(&self) -> usize { 65 | self.buffer.get_num_samples() as usize 66 | } 67 | 68 | /// Clear all the samples for all the channels. 69 | pub fn clear(&mut self) { 70 | self.buffer.as_mut().clear(); 71 | } 72 | } 73 | 74 | impl Index for OutputAudioSampleBuffer<'_> { 75 | type Output = [f32]; 76 | 77 | fn index(&self, channel: usize) -> &Self::Output { 78 | if self.channels() < channel { 79 | panic!("channel out of bounds"); 80 | } 81 | 82 | let ptr = self.buffer.get_read_pointer(channel as i32); 83 | let len = self.samples(); 84 | 85 | unsafe { std::slice::from_raw_parts(ptr, len) } 86 | } 87 | } 88 | 89 | impl IndexMut for OutputAudioSampleBuffer<'_> { 90 | fn index_mut(&mut self, channel: usize) -> &mut Self::Output { 91 | if self.channels() < channel { 92 | panic!("channel out of bounds"); 93 | } 94 | 95 | let ptr = self.buffer.as_mut().get_write_pointer(channel as i32); 96 | let len = self.samples(); 97 | 98 | unsafe { std::slice::from_raw_parts_mut(ptr, len) } 99 | } 100 | } 101 | 102 | /// The properties of an audio device. 103 | pub struct AudioDeviceSetup(cxx::UniquePtr); 104 | 105 | unsafe impl Send for AudioDeviceSetup {} 106 | 107 | impl Default for AudioDeviceSetup { 108 | fn default() -> Self { 109 | Self(juce::create_audio_device_setup()) 110 | } 111 | } 112 | 113 | /// The number of channels to use. 114 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 115 | pub enum ChannelCount { 116 | /// Use the default number of channels for the device. 117 | Default, 118 | 119 | /// Use a custom number of channels. 120 | Custom(i32), 121 | } 122 | 123 | unsafe impl Send for ChannelCount {} 124 | 125 | impl AudioDeviceSetup { 126 | /// The name of the output device. 127 | pub fn output_device_name(&self) -> &str { 128 | self.0.output_device_name() 129 | } 130 | 131 | /// Set the name of the output device. 132 | pub fn with_output_device_name(mut self, name: impl AsRef) -> Self { 133 | self.0.pin_mut().set_output_device_name(name.as_ref()); 134 | self 135 | } 136 | 137 | /// The name of the input device. 138 | pub fn input_device_name(&self) -> &str { 139 | self.0.input_device_name() 140 | } 141 | 142 | /// Set the name of the input device. 143 | pub fn with_input_device_name(mut self, name: impl AsRef) -> Self { 144 | self.0.pin_mut().set_input_device_name(name.as_ref()); 145 | self 146 | } 147 | 148 | /// The sample rate in Hertz. 149 | pub fn sample_rate(&self) -> f64 { 150 | self.0.sample_rate() 151 | } 152 | 153 | /// Set the sample rate in Hertz. 154 | pub fn with_sample_rate(mut self, sample_rate: f64) -> Self { 155 | self.0.pin_mut().set_sample_rate(sample_rate); 156 | self 157 | } 158 | 159 | /// The buffer size. 160 | pub fn buffer_size(&self) -> usize { 161 | self.0.buffer_size() as usize 162 | } 163 | 164 | /// The buffer size to use. 165 | pub fn with_buffer_size(mut self, buffer_size: usize) -> Self { 166 | self.0.pin_mut().set_buffer_size(buffer_size as i32); 167 | self 168 | } 169 | 170 | /// The number of input channels. 171 | pub fn input_channels(&self) -> ChannelCount { 172 | if self.0.using_default_input_channels() { 173 | ChannelCount::Default 174 | } else { 175 | ChannelCount::Custom(self.0.number_of_input_channels()) 176 | } 177 | } 178 | 179 | // Set the number of input channels. 180 | pub fn with_input_channels(mut self, channels: ChannelCount) -> Self { 181 | match channels { 182 | ChannelCount::Default => { 183 | self.0.pin_mut().use_default_input_channels(true); 184 | } 185 | ChannelCount::Custom(count) => { 186 | self.0.pin_mut().use_default_input_channels(false); 187 | self.0.pin_mut().set_number_of_input_channels(count); 188 | } 189 | } 190 | 191 | self 192 | } 193 | 194 | /// The number of output channels. 195 | pub fn output_channels(&self) -> ChannelCount { 196 | if self.0.using_default_output_channels() { 197 | ChannelCount::Default 198 | } else { 199 | ChannelCount::Custom(self.0.number_of_output_channels()) 200 | } 201 | } 202 | 203 | /// Set the number of output channels. 204 | pub fn with_output_channels(mut self, channels: ChannelCount) -> Self { 205 | match channels { 206 | ChannelCount::Default => { 207 | self.0.pin_mut().use_default_output_channels(true); 208 | } 209 | ChannelCount::Custom(count) => { 210 | self.0.pin_mut().use_default_output_channels(false); 211 | self.0.pin_mut().set_number_of_output_channels(count); 212 | } 213 | } 214 | 215 | self 216 | } 217 | } 218 | 219 | slotmap::new_key_type! { 220 | struct AudioCallbackKey; 221 | } 222 | 223 | /// Manages the state of an audio device. 224 | pub struct AudioDeviceManager { 225 | device_manager: cxx::UniquePtr, 226 | callbacks: SlotMap>, 227 | _juce: JUCE, 228 | } 229 | 230 | impl AudioDeviceManager { 231 | /// Create a new [`AudioDeviceManager`]. 232 | pub fn new(juce: &JUCE) -> Self { 233 | Self { 234 | device_manager: juce::create_audio_device_manager(), 235 | callbacks: SlotMap::with_key(), 236 | _juce: juce.clone(), 237 | } 238 | } 239 | 240 | /// Resets to a default device setup. 241 | pub fn initialise(&mut self, input_channels: usize, output_channels: usize) -> Result<()> { 242 | self.device_manager 243 | .pin_mut() 244 | .initialise_with_default_devices(input_channels as i32, output_channels as i32) 245 | } 246 | 247 | /// Get the current device setup. 248 | pub fn audio_device_setup(&self) -> AudioDeviceSetup { 249 | AudioDeviceSetup(self.device_manager.get_audio_device_setup()) 250 | } 251 | 252 | /// Changes the current device or its settings. 253 | pub fn set_audio_device_setup(&mut self, setup: &AudioDeviceSetup) { 254 | self.device_manager 255 | .pin_mut() 256 | .set_audio_device_setup(&setup.0); 257 | } 258 | 259 | /// Play a test sound. 260 | pub fn play_test_sound(&mut self) { 261 | self.device_manager.pin_mut().play_test_sound(); 262 | } 263 | 264 | /// Get the available device types. 265 | pub fn device_types(&mut self) -> Vec { 266 | let available_device_types = self.device_manager.pin_mut().get_available_device_types(); 267 | 268 | (0..available_device_types.size()) 269 | .map(|i| available_device_types.get_unchecked(i)) 270 | .map(|device_type_ptr| { 271 | let device_type_ref = unsafe { 272 | device_type_ptr 273 | .as_mut() 274 | .expect("device type ptr should not be null") 275 | }; 276 | 277 | unsafe { Pin::new_unchecked(device_type_ref) } 278 | }) 279 | .collect() 280 | } 281 | 282 | /// Get the current device type. 283 | pub fn current_device_type(&self) -> Option { 284 | let device_type = self.device_manager.get_current_device_type_object(); 285 | 286 | unsafe { device_type.as_mut().map(|ptr| Pin::new_unchecked(ptr)) } 287 | } 288 | 289 | /// Get the current [`AudioIODevice`]. 290 | pub fn current_device(&mut self) -> Option { 291 | let current_device = self.device_manager.pin_mut().get_current_audio_device(); 292 | 293 | unsafe { current_device.as_mut().map(|ptr| Pin::new_unchecked(ptr)) } 294 | } 295 | 296 | /// Registers an audio callback. 297 | pub fn add_audio_callback( 298 | &mut self, 299 | callback: impl AudioIODeviceCallback + 'static, 300 | ) -> AudioCallbackHandle { 301 | let callback = Box::new(callback); 302 | let callback = juce::wrap_audio_callback(Box::new(callback)); 303 | 304 | self.device_manager.pin_mut().add_audio_callback(&callback); 305 | let key = self.callbacks.insert(callback); 306 | 307 | AudioCallbackHandle { key } 308 | } 309 | 310 | /// Removes an audio callback. 311 | pub fn remove_audio_callback(&mut self, handle: AudioCallbackHandle) { 312 | if let Some(callback) = self.callbacks.remove(handle.key) { 313 | self.device_manager 314 | .pin_mut() 315 | .remove_audio_callback(&callback); 316 | } 317 | } 318 | 319 | /// Registers an audio device type. 320 | pub fn add_audio_device_type(&mut self, device_type: impl AudioIODeviceType + 'static) { 321 | let device_type = Box::new(device_type); 322 | self.device_manager 323 | .pin_mut() 324 | .add_audio_device_type(Box::new(device_type)); 325 | } 326 | 327 | /// Set the current audio device type to use. 328 | pub fn set_current_audio_device_type(&mut self, device_type: &str) { 329 | self.device_manager 330 | .pin_mut() 331 | .set_current_audio_device_type(device_type); 332 | } 333 | } 334 | 335 | /// A trait that can be implemented to receive audio callbacks. 336 | /// 337 | /// Types that implement this trait can be registered with [`AudioDeviceManager::add_audio_callback`]. 338 | /// 339 | /// This trait requires that implementors are [`Send`] because the callbacks will occur on the audio thread. 340 | pub trait AudioIODeviceCallback: Send { 341 | /// Called when the audio device is about to start. 342 | fn about_to_start(&mut self, device: &mut dyn AudioIODevice); 343 | 344 | /// Process a block of incoming and outgoing audio. 345 | fn process_block( 346 | &mut self, 347 | input: &InputAudioSampleBuffer<'_>, 348 | output: &mut OutputAudioSampleBuffer<'_>, 349 | ); 350 | 351 | /// Called when the audio device has stopped. 352 | fn stopped(&mut self); 353 | } 354 | 355 | pub(crate) type BoxedAudioIODeviceCallback = Box; 356 | pub(crate) type BoxedAudioIODeviceType = Box; 357 | pub(crate) type BoxedAudioIODevice = Box; 358 | 359 | /// A handle to a registered audio callback. 360 | #[must_use] 361 | pub struct AudioCallbackHandle { 362 | key: AudioCallbackKey, 363 | } 364 | 365 | /// A trait representing a type of audio driver (e.g. CoreAudio, ASIO, etc.). 366 | pub trait AudioIODeviceType { 367 | /// The name of the type of driver. 368 | fn name(&self) -> String; 369 | 370 | /// Refreshes the drivers cached list of known devices. 371 | fn scan_for_devices(&mut self); 372 | 373 | /// Returns a list of known input devices. 374 | fn input_devices(&self) -> Vec; 375 | 376 | /// Returns a list of the known output devices. 377 | fn output_devices(&self) -> Vec; 378 | 379 | /// Create an [`AudioIODevice`]. 380 | fn create_device( 381 | &mut self, 382 | input_device_name: &str, 383 | output_device_name: &str, 384 | ) -> Option>; 385 | } 386 | 387 | impl AudioIODeviceType for Pin<&mut juce::AudioIODeviceType> { 388 | fn name(&self) -> String { 389 | juce::get_type_name(self) 390 | } 391 | 392 | fn scan_for_devices(&mut self) { 393 | juce::AudioIODeviceType::scan_for_devices(self.as_mut()) 394 | } 395 | 396 | fn input_devices(&self) -> Vec { 397 | juce::get_input_device_names(self) 398 | } 399 | 400 | fn output_devices(&self) -> Vec { 401 | juce::get_output_device_names(self) 402 | } 403 | 404 | fn create_device( 405 | &mut self, 406 | input_device_name: &str, 407 | output_device_name: &str, 408 | ) -> Option> { 409 | let device = juce::new_device(self.as_mut(), input_device_name, output_device_name); 410 | 411 | (!device.is_null()).then(|| -> Box { Box::new(device) }) 412 | } 413 | } 414 | 415 | /// A trait representing an audio device. 416 | pub trait AudioIODevice { 417 | /// The name of the device. 418 | fn name(&self) -> &str; 419 | 420 | /// The type of the device. 421 | fn type_name(&self) -> &str; 422 | 423 | /// The current sample rate. 424 | fn sample_rate(&mut self) -> f64; 425 | 426 | /// The current buffer size. 427 | fn buffer_size(&mut self) -> usize; 428 | 429 | /// The available sample rates. 430 | fn available_sample_rates(&mut self) -> Vec; 431 | 432 | /// The available buffer sizes. 433 | fn available_buffer_sizes(&mut self) -> Vec; 434 | 435 | /// Tries to open the device so that it can be used for audio processing. 436 | fn open(&mut self, sample_rate: f64, buffer_size: usize) -> Result<()>; 437 | 438 | /// Close the device. 439 | fn close(&mut self); 440 | 441 | /// The number of input channels. 442 | fn input_channels(&self) -> i32; 443 | 444 | /// The number of output channels. 445 | fn output_channels(&self) -> i32; 446 | } 447 | 448 | impl AudioIODevice for Pin<&mut juce::AudioIODevice> { 449 | fn name(&self) -> &str { 450 | juce::get_device_name(self) 451 | } 452 | 453 | fn type_name(&self) -> &str { 454 | juce::get_device_type_name(self) 455 | } 456 | 457 | fn sample_rate(&mut self) -> f64 { 458 | juce::AudioIODevice::get_current_sample_rate(self.as_mut()) 459 | } 460 | 461 | fn buffer_size(&mut self) -> usize { 462 | juce::AudioIODevice::get_current_buffer_size_samples(self.as_mut()) as usize 463 | } 464 | 465 | fn available_sample_rates(&mut self) -> Vec { 466 | juce::get_available_sample_rates(self.as_mut()) 467 | } 468 | 469 | fn available_buffer_sizes(&mut self) -> Vec { 470 | juce::get_available_buffer_sizes(self.as_mut()) 471 | } 472 | 473 | fn open(&mut self, sample_rate: f64, buffer_size: usize) -> Result<()> { 474 | juce::open(self.as_mut(), sample_rate, buffer_size) 475 | } 476 | 477 | fn close(&mut self) { 478 | juce::AudioIODevice::close(self.as_mut()); 479 | } 480 | 481 | fn input_channels(&self) -> i32 { 482 | juce::count_active_input_channels(self) 483 | } 484 | 485 | fn output_channels(&self) -> i32 { 486 | juce::count_active_output_channels(self) 487 | } 488 | } 489 | 490 | impl AudioIODevice for cxx::UniquePtr { 491 | fn name(&self) -> &str { 492 | self.as_ref().map(juce::get_device_name).unwrap_or_default() 493 | } 494 | 495 | fn type_name(&self) -> &str { 496 | self.as_ref() 497 | .map(juce::get_device_type_name) 498 | .unwrap_or_default() 499 | } 500 | 501 | fn sample_rate(&mut self) -> f64 { 502 | self.as_mut() 503 | .map(|this| this.get_current_sample_rate()) 504 | .unwrap_or_default() 505 | } 506 | 507 | fn buffer_size(&mut self) -> usize { 508 | self.as_mut() 509 | .map(|this| this.get_current_buffer_size_samples() as usize) 510 | .unwrap_or_default() 511 | } 512 | 513 | fn available_sample_rates(&mut self) -> Vec { 514 | self.as_mut() 515 | .map(juce::get_available_sample_rates) 516 | .unwrap_or_default() 517 | } 518 | 519 | fn available_buffer_sizes(&mut self) -> Vec { 520 | self.as_mut() 521 | .map(juce::get_available_buffer_sizes) 522 | .unwrap_or_default() 523 | } 524 | 525 | fn open(&mut self, sample_rate: f64, buffer_size: usize) -> Result<()> { 526 | if let Some(this) = self.as_mut() { 527 | juce::open(this, sample_rate, buffer_size)?; 528 | } 529 | 530 | Ok(()) 531 | } 532 | 533 | fn close(&mut self) { 534 | if let Some(this) = self.as_mut() { 535 | this.close(); 536 | } 537 | } 538 | 539 | fn input_channels(&self) -> i32 { 540 | self.as_ref() 541 | .map(juce::count_active_input_channels) 542 | .unwrap_or_default() 543 | } 544 | 545 | fn output_channels(&self) -> i32 { 546 | self.as_ref() 547 | .map(juce::count_active_output_channels) 548 | .unwrap_or_default() 549 | } 550 | } 551 | 552 | pub(crate) mod ffi { 553 | use super::*; 554 | 555 | pub mod audio_io_device_callback { 556 | use super::*; 557 | 558 | pub fn about_to_start( 559 | mut self_: Pin<&mut BoxedAudioIODeviceCallback>, 560 | mut device: Pin<&mut juce::AudioIODevice>, 561 | ) { 562 | self_.about_to_start(&mut device.as_mut()); 563 | } 564 | 565 | pub fn process_block( 566 | mut self_: Pin<&mut BoxedAudioIODeviceCallback>, 567 | input: &juce::AudioSampleBuffer, 568 | output: Pin<&mut juce::AudioSampleBuffer>, 569 | ) { 570 | let input = InputAudioSampleBuffer::new(input); 571 | let mut output = OutputAudioSampleBuffer::new(output); 572 | 573 | self_.process_block(&input, &mut output); 574 | } 575 | 576 | pub fn stopped(mut self_: Pin<&mut BoxedAudioIODeviceCallback>) { 577 | self_.stopped() 578 | } 579 | } 580 | 581 | pub mod audio_io_device_type { 582 | use {super::*, std::ptr::null_mut}; 583 | 584 | pub fn name(self_: &BoxedAudioIODeviceType) -> String { 585 | self_.name() 586 | } 587 | 588 | pub fn scan_for_devices(mut self_: Pin<&mut BoxedAudioIODeviceType>) { 589 | self_.scan_for_devices() 590 | } 591 | 592 | pub fn get_device_names(self_: &BoxedAudioIODeviceType, input: bool) -> Vec { 593 | if input { 594 | self_.input_devices() 595 | } else { 596 | self_.output_devices() 597 | } 598 | } 599 | 600 | pub fn create_device( 601 | mut self_: Pin<&mut BoxedAudioIODeviceType>, 602 | input_name: &str, 603 | output_name: &str, 604 | ) -> *mut BoxedAudioIODevice { 605 | let device = self_.as_mut().create_device(input_name, output_name); 606 | 607 | device 608 | .map(|device| Box::into_raw(Box::new(device))) 609 | .unwrap_or(null_mut()) 610 | } 611 | 612 | pub fn destroy_device(device: *mut BoxedAudioIODevice) { 613 | if device.is_null() { 614 | return; 615 | } 616 | 617 | let _ = unsafe { Box::from_raw(device) }; 618 | } 619 | } 620 | 621 | pub mod audio_io_device { 622 | use super::*; 623 | 624 | pub fn device_name(self_: &BoxedAudioIODevice) -> String { 625 | self_.name().to_string() 626 | } 627 | 628 | pub fn device_type_name(self_: &BoxedAudioIODevice) -> String { 629 | self_.type_name().to_string() 630 | } 631 | 632 | pub fn device_sample_rate(mut self_: Pin<&mut BoxedAudioIODevice>) -> f64 { 633 | self_.sample_rate() 634 | } 635 | 636 | pub fn device_buffer_size(mut self_: Pin<&mut BoxedAudioIODevice>) -> usize { 637 | self_.buffer_size() 638 | } 639 | 640 | pub fn device_available_sample_rates(mut self_: Pin<&mut BoxedAudioIODevice>) -> Vec { 641 | self_.available_sample_rates() 642 | } 643 | 644 | pub fn device_available_buffer_sizes( 645 | mut self_: Pin<&mut BoxedAudioIODevice>, 646 | ) -> Vec { 647 | self_.available_buffer_sizes() 648 | } 649 | 650 | pub fn device_open( 651 | mut self_: Pin<&mut BoxedAudioIODevice>, 652 | sample_rate: f64, 653 | buffer_size: usize, 654 | ) -> String { 655 | match self_.open(sample_rate, buffer_size) { 656 | Ok(()) => String::default(), 657 | Err(error) => error.to_string(), 658 | } 659 | } 660 | 661 | pub fn device_close(mut self_: Pin<&mut BoxedAudioIODevice>) { 662 | self_.close() 663 | } 664 | } 665 | } 666 | 667 | /// Controls for the system volume. 668 | pub struct SystemAudioVolume; 669 | 670 | impl SystemAudioVolume { 671 | /// Get the current system volume. 672 | pub fn get_gain() -> f32 { 673 | juce::get_gain() 674 | } 675 | 676 | /// Set the system volume. 677 | pub fn set_gain(gain: f32) { 678 | juce::set_gain(gain.clamp(0.0, 1.0)) 679 | } 680 | 681 | /// Returns true if the system audio output is muted. 682 | pub fn is_muted() -> bool { 683 | juce::is_muted() 684 | } 685 | 686 | /// Mute the system audio output. 687 | pub fn mute() { 688 | juce::set_muted(true); 689 | } 690 | 691 | /// Unmute the system audio output. 692 | pub fn unmute() { 693 | juce::set_muted(false); 694 | } 695 | } 696 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rust bindings for [JUCE](https://juce.com/) using [cxx](https://github.com/dtolnay/cxx). 2 | 3 | pub mod juce_audio_basics; 4 | pub mod juce_audio_devices; 5 | 6 | use { 7 | juce_audio_devices::{ 8 | ffi::{ 9 | audio_io_device::{ 10 | device_available_buffer_sizes, device_available_sample_rates, device_buffer_size, 11 | device_close, device_name, device_open, device_sample_rate, device_type_name, 12 | }, 13 | audio_io_device_callback::{about_to_start, process_block, stopped}, 14 | audio_io_device_type::{ 15 | create_device, destroy_device, get_device_names, name, scan_for_devices, 16 | }, 17 | }, 18 | BoxedAudioIODevice, BoxedAudioIODeviceCallback, BoxedAudioIODeviceType, 19 | }, 20 | std::{ 21 | rc::Rc, 22 | sync::atomic::{AtomicBool, Ordering}, 23 | }, 24 | }; 25 | 26 | /// Returns the version of the JUCE library. 27 | pub fn juce_version() -> String { 28 | juce::version() 29 | } 30 | 31 | /// A handle to the JUCE runtime. Required for certain JUCE classes. 32 | /// 33 | /// Once all references to this object are dropped, the JUCE runtime will be shut down. 34 | #[must_use] 35 | #[derive(Clone)] 36 | pub struct JUCE { 37 | _app: Rc, 38 | } 39 | 40 | static IS_JUCE_RUNNING: AtomicBool = AtomicBool::new(false); 41 | 42 | struct JuceApp; 43 | 44 | impl JuceApp { 45 | fn new() -> Self { 46 | juce::initialise_juce(); 47 | 48 | #[cfg(target_os = "macos")] 49 | juce::initialise_ns_application(); 50 | 51 | Self 52 | } 53 | } 54 | 55 | impl Drop for JuceApp { 56 | fn drop(&mut self) { 57 | juce::shutdown_juce(); 58 | 59 | IS_JUCE_RUNNING.store(false, Ordering::SeqCst); 60 | } 61 | } 62 | 63 | #[derive(Debug)] 64 | enum InitialiseError { 65 | JuceAlreadyInitialised, 66 | } 67 | 68 | impl std::error::Error for InitialiseError {} 69 | 70 | impl std::fmt::Display for InitialiseError { 71 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 72 | match self { 73 | Self::JuceAlreadyInitialised => write!(f, "JUCE has already been initialised"), 74 | } 75 | } 76 | } 77 | 78 | impl JUCE { 79 | /// Initialises the JUCE runtime. 80 | /// 81 | /// # Panics 82 | /// 83 | /// This function will panic if the JUCE runtime is already initialised. 84 | pub fn initialise() -> Self { 85 | Self::try_initialise().unwrap() 86 | } 87 | 88 | fn try_initialise() -> std::result::Result { 89 | let result = 90 | IS_JUCE_RUNNING.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst); 91 | 92 | if result.is_err() { 93 | return Err(InitialiseError::JuceAlreadyInitialised); 94 | } 95 | 96 | Ok(Self { 97 | _app: Rc::new(JuceApp::new()), 98 | }) 99 | } 100 | } 101 | 102 | pub type Exception = cxx::Exception; 103 | pub type Result = std::result::Result; 104 | 105 | #[cxx::bridge(namespace = "cxx_juce")] 106 | pub(crate) mod juce { 107 | extern "Rust" { 108 | type BoxedAudioIODeviceCallback; 109 | 110 | #[namespace = "audio_io_device_callback"] 111 | #[cxx_name = "aboutToStart"] 112 | fn about_to_start( 113 | callback: Pin<&mut BoxedAudioIODeviceCallback>, 114 | device: Pin<&mut AudioIODevice>, 115 | ); 116 | 117 | #[namespace = "audio_io_device_callback"] 118 | #[cxx_name = "processBlock"] 119 | fn process_block( 120 | callback: Pin<&mut BoxedAudioIODeviceCallback>, 121 | input: &AudioSampleBuffer, 122 | output: Pin<&mut AudioSampleBuffer>, 123 | ); 124 | 125 | #[namespace = "audio_io_device_callback"] 126 | #[cxx_name = "stopped"] 127 | fn stopped(callback: Pin<&mut BoxedAudioIODeviceCallback>); 128 | 129 | type BoxedAudioIODeviceType; 130 | 131 | #[namespace = "audio_io_device_type"] 132 | #[cxx_name = "name"] 133 | fn name(self_: &BoxedAudioIODeviceType) -> String; 134 | 135 | #[namespace = "audio_io_device_type"] 136 | #[cxx_name = "scanForDevices"] 137 | fn scan_for_devices(self_: Pin<&mut BoxedAudioIODeviceType>); 138 | 139 | #[namespace = "audio_io_device_type"] 140 | #[cxx_name = "getDeviceNames"] 141 | fn get_device_names(self_: &BoxedAudioIODeviceType, input: bool) -> Vec; 142 | 143 | #[namespace = "audio_io_device_type"] 144 | #[cxx_name = "createDevice"] 145 | fn create_device( 146 | self_: Pin<&mut BoxedAudioIODeviceType>, 147 | input_device_name: &str, 148 | output_device_name: &str, 149 | ) -> *mut BoxedAudioIODevice; 150 | 151 | #[namespace = "audio_io_device_type"] 152 | #[cxx_name = "destroyDevice"] 153 | unsafe fn destroy_device(self_: *mut BoxedAudioIODevice); 154 | 155 | type BoxedAudioIODevice; 156 | 157 | #[namespace = "audio_io_device"] 158 | #[cxx_name = "deviceName"] 159 | pub fn device_name(self_: &BoxedAudioIODevice) -> String; 160 | 161 | #[namespace = "audio_io_device"] 162 | #[cxx_name = "typeName"] 163 | pub fn device_type_name(self_: &BoxedAudioIODevice) -> String; 164 | 165 | #[namespace = "audio_io_device"] 166 | #[cxx_name = "sampleRate"] 167 | pub fn device_sample_rate(self_: Pin<&mut BoxedAudioIODevice>) -> f64; 168 | 169 | #[namespace = "audio_io_device"] 170 | #[cxx_name = "bufferSize"] 171 | pub fn device_buffer_size(self_: Pin<&mut BoxedAudioIODevice>) -> usize; 172 | 173 | #[namespace = "audio_io_device"] 174 | #[cxx_name = "availableSampleRates"] 175 | pub fn device_available_sample_rates(self_: Pin<&mut BoxedAudioIODevice>) -> Vec; 176 | 177 | #[namespace = "audio_io_device"] 178 | #[cxx_name = "availableBufferSizes"] 179 | pub fn device_available_buffer_sizes(self_: Pin<&mut BoxedAudioIODevice>) -> Vec; 180 | 181 | #[namespace = "audio_io_device"] 182 | #[cxx_name = "open"] 183 | pub fn device_open( 184 | self_: Pin<&mut BoxedAudioIODevice>, 185 | sample_rate: f64, 186 | buffer_size: usize, 187 | ) -> String; 188 | 189 | #[namespace = "audio_io_device"] 190 | #[cxx_name = "close"] 191 | pub fn device_close(self_: Pin<&mut BoxedAudioIODevice>); 192 | } 193 | 194 | unsafe extern "C++" { 195 | include!("cxx-juce/bridge/cxx_juce.h"); 196 | 197 | #[rust_name = "version"] 198 | pub fn juceVersion() -> String; 199 | 200 | #[rust_name = "initialise_juce"] 201 | pub fn initialiseJuce(); 202 | 203 | #[rust_name = "shutdown_juce"] 204 | pub fn shutdownJuce(); 205 | 206 | #[cfg(target_os = "macos")] 207 | #[namespace = "juce"] 208 | #[rust_name = "initialise_ns_application"] 209 | pub fn initialiseNSApplication(); 210 | 211 | #[namespace = "juce"] 212 | pub type AudioIODeviceTypeArray; 213 | 214 | pub fn size(self: &AudioIODeviceTypeArray) -> i32; 215 | 216 | #[rust_name = "get_unchecked"] 217 | pub fn getUnchecked(self: &AudioIODeviceTypeArray, index: i32) -> *mut AudioIODeviceType; 218 | 219 | pub type AudioDeviceSetup; 220 | 221 | #[rust_name = "create_audio_device_setup"] 222 | pub fn createAudioDeviceSetup() -> UniquePtr; 223 | 224 | #[rust_name = "output_device_name"] 225 | pub fn outputDeviceName(self: &AudioDeviceSetup) -> &str; 226 | 227 | #[rust_name = "input_device_name"] 228 | pub fn inputDeviceName(self: &AudioDeviceSetup) -> &str; 229 | 230 | #[rust_name = "sample_rate"] 231 | pub fn sampleRate(self: &AudioDeviceSetup) -> f64; 232 | 233 | #[rust_name = "buffer_size"] 234 | pub fn bufferSize(self: &AudioDeviceSetup) -> i32; 235 | 236 | #[rust_name = "set_output_device_name"] 237 | pub fn setOutputDeviceName(self: Pin<&mut AudioDeviceSetup>, name: &str); 238 | 239 | #[rust_name = "set_input_device_name"] 240 | pub fn setInputDeviceName(self: Pin<&mut AudioDeviceSetup>, name: &str); 241 | 242 | #[rust_name = "set_sample_rate"] 243 | pub fn setSampleRate(self: Pin<&mut AudioDeviceSetup>, sample_rate: f64); 244 | 245 | #[rust_name = "set_buffer_size"] 246 | pub fn setBufferSize(self: Pin<&mut AudioDeviceSetup>, buffer_size: i32); 247 | 248 | #[rust_name = "number_of_input_channels"] 249 | pub fn numberOfInputChannels(self: &AudioDeviceSetup) -> i32; 250 | 251 | #[rust_name = "set_number_of_input_channels"] 252 | pub fn setNumberOfInputChannels( 253 | self: Pin<&mut AudioDeviceSetup>, 254 | number_of_input_channels: i32, 255 | ); 256 | 257 | #[rust_name = "use_default_input_channels"] 258 | pub fn useDefaultInputChannels(self: Pin<&mut AudioDeviceSetup>, use_default: bool); 259 | 260 | #[rust_name = "using_default_input_channels"] 261 | pub fn usingDefaultInputChannels(self: &AudioDeviceSetup) -> bool; 262 | 263 | #[rust_name = "number_of_output_channels"] 264 | pub fn numberOfOutputChannels(self: &AudioDeviceSetup) -> i32; 265 | 266 | #[rust_name = "set_number_of_output_channels"] 267 | pub fn setNumberOfOutputChannels( 268 | self: Pin<&mut AudioDeviceSetup>, 269 | number_of_output_channels: i32, 270 | ); 271 | 272 | #[rust_name = "use_default_output_channels"] 273 | pub fn useDefaultOutputChannels(self: Pin<&mut AudioDeviceSetup>, use_default: bool); 274 | 275 | #[rust_name = "using_default_output_channels"] 276 | pub fn usingDefaultOutputChannels(self: &AudioDeviceSetup) -> bool; 277 | 278 | pub type AudioDeviceManager; 279 | 280 | #[rust_name = "create_audio_device_manager"] 281 | pub fn createAudioDeviceManager() -> UniquePtr; 282 | 283 | #[rust_name = "wrap_audio_callback"] 284 | pub fn wrapAudioCallback( 285 | callback: Box, 286 | ) -> UniquePtr; 287 | 288 | #[rust_name = "initialise_with_default_devices"] 289 | pub fn initialiseWithDefaultDevices( 290 | self: Pin<&mut AudioDeviceManager>, 291 | num_input_channels: i32, 292 | num_output_channels: i32, 293 | ) -> Result<()>; 294 | 295 | #[rust_name = "get_audio_device_setup"] 296 | pub fn getAudioDeviceSetup(self: &AudioDeviceManager) -> UniquePtr; 297 | 298 | #[rust_name = "set_audio_device_setup"] 299 | pub fn setAudioDeviceSetup(self: Pin<&mut AudioDeviceManager>, setup: &AudioDeviceSetup); 300 | 301 | #[rust_name = "get_current_audio_device"] 302 | pub fn getCurrentAudioDevice(self: Pin<&mut AudioDeviceManager>) -> *mut AudioIODevice; 303 | 304 | #[rust_name = "get_available_device_types"] 305 | pub fn getAvailableDeviceTypes( 306 | self: Pin<&mut AudioDeviceManager>, 307 | ) -> &AudioIODeviceTypeArray; 308 | 309 | #[rust_name = "get_current_device_type_object"] 310 | pub fn getCurrentDeviceTypeObject(self: &AudioDeviceManager) -> *mut AudioIODeviceType; 311 | 312 | #[rust_name = "play_test_sound"] 313 | pub fn playTestSound(self: Pin<&mut AudioDeviceManager>); 314 | 315 | #[rust_name = "add_audio_callback"] 316 | pub fn addAudioCallback( 317 | self: Pin<&mut AudioDeviceManager>, 318 | callback: &UniquePtr, 319 | ); 320 | 321 | #[rust_name = "remove_audio_callback"] 322 | pub fn removeAudioCallback( 323 | self: Pin<&mut AudioDeviceManager>, 324 | callback: &UniquePtr, 325 | ); 326 | 327 | #[rust_name = "add_audio_device_type"] 328 | pub fn addAudioDeviceType( 329 | self: Pin<&mut AudioDeviceManager>, 330 | device_type: Box, 331 | ); 332 | 333 | #[rust_name = "set_current_audio_device_type"] 334 | pub fn setCurrentAudioDeviceType(self: Pin<&mut AudioDeviceManager>, device_type: &str); 335 | 336 | #[namespace = "juce"] 337 | pub type AudioIODevice; 338 | 339 | #[namespace = "cxx_juce::audio_io_device"] 340 | #[rust_name = "get_device_name"] 341 | pub fn getDeviceName(self_: &AudioIODevice) -> &str; 342 | 343 | #[namespace = "cxx_juce::audio_io_device"] 344 | #[rust_name = "get_device_type_name"] 345 | pub fn getDeviceTypeName(self_: &AudioIODevice) -> &str; 346 | 347 | #[rust_name = "get_current_sample_rate"] 348 | pub fn getCurrentSampleRate(self: Pin<&mut AudioIODevice>) -> f64; 349 | 350 | #[rust_name = "get_current_buffer_size_samples"] 351 | pub fn getCurrentBufferSizeSamples(self: Pin<&mut AudioIODevice>) -> i32; 352 | 353 | #[namespace = "cxx_juce::audio_io_device"] 354 | #[rust_name = "get_available_sample_rates"] 355 | pub fn getAvailableSampleRates(self_: Pin<&mut AudioIODevice>) -> Vec; 356 | 357 | #[namespace = "cxx_juce::audio_io_device"] 358 | #[rust_name = "get_available_buffer_sizes"] 359 | pub fn getAvailableBufferSizes(self_: Pin<&mut AudioIODevice>) -> Vec; 360 | 361 | #[namespace = "cxx_juce::audio_io_device"] 362 | #[rust_name = "open"] 363 | pub fn open( 364 | self_: Pin<&mut AudioIODevice>, 365 | sample_rate: f64, 366 | buffer_size: usize, 367 | ) -> Result<()>; 368 | 369 | #[rust_name = "close"] 370 | pub fn close(self: Pin<&mut AudioIODevice>); 371 | 372 | #[namespace = "cxx_juce::audio_io_device"] 373 | #[rust_name = "count_active_input_channels"] 374 | pub fn countActiveInputChannels(self_: &AudioIODevice) -> i32; 375 | 376 | #[namespace = "cxx_juce::audio_io_device"] 377 | #[rust_name = "count_active_output_channels"] 378 | pub fn countActiveOutputChannels(self_: &AudioIODevice) -> i32; 379 | 380 | #[namespace = "juce"] 381 | pub type AudioIODeviceType; 382 | 383 | #[namespace = "cxx_juce::audio_io_device_type"] 384 | #[rust_name = "get_type_name"] 385 | pub fn getTypeName(self_: &AudioIODeviceType) -> String; 386 | 387 | #[rust_name = "scan_for_devices"] 388 | pub fn scanForDevices(self: Pin<&mut AudioIODeviceType>); 389 | 390 | #[namespace = "cxx_juce::audio_io_device_type"] 391 | #[rust_name = "get_input_device_names"] 392 | pub fn getInputDeviceNames(self_: &AudioIODeviceType) -> Vec; 393 | 394 | #[namespace = "cxx_juce::audio_io_device_type"] 395 | #[rust_name = "get_output_device_names"] 396 | pub fn getOutputDeviceNames(self_: &AudioIODeviceType) -> Vec; 397 | 398 | #[namespace = "cxx_juce::audio_io_device_type"] 399 | #[rust_name = "new_device"] 400 | pub fn createDevice( 401 | self_: Pin<&mut AudioIODeviceType>, 402 | input_device_name: &str, 403 | output_device_name: &str, 404 | ) -> UniquePtr; 405 | 406 | #[namespace = "juce"] 407 | pub type AudioSampleBuffer; 408 | 409 | #[rust_name = "get_num_channels"] 410 | pub fn getNumChannels(self: &AudioSampleBuffer) -> i32; 411 | 412 | #[rust_name = "get_num_samples"] 413 | pub fn getNumSamples(self: &AudioSampleBuffer) -> i32; 414 | 415 | #[rust_name = "get_read_pointer"] 416 | pub fn getReadPointer(self: &AudioSampleBuffer, channel: i32) -> *const f32; 417 | 418 | #[rust_name = "get_write_pointer"] 419 | pub fn getWritePointer(self: Pin<&mut AudioSampleBuffer>, channel: i32) -> *mut f32; 420 | 421 | #[rust_name = "clear"] 422 | pub fn clear(self: Pin<&mut AudioSampleBuffer>); 423 | 424 | pub type AudioCallbackWrapper; 425 | 426 | #[namespace = "cxx_juce::system_audio_volume"] 427 | #[rust_name = "set_muted"] 428 | pub fn setMuted(muted: bool); 429 | 430 | #[namespace = "cxx_juce::system_audio_volume"] 431 | #[rust_name = "is_muted"] 432 | pub fn isMuted() -> bool; 433 | 434 | #[namespace = "cxx_juce::system_audio_volume"] 435 | #[rust_name = "set_gain"] 436 | pub fn setGain(gain: f32); 437 | 438 | #[namespace = "cxx_juce::system_audio_volume"] 439 | #[rust_name = "get_gain"] 440 | pub fn getGain() -> f32; 441 | 442 | #[namespace = "juce"] 443 | pub type SingleThreadedIIRFilter; 444 | 445 | #[namespace = "cxx_juce::iir_filter"] 446 | #[rust_name = "create_iir_filter"] 447 | pub fn createIIRFilter(coefficients: [f32; 5]) -> UniquePtr; 448 | 449 | #[namespace = "juce"] 450 | #[rust_name = "process_samples"] 451 | pub unsafe fn processSamples( 452 | self: Pin<&mut SingleThreadedIIRFilter>, 453 | samples: *mut f32, 454 | num_samples: i32, 455 | ); 456 | 457 | #[namespace = "cxx_juce::iir_filter"] 458 | #[rust_name = "make_low_pass"] 459 | pub fn makeLowPass(sample_rate: f64, frequency: f64, q: f64) -> [f32; 5]; 460 | 461 | #[namespace = "cxx_juce::iir_filter"] 462 | #[rust_name = "make_high_pass"] 463 | pub fn makeHighPass(sample_rate: f64, frequency: f64, q: f64) -> [f32; 5]; 464 | 465 | #[namespace = "cxx_juce::iir_filter"] 466 | #[rust_name = "make_notch_filter"] 467 | pub fn makeNotchFilter(sample_rate: f64, frequency: f64, q: f64) -> [f32; 5]; 468 | } 469 | } 470 | 471 | #[cfg(test)] 472 | mod test { 473 | use super::*; 474 | 475 | fn try_to_initialise_juce_on_new_thread() -> std::thread::Result<()> { 476 | std::thread::spawn(move || { 477 | let _juce = JUCE::initialise(); 478 | }) 479 | .join() 480 | } 481 | 482 | #[test] 483 | #[should_panic] 484 | fn initialising_juce_twice_on_the_same_thread_should_panic() { 485 | let _juce = JUCE::initialise(); 486 | let _juce = JUCE::initialise(); 487 | } 488 | 489 | #[test] 490 | fn initialising_juce_again_on_the_same_thread_after_shutdown_is_ok() { 491 | let juce = JUCE::initialise(); 492 | drop(juce); 493 | 494 | let _juce = JUCE::initialise(); 495 | } 496 | 497 | #[test] 498 | fn juce_cant_be_initialised_simultaneously_on_two_different_threads() { 499 | let _juce = JUCE::initialise(); 500 | 501 | assert!(try_to_initialise_juce_on_new_thread().is_err()); 502 | } 503 | 504 | #[test] 505 | fn juce_can_run_on_a_different_thread_after_finishing_on_another() { 506 | let juce = JUCE::initialise(); 507 | drop(juce); 508 | 509 | assert!(try_to_initialise_juce_on_new_thread().is_ok()); 510 | } 511 | 512 | #[test] 513 | fn juce_is_shutdown_once_all_references_have_been_dropped() { 514 | let a = JUCE::initialise(); 515 | let b = a.clone(); 516 | 517 | drop(a); 518 | 519 | assert!(try_to_initialise_juce_on_new_thread().is_err()); 520 | 521 | drop(b); 522 | 523 | assert!(try_to_initialise_juce_on_new_thread().is_ok()); 524 | } 525 | } 526 | -------------------------------------------------------------------------------- /tests/juce_audio_devices.rs: -------------------------------------------------------------------------------- 1 | use cxx_juce::{ 2 | juce_audio_devices::{ 3 | AudioDeviceManager, AudioDeviceSetup, AudioIODevice, AudioIODeviceType, ChannelCount, 4 | }, 5 | Result, JUCE, 6 | }; 7 | 8 | #[derive(Default)] 9 | struct MockAudioDeviceType { 10 | input_devices: Vec, 11 | output_devices: Vec, 12 | } 13 | 14 | impl AudioIODeviceType for MockAudioDeviceType { 15 | fn name(&self) -> String { 16 | "Test".to_string() 17 | } 18 | 19 | fn scan_for_devices(&mut self) { 20 | self.input_devices = ["Microphone", "Audio Interface", "Headset"] 21 | .into_iter() 22 | .map(String::from) 23 | .collect(); 24 | 25 | self.output_devices = ["Speakers", "Headphones"] 26 | .into_iter() 27 | .map(String::from) 28 | .collect(); 29 | } 30 | 31 | fn input_devices(&self) -> Vec { 32 | self.input_devices.clone() 33 | } 34 | 35 | fn output_devices(&self) -> Vec { 36 | self.output_devices.clone() 37 | } 38 | 39 | fn create_device( 40 | &mut self, 41 | input_device_name: &str, 42 | output_device_name: &str, 43 | ) -> Option> { 44 | Some(Box::new(MockAudioDevice { 45 | name: format!("{} / {}", input_device_name, output_device_name), 46 | type_name: self.name(), 47 | sample_rate: 44100.0, 48 | buffer_size: 128, 49 | })) 50 | } 51 | } 52 | 53 | struct MockAudioDevice { 54 | name: String, 55 | type_name: String, 56 | sample_rate: f64, 57 | buffer_size: usize, 58 | } 59 | 60 | impl AudioIODevice for MockAudioDevice { 61 | fn name(&self) -> &str { 62 | &self.name 63 | } 64 | 65 | fn type_name(&self) -> &str { 66 | &self.type_name 67 | } 68 | 69 | fn sample_rate(&mut self) -> f64 { 70 | self.sample_rate 71 | } 72 | 73 | fn buffer_size(&mut self) -> usize { 74 | self.buffer_size 75 | } 76 | 77 | fn available_sample_rates(&mut self) -> Vec { 78 | vec![44100.0, 48000.0] 79 | } 80 | 81 | fn available_buffer_sizes(&mut self) -> Vec { 82 | vec![128, 256, 512] 83 | } 84 | 85 | fn open(&mut self, sample_rate: f64, buffer_size: usize) -> Result<()> { 86 | self.sample_rate = sample_rate; 87 | self.buffer_size = buffer_size; 88 | Ok(()) 89 | } 90 | 91 | fn close(&mut self) {} 92 | 93 | fn input_channels(&self) -> i32 { 94 | 2 95 | } 96 | 97 | fn output_channels(&self) -> i32 { 98 | 2 99 | } 100 | } 101 | 102 | #[test] 103 | fn can_query_audio_device_types() { 104 | let juce = JUCE::initialise(); 105 | let mut audio_device_manager = AudioDeviceManager::new(&juce); 106 | audio_device_manager.add_audio_device_type(MockAudioDeviceType::default()); 107 | audio_device_manager.set_current_audio_device_type("Test"); 108 | 109 | let mut device_type = audio_device_manager.current_device_type().unwrap(); 110 | 111 | assert_eq!(device_type.name(), "Test"); 112 | 113 | device_type.scan_for_devices(); 114 | 115 | assert_eq!( 116 | device_type.input_devices(), 117 | ["Microphone", "Audio Interface", "Headset"] 118 | .into_iter() 119 | .map(String::from) 120 | .collect::>() 121 | ); 122 | 123 | assert_eq!( 124 | device_type.output_devices(), 125 | ["Speakers", "Headphones"] 126 | .into_iter() 127 | .map(String::from) 128 | .collect::>() 129 | ); 130 | } 131 | 132 | #[test] 133 | fn can_configure_audio_device_setup() { 134 | let juce = JUCE::initialise(); 135 | let mut audio_device_manager = AudioDeviceManager::new(&juce); 136 | audio_device_manager.add_audio_device_type(MockAudioDeviceType::default()); 137 | audio_device_manager.set_current_audio_device_type("Test"); 138 | audio_device_manager 139 | .current_device_type() 140 | .unwrap() 141 | .scan_for_devices(); 142 | 143 | let setup = AudioDeviceSetup::default() 144 | .with_buffer_size(512) 145 | .with_sample_rate(48000.0) 146 | .with_input_device_name("Microphone") 147 | .with_output_device_name("Speakers"); 148 | 149 | audio_device_manager.set_audio_device_setup(&setup); 150 | 151 | let current_setup = audio_device_manager.audio_device_setup(); 152 | 153 | assert_eq!(current_setup.buffer_size(), 512); 154 | assert_eq!(current_setup.sample_rate(), 48000.0); 155 | assert_eq!(current_setup.input_device_name(), "Microphone"); 156 | assert_eq!(current_setup.output_device_name(), "Speakers"); 157 | } 158 | 159 | #[test] 160 | fn can_create_devices() { 161 | let juce = JUCE::initialise(); 162 | let mut audio_device_manager = AudioDeviceManager::new(&juce); 163 | audio_device_manager.add_audio_device_type(MockAudioDeviceType::default()); 164 | audio_device_manager.set_current_audio_device_type("Test"); 165 | audio_device_manager 166 | .current_device_type() 167 | .unwrap() 168 | .scan_for_devices(); 169 | 170 | let device = audio_device_manager 171 | .current_device_type() 172 | .unwrap() 173 | .create_device("Microphone", "Speakers") 174 | .expect("failed to create device"); 175 | 176 | assert_eq!(device.name(), "Microphone / Speakers"); 177 | assert_eq!(device.type_name(), "Test"); 178 | } 179 | 180 | #[test] 181 | fn can_configure_channel_count_in_audio_device_setup() { 182 | let setup = AudioDeviceSetup::default() 183 | .with_input_channels(ChannelCount::Custom(4)) 184 | .with_output_channels(ChannelCount::Default); 185 | 186 | assert_eq!(setup.input_channels(), ChannelCount::Custom(4)); 187 | assert_eq!(setup.output_channels(), ChannelCount::Default); 188 | } 189 | --------------------------------------------------------------------------------