Add basic MIDI device selection using midir
This commit is contained in:
parent
98b8571dc9
commit
2ed95bb52d
3 changed files with 171 additions and 3 deletions
66
Cargo.lock
generated
66
Cargo.lock
generated
|
@ -89,6 +89,18 @@ dependencies = [
|
||||||
"nix 0.23.2",
|
"nix 0.23.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alsa"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44"
|
||||||
|
dependencies = [
|
||||||
|
"alsa-sys",
|
||||||
|
"bitflags",
|
||||||
|
"libc",
|
||||||
|
"nix 0.24.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alsa-sys"
|
name = "alsa-sys"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -856,6 +868,26 @@ dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coremidi"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a7847ca018a67204508b77cb9e6de670125075f7464fff5f673023378fa34f5"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"coremidi-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coremidi-sys"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79a6deed0c97b2d40abbab77e4c97f81d71e162600423382c277dd640019116c"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-text"
|
name = "cosmic-text"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -881,7 +913,7 @@ version = "0.14.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f342c1b63e185e9953584ff2199726bf53850d96610a310e3aca09e9405a2d0b"
|
checksum = "f342c1b63e185e9953584ff2199726bf53850d96610a310e3aca09e9405a2d0b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alsa",
|
"alsa 0.6.0",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"coreaudio-rs",
|
"coreaudio-rs",
|
||||||
"jni",
|
"jni",
|
||||||
|
@ -2252,6 +2284,22 @@ dependencies = [
|
||||||
"nih_plug",
|
"nih_plug",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "midir"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a456444d83e7ead06ae6a5c0a215ed70282947ff3897fb45fcb052b757284731"
|
||||||
|
dependencies = [
|
||||||
|
"alsa 0.7.0",
|
||||||
|
"bitflags",
|
||||||
|
"coremidi",
|
||||||
|
"js-sys",
|
||||||
|
"libc",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
"windows 0.43.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -2363,6 +2411,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"midi-consts",
|
"midi-consts",
|
||||||
|
"midir",
|
||||||
"nih_plug_derive",
|
"nih_plug_derive",
|
||||||
"objc",
|
"objc",
|
||||||
"parking_lot 0.12.1",
|
"parking_lot 0.12.1",
|
||||||
|
@ -4564,6 +4613,21 @@ dependencies = [
|
||||||
"windows_x86_64_msvc 0.37.0",
|
"windows_x86_64_msvc 0.37.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.43.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc 0.42.1",
|
||||||
|
"windows_i686_gnu 0.42.1",
|
||||||
|
"windows_i686_msvc 0.42.1",
|
||||||
|
"windows_x86_64_gnu 0.42.1",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc 0.42.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.42.0"
|
version = "0.42.0"
|
||||||
|
|
|
@ -52,7 +52,7 @@ assert_process_allocs = ["dep:assert_no_alloc"]
|
||||||
# Enables an export target for standalone binaries through the
|
# Enables an export target for standalone binaries through the
|
||||||
# `nih_export_standalone()` function. Disabled by default as this requires
|
# `nih_export_standalone()` function. Disabled by default as this requires
|
||||||
# building additional dependencies for audio and MIDI handling.
|
# building additional dependencies for audio and MIDI handling.
|
||||||
standalone = ["dep:baseview", "dep:clap", "dep:cpal", "dep:jack", "dep:rtrb"]
|
standalone = ["dep:baseview", "dep:clap", "dep:cpal", "dep:jack", "dep:midir", "dep:rtrb"]
|
||||||
# Enables the `nih_export_vst3!()` macro. Enabled by default. This feature
|
# 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
|
# 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
|
# wrapper you might otherwise still include a couple (unused) symbols from the
|
||||||
|
@ -113,6 +113,7 @@ cpal = { version = "0.14.1", optional = true }
|
||||||
# Current upstream JACK always links to libjack, even when using the default
|
# Current upstream JACK always links to libjack, even when using the default
|
||||||
# dynamic loading feature
|
# dynamic loading feature
|
||||||
jack = { git = "https://github.com/robbert-vdh/rust-jack.git", tag = "tmp-handle-library-failure", optional = true }
|
jack = { git = "https://github.com/robbert-vdh/rust-jack.git", tag = "tmp-handle-library-failure", optional = true }
|
||||||
|
midir = { version = "0.9.1", optional = true }
|
||||||
rtrb = { version = "0.2.2", optional = true }
|
rtrb = { version = "0.2.2", optional = true }
|
||||||
|
|
||||||
# Used for the `vst3` feature
|
# Used for the `vst3` feature
|
||||||
|
|
|
@ -6,6 +6,8 @@ use cpal::{
|
||||||
StreamConfig,
|
StreamConfig,
|
||||||
};
|
};
|
||||||
use crossbeam::sync::{Parker, Unparker};
|
use crossbeam::sync::{Parker, Unparker};
|
||||||
|
use midir::{MidiInput, MidiInputPort, MidiOutput, MidiOutputPort};
|
||||||
|
use parking_lot::Mutex;
|
||||||
use rtrb::RingBuffer;
|
use rtrb::RingBuffer;
|
||||||
|
|
||||||
use super::super::config::WrapperConfig;
|
use super::super::config::WrapperConfig;
|
||||||
|
@ -24,7 +26,9 @@ pub struct CpalMidir {
|
||||||
input: Option<CpalDevice>,
|
input: Option<CpalDevice>,
|
||||||
output: CpalDevice,
|
output: CpalDevice,
|
||||||
|
|
||||||
// TODO: MIDI
|
midi_input: Option<Mutex<MidirInputDevice>>,
|
||||||
|
midi_output: Option<Mutex<(MidirOutputDevice)>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// All data needed for a CPAL input or output stream.
|
/// All data needed for a CPAL input or output stream.
|
||||||
struct CpalDevice {
|
struct CpalDevice {
|
||||||
|
@ -33,6 +37,20 @@ struct CpalDevice {
|
||||||
pub sample_format: SampleFormat,
|
pub sample_format: SampleFormat,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// All data needed to create a Midir input stream.
|
||||||
|
struct MidirInputDevice {
|
||||||
|
pub backend: MidiInput,
|
||||||
|
pub port: MidiInputPort,
|
||||||
|
// The name can be retrieved from the port, not sure why the connect function needs the name
|
||||||
|
// again
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All data needed to create a Midir output stream.
|
||||||
|
struct MidirOutputDevice {
|
||||||
|
pub backend: MidiOutput,
|
||||||
|
pub port: MidiOutputPort,
|
||||||
|
}
|
||||||
|
|
||||||
impl<P: Plugin> Backend<P> for CpalMidir {
|
impl<P: Plugin> Backend<P> for CpalMidir {
|
||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -311,12 +329,97 @@ impl CpalMidir {
|
||||||
nih_warn!("Auxiliary outputs are not supported with this audio backend");
|
nih_warn!("Auxiliary outputs are not supported with this audio backend");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let midi_input = match &config.midi_input {
|
||||||
|
Some(midi_input_name) => {
|
||||||
|
// Midir lets us preemptively ignore MIDI messages we'll never use like active
|
||||||
|
// sensing and timing, but for maximum flexibility with NIH-plug's SysEx parsing
|
||||||
|
// types (which could technically be used to also parse those things) we won't do
|
||||||
|
// that.
|
||||||
|
let midi_backend = MidiInput::new(P::NAME)
|
||||||
|
.context("Could not initialize the MIDI input backend")?;
|
||||||
|
let available_ports = midi_backend.ports();
|
||||||
|
|
||||||
|
// In case there somehow is a MIDI port with an empty name, we'll still want to
|
||||||
|
// preserve the behavior of an empty argument resulting in a listing of options.
|
||||||
|
let found_port = if !midi_input_name.is_empty() {
|
||||||
|
// This API is a bit weird
|
||||||
|
available_ports
|
||||||
|
.iter()
|
||||||
|
.find(|port| midi_backend.port_name(port).as_deref() == Ok(midi_input_name))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
match found_port {
|
||||||
|
Some(port) => Some(Mutex::new(MidirInputDevice {
|
||||||
|
backend: midi_backend,
|
||||||
|
port: port.clone(),
|
||||||
|
})),
|
||||||
|
None => {
|
||||||
|
let mut message = format!(
|
||||||
|
"Unknown input MIDI device '{midi_input_name}'. Available devices are:"
|
||||||
|
);
|
||||||
|
for port in available_ports {
|
||||||
|
match midi_backend.port_name(&port) {
|
||||||
|
Ok(device_name) => message.push_str(&format!("\n{device_name}")),
|
||||||
|
Err(err) => message.push_str(&format!("\nERROR: {err:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::bail!(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let midi_output = match &config.midi_output {
|
||||||
|
Some(midi_output_name) => {
|
||||||
|
let midi_backend = MidiOutput::new(P::NAME)
|
||||||
|
.context("Could not initialize the MIDI output backend")?;
|
||||||
|
let available_ports = midi_backend.ports();
|
||||||
|
|
||||||
|
let found_port = if !midi_output_name.is_empty() {
|
||||||
|
available_ports.iter().find(|port| {
|
||||||
|
midi_backend.port_name(port).as_deref() == Ok(midi_output_name)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
match found_port {
|
||||||
|
Some(port) => Some(Mutex::new(MidirOutputDevice {
|
||||||
|
backend: midi_backend,
|
||||||
|
port: port.clone(),
|
||||||
|
})),
|
||||||
|
None => {
|
||||||
|
let mut message = format!(
|
||||||
|
"Unknown output MIDI device '{midi_output_name}'. Available devices \
|
||||||
|
are:"
|
||||||
|
);
|
||||||
|
for port in available_ports {
|
||||||
|
match midi_backend.port_name(&port) {
|
||||||
|
Ok(device_name) => message.push_str(&format!("\n{device_name}")),
|
||||||
|
Err(err) => message.push_str(&format!("\nERROR: {err:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::bail!(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(CpalMidir {
|
Ok(CpalMidir {
|
||||||
config,
|
config,
|
||||||
audio_io_layout,
|
audio_io_layout,
|
||||||
|
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
|
|
||||||
|
midi_input,
|
||||||
|
midi_output,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue