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.

711 lines
19KB

  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. #if JUCE_INCLUDED_FILE
  21. #if JUCE_MAC
  22. //==============================================================================
  23. #undef log
  24. #define log(a) Logger::writeToLog(a)
  25. static bool logAnyErrorsMidi (const OSStatus err, const int lineNum)
  26. {
  27. if (err == noErr)
  28. return true;
  29. log (T("CoreMidi error: ") + String (lineNum) + T(" - ") + String::toHexString ((int)err));
  30. jassertfalse
  31. return false;
  32. }
  33. #undef OK
  34. #define OK(a) logAnyErrorsMidi(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 + T(" ") + 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 = 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. //==============================================================================
  142. const StringArray MidiOutput::getDevices()
  143. {
  144. StringArray s;
  145. const ItemCount num = MIDIGetNumberOfDestinations();
  146. for (ItemCount i = 0; i < num; ++i)
  147. {
  148. MIDIEndpointRef dest = MIDIGetDestination (i);
  149. if (dest != 0)
  150. {
  151. String name (getConnectedEndpointName (dest));
  152. if (name.isEmpty())
  153. name = "<error>";
  154. s.add (name);
  155. }
  156. else
  157. {
  158. s.add ("<error>");
  159. }
  160. }
  161. return s;
  162. }
  163. int MidiOutput::getDefaultDeviceIndex()
  164. {
  165. return 0;
  166. }
  167. static MIDIClientRef globalMidiClient;
  168. static bool hasGlobalClientBeenCreated = false;
  169. static bool makeSureClientExists()
  170. {
  171. if (! hasGlobalClientBeenCreated)
  172. {
  173. String name (T("JUCE"));
  174. if (JUCEApplication::getInstance() != 0)
  175. name = JUCEApplication::getInstance()->getApplicationName();
  176. CFStringRef appName = PlatformUtilities::juceStringToCFString (name);
  177. hasGlobalClientBeenCreated = OK (MIDIClientCreate (appName, 0, 0, &globalMidiClient));
  178. CFRelease (appName);
  179. }
  180. return hasGlobalClientBeenCreated;
  181. }
  182. class MidiPortAndEndpoint
  183. {
  184. public:
  185. MidiPortAndEndpoint (MIDIPortRef port_, MIDIEndpointRef endPoint_) throw()
  186. : port (port_), endPoint (endPoint_)
  187. {
  188. }
  189. ~MidiPortAndEndpoint() throw()
  190. {
  191. if (port != 0)
  192. MIDIPortDispose (port);
  193. if (port == 0 && endPoint != 0) // if port == 0, it means we created the endpoint, so it's safe to delete it
  194. MIDIEndpointDispose (endPoint);
  195. }
  196. MIDIPortRef port;
  197. MIDIEndpointRef endPoint;
  198. };
  199. MidiOutput* MidiOutput::openDevice (int index)
  200. {
  201. MidiOutput* mo = 0;
  202. if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfDestinations())
  203. {
  204. MIDIEndpointRef endPoint = MIDIGetDestination (index);
  205. CFStringRef pname;
  206. if (OK (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname)))
  207. {
  208. log (T("CoreMidi - opening out: ") + PlatformUtilities::cfStringToJuceString (pname));
  209. if (makeSureClientExists())
  210. {
  211. MIDIPortRef port;
  212. if (OK (MIDIOutputPortCreate (globalMidiClient, pname, &port)))
  213. {
  214. mo = new MidiOutput();
  215. mo->internal = (void*) new MidiPortAndEndpoint (port, endPoint);
  216. }
  217. }
  218. CFRelease (pname);
  219. }
  220. }
  221. return mo;
  222. }
  223. MidiOutput* MidiOutput::createNewDevice (const String& deviceName)
  224. {
  225. MidiOutput* mo = 0;
  226. if (makeSureClientExists())
  227. {
  228. MIDIEndpointRef endPoint;
  229. CFStringRef name = PlatformUtilities::juceStringToCFString (deviceName);
  230. if (OK (MIDISourceCreate (globalMidiClient, name, &endPoint)))
  231. {
  232. mo = new MidiOutput();
  233. mo->internal = (void*) new MidiPortAndEndpoint (0, endPoint);
  234. }
  235. CFRelease (name);
  236. }
  237. return mo;
  238. }
  239. MidiOutput::~MidiOutput()
  240. {
  241. MidiPortAndEndpoint* const mpe = (MidiPortAndEndpoint*) internal;
  242. delete mpe;
  243. }
  244. void MidiOutput::reset()
  245. {
  246. }
  247. bool MidiOutput::getVolume (float& leftVol, float& rightVol)
  248. {
  249. return false;
  250. }
  251. void MidiOutput::setVolume (float leftVol, float rightVol)
  252. {
  253. }
  254. void MidiOutput::sendMessageNow (const MidiMessage& message)
  255. {
  256. MidiPortAndEndpoint* const mpe = (MidiPortAndEndpoint*) internal;
  257. if (message.isSysEx())
  258. {
  259. const int maxPacketSize = 256;
  260. int pos = 0, bytesLeft = message.getRawDataSize();
  261. const int numPackets = (bytesLeft + maxPacketSize - 1) / maxPacketSize;
  262. HeapBlock <MIDIPacketList> packets;
  263. packets.malloc (32 * numPackets + message.getRawDataSize(), 1);
  264. packets->numPackets = numPackets;
  265. MIDIPacket* p = packets->packet;
  266. for (int i = 0; i < numPackets; ++i)
  267. {
  268. p->timeStamp = 0;
  269. p->length = jmin (maxPacketSize, bytesLeft);
  270. memcpy (p->data, message.getRawData() + pos, p->length);
  271. pos += p->length;
  272. bytesLeft -= p->length;
  273. p = MIDIPacketNext (p);
  274. }
  275. if (mpe->port != 0)
  276. MIDISend (mpe->port, mpe->endPoint, packets);
  277. else
  278. MIDIReceived (mpe->endPoint, packets);
  279. }
  280. else
  281. {
  282. MIDIPacketList packets;
  283. packets.numPackets = 1;
  284. packets.packet[0].timeStamp = 0;
  285. packets.packet[0].length = message.getRawDataSize();
  286. *(int*) (packets.packet[0].data) = *(const int*) message.getRawData();
  287. if (mpe->port != 0)
  288. MIDISend (mpe->port, mpe->endPoint, &packets);
  289. else
  290. MIDIReceived (mpe->endPoint, &packets);
  291. }
  292. }
  293. //==============================================================================
  294. const StringArray MidiInput::getDevices()
  295. {
  296. StringArray s;
  297. const ItemCount num = MIDIGetNumberOfSources();
  298. for (ItemCount i = 0; i < num; ++i)
  299. {
  300. MIDIEndpointRef source = MIDIGetSource (i);
  301. if (source != 0)
  302. {
  303. String name (getConnectedEndpointName (source));
  304. if (name.isEmpty())
  305. name = "<error>";
  306. s.add (name);
  307. }
  308. else
  309. {
  310. s.add ("<error>");
  311. }
  312. }
  313. return s;
  314. }
  315. int MidiInput::getDefaultDeviceIndex()
  316. {
  317. return 0;
  318. }
  319. //==============================================================================
  320. struct MidiPortAndCallback
  321. {
  322. MidiInput* input;
  323. MidiPortAndEndpoint* portAndEndpoint;
  324. MidiInputCallback* callback;
  325. MemoryBlock pendingData;
  326. int pendingBytes;
  327. double pendingDataTime;
  328. bool active;
  329. };
  330. static CriticalSection callbackLock;
  331. static VoidArray activeCallbacks;
  332. static void processSysex (MidiPortAndCallback* const mpc, const uint8*& d, int& size, const double time)
  333. {
  334. if (*d == 0xf0)
  335. {
  336. mpc->pendingBytes = 0;
  337. mpc->pendingDataTime = time;
  338. }
  339. mpc->pendingData.ensureSize (mpc->pendingBytes + size, false);
  340. uint8* totalMessage = (uint8*) mpc->pendingData.getData();
  341. uint8* dest = totalMessage + mpc->pendingBytes;
  342. while (size > 0)
  343. {
  344. if (mpc->pendingBytes > 0 && *d >= 0x80)
  345. {
  346. if (*d >= 0xfa || *d == 0xf8)
  347. {
  348. mpc->callback->handleIncomingMidiMessage (mpc->input, MidiMessage (*d, time));
  349. ++d;
  350. --size;
  351. }
  352. else
  353. {
  354. if (*d == 0xf7)
  355. {
  356. *dest++ = *d++;
  357. mpc->pendingBytes++;
  358. --size;
  359. }
  360. break;
  361. }
  362. }
  363. else
  364. {
  365. *dest++ = *d++;
  366. mpc->pendingBytes++;
  367. --size;
  368. }
  369. }
  370. if (totalMessage [mpc->pendingBytes - 1] == 0xf7)
  371. {
  372. mpc->callback->handleIncomingMidiMessage (mpc->input, MidiMessage (totalMessage,
  373. mpc->pendingBytes,
  374. mpc->pendingDataTime));
  375. mpc->pendingBytes = 0;
  376. }
  377. else
  378. {
  379. mpc->callback->handlePartialSysexMessage (mpc->input,
  380. totalMessage,
  381. mpc->pendingBytes,
  382. mpc->pendingDataTime);
  383. }
  384. }
  385. static void midiInputProc (const MIDIPacketList* pktlist,
  386. void* readProcRefCon,
  387. void* srcConnRefCon)
  388. {
  389. double time = Time::getMillisecondCounterHiRes() * 0.001;
  390. const double originalTime = time;
  391. MidiPortAndCallback* const mpc = (MidiPortAndCallback*) readProcRefCon;
  392. const ScopedLock sl (callbackLock);
  393. if (activeCallbacks.contains (mpc) && mpc->active)
  394. {
  395. const MIDIPacket* packet = &pktlist->packet[0];
  396. for (unsigned int i = 0; i < pktlist->numPackets; ++i)
  397. {
  398. const uint8* d = (const uint8*) (packet->data);
  399. int size = packet->length;
  400. while (size > 0)
  401. {
  402. time = originalTime;
  403. if (mpc->pendingBytes > 0 || d[0] == 0xf0)
  404. {
  405. processSysex (mpc, d, size, time);
  406. }
  407. else
  408. {
  409. int used = 0;
  410. const MidiMessage m (d, size, used, 0, time);
  411. if (used <= 0)
  412. {
  413. jassertfalse // malformed midi message
  414. break;
  415. }
  416. else
  417. {
  418. mpc->callback->handleIncomingMidiMessage (mpc->input, m);
  419. }
  420. size -= used;
  421. d += used;
  422. }
  423. }
  424. packet = MIDIPacketNext (packet);
  425. }
  426. }
  427. }
  428. MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
  429. {
  430. MidiInput* mi = 0;
  431. if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfSources())
  432. {
  433. MIDIEndpointRef endPoint = MIDIGetSource (index);
  434. if (endPoint != 0)
  435. {
  436. CFStringRef pname;
  437. if (OK (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname)))
  438. {
  439. log (T("CoreMidi - opening inp: ") + PlatformUtilities::cfStringToJuceString (pname));
  440. if (makeSureClientExists())
  441. {
  442. MIDIPortRef port;
  443. MidiPortAndCallback* const mpc = new MidiPortAndCallback();
  444. mpc->active = false;
  445. if (OK (MIDIInputPortCreate (globalMidiClient, pname, midiInputProc, mpc, &port)))
  446. {
  447. if (OK (MIDIPortConnectSource (port, endPoint, 0)))
  448. {
  449. mpc->portAndEndpoint = new MidiPortAndEndpoint (port, endPoint);
  450. mpc->callback = callback;
  451. mpc->pendingBytes = 0;
  452. mpc->pendingData.ensureSize (128);
  453. mi = new MidiInput (getDevices() [index]);
  454. mpc->input = mi;
  455. mi->internal = (void*) mpc;
  456. const ScopedLock sl (callbackLock);
  457. activeCallbacks.add (mpc);
  458. }
  459. else
  460. {
  461. OK (MIDIPortDispose (port));
  462. delete mpc;
  463. }
  464. }
  465. else
  466. {
  467. delete mpc;
  468. }
  469. }
  470. }
  471. CFRelease (pname);
  472. }
  473. }
  474. return mi;
  475. }
  476. MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
  477. {
  478. MidiInput* mi = 0;
  479. if (makeSureClientExists())
  480. {
  481. MidiPortAndCallback* const mpc = new MidiPortAndCallback();
  482. mpc->active = false;
  483. MIDIEndpointRef endPoint;
  484. CFStringRef name = PlatformUtilities::juceStringToCFString(deviceName);
  485. if (OK (MIDIDestinationCreate (globalMidiClient, name, midiInputProc, mpc, &endPoint)))
  486. {
  487. mpc->portAndEndpoint = new MidiPortAndEndpoint (0, endPoint);
  488. mpc->callback = callback;
  489. mpc->pendingBytes = 0;
  490. mpc->pendingData.ensureSize (128);
  491. mi = new MidiInput (deviceName);
  492. mpc->input = mi;
  493. mi->internal = (void*) mpc;
  494. const ScopedLock sl (callbackLock);
  495. activeCallbacks.add (mpc);
  496. }
  497. else
  498. {
  499. delete mpc;
  500. }
  501. CFRelease (name);
  502. }
  503. return mi;
  504. }
  505. MidiInput::MidiInput (const String& name_)
  506. : name (name_)
  507. {
  508. }
  509. MidiInput::~MidiInput()
  510. {
  511. MidiPortAndCallback* const mpc = (MidiPortAndCallback*) internal;
  512. mpc->active = false;
  513. callbackLock.enter();
  514. activeCallbacks.removeValue (mpc);
  515. callbackLock.exit();
  516. if (mpc->portAndEndpoint->port != 0)
  517. OK (MIDIPortDisconnectSource (mpc->portAndEndpoint->port, mpc->portAndEndpoint->endPoint));
  518. delete mpc->portAndEndpoint;
  519. delete mpc;
  520. }
  521. void MidiInput::start()
  522. {
  523. MidiPortAndCallback* const mpc = (MidiPortAndCallback*) internal;
  524. const ScopedLock sl (callbackLock);
  525. mpc->active = true;
  526. }
  527. void MidiInput::stop()
  528. {
  529. MidiPortAndCallback* const mpc = (MidiPortAndCallback*) internal;
  530. const ScopedLock sl (callbackLock);
  531. mpc->active = false;
  532. }
  533. #undef log
  534. #else
  535. MidiOutput::~MidiOutput()
  536. {
  537. }
  538. void MidiOutput::reset()
  539. {
  540. }
  541. bool MidiOutput::getVolume (float& leftVol, float& rightVol)
  542. {
  543. return false;
  544. }
  545. void MidiOutput::setVolume (float leftVol, float rightVol)
  546. {
  547. }
  548. void MidiOutput::sendMessageNow (const MidiMessage& message)
  549. {
  550. }
  551. const StringArray MidiOutput::getDevices()
  552. {
  553. return StringArray();
  554. }
  555. MidiOutput* MidiOutput::openDevice (int index)
  556. {
  557. return 0;
  558. }
  559. const StringArray MidiInput::getDevices()
  560. {
  561. return StringArray();
  562. }
  563. MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
  564. {
  565. return 0;
  566. }
  567. #endif
  568. #endif