Skip to content

Commit a1b5fbb

Browse files
committed
Add OSC action
1 parent b71f633 commit a1b5fbb

File tree

8 files changed

+1301
-4
lines changed

8 files changed

+1301
-4
lines changed

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ target_sources(
107107
src/macro-core/macro-action-macro.hpp
108108
src/macro-core/macro-action-media.cpp
109109
src/macro-core/macro-action-media.hpp
110+
src/macro-core/macro-action-osc.cpp
111+
src/macro-core/macro-action-osc.hpp
110112
src/macro-core/macro-action-plugin-state.cpp
111113
src/macro-core/macro-action-plugin-state.hpp
112114
src/macro-core/macro-action-profile.cpp
@@ -266,6 +268,8 @@ target_sources(
266268
src/utils/obs-dock.hpp
267269
src/utils/obs-module-helper.cpp
268270
src/utils/obs-module-helper.hpp
271+
src/utils/osc-helpers.cpp
272+
src/utils/osc-helpers.hpp
269273
src/utils/priority-helper.cpp
270274
src/utils/priority-helper.hpp
271275
src/utils/process-config.cpp

data/locale/en-US.ini

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,7 @@ AdvSceneSwitcher.action.projector.entry.monitor="on{{monitors}}"
673673
AdvSceneSwitcher.action.midi="MIDI"
674674
AdvSceneSwitcher.action.midi.entry="Send message to {{device}}:"
675675
AdvSceneSwitcher.action.midi.entry.listen="Set MIDI message selection to messages incoming on {{listenDevices}}: {{listenButton}}"
676+
AdvSceneSwitcher.action.osc="Open Sound Control"
676677

677678
; Transition Tab
678679
AdvSceneSwitcher.transitionTab.title="Transition"
@@ -1013,6 +1014,21 @@ AdvSceneSwitcher.midi.stopListen="Stop listening"
10131014
AdvSceneSwitcher.midi.startListenFail="Device is busy!\nSomething else is already listening!"
10141015
AdvSceneSwitcher.midi.deviceOpenFail="Failed to initialize MIDI device!"
10151016

1017+
AdvSceneSwitcher.osc.network="Network"
1018+
AdvSceneSwitcher.osc.network.protocol="Protocol:"
1019+
AdvSceneSwitcher.osc.network.address="Address:"
1020+
AdvSceneSwitcher.osc.network.port="Port:"
1021+
AdvSceneSwitcher.osc.message="Message"
1022+
AdvSceneSwitcher.osc.message.type.none="None"
1023+
AdvSceneSwitcher.osc.message.type.float="Float"
1024+
AdvSceneSwitcher.osc.message.type.int="Integer"
1025+
AdvSceneSwitcher.osc.message.type.string="String"
1026+
AdvSceneSwitcher.osc.message.type.binaryBlob="Binary blob"
1027+
AdvSceneSwitcher.osc.message.type.true="True"
1028+
AdvSceneSwitcher.osc.message.type.false="False"
1029+
AdvSceneSwitcher.osc.message.type.infinity="Infinitum"
1030+
AdvSceneSwitcher.osc.message.type.null="Nil"
1031+
10161032
AdvSceneSwitcher.selectScene="--select scene--"
10171033
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
10181034
AdvSceneSwitcher.selectCurrentScene="Current Scene"

src/macro-core/macro-action-osc.cpp

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
#include "macro-action-osc.hpp"
2+
#include "utility.hpp"
3+
4+
#include <system_error>
5+
#include <QGroupBox>
6+
7+
namespace advss {
8+
9+
const std::string MacroActionOSC::id = "osc";
10+
11+
bool MacroActionOSC::_registered = MacroActionFactory::Register(
12+
MacroActionOSC::id, {MacroActionOSC::Create, MacroActionOSCEdit::Create,
13+
"AdvSceneSwitcher.action.osc"});
14+
15+
MacroActionOSC::MacroActionOSC(Macro *m)
16+
: MacroAction(m),
17+
_ctx(asio::io_context()),
18+
_tcpSocket(asio::ip::tcp::socket(_ctx)),
19+
_udpSocket(asio::ip::udp::socket(_ctx))
20+
{
21+
}
22+
23+
void MacroActionOSC::SendOSCTCPMessage(const asio::mutable_buffer &buffer)
24+
{
25+
try {
26+
asio::write(_tcpSocket, asio::buffer(buffer));
27+
} catch (const std::exception &e) {
28+
blog(LOG_WARNING,
29+
"failed to send OSC message \"%s\" via TCP %s %d: %s",
30+
_message.ToString().c_str(), _ip.c_str(), _port.GetValue(),
31+
e.what());
32+
}
33+
}
34+
35+
void MacroActionOSC::SendOSCUDPMessage(const asio::mutable_buffer &buffer)
36+
{
37+
try {
38+
_udpSocket.send_to(buffer, _updEndpoint);
39+
} catch (const std::exception &e) {
40+
blog(LOG_WARNING,
41+
"failed to send OSC message \"%s\" via UDP %s %d: %s",
42+
_message.ToString().c_str(), _ip.c_str(), _port.GetValue(),
43+
e.what());
44+
}
45+
}
46+
47+
void MacroActionOSC::UDPReconnect()
48+
{
49+
asio::error_code ec;
50+
asio::ip::udp::resolver resolver(_ctx);
51+
auto endpoints = resolver.resolve(asio::ip::udp::v4(), _ip.c_str(),
52+
std::to_string(_port.GetValue()), ec);
53+
if (ec) {
54+
endpoints = resolver.resolve(asio::ip::udp::v6(), _ip.c_str(),
55+
std::to_string(_port.GetValue()),
56+
ec);
57+
}
58+
if (ec) {
59+
blog(LOG_WARNING, "failed to get IP for \"%s\": %s",
60+
_ip.c_str(), ec.message().c_str());
61+
return;
62+
}
63+
64+
try {
65+
_updEndpoint = endpoints.begin()->endpoint();
66+
_udpSocket = asio::ip::udp::socket(_ctx);
67+
_udpSocket.open(endpoints.begin()->endpoint().protocol());
68+
} catch (const std::exception &e) {
69+
blog(LOG_WARNING, "failed to connect to UDP %s %d: %s",
70+
_ip.c_str(), _port.GetValue(), e.what());
71+
}
72+
}
73+
74+
void MacroActionOSC::TCPReconnect()
75+
{
76+
asio::error_code ec;
77+
asio::ip::tcp::resolver resolver(_ctx);
78+
auto endpoints = resolver.resolve(asio::ip::tcp::v4(), _ip.c_str(),
79+
std::to_string(_port.GetValue()), ec);
80+
if (ec) {
81+
endpoints = resolver.resolve(asio::ip::tcp::v6(), _ip.c_str(),
82+
std::to_string(_port.GetValue()),
83+
ec);
84+
}
85+
if (ec) {
86+
blog(LOG_WARNING, "failed to get IP for \"%s\": %s",
87+
_ip.c_str(), ec.message().c_str());
88+
return;
89+
}
90+
try {
91+
_tcpSocket = asio::ip::tcp::socket(_ctx);
92+
_tcpSocket.connect(endpoints.begin()->endpoint());
93+
} catch (const std::exception &e) {
94+
blog(LOG_WARNING, "failed to connect to TCP %s %d: %s",
95+
_ip.c_str(), _port.GetValue(), e.what());
96+
}
97+
}
98+
99+
void MacroActionOSC::CheckReconnect()
100+
{
101+
if (_protocol == Protocol::TCP &&
102+
(_reconnect || !_tcpSocket.is_open())) {
103+
TCPReconnect();
104+
}
105+
106+
if (_protocol == Protocol::UDP &&
107+
(_reconnect || !_udpSocket.is_open())) {
108+
UDPReconnect();
109+
}
110+
}
111+
112+
bool MacroActionOSC::PerformAction()
113+
{
114+
auto buffer = _message.GetBuffer();
115+
if (!buffer.has_value()) {
116+
blog(LOG_WARNING, "failed to create or fill OSC buffer!");
117+
return true;
118+
}
119+
120+
CheckReconnect();
121+
122+
if (_protocol == Protocol::TCP &&
123+
(_reconnect || !_tcpSocket.is_open())) {
124+
TCPReconnect();
125+
}
126+
127+
if (_protocol == Protocol::UDP &&
128+
(_reconnect || !_udpSocket.is_open())) {
129+
UDPReconnect();
130+
}
131+
132+
auto rawMessage = asio::buffer(*buffer);
133+
134+
switch (_protocol) {
135+
case MacroActionOSC::Protocol::TCP:
136+
SendOSCTCPMessage(rawMessage);
137+
break;
138+
case MacroActionOSC::Protocol::UDP:
139+
SendOSCUDPMessage(rawMessage);
140+
break;
141+
default:
142+
break;
143+
}
144+
145+
return true;
146+
}
147+
148+
void MacroActionOSC::LogAction() const
149+
{
150+
vblog(LOG_INFO, "sending OSC message '%s' to %s %s %d",
151+
_message.ToString().c_str(),
152+
_protocol == Protocol::UDP ? "UDP" : "TCP", _ip.c_str(),
153+
_port.GetValue());
154+
}
155+
156+
bool MacroActionOSC::Save(obs_data_t *obj) const
157+
{
158+
MacroAction::Save(obj);
159+
obs_data_set_int(obj, "protocol", static_cast<int>(_protocol));
160+
_ip.Save(obj, "ip");
161+
_port.Save(obj, "port");
162+
_message.Save(obj);
163+
return true;
164+
}
165+
166+
bool MacroActionOSC::Load(obs_data_t *obj)
167+
{
168+
MacroAction::Load(obj);
169+
_protocol = static_cast<Protocol>(obs_data_get_int(obj, "protocol"));
170+
_ip.Load(obj, "ip");
171+
_port.Load(obj, "port");
172+
_message.Load(obj);
173+
return true;
174+
}
175+
176+
void MacroActionOSC::SetProtocol(Protocol p)
177+
{
178+
_protocol = p;
179+
_reconnect = true;
180+
}
181+
182+
void MacroActionOSC::SetIP(const std::string &ip)
183+
{
184+
_ip = ip;
185+
_reconnect = true;
186+
}
187+
188+
void MacroActionOSC::SetPortNr(IntVariable port)
189+
{
190+
_port = port;
191+
_reconnect = true;
192+
}
193+
194+
static void populateProtocolSelection(QComboBox *list)
195+
{
196+
list->addItem("TCP");
197+
list->addItem("UDP");
198+
}
199+
200+
MacroActionOSCEdit::MacroActionOSCEdit(
201+
QWidget *parent, std::shared_ptr<MacroActionOSC> entryData)
202+
: QWidget(parent),
203+
_protocol(new QComboBox(this)),
204+
_ip(new VariableLineEdit(this)),
205+
_port(new VariableSpinBox(this)),
206+
_message(new OSCMessageEdit(this))
207+
{
208+
populateProtocolSelection(_protocol);
209+
_port->setMaximum(65535);
210+
211+
auto networkGroup =
212+
new QGroupBox(obs_module_text("AdvSceneSwitcher.osc.network"));
213+
auto networkLayout = new QGridLayout;
214+
int row = 0;
215+
networkLayout->addWidget(
216+
new QLabel(obs_module_text(
217+
"AdvSceneSwitcher.osc.network.protocol")),
218+
row, 0);
219+
networkLayout->addWidget(_protocol, row, 1);
220+
++row;
221+
networkLayout->addWidget(
222+
new QLabel(obs_module_text(
223+
"AdvSceneSwitcher.osc.network.address")),
224+
row, 0);
225+
networkLayout->addWidget(_ip, row, 1);
226+
++row;
227+
networkLayout->addWidget(new QLabel(obs_module_text(
228+
"AdvSceneSwitcher.osc.network.port")),
229+
row, 0);
230+
networkLayout->addWidget(_port, row, 1);
231+
networkGroup->setLayout(networkLayout);
232+
233+
auto messageGroup =
234+
new QGroupBox(obs_module_text("AdvSceneSwitcher.osc.message"));
235+
auto messageLayout = new QHBoxLayout();
236+
messageLayout->addWidget(_message);
237+
messageGroup->setLayout(messageLayout);
238+
239+
auto mainLayout = new QVBoxLayout;
240+
mainLayout->addWidget(networkGroup);
241+
mainLayout->addWidget(messageGroup);
242+
setLayout(mainLayout);
243+
244+
QWidget::connect(_ip, SIGNAL(editingFinished()), this,
245+
SLOT(IpChanged()));
246+
QWidget::connect(_protocol, SIGNAL(currentIndexChanged(int)), this,
247+
SLOT(ProtocolChanged(int)));
248+
QWidget::connect(
249+
_port,
250+
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
251+
this, SLOT(PortChanged(const NumberVariable<int> &)));
252+
QWidget::connect(_message, SIGNAL(MessageChanged(const OSCMessage &)),
253+
this, SLOT(MessageChanged(const OSCMessage &)));
254+
255+
_entryData = entryData;
256+
UpdateEntryData();
257+
_loading = false;
258+
}
259+
260+
void MacroActionOSCEdit::UpdateEntryData()
261+
{
262+
if (!_entryData) {
263+
return;
264+
}
265+
266+
_protocol->setCurrentIndex(static_cast<int>(_entryData->GetProtocol()));
267+
_ip->setText(_entryData->GetIP());
268+
_port->SetValue(_entryData->GetPortNr());
269+
_message->SetMessage(_entryData->_message);
270+
271+
adjustSize();
272+
updateGeometry();
273+
}
274+
275+
void MacroActionOSCEdit::IpChanged()
276+
{
277+
if (_loading || !_entryData) {
278+
return;
279+
}
280+
281+
auto lock = LockContext();
282+
_entryData->SetIP(_ip->text().toStdString());
283+
}
284+
285+
void MacroActionOSCEdit::ProtocolChanged(int value)
286+
{
287+
if (_loading || !_entryData) {
288+
return;
289+
}
290+
291+
auto lock = LockContext();
292+
_entryData->SetProtocol(static_cast<MacroActionOSC::Protocol>(value));
293+
}
294+
295+
void MacroActionOSCEdit::PortChanged(const NumberVariable<int> &value)
296+
{
297+
if (_loading || !_entryData) {
298+
return;
299+
}
300+
301+
auto lock = LockContext();
302+
_entryData->SetPortNr(value);
303+
}
304+
305+
void MacroActionOSCEdit::MessageChanged(const OSCMessage &m)
306+
{
307+
if (_loading || !_entryData) {
308+
return;
309+
}
310+
311+
auto lock = LockContext();
312+
_entryData->_message = m;
313+
314+
adjustSize();
315+
updateGeometry();
316+
}
317+
318+
} // namespace advss

0 commit comments

Comments
 (0)