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