1
0
Fork 0

Add most of a CPAL standalone backend

For ALSA, CoreAudio, and WASAPI.
This commit is contained in:
Robbert van der Helm 2022-08-19 17:11:24 +02:00
parent ca2e318551
commit a011eaa07c
6 changed files with 490 additions and 24 deletions

304
Cargo.lock generated
View file

@ -62,6 +62,28 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]]
name = "alsa"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5915f52fe2cf65e83924d037b6c5290b7cee097c6b5c8700746e6168a343fd6b"
dependencies = [
"alsa-sys",
"bitflags",
"libc",
"nix 0.23.1",
]
[[package]]
name = "alsa-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "android_glue"
version = "0.2.3"
@ -330,7 +352,7 @@ dependencies = [
"cocoa",
"core-foundation 0.9.3",
"keyboard-types",
"nix",
"nix 0.22.3",
"objc",
"raw-window-handle",
"uuid",
@ -348,7 +370,7 @@ dependencies = [
"cocoa",
"core-foundation 0.9.3",
"keyboard-types",
"nix",
"nix 0.22.3",
"objc",
"raw-window-handle",
"uuid",
@ -358,6 +380,25 @@ dependencies = [
"xcb-util",
]
[[package]]
name = "bindgen"
version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
]
[[package]]
name = "bit-set"
version = "0.5.2"
@ -431,6 +472,12 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
[[package]]
name = "cache-padded"
version = "1.2.0"
@ -453,6 +500,21 @@ dependencies = [
"jobserver",
]
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
@ -486,6 +548,17 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919"
[[package]]
name = "clang-sys"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "3.2.5"
@ -597,6 +670,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "combine"
version = "4.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
dependencies = [
"bytes",
"memchr",
]
[[package]]
name = "concurrent-queue"
version = "1.2.2"
@ -730,6 +813,50 @@ dependencies = [
"objc",
]
[[package]]
name = "coreaudio-rs"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88"
dependencies = [
"bitflags",
"coreaudio-sys",
]
[[package]]
name = "coreaudio-sys"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dff444d80630d7073077d38d40b4501fd518bd2b922c2a55edcc8b0f7be57e6"
dependencies = [
"bindgen",
]
[[package]]
name = "cpal"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74117836a5124f3629e4b474eed03e479abaf98988b4bb317e29f08cfe0e4116"
dependencies = [
"alsa",
"core-foundation-sys 0.8.3",
"coreaudio-rs",
"jni",
"js-sys",
"lazy_static",
"libc",
"mach",
"ndk 0.6.0",
"ndk-glue 0.6.2",
"nix 0.23.1",
"oboe",
"parking_lot 0.11.2",
"stdweb",
"thiserror",
"web-sys",
"winapi",
]
[[package]]
name = "crc32fast"
version = "1.3.2"
@ -1504,6 +1631,12 @@ dependencies = [
"version_check",
]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "gloo-timers"
version = "0.2.4"
@ -1989,6 +2122,20 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "jni"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
dependencies = [
"cesu8",
"combine",
"jni-sys",
"log",
"thiserror",
"walkdir",
]
[[package]]
name = "jni-sys"
version = "0.3.0"
@ -2086,6 +2233,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.126"
@ -2191,6 +2344,15 @@ dependencies = [
"lyon_path",
]
[[package]]
name = "mach"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
dependencies = [
"libc",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
@ -2334,7 +2496,20 @@ checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d"
dependencies = [
"bitflags",
"jni-sys",
"ndk-sys",
"ndk-sys 0.2.2",
"num_enum",
"thiserror",
]
[[package]]
name = "ndk"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4"
dependencies = [
"bitflags",
"jni-sys",
"ndk-sys 0.3.0",
"num_enum",
"thiserror",
]
@ -2354,10 +2529,25 @@ dependencies = [
"lazy_static",
"libc",
"log",
"ndk",
"ndk 0.5.0",
"ndk-context",
"ndk-macro",
"ndk-sys",
"ndk-sys 0.2.2",
]
[[package]]
name = "ndk-glue"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f"
dependencies = [
"lazy_static",
"libc",
"log",
"ndk 0.6.0",
"ndk-context",
"ndk-macro",
"ndk-sys 0.3.0",
]
[[package]]
@ -2379,6 +2569,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121"
[[package]]
name = "ndk-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97"
dependencies = [
"jni-sys",
]
[[package]]
name = "nih_plug"
version = "0.0.0"
@ -2393,6 +2592,7 @@ dependencies = [
"cfg-if 1.0.0",
"clap",
"clap-sys",
"cpal",
"crossbeam",
"jack",
"lazy_static",
@ -2488,6 +2688,19 @@ dependencies = [
"memoffset",
]
[[package]]
name = "nix"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [
"bitflags",
"cc",
"cfg-if 1.0.0",
"libc",
"memoffset",
]
[[package]]
name = "nohash-hasher"
version = "0.2.0"
@ -2513,6 +2726,17 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-integer"
version = "0.1.45"
@ -2653,6 +2877,29 @@ dependencies = [
"memchr",
]
[[package]]
name = "oboe"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27f63c358b4fa0fbcfefd7c8be5cfc39c08ce2389f5325687e7762a48d30a5c1"
dependencies = [
"jni",
"ndk 0.6.0",
"ndk-context",
"num-derive",
"num-traits",
"oboe-sys",
]
[[package]]
name = "oboe-sys"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd"
dependencies = [
"cc",
]
[[package]]
name = "once_cell"
version = "1.12.0"
@ -2813,6 +3060,12 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -3236,6 +3489,21 @@ dependencies = [
"winapi",
]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "renderdoc-sys"
version = "0.7.1"
@ -3492,6 +3760,12 @@ dependencies = [
"libc",
]
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "sid"
version = "0.6.1"
@ -3585,7 +3859,7 @@ dependencies = [
"lazy_static",
"log",
"memmap2",
"nix",
"nix 0.22.3",
"pkg-config",
"wayland-client",
"wayland-cursor",
@ -3661,6 +3935,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stdweb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
[[package]]
name = "stft"
version = "0.1.0"
@ -4235,7 +4515,7 @@ dependencies = [
"bitflags",
"downcast-rs",
"libc",
"nix",
"nix 0.22.3",
"scoped-tls",
"wayland-commons",
"wayland-scanner",
@ -4248,7 +4528,7 @@ version = "0.29.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e"
dependencies = [
"nix",
"nix 0.22.3",
"once_cell",
"smallvec",
"wayland-sys",
@ -4260,7 +4540,7 @@ version = "0.29.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd"
dependencies = [
"nix",
"nix 0.22.3",
"wayland-client",
"xcursor",
]
@ -4576,9 +4856,9 @@ dependencies = [
"libc",
"log",
"mio",
"ndk",
"ndk-glue",
"ndk-sys",
"ndk 0.5.0",
"ndk-glue 0.5.2",
"ndk-sys 0.2.2",
"objc",
"parking_lot 0.11.2",
"percent-encoding",

View file

@ -49,7 +49,7 @@ assert_process_allocs = ["dep:assert_no_alloc"]
# Enables an export target for standalone binaries through the
# `nih_export_standalone()` function. Disabled by default as this requires
# building additional dependencies for audio and MIDI handling.
standalone = ["dep:baseview", "dep:clap", "dep:jack"]
standalone = ["dep:baseview", "dep:clap", "dep:cpal", "dep:jack"]
# Enables the `nih_export_vst3!()` macro. Enabled by default. This feature
# exists mostly for GPL-compliance reasons, since even if you don't use the VST3
# wrapper you might otherwise still include a couple (unused) symbols from the
@ -97,6 +97,7 @@ assert_no_alloc = { version = "1.1", optional = true }
baseview = { git = "https://github.com/robbert-vdh/baseview.git", branch = "feature/resize", features = ["opengl"], optional = true }
# All the claps!
clap = { version = "3.2", features = ["derive"], optional = true }
cpal = { version = "0.13.5", optional = true }
# The current upstream jack panics when it can't load the JACK library, which breaks the backend fallback
jack = { git = "https://github.com/robbert-vdh/rust-jack.git", branch = "feature/handle-library-failure", optional = true }

View file

@ -70,16 +70,66 @@ pub fn nih_export_standalone_with_args<P: Plugin, Args: IntoIterator<Item = Stri
.unwrap_or_else(|err| err.exit());
match config.backend {
config::BackendType::Auto => match backend::Jack::new::<P>(config.clone()) {
Ok(backend) => {
config::BackendType::Auto => {
let result = backend::Jack::new::<P>(config.clone()).map(|backend| {
nih_log!("Using the JACK backend");
run_wrapper::<P, _>(backend, config)
}
Err(_) => {
nih_log!("Could not initialize JACK, falling back to the dummy audio backend");
run_wrapper::<P, _>(backend, config.clone())
});
#[cfg(target_os = "linux")]
let result = result.or_else(|_| {
match backend::Cpal::new::<P>(config.clone(), cpal::HostId::Alsa) {
Ok(backend) => {
nih_log!("Using the ALSA backend");
Ok(run_wrapper::<P, _>(backend, config.clone()))
}
Err(err) => {
nih_error!(
"Could not initialize either the JACK or the ALSA backends, falling \
back to the dummy audio backend"
);
Err(err)
}
}
});
#[cfg(target_os = "macos")]
let result = result.or_else(|_| {
match backend::Cpal::new::<P>(config.clone(), cpal::HostId::CoreAudio) {
Ok(backend) => {
nih_log!("Using the CoreAudio backend");
Ok(run_wrapper::<P, _>(backend, config.clone()))
}
Err(err) => {
nih_error!(
"Could not initialize either the JACK or the CoreAudio backends, \
falling back to the dummy audio backend"
);
Err(err)
}
}
});
#[cfg(target_os = "windows")]
let result = result.or_else(|_| {
match backend::Cpal::new::<P>(config.clone(), cpal::HostId::Wasapi) {
Ok(backend) => {
nih_log!("Using the WASAPI backend");
Ok(run_wrapper::<P, _>(backend, config.clone()))
}
Err(err) => {
nih_error!(
"Could not initialize either the JACK or the WASAPI backends, falling \
back to the dummy audio backend"
);
Err(err)
}
}
});
result.unwrap_or_else(|_| {
nih_error!("Falling back to the dummy audio backend, audio and MIDI will not work");
run_wrapper::<P, _>(backend::Dummy::new::<P>(config.clone()), config)
}
},
})
}
config::BackendType::Jack => match backend::Jack::new::<P>(config.clone()) {
Ok(backend) => run_wrapper::<P, _>(backend, config),
Err(err) => {
@ -87,6 +137,36 @@ pub fn nih_export_standalone_with_args<P: Plugin, Args: IntoIterator<Item = Stri
false
}
},
#[cfg(target_os = "linux")]
config::BackendType::Alsa => {
match backend::Cpal::new::<P>(config.clone(), cpal::HostId::Alsa) {
Ok(backend) => run_wrapper::<P, _>(backend, config),
Err(err) => {
nih_error!("Could not initialize the ALSA backend: {:#}", err);
false
}
}
}
#[cfg(target_os = "macos")]
config::BackendType::CoreAudio => {
match backend::Cpal::new::<P>(config.clone(), cpal::HostId::CoreAudio) {
Ok(backend) => run_wrapper::<P, _>(backend, config),
Err(err) => {
nih_error!("Could not initialize the CoreAudio backend: {:#}", err);
false
}
}
}
#[cfg(target_os = "windows")]
config::BackendType::Wasapi => {
match backend::Cpal::new::<P>(config.clone(), cpal::HostId::Wasapi) {
Ok(backend) => run_wrapper::<P, _>(backend, config),
Err(err) => {
nih_error!("Could not initialize the WASAPI backend: {:#}", err);
false
}
}
}
config::BackendType::Dummy => {
run_wrapper::<P, _>(backend::Dummy::new::<P>(config.clone()), config)
}

View file

@ -1,12 +1,15 @@
pub use self::dummy::Dummy;
pub use self::jack::Jack;
pub use crate::buffer::Buffer;
use crate::context::Transport;
use crate::midi::NoteEvent;
mod cpal;
mod dummy;
mod jack;
pub use self::cpal::Cpal;
pub use self::dummy::Dummy;
pub use self::jack::Jack;
pub use crate::buffer::Buffer;
/// An audio+MIDI backend for the standalone wrapper.
pub trait Backend: 'static + Send + Sync {
/// Start processing audio and MIDI on this thread. The process callback will be called whenever

View file

@ -0,0 +1,82 @@
use anyhow::{Context, Result};
use cpal::traits::*;
use super::super::config::WrapperConfig;
use super::Backend;
use crate::buffer::Buffer;
use crate::context::Transport;
use crate::midi::NoteEvent;
use crate::plugin::Plugin;
/// Uses CPAL for audio and midir for MIDI.
pub struct Cpal {
// TODO:
// TODO: MIDI
}
impl Backend for Cpal {
fn run(
&mut self,
mut cb: impl FnMut(&mut Buffer, Transport, &[NoteEvent], &mut Vec<NoteEvent>) -> bool
+ 'static
+ Send,
) {
// TODO:
}
}
impl Cpal {
/// Initialize the backend with the specified host. Returns an error if this failed for whatever
/// reason.
pub fn new<P: Plugin>(config: WrapperConfig, cpal_host_id: cpal::HostId) -> Result<Self> {
let host = cpal::host_from_id(cpal_host_id).context("The Audio API is unavailable")?;
// No input device is connected unless requested by the user to avoid feedback loops
let input_device = config
.input_device
.map(|name| -> Result<cpal::Device> {
let device = host
.input_devices()
.context("No audio input devices available")?
// `.name()` returns a `Result` with a non-Eq error type so you can't compare this
// directly
.find(|d| d.name().as_deref().map(|n| n == name).unwrap_or(false))
.with_context(|| {
// This is a bit awkward, but instead of adding a dedicated option we'll just
// list all of the available devices in the error message when the chosen device
// does not exist
let mut message =
format!("Unknown input device '{name}'. Available devices are:");
for device_name in host.input_devices().unwrap().flat_map(|d| d.name()) {
message.push_str(&format!("\n{device_name}"))
}
message
})?;
Ok(device)
})
.transpose()?;
let output_device = match config.output_device {
Some(name) => host
.output_devices()
.context("No audio output devices available")?
.find(|d| d.name().as_deref().map(|n| n == name).unwrap_or(false))
.with_context(|| {
let mut message =
format!("Unknown output device '{name}'. Available devices are:");
for device_name in host.output_devices().unwrap().flat_map(|d| d.name()) {
message.push_str(&format!("\n{device_name}"))
}
message
})?,
None => host
.default_output_device()
.context("No default audio output device available")?,
};
anyhow::bail!("Not yet implemented");
}
}

View file

@ -10,6 +10,17 @@ pub struct WrapperConfig {
/// no audio input or output if the other backends are not available.
#[clap(value_parser, short = 'b', long, default_value = "auto")]
pub backend: BackendType,
/// The input device for the ALSA, CoreAudio, and WASAPI backends. No input will be connected if
/// this is not specified.
///
/// Specifying an empty string or other invalid value will list all available input devices.
#[clap(value_parser, long)]
pub input_device: Option<String>,
/// The output device for the ALSA, CoreAudio, and WASAPI backends.
///
/// Specifying an empty string or other invalid value will list all available output devices.
#[clap(value_parser, long)]
pub output_device: Option<String>,
// These will default to the plugin's default input and output channel count. We could set the
// default value here to match those, but that would require a custom Args+FromArgMatches
@ -81,6 +92,15 @@ pub enum BackendType {
Auto,
/// Use JACK for audio and MIDI.
Jack,
/// Use ALSA for audio and MIDI.
#[cfg(target_os = "linux")]
Alsa,
/// Use CoreAudio for audio and MIDI.
#[cfg(target_os = "macos")]
CoreAudio,
/// Use WASAPI for audio and MIDI.
#[cfg(target_os = "windows")]
Wasapi,
/// Does not playback or receive any audio or MIDI.
Dummy,
}