MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
MIDIBackend.cpp
Go to the documentation of this file.
1#include "MIDIBackend.hpp"
3
4#include <rtmidi/RtMidi.h>
5
6namespace MayaFlux::Core {
7
12
14 : m_config(std::move(config))
15{
16}
17
19{
20 if (m_initialized.load()) {
21 shutdown();
22 }
23}
24
26{
27 if (m_initialized.load()) {
29 "MIDIBackend already initialized");
30 return true;
31 }
32
33 try {
35 "Initializing MIDI Backend (RtMidi version: {})", get_version());
36
37 m_initialized.store(true);
40
42 "MIDIBackend initialized with {} port(s)", m_enumerated_devices.size());
43
44 return true;
45
46 } catch (const RtMidiError& error) {
48 "RtMidi error during initialization: {}", error.getMessage());
49 return false;
50 }
51}
52
54{
55 if (!m_initialized.load()) {
57 "Cannot start MIDIBackend: not initialized");
58 return;
59 }
60
61 if (m_running.load()) {
63 "MIDIBackend already running");
64 return;
65 }
66
67 std::vector<uint32_t> inputs_to_open;
68 {
69 std::lock_guard lock(m_devices_mutex);
70 for (const auto& [id, info] : m_enumerated_devices) {
71 if (info.is_input) {
72 inputs_to_open.push_back(id);
73 }
74 }
75 }
76
77 for (uint32_t id : inputs_to_open) {
78 open_device(id);
79 }
80
81 m_running.store(true);
82
84 "MIDIBackend started with {} open port(s)", get_open_devices().size());
85}
86
88{
89 if (!m_running.load()) {
90 return;
91 }
92
93 {
94 std::lock_guard lock(m_devices_mutex);
95 for (auto& [id, state] : m_open_devices) {
96 state->active.store(false);
97 if (state->midi_in) {
98 try {
99 state->midi_in->closePort();
100 } catch (const RtMidiError& error) {
102 "Error closing MIDI port {}: {}", state->info.name, error.getMessage());
103 }
104 }
105 }
106 }
107
108 m_running.store(false);
109
111 "MIDIBackend stopped");
112}
113
115{
116 if (!m_initialized.load()) {
117 return;
118 }
119
120 stop();
121
122 {
123 std::lock_guard lock(m_devices_mutex);
124 m_open_devices.clear();
125 m_enumerated_devices.clear();
126 }
127
128 m_initialized.store(false);
129
131 "MIDIBackend shutdown complete");
132}
133
134std::vector<InputDeviceInfo> MIDIBackend::get_devices() const
135{
136 std::lock_guard lock(m_devices_mutex);
137
138 std::vector<InputDeviceInfo> result;
139 result.reserve(m_enumerated_devices.size());
140
141 for (const auto& [id, info] : m_enumerated_devices) {
142 result.push_back(info);
143 }
144
145 return result;
146}
147
149{
150 if (!m_initialized.load()) {
151 return 0;
152 }
153
154 std::vector<InputDeviceInfo> newly_added_devices;
155
156 try {
157 RtMidiIn midi_in;
158 unsigned int port_count = midi_in.getPortCount();
159
160 {
161 std::lock_guard lock(m_devices_mutex);
162
163 for (unsigned int i = 0; i < port_count; ++i) {
164 std::string port_name = midi_in.getPortName(i);
165
166 if (!port_matches_filter(port_name)) {
167 continue;
168 }
169
170 uint32_t dev_id = find_or_assign_device_id(i);
171
172 MIDIPortInfo info {};
173 info.id = dev_id;
174 info.name = port_name;
175 info.backend_type = InputType::MIDI;
176 info.is_connected = true;
177 info.is_input = true;
178 info.is_output = false;
179 info.port_number = static_cast<uint8_t>(i);
180 info.rtmidi_port_number = i;
181
182 bool is_new = (m_enumerated_devices.find(dev_id) == m_enumerated_devices.end());
183
184 m_enumerated_devices[dev_id] = info;
185
186 if (is_new) {
187 newly_added_devices.push_back(info);
188 }
189 }
190 }
191
192 for (const auto& info : newly_added_devices) {
194 "MIDI port found: {}", info.name);
195 notify_device_change(info, true);
196 }
197
198 {
199 std::lock_guard lock(m_devices_mutex);
200 return m_enumerated_devices.size();
201 }
202 } catch (const RtMidiError& error) {
204 "Error enumerating MIDI ports: {}", error.getMessage());
205 return 0;
206 }
207}
208
209bool MIDIBackend::open_device(uint32_t device_id)
210{
211 std::lock_guard lock(m_devices_mutex);
212
213 if (m_open_devices.find(device_id) != m_open_devices.end()) {
215 "MIDI port {} already open", device_id);
216 return true;
217 }
218
219 auto it = m_enumerated_devices.find(device_id);
220 if (it == m_enumerated_devices.end()) {
222 "MIDI port {} not found", device_id);
223 return false;
224 }
225
226 try {
227 auto state = std::make_shared<MIDIPortState>();
228 state->info = it->second;
229 state->device_id = device_id;
230 state->input_callback = m_input_callback;
231 state->midi_in = std::make_unique<RtMidiIn>();
232
233 state->midi_in->openPort(state->info.rtmidi_port_number, state->info.name);
234 state->midi_in->setCallback(&MIDIBackend::rtmidi_callback, state.get());
235 state->midi_in->ignoreTypes(false, false, false);
236 state->active.store(true);
237
238 m_open_devices.insert_or_assign(device_id, state);
239
241 "Opened MIDI port {}: {}", device_id, state->info.name);
242
243 return true;
244
245 } catch (const RtMidiError& error) {
247 "Failed to open MIDI port {}: {}", it->second.name, error.getMessage());
248 return false;
249 }
250}
251
252void MIDIBackend::close_device(uint32_t device_id)
253{
254 std::lock_guard lock(m_devices_mutex);
255
256 auto it = m_open_devices.find(device_id);
257 if (it == m_open_devices.end()) {
258 return;
259 }
260
261 it->second->active.store(false);
262 if (it->second->midi_in) {
263 try {
264 it->second->midi_in->closePort();
265 } catch (const RtMidiError& error) {
267 "Error closing MIDI port {}: {}", it->second->info.name, error.getMessage());
268 }
269 }
270
272 "Closed MIDI port {}: {}", device_id, it->second->info.name);
273
274 m_open_devices.erase(it);
275}
276
277bool MIDIBackend::is_device_open(uint32_t device_id) const
278{
279 std::lock_guard lock(m_devices_mutex);
280 return m_open_devices.find(device_id) != m_open_devices.end();
281}
282
283std::vector<uint32_t> MIDIBackend::get_open_devices() const
284{
285 std::lock_guard lock(m_devices_mutex);
286
287 std::vector<uint32_t> result;
288 result.reserve(m_open_devices.size());
289
290 for (const auto& [id, state] : m_open_devices) {
291 result.push_back(id);
292 }
293
294 return result;
295}
296
298{
299 std::lock_guard lock(m_callback_mutex);
300 m_input_callback = std::move(callback);
301}
302
304{
305 std::lock_guard lock(m_callback_mutex);
306 m_device_callback = std::move(callback);
307}
308
309std::string MIDIBackend::get_version() const
310{
311 return std::string(RtMidi::getVersion());
312}
313
314// ===================================================================================
315// Private Implementation
316// ===================================================================================
317
318bool MIDIBackend::port_matches_filter(const std::string& port_name) const
319{
320 if (m_config.input_port_filters.empty()) {
321 return true;
322 }
323
324 return std::ranges::any_of(m_config.input_port_filters,
325 [&port_name](const std::string& filter) {
326 return port_name.find(filter) != std::string::npos;
327 });
328}
329
330uint32_t MIDIBackend::find_or_assign_device_id(unsigned int rtmidi_port)
331{
332 for (const auto& [id, info] : m_enumerated_devices) {
333 if (info.rtmidi_port_number == rtmidi_port) {
334 return id;
335 }
336 }
337 return m_next_device_id++;
338}
339
341{
343 return;
344 }
345
346 try {
347 uint32_t dev_id = 0;
348
349 MIDIPortInfo info {};
350 info.id = dev_id;
351 info.name = m_config.virtual_port_name;
352 info.backend_type = InputType::MIDI;
353 info.is_connected = true;
354 info.is_input = true;
355 info.is_output = false;
356 info.port_number = 255; // Special marker for virtual port
357 info.rtmidi_port_number = 0;
358
359 m_enumerated_devices[dev_id] = info;
360
362 "Created virtual MIDI port: {}", m_config.virtual_port_name);
363
364 } catch (const RtMidiError& error) {
366 "Failed to create virtual MIDI port: {}", error.getMessage());
367 }
368}
369
370void MIDIBackend::rtmidi_callback(double /*timestamp*/, std::vector<unsigned char>* message, void* user_data)
371{
372 auto* state = static_cast<MIDIPortState*>(user_data);
373 if (!state || !message || message->empty()) {
374 return;
375 }
376
378 message->at(0),
379 message->size() > 1 ? message->at(1) : 0,
380 message->size() > 2 ? message->at(2) : 0,
381 state->device_id);
382
383 if (state->input_callback) {
384 state->input_callback(value);
385 }
386}
387
388void MIDIBackend::notify_device_change(const InputDeviceInfo& info, bool connected)
389{
390 std::lock_guard lock(m_callback_mutex);
391 if (m_device_callback) {
392 m_device_callback(info, connected);
393 }
394}
395
396InputValue MIDIBackend::parse_midi_message(uint32_t device_id, const std::vector<unsigned char>& message) const
397{
398 if (message.empty()) {
399 return InputValue::make_bytes({}, device_id, InputType::MIDI);
400 }
401
402 uint8_t status = message[0];
403 uint8_t data1 = (message.size() > 1) ? message[1] : 0;
404 uint8_t data2 = (message.size() > 2) ? message[2] : 0;
405
406 return InputValue::make_midi(status, data1, data2, device_id);
407}
408
409} // namespace MayaFlux::Core
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
uint32_t find_or_assign_device_id(unsigned int rtmidi_port)
std::string get_version() const override
Get backend version string.
std::unordered_map< uint32_t, MIDIPortInfo > m_enumerated_devices
bool is_device_open(uint32_t device_id) const override
Check if a device is currently open.
bool port_matches_filter(const std::string &port_name) const
std::atomic< bool > m_running
void start() override
Start listening for input events.
size_t refresh_devices() override
Refresh the device list.
std::unordered_map< uint32_t, std::shared_ptr< MIDIPortState > > m_open_devices
void set_input_callback(InputCallback callback) override
Register callback for input values.
DeviceCallback m_device_callback
std::atomic< bool > m_initialized
static void rtmidi_callback(double timestamp, std::vector< unsigned char > *message, void *user_data)
std::vector< uint32_t > get_open_devices() const override
Get list of currently open device IDs.
bool initialize() override
Initialize the input backend.
std::vector< InputDeviceInfo > get_devices() const override
Get list of available devices.
void close_device(uint32_t device_id) override
Close a previously opened device.
void stop() override
Stop listening for input events.
InputValue parse_midi_message(uint32_t device_id, const std::vector< unsigned char > &message) const
void shutdown() override
Shutdown and release all resources.
void notify_device_change(const InputDeviceInfo &info, bool connected)
bool open_device(uint32_t device_id) override
Open a device for input.
void set_device_callback(DeviceCallback callback) override
Register callback for device connect/disconnect events.
RtMidi-based MIDI input backend.
@ MIDI
MIDI controllers and instruments.
std::function< void(const InputValue &)> InputCallback
Callback signature for input events.
std::function< void(const InputDeviceInfo &, bool connected)> DeviceCallback
Callback signature for device connection/disconnection events.
@ InputBackend
Input device backend (HID, MIDI, OSC)
@ Core
Core engine, backend, subsystems.
uint32_t id
Unique device identifier within backend.
Information about a connected input device.
static InputValue make_bytes(std::vector< uint8_t > v, uint32_t dev_id, InputType src)
static InputValue make_midi(uint8_t status, uint8_t d1, uint8_t d2, uint32_t dev_id)
Generic input value container.
std::vector< std::string > input_port_filters