]> icculus.org git repositories - divverent/darkplaces.git/blob - snd_win.c
hopefully fix the 3dfx target
[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         int i;
192
193         memset ((void *)&sn, 0, sizeof (sn));
194
195         shm = &sn;
196
197         shm->channels = 2;
198         shm->samplebits = 16;
199         i = COM_CheckParm ("-sndspeed"); // LordHavoc: -sndspeed option
200         if (i && i != (com_argc - 1))
201                 shm->speed = atoi(com_argv[i+1]);
202         else
203                 shm->speed = 11025;
204
205         memset (&format, 0, sizeof(format));
206         format.wFormatTag = WAVE_FORMAT_PCM;
207     format.nChannels = shm->channels;
208     format.wBitsPerSample = shm->samplebits;
209     format.nSamplesPerSec = shm->speed;
210     format.nBlockAlign = format.nChannels
211                 *format.wBitsPerSample / 8;
212     format.cbSize = 0;
213     format.nAvgBytesPerSec = format.nSamplesPerSec
214                 *format.nBlockAlign; 
215
216         if (!hInstDS)
217         {
218                 hInstDS = LoadLibrary("dsound.dll");
219                 
220                 if (hInstDS == NULL)
221                 {
222                         Con_SafePrintf ("Couldn't load dsound.dll\n");
223                         return SIS_FAILURE;
224                 }
225
226                 pDirectSoundCreate = (void *)GetProcAddress(hInstDS,"DirectSoundCreate");
227
228                 if (!pDirectSoundCreate)
229                 {
230                         Con_SafePrintf ("Couldn't get DS proc addr\n");
231                         return SIS_FAILURE;
232                 }
233         }
234
235         while ((hresult = iDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK)
236         {
237                 if (hresult != DSERR_ALLOCATED)
238                 {
239                         Con_SafePrintf ("DirectSound create failed\n");
240                         return SIS_FAILURE;
241                 }
242
243                 if (MessageBox (NULL,
244                                                 "The sound hardware is in use by another app.\n\n"
245                                             "Select Retry to try to start sound again or Cancel to run Quake with no sound.",
246                                                 "Sound not available",
247                                                 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
248                 {
249                         Con_SafePrintf ("DirectSoundCreate failure\n"
250                                                         "  hardware already in use\n");
251                         return SIS_NOTAVAIL;
252                 }
253         }
254
255         dscaps.dwSize = sizeof(dscaps);
256
257         if (DS_OK != pDS->lpVtbl->GetCaps (pDS, &dscaps))
258         {
259                 Con_SafePrintf ("Couldn't get DS caps\n");
260         }
261
262         if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
263         {
264                 Con_SafePrintf ("No DirectSound driver installed\n");
265                 FreeSound ();
266                 return SIS_FAILURE;
267         }
268
269         if (DS_OK != pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_EXCLUSIVE))
270         {
271                 Con_SafePrintf ("Set coop level failed\n");
272                 FreeSound ();
273                 return SIS_FAILURE;
274         }
275
276 // get access to the primary buffer, if possible, so we can set the
277 // sound hardware format
278         memset (&dsbuf, 0, sizeof(dsbuf));
279         dsbuf.dwSize = sizeof(DSBUFFERDESC);
280         dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
281         dsbuf.dwBufferBytes = 0;
282         dsbuf.lpwfxFormat = NULL;
283
284         memset(&dsbcaps, 0, sizeof(dsbcaps));
285         dsbcaps.dwSize = sizeof(dsbcaps);
286         primary_format_set = false;
287
288         if (!COM_CheckParm ("-snoforceformat"))
289         {
290                 if (DS_OK == pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL))
291                 {
292                         pformat = format;
293
294                         if (DS_OK != pDSPBuf->lpVtbl->SetFormat (pDSPBuf, &pformat))
295                         {
296                                 if (snd_firsttime)
297                                         Con_SafePrintf ("Set primary sound buffer format: no\n");
298                         }
299                         else
300                         {
301                                 if (snd_firsttime)
302                                         Con_SafePrintf ("Set primary sound buffer format: yes\n");
303
304                                 primary_format_set = true;
305                         }
306                 }
307         }
308
309         if (!primary_format_set || !COM_CheckParm ("-primarysound"))
310         {
311         // create the secondary buffer we'll actually work with
312                 memset (&dsbuf, 0, sizeof(dsbuf));
313                 dsbuf.dwSize = sizeof(DSBUFFERDESC);
314                 dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE;
315                 dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
316                 dsbuf.lpwfxFormat = &format;
317
318                 memset(&dsbcaps, 0, sizeof(dsbcaps));
319                 dsbcaps.dwSize = sizeof(dsbcaps);
320
321                 if (DS_OK != pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL))
322                 {
323                         Con_SafePrintf ("DS:CreateSoundBuffer Failed");
324                         FreeSound ();
325                         return SIS_FAILURE;
326                 }
327
328                 shm->channels = format.nChannels;
329                 shm->samplebits = format.wBitsPerSample;
330                 shm->speed = format.nSamplesPerSec;
331
332                 if (DS_OK != pDSBuf->lpVtbl->GetCaps (pDSBuf, &dsbcaps))
333                 {
334                         Con_SafePrintf ("DS:GetCaps failed\n");
335                         FreeSound ();
336                         return SIS_FAILURE;
337                 }
338
339                 if (snd_firsttime)
340                         Con_SafePrintf ("Using secondary sound buffer\n");
341         }
342         else
343         {
344                 if (DS_OK != pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_WRITEPRIMARY))
345                 {
346                         Con_SafePrintf ("Set coop level failed\n");
347                         FreeSound ();
348                         return SIS_FAILURE;
349                 }
350
351                 if (DS_OK != pDSPBuf->lpVtbl->GetCaps (pDSPBuf, &dsbcaps))
352                 {
353                         Con_Printf ("DS:GetCaps failed\n");
354                         return SIS_FAILURE;
355                 }
356
357                 pDSBuf = pDSPBuf;
358                 Con_SafePrintf ("Using primary sound buffer\n");
359         }
360
361         // Make sure mixer is active
362         pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
363
364         if (snd_firsttime)
365                 Con_SafePrintf("   %d channel(s)\n"
366                                "   %d bits/sample\n"
367                                            "   %d bytes/sec\n",
368                                            shm->channels, shm->samplebits, shm->speed);
369         
370         gSndBufSize = dsbcaps.dwBufferBytes;
371
372 // initialize the buffer
373         reps = 0;
374
375         while ((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, &lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
376         {
377                 if (hresult != DSERR_BUFFERLOST)
378                 {
379                         Con_SafePrintf ("SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n");
380                         FreeSound ();
381                         return SIS_FAILURE;
382                 }
383
384                 if (++reps > 10000)
385                 {
386                         Con_SafePrintf ("SNDDMA_InitDirect: DS: couldn't restore buffer\n");
387                         FreeSound ();
388                         return SIS_FAILURE;
389                 }
390
391         }
392
393         memset(lpData, 0, dwSize);
394 //              lpData[4] = lpData[5] = 0x7f;   // force a pop for debugging
395
396         pDSBuf->lpVtbl->Unlock(pDSBuf, lpData, dwSize, NULL, 0);
397
398         /* we don't want anyone to access the buffer directly w/o locking it first. */
399         lpData = NULL; 
400
401         pDSBuf->lpVtbl->Stop(pDSBuf);
402         pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &mmstarttime.u.sample, &dwWrite);
403         pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
404
405         shm->soundalive = true;
406         shm->splitbuffer = false;
407         shm->samples = gSndBufSize/(shm->samplebits/8);
408         shm->samplepos = 0;
409         shm->submission_chunk = 1;
410         shm->buffer = (unsigned char *) lpData;
411         sample16 = (shm->samplebits/8) - 1;
412
413         dsound_init = true;
414
415         return SIS_SUCCESS;
416 }
417
418
419 /*
420 ==================
421 SNDDM_InitWav
422
423 Crappy windows multimedia base
424 ==================
425 */
426 qboolean SNDDMA_InitWav (void)
427 {
428         WAVEFORMATEX  format; 
429         int                             i;
430         HRESULT                 hr;
431         
432         snd_sent = 0;
433         snd_completed = 0;
434
435         shm = &sn;
436
437         shm->channels = 2;
438         shm->samplebits = 16;
439         i = COM_CheckParm ("-sndspeed"); // LordHavoc: -sndspeed option
440         if (i && i != (com_argc - 1))
441                 shm->speed = atoi(com_argv[i+1]);
442         else
443                 shm->speed = 11025;
444
445         memset (&format, 0, sizeof(format));
446         format.wFormatTag = WAVE_FORMAT_PCM;
447         format.nChannels = shm->channels;
448         format.wBitsPerSample = shm->samplebits;
449         format.nSamplesPerSec = shm->speed;
450         format.nBlockAlign = format.nChannels
451                 *format.wBitsPerSample / 8;
452         format.cbSize = 0;
453         format.nAvgBytesPerSec = format.nSamplesPerSec
454                 *format.nBlockAlign; 
455         
456         /* Open a waveform device for output using window callback. */ 
457         while ((hr = waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER, 
458                                         &format, 
459                                         0, 0L, CALLBACK_NULL)) != MMSYSERR_NOERROR)
460         {
461                 if (hr != MMSYSERR_ALLOCATED)
462                 {
463                         Con_SafePrintf ("waveOutOpen failed\n");
464                         return false;
465                 }
466
467                 if (MessageBox (NULL,
468                                                 "The sound hardware is in use by another app.\n\n"
469                                             "Select Retry to try to start sound again or Cancel to run Quake with no sound.",
470                                                 "Sound not available",
471                                                 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
472                 {
473                         Con_SafePrintf ("waveOutOpen failure;\n"
474                                                         "  hardware already in use\n");
475                         return false;
476                 }
477         } 
478
479         /* 
480          * Allocate and lock memory for the waveform data. The memory 
481          * for waveform data must be globally allocated with 
482          * GMEM_MOVEABLE and GMEM_SHARE flags. 
483
484         */ 
485         gSndBufSize = WAV_BUFFERS*WAV_BUFFER_SIZE;
486         hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize); 
487         if (!hData) 
488         { 
489                 Con_SafePrintf ("Sound: Out of memory.\n");
490                 FreeSound ();
491                 return false; 
492         }
493         lpData = GlobalLock(hData);
494         if (!lpData)
495         { 
496                 Con_SafePrintf ("Sound: Failed to lock.\n");
497                 FreeSound ();
498                 return false; 
499         } 
500         memset (lpData, 0, gSndBufSize);
501
502         /* 
503          * Allocate and lock memory for the header. This memory must 
504          * also be globally allocated with GMEM_MOVEABLE and 
505          * GMEM_SHARE flags. 
506          */ 
507         hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, 
508                 (DWORD) sizeof(WAVEHDR) * WAV_BUFFERS); 
509
510         if (hWaveHdr == NULL)
511         { 
512                 Con_SafePrintf ("Sound: Failed to Alloc header.\n");
513                 FreeSound ();
514                 return false; 
515         } 
516
517         lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr); 
518
519         if (lpWaveHdr == NULL)
520         { 
521                 Con_SafePrintf ("Sound: Failed to lock header.\n");
522                 FreeSound ();
523                 return false; 
524         }
525
526         memset (lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS);
527
528         /* After allocation, set up and prepare headers. */ 
529         for (i=0 ; i<WAV_BUFFERS ; i++)
530         {
531                 lpWaveHdr[i].dwBufferLength = WAV_BUFFER_SIZE; 
532                 lpWaveHdr[i].lpData = lpData + i*WAV_BUFFER_SIZE;
533
534                 if (waveOutPrepareHeader(hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)) !=
535                                 MMSYSERR_NOERROR)
536                 {
537                         Con_SafePrintf ("Sound: failed to prepare wave headers\n");
538                         FreeSound ();
539                         return false;
540                 }
541         }
542
543         shm->soundalive = true;
544         shm->splitbuffer = false;
545         shm->samples = gSndBufSize/(shm->samplebits/8);
546         shm->samplepos = 0;
547         shm->submission_chunk = 1;
548         shm->buffer = (unsigned char *) lpData;
549         sample16 = (shm->samplebits/8) - 1;
550
551         wav_init = true;
552
553         return true;
554 }
555
556 /*
557 ==================
558 SNDDMA_Init
559
560 Try to find a sound device to mix for.
561 Returns false if nothing is found.
562 ==================
563 */
564
565 qboolean SNDDMA_Init(void)
566 {
567         sndinitstat     stat;
568
569         if (COM_CheckParm ("-wavonly"))
570                 wavonly = true;
571
572         dsound_init = wav_init = 0;
573
574         stat = SIS_FAILURE;     // assume DirectSound won't initialize
575
576         /* Init DirectSound */
577         if (!wavonly)
578         {
579                 if (snd_firsttime || snd_isdirect)
580                 {
581                         stat = SNDDMA_InitDirect ();;
582
583                         if (stat == SIS_SUCCESS)
584                         {
585                                 snd_isdirect = true;
586
587                                 if (snd_firsttime)
588                                         Con_SafePrintf ("DirectSound initialized\n");
589                         }
590                         else
591                         {
592                                 snd_isdirect = false;
593                                 Con_SafePrintf ("DirectSound failed to init\n");
594                         }
595                 }
596         }
597
598 // if DirectSound didn't succeed in initializing, try to initialize
599 // waveOut sound, unless DirectSound failed because the hardware is
600 // already allocated (in which case the user has already chosen not
601 // to have sound)
602         if (!dsound_init && (stat != SIS_NOTAVAIL))
603         {
604                 if (snd_firsttime || snd_iswave)
605                 {
606
607                         snd_iswave = SNDDMA_InitWav ();
608
609                         if (snd_iswave)
610                         {
611                                 if (snd_firsttime)
612                                         Con_SafePrintf ("Wave sound initialized\n");
613                         }
614                         else
615                         {
616                                 Con_SafePrintf ("Wave sound failed to init\n");
617                         }
618                 }
619         }
620
621         snd_firsttime = false;
622
623         if (!dsound_init && !wav_init)
624         {
625 //              if (snd_firsttime)
626 //                      Con_SafePrintf ("No sound device initialized\n");
627
628                 return 0;
629         }
630
631         return 1;
632 }
633
634 /*
635 ==============
636 SNDDMA_GetDMAPos
637
638 return the current sample position (in mono samples read)
639 inside the recirculating dma buffer, so the mixing code will know
640 how many sample are required to fill it up.
641 ===============
642 */
643 int SNDDMA_GetDMAPos(void)
644 {
645         MMTIME  mmtime;
646         int             s;
647         DWORD   dwWrite;
648
649         if (dsound_init) 
650         {
651                 mmtime.wType = TIME_SAMPLES;
652                 pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &mmtime.u.sample, &dwWrite);
653                 s = mmtime.u.sample - mmstarttime.u.sample;
654         }
655         else if (wav_init)
656         {
657                 s = snd_sent * WAV_BUFFER_SIZE;
658         }
659
660
661         s >>= sample16;
662
663         s &= (shm->samples-1);
664
665         return s;
666 }
667
668 /*
669 ==============
670 SNDDMA_Submit
671
672 Send sound to device if buffer isn't really the dma buffer
673 ===============
674 */
675 void SNDDMA_Submit(void)
676 {
677         LPWAVEHDR       h;
678         int                     wResult;
679
680         if (!wav_init)
681                 return;
682
683         //
684         // find which sound blocks have completed
685         //
686         while (1)
687         {
688                 if ( snd_completed == snd_sent )
689                 {
690                         Con_DPrintf ("Sound overrun\n");
691                         break;
692                 }
693
694                 if ( ! (lpWaveHdr[ snd_completed & WAV_MASK].dwFlags & WHDR_DONE) )
695                 {
696                         break;
697                 }
698
699                 snd_completed++;        // this buffer has been played
700         }
701
702         //
703         // submit two new sound blocks
704         //
705         while (((snd_sent - snd_completed) >> sample16) < 4)
706         {
707                 h = lpWaveHdr + ( snd_sent&WAV_MASK );
708
709                 snd_sent++;
710                 /* 
711                  * Now the data block can be sent to the output device. The 
712                  * waveOutWrite function returns immediately and waveform 
713                  * data is sent to the output device in the background. 
714                  */ 
715                 wResult = waveOutWrite(hWaveOut, h, sizeof(WAVEHDR)); 
716
717                 if (wResult != MMSYSERR_NOERROR)
718                 { 
719                         Con_SafePrintf ("Failed to write block to device\n");
720                         FreeSound ();
721                         return; 
722                 } 
723         }
724 }
725
726 /*
727 ==============
728 SNDDMA_Shutdown
729
730 Reset the sound device for exiting
731 ===============
732 */
733 void SNDDMA_Shutdown(void)
734 {
735         FreeSound ();
736 }
737