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