midi2 0.2.3

Ergonomic, versatile, strong types wrapping MIDI 2.0 message data.
Documentation

🎹 MIDI2 🎹

Ergonomic, versatile, strong types wrapping MIDI 2.0 message data.

This implementation of MIDI 2.0 is based on the 1.1 revision of the specifications. For detailed midi2 specification see the documentation on which this crate is based.

⚠️ Note! ⚠️

This crate is still in its alpha phase and is not recommended for production.

We would welcome contributions! Please refer to the CONTRIBUTOR.md

Strongly Typed Message Wrappers

A strongly typed message wrapper is provided for every message in the MIDI 2.0 specification.

use midi2::prelude::*;

// Messages have a simple setter / getter interface
let mut note_on = channel_voice2::NoteOn::new_arr();
note_on.set_group(u4::new(0x8));
note_on.set_channel(u4::new(0xA));
note_on.set_note(u7::new(0x5E));
note_on.set_velocity(0x6A14);

assert_eq!(note_on.group(), u4::new(0x8));
assert_eq!(note_on.channel(), u4::new(0xA));
assert_eq!(note_on.note(), u7::new(0x5E));
assert_eq!(note_on.velocity(), 0x6A14);

// Messages wrap an underlying buffer of data which can be read as an
// ordinary slice.
let mut composer_name = flex_data::ComposerName::<Vec<u32>>::new();
composer_name.set_name("Pinch b2b Peverelist");
assert_eq!(
    composer_name.data(), 
    &[
        0xD050_0105,
        0x5069_6E63,
        0x6820_6232,
        0x6220_5065,
        0xD0D0_0105,
        0x7665_7265,
        0x6C69_7374,
        0x0000_0000,
]);

Aggregate Message Types

All message wrappers are grouped into aggregate enum types. There's a top level enum type which can represent all messages, and there's sub enum types for each different UMP type specified by the MIDI 2.0 documentation.

fn handle_message(buffer: &[u32]) {
    use midi2::prelude::*;

    match UmpMessage::try_from(buffer) {
        Ok(UmpMessage::ChannelVoice2(m)) => {
            println!("Channel Voice2: channel: {}", m.channel());
            match m {
                channel_voice2::ChannelVoice2::NoteOn(m) => {
                    println!("Note On! note: {}, velocity: {}", m.note(), m.velocity());
                }
                channel_voice2::ChannelVoice2::NoteOff(m) => {
                    println!("Note Off! note: {}, velocity: {}", m.note(), m.velocity());
                }
                _ => {}
            }
        }
        Ok(UmpMessage::Sysex7(m)) => {
            println!(
                "Sysex 7bit: payload: {:?}",
                m.payload().collect::<Vec<u7>>()
            );
        }
        Ok(UmpMessage::FlexData(m)) => {
            use midi2::flex_data::FlexDataMessage;

            println!("FlexData: bank: {:?}", m.bank());
            match m {
                _ => {}, // further matching on different flex data types
            }
        }
        // further matching on other message types
        Err(e) => {
            println!("Error parsing ump buffer: {:?}", e);
        }
        _ => {}
    }
}

Full Sysex Support

Sysex message can be represented with MIDI 2.0 Universal Message Packets.

use midi2::prelude::*;

let mut message = sysex7::Sysex7::<Vec<u32>>::new();
message.set_payload((0u8..30u8).map(u7::new));
message.set_group(u4::new(0xA));

assert_eq!(
    message.data(),
    &[
        0x3A16_0001,
        0x0203_0405,
        0x3A26_0607,
        0x0809_0A0B,
        0x3A26_0C0D,
        0x0E0F_1011,
        0x3A26_1213,
        0x1415_1617,
        0x3A36_1819,
        0x1A1B_1C1D,
    ],
);

Or with classical MIDI 2.0 byte streams.

use midi2::prelude::*;

let mut message = sysex7::Sysex7::<Vec<u8>>::new();
message.set_payload((0u8..30u8).map(u7::new));

assert_eq!(
    message.data(),
    &[
        0xF0, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
        0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
        0x1B, 0x1C, 0x1D, 0xF7,
    ],
);

Jitter Ruduction Support

All ump messages have an optional jitter reduction header prepended before its message packets.

use midi2::prelude::*;

let mut message = channel_voice1::ChannelPressure::new_arr();
assert_eq!(message.data(), &[0x20D0_0000]);

message.set_jitter_reduction(Some(JitterReduction::Timestamp(0x1234)));
assert_eq!(message.data(), &[0x0020_1234, 0x20D0_0000]);

