Vuo  2.0.2
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 
19 extern "C"
20 {
21 #include "module.h"
22 
23 #ifdef VUO_COMPILER
25  "title" : "VuoMidi",
26  "dependencies" : [
27  "VuoApp",
28  "VuoMidiController",
29  "VuoMidiInputDevice",
30  "VuoMidiOutputDevice",
31  "VuoMidiNote",
32  "VuoMidiPitchBend",
33  "VuoOsStatus",
34  "VuoList_VuoMidiInputDevice",
35  "VuoList_VuoMidiOutputDevice",
36  "rtmidi",
37  "CoreAudio.framework",
38  "CoreMIDI.framework"
39  ]
40  });
41 #endif
42 }
43 
46 unsigned int VuoMidi_useCount = 0;
47 MIDIClientRef VuoMidi_client;
48 
53 {
55  try
56  {
57  RtMidiIn *midiin = new RtMidiIn();
58  unsigned int portCount = midiin->getPortCount();
59  for (unsigned int i = 0; i < portCount; ++i)
60  VuoListAppendValue_VuoMidiInputDevice(inputDevices, VuoMidiInputDevice_make(i, VuoText_make(midiin->getPortName(i).c_str())));
61  delete midiin;
62  }
63  catch(...) {}
64  return inputDevices;
65 }
66 
71 {
73  try
74  {
75  RtMidiOut *midiout = new RtMidiOut();
76  unsigned int portCount = midiout->getPortCount();
77  for (unsigned int i = 0; i < portCount; ++i)
78  VuoListAppendValue_VuoMidiOutputDevice(outputDevices, VuoMidiOutputDevice_make(i, VuoText_make(midiout->getPortName(i).c_str())));
79  delete midiout;
80  }
81  catch(...) {}
82  return outputDevices;
83 }
84 
88 static void VuoMidi_reconfigurationCallback(const MIDINotification *message, void *refCon)
89 {
90  if (message->messageID != kMIDIMsgSetupChanged)
91  return;
92 
95 }
96 
103 void VuoMidi_use(void)
104 {
105  if (__sync_add_and_fetch(&VuoMidi_useCount, 1) == 1)
107  OSStatus ret = MIDIClientCreate(CFSTR("VuoMidi_use"), VuoMidi_reconfigurationCallback, NULL, &VuoMidi_client);
108  if (ret)
109  {
110  char *description = VuoOsStatus_getText(ret);
111  VUserLog("Error: Couldn't register device change listener: %s", description);
112  free(description);
113  }
114  });
115 }
116 
123 void VuoMidi_disuse(void)
124 {
125  if (VuoMidi_useCount <= 0)
126  {
127  VUserLog("Error: Unbalanced VuoMidi_use() / _disuse() calls.");
128  return;
129  }
130 
131  if (__sync_sub_and_fetch(&VuoMidi_useCount, 1) == 0)
133  OSStatus ret = MIDIClientDispose(VuoMidi_client);
134  if (ret)
135  {
136  char *description = VuoOsStatus_getText(ret);
137  VUserLog("Error: Couldn't unregister device change listener: %s", description);
138  free(description);
139  }
140  });
141 }
142 
152 {
153  VuoMidi_inputDeviceCallbacks.addTrigger(inputDevices);
154  VuoMidi_outputDeviceCallbacks.addTrigger(outputDevices);
155  inputDevices(VuoMidi_getInputDevices());
156  outputDevices(VuoMidi_getOutputDevices());
157 }
158 
166 {
167  VuoMidi_inputDeviceCallbacks.removeTrigger(inputDevices);
168  VuoMidi_outputDeviceCallbacks.removeTrigger(outputDevices);
169 }
170 
172 
177 {
178  RtMidiOut *midiout = NULL;
179  try
180  {
181  VuoMidiOutputDevice realizedDevice;
182  if (!VuoMidiOutputDevice_realize(md, &realizedDevice))
183  throw RtError("No matching device found");
184  VuoMidiOutputDevice_retain(realizedDevice);
185 
186  midiout = new RtMidiOut();
187  midiout->openPort(realizedDevice.id, "VuoMidiOut_make");
188 
189  VuoMidiOutputDevice_release(realizedDevice);
190  }
191  catch (RtError &error)
192  {
194  VUserLog("Failed to open the specified MIDI device (%s) :: %s.", VuoMidiOutputDevice_getSummary(md), error.what());
195  if (midiout)
196  delete midiout;
197  return NULL;
198  }
199 
201  return (VuoMidiOut)midiout;
202 }
203 
208 {
209  if (!mo)
210  return;
211 
212  RtMidiOut *midiout = (RtMidiOut *)mo;
213  std::vector<unsigned char> message;
214  message.push_back(0x80 + (note.isNoteOn ? 0x10 : 0) + MIN(note.channel-1,15));
215  message.push_back(MIN(note.noteNumber,127));
216  message.push_back(MIN(note.velocity,127));
217  midiout->sendMessage(&message);
218 }
219 
224 {
225  if (!mo)
226  return;
227 
228  RtMidiOut *midiout = (RtMidiOut *)mo;
229  std::vector<unsigned char> message;
230  message.push_back(0xB0 + MIN(controller.channel-1,15));
231  message.push_back(MIN(controller.controllerNumber,127));
232  message.push_back(MIN(controller.value,127));
233  midiout->sendMessage(&message);
234 }
235 
240 {
241  if (!mo)
242  return;
243 
244  RtMidiOut *midiout = (RtMidiOut *)mo;
245  std::vector<unsigned char> message;
246  message.push_back(0xE0 + MIN(pitchBend.channel-1,15));
247  message.push_back(pitchBend.value & 0x7f);
248  message.push_back((pitchBend.value >> 7) & 0x7f);
249  midiout->sendMessage(&message);
250 }
251 
256 {
257  if (!mo)
258  return;
259 
260  RtMidiOut *midiout = (RtMidiOut *)mo;
261  delete midiout;
262 }
263 
268 {
269  RtMidiIn *midiin;
270 
271  dispatch_queue_t callbackQueue;
272  void (*receivedNote)(void *, VuoMidiNote);
275  void *context;
276 };
277 
281 void VuoMidiIn_receivedEvent(double timeStamp, std::vector< unsigned char > *message, void *userData)
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 RtError("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 (RtError &error)
368  {
370  VUserLog("Error: Failed to open the specified MIDI device (%s) :: %s.", VuoMidiInputDevice_getSummary(md), error.what());
371  if (midiin)
372  delete midiin;
373  return NULL;
374  }
375 
376  return (VuoMidiIn)mii;
377 }
378 
385 (
386  VuoMidiIn mi,
387  void (*receivedNote)(void *context, VuoMidiNote note),
388  void (*receivedController)(void *context, VuoMidiController controller),
389  void (*receivedPitchBend)(void *context, VuoMidiPitchBend pitchBend),
390  void *context
391 )
392 {
393  if (!mi)
394  return;
395  struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)mi;
396  dispatch_async(mii->callbackQueue, ^{
397  mii->receivedNote = receivedNote;
398  mii->receivedController = receivedController;
399  mii->receivedPitchBend = receivedPitchBend;
400  mii->context = context;
401  });
402 }
403 
410 {
411  if (!mi)
412  return;
413  struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)mi;
414  dispatch_sync(mii->callbackQueue, ^{
415  mii->receivedNote = NULL;
416  mii->receivedController = NULL;
417  mii->context = NULL;
418  });
419 }
420 
425 {
426  if (!mi)
427  return;
428  struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)mi;
429  dispatch_release(mii->callbackQueue);
430  delete mii->midiin;
431  free(mii);
432 }
433 
435 #define setRealizedDevice(newDevice) \
436  realizedDevice->id = newDevice.id; \
437  realizedDevice->name = VuoText_make(newDevice.name);
438 
452 {
453  // Already have ID and name; nothing to do.
454  if (device.id != -1 && !VuoText_isEmpty(device.name))
455  {
456  setRealizedDevice(device);
457  return true;
458  }
459 
460  // Otherwise, try to find a matching device.
461 
462  VDebugLog("Requested device: %s", json_object_to_json_string(VuoMidiInputDevice_getJson(device)));
464  VuoLocal(devices);
465  __block bool found = false;
466 
467  // First pass: try to find an exact match by ID.
469  if (device.id != -1 && device.id == item.id)
470  {
471  VDebugLog("Matched by ID: %s",json_object_to_json_string(VuoMidiInputDevice_getJson(item)));
472  setRealizedDevice(item);
473  found = true;
474  return false;
475  }
476  return true;
477  });
478 
479  // Second pass: try to find a match by name.
480  if (!found)
482  if (!VuoText_isEmpty(device.name) && VuoText_compare(item.name, (VuoTextComparison){VuoTextComparison_Contains, true, ""}, device.name))
483  {
484  VDebugLog("Matched by name: %s",json_object_to_json_string(VuoMidiInputDevice_getJson(item)));
485  setRealizedDevice(item);
486  found = true;
487  return false;
488  }
489  return true;
490  });
491 
492  // Third pass: if the user hasn't specified a device, use the first device.
493  if (!found && device.id == -1 && VuoText_isEmpty(device.name))
494  {
496  {
498  VDebugLog("Using first device: %s",json_object_to_json_string(VuoMidiInputDevice_getJson(item)));
499  setRealizedDevice(item);
500  found = true;
501  }
502  }
503 
504  if (!found)
505  VDebugLog("No matching device found.");
506 
507  return found;
508 }
509 
523 {
524  // Already have ID and name; nothing to do.
525  if (device.id != -1 && !VuoText_isEmpty(device.name))
526  {
527  setRealizedDevice(device);
528  return true;
529  }
530 
531  // Otherwise, try to find a matching device.
532 
533  VDebugLog("Requested device: %s", json_object_to_json_string(VuoMidiOutputDevice_getJson(device)));
535  VuoLocal(devices);
536  __block bool found = false;
537 
538  // First pass: try to find an exact match by ID.
540  if (device.id != -1 && device.id == item.id)
541  {
542  VDebugLog("Matched by ID: %s",json_object_to_json_string(VuoMidiOutputDevice_getJson(item)));
543  setRealizedDevice(item);
544  found = true;
545  return false;
546  }
547  return true;
548  });
549 
550  // Second pass: try to find a match by name.
551  if (!found)
553  if (!VuoText_isEmpty(device.name) && VuoText_compare(item.name, (VuoTextComparison){VuoTextComparison_Contains, true, ""}, device.name))
554  {
555  VDebugLog("Matched by name: %s",json_object_to_json_string(VuoMidiOutputDevice_getJson(item)));
556  setRealizedDevice(item);
557  found = true;
558  return false;
559  }
560  return true;
561  });
562 
563  // Third pass: if the user hasn't specified a device, use the first device.
564  if (!found && device.id == -1 && VuoText_isEmpty(device.name))
565  {
567  {
569  VDebugLog("Using first device: %s",json_object_to_json_string(VuoMidiOutputDevice_getJson(item)));
570  setRealizedDevice(item);
571  found = true;
572  }
573  }
574 
575  if (!found)
576  VDebugLog("No matching device found.");
577 
578  return found;
579 }