0f62112c519daaf3260630b7be6683037c7bcea4
[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->sampleframes = shm->samples / shm->format.channels;
401         shm->samplepos = 0;
402         shm->buffer = (unsigned char *) lpData;
403
404         dsound_init = true;
405
406         return SIS_SUCCESS;
407 }
408
409
410 /*
411 ==================
412 SNDDM_InitWav
413
414 Crappy windows multimedia base
415 ==================
416 */
417 qboolean SNDDMA_InitWav (void)
418 {
419         WAVEFORMATEX  format;
420         int                             i;
421         HRESULT                 hr;
422
423         snd_sent = 0;
424         snd_completed = 0;
425
426         memset((void *)shm, 0, sizeof(*shm));
427         shm->format.channels = 2;
428         shm->format.width = 2;
429 // COMMANDLINEOPTION: Windows Sound: -sndspeed <hz> chooses 44100 hz, 22100 hz, or 11025 hz sound output rate
430         i = COM_CheckParm ("-sndspeed");
431         if (i && i != (com_argc - 1))
432                 shm->format.speed = atoi(com_argv[i+1]);
433         else
434                 shm->format.speed = 44100;
435
436         memset (&format, 0, sizeof(format));
437         format.wFormatTag = WAVE_FORMAT_PCM;
438         format.nChannels = shm->format.channels;
439         format.wBitsPerSample = shm->format.width * 8;
440         format.nSamplesPerSec = shm->format.speed;
441         format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
442         format.cbSize = 0;
443         format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
444
445         // Open a waveform device for output using window callback
446         while ((hr = waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER,
447                                         &format,
448                                         0, 0L, CALLBACK_NULL)) != MMSYSERR_NOERROR)
449         {
450                 if (hr != MMSYSERR_ALLOCATED)
451                 {
452                         Con_Print("waveOutOpen failed\n");
453                         return false;
454                 }
455
456                 if (MessageBox (NULL,
457                                                 "The sound hardware is in use by another app.\n\n"
458                                             "Select Retry to try to start sound again or Cancel to run Quake with no sound.",
459                                                 "Sound not available",
460                                                 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
461                 {
462                         Con_Print("waveOutOpen failure;\n  hardware already in use\n");
463                         return false;
464                 }
465         }
466
467         /*
468          * Allocate and lock memory for the waveform data. The memory
469          * for waveform data must be globally allocated with
470          * GMEM_MOVEABLE and GMEM_SHARE flags.
471          */
472         gSndBufSize = WAV_BUFFERS*WAV_BUFFER_SIZE;
473         hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize);
474         if (!hData)
475         {
476                 Con_Print("Sound: Out of memory.\n");
477                 FreeSound ();
478                 return false;
479         }
480         lpData = GlobalLock(hData);
481         if (!lpData)
482         {
483                 Con_Print("Sound: Failed to lock.\n");
484                 FreeSound ();
485                 return false;
486         }
487         memset (lpData, 0, gSndBufSize);
488
489         /*
490          * Allocate and lock memory for the header. This memory must
491          * also be globally allocated with GMEM_MOVEABLE and
492          * GMEM_SHARE flags.
493          */
494         hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
495                 (DWORD) sizeof(WAVEHDR) * WAV_BUFFERS);
496
497         if (hWaveHdr == NULL)
498         {
499                 Con_Print("Sound: Failed to Alloc header.\n");
500                 FreeSound ();
501                 return false;
502         }
503
504         lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr);
505
506         if (lpWaveHdr == NULL)
507         {
508                 Con_Print("Sound: Failed to lock header.\n");
509                 FreeSound ();
510                 return false;
511         }
512
513         memset (lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS);
514
515         // After allocation, set up and prepare headers
516         for (i=0 ; i<WAV_BUFFERS ; i++)
517         {
518                 lpWaveHdr[i].dwBufferLength = WAV_BUFFER_SIZE;
519                 lpWaveHdr[i].lpData = lpData + i*WAV_BUFFER_SIZE;
520
521                 if (waveOutPrepareHeader(hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)) !=
522                                 MMSYSERR_NOERROR)
523                 {
524                         Con_Print("Sound: failed to prepare wave headers\n");
525                         FreeSound ();
526                         return false;
527                 }
528         }
529
530         shm->samples = gSndBufSize / shm->format.width;
531         shm->sampleframes = shm->samples / shm->format.channels;
532         shm->samplepos = 0;
533         shm->buffer = (unsigned char *) lpData;
534
535         prev_painted = 0;
536         paintpot = 0;
537
538         wav_init = true;
539
540         return true;
541 }
542
543 /*
544 ==================
545 SNDDMA_Init
546
547 Try to find a sound device to mix for.
548 Returns false if nothing is found.
549 ==================
550 */
551 qboolean SNDDMA_Init(void)
552 {
553         sndinitstat     stat;
554
555 // COMMANDLINEOPTION: Windows Sound: -wavonly uses wave sound instead of DirectSound
556         if (COM_CheckParm ("-wavonly"))
557                 wavonly = true;
558
559         dsound_init = false;
560         wav_init = false;
561
562         stat = SIS_FAILURE;     // assume DirectSound won't initialize
563
564         // Init DirectSound
565         if (!wavonly)
566         {
567                 stat = SNDDMA_InitDirect ();
568
569                 if (stat == SIS_SUCCESS)
570                         Con_Print("DirectSound initialized\n");
571                 else
572                         Con_Print("DirectSound failed to init\n");
573         }
574
575         // if DirectSound didn't succeed in initializing, try to initialize
576         // waveOut sound, unless DirectSound failed because the hardware is
577         // already allocated (in which case the user has already chosen not
578         // to have sound)
579         if (!dsound_init && (stat != SIS_NOTAVAIL))
580         {
581                 if (SNDDMA_InitWav ())
582                         Con_Print("Wave sound initialized\n");
583                 else
584                         Con_Print("Wave sound failed to init\n");
585         }
586
587         if (!dsound_init && !wav_init)
588                 return false;
589
590         return true;
591 }
592
593 /*
594 ==============
595 SNDDMA_GetDMAPos
596
597 return the current sample position (in mono samples read)
598 inside the recirculating dma buffer, so the mixing code will know
599 how many sample are required to fill it up.
600 ===============
601 */
602 int SNDDMA_GetDMAPos(void)
603 {
604         DWORD dwTime, s;
605
606         if (dsound_init)
607         {
608                 pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &dwTime, NULL);
609                 s = dwTime - dwStartTime;
610         }
611         else if (wav_init)
612         {
613                 // Find which sound blocks have completed
614                 for (;;)
615                 {
616                         if (snd_completed == snd_sent)
617                         {
618                                 Con_DPrint("Sound overrun\n");
619                                 break;
620                         }
621
622                         if (!(lpWaveHdr[snd_completed & WAV_MASK].dwFlags & WHDR_DONE))
623                                 break;
624
625                         snd_completed++;        // this buffer has been played
626                 }
627
628                 s = snd_completed * WAV_BUFFER_SIZE;
629         }
630         else
631                 return 0;
632
633         return (s >> (shm->format.width - 1)) & (shm->samples - 1);
634 }
635
636 /*
637 ==============
638 SNDDMA_Submit
639
640 Send sound to device if buffer isn't really the dma buffer
641 ===============
642 */
643 void SNDDMA_Submit(void)
644 {
645         LPWAVEHDR       h;
646         int                     wResult;
647
648         // DirectSound doesn't need this
649         if (!wav_init)
650                 return;
651
652         paintpot += (paintedtime - prev_painted) * shm->format.channels * shm->format.width;
653         if (paintpot > WAV_BUFFERS * WAV_BUFFER_SIZE)
654                 paintpot = WAV_BUFFERS * WAV_BUFFER_SIZE;
655         prev_painted = paintedtime;
656
657         // submit new sound blocks
658         while (paintpot > WAV_BUFFER_SIZE)
659         {
660                 h = lpWaveHdr + (snd_sent & WAV_MASK);
661
662                 snd_sent++;
663                 /*
664                  * Now the data block can be sent to the output device. The
665                  * waveOutWrite function returns immediately and waveform
666                  * data is sent to the output device in the background.
667                  */
668                 wResult = waveOutWrite(hWaveOut, h, sizeof(WAVEHDR));
669
670                 if (wResult != MMSYSERR_NOERROR)
671                 {
672                         Con_Print("Failed to write block to device\n");
673                         FreeSound ();
674                         return;
675                 }
676
677                 paintpot -= WAV_BUFFER_SIZE;
678         }
679 }
680
681 /*
682 ==============
683 SNDDMA_Shutdown
684
685 Reset the sound device for exiting
686 ===============
687 */
688 void SNDDMA_Shutdown(void)
689 {
690         FreeSound ();
691 }
692
693
694 static DWORD dsound_dwSize;
695 static DWORD dsound_dwSize2;
696 static DWORD *dsound_pbuf;
697 static DWORD *dsound_pbuf2;
698
699 void *S_LockBuffer(void)
700 {
701         int reps;
702         HRESULT hresult;
703         DWORD   dwStatus;
704
705         if (pDSBuf)
706         {
707                 // if the buffer was lost or stopped, restore it and/or restart it
708                 if (pDSBuf->lpVtbl->GetStatus (pDSBuf, &dwStatus) != DS_OK)
709                         Con_Print("Couldn't get sound buffer status\n");
710
711                 if (dwStatus & DSBSTATUS_BUFFERLOST)
712                         pDSBuf->lpVtbl->Restore (pDSBuf);
713
714                 if (!(dwStatus & DSBSTATUS_PLAYING))
715                         pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
716
717                 reps = 0;
718
719                 while ((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, (LPVOID*)&dsound_pbuf, &dsound_dwSize, (LPVOID*)&dsound_pbuf2, &dsound_dwSize2, 0)) != DS_OK)
720                 {
721                         if (hresult != DSERR_BUFFERLOST)
722                         {
723                                 Con_Print("S_LockBuffer: DS: Lock Sound Buffer Failed\n");
724                                 S_Shutdown ();
725                                 S_Startup ();
726                                 return NULL;
727                         }
728
729                         if (++reps > 10000)
730                         {
731                                 Con_Print("S_LockBuffer: DS: couldn't restore buffer\n");
732                                 S_Shutdown ();
733                                 S_Startup ();
734                                 return NULL;
735                         }
736                 }
737                 return dsound_pbuf;
738         }
739         else if (wav_init)
740                 return shm->buffer;
741         else
742                 return NULL;
743 }
744
745 void S_UnlockBuffer(void)
746 {
747         if (pDSBuf)
748                 pDSBuf->lpVtbl->Unlock(pDSBuf, dsound_pbuf, dsound_dwSize, dsound_pbuf2, dsound_dwSize2);
749 }
750