Home

Awesome

ni-midi2 ni-midi2 CI codecov

ni-midi2 is a C++ library for working with MIDI 2 Universal MIDI Packets (UMP) and MIDI-CI 1.2 messages.

Library Design

The library provides the basic functionality of UMP 1.1 and MIDI-CI 1.2 by providing base classes for all UMP 1.1 packet types, (Universal) System Exclusive messages and MIDI-CI messages.

struct universal_packet;

struct utility_message;
struct system_message;
struct midi1_channel_voice_message;
struct midi2_channel_voice_message;
struct data_message;
struct sysex7_packet;
struct extended_data_message;
struct sysex8_packet;
struct flex_data_message;
struct stream_message;

struct sysex;
struct sysex7;
struct sysex8;

struct universal_sysex::message;
struct midi::ci::message;

There are concrete types for controllers, velocity and pitch, plus type aliases for common message field types.

struct velocity;

struct pitch_bend;
struct pitch_bend_sensitivity;

struct pitch_7_9;
struct pitch_7_25;
struct pitch_increment;

struct controller_value;
struct controller_increment;

using group_t      = uint4_t;
using status_t     = uint8_t;
using channel_t    = uint4_t;
using controller_t = uint7_t;
...

Mathematical operators allow to do integer / fixed point math on pitches and controllers, type constructors allow initialization with values of different resolution.

pitch_with_pb = a_pitch_7_25 + a_pitch_bend_value * cur_pitch_bend_sensitivity;
a_controller_value += a_controller_increment;

auto from7bit = controller_value{ uint7_t{ 44 } };
auto from32bit = controller_value{ uint32_t{ 0x45883312 } };
auto fromFloat = pitch_7_25 { 66.1f };

For more information see Common Types and Scaling Helpers.

Conrete instances of packets or messages are created using factory functions.

Incoming packets and messages are inspected using data views.

The library is completed by a number of helper functionalities dealing with conversion from / to MIDI 1 byte stream data format, collecting sysex messages and more.

Factory Functions

UMPs and SysEx messages are created using free factory functions, there are no classes / constructors available to create specific packets or MIDI-CI messages.

Examples for UMP factory functions:

system_message make_system_message(group_t, status_t, uint7_t data1 = 0, uint7_t data2 = 0);
system_message make_song_position_message(group_t, uint14_t position);

midi1_channel_voice_message make_midi1_note_on_message(group_t, channel_t, note_nr_t, velocity);
midi1_channel_voice_message make_midi1_program_change_message(group_t, channel_t, program_t);
midi1_channel_voice_message make_midi1_pitch_bend_message(group_t, channel_t, pitch_bend);
...

midi2_channel_voice_message make_midi2_note_on_message(group_t, channel_t, note_nr_t, velocity);
midi2_channel_voice_message make_midi2_note_on_message(group_t, channel_t, note_nr_t, velocity, pitch_7_9);
midi2_channel_voice_message make_registered_controller_message(
    group_t, channel_t, uint7_t bank, uint7_t index, controller_value);
...

data_message make_sysex7_complete_packet(group_t = 0);
...

flex_data_message make_set_tempo_message(group_t, uint32_t ten_ns_per_quarter_note);
...

stream_message make_endpoint_discovery_message(
    uint8_t filter, uint8_t ump_version_major = 1, uint8_t ump_version_minor = 1);
...

Examples for MIDI-CI factory functions:

ci::message make_discovery_inquiry(muid_t            source_muid,
                          const identity_t& identity,
                          uint7_t           categories,
                          uint28_t          max_message_size,
                          uint7_t           output_path_id = 0);
...

ci::message make_midi_message_report_reply(
    muid_t source_muid, uint7_t system_messages, uint7_t channel_controller_messages,
    uint7_t note_data_messages, uint7_t device_id = 0x7F);

Data Views

One can inspect incoming UMPs using packet data views:

if (packet.type() == packet_type::system)
{
    auto m = system_message_view{ packet };

    // access message data
    if (m.status() == system_status::song_select)
    {
        auto song = m.data_byte_1();
    }
}

Alternatively one can use std::optional to check and cast in a single statement:

