OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
qml_ump_outbound_socket.hpp
1#pragma once
2#include <ossia/network/context.hpp>
3
4#include <ossia-qt/protocols/utils.hpp>
5
6#include <QJSValue>
7#include <QObject>
8#include <QQmlEngine>
9#include <QVariant>
10
11#include <libremidi/libremidi.hpp>
12
13#include <nano_observer.hpp>
14
15#include <verdigris>
16
17namespace ossia::qt
18{
19/*
20 var devices = Protocols.outboundUMPDevices();
21 var ump = Protocols.outboundUMP({
22 Transport: devices[0],
23 onOpen: function(socket) {
24 console.log("UMP port opened!");
25 // Send MIDI 2.0 note on with velocity and attribute
26 socket.sendNoteOn(1, 60, 32768, 0, 0); // group 1, middle C, velocity 50%, no attribute
27
28 // Send raw UMP message (4 32-bit words)
29 socket.sendMessage([0x40902C00, 0x80000000, 0, 0]); // MIDI 2.0 Note On
30 },
31 onError: function(error) {
32 console.log("UMP error:", error);
33 }
34 });
35*/
36class qml_ump_outbound_socket
37 : public QObject
38 , public Nano::Observer
39{
40 W_OBJECT(qml_ump_outbound_socket)
41public:
42 qml_ump_outbound_socket() { }
43
44 ~qml_ump_outbound_socket() { close(); }
45
46 void open(const libremidi::port_information& pi)
47 {
48 try
49 {
50 // Configure UMP output
51 libremidi::output_configuration config;
52
53 // Create UMP output with MIDI 2.0 API
54 m_ump_out = std::make_unique<libremidi::midi_out>(
55 config, libremidi::midi2::default_api());
56
57 // Open the port
58 if(auto err = m_ump_out->open_port(static_cast<const libremidi::output_port&>(pi));
59 err != stdx::error{})
60 {
61 if(onError.isCallable())
62 {
63 const auto& msg = err.message();
64 run_on_qt_thread(
65 { onError.call({QString::fromUtf8(msg.data(), msg.size())}); });
66 }
67 return;
68 }
69
70 m_is_open = true;
71
72 // Call onOpen callback
73 if(onOpen.isCallable())
74 {
75 run_on_qt_thread({ onOpen.call({qjsEngine(this)->newQObject(this)}); });
76 }
77 }
78 catch(const std::exception& e)
79 {
80 if(onError.isCallable())
81 {
82 run_on_qt_thread({ onError.call({QString::fromUtf8(e.what())}); });
83 }
84 }
85 }
86
87 void open()
88 {
89 try
90 {
91 // Create UMP observer to enumerate ports
92 m_observer = std::make_unique<libremidi::observer>(
93 libremidi::observer_configuration{}, libremidi::midi2::default_api());
94
95 // Get available UMP output ports
96 auto ports = m_observer->get_output_ports();
97
98 if(ports.empty())
99 {
100 if(onError.isCallable())
101 {
102 run_on_qt_thread({ onError.call({"No UMP output ports available"}); });
103 }
104 return;
105 }
106
107 // Default to first available port
108 open(ports[0]);
109 }
110 catch(const std::exception& e)
111 {
112 if(onError.isCallable())
113 {
114 run_on_qt_thread({ onError.call({QString::fromUtf8(e.what())}); });
115 }
116 }
117 }
118
119 void close()
120 {
121 if(m_is_open)
122 {
123 m_is_open = false;
124 m_ump_out.reset();
125 m_observer.reset();
126
127 if(onClose.isCallable())
128 {
129 run_on_qt_thread({ onClose.call(); });
130 }
131 }
132 }
133 W_SLOT(close)
134
135 // Send raw UMP message (expects 4 32-bit words)
136 void sendMessage(QVariantList words)
137 {
138 if(!m_is_open || !m_ump_out)
139 return;
140
141 if(words.size() != 4)
142 {
143 if(onError.isCallable())
144 {
145 run_on_qt_thread({ onError.call({"UMP message must contain exactly 4 words"}); });
146 }
147 return;
148 }
149
150 libremidi::ump msg;
151 msg.timestamp = 0;
152 for(int i = 0; i < 4; ++i)
153 {
154 msg.data[i] = static_cast<uint32_t>(words[i].toUInt());
155 }
156
157 if(auto err = m_ump_out->send_ump(msg); err != stdx::error{})
158 {
159 if(onError.isCallable())
160 {
161 const auto& errmsg = err.message();
162 run_on_qt_thread(
163 { onError.call({QString::fromUtf8(errmsg.data(), errmsg.size())}); });
164 }
165 }
166 }
167 W_SLOT(sendMessage)
168
169 // MIDI 2.0 convenience methods
170 void sendNoteOn(int group, int channel, int note, int velocity, int attributeType, int attribute)
171 {
172 if(!m_is_open || !m_ump_out)
173 return;
174
175 if(group < 1 || group > 16 || channel < 1 || channel > 16 ||
176 note < 0 || note > 127 || velocity < 0 || velocity > 65535)
177 return;
178
179 libremidi::ump msg;
180 msg.timestamp = 0;
181 // Message Type 4 (MIDI 2.0), Group, Status 0x9 (Note On), Channel
182 msg.data[0] = (0x4 << 28) | ((group - 1) << 24) | (0x90 << 16) | ((channel - 1) << 16) | (note << 8) | attributeType;
183 // Velocity (16-bit) and Attribute (16-bit)
184 msg.data[1] = (velocity << 16) | (attribute & 0xFFFF);
185 msg.data[2] = 0;
186 msg.data[3] = 0;
187
188 m_ump_out->send_ump(msg);
189 }
190 W_SLOT(sendNoteOn)
191
192 void sendNoteOff(int group, int channel, int note, int velocity, int attributeType, int attribute)
193 {
194 if(!m_is_open || !m_ump_out)
195 return;
196
197 if(group < 1 || group > 16 || channel < 1 || channel > 16 ||
198 note < 0 || note > 127 || velocity < 0 || velocity > 65535)
199 return;
200
201 libremidi::ump msg;
202 msg.timestamp = 0;
203 // Message Type 4 (MIDI 2.0), Group, Status 0x8 (Note Off), Channel
204 msg.data[0] = (0x4 << 28) | ((group - 1) << 24) | (0x80 << 16) | ((channel - 1) << 16) | (note << 8) | attributeType;
205 // Velocity (16-bit) and Attribute (16-bit)
206 msg.data[1] = (velocity << 16) | (attribute & 0xFFFF);
207 msg.data[2] = 0;
208 msg.data[3] = 0;
209
210 m_ump_out->send_ump(msg);
211 }
212 W_SLOT(sendNoteOff)
213
214 void sendControlChange(int group, int channel, int controller, int value)
215 {
216 if(!m_is_open || !m_ump_out)
217 return;
218
219 if(group < 1 || group > 16 || channel < 1 || channel > 16 ||
220 controller < 0 || controller > 127)
221 return;
222
223 libremidi::ump msg;
224 msg.timestamp = 0;
225 // Message Type 4 (MIDI 2.0), Group, Status 0xB (Control Change), Channel, Controller
226 msg.data[0] = (0x4 << 28) | ((group - 1) << 24) | (0xB0 << 16) | ((channel - 1) << 16) | (controller << 8);
227 // 32-bit value
228 msg.data[1] = static_cast<uint32_t>(value);
229 msg.data[2] = 0;
230 msg.data[3] = 0;
231
232 m_ump_out->send_ump(msg);
233 }
234 W_SLOT(sendControlChange)
235
236 void sendProgramChange(int group, int channel, int program, int bankMSB, int bankLSB)
237 {
238 if(!m_is_open || !m_ump_out)
239 return;
240
241 if(group < 1 || group > 16 || channel < 1 || channel > 16 ||
242 program < 0 || program > 127 || bankMSB < 0 || bankMSB > 127 || bankLSB < 0 || bankLSB > 127)
243 return;
244
245 libremidi::ump msg;
246 msg.timestamp = 0;
247 // Message Type 4 (MIDI 2.0), Group, Status 0xC (Program Change), Channel
248 msg.data[0] = (0x4 << 28) | ((group - 1) << 24) | (0xC0 << 16) | ((channel - 1) << 16);
249 // Program, Bank MSB, Bank LSB
250 msg.data[1] = (program << 24) | (bankMSB << 8) | bankLSB;
251 msg.data[2] = 0;
252 msg.data[3] = 0;
253
254 m_ump_out->send_ump(msg);
255 }
256 W_SLOT(sendProgramChange)
257
258 void sendPitchBend(int group, int channel, int value)
259 {
260 if(!m_is_open || !m_ump_out)
261 return;
262
263 if(group < 1 || group > 16 || channel < 1 || channel > 16)
264 return;
265
266 libremidi::ump msg;
267 msg.timestamp = 0;
268 // Message Type 4 (MIDI 2.0), Group, Status 0xE (Pitch Bend), Channel
269 msg.data[0] = (0x4 << 28) | ((group - 1) << 24) | (0xE0 << 16) | ((channel - 1) << 16);
270 // 32-bit pitch bend value
271 msg.data[1] = static_cast<uint32_t>(value);
272 msg.data[2] = 0;
273 msg.data[3] = 0;
274
275 m_ump_out->send_ump(msg);
276 }
277 W_SLOT(sendPitchBend)
278
279 // Callbacks
280 QJSValue onOpen;
281 QJSValue onClose;
282 QJSValue onError;
283
284private:
285 std::unique_ptr<libremidi::observer> m_observer;
286 std::unique_ptr<libremidi::midi_out> m_ump_out;
287 std::atomic_bool m_is_open{false};
288};
289
290}
Definition qml_device.cpp:43