SceneSwitcher/plugins/midi/midi-helpers.cpp
2024-08-14 21:51:42 +02:00

1042 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());
} catch (const std::exception &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