Skip to content

Commit

Permalink
[core] Add support for raw midi / ump input. Fixes #101
Browse files Browse the repository at this point in the history
  • Loading branch information
jcelerier committed Aug 25, 2024
1 parent 625836b commit 3d241f6
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 29 deletions.
1 change: 1 addition & 0 deletions cmake/libremidi.examples.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ add_example(qmidiin)
add_example(sysextest)
add_example(minimal)
add_example(midi2_echo)
add_example(rawmidiin)

if(LIBREMIDI_NI_MIDI2)
add_example(midi2_interop)
Expand Down
4 changes: 3 additions & 1 deletion examples/midiprobe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ int main()
// On Windows 10, apparently the MIDI devices aren't exactly available as soon as the app open...
std::this_thread::sleep_for(std::chrono::milliseconds(100));

libremidi::observer midi{{}, libremidi::observer_configuration_for(api)};
libremidi::observer midi{
{.track_hardware = true, .track_virtual = true},
libremidi::observer_configuration_for(api)};
{
// Check inputs.
auto ports = midi.get_input_ports();
Expand Down
41 changes: 41 additions & 0 deletions examples/rawmidiin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//*****************************************//
// cmidiin.cpp
// by Gary Scavone, 2003-2004.
//
// Simple program to test MIDI input and
// use of a user callback function.
//
//*****************************************//

#include "utils.hpp"

#include <libremidi/libremidi.hpp>

#include <cstdlib>
#include <iostream>

int main(int argc, const char** argv)
{
// Read command line arguments
libremidi::examples::arguments args{argc, argv};

libremidi::midi_in midiin{
{.on_message = {},
.on_raw_data =
[](std::span<const uint8_t> data, libremidi::timestamp t) {
std::cout << std::dec << t << ": " << std::hex;
for (auto byte : data)
std::cout << int(byte) << " ";
std::cout << std::endl;
}},
libremidi::midi_in_configuration_for(
args.api)}; // Passing the second configuration argument can be skipped to use the default MIDI API

if (!args.open_port(midiin))
return 1;

std::cout << "\nReading MIDI input ... press <enter> to quit.\n";
int c;
while ((c = getchar()) != '\n' && c != EOF)
;
}
6 changes: 3 additions & 3 deletions include/libremidi/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ class client
port,
input_configuration{
.on_message
= [this, port](libremidi::message&& m) {
configuration.on_message(port, std::move(m));
},
= [this,
port](libremidi::message&& m) { configuration.on_message(port, std::move(m)); },
.on_raw_data = {},
.get_timestamp = {},

.on_error = configuration.on_error,
Expand Down
80 changes: 61 additions & 19 deletions include/libremidi/detail/midi_stream_decoder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,26 @@ struct input_state_machine : input_state_machine_base<input_configuration>
// Function to process a byte stream which may contain multiple successive
// MIDI events (CoreMIDI, ALSA Sequencer can work like this)
void on_bytes_multi(std::span<const uint8_t> bytes, int64_t timestamp)
{
if (this->configuration.on_message)
on_bytes_multi_segmented(this->configuration.on_message, bytes, timestamp);
if (this->configuration.on_raw_data)
this->configuration.on_raw_data(bytes, timestamp);
}

// Function to process bytes corresponding to at most one midi event
// e.g. a midi channel event or a single sysex
void on_bytes(std::span<const uint8_t> bytes, int64_t timestamp)
{
if (this->configuration.on_message)
on_bytes_segmented(this->configuration.on_message, bytes, timestamp);
if (this->configuration.on_raw_data)
this->configuration.on_raw_data(bytes, timestamp);
}

private:
void on_bytes_multi_segmented(
const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)
{
int64_t nBytes = bytes.size();
int64_t iByte = 0;
Expand All @@ -118,7 +138,7 @@ struct input_state_machine : input_state_machine_base<input_configuration>
switch (state)
{
case in_sysex: {
return on_continue_sysex(bytes, finished_sysex);
return on_continue_sysex(cb, bytes, finished_sysex);
}
case main: {
while (iByte < nBytes)
Expand Down Expand Up @@ -214,7 +234,7 @@ struct input_state_machine : input_state_machine_base<input_configuration>
message.assign(begin, begin + size);
message.timestamp = timestamp;

this->configuration.on_message(std::move(message));
cb(std::move(message));
message.clear();

iByte += size;
Expand All @@ -224,7 +244,8 @@ struct input_state_machine : input_state_machine_base<input_configuration>
}
}

void on_continue_sysex(std::span<const uint8_t> bytes, bool finished_sysex)
void on_continue_sysex(
const message_callback& cb, std::span<const uint8_t> bytes, bool finished_sysex)
{
if (finished_sysex)
state = main;
Expand All @@ -238,14 +259,16 @@ struct input_state_machine : input_state_machine_base<input_configuration>
message.insert(message.end(), bytes.begin(), bytes.end());
if (finished_sysex)
{
this->configuration.on_message(std::move(message));
cb(std::move(message));
message.clear();
}
}
return;
}

void on_main(std::span<const uint8_t> bytes, int64_t timestamp, bool finished_sysex)
void on_main(
const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp,
bool finished_sysex)
{
switch (bytes[0])
{
Expand All @@ -260,7 +283,7 @@ struct input_state_machine : input_state_machine_base<input_configuration>
message.timestamp = timestamp;
if (finished_sysex)
{
this->configuration.on_message(std::move(message));
cb(std::move(message));
message.clear();
}
}
Expand All @@ -286,13 +309,12 @@ struct input_state_machine : input_state_machine_base<input_configuration>
message.assign(bytes.begin(), bytes.end());
message.timestamp = timestamp;

this->configuration.on_message(std::move(message));
cb(std::move(message));
message.clear();
}

// Function to process bytes corresponding to at most one midi event
// e.g. a midi channel event or a single sysex
void on_bytes(std::span<const uint8_t> bytes, int64_t timestamp)
void
on_bytes_segmented(const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)
{
if (bytes.empty())
return;
Expand All @@ -301,21 +323,22 @@ struct input_state_machine : input_state_machine_base<input_configuration>
switch (state)
{
case in_sysex:
return on_continue_sysex(bytes, finished_sysex);
return on_continue_sysex(cb, bytes, finished_sysex);

case main:
return on_main(bytes, timestamp, finished_sysex);
return on_main(cb, bytes, timestamp, finished_sysex);
}
}

public:
libremidi::message message;

private:
enum
{
main,
in_sysex
} state{main};

};
}

