diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 0c1164cc7..8c3873428 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -592,7 +592,15 @@ AdvSceneSwitcher.condition.run="Run" AdvSceneSwitcher.condition.run.entry="Process exits before timeout of{{timeout}}seconds" AdvSceneSwitcher.condition.run.entry.exit="{{checkExitCode}}Check for exit code{{exitCode}}" AdvSceneSwitcher.condition.midi="MIDI" +<<<<<<< HEAD AdvSceneSwitcher.condition.midi.entry="Message was received from{{device}}which matches:" +======= +AdvSceneSwitcher.condition.midi.condition.message="MIDI message was received" +AdvSceneSwitcher.condition.midi.condition.deviceName="MIDI device is connected" +AdvSceneSwitcher.condition.midi.condition.deviceName.info="On some platforms with certain APIs dynamic MIDI device detection might not function.\nSo this type of check might not be 100% reliable in all circumstances." +AdvSceneSwitcher.condition.midi.entry.message="{{conditions}}from{{device}}which matches:" +AdvSceneSwitcher.condition.midi.entry.deviceName="{{conditions}}{{deviceNames}}{{regex}}{{deviceListInfo}}" +>>>>>>> c4e35d1a (Add MIDI device name check) AdvSceneSwitcher.condition.midi.entry.listen="Set MIDI message selection to messages incoming on selected device:{{listenButton}}" AdvSceneSwitcher.condition.display="Display" AdvSceneSwitcher.condition.display.type.displayName="Name of connected displays matches" diff --git a/data/locale/fr-FR.ini b/data/locale/fr-FR.ini index ce30e3b5d..c04ac0b31 100644 --- a/data/locale/fr-FR.ini +++ b/data/locale/fr-FR.ini @@ -446,7 +446,6 @@ AdvSceneSwitcher.condition.run="Exécution" AdvSceneSwitcher.condition.run.entry="Le processus se termine avant le délai de{{timeout}}secondes" AdvSceneSwitcher.condition.run.entry.exit="{{checkExitCode}}Vérifier le code de sortie{{exitCode}} AdvSceneSwitcher.condition.midi="MIDI" -AdvSceneSwitcher.condition.midi.entry="Message reçu depuis{{device}}qui correspond à :" AdvSceneSwitcher.condition.midi.entry.listen="Définir la sélection des messages MIDI pour les messages entrants sur l'appareil sélectionné :{{listenButton}}" AdvSceneSwitcher.condition.display="Affichage" AdvSceneSwitcher.condition.display.type.displayName="Le nom des écrans connectés correspond à" diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini index 4fa4fa1e7..b44cc29d7 100644 --- a/data/locale/zh-CN.ini +++ b/data/locale/zh-CN.ini @@ -434,7 +434,6 @@ AdvSceneSwitcher.condition.run="运行" AdvSceneSwitcher.condition.run.entry="进程在超时 {{timeout}} 秒之前退出" AdvSceneSwitcher.condition.run.entry.exit="{{checkExitCode}}检查退出代码{{exitCode}}" AdvSceneSwitcher.condition.midi="MIDI" -AdvSceneSwitcher.condition.midi.entry="从{{device}} 收到的信息,符合:(从不咕咕的阿坤:这个好像是声卡工作是录音相关的功能,英文太抽象了,我就硬译过来了,如有相关设备,音乐好的,可以根据英文翻译好发给我!)" AdvSceneSwitcher.condition.midi.entry.listen="将MIDI信息选择设置为从选定设备上接收的信息: {{listenButton}}" AdvSceneSwitcher.condition.display="显示器" AdvSceneSwitcher.condition.display.type.displayName="连接的显示器的名称匹配" diff --git a/plugins/midi/macro-condition-midi.cpp b/plugins/midi/macro-condition-midi.cpp index 3815b105d..3ad17fba2 100644 --- a/plugins/midi/macro-condition-midi.cpp +++ b/plugins/midi/macro-condition-midi.cpp @@ -12,39 +12,35 @@ bool MacroConditionMidi::_registered = MacroConditionFactory::Register( {MacroConditionMidi::Create, MacroConditionMidiEdit::Create, "AdvSceneSwitcher.condition.midi"}); +static const std::map + conditionTypes = { + {MacroConditionMidi::Condition::MIDI_MESSAGE, + "AdvSceneSwitcher.condition.midi.condition.message"}, + {MacroConditionMidi::Condition::MIDI_CONNECTED_DEVICE_NAMES, + "AdvSceneSwitcher.condition.midi.condition.deviceName"}, +}; + bool MacroConditionMidi::CheckCondition() { - if (!_messageBuffer) { - return false; - } - - const bool macroWasPausedSinceLastCheck = - MacroWasPausedSince(GetMacro(), _lastCheck); - _lastCheck = std::chrono::high_resolution_clock::now(); - if (macroWasPausedSinceLastCheck) { - _messageBuffer->Clear(); - return false; + switch (_condition) { + case Condition::MIDI_MESSAGE: + return CheckMessage(); + case Condition::MIDI_CONNECTED_DEVICE_NAMES: + return CheckConnectedDevcieNames(); + default: + break; } - - while (!_messageBuffer->Empty()) { - auto message = _messageBuffer->ConsumeMessage(); - if (!message) { - continue; - } - if (message->Matches(_message)) { - SetVariableValues(*message); - return true; - } - } - return false; } bool MacroConditionMidi::Save(obs_data_t *obj) const { MacroCondition::Save(obj); + obs_data_set_int(obj, "condition", static_cast(_condition)); _message.Save(obj); _device.Save(obj); + _deviceName.Save(obj, "deviceName"); + _regex.Save(obj); return true; } @@ -53,13 +49,18 @@ bool MacroConditionMidi::Load(obs_data_t *obj) MacroCondition::Load(obj); _message.Load(obj); _device.Load(obj); - _messageBuffer = _device.RegisterForMidiMessages(); + _deviceName.Load(obj, "deviceName"); + _regex.Load(obj); + SetCondition( + static_cast(obs_data_get_int(obj, "condition"))); return true; } std::string MacroConditionMidi::GetShortDesc() const { - return _device.Name(); + return _condition == MacroConditionMidi::Condition::MIDI_MESSAGE + ? _device.Name() + : ""; } void MacroConditionMidi::SetDevice(const MidiDevice &dev) @@ -68,9 +69,64 @@ void MacroConditionMidi::SetDevice(const MidiDevice &dev) _messageBuffer = dev.RegisterForMidiMessages(); } +void MacroConditionMidi::SetCondition(Condition condition) +{ + _condition = condition; + if (_condition == Condition::MIDI_MESSAGE) { + _messageBuffer = _device.RegisterForMidiMessages(); + } + SetupTempVars(); +} + +bool MacroConditionMidi::CheckMessage() +{ + if (!_messageBuffer) { + return false; + } + + const bool macroWasPausedSinceLastCheck = + MacroWasPausedSince(GetMacro(), _lastCheck); + _lastCheck = std::chrono::high_resolution_clock::now(); + if (macroWasPausedSinceLastCheck) { + _messageBuffer->Clear(); + return false; + } + + while (!_messageBuffer->Empty()) { + auto message = _messageBuffer->ConsumeMessage(); + if (!message) { + continue; + } + if (message->Matches(_message)) { + SetVariableValues(*message); + return true; + } + } + + return false; +} + +bool MacroConditionMidi::CheckConnectedDevcieNames() +{ + auto deviceNames = GetDeviceNames(); + if (!_regex.Enabled()) { + return std::find(deviceNames.begin(), deviceNames.end(), + std::string(_deviceName)) != deviceNames.end(); + } + for (const auto &deviceName : deviceNames) { + if (_regex.Matches(deviceName, _deviceName)) { + return true; + } + } + return false; +} + void MacroConditionMidi::SetupTempVars() { MacroCondition::SetupTempVars(); + if (_condition == Condition::MIDI_CONNECTED_DEVICE_NAMES) { + return; + } AddTempvar("type", obs_module_text("AdvSceneSwitcher.tempVar.midi.type")); AddTempvar("channel", @@ -98,16 +154,45 @@ void MacroConditionMidi::SetVariableValues(const MidiMessage &m) SetTempVarValue("value2", std::to_string(m.Value())); } +static void populateConditionSelection(QComboBox *list) +{ + for (const auto &[_, name] : conditionTypes) { + list->addItem(obs_module_text(name.c_str())); + } +} + MacroConditionMidiEdit::MacroConditionMidiEdit( QWidget *parent, std::shared_ptr entryData) : QWidget(parent), + _conditions(new QComboBox()), _devices(new MidiDeviceSelection(this, MidiDeviceType::INPUT)), _message(new MidiMessageSelection(this)), _resetMidiDevices(new QPushButton( obs_module_text("AdvSceneSwitcher.midi.resetDevices"))), _listen(new QPushButton( - obs_module_text("AdvSceneSwitcher.midi.startListen"))) + obs_module_text("AdvSceneSwitcher.midi.startListen"))), + _deviceNames(new QComboBox()), + _regex(new RegexConfigWidget()), + _deviceListInfo(new QLabel()), + _listenLayout(new QHBoxLayout()), + _entryLayout(new QHBoxLayout()) { + _deviceNames->addItems(GetDeviceNamesAsQStringList()); + _deviceNames->setEditable(true); + + QString path = GetThemeTypeName() == "Light" + ? ":/res/images/help.svg" + : ":/res/images/help_light.svg"; + QIcon icon(path); + QPixmap pixmap = icon.pixmap(QSize(16, 16)); + _deviceListInfo->setPixmap(pixmap); + _deviceListInfo->setToolTip(obs_module_text( + "AdvSceneSwitcher.condition.midi.condition.deviceName.info")); + + populateConditionSelection(_conditions); + + QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ConditionChanged(int))); QWidget::connect(_devices, SIGNAL(DeviceSelectionChanged(const MidiDevice &)), this, @@ -121,19 +206,21 @@ MacroConditionMidiEdit::MacroConditionMidiEdit( SLOT(ToggleListen())); QWidget::connect(&_listenTimer, SIGNAL(timeout()), this, SLOT(SetMessageSelectionToLastReceived())); + QWidget::connect(_deviceNames, + SIGNAL(currentTextChanged(const QString &)), this, + SLOT(DeviceNameChanged(const QString &))); + QWidget::connect(_regex, + SIGNAL(RegexConfigChanged(const RegexConfig &)), this, + SLOT(RegexChanged(const RegexConfig &))); - auto entryLayout = new QHBoxLayout; - PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.midi.entry"), - entryLayout, {{"{{device}}", _devices}}); - auto listenLayout = new QHBoxLayout; PlaceWidgets( obs_module_text("AdvSceneSwitcher.condition.midi.entry.listen"), - listenLayout, {{"{{listenButton}}", _listen}}); + _listenLayout, {{"{{listenButton}}", _listen}}); auto mainLayout = new QVBoxLayout; - mainLayout->addLayout(entryLayout); + mainLayout->addLayout(_entryLayout); mainLayout->addWidget(_message); - mainLayout->addLayout(listenLayout); + mainLayout->addLayout(_listenLayout); mainLayout->addWidget(_resetMidiDevices); setLayout(mainLayout); @@ -155,11 +242,14 @@ void MacroConditionMidiEdit::UpdateEntryData() return; } + _conditions->setCurrentIndex( + static_cast(_entryData->GetCondition())); _message->SetMessage(_entryData->_message); _devices->SetDevice(_entryData->GetDevice()); - - adjustSize(); - updateGeometry(); + _deviceNames->setCurrentText(QString::fromStdString( + _entryData->_deviceName.UnresolvedValue())); + _regex->SetRegexConfig(_entryData->_regex); + SetWidgetVisibility(); } void MacroConditionMidiEdit::DeviceSelectionChanged(const MidiDevice &device) @@ -211,6 +301,46 @@ void MacroConditionMidiEdit::EnableListening(bool enable) } } +void MacroConditionMidiEdit::SetWidgetVisibility() +{ + _entryLayout->removeWidget(_conditions); + _entryLayout->removeWidget(_devices); + _entryLayout->removeWidget(_deviceNames); + _entryLayout->removeWidget(_regex); + + ClearLayout(_entryLayout); + + std::unordered_map widgetPlaceholders = { + {"{{conditions}}", _conditions}, + {"{{device}}", _devices}, + {"{{deviceNames}}", _deviceNames}, + {"{{regex}}", _regex}, + {"{{deviceListInfo}}", _deviceListInfo}, + }; + + const bool isMessageCheck = _entryData->GetCondition() == + MacroConditionMidi::Condition::MIDI_MESSAGE; + + auto layoutString = + isMessageCheck + ? "AdvSceneSwitcher.condition.midi.entry.message" + : "AdvSceneSwitcher.condition.midi.entry.deviceName"; + + PlaceWidgets(obs_module_text(layoutString), _entryLayout, + widgetPlaceholders); + + SetLayoutVisible(_listenLayout, isMessageCheck); + _devices->setVisible(isMessageCheck); + _message->setVisible(isMessageCheck); + _resetMidiDevices->setVisible(isMessageCheck); + _deviceNames->setVisible(!isMessageCheck); + _regex->setVisible(!isMessageCheck); + _deviceListInfo->setVisible(!isMessageCheck); + + adjustSize(); + updateGeometry(); +} + void MacroConditionMidiEdit::ToggleListen() { if (!_entryData) { @@ -249,4 +379,41 @@ void MacroConditionMidiEdit::SetMessageSelectionToLastReceived() _entryData->_message = *message; } +void MacroConditionMidiEdit::ConditionChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->SetCondition( + static_cast(value)); + SetWidgetVisibility(); + emit HeaderInfoChanged( + QString::fromStdString(_entryData->GetShortDesc())); +} + +void MacroConditionMidiEdit::DeviceNameChanged(const QString &deviceName) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_deviceName = deviceName.toStdString(); +} + +void MacroConditionMidiEdit::RegexChanged(const RegexConfig &conf) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_regex = conf; + + adjustSize(); + updateGeometry(); +} + } // namespace advss diff --git a/plugins/midi/macro-condition-midi.hpp b/plugins/midi/macro-condition-midi.hpp index 5237ad15d..3e37b5b36 100644 --- a/plugins/midi/macro-condition-midi.hpp +++ b/plugins/midi/macro-condition-midi.hpp @@ -1,6 +1,8 @@ #pragma once #include "macro-condition-edit.hpp" #include "midi-helpers.hpp" +#include "regex-config.hpp" +#include "variable-line-edit.hpp" #include #include @@ -23,11 +25,23 @@ class MacroConditionMidi : public MacroCondition { void SetDevice(const MidiDevice &dev); const MidiDevice &GetDevice() const { return _device; } MidiMessage _message; + StringVariable _deviceName; + RegexConfig _regex; + + enum class Condition { + MIDI_MESSAGE, + MIDI_CONNECTED_DEVICE_NAMES, + }; + void SetCondition(Condition); + Condition GetCondition() const { return _condition; } private: + bool CheckMessage(); + bool CheckConnectedDevcieNames(); void SetupTempVars(); void SetVariableValues(const MidiMessage &); + Condition _condition = Condition::MIDI_MESSAGE; MidiDevice _device; MidiMessageBuffer _messageBuffer; std::chrono::high_resolution_clock::time_point _lastCheck{}; @@ -58,18 +72,29 @@ private slots: void ResetMidiDevices(); void ToggleListen(); void SetMessageSelectionToLastReceived(); + void ConditionChanged(int); + void DeviceNameChanged(const QString &); + void RegexChanged(const RegexConfig &); signals: void HeaderInfoChanged(const QString &); private: void EnableListening(bool); + void SetWidgetVisibility(); std::shared_ptr _entryData; + QComboBox *_conditions; MidiDeviceSelection *_devices; MidiMessageSelection *_message; QPushButton *_resetMidiDevices; QPushButton *_listen; + QComboBox *_deviceNames; + RegexConfigWidget *_regex; + QLabel *_deviceListInfo; + QHBoxLayout *_listenLayout; + QHBoxLayout *_entryLayout; + QTimer _listenTimer; MidiMessageBuffer _messageBuffer; bool _currentlyListening = false; diff --git a/plugins/midi/midi-helpers.cpp b/plugins/midi/midi-helpers.cpp index 00392cb3b..8314f6ea2 100644 --- a/plugins/midi/midi-helpers.cpp +++ b/plugins/midi/midi-helpers.cpp @@ -22,7 +22,8 @@ static bool setupDeviceObservers() static std::vector observers; for (auto api : libremidi::available_apis()) { libremidi::observer_configuration cbs; - cbs.input_added = [=](const libremidi::input_port &p) { + cbs.track_virtual = true; + cbs.input_added = [](const libremidi::input_port &p) { auto dev = MidiDeviceInstance::GetDevice(p); if (!dev) { return; @@ -32,7 +33,7 @@ static bool setupDeviceObservers() dev->ClosePort(); dev->OpenPort(); }; - cbs.input_removed = [=](const libremidi::input_port &p) { + cbs.input_removed = [](const libremidi::input_port &p) { auto dev = MidiDeviceInstance::GetDevice(p); if (!dev) { return; @@ -40,7 +41,7 @@ static bool setupDeviceObservers() blog(LOG_INFO, "MIDI input removed: %s", p.port_name.c_str()); }; - cbs.output_added = [=](const libremidi::output_port &p) { + cbs.output_added = [](const libremidi::output_port &p) { auto dev = MidiDeviceInstance::GetDevice(p); if (!dev) { return; @@ -50,7 +51,7 @@ static bool setupDeviceObservers() dev->ClosePort(); dev->OpenPort(); }; - cbs.output_removed = [=](const libremidi::output_port &p) { + cbs.output_removed = [](const libremidi::output_port &p) { auto dev = MidiDeviceInstance::GetDevice(p); if (!dev) { return; @@ -328,35 +329,95 @@ advss::MidiDeviceInstance::GetDevice(const libremidi::output_port &p) static inline QStringList getInputDeviceNames() { - QStringList devices; + static QStringList devices; + static std::mutex m; + static std::vector observers; + static bool setupDone = false; + + if (setupDone) { + std::lock_guard lock(m); + return devices; + } + + // Set up observers try { - libremidi::observer obs; - for (const libremidi::input_port &port : - obs.get_input_ports()) { - devices << QString::fromStdString( - getNameFromPortInformation(port)); + for (auto api : libremidi::available_apis()) { + libremidi::observer_configuration cbs; + cbs.track_virtual = true; + cbs.input_added = [](const libremidi::input_port &p) { + std::lock_guard lock(m); + auto device = QString::fromStdString( + getNameFromPortInformation(p)); + if (devices.indexOf(device) == -1) { + devices << device; + } + }; + cbs.input_removed = [](const libremidi::input_port &p) { + std::lock_guard lock(m); + auto device = QString::fromStdString( + getNameFromPortInformation(p)); + if (int i = devices.indexOf(device) != -1) { + devices.removeAt(i); + } + }; + observers.emplace_back( + cbs, + libremidi::observer_configuration_for(api)); } } catch (const libremidi::driver_error &error) { blog(LOG_WARNING, "Failed to get midi input devices: %s", error.what()); } + + setupDone = true; return devices; } static inline QStringList getOutputDeviceNames() { - QStringList devices; + static QStringList devices; + static std::mutex m; + static std::vector observers; + static bool setupDone = false; + + if (setupDone) { + std::lock_guard lock(m); + return devices; + } + + // Set up observers try { - libremidi::observer obs; - for (const libremidi::output_port &port : - obs.get_output_ports()) { - devices << QString::fromStdString( - getNameFromPortInformation(port)); + for (auto api : libremidi::available_apis()) { + libremidi::observer_configuration cbs; + cbs.track_virtual = true; + cbs.output_added = [](const libremidi::output_port &p) { + std::lock_guard lock(m); + auto device = QString::fromStdString( + getNameFromPortInformation(p)); + if (devices.indexOf(device) == -1) { + devices << device; + } + }; + cbs.output_removed = + [](const libremidi::output_port &p) { + std::lock_guard lock(m); + auto device = QString::fromStdString( + getNameFromPortInformation(p)); + if (int i = devices.indexOf(device) != + -1) { + devices.removeAt(i); + } + }; + observers.emplace_back( + cbs, + libremidi::observer_configuration_for(api)); } } catch (const libremidi::driver_error &error) { blog(LOG_WARNING, "Failed to get midi output devices: %s", error.what()); } + + setupDone = true; return devices; } @@ -997,6 +1058,28 @@ void MidiMessageSelection::ValueChanged(const NumberVariable &v) emit MidiMessageChanged((_currentSelection)); } +std::vector GetDeviceNames() +{ + std::vector result; + const auto names = GetDeviceNamesAsQStringList(); + for (const auto &name : names) { + result.emplace_back(name.toStdString()); + } + return result; +} + +QStringList GetDeviceNamesAsQStringList() +{ + QStringList result; + for (const auto &input : getInputDeviceNames()) { + result << input; + } + for (const auto &output : getOutputDeviceNames()) { + result << output; + } + return result; +} + QStringList GetAllNotes() { static bool done = false; diff --git a/plugins/midi/midi-helpers.hpp b/plugins/midi/midi-helpers.hpp index c1f828f5a..95cfc88ea 100644 --- a/plugins/midi/midi-helpers.hpp +++ b/plugins/midi/midi-helpers.hpp @@ -171,6 +171,8 @@ private slots: const MidiDeviceType _type; }; +std::vector GetDeviceNames(); +QStringList GetDeviceNamesAsQStringList(); QStringList GetAllNotes(); } // namespace advss