NOTE: For this reason all messages need an extra u32 at the start of their buffers to accomodate the header data. For example, the minimum size buffer for a ChannelVoice2 message is 3, rather than 2.

Almost Entirely #![no_std] Friendly

#![no_std] is a first class use case in midi2. All message types can be read and written without allocation, even messages of arbitrary length, like sysex or flex-data.

You'll want to setup midi2 without default features to compile without the std feature.

// Cargo.toml
midi2 = { version = "0.2.3", default-features = false, features = [<required-message-types>],  }

Generic Representation

All messages are generic over their representation. For example, a simple non-allocating use case would be to represent messages within a fixed size array.

use midi2::prelude::*;

let mut message = sysex8::Sysex8::<[u32; 17]>::try_new()
    .expect("Buffer is large enough for min message size");

// in this mode methods which would require a 
// buffer resize are fallible
assert_eq!(message.try_set_payload(0..50), Ok(()));

// if there's not enough room in the buffer to 
// accomodate the resize then an overflow error is returned.
assert_eq!(message.try_set_payload(0..60), Err(midi2::error::BufferOverflow));

A more advanced use case might be to make a custom buffer which uses an arena allocater to back your messages. See the buffer docs for more info.

Borrowed Messages

When reading messages from an existing buffer, the message wrappers own a borrowed reference to the data, so no copying or allocation takes place. In this case the generic message buffer type is &[u32].

use midi2::prelude::*;

let buffer = [
    0xD050_0100_u32,
    0x4469_6769,
    0x7461_6C20,
    0x4175_6469,
    0xD090_0100,
    0x6F20_576F,
    0x726B_7374,
    0x6174_696F,
    0xD0D0_0100,
    0x6E20_2D20,
    0x4441_5733,
    0x362D_3136,
];
let Ok(message) = UmpMessage::try_from(&buffer[..]) else {
    panic!();
};

Of course this means that such borrowed messages are imutable and also have their lifetimes tied to the original buffer.

To remedy this messages can be rebuffered into a different generic backing buffer type.

use midi2::prelude::*;

let mut owned: UmpMessage::<[u32; 5]> = {
    let buffer = [0x1AF3_4F00_u32];
    // the borrowed message is imutable and cannot outlive `buffer`
    let borrowed = UmpMessage::try_from(&buffer[..]).expect("Data is valid");
    borrowed.try_rebuffer_into().expect("Buffer is large enough")
};

// the owned message is mutable an liberated from the buffer lifetime.
owned.set_jitter_reduction(Some(JitterReduction::Timestamp(0x1234)));
assert_eq!(owned.data(), &[0x0020_1234, 0x1AF3_4F00])

Supports For Classical MIDI Byte Stream Messages

Messages which can be represented in classical midi byte stream format are also supported. To do this simply use a backing buffer over u8 instead of u32! ✨🎩

use midi2::prelude::*;

let mut message = channel_voice1::ChannelPressure::new_arr_bytes();
message.set_channel(u4::new(0x6));
message.set_pressure(u7::new(0x09));

assert_eq!(message.data(), &[0xD6, 0x09]);

Messages represented in bytes can be transformed to ump and back using convertion traits.

use midi2::{
    prelude::*,
    channel_voice1::ChannelPressure,
};

let message = ChannelPressure::new_arr_bytes();
let message: ChannelPressure<[u32; 5]> = message.try_into_ump().
    expect("Buffer is large enough");

assert_eq!(message.data(), &[0x20D0_0000]);

Cargo Features

midi2 provides several compile-time features that you can enable or disable to customize its functionality according to your needs.

Here's a list of available features:

  • default:

    • std - Include buffer integration for std::vec::Vec and enable allocating getters for values which return std::string::String values.
    • channel-voice2 — Include message wrappers for the MIDI 2.0 channel voice message type.
    • sysex7 — Include message wrappers for the MIDI 7bit system exclusive message type.
    • ci — 🚧 WIP 🚧
  • optional: These features are not enabled by default and can be included by adding them to your Cargo.toml.

    • flex-data - Include message wrappers for the MIDI 2.0 Flex Data message type.
    • channel-voice1 - Include message wrappers for the classical MIDI channel voice message type.
    • sysex8 - Include message wrappers for the MIDI 2.0 System Exclusive 8bit message type.
    • system-common - Include message wrappers for the MIDI 2.0 System Common / System Real Time message type.
    • ump-stream - Include message wrappers for the MIDI 2.0 Ump Stream message type.