1
0
Fork 0

Add basic MIDI device selection using midir

This commit is contained in:
Robbert van der Helm 2023-02-25 17:19:06 +01:00
parent 98b8571dc9
commit 2ed95bb52d
3 changed files with 171 additions and 3 deletions

66
Cargo.lock generated
View file

@ -89,6 +89,18 @@ dependencies = [
"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]]
name = "alsa-sys"
version = "0.3.1"
@ -856,6 +868,26 @@ dependencies = [
"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]]
name = "cosmic-text"
version = "0.6.0"
@ -881,7 +913,7 @@ version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f342c1b63e185e9953584ff2199726bf53850d96610a310e3aca09e9405a2d0b"
dependencies = [
"alsa",
"alsa 0.6.0",
"core-foundation-sys",
"coreaudio-rs",
"jni",
@ -2252,6 +2284,22 @@ dependencies = [
"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]]
name = "minimal-lexical"
version = "0.2.1"
@ -2363,6 +2411,7 @@ dependencies = [
"libc",
"log",
"midi-consts",
"midir",
"nih_plug_derive",
"objc",
"parking_lot 0.12.1",
@ -4564,6 +4613,21 @@ dependencies = [
"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]]
name = "windows-sys"
version = "0.42.0"

View file

@ -52,7 +52,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: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
# 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
@ -113,6 +113,7 @@ cpal = { version = "0.14.1", optional = true }
# Current upstream JACK always links to libjack, even when using the default
# dynamic loading feature
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 }
# Used for the `vst3` feature

View file

@ -6,6 +6,8 @@ use cpal::{
StreamConfig,
};
use crossbeam::sync::{Parker, Unparker};
use midir::{MidiInput, MidiInputPort, MidiOutput, MidiOutputPort};
use parking_lot::Mutex;
use rtrb::RingBuffer;
use super::super::config::WrapperConfig;
@ -24,7 +26,9 @@ pub struct CpalMidir {
input: Option<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.
struct CpalDevice {
@ -33,6 +37,20 @@ struct CpalDevice {
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 {
fn run(
&mut self,
@ -311,12 +329,97 @@ impl CpalMidir {
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 {
config,
audio_io_layout,
input,
output,
midi_input,
midi_output,
})
}