Vuo  2.0.3
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  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  struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)userData;
283  unsigned char channel = ((*message)[0] & 0x0f) + 1;
284  if (((*message)[0] & 0xf0) == 0x80) // Note Off
285  {
286  VuoMidiNote mn = VuoMidiNote_make(channel, false, (*message)[2], (*message)[1]);
287  dispatch_async(mii->callbackQueue, ^{
288  if (mii->receivedNote)
289  mii->receivedNote(mii->context, mn);
290  });
291  }
292  else if (((*message)[0] & 0xf0) == 0x90) // Note On
293  {
294  unsigned char velocity = (*message)[2];
295 
296  // Convert note-on messages with 0 velocity into note-off messages.
297  bool isNoteOn = (velocity != 0);
298 
299  VuoMidiNote mn = VuoMidiNote_make(channel, isNoteOn, velocity, (*message)[1]);
300  dispatch_async(mii->callbackQueue, ^{
301  if (mii->receivedNote)
302  mii->receivedNote(mii->context, mn);
303  });
304  }
305  else if (((*message)[0] & 0xf0) == 0xb0) // Control Change
306  {
307  VuoMidiController mc = VuoMidiController_make(channel, (*message)[1], (*message)[2]);
308  dispatch_async(mii->callbackQueue, ^{
309  if (mii->receivedController)
310  mii->receivedController(mii->context, mc);
311  });
312  }
313  else if (((*message)[0] & 0xf0) == 0xe0) // Pitch Bend Change
314  {
315  VuoMidiPitchBend mp = VuoMidiPitchBend_make(channel, (*message)[1] + ((*message)[2] << 7));
316  dispatch_async(mii->callbackQueue, ^{
317  if (mii->receivedPitchBend)
318  mii->receivedPitchBend(mii->context, mp);
319  });
320  }
321  else
322  {
323  const char *hex = "0123456789abcdef";
324  std::string messageHex;
325  for (size_t i = 0; i < message->size(); ++i)
326  {
327  messageHex.push_back(hex[(*message)[i] >> 4]);
328  messageHex.push_back(hex[(*message)[i] & 0x0f]);
329  }
330  VUserLog("Warning: Received unknown message: 0x%s", messageHex.c_str());
331  }
332 }
333 
335 
340 {
341  struct VuoMidiIn_internal *mii;
342  RtMidiIn *midiin = NULL;
343  try
344  {
345  VuoMidiInputDevice realizedDevice;
346  if (!VuoMidiInputDevice_realize(md, &realizedDevice))
347  throw RtError("No matching device found");
348  VuoMidiInputDevice_retain(realizedDevice);
349 
350  midiin = new RtMidiIn();
351  midiin->openPort(realizedDevice.id, "VuoMidiIn_make");
352 
353  VuoMidiInputDevice_release(realizedDevice);
354 
355  mii = (struct VuoMidiIn_internal *)calloc(1, sizeof(struct VuoMidiIn_internal));
357  mii->midiin = midiin;
358  mii->callbackQueue = dispatch_queue_create("vuo.midi.receive", NULL);
359  // Trigger functions (receiveNote, ...) are NULLed by calloc.
360 
361  midiin->setCallback(&VuoMidiIn_receivedEvent, mii);
362 
363  // Ignore SysEx, timing, and active sensing for now.
364  midiin->ignoreTypes(true,true,true);
365  }
366  catch (RtError &error)
367  {
369  VUserLog("Error: Failed to open the specified MIDI device (%s) :: %s.", VuoMidiInputDevice_getSummary(md), error.what());
370  delete midiin;
371  return NULL;
372  }
373 
374  return (VuoMidiIn)mii;
375 }
376 
383 (
384  VuoMidiIn mi,
385  void (*receivedNote)(void *context, VuoMidiNote note),
386  void (*receivedController)(void *context, VuoMidiController controller),
387  void (*receivedPitchBend)(void *context, VuoMidiPitchBend pitchBend),
388  void *context
389 )
390 {
391  if (!mi)
392  return;
393  struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)mi;
394  dispatch_async(mii->callbackQueue, ^{
395  mii->receivedNote = receivedNote;
396  mii->receivedController = receivedController;
397  mii->receivedPitchBend = receivedPitchBend;
398  mii->context = context;
399  });
400 }
401 
408 {
409  if (!mi)
410  return;
411  struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)mi;
412  dispatch_sync(mii->callbackQueue, ^{
413  mii->receivedNote = NULL;
414  mii->receivedController = NULL;
415  mii->context = NULL;
416  });
417 }
418 
423 {
424  if (!mi)
425  return;
426  struct VuoMidiIn_internal *mii = (struct VuoMidiIn_internal *)mi;
427  dispatch_release(mii->callbackQueue);
428  delete mii->midiin;
429  free(mii);
430 }
431 
433 #define setRealizedDevice(newDevice) \
434  realizedDevice->id = newDevice.id; \
435  realizedDevice->name = VuoText_make(newDevice.name);
436 
450 {
451  // Already have ID and name; nothing to do.
452  if (device.id != -1 && !VuoText_isEmpty(device.name))
453  {
454  setRealizedDevice(device);
455  return true;
456  }
457 
458  // Otherwise, try to find a matching device.
459 
460  VDebugLog("Requested device: %s", json_object_to_json_string(VuoMidiInputDevice_getJson(device)));
462  VuoLocal(devices);
463  __block bool found = false;
464 
465  // First pass: try to find an exact match by ID.
467  if (device.id != -1 && device.id == item.id)
468  {
469  VDebugLog("Matched by ID: %s",json_object_to_json_string(VuoMidiInputDevice_getJson(item)));
470  setRealizedDevice(item);
471  found = true;
472  return false;
473  }
474  return true;
475  });
476 
477  // Second pass: try to find a match by name.
478  if (!found)
480  if (!VuoText_isEmpty(device.name) && VuoText_compare(item.name, (VuoTextComparison){VuoTextComparison_Contains, true, ""}, device.name))
481  {
482  VDebugLog("Matched by name: %s",json_object_to_json_string(VuoMidiInputDevice_getJson(item)));
483  setRealizedDevice(item);
484  found = true;
485  return false;
486  }
487  return true;
488  });
489 
490  // Third pass: if the user hasn't specified a device, use the first device.
491  if (!found && device.id == -1 && VuoText_isEmpty(device.name))
492  {
494  {
496  VDebugLog("Using first device: %s",json_object_to_json_string(VuoMidiInputDevice_getJson(item)));
497  setRealizedDevice(item);
498  found = true;
499  }
500  }
501 
502  if (!found)
503  VDebugLog("No matching device found.");
504 
505  return found;
506 }
507 
521 {
522  // Already have ID and name; nothing to do.
523  if (device.id != -1 && !VuoText_isEmpty(device.name))
524  {
525  setRealizedDevice(device);
526  return true;
527  }
528 
529  // Otherwise, try to find a matching device.
530 
531  VDebugLog("Requested device: %s", json_object_to_json_string(VuoMidiOutputDevice_getJson(device)));
533  VuoLocal(devices);
534  __block bool found = false;
535 
536  // First pass: try to find an exact match by ID.
538  if (device.id != -1 && device.id == item.id)
539  {
540  VDebugLog("Matched by ID: %s",json_object_to_json_string(VuoMidiOutputDevice_getJson(item)));
541  setRealizedDevice(item);
542  found = true;
543  return false;
544  }
545  return true;
546  });
547 
548  // Second pass: try to find a match by name.
549  if (!found)
551  if (!VuoText_isEmpty(device.name) && VuoText_compare(item.name, (VuoTextComparison){VuoTextComparison_Contains, true, ""}, device.name))
552  {
553  VDebugLog("Matched by name: %s",json_object_to_json_string(VuoMidiOutputDevice_getJson(item)));
554  setRealizedDevice(item);
555  found = true;
556  return false;
557  }
558  return true;
559  });
560 
561  // Third pass: if the user hasn't specified a device, use the first device.
562  if (!found && device.id == -1 && VuoText_isEmpty(device.name))
563  {
565  {
567  VDebugLog("Using first device: %s",json_object_to_json_string(VuoMidiOutputDevice_getJson(item)));
568  setRealizedDevice(item);
569  found = true;
570  }
571  }
572 
573  if (!found)
574  VDebugLog("No matching device found.");
575 
576  return found;
577 }