Expand All @@ -325,9 +348,7 @@ struct input_state_machine : input_state_machine_base<ump_input_configuration>
{
using input_state_machine_base::input_state_machine_base;

// Function to process a byte stream which may contain multiple successive
// MIDI events (CoreMIDI, ALSA Sequencer can work like this)

public:
void on_bytes_multi(std::span<const unsigned char> bytes, int64_t timestamp)
{
auto ptr = reinterpret_cast<const uint32_t*>(bytes.data());
Expand All @@ -336,6 +357,26 @@ struct input_state_machine : input_state_machine_base<ump_input_configuration>
}

void on_bytes_multi(std::span<const uint32_t> bytes, int64_t timestamp)
{
if (this->configuration.on_message)
on_bytes_multi_segmented(this->configuration.on_message, bytes, timestamp);
if (this->configuration.on_raw_data)
this->configuration.on_raw_data(bytes, timestamp);
}

void on_bytes(std::span<const uint32_t> bytes, int64_t timestamp)
{
if (this->configuration.on_message)
on_bytes_segmented(this->configuration.on_message, bytes, timestamp);
if (this->configuration.on_raw_data)
this->configuration.on_raw_data(bytes, timestamp);
}

private:
// Function to process a byte stream which may contain multiple successive
// MIDI events (CoreMIDI, ALSA Sequencer can work like this)
void on_bytes_multi_segmented(
const ump_callback& cb, std::span<const uint32_t> bytes, int64_t timestamp)
{
auto count = bytes.size();
auto ump_stream = bytes.data();
Expand All @@ -352,15 +393,16 @@ struct input_state_machine : input_state_machine_base<ump_input_configuration>
break;

const auto ump_uints = cmidi2_ump_get_num_bytes(ump_stream[0]) / 4;
on_bytes({ump_stream, ump_stream + ump_uints}, timestamp);
on_bytes_segmented(cb, {ump_stream, ump_stream + ump_uints}, timestamp);

ump_stream += ump_uints;
count -= ump_uints;
}
}

