]> git.xonotic.org Git - xonotic/darkplaces.git/blob - snd_win.c
2752446fb9ba3c2e3c7373d22037ab1e36889733
[xonotic/darkplaces.git] / snd_win.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 #include "quakedef.h"
21 #include "snd_main.h"
22 #include <windows.h>
23 #include <dsound.h>
24
25 extern HWND mainwindow;
26
27 #define iDirectSoundCreate(a,b,c)       pDirectSoundCreate(a,b,c)
28
29 HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter);
30
31 // Wave output: 64KB in 64 buffers of 1KB
32 // (64KB is > 1 sec at 16-bit 22050 Hz mono, and is 1/3 sec at 16-bit 44100 Hz stereo)
33 #define WAV_BUFFERS                             64
34 #define WAV_MASK                                (WAV_BUFFERS - 1)
35 #define WAV_BUFFER_SIZE                 1024
36
37 // DirectSound output: 64KB in 1 buffer
38 #define SECONDARY_BUFFER_SIZE   (64 * 1024)
39
40 typedef enum sndinitstat_e {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat;
41
42 static qboolean wavonly;
43 static qboolean dsound_init;
44 static qboolean wav_init;
45 static qboolean primary_format_set;
46
47 static int      snd_sent, snd_completed;
48
49 static int prev_painted;
50 static unsigned int paintpot;
51
52
53 /*
54  * Global variables. Must be visible to window-procedure function
55  *  so it can unlock and free the data block after it has been played.
56  */
57
58 HANDLE          hData;
59 HPSTR           lpData, lpData2;
60
61 HGLOBAL         hWaveHdr;
62 LPWAVEHDR       lpWaveHdr;
63
64 HWAVEOUT    hWaveOut;
65
66 WAVEOUTCAPS     wavecaps;
67
68 DWORD   gSndBufSize;
69
70 DWORD   dwStartTime;
71
72 LPDIRECTSOUND pDS;
73 LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf;
74
75 HINSTANCE hInstDS;
76
77 qboolean SNDDMA_InitWav (void);
78 sndinitstat SNDDMA_InitDirect (void);
79
80 /*
81 ==================
82 S_BlockSound
83 ==================
84 */
85 void S_BlockSound (void)
86 {
87         // DirectSound takes care of blocking itself
88         if (wav_init)
89         {
90                 snd_blocked++;
91
92                 if (snd_blocked == 1)
93                         waveOutReset (hWaveOut);
94         }
95 }
96
97
98 /*
99 ==================
100 S_UnblockSound
101 ==================
102 */
103 void S_UnblockSound (void)
104 {
105         // DirectSound takes care of blocking itself
106         if (wav_init)
107                 snd_blocked--;
108 }
109
110
111 /*
112 ==================
113 FreeSound
114 ==================
115 */
116 void FreeSound (void)
117 {
118         int             i;
119
120         if (pDSBuf)
121         {
122                 pDSBuf->lpVtbl->Stop(pDSBuf);
123                 pDSBuf->lpVtbl->Release(pDSBuf);
124         }
125
126         // only release primary buffer if it's not also the mixing buffer we just released
127         if (pDSPBuf && (pDSBuf != pDSPBuf))
128         {
129                 pDSPBuf->lpVtbl->Release(pDSPBuf);
130         }
131
132         if (pDS)
133         {
134                 pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_NORMAL);
135                 pDS->lpVtbl->Release(pDS);
136         }
137
138         if (hWaveOut)
139         {
140                 waveOutReset (hWaveOut);
141
142                 if (lpWaveHdr)
143                 {
144                         for (i=0 ; i< WAV_BUFFERS ; i++)
145                                 waveOutUnprepareHeader (hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR));
146                 }
147
148                 waveOutClose (hWaveOut);
149
150                 if (hWaveHdr)
151                 {
152                         GlobalUnlock(hWaveHdr);
153                         GlobalFree(hWaveHdr);
154                 }
155
156                 if (hData)
157                 {
158                         GlobalUnlock(hData);
159                         GlobalFree(hData);
160                 }
161
162         }
163
164         pDS = NULL;
165         pDSBuf = NULL;
166         pDSPBuf = NULL;
167         hWaveOut = 0;
168         hData = 0;
169         hWaveHdr = 0;
170         lpData = NULL;
171         lpWaveHdr = NULL;
172         dsound_init = false;
173         wav_init = false;
174 }
175
176
177 /*
178 ==================
179 SNDDMA_InitDirect
180
181 Direct-Sound support
182 ==================
183 */
184 sndinitstat SNDDMA_InitDirect (void)
185 {
186         DSBUFFERDESC    dsbuf;
187         DSBCAPS                 dsbcaps;
188         DWORD                   dwSize;
189         DSCAPS                  dscaps;
190         WAVEFORMATEX    format, pformat;
191         HRESULT                 hresult;
192         int                             reps;
193         int i;
194
195         memset((void *)shm, 0, sizeof(*shm));
196         shm->format.channels = 2;
197         shm->format.width = 2;
198 // COMMANDLINEOPTION: Windows Sound: -sndspeed <hz> chooses 44100 hz, 22100 hz, or 11025 hz sound output rate
199         i = COM_CheckParm ("-sndspeed");
200         if (i && i != (com_argc - 1))
201                 shm->format.speed = atoi(com_argv[i+1]);
202         else
203                 shm->format.speed = 44100;
204
205         memset (&format, 0, sizeof(format));
206         format.wFormatTag = WAVE_FORMAT_PCM;
207     format.nChannels = shm->format.channels;
208     format.wBitsPerSample = shm->format.width * 8;
209     format.nSamplesPerSec = shm->format.speed;
210     format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
211     format.cbSize = 0;
212     format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
213
214         if (!hInstDS)
215         {
216                 hInstDS = LoadLibrary("dsound.dll");
217
218                 if (hInstDS == NULL)
219                 {
220                         Con_Print("Couldn't load dsound.dll\n");
221                         return SIS_FAILURE;
222                 }
223
224                 pDirectSoundCreate = (void *)GetProcAddress(hInstDS,"DirectSoundCreate");
225
226                 if (!pDirectSoundCreate)
227                 {
228                         Con_Print("Couldn't get DS proc addr\n");
229                         return SIS_FAILURE;
230                 }
231         }
232
233         while ((hresult = iDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK)
234         {
235                 if (hresult != DSERR_ALLOCATED)
236                 {
237                         Con_Print("DirectSound create failed\n");
238                         return SIS_FAILURE;
239                 }
240
241                 if (MessageBox (NULL,
242                                                 "The sound hardware is in use by another app.\n\n"
243                                             "Select Retry to try to start sound again or Cancel to run Quake with no sound.",
244                                                 "Sound not available",
245                                                 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
246                 {
247                         Con_Print("DirectSoundCreate failure\n  hardware already in use\n");
248                         return SIS_NOTAVAIL;
249                 }
250         }
251
252         dscaps.dwSize = sizeof(dscaps);
253
254         if (DS_OK != pDS->lpVtbl->GetCaps (pDS, &dscaps))
255         {
256                 Con_Print("Couldn't get DS caps\n");
257         }
258
259         if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
260         {
261                 Con_Print("No DirectSound driver installed\n");
262                 FreeSound ();
263                 return SIS_FAILURE;
264         }
265
266         if (DS_OK != pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_EXCLUSIVE))
267         {
268                 Con_Print("Set coop level failed\n");
269                 FreeSound ();
270                 return SIS_FAILURE;
271         }
272
273         // get access to the primary buffer, if possible, so we can set the
274         // sound hardware format
275         memset (&dsbuf, 0, sizeof(dsbuf));
276         dsbuf.dwSize = sizeof(DSBUFFERDESC);
277         dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
278         dsbuf.dwBufferBytes = 0;
279         dsbuf.lpwfxFormat = NULL;
280
281         memset(&dsbcaps, 0, sizeof(dsbcaps));
282         dsbcaps.dwSize = sizeof(dsbcaps);
283         primary_format_set = false;
284
285 // COMMANDLINEOPTION: Windows DirectSound: -snoforceformat uses the format that DirectSound returns, rather than forcing it
286         if (!COM_CheckParm ("-snoforceformat"))
287         {
288                 if (DS_OK == pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL))
289                 {
290                         pformat = format;
291
292                         if (DS_OK != pDSPBuf->lpVtbl->SetFormat (pDSPBuf, &pformat))
293                         {
294                                 Con_Print("Set primary sound buffer format: no\n");
295                         }
296                         else
297                         {
298                                 Con_Print("Set primary sound buffer format: yes\n");
299
300                                 primary_format_set = true;
301                         }
302                 }
303         }
304
305 // COMMANDLINEOPTION: Windows DirectSound: -primarysound locks the sound hardware for exclusive use
306         if (!primary_format_set || !COM_CheckParm ("-primarysound"))
307         {
308                 // create the secondary buffer we'll actually work with
309                 memset (&dsbuf, 0, sizeof(dsbuf));
310                 dsbuf.dwSize = sizeof(DSBUFFERDESC);
311                 dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE;
312                 dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
313                 dsbuf.lpwfxFormat = &format;
314
315                 memset(&dsbcaps, 0, sizeof(dsbcaps));
316                 dsbcaps.dwSize = sizeof(dsbcaps);
317
318                 if (DS_OK != pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL))
319                 {
320                         Con_Print("DS:CreateSoundBuffer Failed\n");
321                         FreeSound ();
322                         return SIS_FAILURE;
323                 }
324
325                 shm->format.channels = format.nChannels;
326                 shm->format.width = format.wBitsPerSample / 8;
327                 shm->format.speed = format.nSamplesPerSec;
328
329                 if (DS_OK != pDSBuf->lpVtbl->GetCaps (pDSBuf, &dsbcaps))
330                 {
331                         Con_Print("DS:GetCaps failed\n");
332                         FreeSound ();
333                         return SIS_FAILURE;
334                 }
335
336                 Con_Print("Using secondary sound buffer\n");
337         }
338         else
339         {
340                 if (DS_OK != pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_WRITEPRIMARY))
341                 {
342                         Con_Print("Set coop level failed\n");
343                         FreeSound ();
344                         return SIS_FAILURE;
345                 }
346
347                 if (DS_OK != pDSPBuf->lpVtbl->GetCaps (pDSPBuf, &dsbcaps))
348                 {
349                         Con_Print("DS:GetCaps failed\n");
350                         return SIS_FAILURE;
351                 }
352
353                 pDSBuf = pDSPBuf;
354                 Con_Print("Using primary sound buffer\n");
355         }
356
357         // Make sure mixer is active
358         pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
359
360         Con_Printf("   %d channel(s)\n"
361                        "   %d bits/sample\n"
362                                    "   %d samples/sec\n",
363                                    shm->format.channels, shm->format.width * 8, shm->format.speed);
364
365         gSndBufSize = dsbcaps.dwBufferBytes;
366
367         // initialize the buffer
368         reps = 0;
369
370         while ((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, (LPVOID*)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
371         {
372                 if (hresult != DSERR_BUFFERLOST)
373                 {
374                         Con_Print("SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n");
375                         FreeSound ();
376                         return SIS_FAILURE;
377                 }
378
379                 if (++reps > 10000)
380                 {
381                         Con_Print("SNDDMA_InitDirect: DS: couldn't restore buffer\n");
382                         FreeSound ();
383                         return SIS_FAILURE;
384                 }
385
386         }
387
388         memset(lpData, 0, dwSize);
389
390         pDSBuf->lpVtbl->Unlock(pDSBuf, lpData, dwSize, NULL, 0);
391
392         // we don't want anyone to access the buffer directly w/o locking it first.
393         lpData = NULL;
394
395         pDSBuf->lpVtbl->Stop(pDSBuf);
396         pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &dwStartTime, NULL);
397         pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
398
399         shm->samples = gSndBufSize / shm->format.width;
400         shm->samplepos = 0;
401         shm->buffer = (unsigned char *) lpData;
402
403         dsound_init = true;
404
405         return SIS_SUCCESS;
406 }
407
408
409 /*
410 ==================
411 SNDDM_InitWav
412
413 Crappy windows multimedia base
414 ==================
415 */
416 qboolean SNDDMA_InitWav (void)
417 {
418         WAVEFORMATEX  format;
419         int                             i;
420         HRESULT                 hr;
421
422         snd_sent = 0;
423         snd_completed = 0;
424
425         memset((void *)shm, 0, sizeof(*shm));
426         shm->format.channels = 2;
427         shm->format.width = 2;
428 // COMMANDLINEOPTION: Windows Sound: -sndspeed <hz> chooses 44100 hz, 22100 hz, or 11025 hz sound output rate
429         i = COM_CheckParm ("-sndspeed");
430         if (i && i != (com_argc - 1))
431                 shm->format.speed = atoi(com_argv[i+1]);
432         else
433                 shm->format.speed = 44100;
434
435         memset (&format, 0, sizeof(format));
436         format.wFormatTag = WAVE_FORMAT_PCM;
437         format.nChannels = shm->format.channels;
438         format.wBitsPerSample = shm->format.width * 8;
439         format.nSamplesPerSec = shm->format.speed;
440         format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
441         format.cbSize = 0;
442         format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
443
444         // Open a waveform device for output using window callback
445         while ((hr = waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER,
446                                         &format,
447                                         0, 0L, CALLBACK_NULL)) != MMSYSERR_NOERROR)
448         {
449                 if (hr != MMSYSERR_ALLOCATED)
450                 {
451                         Con_Print("waveOutOpen failed\n");
452                         return false;
453                 }
454
455                 if (MessageBox (NULL,
456                                                 "The sound hardware is in use by another app.\n\n"
457                                             "Select Retry to try to start sound again or Cancel to run Quake with no sound.",
458                                                 "Sound not available",
459                                                 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
460                 {
461                         Con_Print("waveOutOpen failure;\n  hardware already in use\n");
462                         return false;
463                 }
464         }
465
466         /*
467          * Allocate and lock memory for the waveform data. The memory
468          * for waveform data must be globally allocated with
469          * GMEM_MOVEABLE and GMEM_SHARE flags.
470          */
471         gSndBufSize = WAV_BUFFERS*WAV_BUFFER_SIZE;
472         hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize);
473         if (!hData)
474         {
475                 Con_Print("Sound: Out of memory.\n");
476                 FreeSound ();
477                 return false;
478         }
479         lpData = GlobalLock(hData);
480         if (!lpData)
481         {
482                 Con_Print("Sound: Failed to lock.\n");
483                 FreeSound ();
484                 return false;
485         }
486         memset (lpData, 0, gSndBufSize);
487
488         /*
489          * Allocate and lock memory for the header. This memory must
490          * also be globally allocated with GMEM_MOVEABLE and
491          * GMEM_SHARE flags.
492          */
493         hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
494                 (DWORD) sizeof(WAVEHDR) * WAV_BUFFERS);
495
496         if (hWaveHdr == NULL)
497         {
498                 Con_Print("Sound: Failed to Alloc header.\n");
499                 FreeSound ();
500                 return false;
501         }
502
503         lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr);
504
505         if (lpWaveHdr == NULL)
506         {
507                 Con_Print("Sound: Failed to lock header.\n");
508                 FreeSound ();
509                 return false;
510         }
511
512         memset (lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS);
513
514         // After allocation, set up and prepare headers
515         for (i=0 ; i<WAV_BUFFERS ; i++)
516         {
517                 lpWaveHdr[i].dwBufferLength = WAV_BUFFER_SIZE;
518                 lpWaveHdr[i].lpData = lpData + i*WAV_BUFFER_SIZE;
519
520                 if (waveOutPrepareHeader(hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)) !=
521                                 MMSYSERR_NOERROR)
522                 {
523                         Con_Print("Sound: failed to prepare wave headers\n");
524                         FreeSound ();
525                         return false;
526                 }
527         }
528
529         shm->samples = gSndBufSize / shm->format.width;
530         shm->samplepos = 0;
531         shm->buffer = (unsigned char *) lpData;
532
533         prev_painted = 0;
534         paintpot = 0;
535
536         wav_init = true;
537
538         return true;
539 }
540
541 /*
542 ==================
543 SNDDMA_Init
544
545 Try to find a sound device to mix for.
546 Returns false if nothing is found.
547 ==================
548 */
549 qboolean SNDDMA_Init(void)
550 {
551         sndinitstat     stat;
552
553 // COMMANDLINEOPTION: Windows Sound: -wavonly uses wave sound instead of DirectSound
554         if (COM_CheckParm ("-wavonly"))
555                 wavonly = true;
556
557         dsound_init = false;
558         wav_init = false;
559
560         stat = SIS_FAILURE;     // assume DirectSound won't initialize
561
562         // Init DirectSound
563         if (!wavonly)
564         {
565                 stat = SNDDMA_InitDirect ();
566
567                 if (stat == SIS_SUCCESS)
568                         Con_Print("DirectSound initialized\n");
569                 else
570                         Con_Print("DirectSound failed to init\n");
571         }
572
573         // if DirectSound didn't succeed in initializing, try to initialize
574         // waveOut sound, unless DirectSound failed because the hardware is
575         // already allocated (in which case the user has already chosen not
576         // to have sound)
577         if (!dsound_init && (stat != SIS_NOTAVAIL))
578         {
579                 if (SNDDMA_InitWav ())
580                         Con_Print("Wave sound initialized\n");
581                 else
582                         Con_Print("Wave sound failed to init\n");
583         }
584
585         if (!dsound_init && !wav_init)
586                 return false;
587
588         return true;
589 }
590
591 /*
592 ==============
593 SNDDMA_GetDMAPos
594
595 return the current sample position (in mono samples read)
596 inside the recirculating dma buffer, so the mixing code will know
597 how many sample are required to fill it up.
598 ===============
599 */
600 int SNDDMA_GetDMAPos(void)
601 {
602         DWORD dwTime, s;
603
604         if (dsound_init)
605         {
606                 pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &dwTime, NULL);
607                 s = dwTime - dwStartTime;
608         }
609         else if (wav_init)
610         {
611                 // Find which sound blocks have completed
612                 for (;;)
613                 {
614                         if (snd_completed == snd_sent)
615                         {
616                                 Con_DPrint("Sound overrun\n");
617                                 break;
618                         }
619
620                         if (!(lpWaveHdr[snd_completed & WAV_MASK].dwFlags & WHDR_DONE))
621                                 break;
622
623                         snd_completed++;        // this buffer has been played
624                 }
625
626                 s = snd_completed * WAV_BUFFER_SIZE;
627         }
628         else
629                 return 0;
630
631         return (s >> (shm->format.width - 1)) & (shm->samples - 1);
632 }
633
634 /*
635 ==============
636 SNDDMA_Submit
637
638 Send sound to device if buffer isn't really the dma buffer
639 ===============
640 */
641 void SNDDMA_Submit(void)
642 {
643         LPWAVEHDR       h;
644         int                     wResult;
645
646         // DirectSound doesn't need this
647         if (!wav_init)
648                 return;
649
650         paintpot += (paintedtime - prev_painted) * shm->format.channels * shm->format.width;
651         if (paintpot > WAV_BUFFERS * WAV_BUFFER_SIZE)
652                 paintpot = WAV_BUFFERS * WAV_BUFFER_SIZE;
653         prev_painted = paintedtime;
654
655         // submit new sound blocks
656         while (paintpot > WAV_BUFFER_SIZE)
657         {
658                 h = lpWaveHdr + (snd_sent & WAV_MASK);
659
660                 snd_sent++;
661                 /*
662                  * Now the data block can be sent to the output device. The
663                  * waveOutWrite function returns immediately and waveform
664                  * data is sent to the output device in the background.
665                  */
666                 wResult = waveOutWrite(hWaveOut, h, sizeof(WAVEHDR));
667
668                 if (wResult != MMSYSERR_NOERROR)
669                 {
670                         Con_Print("Failed to write block to device\n");
671                         FreeSound ();
672                         return;
673                 }
674
675                 paintpot -= WAV_BUFFER_SIZE;
676         }
677 }
678
679 /*
680 ==============
681 SNDDMA_Shutdown
682
683 Reset the sound device for exiting
684 ===============
685 */
686 void SNDDMA_Shutdown(void)
687 {
688         FreeSound ();
689 }
690
691
692 static DWORD dsound_dwSize;
693 static DWORD dsound_dwSize2;
694 static DWORD *dsound_pbuf;
695 static DWORD *dsound_pbuf2;
696
697 void *S_LockBuffer(void)
698 {
699         int reps;
700         HRESULT hresult;
701         DWORD   dwStatus;
702
703         if (pDSBuf)
704         {
705                 // if the buffer was lost or stopped, restore it and/or restart it
706                 if (pDSBuf->lpVtbl->GetStatus (pDSBuf, &dwStatus) != DS_OK)
707                         Con_Print("Couldn't get sound buffer status\n");
708
709                 if (dwStatus & DSBSTATUS_BUFFERLOST)
710                         pDSBuf->lpVtbl->Restore (pDSBuf);
711
712                 if (!(dwStatus & DSBSTATUS_PLAYING))
713                         pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
714
715                 reps = 0;
716
717                 while ((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, (LPVOID*)&dsound_pbuf, &dsound_dwSize, (LPVOID*)&dsound_pbuf2, &dsound_dwSize2, 0)) != DS_OK)
718                 {
719                         if (hresult != DSERR_BUFFERLOST)
720                         {
721                                 Con_Print("S_LockBuffer: DS: Lock Sound Buffer Failed\n");
722                                 S_Shutdown ();
723                                 S_Startup ();
724                                 return NULL;
725                         }
726
727                         if (++reps > 10000)
728                         {
729                                 Con_Print("S_LockBuffer: DS: couldn't restore buffer\n");
730                                 S_Shutdown ();
731                                 S_Startup ();
732                                 return NULL;
733                         }
734                 }
735                 return dsound_pbuf;
736         }
737         else if (wav_init)
738                 return shm->buffer;
739         else
740                 return NULL;
741 }
742
743 void S_UnlockBuffer(void)
744 {
745         if (pDSBuf)
746                 pDSBuf->lpVtbl->Unlock(pDSBuf, dsound_pbuf, dsound_dwSize, dsound_pbuf2, dsound_dwSize2);
747 }
748