#midi #audio #music #sound

rtmidi

Safe wrapper for RtMidi, realtime MIDI input/output

2 unstable releases

0.2.0 Apr 18, 2021
0.1.0 Mar 2, 2021

#46 in #midi

MIT license

32KB
563 lines

RtMidi crates.io Test

A safe wrapper around RtMidi that provides a common API (Application Programming Interface) for realtime MIDI input/output across Linux (ALSA & JACK), macOS (CoreMIDI & JACK), and Windows (Multimedia Library) operating systems.


lib.rs:

RtMidi

A safe wrapper around RtMidi that provides a common API (Application Programming Interface) for realtime MIDI input/output across Linux (ALSA & JACK), macOS (CoreMIDI & JACK), and Windows (Multimedia Library) operating systems.

Where applicable, multiple API support can be compiled and a particular API specified when creating an RtMidi instance.

MIDI input and output functionality are separated into two structs, [RtMidiIn] and [RtMidiOut]. Each instance supports only a single MIDI connection. RtMidi does not provide timing functionality (i.e., output messages are sent immediately). Input messages are timestamped with delta times in seconds (via an [f64] type). MIDI data is passed to the user as raw bytes using a &[u8].

Probing Ports / Devices

A client generally must query the available MIDI ports before deciding which to use. The following example outlines how this can be done.

use rtmidi::{RtMidiIn, RtMidiOut, RtMidiError};

fn main() -> Result<(), RtMidiError> {
    // Initialise MIDI input
    let input = RtMidiIn::new(Default::default())?;

    // Get number of input ports
    let input_ports = input.port_count()?;
    println!("There are {} MIDI input sources available.", input_ports);

    // List input ports
    for port in 0..input_ports {
        println!("\tInput Port #{}: {}", port+1, input.port_name(port)?);
    }

    // Initialise MIDI output
    let output = RtMidiOut::new(Default::default())?;

    // Get number of output ports
    let output_ports = output.port_count()?;
    println!("There are {} MIDI output ports available.", output_ports);

    // List output ports
    for port in 0..output_ports {
        println!("\tOutput Port #{}: {}", port+1, output.port_name(port)?);
    }

    Ok(())
}

Note that the port enumeration is system specific and will change if any devices are unplugged or plugged (or a new virtual port opened or closed) by the user. Thus, the port numbers should be verified immediately before opening a port. As well, if a user unplugs a device (or closes a virtual port) while a port connection exists to that device/port, a MIDI system error will be generated.

MIDI Output

RtMidiOut provides simple functionality to immediately send messages over a MIDI connection. No timing functionality is provided.

use std::thread::sleep;
use std::time::Duration;
use rtmidi::{RtMidiOut, RtMidiError};

fn main() -> Result<(), RtMidiError> {
    // Initialise MIDI output
    let output = RtMidiOut::new(Default::default())?;

    // Check available ports
    let ports = output.port_count()?;
    if ports < 1 {
        eprintln!("No ports available!");
        return Ok(());
    }

    // Open first available port
    output.open_port(0, "RtMidi Output")?;

    // Program change: 192, 5
    output.message(&[192, 5])?;

    // Control Change: 176, 7, 100 (volume)
    output.message(&[176, 7, 100])?;

    // Note On: 144, 64, 90
    output.message(&[144, 64, 90])?;

    sleep(Duration::from_millis(500));

    // Note Off: 128, 64, 40
    output.message(&[128, 64, 40])?;

    Ok(())
}

MIDI Input

[RtMidiIn] uses an internal callback function or thread to receive incoming MIDI messages from a port or device. These messages are then either queued and read by the user via calls to [RtMidiIn::message] or immediately passed to a user-specified callback function (which must be "registered" using [RtMidiIn::set_callback]). Note that if you have multiple instances of [RtMidiIn], each may have its own thread.

[RtMidiIn] provides [RtMidiIn::ignore_types] to specify that certain MIDI message types be ignored. By default, system exclusive, timing, and active sensing messages are ignored.

It is necessary to set the callback immediately after opening the port to avoid having incoming messages written to the queue (which is not emptied when a callback function is set). If you are worried about this happening, you can check the queue using [RtMidiIn::message] to verify it is empty (after the callback is set).

use std::io::{stdin, Read};
use rtmidi::{RtMidiIn, RtMidiError};

fn main() -> Result<(), RtMidiError> {
    // Initialise MIDI input
    let input = RtMidiIn::new(Default::default())?;

    // Check available ports
    let ports = input.port_count()?;
    if ports < 1 {
        eprintln!("No ports available!");
        return Ok(());
    }

    // Open first available port
    input.open_port(0, "RtMidi Input")?;

    // Set our callback function.  This should be done immediately after
    // opening the port to avoid having incoming messages written to the
    // queue.
    input.set_callback(|timestamp, message| {
        for (index, byte) in message.iter().enumerate() {
            println!("Byte {} = 0x{:02x}, ", index, byte);
        }
    })?;

    // Don't ignore sysex, timing, or active sensing messages.
    input.ignore_types(false, false, false)?;

    println!("Reading MIDI input ...");
    stdin().read(&mut [0]).unwrap();

    Ok(())
}

No runtime deps

~0–1.4MB
~30K SLoC