The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

590 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. // (This file gets included by juce_mac_NativeCode.mm, rather than being
  19. // compiled on its own).
  20. #ifdef JUCE_INCLUDED_FILE
  21. //==============================================================================
  22. #undef log
  23. #define log(a) Logger::writeToLog(a)
  24. static bool logAnyErrorsMidi (const OSStatus err, const int lineNum)
  25. {
  26. if (err == noErr)
  27. return true;
  28. log (T("CoreMidi error: ") + String (lineNum) + T(" - ") + String::toHexString ((int)err));
  29. jassertfalse
  30. return false;
  31. }
  32. #undef OK
  33. #define OK(a) logAnyErrorsMidi(a, __LINE__)
  34. //==============================================================================
  35. static const String getEndpointName (MIDIEndpointRef endpoint, bool isExternal)
  36. {
  37. String result;
  38. CFStringRef str = 0;
  39. MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &str);
  40. if (str != 0)
  41. {
  42. result = PlatformUtilities::cfStringToJuceString (str);
  43. CFRelease (str);
  44. str = 0;
  45. }
  46. MIDIEntityRef entity = 0;
  47. MIDIEndpointGetEntity (endpoint, &entity);
  48. if (entity == 0)
  49. return result; // probably virtual
  50. if (result.isEmpty())
  51. {
  52. // endpoint name has zero length - try the entity
  53. MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str);
  54. if (str != 0)
  55. {
  56. result += PlatformUtilities::cfStringToJuceString (str);
  57. CFRelease (str);
  58. str = 0;
  59. }
  60. }
  61. // now consider the device's name
  62. MIDIDeviceRef device = 0;
  63. MIDIEntityGetDevice (entity, &device);
  64. if (device == 0)
  65. return result;
  66. MIDIObjectGetStringProperty (device, kMIDIPropertyName, &str);
  67. if (str != 0)
  68. {
  69. const String s (PlatformUtilities::cfStringToJuceString (str));
  70. CFRelease (str);
  71. // if an external device has only one entity, throw away
  72. // the endpoint name and just use the device name
  73. if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2)
  74. {
  75. result = s;
  76. }
  77. else if (! result.startsWithIgnoreCase (s))
  78. {
  79. // prepend the device name to the entity name
  80. result = (s + T(" ") + result).trimEnd();
  81. }
  82. }
  83. return result;
  84. }
  85. static const String getConnectedEndpointName (MIDIEndpointRef endpoint)
  86. {
  87. String result;
  88. // Does the endpoint have connections?
  89. CFDataRef connections = 0;
  90. int numConnections = 0;
  91. MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections);
  92. if (connections != 0)
  93. {
  94. numConnections = CFDataGetLength (connections) / sizeof (MIDIUniqueID);
  95. if (numConnections > 0)
  96. {
  97. const SInt32* pid = reinterpret_cast <const SInt32*> (CFDataGetBytePtr (connections));
  98. for (int i = 0; i < numConnections; ++i, ++pid)
  99. {
  100. MIDIUniqueID uid = EndianS32_BtoN (*pid);
  101. MIDIObjectRef connObject;
  102. MIDIObjectType connObjectType;
  103. OSStatus err = MIDIObjectFindByUniqueID (uid, &connObject, &connObjectType);
  104. if (err == noErr)
  105. {
  106. String s;
  107. if (connObjectType == kMIDIObjectType_ExternalSource
  108. || connObjectType == kMIDIObjectType_ExternalDestination)
  109. {
  110. // Connected to an external device's endpoint (10.3 and later).
  111. s = getEndpointName (static_cast <MIDIEndpointRef> (connObject), true);
  112. }
  113. else
  114. {
  115. // Connected to an external device (10.2) (or something else, catch-all)
  116. CFStringRef str = 0;
  117. MIDIObjectGetStringProperty (connObject, kMIDIPropertyName, &str);
  118. if (str != 0)
  119. {
  120. s = PlatformUtilities::cfStringToJuceString (str);
  121. CFRelease (str);
  122. }
  123. }
  124. if (s.isNotEmpty())
  125. {
  126. if (result.isNotEmpty())
  127. result += (", ");
  128. result += s;
  129. }
  130. }
  131. }
  132. }
  133. CFRelease (connections);
  134. }
  135. if (result.isNotEmpty())
  136. return result;
  137. // Here, either the endpoint had no connections, or we failed to obtain names for any of them.
  138. return getEndpointName (endpoint, false);
  139. }
  140. //==============================================================================
  141. const StringArray MidiOutput::getDevices()
  142. {
  143. StringArray s;
  144. const ItemCount num = MIDIGetNumberOfDestinations();
  145. for (ItemCount i = 0; i < num; ++i)
  146. {
  147. MIDIEndpointRef dest = MIDIGetDestination (i);
  148. if (dest != 0)
  149. {
  150. String name (getConnectedEndpointName (dest));
  151. if (name.isEmpty())
  152. name = "<error>";
  153. s.add (name);
  154. }
  155. else
  156. {
  157. s.add ("<error>");
  158. }
  159. }
  160. return s;
  161. }
  162. int MidiOutput::getDefaultDeviceIndex()
  163. {
  164. return 0;
  165. }
  166. static MIDIClientRef globalMidiClient;
  167. static bool hasGlobalClientBeenCreated = false;
  168. static bool makeSureClientExists()
  169. {
  170. if (! hasGlobalClientBeenCreated)
  171. {
  172. String name (T("JUCE"));
  173. if (JUCEApplication::getInstance() != 0)
  174. name = JUCEApplication::getInstance()->getApplicationName();
  175. CFStringRef appName = PlatformUtilities::juceStringToCFString (name);
  176. hasGlobalClientBeenCreated = OK (MIDIClientCreate (appName, 0, 0, &globalMidiClient));
  177. CFRelease (appName);
  178. }
  179. return hasGlobalClientBeenCreated;
  180. }
  181. struct MidiPortAndEndpoint
  182. {
  183. MIDIPortRef port;
  184. MIDIEndpointRef endPoint;
  185. };
  186. MidiOutput* MidiOutput::openDevice (int index)
  187. {
  188. MidiOutput* mo = 0;
  189. if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfDestinations())
  190. {
  191. MIDIEndpointRef endPoint = MIDIGetDestination (index);
  192. CFStringRef pname;
  193. if (OK (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname)))
  194. {
  195. log (T("CoreMidi - opening out: ") + PlatformUtilities::cfStringToJuceString (pname));
  196. if (makeSureClientExists())
  197. {
  198. MIDIPortRef port;
  199. if (OK (MIDIOutputPortCreate (globalMidiClient, pname, &port)))
  200. {
  201. MidiPortAndEndpoint* mpe = new MidiPortAndEndpoint();
  202. mpe->port = port;
  203. mpe->endPoint = endPoint;
  204. mo = new MidiOutput();
  205. mo->internal = (void*)mpe;
  206. }
  207. }
  208. CFRelease (pname);
  209. }
  210. }
  211. return mo;
  212. }
  213. MidiOutput::~MidiOutput()
  214. {
  215. MidiPortAndEndpoint* const mpe = (MidiPortAndEndpoint*)internal;
  216. MIDIPortDispose (mpe->port);
  217. delete mpe;
  218. }
  219. void MidiOutput::reset()
  220. {
  221. }
  222. bool MidiOutput::getVolume (float& leftVol, float& rightVol)
  223. {
  224. return false;
  225. }
  226. void MidiOutput::setVolume (float leftVol, float rightVol)
  227. {
  228. }
  229. void MidiOutput::sendMessageNow (const MidiMessage& message)
  230. {
  231. MidiPortAndEndpoint* const mpe = (MidiPortAndEndpoint*)internal;
  232. if (message.isSysEx())
  233. {
  234. const int maxPacketSize = 256;
  235. int pos = 0, bytesLeft = message.getRawDataSize();
  236. const int numPackets = (bytesLeft + maxPacketSize - 1) / maxPacketSize;
  237. MIDIPacketList* const packets = (MIDIPacketList*) juce_malloc (32 * numPackets + message.getRawDataSize());
  238. packets->numPackets = numPackets;
  239. MIDIPacket* p = packets->packet;
  240. for (int i = 0; i < numPackets; ++i)
  241. {
  242. p->timeStamp = 0;
  243. p->length = jmin (maxPacketSize, bytesLeft);
  244. memcpy (p->data, message.getRawData() + pos, p->length);
  245. pos += p->length;
  246. bytesLeft -= p->length;
  247. p = MIDIPacketNext (p);
  248. }
  249. MIDISend (mpe->port, mpe->endPoint, packets);
  250. juce_free (packets);
  251. }
  252. else
  253. {
  254. MIDIPacketList packets;
  255. packets.numPackets = 1;
  256. packets.packet[0].timeStamp = 0;
  257. packets.packet[0].length = message.getRawDataSize();
  258. *(int*) (packets.packet[0].data) = *(const int*) message.getRawData();
  259. MIDISend (mpe->port, mpe->endPoint, &packets);
  260. }
  261. }
  262. //==============================================================================
  263. const StringArray MidiInput::getDevices()
  264. {
  265. StringArray s;
  266. const ItemCount num = MIDIGetNumberOfSources();
  267. for (ItemCount i = 0; i < num; ++i)
  268. {
  269. MIDIEndpointRef source = MIDIGetSource (i);
  270. if (source != 0)
  271. {
  272. String name (getConnectedEndpointName (source));
  273. if (name.isEmpty())
  274. name = "<error>";
  275. s.add (name);
  276. }
  277. else
  278. {
  279. s.add ("<error>");
  280. }
  281. }
  282. return s;
  283. }
  284. int MidiInput::getDefaultDeviceIndex()
  285. {
  286. return 0;
  287. }
  288. //==============================================================================
  289. struct MidiPortAndCallback
  290. {
  291. MidiInput* input;
  292. MIDIPortRef port;
  293. MIDIEndpointRef endPoint;
  294. MidiInputCallback* callback;
  295. MemoryBlock pendingData;
  296. int pendingBytes;
  297. double pendingDataTime;
  298. bool active;
  299. };
  300. static CriticalSection callbackLock;
  301. static VoidArray activeCallbacks;
  302. static void processSysex (MidiPortAndCallback* const mpe, const uint8*& d, int& size, const double time)
  303. {
  304. if (*d == 0xf0)
  305. {
  306. mpe->pendingBytes = 0;
  307. mpe->pendingDataTime = time;
  308. }
  309. mpe->pendingData.ensureSize (mpe->pendingBytes + size, false);
  310. uint8* totalMessage = (uint8*) mpe->pendingData.getData();
  311. uint8* dest = totalMessage + mpe->pendingBytes;
  312. while (size > 0)
  313. {
  314. if (mpe->pendingBytes > 0 && *d >= 0x80)
  315. {
  316. if (*d >= 0xfa || *d == 0xf8)
  317. {
  318. mpe->callback->handleIncomingMidiMessage (mpe->input, MidiMessage (*d, time));
  319. ++d;
  320. --size;
  321. }
  322. else
  323. {
  324. if (*d == 0xf7)
  325. {
  326. *dest++ = *d++;
  327. mpe->pendingBytes++;
  328. --size;
  329. }
  330. break;
  331. }
  332. }
  333. else
  334. {
  335. *dest++ = *d++;
  336. mpe->pendingBytes++;
  337. --size;
  338. }
  339. }
  340. if (totalMessage [mpe->pendingBytes - 1] == 0xf7)
  341. {
  342. mpe->callback->handleIncomingMidiMessage (mpe->input, MidiMessage (totalMessage,
  343. mpe->pendingBytes,
  344. mpe->pendingDataTime));
  345. mpe->pendingBytes = 0;
  346. }
  347. else
  348. {
  349. mpe->callback->handlePartialSysexMessage (mpe->input,
  350. totalMessage,
  351. mpe->pendingBytes,
  352. mpe->pendingDataTime);
  353. }
  354. }
  355. static void midiInputProc (const MIDIPacketList* pktlist,
  356. void* readProcRefCon,
  357. void* srcConnRefCon)
  358. {
  359. double time = Time::getMillisecondCounterHiRes() * 0.001;
  360. const double originalTime = time;
  361. MidiPortAndCallback* const mpe = (MidiPortAndCallback*) readProcRefCon;
  362. const ScopedLock sl (callbackLock);
  363. if (activeCallbacks.contains (mpe) && mpe->active)
  364. {
  365. const MIDIPacket* packet = &pktlist->packet[0];
  366. for (unsigned int i = 0; i < pktlist->numPackets; ++i)
  367. {
  368. const uint8* d = (const uint8*) (packet->data);
  369. int size = packet->length;
  370. while (size > 0)
  371. {
  372. time = originalTime;
  373. if (mpe->pendingBytes > 0 || d[0] == 0xf0)
  374. {
  375. processSysex (mpe, d, size, time);
  376. }
  377. else
  378. {
  379. int used = 0;
  380. const MidiMessage m (d, size, used, 0, time);
  381. if (used <= 0)
  382. {
  383. jassertfalse // malformed midi message
  384. break;
  385. }
  386. else
  387. {
  388. mpe->callback->handleIncomingMidiMessage (mpe->input, m);
  389. }
  390. size -= used;
  391. d += used;
  392. }
  393. }
  394. packet = MIDIPacketNext (packet);
  395. }
  396. }
  397. }
  398. MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
  399. {
  400. MidiInput* mi = 0;
  401. if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfSources())
  402. {
  403. MIDIEndpointRef endPoint = MIDIGetSource (index);
  404. if (endPoint != 0)
  405. {
  406. CFStringRef pname;
  407. if (OK (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname)))
  408. {
  409. log (T("CoreMidi - opening inp: ") + PlatformUtilities::cfStringToJuceString (pname));
  410. if (makeSureClientExists())
  411. {
  412. MIDIPortRef port;
  413. MidiPortAndCallback* const mpe = new MidiPortAndCallback();
  414. mpe->active = false;
  415. if (OK (MIDIInputPortCreate (globalMidiClient, pname, midiInputProc, mpe, &port)))
  416. {
  417. if (OK (MIDIPortConnectSource (port, endPoint, 0)))
  418. {
  419. mpe->port = port;
  420. mpe->endPoint = endPoint;
  421. mpe->callback = callback;
  422. mpe->pendingBytes = 0;
  423. mpe->pendingData.ensureSize (128);
  424. mi = new MidiInput (getDevices() [index]);
  425. mpe->input = mi;
  426. mi->internal = (void*) mpe;
  427. const ScopedLock sl (callbackLock);
  428. activeCallbacks.add (mpe);
  429. }
  430. else
  431. {
  432. OK (MIDIPortDispose (port));
  433. delete mpe;
  434. }
  435. }
  436. else
  437. {
  438. delete mpe;
  439. }
  440. }
  441. }
  442. CFRelease (pname);
  443. }
  444. }
  445. return mi;
  446. }
  447. MidiInput::MidiInput (const String& name_)
  448. : name (name_)
  449. {
  450. }
  451. MidiInput::~MidiInput()
  452. {
  453. MidiPortAndCallback* const mpe = (MidiPortAndCallback*) internal;
  454. mpe->active = false;
  455. callbackLock.enter();
  456. activeCallbacks.removeValue (mpe);
  457. callbackLock.exit();
  458. OK (MIDIPortDisconnectSource (mpe->port, mpe->endPoint));
  459. OK (MIDIPortDispose (mpe->port));
  460. delete mpe;
  461. }
  462. void MidiInput::start()
  463. {
  464. MidiPortAndCallback* const mpe = (MidiPortAndCallback*) internal;
  465. const ScopedLock sl (callbackLock);
  466. mpe->active = true;
  467. }
  468. void MidiInput::stop()
  469. {
  470. MidiPortAndCallback* const mpe = (MidiPortAndCallback*) internal;
  471. const ScopedLock sl (callbackLock);
  472. mpe->active = false;
  473. }
  474. #undef log
  475. #endif