/* =========================================================================== Return to Castle Wolfenstein multiplayer GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). RTCW MP Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. RTCW MP Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RTCW MP Source Code. If not, see . In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include #include "../client/snd_local.h" #include "win_local.h" HRESULT ( WINAPI * pDirectSoundCreate )( GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter ); #define iDirectSoundCreate( a,b,c ) pDirectSoundCreate( a,b,c ) #define SECONDARY_BUFFER_SIZE 0x10000 static qboolean dsound_init; static int sample16; static DWORD gSndBufSize; static DWORD locksize; static LPDIRECTSOUND pDS; static LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf; static HINSTANCE hInstDS; static qboolean snd_iswave = qfalse; static const char *DSoundError( int error ) { switch ( error ) { case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST"; case DSERR_INVALIDCALL: return "DSERR_INVALIDCALLS"; case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM"; case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED"; } return "unknown"; } HGLOBAL hWaveHdr; LPWAVEHDR lpWaveHdr; HWAVEOUT hWaveOut; WAVEOUTCAPS wavecaps; DWORD gSndBufSize; MMTIME mmstarttime; HANDLE hData; HPSTR lpData, lpData2; static qboolean wav_init = qfalse; // starts at 0 for disabled static int snd_buffer_count = 0; static int sample16; static int snd_sent, snd_completed; // 64K is > 1 second at 16-bit, 22050 Hz #define WAV_BUFFERS 64 #define WAV_MASK 0x3F #define WAV_BUFFER_SIZE 0x0400 extern cvar_t *s_wavonly; /* ================== FreeSound ================== */ void FreeSound( void ) { int i; Com_DPrintf( "Shutting down sound system\n" ); if ( hWaveOut ) { Com_DPrintf( "...resetting waveOut\n" ); waveOutReset( hWaveOut ); if ( lpWaveHdr ) { Com_DPrintf( "...unpreparing headers\n" ); for ( i = 0 ; i < WAV_BUFFERS ; i++ ) waveOutUnprepareHeader( hWaveOut, lpWaveHdr + i, sizeof( WAVEHDR ) ); } Com_DPrintf( "...closing waveOut\n" ); waveOutClose( hWaveOut ); if ( hWaveHdr ) { Com_DPrintf( "...freeing WAV header\n" ); GlobalUnlock( hWaveHdr ); GlobalFree( hWaveHdr ); } if ( hData ) { Com_DPrintf( "...freeing WAV buffer\n" ); GlobalUnlock( hData ); GlobalFree( hData ); } } hWaveOut = 0; hData = 0; hWaveHdr = 0; lpData = NULL; lpWaveHdr = NULL; wav_init = qfalse; } /* ================== SNDDM_InitWav Crappy windows multimedia base ================== */ qboolean SNDDMA_InitWav( void ) { WAVEFORMATEX format; int i; HRESULT hr; Com_Printf( "Initializing wave sound\n" ); snd_sent = 0; snd_completed = 0; dma.channels = 2; dma.samplebits = 16; if ( s_khz->value == 44 ) { dma.speed = 44100; } if ( s_khz->value == 22 ) { dma.speed = 22050; } else { dma.speed = 11025; } memset( &format, 0, sizeof( format ) ); format.wFormatTag = WAVE_FORMAT_PCM; format.nChannels = dma.channels; format.wBitsPerSample = dma.samplebits; format.nSamplesPerSec = dma.speed; format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; format.cbSize = 0; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; /* Open a waveform device for output using window callback. */ Com_DPrintf( "...opening waveform device: " ); while ( ( hr = waveOutOpen( (LPHWAVEOUT)&hWaveOut, WAVE_MAPPER, &format, 0, 0L, CALLBACK_NULL ) ) != MMSYSERR_NOERROR ) { if ( hr != MMSYSERR_ALLOCATED ) { Com_Printf( "failed\n" ); return qfalse; } if ( MessageBox( NULL, "The sound hardware is in use by another app.\n\n" "Select Retry to try to start sound again or Cancel to run Wolfenstein with no sound.", "Sound not available", MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION ) != IDRETRY ) { Com_Printf( "hw in use\n" ); return qfalse; } } Com_DPrintf( "ok\n" ); /* * Allocate and lock memory for the waveform data. The memory * for waveform data must be globally allocated with * GMEM_MOVEABLE and GMEM_SHARE flags. */ Com_DPrintf( "...allocating waveform buffer: " ); gSndBufSize = WAV_BUFFERS * WAV_BUFFER_SIZE; hData = GlobalAlloc( GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize ); if ( !hData ) { Com_Printf( " failed\n" ); FreeSound(); return qfalse; } Com_DPrintf( "ok\n" ); Com_DPrintf( "...locking waveform buffer: " ); lpData = GlobalLock( hData ); if ( !lpData ) { Com_Printf( " failed\n" ); FreeSound(); return qfalse; } memset( lpData, 0, gSndBufSize ); Com_DPrintf( "ok\n" ); /* * Allocate and lock memory for the header. This memory must * also be globally allocated with GMEM_MOVEABLE and * GMEM_SHARE flags. */ Com_DPrintf( "...allocating waveform header: " ); hWaveHdr = GlobalAlloc( GMEM_MOVEABLE | GMEM_SHARE, (DWORD) sizeof( WAVEHDR ) * WAV_BUFFERS ); if ( hWaveHdr == NULL ) { Com_Printf( "failed\n" ); FreeSound(); return qfalse; } Com_DPrintf( "ok\n" ); Com_DPrintf( "...locking waveform header: " ); lpWaveHdr = (LPWAVEHDR) GlobalLock( hWaveHdr ); if ( lpWaveHdr == NULL ) { Com_Printf( "failed\n" ); FreeSound(); return qfalse; } memset( lpWaveHdr, 0, sizeof( WAVEHDR ) * WAV_BUFFERS ); Com_DPrintf( "ok\n" ); /* After allocation, set up and prepare headers. */ Com_DPrintf( "...preparing headers: " ); for ( i = 0 ; i < WAV_BUFFERS ; i++ ) { lpWaveHdr[i].dwBufferLength = WAV_BUFFER_SIZE; lpWaveHdr[i].lpData = lpData + i * WAV_BUFFER_SIZE; if ( waveOutPrepareHeader( hWaveOut, lpWaveHdr + i, sizeof( WAVEHDR ) ) != MMSYSERR_NOERROR ) { Com_Printf( "failed\n" ); FreeSound(); return qfalse; } } Com_DPrintf( "ok\n" ); dma.samples = gSndBufSize / ( dma.samplebits / 8 ); dma.samplepos = 0; dma.submission_chunk = 512; dma.buffer = (unsigned char *) lpData; sample16 = ( dma.samplebits / 8 ) - 1; wav_init = qtrue; return qtrue; } /* ================== SNDDMA_Shutdown ================== */ void SNDDMA_Shutdown( void ) { Com_DPrintf( "Shutting down sound system\n" ); if ( wav_init ) { FreeSound(); return; wav_init = qfalse; } if ( pDS ) { Com_DPrintf( "Destroying DS buffers\n" ); if ( pDS ) { Com_DPrintf( "...setting NORMAL coop level\n" ); pDS->lpVtbl->SetCooperativeLevel( pDS, g_wv.hWnd, DSSCL_NORMAL ); } if ( pDSBuf ) { Com_DPrintf( "...stopping and releasing sound buffer\n" ); pDSBuf->lpVtbl->Stop( pDSBuf ); pDSBuf->lpVtbl->Release( pDSBuf ); } // only release primary buffer if it's not also the mixing buffer we just released if ( pDSPBuf && ( pDSBuf != pDSPBuf ) ) { Com_DPrintf( "...releasing primary buffer\n" ); pDSPBuf->lpVtbl->Release( pDSPBuf ); } pDSBuf = NULL; pDSPBuf = NULL; dma.buffer = NULL; Com_DPrintf( "...releasing DS object\n" ); pDS->lpVtbl->Release( pDS ); } if ( hInstDS ) { Com_DPrintf( "...freeing DSOUND.DLL\n" ); FreeLibrary( hInstDS ); hInstDS = NULL; } pDS = NULL; pDSBuf = NULL; pDSPBuf = NULL; dsound_init = qfalse; memset( (void *)&dma, 0, sizeof( dma ) ); } /* ================== SNDDMA_Init Initialize direct sound Returns false if failed ================== */ int SNDDMA_Init( void ) { memset( (void *)&dma, 0, sizeof( dma ) ); dsound_init = 0; if ( !SNDDMA_InitDS() ) { // DHM - Nerve :: If DS fails, try Wav snd_iswave = SNDDMA_InitWav(); if ( snd_iswave ) { Com_Printf( "Wave sound initialized successfully\n" ); } else { return 0; } } else { dsound_init = qtrue; Com_Printf( "DirectSound initialized successfully\n" ); } return 1; } int SNDDMA_InitDS() { HRESULT hresult; qboolean pauseTried; DSBUFFERDESC dsbuf; DSBCAPS dsbcaps; WAVEFORMATEX format; int clear; if ( s_wavonly->integer ) { snd_iswave = SNDDMA_InitWav(); if ( snd_iswave ) { Com_Printf( "Wave sound init succeeded\n" ); } return 1; } Com_Printf( "Initializing DirectSound\n" ); if ( !hInstDS ) { Com_DPrintf( "...loading dsound.dll: " ); hInstDS = LoadLibrary( "dsound.dll" ); if ( hInstDS == NULL ) { Com_Printf( "failed\n" ); return 0; } Com_DPrintf( "ok\n" ); pDirectSoundCreate = ( long ( __stdcall * )( struct _GUID *,struct IDirectSound **,struct IUnknown * ) ) GetProcAddress( hInstDS,"DirectSoundCreate" ); if ( !pDirectSoundCreate ) { Com_Printf( "*** couldn't get DS proc addr ***\n" ); return 0; } } Com_DPrintf( "...creating DS object: " ); pauseTried = qfalse; while ( ( hresult = iDirectSoundCreate( NULL, &pDS, NULL ) ) != DS_OK ) { if ( hresult != DSERR_ALLOCATED ) { Com_Printf( "failed\n" ); return 0; } if ( pauseTried ) { Com_Printf( "failed, hardware already in use\n" ); return 0; } // first try just waiting five seconds and trying again // this will handle the case of a sysyem beep playing when the // game starts Com_DPrintf( "retrying...\n" ); Sleep( 3000 ); pauseTried = qtrue; } Com_DPrintf( "ok\n" ); Com_DPrintf( "...setting DSSCL_NORMAL coop level: " ); if ( DS_OK != pDS->lpVtbl->SetCooperativeLevel( pDS, g_wv.hWnd, DSSCL_NORMAL ) ) { Com_Printf( "failed\n" ); SNDDMA_Shutdown(); return qfalse; } Com_DPrintf( "ok\n" ); // create the secondary buffer we'll actually work with dma.channels = 2; dma.samplebits = 16; if ( s_khz->integer == 44 ) { dma.speed = 44100; } else if ( s_khz->integer == 22 ) { dma.speed = 22050; } else { dma.speed = 11025; } memset( &format, 0, sizeof( format ) ); format.wFormatTag = WAVE_FORMAT_PCM; format.nChannels = dma.channels; format.wBitsPerSample = dma.samplebits; format.nSamplesPerSec = dma.speed; format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; format.cbSize = 0; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; memset( &dsbuf, 0, sizeof( dsbuf ) ); dsbuf.dwSize = sizeof( DSBUFFERDESC ); // Micah: take advantage of 2D hardware.if available. dsbuf.dwFlags = DSBCAPS_LOCHARDWARE; dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE; dsbuf.lpwfxFormat = &format; memset( &dsbcaps, 0, sizeof( dsbcaps ) ); dsbcaps.dwSize = sizeof( dsbcaps ); Com_DPrintf( "...creating secondary buffer: " ); if ( DS_OK == pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ) ) { Com_Printf( "locked hardware. ok\n" ); } else { // Couldn't get hardware, fallback to software. dsbuf.dwFlags = DSBCAPS_LOCSOFTWARE; if ( DS_OK != pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ) ) { Com_Printf( "failed\n" ); SNDDMA_Shutdown(); return qfalse; } Com_DPrintf( "forced to software. ok\n" ); } // Make sure mixer is active if ( DS_OK != pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING ) ) { Com_Printf( "*** Looped sound play failed ***\n" ); SNDDMA_Shutdown(); return qfalse; } // get the returned buffer size if ( DS_OK != pDSBuf->lpVtbl->GetCaps( pDSBuf, &dsbcaps ) ) { Com_Printf( "*** GetCaps failed ***\n" ); SNDDMA_Shutdown(); return qfalse; } gSndBufSize = dsbcaps.dwBufferBytes; dma.channels = format.nChannels; dma.samplebits = format.wBitsPerSample; dma.speed = format.nSamplesPerSec; dma.samples = gSndBufSize / ( dma.samplebits / 8 ); dma.submission_chunk = 1; dma.buffer = NULL; // must be locked first // DHM - Nerve :: Clear out the buffer in case it has garbage that would loop while menu loads SNDDMA_BeginPainting(); if ( dma.samplebits == 8 ) { clear = 0x80; } else { clear = 0; } if ( dma.buffer ) { Com_Memset( dma.buffer, clear, dma.samples * dma.samplebits / 8 ); } SNDDMA_Submit(); // dhm sample16 = ( dma.samplebits / 8 ) - 1; return 1; } /* ============== SNDDMA_GetDMAPos return the current sample position (in mono samples read) inside the recirculating dma buffer, so the mixing code will know how many sample are required to fill it up. =============== */ int SNDDMA_GetDMAPos( void ) { MMTIME mmtime; int s; DWORD dwWrite; if ( wav_init ) { s = snd_sent * WAV_BUFFER_SIZE; s >>= sample16; s &= ( dma.samples - 1 ); return s; } if ( !dsound_init || !pDSBuf ) { return 0; } mmtime.wType = TIME_SAMPLES; pDSBuf->lpVtbl->GetCurrentPosition( pDSBuf, &mmtime.u.sample, &dwWrite ); s = mmtime.u.sample; s >>= sample16; s &= ( dma.samples - 1 ); return s; } /* ============== SNDDMA_BeginPainting Makes sure dma.buffer is valid =============== */ void SNDDMA_BeginPainting( void ) { int reps; DWORD dwSize2; DWORD *pbuf, *pbuf2; HRESULT hresult; DWORD dwStatus; if ( !pDSBuf ) { return; } // if the buffer was lost or stopped, restore it and/or restart it if ( pDSBuf->lpVtbl->GetStatus( pDSBuf, &dwStatus ) != DS_OK ) { Com_Printf( "Couldn't get sound buffer status\n" ); } if ( dwStatus & DSBSTATUS_BUFFERLOST ) { pDSBuf->lpVtbl->Restore( pDSBuf ); } if ( !( dwStatus & DSBSTATUS_PLAYING ) ) { pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING ); } // lock the dsound buffer reps = 0; dma.buffer = NULL; while ( ( hresult = pDSBuf->lpVtbl->Lock( pDSBuf, 0, gSndBufSize, &pbuf, &locksize, &pbuf2, &dwSize2, 0 ) ) != DS_OK ) { if ( hresult != DSERR_BUFFERLOST ) { Com_Printf( "SNDDMA_BeginPainting: Lock failed with error '%s'\n", DSoundError( hresult ) ); S_Shutdown(); return; } else { pDSBuf->lpVtbl->Restore( pDSBuf ); } if ( ++reps > 2 ) { return; } } dma.buffer = (unsigned char *)pbuf; } /* ============== SNDDMA_Submit Send sound to device if buffer isn't really the dma buffer Also unlocks the dsound buffer =============== */ void SNDDMA_Submit( void ) { LPWAVEHDR h; int wResult; // unlock the dsound buffer if ( pDSBuf ) { pDSBuf->lpVtbl->Unlock( pDSBuf, dma.buffer, locksize, NULL, 0 ); } if ( !wav_init ) { return; } // // find which sound blocks have completed // while ( 1 ) { if ( snd_completed == snd_sent ) { Com_DPrintf( "Sound overrun\n" ); break; } if ( !( lpWaveHdr[ snd_completed & WAV_MASK].dwFlags & WHDR_DONE ) ) { break; } snd_completed++; // this buffer has been played } //Com_Printf ("completed %i\n", snd_completed); // // submit a few new sound blocks // while ( ( ( snd_sent - snd_completed ) >> sample16 ) < 8 ) { h = lpWaveHdr + ( snd_sent & WAV_MASK ); if ( s_paintedtime / 256 <= snd_sent ) { break; // Com_Printf ("submit overrun\n"); } //Com_Printf ("send %i\n", snd_sent); snd_sent++; /* * Now the data block can be sent to the output device. The * waveOutWrite function returns immediately and waveform * data is sent to the output device in the background. */ wResult = waveOutWrite( hWaveOut, h, sizeof( WAVEHDR ) ); if ( wResult != MMSYSERR_NOERROR ) { Com_Printf( "Failed to write block to device\n" ); FreeSound(); return; } } } /* ================= SNDDMA_Activate When we change windows we need to do this ================= */ void SNDDMA_Activate( void ) { if ( !pDS ) { return; } if ( DS_OK != pDS->lpVtbl->SetCooperativeLevel( pDS, g_wv.hWnd, DSSCL_PRIORITY ) ) { Com_Printf( "sound SetCooperativeLevel failed\n" ); SNDDMA_Shutdown(); } }