mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-21 17:34:57 -05:00
Add MIDI condition and action
This new action will allow you to send MIDI messages to the selected device. The condition type will allow you to perform actions based on the MIDI messages received from the selected device.
This commit is contained in:
parent
a992f7a0e8
commit
530bbc07c5
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -16,3 +16,6 @@
|
|||
[submodule "deps/tesseract"]
|
||||
path = deps/tesseract
|
||||
url = https://github.com/tesseract-ocr/tesseract.git
|
||||
[submodule "deps/libremidi"]
|
||||
path = deps/libremidi
|
||||
url = https://github.com/jcelerier/libremidi.git
|
||||
|
|
|
|||
|
|
@ -431,6 +431,9 @@ AdvSceneSwitcher.condition.variable.entry="{{variables}}{{conditions}}{{strValue
|
|||
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"
|
||||
AdvSceneSwitcher.condition.midi.entry="Mesasge was received from {{device}} which matches:"
|
||||
AdvSceneSwitcher.condition.midi.entry.listen="Set MIDI message selection to messages incoming on selected device: {{listenButton}}"
|
||||
|
||||
; Macro Actions
|
||||
AdvSceneSwitcher.action.switchScene="Switch scene"
|
||||
|
|
@ -650,7 +653,9 @@ AdvSceneSwitcher.action.projector.windowed="Windowed"
|
|||
AdvSceneSwitcher.action.projector.fullscreen="Fullscreen"
|
||||
AdvSceneSwitcher.action.projector.entry="Open{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.projector.entry.monitor="on{{monitors}}"
|
||||
|
||||
AdvSceneSwitcher.action.midi="MIDI"
|
||||
AdvSceneSwitcher.action.midi.entry="Send message to {{device}}:"
|
||||
AdvSceneSwitcher.action.midi.entry.listen="Set MIDI message selection to messages incoming on {{listenDevices}}: {{listenButton}}"
|
||||
|
||||
; Transition Tab
|
||||
AdvSceneSwitcher.transitionTab.title="Transition"
|
||||
|
|
@ -952,6 +957,44 @@ AdvSceneSwitcher.process.entry.workingDirectory="Working directory:{{workingDire
|
|||
|
||||
AdvSceneSwitcher.math.expressionFail="Failed evaluate expression"
|
||||
|
||||
AdvSceneSwitcher.midi.deviceNamePattern="[Port #%1] %2"
|
||||
AdvSceneSwitcher.midi.message.type="Type:"
|
||||
AdvSceneSwitcher.midi.message.type.optional="Any"
|
||||
AdvSceneSwitcher.midi.message.type.invalid="Invalid"
|
||||
AdvSceneSwitcher.midi.message.type.noteOff="Note Off"
|
||||
AdvSceneSwitcher.midi.message.type.noteOn="Note On"
|
||||
AdvSceneSwitcher.midi.message.type.polyphonicPressure="Polyphonic Pressure"
|
||||
AdvSceneSwitcher.midi.message.type.controlChange="Control Change"
|
||||
AdvSceneSwitcher.midi.message.type.programChange="Program Change"
|
||||
AdvSceneSwitcher.midi.message.type.channelAftertouch="Channel Aftertouch"
|
||||
AdvSceneSwitcher.midi.message.type.pitchBend="Pitch Bend"
|
||||
AdvSceneSwitcher.midi.message.type.systemExclusive="System Exclusive"
|
||||
AdvSceneSwitcher.midi.message.type.timeCode="Time Code"
|
||||
AdvSceneSwitcher.midi.message.type.songPositionPointer="Song Position Pointer"
|
||||
AdvSceneSwitcher.midi.message.type.songSelect="Song Select"
|
||||
AdvSceneSwitcher.midi.message.type.reserved1="Reserved (1)"
|
||||
AdvSceneSwitcher.midi.message.type.reserved2="Reserved (2)"
|
||||
AdvSceneSwitcher.midi.message.type.tuneRequest="Tune Request"
|
||||
AdvSceneSwitcher.midi.message.type.endOfSystemExclusive="End of System Exclusive"
|
||||
AdvSceneSwitcher.midi.message.type.timeClock="Time Clock"
|
||||
AdvSceneSwitcher.midi.message.type.reserved3="Reserved (3)"
|
||||
AdvSceneSwitcher.midi.message.type.startFile="Start File"
|
||||
AdvSceneSwitcher.midi.message.type.continueFile="Continue File"
|
||||
AdvSceneSwitcher.midi.message.type.stopFile="Stop File"
|
||||
AdvSceneSwitcher.midi.message.type.reserved4="Reserved (4)"
|
||||
AdvSceneSwitcher.midi.message.type.activeSensing="Active Sensing"
|
||||
AdvSceneSwitcher.midi.message.type.systemReset="System Reset"
|
||||
AdvSceneSwitcher.midi.message.type.unknown="Unknown"
|
||||
AdvSceneSwitcher.midi.message.channel="Channel:"
|
||||
AdvSceneSwitcher.midi.message.note="Note / Value(1):"
|
||||
AdvSceneSwitcher.midi.message.value="Value(2):"
|
||||
AdvSceneSwitcher.midi.message.placeholder="Any"
|
||||
AdvSceneSwitcher.midi.resetDevices="Reconnect all MIDI devices"
|
||||
AdvSceneSwitcher.midi.startListen="Start listening"
|
||||
AdvSceneSwitcher.midi.stopListen="Stop listening"
|
||||
AdvSceneSwitcher.midi.startListenFail="Device is busy!\nSomething else is already listening!"
|
||||
AdvSceneSwitcher.midi.deviceOpenFail="Failed to initialize MIDI device!"
|
||||
|
||||
AdvSceneSwitcher.selectScene="--select scene--"
|
||||
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
|
||||
AdvSceneSwitcher.selectCurrentScene="Current Scene"
|
||||
|
|
|
|||
24
data/res/images/DarkNote.svg
Normal file
24
data/res/images/DarkNote.svg
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
<svg width="640" height="640" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" version="1.1">
|
||||
<defs>
|
||||
<path d="m599.62,325.49c0,151.77 -123.21,274.98 -274.98,274.98c-151.76,0 -274.97,-123.21 -274.97,-274.98c0,-151.76 123.21,-274.97 274.97,-274.97c151.77,0 274.98,123.21 274.98,274.97z" id="c3Wyq0kfYS"/>
|
||||
<mask height="689.95" id="maskanTyyngos" maskUnits="userSpaceOnUse" width="689.95" x="-20.33" y="-19.48">
|
||||
<rect fill="white" height="689.95" id="svg_1" width="689.95" x="-20.33" y="-19.48"/>
|
||||
<use fill="black" id="svg_2" xlink:href="#c3Wyq0kfYS"/>
|
||||
</mask>
|
||||
<path d="m334.97,96.07c4.54,0 8.23,3.68 8.23,8.23c0,46.94 0,179.54 0,226.48c0,4.54 -3.69,8.22 -8.23,8.22c-5.77,0 -14.88,0 -20.66,0c-4.54,0 -8.22,-3.68 -8.22,-8.22c0,-46.94 0,-179.54 0,-226.48c0,-4.55 3.68,-8.23 8.22,-8.23c5.78,0 14.89,0 20.66,0z" id="fxMeu932c"/>
|
||||
<path d="m471.96,338.21c0.01,3.1 -2.5,5.62 -5.59,5.63c-32.02,0.09 -122.45,0.37 -154.46,0.47c-3.1,0.01 -5.61,-2.49 -5.62,-5.59c-0.02,-6.3 -0.06,-19.59 -0.08,-25.89c-0.01,-3.1 2.49,-5.62 5.59,-5.63c32.01,-0.09 122.44,-0.37 154.45,-0.47c3.1,-0.01 5.62,2.5 5.63,5.59c0.02,6.3 0.06,19.59 0.08,25.89z" id="h65nGmP2BL"/>
|
||||
</defs>
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<path fill="none" fill-opacity="null" id="svg_11" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02"/>
|
||||
<ellipse cx="103.84" cy="562.04" fill="#fefefe" id="svg_3" rx="80" ry="45" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" transform="rotate(-18.699, 103.843, 562.038)"/>
|
||||
<ellipse cx="506" cy="554" fill="#fefefe" id="svg_4" rx="77" ry="45" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" transform="rotate(-18.699, 506, 554)"/>
|
||||
<rect fill="#fefefe" height="359" id="svg_7" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" stroke-width="0" width="58" x="122" y="185"/>
|
||||
<rect fill="#fefefe" height="453" id="svg_8" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" width="58" x="522" y="84.5"/>
|
||||
<rect fill="#fefefe" height="37" id="svg_9" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" transform="rotate(-14.1985, 351.556, 146.245)" width="463.07" x="120.02" y="127.74"/>
|
||||
<rect fill="none" fill-opacity="null" height="0" id="svg_10" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" width="0" x="538.25" y="107.12"/>
|
||||
<path d="m537.42,112.06l0,-67.88l17.58,67.88z" fill="#fefefe" id="svg_16" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" transform="rotate(-90, 546.203, 78.1248)"/>
|
||||
<rect fill="#fefefe" height="0" id="svg_18" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" width="0" x="82.12" y="287.56"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
24
data/res/images/LightNote.svg
Normal file
24
data/res/images/LightNote.svg
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
<svg width="640" height="640" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" version="1.1">
|
||||
<defs>
|
||||
<path d="m599.62,325.49c0,151.77 -123.21,274.98 -274.98,274.98c-151.76,0 -274.97,-123.21 -274.97,-274.98c0,-151.76 123.21,-274.97 274.97,-274.97c151.77,0 274.98,123.21 274.98,274.97z" id="c3Wyq0kfYS"/>
|
||||
<mask height="689.95" id="maskanTyyngos" maskUnits="userSpaceOnUse" width="689.95" x="-20.33" y="-19.48">
|
||||
<rect fill="white" height="689.95" id="svg_1" width="689.95" x="-20.33" y="-19.48"/>
|
||||
<use fill="black" id="svg_2" xlink:href="#c3Wyq0kfYS"/>
|
||||
</mask>
|
||||
<path d="m334.97,96.07c4.54,0 8.23,3.68 8.23,8.23c0,46.94 0,179.54 0,226.48c0,4.54 -3.69,8.22 -8.23,8.22c-5.77,0 -14.88,0 -20.66,0c-4.54,0 -8.22,-3.68 -8.22,-8.22c0,-46.94 0,-179.54 0,-226.48c0,-4.55 3.68,-8.23 8.22,-8.23c5.78,0 14.89,0 20.66,0z" id="fxMeu932c"/>
|
||||
<path d="m471.96,338.21c0.01,3.1 -2.5,5.62 -5.59,5.63c-32.02,0.09 -122.45,0.37 -154.46,0.47c-3.1,0.01 -5.61,-2.49 -5.62,-5.59c-0.02,-6.3 -0.06,-19.59 -0.08,-25.89c-0.01,-3.1 2.49,-5.62 5.59,-5.63c32.01,-0.09 122.44,-0.37 154.45,-0.47c3.1,-0.01 5.62,2.5 5.63,5.59c0.02,6.3 0.06,19.59 0.08,25.89z" id="h65nGmP2BL"/>
|
||||
</defs>
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<path fill="none" fill-opacity="null" id="svg_11" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02"/>
|
||||
<ellipse cx="103.84" cy="562.04" fill="#202020" id="svg_3" rx="80" ry="45" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" transform="rotate(-18.699, 103.843, 562.038)"/>
|
||||
<ellipse cx="506" cy="554" fill="#202020" id="svg_4" rx="77" ry="45" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" transform="rotate(-18.699, 506, 554)"/>
|
||||
<rect fill="#202020" height="359" id="svg_7" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" stroke-width="0" width="58" x="122" y="185"/>
|
||||
<rect fill="#202020" height="453" id="svg_8" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" width="58" x="522" y="84.5"/>
|
||||
<rect fill="#202020" height="37" id="svg_9" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" transform="rotate(-14.1985, 351.556, 146.245)" width="463.07" x="120.02" y="127.74"/>
|
||||
<rect fill="none" fill-opacity="null" height="0" id="svg_10" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" width="0" x="538.25" y="107.12"/>
|
||||
<path d="m537.42,112.06l0,-67.88l17.58,67.88z" fill="#202020" id="svg_16" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" transform="rotate(-90, 546.203, 78.1248)"/>
|
||||
<rect fill="#202020" height="0" id="svg_18" stroke="#000000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="0.02" width="0" x="82.12" y="287.56"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
1
deps/libremidi
vendored
Submodule
1
deps/libremidi
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 56147984eea2bd4aeeb5fb51b677f058d29daef8
|
||||
|
|
@ -17,5 +17,6 @@ install_advss_plugin_dependency(...)
|
|||
... to install the plugin and its dependencies.
|
||||
#]]
|
||||
|
||||
add_subdirectory(midi)
|
||||
add_subdirectory(openvr)
|
||||
add_subdirectory(video)
|
||||
|
|
|
|||
25
src/macro-external/midi/CMakeLists.txt
Normal file
25
src/macro-external/midi/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
project(advanced-scene-switcher-midi)
|
||||
|
||||
# --- Check libremidi requirements ---
|
||||
|
||||
get_target_property(ADVSS_SOURCE_DIR advanced-scene-switcher-lib SOURCE_DIR)
|
||||
add_subdirectory("${ADVSS_SOURCE_DIR}/deps/libremidi"
|
||||
"${ADVSS_SOURCE_DIR}/deps/libremidi/build")
|
||||
|
||||
# --- End of section ---
|
||||
|
||||
add_library(${PROJECT_NAME} MODULE)
|
||||
|
||||
target_sources(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE macro-condition-midi.cpp macro-condition-midi.hpp
|
||||
macro-action-midi.cpp macro-action-midi.hpp midi-helpers.cpp
|
||||
midi-helpers.hpp)
|
||||
|
||||
setup_advss_plugin(${PROJECT_NAME})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE "${ADVSS_SOURCE_DIR}/deps/libremidi/include")
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE libremidi)
|
||||
install_advss_plugin(${PROJECT_NAME})
|
||||
206
src/macro-external/midi/macro-action-midi.cpp
Normal file
206
src/macro-external/midi/macro-action-midi.cpp
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
#include "macro-action-midi.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionMidi::id = "midi";
|
||||
|
||||
bool MacroActionMidi::_registered = MacroActionFactory::Register(
|
||||
MacroActionMidi::id,
|
||||
{MacroActionMidi::Create, MacroActionMidiEdit::Create,
|
||||
"AdvSceneSwitcher.action.midi"});
|
||||
|
||||
bool MacroActionMidi::PerformAction()
|
||||
{
|
||||
if (!_device.SendMessge(_message)) {
|
||||
blog(LOG_WARNING,
|
||||
"failed to send midi message \"%s\" to \"%s\"",
|
||||
_message.ToString().c_str(), _device.Name().c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionMidi::LogAction() const
|
||||
{
|
||||
vblog(LOG_INFO, "send midi message \"%s\" to \"%s\"",
|
||||
_message.ToString().c_str(), _device.Name().c_str());
|
||||
}
|
||||
|
||||
bool MacroActionMidi::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_message.Save(obj);
|
||||
_device.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionMidi::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_message.Load(obj);
|
||||
_device.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionMidi::GetShortDesc() const
|
||||
{
|
||||
return _device.Name();
|
||||
}
|
||||
|
||||
MacroActionMidiEdit::MacroActionMidiEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionMidi> entryData)
|
||||
: QWidget(parent),
|
||||
_devices(new MidiDeviceSelection(this, MidiDeviceType::OUTPUT)),
|
||||
_message(new MidiMessageSelection(this)),
|
||||
_resetMidiDevices(new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.midi.resetDevices"))),
|
||||
_listenDevices(new MidiDeviceSelection(this, MidiDeviceType::INPUT)),
|
||||
_listen(new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.midi.startListen")))
|
||||
{
|
||||
QWidget::connect(_devices,
|
||||
SIGNAL(DeviceSelectionChanged(const MidiDevice &)),
|
||||
this,
|
||||
SLOT(DeviceSelectionChanged(const MidiDevice &)));
|
||||
QWidget::connect(_message,
|
||||
SIGNAL(MidiMessageChanged(const MidiMessage &)), this,
|
||||
SLOT(MidiMessageChanged(const MidiMessage &)));
|
||||
QWidget::connect(_resetMidiDevices, SIGNAL(clicked()), this,
|
||||
SLOT(ResetMidiDevices()));
|
||||
QWidget::connect(_listen, SIGNAL(clicked()), this,
|
||||
SLOT(ToggleListen()));
|
||||
QWidget::connect(
|
||||
_listenDevices,
|
||||
SIGNAL(DeviceSelectionChanged(const MidiDevice &)), this,
|
||||
SLOT(ListenDeviceSelectionChanged(const MidiDevice &)));
|
||||
QWidget::connect(&_listenTimer, SIGNAL(timeout()), this,
|
||||
SLOT(SetMessageSelectionToLastReceived()));
|
||||
|
||||
auto entryLayout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.midi.entry"),
|
||||
entryLayout, {{"{{device}}", _devices}});
|
||||
auto listenLayout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.midi.entry.listen"),
|
||||
listenLayout,
|
||||
{{"{{listenButton}}", _listen},
|
||||
{"{{listenDevices}}", _listenDevices}});
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addWidget(_message);
|
||||
mainLayout->addLayout(listenLayout);
|
||||
mainLayout->addWidget(_resetMidiDevices);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_listenTimer.setInterval(100);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
MacroActionMidiEdit::~MacroActionMidiEdit()
|
||||
{
|
||||
EnableListening(false);
|
||||
}
|
||||
|
||||
void MacroActionMidiEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_message->SetMessage(_entryData->_message);
|
||||
_devices->SetDevice(_entryData->_device);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionMidiEdit::DeviceSelectionChanged(const MidiDevice &device)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_entryData->_device = device;
|
||||
}
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionMidiEdit::ListenDeviceSelectionChanged(const MidiDevice &dev)
|
||||
{
|
||||
if (_currentlyListening) {
|
||||
ToggleListen();
|
||||
}
|
||||
_listenDevice = dev;
|
||||
}
|
||||
|
||||
void MacroActionMidiEdit::MidiMessageChanged(const MidiMessage &message)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_message = message;
|
||||
}
|
||||
|
||||
void MacroActionMidiEdit::EnableListening(bool enable)
|
||||
{
|
||||
if (_currentlyListening == enable) {
|
||||
return;
|
||||
}
|
||||
_listenDevice.UseForMessageSelection(enable);
|
||||
if (enable) {
|
||||
_listenTimer.start();
|
||||
} else {
|
||||
_listenTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionMidiEdit::ResetMidiDevices()
|
||||
{
|
||||
auto lock = LockContext();
|
||||
MidiDeviceInstance::ResetAllDevices();
|
||||
}
|
||||
|
||||
void MacroActionMidiEdit::ToggleListen()
|
||||
{
|
||||
if (!_entryData || !_listenDevice.DeviceSelected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_currentlyListening && _listenDevice.IsUsedForMessageSelection()) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.midi.startListenFail"));
|
||||
return;
|
||||
}
|
||||
|
||||
_listen->setText(
|
||||
_currentlyListening
|
||||
? obs_module_text("AdvSceneSwitcher.midi.startListen")
|
||||
: obs_module_text("AdvSceneSwitcher.midi.stopListen"));
|
||||
EnableListening(!_currentlyListening);
|
||||
_currentlyListening = !_currentlyListening;
|
||||
_message->setDisabled(_currentlyListening);
|
||||
}
|
||||
|
||||
void MacroActionMidiEdit::SetMessageSelectionToLastReceived()
|
||||
{
|
||||
auto lock = LockContext();
|
||||
auto messages = _listenDevice.GetMessages(true);
|
||||
if (!_entryData || !messages || messages->empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_message->SetMessage(messages->back());
|
||||
_entryData->_message = messages->back();
|
||||
_listenDevice.ClearMessageBuffer();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
75
src/macro-external/midi/macro-action-midi.hpp
Normal file
75
src/macro-external/midi/macro-action-midi.hpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "midi-helpers.hpp"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionMidi : public MacroAction {
|
||||
public:
|
||||
MacroActionMidi(Macro *m) : MacroAction(m, true) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetShortDesc() const;
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionMidi>(m);
|
||||
}
|
||||
|
||||
MidiDevice _device;
|
||||
MidiMessage _message;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionMidiEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionMidiEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionMidi> entryData = nullptr);
|
||||
virtual ~MacroActionMidiEdit();
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionMidiEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionMidi>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void DeviceSelectionChanged(const MidiDevice &);
|
||||
void ListenDeviceSelectionChanged(const MidiDevice &);
|
||||
void MidiMessageChanged(const MidiMessage &);
|
||||
void ResetMidiDevices();
|
||||
void ToggleListen();
|
||||
void SetMessageSelectionToLastReceived();
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
private:
|
||||
void EnableListening(bool);
|
||||
|
||||
std::shared_ptr<MacroActionMidi> _entryData;
|
||||
|
||||
MidiDeviceSelection *_devices;
|
||||
MidiMessageSelection *_message;
|
||||
MidiDeviceSelection *_listenDevices;
|
||||
QPushButton *_resetMidiDevices;
|
||||
QPushButton *_listen;
|
||||
MidiDevice _listenDevice;
|
||||
QTimer _listenTimer;
|
||||
bool _currentlyListening = false;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
196
src/macro-external/midi/macro-condition-midi.cpp
Normal file
196
src/macro-external/midi/macro-condition-midi.cpp
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
#include "macro-condition-midi.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionMidi::id = "midi";
|
||||
|
||||
bool MacroConditionMidi::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionMidi::id,
|
||||
{MacroConditionMidi::Create, MacroConditionMidiEdit::Create,
|
||||
"AdvSceneSwitcher.condition.midi"});
|
||||
|
||||
bool MacroConditionMidi::CheckCondition()
|
||||
{
|
||||
auto messages = _device.GetMessages();
|
||||
if (!messages) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &m : *messages) {
|
||||
if (m.Matches(_message)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionMidi::Save(obs_data_t *obj) const
|
||||
{
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionMidi::GetShortDesc() const
|
||||
{
|
||||
return _device.Name();
|
||||
}
|
||||
|
||||
MacroConditionMidiEdit::MacroConditionMidiEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionMidi> entryData)
|
||||
: QWidget(parent),
|
||||
_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")))
|
||||
{
|
||||
QWidget::connect(_devices,
|
||||
SIGNAL(DeviceSelectionChanged(const MidiDevice &)),
|
||||
this,
|
||||
SLOT(DeviceSelectionChanged(const MidiDevice &)));
|
||||
QWidget::connect(_message,
|
||||
SIGNAL(MidiMessageChanged(const MidiMessage &)), this,
|
||||
SLOT(MidiMessageChanged(const MidiMessage &)));
|
||||
QWidget::connect(_resetMidiDevices, SIGNAL(clicked()), this,
|
||||
SLOT(ResetMidiDevices()));
|
||||
QWidget::connect(_listen, SIGNAL(clicked()), this,
|
||||
SLOT(ToggleListen()));
|
||||
QWidget::connect(&_listenTimer, SIGNAL(timeout()), this,
|
||||
SLOT(SetMessageSelectionToLastReceived()));
|
||||
|
||||
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}});
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addWidget(_message);
|
||||
mainLayout->addLayout(listenLayout);
|
||||
mainLayout->addWidget(_resetMidiDevices);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_listenTimer.setInterval(100);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
MacroConditionMidiEdit::~MacroConditionMidiEdit()
|
||||
{
|
||||
EnableListening(false);
|
||||
}
|
||||
|
||||
void MacroConditionMidiEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_message->SetMessage(_entryData->_message);
|
||||
_devices->SetDevice(_entryData->_device);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionMidiEdit::DeviceSelectionChanged(const MidiDevice &device)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentlyListening) {
|
||||
ToggleListen();
|
||||
}
|
||||
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_entryData->_device = device;
|
||||
}
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionMidiEdit::MidiMessageChanged(const MidiMessage &message)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_message = message;
|
||||
}
|
||||
|
||||
void MacroConditionMidiEdit::ResetMidiDevices()
|
||||
{
|
||||
auto lock = LockContext();
|
||||
MidiDeviceInstance::ResetAllDevices();
|
||||
}
|
||||
|
||||
void MacroConditionMidiEdit::EnableListening(bool enable)
|
||||
{
|
||||
if (_currentlyListening == enable) {
|
||||
return;
|
||||
}
|
||||
_entryData->_device.UseForMessageSelection(enable);
|
||||
if (enable) {
|
||||
_listenTimer.start();
|
||||
} else {
|
||||
_listenTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionMidiEdit::ToggleListen()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_currentlyListening &&
|
||||
_entryData->_device.IsUsedForMessageSelection()) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.midi.startListenFail"));
|
||||
return;
|
||||
}
|
||||
|
||||
_listen->setText(
|
||||
_currentlyListening
|
||||
? obs_module_text("AdvSceneSwitcher.midi.startListen")
|
||||
: obs_module_text("AdvSceneSwitcher.midi.stopListen"));
|
||||
EnableListening(!_currentlyListening);
|
||||
_currentlyListening = !_currentlyListening;
|
||||
_message->setDisabled(_currentlyListening);
|
||||
}
|
||||
|
||||
void MacroConditionMidiEdit::SetMessageSelectionToLastReceived()
|
||||
{
|
||||
auto lock = LockContext();
|
||||
auto messages = _entryData->_device.GetMessages(true);
|
||||
if (!_entryData || !messages || messages->empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_message->SetMessage(messages->back());
|
||||
_entryData->_message = messages->back();
|
||||
_entryData->_device.ClearMessageBuffer();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
71
src/macro-external/midi/macro-condition-midi.hpp
Normal file
71
src/macro-external/midi/macro-condition-midi.hpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "midi-helpers.hpp"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionMidi : public MacroCondition {
|
||||
public:
|
||||
MacroConditionMidi(Macro *m) : MacroCondition(m, true) {}
|
||||
bool CheckCondition();
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetShortDesc() const;
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroCondition> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroConditionMidi>(m);
|
||||
}
|
||||
|
||||
MidiDevice _device;
|
||||
MidiMessage _message;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionMidiEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionMidiEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionMidi> cond = nullptr);
|
||||
virtual ~MacroConditionMidiEdit();
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionMidiEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionMidi>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void DeviceSelectionChanged(const MidiDevice &);
|
||||
void MidiMessageChanged(const MidiMessage &);
|
||||
void ResetMidiDevices();
|
||||
void ToggleListen();
|
||||
void SetMessageSelectionToLastReceived();
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
private:
|
||||
void EnableListening(bool);
|
||||
|
||||
std::shared_ptr<MacroConditionMidi> _entryData;
|
||||
|
||||
MidiDeviceSelection *_devices;
|
||||
MidiMessageSelection *_message;
|
||||
QPushButton *_resetMidiDevices;
|
||||
QPushButton *_listen;
|
||||
QTimer _listenTimer;
|
||||
bool _currentlyListening = false;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
930
src/macro-external/midi/midi-helpers.cpp
Normal file
930
src/macro-external/midi/midi-helpers.cpp
Normal file
|
|
@ -0,0 +1,930 @@
|
|||
#include "midi-helpers.hpp"
|
||||
|
||||
#include <utility.hpp>
|
||||
#include <obs-module-helper.hpp>
|
||||
#include <switcher-data.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
static std::map<std::pair<MidiDeviceType, int>, MidiDeviceInstance *>
|
||||
SetupMidiMessageVector()
|
||||
{
|
||||
GetSwitcher()->AddResetForNextIntervalFunction(
|
||||
MidiDeviceInstance::ClearMessageBuffersOfAllDevices);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::map<std::pair<MidiDeviceType, int>, MidiDeviceInstance *>
|
||||
MidiDeviceInstance::devices = SetupMidiMessageVector();
|
||||
|
||||
void MidiDeviceInstance::ClearMessageBuffersOfAllDevices()
|
||||
{
|
||||
for (auto const &[_, device] : MidiDeviceInstance::devices) {
|
||||
if (device->_skipBufferClear) {
|
||||
continue;
|
||||
}
|
||||
device->ClearMessageBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDeviceInstance::ResetAllDevices()
|
||||
{
|
||||
for (auto const &[_, device] : MidiDeviceInstance::devices) {
|
||||
if (device->_skipBufferClear) {
|
||||
continue;
|
||||
}
|
||||
device->ClosePort();
|
||||
device->ClearMessageBuffer();
|
||||
device->OpenPort();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDeviceInstance::ClearMessageBuffer()
|
||||
{
|
||||
_messages.clear();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
auto 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);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void MidiMessage::Load(obs_data_t *obj)
|
||||
{
|
||||
auto 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");
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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::GetDevice(MidiDeviceType type, int port)
|
||||
{
|
||||
if (port < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto key = std::make_pair(type, port);
|
||||
auto it = devices.find(key);
|
||||
if (it != devices.end()) {
|
||||
it->second->OpenPort();
|
||||
return it->second;
|
||||
}
|
||||
|
||||
try {
|
||||
auto device = new MidiDeviceInstance();
|
||||
device->_type = type;
|
||||
device->_port = port;
|
||||
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 #%d: %s",
|
||||
type == MidiDeviceType::INPUT ? "input" : "output", port,
|
||||
error.what());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MidiDevice::Save(obs_data_t *obj) const
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_int(data, "type", static_cast<int>(_type));
|
||||
obs_data_set_int(data, "port", _dev ? _dev->_port : -1);
|
||||
obs_data_set_obj(obj, "midiDevice", data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void MidiDevice::Load(obs_data_t *obj)
|
||||
{
|
||||
auto 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);
|
||||
_port = obs_data_get_int(data, "port");
|
||||
_dev = MidiDeviceInstance::GetDevice(_type, _port);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
bool MidiDevice::SendMessge(const MidiMessage &m)
|
||||
{
|
||||
if (_type == MidiDeviceType::INPUT || _port == -1 || !_dev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _dev->SendMessge(m);
|
||||
}
|
||||
|
||||
bool MidiDeviceInstance::OpenPort()
|
||||
{
|
||||
if ((_type == MidiDeviceType::INPUT && in.is_port_open()) ||
|
||||
(_type == MidiDeviceType::OUTPUT && out.is_port_open())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_type == MidiDeviceType::OUTPUT) {
|
||||
try {
|
||||
out.open_port(_port);
|
||||
blog(LOG_INFO, "Opened output midi port #%d", _port);
|
||||
return true;
|
||||
} catch (const libremidi::driver_error &error) {
|
||||
blog(LOG_WARNING,
|
||||
"Failed to open output midi port #%d: %s", _port,
|
||||
error.what());
|
||||
} catch (const libremidi::system_error &error) {
|
||||
blog(LOG_WARNING,
|
||||
"Failed to open output midi port #%d: %s", _port,
|
||||
error.what());
|
||||
} catch (const libremidi::midi_exception &error) {
|
||||
blog(LOG_WARNING,
|
||||
"Failed to open output midi port #%d: %s", _port,
|
||||
error.what());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cb = [this](const libremidi::message &m) {
|
||||
this->ReceiveMidiMessage(m);
|
||||
};
|
||||
|
||||
in.set_callback(cb);
|
||||
try {
|
||||
in.open_port(_port);
|
||||
blog(LOG_INFO, "Opened input midi port #%d", _port);
|
||||
return true;
|
||||
} catch (const libremidi::driver_error &error) {
|
||||
blog(LOG_WARNING, "Failed to open input midi port #%d: %s",
|
||||
_port, error.what());
|
||||
} catch (const libremidi::system_error &error) {
|
||||
blog(LOG_WARNING, "Failed to open input midi port #%d: %s",
|
||||
_port, error.what());
|
||||
} catch (const libremidi::midi_exception &error) {
|
||||
blog(LOG_WARNING, "Failed to open input midi port #%d: %s",
|
||||
_port, error.what());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MidiDeviceInstance::ClosePort()
|
||||
{
|
||||
if ((_type == MidiDeviceType::INPUT && !in.is_port_open()) ||
|
||||
(_type == MidiDeviceType::OUTPUT && !out.is_port_open())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_type == MidiDeviceType::OUTPUT) {
|
||||
try {
|
||||
out.close_port();
|
||||
blog(LOG_INFO, "Closed output midi port #%d", _port);
|
||||
} catch (const libremidi::driver_error &error) {
|
||||
blog(LOG_WARNING,
|
||||
"Failed to close output midi port #%d: %s", _port,
|
||||
error.what());
|
||||
} catch (const libremidi::system_error &error) {
|
||||
blog(LOG_WARNING,
|
||||
"Failed to close output midi port #%d: %s", _port,
|
||||
error.what());
|
||||
} catch (const libremidi::midi_exception &error) {
|
||||
blog(LOG_WARNING,
|
||||
"Failed to close output midi port #%d: %s", _port,
|
||||
error.what());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto cb = [this](const libremidi::message &m) {
|
||||
this->ReceiveMidiMessage(m);
|
||||
};
|
||||
|
||||
in.set_callback(cb);
|
||||
try {
|
||||
in.close_port();
|
||||
blog(LOG_INFO, "Closed input midi port #%d", _port);
|
||||
} catch (const libremidi::driver_error &error) {
|
||||
blog(LOG_WARNING, "Failed to close input midi port #%d: %s",
|
||||
_port, error.what());
|
||||
} catch (const libremidi::system_error &error) {
|
||||
blog(LOG_WARNING, "Failed to close input midi port #%d: %s",
|
||||
_port, error.what());
|
||||
} catch (const libremidi::midi_exception &error) {
|
||||
blog(LOG_WARNING, "Failed to close input midi port #%d: %s",
|
||||
_port, 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::message::note_off(channel, note, value);
|
||||
break;
|
||||
case libremidi::message_type::NOTE_ON:
|
||||
message = libremidi::message::note_on(channel, note, value);
|
||||
break;
|
||||
case libremidi::message_type::CONTROL_CHANGE:
|
||||
message = libremidi::message::control_change(channel, note,
|
||||
value);
|
||||
break;
|
||||
case libremidi::message_type::PROGRAM_CHANGE:
|
||||
message = libremidi::message::program_change(channel, value);
|
||||
break;
|
||||
case libremidi::message_type::PITCH_BEND:
|
||||
message = libremidi::message::pitch_bend(
|
||||
channel, (value <= 64 ? 0 : value - 64) * 2, value);
|
||||
break;
|
||||
case libremidi::message_type::POLY_PRESSURE:
|
||||
message =
|
||||
libremidi::message::poly_pressure(channel, note, value);
|
||||
break;
|
||||
case libremidi::message_type::AFTERTOUCH:
|
||||
message = libremidi::message::aftertouch(channel, value);
|
||||
break;
|
||||
default:
|
||||
message = {libremidi::message::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;
|
||||
}
|
||||
|
||||
const std::vector<MidiMessage> &MidiDeviceInstance::GetMessages()
|
||||
{
|
||||
return _messages;
|
||||
}
|
||||
|
||||
void MidiDeviceInstance::ReceiveMidiMessage(const libremidi::message &msg)
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_messages.emplace_back(msg);
|
||||
vblog(LOG_INFO, "received midi: %s",
|
||||
MidiMessage::ToString(msg).c_str());
|
||||
}
|
||||
|
||||
void MidiDevice::UseForMessageSelection(bool skipBufferClear)
|
||||
{
|
||||
if (!_dev) {
|
||||
return;
|
||||
}
|
||||
|
||||
blog(LOG_INFO, "%s \"listen\" mode for midi input device \"%s\"! %s",
|
||||
skipBufferClear ? "Enable" : "Disable", Name().c_str(),
|
||||
skipBufferClear
|
||||
? "This will block incoming messages from being processed!"
|
||||
: "");
|
||||
ClearMessageBuffer();
|
||||
_dev->_skipBufferClear = skipBufferClear;
|
||||
}
|
||||
|
||||
bool MidiDevice::IsUsedForMessageSelection()
|
||||
{
|
||||
return _dev && _dev->_skipBufferClear;
|
||||
}
|
||||
|
||||
void MidiDevice::ClearMessageBuffer()
|
||||
{
|
||||
if (_dev) {
|
||||
_dev->ClearMessageBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<MidiMessage> *MidiDevice::GetMessages(bool ignoreSkip)
|
||||
{
|
||||
if (_type == MidiDeviceType::OUTPUT || _port == -1 || !_dev ||
|
||||
(_dev->_skipBufferClear && !ignoreSkip)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &_dev->GetMessages();
|
||||
}
|
||||
|
||||
static QString portToName(bool input, int port)
|
||||
{
|
||||
std::string name;
|
||||
try {
|
||||
if (input) {
|
||||
auto midiin = libremidi::midi_in();
|
||||
name = midiin.get_port_name(port);
|
||||
} else {
|
||||
auto midiout = libremidi::midi_out();
|
||||
name = midiout.get_port_name(port);
|
||||
}
|
||||
} catch (const libremidi::driver_error &error) {
|
||||
blog(LOG_WARNING,
|
||||
"Failed to get midi %s device name of port #%d: %s",
|
||||
input ? "input" : "output", port, error.what());
|
||||
}
|
||||
|
||||
const QString deviceNamePattern =
|
||||
obs_module_text("AdvSceneSwitcher.midi.deviceNamePattern");
|
||||
return deviceNamePattern.arg(QString::number(port),
|
||||
QString::fromStdString(name));
|
||||
}
|
||||
|
||||
std::string MidiDevice::GetInputName() const
|
||||
{
|
||||
return portToName(true, _port).toStdString();
|
||||
}
|
||||
|
||||
std::string MidiDevice::GetOutputName() const
|
||||
{
|
||||
return portToName(false, _port).toStdString();
|
||||
}
|
||||
|
||||
std::string MidiDevice::Name() const
|
||||
{
|
||||
if (_port < 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (_type == MidiDeviceType::INPUT) {
|
||||
return GetInputName();
|
||||
}
|
||||
return GetOutputName();
|
||||
}
|
||||
|
||||
static inline QStringList getInputDeviceNames()
|
||||
{
|
||||
QStringList devices;
|
||||
try {
|
||||
auto midiin = libremidi::midi_in();
|
||||
auto nPorts = midiin.get_port_count();
|
||||
for (unsigned i = 0; i < nPorts; i++) {
|
||||
devices << portToName(true, i);
|
||||
}
|
||||
} 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 {
|
||||
auto midiout = libremidi::midi_out();
|
||||
auto nPorts = midiout.get_port_count();
|
||||
for (unsigned i = 0; i < nPorts; i++) {
|
||||
devices << portToName(false, i);
|
||||
}
|
||||
} catch (const libremidi::driver_error &error) {
|
||||
blog(LOG_WARNING, "Failed to get midi output devices: %s",
|
||||
error.what());
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
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 devInstance = MidiDeviceInstance::GetDevice(_type, idx - 1);
|
||||
if (!devInstance) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.midi.deviceOpenFail"));
|
||||
const QSignalBlocker b(this);
|
||||
setCurrentIndex(0);
|
||||
idx = 0;
|
||||
}
|
||||
|
||||
MidiDevice dev;
|
||||
dev._type = _type;
|
||||
dev._port = idx - 1;
|
||||
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()
|
||||
{
|
||||
QStringList result;
|
||||
QStringList notes = {"C", "C#", "D", "D#", "E", "F",
|
||||
"F#", "G", "G#", "A", "A#", "B"};
|
||||
for (int octave = -1; octave <= 9; octave++) {
|
||||
for (int noteIndex = 0; noteIndex < 12; noteIndex++) {
|
||||
int midiNote = octave * 12 + noteIndex;
|
||||
result << notes[noteIndex] + QString::number(octave);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
172
src/macro-external/midi/midi-helpers.hpp
Normal file
172
src/macro-external/midi/midi-helpers.hpp
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#pragma once
|
||||
#include <variable-spinbox.hpp>
|
||||
#include <variable-number.hpp>
|
||||
#include <variable-string.hpp>
|
||||
#include <QComboBox>
|
||||
#include <obs-data.h>
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4005)
|
||||
#define LIBREMIDI_HEADER_ONLY 1
|
||||
#include <libremidi/libremidi.hpp>
|
||||
#pragma warning(pop)
|
||||
|
||||
namespace advss {
|
||||
|
||||
// Based on https://github.com/nhielost/obs-midi-mg MMGMessage
|
||||
class MidiMessage {
|
||||
public:
|
||||
MidiMessage() = default;
|
||||
MidiMessage(const libremidi::message &message);
|
||||
|
||||
void Save(obs_data_t *obj) const;
|
||||
void Load(obs_data_t *obj);
|
||||
|
||||
bool Matches(const MidiMessage &) const;
|
||||
|
||||
static std::string ToString(const libremidi::message &msg);
|
||||
static std::string MidiTypeToString(libremidi::message_type type);
|
||||
static std::string GetMidiType(const libremidi::message &msg);
|
||||
static int GetMidiNote(const libremidi::message &msg);
|
||||
static int GetMidiValue(const libremidi::message &msg);
|
||||
|
||||
std::string ToString() const;
|
||||
libremidi::message_type Type() const { return _type; }
|
||||
int Channel() const { return _channel; }
|
||||
int Note() const { return _note; }
|
||||
int Value() const { return _value; }
|
||||
|
||||
private:
|
||||
// Values which don't appear for channel, note, and value will be used
|
||||
// to indicate whether this part of the message is optional and can be
|
||||
// disregarded (e.g.during comparison using Matches())
|
||||
static const int optionalChannelIndicator = 0;
|
||||
static const int optionalNoteIndicator = -1;
|
||||
static const int optionalValueIndicator = -1;
|
||||
|
||||
bool _typeIsOptional = true;
|
||||
libremidi::message_type _type = libremidi::message_type::INVALID;
|
||||
NumberVariable<int> _channel = optionalChannelIndicator;
|
||||
NumberVariable<int> _note = optionalNoteIndicator;
|
||||
NumberVariable<int> _value = optionalValueIndicator;
|
||||
|
||||
friend class MidiMessageSelection;
|
||||
};
|
||||
|
||||
enum class MidiDeviceType {
|
||||
INPUT,
|
||||
OUTPUT,
|
||||
};
|
||||
|
||||
class MidiDeviceInstance {
|
||||
public:
|
||||
static MidiDeviceInstance *GetDevice(MidiDeviceType type, int port);
|
||||
static void ClearMessageBuffersOfAllDevices();
|
||||
static void ResetAllDevices();
|
||||
|
||||
private:
|
||||
MidiDeviceInstance() = default;
|
||||
~MidiDeviceInstance() = default;
|
||||
bool OpenPort();
|
||||
void ClosePort();
|
||||
bool SendMessge(const MidiMessage &);
|
||||
const std::vector<MidiMessage> &GetMessages();
|
||||
void ReceiveMidiMessage(const libremidi::message &);
|
||||
void ClearMessageBuffer();
|
||||
|
||||
static std::map<std::pair<MidiDeviceType, int>, MidiDeviceInstance *>
|
||||
devices;
|
||||
|
||||
bool _skipBufferClear = false;
|
||||
|
||||
MidiDeviceType _type = MidiDeviceType::INPUT;
|
||||
int _port = -1;
|
||||
libremidi::midi_in in;
|
||||
libremidi::midi_out out;
|
||||
std::vector<MidiMessage> _messages;
|
||||
|
||||
friend class MidiDevice;
|
||||
};
|
||||
|
||||
class MidiDevice {
|
||||
public:
|
||||
MidiDevice() = default;
|
||||
|
||||
void Save(obs_data_t *obj) const;
|
||||
void Load(obs_data_t *obj);
|
||||
|
||||
bool SendMessge(const MidiMessage &);
|
||||
|
||||
const std::vector<MidiMessage> * // Might resize! Only call
|
||||
GetMessages(bool ignoreListenMode = false); // while holding switcher
|
||||
// lock!
|
||||
std::string Name() const;
|
||||
|
||||
// Used for "listen" mode of message selection
|
||||
// Listen mode disables automatic clearing of buffers
|
||||
void UseForMessageSelection(bool);
|
||||
bool IsUsedForMessageSelection();
|
||||
void ClearMessageBuffer();
|
||||
bool DeviceSelected() { return !!_dev; }
|
||||
|
||||
private:
|
||||
std::string GetInputName() const;
|
||||
std::string GetOutputName() const;
|
||||
|
||||
MidiDeviceType _type = MidiDeviceType::INPUT;
|
||||
int _port = -1;
|
||||
MidiDeviceInstance *_dev = nullptr;
|
||||
|
||||
friend class MidiDeviceSelection;
|
||||
};
|
||||
|
||||
class MidiMessageSelection : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MidiMessageSelection(QWidget *parent);
|
||||
void SetMessage(const MidiMessage &);
|
||||
|
||||
private slots:
|
||||
void TypeChanged(const QString &);
|
||||
void ChannelChanged(const NumberVariable<int> &);
|
||||
void NoteChanged(const NumberVariable<int> &);
|
||||
void NoteStringIdxChanged(int);
|
||||
void ShowNote(bool);
|
||||
void ValueChanged(const NumberVariable<int> &);
|
||||
|
||||
signals:
|
||||
void MidiMessageChanged(const MidiMessage &);
|
||||
|
||||
private:
|
||||
static libremidi::message_type TextToMidiType(const QString &);
|
||||
|
||||
QComboBox *_type;
|
||||
VariableSpinBox *_channel;
|
||||
VariableSpinBox *_noteValue;
|
||||
QComboBox *_noteString;
|
||||
QPushButton *_noteValueStringToggle;
|
||||
VariableSpinBox *_value;
|
||||
|
||||
MidiMessage _currentSelection;
|
||||
};
|
||||
|
||||
class MidiDeviceSelection : public QComboBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MidiDeviceSelection(QWidget *parent, MidiDeviceType);
|
||||
void SetDevice(const MidiDevice &);
|
||||
|
||||
private slots:
|
||||
void IdxChangedHelper(int);
|
||||
signals:
|
||||
void DeviceSelectionChanged(const MidiDevice &);
|
||||
|
||||
private:
|
||||
const MidiDeviceType _type;
|
||||
};
|
||||
|
||||
QStringList GetAllNotes();
|
||||
|
||||
} // namespace advss
|
||||
Loading…
Reference in New Issue
Block a user