1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22 
23 #if SDL_AUDIO_DRIVER_DSOUND
24 
25 /* Allow access to a raw mixing buffer */
26 
27 #include "SDL_assert.h"
28 #include "SDL_timer.h"
29 #include "SDL_loadso.h"
30 #include "SDL_audio.h"
31 #include "../SDL_audio_c.h"
32 #include "SDL_directsound.h"
33 
34 #ifndef WAVE_FORMAT_IEEE_FLOAT
35 #define WAVE_FORMAT_IEEE_FLOAT 0x0003
36 #endif
37 
38 /* DirectX function pointers for audio */
39 static void* DSoundDLL = NULL;
40 typedef HRESULT (WINAPI *fnDirectSoundCreate8)(LPGUID,LPDIRECTSOUND*,LPUNKNOWN);
41 typedef HRESULT (WINAPI *fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
42 typedef HRESULT (WINAPI *fnDirectSoundCaptureCreate8)(LPCGUID,LPDIRECTSOUNDCAPTURE8 *,LPUNKNOWN);
43 typedef HRESULT (WINAPI *fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW,LPVOID);
44 static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL;
45 static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL;
46 static fnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL;
47 static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL;
48 
49 static void
DSOUND_Unload(void)50 DSOUND_Unload(void)
51 {
52     pDirectSoundCreate8 = NULL;
53     pDirectSoundEnumerateW = NULL;
54     pDirectSoundCaptureCreate8 = NULL;
55     pDirectSoundCaptureEnumerateW = NULL;
56 
57     if (DSoundDLL != NULL) {
58         SDL_UnloadObject(DSoundDLL);
59         DSoundDLL = NULL;
60     }
61 }
62 
63 
64 static int
DSOUND_Load(void)65 DSOUND_Load(void)
66 {
67     int loaded = 0;
68 
69     DSOUND_Unload();
70 
71     DSoundDLL = SDL_LoadObject("DSOUND.DLL");
72     if (DSoundDLL == NULL) {
73         SDL_SetError("DirectSound: failed to load DSOUND.DLL");
74     } else {
75         /* Now make sure we have DirectX 8 or better... */
76         #define DSOUNDLOAD(f) { \
77             p##f = (fn##f) SDL_LoadFunction(DSoundDLL, #f); \
78             if (!p##f) loaded = 0; \
79         }
80         loaded = 1;  /* will reset if necessary. */
81         DSOUNDLOAD(DirectSoundCreate8);
82         DSOUNDLOAD(DirectSoundEnumerateW);
83         DSOUNDLOAD(DirectSoundCaptureCreate8);
84         DSOUNDLOAD(DirectSoundCaptureEnumerateW);
85         #undef DSOUNDLOAD
86 
87         if (!loaded) {
88             SDL_SetError("DirectSound: System doesn't appear to have DX8.");
89         }
90     }
91 
92     if (!loaded) {
93         DSOUND_Unload();
94     }
95 
96     return loaded;
97 }
98 
99 static int
SetDSerror(const char * function,int code)100 SetDSerror(const char *function, int code)
101 {
102     static const char *error;
103     static char errbuf[1024];
104 
105     errbuf[0] = 0;
106     switch (code) {
107     case E_NOINTERFACE:
108         error = "Unsupported interface -- Is DirectX 8.0 or later installed?";
109         break;
110     case DSERR_ALLOCATED:
111         error = "Audio device in use";
112         break;
113     case DSERR_BADFORMAT:
114         error = "Unsupported audio format";
115         break;
116     case DSERR_BUFFERLOST:
117         error = "Mixing buffer was lost";
118         break;
119     case DSERR_CONTROLUNAVAIL:
120         error = "Control requested is not available";
121         break;
122     case DSERR_INVALIDCALL:
123         error = "Invalid call for the current state";
124         break;
125     case DSERR_INVALIDPARAM:
126         error = "Invalid parameter";
127         break;
128     case DSERR_NODRIVER:
129         error = "No audio device found";
130         break;
131     case DSERR_OUTOFMEMORY:
132         error = "Out of memory";
133         break;
134     case DSERR_PRIOLEVELNEEDED:
135         error = "Caller doesn't have priority";
136         break;
137     case DSERR_UNSUPPORTED:
138         error = "Function not supported";
139         break;
140     default:
141         SDL_snprintf(errbuf, SDL_arraysize(errbuf),
142                      "%s: Unknown DirectSound error: 0x%x", function, code);
143         break;
144     }
145     if (!errbuf[0]) {
146         SDL_snprintf(errbuf, SDL_arraysize(errbuf), "%s: %s", function,
147                      error);
148     }
149     return SDL_SetError("%s", errbuf);
150 }
151 
152 static void
DSOUND_FreeDeviceHandle(void * handle)153 DSOUND_FreeDeviceHandle(void *handle)
154 {
155     SDL_free(handle);
156 }
157 
158 static BOOL CALLBACK
FindAllDevs(LPGUID guid,LPCWSTR desc,LPCWSTR module,LPVOID data)159 FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID data)
160 {
161     const int iscapture = (int) ((size_t) data);
162     if (guid != NULL) {  /* skip default device */
163         char *str = WIN_LookupAudioDeviceName(desc, guid);
164         if (str != NULL) {
165             LPGUID cpyguid = (LPGUID) SDL_malloc(sizeof (GUID));
166             SDL_memcpy(cpyguid, guid, sizeof (GUID));
167             SDL_AddAudioDevice(iscapture, str, cpyguid);
168             SDL_free(str);  /* addfn() makes a copy of this string. */
169         }
170     }
171     return TRUE;  /* keep enumerating. */
172 }
173 
174 static void
DSOUND_DetectDevices(void)175 DSOUND_DetectDevices(void)
176 {
177     pDirectSoundCaptureEnumerateW(FindAllDevs, (void *) ((size_t) 1));
178     pDirectSoundEnumerateW(FindAllDevs, (void *) ((size_t) 0));
179 }
180 
181 
182 static void
DSOUND_WaitDevice(_THIS)183 DSOUND_WaitDevice(_THIS)
184 {
185     DWORD status = 0;
186     DWORD cursor = 0;
187     DWORD junk = 0;
188     HRESULT result = DS_OK;
189 
190     /* Semi-busy wait, since we have no way of getting play notification
191        on a primary mixing buffer located in hardware (DirectX 5.0)
192      */
193     result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
194                                                    &junk, &cursor);
195     if (result != DS_OK) {
196         if (result == DSERR_BUFFERLOST) {
197             IDirectSoundBuffer_Restore(this->hidden->mixbuf);
198         }
199 #ifdef DEBUG_SOUND
200         SetDSerror("DirectSound GetCurrentPosition", result);
201 #endif
202         return;
203     }
204 
205     while ((cursor / this->spec.size) == this->hidden->lastchunk) {
206         /* FIXME: find out how much time is left and sleep that long */
207         SDL_Delay(1);
208 
209         /* Try to restore a lost sound buffer */
210         IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status);
211         if ((status & DSBSTATUS_BUFFERLOST)) {
212             IDirectSoundBuffer_Restore(this->hidden->mixbuf);
213             IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status);
214             if ((status & DSBSTATUS_BUFFERLOST)) {
215                 break;
216             }
217         }
218         if (!(status & DSBSTATUS_PLAYING)) {
219             result = IDirectSoundBuffer_Play(this->hidden->mixbuf, 0, 0,
220                                              DSBPLAY_LOOPING);
221             if (result == DS_OK) {
222                 continue;
223             }
224 #ifdef DEBUG_SOUND
225             SetDSerror("DirectSound Play", result);
226 #endif
227             return;
228         }
229 
230         /* Find out where we are playing */
231         result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
232                                                        &junk, &cursor);
233         if (result != DS_OK) {
234             SetDSerror("DirectSound GetCurrentPosition", result);
235             return;
236         }
237     }
238 }
239 
240 static void
DSOUND_PlayDevice(_THIS)241 DSOUND_PlayDevice(_THIS)
242 {
243     /* Unlock the buffer, allowing it to play */
244     if (this->hidden->locked_buf) {
245         IDirectSoundBuffer_Unlock(this->hidden->mixbuf,
246                                   this->hidden->locked_buf,
247                                   this->spec.size, NULL, 0);
248     }
249 }
250 
251 static Uint8 *
DSOUND_GetDeviceBuf(_THIS)252 DSOUND_GetDeviceBuf(_THIS)
253 {
254     DWORD cursor = 0;
255     DWORD junk = 0;
256     HRESULT result = DS_OK;
257     DWORD rawlen = 0;
258 
259     /* Figure out which blocks to fill next */
260     this->hidden->locked_buf = NULL;
261     result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
262                                                    &junk, &cursor);
263     if (result == DSERR_BUFFERLOST) {
264         IDirectSoundBuffer_Restore(this->hidden->mixbuf);
265         result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
266                                                        &junk, &cursor);
267     }
268     if (result != DS_OK) {
269         SetDSerror("DirectSound GetCurrentPosition", result);
270         return (NULL);
271     }
272     cursor /= this->spec.size;
273 #ifdef DEBUG_SOUND
274     /* Detect audio dropouts */
275     {
276         DWORD spot = cursor;
277         if (spot < this->hidden->lastchunk) {
278             spot += this->hidden->num_buffers;
279         }
280         if (spot > this->hidden->lastchunk + 1) {
281             fprintf(stderr, "Audio dropout, missed %d fragments\n",
282                     (spot - (this->hidden->lastchunk + 1)));
283         }
284     }
285 #endif
286     this->hidden->lastchunk = cursor;
287     cursor = (cursor + 1) % this->hidden->num_buffers;
288     cursor *= this->spec.size;
289 
290     /* Lock the audio buffer */
291     result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor,
292                                      this->spec.size,
293                                      (LPVOID *) & this->hidden->locked_buf,
294                                      &rawlen, NULL, &junk, 0);
295     if (result == DSERR_BUFFERLOST) {
296         IDirectSoundBuffer_Restore(this->hidden->mixbuf);
297         result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor,
298                                          this->spec.size,
299                                          (LPVOID *) & this->
300                                          hidden->locked_buf, &rawlen, NULL,
301                                          &junk, 0);
302     }
303     if (result != DS_OK) {
304         SetDSerror("DirectSound Lock", result);
305         return (NULL);
306     }
307     return (this->hidden->locked_buf);
308 }
309 
310 static int
DSOUND_CaptureFromDevice(_THIS,void * buffer,int buflen)311 DSOUND_CaptureFromDevice(_THIS, void *buffer, int buflen)
312 {
313     struct SDL_PrivateAudioData *h = this->hidden;
314     DWORD junk, cursor, ptr1len, ptr2len;
315     VOID *ptr1, *ptr2;
316 
317     SDL_assert(buflen == this->spec.size);
318 
319     while (SDL_TRUE) {
320         if (SDL_AtomicGet(&this->shutdown)) {  /* in case the buffer froze... */
321             SDL_memset(buffer, this->spec.silence, buflen);
322             return buflen;
323         }
324 
325         if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) {
326             return -1;
327         }
328         if ((cursor / this->spec.size) == h->lastchunk) {
329             SDL_Delay(1);  /* FIXME: find out how much time is left and sleep that long */
330         } else {
331             break;
332         }
333     }
334 
335     if (IDirectSoundCaptureBuffer_Lock(h->capturebuf, h->lastchunk * this->spec.size, this->spec.size, &ptr1, &ptr1len, &ptr2, &ptr2len, 0) != DS_OK) {
336         return -1;
337     }
338 
339     SDL_assert(ptr1len == this->spec.size);
340     SDL_assert(ptr2 == NULL);
341     SDL_assert(ptr2len == 0);
342 
343     SDL_memcpy(buffer, ptr1, ptr1len);
344 
345     if (IDirectSoundCaptureBuffer_Unlock(h->capturebuf, ptr1, ptr1len, ptr2, ptr2len) != DS_OK) {
346         return -1;
347     }
348 
349     h->lastchunk = (h->lastchunk + 1) % h->num_buffers;
350 
351     return ptr1len;
352 }
353 
354 static void
DSOUND_FlushCapture(_THIS)355 DSOUND_FlushCapture(_THIS)
356 {
357     struct SDL_PrivateAudioData *h = this->hidden;
358     DWORD junk, cursor;
359     if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) == DS_OK) {
360         h->lastchunk = cursor / this->spec.size;
361     }
362 }
363 
364 static void
DSOUND_CloseDevice(_THIS)365 DSOUND_CloseDevice(_THIS)
366 {
367     if (this->hidden->mixbuf != NULL) {
368         IDirectSoundBuffer_Stop(this->hidden->mixbuf);
369         IDirectSoundBuffer_Release(this->hidden->mixbuf);
370     }
371     if (this->hidden->sound != NULL) {
372         IDirectSound_Release(this->hidden->sound);
373     }
374     if (this->hidden->capturebuf != NULL) {
375         IDirectSoundCaptureBuffer_Stop(this->hidden->capturebuf);
376         IDirectSoundCaptureBuffer_Release(this->hidden->capturebuf);
377     }
378     if (this->hidden->capture != NULL) {
379         IDirectSoundCapture_Release(this->hidden->capture);
380     }
381     SDL_free(this->hidden);
382 }
383 
384 /* This function tries to create a secondary audio buffer, and returns the
385    number of audio chunks available in the created buffer. This is for
386    playback devices, not capture.
387 */
388 static int
CreateSecondary(_THIS,const DWORD bufsize,WAVEFORMATEX * wfmt)389 CreateSecondary(_THIS, const DWORD bufsize, WAVEFORMATEX *wfmt)
390 {
391     LPDIRECTSOUND sndObj = this->hidden->sound;
392     LPDIRECTSOUNDBUFFER *sndbuf = &this->hidden->mixbuf;
393     HRESULT result = DS_OK;
394     DSBUFFERDESC format;
395     LPVOID pvAudioPtr1, pvAudioPtr2;
396     DWORD dwAudioBytes1, dwAudioBytes2;
397 
398     /* Try to create the secondary buffer */
399     SDL_zero(format);
400     format.dwSize = sizeof(format);
401     format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
402     format.dwFlags |= DSBCAPS_GLOBALFOCUS;
403     format.dwBufferBytes = bufsize;
404     format.lpwfxFormat = wfmt;
405     result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
406     if (result != DS_OK) {
407         return SetDSerror("DirectSound CreateSoundBuffer", result);
408     }
409     IDirectSoundBuffer_SetFormat(*sndbuf, wfmt);
410 
411     /* Silence the initial audio buffer */
412     result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes,
413                                      (LPVOID *) & pvAudioPtr1, &dwAudioBytes1,
414                                      (LPVOID *) & pvAudioPtr2, &dwAudioBytes2,
415                                      DSBLOCK_ENTIREBUFFER);
416     if (result == DS_OK) {
417         SDL_memset(pvAudioPtr1, this->spec.silence, dwAudioBytes1);
418         IDirectSoundBuffer_Unlock(*sndbuf,
419                                   (LPVOID) pvAudioPtr1, dwAudioBytes1,
420                                   (LPVOID) pvAudioPtr2, dwAudioBytes2);
421     }
422 
423     /* We're ready to go */
424     return 0;
425 }
426 
427 /* This function tries to create a capture buffer, and returns the
428    number of audio chunks available in the created buffer. This is for
429    capture devices, not playback.
430 */
431 static int
CreateCaptureBuffer(_THIS,const DWORD bufsize,WAVEFORMATEX * wfmt)432 CreateCaptureBuffer(_THIS, const DWORD bufsize, WAVEFORMATEX *wfmt)
433 {
434     LPDIRECTSOUNDCAPTURE capture = this->hidden->capture;
435     LPDIRECTSOUNDCAPTUREBUFFER *capturebuf = &this->hidden->capturebuf;
436     DSCBUFFERDESC format;
437 //    DWORD junk, cursor;
438     HRESULT result;
439 
440     SDL_zero(format);
441     format.dwSize = sizeof (format);
442     format.dwFlags = DSCBCAPS_WAVEMAPPED;
443     format.dwBufferBytes = bufsize;
444     format.lpwfxFormat = wfmt;
445 
446     result = IDirectSoundCapture_CreateCaptureBuffer(capture, &format, capturebuf, NULL);
447     if (result != DS_OK) {
448         return SetDSerror("DirectSound CreateCaptureBuffer", result);
449     }
450 
451     result = IDirectSoundCaptureBuffer_Start(*capturebuf, DSCBSTART_LOOPING);
452     if (result != DS_OK) {
453         IDirectSoundCaptureBuffer_Release(*capturebuf);
454         return SetDSerror("DirectSound Start", result);
455     }
456 
457 #if 0
458     /* presumably this starts at zero, but just in case... */
459     result = IDirectSoundCaptureBuffer_GetCurrentPosition(*capturebuf, &junk, &cursor);
460     if (result != DS_OK) {
461         IDirectSoundCaptureBuffer_Stop(*capturebuf);
462         IDirectSoundCaptureBuffer_Release(*capturebuf);
463         return SetDSerror("DirectSound GetCurrentPosition", result);
464     }
465 
466     this->hidden->lastchunk = cursor / this->spec.size;
467 #endif
468 
469     return 0;
470 }
471 
472 static int
DSOUND_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)473 DSOUND_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
474 {
475     const DWORD numchunks = 8;
476     HRESULT result;
477     SDL_bool valid_format = SDL_FALSE;
478     SDL_bool tried_format = SDL_FALSE;
479     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
480     LPGUID guid = (LPGUID) handle;
481 	DWORD bufsize;
482 
483     /* Initialize all variables that we clean on shutdown */
484     this->hidden = (struct SDL_PrivateAudioData *)
485         SDL_malloc((sizeof *this->hidden));
486     if (this->hidden == NULL) {
487         return SDL_OutOfMemory();
488     }
489     SDL_zerop(this->hidden);
490 
491     /* Open the audio device */
492     if (iscapture) {
493         result = pDirectSoundCaptureCreate8(guid, &this->hidden->capture, NULL);
494         if (result != DS_OK) {
495             return SetDSerror("DirectSoundCaptureCreate8", result);
496         }
497     } else {
498         result = pDirectSoundCreate8(guid, &this->hidden->sound, NULL);
499         if (result != DS_OK) {
500             return SetDSerror("DirectSoundCreate8", result);
501         }
502         result = IDirectSound_SetCooperativeLevel(this->hidden->sound,
503                                                   GetDesktopWindow(),
504                                                   DSSCL_NORMAL);
505         if (result != DS_OK) {
506             return SetDSerror("DirectSound SetCooperativeLevel", result);
507         }
508     }
509 
510     while ((!valid_format) && (test_format)) {
511         switch (test_format) {
512         case AUDIO_U8:
513         case AUDIO_S16:
514         case AUDIO_S32:
515         case AUDIO_F32:
516             tried_format = SDL_TRUE;
517 
518             this->spec.format = test_format;
519 
520             /* Update the fragment size as size in bytes */
521             SDL_CalculateAudioSpec(&this->spec);
522 
523             bufsize = numchunks * this->spec.size;
524             if ((bufsize < DSBSIZE_MIN) || (bufsize > DSBSIZE_MAX)) {
525                 SDL_SetError("Sound buffer size must be between %d and %d",
526                              (DSBSIZE_MIN < numchunks) ? 1 : DSBSIZE_MIN / numchunks,
527                              DSBSIZE_MAX / numchunks);
528             } else {
529                 int rc;
530 				WAVEFORMATEX wfmt;
531                 SDL_zero(wfmt);
532                 if (SDL_AUDIO_ISFLOAT(this->spec.format)) {
533                     wfmt.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
534                 } else {
535                     wfmt.wFormatTag = WAVE_FORMAT_PCM;
536                 }
537 
538                 wfmt.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
539                 wfmt.nChannels = this->spec.channels;
540                 wfmt.nSamplesPerSec = this->spec.freq;
541                 wfmt.nBlockAlign = wfmt.nChannels * (wfmt.wBitsPerSample / 8);
542                 wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign;
543 
544                 rc = iscapture ? CreateCaptureBuffer(this, bufsize, &wfmt) : CreateSecondary(this, bufsize, &wfmt);
545                 if (rc == 0) {
546                     this->hidden->num_buffers = numchunks;
547                     valid_format = SDL_TRUE;
548                 }
549             }
550             break;
551         }
552         test_format = SDL_NextAudioFormat();
553     }
554 
555     if (!valid_format) {
556         if (tried_format) {
557             return -1;  /* CreateSecondary() should have called SDL_SetError(). */
558         }
559         return SDL_SetError("DirectSound: Unsupported audio format");
560     }
561 
562     /* Playback buffers will auto-start playing in DSOUND_WaitDevice() */
563 
564     return 0;                   /* good to go. */
565 }
566 
567 
568 static void
DSOUND_Deinitialize(void)569 DSOUND_Deinitialize(void)
570 {
571     DSOUND_Unload();
572 }
573 
574 
575 static int
DSOUND_Init(SDL_AudioDriverImpl * impl)576 DSOUND_Init(SDL_AudioDriverImpl * impl)
577 {
578     if (!DSOUND_Load()) {
579         return 0;
580     }
581 
582     /* Set the function pointers */
583     impl->DetectDevices = DSOUND_DetectDevices;
584     impl->OpenDevice = DSOUND_OpenDevice;
585     impl->PlayDevice = DSOUND_PlayDevice;
586     impl->WaitDevice = DSOUND_WaitDevice;
587     impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
588     impl->CaptureFromDevice = DSOUND_CaptureFromDevice;
589     impl->FlushCapture = DSOUND_FlushCapture;
590     impl->CloseDevice = DSOUND_CloseDevice;
591     impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle;
592     impl->Deinitialize = DSOUND_Deinitialize;
593 
594     impl->HasCaptureSupport = SDL_TRUE;
595 
596     return 1;   /* this audio target is available. */
597 }
598 
599 AudioBootStrap DSOUND_bootstrap = {
600     "directsound", "DirectSound", DSOUND_Init, 0
601 };
602 
603 #endif /* SDL_AUDIO_DRIVER_DSOUND */
604 
605 /* vi: set ts=4 sw=4 expandtab: */
606