I am using the crate midir
in version 0.10.0 to receive and send MIDI signals on my Linux machine. Based on the example code in their repository, I created a small example where I just open an input and an output connection. It basically works. However, in graphical patch bay apps, such as Patchance or qjackctl, each port appears in its own box.
I tried some other music apps. Some are represented by multiple boxes, some are represented by a single box with in- and outputs. SetBFree is an example of the latter (see below). My application should receive MIDI input signals, modify them and send them to other apps. Therefore, I'd like the ports to be grouped into the same box, grouping together its input and output ports.
Is it possible? How can I control how my app is recognized by patch bay apps?
I have already tried to use the same name in MidiInput::new
and MidiOutput::new
, but the effect is the same -- except that the system appends a random integer to its name.
I would be grateful for any hints, including exemplary C/C++ code or other MIDI libraries, or documentation. Only condition is that it should work on Linux.
My code (a little quick & dirty, adapted from midir):
use std::error::Error;
use std::io::{stdin, stdout, Write};
use midir::{Ignore, MidiInput, MidiIO, MidiOutput};
fn main() {
match run() {
Ok(_) => (),
Err(err) => println!("Error: {}", err),
}
}
fn get_port<M: MidiIO>(midi_io: &M) -> Result<M::Port, Box<dyn Error>> {
// Get an input port (read from console if multiple are available)
let in_ports = midi_io.ports();
let in_port = match in_ports.len() {
0 => return Err("no input port found".into()),
1 => {
println!(
"Choosing the only available input port: {}",
midi_io.port_name(&in_ports[0]).unwrap()
);
&in_ports[0]
}
_ => {
println!("\nAvailable input ports:");
for (i, p) in in_ports.iter().enumerate() {
println!("{}: {}", i, midi_io.port_name(p).unwrap());
}
print!("Please select input port: ");
stdout().flush()?;
let mut input = String::new();
stdin().read_line(&mut input)?;
in_ports
.get(input.trim().parse::<usize>()?)
.ok_or("invalid input port selected")?
}
};
Ok(in_port.clone())
}
fn run() -> Result<(), Box<dyn Error>> {
let mut input = String::new();
let mut midi_in = MidiInput::new("midir reading input")?;
let mut midi_out = MidiOutput::new("midir reading output")?;
midi_in.ignore(Ignore::None);
let in_port = get_port(&midi_in).unwrap();
let out_port = get_port(&midi_out).unwrap();
println!("\nOpening input connection");
let in_port_name = midi_in.port_name(&in_port)?;
// _conn_in needs to be a named parameter, because it needs to be kept alive until the end of the scope
let _conn_in = midi_in.connect(
&in_port,
"midir-read-input",
move |stamp, message, _| {
println!("{}: {:?} (len = {})", stamp, message, message.len());
},
(),
)?;
println!("\nOpening output connection");
let _conn_out = midi_out.connect(
&out_port,
"midir-write-output",
);
println!(
"Connection open, reading input from '{}' (press enter to exit) ...",
in_port_name
);
input.clear();
stdin().read_line(&mut input)?; // wait for next enter key press
println!("Closing connection");
Ok(())
}
It seems like it is impossible to do this in midir
as it creates a new JACK connection for each port, which is just bad api design. midir
does have methods on its private Client
struct with just this functionality so idk they went the way they did.
The jack
crate has a midi example which works how you want. There's also the pipewire
crate.
Using some sketchy transmutes and copies, you can prove that it's the multiple JACK connections in midir
that are the issue (DO NOT ACTUALLY USE THIS CODE).
//! I wouldn't recommend running this either. It could maybe break your audio server.
use std::error::Error;
use std::io::{stdin, stdout, Write};
use std::mem::ManuallyDrop;
use midir::{Ignore, MidiIO, MidiInput, MidiOutput};
fn main() {
match run() {
Ok(_) => (),
Err(err) => println!("Error: {}", err),
}
}
fn get_port<M: MidiIO>(midi_io: &M) -> Result<M::Port, Box<dyn Error>> {
// Get an input port (read from console if multiple are available)
let in_ports = midi_io.ports();
let in_port = match in_ports.len() {
0 => return Err("no input port found".into()),
1 => {
println!(
"Choosing the only available input port: {}",
midi_io.port_name(&in_ports[0]).unwrap()
);
&in_ports[0]
}
_ => {
println!("\nAvailable input ports:");
for (i, p) in in_ports.iter().enumerate() {
println!("{}: {}", i, midi_io.port_name(p).unwrap());
}
print!("Please select input port: ");
stdout().flush()?;
let mut input = String::new();
stdin().read_line(&mut input)?;
in_ports
.get(input.trim().parse::<usize>()?)
.ok_or("invalid input port selected")?
}
};
Ok(in_port.clone())
}
/// # Safety
/// Probablly not safe at all.
unsafe fn duplicate_as_midi_in(conn: &MidiInput) -> MidiInput {
// Safety: maybe safe, uh breaks Midir library public interface.
// you dont wanna drop this though.
unsafe { std::ptr::read(conn) }
}
/// # Safety
/// The output [`MidiOutput`] should be a valid instance. A lot of segfaults and errors
/// when dropping though.
unsafe fn duplicate_as_midi_out(conn: &MidiInput, verify: Ignore) -> MidiOutput {
struct MidiInputShadow {
flags: Ignore,
jack_connection: Option<*const ()>,
}
const _: () = assert!(size_of::<MidiInputShadow>() == size_of::<MidiInput>());
const _: () = assert!(size_of::<MidiInputShadow>() == 24);
// Safety: VERY UNSAFE relies on `MidiInputShadow` having the same layout as `MidiInput`.
// Rust **does NOT** guarantee any layout for repr(Rust) types.
// The existence of Rust dylibs sort of necessitates these being the same though.
let MidiInputShadow {
flags,
jack_connection,
} = unsafe { std::ptr::read(conn as *const MidiInput as *const MidiInputShadow) };
// runtime layout checks idea from cve-rs
assert_eq!(flags, verify, "BAD: type layout was different");
assert!(jack_connection.is_some(), "can't verify layout");
assert!(
jack_connection != Some(std::ptr::null()),
"can't verify layout"
);
// Safety: layout should be good now
unsafe { std::mem::transmute(jack_connection) }
}
// NOTE: this particular setup doesn't segfault but prob can still break your audio server.
// Also input port 2 doesn't work, and output ports dont seem to do anything but
// it doesn't seem like they're used.
/// Creates 2 input ports and 2 output ports in one audio node.
fn run() -> Result<(), Box<dyn Error>> {
let mut input = String::new();
let mut jack_connection: MidiInput = MidiInput::new("my midir app")?;
jack_connection.ignore(Ignore::None);
let midi_in = unsafe { duplicate_as_midi_in(&jack_connection) };
let midi_in2 = unsafe { duplicate_as_midi_in(&jack_connection) };
let midi_out = unsafe { duplicate_as_midi_out(&jack_connection, Ignore::None) };
let midi_out2 = unsafe { duplicate_as_midi_out(&jack_connection, Ignore::None) };
// segfaults if this is uncommented
// let midi_out3 = unsafe { duplicate_as_midi_out(&jack_connection, Ignore::None) };
// drop(midi_out3);
let in_port = get_port(&midi_in).unwrap();
let in_port2 = get_port(&midi_in2).unwrap();
let out_port = get_port(&midi_out).unwrap();
let out_port2 = get_port(&midi_out2).unwrap();
println!();
let in_port_name = midi_in.port_name(&in_port)?;
println!("Opening input connection 1");
// ManuallyDrop or else double frees. Prob still double frees in `connect` but doesn't segfault
let _conn_in = ManuallyDrop::new(midi_in.connect(
&in_port,
"midir-read-input1",
move |stamp, message, _| {
println!("input 1: {}: {:?} (len = {})", stamp, message, message.len());
},
(),
)?);
// NOTE: input port 2 doesn't seem to work on my pc.
println!("Opening input connection 2");
let _conn_in2 = ManuallyDrop::new(midi_in2.connect(
&in_port2,
"midir-read-input2",
move |stamp, message, _| {
println!("input 2: {}: {:?} (len = {})", stamp, message, message.len());
},
(),
)?);
println!("Opening output connection 1");
let _conn_out = ManuallyDrop::new(midi_out.connect(&out_port, "midir-write-output1"));
println!("Opening output connection 2");
let _conn_out2 = ManuallyDrop::new(midi_out2.connect(&out_port2, "midir-write-output2"));
println!(
"Connection open, reading input from '{}' (press enter to exit) ...",
in_port_name
);
input.clear();
stdin().read_line(&mut input)?; // wait for next enter key press
println!("Closing connection");
drop(jack_connection);
Ok(())
}