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