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.

662 lines
20KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-10 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. #if JUCE_INCLUDED_FILE
  21. #if JUCE_MAC
  22. //==============================================================================
  23. namespace CoreMidiHelpers
  24. {
  25. static bool logError (const OSStatus err, const int lineNum)
  26. {
  27. if (err == noErr)
  28. return true;
  29. Logger::writeToLog ("CoreMidi error: " + String (lineNum) + " - " + String::toHexString ((int) err));
  30. jassertfalse;
  31. return false;
  32. }
  33. #undef CHECK_ERROR
  34. #define CHECK_ERROR(a) CoreMidiHelpers::logError (a, __LINE__)
  35. //==============================================================================
  36. static const String getEndpointName (MIDIEndpointRef endpoint, bool isExternal)
  37. {
  38. String result;
  39. CFStringRef str = 0;
  40. MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &str);
  41. if (str != 0)
  42. {
  43. result = PlatformUtilities::cfStringToJuceString (str);
  44. CFRelease (str);
  45. str = 0;
  46. }
  47. MIDIEntityRef entity = 0;
  48. MIDIEndpointGetEntity (endpoint, &entity);
  49. if (entity == 0)
  50. return result; // probably virtual
  51. if (result.isEmpty())
  52. {
  53. // endpoint name has zero length - try the entity
  54. MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str);
  55. if (str != 0)
  56. {
  57. result += PlatformUtilities::cfStringToJuceString (str);
  58. CFRelease (str);
  59. str = 0;
  60. }
  61. }
  62. // now consider the device's name
  63. MIDIDeviceRef device = 0;
  64. MIDIEntityGetDevice (entity, &device);
  65. if (device == 0)
  66. return result;
  67. MIDIObjectGetStringProperty (device, kMIDIPropertyName, &str);
  68. if (str != 0)
  69. {
  70. const String s (PlatformUtilities::cfStringToJuceString (str));
  71. CFRelease (str);
  72. // if an external device has only one entity, throw away
  73. // the endpoint name and just use the device name
  74. if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2)
  75. {
  76. result = s;
  77. }
  78. else if (! result.startsWithIgnoreCase (s))
  79. {
  80. // prepend the device name to the entity name
  81. result = (s + " " + result).trimEnd();
  82. }
  83. }
  84. return result;
  85. }
  86. static const String getConnectedEndpointName (MIDIEndpointRef endpoint)
  87. {
  88. String result;
  89. // Does the endpoint have connections?
  90. CFDataRef connections = 0;
  91. int numConnections = 0;
  92. MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections);
  93. if (connections != 0)
  94. {
  95. numConnections = (int) (CFDataGetLength (connections) / sizeof (MIDIUniqueID));
  96. if (numConnections > 0)
  97. {
  98. const SInt32* pid = reinterpret_cast <const SInt32*> (CFDataGetBytePtr (connections));
  99. for (int i = 0; i < numConnections; ++i, ++pid)
  100. {
  101. MIDIUniqueID uid = EndianS32_BtoN (*pid);
  102. MIDIObjectRef connObject;
  103. MIDIObjectType connObjectType;
  104. OSStatus err = MIDIObjectFindByUniqueID (uid, &connObject, &connObjectType);
  105. if (err == noErr)
  106. {
  107. String s;
  108. if (connObjectType == kMIDIObjectType_ExternalSource
  109. || connObjectType == kMIDIObjectType_ExternalDestination)
  110. {
  111. // Connected to an external device's endpoint (10.3 and later).
  112. s = getEndpointName (static_cast <MIDIEndpointRef> (connObject), true);
  113. }
  114. else
  115. {
  116. // Connected to an external device (10.2) (or something else, catch-all)
  117. CFStringRef str = 0;
  118. MIDIObjectGetStringProperty (connObject, kMIDIPropertyName, &str);
  119. if (str != 0)
  120. {
  121. s = PlatformUtilities::cfStringToJuceString (str);
  122. CFRelease (str);
  123. }
  124. }
  125. if (s.isNotEmpty())
  126. {
  127. if (result.isNotEmpty())
  128. result += ", ";
  129. result += s;
  130. }
  131. }
  132. }
  133. }
  134. CFRelease (connections);
  135. }
  136. if (result.isNotEmpty())
  137. return result;
  138. // Here, either the endpoint had no connections, or we failed to obtain names for any of them.
  139. return getEndpointName (endpoint, false);
  140. }
  141. static MIDIClientRef getGlobalMidiClient()
  142. {
  143. static MIDIClientRef globalMidiClient = 0;
  144. if (globalMidiClient == 0)
  145. {
  146. String name ("JUCE");
  147. if (JUCEApplication::getInstance() != 0)
  148. name = JUCEApplication::getInstance()->getApplicationName();
  149. CFStringRef appName = PlatformUtilities::juceStringToCFString (name);
  150. CHECK_ERROR (MIDIClientCreate (appName, 0, 0, &globalMidiClient));
  151. CFRelease (appName);
  152. }
  153. return globalMidiClient;
  154. }
  155. //==============================================================================
  156. class MidiPortAndEndpoint
  157. {
  158. public:
  159. MidiPortAndEndpoint (MIDIPortRef port_, MIDIEndpointRef endPoint_)
  160. : port (port_), endPoint (endPoint_)
  161. {
  162. }
  163. ~MidiPortAndEndpoint()
  164. {
  165. if (port != 0)
  166. MIDIPortDispose (port);
  167. if (port == 0 && endPoint != 0) // if port == 0, it means we created the endpoint, so it's safe to delete it
  168. MIDIEndpointDispose (endPoint);
  169. }
  170. MIDIPortRef port;
  171. MIDIEndpointRef endPoint;
  172. };
  173. //==============================================================================
  174. class MidiPortAndCallback
  175. {
  176. public:
  177. MidiInput* input;
  178. MidiPortAndEndpoint* portAndEndpoint;
  179. MidiInputCallback* callback;
  180. MemoryBlock pendingData;
  181. int pendingBytes;
  182. double pendingDataTime;
  183. bool active;
  184. void processSysex (const uint8*& d, int& size, const double time)
  185. {
  186. if (*d == 0xf0)
  187. {
  188. pendingBytes = 0;
  189. pendingDataTime = time;
  190. }
  191. pendingData.ensureSize (pendingBytes + size, false);
  192. uint8* totalMessage = (uint8*) pendingData.getData();
  193. uint8* dest = totalMessage + pendingBytes;
  194. while (size > 0)
  195. {
  196. if (pendingBytes > 0 && *d >= 0x80)
  197. {
  198. if (*d >= 0xfa || *d == 0xf8)
  199. {
  200. callback->handleIncomingMidiMessage (input, MidiMessage (*d, time));
  201. ++d;
  202. --size;
  203. }
  204. else
  205. {
  206. if (*d == 0xf7)
  207. {
  208. *dest++ = *d++;
  209. pendingBytes++;
  210. --size;
  211. }
  212. break;
  213. }
  214. }
  215. else
  216. {
  217. *dest++ = *d++;
  218. pendingBytes++;
  219. --size;
  220. }
  221. }
  222. if (totalMessage [pendingBytes - 1] == 0xf7)
  223. {
  224. callback->handleIncomingMidiMessage (input, MidiMessage (totalMessage, pendingBytes, pendingDataTime));
  225. pendingBytes = 0;
  226. }
  227. else
  228. {
  229. callback->handlePartialSysexMessage (input, totalMessage, pendingBytes, pendingDataTime);
  230. }
  231. }
  232. };
  233. static CriticalSection callbackLock;
  234. static Array<void*> activeCallbacks;
  235. static void midiInputProc (const MIDIPacketList* pktlist,
  236. void* readProcRefCon,
  237. void* /*srcConnRefCon*/)
  238. {
  239. double time = Time::getMillisecondCounterHiRes() * 0.001;
  240. const double originalTime = time;
  241. MidiPortAndCallback* const mpc = (MidiPortAndCallback*) readProcRefCon;
  242. const ScopedLock sl (CoreMidiHelpers::callbackLock);
  243. if (CoreMidiHelpers::activeCallbacks.contains (mpc) && mpc->active)
  244. {
  245. const MIDIPacket* packet = &pktlist->packet[0];
  246. for (unsigned int i = 0; i < pktlist->numPackets; ++i)
  247. {
  248. const uint8* d = (const uint8*) (packet->data);
  249. int size = packet->length;
  250. while (size > 0)
  251. {
  252. time = originalTime;
  253. if (mpc->pendingBytes > 0 || d[0] == 0xf0)
  254. {
  255. mpc->processSysex (d, size, time);
  256. }
  257. else
  258. {
  259. int used = 0;
  260. const MidiMessage m (d, size, used, 0, time);
  261. if (used <= 0)
  262. {
  263. jassertfalse; // malformed midi message
  264. break;
  265. }
  266. else
  267. {
  268. mpc->callback->handleIncomingMidiMessage (mpc->input, m);
  269. }
  270. size -= used;
  271. d += used;
  272. }
  273. }
  274. packet = MIDIPacketNext (packet);
  275. }
  276. }
  277. }
  278. }
  279. //==============================================================================
  280. const StringArray MidiOutput::getDevices()
  281. {
  282. StringArray s;
  283. const ItemCount num = MIDIGetNumberOfDestinations();
  284. for (ItemCount i = 0; i < num; ++i)
  285. {
  286. MIDIEndpointRef dest = MIDIGetDestination (i);
  287. if (dest != 0)
  288. {
  289. String name (CoreMidiHelpers::getConnectedEndpointName (dest));
  290. if (name.isEmpty())
  291. name = "<error>";
  292. s.add (name);
  293. }
  294. else
  295. {
  296. s.add ("<error>");
  297. }
  298. }
  299. return s;
  300. }
  301. int MidiOutput::getDefaultDeviceIndex()
  302. {
  303. return 0;
  304. }
  305. MidiOutput* MidiOutput::openDevice (int index)
  306. {
  307. MidiOutput* mo = 0;
  308. if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfDestinations())
  309. {
  310. MIDIEndpointRef endPoint = MIDIGetDestination (index);
  311. CFStringRef pname;
  312. if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname)))
  313. {
  314. MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient();
  315. MIDIPortRef port;
  316. if (client != 0 && CHECK_ERROR (MIDIOutputPortCreate (client, pname, &port)))
  317. {
  318. mo = new MidiOutput();
  319. mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint);
  320. }
  321. CFRelease (pname);
  322. }
  323. }
  324. return mo;
  325. }
  326. MidiOutput* MidiOutput::createNewDevice (const String& deviceName)
  327. {
  328. MidiOutput* mo = 0;
  329. MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient();
  330. MIDIEndpointRef endPoint;
  331. CFStringRef name = PlatformUtilities::juceStringToCFString (deviceName);
  332. if (client != 0 && CHECK_ERROR (MIDISourceCreate (client, name, &endPoint)))
  333. {
  334. mo = new MidiOutput();
  335. mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint);
  336. }
  337. CFRelease (name);
  338. return mo;
  339. }
  340. MidiOutput::~MidiOutput()
  341. {
  342. delete static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal);
  343. }
  344. void MidiOutput::reset()
  345. {
  346. }
  347. bool MidiOutput::getVolume (float& /*leftVol*/, float& /*rightVol*/)
  348. {
  349. return false;
  350. }
  351. void MidiOutput::setVolume (float /*leftVol*/, float /*rightVol*/)
  352. {
  353. }
  354. void MidiOutput::sendMessageNow (const MidiMessage& message)
  355. {
  356. CoreMidiHelpers::MidiPortAndEndpoint* const mpe = static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal);
  357. if (message.isSysEx())
  358. {
  359. const int maxPacketSize = 256;
  360. int pos = 0, bytesLeft = message.getRawDataSize();
  361. const int numPackets = (bytesLeft + maxPacketSize - 1) / maxPacketSize;
  362. HeapBlock <MIDIPacketList> packets;
  363. packets.malloc (32 * numPackets + message.getRawDataSize(), 1);
  364. packets->numPackets = numPackets;
  365. MIDIPacket* p = packets->packet;
  366. for (int i = 0; i < numPackets; ++i)
  367. {
  368. p->timeStamp = 0;
  369. p->length = jmin (maxPacketSize, bytesLeft);
  370. memcpy (p->data, message.getRawData() + pos, p->length);
  371. pos += p->length;
  372. bytesLeft -= p->length;
  373. p = MIDIPacketNext (p);
  374. }
  375. if (mpe->port != 0)
  376. MIDISend (mpe->port, mpe->endPoint, packets);
  377. else
  378. MIDIReceived (mpe->endPoint, packets);
  379. }
  380. else
  381. {
  382. MIDIPacketList packets;
  383. packets.numPackets = 1;
  384. packets.packet[0].timeStamp = 0;
  385. packets.packet[0].length = message.getRawDataSize();
  386. *(int*) (packets.packet[0].data) = *(const int*) message.getRawData();
  387. if (mpe->port != 0)
  388. MIDISend (mpe->port, mpe->endPoint, &packets);
  389. else
  390. MIDIReceived (mpe->endPoint, &packets);
  391. }
  392. }
  393. //==============================================================================
  394. const StringArray MidiInput::getDevices()
  395. {
  396. StringArray s;
  397. const ItemCount num = MIDIGetNumberOfSources();
  398. for (ItemCount i = 0; i < num; ++i)
  399. {
  400. MIDIEndpointRef source = MIDIGetSource (i);
  401. if (source != 0)
  402. {
  403. String name (CoreMidiHelpers::getConnectedEndpointName (source));
  404. if (name.isEmpty())
  405. name = "<error>";
  406. s.add (name);
  407. }
  408. else
  409. {
  410. s.add ("<error>");
  411. }
  412. }
  413. return s;
  414. }
  415. int MidiInput::getDefaultDeviceIndex()
  416. {
  417. return 0;
  418. }
  419. MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
  420. {
  421. using namespace CoreMidiHelpers;
  422. MidiInput* mi = 0;
  423. if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfSources())
  424. {
  425. MIDIEndpointRef endPoint = MIDIGetSource (index);
  426. if (endPoint != 0)
  427. {
  428. CFStringRef pname;
  429. if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname)))
  430. {
  431. MIDIClientRef client = getGlobalMidiClient();
  432. if (client != 0)
  433. {
  434. MIDIPortRef port;
  435. ScopedPointer <MidiPortAndCallback> mpc (new MidiPortAndCallback());
  436. mpc->active = false;
  437. if (CHECK_ERROR (MIDIInputPortCreate (client, pname, midiInputProc, mpc, &port)))
  438. {
  439. if (CHECK_ERROR (MIDIPortConnectSource (port, endPoint, 0)))
  440. {
  441. mpc->portAndEndpoint = new MidiPortAndEndpoint (port, endPoint);
  442. mpc->callback = callback;
  443. mpc->pendingBytes = 0;
  444. mpc->pendingData.ensureSize (128);
  445. mi = new MidiInput (getDevices() [index]);
  446. mpc->input = mi;
  447. mi->internal = mpc;
  448. const ScopedLock sl (callbackLock);
  449. activeCallbacks.add (mpc.release());
  450. }
  451. else
  452. {
  453. CHECK_ERROR (MIDIPortDispose (port));
  454. }
  455. }
  456. }
  457. }
  458. CFRelease (pname);
  459. }
  460. }
  461. return mi;
  462. }
  463. MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
  464. {
  465. using namespace CoreMidiHelpers;
  466. MidiInput* mi = 0;
  467. MIDIClientRef client = getGlobalMidiClient();
  468. if (client != 0)
  469. {
  470. ScopedPointer <MidiPortAndCallback> mpc (new MidiPortAndCallback());
  471. mpc->active = false;
  472. MIDIEndpointRef endPoint;
  473. CFStringRef name = PlatformUtilities::juceStringToCFString(deviceName);
  474. if (CHECK_ERROR (MIDIDestinationCreate (client, name, midiInputProc, mpc, &endPoint)))
  475. {
  476. mpc->portAndEndpoint = new MidiPortAndEndpoint (0, endPoint);
  477. mpc->callback = callback;
  478. mpc->pendingBytes = 0;
  479. mpc->pendingData.ensureSize (128);
  480. mi = new MidiInput (deviceName);
  481. mpc->input = mi;
  482. mi->internal = mpc;
  483. const ScopedLock sl (callbackLock);
  484. activeCallbacks.add (mpc.release());
  485. }
  486. CFRelease (name);
  487. }
  488. return mi;
  489. }
  490. MidiInput::MidiInput (const String& name_)
  491. : name (name_)
  492. {
  493. }
  494. MidiInput::~MidiInput()
  495. {
  496. using namespace CoreMidiHelpers;
  497. MidiPortAndCallback* const mpc = static_cast<MidiPortAndCallback*> (internal);
  498. mpc->active = false;
  499. {
  500. const ScopedLock sl (callbackLock);
  501. activeCallbacks.removeValue (mpc);
  502. }
  503. if (mpc->portAndEndpoint->port != 0)
  504. CHECK_ERROR (MIDIPortDisconnectSource (mpc->portAndEndpoint->port, mpc->portAndEndpoint->endPoint));
  505. delete mpc->portAndEndpoint;
  506. delete mpc;
  507. }
  508. void MidiInput::start()
  509. {
  510. const ScopedLock sl (CoreMidiHelpers::callbackLock);
  511. static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = true;
  512. }
  513. void MidiInput::stop()
  514. {
  515. const ScopedLock sl (CoreMidiHelpers::callbackLock);
  516. static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = false;
  517. }
  518. #undef CHECK_ERROR
  519. //==============================================================================
  520. #else // Stubs for iOS...
  521. MidiOutput::~MidiOutput() {}
  522. void MidiOutput::reset() {}
  523. bool MidiOutput::getVolume (float& /*leftVol*/, float& /*rightVol*/) { return false; }
  524. void MidiOutput::setVolume (float /*leftVol*/, float /*rightVol*/) {}
  525. void MidiOutput::sendMessageNow (const MidiMessage& message) {}
  526. const StringArray MidiOutput::getDevices() { return StringArray(); }
  527. MidiOutput* MidiOutput::openDevice (int index) { return 0; }
  528. const StringArray MidiInput::getDevices() { return StringArray(); }
  529. MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) { return 0; }
  530. #endif
  531. #endif