1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    The code included in this file is provided under the terms of the ISC license
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12    To use, copy, modify, and/or distribute this software for any purpose with or
13    without fee is hereby granted provided that the above copyright notice and
14    this permission notice appear in all copies.
15 
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18    DISCLAIMED.
19 
20   ==============================================================================
21 */
22 
23 extern "C"
24 {
25     // Declare just the minimum number of interfaces for the DSound objects that we need..
26     struct DSBUFFERDESC
27     {
28         DWORD dwSize;
29         DWORD dwFlags;
30         DWORD dwBufferBytes;
31         DWORD dwReserved;
32         LPWAVEFORMATEX lpwfxFormat;
33         GUID guid3DAlgorithm;
34     };
35 
36     struct IDirectSoundBuffer;
37 
38     #undef INTERFACE
39     #define INTERFACE IDirectSound
DECLARE_INTERFACE_(IDirectSound,IUnknown)40     DECLARE_INTERFACE_(IDirectSound, IUnknown)
41     {
42         STDMETHOD(QueryInterface)       (THIS_ REFIID, LPVOID*) PURE;
43         STDMETHOD_(ULONG,AddRef)        (THIS) PURE;
44         STDMETHOD_(ULONG,Release)       (THIS) PURE;
45         STDMETHOD(CreateSoundBuffer)    (THIS_ DSBUFFERDESC*, IDirectSoundBuffer**, LPUNKNOWN) PURE;
46         STDMETHOD(GetCaps)              (THIS_ void*) PURE;
47         STDMETHOD(DuplicateSoundBuffer) (THIS_ IDirectSoundBuffer*, IDirectSoundBuffer**) PURE;
48         STDMETHOD(SetCooperativeLevel)  (THIS_ HWND, DWORD) PURE;
49         STDMETHOD(Compact)              (THIS) PURE;
50         STDMETHOD(GetSpeakerConfig)     (THIS_ LPDWORD) PURE;
51         STDMETHOD(SetSpeakerConfig)     (THIS_ DWORD) PURE;
52         STDMETHOD(Initialize)           (THIS_ const GUID*) PURE;
53     };
54 
55     #undef INTERFACE
56     #define INTERFACE IDirectSoundBuffer
DECLARE_INTERFACE_(IDirectSoundBuffer,IUnknown)57     DECLARE_INTERFACE_(IDirectSoundBuffer, IUnknown)
58     {
59         STDMETHOD(QueryInterface)       (THIS_ REFIID, LPVOID*) PURE;
60         STDMETHOD_(ULONG,AddRef)        (THIS) PURE;
61         STDMETHOD_(ULONG,Release)       (THIS) PURE;
62         STDMETHOD(GetCaps)              (THIS_ void*) PURE;
63         STDMETHOD(GetCurrentPosition)   (THIS_ LPDWORD, LPDWORD) PURE;
64         STDMETHOD(GetFormat)            (THIS_ LPWAVEFORMATEX, DWORD, LPDWORD) PURE;
65         STDMETHOD(GetVolume)            (THIS_ LPLONG) PURE;
66         STDMETHOD(GetPan)               (THIS_ LPLONG) PURE;
67         STDMETHOD(GetFrequency)         (THIS_ LPDWORD) PURE;
68         STDMETHOD(GetStatus)            (THIS_ LPDWORD) PURE;
69         STDMETHOD(Initialize)           (THIS_ IDirectSound*, DSBUFFERDESC*) PURE;
70         STDMETHOD(Lock)                 (THIS_ DWORD, DWORD, LPVOID*, LPDWORD, LPVOID*, LPDWORD, DWORD) PURE;
71         STDMETHOD(Play)                 (THIS_ DWORD, DWORD, DWORD) PURE;
72         STDMETHOD(SetCurrentPosition)   (THIS_ DWORD) PURE;
73         STDMETHOD(SetFormat)            (THIS_ const WAVEFORMATEX*) PURE;
74         STDMETHOD(SetVolume)            (THIS_ LONG) PURE;
75         STDMETHOD(SetPan)               (THIS_ LONG) PURE;
76         STDMETHOD(SetFrequency)         (THIS_ DWORD) PURE;
77         STDMETHOD(Stop)                 (THIS) PURE;
78         STDMETHOD(Unlock)               (THIS_ LPVOID, DWORD, LPVOID, DWORD) PURE;
79         STDMETHOD(Restore)              (THIS) PURE;
80     };
81 
82     //==============================================================================
83     struct DSCBUFFERDESC
84     {
85         DWORD dwSize;
86         DWORD dwFlags;
87         DWORD dwBufferBytes;
88         DWORD dwReserved;
89         LPWAVEFORMATEX lpwfxFormat;
90     };
91 
92     struct IDirectSoundCaptureBuffer;
93 
94     #undef INTERFACE
95     #define INTERFACE IDirectSoundCapture
DECLARE_INTERFACE_(IDirectSoundCapture,IUnknown)96     DECLARE_INTERFACE_(IDirectSoundCapture, IUnknown)
97     {
98         STDMETHOD(QueryInterface)       (THIS_ REFIID, LPVOID*) PURE;
99         STDMETHOD_(ULONG,AddRef)        (THIS) PURE;
100         STDMETHOD_(ULONG,Release)       (THIS) PURE;
101         STDMETHOD(CreateCaptureBuffer)  (THIS_ DSCBUFFERDESC*, IDirectSoundCaptureBuffer**, LPUNKNOWN) PURE;
102         STDMETHOD(GetCaps)              (THIS_ void*) PURE;
103         STDMETHOD(Initialize)           (THIS_ const GUID*) PURE;
104     };
105 
106     #undef INTERFACE
107     #define INTERFACE IDirectSoundCaptureBuffer
DECLARE_INTERFACE_(IDirectSoundCaptureBuffer,IUnknown)108     DECLARE_INTERFACE_(IDirectSoundCaptureBuffer, IUnknown)
109     {
110         STDMETHOD(QueryInterface)       (THIS_ REFIID, LPVOID*) PURE;
111         STDMETHOD_(ULONG,AddRef)        (THIS) PURE;
112         STDMETHOD_(ULONG,Release)       (THIS) PURE;
113         STDMETHOD(GetCaps)              (THIS_ void*) PURE;
114         STDMETHOD(GetCurrentPosition)   (THIS_ LPDWORD, LPDWORD) PURE;
115         STDMETHOD(GetFormat)            (THIS_ LPWAVEFORMATEX, DWORD, LPDWORD) PURE;
116         STDMETHOD(GetStatus)            (THIS_ LPDWORD) PURE;
117         STDMETHOD(Initialize)           (THIS_ IDirectSoundCapture*, DSCBUFFERDESC*) PURE;
118         STDMETHOD(Lock)                 (THIS_ DWORD, DWORD, LPVOID*, LPDWORD, LPVOID*, LPDWORD, DWORD) PURE;
119         STDMETHOD(Start)                (THIS_ DWORD) PURE;
120         STDMETHOD(Stop)                 (THIS) PURE;
121         STDMETHOD(Unlock)               (THIS_ LPVOID, DWORD, LPVOID, DWORD) PURE;
122     };
123 
124     #undef INTERFACE
125 }
126 
127 namespace juce
128 {
129 
130 //==============================================================================
131 namespace DSoundLogging
132 {
getErrorMessage(HRESULT hr)133     String getErrorMessage (HRESULT hr)
134     {
135         const char* result = nullptr;
136 
137         switch (hr)
138         {
139             case MAKE_HRESULT(1, 0x878, 10):    result = "Device already allocated"; break;
140             case MAKE_HRESULT(1, 0x878, 30):    result = "Control unavailable"; break;
141             case E_INVALIDARG:                  result = "Invalid parameter"; break;
142             case MAKE_HRESULT(1, 0x878, 50):    result = "Invalid call"; break;
143             case E_FAIL:                        result = "Generic error"; break;
144             case MAKE_HRESULT(1, 0x878, 70):    result = "Priority level error"; break;
145             case E_OUTOFMEMORY:                 result = "Out of memory"; break;
146             case MAKE_HRESULT(1, 0x878, 100):   result = "Bad format"; break;
147             case E_NOTIMPL:                     result = "Unsupported function"; break;
148             case MAKE_HRESULT(1, 0x878, 120):   result = "No driver"; break;
149             case MAKE_HRESULT(1, 0x878, 130):   result = "Already initialised"; break;
150             case CLASS_E_NOAGGREGATION:         result = "No aggregation"; break;
151             case MAKE_HRESULT(1, 0x878, 150):   result = "Buffer lost"; break;
152             case MAKE_HRESULT(1, 0x878, 160):   result = "Another app has priority"; break;
153             case MAKE_HRESULT(1, 0x878, 170):   result = "Uninitialised"; break;
154             case E_NOINTERFACE:                 result = "No interface"; break;
155             case S_OK:                          result = "No error"; break;
156             default:                            return "Unknown error: " + String ((int) hr);
157         }
158 
159         return result;
160     }
161 
162     //==============================================================================
163    #if JUCE_DIRECTSOUND_LOGGING
logMessage(String message)164     static void logMessage (String message)
165     {
166         message = "DSOUND: " + message;
167         DBG (message);
168         Logger::writeToLog (message);
169     }
170 
logError(HRESULT hr,int lineNum)171     static void logError (HRESULT hr, int lineNum)
172     {
173         if (FAILED (hr))
174         {
175             String error ("Error at line ");
176             error << lineNum << ": " << getErrorMessage (hr);
177             logMessage (error);
178         }
179     }
180 
181     #define JUCE_DS_LOG(a)        DSoundLogging::logMessage(a);
182     #define JUCE_DS_LOG_ERROR(a)  DSoundLogging::logError(a, __LINE__);
183    #else
184     #define JUCE_DS_LOG(a)
185     #define JUCE_DS_LOG_ERROR(a)
186    #endif
187 }
188 
189 //==============================================================================
190 namespace
191 {
192     #define DSOUND_FUNCTION(functionName, params) \
193         typedef HRESULT (WINAPI *type##functionName) params; \
194         static type##functionName ds##functionName = nullptr;
195 
196     #define DSOUND_FUNCTION_LOAD(functionName) \
197         ds##functionName = (type##functionName) GetProcAddress (h, #functionName);  \
198         jassert (ds##functionName != nullptr);
199 
200     typedef BOOL (CALLBACK *LPDSENUMCALLBACKW) (LPGUID, LPCWSTR, LPCWSTR, LPVOID);
201     typedef BOOL (CALLBACK *LPDSENUMCALLBACKA) (LPGUID, LPCSTR, LPCSTR, LPVOID);
202 
203     DSOUND_FUNCTION (DirectSoundCreate, (const GUID*, IDirectSound**, LPUNKNOWN))
204     DSOUND_FUNCTION (DirectSoundCaptureCreate, (const GUID*, IDirectSoundCapture**, LPUNKNOWN))
205     DSOUND_FUNCTION (DirectSoundEnumerateW, (LPDSENUMCALLBACKW, LPVOID))
206     DSOUND_FUNCTION (DirectSoundCaptureEnumerateW, (LPDSENUMCALLBACKW, LPVOID))
207 
initialiseDSoundFunctions()208     void initialiseDSoundFunctions()
209     {
210         if (dsDirectSoundCreate == nullptr)
211         {
212             HMODULE h = LoadLibraryA ("dsound.dll");
213 
214             DSOUND_FUNCTION_LOAD (DirectSoundCreate)
215             DSOUND_FUNCTION_LOAD (DirectSoundCaptureCreate)
216             DSOUND_FUNCTION_LOAD (DirectSoundEnumerateW)
217             DSOUND_FUNCTION_LOAD (DirectSoundCaptureEnumerateW)
218         }
219     }
220 
221     // the overall size of buffer used is this value x the block size
222     enum { blocksPerOverallBuffer = 16 };
223 }
224 
225 //==============================================================================
226 class DSoundInternalOutChannel
227 {
228 public:
DSoundInternalOutChannel(const String & name_,const GUID & guid_,int rate,int bufferSize,float * left,float * right)229     DSoundInternalOutChannel (const String& name_, const GUID& guid_, int rate,
230                               int bufferSize, float* left, float* right)
231         : bitDepth (16), name (name_), guid (guid_), sampleRate (rate),
232           bufferSizeSamples (bufferSize), leftBuffer (left), rightBuffer (right),
233           pDirectSound (nullptr), pOutputBuffer (nullptr)
234     {
235     }
236 
~DSoundInternalOutChannel()237     ~DSoundInternalOutChannel()
238     {
239         close();
240     }
241 
close()242     void close()
243     {
244         if (pOutputBuffer != nullptr)
245         {
246             JUCE_DS_LOG ("closing output: " + name);
247             HRESULT hr = pOutputBuffer->Stop();
248             JUCE_DS_LOG_ERROR (hr); ignoreUnused (hr);
249 
250             pOutputBuffer->Release();
251             pOutputBuffer = nullptr;
252         }
253 
254         if (pDirectSound != nullptr)
255         {
256             pDirectSound->Release();
257             pDirectSound = nullptr;
258         }
259     }
260 
open()261     String open()
262     {
263         JUCE_DS_LOG ("opening output: " + name + "  rate=" + String (sampleRate)
264                        + " bits=" + String (bitDepth) + " buf=" + String (bufferSizeSamples));
265 
266         pDirectSound = nullptr;
267         pOutputBuffer = nullptr;
268         writeOffset = 0;
269         xruns = 0;
270 
271         firstPlayTime = true;
272         lastPlayTime = 0;
273 
274         String error;
275         HRESULT hr = E_NOINTERFACE;
276 
277         if (dsDirectSoundCreate != nullptr)
278             hr = dsDirectSoundCreate (&guid, &pDirectSound, nullptr);
279 
280         if (SUCCEEDED (hr))
281         {
282             bytesPerBuffer = (bufferSizeSamples * (bitDepth >> 2)) & ~15;
283             ticksPerBuffer = bytesPerBuffer * Time::getHighResolutionTicksPerSecond() / (sampleRate * (bitDepth >> 2));
284             totalBytesPerBuffer = (blocksPerOverallBuffer * bytesPerBuffer) & ~15;
285             const int numChannels = 2;
286 
287             hr = pDirectSound->SetCooperativeLevel (GetDesktopWindow(), 2 /* DSSCL_PRIORITY */);
288             JUCE_DS_LOG_ERROR (hr);
289 
290             if (SUCCEEDED (hr))
291             {
292                 IDirectSoundBuffer* pPrimaryBuffer;
293 
294                 DSBUFFERDESC primaryDesc = {};
295                 primaryDesc.dwSize = sizeof (DSBUFFERDESC);
296                 primaryDesc.dwFlags = 1 /* DSBCAPS_PRIMARYBUFFER */;
297                 primaryDesc.dwBufferBytes = 0;
298                 primaryDesc.lpwfxFormat = 0;
299 
300                 JUCE_DS_LOG ("co-op level set");
301                 hr = pDirectSound->CreateSoundBuffer (&primaryDesc, &pPrimaryBuffer, 0);
302                 JUCE_DS_LOG_ERROR (hr);
303 
304                 if (SUCCEEDED (hr))
305                 {
306                     WAVEFORMATEX wfFormat;
307                     wfFormat.wFormatTag       = WAVE_FORMAT_PCM;
308                     wfFormat.nChannels        = (unsigned short) numChannels;
309                     wfFormat.nSamplesPerSec   = (DWORD) sampleRate;
310                     wfFormat.wBitsPerSample   = (unsigned short) bitDepth;
311                     wfFormat.nBlockAlign      = (unsigned short) (wfFormat.nChannels * wfFormat.wBitsPerSample / 8);
312                     wfFormat.nAvgBytesPerSec  = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign;
313                     wfFormat.cbSize = 0;
314 
315                     hr = pPrimaryBuffer->SetFormat (&wfFormat);
316                     JUCE_DS_LOG_ERROR (hr);
317 
318                     if (SUCCEEDED (hr))
319                     {
320                         DSBUFFERDESC secondaryDesc = {};
321                         secondaryDesc.dwSize = sizeof (DSBUFFERDESC);
322                         secondaryDesc.dwFlags =  0x8000 /* DSBCAPS_GLOBALFOCUS */
323                                                   | 0x10000 /* DSBCAPS_GETCURRENTPOSITION2 */;
324                         secondaryDesc.dwBufferBytes = (DWORD) totalBytesPerBuffer;
325                         secondaryDesc.lpwfxFormat = &wfFormat;
326 
327                         hr = pDirectSound->CreateSoundBuffer (&secondaryDesc, &pOutputBuffer, 0);
328                         JUCE_DS_LOG_ERROR (hr);
329 
330                         if (SUCCEEDED (hr))
331                         {
332                             JUCE_DS_LOG ("buffer created");
333 
334                             DWORD dwDataLen;
335                             unsigned char* pDSBuffData;
336 
337                             hr = pOutputBuffer->Lock (0, (DWORD) totalBytesPerBuffer,
338                                                       (LPVOID*) &pDSBuffData, &dwDataLen, 0, 0, 0);
339                             JUCE_DS_LOG_ERROR (hr);
340 
341                             if (SUCCEEDED (hr))
342                             {
343                                 zeromem (pDSBuffData, dwDataLen);
344 
345                                 hr = pOutputBuffer->Unlock (pDSBuffData, dwDataLen, 0, 0);
346 
347                                 if (SUCCEEDED (hr))
348                                 {
349                                     hr = pOutputBuffer->SetCurrentPosition (0);
350 
351                                     if (SUCCEEDED (hr))
352                                     {
353                                         hr = pOutputBuffer->Play (0, 0, 1 /* DSBPLAY_LOOPING */);
354 
355                                         if (SUCCEEDED (hr))
356                                             return {};
357                                     }
358                                 }
359                             }
360                         }
361                     }
362                 }
363             }
364         }
365 
366         error = DSoundLogging::getErrorMessage (hr);
367         close();
368         return error;
369     }
370 
synchronisePosition()371     void synchronisePosition()
372     {
373         if (pOutputBuffer != nullptr)
374         {
375             DWORD playCursor;
376             pOutputBuffer->GetCurrentPosition (&playCursor, &writeOffset);
377         }
378     }
379 
service()380     bool service()
381     {
382         if (pOutputBuffer == 0)
383             return true;
384 
385         DWORD playCursor, writeCursor;
386 
387         for (;;)
388         {
389             HRESULT hr = pOutputBuffer->GetCurrentPosition (&playCursor, &writeCursor);
390 
391             if (hr == MAKE_HRESULT (1, 0x878, 150)) // DSERR_BUFFERLOST
392             {
393                 pOutputBuffer->Restore();
394                 continue;
395             }
396 
397             if (SUCCEEDED (hr))
398                 break;
399 
400             JUCE_DS_LOG_ERROR (hr);
401             jassertfalse;
402             return true;
403         }
404 
405         auto currentPlayTime = Time::getHighResolutionTicks();
406         if (! firstPlayTime)
407         {
408             auto expectedBuffers = (currentPlayTime - lastPlayTime) / ticksPerBuffer;
409 
410             playCursor += static_cast<DWORD> (expectedBuffers * bytesPerBuffer);
411         }
412         else
413             firstPlayTime = false;
414 
415         lastPlayTime = currentPlayTime;
416 
417         int playWriteGap = (int) (writeCursor - playCursor);
418         if (playWriteGap < 0)
419             playWriteGap += totalBytesPerBuffer;
420 
421         int bytesEmpty = (int) (playCursor - writeOffset);
422         if (bytesEmpty < 0)
423             bytesEmpty += totalBytesPerBuffer;
424 
425         if (bytesEmpty > (totalBytesPerBuffer - playWriteGap))
426         {
427             writeOffset = writeCursor;
428             bytesEmpty = totalBytesPerBuffer - playWriteGap;
429 
430             // buffer underflow
431             xruns++;
432         }
433 
434         if (bytesEmpty >= bytesPerBuffer)
435         {
436             int* buf1 = nullptr;
437             int* buf2 = nullptr;
438             DWORD dwSize1 = 0;
439             DWORD dwSize2 = 0;
440 
441             HRESULT hr = pOutputBuffer->Lock (writeOffset, (DWORD) bytesPerBuffer,
442                                               (void**) &buf1, &dwSize1,
443                                               (void**) &buf2, &dwSize2, 0);
444 
445             if (hr == MAKE_HRESULT (1, 0x878, 150)) // DSERR_BUFFERLOST
446             {
447                 pOutputBuffer->Restore();
448 
449                 hr = pOutputBuffer->Lock (writeOffset, (DWORD) bytesPerBuffer,
450                                           (void**) &buf1, &dwSize1,
451                                           (void**) &buf2, &dwSize2, 0);
452             }
453 
454             if (SUCCEEDED (hr))
455             {
456                 if (bitDepth == 16)
457                 {
458                     const float* left = leftBuffer;
459                     const float* right = rightBuffer;
460                     int samples1 = (int) (dwSize1 >> 2);
461                     int samples2 = (int) (dwSize2 >> 2);
462 
463                     if (left == nullptr)
464                     {
465                         for (int* dest = buf1; --samples1 >= 0;)  *dest++ = convertInputValues (0, *right++);
466                         for (int* dest = buf2; --samples2 >= 0;)  *dest++ = convertInputValues (0, *right++);
467                     }
468                     else if (right == nullptr)
469                     {
470                         for (int* dest = buf1; --samples1 >= 0;)  *dest++ = convertInputValues (*left++, 0);
471                         for (int* dest = buf2; --samples2 >= 0;)  *dest++ = convertInputValues (*left++, 0);
472                     }
473                     else
474                     {
475                         for (int* dest = buf1; --samples1 >= 0;)  *dest++ = convertInputValues (*left++, *right++);
476                         for (int* dest = buf2; --samples2 >= 0;)  *dest++ = convertInputValues (*left++, *right++);
477                     }
478                 }
479                 else
480                 {
481                     jassertfalse;
482                 }
483 
484                 writeOffset = (writeOffset + dwSize1 + dwSize2) % totalBytesPerBuffer;
485 
486                 pOutputBuffer->Unlock (buf1, dwSize1, buf2, dwSize2);
487             }
488             else
489             {
490                 jassertfalse;
491                 JUCE_DS_LOG_ERROR (hr);
492             }
493 
494             bytesEmpty -= bytesPerBuffer;
495             return true;
496         }
497         else
498         {
499             return false;
500         }
501     }
502 
503     int bitDepth, xruns;
504     bool doneFlag;
505 
506 private:
507     String name;
508     GUID guid;
509     int sampleRate, bufferSizeSamples;
510     float* leftBuffer;
511     float* rightBuffer;
512 
513     IDirectSound* pDirectSound;
514     IDirectSoundBuffer* pOutputBuffer;
515     DWORD writeOffset;
516     int totalBytesPerBuffer, bytesPerBuffer;
517 
518     bool firstPlayTime;
519     int64 lastPlayTime, ticksPerBuffer;
520 
convertInputValues(const float l,const float r)521     static int convertInputValues (const float l, const float r) noexcept
522     {
523         return jlimit (-32768, 32767, roundToInt (32767.0f * r)) << 16
524                 | (0xffff & jlimit (-32768, 32767, roundToInt (32767.0f * l)));
525     }
526 
527     JUCE_DECLARE_NON_COPYABLE (DSoundInternalOutChannel)
528 };
529 
530 //==============================================================================
531 struct DSoundInternalInChannel
532 {
533 public:
DSoundInternalInChanneljuce::DSoundInternalInChannel534     DSoundInternalInChannel (const String& name_, const GUID& guid_, int rate,
535                              int bufferSize, float* left, float* right)
536         : name (name_), guid (guid_), sampleRate (rate),
537           bufferSizeSamples (bufferSize), leftBuffer (left), rightBuffer (right)
538     {
539     }
540 
~DSoundInternalInChanneljuce::DSoundInternalInChannel541     ~DSoundInternalInChannel()
542     {
543         close();
544     }
545 
closejuce::DSoundInternalInChannel546     void close()
547     {
548         if (pInputBuffer != nullptr)
549         {
550             JUCE_DS_LOG ("closing input: " + name);
551             HRESULT hr = pInputBuffer->Stop();
552             JUCE_DS_LOG_ERROR (hr); ignoreUnused (hr);
553 
554             pInputBuffer->Release();
555             pInputBuffer = nullptr;
556         }
557 
558         if (pDirectSoundCapture != nullptr)
559         {
560             pDirectSoundCapture->Release();
561             pDirectSoundCapture = nullptr;
562         }
563 
564         if (pDirectSound != nullptr)
565         {
566             pDirectSound->Release();
567             pDirectSound = nullptr;
568         }
569     }
570 
openjuce::DSoundInternalInChannel571     String open()
572     {
573         JUCE_DS_LOG ("opening input: " + name
574                        + "  rate=" + String (sampleRate) + " bits=" + String (bitDepth) + " buf=" + String (bufferSizeSamples));
575 
576         pDirectSound = nullptr;
577         pDirectSoundCapture = nullptr;
578         pInputBuffer = nullptr;
579         readOffset = 0;
580         totalBytesPerBuffer = 0;
581 
582         HRESULT hr = dsDirectSoundCaptureCreate != nullptr
583                         ? dsDirectSoundCaptureCreate (&guid, &pDirectSoundCapture, nullptr)
584                         : E_NOINTERFACE;
585 
586         if (SUCCEEDED (hr))
587         {
588             const int numChannels = 2;
589             bytesPerBuffer = (bufferSizeSamples * (bitDepth >> 2)) & ~15;
590             totalBytesPerBuffer = (blocksPerOverallBuffer * bytesPerBuffer) & ~15;
591 
592             WAVEFORMATEX wfFormat;
593             wfFormat.wFormatTag       = WAVE_FORMAT_PCM;
594             wfFormat.nChannels        = (unsigned short)numChannels;
595             wfFormat.nSamplesPerSec   = (DWORD) sampleRate;
596             wfFormat.wBitsPerSample   = (unsigned short) bitDepth;
597             wfFormat.nBlockAlign      = (unsigned short) (wfFormat.nChannels * (wfFormat.wBitsPerSample / 8));
598             wfFormat.nAvgBytesPerSec  = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign;
599             wfFormat.cbSize = 0;
600 
601             DSCBUFFERDESC captureDesc = {};
602             captureDesc.dwSize = sizeof (DSCBUFFERDESC);
603             captureDesc.dwFlags = 0;
604             captureDesc.dwBufferBytes = (DWORD) totalBytesPerBuffer;
605             captureDesc.lpwfxFormat = &wfFormat;
606 
607             JUCE_DS_LOG ("object created");
608             hr = pDirectSoundCapture->CreateCaptureBuffer (&captureDesc, &pInputBuffer, 0);
609 
610             if (SUCCEEDED (hr))
611             {
612                 hr = pInputBuffer->Start (1 /* DSCBSTART_LOOPING */);
613 
614                 if (SUCCEEDED (hr))
615                     return {};
616             }
617         }
618 
619         JUCE_DS_LOG_ERROR (hr);
620         const String error (DSoundLogging::getErrorMessage (hr));
621         close();
622 
623         return error;
624     }
625 
synchronisePositionjuce::DSoundInternalInChannel626     void synchronisePosition()
627     {
628         if (pInputBuffer != nullptr)
629         {
630             DWORD capturePos;
631             pInputBuffer->GetCurrentPosition (&capturePos, (DWORD*) &readOffset);
632         }
633     }
634 
servicejuce::DSoundInternalInChannel635     bool service()
636     {
637         if (pInputBuffer == 0)
638             return true;
639 
640         DWORD capturePos, readPos;
641         HRESULT hr = pInputBuffer->GetCurrentPosition (&capturePos, &readPos);
642         JUCE_DS_LOG_ERROR (hr);
643 
644         if (FAILED (hr))
645             return true;
646 
647         int bytesFilled = (int) (readPos - readOffset);
648 
649         if (bytesFilled < 0)
650             bytesFilled += totalBytesPerBuffer;
651 
652         if (bytesFilled >= bytesPerBuffer)
653         {
654             short* buf1 = nullptr;
655             short* buf2 = nullptr;
656             DWORD dwsize1 = 0;
657             DWORD dwsize2 = 0;
658 
659             hr = pInputBuffer->Lock ((DWORD) readOffset, (DWORD) bytesPerBuffer,
660                                              (void**) &buf1, &dwsize1,
661                                              (void**) &buf2, &dwsize2, 0);
662 
663             if (SUCCEEDED (hr))
664             {
665                 if (bitDepth == 16)
666                 {
667                     const float g = 1.0f / 32768.0f;
668 
669                     float* destL = leftBuffer;
670                     float* destR = rightBuffer;
671                     int samples1 = (int) (dwsize1 >> 2);
672                     int samples2 = (int) (dwsize2 >> 2);
673 
674                     if (destL == nullptr)
675                     {
676                         for (const short* src = buf1; --samples1 >= 0;) { ++src; *destR++ = *src++ * g; }
677                         for (const short* src = buf2; --samples2 >= 0;) { ++src; *destR++ = *src++ * g; }
678                     }
679                     else if (destR == nullptr)
680                     {
681                         for (const short* src = buf1; --samples1 >= 0;) { *destL++ = *src++ * g; ++src; }
682                         for (const short* src = buf2; --samples2 >= 0;) { *destL++ = *src++ * g; ++src; }
683                     }
684                     else
685                     {
686                         for (const short* src = buf1; --samples1 >= 0;) { *destL++ = *src++ * g; *destR++ = *src++ * g; }
687                         for (const short* src = buf2; --samples2 >= 0;) { *destL++ = *src++ * g; *destR++ = *src++ * g; }
688                     }
689                 }
690                 else
691                 {
692                     jassertfalse;
693                 }
694 
695                 readOffset = (readOffset + dwsize1 + dwsize2) % totalBytesPerBuffer;
696 
697                 pInputBuffer->Unlock (buf1, dwsize1, buf2, dwsize2);
698             }
699             else
700             {
701                 JUCE_DS_LOG_ERROR (hr);
702                 jassertfalse;
703             }
704 
705             bytesFilled -= bytesPerBuffer;
706 
707             return true;
708         }
709         else
710         {
711             return false;
712         }
713     }
714 
715     unsigned int readOffset;
716     int bytesPerBuffer, totalBytesPerBuffer;
717     int bitDepth = 16;
718     bool doneFlag;
719 
720 private:
721     String name;
722     GUID guid;
723     int sampleRate, bufferSizeSamples;
724     float* leftBuffer;
725     float* rightBuffer;
726 
727     IDirectSound* pDirectSound = nullptr;
728     IDirectSoundCapture* pDirectSoundCapture = nullptr;
729     IDirectSoundCaptureBuffer* pInputBuffer = nullptr;
730 
731     JUCE_DECLARE_NON_COPYABLE (DSoundInternalInChannel)
732 };
733 
734 //==============================================================================
735 class DSoundAudioIODevice  : public AudioIODevice,
736                              public Thread
737 {
738 public:
DSoundAudioIODevice(const String & deviceName,const int outputDeviceIndex_,const int inputDeviceIndex_)739     DSoundAudioIODevice (const String& deviceName,
740                          const int outputDeviceIndex_,
741                          const int inputDeviceIndex_)
742         : AudioIODevice (deviceName, "DirectSound"),
743           Thread ("JUCE DSound"),
744           outputDeviceIndex (outputDeviceIndex_),
745           inputDeviceIndex (inputDeviceIndex_)
746     {
747         if (outputDeviceIndex_ >= 0)
748         {
749             outChannels.add (TRANS("Left"));
750             outChannels.add (TRANS("Right"));
751         }
752 
753         if (inputDeviceIndex_ >= 0)
754         {
755             inChannels.add (TRANS("Left"));
756             inChannels.add (TRANS("Right"));
757         }
758     }
759 
~DSoundAudioIODevice()760     ~DSoundAudioIODevice()
761     {
762         close();
763     }
764 
open(const BigInteger & inputChannels,const BigInteger & outputChannels,double newSampleRate,int newBufferSize)765     String open (const BigInteger& inputChannels,
766                  const BigInteger& outputChannels,
767                  double newSampleRate, int newBufferSize) override
768     {
769         lastError = openDevice (inputChannels, outputChannels, newSampleRate, newBufferSize);
770         isOpen_ = lastError.isEmpty();
771 
772         return lastError;
773     }
774 
close()775     void close() override
776     {
777         stop();
778 
779         if (isOpen_)
780         {
781             closeDevice();
782             isOpen_ = false;
783         }
784     }
785 
isOpen()786     bool isOpen() override                              { return isOpen_ && isThreadRunning(); }
getCurrentBufferSizeSamples()787     int getCurrentBufferSizeSamples() override          { return bufferSizeSamples; }
getCurrentSampleRate()788     double getCurrentSampleRate() override              { return sampleRate; }
getActiveOutputChannels() const789     BigInteger getActiveOutputChannels() const override { return enabledOutputs; }
getActiveInputChannels() const790     BigInteger getActiveInputChannels() const override  { return enabledInputs; }
getOutputLatencyInSamples()791     int getOutputLatencyInSamples() override            { return (int) (getCurrentBufferSizeSamples() * 1.5); }
getInputLatencyInSamples()792     int getInputLatencyInSamples() override             { return getOutputLatencyInSamples(); }
getOutputChannelNames()793     StringArray getOutputChannelNames() override        { return outChannels; }
getInputChannelNames()794     StringArray getInputChannelNames() override         { return inChannels; }
795 
getAvailableSampleRates()796     Array<double> getAvailableSampleRates() override
797     {
798         return { 44100.0, 48000.0, 88200.0, 96000.0 };
799     }
800 
getAvailableBufferSizes()801     Array<int> getAvailableBufferSizes() override
802     {
803         Array<int> r;
804         int n = 64;
805 
806         for (int i = 0; i < 50; ++i)
807         {
808             r.add (n);
809             n += (n < 512) ? 32
810                            : ((n < 1024) ? 64
811                                          : ((n < 2048) ? 128 : 256));
812         }
813 
814         return r;
815     }
816 
getDefaultBufferSize()817     int getDefaultBufferSize() override                 { return 2560; }
818 
getCurrentBitDepth()819     int getCurrentBitDepth() override
820     {
821         int bits = 256;
822 
823         for (int i = inChans.size(); --i >= 0;)
824             bits = jmin (bits, inChans[i]->bitDepth);
825 
826         for (int i = outChans.size(); --i >= 0;)
827             bits = jmin (bits, outChans[i]->bitDepth);
828 
829         if (bits > 32)
830             bits = 16;
831 
832         return bits;
833     }
834 
start(AudioIODeviceCallback * call)835     void start (AudioIODeviceCallback* call) override
836     {
837         if (isOpen_ && call != nullptr && ! isStarted)
838         {
839             if (! isThreadRunning())
840             {
841                 // something gone wrong and the thread's stopped..
842                 isOpen_ = false;
843                 return;
844             }
845 
846             call->audioDeviceAboutToStart (this);
847 
848             const ScopedLock sl (startStopLock);
849             callback = call;
850             isStarted = true;
851         }
852     }
853 
stop()854     void stop() override
855     {
856         if (isStarted)
857         {
858             auto* callbackLocal = callback;
859 
860             {
861                 const ScopedLock sl (startStopLock);
862                 isStarted = false;
863             }
864 
865             if (callbackLocal != nullptr)
866                 callbackLocal->audioDeviceStopped();
867         }
868     }
869 
isPlaying()870     bool isPlaying() override            { return isStarted && isOpen_ && isThreadRunning(); }
getLastError()871     String getLastError() override       { return lastError; }
872 
getXRunCount() const873     int getXRunCount() const noexcept override
874     {
875         return outChans[0] != nullptr ? outChans[0]->xruns : -1;
876     }
877 
878     //==============================================================================
879     StringArray inChannels, outChannels;
880     int outputDeviceIndex, inputDeviceIndex;
881 
882 private:
883     bool isOpen_ = false;
884     bool isStarted = false;
885     String lastError;
886 
887     OwnedArray<DSoundInternalInChannel> inChans;
888     OwnedArray<DSoundInternalOutChannel> outChans;
889     WaitableEvent startEvent;
890 
891     int bufferSizeSamples = 0;
892     double sampleRate = 0;
893     BigInteger enabledInputs, enabledOutputs;
894     AudioBuffer<float> inputBuffers, outputBuffers;
895 
896     AudioIODeviceCallback* callback = nullptr;
897     CriticalSection startStopLock;
898 
899     String openDevice (const BigInteger& inputChannels,
900                        const BigInteger& outputChannels,
901                        double sampleRate_, int bufferSizeSamples_);
902 
closeDevice()903     void closeDevice()
904     {
905         isStarted = false;
906         stopThread (5000);
907 
908         inChans.clear();
909         outChans.clear();
910     }
911 
resync()912     void resync()
913     {
914         if (! threadShouldExit())
915         {
916             sleep (5);
917 
918             for (int i = 0; i < outChans.size(); ++i)
919                 outChans.getUnchecked(i)->synchronisePosition();
920 
921             for (int i = 0; i < inChans.size(); ++i)
922                 inChans.getUnchecked(i)->synchronisePosition();
923         }
924     }
925 
926 public:
run()927     void run() override
928     {
929         while (! threadShouldExit())
930         {
931             if (wait (100))
932                 break;
933         }
934 
935         const int latencyMs = (int) (bufferSizeSamples * 1000.0 / sampleRate);
936         const int maxTimeMS = jmax (5, 3 * latencyMs);
937 
938         while (! threadShouldExit())
939         {
940             int numToDo = 0;
941             uint32 startTime = Time::getMillisecondCounter();
942 
943             for (int i = inChans.size(); --i >= 0;)
944             {
945                 inChans.getUnchecked(i)->doneFlag = false;
946                 ++numToDo;
947             }
948 
949             for (int i = outChans.size(); --i >= 0;)
950             {
951                 outChans.getUnchecked(i)->doneFlag = false;
952                 ++numToDo;
953             }
954 
955             if (numToDo > 0)
956             {
957                 const int maxCount = 3;
958                 int count = maxCount;
959 
960                 for (;;)
961                 {
962                     for (int i = inChans.size(); --i >= 0;)
963                     {
964                         DSoundInternalInChannel* const in = inChans.getUnchecked(i);
965 
966                         if ((! in->doneFlag) && in->service())
967                         {
968                             in->doneFlag = true;
969                             --numToDo;
970                         }
971                     }
972 
973                     for (int i = outChans.size(); --i >= 0;)
974                     {
975                         DSoundInternalOutChannel* const out = outChans.getUnchecked(i);
976 
977                         if ((! out->doneFlag) && out->service())
978                         {
979                             out->doneFlag = true;
980                             --numToDo;
981                         }
982                     }
983 
984                     if (numToDo <= 0)
985                         break;
986 
987                     if (Time::getMillisecondCounter() > startTime + maxTimeMS)
988                     {
989                         resync();
990                         break;
991                     }
992 
993                     if (--count <= 0)
994                     {
995                         Sleep (1);
996                         count = maxCount;
997                     }
998 
999                     if (threadShouldExit())
1000                         return;
1001                 }
1002             }
1003             else
1004             {
1005                 sleep (1);
1006             }
1007 
1008             const ScopedLock sl (startStopLock);
1009 
1010             if (isStarted)
1011             {
1012                 callback->audioDeviceIOCallback (inputBuffers.getArrayOfReadPointers(), inputBuffers.getNumChannels(),
1013                                                  outputBuffers.getArrayOfWritePointers(), outputBuffers.getNumChannels(),
1014                                                  bufferSizeSamples);
1015             }
1016             else
1017             {
1018                 outputBuffers.clear();
1019                 sleep (1);
1020             }
1021         }
1022     }
1023 
1024     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODevice)
1025 };
1026 
1027 //==============================================================================
1028 struct DSoundDeviceList
1029 {
1030     StringArray outputDeviceNames, inputDeviceNames;
1031     Array<GUID> outputGuids, inputGuids;
1032 
scanjuce::DSoundDeviceList1033     void scan()
1034     {
1035         outputDeviceNames.clear();
1036         inputDeviceNames.clear();
1037         outputGuids.clear();
1038         inputGuids.clear();
1039 
1040         if (dsDirectSoundEnumerateW != 0)
1041         {
1042             dsDirectSoundEnumerateW (outputEnumProcW, this);
1043             dsDirectSoundCaptureEnumerateW (inputEnumProcW, this);
1044         }
1045     }
1046 
operator !=juce::DSoundDeviceList1047     bool operator!= (const DSoundDeviceList& other) const noexcept
1048     {
1049         return outputDeviceNames != other.outputDeviceNames
1050             || inputDeviceNames != other.inputDeviceNames
1051             || outputGuids != other.outputGuids
1052             || inputGuids != other.inputGuids;
1053     }
1054 
1055 private:
enumProcjuce::DSoundDeviceList1056     static BOOL enumProc (LPGUID lpGUID, String desc, StringArray& names, Array<GUID>& guids)
1057     {
1058         desc = desc.trim();
1059 
1060         if (desc.isNotEmpty())
1061         {
1062             const String origDesc (desc);
1063 
1064             int n = 2;
1065             while (names.contains (desc))
1066                 desc = origDesc + " (" + String (n++) + ")";
1067 
1068             names.add (desc);
1069             guids.add (lpGUID != nullptr ? *lpGUID : GUID());
1070         }
1071 
1072         return TRUE;
1073     }
1074 
outputEnumProcjuce::DSoundDeviceList1075     BOOL outputEnumProc (LPGUID guid, LPCWSTR desc)  { return enumProc (guid, desc, outputDeviceNames, outputGuids); }
inputEnumProcjuce::DSoundDeviceList1076     BOOL inputEnumProc  (LPGUID guid, LPCWSTR desc)  { return enumProc (guid, desc, inputDeviceNames,  inputGuids); }
1077 
outputEnumProcWjuce::DSoundDeviceList1078     static BOOL CALLBACK outputEnumProcW (LPGUID lpGUID, LPCWSTR description, LPCWSTR, LPVOID object)
1079     {
1080         return static_cast<DSoundDeviceList*> (object)->outputEnumProc (lpGUID, description);
1081     }
1082 
inputEnumProcWjuce::DSoundDeviceList1083     static BOOL CALLBACK inputEnumProcW (LPGUID lpGUID, LPCWSTR description, LPCWSTR, LPVOID object)
1084     {
1085         return static_cast<DSoundDeviceList*> (object)->inputEnumProc (lpGUID, description);
1086     }
1087 };
1088 
1089 //==============================================================================
openDevice(const BigInteger & inputChannels,const BigInteger & outputChannels,double sampleRate_,int bufferSizeSamples_)1090 String DSoundAudioIODevice::openDevice (const BigInteger& inputChannels,
1091                                         const BigInteger& outputChannels,
1092                                         double sampleRate_, int bufferSizeSamples_)
1093 {
1094     closeDevice();
1095 
1096     sampleRate = sampleRate_ > 0.0 ? sampleRate_ : 44100.0;
1097 
1098     if (bufferSizeSamples_ <= 0)
1099         bufferSizeSamples_ = 960; // use as a default size if none is set.
1100 
1101     bufferSizeSamples = bufferSizeSamples_ & ~7;
1102 
1103     DSoundDeviceList dlh;
1104     dlh.scan();
1105 
1106     enabledInputs = inputChannels;
1107     enabledInputs.setRange (inChannels.size(),
1108                             enabledInputs.getHighestBit() + 1 - inChannels.size(),
1109                             false);
1110 
1111     inputBuffers.setSize (enabledInputs.countNumberOfSetBits(), bufferSizeSamples);
1112     inputBuffers.clear();
1113     int numIns = 0;
1114 
1115     for (int i = 0; i <= enabledInputs.getHighestBit(); i += 2)
1116     {
1117         float* left  = enabledInputs[i]     ? inputBuffers.getWritePointer (numIns++) : nullptr;
1118         float* right = enabledInputs[i + 1] ? inputBuffers.getWritePointer (numIns++) : nullptr;
1119 
1120         if (left != nullptr || right != nullptr)
1121             inChans.add (new DSoundInternalInChannel (dlh.inputDeviceNames [inputDeviceIndex],
1122                                                       dlh.inputGuids [inputDeviceIndex],
1123                                                       (int) sampleRate, bufferSizeSamples,
1124                                                       left, right));
1125     }
1126 
1127     enabledOutputs = outputChannels;
1128     enabledOutputs.setRange (outChannels.size(),
1129                              enabledOutputs.getHighestBit() + 1 - outChannels.size(),
1130                              false);
1131 
1132     outputBuffers.setSize (enabledOutputs.countNumberOfSetBits(), bufferSizeSamples);
1133     outputBuffers.clear();
1134     int numOuts = 0;
1135 
1136     for (int i = 0; i <= enabledOutputs.getHighestBit(); i += 2)
1137     {
1138         float* left  = enabledOutputs[i]     ? outputBuffers.getWritePointer (numOuts++) : nullptr;
1139         float* right = enabledOutputs[i + 1] ? outputBuffers.getWritePointer (numOuts++) : nullptr;
1140 
1141         if (left != nullptr || right != nullptr)
1142             outChans.add (new DSoundInternalOutChannel (dlh.outputDeviceNames[outputDeviceIndex],
1143                                                         dlh.outputGuids [outputDeviceIndex],
1144                                                         (int) sampleRate, bufferSizeSamples,
1145                                                         left, right));
1146     }
1147 
1148     String error;
1149 
1150     // boost our priority while opening the devices to try to get better sync between them
1151     const int oldThreadPri = GetThreadPriority (GetCurrentThread());
1152     const DWORD oldProcPri = GetPriorityClass (GetCurrentProcess());
1153     SetThreadPriority (GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
1154     SetPriorityClass (GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
1155 
1156     for (int i = 0; i < outChans.size(); ++i)
1157     {
1158         error = outChans[i]->open();
1159 
1160         if (error.isNotEmpty())
1161         {
1162             error = "Error opening " + dlh.outputDeviceNames[i] + ": \"" + error + "\"";
1163             break;
1164         }
1165     }
1166 
1167     if (error.isEmpty())
1168     {
1169         for (int i = 0; i < inChans.size(); ++i)
1170         {
1171             error = inChans[i]->open();
1172 
1173             if (error.isNotEmpty())
1174             {
1175                 error = "Error opening " + dlh.inputDeviceNames[i] + ": \"" + error + "\"";
1176                 break;
1177             }
1178         }
1179     }
1180 
1181     if (error.isEmpty())
1182     {
1183         for (int i = 0; i < outChans.size(); ++i)
1184             outChans.getUnchecked(i)->synchronisePosition();
1185 
1186         for (int i = 0; i < inChans.size(); ++i)
1187             inChans.getUnchecked(i)->synchronisePosition();
1188 
1189         startThread (9);
1190         sleep (10);
1191 
1192         notify();
1193     }
1194     else
1195     {
1196         JUCE_DS_LOG ("Opening failed: " + error);
1197     }
1198 
1199     SetThreadPriority (GetCurrentThread(), oldThreadPri);
1200     SetPriorityClass (GetCurrentProcess(), oldProcPri);
1201 
1202     return error;
1203 }
1204 
1205 //==============================================================================
1206 class DSoundAudioIODeviceType  : public AudioIODeviceType,
1207                                  private DeviceChangeDetector
1208 {
1209 public:
DSoundAudioIODeviceType()1210     DSoundAudioIODeviceType()
1211         : AudioIODeviceType ("DirectSound"),
1212           DeviceChangeDetector (L"DirectSound")
1213     {
1214         initialiseDSoundFunctions();
1215     }
1216 
scanForDevices()1217     void scanForDevices() override
1218     {
1219         hasScanned = true;
1220         deviceList.scan();
1221     }
1222 
getDeviceNames(bool wantInputNames) const1223     StringArray getDeviceNames (bool wantInputNames) const override
1224     {
1225         jassert (hasScanned); // need to call scanForDevices() before doing this
1226 
1227         return wantInputNames ? deviceList.inputDeviceNames
1228                               : deviceList.outputDeviceNames;
1229     }
1230 
getDefaultDeviceIndex(bool) const1231     int getDefaultDeviceIndex (bool /*forInput*/) const override
1232     {
1233         jassert (hasScanned); // need to call scanForDevices() before doing this
1234         return 0;
1235     }
1236 
getIndexOfDevice(AudioIODevice * device,bool asInput) const1237     int getIndexOfDevice (AudioIODevice* device, bool asInput) const override
1238     {
1239         jassert (hasScanned); // need to call scanForDevices() before doing this
1240 
1241         if (DSoundAudioIODevice* const d = dynamic_cast<DSoundAudioIODevice*> (device))
1242             return asInput ? d->inputDeviceIndex
1243                            : d->outputDeviceIndex;
1244 
1245         return -1;
1246     }
1247 
hasSeparateInputsAndOutputs() const1248     bool hasSeparateInputsAndOutputs() const override   { return true; }
1249 
createDevice(const String & outputDeviceName,const String & inputDeviceName)1250     AudioIODevice* createDevice (const String& outputDeviceName,
1251                                  const String& inputDeviceName) override
1252     {
1253         jassert (hasScanned); // need to call scanForDevices() before doing this
1254 
1255         const int outputIndex = deviceList.outputDeviceNames.indexOf (outputDeviceName);
1256         const int inputIndex = deviceList.inputDeviceNames.indexOf (inputDeviceName);
1257 
1258         if (outputIndex >= 0 || inputIndex >= 0)
1259             return new DSoundAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
1260                                                                           : inputDeviceName,
1261                                             outputIndex, inputIndex);
1262 
1263         return nullptr;
1264     }
1265 
1266 private:
1267     DSoundDeviceList deviceList;
1268     bool hasScanned = false;
1269 
systemDeviceChanged()1270     void systemDeviceChanged() override
1271     {
1272         DSoundDeviceList newList;
1273         newList.scan();
1274 
1275         if (newList != deviceList)
1276         {
1277             deviceList = newList;
1278             callDeviceChangeListeners();
1279         }
1280     }
1281 
1282     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODeviceType)
1283 };
1284 
1285 } // namespace juce
1286