mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-22 01:44:49 -05:00
1039 lines
31 KiB
C++
1039 lines
31 KiB
C++
#include "midi-helpers.hpp"
|
|
|
|
#include <layout-helpers.hpp>
|
|
#include <log-helper.hpp>
|
|
#include <obs-module-helper.hpp>
|
|
#include <path-helpers.hpp>
|
|
#include <plugin-state-helpers.hpp>
|
|
#include <selection-helpers.hpp>
|
|
#include <sync-helpers.hpp>
|
|
#include <ui-helpers.hpp>
|
|
#include <utility.hpp>
|
|
|
|
#undef DispatchMessage
|
|
|
|
namespace advss {
|
|
|
|
std::map<std::pair<MidiDeviceType, std::string>, MidiDeviceInstance *>
|
|
MidiDeviceInstance::devices = {};
|
|
|
|
static bool setupDeviceObservers()
|
|
{
|
|
static std::vector<libremidi::observer> observers;
|
|
try {
|
|
for (auto api : libremidi::available_apis()) {
|
|
libremidi::observer_configuration cbs;
|
|
cbs.input_added = [=](const libremidi::input_port &p) {
|
|
auto dev = MidiDeviceInstance::GetDevice(p);
|
|
if (!dev) {
|
|
return;
|
|
}
|
|
blog(LOG_INFO, "MIDI input connected: %s",
|
|
p.port_name.c_str());
|
|
dev->ClosePort();
|
|
dev->OpenPort();
|
|
};
|
|
cbs.input_removed = [=](const libremidi::input_port &p) {
|
|
auto dev = MidiDeviceInstance::GetDevice(p);
|
|
if (!dev) {
|
|
return;
|
|
}
|
|
blog(LOG_INFO, "MIDI input removed: %s",
|
|
p.port_name.c_str());
|
|
};
|
|
cbs.output_added = [=](const libremidi::output_port &p) {
|
|
auto dev = MidiDeviceInstance::GetDevice(p);
|
|
if (!dev) {
|
|
return;
|
|
}
|
|
blog(LOG_INFO, "MIDI output connected: %s",
|
|
p.port_name.c_str());
|
|
dev->ClosePort();
|
|
dev->OpenPort();
|
|
};
|
|
cbs.output_removed =
|
|
[=](const libremidi::output_port &p) {
|
|
auto dev =
|
|
MidiDeviceInstance::GetDevice(
|
|
p);
|
|
if (!dev) {
|
|
return;
|
|
}
|
|
blog(LOG_INFO,
|
|
"MIDI output removed: %s",
|
|
p.port_name.c_str());
|
|
};
|
|
observers.emplace_back(
|
|
cbs,
|
|
libremidi::observer_configuration_for(api));
|
|
}
|
|
} catch (const libremidi::driver_error &error) {
|
|
blog(LOG_WARNING, "Failed to setup midi device observers: %s",
|
|
error.what());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool deviceObserversAreSetup = setupDeviceObservers();
|
|
|
|
void MidiDeviceInstance::ResetAllDevices()
|
|
{
|
|
for (auto const &[_, device] : MidiDeviceInstance::devices) {
|
|
device->ClosePort();
|
|
device->OpenPort();
|
|
}
|
|
}
|
|
|
|
MidiMessage::MidiMessage(const libremidi::message &message)
|
|
{
|
|
_typeIsOptional = false;
|
|
_type = message.get_message_type();
|
|
_channel = message.get_channel();
|
|
_note = GetMidiNote(message);
|
|
_value = GetMidiValue(message);
|
|
}
|
|
|
|
void MidiMessage::Save(obs_data_t *obj) const
|
|
{
|
|
OBSDataAutoRelease data = obs_data_create();
|
|
obs_data_set_bool(data, "typeIsOptional", _typeIsOptional);
|
|
obs_data_set_int(data, "type", static_cast<int>(_type));
|
|
_channel.Save(data, "channel");
|
|
_note.Save(data, "note");
|
|
_value.Save(data, "value");
|
|
obs_data_set_obj(obj, "midiMessage", data);
|
|
}
|
|
|
|
void MidiMessage::Load(obs_data_t *obj)
|
|
{
|
|
OBSDataAutoRelease data = obs_data_get_obj(obj, "midiMessage");
|
|
_typeIsOptional = obs_data_get_bool(data, "typeIsOptional");
|
|
_type = static_cast<libremidi::message_type>(
|
|
obs_data_get_int(data, "type"));
|
|
_channel.Load(data, "channel");
|
|
_note.Load(data, "note");
|
|
_value.Load(data, "value");
|
|
}
|
|
|
|
int MidiMessage::GetMidiNote(const libremidi::message &msg)
|
|
{
|
|
switch (msg.get_message_type()) {
|
|
case libremidi::message_type::NOTE_OFF:
|
|
case libremidi::message_type::NOTE_ON:
|
|
case libremidi::message_type::CONTROL_CHANGE:
|
|
return msg[1];
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int MidiMessage::GetMidiValue(const libremidi::message &msg)
|
|
{
|
|
switch (msg.get_message_type()) {
|
|
case libremidi::message_type::NOTE_ON:
|
|
case libremidi::message_type::NOTE_OFF:
|
|
case libremidi::message_type::CONTROL_CHANGE:
|
|
case libremidi::message_type::PITCH_BEND:
|
|
return msg[2];
|
|
case libremidi::message_type::PROGRAM_CHANGE:
|
|
return msg[1];
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
std::string MidiMessage::ToString() const
|
|
{
|
|
return "Type: " + MidiTypeToString(_type) +
|
|
" Note: " + std::to_string(_note) +
|
|
" Channel: " + std::to_string(_channel) +
|
|
" Value: " + std::to_string(_value);
|
|
}
|
|
|
|
void MidiMessage::ResolveVariables()
|
|
{
|
|
_channel.ResolveVariables();
|
|
_note.ResolveVariables();
|
|
_value.ResolveVariables();
|
|
}
|
|
|
|
std::string MidiMessage::ToString(const libremidi::message &msg)
|
|
{
|
|
return "Type: " + GetMidiType(msg) +
|
|
" Note: " + std::to_string(GetMidiNote(msg)) +
|
|
" Channel: " + std::to_string(msg.get_channel()) +
|
|
" Value: " + std::to_string(GetMidiValue(msg));
|
|
}
|
|
|
|
std::string MidiMessage::MidiTypeToString(libremidi::message_type type)
|
|
{
|
|
switch (type) {
|
|
case libremidi::message_type::INVALID:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.invalid");
|
|
// Standard Messages
|
|
case libremidi::message_type::NOTE_OFF:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.noteOff");
|
|
case libremidi::message_type::NOTE_ON:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.noteOn");
|
|
case libremidi::message_type::POLY_PRESSURE:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.polyphonicPressure");
|
|
case libremidi::message_type::CONTROL_CHANGE:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.controlChange");
|
|
case libremidi::message_type::PROGRAM_CHANGE:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.programChange");
|
|
case libremidi::message_type::AFTERTOUCH:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.channelAftertouch");
|
|
case libremidi::message_type::PITCH_BEND:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.pitchBend");
|
|
// System Common Messages
|
|
case libremidi::message_type::SYSTEM_EXCLUSIVE:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.systemExclusive");
|
|
case libremidi::message_type::TIME_CODE:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.timeCode");
|
|
case libremidi::message_type::SONG_POS_POINTER:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.songPositionPointer");
|
|
case libremidi::message_type::SONG_SELECT:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.songSelect");
|
|
case libremidi::message_type::RESERVED1:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.reserved1");
|
|
case libremidi::message_type::RESERVED2:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.reserved2");
|
|
case libremidi::message_type::TUNE_REQUEST:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.tuneRequest");
|
|
case libremidi::message_type::EOX:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.endOfSystemExclusive");
|
|
// System Realtime Messages
|
|
case libremidi::message_type::TIME_CLOCK:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.timeClock");
|
|
case libremidi::message_type::RESERVED3:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.reserved3");
|
|
case libremidi::message_type::START:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.startFile");
|
|
case libremidi::message_type::CONTINUE:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.continueFile");
|
|
case libremidi::message_type::STOP:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.stopFile");
|
|
case libremidi::message_type::RESERVED4:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.reserved4");
|
|
case libremidi::message_type::ACTIVE_SENSING:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.activeSensing");
|
|
case libremidi::message_type::SYSTEM_RESET:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.systemReset");
|
|
default:
|
|
return obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.unknown");
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string MidiMessage::GetMidiType(const libremidi::message &msg)
|
|
{
|
|
return MidiTypeToString(msg.get_message_type());
|
|
}
|
|
|
|
bool MidiMessage::Matches(const MidiMessage &m) const
|
|
{
|
|
const bool channelMatch = _channel == optionalChannelIndicator ||
|
|
m._channel == optionalChannelIndicator ||
|
|
(_channel == m._channel);
|
|
const bool noteMatch = _note == optionalNoteIndicator ||
|
|
m._note == optionalNoteIndicator ||
|
|
(_note == m._note);
|
|
const bool valueMatch = _value == optionalValueIndicator ||
|
|
m._value == optionalValueIndicator ||
|
|
(_value == m._value);
|
|
const bool typeMatch = _typeIsOptional || m._typeIsOptional ||
|
|
(_type == m._type);
|
|
return channelMatch && noteMatch && valueMatch && typeMatch;
|
|
}
|
|
|
|
MidiDeviceInstance *
|
|
MidiDeviceInstance::GetDeviceAndOpen(MidiDeviceType type,
|
|
const std::string &name)
|
|
{
|
|
if (name.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto key = std::make_pair(type, name);
|
|
auto it = devices.find(key);
|
|
if (it != devices.end()) {
|
|
it->second->OpenPort();
|
|
return it->second;
|
|
}
|
|
|
|
try {
|
|
auto device = new MidiDeviceInstance();
|
|
device->_type = type;
|
|
device->_name = name;
|
|
devices[key] = device;
|
|
device->OpenPort();
|
|
return device;
|
|
} catch (const libremidi::driver_error &error) {
|
|
blog(LOG_WARNING,
|
|
"Failed to create midi %s device instance for port '%s': %s",
|
|
type == MidiDeviceType::INPUT ? "input" : "output",
|
|
name.c_str(), error.what());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static std::string
|
|
getNameFromPortInformation(const libremidi::port_information &info)
|
|
{
|
|
std::string name = info.manufacturer + " " + info.device_name + " " +
|
|
info.port_name + "] " + info.display_name;
|
|
name.erase(name.begin(), std::find_if(name.begin(), name.end(),
|
|
[](char c) { return c != ' '; }));
|
|
return "[" + name;
|
|
}
|
|
|
|
MidiDeviceInstance *
|
|
advss::MidiDeviceInstance::GetDevice(const libremidi::input_port &p)
|
|
{
|
|
auto key = std::make_pair(MidiDeviceType::INPUT,
|
|
getNameFromPortInformation(p));
|
|
auto it = MidiDeviceInstance::devices.find(key);
|
|
if (it == devices.end()) {
|
|
return nullptr;
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
MidiDeviceInstance *
|
|
advss::MidiDeviceInstance::GetDevice(const libremidi::output_port &p)
|
|
{
|
|
auto key = std::make_pair(MidiDeviceType::OUTPUT,
|
|
getNameFromPortInformation(p));
|
|
auto it = MidiDeviceInstance::devices.find(key);
|
|
if (it == devices.end()) {
|
|
return nullptr;
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
static inline QStringList getInputDeviceNames()
|
|
{
|
|
QStringList devices;
|
|
try {
|
|
libremidi::observer obs;
|
|
for (const libremidi::input_port &port :
|
|
obs.get_input_ports()) {
|
|
devices << QString::fromStdString(
|
|
getNameFromPortInformation(port));
|
|
}
|
|
} catch (const libremidi::driver_error &error) {
|
|
blog(LOG_WARNING, "Failed to get midi input devices: %s",
|
|
error.what());
|
|
}
|
|
return devices;
|
|
}
|
|
|
|
static inline QStringList getOutputDeviceNames()
|
|
{
|
|
QStringList devices;
|
|
try {
|
|
libremidi::observer obs;
|
|
for (const libremidi::output_port &port :
|
|
obs.get_output_ports()) {
|
|
devices << QString::fromStdString(
|
|
getNameFromPortInformation(port));
|
|
}
|
|
} catch (const libremidi::driver_error &error) {
|
|
blog(LOG_WARNING, "Failed to get midi output devices: %s",
|
|
error.what());
|
|
}
|
|
return devices;
|
|
}
|
|
|
|
static std::string getPortNameFromNumber(MidiDeviceType type, int port)
|
|
{
|
|
if (port < 0) {
|
|
return "";
|
|
}
|
|
|
|
QStringList devices;
|
|
if (type == MidiDeviceType::INPUT) {
|
|
devices = getInputDeviceNames();
|
|
} else {
|
|
devices = getOutputDeviceNames();
|
|
}
|
|
|
|
if (port >= devices.size()) {
|
|
return "";
|
|
}
|
|
return devices.at(port).toStdString();
|
|
}
|
|
|
|
MidiDeviceInstance *MidiDeviceInstance::GetDeviceAndOpen(MidiDeviceType type,
|
|
int port)
|
|
{
|
|
std::string name = getPortNameFromNumber(type, port);
|
|
return GetDeviceAndOpen(type, name);
|
|
}
|
|
|
|
void MidiDevice::Save(obs_data_t *obj) const
|
|
{
|
|
OBSDataAutoRelease data = obs_data_create();
|
|
obs_data_set_int(data, "type", static_cast<int>(_type));
|
|
obs_data_set_string(data, "portName", _dev ? _dev->_name.c_str() : "");
|
|
obs_data_set_obj(obj, "midiDevice", data);
|
|
}
|
|
|
|
void MidiDevice::Load(obs_data_t *obj)
|
|
{
|
|
OBSDataAutoRelease data = obs_data_get_obj(obj, "midiDevice");
|
|
_type = static_cast<MidiDeviceType>(obs_data_get_int(data, "type"));
|
|
obs_data_set_default_int(data, "port", -1);
|
|
// TODO: Remove this fallback at some point
|
|
if (obs_data_has_user_value(data, "port")) {
|
|
auto port = obs_data_get_int(data, "port");
|
|
_dev = MidiDeviceInstance::GetDeviceAndOpen(_type, port);
|
|
if (_dev) {
|
|
_name = _dev->_name;
|
|
}
|
|
} else {
|
|
_name = obs_data_get_string(data, "portName");
|
|
_dev = MidiDeviceInstance::GetDeviceAndOpen(_type, _name);
|
|
}
|
|
}
|
|
|
|
bool MidiDevice::SendMessge(const MidiMessage &m) const
|
|
{
|
|
if (_type == MidiDeviceType::INPUT || _name.empty() || !_dev) {
|
|
return false;
|
|
}
|
|
|
|
return _dev->SendMessge(m);
|
|
}
|
|
|
|
static std::optional<libremidi::output_port>
|
|
getOutPortFromName(const std::string &name)
|
|
{
|
|
try {
|
|
libremidi::observer obs;
|
|
for (const libremidi::output_port &port :
|
|
obs.get_output_ports()) {
|
|
if (getNameFromPortInformation(port) == name) {
|
|
return port;
|
|
}
|
|
}
|
|
} catch (const libremidi::driver_error &error) {
|
|
blog(LOG_WARNING, "%s: %s", __func__, error.what());
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static std::optional<libremidi::input_port>
|
|
getInPortFromName(const std::string &name)
|
|
{
|
|
try {
|
|
libremidi::observer obs;
|
|
for (const libremidi::input_port &port :
|
|
obs.get_input_ports()) {
|
|
if (getNameFromPortInformation(port) == name) {
|
|
return port;
|
|
}
|
|
}
|
|
} catch (const libremidi::driver_error &error) {
|
|
blog(LOG_WARNING, "%s: %s", __func__, error.what());
|
|
}
|
|
return {};
|
|
}
|
|
|
|
bool MidiDeviceInstance::OpenPort()
|
|
{
|
|
if (IsOpened()) {
|
|
return true;
|
|
}
|
|
|
|
if (_type == MidiDeviceType::OUTPUT) {
|
|
auto port = getOutPortFromName(_name);
|
|
if (!port) {
|
|
blog(LOG_INFO,
|
|
"Could not find output port with name '%s'",
|
|
_name.c_str());
|
|
return false;
|
|
}
|
|
try {
|
|
_out.open_port(*port);
|
|
blog(LOG_INFO, "Opened output midi port '%s'",
|
|
_name.c_str());
|
|
return true;
|
|
} catch (const libremidi::driver_error &error) {
|
|
blog(LOG_WARNING,
|
|
"Failed to open output midi port '%s': %s",
|
|
_name.c_str(), error.what());
|
|
} catch (const libremidi::system_error &error) {
|
|
blog(LOG_WARNING,
|
|
"Failed to open output midi port '%s': %s",
|
|
_name.c_str(), error.what());
|
|
} catch (const libremidi::midi_exception &error) {
|
|
blog(LOG_WARNING,
|
|
"Failed to open output midi port '%s': %s",
|
|
_name.c_str(), error.what());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
auto cb = [this](libremidi::message &&m) {
|
|
ReceiveMidiMessage(std::move(m));
|
|
};
|
|
|
|
_in = libremidi::midi_in{
|
|
libremidi::input_configuration{.on_message = cb}};
|
|
|
|
auto port = getInPortFromName(_name);
|
|
if (!port) {
|
|
blog(LOG_INFO, "Could not find input port with name '%s'",
|
|
_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
_in.open_port(*port);
|
|
blog(LOG_INFO, "Opened input midi port '%s'", _name.c_str());
|
|
return true;
|
|
} catch (const libremidi::driver_error &error) {
|
|
blog(LOG_WARNING, "Failed to open input midi port '%s': %s",
|
|
_name.c_str(), error.what());
|
|
} catch (const libremidi::system_error &error) {
|
|
blog(LOG_WARNING, "Failed to open input midi port '%s': %s",
|
|
_name.c_str(), error.what());
|
|
} catch (const libremidi::midi_exception &error) {
|
|
blog(LOG_WARNING, "Failed to open input midi port '%s': %s",
|
|
_name.c_str(), error.what());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MidiDeviceInstance::IsOpened() const
|
|
{
|
|
return (_type == MidiDeviceType::INPUT && _in.is_port_open()) ||
|
|
(_type == MidiDeviceType::OUTPUT && _out.is_port_open());
|
|
}
|
|
|
|
void MidiDeviceInstance::ClosePort()
|
|
{
|
|
if (!IsOpened()) {
|
|
return;
|
|
}
|
|
|
|
if (_type == MidiDeviceType::OUTPUT) {
|
|
try {
|
|
_out.close_port();
|
|
blog(LOG_INFO, "Closed output midi port '%s'",
|
|
_name.c_str());
|
|
} catch (const libremidi::driver_error &error) {
|
|
blog(LOG_WARNING,
|
|
"Failed to close output midi port '%s': %s",
|
|
_name.c_str(), error.what());
|
|
} catch (const libremidi::system_error &error) {
|
|
blog(LOG_WARNING,
|
|
"Failed to close output midi port '%s': %s",
|
|
_name.c_str(), error.what());
|
|
} catch (const libremidi::midi_exception &error) {
|
|
blog(LOG_WARNING,
|
|
"Failed to close output midi port '%s': %s",
|
|
_name.c_str(), error.what());
|
|
}
|
|
return;
|
|
}
|
|
|
|
try {
|
|
_in.close_port();
|
|
blog(LOG_INFO, "Closed input midi port '%s'", _name.c_str());
|
|
} catch (const libremidi::driver_error &error) {
|
|
blog(LOG_WARNING, "Failed to close input midi port '%s': %s",
|
|
_name.c_str(), error.what());
|
|
} catch (const libremidi::system_error &error) {
|
|
blog(LOG_WARNING, "Failed to close input midi port '%s': %s",
|
|
_name.c_str(), error.what());
|
|
} catch (const libremidi::midi_exception &error) {
|
|
blog(LOG_WARNING, "Failed to close input midi port '%s': %s",
|
|
_name.c_str(), error.what());
|
|
}
|
|
}
|
|
|
|
bool MidiDeviceInstance::SendMessge(const MidiMessage &m)
|
|
{
|
|
libremidi::message message;
|
|
int channel = m.Channel();
|
|
int note = m.Note();
|
|
int value = m.Value();
|
|
|
|
switch (m.Type()) {
|
|
case libremidi::message_type::NOTE_OFF:
|
|
message = libremidi::channel_events::note_off(channel, note,
|
|
value);
|
|
break;
|
|
case libremidi::message_type::NOTE_ON:
|
|
message = libremidi::channel_events::note_on(channel, note,
|
|
value);
|
|
break;
|
|
case libremidi::message_type::CONTROL_CHANGE:
|
|
message = libremidi::channel_events::control_change(
|
|
channel, note, value);
|
|
break;
|
|
case libremidi::message_type::PROGRAM_CHANGE:
|
|
message = libremidi::channel_events::program_change(channel,
|
|
value);
|
|
break;
|
|
case libremidi::message_type::PITCH_BEND:
|
|
message = libremidi::channel_events::pitch_bend(
|
|
channel, (value <= 64 ? 0 : value - 64) * 2, value);
|
|
break;
|
|
case libremidi::message_type::POLY_PRESSURE:
|
|
message = libremidi::channel_events::poly_pressure(channel,
|
|
note, value);
|
|
break;
|
|
case libremidi::message_type::AFTERTOUCH:
|
|
message = libremidi::channel_events::aftertouch(channel, value);
|
|
break;
|
|
default:
|
|
message = {libremidi::channel_events::make_command(m.Type(),
|
|
channel),
|
|
(unsigned char)note, (unsigned char)value};
|
|
blog(LOG_WARNING,
|
|
"sending midi message of non-default type \"%s\"",
|
|
MidiMessage::MidiTypeToString(m.Type()).c_str());
|
|
break;
|
|
}
|
|
|
|
try {
|
|
_out.send_message(message);
|
|
return true;
|
|
} catch (const libremidi::driver_error &err) {
|
|
blog(LOG_WARNING, "%s", err.what());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
MidiMessageBuffer MidiDeviceInstance::RegisterForMidiMessages()
|
|
{
|
|
return _dispatcher.RegisterClient();
|
|
}
|
|
|
|
void MidiDeviceInstance::ReceiveMidiMessage(libremidi::message &&msg)
|
|
{
|
|
_dispatcher.DispatchMessage(msg);
|
|
vblog(LOG_INFO, "received midi: %s",
|
|
MidiMessage::ToString(msg).c_str());
|
|
}
|
|
|
|
[[nodiscard]] MidiMessageBuffer MidiDevice::RegisterForMidiMessages() const
|
|
{
|
|
if (_type == MidiDeviceType::OUTPUT || _name.empty() || !_dev) {
|
|
return {};
|
|
}
|
|
|
|
return _dev->RegisterForMidiMessages();
|
|
}
|
|
|
|
std::string MidiDevice::Name() const
|
|
{
|
|
return _name;
|
|
}
|
|
|
|
MidiDeviceSelection::MidiDeviceSelection(QWidget *parent, MidiDeviceType t)
|
|
: QComboBox(parent),
|
|
_type(t)
|
|
{
|
|
AddSelectionEntry(this, obs_module_text("AdvSceneSwitcher.selectItem"));
|
|
|
|
if (_type == MidiDeviceType::INPUT) {
|
|
addItems(getInputDeviceNames());
|
|
} else {
|
|
addItems(getOutputDeviceNames());
|
|
}
|
|
|
|
QWidget::connect(this, SIGNAL(currentIndexChanged(int)), this,
|
|
SLOT(IdxChangedHelper(int)));
|
|
}
|
|
|
|
void MidiDeviceSelection::SetDevice(const MidiDevice &_dev)
|
|
{
|
|
setCurrentText(QString::fromStdString(_dev.Name()));
|
|
}
|
|
|
|
void MidiDeviceSelection::IdxChangedHelper(int idx)
|
|
{
|
|
if (idx == 0) {
|
|
emit DeviceSelectionChanged(MidiDevice());
|
|
}
|
|
|
|
auto name = currentText().toStdString();
|
|
auto devInstance = MidiDeviceInstance::GetDeviceAndOpen(_type, name);
|
|
if (!devInstance) {
|
|
DisplayMessage(obs_module_text(
|
|
"AdvSceneSwitcher.midi.deviceOpenFail"));
|
|
const QSignalBlocker b(this);
|
|
setCurrentIndex(0);
|
|
idx = 0;
|
|
}
|
|
|
|
MidiDevice dev;
|
|
dev._type = _type;
|
|
dev._name = name;
|
|
dev._dev = devInstance;
|
|
emit DeviceSelectionChanged(dev);
|
|
}
|
|
|
|
static void populateMidiMessageTypeSelection(QComboBox *list)
|
|
{
|
|
list->addItem(
|
|
obs_module_text("AdvSceneSwitcher.midi.message.type.optional"));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::NOTE_OFF)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::NOTE_ON)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::POLY_PRESSURE)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::CONTROL_CHANGE)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::PROGRAM_CHANGE)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::AFTERTOUCH)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::PITCH_BEND)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::SYSTEM_EXCLUSIVE)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::TIME_CODE)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::SONG_POS_POINTER)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::SONG_SELECT)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::RESERVED1)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::RESERVED2)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::TUNE_REQUEST)));
|
|
list->addItem(QString::fromStdString(
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::EOX)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::TIME_CLOCK)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::RESERVED3)));
|
|
list->addItem(QString::fromStdString(
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::START)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::CONTINUE)));
|
|
list->addItem(QString::fromStdString(
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::STOP)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::RESERVED4)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::ACTIVE_SENSING)));
|
|
list->addItem(QString::fromStdString(MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::SYSTEM_RESET)));
|
|
}
|
|
|
|
MidiMessageSelection::MidiMessageSelection(QWidget *parent)
|
|
: QWidget(parent),
|
|
_type(new QComboBox()),
|
|
_channel(new VariableSpinBox()),
|
|
_noteValue(new VariableSpinBox()),
|
|
_noteString(new QComboBox()),
|
|
_noteValueStringToggle(new QPushButton()),
|
|
_value(new VariableSpinBox())
|
|
{
|
|
populateMidiMessageTypeSelection(_type);
|
|
|
|
_noteString->addItem(
|
|
obs_module_text("AdvSceneSwitcher.midi.message.placeholder"));
|
|
_noteString->addItems(GetAllNotes());
|
|
_noteString->setEditable(true);
|
|
_noteString->setInsertPolicy(QComboBox::NoInsert);
|
|
|
|
_noteValueStringToggle->setMaximumWidth(22);
|
|
_noteValueStringToggle->setCheckable(true);
|
|
const auto path = GetDataFilePath("res/images/" + GetThemeTypeName() +
|
|
"Note.svg");
|
|
SetButtonIcon(_noteValueStringToggle, path.c_str());
|
|
|
|
_channel->specialValueText(
|
|
obs_module_text("AdvSceneSwitcher.midi.message.placeholder"));
|
|
_noteValue->specialValueText(
|
|
obs_module_text("AdvSceneSwitcher.midi.message.placeholder"));
|
|
_value->specialValueText(
|
|
obs_module_text("AdvSceneSwitcher.midi.message.placeholder"));
|
|
|
|
_channel->setMinimum(MidiMessage::optionalChannelIndicator);
|
|
_noteValue->setMinimum(MidiMessage::optionalNoteIndicator);
|
|
_value->setMinimum(MidiMessage::optionalValueIndicator);
|
|
|
|
_channel->setMaximum(16);
|
|
_noteValue->setMaximum(127);
|
|
_value->setMaximum(127);
|
|
|
|
connect(_type, SIGNAL(currentTextChanged(const QString &)), this,
|
|
SLOT(TypeChanged(const QString &)));
|
|
connect(_channel,
|
|
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
|
this, SLOT(ChannelChanged(const NumberVariable<int> &)));
|
|
connect(_noteValue,
|
|
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
|
this, SLOT(NoteChanged(const NumberVariable<int> &)));
|
|
connect(_noteString, SIGNAL(currentIndexChanged(int)), this,
|
|
SLOT(NoteStringIdxChanged(int)));
|
|
connect(_noteValueStringToggle, SIGNAL(toggled(bool)), this,
|
|
SLOT(ShowNote(bool)));
|
|
connect(_value,
|
|
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
|
this, SLOT(ValueChanged(const NumberVariable<int> &)));
|
|
|
|
auto layout = new QGridLayout;
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
layout->addWidget(new QLabel(obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type")),
|
|
0, 0);
|
|
layout->addWidget(_type, 0, 1);
|
|
layout->addWidget(new QLabel(obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.channel")),
|
|
1, 0);
|
|
layout->addWidget(_channel, 1, 1);
|
|
layout->addWidget(new QLabel(obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.note")),
|
|
2, 0);
|
|
auto noteLayout = new QHBoxLayout;
|
|
noteLayout->addWidget(_noteValue);
|
|
noteLayout->addWidget(_noteString);
|
|
noteLayout->addWidget(_noteValueStringToggle);
|
|
noteLayout->setContentsMargins(0, 0, 0, 0);
|
|
layout->addLayout(noteLayout, 2, 1);
|
|
layout->addWidget(new QLabel(obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.value")),
|
|
3, 0);
|
|
layout->addWidget(_value, 3, 1);
|
|
|
|
// Reduce label column to its minimum size
|
|
MinimizeSizeOfColumn(layout, 0);
|
|
|
|
setLayout(layout);
|
|
|
|
ShowNote(false);
|
|
}
|
|
|
|
void MidiMessageSelection::SetMessage(const MidiMessage &m)
|
|
{
|
|
_currentSelection = m;
|
|
const QSignalBlocker b(this);
|
|
if (m._typeIsOptional) {
|
|
_type->setCurrentText(obs_module_text(
|
|
"AdvSceneSwitcher.midi.message.type.optional"));
|
|
} else {
|
|
_type->setCurrentText(QString::fromStdString(
|
|
MidiMessage::MidiTypeToString(m._type)));
|
|
}
|
|
_channel->SetValue(m._channel);
|
|
_noteValue->SetValue(m._note);
|
|
_noteString->setCurrentIndex(m._note + 1);
|
|
_value->SetValue(m._value);
|
|
}
|
|
|
|
libremidi::message_type
|
|
MidiMessageSelection::TextToMidiType(const QString &text)
|
|
{
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::NOTE_OFF)) {
|
|
return libremidi::message_type::NOTE_OFF;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::NOTE_ON)) {
|
|
return libremidi::message_type::NOTE_ON;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::POLY_PRESSURE)) {
|
|
return libremidi::message_type::POLY_PRESSURE;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::CONTROL_CHANGE)) {
|
|
return libremidi::message_type::CONTROL_CHANGE;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::PROGRAM_CHANGE)) {
|
|
return libremidi::message_type::PROGRAM_CHANGE;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::AFTERTOUCH)) {
|
|
return libremidi::message_type::AFTERTOUCH;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::PITCH_BEND)) {
|
|
return libremidi::message_type::PITCH_BEND;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::SYSTEM_EXCLUSIVE)) {
|
|
return libremidi::message_type::SYSTEM_EXCLUSIVE;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::TIME_CODE)) {
|
|
return libremidi::message_type::TIME_CODE;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::SONG_POS_POINTER)) {
|
|
return libremidi::message_type::SONG_POS_POINTER;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::SONG_SELECT)) {
|
|
return libremidi::message_type::SONG_SELECT;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::RESERVED1)) {
|
|
return libremidi::message_type::RESERVED1;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::RESERVED2)) {
|
|
return libremidi::message_type::RESERVED2;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::TUNE_REQUEST)) {
|
|
return libremidi::message_type::TUNE_REQUEST;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::EOX)) {
|
|
return libremidi::message_type::EOX;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::TIME_CLOCK)) {
|
|
return libremidi::message_type::TIME_CLOCK;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::RESERVED3)) {
|
|
return libremidi::message_type::RESERVED3;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::START)) {
|
|
return libremidi::message_type::START;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::CONTINUE)) {
|
|
return libremidi::message_type::CONTINUE;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::STOP)) {
|
|
return libremidi::message_type::STOP;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(libremidi::message_type::RESERVED4)) {
|
|
return libremidi::message_type::RESERVED4;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::ACTIVE_SENSING)) {
|
|
return libremidi::message_type::ACTIVE_SENSING;
|
|
}
|
|
if (text.toStdString() ==
|
|
MidiMessage::MidiTypeToString(
|
|
libremidi::message_type::SYSTEM_RESET)) {
|
|
return libremidi::message_type::SYSTEM_RESET;
|
|
}
|
|
return libremidi::message_type::INVALID;
|
|
}
|
|
|
|
void MidiMessageSelection::TypeChanged(const QString &text)
|
|
{
|
|
_currentSelection._typeIsOptional =
|
|
text ==
|
|
obs_module_text("AdvSceneSwitcher.midi.message.type.optional");
|
|
if (_currentSelection._typeIsOptional) {
|
|
emit MidiMessageChanged(_currentSelection);
|
|
return;
|
|
}
|
|
|
|
_currentSelection._type = TextToMidiType(text);
|
|
emit MidiMessageChanged(_currentSelection);
|
|
}
|
|
|
|
void MidiMessageSelection::ChannelChanged(const NumberVariable<int> &c)
|
|
{
|
|
_currentSelection._channel = c;
|
|
emit MidiMessageChanged(_currentSelection);
|
|
}
|
|
|
|
void MidiMessageSelection::NoteChanged(const NumberVariable<int> &n)
|
|
{
|
|
const QSignalBlocker b(_noteString);
|
|
_noteString->setCurrentIndex(n.GetFixedValue() + 1);
|
|
_currentSelection._note = n;
|
|
emit MidiMessageChanged(_currentSelection);
|
|
}
|
|
|
|
void MidiMessageSelection::NoteStringIdxChanged(int value)
|
|
{
|
|
const QSignalBlocker b(_noteValue);
|
|
_currentSelection._note = value;
|
|
_noteValue->SetFixedValue(value - 1);
|
|
emit MidiMessageChanged(_currentSelection);
|
|
}
|
|
|
|
void MidiMessageSelection::ShowNote(bool show)
|
|
{
|
|
_noteValue->setVisible(!show);
|
|
_noteString->setVisible(show);
|
|
}
|
|
|
|
void MidiMessageSelection::ValueChanged(const NumberVariable<int> &v)
|
|
{
|
|
_currentSelection._value = v;
|
|
emit MidiMessageChanged((_currentSelection));
|
|
}
|
|
|
|
QStringList GetAllNotes()
|
|
{
|
|
static bool done = false;
|
|
static QStringList result;
|
|
static const QStringList notes = {"C", "C#", "D", "D#", "E", "F",
|
|
"F#", "G", "G#", "A", "A#", "B"};
|
|
if (!done) {
|
|
for (int octave = -1; octave <= 9; octave++) {
|
|
for (int noteIndex = 0; noteIndex < 12; noteIndex++) {
|
|
result << notes[noteIndex] +
|
|
QString::number(octave);
|
|
}
|
|
}
|
|
done = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
} // namespace advss
|