libremidi update to v5.4.3 and added virtual midi out as default
This commit is contained in:
@@ -0,0 +1,858 @@
|
||||
#pragma once
|
||||
|
||||
#include <libremidi/error_handler.hpp>
|
||||
#include <libremidi/message.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
|
||||
NAMESPACE_LIBREMIDI
|
||||
{
|
||||
static constexpr auto to_underlying(auto e)
|
||||
{
|
||||
return static_cast<std::underlying_type_t<decltype(e)>>(e);
|
||||
}
|
||||
|
||||
// A clean-room reverse-engineered remote control protocol compatible with many hardware devices.
|
||||
// Thanks https://github.com/NicoG60/TouchMCU !
|
||||
struct remote_control_protocol
|
||||
{
|
||||
enum class device_type : uint8_t
|
||||
{
|
||||
logic_control = 0x10,
|
||||
logic_control_xt = 0x11,
|
||||
mackie_control = 0x14
|
||||
};
|
||||
|
||||
enum class command_to_device : uint8_t
|
||||
{
|
||||
device_query = 0x00,
|
||||
host_connection_reply = 0x02,
|
||||
|
||||
transport_click = 0x0A,
|
||||
lcd_backlight_save = 0x0B,
|
||||
touchless_movable_fader = 0x0C,
|
||||
|
||||
faders_touch_sensitivity = 0x0E,
|
||||
go_offline = 0x0F,
|
||||
update_tc_display = 0x10,
|
||||
update_assignment_display = 0x11,
|
||||
update_lcd = 0x12,
|
||||
firmware_version_request = 0x13,
|
||||
version_reply = 0x14,
|
||||
|
||||
firmware_update = 0x18,
|
||||
|
||||
channel_meter_mode = 0x20,
|
||||
global_lcd_meter_mode = 0x21,
|
||||
|
||||
faders_to_minimum = 0x61,
|
||||
all_leds_off = 0x62,
|
||||
reset = 0x63,
|
||||
};
|
||||
|
||||
enum class command_from_device : uint8_t
|
||||
{
|
||||
host_connection_query = 0x01,
|
||||
host_connection_confirmation = 0x03,
|
||||
host_connection_error = 0x04,
|
||||
version_reply = 0x14,
|
||||
};
|
||||
|
||||
enum class lcd_meter_mode : uint8_t
|
||||
{
|
||||
horizontal = 0x00,
|
||||
vertical = 0x01,
|
||||
};
|
||||
|
||||
enum class fader_sensitivity : uint8_t
|
||||
{
|
||||
sensitivity_0 = 0x00,
|
||||
sensitivity_1 = 0x01,
|
||||
sensitivity_2 = 0x02,
|
||||
sensitivity_3 = 0x03,
|
||||
sensitivity_4 = 0x04,
|
||||
|
||||
sensitivity_default = sensitivity_3,
|
||||
};
|
||||
|
||||
// CC Message:
|
||||
// 0b0LMMVVVV
|
||||
// L: toggle underneath LED
|
||||
// MM: mode as led_ring_mode
|
||||
// VVVV: value
|
||||
enum class led_ring_mode : uint8_t
|
||||
{
|
||||
mode_0 = 0b00, // one led only
|
||||
mode_1 = 0b01, // pan pot
|
||||
mode_2 = 0b10, // fill leds from left
|
||||
mode_3 = 0b11, // fill leds from middle
|
||||
};
|
||||
|
||||
enum class pot : uint8_t
|
||||
{
|
||||
pot_0 = 0x00,
|
||||
pot_1 = 0x01,
|
||||
pot_2 = 0x02,
|
||||
pot_3 = 0x03,
|
||||
pot_4 = 0x04,
|
||||
pot_5 = 0x05,
|
||||
pot_6 = 0x06,
|
||||
pot_7 = 0x07,
|
||||
};
|
||||
|
||||
enum class fader : uint8_t
|
||||
{
|
||||
fader_0 = 0x00,
|
||||
fader_1 = 0x01,
|
||||
fader_2 = 0x02,
|
||||
fader_3 = 0x03,
|
||||
fader_4 = 0x04,
|
||||
fader_5 = 0x05,
|
||||
fader_6 = 0x06,
|
||||
fader_7 = 0x07,
|
||||
fader_master = 0x08,
|
||||
};
|
||||
|
||||
// control changes
|
||||
enum class mixer_control : uint8_t
|
||||
{
|
||||
// rotation: CC
|
||||
// 0b00000001 : clockwise
|
||||
// 0b01000001 : counter-clockwise
|
||||
vpot_rotation_0 = 0x10 + 0x00,
|
||||
vpot_rotation_1 = 0x10 + 0x01,
|
||||
vpot_rotation_2 = 0x10 + 0x02,
|
||||
vpot_rotation_3 = 0x10 + 0x03,
|
||||
vpot_rotation_4 = 0x10 + 0x04,
|
||||
vpot_rotation_5 = 0x10 + 0x05,
|
||||
vpot_rotation_6 = 0x10 + 0x06,
|
||||
vpot_rotation_7 = 0x10 + 0x07,
|
||||
|
||||
external_control = 0x2E,
|
||||
|
||||
// led ring: CC
|
||||
vpot_led_0 = 0x30 + 0x00,
|
||||
vpot_led_1 = 0x30 + 0x01,
|
||||
vpot_led_2 = 0x30 + 0x02,
|
||||
vpot_led_3 = 0x30 + 0x03,
|
||||
vpot_led_4 = 0x30 + 0x04,
|
||||
vpot_led_5 = 0x30 + 0x05,
|
||||
vpot_led_6 = 0x30 + 0x06,
|
||||
vpot_led_7 = 0x30 + 0x07,
|
||||
|
||||
jog_wheel = 0x3C,
|
||||
|
||||
timecode_digit_0 = 0x40 + 0x00,
|
||||
timecode_digit_1 = 0x40 + 0x01,
|
||||
timecode_digit_2 = 0x40 + 0x02,
|
||||
timecode_digit_3 = 0x40 + 0x03,
|
||||
timecode_digit_4 = 0x40 + 0x04,
|
||||
timecode_digit_5 = 0x40 + 0x05,
|
||||
timecode_digit_6 = 0x40 + 0x06,
|
||||
timecode_digit_7 = 0x40 + 0x07,
|
||||
timecode_digit_8 = 0x40 + 0x08,
|
||||
timecode_digit_9 = 0x40 + 0x09,
|
||||
|
||||
assignment_digit_0 = 0x4A,
|
||||
assignment_digit_1 = 0x4B,
|
||||
};
|
||||
|
||||
// note events
|
||||
enum class mixer_command : uint8_t
|
||||
{
|
||||
vpot_click_0 = 0x20 + 0x00,
|
||||
vpot_click_1 = 0x20 + 0x01,
|
||||
vpot_click_2 = 0x20 + 0x02,
|
||||
vpot_click_3 = 0x20 + 0x03,
|
||||
vpot_click_4 = 0x20 + 0x04,
|
||||
vpot_click_5 = 0x20 + 0x05,
|
||||
vpot_click_6 = 0x20 + 0x06,
|
||||
vpot_click_7 = 0x20 + 0x07,
|
||||
|
||||
rec_0 = 0x00 + 0x00,
|
||||
rec_1 = 0x00 + 0x01,
|
||||
rec_2 = 0x00 + 0x02,
|
||||
rec_3 = 0x00 + 0x03,
|
||||
rec_4 = 0x00 + 0x04,
|
||||
rec_5 = 0x00 + 0x05,
|
||||
rec_6 = 0x00 + 0x06,
|
||||
rec_7 = 0x00 + 0x07,
|
||||
|
||||
solo_0 = 0x08 + 0x00,
|
||||
solo_1 = 0x08 + 0x01,
|
||||
solo_2 = 0x08 + 0x02,
|
||||
solo_3 = 0x08 + 0x03,
|
||||
solo_4 = 0x08 + 0x04,
|
||||
solo_5 = 0x08 + 0x05,
|
||||
solo_6 = 0x08 + 0x06,
|
||||
solo_7 = 0x08 + 0x07,
|
||||
|
||||
mute_0 = 0x10 + 0x00,
|
||||
mute_1 = 0x10 + 0x01,
|
||||
mute_2 = 0x10 + 0x02,
|
||||
mute_3 = 0x10 + 0x03,
|
||||
mute_4 = 0x10 + 0x04,
|
||||
mute_5 = 0x10 + 0x05,
|
||||
mute_6 = 0x10 + 0x06,
|
||||
mute_7 = 0x10 + 0x07,
|
||||
|
||||
sel_0 = 0x18 + 0x00,
|
||||
sel_1 = 0x18 + 0x01,
|
||||
sel_2 = 0x18 + 0x02,
|
||||
sel_3 = 0x18 + 0x03,
|
||||
sel_4 = 0x18 + 0x04,
|
||||
sel_5 = 0x18 + 0x05,
|
||||
sel_6 = 0x18 + 0x06,
|
||||
sel_7 = 0x18 + 0x07,
|
||||
|
||||
// TODO metering
|
||||
assign_track = 0x28,
|
||||
assign_send = 0x29,
|
||||
assign_pan = 0x2A,
|
||||
assign_plugin = 0x2B,
|
||||
assign_eq = 0x2C,
|
||||
assign_instrument = 0x2D,
|
||||
|
||||
bank_left = 0x2E,
|
||||
bank_right = 0x2F,
|
||||
channel_left = 0x30,
|
||||
channel_right = 0x31,
|
||||
flip = 0x32,
|
||||
global = 0x33,
|
||||
|
||||
name_value_button = 0x34,
|
||||
smpte_beats_button = 0x35,
|
||||
|
||||
f1 = 0x36 + 0x00,
|
||||
f2 = 0x36 + 0x01,
|
||||
f3 = 0x36 + 0x02,
|
||||
f4 = 0x36 + 0x03,
|
||||
f5 = 0x36 + 0x04,
|
||||
f6 = 0x36 + 0x05,
|
||||
f7 = 0x36 + 0x06,
|
||||
f8 = 0x36 + 0x07,
|
||||
|
||||
midi_tracks = 0x3E,
|
||||
inputs = 0x3F,
|
||||
audio_tracks = 0x40,
|
||||
audio_instruments = 0x41,
|
||||
aux = 0x42,
|
||||
busses = 0x43,
|
||||
outputs = 0x44,
|
||||
user = 0x45,
|
||||
|
||||
shift = 0x46,
|
||||
option = 0x47,
|
||||
control = 0x48,
|
||||
alt = 0x49,
|
||||
|
||||
save = 0x50,
|
||||
undo = 0x51,
|
||||
cancel = 0x52,
|
||||
enter = 0x53,
|
||||
|
||||
markers = 0x54,
|
||||
nudge = 0x55,
|
||||
cycle = 0x56,
|
||||
drop = 0x57,
|
||||
replace = 0x58,
|
||||
click = 0x59,
|
||||
solo = 0x5a,
|
||||
|
||||
rewind = 0x5b,
|
||||
forward = 0x5c,
|
||||
stop = 0x5d,
|
||||
play = 0x5e,
|
||||
record = 0x5f,
|
||||
|
||||
up = 0x60,
|
||||
down = 0x61,
|
||||
left = 0x62,
|
||||
right = 0x63,
|
||||
zoom = 0x64,
|
||||
scrub = 0x65,
|
||||
|
||||
user_switch_1 = 0x66,
|
||||
user_switch_2 = 0x67,
|
||||
|
||||
fader_touched_0 = 0x68,
|
||||
fader_touched_1 = 0x69,
|
||||
fader_touched_2 = 0x6a,
|
||||
fader_touched_3 = 0x6b,
|
||||
fader_touched_4 = 0x6c,
|
||||
fader_touched_5 = 0x6d,
|
||||
fader_touched_6 = 0x6e,
|
||||
fader_touched_7 = 0x6f,
|
||||
fader_touched_master = 0x70,
|
||||
|
||||
smpte_led = 0x71,
|
||||
beats_led = 0x72,
|
||||
rude_solo_led = 0x73,
|
||||
|
||||
relay_click = 0x76,
|
||||
};
|
||||
|
||||
template <std::size_t N>
|
||||
using arr = std::array<uint8_t, N>;
|
||||
|
||||
device_type type = device_type::mackie_control;
|
||||
|
||||
static libremidi::message make_command_impl(auto&&... data)
|
||||
{
|
||||
using namespace std;
|
||||
libremidi::message m;
|
||||
m.bytes.reserve((std::ssize(data) + ...));
|
||||
(m.bytes.insert(m.bytes.end(), begin(data), end(data)), ...);
|
||||
return m;
|
||||
}
|
||||
|
||||
libremidi::message make_command(command_to_device c, auto&&... data)
|
||||
{
|
||||
using namespace std;
|
||||
const auto type = to_underlying(this->type);
|
||||
const auto cmd = to_underlying(c);
|
||||
const uint8_t header[6]{0xF0, 0x00, 0x00, 0x66, type, cmd};
|
||||
const uint8_t footer[1]{0xF7};
|
||||
return make_command_impl(header, data..., footer);
|
||||
}
|
||||
|
||||
auto device_query() { return make_command(command_to_device::device_query); }
|
||||
|
||||
auto response_to_challenge(arr<4> c)
|
||||
{
|
||||
arr<4> r;
|
||||
|
||||
r[0] = 0x7F & (c[0] + (c[1] ^ 0x0A) - c[3]);
|
||||
r[1] = 0x7F & ((c[2] >> 4) ^ (c[0] + c[3]));
|
||||
r[2] = 0x7F & (c[3] - (c[2] << 2) ^ (c[0] | c[1]));
|
||||
r[3] = 0x7F & (c[1] - c[2] + (0xF0 ^ (c[3] << 4)));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
auto host_connection_reply(arr<7> serial, arr<4> challenge_code)
|
||||
{
|
||||
const auto res = response_to_challenge(challenge_code);
|
||||
return make_command(command_to_device::host_connection_reply, serial, res);
|
||||
}
|
||||
|
||||
auto transport_click(bool enabled)
|
||||
{
|
||||
return make_command(
|
||||
command_to_device::transport_click, arr<1>{uint8_t(enabled ? 0x01 : 0x00)});
|
||||
}
|
||||
|
||||
auto lcd_backlight_save(uint8_t timeout)
|
||||
{
|
||||
// 0: instant off otherwise timeout in minutes
|
||||
return make_command(command_to_device::lcd_backlight_save, arr<1>{timeout});
|
||||
}
|
||||
|
||||
auto touchless_movable_fader(bool enabled)
|
||||
{
|
||||
return make_command(
|
||||
command_to_device::touchless_movable_fader, arr<1>{uint8_t(enabled ? 0x01 : 0x00)});
|
||||
}
|
||||
|
||||
auto faders_touch_sensitivity(uint8_t fader_id, fader_sensitivity sens)
|
||||
{
|
||||
return make_command(
|
||||
command_to_device::faders_touch_sensitivity, arr<2>{fader_id, to_underlying(sens)});
|
||||
}
|
||||
|
||||
auto go_offline() { return make_command(command_to_device::go_offline, arr<1>{0x7F}); }
|
||||
|
||||
auto update_tc_display()
|
||||
{
|
||||
// FIXME 1 .. 10
|
||||
return make_command(command_to_device::update_tc_display, arr<10>{});
|
||||
}
|
||||
|
||||
auto update_assignment_display()
|
||||
{
|
||||
// FIXME 1 .. 2
|
||||
return make_command(command_to_device::update_assignment_display, arr<2>{});
|
||||
}
|
||||
|
||||
auto update_lcd(std::string_view txt, int pos)
|
||||
{
|
||||
// FIXME
|
||||
if (pos < 0 || pos >= 112)
|
||||
return libremidi::message{};
|
||||
|
||||
int len = int(std::ssize(txt));
|
||||
|
||||
if (len > (112 - pos))
|
||||
{
|
||||
txt = txt.substr(0, 112 - pos);
|
||||
len = 112 - pos;
|
||||
}
|
||||
|
||||
uint8_t buf[128];
|
||||
const int N = std::min(len, 112 - pos);
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
buf[i + pos] = charmap_lcd(txt[i]);
|
||||
}
|
||||
buf[55] = '\n';
|
||||
buf[111] = '\n';
|
||||
|
||||
uint8_t cmd_pos = pos;
|
||||
|
||||
return make_command(command_to_device::update_lcd, arr<1>{cmd_pos}, std::span(buf + pos, len));
|
||||
}
|
||||
|
||||
auto update_lcd(std::string_view txt)
|
||||
{
|
||||
uint8_t buf[112] = {};
|
||||
for (int i = 0; i < std::min(int(std::ssize(txt)), 112); i++)
|
||||
{
|
||||
buf[i] = charmap_lcd(txt[i]);
|
||||
}
|
||||
buf[55] = '\n';
|
||||
buf[111] = '\n';
|
||||
return make_command(command_to_device::update_lcd, arr<1>{0}, std::span(buf, 112));
|
||||
}
|
||||
|
||||
auto firmware_version_request()
|
||||
{
|
||||
return make_command(command_to_device::firmware_version_request, arr<1>{0});
|
||||
}
|
||||
|
||||
auto firmware_update(std::span<uint8_t> firmware)
|
||||
{
|
||||
return make_command(command_to_device::firmware_update, firmware);
|
||||
}
|
||||
|
||||
auto channel_meter_mode(uint8_t fader_id, bool level_meter, bool peak_hold, bool signal_led)
|
||||
{
|
||||
uint8_t mode = 0;
|
||||
|
||||
if (signal_led)
|
||||
mode |= 0b1;
|
||||
if (peak_hold)
|
||||
mode |= 0b10;
|
||||
if (level_meter)
|
||||
mode |= 0b100;
|
||||
|
||||
return make_command(command_to_device::channel_meter_mode, arr<2>{fader_id, mode});
|
||||
}
|
||||
|
||||
auto global_lcd_meter_mode(lcd_meter_mode mode)
|
||||
{
|
||||
return make_command(command_to_device::global_lcd_meter_mode, arr<1>{to_underlying(mode)});
|
||||
}
|
||||
|
||||
auto faders_to_minimum() { return make_command(command_to_device::faders_to_minimum); }
|
||||
|
||||
auto all_leds_off() { return make_command(command_to_device::all_leds_off); }
|
||||
|
||||
auto reset() { return make_command(command_to_device::reset); }
|
||||
|
||||
static auto timecode(int hi, int mi, int si, int framei)
|
||||
{
|
||||
std::vector<libremidi::message> msg;
|
||||
auto h = std::to_string(hi);
|
||||
while (h.size() < 3)
|
||||
h.insert(h.begin(), '0');
|
||||
auto m = std::to_string(mi);
|
||||
while (m.size() < 2)
|
||||
m.insert(m.begin(), '0');
|
||||
auto s = std::to_string(si);
|
||||
while (s.size() < 2)
|
||||
s.insert(s.begin(), '0');
|
||||
auto f = std::to_string(framei);
|
||||
while (f.size() < 3)
|
||||
f.insert(f.begin(), '0');
|
||||
|
||||
using ce = libremidi::channel_events;
|
||||
msg.push_back(ce::control_change(1, 0x49, charmap_7segment(h[0])));
|
||||
msg.push_back(ce::control_change(1, 0x48, charmap_7segment(h[1])));
|
||||
msg.push_back(ce::control_change(1, 0x47, charmap_7segment(h[2])));
|
||||
|
||||
msg.push_back(ce::control_change(1, 0x46, charmap_7segment(m[0])));
|
||||
msg.push_back(ce::control_change(1, 0x45, charmap_7segment(m[1])));
|
||||
|
||||
msg.push_back(ce::control_change(1, 0x44, charmap_7segment(s[0])));
|
||||
msg.push_back(ce::control_change(1, 0x43, charmap_7segment(s[1])));
|
||||
|
||||
msg.push_back(ce::control_change(1, 0x42, charmap_7segment(f[0])));
|
||||
msg.push_back(ce::control_change(1, 0x41, charmap_7segment(f[1])));
|
||||
msg.push_back(ce::control_change(1, 0x40, charmap_7segment(f[2])));
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
static uint8_t charmap_7segment(char c, bool dot)
|
||||
{
|
||||
uint8_t res = charmap_7segment(c);
|
||||
if (dot)
|
||||
res |= 0b00100000;
|
||||
return res;
|
||||
};
|
||||
|
||||
static uint8_t charmap_7segment(char c)
|
||||
{
|
||||
// FIXME there are some more characters but what to map them to ? :)
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return c - 'a' + 1;
|
||||
else if (c >= 'A' && c <= 'Z')
|
||||
return c - 'A' + 1;
|
||||
else if (c >= '0' && c <= '9')
|
||||
return c - '0' + 0x30;
|
||||
else
|
||||
switch (c)
|
||||
{
|
||||
case '[':
|
||||
case '{':
|
||||
return 0x1B;
|
||||
case '\\':
|
||||
case '~': // yen too ?
|
||||
return 0x1C;
|
||||
case ']':
|
||||
case '}':
|
||||
return 0x1D;
|
||||
case '^':
|
||||
return 0x1E;
|
||||
case '_':
|
||||
return 0x1F;
|
||||
|
||||
case '!':
|
||||
return 0x21;
|
||||
case '"':
|
||||
return 0x22;
|
||||
case '#':
|
||||
return 0x23;
|
||||
case '$':
|
||||
return 0x24;
|
||||
case '%':
|
||||
return 0x25;
|
||||
case '&':
|
||||
return 0x26;
|
||||
case '\'':
|
||||
return 0x27;
|
||||
case '(':
|
||||
return 0x28;
|
||||
case ')':
|
||||
return 0x29;
|
||||
case '*':
|
||||
return 0x2A;
|
||||
case '+':
|
||||
return 0x2B;
|
||||
case ',':
|
||||
return 0x2C;
|
||||
case '-':
|
||||
return 0x2D;
|
||||
case '.':
|
||||
return 0x2E;
|
||||
case '/':
|
||||
return 0x2F;
|
||||
|
||||
case ':':
|
||||
return 0x3A;
|
||||
case ';':
|
||||
return 0x3B;
|
||||
case '<':
|
||||
return 0x3C;
|
||||
case '=':
|
||||
return 0x3D;
|
||||
case '>':
|
||||
return 0x3E;
|
||||
case '?':
|
||||
return 0x3F;
|
||||
|
||||
default:
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
static uint8_t charmap_lcd(char c)
|
||||
{
|
||||
// FIXME there are some more characters but what to map them to ? :)
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return c - 'a' + 0x61;
|
||||
else if (c >= 'A' && c <= 'Z')
|
||||
return c - 'A' + 0x41;
|
||||
else if (c >= '0' && c <= '9')
|
||||
return c - '0' + 0x30;
|
||||
else
|
||||
switch (c)
|
||||
{
|
||||
case '!':
|
||||
return 0x21;
|
||||
case '"':
|
||||
return 0x22;
|
||||
case '#':
|
||||
return 0x23;
|
||||
case '$':
|
||||
return 0x24;
|
||||
case '%':
|
||||
return 0x25;
|
||||
case '&':
|
||||
return 0x26;
|
||||
case '\'':
|
||||
return 0x27;
|
||||
case '(':
|
||||
return 0x28;
|
||||
case ')':
|
||||
return 0x29;
|
||||
case '*':
|
||||
return 0x2A;
|
||||
case '+':
|
||||
return 0x2B;
|
||||
case ',':
|
||||
return 0x2C;
|
||||
case '-':
|
||||
return 0x2D;
|
||||
case '.':
|
||||
return 0x2E;
|
||||
case '/':
|
||||
return 0x2F;
|
||||
|
||||
case ':':
|
||||
return 0x3A;
|
||||
case ';':
|
||||
return 0x3B;
|
||||
case '<':
|
||||
return 0x3C;
|
||||
case '=':
|
||||
return 0x3D;
|
||||
case '>':
|
||||
return 0x3E;
|
||||
case '?':
|
||||
return 0x3F;
|
||||
|
||||
case '@':
|
||||
return 0x40;
|
||||
case '[':
|
||||
return 0x5B;
|
||||
case '~': // Yen symbol... builtin mojibake?
|
||||
return 0x5C;
|
||||
case ']':
|
||||
return 0x5D;
|
||||
case '^':
|
||||
return 0x5E;
|
||||
case '_':
|
||||
return 0x5F;
|
||||
case '`':
|
||||
return 0x60;
|
||||
case '{':
|
||||
return 0x7B;
|
||||
case '|':
|
||||
return 0x7C;
|
||||
case '}':
|
||||
return 0x7D;
|
||||
case '\u000E':
|
||||
return 0x7E;
|
||||
case '\u000F':
|
||||
return 0x7F;
|
||||
default:
|
||||
return c; // gives access to the bubble first row 0x00 > 0x0F
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct rcp_configuration
|
||||
{
|
||||
//! How to send MIDI messages to the device.
|
||||
//! Note: this function *will* be called from different thread,
|
||||
//! thus it has to be thread-safe, for instance
|
||||
//! by storing the message in an event queue.
|
||||
std::function<void(libremidi::message&&)> midi_out;
|
||||
|
||||
std::function<void(libremidi::remote_control_protocol::device_type)> on_connected;
|
||||
std::function<void(libremidi::remote_control_protocol::mixer_command, bool)> on_command;
|
||||
std::function<void(libremidi::remote_control_protocol::mixer_control, int)> on_control;
|
||||
std::function<void(libremidi::remote_control_protocol::fader, uint16_t)> on_fader;
|
||||
|
||||
libremidi::midi_error_callback on_error{};
|
||||
};
|
||||
|
||||
struct remote_control_processor : libremidi::error_handler
|
||||
{
|
||||
using rcp = libremidi::remote_control_protocol;
|
||||
rcp_configuration configuration;
|
||||
rcp impl;
|
||||
|
||||
explicit remote_control_processor(rcp_configuration conf)
|
||||
: configuration{std::move(conf)}
|
||||
{
|
||||
assert(configuration.midi_out);
|
||||
|
||||
if (!configuration.on_error)
|
||||
configuration.on_error = [](std::string_view s, auto&&...) {
|
||||
std::fprintf(stderr, "libremidi: rcp error: %s\n", s.data());
|
||||
};
|
||||
|
||||
if (!configuration.on_connected)
|
||||
configuration.on_connected
|
||||
= [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_connected"); };
|
||||
|
||||
if (!configuration.on_command)
|
||||
configuration.on_command
|
||||
= [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_command"); };
|
||||
|
||||
if (!configuration.on_control)
|
||||
configuration.on_control
|
||||
= [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_control"); };
|
||||
|
||||
if (!configuration.on_fader)
|
||||
configuration.on_fader
|
||||
= [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_fader"); };
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
current_state = waiting_for_query;
|
||||
configuration.midi_out(impl.device_query());
|
||||
}
|
||||
|
||||
void on_midi(const libremidi::message& message)
|
||||
{
|
||||
switch (message.get_message_type())
|
||||
{
|
||||
case libremidi::message_type::SYSTEM_EXCLUSIVE:
|
||||
if (auto N = message.size(); N >= 7)
|
||||
{
|
||||
const uint8_t* bytes = message.bytes.data();
|
||||
|
||||
// strip 0xF0 & 0xF7
|
||||
bytes += 1;
|
||||
N -= 2;
|
||||
|
||||
// Mackie manufacturer ID check
|
||||
if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x66)
|
||||
{
|
||||
impl.type = static_cast<rcp::device_type>(bytes[3]);
|
||||
|
||||
// strip header
|
||||
bytes += 4;
|
||||
N -= 4;
|
||||
on_rcp_command(std::span(bytes, N));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
libremidi_handle_error(configuration, "Invalid sysex");
|
||||
}
|
||||
break;
|
||||
case libremidi::message_type::NOTE_ON:
|
||||
configuration.on_command(static_cast<rcp::mixer_command>(message[1]), message[2] > 0);
|
||||
break;
|
||||
case libremidi::message_type::NOTE_OFF:
|
||||
break;
|
||||
case libremidi::message_type::CONTROL_CHANGE:
|
||||
configuration.on_control(static_cast<rcp::mixer_control>(message[1]), message[2]);
|
||||
break;
|
||||
case libremidi::message_type::PITCH_BEND: {
|
||||
uint16_t value = message.bytes[2] * 128 + message.bytes[1];
|
||||
configuration.on_fader(static_cast<rcp::fader>(uint8_t(message.get_channel() - 1)), value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void on_rcp_command(std::span<const uint8_t> cmd)
|
||||
{
|
||||
if (cmd.empty())
|
||||
{
|
||||
libremidi_handle_error(configuration, "on_rcp_command: empty command");
|
||||
return;
|
||||
}
|
||||
|
||||
auto command = static_cast<rcp::command_from_device>(cmd[0]);
|
||||
cmd = cmd.subspan(1);
|
||||
switch (command)
|
||||
{
|
||||
case rcp::command_from_device::host_connection_query: {
|
||||
if (cmd.size() == 11)
|
||||
{
|
||||
current_state = got_query;
|
||||
|
||||
std::array<uint8_t, 7> serial;
|
||||
std::array<uint8_t, 4> challenge;
|
||||
std::copy_n(cmd.data(), 7, serial.begin());
|
||||
std::copy_n(cmd.data() + 7, 4, challenge.begin());
|
||||
|
||||
configuration.midi_out(impl.host_connection_reply(serial, challenge));
|
||||
}
|
||||
else
|
||||
libremidi_handle_error(configuration, "host_connection_query: invalid size");
|
||||
break;
|
||||
}
|
||||
case rcp::command_from_device::host_connection_confirmation:
|
||||
current_state = connected;
|
||||
configuration.on_connected(impl.type);
|
||||
break;
|
||||
case rcp::command_from_device::host_connection_error:
|
||||
current_state = errored;
|
||||
libremidi_handle_error(configuration, "host_connection_error");
|
||||
break;
|
||||
case rcp::command_from_device::version_reply: {
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void update_timecode(int h, int m, int s, int f)
|
||||
{
|
||||
for (auto&& m : rcp::timecode(h, m, s, f))
|
||||
configuration.midi_out(std::move(m));
|
||||
}
|
||||
|
||||
void update_lcd(std::string_view v)
|
||||
{
|
||||
auto res = impl.update_lcd(v);
|
||||
if (!res.empty())
|
||||
configuration.midi_out(std::move(res));
|
||||
}
|
||||
|
||||
void update_lcd(std::string_view v, int pos)
|
||||
{
|
||||
auto res = impl.update_lcd(v, pos);
|
||||
if (!res.empty())
|
||||
configuration.midi_out(std::move(res));
|
||||
}
|
||||
|
||||
void command(remote_control_protocol::mixer_command c, bool press)
|
||||
{
|
||||
using ce = libremidi::channel_events;
|
||||
configuration.midi_out(ce::note_on(1, to_underlying(c), press ? 127 : 0));
|
||||
configuration.midi_out(ce::note_off(1, to_underlying(c), press ? 127 : 0));
|
||||
}
|
||||
|
||||
void control(remote_control_protocol::mixer_control c, int value)
|
||||
{
|
||||
using ce = libremidi::channel_events;
|
||||
configuration.midi_out(ce::control_change(1, to_underlying(c), value));
|
||||
}
|
||||
|
||||
void fader(remote_control_protocol::fader c, uint16_t value)
|
||||
{
|
||||
int idx = to_underlying(c);
|
||||
|
||||
using ce = libremidi::channel_events;
|
||||
configuration.midi_out(ce::pitch_bend(idx + 1, value));
|
||||
}
|
||||
|
||||
// State machine
|
||||
enum
|
||||
{
|
||||
waiting_for_query,
|
||||
got_query,
|
||||
connected,
|
||||
errored
|
||||
} current_state{waiting_for_query};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user