if (auto m = as_system_message_view(packet))
{
    // access message data
    if (m->status() == system_status::song_position)
    {
        auto pos = m->get_song_position();
    }
}

There are packet data views available for the majority of UMP message types.

struct utility_message_view;
struct system_message_view;
struct midi1_channel_voice_message_view;
struct midi2_channel_voice_message_view;

struct sysex7_packet_view;
struct sysex8_packet_view;

struct flex_data_message_view;

struct endpoint_discovery_view;
struct endpoint_info_view;
struct device_identity_view;
struct endpoint_name_view;
struct product_instance_id_view;
struct stream_configuration_view;

struct function_block_discovery_view;
struct function_block_info_view;
struct function_block_name_view;

The same is true for Universal SysEx and MIDI-CI messages.

struct universal_sysex::message_view;
universal_sysex::identity_reply_view;

struct capability_inquiry_view;
struct ci::discovery_inquiry_view;
struct ci::discovery_reply_view;
...
struct ci::profile_inquiry_reply_view;
...
struct ci::property_data_message_view;
...

As the content / size of such messages may not comply to the specification, it is recommended to either use the std::optional helpers like ci::as<profile_specific_data_view>() or as_universal_sysex_view() or the validate() helpers of the message view structs.

Additional Functionalities

MIDI 1 byte stream data helpers

The library provides a midi1_byte_stream_parser class and also free helper functions to convert from_midi1_byte_stream() and to_midi1_byte_stream().

The midi1_byte_stream_parser can be configured to automatically parse and collect Sysex7 messages.

For more information see midi1_byte_stream.md.

Sysex collectors

The sysex7_collector class allows to easily collect System Exclusive messages (like MIDI-CI 1.2).

sysex7_collector c {
    [](const sysex7 &s)
    {
        // do something with message
        ...
    }
};

if (is_sysex7_packet(p))
    c.feed(p);

sysex8_collector does the same for System Exclusive 8 messages.

sysex8_collector c {
    [](const sysex8 &s, uint8_t stream_id)
    {
        // do something with message
        ...
    }
};

if (is_sysex8_packet(p))
    c.feed(p);

For more information see sysex_collector.md.

Jitter Reduction Timestamps

There are experimental implementations for a Jitter Reduction Timestamps clock generator and follower available.

struct jr_timestamp_t;
struct jr_clock_message;
struct jr_timestamp_message;

class jr_clock;
class jr_clock_follower;

Please be aware that these classes only demonstrate the general concept of Jitter Reduction Timestamps, they are not intended as a ready-to-use production solution.

Detail documentation and example files

There is a growing number of code examples and more detailed documentation available in the docs folder. Enable the cmake option NIMIDI2_EXAMPLES to build the example code.

Until documentation is completed you may also find the unit test code helpful.

Building and Testing

The library uses cmake as its build system. Simply call cmake on the top level source folder to generate a build recipe for your preferred compiler and IDE / make.

There are some cmake options that allow customization of what is build and how:

option( NIMIDI2_TREAT_WARNINGS_AS_ERRORS "Treat compile warnings as errors"   OFF )
option( NIMIDI2_UNITY_BUILDS             "Build ni-midi2 with unity builds"   ON  )
option( NIMIDI2_TESTS                    "Build ni-midi2 Tests"     ${IS_NIMIDI2} )
option( NIMIDI2_EXAMPLES                 "Build ni-midi2 examples"  ${IS_NIMIDI2} )

If you do not need to build unit tests, specify -DNIMIDI2_TESTS=OFF on the cmake command line. This is the default if this project is included via add_subdirectory into your project.

If you want to build the unit tests, the CMakeLists.txt file requires to find_package(GTest "1.11.0") through standard cmake mechanisms. The library itself only depends on the C++17 standard library.

By default, this project enables cmake unity builds on its targets, you may turn them off by passing -DNIMIDI2_UNITY_BUILDS=OFF on the cmake command line.

In case you plan to contribute please pass -DNIMIDI2_TREAT_WARNINGS_AS_ERRORS=ON on the cmake command line, this may help with keeping the code free of warning messages.

TODOs