OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
qml_ump_inbound_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.inboundUMPDevices();
21 var ump = Protocols.inboundUMP({
22 Transport: devices[0],
23 onOpen: function(socket) {
24 console.log("UMP port opened!");
25 },
26 onMessage: function(msg) {
27 console.log("UMP message:", msg);
28 // msg contains parsed UMP data
29 },
30 onError: function(error) {
31 console.log("UMP error:", error);
32 }
33 });
34*/
35class qml_ump_inbound_socket
36 : public QObject
37 , public Nano::Observer
38{
39 W_OBJECT(qml_ump_inbound_socket)
40public:
41 struct state
42 {
43 std::atomic_bool alive{true};
44 };
45
46 qml_ump_inbound_socket()
47 : m_state{std::make_shared<state>()}
48 {
49 }
50
51 ~qml_ump_inbound_socket()
52 {
53 m_state->alive = false;
54 close();
55 }
56
57 void open(const libremidi::port_information& pi)
58 {
59 try
60 {
61 // Configure UMP input
62 auto st = m_state;
63 auto self = QPointer{this};
64
65 libremidi::ump_input_configuration config;
66 config.on_message
67 = [st, self](const libremidi::ump& message) {
68 if(!st->alive)
69 return;
70
71 // Convert UMP message to QVariantMap for QML
72 QVariantMap msg;
73 msg["timestamp"] = QVariant::fromValue(message.timestamp);
74
75 // Convert UMP data (4 32-bit words)
76 QVariantList words;
77 for(int i = 0; i < 4; ++i)
78 {
79 words.append(static_cast<quint32>(message.data[i]));
80 }
81 msg["words"] = words;
82
83 // Send to QML in Qt thread
84 ossia::qt::run_async(
85 self.get(),
86 [self, msg]() {
87 if(!self.get())
88 return;
89 if(self->onMessage.isCallable())
90 {
91 self->onMessage.call({qjsEngine(self.get())->toScriptValue(msg)});
92 }
93 },
94 Qt::AutoConnection);
95 };
96
97 // Create UMP input with MIDI 2.0 API
98 m_ump_in = std::make_unique<libremidi::midi_in>(
99 config, libremidi::midi2::default_api());
100
101 // Open the port
102 if(auto err = m_ump_in->open_port(static_cast<const libremidi::input_port&>(pi));
103 err != stdx::error{})
104 {
105 if(onError.isCallable())
106 {
107 const auto& msg = err.message();
108 onError.call({QString::fromUtf8(msg.data(), msg.size())});
109 }
110 return;
111 }
112
113 m_is_open = true;
114
115 // Call onOpen callback
116 if(onOpen.isCallable())
117 {
118 onOpen.call({qjsEngine(this)->newQObject(this)});
119 }
120 }
121 catch(const std::exception& e)
122 {
123 if(onError.isCallable())
124 {
125 onError.call({QString::fromUtf8(e.what())});
126 }
127 }
128 }
129
130 void open()
131 {
132 try
133 {
134 // Create UMP observer to enumerate ports
135 m_observer = std::make_unique<libremidi::observer>(
136 libremidi::observer_configuration{}, libremidi::midi2::default_api());
137
138 // Get available UMP input ports
139 auto ports = m_observer->get_input_ports();
140
141 if(ports.empty())
142 {
143 if(onError.isCallable())
144 {
145 onError.call({"No UMP input ports available"});
146 }
147 return;
148 }
149
150 // Default to first available port
151 open(ports[0]);
152 }
153 catch(const std::exception& e)
154 {
155 if(onError.isCallable())
156 {
157 onError.call({QString::fromUtf8(e.what())});
158 }
159 }
160 }
161
162 void close()
163 {
164 if(m_is_open)
165 {
166 m_is_open = false;
167 m_ump_in.reset();
168 m_observer.reset();
169
170 if(onClose.isCallable())
171 {
172 onClose.call();
173 }
174 }
175 }
176 W_SLOT(close)
177
178 // Callbacks
179 QJSValue onOpen;
180 QJSValue onClose;
181 QJSValue onError;
182 QJSValue onMessage;
183
184private:
185 std::shared_ptr<state> m_state;
186 std::unique_ptr<libremidi::observer> m_observer;
187 std::unique_ptr<libremidi::midi_in> m_ump_in;
188 std::atomic_bool m_is_open{false};
189};
190
191}
Definition qml_device.cpp:43