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