// Function to process bytes corresponding to at most one midi event
void on_bytes(std::span<const uint32_t> bytes, int64_t timestamp)
void
on_bytes_segmented(const ump_callback& cb, std::span<const uint32_t> bytes, int64_t timestamp)
{
// Filter according to message type
switch(cmidi2_ump_get_message_type(bytes.data()))
Expand Down Expand Up @@ -408,7 +450,7 @@ struct input_state_machine : input_state_machine_base<ump_input_configuration>
libremidi::ump msg;
std::copy(bytes.begin(), bytes.end(), msg.data);
msg.timestamp = timestamp;
configuration.on_message(std::move(msg));
cb(std::move(msg));
}
};
}
Expand Down
15 changes: 12 additions & 3 deletions include/libremidi/input_configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
#include <libremidi/ump.hpp>

#include <functional>
#include <string>

namespace libremidi
{
Expand Down Expand Up @@ -41,14 +40,18 @@ enum timestamp_mode

using timestamp = int64_t;
using message_callback = std::function<void(message&& message)>;
using raw_callback = std::function<void(std::span<uint8_t>, timestamp)>;
using raw_callback = std::function<void(std::span<const uint8_t>, timestamp)>;
using timestamp_callback = std::function<timestamp(timestamp)>;
struct input_configuration
{
//! Set a callback function to be invoked for incoming MIDI messages.
//! Mandatory!
//! Either this or on_raw_message must be set
message_callback on_message;

//! Invoked for incoming MIDI bytes. No transformation, no filtering, no packetization,
//! just the MIDI data straight from the source.
raw_callback on_raw_data;

//! Set a custom callback function to be invoked for timestamping MIDI messages.
//! Input: the API provided timestamp in nanoseconds, if available, for reference.
//! (e.g. the same as "Absolute").
Expand Down Expand Up @@ -81,11 +84,17 @@ struct input_configuration
};

using ump_callback = std::function<void(ump&&)>;
using raw_ump_callback = std::function<void(std::span<const uint32_t>, timestamp)>;
struct ump_input_configuration
{
//! Set a callback function to be invoked for incoming UMP messages.
//! Either this or on_raw_message must be set
ump_callback on_message;

//! Invoked for incoming UMP bytes. No transformation, no filtering, no packetization,
//! just the UMP data straight from the source.
raw_ump_callback on_raw_data;

//! Set a custom callback function to be invoked for timestamping MIDI messages.
//! Input: the API provided timestamp in nanoseconds, if available, for reference.
//! (e.g. the same as "Absolute").
Expand Down
5 changes: 3 additions & 2 deletions include/libremidi/midi_in.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_c
return c2;
}

LIBREMIDI_INLINE auto make_midi_in(auto base_conf, std::any api_conf, auto backends)
static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>
make_midi_in(auto base_conf, std::any api_conf, auto backends)
{
std::unique_ptr<midi_in_api> ptr;

assert(base_conf.on_message);
assert(base_conf.on_message || base_conf.on_raw_data);

auto from_api = [&]<typename T>(T& /*backend*/) mutable {
if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf))
Expand Down
2 changes: 1 addition & 1 deletion include/libremidi/observer_configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct LIBREMIDI_EXPORT port_information

// ALSA Raw: { uint16_t card, device, sub, padding; }
// ALSA Seq: { uint32_t client, uint32_t port; }
// CoreMIDI: MidiObjectRef
// CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t)
// WebMIDI: unused
// JACK: jack_port_id_t
// PipeWire: port.id
Expand Down

0 comments on commit 3d241f6

Please sign in to comment.