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.

1227 lines
46KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 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. #ifndef JUCE_WASAPI_LOGGING
  19. #define JUCE_WASAPI_LOGGING 0
  20. #endif
  21. //==============================================================================
  22. namespace WasapiClasses
  23. {
  24. void logFailure (HRESULT hr)
  25. {
  26. (void) hr;
  27. jassert (hr != 0x800401f0); // If you hit this, it means you're trying to call from
  28. // a thread which hasn't been initialised with CoInitialize().
  29. #if JUCE_WASAPI_LOGGING
  30. if (FAILED (hr))
  31. {
  32. const char* m = nullptr;
  33. switch (hr)
  34. {
  35. case E_POINTER: m = "E_POINTER"; break;
  36. case E_INVALIDARG: m = "E_INVALIDARG"; break;
  37. case AUDCLNT_E_NOT_INITIALIZED: m = "AUDCLNT_E_NOT_INITIALIZED"; break;
  38. case AUDCLNT_E_ALREADY_INITIALIZED: m = "AUDCLNT_E_ALREADY_INITIALIZED"; break;
  39. case AUDCLNT_E_WRONG_ENDPOINT_TYPE: m = "AUDCLNT_E_WRONG_ENDPOINT_TYPE"; break;
  40. case AUDCLNT_E_DEVICE_INVALIDATED: m = "AUDCLNT_E_DEVICE_INVALIDATED"; break;
  41. case AUDCLNT_E_NOT_STOPPED: m = "AUDCLNT_E_NOT_STOPPED"; break;
  42. case AUDCLNT_E_BUFFER_TOO_LARGE: m = "AUDCLNT_E_BUFFER_TOO_LARGE"; break;
  43. case AUDCLNT_E_OUT_OF_ORDER: m = "AUDCLNT_E_OUT_OF_ORDER"; break;
  44. case AUDCLNT_E_UNSUPPORTED_FORMAT: m = "AUDCLNT_E_UNSUPPORTED_FORMAT"; break;
  45. case AUDCLNT_E_INVALID_SIZE: m = "AUDCLNT_E_INVALID_SIZE"; break;
  46. case AUDCLNT_E_DEVICE_IN_USE: m = "AUDCLNT_E_DEVICE_IN_USE"; break;
  47. case AUDCLNT_E_BUFFER_OPERATION_PENDING: m = "AUDCLNT_E_BUFFER_OPERATION_PENDING"; break;
  48. case AUDCLNT_E_THREAD_NOT_REGISTERED: m = "AUDCLNT_E_THREAD_NOT_REGISTERED"; break;
  49. case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: m = "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED"; break;
  50. case AUDCLNT_E_ENDPOINT_CREATE_FAILED: m = "AUDCLNT_E_ENDPOINT_CREATE_FAILED"; break;
  51. case AUDCLNT_E_SERVICE_NOT_RUNNING: m = "AUDCLNT_E_SERVICE_NOT_RUNNING"; break;
  52. case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED: m = "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED"; break;
  53. case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: m = "AUDCLNT_E_EXCLUSIVE_MODE_ONLY"; break;
  54. case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: m = "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL"; break;
  55. case AUDCLNT_E_EVENTHANDLE_NOT_SET: m = "AUDCLNT_E_EVENTHANDLE_NOT_SET"; break;
  56. case AUDCLNT_E_INCORRECT_BUFFER_SIZE: m = "AUDCLNT_E_INCORRECT_BUFFER_SIZE"; break;
  57. case AUDCLNT_E_BUFFER_SIZE_ERROR: m = "AUDCLNT_E_BUFFER_SIZE_ERROR"; break;
  58. case AUDCLNT_S_BUFFER_EMPTY: m = "AUDCLNT_S_BUFFER_EMPTY"; break;
  59. case AUDCLNT_S_THREAD_ALREADY_REGISTERED: m = "AUDCLNT_S_THREAD_ALREADY_REGISTERED"; break;
  60. default: break;
  61. }
  62. Logger::writeToLog ("WASAPI error: " + (m != nullptr ? String (m)
  63. : String::toHexString ((int) hr)));
  64. }
  65. #endif
  66. }
  67. #undef check
  68. bool check (HRESULT hr)
  69. {
  70. logFailure (hr);
  71. return SUCCEEDED (hr);
  72. }
  73. //==============================================================================
  74. namespace
  75. {
  76. enum EDataFlow
  77. {
  78. eRender = 0,
  79. eCapture = (eRender + 1),
  80. eAll = (eCapture + 1)
  81. };
  82. enum { DEVICE_STATE_ACTIVE = 1 };
  83. struct __declspec (uuid ("D666063F-1587-4E43-81F1-B948E807363F")) IMMDevice : public IUnknown
  84. {
  85. virtual HRESULT STDMETHODCALLTYPE Activate(REFIID, DWORD, PROPVARIANT*, void**) = 0;
  86. virtual HRESULT STDMETHODCALLTYPE OpenPropertyStore(DWORD, IPropertyStore**) = 0;
  87. virtual HRESULT STDMETHODCALLTYPE GetId(LPWSTR*) = 0;
  88. virtual HRESULT STDMETHODCALLTYPE GetState(DWORD*) = 0;
  89. };
  90. struct __declspec (uuid ("1BE09788-6894-4089-8586-9A2A6C265AC5")) IMMEndpoint : public IUnknown
  91. {
  92. virtual HRESULT STDMETHODCALLTYPE GetDataFlow(EDataFlow*) = 0;
  93. };
  94. struct IMMDeviceCollection : public IUnknown
  95. {
  96. virtual HRESULT STDMETHODCALLTYPE GetCount(UINT*) = 0;
  97. virtual HRESULT STDMETHODCALLTYPE Item(UINT, IMMDevice**) = 0;
  98. };
  99. enum ERole
  100. {
  101. eConsole = 0,
  102. eMultimedia = (eConsole + 1),
  103. eCommunications = (eMultimedia + 1)
  104. };
  105. struct IMMNotificationClient : public IUnknown
  106. {
  107. virtual HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR, DWORD) = 0;
  108. virtual HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR) = 0;
  109. virtual HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR) = 0;
  110. virtual HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow, ERole, LPCWSTR) = 0;
  111. virtual HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY) = 0;
  112. };
  113. struct __declspec (uuid ("A95664D2-9614-4F35-A746-DE8DB63617E6")) IMMDeviceEnumerator : public IUnknown
  114. {
  115. virtual HRESULT STDMETHODCALLTYPE EnumAudioEndpoints(EDataFlow, DWORD, IMMDeviceCollection**) = 0;
  116. virtual HRESULT STDMETHODCALLTYPE GetDefaultAudioEndpoint(EDataFlow, ERole, IMMDevice**) = 0;
  117. virtual HRESULT STDMETHODCALLTYPE GetDevice(LPCWSTR, IMMDevice**) = 0;
  118. virtual HRESULT STDMETHODCALLTYPE RegisterEndpointNotificationCallback(IMMNotificationClient*) = 0;
  119. virtual HRESULT STDMETHODCALLTYPE UnregisterEndpointNotificationCallback(IMMNotificationClient*) = 0;
  120. };
  121. struct __declspec (uuid ("BCDE0395-E52F-467C-8E3D-C4579291692E")) MMDeviceEnumerator;
  122. }
  123. String getDeviceID (IMMDevice* const device)
  124. {
  125. String s;
  126. WCHAR* deviceId = nullptr;
  127. if (check (device->GetId (&deviceId)))
  128. {
  129. s = String (deviceId);
  130. CoTaskMemFree (deviceId);
  131. }
  132. return s;
  133. }
  134. EDataFlow getDataFlow (const ComSmartPtr<IMMDevice>& device)
  135. {
  136. EDataFlow flow = eRender;
  137. ComSmartPtr <IMMEndpoint> endPoint;
  138. if (check (device.QueryInterface (endPoint)))
  139. (void) check (endPoint->GetDataFlow (&flow));
  140. return flow;
  141. }
  142. int refTimeToSamples (const REFERENCE_TIME& t, const double sampleRate) noexcept
  143. {
  144. return roundDoubleToInt (sampleRate * ((double) t) * 0.0000001);
  145. }
  146. void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* const src) noexcept
  147. {
  148. memcpy (&dest, src, src->wFormatTag == WAVE_FORMAT_EXTENSIBLE ? sizeof (WAVEFORMATEXTENSIBLE)
  149. : sizeof (WAVEFORMATEX));
  150. }
  151. //==============================================================================
  152. class WASAPIDeviceBase
  153. {
  154. public:
  155. WASAPIDeviceBase (const ComSmartPtr <IMMDevice>& device_, const bool useExclusiveMode_)
  156. : device (device_),
  157. sampleRate (0),
  158. defaultSampleRate (0),
  159. numChannels (0),
  160. actualNumChannels (0),
  161. minBufferSize (0),
  162. defaultBufferSize (0),
  163. latencySamples (0),
  164. useExclusiveMode (useExclusiveMode_),
  165. sampleRateHasChanged (false)
  166. {
  167. clientEvent = CreateEvent (0, false, false, _T("JuceWASAPI"));
  168. ComSmartPtr <IAudioClient> tempClient (createClient());
  169. if (tempClient == nullptr)
  170. return;
  171. REFERENCE_TIME defaultPeriod, minPeriod;
  172. if (! check (tempClient->GetDevicePeriod (&defaultPeriod, &minPeriod)))
  173. return;
  174. WAVEFORMATEX* mixFormat = nullptr;
  175. if (! check (tempClient->GetMixFormat (&mixFormat)))
  176. return;
  177. WAVEFORMATEXTENSIBLE format;
  178. copyWavFormat (format, mixFormat);
  179. CoTaskMemFree (mixFormat);
  180. actualNumChannels = numChannels = format.Format.nChannels;
  181. defaultSampleRate = format.Format.nSamplesPerSec;
  182. minBufferSize = refTimeToSamples (minPeriod, defaultSampleRate);
  183. defaultBufferSize = refTimeToSamples (defaultPeriod, defaultSampleRate);
  184. rates.addUsingDefaultSort (defaultSampleRate);
  185. static const double ratesToTest[] = { 44100.0, 48000.0, 88200.0, 96000.0 };
  186. for (int i = 0; i < numElementsInArray (ratesToTest); ++i)
  187. {
  188. if (ratesToTest[i] == defaultSampleRate)
  189. continue;
  190. format.Format.nSamplesPerSec = (DWORD) roundDoubleToInt (ratesToTest[i]);
  191. if (SUCCEEDED (tempClient->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
  192. (WAVEFORMATEX*) &format, 0)))
  193. if (! rates.contains (ratesToTest[i]))
  194. rates.addUsingDefaultSort (ratesToTest[i]);
  195. }
  196. }
  197. ~WASAPIDeviceBase()
  198. {
  199. device = nullptr;
  200. CloseHandle (clientEvent);
  201. }
  202. bool isOk() const noexcept { return defaultBufferSize > 0 && defaultSampleRate > 0; }
  203. bool openClient (const double newSampleRate, const BigInteger& newChannels)
  204. {
  205. sampleRate = newSampleRate;
  206. channels = newChannels;
  207. channels.setRange (actualNumChannels, channels.getHighestBit() + 1 - actualNumChannels, false);
  208. numChannels = channels.getHighestBit() + 1;
  209. if (numChannels == 0)
  210. return true;
  211. client = createClient();
  212. if (client != nullptr
  213. && (tryInitialisingWithFormat (true, 4) || tryInitialisingWithFormat (false, 4)
  214. || tryInitialisingWithFormat (false, 3) || tryInitialisingWithFormat (false, 2)))
  215. {
  216. sampleRateHasChanged = false;
  217. channelMaps.clear();
  218. for (int i = 0; i <= channels.getHighestBit(); ++i)
  219. if (channels[i])
  220. channelMaps.add (i);
  221. REFERENCE_TIME latency;
  222. if (check (client->GetStreamLatency (&latency)))
  223. latencySamples = refTimeToSamples (latency, sampleRate);
  224. (void) check (client->GetBufferSize (&actualBufferSize));
  225. createSessionEventCallback();
  226. return check (client->SetEventHandle (clientEvent));
  227. }
  228. return false;
  229. }
  230. void closeClient()
  231. {
  232. if (client != nullptr)
  233. client->Stop();
  234. deleteSessionEventCallback();
  235. client = nullptr;
  236. ResetEvent (clientEvent);
  237. }
  238. void deviceSampleRateChanged()
  239. {
  240. sampleRateHasChanged = true;
  241. }
  242. //==============================================================================
  243. ComSmartPtr <IMMDevice> device;
  244. ComSmartPtr <IAudioClient> client;
  245. double sampleRate, defaultSampleRate;
  246. int numChannels, actualNumChannels;
  247. int minBufferSize, defaultBufferSize, latencySamples;
  248. const bool useExclusiveMode;
  249. Array <double> rates;
  250. HANDLE clientEvent;
  251. BigInteger channels;
  252. Array <int> channelMaps;
  253. UINT32 actualBufferSize;
  254. int bytesPerSample;
  255. bool sampleRateHasChanged;
  256. virtual void updateFormat (bool isFloat) = 0;
  257. private:
  258. //==============================================================================
  259. class SessionEventCallback : public ComBaseClassHelper <IAudioSessionEvents>
  260. {
  261. public:
  262. SessionEventCallback (WASAPIDeviceBase& owner_) : owner (owner_) {}
  263. JUCE_COMRESULT OnDisplayNameChanged (LPCWSTR, LPCGUID) { return S_OK; }
  264. JUCE_COMRESULT OnIconPathChanged (LPCWSTR, LPCGUID) { return S_OK; }
  265. JUCE_COMRESULT OnSimpleVolumeChanged (float, BOOL, LPCGUID) { return S_OK; }
  266. JUCE_COMRESULT OnChannelVolumeChanged (DWORD, float*, DWORD, LPCGUID) { return S_OK; }
  267. JUCE_COMRESULT OnGroupingParamChanged (LPCGUID, LPCGUID) { return S_OK; }
  268. JUCE_COMRESULT OnStateChanged (AudioSessionState) { return S_OK; }
  269. JUCE_COMRESULT OnSessionDisconnected (AudioSessionDisconnectReason reason)
  270. {
  271. if (reason == DisconnectReasonFormatChanged)
  272. owner.deviceSampleRateChanged();
  273. return S_OK;
  274. }
  275. private:
  276. WASAPIDeviceBase& owner;
  277. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SessionEventCallback)
  278. };
  279. ComSmartPtr <IAudioSessionControl> audioSessionControl;
  280. ComSmartPtr <SessionEventCallback> sessionEventCallback;
  281. void createSessionEventCallback()
  282. {
  283. deleteSessionEventCallback();
  284. client->GetService (__uuidof (IAudioSessionControl),
  285. (void**) audioSessionControl.resetAndGetPointerAddress());
  286. if (audioSessionControl != nullptr)
  287. {
  288. sessionEventCallback = new SessionEventCallback (*this);
  289. audioSessionControl->RegisterAudioSessionNotification (sessionEventCallback);
  290. sessionEventCallback->Release(); // (required because ComBaseClassHelper objects are constructed with a ref count of 1)
  291. }
  292. }
  293. void deleteSessionEventCallback()
  294. {
  295. if (audioSessionControl != nullptr && sessionEventCallback != nullptr)
  296. audioSessionControl->UnregisterAudioSessionNotification (sessionEventCallback);
  297. audioSessionControl = nullptr;
  298. sessionEventCallback = nullptr;
  299. }
  300. //==============================================================================
  301. const ComSmartPtr <IAudioClient> createClient()
  302. {
  303. ComSmartPtr <IAudioClient> client;
  304. if (device != nullptr)
  305. {
  306. HRESULT hr = device->Activate (__uuidof (IAudioClient), CLSCTX_INPROC_SERVER, 0, (void**) client.resetAndGetPointerAddress());
  307. logFailure (hr);
  308. }
  309. return client;
  310. }
  311. bool tryInitialisingWithFormat (const bool useFloat, const int bytesPerSampleToTry)
  312. {
  313. WAVEFORMATEXTENSIBLE format = { 0 };
  314. if (numChannels <= 2 && bytesPerSampleToTry <= 2)
  315. {
  316. format.Format.wFormatTag = WAVE_FORMAT_PCM;
  317. }
  318. else
  319. {
  320. format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
  321. format.Format.cbSize = sizeof (WAVEFORMATEXTENSIBLE) - sizeof (WAVEFORMATEX);
  322. }
  323. format.Format.nSamplesPerSec = (DWORD) roundDoubleToInt (sampleRate);
  324. format.Format.nChannels = (WORD) numChannels;
  325. format.Format.wBitsPerSample = (WORD) (8 * bytesPerSampleToTry);
  326. format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * numChannels * bytesPerSampleToTry);
  327. format.Format.nBlockAlign = (WORD) (numChannels * bytesPerSampleToTry);
  328. format.SubFormat = useFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM;
  329. format.Samples.wValidBitsPerSample = format.Format.wBitsPerSample;
  330. switch (numChannels)
  331. {
  332. case 1: format.dwChannelMask = SPEAKER_FRONT_CENTER; break;
  333. case 2: format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; break;
  334. case 4: format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break;
  335. case 6: format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break;
  336. case 8: format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER; break;
  337. default: break;
  338. }
  339. WAVEFORMATEXTENSIBLE* nearestFormat = nullptr;
  340. HRESULT hr = client->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
  341. (WAVEFORMATEX*) &format, useExclusiveMode ? nullptr : (WAVEFORMATEX**) &nearestFormat);
  342. logFailure (hr);
  343. if (hr == S_FALSE && format.Format.nSamplesPerSec == nearestFormat->Format.nSamplesPerSec)
  344. {
  345. copyWavFormat (format, (WAVEFORMATEX*) nearestFormat);
  346. hr = S_OK;
  347. }
  348. CoTaskMemFree (nearestFormat);
  349. REFERENCE_TIME defaultPeriod = 0, minPeriod = 0;
  350. if (useExclusiveMode)
  351. check (client->GetDevicePeriod (&defaultPeriod, &minPeriod));
  352. GUID session;
  353. if (hr == S_OK
  354. && check (client->Initialize (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
  355. AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
  356. defaultPeriod, defaultPeriod, (WAVEFORMATEX*) &format, &session)))
  357. {
  358. actualNumChannels = format.Format.nChannels;
  359. const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
  360. bytesPerSample = format.Format.wBitsPerSample / 8;
  361. updateFormat (isFloat);
  362. return true;
  363. }
  364. return false;
  365. }
  366. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIDeviceBase)
  367. };
  368. //==============================================================================
  369. class WASAPIInputDevice : public WASAPIDeviceBase
  370. {
  371. public:
  372. WASAPIInputDevice (const ComSmartPtr <IMMDevice>& device_, const bool useExclusiveMode_)
  373. : WASAPIDeviceBase (device_, useExclusiveMode_),
  374. reservoir (1, 1)
  375. {
  376. }
  377. ~WASAPIInputDevice()
  378. {
  379. close();
  380. }
  381. bool open (const double newSampleRate, const BigInteger& newChannels)
  382. {
  383. reservoirSize = 0;
  384. reservoirCapacity = 16384;
  385. reservoir.setSize (actualNumChannels * reservoirCapacity * sizeof (float));
  386. return openClient (newSampleRate, newChannels)
  387. && (numChannels == 0 || check (client->GetService (__uuidof (IAudioCaptureClient),
  388. (void**) captureClient.resetAndGetPointerAddress())));
  389. }
  390. void close()
  391. {
  392. closeClient();
  393. captureClient = nullptr;
  394. reservoir.setSize (0);
  395. }
  396. template <class SourceType>
  397. void updateFormatWithType (SourceType*)
  398. {
  399. typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> NativeType;
  400. converter = new AudioData::ConverterInstance <AudioData::Pointer <SourceType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const>, NativeType> (actualNumChannels, 1);
  401. }
  402. void updateFormat (bool isFloat)
  403. {
  404. if (isFloat) updateFormatWithType ((AudioData::Float32*) 0);
  405. else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) 0);
  406. else if (bytesPerSample == 3) updateFormatWithType ((AudioData::Int24*) 0);
  407. else updateFormatWithType ((AudioData::Int16*) 0);
  408. }
  409. void copyBuffers (float** destBuffers, int numDestBuffers, int bufferSize, Thread& thread)
  410. {
  411. if (numChannels <= 0)
  412. return;
  413. int offset = 0;
  414. while (bufferSize > 0)
  415. {
  416. if (reservoirSize > 0) // There's stuff in the reservoir, so use that...
  417. {
  418. const int samplesToDo = jmin (bufferSize, (int) reservoirSize);
  419. for (int i = 0; i < numDestBuffers; ++i)
  420. converter->convertSamples (destBuffers[i] + offset, 0, reservoir.getData(), channelMaps.getUnchecked(i), samplesToDo);
  421. bufferSize -= samplesToDo;
  422. offset += samplesToDo;
  423. reservoirSize = 0;
  424. }
  425. else
  426. {
  427. UINT32 packetLength = 0;
  428. if (! check (captureClient->GetNextPacketSize (&packetLength)))
  429. break;
  430. if (packetLength == 0)
  431. {
  432. if (thread.threadShouldExit()
  433. || WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
  434. break;
  435. continue;
  436. }
  437. uint8* inputData;
  438. UINT32 numSamplesAvailable;
  439. DWORD flags;
  440. if (check (captureClient->GetBuffer (&inputData, &numSamplesAvailable, &flags, 0, 0)))
  441. {
  442. const int samplesToDo = jmin (bufferSize, (int) numSamplesAvailable);
  443. for (int i = 0; i < numDestBuffers; ++i)
  444. converter->convertSamples (destBuffers[i] + offset, 0, inputData, channelMaps.getUnchecked(i), samplesToDo);
  445. bufferSize -= samplesToDo;
  446. offset += samplesToDo;
  447. if (samplesToDo < (int) numSamplesAvailable)
  448. {
  449. reservoirSize = jmin ((int) (numSamplesAvailable - samplesToDo), reservoirCapacity);
  450. memcpy ((uint8*) reservoir.getData(), inputData + bytesPerSample * actualNumChannels * samplesToDo,
  451. (size_t) (bytesPerSample * actualNumChannels * reservoirSize));
  452. }
  453. captureClient->ReleaseBuffer (numSamplesAvailable);
  454. }
  455. }
  456. }
  457. }
  458. ComSmartPtr <IAudioCaptureClient> captureClient;
  459. MemoryBlock reservoir;
  460. int reservoirSize, reservoirCapacity;
  461. ScopedPointer <AudioData::Converter> converter;
  462. private:
  463. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIInputDevice)
  464. };
  465. //==============================================================================
  466. class WASAPIOutputDevice : public WASAPIDeviceBase
  467. {
  468. public:
  469. WASAPIOutputDevice (const ComSmartPtr <IMMDevice>& device_, const bool useExclusiveMode_)
  470. : WASAPIDeviceBase (device_, useExclusiveMode_)
  471. {
  472. }
  473. ~WASAPIOutputDevice()
  474. {
  475. close();
  476. }
  477. bool open (const double newSampleRate, const BigInteger& newChannels)
  478. {
  479. return openClient (newSampleRate, newChannels)
  480. && (numChannels == 0 || check (client->GetService (__uuidof (IAudioRenderClient), (void**) renderClient.resetAndGetPointerAddress())));
  481. }
  482. void close()
  483. {
  484. closeClient();
  485. renderClient = nullptr;
  486. }
  487. template <class DestType>
  488. void updateFormatWithType (DestType*)
  489. {
  490. typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> NativeType;
  491. converter = new AudioData::ConverterInstance <NativeType, AudioData::Pointer <DestType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> > (1, actualNumChannels);
  492. }
  493. void updateFormat (bool isFloat)
  494. {
  495. if (isFloat) updateFormatWithType ((AudioData::Float32*) 0);
  496. else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) 0);
  497. else if (bytesPerSample == 3) updateFormatWithType ((AudioData::Int24*) 0);
  498. else updateFormatWithType ((AudioData::Int16*) 0);
  499. }
  500. void copyBuffers (const float** const srcBuffers, const int numSrcBuffers, int bufferSize, Thread& thread)
  501. {
  502. if (numChannels <= 0)
  503. return;
  504. int offset = 0;
  505. while (bufferSize > 0)
  506. {
  507. UINT32 padding = 0;
  508. if (! check (client->GetCurrentPadding (&padding)))
  509. return;
  510. int samplesToDo = useExclusiveMode ? bufferSize
  511. : jmin ((int) (actualBufferSize - padding), bufferSize);
  512. if (samplesToDo <= 0)
  513. {
  514. if (thread.threadShouldExit()
  515. || WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
  516. break;
  517. continue;
  518. }
  519. uint8* outputData = nullptr;
  520. if (check (renderClient->GetBuffer ((UINT32) samplesToDo, &outputData)))
  521. {
  522. for (int i = 0; i < numSrcBuffers; ++i)
  523. converter->convertSamples (outputData, channelMaps.getUnchecked(i), srcBuffers[i] + offset, 0, samplesToDo);
  524. renderClient->ReleaseBuffer ((UINT32) samplesToDo, 0);
  525. offset += samplesToDo;
  526. bufferSize -= samplesToDo;
  527. }
  528. }
  529. }
  530. ComSmartPtr <IAudioRenderClient> renderClient;
  531. ScopedPointer <AudioData::Converter> converter;
  532. private:
  533. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIOutputDevice)
  534. };
  535. //==============================================================================
  536. class WASAPIAudioIODevice : public AudioIODevice,
  537. public Thread
  538. {
  539. public:
  540. WASAPIAudioIODevice (const String& deviceName,
  541. const String& outputDeviceId_,
  542. const String& inputDeviceId_,
  543. const bool useExclusiveMode_)
  544. : AudioIODevice (deviceName, "Windows Audio"),
  545. Thread ("Juce WASAPI"),
  546. outputDeviceId (outputDeviceId_),
  547. inputDeviceId (inputDeviceId_),
  548. useExclusiveMode (useExclusiveMode_),
  549. isOpen_ (false),
  550. isStarted (false),
  551. currentBufferSizeSamples (0),
  552. currentSampleRate (0),
  553. callback (nullptr)
  554. {
  555. }
  556. ~WASAPIAudioIODevice()
  557. {
  558. close();
  559. }
  560. bool initialise()
  561. {
  562. latencyIn = latencyOut = 0;
  563. Array <double> ratesIn, ratesOut;
  564. if (createDevices())
  565. {
  566. jassert (inputDevice != nullptr || outputDevice != nullptr);
  567. if (inputDevice != nullptr && outputDevice != nullptr)
  568. {
  569. defaultSampleRate = jmin (inputDevice->defaultSampleRate, outputDevice->defaultSampleRate);
  570. minBufferSize = jmin (inputDevice->minBufferSize, outputDevice->minBufferSize);
  571. defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize);
  572. sampleRates = inputDevice->rates;
  573. sampleRates.removeValuesNotIn (outputDevice->rates);
  574. }
  575. else
  576. {
  577. WASAPIDeviceBase* d = inputDevice != nullptr ? static_cast<WASAPIDeviceBase*> (inputDevice)
  578. : static_cast<WASAPIDeviceBase*> (outputDevice);
  579. defaultSampleRate = d->defaultSampleRate;
  580. minBufferSize = d->minBufferSize;
  581. defaultBufferSize = d->defaultBufferSize;
  582. sampleRates = d->rates;
  583. }
  584. bufferSizes.addUsingDefaultSort (defaultBufferSize);
  585. if (minBufferSize != defaultBufferSize)
  586. bufferSizes.addUsingDefaultSort (minBufferSize);
  587. int n = 64;
  588. for (int i = 0; i < 40; ++i)
  589. {
  590. if (n >= minBufferSize && n <= 2048 && ! bufferSizes.contains (n))
  591. bufferSizes.addUsingDefaultSort (n);
  592. n += (n < 512) ? 32 : (n < 1024 ? 64 : 128);
  593. }
  594. return true;
  595. }
  596. return false;
  597. }
  598. StringArray getOutputChannelNames()
  599. {
  600. StringArray outChannels;
  601. if (outputDevice != nullptr)
  602. for (int i = 1; i <= outputDevice->actualNumChannels; ++i)
  603. outChannels.add ("Output channel " + String (i));
  604. return outChannels;
  605. }
  606. StringArray getInputChannelNames()
  607. {
  608. StringArray inChannels;
  609. if (inputDevice != nullptr)
  610. for (int i = 1; i <= inputDevice->actualNumChannels; ++i)
  611. inChannels.add ("Input channel " + String (i));
  612. return inChannels;
  613. }
  614. int getNumSampleRates() { return sampleRates.size(); }
  615. double getSampleRate (int index) { return sampleRates [index]; }
  616. int getNumBufferSizesAvailable() { return bufferSizes.size(); }
  617. int getBufferSizeSamples (int index) { return bufferSizes [index]; }
  618. int getDefaultBufferSize() { return defaultBufferSize; }
  619. int getCurrentBufferSizeSamples() { return currentBufferSizeSamples; }
  620. double getCurrentSampleRate() { return currentSampleRate; }
  621. int getCurrentBitDepth() { return 32; }
  622. int getOutputLatencyInSamples() { return latencyOut; }
  623. int getInputLatencyInSamples() { return latencyIn; }
  624. BigInteger getActiveOutputChannels() const { return outputDevice != nullptr ? outputDevice->channels : BigInteger(); }
  625. BigInteger getActiveInputChannels() const { return inputDevice != nullptr ? inputDevice->channels : BigInteger(); }
  626. String getLastError() { return lastError; }
  627. String open (const BigInteger& inputChannels, const BigInteger& outputChannels,
  628. double sampleRate, int bufferSizeSamples)
  629. {
  630. close();
  631. lastError = String::empty;
  632. if (sampleRates.size() == 0 && inputDevice != nullptr && outputDevice != nullptr)
  633. {
  634. lastError = "The input and output devices don't share a common sample rate!";
  635. return lastError;
  636. }
  637. currentBufferSizeSamples = bufferSizeSamples <= 0 ? defaultBufferSize : jmax (bufferSizeSamples, minBufferSize);
  638. currentSampleRate = sampleRate > 0 ? sampleRate : defaultSampleRate;
  639. if (inputDevice != nullptr && ! inputDevice->open (currentSampleRate, inputChannels))
  640. {
  641. lastError = "Couldn't open the input device!";
  642. return lastError;
  643. }
  644. if (outputDevice != nullptr && ! outputDevice->open (currentSampleRate, outputChannels))
  645. {
  646. close();
  647. lastError = "Couldn't open the output device!";
  648. return lastError;
  649. }
  650. if (inputDevice != nullptr) ResetEvent (inputDevice->clientEvent);
  651. if (outputDevice != nullptr) ResetEvent (outputDevice->clientEvent);
  652. startThread (8);
  653. Thread::sleep (5);
  654. if (inputDevice != nullptr && inputDevice->client != nullptr)
  655. {
  656. latencyIn = (int) (inputDevice->latencySamples + currentBufferSizeSamples);
  657. if (! check (inputDevice->client->Start()))
  658. {
  659. close();
  660. lastError = "Couldn't start the input device!";
  661. return lastError;
  662. }
  663. }
  664. if (outputDevice != nullptr && outputDevice->client != nullptr)
  665. {
  666. latencyOut = (int) (outputDevice->latencySamples + currentBufferSizeSamples);
  667. if (! check (outputDevice->client->Start()))
  668. {
  669. close();
  670. lastError = "Couldn't start the output device!";
  671. return lastError;
  672. }
  673. }
  674. isOpen_ = true;
  675. return lastError;
  676. }
  677. void close()
  678. {
  679. stop();
  680. signalThreadShouldExit();
  681. if (inputDevice != nullptr) SetEvent (inputDevice->clientEvent);
  682. if (outputDevice != nullptr) SetEvent (outputDevice->clientEvent);
  683. stopThread (5000);
  684. if (inputDevice != nullptr) inputDevice->close();
  685. if (outputDevice != nullptr) outputDevice->close();
  686. isOpen_ = false;
  687. }
  688. bool isOpen() { return isOpen_ && isThreadRunning(); }
  689. bool isPlaying() { return isStarted && isOpen_ && isThreadRunning(); }
  690. void start (AudioIODeviceCallback* call)
  691. {
  692. if (isOpen_ && call != nullptr && ! isStarted)
  693. {
  694. if (! isThreadRunning())
  695. {
  696. // something's gone wrong and the thread's stopped..
  697. isOpen_ = false;
  698. return;
  699. }
  700. call->audioDeviceAboutToStart (this);
  701. const ScopedLock sl (startStopLock);
  702. callback = call;
  703. isStarted = true;
  704. }
  705. }
  706. void stop()
  707. {
  708. if (isStarted)
  709. {
  710. AudioIODeviceCallback* const callbackLocal = callback;
  711. {
  712. const ScopedLock sl (startStopLock);
  713. isStarted = false;
  714. }
  715. if (callbackLocal != nullptr)
  716. callbackLocal->audioDeviceStopped();
  717. }
  718. }
  719. void setMMThreadPriority()
  720. {
  721. DynamicLibrary dll ("avrt.dll");
  722. JUCE_LOAD_WINAPI_FUNCTION (dll, AvSetMmThreadCharacteristicsW, avSetMmThreadCharacteristics, HANDLE, (LPCWSTR, LPDWORD))
  723. JUCE_LOAD_WINAPI_FUNCTION (dll, AvSetMmThreadPriority, avSetMmThreadPriority, HANDLE, (HANDLE, AVRT_PRIORITY))
  724. if (avSetMmThreadCharacteristics != 0 && avSetMmThreadPriority != 0)
  725. {
  726. DWORD dummy = 0;
  727. HANDLE h = avSetMmThreadCharacteristics (L"Pro Audio", &dummy);
  728. if (h != 0)
  729. avSetMmThreadPriority (h, AVRT_PRIORITY_NORMAL);
  730. }
  731. }
  732. void run()
  733. {
  734. setMMThreadPriority();
  735. const int bufferSize = currentBufferSizeSamples;
  736. const int numInputBuffers = getActiveInputChannels().countNumberOfSetBits();
  737. const int numOutputBuffers = getActiveOutputChannels().countNumberOfSetBits();
  738. bool sampleRateChanged = false;
  739. AudioSampleBuffer ins (jmax (1, numInputBuffers), bufferSize + 32);
  740. AudioSampleBuffer outs (jmax (1, numOutputBuffers), bufferSize + 32);
  741. float** const inputBuffers = ins.getArrayOfChannels();
  742. float** const outputBuffers = outs.getArrayOfChannels();
  743. ins.clear();
  744. while (! threadShouldExit())
  745. {
  746. if (inputDevice != nullptr)
  747. {
  748. inputDevice->copyBuffers (inputBuffers, numInputBuffers, bufferSize, *this);
  749. if (threadShouldExit())
  750. break;
  751. if (inputDevice->sampleRateHasChanged)
  752. sampleRateChanged = true;
  753. }
  754. {
  755. const ScopedLock sl (startStopLock);
  756. if (isStarted)
  757. callback->audioDeviceIOCallback (const_cast <const float**> (inputBuffers), numInputBuffers,
  758. outputBuffers, numOutputBuffers, bufferSize);
  759. else
  760. outs.clear();
  761. }
  762. if (outputDevice != nullptr)
  763. {
  764. outputDevice->copyBuffers (const_cast <const float**> (outputBuffers), numOutputBuffers, bufferSize, *this);
  765. if (outputDevice->sampleRateHasChanged)
  766. sampleRateChanged = true;
  767. }
  768. if (sampleRateChanged)
  769. {
  770. // xxx one of the devices has had its sample rate changed externally.. not 100% sure how
  771. // to handle this..
  772. }
  773. }
  774. }
  775. //==============================================================================
  776. String outputDeviceId, inputDeviceId;
  777. String lastError;
  778. private:
  779. // Device stats...
  780. ScopedPointer<WASAPIInputDevice> inputDevice;
  781. ScopedPointer<WASAPIOutputDevice> outputDevice;
  782. const bool useExclusiveMode;
  783. double defaultSampleRate;
  784. int minBufferSize, defaultBufferSize;
  785. int latencyIn, latencyOut;
  786. Array <double> sampleRates;
  787. Array <int> bufferSizes;
  788. // Active state...
  789. bool isOpen_, isStarted;
  790. int currentBufferSizeSamples;
  791. double currentSampleRate;
  792. AudioIODeviceCallback* callback;
  793. CriticalSection startStopLock;
  794. //==============================================================================
  795. bool createDevices()
  796. {
  797. ComSmartPtr <IMMDeviceEnumerator> enumerator;
  798. if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator))))
  799. return false;
  800. ComSmartPtr <IMMDeviceCollection> deviceCollection;
  801. if (! check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress())))
  802. return false;
  803. UINT32 numDevices = 0;
  804. if (! check (deviceCollection->GetCount (&numDevices)))
  805. return false;
  806. for (UINT32 i = 0; i < numDevices; ++i)
  807. {
  808. ComSmartPtr <IMMDevice> device;
  809. if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress())))
  810. continue;
  811. const String deviceId (getDeviceID (device));
  812. if (deviceId.isEmpty())
  813. continue;
  814. const EDataFlow flow = getDataFlow (device);
  815. if (deviceId == inputDeviceId && flow == eCapture)
  816. inputDevice = new WASAPIInputDevice (device, useExclusiveMode);
  817. else if (deviceId == outputDeviceId && flow == eRender)
  818. outputDevice = new WASAPIOutputDevice (device, useExclusiveMode);
  819. }
  820. return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk()))
  821. && (inputDeviceId.isEmpty() || (inputDevice != nullptr && inputDevice->isOk()));
  822. }
  823. //==============================================================================
  824. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODevice)
  825. };
  826. //==============================================================================
  827. class WASAPIAudioIODeviceType : public AudioIODeviceType,
  828. private DeviceChangeDetector
  829. {
  830. public:
  831. WASAPIAudioIODeviceType()
  832. : AudioIODeviceType ("Windows Audio"),
  833. DeviceChangeDetector (L"Windows Audio"),
  834. hasScanned (false)
  835. {
  836. }
  837. //==============================================================================
  838. void scanForDevices()
  839. {
  840. hasScanned = true;
  841. outputDeviceNames.clear();
  842. inputDeviceNames.clear();
  843. outputDeviceIds.clear();
  844. inputDeviceIds.clear();
  845. scan (outputDeviceNames, inputDeviceNames,
  846. outputDeviceIds, inputDeviceIds);
  847. }
  848. StringArray getDeviceNames (bool wantInputNames) const
  849. {
  850. jassert (hasScanned); // need to call scanForDevices() before doing this
  851. return wantInputNames ? inputDeviceNames
  852. : outputDeviceNames;
  853. }
  854. int getDefaultDeviceIndex (bool /*forInput*/) const
  855. {
  856. jassert (hasScanned); // need to call scanForDevices() before doing this
  857. return 0;
  858. }
  859. int getIndexOfDevice (AudioIODevice* device, bool asInput) const
  860. {
  861. jassert (hasScanned); // need to call scanForDevices() before doing this
  862. WASAPIAudioIODevice* const d = dynamic_cast <WASAPIAudioIODevice*> (device);
  863. return d == nullptr ? -1 : (asInput ? inputDeviceIds.indexOf (d->inputDeviceId)
  864. : outputDeviceIds.indexOf (d->outputDeviceId));
  865. }
  866. bool hasSeparateInputsAndOutputs() const { return true; }
  867. AudioIODevice* createDevice (const String& outputDeviceName,
  868. const String& inputDeviceName)
  869. {
  870. jassert (hasScanned); // need to call scanForDevices() before doing this
  871. const bool useExclusiveMode = false;
  872. ScopedPointer<WASAPIAudioIODevice> device;
  873. const int outputIndex = outputDeviceNames.indexOf (outputDeviceName);
  874. const int inputIndex = inputDeviceNames.indexOf (inputDeviceName);
  875. if (outputIndex >= 0 || inputIndex >= 0)
  876. {
  877. device = new WASAPIAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
  878. : inputDeviceName,
  879. outputDeviceIds [outputIndex],
  880. inputDeviceIds [inputIndex],
  881. useExclusiveMode);
  882. if (! device->initialise())
  883. device = nullptr;
  884. }
  885. return device.release();
  886. }
  887. //==============================================================================
  888. StringArray outputDeviceNames, outputDeviceIds;
  889. StringArray inputDeviceNames, inputDeviceIds;
  890. private:
  891. bool hasScanned;
  892. //==============================================================================
  893. static String getDefaultEndpoint (IMMDeviceEnumerator* const enumerator, const bool forCapture)
  894. {
  895. String s;
  896. IMMDevice* dev = nullptr;
  897. if (check (enumerator->GetDefaultAudioEndpoint (forCapture ? eCapture : eRender,
  898. eMultimedia, &dev)))
  899. {
  900. WCHAR* deviceId = nullptr;
  901. if (check (dev->GetId (&deviceId)))
  902. {
  903. s = deviceId;
  904. CoTaskMemFree (deviceId);
  905. }
  906. dev->Release();
  907. }
  908. return s;
  909. }
  910. //==============================================================================
  911. void scan (StringArray& outputDeviceNames,
  912. StringArray& inputDeviceNames,
  913. StringArray& outputDeviceIds,
  914. StringArray& inputDeviceIds)
  915. {
  916. ComSmartPtr <IMMDeviceEnumerator> enumerator;
  917. if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator))))
  918. return;
  919. const String defaultRenderer (getDefaultEndpoint (enumerator, false));
  920. const String defaultCapture (getDefaultEndpoint (enumerator, true));
  921. ComSmartPtr <IMMDeviceCollection> deviceCollection;
  922. UINT32 numDevices = 0;
  923. if (! (check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress()))
  924. && check (deviceCollection->GetCount (&numDevices))))
  925. return;
  926. for (UINT32 i = 0; i < numDevices; ++i)
  927. {
  928. ComSmartPtr <IMMDevice> device;
  929. if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress())))
  930. continue;
  931. DWORD state = 0;
  932. if (! (check (device->GetState (&state)) && state == DEVICE_STATE_ACTIVE))
  933. continue;
  934. const String deviceId (getDeviceID (device));
  935. String name;
  936. {
  937. ComSmartPtr <IPropertyStore> properties;
  938. if (! check (device->OpenPropertyStore (STGM_READ, properties.resetAndGetPointerAddress())))
  939. continue;
  940. PROPVARIANT value;
  941. PropVariantInit (&value);
  942. if (check (properties->GetValue (PKEY_Device_FriendlyName, &value)))
  943. name = value.pwszVal;
  944. PropVariantClear (&value);
  945. }
  946. const EDataFlow flow = getDataFlow (device);
  947. if (flow == eRender)
  948. {
  949. const int index = (deviceId == defaultRenderer) ? 0 : -1;
  950. outputDeviceIds.insert (index, deviceId);
  951. outputDeviceNames.insert (index, name);
  952. }
  953. else if (flow == eCapture)
  954. {
  955. const int index = (deviceId == defaultCapture) ? 0 : -1;
  956. inputDeviceIds.insert (index, deviceId);
  957. inputDeviceNames.insert (index, name);
  958. }
  959. }
  960. inputDeviceNames.appendNumbersToDuplicates (false, false);
  961. outputDeviceNames.appendNumbersToDuplicates (false, false);
  962. }
  963. //==============================================================================
  964. void systemDeviceChanged()
  965. {
  966. StringArray newOutNames, newInNames, newOutIds, newInIds;
  967. scan (newOutNames, newInNames, newOutIds, newInIds);
  968. if (newOutNames != outputDeviceNames
  969. || newInNames != inputDeviceNames
  970. || newOutIds != outputDeviceIds
  971. || newInIds != inputDeviceIds)
  972. {
  973. hasScanned = true;
  974. outputDeviceNames = newOutNames;
  975. inputDeviceNames = newInNames;
  976. outputDeviceIds = newOutIds;
  977. inputDeviceIds = newInIds;
  978. callDeviceChangeListeners();
  979. }
  980. }
  981. //==============================================================================
  982. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODeviceType)
  983. };
  984. }
  985. //==============================================================================
  986. AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI()
  987. {
  988. if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
  989. return new WasapiClasses::WASAPIAudioIODeviceType();
  990. return nullptr;
  991. }