Files
viseq/include/libremidi/detail/midi_stream_decoder.hpp
T

466 lines
11 KiB
C++
Raw Normal View History

2024-10-09 02:01:23 +02:00
#pragma once
#include <libremidi/detail/midi_in.hpp>
2024-10-28 19:24:18 +01:00
#include <libremidi/cmidi2.hpp>
2024-10-09 02:01:23 +02:00
#include <chrono>
#include <cstdint>
#include <cinttypes>
#include <span>
namespace libremidi
{
static inline int64_t system_ns() noexcept
{
namespace clk = std::chrono;
return clk::duration_cast<clk::nanoseconds>(clk::steady_clock::now().time_since_epoch()).count();
}
struct timestamp_backend_info
{
// The API provides some kind of timestamping
bool has_absolute_timestamps{};
// The provided timestamping is equivalent or more precise than
// e.g. clock_gettime(CLOCK_MONOTONIC)
bool absolute_is_monotonic{};
// The API can provide samples in a buffer cycle (only PipeWire and JACK so far)
bool has_samples{};
};
2024-10-28 19:24:18 +01:00
template<typename Configuration>
struct input_state_machine_base
{
const Configuration& configuration;
explicit input_state_machine_base(const Configuration& conf)
: configuration{conf}
{
}
template <timestamp_backend_info info>
int64_t timestamp(auto to_ns, int64_t samples)
{
switch (configuration.timestamps)
{
default:
case timestamp_mode::NoTimestamp:
return 0;
case timestamp_mode::Relative: {
int64_t time_ns;
if constexpr (info.has_absolute_timestamps)
time_ns = to_ns();
else
time_ns = system_ns();
int64_t res;
if (first_message)
{
first_message = false;
res = 0;
}
else
{
res = time_ns - last_time_ns;
}
last_time_ns = time_ns;
return res;
}
case timestamp_mode::Absolute:
if constexpr (info.has_absolute_timestamps)
return to_ns();
else
return system_ns();
case timestamp_mode::SystemMonotonic:
if constexpr (info.absolute_is_monotonic)
return to_ns();
else
return system_ns();
case timestamp_mode::AudioFrame:
if constexpr (info.has_samples)
return samples;
else
return 0;
case timestamp_mode::Custom:
return configuration.get_timestamp(to_ns());
}
}
int64_t last_time_ns = 0;
bool first_message = true;
};
2024-10-09 02:01:23 +02:00
namespace midi1
{
2024-10-28 19:24:18 +01:00
struct input_state_machine : input_state_machine_base<input_configuration>
2024-10-09 02:01:23 +02:00
{
2024-10-28 19:24:18 +01:00
using input_state_machine_base::input_state_machine_base;
void reset()
2024-10-09 02:01:23 +02:00
{
2024-10-28 19:24:18 +01:00
message.bytes.clear();
message.timestamp = {};
state = main;
2024-10-09 02:01:23 +02:00
}
bool has_finished_sysex(std::span<const uint8_t> bytes) const noexcept
{
return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7));
}
// 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)
2024-10-28 19:24:18 +01:00
{
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)
2024-10-09 02:01:23 +02:00
{
int64_t nBytes = bytes.size();
int64_t iByte = 0;
const bool finished_sysex = has_finished_sysex(bytes);
switch (state)
{
case in_sysex: {
2024-10-28 19:24:18 +01:00
return on_continue_sysex(cb, bytes, finished_sysex);
2024-10-09 02:01:23 +02:00
}
case main: {
while (iByte < nBytes)
{
int64_t size = 1;
// We are expecting that the next byte in the packet is a status
// byte.
const auto status = bytes[iByte];
if (!(status & 0x80))
break;
// Determine the number of bytes in the MIDI message.
if (status < 0xC0)
size = 3;
else if (status < 0xE0)
size = 2;
else if (status < 0xF0)
size = 3;
else if (status == 0xF0)
{
if (configuration.ignore_sysex)
{
size = 0;
iByte = nBytes;
}
else
{
size = nBytes - iByte;
}
if (bytes[nBytes - 1] != 0xF7)
{
// We know per CoreMIDI API there can't be anything else in this packet
state = in_sysex;
message.assign(bytes.begin(), bytes.begin() + size);
message.timestamp = timestamp;
return;
}
}
else if (status == 0xF1)
{
// A MIDI time code message
if (configuration.ignore_timing)
{
size = 0;
iByte += 2;
}
else
{
size = 2;
}
}
else if (status == 0xF2)
size = 3;
else if (status == 0xF3)
size = 2;
else if (status == 0xF8)
{
// A MIDI timing tick message
if (configuration.ignore_timing)
{
size = 0;
iByte += 1;
}
else
{
size = 1;
}
}
else if (status == 0xFE)
{
// A MIDI active sensing message
if (configuration.ignore_sensing)
{
size = 0;
iByte += 1;
}
else
{
size = 1;
}
}
else
{
// Remaining real-time messages
size = 1;
}
// Now process the actual bytes of the message
if (size > 0)
{
auto begin = bytes.begin() + iByte;
message.assign(begin, begin + size);
message.timestamp = timestamp;
2024-10-28 19:24:18 +01:00
cb(std::move(message));
2024-10-09 02:01:23 +02:00
message.clear();
iByte += size;
}
}
}
}
}
2024-10-28 19:24:18 +01:00
void on_continue_sysex(
const message_callback& cb, std::span<const uint8_t> bytes, bool finished_sysex)
2024-10-09 02:01:23 +02:00
{
if (finished_sysex)
state = main;
if (configuration.ignore_sysex)
{
return;
}
else
{
message.insert(message.end(), bytes.begin(), bytes.end());
if (finished_sysex)
{
2024-10-28 19:24:18 +01:00
cb(std::move(message));
2024-10-09 02:01:23 +02:00
message.clear();
}
}
return;
}
2024-10-28 19:24:18 +01:00
void on_main(
const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp,
bool finished_sysex)
2024-10-09 02:01:23 +02:00
{
switch (bytes[0])
{
// SYSEX start
case 0xF0: {
if (!finished_sysex)
state = in_sysex;
if (!this->configuration.ignore_sysex)
{
message.assign(bytes.begin(), bytes.end());
message.timestamp = timestamp;
if (finished_sysex)
{
2024-10-28 19:24:18 +01:00
cb(std::move(message));
2024-10-09 02:01:23 +02:00
message.clear();
}
}
return;
}
case 0xF1:
case 0xF8:
if (this->configuration.ignore_timing)
return;
break;
case 0xFE:
if (this->configuration.ignore_sensing)
return;
break;
default:
break;
}
message.assign(bytes.begin(), bytes.end());
message.timestamp = timestamp;
2024-10-28 19:24:18 +01:00
cb(std::move(message));
2024-10-09 02:01:23 +02:00
message.clear();
}
2024-10-28 19:24:18 +01:00
void
on_bytes_segmented(const message_callback& cb, std::span<const uint8_t> bytes, int64_t timestamp)
2024-10-09 02:01:23 +02:00
{
if (bytes.empty())
return;
const bool finished_sysex = has_finished_sysex(bytes);
switch (state)
{
case in_sysex:
2024-10-28 19:24:18 +01:00
return on_continue_sysex(cb, bytes, finished_sysex);
2024-10-09 02:01:23 +02:00
case main:
2024-10-28 19:24:18 +01:00
return on_main(cb, bytes, timestamp, finished_sysex);
2024-10-09 02:01:23 +02:00
}
}
2024-10-28 19:24:18 +01:00
public:
libremidi::message message;
private:
enum
2024-10-09 02:01:23 +02:00
{
2024-10-28 19:24:18 +01:00
main,
in_sysex
} state{main};
};
}
2024-10-09 02:01:23 +02:00
2024-10-28 19:24:18 +01:00
namespace midi2
{
struct input_state_machine : input_state_machine_base<ump_input_configuration>
{
using input_state_machine_base::input_state_machine_base;
2024-10-09 02:01:23 +02:00
2024-10-28 19:24:18 +01:00
public:
void on_bytes_multi(std::span<const unsigned char> bytes, int64_t timestamp)
{
auto ptr = reinterpret_cast<const uint32_t*>(bytes.data());
auto sz = bytes.size() / 4;
return on_bytes_multi({ptr, sz}, timestamp);
}
2024-10-09 02:01:23 +02:00
2024-10-28 19:24:18 +01:00
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);
}
2024-10-09 02:01:23 +02:00
2024-10-28 19:24:18 +01:00
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);
}
2024-10-09 02:01:23 +02:00
2024-10-28 19:24:18 +01:00
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();
while (count > 0)
{
// Handle NOOP (or padding)
while (count > 0 && ump_stream[0] == 0)
{
count--;
ump_stream++;
}
2024-10-09 02:01:23 +02:00
2024-10-28 19:24:18 +01:00
if (count == 0)
break;
2024-10-09 02:01:23 +02:00
2024-10-28 19:24:18 +01:00
const auto ump_uints = cmidi2_ump_get_num_bytes(ump_stream[0]) / 4;
on_bytes_segmented(cb, {ump_stream, ump_stream + ump_uints}, timestamp);
2024-10-09 02:01:23 +02:00
2024-10-28 19:24:18 +01:00
ump_stream += ump_uints;
count -= ump_uints;
2024-10-09 02:01:23 +02:00
}
}
2024-10-28 19:24:18 +01:00
// Function to process bytes corresponding to at most one midi event
void
on_bytes_segmented(const ump_callback& cb, std::span<const uint32_t> bytes, int64_t timestamp)
2024-10-09 02:01:23 +02:00
{
2024-10-28 19:24:18 +01:00
// Filter according to message type
switch(cmidi2_ump_get_message_type(bytes.data()))
{
case CMIDI2_MESSAGE_TYPE_UTILITY:
{
// All the utility messages are about timing
if (this->configuration.ignore_timing)
return;
break;
}
2024-10-09 02:01:23 +02:00
2024-10-28 19:24:18 +01:00
case CMIDI2_MESSAGE_TYPE_SYSTEM:
{
if (this->configuration.ignore_timing)
{
auto status = cmidi2_ump_get_system_message_byte2(bytes.data());
switch(status)
{
case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE:
case CMIDI2_SYSTEM_STATUS_SONG_POSITION:
case CMIDI2_SYSTEM_STATUS_TIMING_CLOCK:
return;
}
}
if (this->configuration.ignore_sensing)
{
auto status = cmidi2_ump_get_system_message_byte2(bytes.data());
if(status == CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING)
return;
}
break;
}
case CMIDI2_MESSAGE_TYPE_SYSEX7:
case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:
{
if (this->configuration.ignore_sysex)
return;
break;
}
}
libremidi::ump msg;
std::copy(bytes.begin(), bytes.end(), msg.data);
msg.timestamp = timestamp;
cb(std::move(msg));
}
2024-10-09 02:01:23 +02:00
};
}
}