Added 3D sound support to the Win32 backend (DirectSound and MMSYSTEM), i.e. to the...
[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
23 #ifndef DIRECTSOUND_VERSION
24 #       define DIRECTSOUND_VERSION 0x0500  /* Version 5.0 */
25 #endif
26 #include <windows.h>
27 #include <mmsystem.h>
28 #include <dsound.h>
29
30 // ==============================================================================
31
32 #ifndef _WAVEFORMATEXTENSIBLE_
33 #define _WAVEFORMATEXTENSIBLE_
34 typedef struct
35 {
36         WAVEFORMATEX Format;
37         union
38         {
39                 WORD wValidBitsPerSample;       // bits of precision
40                 WORD wSamplesPerBlock;          // valid if wBitsPerSample==0
41                 WORD wReserved;                         // If neither applies, set to zero
42         } Samples;
43     DWORD dwChannelMask;                        // which channels are present in stream
44     GUID SubFormat;
45 } WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;
46 #endif
47
48 #if !defined(WAVE_FORMAT_EXTENSIBLE)
49 #       define WAVE_FORMAT_EXTENSIBLE 0xFFFE
50 #endif
51
52 // Some speaker positions
53 #ifndef SPEAKER_FRONT_LEFT
54 #       define SPEAKER_FRONT_LEFT                               0x1
55 #       define SPEAKER_FRONT_RIGHT                              0x2
56 #       define SPEAKER_FRONT_CENTER                             0x4
57 #       define SPEAKER_LOW_FREQUENCY                    0x8
58 #       define SPEAKER_BACK_LEFT                                0x10
59 #       define SPEAKER_BACK_RIGHT                               0x20
60 #       define SPEAKER_FRONT_LEFT_OF_CENTER             0x40
61 #       define SPEAKER_FRONT_RIGHT_OF_CENTER    0x80
62 // ... we never use the other values
63 #endif
64
65 // KSDATAFORMAT_SUBTYPE_PCM = GUID "00000001-0000-0010-8000-00aa00389b71"
66 static const GUID MY_KSDATAFORMAT_SUBTYPE_PCM =
67 {
68         0x00000001,
69         0x0000,
70         0x0010,
71         {
72                 0x80, 0x00,
73                 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
74         }
75 };
76
77
78 // ==============================================================================
79
80 extern HWND mainwindow;
81
82 HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter);
83
84 // Wave output: 64KB in 64 buffers of 1KB
85 // (64KB is > 1 sec at 16-bit 22050 Hz mono, and is 1/3 sec at 16-bit 44100 Hz stereo)
86 #define WAV_BUFFERS             64
87 #define WAV_MASK                (WAV_BUFFERS - 1)
88 static unsigned int wav_buffer_size;
89
90 // DirectSound output: 64KB in 1 buffer
91 #define SECONDARY_BUFFER_SIZE(fmt_ptr) ((fmt_ptr)->width * (fmt_ptr)->channels * (fmt_ptr)->speed / 2)
92
93 typedef enum sndinitstat_e {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat;
94
95 static qboolean dsound_init;
96 static qboolean wav_init;
97 static qboolean primary_format_set;
98
99 static int      snd_sent, snd_completed;
100
101 static int prev_painted;
102 static unsigned int paintpot;
103
104 static unsigned int dsound_time;
105
106
107 /*
108  * Global variables. Must be visible to window-procedure function
109  *  so it can unlock and free the data block after it has been played.
110  */
111
112 HANDLE          hData;
113 HPSTR           lpData, lpData2;
114
115 HGLOBAL         hWaveHdr;
116 LPWAVEHDR       lpWaveHdr;
117
118 HWAVEOUT    hWaveOut;
119
120 WAVEOUTCAPS     wavecaps;
121
122 DWORD   gSndBufSize;
123
124 DWORD   dwStartTime;
125
126 LPDIRECTSOUND pDS;
127 LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf;
128
129 HINSTANCE hInstDS;
130
131 qboolean SNDDMA_InitWav (void);
132 sndinitstat SNDDMA_InitDirect (void);
133
134
135 /*
136 ==================
137 SndSys_BuildWaveFormat
138 ==================
139 */
140 static qboolean SndSys_BuildWaveFormat (const snd_format_t* requested, WAVEFORMATEXTENSIBLE* fmt_ptr)
141 {
142         WAVEFORMATEX* pfmtex;
143
144         memset (fmt_ptr, 0, sizeof(*fmt_ptr));
145
146         pfmtex = &fmt_ptr->Format;
147         pfmtex->nChannels = requested->channels;
148     pfmtex->wBitsPerSample = requested->width * 8;
149     pfmtex->nSamplesPerSec = requested->speed;
150         pfmtex->nBlockAlign = pfmtex->nChannels * pfmtex->wBitsPerSample / 8;
151         pfmtex->nAvgBytesPerSec = pfmtex->nSamplesPerSec * pfmtex->nBlockAlign;
152
153         if (requested->channels <= 2)
154         {
155                 pfmtex->wFormatTag = WAVE_FORMAT_PCM;
156                 pfmtex->cbSize = 0;
157         }
158         else
159         {
160                 pfmtex->wFormatTag = WAVE_FORMAT_EXTENSIBLE;
161                 pfmtex->cbSize = sizeof(*fmt_ptr) - sizeof(fmt_ptr->Format);
162                 fmt_ptr->Samples.wValidBitsPerSample = fmt_ptr->Format.wBitsPerSample;
163                 fmt_ptr->SubFormat = MY_KSDATAFORMAT_SUBTYPE_PCM;
164
165                 // Build the channel mask
166                 fmt_ptr->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
167                 switch (requested->channels)
168                 {
169                         case 8:
170                                 fmt_ptr->dwChannelMask |= SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER;
171                                 // no break
172                         case 6:
173                                 fmt_ptr->dwChannelMask |= SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY;
174                                 // no break
175                         case 4:
176                                 fmt_ptr->dwChannelMask |= SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
177                                 break;
178
179                         default:
180                                 Con_Printf("SndSys_BuildWaveFormat: invalid number of channels (%hu)\n", requested->channels);
181                                 return false;
182                 }
183         }
184
185         return true;
186 }
187
188
189 /*
190 ==================
191 SndSys_InitDirectSound
192
193 DirectSound 5 support
194 ==================
195 */
196 static sndinitstat SndSys_InitDirectSound (const snd_format_t* requested)
197 {
198         DSBUFFERDESC                    dsbuf;
199         DSBCAPS                                 dsbcaps;
200         DWORD                                   dwSize;
201         DSCAPS                                  dscaps;
202         WAVEFORMATEXTENSIBLE    format, pformat;
203         HRESULT                                 hresult;
204         int                                             reps;
205
206         if (! SndSys_BuildWaveFormat(requested, &format))
207                 return SIS_FAILURE;
208
209         if (!hInstDS)
210         {
211                 hInstDS = LoadLibrary("dsound.dll");
212
213                 if (hInstDS == NULL)
214                 {
215                         Con_Print("Couldn't load dsound.dll\n");
216                         return SIS_FAILURE;
217                 }
218
219                 pDirectSoundCreate = (void *)GetProcAddress(hInstDS,"DirectSoundCreate");
220
221                 if (!pDirectSoundCreate)
222                 {
223                         Con_Print("Couldn't get DS proc addr\n");
224                         return SIS_FAILURE;
225                 }
226         }
227
228         while ((hresult = pDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK)
229         {
230                 if (hresult != DSERR_ALLOCATED)
231                 {
232                         Con_Print("DirectSound create failed\n");
233                         return SIS_FAILURE;
234                 }
235
236                 if (MessageBox (NULL,
237                                                 "The sound hardware is in use by another app.\n\n"
238                                             "Select Retry to try to start sound again or Cancel to run Quake with no sound.",
239                                                 "Sound not available",
240                                                 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
241                 {
242                         Con_Print("DirectSoundCreate failure\n  hardware already in use\n");
243                         return SIS_NOTAVAIL;
244                 }
245         }
246
247         dscaps.dwSize = sizeof(dscaps);
248
249         if (DS_OK != IDirectSound_GetCaps (pDS, &dscaps))
250         {
251                 Con_Print("Couldn't get DS caps\n");
252         }
253
254         if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
255         {
256                 Con_Print("No DirectSound driver installed\n");
257                 SndSys_Shutdown ();
258                 return SIS_FAILURE;
259         }
260
261         if (DS_OK != IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_EXCLUSIVE))
262         {
263                 Con_Print("Set coop level failed\n");
264                 SndSys_Shutdown ();
265                 return SIS_FAILURE;
266         }
267
268         // get access to the primary buffer, if possible, so we can set the
269         // sound hardware format
270         memset (&dsbuf, 0, sizeof(dsbuf));
271         dsbuf.dwSize = sizeof(DSBUFFERDESC);
272         dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
273         dsbuf.dwBufferBytes = 0;
274         dsbuf.lpwfxFormat = NULL;
275
276         memset(&dsbcaps, 0, sizeof(dsbcaps));
277         dsbcaps.dwSize = sizeof(dsbcaps);
278         primary_format_set = false;
279
280 // COMMANDLINEOPTION: Windows DirectSound: -snoforceformat uses the format that DirectSound returns, rather than forcing it
281         if (!COM_CheckParm ("-snoforceformat"))
282         {
283                 if (DS_OK == IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL))
284                 {
285                         pformat = format;
286
287                         if (DS_OK != IDirectSoundBuffer_SetFormat (pDSPBuf, (WAVEFORMATEX*)&pformat))
288                         {
289                                 Con_Print("Set primary sound buffer format: no\n");
290                         }
291                         else
292                         {
293                                 Con_Print("Set primary sound buffer format: yes\n");
294
295                                 primary_format_set = true;
296                         }
297                 }
298         }
299
300 // COMMANDLINEOPTION: Windows DirectSound: -primarysound locks the sound hardware for exclusive use
301         if (!primary_format_set || !COM_CheckParm ("-primarysound"))
302         {
303                 HRESULT result;
304
305                 // create the secondary buffer we'll actually work with
306                 memset (&dsbuf, 0, sizeof(dsbuf));
307                 dsbuf.dwSize = sizeof(DSBUFFERDESC);
308                 dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE;
309                 dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE(requested);
310                 dsbuf.lpwfxFormat = (WAVEFORMATEX*)&format;
311
312                 memset(&dsbcaps, 0, sizeof(dsbcaps));
313                 dsbcaps.dwSize = sizeof(dsbcaps);
314
315                 result = IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL);
316                 if (result != DS_OK ||
317                         requested->channels != format.Format.nChannels ||
318                         requested->width != format.Format.wBitsPerSample / 8 ||
319                         requested->speed != format.Format.nSamplesPerSec)
320                 {
321                         Con_Printf("DS:CreateSoundBuffer Failed (%d): channels=%u, width=%u, speed=%u\n",
322                                            result, format.Format.nChannels, format.Format.wBitsPerSample / 8, format.Format.nSamplesPerSec);
323                         SndSys_Shutdown ();
324                         return SIS_FAILURE;
325                 }
326
327                 if (DS_OK != IDirectSoundBuffer_GetCaps (pDSBuf, &dsbcaps))
328                 {
329                         Con_Print("DS:GetCaps failed\n");
330                         SndSys_Shutdown ();
331                         return SIS_FAILURE;
332                 }
333
334                 Con_Print("Using secondary sound buffer\n");
335         }
336         else
337         {
338                 if (DS_OK != IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_WRITEPRIMARY))
339                 {
340                         Con_Print("Set coop level failed\n");
341                         SndSys_Shutdown ();
342                         return SIS_FAILURE;
343                 }
344
345                 if (DS_OK != IDirectSoundBuffer_GetCaps (pDSPBuf, &dsbcaps))
346                 {
347                         Con_Print("DS:GetCaps failed\n");
348                         return SIS_FAILURE;
349                 }
350
351                 pDSBuf = pDSPBuf;
352                 Con_Print("Using primary sound buffer\n");
353         }
354
355         // Make sure mixer is active
356         IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
357
358         Con_DPrintf("   %d channel(s)\n"
359                                 "   %d bits/sample\n"
360                                 "   %d samples/sec\n",
361                                 requested->channels, requested->width * 8, requested->speed);
362
363         gSndBufSize = dsbcaps.dwBufferBytes;
364
365         // initialize the buffer
366         reps = 0;
367
368         while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, (LPVOID*)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
369         {
370                 if (hresult != DSERR_BUFFERLOST)
371                 {
372                         Con_Print("SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n");
373                         SndSys_Shutdown ();
374                         return SIS_FAILURE;
375                 }
376
377                 if (++reps > 10000)
378                 {
379                         Con_Print("SNDDMA_InitDirect: DS: couldn't restore buffer\n");
380                         SndSys_Shutdown ();
381                         return SIS_FAILURE;
382                 }
383
384         }
385
386         memset(lpData, 0, dwSize);
387         IDirectSoundBuffer_Unlock(pDSBuf, lpData, dwSize, NULL, 0);
388
389         IDirectSoundBuffer_Stop(pDSBuf);
390         IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
391
392         dwStartTime = 0;
393         dsound_time = 0;
394         snd_renderbuffer = Snd_CreateRingBuffer(requested, gSndBufSize / (requested->width * requested->channels), lpData);
395
396         dsound_init = true;
397
398         return SIS_SUCCESS;
399 }
400
401
402 /*
403 ==================
404 SndSys_InitMmsystem
405
406 Crappy windows multimedia base
407 ==================
408 */
409 static qboolean SndSys_InitMmsystem (const snd_format_t* requested)
410 {
411         WAVEFORMATEXTENSIBLE format;
412         int                             i;
413         HRESULT                 hr;
414
415         if (! SndSys_BuildWaveFormat(requested, &format))
416                 return false;
417
418         // Open a waveform device for output using window callback
419         while ((hr = waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER, (WAVEFORMATEX*)&format,
420                                         0, 0L, CALLBACK_NULL)) != MMSYSERR_NOERROR)
421         {
422                 if (hr != MMSYSERR_ALLOCATED)
423                 {
424                         Con_Print("waveOutOpen failed\n");
425                         return false;
426                 }
427
428                 if (MessageBox (NULL,
429                                                 "The sound hardware is in use by another app.\n\n"
430                                             "Select Retry to try to start sound again or Cancel to run Quake with no sound.",
431                                                 "Sound not available",
432                                                 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
433                 {
434                         Con_Print("waveOutOpen failure;\n  hardware already in use\n");
435                         return false;
436                 }
437         }
438
439         wav_buffer_size = (requested->speed / 2 / WAV_BUFFERS) * requested->channels * requested->width;
440
441         /*
442          * Allocate and lock memory for the waveform data. The memory
443          * for waveform data must be globally allocated with
444          * GMEM_MOVEABLE and GMEM_SHARE flags.
445          */
446         gSndBufSize = WAV_BUFFERS * wav_buffer_size;
447         hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize);
448         if (!hData)
449         {
450                 Con_Print("Sound: Out of memory.\n");
451                 SndSys_Shutdown ();
452                 return false;
453         }
454         lpData = GlobalLock(hData);
455         if (!lpData)
456         {
457                 Con_Print("Sound: Failed to lock.\n");
458                 SndSys_Shutdown ();
459                 return false;
460         }
461         memset (lpData, 0, gSndBufSize);
462
463         /*
464          * Allocate and lock memory for the header. This memory must
465          * also be globally allocated with GMEM_MOVEABLE and
466          * GMEM_SHARE flags.
467          */
468         hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, (DWORD) sizeof(WAVEHDR) * WAV_BUFFERS);
469
470         if (hWaveHdr == NULL)
471         {
472                 Con_Print("Sound: Failed to Alloc header.\n");
473                 SndSys_Shutdown ();
474                 return false;
475         }
476
477         lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr);
478
479         if (lpWaveHdr == NULL)
480         {
481                 Con_Print("Sound: Failed to lock header.\n");
482                 SndSys_Shutdown ();
483                 return false;
484         }
485
486         memset (lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS);
487
488         // After allocation, set up and prepare headers
489         for (i=0 ; i<WAV_BUFFERS ; i++)
490         {
491                 lpWaveHdr[i].dwBufferLength = wav_buffer_size;
492                 lpWaveHdr[i].lpData = lpData + i * wav_buffer_size;
493
494                 if (waveOutPrepareHeader(hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)) != MMSYSERR_NOERROR)
495                 {
496                         Con_Print("Sound: failed to prepare wave headers\n");
497                         SndSys_Shutdown ();
498                         return false;
499                 }
500         }
501
502         snd_renderbuffer = Snd_CreateRingBuffer(requested, gSndBufSize / (requested->width * requested->channels), lpData);
503
504         prev_painted = 0;
505         paintpot = 0;
506
507         snd_sent = 0;
508         snd_completed = 0;
509
510         wav_init = true;
511
512         return true;
513 }
514
515
516 /*
517 ====================
518 SndSys_Init
519
520 Create "snd_renderbuffer" with the proper sound format if the call is successful
521 May return a suggested format if the requested format isn't available
522 ====================
523 */
524 qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
525 {
526         qboolean wavonly;
527         sndinitstat     stat;
528
529         Con_Print ("SndSys_Init: using the Win32 module\n");
530
531 // COMMANDLINEOPTION: Windows Sound: -wavonly uses wave sound instead of DirectSound
532         wavonly = (COM_CheckParm ("-wavonly") != 0);
533         dsound_init = false;
534         wav_init = false;
535
536         stat = SIS_FAILURE;     // assume DirectSound won't initialize
537
538         // Init DirectSound
539         if (!wavonly)
540         {
541                 stat = SndSys_InitDirectSound (requested);
542
543                 if (stat == SIS_SUCCESS)
544                         Con_Print("DirectSound initialized\n");
545                 else
546                         Con_Print("DirectSound failed to init\n");
547         }
548
549         // if DirectSound didn't succeed in initializing, try to initialize
550         // waveOut sound, unless DirectSound failed because the hardware is
551         // already allocated (in which case the user has already chosen not
552         // to have sound)
553         if (!dsound_init && (stat != SIS_NOTAVAIL))
554         {
555                 if (SndSys_InitMmsystem (requested))
556                         Con_Print("Wave sound (MMSYSTEM) initialized\n");
557                 else
558                         Con_Print("Wave sound failed to init\n");
559         }
560
561         return (dsound_init || wav_init);
562 }
563
564
565 /*
566 ====================
567 SndSys_Shutdown
568
569 Stop the sound card, delete "snd_renderbuffer" and free its other resources
570 ====================
571 */
572 void SndSys_Shutdown (void)
573 {
574         if (pDSBuf)
575         {
576                 IDirectSoundBuffer_Stop(pDSBuf);
577                 IDirectSoundBuffer_Release(pDSBuf);
578         }
579
580         // only release primary buffer if it's not also the mixing buffer we just released
581         if (pDSPBuf && (pDSBuf != pDSPBuf))
582         {
583                 IDirectSoundBuffer_Release(pDSPBuf);
584         }
585
586         if (pDS)
587         {
588                 IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_NORMAL);
589                 IDirectSound_Release(pDS);
590         }
591
592         if (hWaveOut)
593         {
594                 waveOutReset (hWaveOut);
595
596                 if (lpWaveHdr)
597                 {
598                         unsigned int i;
599
600                         for (i=0 ; i< WAV_BUFFERS ; i++)
601                                 waveOutUnprepareHeader (hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR));
602                 }
603
604                 waveOutClose (hWaveOut);
605
606                 if (hWaveHdr)
607                 {
608                         GlobalUnlock(hWaveHdr);
609                         GlobalFree(hWaveHdr);
610                 }
611
612                 if (hData)
613                 {
614                         GlobalUnlock(hData);
615                         GlobalFree(hData);
616                 }
617         }
618
619         if (snd_renderbuffer != NULL)
620         {
621                 Mem_Free(snd_renderbuffer);
622                 snd_renderbuffer = NULL;
623         }
624
625         pDS = NULL;
626         pDSBuf = NULL;
627         pDSPBuf = NULL;
628         hWaveOut = 0;
629         hData = 0;
630         hWaveHdr = 0;
631         lpData = NULL;
632         lpWaveHdr = NULL;
633         dsound_init = false;
634         wav_init = false;
635 }
636
637
638 /*
639 ====================
640 SndSys_Submit
641
642 Submit the contents of "snd_renderbuffer" to the sound card
643 ====================
644 */
645 void SndSys_Submit (void)
646 {
647         LPWAVEHDR       h;
648         int                     wResult;
649
650         // DirectSound doesn't need this
651         if (!wav_init)
652                 return;
653
654         paintpot += (snd_renderbuffer->endframe - prev_painted) * snd_renderbuffer->format.channels * snd_renderbuffer->format.width;
655         if (paintpot > WAV_BUFFERS * wav_buffer_size)
656                 paintpot = WAV_BUFFERS * wav_buffer_size;
657         prev_painted = snd_renderbuffer->endframe;
658
659         // submit new sound blocks
660         while (paintpot > wav_buffer_size)
661         {
662                 h = lpWaveHdr + (snd_sent & WAV_MASK);
663
664                 snd_sent++;
665                 /*
666                  * Now the data block can be sent to the output device. The
667                  * waveOutWrite function returns immediately and waveform
668                  * data is sent to the output device in the background.
669                  */
670                 wResult = waveOutWrite(hWaveOut, h, sizeof(WAVEHDR));
671
672                 if (wResult != MMSYSERR_NOERROR)
673                 {
674                         Con_Print("Failed to write block to device\n");
675                         SndSys_Shutdown ();
676                         return;
677                 }
678
679                 paintpot -= wav_buffer_size;
680         }
681
682 }
683
684
685 /*
686 ====================
687 SndSys_GetSoundTime
688
689 Returns the number of sample frames consumed since the sound started
690 ====================
691 */
692 unsigned int SndSys_GetSoundTime (void)
693 {
694         unsigned int factor;
695
696         factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels;
697
698         if (dsound_init)
699         {
700                 DWORD dwTime;
701                 unsigned int diff;
702
703                 IDirectSoundBuffer_GetCurrentPosition(pDSBuf, &dwTime, NULL);
704                 if (dwTime > dwStartTime)
705                         diff = dwTime - dwStartTime;
706                 else
707                         diff = gSndBufSize - dwStartTime + dwTime;
708                 dwStartTime = dwTime;
709
710                 dsound_time += diff / factor;
711                 return dsound_time;
712         }
713
714         if (wav_init)
715         {
716                 // Find which sound blocks have completed
717                 for (;;)
718                 {
719                         if (snd_completed == snd_sent)
720                         {
721                                 Con_DPrint("Sound overrun\n");
722                                 break;
723                         }
724
725                         if (!(lpWaveHdr[snd_completed & WAV_MASK].dwFlags & WHDR_DONE))
726                                 break;
727
728                         snd_completed++;        // this buffer has been played
729                 }
730
731                 return (snd_completed * wav_buffer_size) / factor;
732         }
733
734         return 0;
735 }
736
737
738 static DWORD dsound_dwSize;
739 static DWORD dsound_dwSize2;
740 static DWORD *dsound_pbuf;
741 static DWORD *dsound_pbuf2;
742
743 /*
744 ====================
745 SndSys_LockRenderBuffer
746
747 Get the exclusive lock on "snd_renderbuffer"
748 ====================
749 */
750 qboolean SndSys_LockRenderBuffer (void)
751 {
752         int reps;
753         HRESULT hresult;
754         DWORD   dwStatus;
755
756         if (pDSBuf)
757         {
758                 // if the buffer was lost or stopped, restore it and/or restart it
759                 if (IDirectSoundBuffer_GetStatus (pDSBuf, &dwStatus) != DS_OK)
760                         Con_Print("Couldn't get sound buffer status\n");
761
762                 if (dwStatus & DSBSTATUS_BUFFERLOST)
763                 {
764                         Con_Print("DSound buffer is lost!!\n");
765                         IDirectSoundBuffer_Restore (pDSBuf);
766                 }
767
768                 if (!(dwStatus & DSBSTATUS_PLAYING))
769                         IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
770
771                 reps = 0;
772
773                 while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, (LPVOID*)&dsound_pbuf, &dsound_dwSize, (LPVOID*)&dsound_pbuf2, &dsound_dwSize2, 0)) != DS_OK)
774                 {
775                         if (hresult != DSERR_BUFFERLOST)
776                         {
777                                 Con_Print("S_LockBuffer: DS: Lock Sound Buffer Failed\n");
778                                 S_Shutdown ();
779                                 S_Startup ();
780                                 return false;
781                         }
782
783                         if (++reps > 10000)
784                         {
785                                 Con_Print("S_LockBuffer: DS: couldn't restore buffer\n");
786                                 S_Shutdown ();
787                                 S_Startup ();
788                                 return false;
789                         }
790                 }
791
792                 if ((void*)dsound_pbuf != snd_renderbuffer->ring)
793                         Sys_Error("SndSys_LockRenderBuffer: the ring address has changed!!!\n");
794                 return true;
795         }
796
797         return wav_init;
798 }
799
800
801 /*
802 ====================
803 SndSys_UnlockRenderBuffer
804
805 Release the exclusive lock on "snd_renderbuffer"
806 ====================
807 */
808 void SndSys_UnlockRenderBuffer (void)
809 {
810         if (pDSBuf)
811                 IDirectSoundBuffer_Unlock(pDSBuf, dsound_pbuf, dsound_dwSize, dsound_pbuf2, dsound_dwSize2);
812 }