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 qml_ump_inbound_socket() { }
42
43 ~qml_ump_inbound_socket() { close(); }
44
45 void open(const libremidi::port_information& pi)
46 {
47 try
48 {
49 // Configure UMP input
50 libremidi::ump_input_configuration config;
51 config.on_message
52 = [this](const libremidi::ump& message) { handleUMPMessage(message); };
53
54 // Create UMP input with MIDI 2.0 API
55 m_ump_in = std::make_unique<libremidi::midi_in>(
56 config, libremidi::midi2::default_api());
57
58 // Open the port
59 if(auto err = m_ump_in->open_port(static_cast<const libremidi::input_port&>(pi));
60 err != stdx::error{})
61 {
62 if(onError.isCallable())
63 {
64 const auto& msg = err.message();
65 run_on_qt_thread(
66 { onError.call({QString::fromUtf8(msg.data(), msg.size())}); });
67 }
68 return;
69 }
70
71 m_is_open = true;
72
73 // Call onOpen callback
74 if(onOpen.isCallable())
75 {
76 run_on_qt_thread({ onOpen.call({qjsEngine(this)->newQObject(this)}); });
77 }
78 }
79 catch(const std::exception& e)
80 {
81 if(onError.isCallable())
82 {
83 run_on_qt_thread({ onError.call({QString::fromUtf8(e.what())}); });
84 }
85 }
86 }
87
88 void open()
89 {
90 try
91 {
92 // Create UMP observer to enumerate ports
93 m_observer = std::make_unique<libremidi::observer>(
94 libremidi::observer_configuration{}, libremidi::midi2::default_api());
95
96 // Get available UMP input ports
97 auto ports = m_observer->get_input_ports();
98
99 if(ports.empty())
100 {
101 if(onError.isCallable())
102 {
103 run_on_qt_thread({ onError.call({"No UMP input ports available"}); });
104 }
105 return;
106 }
107
108 // Default to first available port
109 open(ports[0]);
110 }
111 catch(const std::exception& e)
112 {
113 if(onError.isCallable())
114 {
115 run_on_qt_thread({ onError.call({QString::fromUtf8(e.what())}); });
116 }
117 }
118 }
119
120 void close()
121 {
122 if(m_is_open)
123 {
124 m_is_open = false;
125 m_ump_in.reset();
126 m_observer.reset();
127
128 if(onClose.isCallable())
129 {
130 run_on_qt_thread({ onClose.call(); });
131 }
132 }
133 }
134 W_SLOT(close)
135
136 // Callbacks
137 QJSValue onOpen;
138 QJSValue onClose;
139 QJSValue onError;
140 QJSValue onMessage;
141
142private:
143 void handleUMPMessage(const libremidi::ump& message)
144 {
145 if(!onMessage.isCallable())
146 return;
147
148 // Convert UMP message to QVariantMap for QML
149 QVariantMap msg;
150 msg["timestamp"] = QVariant::fromValue(message.timestamp);
151
152 // Convert UMP data (4 32-bit words)
153 QVariantList words;
154 for(int i = 0; i < 4; ++i)
155 {
156 words.append(static_cast<quint32>(message.data[i]));
157 }
158 msg["words"] = words;
159
160 // Send to QML in Qt thread
161 ossia::qt::run_async(this, [this, msg]() {
162 if(onMessage.isCallable())
163 {
164 onMessage.call({qjsEngine(this)->toScriptValue(msg)});
165 }
166 }, Qt::AutoConnection);
167 }
168
169 std::unique_ptr<libremidi::observer> m_observer;
170 std::unique_ptr<libremidi::midi_in> m_ump_in;
171 std::atomic_bool m_is_open{false};
172};
173
174}
Definition qml_device.cpp:43