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