Add MIDI device name check

This commit is contained in:
WarmUpTill 2024-03-11 20:27:26 +01:00
parent efaf9a2ef3
commit c7afdd817b
7 changed files with 337 additions and 54 deletions

View File

@ -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"

View File

@ -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 à"

View File

@ -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="连接的显示器的名称匹配"

View File

@ -12,7 +12,73 @@ bool MacroConditionMidi::_registered = MacroConditionFactory::Register(
{MacroConditionMidi::Create, MacroConditionMidiEdit::Create,
"AdvSceneSwitcher.condition.midi"});
static const std::map<MacroConditionMidi::Condition, std::string>
conditionTypes = {
{MacroConditionMidi::Condition::MIDI_MESSAGE,
"AdvSceneSwitcher.condition.midi.condition.message"},
{MacroConditionMidi::Condition::MIDI_CONNECTED_DEVICE_NAMES,
"AdvSceneSwitcher.condition.midi.condition.deviceName"},
};
bool MacroConditionMidi::CheckCondition()
{
switch (_condition) {
case Condition::MIDI_MESSAGE:
return CheckMessage();
case Condition::MIDI_CONNECTED_DEVICE_NAMES:
return CheckConnectedDevcieNames();
default:
break;
}
return false;
}
bool MacroConditionMidi::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
_message.Save(obj);
_device.Save(obj);
_deviceName.Save(obj, "deviceName");
_regex.Save(obj);
return true;
}
bool MacroConditionMidi::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_message.Load(obj);
_device.Load(obj);
_deviceName.Load(obj, "deviceName");
_regex.Load(obj);
SetCondition(
static_cast<Condition>(obs_data_get_int(obj, "condition")));
return true;
}
std::string MacroConditionMidi::GetShortDesc() const
{
return _condition == MacroConditionMidi::Condition::MIDI_MESSAGE
? _device.Name()
: "";
}
void MacroConditionMidi::SetDevice(const MidiDevice &dev)
{
_device = 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;
@ -40,37 +106,27 @@ bool MacroConditionMidi::CheckCondition()
return false;
}
bool MacroConditionMidi::Save(obs_data_t *obj) const
bool MacroConditionMidi::CheckConnectedDevcieNames()
{
MacroCondition::Save(obj);
_message.Save(obj);
_device.Save(obj);
return true;
}
bool MacroConditionMidi::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_message.Load(obj);
_device.Load(obj);
_messageBuffer = _device.RegisterForMidiMessages();
return true;
}
std::string MacroConditionMidi::GetShortDesc() const
{
return _device.Name();
}
void MacroConditionMidi::SetDevice(const MidiDevice &dev)
{
_device = dev;
_messageBuffer = dev.RegisterForMidiMessages();
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<MacroConditionMidi> 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<int>(_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<std::string, QWidget *> 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<MacroConditionMidi::Condition>(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

View File

@ -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 <QPushButton>
#include <QTimer>
@ -23,11 +25,23 @@ public:
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<MacroConditionMidi> _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;

View File

@ -22,7 +22,8 @@ static bool setupDeviceObservers()
static std::vector<libremidi::observer> 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<libremidi::observer> observers;
static bool setupDone = false;
if (setupDone) {
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<libremidi::observer> observers;
static bool setupDone = false;
if (setupDone) {
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<int> &v)
emit MidiMessageChanged((_currentSelection));
}
std::vector<std::string> GetDeviceNames()
{
std::vector<std::string> 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;

View File

@ -171,6 +171,8 @@ private:
const MidiDeviceType _type;
};
std::vector<std::string> GetDeviceNames();
QStringList GetDeviceNamesAsQStringList();
QStringList GetAllNotes();
} // namespace advss