#include "midi-helpers.hpp" #include #include #include #include #include #include #include #include #include #undef DispatchMessage namespace advss { std::map, MidiDeviceInstance *> MidiDeviceInstance::devices = {}; static bool setupDeviceObservers() { static std::vector 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(_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( 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(_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(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 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 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 &)), this, SLOT(ChannelChanged(const NumberVariable &))); connect(_noteValue, SIGNAL(NumberVariableChanged(const NumberVariable &)), this, SLOT(NoteChanged(const NumberVariable &))); connect(_noteString, SIGNAL(currentIndexChanged(int)), this, SLOT(NoteStringIdxChanged(int))); connect(_noteValueStringToggle, SIGNAL(toggled(bool)), this, SLOT(ShowNote(bool))); connect(_value, SIGNAL(NumberVariableChanged(const NumberVariable &)), this, SLOT(ValueChanged(const NumberVariable &))); 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 &c) { _currentSelection._channel = c; emit MidiMessageChanged(_currentSelection); } void MidiMessageSelection::NoteChanged(const NumberVariable &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 &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