OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
qml_midi_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.inboundMIDIDevices();
21 JSON.stringify(devices[0]);
22 var midi = Protocols.inboundMIDI({
23 Transport: { PortName: "My MIDI Controller" },
24 onOpen: function(socket) {
25 console.log("MIDI port opened!");
26 },
27 onMessage: function(msg) {
28 console.log("MIDI message:", msg.bytes);
29 },
30 onError: function(error) {
31 console.log("MIDI error:", error);
32 }
33 });
34*/
35class qml_midi_inbound_socket
36 : public QObject
37 , public Nano::Observer
38{
39 W_OBJECT(qml_midi_inbound_socket)
40public:
41 struct state
42 {
43 std::atomic_bool alive{true};
44 };
45
46 qml_midi_inbound_socket()
47 : m_state{std::make_shared<state>()}
48 {
49 }
50
51 ~qml_midi_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 MIDI input
62 auto st = m_state;
63 auto self = QPointer{this};
64
65 libremidi::input_configuration config;
66 config.on_message
67 = [st, self](const libremidi::message& message) {
68 if(!st->alive)
69 return;
70
71 // Convert MIDI message to QVariantMap for QML
72 QVariantMap msg;
73 msg["timestamp"] = QVariant::fromValue(message.timestamp);
74
75 // Convert bytes to QVariantList
76 QVariantList bytes;
77 for(const auto& byte : message.bytes)
78 {
79 bytes.append(static_cast<int>(byte));
80 }
81 msg["bytes"] = bytes;
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 config.ignore_sysex = false;
97 config.ignore_timing = false;
98 config.ignore_sensing = true;
99
100 // Create MIDI input
101 m_midi_in = std::make_unique<libremidi::midi_in>(config);
102
103 // Open the port
104 if(auto err = m_midi_in->open_port(static_cast<const libremidi::input_port&>(pi));
105 err != stdx::error{})
106 {
107 if(onError.isCallable())
108 {
109 const auto& msg = err.message();
110 onError.call({QString::fromUtf8(msg.data(), msg.size())});
111 }
112 return;
113 }
114
115 m_is_open = true;
116
117 // Call onOpen callback
118 if(onOpen.isCallable())
119 {
120 onOpen.call({qjsEngine(this)->newQObject(this)});
121 }
122 }
123 catch(const std::exception& e)
124 {
125 if(onError.isCallable())
126 {
127 onError.call({QString::fromUtf8(e.what())});
128 }
129 }
130 }
131
132 void open()
133 {
134 try
135 {
136 // Create MIDI observer to enumerate ports
137 m_observer = std::make_unique<libremidi::observer>();
138
139 // Get available MIDI input ports
140 auto ports = m_observer->get_input_ports();
141
142 if(ports.empty())
143 {
144 if(onError.isCallable())
145 {
146 onError.call({"No MIDI input ports available"});
147 }
148 return;
149 }
150
151 // Default to first available port
152 open(ports[0]);
153 }
154 catch(const std::exception& e)
155 {
156 if(onError.isCallable())
157 {
158 onError.call({QString::fromUtf8(e.what())});
159 }
160 }
161 }
162
163 void close()
164 {
165 if(m_is_open)
166 {
167 m_is_open = false;
168 m_midi_in.reset();
169 m_observer.reset();
170
171 if(onClose.isCallable())
172 {
173 onClose.call();
174 }
175 }
176 }
177 W_SLOT(close)
178
179 // Callbacks
180 QJSValue onOpen;
181 QJSValue onClose;
182 QJSValue onError;
183 QJSValue onMessage;
184
185private:
186 std::shared_ptr<state> m_state;
187 std::unique_ptr<libremidi::observer> m_observer;
188 std::unique_ptr<libremidi::midi_in> m_midi_in;
189 std::atomic_bool m_is_open{false};
190};
191
192}
Definition qml_device.cpp:43