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