Vuo 2.4.4
Loading...
Searching...
No Matches
VuoMidi.cc
Go to the documentation of this file.
1
10#include "VuoMidi.h"
11
12#include <RtMidi/RtMidi.h>
13#include "VuoApp.h"
14#include "VuoOsStatus.h"
15#include "VuoTriggerSet.hh"
16
17#include <CoreMIDI/CoreMIDI.h>
18
19extern "C"
20{
21#ifdef VUO_COMPILER
23 "title" : "VuoMidi",
24 "dependencies" : [
25 "VuoApp",
26 "VuoMidiController",
27 "VuoMidiInputDevice",
28 "VuoMidiOutputDevice",
29 "VuoMidiNote",
30 "VuoMidiPitchBend",
31 "VuoOsStatus",
32 "VuoList_VuoMidiInputDevice",
33 "VuoList_VuoMidiOutputDevice",
34 "rtmidi",
35 "CoreAudio.framework",
36 "CoreMIDI.framework"
37 ]
38 });
39#endif
40}
41
44unsigned int VuoMidi_useCount = 0;
45MIDIClientRef VuoMidi_client;
46
51{
53 try
54 {
55 RtMidiIn *midiin = new RtMidiIn();
56 unsigned int portCount = midiin->getPortCount();
57 for (unsigned int i = 0; i < portCount; ++i)
58 VuoListAppendValue_VuoMidiInputDevice(inputDevices, VuoMidiInputDevice_make(i, VuoText_make(midiin->getPortName(i).c_str())));
59 delete midiin;
60 }
61 catch(...) {}
62 return inputDevices;
63}
64
69{
71 try
72 {
73 RtMidiOut *midiout = new RtMidiOut();
74 unsigned int portCount = midiout->getPortCount();
75 for (unsigned int i = 0; i < portCount; ++i)
76 VuoListAppendValue_VuoMidiOutputDevice(outputDevices, VuoMidiOutputDevice_make(i, VuoText_make(midiout->getPortName(i).c_str())));
77 delete midiout;
78 }
79 catch(...) {}
80 return outputDevices;
81}
82
86static void VuoMidi_reconfigurationCallback(const MIDINotification *message, void *refCon)
87{
88 if (message->messageID != kMIDIMsgSetupChanged)
89 return;
90
93}
94
101void VuoMidi_use(void)
102{
103 if (__sync_add_and_fetch(&VuoMidi_useCount, 1) == 1)
105 OSStatus ret = MIDIClientCreate(CFSTR("VuoMidi_use"), VuoMidi_reconfigurationCallback, NULL, &VuoMidi_client);
106 if (ret)
107 {
108 char *description = VuoOsStatus_getText(ret);
109 VUserLog("Error: Couldn't register device change listener: %s", description);
110 free(description);
111 }
112 });
113}
114
122{
123 if (VuoMidi_useCount <= 0)
124 {
125 VUserLog("Error: Unbalanced VuoMidi_use() / _disuse() calls.");
126 return;
127 }
128
129 if (__sync_sub_and_fetch(&VuoMidi_useCount, 1) == 0)
131 OSStatus ret = MIDIClientDispose(VuoMidi_client);
132 if (ret)
133 {
134 char *description = VuoOsStatus_getText(ret);
135 VUserLog("Error: Couldn't unregister device change listener: %s", description);
136 free(description);
137 }
138 });
139}
140
150{
151 VuoMidi_inputDeviceCallbacks.addTrigger(inputDevices);
152 VuoMidi_outputDeviceCallbacks.addTrigger(outputDevices);
153 inputDevices(VuoMidi_getInputDevices());
154 outputDevices(VuoMidi_getOutputDevices());
155}
156
164{
165 VuoMidi_inputDeviceCallbacks.removeTrigger(inputDevices);
166 VuoMidi_outputDeviceCallbacks.removeTrigger(outputDevices);
167}
168
170
175{
176 RtMidiOut *midiout = NULL;
177 try
178 {
179 VuoMidiOutputDevice realizedDevice;
180 if (!VuoMidiOutputDevice_realize(md, &realizedDevice))
181 throw RtMidiError("No matching device found");
182 VuoMidiOutputDevice_retain(realizedDevice);
183
184 midiout = new RtMidiOut();
185 midiout->openPort(realizedDevice.id, "VuoMidiOut_make");
186
187 VuoMidiOutputDevice_release(realizedDevice);
188 }
189 catch (RtMidiError &error)
190 {
192 VUserLog("Failed to open the specified MIDI device (%s) :: %s.", VuoMidiOutputDevice_getSummary(md), error.what());
193 delete midiout;
194 return NULL;
195 }
196
198 return (VuoMidiOut)midiout;
199}
200
205{
206 if (!mo)
207 return;
208
209 RtMidiOut *midiout = (RtMidiOut *)mo;
210 std::vector<unsigned char> message;
211 message.push_back(0x80 + (note.isNoteOn ? 0x10 : 0) + MIN(note.channel-1,15));
212 message.push_back(MIN(note.noteNumber,127));
213 message.push_back(MIN(note.velocity,127));
214 midiout->sendMessage(&message);
215}
216
221{
222 if (!mo)
223 return;
224
225 RtMidiOut *midiout = (RtMidiOut *)mo;
226 std::vector<unsigned char> message;
227 message.push_back(0xB0 + MIN(controller.channel-1,15));
228 message.push_back(MIN(controller.controllerNumber,127));
229 message.push_back(MIN(controller.value,127));
230 midiout->sendMessage(&message);
231}
232
237{
238 if (!mo)
239 return;
240
241 RtMidiOut *midiout = (RtMidiOut *)mo;
242 std::vector<unsigned char> message;
243 message.push_back(0xE0 + MIN(pitchBend.channel-1,15));
244 message.push_back(pitchBend.value & 0x7f);
245 message.push_back((pitchBend.value >> 7) & 0x7f);
246 midiout->sendMessage(&message);
247}
248
253{
254 if (!mo)
255 return;
256
257 RtMidiOut *midiout = (RtMidiOut *)mo;
258 delete midiout;
259}
260
265{
266 RtMidiIn *midiin;
267
268 dispatch_queue_t callbackQueue;
269 void (*receivedNote)(void *, VuoMidiNote);
272 void *context;
273};
274
278void VuoMidiIn_receivedEvent(double timeStamp, std::vector< unsigned char > *message, void *userData)
279{
280 if (message->size() == 0)
281 return;
282
283 struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)userData;
284 unsigned char channel = ((*message)[0] & 0x0f) + 1;
285 if (((*message)[0] & 0xf0) == 0x80) // Note Off
286 {
287 VuoMidiNote mn = VuoMidiNote_make(channel, false, (*message)[2], (*message)[1]);
288 dispatch_async(mii->callbackQueue, ^{
289 if (mii->receivedNote)
290 mii->receivedNote(mii->context, mn);
291 });
292 }
293 else if (((*message)[0] & 0xf0) == 0x90) // Note On
294 {
295 unsigned char velocity = (*message)[2];
296
297 // Convert note-on messages with 0 velocity into note-off messages.
298 bool isNoteOn = (velocity != 0);
299
300 VuoMidiNote mn = VuoMidiNote_make(channel, isNoteOn, velocity, (*message)[1]);
301 dispatch_async(mii->callbackQueue, ^{
302 if (mii->receivedNote)
303 mii->receivedNote(mii->context, mn);
304 });
305 }
306 else if (((*message)[0] & 0xf0) == 0xb0) // Control Change
307 {
308 VuoMidiController mc = VuoMidiController_make(channel, (*message)[1], (*message)[2]);
309 dispatch_async(mii->callbackQueue, ^{
310 if (mii->receivedController)
311 mii->receivedController(mii->context, mc);
312 });
313 }
314 else if (((*message)[0] & 0xf0) == 0xe0) // Pitch Bend Change
315 {
316 VuoMidiPitchBend mp = VuoMidiPitchBend_make(channel, (*message)[1] + ((*message)[2] << 7));
317 dispatch_async(mii->callbackQueue, ^{
318 if (mii->receivedPitchBend)
319 mii->receivedPitchBend(mii->context, mp);
320 });
321 }
322 else
323 {
324 const char *hex = "0123456789abcdef";
325 std::string messageHex;
326 for (size_t i = 0; i < message->size(); ++i)
327 {
328 messageHex.push_back(hex[(*message)[i] >> 4]);
329 messageHex.push_back(hex[(*message)[i] & 0x0f]);
330 }
331 VUserLog("Warning: Received unknown message: 0x%s", messageHex.c_str());
332 }
333}
334
336
341{
342 struct VuoMidiIn_internal *mii;
343 RtMidiIn *midiin = NULL;
344 try
345 {
346 VuoMidiInputDevice realizedDevice;
347 if (!VuoMidiInputDevice_realize(md, &realizedDevice))
348 throw RtMidiError("No matching device found");
349 VuoMidiInputDevice_retain(realizedDevice);
350
351 midiin = new RtMidiIn();
352 midiin->openPort(realizedDevice.id, "VuoMidiIn_make");
353
354 VuoMidiInputDevice_release(realizedDevice);
355
356 mii = (struct VuoMidiIn_internal *)calloc(1, sizeof(struct VuoMidiIn_internal));
358 mii->midiin = midiin;
359 mii->callbackQueue = dispatch_queue_create("vuo.midi.receive", NULL);
360 // Trigger functions (receiveNote, ...) are NULLed by calloc.
361
362 midiin->setCallback(&VuoMidiIn_receivedEvent, mii);
363
364 // Ignore SysEx, timing, and active sensing for now.
365 midiin->ignoreTypes(true,true,true);
366 }
367 catch (RtMidiError &error)
368 {
370 VUserLog("Error: Failed to open the specified MIDI device (%s) :: %s.", VuoMidiInputDevice_getSummary(md), error.what());
371 delete midiin;
372 return NULL;
373 }
374
375 return (VuoMidiIn)mii;
376}
377
384(
385 VuoMidiIn mi,
386 void (*receivedNote)(void *context, VuoMidiNote note),
387 void (*receivedController)(void *context, VuoMidiController controller),
388 void (*receivedPitchBend)(void *context, VuoMidiPitchBend pitchBend),
389 void *context
390)
391{
392 if (!mi)
393 return;
394 struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)mi;
395 dispatch_async(mii->callbackQueue, ^{
396 mii->receivedNote = receivedNote;
397 mii->receivedController = receivedController;
398 mii->receivedPitchBend = receivedPitchBend;
399 mii->context = context;
400 });
401}
402
409{
410 if (!mi)
411 return;
412 struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)mi;
413 dispatch_sync(mii->callbackQueue, ^{
414 mii->receivedNote = NULL;
415 mii->receivedController = NULL;
416 mii->context = NULL;
417 });
418}
419
424{
425 if (!mi)
426 return;
427 struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)mi;
428 dispatch_release(mii->callbackQueue);
429 delete mii->midiin;
430 free(mii);
431}
432
434#define setRealizedDevice(newDevice) \
435 realizedDevice->id = newDevice.id; \
436 realizedDevice->name = VuoText_make(newDevice.name);
437
451{
452 // Already have ID and name; nothing to do.
453 if (device.id != -1 && !VuoText_isEmpty(device.name))
454 {
455 setRealizedDevice(device);
456 return true;
457 }
458
459 // Otherwise, try to find a matching device.
460
461 VUserLog("Requested device: %s", json_object_to_json_string(VuoMidiInputDevice_getJson(device)));
463 VuoLocal(devices);
464 __block bool found = false;
465
466 // First pass: try to find an exact match by ID.
468 if (device.id != -1 && device.id == item.id)
469 {
470 VUserLog("Matched by ID: %s",json_object_to_json_string(VuoMidiInputDevice_getJson(item)));
471 setRealizedDevice(item);
472 found = true;
473 return false;
474 }
475 return true;
476 });
477
478 // Second pass: try to find a match by name.
479 if (!found)
481 if (!VuoText_isEmpty(device.name) && VuoText_compare(item.name, (VuoTextComparison){VuoTextComparison_Contains, true}, device.name))
482 {
483 VUserLog("Matched by name: %s",json_object_to_json_string(VuoMidiInputDevice_getJson(item)));
484 setRealizedDevice(item);
485 found = true;
486 return false;
487 }
488 return true;
489 });
490
491 // Third pass: if the user hasn't specified a device, use the first device.
492 if (!found && device.id == -1 && VuoText_isEmpty(device.name))
493 {
495 {
497 VUserLog("Using first device: %s",json_object_to_json_string(VuoMidiInputDevice_getJson(item)));
498 setRealizedDevice(item);
499 found = true;
500 }
501 }
502
503 if (!found)
504 VUserLog("No matching device found.");
505
506 return found;
507}
508
522{
523 // Already have ID and name; nothing to do.
524 if (device.id != -1 && !VuoText_isEmpty(device.name))
525 {
526 setRealizedDevice(device);
527 return true;
528 }
529
530 // Otherwise, try to find a matching device.
531
532 VUserLog("Requested device: %s", json_object_to_json_string(VuoMidiOutputDevice_getJson(device)));
534 VuoLocal(devices);
535 __block bool found = false;
536
537 // First pass: try to find an exact match by ID.
539 if (device.id != -1 && device.id == item.id)
540 {
541 VUserLog("Matched by ID: %s",json_object_to_json_string(VuoMidiOutputDevice_getJson(item)));
542 setRealizedDevice(item);
543 found = true;
544 return false;
545 }
546 return true;
547 });
548
549 // Second pass: try to find a match by name.
550 if (!found)
552 if (!VuoText_isEmpty(device.name) && VuoText_compare(item.name, (VuoTextComparison){VuoTextComparison_Contains, true}, device.name))
553 {
554 VUserLog("Matched by name: %s",json_object_to_json_string(VuoMidiOutputDevice_getJson(item)));
555 setRealizedDevice(item);
556 found = true;
557 return false;
558 }
559 return true;
560 });
561
562 // Third pass: if the user hasn't specified a device, use the first device.
563 if (!found && device.id == -1 && VuoText_isEmpty(device.name))
564 {
566 {
568 VUserLog("Using first device: %s",json_object_to_json_string(VuoMidiOutputDevice_getJson(item)));
569 setRealizedDevice(item);
570 found = true;
571 }
572 }
573
574 if (!found)
575 VUserLog("No matching device found.");
576
577 return found;
578}