Vuo  2.3.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 RtMidiError("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 (RtMidiError &error)
192  {
194  VUserLog("Failed to open the specified MIDI device (%s) :: %s.", VuoMidiOutputDevice_getSummary(md), error.what());
195  delete midiout;
196  return NULL;
197  }
198 
200  return (VuoMidiOut)midiout;
201 }
202 
207 {
208  if (!mo)
209  return;
210 
211  RtMidiOut *midiout = (RtMidiOut *)mo;
212  std::vector<unsigned char> message;
213  message.push_back(0x80 + (note.isNoteOn ? 0x10 : 0) + MIN(note.channel-1,15));
214  message.push_back(MIN(note.noteNumber,127));
215  message.push_back(MIN(note.velocity,127));
216  midiout->sendMessage(&message);
217 }
218 
223 {
224  if (!mo)
225  return;
226 
227  RtMidiOut *midiout = (RtMidiOut *)mo;
228  std::vector<unsigned char> message;
229  message.push_back(0xB0 + MIN(controller.channel-1,15));
230  message.push_back(MIN(controller.controllerNumber,127));
231  message.push_back(MIN(controller.value,127));
232  midiout->sendMessage(&message);
233 }
234 
239 {
240  if (!mo)
241  return;
242 
243  RtMidiOut *midiout = (RtMidiOut *)mo;
244  std::vector<unsigned char> message;
245  message.push_back(0xE0 + MIN(pitchBend.channel-1,15));
246  message.push_back(pitchBend.value & 0x7f);
247  message.push_back((pitchBend.value >> 7) & 0x7f);
248  midiout->sendMessage(&message);
249 }
250 
255 {
256  if (!mo)
257  return;
258 
259  RtMidiOut *midiout = (RtMidiOut *)mo;
260  delete midiout;
261 }
262 
267 {
268  RtMidiIn *midiin;
269 
270  dispatch_queue_t callbackQueue;
271  void (*receivedNote)(void *, VuoMidiNote);
274  void *context;
275 };
276 
280 void VuoMidiIn_receivedEvent(double timeStamp, std::vector< unsigned char > *message, void *userData)
281 {
282  if (message->size() == 0)
283  return;
284 
285  struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)userData;
286  unsigned char channel = ((*message)[0] & 0x0f) + 1;
287  if (((*message)[0] & 0xf0) == 0x80) // Note Off
288  {
289  VuoMidiNote mn = VuoMidiNote_make(channel, false, (*message)[2], (*message)[1]);
290  dispatch_async(mii->callbackQueue, ^{
291  if (mii->receivedNote)
292  mii->receivedNote(mii->context, mn);
293  });
294  }
295  else if (((*message)[0] & 0xf0) == 0x90) // Note On
296  {
297  unsigned char velocity = (*message)[2];
298 
299  // Convert note-on messages with 0 velocity into note-off messages.
300  bool isNoteOn = (velocity != 0);
301 
302  VuoMidiNote mn = VuoMidiNote_make(channel, isNoteOn, velocity, (*message)[1]);
303  dispatch_async(mii->callbackQueue, ^{
304  if (mii->receivedNote)
305  mii->receivedNote(mii->context, mn);
306  });
307  }
308  else if (((*message)[0] & 0xf0) == 0xb0) // Control Change
309  {
310  VuoMidiController mc = VuoMidiController_make(channel, (*message)[1], (*message)[2]);
311  dispatch_async(mii->callbackQueue, ^{
312  if (mii->receivedController)
313  mii->receivedController(mii->context, mc);
314  });
315  }
316  else if (((*message)[0] & 0xf0) == 0xe0) // Pitch Bend Change
317  {
318  VuoMidiPitchBend mp = VuoMidiPitchBend_make(channel, (*message)[1] + ((*message)[2] << 7));
319  dispatch_async(mii->callbackQueue, ^{
320  if (mii->receivedPitchBend)
321  mii->receivedPitchBend(mii->context, mp);
322  });
323  }
324  else
325  {
326  const char *hex = "0123456789abcdef";
327  std::string messageHex;
328  for (size_t i = 0; i < message->size(); ++i)
329  {
330  messageHex.push_back(hex[(*message)[i] >> 4]);
331  messageHex.push_back(hex[(*message)[i] & 0x0f]);
332  }
333  VUserLog("Warning: Received unknown message: 0x%s", messageHex.c_str());
334  }
335 }
336 
338 
343 {
344  struct VuoMidiIn_internal *mii;
345  RtMidiIn *midiin = NULL;
346  try
347  {
348  VuoMidiInputDevice realizedDevice;
349  if (!VuoMidiInputDevice_realize(md, &realizedDevice))
350  throw RtMidiError("No matching device found");
351  VuoMidiInputDevice_retain(realizedDevice);
352 
353  midiin = new RtMidiIn();
354  midiin->openPort(realizedDevice.id, "VuoMidiIn_make");
355 
356  VuoMidiInputDevice_release(realizedDevice);
357 
358  mii = (struct VuoMidiIn_internal *)calloc(1, sizeof(struct VuoMidiIn_internal));
360  mii->midiin = midiin;
361  mii->callbackQueue = dispatch_queue_create("vuo.midi.receive", NULL);
362  // Trigger functions (receiveNote, ...) are NULLed by calloc.
363 
364  midiin->setCallback(&VuoMidiIn_receivedEvent, mii);
365 
366  // Ignore SysEx, timing, and active sensing for now.
367  midiin->ignoreTypes(true,true,true);
368  }
369  catch (RtMidiError &error)
370  {
372  VUserLog("Error: Failed to open the specified MIDI device (%s) :: %s.", VuoMidiInputDevice_getSummary(md), error.what());
373  delete midiin;
374  return NULL;
375  }
376 
377  return (VuoMidiIn)mii;
378 }
379 
386 (
387  VuoMidiIn mi,
388  void (*receivedNote)(void *context, VuoMidiNote note),
389  void (*receivedController)(void *context, VuoMidiController controller),
390  void (*receivedPitchBend)(void *context, VuoMidiPitchBend pitchBend),
391  void *context
392 )
393 {
394  if (!mi)
395  return;
396  struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)mi;
397  dispatch_async(mii->callbackQueue, ^{
398  mii->receivedNote = receivedNote;
399  mii->receivedController = receivedController;
400  mii->receivedPitchBend = receivedPitchBend;
401  mii->context = context;
402  });
403 }
404 
411 {
412  if (!mi)
413  return;
414  struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)mi;
415  dispatch_sync(mii->callbackQueue, ^{
416  mii->receivedNote = NULL;
417  mii->receivedController = NULL;
418  mii->context = NULL;
419  });
420 }
421 
426 {
427  if (!mi)
428  return;
429  struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)mi;
430  dispatch_release(mii->callbackQueue);
431  delete mii->midiin;
432  free(mii);
433 }
434 
436 #define setRealizedDevice(newDevice) \
437  realizedDevice->id = newDevice.id; \
438  realizedDevice->name = VuoText_make(newDevice.name);
439 
453 {
454  // Already have ID and name; nothing to do.
455  if (device.id != -1 && !VuoText_isEmpty(device.name))
456  {
457  setRealizedDevice(device);
458  return true;
459  }
460 
461  // Otherwise, try to find a matching device.
462 
463  VDebugLog("Requested device: %s", json_object_to_json_string(VuoMidiInputDevice_getJson(device)));
465  VuoLocal(devices);
466  __block bool found = false;
467 
468  // First pass: try to find an exact match by ID.
470  if (device.id != -1 && device.id == item.id)
471  {
472  VDebugLog("Matched by ID: %s",json_object_to_json_string(VuoMidiInputDevice_getJson(item)));
473  setRealizedDevice(item);
474  found = true;
475  return false;
476  }
477  return true;
478  });
479 
480  // Second pass: try to find a match by name.
481  if (!found)
483  if (!VuoText_isEmpty(device.name) && VuoText_compare(item.name, (VuoTextComparison){VuoTextComparison_Contains, true}, device.name))
484  {
485  VDebugLog("Matched by name: %s",json_object_to_json_string(VuoMidiInputDevice_getJson(item)));
486  setRealizedDevice(item);
487  found = true;
488  return false;
489  }
490  return true;
491  });
492 
493  // Third pass: if the user hasn't specified a device, use the first device.
494  if (!found && device.id == -1 && VuoText_isEmpty(device.name))
495  {
497  {
499  VDebugLog("Using first device: %s",json_object_to_json_string(VuoMidiInputDevice_getJson(item)));
500  setRealizedDevice(item);
501  found = true;
502  }
503  }
504 
505  if (!found)
506  VDebugLog("No matching device found.");
507 
508  return found;
509 }
510 
524 {
525  // Already have ID and name; nothing to do.
526  if (device.id != -1 && !VuoText_isEmpty(device.name))
527  {
528  setRealizedDevice(device);
529  return true;
530  }
531 
532  // Otherwise, try to find a matching device.
533 
534  VDebugLog("Requested device: %s", json_object_to_json_string(VuoMidiOutputDevice_getJson(device)));
536  VuoLocal(devices);
537  __block bool found = false;
538 
539  // First pass: try to find an exact match by ID.
541  if (device.id != -1 && device.id == item.id)
542  {
543  VDebugLog("Matched by ID: %s",json_object_to_json_string(VuoMidiOutputDevice_getJson(item)));
544  setRealizedDevice(item);
545  found = true;
546  return false;
547  }
548  return true;
549  });
550 
551  // Second pass: try to find a match by name.
552  if (!found)
554  if (!VuoText_isEmpty(device.name) && VuoText_compare(item.name, (VuoTextComparison){VuoTextComparison_Contains, true}, device.name))
555  {
556  VDebugLog("Matched by name: %s",json_object_to_json_string(VuoMidiOutputDevice_getJson(item)));
557  setRealizedDevice(item);
558  found = true;
559  return false;
560  }
561  return true;
562  });
563 
564  // Third pass: if the user hasn't specified a device, use the first device.
565  if (!found && device.id == -1 && VuoText_isEmpty(device.name))
566  {
568  {
570  VDebugLog("Using first device: %s",json_object_to_json_string(VuoMidiOutputDevice_getJson(item)));
571  setRealizedDevice(item);
572  found = true;
573  }
574  }
575 
576  if (!found)
577  VDebugLog("No matching device found.");
578 
579  return found;
580 }