Audio plugin host https://kx.studio/carla
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.

juce_win32_Midi.cpp 67KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. The code included in this file is provided under the terms of the ISC license
  8. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  9. To use, copy, modify, and/or distribute this software for any purpose with or
  10. without fee is hereby granted provided that the above copyright notice and
  11. this permission notice appear in all copies.
  12. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  13. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  14. DISCLAIMED.
  15. ==============================================================================
  16. */
  17. #ifndef DRV_QUERYDEVICEINTERFACE
  18. #define DRV_RESERVED 0x0800
  19. #define DRV_QUERYDEVICEINTERFACE (DRV_RESERVED + 12)
  20. #define DRV_QUERYDEVICEINTERFACESIZE (DRV_RESERVED + 13)
  21. #endif
  22. namespace juce
  23. {
  24. struct MidiServiceType
  25. {
  26. struct InputWrapper
  27. {
  28. virtual ~InputWrapper() {}
  29. virtual String getDeviceIdentifier() = 0;
  30. virtual String getDeviceName() = 0;
  31. virtual void start() = 0;
  32. virtual void stop() = 0;
  33. };
  34. struct OutputWrapper
  35. {
  36. virtual ~OutputWrapper() {}
  37. virtual String getDeviceIdentifier() = 0;
  38. virtual String getDeviceName() = 0;
  39. virtual void sendMessageNow (const MidiMessage&) = 0;
  40. };
  41. MidiServiceType() {}
  42. virtual ~MidiServiceType() {}
  43. virtual Array<MidiDeviceInfo> getAvailableDevices (bool) = 0;
  44. virtual MidiDeviceInfo getDefaultDevice (bool) = 0;
  45. virtual InputWrapper* createInputWrapper (MidiInput&, const String&, MidiInputCallback&) = 0;
  46. virtual OutputWrapper* createOutputWrapper (const String&) = 0;
  47. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiServiceType)
  48. };
  49. //==============================================================================
  50. struct Win32MidiService : public MidiServiceType,
  51. private Timer
  52. {
  53. Win32MidiService() {}
  54. Array<MidiDeviceInfo> getAvailableDevices (bool isInput) override
  55. {
  56. return isInput ? Win32InputWrapper::getAvailableDevices()
  57. : Win32OutputWrapper::getAvailableDevices();
  58. }
  59. MidiDeviceInfo getDefaultDevice (bool isInput) override
  60. {
  61. return isInput ? Win32InputWrapper::getDefaultDevice()
  62. : Win32OutputWrapper::getDefaultDevice();
  63. }
  64. InputWrapper* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override
  65. {
  66. return new Win32InputWrapper (*this, input, deviceIdentifier, callback);
  67. }
  68. OutputWrapper* createOutputWrapper (const String& deviceIdentifier) override
  69. {
  70. return new Win32OutputWrapper (*this, deviceIdentifier);
  71. }
  72. private:
  73. struct Win32InputWrapper;
  74. //==============================================================================
  75. struct MidiInCollector : public ReferenceCountedObject
  76. {
  77. MidiInCollector (Win32MidiService& s, MidiDeviceInfo d)
  78. : deviceInfo (d), midiService (s)
  79. {
  80. }
  81. ~MidiInCollector()
  82. {
  83. stop();
  84. if (deviceHandle != 0)
  85. {
  86. for (int count = 5; --count >= 0;)
  87. {
  88. if (midiInClose (deviceHandle) == MMSYSERR_NOERROR)
  89. break;
  90. Sleep (20);
  91. }
  92. }
  93. }
  94. using Ptr = ReferenceCountedObjectPtr<MidiInCollector>;
  95. void addClient (Win32InputWrapper* c)
  96. {
  97. const ScopedLock sl (clientLock);
  98. jassert (! clients.contains (c));
  99. clients.add (c);
  100. }
  101. void removeClient (Win32InputWrapper* c)
  102. {
  103. const ScopedLock sl (clientLock);
  104. clients.removeFirstMatchingValue (c);
  105. startOrStop();
  106. midiService.asyncCheckForUnusedCollectors();
  107. }
  108. void handleMessage (const uint8* bytes, uint32 timeStamp)
  109. {
  110. if (bytes[0] >= 0x80 && isStarted.load())
  111. {
  112. {
  113. auto len = MidiMessage::getMessageLengthFromFirstByte (bytes[0]);
  114. auto time = convertTimeStamp (timeStamp);
  115. const ScopedLock sl (clientLock);
  116. for (auto* c : clients)
  117. c->pushMidiData (bytes, len, time);
  118. }
  119. writeFinishedBlocks();
  120. }
  121. }
  122. void handleSysEx (MIDIHDR* hdr, uint32 timeStamp)
  123. {
  124. if (isStarted.load() && hdr->dwBytesRecorded > 0)
  125. {
  126. {
  127. auto time = convertTimeStamp (timeStamp);
  128. const ScopedLock sl (clientLock);
  129. for (auto* c : clients)
  130. c->pushMidiData (hdr->lpData, (int) hdr->dwBytesRecorded, time);
  131. }
  132. writeFinishedBlocks();
  133. }
  134. }
  135. void startOrStop()
  136. {
  137. const ScopedLock sl (clientLock);
  138. if (countRunningClients() == 0)
  139. stop();
  140. else
  141. start();
  142. }
  143. void start()
  144. {
  145. if (deviceHandle != 0 && ! isStarted.load())
  146. {
  147. activeMidiCollectors.addIfNotAlreadyThere (this);
  148. for (int i = 0; i < (int) numHeaders; ++i)
  149. {
  150. headers[i].prepare (deviceHandle);
  151. headers[i].write (deviceHandle);
  152. }
  153. startTime = Time::getMillisecondCounterHiRes();
  154. auto res = midiInStart (deviceHandle);
  155. if (res == MMSYSERR_NOERROR)
  156. isStarted = true;
  157. else
  158. unprepareAllHeaders();
  159. }
  160. }
  161. void stop()
  162. {
  163. if (isStarted.load())
  164. {
  165. isStarted = false;
  166. midiInReset (deviceHandle);
  167. midiInStop (deviceHandle);
  168. activeMidiCollectors.removeFirstMatchingValue (this);
  169. unprepareAllHeaders();
  170. }
  171. }
  172. static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance,
  173. DWORD_PTR midiMessage, DWORD_PTR timeStamp)
  174. {
  175. auto* collector = reinterpret_cast<MidiInCollector*> (dwInstance);
  176. // This is primarily a check for the collector being a dangling
  177. // pointer, as the callback can sometimes be delayed
  178. if (activeMidiCollectors.contains (collector))
  179. {
  180. if (uMsg == MIM_DATA)
  181. collector->handleMessage ((const uint8*) &midiMessage, (uint32) timeStamp);
  182. else if (uMsg == MIM_LONGDATA)
  183. collector->handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp);
  184. }
  185. }
  186. MidiDeviceInfo deviceInfo;
  187. HMIDIIN deviceHandle = 0;
  188. private:
  189. Win32MidiService& midiService;
  190. CriticalSection clientLock;
  191. Array<Win32InputWrapper*> clients;
  192. std::atomic<bool> isStarted { false };
  193. double startTime = 0;
  194. // This static array is used to prevent occasional callbacks to objects that are
  195. // in the process of being deleted
  196. static Array<MidiInCollector*, CriticalSection> activeMidiCollectors;
  197. int countRunningClients() const
  198. {
  199. int num = 0;
  200. for (auto* c : clients)
  201. if (c->started)
  202. ++num;
  203. return num;
  204. }
  205. struct MidiHeader
  206. {
  207. MidiHeader() {}
  208. void prepare (HMIDIIN device)
  209. {
  210. zerostruct (hdr);
  211. hdr.lpData = data;
  212. hdr.dwBufferLength = (DWORD) numElementsInArray (data);
  213. midiInPrepareHeader (device, &hdr, sizeof (hdr));
  214. }
  215. void unprepare (HMIDIIN device)
  216. {
  217. if ((hdr.dwFlags & WHDR_DONE) != 0)
  218. {
  219. int c = 10;
  220. while (--c >= 0 && midiInUnprepareHeader (device, &hdr, sizeof (hdr)) == MIDIERR_STILLPLAYING)
  221. Thread::sleep (20);
  222. jassert (c >= 0);
  223. }
  224. }
  225. void write (HMIDIIN device)
  226. {
  227. hdr.dwBytesRecorded = 0;
  228. midiInAddBuffer (device, &hdr, sizeof (hdr));
  229. }
  230. void writeIfFinished (HMIDIIN device)
  231. {
  232. if ((hdr.dwFlags & WHDR_DONE) != 0)
  233. write (device);
  234. }
  235. MIDIHDR hdr;
  236. char data[256];
  237. JUCE_DECLARE_NON_COPYABLE (MidiHeader)
  238. };
  239. enum { numHeaders = 32 };
  240. MidiHeader headers[numHeaders];
  241. void writeFinishedBlocks()
  242. {
  243. for (int i = 0; i < (int) numHeaders; ++i)
  244. headers[i].writeIfFinished (deviceHandle);
  245. }
  246. void unprepareAllHeaders()
  247. {
  248. for (int i = 0; i < (int) numHeaders; ++i)
  249. headers[i].unprepare (deviceHandle);
  250. }
  251. double convertTimeStamp (uint32 timeStamp)
  252. {
  253. auto t = startTime + timeStamp;
  254. auto now = Time::getMillisecondCounterHiRes();
  255. if (t > now)
  256. {
  257. if (t > now + 2.0)
  258. startTime -= 1.0;
  259. t = now;
  260. }
  261. return t * 0.001;
  262. }
  263. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInCollector)
  264. };
  265. //==============================================================================
  266. template<class WrapperType>
  267. struct Win32MidiDeviceQuery
  268. {
  269. static Array<MidiDeviceInfo> getAvailableDevices()
  270. {
  271. StringArray deviceNames, deviceIDs;
  272. auto deviceCaps = WrapperType::getDeviceCaps();
  273. for (int i = 0; i < deviceCaps.size(); ++i)
  274. {
  275. deviceNames.add (deviceCaps[i].szPname);
  276. auto identifier = getInterfaceIDForDevice ((UINT) i);
  277. if (identifier.isNotEmpty())
  278. deviceIDs.add (identifier);
  279. else
  280. deviceIDs.add (deviceNames[i]);
  281. }
  282. deviceNames.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
  283. deviceIDs .appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
  284. Array<MidiDeviceInfo> devices;
  285. for (int i = 0; i < deviceNames.size(); ++i)
  286. devices.add ({ deviceNames[i], deviceIDs[i] });
  287. return devices;
  288. }
  289. private:
  290. static String getInterfaceIDForDevice (UINT id)
  291. {
  292. ULONG size = 0;
  293. if (WrapperType::sendMidiMessage ((UINT_PTR) id, DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR) &size, 0) == MMSYSERR_NOERROR)
  294. {
  295. WCHAR interfaceName[512] = {};
  296. if (isPositiveAndBelow (size, sizeof (interfaceName))
  297. && WrapperType::sendMidiMessage ((UINT_PTR) id, DRV_QUERYDEVICEINTERFACE,
  298. (DWORD_PTR) interfaceName, sizeof (interfaceName)) == MMSYSERR_NOERROR)
  299. {
  300. return interfaceName;
  301. }
  302. }
  303. return {};
  304. }
  305. };
  306. struct Win32InputWrapper : public InputWrapper,
  307. public Win32MidiDeviceQuery<Win32InputWrapper>
  308. {
  309. Win32InputWrapper (Win32MidiService& parentService, MidiInput& midiInput, const String& deviceIdentifier, MidiInputCallback& c)
  310. : input (midiInput), callback (c)
  311. {
  312. collector = getOrCreateCollector (parentService, deviceIdentifier);
  313. collector->addClient (this);
  314. }
  315. ~Win32InputWrapper()
  316. {
  317. collector->removeClient (this);
  318. }
  319. static MidiInCollector::Ptr getOrCreateCollector (Win32MidiService& parentService, const String& deviceIdentifier)
  320. {
  321. UINT deviceID = MIDI_MAPPER;
  322. String deviceName;
  323. auto devices = getAvailableDevices();
  324. for (int i = 0; i < devices.size(); ++i)
  325. {
  326. auto d = devices.getUnchecked (i);
  327. if (d.identifier == deviceIdentifier)
  328. {
  329. deviceID = i;
  330. deviceName = d.name;
  331. break;
  332. }
  333. }
  334. const ScopedLock sl (parentService.activeCollectorLock);
  335. for (auto& c : parentService.activeCollectors)
  336. if (c->deviceInfo.identifier == deviceIdentifier)
  337. return c;
  338. MidiInCollector::Ptr c (new MidiInCollector (parentService, { deviceName, deviceIdentifier }));
  339. HMIDIIN h;
  340. auto err = midiInOpen (&h, deviceID,
  341. (DWORD_PTR) &MidiInCollector::midiInCallback,
  342. (DWORD_PTR) (MidiInCollector*) c.get(),
  343. CALLBACK_FUNCTION);
  344. if (err != MMSYSERR_NOERROR)
  345. throw std::runtime_error ("Failed to create Windows input device wrapper");
  346. c->deviceHandle = h;
  347. parentService.activeCollectors.add (c);
  348. return c;
  349. }
  350. static DWORD sendMidiMessage (UINT_PTR deviceID, UINT msg, DWORD_PTR arg1, DWORD_PTR arg2)
  351. {
  352. return midiInMessage ((HMIDIIN) deviceID, msg, arg1, arg2);
  353. }
  354. static Array<MIDIINCAPS> getDeviceCaps()
  355. {
  356. Array<MIDIINCAPS> devices;
  357. for (UINT i = 0; i < midiInGetNumDevs(); ++i)
  358. {
  359. MIDIINCAPS mc = {};
  360. if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
  361. devices.add (mc);
  362. }
  363. return devices;
  364. }
  365. static MidiDeviceInfo getDefaultDevice() { return getAvailableDevices().getFirst(); }
  366. void start() override { started = true; concatenator.reset(); collector->startOrStop(); }
  367. void stop() override { started = false; collector->startOrStop(); concatenator.reset(); }
  368. String getDeviceIdentifier() override { return collector->deviceInfo.identifier; }
  369. String getDeviceName() override { return collector->deviceInfo.name; }
  370. void pushMidiData (const void* inputData, int numBytes, double time)
  371. {
  372. concatenator.pushMidiData (inputData, numBytes, time, &input, callback);
  373. }
  374. MidiInput& input;
  375. MidiInputCallback& callback;
  376. MidiDataConcatenator concatenator { 4096 };
  377. MidiInCollector::Ptr collector;
  378. bool started = false;
  379. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32InputWrapper)
  380. };
  381. //==============================================================================
  382. struct MidiOutHandle : public ReferenceCountedObject
  383. {
  384. using Ptr = ReferenceCountedObjectPtr<MidiOutHandle>;
  385. MidiOutHandle (Win32MidiService& parent, MidiDeviceInfo d, HMIDIOUT h)
  386. : owner (parent), deviceInfo (d), handle (h)
  387. {
  388. owner.activeOutputHandles.add (this);
  389. }
  390. ~MidiOutHandle()
  391. {
  392. if (handle != nullptr)
  393. midiOutClose (handle);
  394. owner.activeOutputHandles.removeFirstMatchingValue (this);
  395. }
  396. Win32MidiService& owner;
  397. MidiDeviceInfo deviceInfo;
  398. HMIDIOUT handle;
  399. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutHandle)
  400. };
  401. //==============================================================================
  402. struct Win32OutputWrapper : public OutputWrapper,
  403. public Win32MidiDeviceQuery<Win32OutputWrapper>
  404. {
  405. Win32OutputWrapper (Win32MidiService& p, const String& deviceIdentifier)
  406. : parent (p)
  407. {
  408. auto devices = getAvailableDevices();
  409. UINT deviceID = MIDI_MAPPER;
  410. String deviceName;
  411. for (int i = 0; i < devices.size(); ++i)
  412. {
  413. auto d = devices.getUnchecked (i);
  414. if (d.identifier == deviceIdentifier)
  415. {
  416. deviceID = i;
  417. deviceName = d.name;
  418. break;
  419. }
  420. }
  421. if (deviceID == MIDI_MAPPER)
  422. {
  423. // use the microsoft sw synth as a default - best not to allow deviceID
  424. // to be MIDI_MAPPER, or else device sharing breaks
  425. for (int i = 0; i < devices.size(); ++i)
  426. if (devices[i].name.containsIgnoreCase ("microsoft"))
  427. deviceID = (UINT) i;
  428. }
  429. for (int i = parent.activeOutputHandles.size(); --i >= 0;)
  430. {
  431. auto* activeHandle = parent.activeOutputHandles.getUnchecked (i);
  432. if (activeHandle->deviceInfo.identifier == deviceIdentifier)
  433. {
  434. han = activeHandle;
  435. return;
  436. }
  437. }
  438. for (int i = 4; --i >= 0;)
  439. {
  440. HMIDIOUT h = 0;
  441. auto res = midiOutOpen (&h, deviceID, 0, 0, CALLBACK_NULL);
  442. if (res == MMSYSERR_NOERROR)
  443. {
  444. han = new MidiOutHandle (parent, { deviceName, deviceIdentifier }, h);
  445. return;
  446. }
  447. if (res == MMSYSERR_ALLOCATED)
  448. Sleep (100);
  449. else
  450. break;
  451. }
  452. throw std::runtime_error ("Failed to create Windows output device wrapper");
  453. }
  454. void sendMessageNow (const MidiMessage& message) override
  455. {
  456. if (message.getRawDataSize() > 3 || message.isSysEx())
  457. {
  458. MIDIHDR h = {};
  459. h.lpData = (char*) message.getRawData();
  460. h.dwBytesRecorded = h.dwBufferLength = (DWORD) message.getRawDataSize();
  461. if (midiOutPrepareHeader (han->handle, &h, sizeof (MIDIHDR)) == MMSYSERR_NOERROR)
  462. {
  463. auto res = midiOutLongMsg (han->handle, &h, sizeof (MIDIHDR));
  464. if (res == MMSYSERR_NOERROR)
  465. {
  466. while ((h.dwFlags & MHDR_DONE) == 0)
  467. Sleep (1);
  468. int count = 500; // 1 sec timeout
  469. while (--count >= 0)
  470. {
  471. res = midiOutUnprepareHeader (han->handle, &h, sizeof (MIDIHDR));
  472. if (res == MIDIERR_STILLPLAYING)
  473. Sleep (2);
  474. else
  475. break;
  476. }
  477. }
  478. }
  479. }
  480. else
  481. {
  482. for (int i = 0; i < 50; ++i)
  483. {
  484. if (midiOutShortMsg (han->handle, *(unsigned int*) message.getRawData()) != MIDIERR_NOTREADY)
  485. break;
  486. Sleep (1);
  487. }
  488. }
  489. }
  490. static DWORD sendMidiMessage (UINT_PTR deviceID, UINT msg, DWORD_PTR arg1, DWORD_PTR arg2)
  491. {
  492. return midiOutMessage ((HMIDIOUT) deviceID, msg, arg1, arg2);
  493. }
  494. static Array<MIDIOUTCAPS> getDeviceCaps()
  495. {
  496. Array<MIDIOUTCAPS> devices;
  497. for (UINT i = 0; i < midiOutGetNumDevs(); ++i)
  498. {
  499. MIDIOUTCAPS mc = {};
  500. if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
  501. devices.add (mc);
  502. }
  503. return devices;
  504. }
  505. static MidiDeviceInfo getDefaultDevice()
  506. {
  507. auto defaultIndex = []()
  508. {
  509. auto deviceCaps = getDeviceCaps();
  510. for (int i = 0; i < deviceCaps.size(); ++i)
  511. if ((deviceCaps[i].wTechnology & MOD_MAPPER) != 0)
  512. return i;
  513. return 0;
  514. }();
  515. return getAvailableDevices()[defaultIndex];
  516. }
  517. String getDeviceIdentifier() override { return han->deviceInfo.identifier; }
  518. String getDeviceName() override { return han->deviceInfo.name; }
  519. Win32MidiService& parent;
  520. MidiOutHandle::Ptr han;
  521. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32OutputWrapper)
  522. };
  523. //==============================================================================
  524. void asyncCheckForUnusedCollectors()
  525. {
  526. startTimer (10);
  527. }
  528. void timerCallback() override
  529. {
  530. stopTimer();
  531. const ScopedLock sl (activeCollectorLock);
  532. for (int i = activeCollectors.size(); --i >= 0;)
  533. if (activeCollectors.getObjectPointer(i)->getReferenceCount() == 1)
  534. activeCollectors.remove (i);
  535. }
  536. CriticalSection activeCollectorLock;
  537. ReferenceCountedArray<MidiInCollector> activeCollectors;
  538. Array<MidiOutHandle*> activeOutputHandles;
  539. };
  540. Array<Win32MidiService::MidiInCollector*, CriticalSection> Win32MidiService::MidiInCollector::activeMidiCollectors;
  541. //==============================================================================
  542. //==============================================================================
  543. #if JUCE_USE_WINRT_MIDI
  544. #ifndef JUCE_FORCE_WINRT_MIDI
  545. #define JUCE_FORCE_WINRT_MIDI 0
  546. #endif
  547. #ifndef JUCE_WINRT_MIDI_LOGGING
  548. #define JUCE_WINRT_MIDI_LOGGING 0
  549. #endif
  550. #if JUCE_WINRT_MIDI_LOGGING
  551. #define JUCE_WINRT_MIDI_LOG(x) DBG(x)
  552. #else
  553. #define JUCE_WINRT_MIDI_LOG(x)
  554. #endif
  555. using namespace Microsoft::WRL;
  556. using namespace ABI::Windows::Foundation;
  557. using namespace ABI::Windows::Foundation::Collections;
  558. using namespace ABI::Windows::Devices::Midi;
  559. using namespace ABI::Windows::Devices::Enumeration;
  560. using namespace ABI::Windows::Storage::Streams;
  561. //==============================================================================
  562. struct WinRTMidiService : public MidiServiceType
  563. {
  564. public:
  565. //==============================================================================
  566. WinRTMidiService()
  567. {
  568. auto* wrtWrapper = WinRTWrapper::getInstance();
  569. if (! wrtWrapper->isInitialised())
  570. throw std::runtime_error ("Failed to initialise the WinRT wrapper");
  571. midiInFactory = wrtWrapper->getWRLFactory<IMidiInPortStatics> (&RuntimeClass_Windows_Devices_Midi_MidiInPort[0]);
  572. if (midiInFactory == nullptr)
  573. throw std::runtime_error ("Failed to create midi in factory");
  574. midiOutFactory = wrtWrapper->getWRLFactory<IMidiOutPortStatics> (&RuntimeClass_Windows_Devices_Midi_MidiOutPort[0]);
  575. if (midiOutFactory == nullptr)
  576. throw std::runtime_error ("Failed to create midi out factory");
  577. // The WinRT BLE MIDI API doesn't provide callbacks when devices become disconnected,
  578. // but it does require a disconnection via the API before a device will reconnect again.
  579. // We can monitor the BLE connection state of paired devices to get callbacks when
  580. // connections are broken.
  581. bleDeviceWatcher.reset (new BLEDeviceWatcher());
  582. if (! bleDeviceWatcher->start())
  583. throw std::runtime_error ("Failed to start the BLE device watcher");
  584. inputDeviceWatcher.reset (new MidiIODeviceWatcher<IMidiInPortStatics> (midiInFactory));
  585. if (! inputDeviceWatcher->start())
  586. throw std::runtime_error ("Failed to start the midi input device watcher");
  587. outputDeviceWatcher.reset (new MidiIODeviceWatcher<IMidiOutPortStatics> (midiOutFactory));
  588. if (! outputDeviceWatcher->start())
  589. throw std::runtime_error ("Failed to start the midi output device watcher");
  590. }
  591. Array<MidiDeviceInfo> getAvailableDevices (bool isInput) override
  592. {
  593. return isInput ? inputDeviceWatcher ->getAvailableDevices()
  594. : outputDeviceWatcher->getAvailableDevices();
  595. }
  596. MidiDeviceInfo getDefaultDevice (bool isInput) override
  597. {
  598. return isInput ? inputDeviceWatcher ->getDefaultDevice()
  599. : outputDeviceWatcher->getDefaultDevice();
  600. }
  601. InputWrapper* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override
  602. {
  603. return new WinRTInputWrapper (*this, input, deviceIdentifier, callback);
  604. }
  605. OutputWrapper* createOutputWrapper (const String& deviceIdentifier) override
  606. {
  607. return new WinRTOutputWrapper (*this, deviceIdentifier);
  608. }
  609. private:
  610. //==============================================================================
  611. class DeviceCallbackHandler
  612. {
  613. public:
  614. virtual ~DeviceCallbackHandler() {};
  615. virtual HRESULT addDevice (IDeviceInformation*) = 0;
  616. virtual HRESULT removeDevice (IDeviceInformationUpdate*) = 0;
  617. virtual HRESULT updateDevice (IDeviceInformationUpdate*) = 0;
  618. bool attach (HSTRING deviceSelector, DeviceInformationKind infoKind)
  619. {
  620. auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
  621. if (wrtWrapper == nullptr)
  622. {
  623. JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
  624. return false;
  625. }
  626. auto deviceInfoFactory = wrtWrapper->getWRLFactory<IDeviceInformationStatics2> (&RuntimeClass_Windows_Devices_Enumeration_DeviceInformation[0]);
  627. if (deviceInfoFactory == nullptr)
  628. return false;
  629. // A quick way of getting an IVector<HSTRING>...
  630. auto requestedProperties = [wrtWrapper]
  631. {
  632. auto devicePicker = wrtWrapper->activateInstance<IDevicePicker> (&RuntimeClass_Windows_Devices_Enumeration_DevicePicker[0],
  633. __uuidof (IDevicePicker));
  634. jassert (devicePicker != nullptr);
  635. IVector<HSTRING>* result;
  636. auto hr = devicePicker->get_RequestedProperties (&result);
  637. jassert (SUCCEEDED (hr));
  638. hr = result->Clear();
  639. jassert (SUCCEEDED (hr));
  640. return result;
  641. }();
  642. StringArray propertyKeys ("System.Devices.ContainerId",
  643. "System.Devices.Aep.ContainerId",
  644. "System.Devices.Aep.IsConnected");
  645. for (auto& key : propertyKeys)
  646. {
  647. WinRTWrapper::ScopedHString hstr (key);
  648. auto hr = requestedProperties->Append (hstr.get());
  649. if (FAILED (hr))
  650. {
  651. jassertfalse;
  652. return false;
  653. }
  654. }
  655. WinRTWrapper::ComPtr<IIterable<HSTRING>> iter;
  656. auto hr = requestedProperties->QueryInterface (__uuidof (IIterable<HSTRING>), (void**) iter.resetAndGetPointerAddress());
  657. if (FAILED (hr))
  658. {
  659. jassertfalse;
  660. return false;
  661. }
  662. hr = deviceInfoFactory->CreateWatcherWithKindAqsFilterAndAdditionalProperties (deviceSelector, iter, infoKind,
  663. watcher.resetAndGetPointerAddress());
  664. if (FAILED (hr))
  665. {
  666. jassertfalse;
  667. return false;
  668. }
  669. enumerationThread.startThread();
  670. return true;
  671. };
  672. void detach()
  673. {
  674. enumerationThread.stopThread (2000);
  675. if (watcher == nullptr)
  676. return;
  677. auto hr = watcher->Stop();
  678. jassert (SUCCEEDED (hr));
  679. if (deviceAddedToken.value != 0)
  680. {
  681. hr = watcher->remove_Added (deviceAddedToken);
  682. jassert (SUCCEEDED (hr));
  683. deviceAddedToken.value = 0;
  684. }
  685. if (deviceUpdatedToken.value != 0)
  686. {
  687. hr = watcher->remove_Updated (deviceUpdatedToken);
  688. jassert (SUCCEEDED (hr));
  689. deviceUpdatedToken.value = 0;
  690. }
  691. if (deviceRemovedToken.value != 0)
  692. {
  693. hr = watcher->remove_Removed (deviceRemovedToken);
  694. jassert (SUCCEEDED (hr));
  695. deviceRemovedToken.value = 0;
  696. }
  697. watcher = nullptr;
  698. }
  699. template<typename InfoType>
  700. IInspectable* getValueFromDeviceInfo (String key, InfoType* info)
  701. {
  702. __FIMapView_2_HSTRING_IInspectable* properties;
  703. info->get_Properties (&properties);
  704. boolean found = false;
  705. WinRTWrapper::ScopedHString keyHstr (key);
  706. auto hr = properties->HasKey (keyHstr.get(), &found);
  707. if (FAILED (hr))
  708. {
  709. jassertfalse;
  710. return nullptr;
  711. }
  712. if (! found)
  713. return nullptr;
  714. IInspectable* inspectable;
  715. hr = properties->Lookup (keyHstr.get(), &inspectable);
  716. if (FAILED (hr))
  717. {
  718. jassertfalse;
  719. return nullptr;
  720. }
  721. return inspectable;
  722. }
  723. String getGUIDFromInspectable (IInspectable& inspectable)
  724. {
  725. WinRTWrapper::ComPtr<IReference<GUID>> guidRef;
  726. auto hr = inspectable.QueryInterface (__uuidof (IReference<GUID>),
  727. (void**) guidRef.resetAndGetPointerAddress());
  728. if (FAILED (hr))
  729. {
  730. jassertfalse;
  731. return {};
  732. }
  733. GUID result;
  734. hr = guidRef->get_Value (&result);
  735. if (FAILED (hr))
  736. {
  737. jassertfalse;
  738. return {};
  739. }
  740. OLECHAR* resultString;
  741. StringFromCLSID (result, &resultString);
  742. return resultString;
  743. }
  744. bool getBoolFromInspectable (IInspectable& inspectable)
  745. {
  746. WinRTWrapper::ComPtr<IReference<bool>> boolRef;
  747. auto hr = inspectable.QueryInterface (__uuidof (IReference<bool>),
  748. (void**) boolRef.resetAndGetPointerAddress());
  749. if (FAILED (hr))
  750. {
  751. jassertfalse;
  752. return false;
  753. }
  754. boolean result;
  755. hr = boolRef->get_Value (&result);
  756. if (FAILED (hr))
  757. {
  758. jassertfalse;
  759. return false;
  760. }
  761. return result;
  762. }
  763. private:
  764. //==============================================================================
  765. struct DeviceEnumerationThread : public Thread
  766. {
  767. DeviceEnumerationThread (DeviceCallbackHandler& h,
  768. WinRTWrapper::ComPtr<IDeviceWatcher>& w,
  769. EventRegistrationToken& added,
  770. EventRegistrationToken& removed,
  771. EventRegistrationToken& updated)
  772. : Thread ("WinRT Device Enumeration Thread"), handler (h), watcher (w),
  773. deviceAddedToken (added), deviceRemovedToken (removed), deviceUpdatedToken (updated)
  774. {}
  775. void run() override
  776. {
  777. auto handlerPtr = std::addressof (handler);
  778. watcher->add_Added (
  779. Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformation*>> (
  780. [handlerPtr] (IDeviceWatcher*, IDeviceInformation* info) { return handlerPtr->addDevice (info); }
  781. ).Get(),
  782. &deviceAddedToken);
  783. watcher->add_Removed (
  784. Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>> (
  785. [handlerPtr] (IDeviceWatcher*, IDeviceInformationUpdate* infoUpdate) { return handlerPtr->removeDevice (infoUpdate); }
  786. ).Get(),
  787. &deviceRemovedToken);
  788. watcher->add_Updated (
  789. Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>> (
  790. [handlerPtr] (IDeviceWatcher*, IDeviceInformationUpdate* infoUpdate) { return handlerPtr->updateDevice (infoUpdate); }
  791. ).Get(),
  792. &deviceUpdatedToken);
  793. watcher->Start();
  794. }
  795. DeviceCallbackHandler& handler;
  796. WinRTWrapper::ComPtr<IDeviceWatcher>& watcher;
  797. EventRegistrationToken& deviceAddedToken, deviceRemovedToken, deviceUpdatedToken;
  798. };
  799. //==============================================================================
  800. WinRTWrapper::ComPtr<IDeviceWatcher> watcher;
  801. EventRegistrationToken deviceAddedToken { 0 },
  802. deviceRemovedToken { 0 },
  803. deviceUpdatedToken { 0 };
  804. DeviceEnumerationThread enumerationThread { *this, watcher,
  805. deviceAddedToken,
  806. deviceRemovedToken,
  807. deviceUpdatedToken };
  808. };
  809. //==============================================================================
  810. struct BLEDeviceWatcher final : private DeviceCallbackHandler
  811. {
  812. struct DeviceInfo
  813. {
  814. String containerID;
  815. bool isConnected = false;
  816. };
  817. BLEDeviceWatcher() = default;
  818. ~BLEDeviceWatcher()
  819. {
  820. detach();
  821. }
  822. //==============================================================================
  823. HRESULT addDevice (IDeviceInformation* addedDeviceInfo) override
  824. {
  825. HSTRING deviceIDHst;
  826. auto hr = addedDeviceInfo->get_Id (&deviceIDHst);
  827. if (FAILED (hr))
  828. {
  829. JUCE_WINRT_MIDI_LOG ("Failed to query added BLE device ID!");
  830. return S_OK;
  831. }
  832. auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
  833. if (wrtWrapper == nullptr)
  834. {
  835. JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
  836. return false;
  837. }
  838. auto deviceID = wrtWrapper->hStringToString (deviceIDHst);
  839. JUCE_WINRT_MIDI_LOG ("Detected paired BLE device: " << deviceID);
  840. if (auto* containerIDValue = getValueFromDeviceInfo ("System.Devices.Aep.ContainerId", addedDeviceInfo))
  841. {
  842. auto containerID = getGUIDFromInspectable (*containerIDValue);
  843. if (containerID.isNotEmpty())
  844. {
  845. DeviceInfo info = { containerID };
  846. if (auto* connectedValue = getValueFromDeviceInfo ("System.Devices.Aep.IsConnected", addedDeviceInfo))
  847. info.isConnected = getBoolFromInspectable (*connectedValue);
  848. JUCE_WINRT_MIDI_LOG ("Adding BLE device: " << deviceID << " " << info.containerID
  849. << " " << (info.isConnected ? "connected" : "disconnected"));
  850. devices.set (deviceID, info);
  851. return S_OK;
  852. }
  853. }
  854. JUCE_WINRT_MIDI_LOG ("Failed to get a container ID for BLE device: " << deviceID);
  855. return S_OK;
  856. }
  857. HRESULT removeDevice (IDeviceInformationUpdate* removedDeviceInfo) override
  858. {
  859. HSTRING removedDeviceIdHstr;
  860. auto hr = removedDeviceInfo->get_Id (&removedDeviceIdHstr);
  861. if (FAILED (hr))
  862. {
  863. JUCE_WINRT_MIDI_LOG ("Failed to query removed BLE device ID!");
  864. return S_OK;
  865. }
  866. auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
  867. if (wrtWrapper == nullptr)
  868. {
  869. JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
  870. return false;
  871. }
  872. auto removedDeviceId = wrtWrapper->hStringToString (removedDeviceIdHstr);
  873. JUCE_WINRT_MIDI_LOG ("Removing BLE device: " << removedDeviceId);
  874. {
  875. const ScopedLock lock (deviceChanges);
  876. if (devices.contains (removedDeviceId))
  877. {
  878. auto& info = devices.getReference (removedDeviceId);
  879. listeners.call ([&info] (Listener& l) { l.bleDeviceDisconnected (info.containerID); });
  880. devices.remove (removedDeviceId);
  881. JUCE_WINRT_MIDI_LOG ("Removed BLE device: " << removedDeviceId);
  882. }
  883. }
  884. return S_OK;
  885. }
  886. HRESULT updateDevice (IDeviceInformationUpdate* updatedDeviceInfo) override
  887. {
  888. HSTRING updatedDeviceIdHstr;
  889. auto hr = updatedDeviceInfo->get_Id (&updatedDeviceIdHstr);
  890. if (FAILED (hr))
  891. {
  892. JUCE_WINRT_MIDI_LOG ("Failed to query updated BLE device ID!");
  893. return S_OK;
  894. }
  895. auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
  896. if (wrtWrapper == nullptr)
  897. {
  898. JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
  899. return false;
  900. }
  901. auto updatedDeviceId = wrtWrapper->hStringToString (updatedDeviceIdHstr);
  902. JUCE_WINRT_MIDI_LOG ("Updating BLE device: " << updatedDeviceId);
  903. if (auto* connectedValue = getValueFromDeviceInfo ("System.Devices.Aep.IsConnected", updatedDeviceInfo))
  904. {
  905. auto isConnected = getBoolFromInspectable (*connectedValue);
  906. {
  907. const ScopedLock lock (deviceChanges);
  908. if (! devices.contains (updatedDeviceId))
  909. return S_OK;
  910. auto& info = devices.getReference (updatedDeviceId);
  911. if (info.isConnected && ! isConnected)
  912. {
  913. JUCE_WINRT_MIDI_LOG ("BLE device connection status change: " << updatedDeviceId << " " << info.containerID << " " << (isConnected ? "connected" : "disconnected"));
  914. listeners.call ([&info] (Listener& l) { l.bleDeviceDisconnected (info.containerID); });
  915. }
  916. info.isConnected = isConnected;
  917. }
  918. }
  919. return S_OK;
  920. }
  921. //==============================================================================
  922. bool start()
  923. {
  924. WinRTWrapper::ScopedHString deviceSelector ("System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\""
  925. " AND System.Devices.Aep.IsPaired:=System.StructuredQueryType.Boolean#True");
  926. return attach (deviceSelector.get(), DeviceInformationKind::DeviceInformationKind_AssociationEndpoint);
  927. }
  928. //==============================================================================
  929. struct Listener
  930. {
  931. virtual ~Listener() {};
  932. virtual void bleDeviceAdded (const String& containerID) = 0;
  933. virtual void bleDeviceDisconnected (const String& containerID) = 0;
  934. };
  935. void addListener (Listener* l)
  936. {
  937. listeners.add (l);
  938. }
  939. void removeListener (Listener* l)
  940. {
  941. listeners.remove (l);
  942. }
  943. //==============================================================================
  944. ListenerList<Listener> listeners;
  945. HashMap<String, DeviceInfo> devices;
  946. CriticalSection deviceChanges;
  947. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BLEDeviceWatcher);
  948. };
  949. //==============================================================================
  950. struct WinRTMIDIDeviceInfo
  951. {
  952. String deviceID, containerID, name;
  953. bool isDefault = false;
  954. };
  955. //==============================================================================
  956. template <typename COMFactoryType>
  957. struct MidiIODeviceWatcher final : private DeviceCallbackHandler
  958. {
  959. MidiIODeviceWatcher (WinRTWrapper::ComPtr<COMFactoryType>& comFactory)
  960. : factory (comFactory)
  961. {
  962. }
  963. ~MidiIODeviceWatcher()
  964. {
  965. detach();
  966. }
  967. HRESULT addDevice (IDeviceInformation* addedDeviceInfo) override
  968. {
  969. WinRTMIDIDeviceInfo info;
  970. HSTRING deviceID;
  971. auto hr = addedDeviceInfo->get_Id (&deviceID);
  972. if (FAILED (hr))
  973. {
  974. JUCE_WINRT_MIDI_LOG ("Failed to query added MIDI device ID!");
  975. return S_OK;
  976. }
  977. auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
  978. if (wrtWrapper == nullptr)
  979. {
  980. JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
  981. return false;
  982. }
  983. info.deviceID = wrtWrapper->hStringToString (deviceID);
  984. JUCE_WINRT_MIDI_LOG ("Detected MIDI device: " << info.deviceID);
  985. boolean isEnabled = false;
  986. hr = addedDeviceInfo->get_IsEnabled (&isEnabled);
  987. if (FAILED (hr) || ! isEnabled)
  988. {
  989. JUCE_WINRT_MIDI_LOG ("MIDI device not enabled: " << info.deviceID);
  990. return S_OK;
  991. }
  992. // We use the container ID to match a MIDI device with a generic BLE device, if possible
  993. if (auto* containerIDValue = getValueFromDeviceInfo ("System.Devices.ContainerId", addedDeviceInfo))
  994. info.containerID = getGUIDFromInspectable (*containerIDValue);
  995. HSTRING name;
  996. hr = addedDeviceInfo->get_Name (&name);
  997. if (FAILED (hr))
  998. {
  999. JUCE_WINRT_MIDI_LOG ("Failed to query detected MIDI device name for " << info.deviceID);
  1000. return S_OK;
  1001. }
  1002. info.name = wrtWrapper->hStringToString (name);
  1003. boolean isDefault = false;
  1004. hr = addedDeviceInfo->get_IsDefault (&isDefault);
  1005. if (FAILED (hr))
  1006. {
  1007. JUCE_WINRT_MIDI_LOG ("Failed to query detected MIDI device defaultness for " << info.deviceID << " " << info.name);
  1008. return S_OK;
  1009. }
  1010. info.isDefault = isDefault;
  1011. JUCE_WINRT_MIDI_LOG ("Adding MIDI device: " << info.deviceID << " " << info.containerID << " " << info.name);
  1012. {
  1013. const ScopedLock lock (deviceChanges);
  1014. connectedDevices.add (info);
  1015. }
  1016. return S_OK;
  1017. }
  1018. HRESULT removeDevice (IDeviceInformationUpdate* removedDeviceInfo) override
  1019. {
  1020. HSTRING removedDeviceIdHstr;
  1021. auto hr = removedDeviceInfo->get_Id (&removedDeviceIdHstr);
  1022. if (FAILED (hr))
  1023. {
  1024. JUCE_WINRT_MIDI_LOG ("Failed to query removed MIDI device ID!");
  1025. return S_OK;
  1026. }
  1027. auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
  1028. if (wrtWrapper == nullptr)
  1029. {
  1030. JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
  1031. return false;
  1032. }
  1033. auto removedDeviceId = wrtWrapper->hStringToString (removedDeviceIdHstr);
  1034. JUCE_WINRT_MIDI_LOG ("Removing MIDI device: " << removedDeviceId);
  1035. {
  1036. const ScopedLock lock (deviceChanges);
  1037. for (int i = 0; i < connectedDevices.size(); ++i)
  1038. {
  1039. if (connectedDevices[i].deviceID == removedDeviceId)
  1040. {
  1041. connectedDevices.remove (i);
  1042. JUCE_WINRT_MIDI_LOG ("Removed MIDI device: " << removedDeviceId);
  1043. break;
  1044. }
  1045. }
  1046. }
  1047. return S_OK;
  1048. }
  1049. // This is never called
  1050. HRESULT updateDevice (IDeviceInformationUpdate*) override { return S_OK; }
  1051. bool start()
  1052. {
  1053. HSTRING deviceSelector;
  1054. auto hr = factory->GetDeviceSelector (&deviceSelector);
  1055. if (FAILED (hr))
  1056. {
  1057. JUCE_WINRT_MIDI_LOG ("Failed to get MIDI device selector!");
  1058. return false;
  1059. }
  1060. return attach (deviceSelector, DeviceInformationKind::DeviceInformationKind_DeviceInterface);
  1061. }
  1062. Array<MidiDeviceInfo> getAvailableDevices()
  1063. {
  1064. {
  1065. const ScopedLock lock (deviceChanges);
  1066. lastQueriedConnectedDevices = connectedDevices;
  1067. }
  1068. StringArray deviceNames, deviceIDs;
  1069. for (auto info : lastQueriedConnectedDevices.get())
  1070. {
  1071. deviceNames.add (info.name);
  1072. deviceIDs .add (info.containerID);
  1073. }
  1074. deviceNames.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
  1075. deviceIDs .appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
  1076. Array<MidiDeviceInfo> devices;
  1077. for (int i = 0; i < deviceNames.size(); ++i)
  1078. devices.add ({ deviceNames[i], deviceIDs[i] });
  1079. return devices;
  1080. }
  1081. MidiDeviceInfo getDefaultDevice()
  1082. {
  1083. auto& lastDevices = lastQueriedConnectedDevices.get();
  1084. for (auto& d : lastDevices)
  1085. if (d.isDefault)
  1086. return { d.name, d.containerID };
  1087. return {};
  1088. }
  1089. WinRTMIDIDeviceInfo getWinRTDeviceInfoForDevice (const String& deviceIdentifier)
  1090. {
  1091. auto devices = getAvailableDevices();
  1092. for (int i = 0; i < devices.size(); ++i)
  1093. if (devices.getUnchecked (i).identifier == deviceIdentifier)
  1094. return lastQueriedConnectedDevices.get()[i];
  1095. return {};
  1096. }
  1097. WinRTWrapper::ComPtr<COMFactoryType>& factory;
  1098. Array<WinRTMIDIDeviceInfo> connectedDevices;
  1099. CriticalSection deviceChanges;
  1100. ThreadLocalValue<Array<WinRTMIDIDeviceInfo>> lastQueriedConnectedDevices;
  1101. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiIODeviceWatcher);
  1102. };
  1103. //==============================================================================
  1104. template <typename COMFactoryType, typename COMInterfaceType, typename COMType>
  1105. struct OpenMidiPortThread : public Thread
  1106. {
  1107. OpenMidiPortThread (String threadName, String midiDeviceID,
  1108. WinRTWrapper::ComPtr<COMFactoryType>& comFactory,
  1109. WinRTWrapper::ComPtr<COMInterfaceType>& comPort)
  1110. : Thread (threadName),
  1111. deviceID (midiDeviceID),
  1112. factory (comFactory),
  1113. port (comPort)
  1114. {
  1115. }
  1116. ~OpenMidiPortThread()
  1117. {
  1118. stopThread (2000);
  1119. }
  1120. void run() override
  1121. {
  1122. WinRTWrapper::ScopedHString hDeviceId (deviceID);
  1123. WinRTWrapper::ComPtr<IAsyncOperation<COMType*>> asyncOp;
  1124. auto hr = factory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress());
  1125. if (FAILED (hr))
  1126. return;
  1127. hr = asyncOp->put_Completed (Callback<IAsyncOperationCompletedHandler<COMType*>> (
  1128. [this] (IAsyncOperation<COMType*>* asyncOpPtr, AsyncStatus)
  1129. {
  1130. if (asyncOpPtr == nullptr)
  1131. return E_ABORT;
  1132. auto hr = asyncOpPtr->GetResults (port.resetAndGetPointerAddress());
  1133. if (FAILED (hr))
  1134. return hr;
  1135. portOpened.signal();
  1136. return S_OK;
  1137. }
  1138. ).Get());
  1139. // We need to use a timeout here, rather than waiting indefinitely, as the
  1140. // WinRT API can occasionally hang!
  1141. portOpened.wait (2000);
  1142. }
  1143. const String deviceID;
  1144. WinRTWrapper::ComPtr<COMFactoryType>& factory;
  1145. WinRTWrapper::ComPtr<COMInterfaceType>& port;
  1146. WaitableEvent portOpened { true };
  1147. };
  1148. //==============================================================================
  1149. template <typename MIDIIOStaticsType, typename MIDIPort>
  1150. class WinRTIOWrapper : private BLEDeviceWatcher::Listener
  1151. {
  1152. public:
  1153. WinRTIOWrapper (BLEDeviceWatcher& bleWatcher,
  1154. MidiIODeviceWatcher<MIDIIOStaticsType>& midiDeviceWatcher,
  1155. const String& deviceIdentifier)
  1156. : bleDeviceWatcher (bleWatcher)
  1157. {
  1158. {
  1159. const ScopedLock lock (midiDeviceWatcher.deviceChanges);
  1160. deviceInfo = midiDeviceWatcher.getWinRTDeviceInfoForDevice (deviceIdentifier);
  1161. }
  1162. if (deviceInfo.deviceID.isEmpty())
  1163. throw std::runtime_error ("Invalid device index");
  1164. JUCE_WINRT_MIDI_LOG ("Creating JUCE MIDI IO: " << deviceInfo.deviceID);
  1165. if (deviceInfo.containerID.isNotEmpty())
  1166. {
  1167. bleDeviceWatcher.addListener (this);
  1168. const ScopedLock lock (bleDeviceWatcher.deviceChanges);
  1169. HashMap<String, BLEDeviceWatcher::DeviceInfo>::Iterator iter (bleDeviceWatcher.devices);
  1170. while (iter.next())
  1171. {
  1172. if (iter.getValue().containerID == deviceInfo.containerID)
  1173. {
  1174. isBLEDevice = true;
  1175. break;
  1176. }
  1177. }
  1178. }
  1179. }
  1180. virtual ~WinRTIOWrapper()
  1181. {
  1182. bleDeviceWatcher.removeListener (this);
  1183. disconnect();
  1184. }
  1185. //==============================================================================
  1186. virtual void disconnect()
  1187. {
  1188. if (midiPort != nullptr)
  1189. {
  1190. if (isBLEDevice)
  1191. midiPort->Release();
  1192. }
  1193. midiPort = nullptr;
  1194. }
  1195. private:
  1196. //==============================================================================
  1197. void bleDeviceAdded (const String& containerID) override
  1198. {
  1199. if (containerID == deviceInfo.containerID)
  1200. isBLEDevice = true;
  1201. }
  1202. void bleDeviceDisconnected (const String& containerID) override
  1203. {
  1204. if (containerID == deviceInfo.containerID)
  1205. {
  1206. JUCE_WINRT_MIDI_LOG ("Disconnecting MIDI port from BLE disconnection: " << deviceInfo.deviceID
  1207. << " " << deviceInfo.containerID << " " << deviceInfo.name);
  1208. disconnect();
  1209. }
  1210. }
  1211. protected:
  1212. //==============================================================================
  1213. BLEDeviceWatcher& bleDeviceWatcher;
  1214. WinRTMIDIDeviceInfo deviceInfo;
  1215. bool isBLEDevice = false;
  1216. WinRTWrapper::ComPtr<MIDIPort> midiPort;
  1217. };
  1218. //==============================================================================
  1219. struct WinRTInputWrapper final : public InputWrapper,
  1220. private WinRTIOWrapper<IMidiInPortStatics, IMidiInPort>
  1221. {
  1222. WinRTInputWrapper (WinRTMidiService& service, MidiInput& input, const String& deviceIdentifier, MidiInputCallback& cb)
  1223. : WinRTIOWrapper <IMidiInPortStatics, IMidiInPort> (*service.bleDeviceWatcher, *service.inputDeviceWatcher, deviceIdentifier),
  1224. inputDevice (input),
  1225. callback (cb)
  1226. {
  1227. OpenMidiPortThread<IMidiInPortStatics, IMidiInPort, MidiInPort> portThread ("Open WinRT MIDI input port",
  1228. deviceInfo.deviceID,
  1229. service.midiInFactory,
  1230. midiPort);
  1231. portThread.startThread();
  1232. portThread.waitForThreadToExit (-1);
  1233. if (midiPort == nullptr)
  1234. {
  1235. JUCE_WINRT_MIDI_LOG ("Timed out waiting for midi input port creation");
  1236. return;
  1237. }
  1238. startTime = Time::getMillisecondCounterHiRes();
  1239. auto hr = midiPort->add_MessageReceived (
  1240. Callback<ITypedEventHandler<MidiInPort*, MidiMessageReceivedEventArgs*>> (
  1241. [this] (IMidiInPort*, IMidiMessageReceivedEventArgs* args) { return midiInMessageReceived (args); }
  1242. ).Get(),
  1243. &midiInMessageToken);
  1244. if (FAILED (hr))
  1245. {
  1246. JUCE_WINRT_MIDI_LOG ("Failed to set MIDI input callback");
  1247. jassertfalse;
  1248. }
  1249. }
  1250. ~WinRTInputWrapper()
  1251. {
  1252. disconnect();
  1253. }
  1254. //==============================================================================
  1255. void start() override
  1256. {
  1257. if (! isStarted)
  1258. {
  1259. concatenator.reset();
  1260. isStarted = true;
  1261. }
  1262. }
  1263. void stop() override
  1264. {
  1265. if (isStarted)
  1266. {
  1267. isStarted = false;
  1268. concatenator.reset();
  1269. }
  1270. }
  1271. String getDeviceIdentifier() override { return deviceInfo.containerID; }
  1272. String getDeviceName() override { return deviceInfo.name; }
  1273. //==============================================================================
  1274. void disconnect() override
  1275. {
  1276. stop();
  1277. if (midiPort != nullptr && midiInMessageToken.value != 0)
  1278. midiPort->remove_MessageReceived (midiInMessageToken);
  1279. WinRTIOWrapper<IMidiInPortStatics, IMidiInPort>::disconnect();
  1280. }
  1281. //==============================================================================
  1282. HRESULT midiInMessageReceived (IMidiMessageReceivedEventArgs* args)
  1283. {
  1284. if (! isStarted)
  1285. return S_OK;
  1286. WinRTWrapper::ComPtr<IMidiMessage> message;
  1287. auto hr = args->get_Message (message.resetAndGetPointerAddress());
  1288. if (FAILED (hr))
  1289. return hr;
  1290. WinRTWrapper::ComPtr<IBuffer> buffer;
  1291. hr = message->get_RawData (buffer.resetAndGetPointerAddress());
  1292. if (FAILED (hr))
  1293. return hr;
  1294. WinRTWrapper::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
  1295. hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress());
  1296. if (FAILED (hr))
  1297. return hr;
  1298. uint8_t* bufferData = nullptr;
  1299. hr = bufferByteAccess->Buffer (&bufferData);
  1300. if (FAILED (hr))
  1301. return hr;
  1302. uint32_t numBytes = 0;
  1303. hr = buffer->get_Length (&numBytes);
  1304. if (FAILED (hr))
  1305. return hr;
  1306. ABI::Windows::Foundation::TimeSpan timespan;
  1307. hr = message->get_Timestamp (&timespan);
  1308. if (FAILED (hr))
  1309. return hr;
  1310. concatenator.pushMidiData (bufferData, numBytes,
  1311. convertTimeStamp (timespan.Duration),
  1312. &inputDevice, callback);
  1313. return S_OK;
  1314. }
  1315. double convertTimeStamp (int64 timestamp)
  1316. {
  1317. auto millisecondsSinceStart = static_cast<double> (timestamp) / 10000.0;
  1318. auto t = startTime + millisecondsSinceStart;
  1319. auto now = Time::getMillisecondCounterHiRes();
  1320. if (t > now)
  1321. {
  1322. if (t > now + 2.0)
  1323. startTime -= 1.0;
  1324. t = now;
  1325. }
  1326. return t * 0.001;
  1327. }
  1328. //==============================================================================
  1329. MidiInput& inputDevice;
  1330. MidiInputCallback& callback;
  1331. MidiDataConcatenator concatenator { 4096 };
  1332. EventRegistrationToken midiInMessageToken { 0 };
  1333. double startTime = 0;
  1334. bool isStarted = false;
  1335. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTInputWrapper);
  1336. };
  1337. //==============================================================================
  1338. struct WinRTOutputWrapper final : public OutputWrapper,
  1339. private WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort>
  1340. {
  1341. WinRTOutputWrapper (WinRTMidiService& service, const String& deviceIdentifier)
  1342. : WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort> (*service.bleDeviceWatcher, *service.outputDeviceWatcher, deviceIdentifier)
  1343. {
  1344. OpenMidiPortThread<IMidiOutPortStatics, IMidiOutPort, IMidiOutPort> portThread ("Open WinRT MIDI output port",
  1345. deviceInfo.deviceID,
  1346. service.midiOutFactory,
  1347. midiPort);
  1348. portThread.startThread();
  1349. portThread.waitForThreadToExit (-1);
  1350. if (midiPort == nullptr)
  1351. throw std::runtime_error ("Timed out waiting for midi output port creation");
  1352. auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
  1353. if (wrtWrapper == nullptr)
  1354. throw std::runtime_error ("Failed to get the WinRTWrapper singleton!");
  1355. auto bufferFactory = wrtWrapper->getWRLFactory<IBufferFactory> (&RuntimeClass_Windows_Storage_Streams_Buffer[0]);
  1356. if (bufferFactory == nullptr)
  1357. throw std::runtime_error ("Failed to create output buffer factory");
  1358. auto hr = bufferFactory->Create (static_cast<UINT32> (65536), buffer.resetAndGetPointerAddress());
  1359. if (FAILED (hr))
  1360. throw std::runtime_error ("Failed to create output buffer");
  1361. hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress());
  1362. if (FAILED (hr))
  1363. throw std::runtime_error ("Failed to get buffer byte access");
  1364. hr = bufferByteAccess->Buffer (&bufferData);
  1365. if (FAILED (hr))
  1366. throw std::runtime_error ("Failed to get buffer data pointer");
  1367. }
  1368. //==============================================================================
  1369. void sendMessageNow (const MidiMessage& message) override
  1370. {
  1371. if (midiPort == nullptr)
  1372. return;
  1373. auto numBytes = message.getRawDataSize();
  1374. auto hr = buffer->put_Length (numBytes);
  1375. if (FAILED (hr))
  1376. {
  1377. jassertfalse;
  1378. return;
  1379. }
  1380. memcpy_s (bufferData, numBytes, message.getRawData(), numBytes);
  1381. midiPort->SendBuffer (buffer);
  1382. }
  1383. String getDeviceIdentifier() override { return deviceInfo.containerID; }
  1384. String getDeviceName() override { return deviceInfo.name; }
  1385. //==============================================================================
  1386. WinRTWrapper::ComPtr<IBuffer> buffer;
  1387. WinRTWrapper::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
  1388. uint8_t* bufferData = nullptr;
  1389. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTOutputWrapper);
  1390. };
  1391. WinRTWrapper::ComPtr<IMidiInPortStatics> midiInFactory;
  1392. WinRTWrapper::ComPtr<IMidiOutPortStatics> midiOutFactory;
  1393. std::unique_ptr<MidiIODeviceWatcher<IMidiInPortStatics>> inputDeviceWatcher;
  1394. std::unique_ptr<MidiIODeviceWatcher<IMidiOutPortStatics>> outputDeviceWatcher;
  1395. std::unique_ptr<BLEDeviceWatcher> bleDeviceWatcher;
  1396. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTMidiService)
  1397. };
  1398. #endif // JUCE_USE_WINRT_MIDI
  1399. //==============================================================================
  1400. //==============================================================================
  1401. #if ! JUCE_MINGW
  1402. extern RTL_OSVERSIONINFOW getWindowsVersionInfo();
  1403. #endif
  1404. struct MidiService : public DeletedAtShutdown
  1405. {
  1406. MidiService()
  1407. {
  1408. #if JUCE_USE_WINRT_MIDI && ! JUCE_MINGW
  1409. #if ! JUCE_FORCE_WINRT_MIDI
  1410. auto windowsVersionInfo = getWindowsVersionInfo();
  1411. if (windowsVersionInfo.dwMajorVersion >= 10 && windowsVersionInfo.dwBuildNumber >= 17763)
  1412. #endif
  1413. {
  1414. try
  1415. {
  1416. internal.reset (new WinRTMidiService());
  1417. return;
  1418. }
  1419. catch (std::runtime_error&) {}
  1420. }
  1421. #endif
  1422. internal.reset (new Win32MidiService());
  1423. }
  1424. ~MidiService()
  1425. {
  1426. clearSingletonInstance();
  1427. }
  1428. static MidiServiceType& getService()
  1429. {
  1430. jassert (getInstance()->internal != nullptr);
  1431. return *getInstance()->internal.get();
  1432. }
  1433. JUCE_DECLARE_SINGLETON (MidiService, false)
  1434. private:
  1435. std::unique_ptr<MidiServiceType> internal;
  1436. };
  1437. JUCE_IMPLEMENT_SINGLETON (MidiService)
  1438. //==============================================================================
  1439. static int findDefaultDeviceIndex (const Array<MidiDeviceInfo>& available, const MidiDeviceInfo& defaultDevice)
  1440. {
  1441. for (int i = 0; i < available.size(); ++i)
  1442. if (available.getUnchecked (i) == defaultDevice)
  1443. return i;
  1444. return 0;
  1445. }
  1446. Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
  1447. {
  1448. return MidiService::getService().getAvailableDevices (true);
  1449. }
  1450. MidiDeviceInfo MidiInput::getDefaultDevice()
  1451. {
  1452. return MidiService::getService().getDefaultDevice (true);
  1453. }
  1454. std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
  1455. {
  1456. if (deviceIdentifier.isEmpty() || callback == nullptr)
  1457. return {};
  1458. std::unique_ptr<MidiInput> in (new MidiInput ({}, deviceIdentifier));
  1459. std::unique_ptr<MidiServiceType::InputWrapper> wrapper;
  1460. try
  1461. {
  1462. wrapper.reset (MidiService::getService().createInputWrapper (*in, deviceIdentifier, *callback));
  1463. }
  1464. catch (std::runtime_error&)
  1465. {
  1466. return {};
  1467. }
  1468. in->setName (wrapper->getDeviceName());
  1469. in->internal = wrapper.release();
  1470. return in;
  1471. }
  1472. StringArray MidiInput::getDevices()
  1473. {
  1474. StringArray deviceNames;
  1475. for (auto& d : getAvailableDevices())
  1476. deviceNames.add (d.name);
  1477. return deviceNames;
  1478. }
  1479. int MidiInput::getDefaultDeviceIndex()
  1480. {
  1481. return findDefaultDeviceIndex (getAvailableDevices(), getDefaultDevice());
  1482. }
  1483. std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback)
  1484. {
  1485. return openDevice (getAvailableDevices()[index].identifier, callback);
  1486. }
  1487. MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier)
  1488. : deviceInfo (deviceName, deviceIdentifier)
  1489. {
  1490. }
  1491. MidiInput::~MidiInput()
  1492. {
  1493. delete static_cast<MidiServiceType::InputWrapper*> (internal);
  1494. }
  1495. void MidiInput::start() { static_cast<MidiServiceType::InputWrapper*> (internal)->start(); }
  1496. void MidiInput::stop() { static_cast<MidiServiceType::InputWrapper*> (internal)->stop(); }
  1497. //==============================================================================
  1498. Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
  1499. {
  1500. return MidiService::getService().getAvailableDevices (false);
  1501. }
  1502. MidiDeviceInfo MidiOutput::getDefaultDevice()
  1503. {
  1504. return MidiService::getService().getDefaultDevice (false);
  1505. }
  1506. std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier)
  1507. {
  1508. if (deviceIdentifier.isEmpty())
  1509. return {};
  1510. std::unique_ptr<MidiServiceType::OutputWrapper> wrapper;
  1511. try
  1512. {
  1513. wrapper.reset (MidiService::getService().createOutputWrapper (deviceIdentifier));
  1514. }
  1515. catch (std::runtime_error&)
  1516. {
  1517. return {};
  1518. }
  1519. std::unique_ptr<MidiOutput> out;
  1520. out.reset (new MidiOutput (wrapper->getDeviceName(), deviceIdentifier));
  1521. out->internal = wrapper.release();
  1522. return out;
  1523. }
  1524. StringArray MidiOutput::getDevices()
  1525. {
  1526. StringArray deviceNames;
  1527. for (auto& d : getAvailableDevices())
  1528. deviceNames.add (d.name);
  1529. return deviceNames;
  1530. }
  1531. int MidiOutput::getDefaultDeviceIndex()
  1532. {
  1533. return findDefaultDeviceIndex (getAvailableDevices(), getDefaultDevice());
  1534. }
  1535. std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index)
  1536. {
  1537. return openDevice (getAvailableDevices()[index].identifier);
  1538. }
  1539. MidiOutput::~MidiOutput()
  1540. {
  1541. stopBackgroundThread();
  1542. delete static_cast<MidiServiceType::OutputWrapper*> (internal);
  1543. }
  1544. void MidiOutput::sendMessageNow (const MidiMessage& message)
  1545. {
  1546. static_cast<MidiServiceType::OutputWrapper*> (internal)->sendMessageNow (message);
  1547. }
  1548. } // namespace juce