//-----------------------------------------------------------------------------
//
//  $Logfile:: /win32/snd_win.c                                               $
// $Revision:: 1                                                              $
//   $Author:: Burger                                                         $
//     $Date:: 12/27/00 7:57p                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// $Log:: /win32/snd_win.c                                                    $
// *
// * 1     12/27/00 7:57p Burger
//
// 20    2/09/99 3:26p Markd
// Added A3D 2.0 support
//
// 19    2/02/99 6:04p Markd
// fixed some EAX initialization stuff
//
// 18    1/22/99 3:18p Markd
// Added mixlevel and environement static variables for eax unnecessary
// changing
//
// 17    1/07/99 6:45p Markd
// Added EAX stuff
//
// 16    11/18/98 7:49p Markd
// Took out some debuggin messages actually made them debugging
//
// 15    11/16/98 8:59p Markd
// took out A3D shutdown stuff for sound when window focus is lost
//
// 14    11/08/98 11:41p Markd
// turn off 3d sound hardware by default
//
// 13    10/23/98 6:15p Markd
// merged a3d stuff
//
// 12    8/27/98 8:11p Markd
// Added s_use3dSound option
//
// 11    8/24/98 6:59p Markd
// Integrated A3D code
//
// 10    8/02/98 9:01p Markd
// Merged code 3.17
//
// 9     7/31/98 10:51a Markd
// fixed bug with sample buffer size
//
// 8     7/29/98 7:06p Markd
// fixed header include for a3d stuff
//
// 7     7/29/98 6:35p Markd
// Integrated A3D stuff
//
// 5     3/17/98 2:04p Markd
// Added Sin Music stuff
//
// 4     1/24/98 5:27p Jimdose
// Eradicated runtime references to Quake
//
// 3     12/30/97 6:06p Jimdose
// Added header text
//
// DESCRIPTION:
//

#include <float.h>
#include "client.h"
#include "snd_loc.h"

#define CINTERFACE 1
#define DIRECTSOUND_VERSION 0x0500
#include <dsound.h>
#include "winquake.h"

#ifdef __USEA3D
#include "../a3d/sin_a3d.h"
#include "../a3d/sin_a3dg.h"
#include "../a3d/a3dwrapperDP.h"

extern int sound_started;             // defined in snd_dma.c

#endif

#define iDirectSoundCreate( a,b,c )   pDirectSoundCreate( a,b,c )

HRESULT ( WINAPI *pDirectSoundCreate )( GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter );

// 64K is > 1 second at 16-bit, 22050 Hz
#define WAV_BUFFERS             64
#define WAV_MASK                0x3F
#define WAV_BUFFER_SIZE         0x0400
#define SECONDARY_BUFFER_SIZE   0x10000

#ifdef __USEA3D
cvar_t  *s_use3dsound;

typedef enum {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL, SIS_FAKEA3D} sndinitstat;
#else
typedef enum {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat;
#endif

cvar_t  *s_wavonly;

static qboolean dsound_init;
static qboolean wav_init;
static qboolean snd_firsttime = true, snd_isdirect, snd_iswave;
static qboolean primary_format_set;

// starts at 0 for disabled
static int snd_buffer_count = 0;
static int sample16;
static int snd_sent, snd_completed;

/*
 * Global variables. Must be visible to window-procedure function
 *  so it can unlock and free the data block after it has been played.
 */


HANDLE hData;
HPSTR lpData, lpData2;

HGLOBAL hWaveHdr;
LPWAVEHDR lpWaveHdr;

HWAVEOUT hWaveOut;

WAVEOUTCAPS wavecaps;

DWORD gSndBufSize;

MMTIME mmstarttime;

LPDIRECTSOUND pDS;
LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf;

HINSTANCE hInstDS;

sndinitstat SNDDMA_InitDirect( void );
qboolean SNDDMA_InitWav( void );

#ifdef __USEA3D
sndinitstat SNDDMA_InitA3D( void );
#endif

/////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _ADD_EAX_

#include "../client/eax.h"

GUID IID_IKsPropertySetDef = {0x31efac30, 0x515c, 0x11d0, {0xa9, 0xaa, 0x00, 0xaa, 0x00, 0x61, 0xbe, 0x93}};
GUID DSPROPSETID_EAX_ReverbPropertiesDef = {0x4a4e6fc1, 0xc341, 0x11d1, {0xb7, 0x3a, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}};
GUID IID_IDirectSound3DBufferDef = {0x279AFA86, 0x4981, 0x11CE, {0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60}};
GUID DSPROPSETID_EAXBUFFER_ReverbPropertiesDef = {0x4a4e6fc0, 0xc341, 0x11d1, {0xb7, 0x3a, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}};

LPDIRECTSOUNDBUFFER pDSBufFL = NULL;
LPDIRECTSOUNDBUFFER pDSBufFR = NULL;
LPDIRECTSOUNDBUFFER pDSBufRL = NULL;
LPDIRECTSOUNDBUFFER pDSBufRR = NULL;
LPDIRECTSOUND3DBUFFER pDSBuf3DFL = NULL;
LPDIRECTSOUND3DBUFFER pDSBuf3DFR = NULL;
LPDIRECTSOUND3DBUFFER pDSBuf3DRL = NULL;
LPDIRECTSOUND3DBUFFER pDSBuf3DRR = NULL;
LPKSPROPERTYSET pDSPropSetFL = NULL;
LPKSPROPERTYSET pDSPropSetFR = NULL;
LPKSPROPERTYSET pDSPropSetRL = NULL;
LPKSPROPERTYSET pDSPropSetRR = NULL;
qboolean EAX_format_set;
DWORD g_dwEAXStart = 0;
DWORD *g_pdwWriteFL = NULL, *g_pdwWriteFR = NULL, *g_pdwWriteRL = NULL, *g_pdwWriteRR = NULL;
DWORD g_dwSizeFL, g_dwSizeFR, g_dwSizeRL, g_dwSizeRR;
#define MAX_LOCK_ATTEMPTS 10
EAXPRESET eax_preset[TOTAL_PRESETS] = {

	{EAX_ENVIRONMENT_GENERIC,0.0F,0.0F,0.0F},// Use for reverb off.
	{EAX_ENVIRONMENT_GENERIC,0.5F,1.493F,0.5F},
	{EAX_ENVIRONMENT_PADDEDCELL,0.25F,0.1F,0.0F},
	{EAX_ENVIRONMENT_ROOM,0.417F,0.4F,0.666F},
	{EAX_ENVIRONMENT_BATHROOM,0.653F,1.499F,0.166F},
	{EAX_ENVIRONMENT_LIVINGROOM,0.208F,0.478F,0.0F},
	{EAX_ENVIRONMENT_STONEROOM,0.5F,2.309F,0.888F},
	{EAX_ENVIRONMENT_AUDITORIUM,0.403F,4.279F,0.5F},
	{EAX_ENVIRONMENT_CONCERTHALL,0.5F,3.961F,0.5F},
	{EAX_ENVIRONMENT_CAVE,0.5F,2.886F,1.304F},
	{EAX_ENVIRONMENT_ARENA,0.361F,7.284F,0.332F},
	{EAX_ENVIRONMENT_HANGAR,0.5F,10.0F,0.3F},
	{EAX_ENVIRONMENT_CARPETEDHALLWAY,0.153F,0.259F,2.0F},
	{EAX_ENVIRONMENT_HALLWAY,0.361F,1.493F,0.0F},
	{EAX_ENVIRONMENT_STONECORRIDOR,0.444F,2.697F,0.638F},
	{EAX_ENVIRONMENT_ALLEY,0.25F,1.752F,0.776F},
	{EAX_ENVIRONMENT_FOREST,0.111F,3.145F,0.472F},
	{EAX_ENVIRONMENT_CITY,0.111F,2.767F,0.224F},
	{EAX_ENVIRONMENT_MOUNTAINS,0.194F,7.841F,0.472F},
	{EAX_ENVIRONMENT_QUARRY,1.0F,1.499F,0.5F},
	{EAX_ENVIRONMENT_PLAIN,0.097F,2.767F,0.224F},
	{EAX_ENVIRONMENT_PARKINGLOT,0.208F,1.652F,1.5F},
	{EAX_ENVIRONMENT_SEWERPIPE,0.652F,2.886F,0.25F},
	{EAX_ENVIRONMENT_UNDERWATER,1.0F,1.499F,0.0F},
	{EAX_ENVIRONMENT_DRUGGED,0.875F,8.392F,1.388F},
	{EAX_ENVIRONMENT_DIZZY,0.139F,17.234F,0.666F},
	{EAX_ENVIRONMENT_PSYCHOTIC,0.486F,7.563F,0.806F},

};
int g_iCurrentEaxPreset = 0;
float g_fCurrentEaxLevel = 0.0f;

cvar_t  *s_checkeax;
cvar_t  *s_eax_reverblevel;

#endif
/////////////////////////////////////////////////////////////////////////////////////////////


void FreeSound( void );

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";
}

/*
** DS_CreateBuffers
*/
static qboolean DS_CreateBuffers( void )
{
	DSBUFFERDESC	dsbuf;
	DSBCAPS			dsbcaps;
	WAVEFORMATEX	pformat, format;
	DWORD			dwWrite;

	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;

	Com_Printf( "Creating DS buffers\n" );

	Com_DPrintf(DEVELOPER_MSG_SOUND, "...setting EXCLUSIVE coop level: ");
	if (DS_OK != IDirectSound_SetCooperativeLevel( pDS, cl_hwnd, DSSCL_EXCLUSIVE ))
	{
		Com_Printf ("failed\n");
		FreeSound ();
		return false;
	}
	Com_DPrintf(DEVELOPER_MSG_SOUND, "ok\n");

// get access to the primary buffer, if possible, so we can set the
// sound hardware format
	memset (&dsbuf, 0, sizeof(dsbuf));
	dsbuf.dwSize = sizeof(DSBUFFERDESC);
	dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
	dsbuf.dwBufferBytes = 0;
	dsbuf.lpwfxFormat = NULL;

	memset(&dsbcaps, 0, sizeof(dsbcaps));
	dsbcaps.dwSize = sizeof(dsbcaps);
	primary_format_set = false;

	Com_DPrintf(DEVELOPER_MSG_SOUND, "...creating primary buffer: ");
	if (DS_OK == IDirectSound_CreateSoundBuffer( pDS, &dsbuf, &pDSPBuf, NULL ))
	{
		pformat = format;

		Com_DPrintf(DEVELOPER_MSG_SOUND, "ok\n");
		if (DS_OK != IDirectSoundBuffer_SetFormat ( pDSPBuf, &pformat ))
		{
			if (snd_firsttime)
				Com_DPrintf (DEVELOPER_MSG_SOUND, "...setting primary sound format: failed\n");
		}
		else
		{
			if (snd_firsttime)
				Com_DPrintf (DEVELOPER_MSG_SOUND, "...setting primary sound format: ok\n");

			primary_format_set = true;
		}
	}
	else
		Com_Printf("failed\n");

//////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _ADD_EAX_
	EAX_format_set = false;

#define _USEEAX
#ifdef _USEEAX
	if ( s_checkeax->value )
		EAX_format_set = SNDEAX_Init( pDS, &format, &dsbcaps );
#endif

	if ( !EAX_format_set )
	{
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////////
	if ( !primary_format_set || !s_primary->value)
	{
		// create the secondary buffer we'll actually work with
		memset ( &dsbuf, 0, sizeof(dsbuf));
		dsbuf.dwSize = sizeof(DSBUFFERDESC);
		dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE;
		dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
		dsbuf.lpwfxFormat = &format;

		memset(&dsbcaps, 0, sizeof(dsbcaps));
		dsbcaps.dwSize = sizeof(dsbcaps);

		Com_DPrintf(DEVELOPER_MSG_SOUND, "...creating secondary buffer: ");
		if (DS_OK != IDirectSound_CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ))
		{
			Com_Printf( "failed\n" );
			FreeSound ();
			return false;
		}
		Com_DPrintf(DEVELOPER_MSG_SOUND, "ok\n");

		dma.channels = format.nChannels;
		dma.samplebits = format.wBitsPerSample;
		dma.speed = format.nSamplesPerSec;

		if (DS_OK != IDirectSoundBuffer_GetCaps ( pDSBuf, &dsbcaps ))
		{
			Com_Printf ("*** GetCaps failed ***\n");
			FreeSound ();
			return false;
		}

		if (snd_firsttime)
			Com_DPrintf (DEVELOPER_MSG_SOUND, "...using secondary sound buffer\n");
	}
	else
	{
		Com_DPrintf(DEVELOPER_MSG_SOUND, "...using primary buffer\n");

		Com_DPrintf(DEVELOPER_MSG_SOUND, "...setting WRITEPRIMARY coop level: ");
		if (DS_OK != IDirectSound_SetCooperativeLevel ( pDS, cl_hwnd, DSSCL_WRITEPRIMARY ))
		{
			Com_Printf( "failed\n");
			FreeSound ();
			return false;
		}
		Com_DPrintf(DEVELOPER_MSG_SOUND, "ok\n");

		if (DS_OK != IDirectSoundBuffer_GetCaps ( pDSPBuf, &dsbcaps ))
		{
			Com_Printf ( "*** GetCaps failed ***\n" );
			return false;
		}

		pDSBuf = pDSPBuf;
	}

	// Make sure mixer is active
	IDirectSoundBuffer_Play( pDSBuf, 0, 0, DSBPLAY_LOOPING );

	if (snd_firsttime)
		Com_Printf( "   %d channel(s)\n"
		            "   %d bits/sample\n"
		            "   %d bytes/sec\n",
		            dma.channels, dma.samplebits, dma.speed );

	gSndBufSize = dsbcaps.dwBufferBytes;

	/* we don't want anyone to access the buffer directly w/o locking it first. */
	lpData = NULL;

	IDirectSoundBuffer_Stop( pDSBuf );
	IDirectSoundBuffer_GetCurrentPosition( pDSBuf, &mmstarttime.u.sample, &dwWrite );
	IDirectSoundBuffer_Play( pDSBuf, 0, 0, DSBPLAY_LOOPING );

//////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _ADD_EAX_
}
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////////

	dma.samples = gSndBufSize/(dma.samplebits/8);
	dma.samplepos = 0;
	dma.submission_chunk = 1;
	dma.buffer = (unsigned char *) lpData;
	sample16 = (dma.samplebits/8) - 1;

	return true;
}

/*
** DS_DestroyBuffers
*/
static void DS_DestroyBuffers( void )
{
	Com_DPrintf(DEVELOPER_MSG_SOUND, "Destroying DS buffers\n");
	if ( pDS )
	{
		Com_DPrintf(DEVELOPER_MSG_SOUND, "...setting NORMAL coop level\n");
		IDirectSound_SetCooperativeLevel( pDS, cl_hwnd, DSSCL_NORMAL );
	}

/////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _ADD_EAX_
	SNDEAX_Release();
#endif
/////////////////////////////////////////////////////////////////////////////////////////////

	if ( pDSBuf )
	{
		Com_DPrintf(DEVELOPER_MSG_SOUND, "...stopping and releasing sound buffer\n");
		IDirectSoundBuffer_Stop( pDSBuf );
		IDirectSoundBuffer_Release( pDSBuf );
	}

	// only release primary buffer if it's not also the mixing buffer we just released
	if ( pDSPBuf && ( pDSBuf != pDSPBuf ) )
	{
		Com_DPrintf(DEVELOPER_MSG_SOUND, "...releasing primary buffer\n");
		IDirectSoundBuffer_Release( pDSPBuf );
	}
	pDSBuf = NULL;
	pDSPBuf = NULL;

	dma.buffer = NULL;
}

/*
==================
FreeSound
==================
*/
void FreeSound (void)
{
	int		i;

	Com_DPrintf(DEVELOPER_MSG_SOUND, "Shutting down sound system\n");

	if ( pDS )
		DS_DestroyBuffers();

	if ( hWaveOut )
	{
		Com_DPrintf(DEVELOPER_MSG_SOUND, "...resetting waveOut\n");
		waveOutReset ( hWaveOut );

		if (lpWaveHdr)
		{
			Com_DPrintf(DEVELOPER_MSG_SOUND, "...unpreparing headers\n");
			for (i=0; i< WAV_BUFFERS; i++)
				waveOutUnprepareHeader ( hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR));
		}

		Com_DPrintf(DEVELOPER_MSG_SOUND, "...closing waveOut\n");
		waveOutClose ( hWaveOut );

		if (hWaveHdr)
		{
			Com_DPrintf(DEVELOPER_MSG_SOUND, "...freeing WAV header\n");
			GlobalUnlock( hWaveHdr );
			GlobalFree( hWaveHdr );
		}

		if (hData)
		{
			Com_DPrintf(DEVELOPER_MSG_SOUND, "...freeing WAV buffer\n");
			GlobalUnlock( hData );
			GlobalFree( hData );
		}

	}

	if ( pDS )
	{
		Com_DPrintf(DEVELOPER_MSG_SOUND, "...releasing DS object\n");
		IDirectSound_Release( pDS );
	}

	if ( hInstDS )
	{
		Com_DPrintf(DEVELOPER_MSG_SOUND, "...freeing DSOUND.DLL\n");
		FreeLibrary( hInstDS );
		hInstDS = NULL;
	}

	pDS = NULL;
	pDSBuf = NULL;
	pDSPBuf = NULL;
	hWaveOut = 0;
	hData = 0;
	hWaveHdr = 0;
	lpData = NULL;
	lpWaveHdr = NULL;
	dsound_init = false;
	wav_init = false;
}

/*
==================
SNDDMA_InitDirect

Direct-Sound support
==================
*/
sndinitstat SNDDMA_InitDirect (void)
{
	DSCAPS		dscaps;
	HRESULT		hresult;

	dma.channels = 2;
	dma.samplebits = 16;

	if (s_khz->value == 96)
		dma.speed = 96000;
	else if (s_khz->value == 48)
		dma.speed = 48000;
	else if (s_khz->value == 8)
		dma.speed = 8000;
	else if (s_khz->value == 44)
		dma.speed = 44100;
	else if (s_khz->value == 22)
		dma.speed = 22050;
	else if (s_khz->value == 11)
		dma.speed = 11025;
	else if (s_khz->value == 33)
		dma.speed = 33075;
	else
		dma.speed = s_khz->value * 1002.2727f;
	if (!dma.speed)
		dma.speed = 11025;

	Com_Printf( "Initializing DirectSound\n");

	if ( !hInstDS )
	{
		Com_DPrintf(DEVELOPER_MSG_SOUND, "...loading dsound.dll: ");

		hInstDS = LoadLibrary("dsound.dll");
		
		if (hInstDS == NULL)
		{
			Com_Printf ("failed\n");
			return SIS_FAILURE;
		}

		Com_DPrintf (DEVELOPER_MSG_SOUND, "ok\n");
		pDirectSoundCreate = (HRESULT (WINAPI *)(GUID FAR *, LPDIRECTSOUND FAR *, IUnknown FAR *)) GetProcAddress(hInstDS,"DirectSoundCreate");
		if (!pDirectSoundCreate)
		{
			Com_Printf ("*** couldn't get DS proc addr ***\n");
			return SIS_FAILURE;
		}
	}

	Com_DPrintf(DEVELOPER_MSG_SOUND, "...creating DS object: ");
	while ( ( hresult = iDirectSoundCreate( NULL, &pDS, NULL ) ) != DS_OK )
	{
		if (hresult != DSERR_ALLOCATED)
		{
			Com_Printf( "failed\n" );
			return SIS_FAILURE;
		}

		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 " GAME_NAME " with no sound.",
		                 "Sound not available",
		                 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION ) != IDRETRY)
		{
			Com_Printf ("failed, hardware already in use\n" );
			return SIS_NOTAVAIL;
		}
	}
	Com_DPrintf(DEVELOPER_MSG_SOUND, "ok\n");

	dscaps.dwSize = sizeof(dscaps);

	if ( DS_OK != IDirectSound_GetCaps( pDS, &dscaps ))
	{
		Com_Printf ("*** couldn't get DS caps ***\n");
	}

	if ( dscaps.dwFlags & DSCAPS_EMULDRIVER )
	{
		Com_DPrintf (DEVELOPER_MSG_SOUND, "...no DSound driver found\n");
		FreeSound();
		return SIS_FAILURE;
	}

	if ( !DS_CreateBuffers() )
		return SIS_FAILURE;

	dsound_init = true;

	Com_DPrintf(DEVELOPER_MSG_SOUND, "...completed successfully\n");

	return SIS_SUCCESS;
}


/*
==================
SNDDMA_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 == 96)
		dma.speed = 96000;
	else if (s_khz->value == 48)
		dma.speed = 48000;
	else if (s_khz->value == 8)
		dma.speed = 8000;
	else if (s_khz->value == 44)
		dma.speed = 44100;
	else if (s_khz->value == 22)
		dma.speed = 22050;
	else if (s_khz->value == 11)
		dma.speed = 11025;
	else if (s_khz->value == 33)
		dma.speed = 33075;
	else
		dma.speed = s_khz->value * 1002.2727f;
	if (!dma.speed)
		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 (DEVELOPER_MSG_SOUND, "...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 false;
		}

		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 " GAME_NAME " with no sound.",
		                 "Sound not available",
		                 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION ) != IDRETRY)
		{
			Com_Printf ( "hw in use\n" );
			return false;
		}
	}
	Com_DPrintf(DEVELOPER_MSG_SOUND, "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 (DEVELOPER_MSG_SOUND, "...allocating waveform buffer: ");
	gSndBufSize = WAV_BUFFERS*WAV_BUFFER_SIZE;
	hData = GlobalAlloc( GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize );
	if (!hData)
	{
		Com_Printf( " failed\n" );
		FreeSound ();
		return false;
	}
	Com_DPrintf(DEVELOPER_MSG_SOUND, "ok\n" );

	Com_DPrintf (DEVELOPER_MSG_SOUND, "...locking waveform buffer: ");
	lpData = (HPSTR)GlobalLock( hData );
	if (!lpData)
	{
		Com_Printf( " failed\n" );
		FreeSound ();
		return false;
	}
	memset ( lpData, 0, gSndBufSize );
	Com_DPrintf(DEVELOPER_MSG_SOUND, "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 (DEVELOPER_MSG_SOUND, "...allocating waveform header: ");
	hWaveHdr = GlobalAlloc( GMEM_MOVEABLE | GMEM_SHARE,
	                        (DWORD) sizeof(WAVEHDR) * WAV_BUFFERS );

	if (hWaveHdr == NULL)
	{
		Com_Printf( "failed\n" );
		FreeSound ();
		return false;
	}
	Com_DPrintf(DEVELOPER_MSG_SOUND, "ok\n");

	Com_DPrintf (DEVELOPER_MSG_SOUND, "...locking waveform header: ");
	lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr);

	if (lpWaveHdr == NULL)
	{
		Com_Printf( "failed\n" );
		FreeSound ();
		return false;
	}
	memset (lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS);
	Com_DPrintf(DEVELOPER_MSG_SOUND, "ok\n");

	/* After allocation, set up and prepare headers. */
	Com_DPrintf (DEVELOPER_MSG_SOUND, "...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 false;
		}
	}
	Com_DPrintf (DEVELOPER_MSG_SOUND, "ok\n");

	gSndBufSize = SECONDARY_BUFFER_SIZE;
	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 = true;

	return true;
}

/*
==================
SNDDMA_Init

Try to find a sound device to mix for.
Returns false if nothing is found.
==================
*/
qboolean SNDDMA_Init(void)
{
	sndinitstat	stat;

	memset ((void *)&dma, 0, sizeof (dma));

	s_wavonly = Cvar_Get ("s_wavonly", "0", 0);

	dsound_init = wav_init = 0;

	stat = SIS_FAILURE; // assume DirectSound won't initialize

#ifdef _ADD_EAX_
	s_checkeax = Cvar_Get ( "s_checkeax", "1", CVAR_ARCHIVE );
	s_eax_reverblevel = Cvar_Get ( "s_eax_reverblevel", "0.4f", CVAR_ARCHIVE );
#endif

#ifdef __USEA3D
	s_use3dsound = Cvar_Get ( "s_use3dsound", "1", CVAR_ARCHIVE );
	if (!s_wavonly->value && s_use3dsound->value)
	{
		// MM - 7/2/98
		stat = SNDDMA_InitA3D ();

		if (stat == SIS_SUCCESS)
		{
			Com_DPrintf( "Aureal A3D initialized\n" );

			snd_isa3d = true;
			snd_isdirect = false;
		}
		else if (stat == SIS_FAKEA3D)
		{
			// Preferably, this will have a GUI event associated with it to inform
			// the user that they have an invalid A3D.dll
			Com_DPrintf( "Non-Aureal A3D.dll found.  This is an unsupported version of A3D.\nDropping to Direct Sound\n" );
			snd_isa3d = false;
		}
		else
		{
			Com_DPrintf( "Aureal A3D initialization failed.  Dropping to Direct Sound\n" );
			snd_isa3d = false;
		}
	}
	else
	{
		snd_isa3d = false;
	}

	// If A3D failed to initialize, drop to ds.
	if (!snd_isa3d)
	{
#endif

	/* Init DirectSound */
	if (!s_wavonly->value)
	{
		if (snd_firsttime || snd_isdirect)
		{
			stat = SNDDMA_InitDirect ();

			if (stat == SIS_SUCCESS)
			{
				snd_isdirect = true;

				if (snd_firsttime)
					Com_Printf ("dsound init succeeded\n" );
			}
			else
			{
				snd_isdirect = false;
				Com_Printf ("*** dsound init failed ***\n");
			}
		}
	}

// if DirectSound didn't succeed in initializing, try to initialize
// waveOut sound, unless DirectSound failed because the hardware is
// already allocated (in which case the user has already chosen not
// to have sound)
	if (!dsound_init && (stat != SIS_NOTAVAIL))
	{
		if (snd_firsttime || snd_iswave)
		{

			snd_iswave = SNDDMA_InitWav ();

			if (snd_iswave)
			{
				if (snd_firsttime)
					Com_Printf ("Wave sound init succeeded\n");
			}
			else
			{
				Com_Printf ("Wave sound init failed\n");
			}
		}
	}

#ifdef __USEA3D
	}
#endif

	snd_firsttime = false;

	snd_buffer_count = 1;

#ifdef __USEA3D
	if (!snd_isa3d && !dsound_init && !wav_init)
#else
	if (!dsound_init && !wav_init)
#endif
	{
		if (snd_firsttime)
			Com_Printf ( "*** No sound device initialized ***\n" );

		return false;
	}

	return true;
}

/*
==============
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;

#ifdef __USEA3D
	if ( snd_isa3d )
	{
		if (A3D_IsMixBuffer())
		{
			A3D_GetMixBufferPos ( &mmtime.u.sample, &dwWrite );
			s = mmtime.u.sample - mmstarttime.u.sample;
		}
	}
	else
#endif

//////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _ADD_EAX_
	if(EAX_format_set) return SNDEAX_GetPos();
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////////

	if (dsound_init)
	{
		mmtime.wType = TIME_SAMPLES;
		IDirectSoundBuffer_GetCurrentPosition( pDSBuf, &mmtime.u.sample, &dwWrite );
		s = mmtime.u.sample - mmstarttime.u.sample;
	}
	else if (wav_init)
	{
		s = snd_sent * WAV_BUFFER_SIZE;
	}
	else
	{
		return 0;
	}

	s >>= sample16;
	s &= (dma.samples-1);

	return s;
}

/*
==============
SNDDMA_BeginPainting

Makes sure dma.buffer is valid
===============
*/
DWORD	locksize;
void SNDDMA_BeginPainting (void)
{
	int reps;
	DWORD dwSize2;
	void *pbuf, *pbuf2;
	HRESULT hresult;
	DWORD dwStatus;


#ifdef __USEA3D
	if (snd_isa3d)
	{
		if (gpclRolloff->modified || gpclDoppler->modified || gpclDistance->modified) {
			gpclRolloff->modified = gpclDoppler->modified = gpclDistance->modified = 0;
			A3D_SetGlobals( gpclDoppler->value, gpclDistance->value, gpclRolloff->value );
		}

		if (gpclMin->modified || gpclMax->modified) {
			hA3D_SetMinMax ( gpclMin->value, gpclMax->value );
			gpclMin->modified = gpclMax->modified = 0;
		}

		if (gpcl2DVolume->modified) {
			gpcl2DVolume->modified = 0;
			A3D_SetMixBufferVolume ( gpcl2DVolume->value );
		}

		if (gpclWaterEq->modified) {
			gpclWaterEq->modified = false;
			hA3D_SetWaterEQ ( gpclWaterEq->value );
		}

#ifdef __A3D_GEOM
		if (gpclNumLeaf->modified) {
			gpclNumLeaf->modified = 0;
			hA3Dg_AdjustLeavesRendered ( gpclNumLeaf->value );
		}

		if (gpclRefGain->modified) {
			gpclRefGain->modified = 0;
			hA3Dg_AdjustRefGain ( gpclRefGain->value );
		}

		if (gpclRefDelay->modified) {
			gpclRefDelay->modified = 0;
			hA3Dg_AdjustRefDelay ( gpclRefDelay->value );
		}

		if (gpclPolyKeep->modified) {
			gpclPolyKeep->modified = 0;
			hA3Dg_AdjustPolyKeep ( gpclPolyKeep->value );
		}

		if (gpclPolySize->modified) {
			gpclPolySize->modified = 0;
			hA3Dg_AdjustPolySize ( gpclPolySize->value );
		}

		if (gpclPolyReflectSize->modified) {
			gpclPolyReflectSize->modified = false;
			hA3Dg_AdjustPolyReflectSize ( gpclPolyReflectSize->value );
		}

		if (gpclNumPolys->modified) {
			gpclNumPolys->modified = 0;
			hA3Dg_AdjustNumPolys ( gpclNumPolys->value );
		}

		if (gpclShowTossed->modified) {
			gpclShowTossed->modified = 0;
			hA3Dg_AdjustShowTossed ( gpclShowTossed->value );
		}

		if (gpclUsePVS->modified) {
			gpclUsePVS->modified = 0;
			hA3Dg_AdjustUsePVS ( gpclUsePVS->value );
		}

		if (gpclBloat->modified = 0) {
			gpclBloat->modified = 0;
			hA3Dg_AdjustBloatFactor ( gpclBloat->value );
		}

		if (gpclReflect->modified) {
			gpclReflect->modified = 0;
			hA3Dg_AdjustSetReflection( gpclReflect->value );
		}

		if (gpclOcclude->modified) {
			gpclOcclude->modified = 0;
			hA3Dg_AdjustSetOcclusion( gpclOcclude->value );
		}

		if (gpclOccFact->modified) {
			gpclOccFact->modified = false;
			hA3Dg_AdjustTransmittance ( gpclOccFact->value );
		}

		if (gpclOccEpsilon->modified) {
			gpclOccEpsilon->modified = 0;
			hA3Dg_AdjustOcclusionEpsilon ( gpclOccEpsilon->value );
		}
#endif // __A3D_GEOM

		// Can't lock if we have no buffer.  This occurs while the
		// main window is deactivated.
		if (!A3D_IsMixBuffer())
		{
			dma.buffer = NULL;
			return;
		}

		A3D_Clear();

		hresult = A3D_LockMixBuffer ( &pbuf, &locksize );
		if (FAILED( hresult ))
		{
			Com_Printf( "SNDDMA_BeginPainting: Lock failed with error '%s'\n", DSoundError( hresult ) );
			dma.buffer = NULL;
			S_Shutdown ();
			return;
		}

		dma.buffer = (unsigned char *)pbuf;
		return;
	}
#endif

	if (!pDSBuf)
		return;

	// if the buffer was lost or stopped, restore it and/or restart it
	if (IDirectSoundBuffer_GetStatus ( pDSBuf, &dwStatus ) != DS_OK)
		Com_Printf ( "Couldn't get sound buffer status\n" );

	if (dwStatus & DSBSTATUS_BUFFERLOST)
		IDirectSoundBuffer_Restore ( pDSBuf );

	if (!(dwStatus & DSBSTATUS_PLAYING))
		IDirectSoundBuffer_Play( pDSBuf, 0, 0, DSBPLAY_LOOPING );

	// lock the dsound buffer
	reps = 0;
	dma.buffer = NULL;

	while ((hresult = IDirectSoundBuffer_Lock( pDSBuf, 0, gSndBufSize, &pbuf, &locksize,
	                                           &pbuf2, &dwSize2, 0 )) != DS_OK)
	{
		if (hresult != DSERR_BUFFERLOST)
		{
			Com_Printf( "S_TransferStereo16: Lock failed with error '%s'\n", DSoundError( hresult ) );
			S_Shutdown ();
			return;
		}
		else
		{
			IDirectSoundBuffer_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;

#ifdef __USEA3D
	if (snd_isa3d)
	{
		if (A3D_IsMixBuffer() && dma.buffer)
			A3D_UnlockMixBuffer ( dma.buffer, locksize );

		// Send Geometry
#ifdef __A3D_GEOM
		if (gpclGeometry->value != 0)
			hA3Dg_RenderGeometry ();
#endif

		// In order to properly play underwater effects,
		// the following line needs to be updated to
		// return true when Blade is underwater.
		//
		// This is currently done in "cl_pred.c"
		/*
			if (pm && pm->waterlevel > 2)
				hA3D_SetEnvironment (A3D_ENV_WATER);
			else
				hA3D_SetEnvironment (A3D_ENV_AIR);
		*/
		A3D_Flush();

		// Set it to playing.  Don't worry about redundant calls.  The wrapper checks.
		A3D_PlayMixBuffer ();

		return;
	}
#endif

	if (!dma.buffer)
		return;

	// unlock the dsound buffer
	if (pDSBuf)
		IDirectSoundBuffer_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 (DEVELOPER_MSG_SOUND, "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 (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_Shutdown

Reset the sound device for exiting
===============
*/
void SNDDMA_Shutdown( void )
{
#ifdef __USEA3D
	if (snd_isa3d)
	{
		Com_Printf ( "Shutting down A3D..." );

		hA3D_Shutdown( cl_hwnd );
		snd_isa3d = false;
		Com_Printf ( "ok\n" );
		return;
	}
#endif

	FreeSound ();
}


/*
===========
S_Activate

Called when the main window gains or loses focus.
The window have been destroyed and recreated
between a deactivate and an activate.
===========
*/
void S_Activate( qboolean active )
{
	if ( active )
	{
#ifdef __USEA3D
		if (snd_isa3d)
		{
//         gSndBufSize = SECONDARY_BUFFER_SIZE;//MIX_BUFFER_SIZE;
		}
		else
#endif
		if ( pDS && cl_hwnd && snd_isdirect )
		{
			DS_CreateBuffers();
		}
	}
	else
	{
#ifdef __USEA3D
		if (snd_isa3d)
		{
//			A3D_UninitBuffers(cl_hwnd);
		}
		else
#endif
		if ( pDS && cl_hwnd && snd_isdirect )
		{
			DS_DestroyBuffers();
		}
	}
}


#ifdef __USEA3D
sndinitstat SNDDMA_InitA3D( void )
{
	HRESULT hresult;

	dma.channels = 2;
	dma.samplebits = 16;

	if (s_khz->value == 96)
		dma.speed = 96000;
	else if (s_khz->value == 48)
		dma.speed = 48000;
	else if (s_khz->value == 8)
		dma.speed = 8000;
	else if (s_khz->value == 44)
		dma.speed = 44100;
	else if (s_khz->value == 22)
		dma.speed = 22050;
	else if (s_khz->value == 11)
		dma.speed = 11025;
	else if (s_khz->value == 33)
		dma.speed = 33075;
	else
		dma.speed = s_khz->value * 1002.2727f;
	if (!dma.speed)
		dma.speed  = 11025;

	Com_Printf( "Initializing Aureal A3D...\n" );

	hresult = hA3D_Init ( cl_hwnd, MAX_CHANNELS, dma.speed, dma.samplebits );
	if (SUCCEEDED ( hresult ))
	{
		DWORD dwWrite;

		Com_Printf( "ok\n" );

		A3D_SetMixBufferSize ( SECONDARY_BUFFER_SIZE );
		hA3D_SetSecondaryBufferSize ( SECONDARY_BUFFER_SIZE );
		hA3D_SetMinBufferSize ( SECONDARY_BUFFER_SIZE );

		gSndBufSize = SECONDARY_BUFFER_SIZE;
		dma.samples = gSndBufSize/(dma.samplebits/8);
		dma.samplepos = 0;
		dma.submission_chunk = 1;
		dma.buffer = (unsigned char *) NULL;
		sample16 = (dma.samplebits/8) - 1;

		// AGW - set mm start time
		hA3D_StartMixBuffer();
		A3D_GetMixBufferPos ( &mmstarttime.u.sample, &dwWrite );

		// Make sure to set the A3D values to correspond to their console vars.
		gpclRolloff->modified = gpclDoppler->modified = gpclDistance->modified = true;
		gpclMin->modified = gpclMax->modified = gpcl2DVolume->modified = true;
		gpclWaterEq->modified = gpclGeometry->modified = true;

#ifdef __A3D_GEOM
		gpclNumLeaf->modified = gpclRefGain->modified = gpclRefDelay->modified = true;
		gpclPolyKeep->modified = gpclPolySize->modified = gpclShowTossed->modified = true;
		gpclPolyReflectSize->modified = gpclNumPolys->modified = gpclUsePVS->modified = true;
		gpclBloat->modified = gpclOcclude->modified = gpclReflect->modified = true;
		gpclOccFact->modified = gpclOccEpsilon->modified = true;
#endif

		return SIS_SUCCESS;
	}
	else
	if (hresult == A3D_FAKE) {
		// Found an imposter!!!
		return SIS_FAKEA3D;
	}
	else
	{
		switch (hresult) {
		case A3D_FAILED_INIT:
			Com_DPrintf ( "  Failed call to A3D::Init\n" ); break;
		case A3D_FAILED_COINIT:
			Com_DPrintf ( "  Failed call to A3D::CoInit\n" ); break;
		case A3D_FAILED_CREATE_RENDERER:
			Com_DPrintf ( "  Failed call to A3D::Create Renderer\n" ); break;
		case A3D_NOA3D:
			Com_DPrintf ( "  Could not find A3D hardware\n" ); break;
		}
	}

	Com_Printf ( "failed\n" );
	return SIS_FAILURE;
}

// This is a console function to dynamically shutdown A3D and start up
// directsound.
void S_disableA3D( void )
{
	sndinitstat stat;

	// If A3D failed to initialize, drop to ds.
	if (!snd_isa3d) {
		Com_Printf ( "A3D is not currently running.\n" );
		return;
	}

	sound_started = false;
	SNDDMA_Shutdown();

	memset ((void *)&dma, 0, sizeof (dma));

	snd_isa3d = dsound_init = wav_init = 0;
	stat = SIS_FAILURE; // assume DirectSound won't initialize

	/* Init DirectSound */
	if (!s_wavonly->value)
	{
		stat = SNDDMA_InitDirect ();

		if (stat == SIS_SUCCESS)
		{
			snd_isdirect = true;
			sound_started = true;

			if (snd_firsttime)
				Com_Printf ( "dsound init succeeded\n" );
		}
		else
		{
			snd_isdirect = false;
			Com_Printf ( "*** dsound init failed ***\n" );
		}
	}

// if DirectSound didn't succeed in initializing, try to initialize
// waveOut sound, unless DirectSound failed because the hardware is
// already allocated (in which case the user has already chosen not
// to have sound)
	if (!dsound_init && (stat != SIS_NOTAVAIL))
	{
		snd_iswave = SNDDMA_InitWav ();

		if (snd_iswave)
		{
			sound_started = true;
			if (snd_firsttime)
				Com_Printf ( "Wave sound init succeeded\n" );
		}
		else
		{
			Com_Printf ( "Wave sound init failed\n" );
		}
	}

	snd_firsttime = false;

	snd_buffer_count = 1;

	if (!snd_isa3d && !dsound_init && !wav_init)
	{
		if (snd_firsttime)
			Com_Printf ( "*** No sound device initialized ***\n" );

		return;
	}

	return;
}

// This is a console function to dynamically shutdown directsound and start up
// A3D.
void S_enableA3D( void )
{
	sndinitstat stat;

	if (snd_isa3d) { // Is A3D already enabled.
		Com_DPrintf ( "A3D already enabled\n" );
		return;
	}

	sound_started = false;
	SNDDMA_Shutdown();

	memset ((void *)&dma, 0, sizeof (dma));

	dsound_init = wav_init = 0;
	stat = SIS_FAILURE; // assume DirectSound won't initialize

	stat = SNDDMA_InitA3D ();

	if (stat == SIS_SUCCESS)
	{
		Com_DPrintf( "Aureal A3D initialized\n" );

		snd_isa3d = true;
		snd_isdirect = false;
		sound_started = true;
	}
	else
	if (stat == SIS_FAKEA3D)
	{
		// Preferably, this will have a GUI event associated with it to inform
		// the user that they have an invalid A3D.dll
		Com_DPrintf( "Non-Aureal A3D.dll found.  This is an unsupported version A3D.\nDropping to Direct Sound\n" );
		snd_isa3d = false;

		S_disableA3D();
	}
	else
	{
		Com_DPrintf( "Aureal A3D initialization failed.  Dropping to Direct Sound\n" );
		snd_isa3d = false;

		S_disableA3D();
	}

}

#endif // __USEA3D && SIN

//////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _ADD_EAX_
/*
   This routine is used to intercept SNDDMA_GetDMAPos. We need to return the total
   number based on both buffers so the rest of the framework behaves the same.
 */
int SNDEAX_GetPos()
{
	DWORD dwPos;
	DWORD dwRtn;

	IDirectSoundBuffer_GetCurrentPosition( pDSBufFL, &dwPos, NULL );

	dwPos -= g_dwEAXStart;
	dwRtn = dwPos << 1;
	dwRtn >>= sample16;
	dwRtn &= dma.samples - 1;
	return (int)dwRtn;
}


/*
   Release all EAX pointers.
 */
void SNDEAX_Release()
{
	if ( pDSPropSetFL != NULL ) IKsPropertySet_Release( pDSPropSetFL );
	pDSPropSetFL = NULL;

	if ( pDSPropSetFR != NULL ) IKsPropertySet_Release( pDSPropSetFR );
	pDSPropSetFR = NULL;

	if ( pDSPropSetRL != NULL ) IKsPropertySet_Release( pDSPropSetRL );
	pDSPropSetRL = NULL;

	if ( pDSPropSetRR != NULL ) IKsPropertySet_Release( pDSPropSetRR );
	pDSPropSetRR = NULL;

	if ( pDSBuf3DFL != NULL ) IDirectSound3DBuffer_Release( pDSBuf3DFL );
	pDSBuf3DFL = NULL;

	if ( pDSBuf3DFR != NULL ) IDirectSound3DBuffer_Release( pDSBuf3DFR );
	pDSBuf3DFR = NULL;

	if ( pDSBuf3DRL != NULL ) IDirectSound3DBuffer_Release( pDSBuf3DRL );
	pDSBuf3DRL = NULL;

	if ( pDSBuf3DRR != NULL ) IDirectSound3DBuffer_Release( pDSBuf3DRR );
	pDSBuf3DRR = NULL;

	if ( pDSBufFL != NULL ) IDirectSoundBuffer_Release( pDSBufFL );
	pDSBufFL = NULL;

	if ( pDSBufFR != NULL ) IDirectSoundBuffer_Release( pDSBufFR );
	pDSBufFR = NULL;

	if ( pDSBufRL != NULL ) IDirectSoundBuffer_Release( pDSBufRL );
	pDSBufRL = NULL;

	if ( pDSBufRR != NULL ) IDirectSoundBuffer_Release( pDSBufRR );
	pDSBufRR = NULL;

	EAX_format_set = false;
}


/*
   Initialization for EAX.
 */
qboolean SNDEAX_Init( LPDIRECTSOUND lpDS, WAVEFORMATEX* lpFormat, DSBCAPS* lpdsbc )
{
	DSBUFFERDESC dsbuf;
	ULONG ulAnswer;
	WAVEFORMATEX wvex;
	DWORD dwSize, dwWrite;
	int reps;
	HRESULT hresult;

	if ( lpDS == NULL ) return false;
	if ( EAX_format_set ) return true;

	// Copy the wave format information and then alter for single channel.
	memcpy( &wvex, lpFormat, sizeof(WAVEFORMATEX));
	wvex.nChannels = 1;
	wvex.nBlockAlign = wvex.nChannels * wvex.wBitsPerSample / 8;
	wvex.nAvgBytesPerSec = wvex.nSamplesPerSec * wvex.nBlockAlign;

	// Create buffers as 3D hardware. Buffer size needs to be 1/2 of
	// SECONDARY_BUFFER_SIZE.
	memset( &dsbuf, 0, sizeof(dsbuf));
	dsbuf.dwSize = sizeof(DSBUFFERDESC);
	dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY|DSBCAPS_CTRL3D;
	dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE/2;
	dsbuf.lpwfxFormat = &wvex;

	if ( DS_OK != IDirectSound_CreateSoundBuffer( lpDS, &dsbuf, &pDSBufFL, NULL ) )
	{
		Com_Printf ( "DS:CreateSoundBuffer for EAX front left failed\n" );
		SNDEAX_Release();
		return false;
	}

	if ( DS_OK != IDirectSound_CreateSoundBuffer( lpDS, &dsbuf, &pDSBufFR, NULL ) )
	{
		Com_Printf ( "DS:CreateSoundBuffer for EAX front right failed\n" );
		SNDEAX_Release();
		return false;
	}

	if ( DS_OK != IDirectSound_CreateSoundBuffer( lpDS, &dsbuf, &pDSBufRL, NULL ) )
	{
		Com_Printf ( "DS:CreateSoundBuffer for EAX rear left failed\n" );
		SNDEAX_Release();
		return false;
	}

	if ( DS_OK != IDirectSound_CreateSoundBuffer( lpDS, &dsbuf, &pDSBufRR, NULL ) )
	{
		Com_Printf ( "DS:CreateSoundBuffer for EAX rear right failed\n" );
		SNDEAX_Release();
		return false;
	}

	if ( DS_OK != IDirectSoundBuffer_QueryInterface( pDSBufFL, &IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DFL ) )
	{
		Com_Printf ( "DS:Query 3DBuffer for EAX front left failed\n" );
		SNDEAX_Release();
		return false;
	}

	if ( DS_OK != IDirectSoundBuffer_QueryInterface( pDSBufFR, &IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DFR ) )
	{
		Com_Printf ( "DS:Query 3DBuffer for EAX front right failed\n" );
		SNDEAX_Release();
		return false;
	}

	if ( DS_OK != IDirectSoundBuffer_QueryInterface( pDSBufRL, &IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DRL ) )
	{
		Com_Printf ( "DS:Query 3DBuffer for EAX rear left failed\n" );
		SNDEAX_Release();
		return false;
	}

	if ( DS_OK != IDirectSoundBuffer_QueryInterface( pDSBufRR, &IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DRR ) )
	{
		Com_Printf ( "DS:Query 3DBuffer for EAX rear right failed\n" );
		SNDEAX_Release();
		return false;
	}

	// Position buffers.
	//lpDS->SetSpeakerConfig(lpDS, DSSPEAKER_QUAD);
	IDirectSound3DBuffer_SetPosition( pDSBuf3DFL, -1.0f, 0.0f, 1.0f, DS3D_IMMEDIATE );
	IDirectSound3DBuffer_SetPosition( pDSBuf3DFR, 1.0f, 0.0f, 1.0f, DS3D_IMMEDIATE );
	IDirectSound3DBuffer_SetPosition( pDSBuf3DRL, -1.0f, 0.0f, -1.0f, DS3D_IMMEDIATE );
	IDirectSound3DBuffer_SetPosition( pDSBuf3DRR, 1.0f, 0.0f, -1.0f, DS3D_IMMEDIATE );

	// Can we get EAX?
	ulAnswer = 0;
	if ( (IDirectSoundBuffer_QueryInterface( pDSBufFL, &IID_IKsPropertySetDef, (LPVOID *)&pDSPropSetFL )) == DS_OK )
	{
		if ( (IKsPropertySet_QuerySupport( pDSPropSetFL, &DSPROPSETID_EAX_ReverbPropertiesDef, DSPROPERTY_EAX_ALL, &ulAnswer )) == DS_OK )
		{
			if ( (ulAnswer & KSPROPERTY_SUPPORT_SET|KSPROPERTY_SUPPORT_GET) != (KSPROPERTY_SUPPORT_SET|KSPROPERTY_SUPPORT_GET) )
			{
				Com_Printf ( "The current device is not EAX capable.\n" );
				SNDEAX_Release();
				return false;
			}
		}
		else
		{
			Com_Printf ( "EAX:QuerySupport for DSPROPSETID_EAX_ReverbProperties Failed\n" );
			SNDEAX_Release();
			return false;
		}
	}
	else
	{
		Com_Printf ( "EAX:QueryInterface for IID_IKsPropertySet FL Failed\n" );
		SNDEAX_Release();
		return false;
	}

	if ( (IDirectSoundBuffer_QueryInterface( pDSBufFR, &IID_IKsPropertySetDef, (LPVOID *)&pDSPropSetFR )) != DS_OK )
	{
		Com_Printf ( "EAX:QueryInterface for IID_IKsPropertySet FR Failed\n" );
		SNDEAX_Release();
		return false;
	}

	if ( (IDirectSoundBuffer_QueryInterface( pDSBufRL, &IID_IKsPropertySetDef, (LPVOID *)&pDSPropSetRL )) != DS_OK )
	{
		Com_Printf ( "EAX:QueryInterface for IID_IKsPropertySet RL Failed\n" );
		SNDEAX_Release();
		return false;
	}

	if ( (IDirectSoundBuffer_QueryInterface( pDSBufRR, &IID_IKsPropertySetDef, (LPVOID *)&pDSPropSetRR )) != DS_OK )
	{
		Com_Printf ( "EAX:QueryInterface for IID_IKsPropertySet RR Failed\n" );
		SNDEAX_Release();
		return false;
	}

	memset( lpdsbc, 0, sizeof(DSBCAPS));
	lpdsbc->dwSize = sizeof(DSBCAPS);

	if (DS_OK != IDirectSoundBuffer_GetCaps ( pDSBufFL, lpdsbc ))
	{
		Com_Printf ( "DS:GetCaps failed for EAX buffer\n" );
		SNDEAX_Release();
		return false;
	}

	IDirectSoundBuffer_Play( pDSBufFL, 0, 0, DSBPLAY_LOOPING );
	IDirectSoundBuffer_Play( pDSBufFR, 0, 0, DSBPLAY_LOOPING );
	IDirectSoundBuffer_Play( pDSBufRL, 0, 0, DSBPLAY_LOOPING );
	IDirectSoundBuffer_Play( pDSBufRR, 0, 0, DSBPLAY_LOOPING );

	gSndBufSize = lpdsbc->dwBufferBytes*2;

	// Test everything just like in the normal initialization.
	reps = 0;
	while ((hresult = IDirectSoundBuffer_Lock( pDSBufFL, 0, lpdsbc->dwBufferBytes, (void **)&lpData, &dwSize, NULL, NULL, 0 )) != DS_OK)
	{
		if (hresult != DSERR_BUFFERLOST)
		{
			Com_Printf ( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed for EAX FL\n" );
			SNDEAX_Release();
			return false;
		}

		if (++reps > 10000)
		{
			Com_Printf ( "SNDDMA_InitDirect: DS: couldn't restore buffer for EAX FL\n" );
			SNDEAX_Release();
			return false;
		}
	}
	memset( lpData, 0, dwSize );
	IDirectSoundBuffer_Unlock( pDSBufFL, lpData, dwSize, NULL, 0 );

	reps = 0;
	while ((hresult = IDirectSoundBuffer_Lock( pDSBufFR, 0, lpdsbc->dwBufferBytes, (void **)&lpData, &dwSize, NULL, NULL, 0 )) != DS_OK)
	{
		if (hresult != DSERR_BUFFERLOST)
		{
			Com_Printf ( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed for EAX FR\n" );
			SNDEAX_Release();
			return false;
		}

		if (++reps > 10000)
		{
			Com_Printf ( "SNDDMA_InitDirect: DS: couldn't restore buffer for EAX FR\n" );
			SNDEAX_Release();
			return false;
		}
	}
	memset( lpData, 0, dwSize );
	IDirectSoundBuffer_Unlock( pDSBufFR, lpData, dwSize, NULL, 0 );

	reps = 0;
	while ((hresult = IDirectSoundBuffer_Lock( pDSBufRL, 0, lpdsbc->dwBufferBytes, (void **)&lpData, &dwSize, NULL, NULL, 0 )) != DS_OK)
	{
		if (hresult != DSERR_BUFFERLOST)
		{
			Com_Printf ( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed for EAX RL\n" );
			SNDEAX_Release();
			return false;
		}

		if (++reps > 10000)
		{
			Com_Printf ( "SNDDMA_InitDirect: DS: couldn't restore buffer for EAX RL\n" );
			SNDEAX_Release();
			return false;
		}
	}
	memset( lpData, 0, dwSize );
	IDirectSoundBuffer_Unlock( pDSBufRL, lpData, dwSize, NULL, 0 );

	reps = 0;
	while ((hresult = IDirectSoundBuffer_Lock( pDSBufRR, 0, lpdsbc->dwBufferBytes, (void **)&lpData, &dwSize, NULL, NULL, 0 )) != DS_OK)
	{
		if (hresult != DSERR_BUFFERLOST)
		{
			Com_Printf ( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed for EAX RR\n" );
			SNDEAX_Release();
			return false;
		}

		if (++reps > 10000)
		{
			Com_Printf ( "SNDDMA_InitDirect: DS: couldn't restore buffer for EAX RR\n" );
			SNDEAX_Release();
			return false;
		}
	}
	memset( lpData, 0, dwSize );
	IDirectSoundBuffer_Unlock( pDSBufRR, lpData, dwSize, NULL, 0 );

	// We will bypass this in the normal init, so we'll do it here.
	lpData = NULL;

	// OK Stop and get our positions and were good to go.
	IDirectSoundBuffer_Stop( pDSBufFL );
	IDirectSoundBuffer_Stop( pDSBufFR );
	IDirectSoundBuffer_Stop( pDSBufRL );
	IDirectSoundBuffer_Stop( pDSBufRR );
	IDirectSoundBuffer_GetCurrentPosition( pDSBufFL, &g_dwEAXStart, &dwWrite );
	IDirectSoundBuffer_SetCurrentPosition( pDSBufFR, g_dwEAXStart );
	IDirectSoundBuffer_SetCurrentPosition( pDSBufRL, g_dwEAXStart );
	IDirectSoundBuffer_SetCurrentPosition( pDSBufRR, g_dwEAXStart );
	IDirectSoundBuffer_Play( pDSBufFL, 0, 0, DSBPLAY_LOOPING );
	IDirectSoundBuffer_Play( pDSBufFR, 0, 0, DSBPLAY_LOOPING );
	IDirectSoundBuffer_Play( pDSBufRL, 0, 0, DSBPLAY_LOOPING );
	IDirectSoundBuffer_Play( pDSBufRR, 0, 0, DSBPLAY_LOOPING );

	if (snd_firsttime)
		Com_Printf ( "EAX initialization successful\n" );

	SNDEAX_SetEnvironment( 0 );
	SNDEAX_SetEnvironmentLevel( s_eax_reverblevel->value );
	s_eax_reverblevel->modified = false;

	return true;
}


/*
 */
void SNDEAX_SetEnvironment( int iPreset )
{
	static int lastiPreset = -1;
	HRESULT hr;

	if ( iPreset == lastiPreset )
		return;

	lastiPreset = iPreset;

	iPreset = iPreset % TOTAL_PRESETS;

	hr = IKsPropertySet_Set( pDSPropSetFL, &DSPROPSETID_EAX_ReverbPropertiesDef,
	                         DSPROPERTY_EAX_ALL, NULL, 0, &eax_preset[iPreset],
	                         sizeof(EAX_REVERBPROPERTIES));
	if ( hr != 0 ) Com_Printf ( "SNDEAX_SetEnvironment: Preset not set\n" );

	g_iCurrentEaxPreset = iPreset;
}


/*
 */
void SNDEAX_SetEnvironmentLevel( float fMixLevel )
{
	static float lastfMixLevel;
	HRESULT hr;

	if ( fMixLevel == lastfMixLevel )
		return;

	lastfMixLevel = fMixLevel;

	hr = IKsPropertySet_Set( pDSPropSetFL, &DSPROPSETID_EAXBUFFER_ReverbPropertiesDef,
	                         DSPROPERTY_EAXBUFFER_REVERBMIX, NULL, 0, &fMixLevel, sizeof(float));
	if ( hr != 0 ) Com_Printf ( "SNDEAX_SetEnvironmentLevel: Buffer level not set FL\n" );

	hr = IKsPropertySet_Set( pDSPropSetFR, &DSPROPSETID_EAXBUFFER_ReverbPropertiesDef,
	                         DSPROPERTY_EAXBUFFER_REVERBMIX, NULL, 0, &fMixLevel, sizeof(float));
	if ( hr != 0 ) Com_Printf ( "SNDEAX_SetEnvironmentLevel: Buffer level not set FR\n" );

	hr = IKsPropertySet_Set( pDSPropSetRL, &DSPROPSETID_EAXBUFFER_ReverbPropertiesDef,
	                         DSPROPERTY_EAXBUFFER_REVERBMIX, NULL, 0, &fMixLevel, sizeof(float));
	if ( hr != 0 ) Com_Printf ( "SNDEAX_SetEnvironmentLevel: Buffer level not set RL\n" );

	hr = IKsPropertySet_Set( pDSPropSetRR, &DSPROPSETID_EAXBUFFER_ReverbPropertiesDef,
	                         DSPROPERTY_EAXBUFFER_REVERBMIX, NULL, 0, &fMixLevel, sizeof(float));
	if ( hr != 0 ) Com_Printf ( "SNDEAX_SetEnvironmentLevel: Buffer level not set RR\n" );

	g_fCurrentEaxLevel = fMixLevel;
}


/*
 */
void SNDEAX_RefreshBuffers()
{
	DWORD dwStatus;

	if ( pDSBufFL )
	{
		if ( IDirectSoundBuffer_GetStatus( pDSBufFL, &dwStatus ) == DS_OK)
		{
			if ( dwStatus & DSBSTATUS_BUFFERLOST ) IDirectSoundBuffer_Restore ( pDSBufFL );
			if ( !(dwStatus & DSBSTATUS_PLAYING) ) IDirectSoundBuffer_Play( pDSBufFL, 0, 0, DSBPLAY_LOOPING );
		}
		else Com_Printf( "SNDEAX_RefreshBuffers: Unable to refresh FL\n" );
	}
	else Com_Printf( "SNDEAX_RefreshBuffers: Buffer does not exist FL\n" );

	if ( pDSBufFR )
	{
		if ( IDirectSoundBuffer_GetStatus( pDSBufFR, &dwStatus ) == DS_OK)
		{
			if ( dwStatus & DSBSTATUS_BUFFERLOST ) IDirectSoundBuffer_Restore ( pDSBufFR );
			if ( !(dwStatus & DSBSTATUS_PLAYING) ) IDirectSoundBuffer_Play( pDSBufFR, 0, 0, DSBPLAY_LOOPING );
		}
		else Com_Printf( "SNDEAX_RefreshBuffers: Unable to refresh FR\n" );
	}
	else Com_Printf( "SNDEAX_RefreshBuffers: Buffer does not exist FR\n" );

	if ( pDSBufRL )
	{
		if ( IDirectSoundBuffer_GetStatus( pDSBufRL, &dwStatus ) == DS_OK)
		{
			if ( dwStatus & DSBSTATUS_BUFFERLOST ) IDirectSoundBuffer_Restore ( pDSBufRL );
			if ( !(dwStatus & DSBSTATUS_PLAYING) ) IDirectSoundBuffer_Play( pDSBufRL, 0, 0, DSBPLAY_LOOPING );
		}
		else Com_Printf( "SNDEAX_RefreshBuffers: Unable to refresh RL\n" );
	}
	else Com_Printf( "SNDEAX_RefreshBuffers: Buffer does not exist RL\n" );

	if ( pDSBufRR )
	{
		if ( IDirectSoundBuffer_GetStatus( pDSBufRR, &dwStatus ) == DS_OK)
		{
			if ( dwStatus & DSBSTATUS_BUFFERLOST ) IDirectSoundBuffer_Restore ( pDSBufRR );
			if ( !(dwStatus & DSBSTATUS_PLAYING) ) IDirectSoundBuffer_Play( pDSBufRR, 0, 0, DSBPLAY_LOOPING );
		}
		else Com_Printf( "SNDEAX_RefreshBuffers: Unable to refresh RR\n" );
	}
	else Com_Printf( "SNDEAX_RefreshBuffers: Buffer does not exist RR\n" );
}


/*
 */
void SNDEAX_LockBuffers()
{
	HRESULT hr;
	DWORD reps;

	SNDEAX_RefreshBuffers();

	if ( pDSBufFL )
	{
		reps = 0;
		while ( (hr = IDirectSoundBuffer_Lock( pDSBufFL, 0, gSndBufSize/2, (void **)&g_pdwWriteFL, &g_dwSizeFL, NULL, NULL, 0 )) != DS_OK )
		{
			if ( ++reps > MAX_LOCK_ATTEMPTS )
			{
				Com_Printf ( "SNDEAX_LockBuffers: Unable to lock FL\n" );
				SNDEAX_UnlockBuffers();
				S_Shutdown ();
				return;
			}
		}
	}
	else Com_Printf( "SNDEAX_LockBuffers: Buffer does not exist FL\n" );

	if ( pDSBufFR )
	{
		reps = 0;
		while ( (hr = IDirectSoundBuffer_Lock( pDSBufFR, 0, gSndBufSize/2, (void **)&g_pdwWriteFR, &g_dwSizeFR, NULL, NULL, 0 )) != DS_OK )
		{
			if ( ++reps > MAX_LOCK_ATTEMPTS )
			{
				Com_Printf ( "SNDEAX_LockBuffers: Unable to lock FR\n" );
				SNDEAX_UnlockBuffers();
				S_Shutdown ();
				return;
			}
		}
	}
	else Com_Printf( "SNDEAX_LockBuffers: Buffer does not exist FR\n" );

	if ( pDSBufRL )
	{
		reps = 0;
		while ( (hr = IDirectSoundBuffer_Lock( pDSBufRL, 0, gSndBufSize/2, (void **)&g_pdwWriteRL, &g_dwSizeRL, NULL, NULL, 0 )) != DS_OK )
		{
			if ( ++reps > MAX_LOCK_ATTEMPTS )
			{
				Com_Printf ( "SNDEAX_LockBuffers: Unable to lock RL\n" );
				SNDEAX_UnlockBuffers();
				S_Shutdown ();
				return;
			}
		}
	}
	else Com_Printf( "SNDEAX_LockBuffers: Buffer does not exist RL\n" );

	if ( pDSBufRR )
	{
		reps = 0;
		while ( (hr = IDirectSoundBuffer_Lock( pDSBufRR, 0, gSndBufSize/2, (void **)&g_pdwWriteRR, &g_dwSizeRR, NULL, NULL, 0 )) != DS_OK )
		{
			if ( ++reps > MAX_LOCK_ATTEMPTS )
			{
				Com_Printf ( "SNDEAX_LockBuffers: Unable to lock RR\n" );
				SNDEAX_UnlockBuffers();
				S_Shutdown ();
				return;
			}
		}
	}
	else Com_Printf( "SNDEAX_LockBuffers: Buffer does not exist RR\n" );
}


/*
 */
void SNDEAX_UnlockBuffers()
{
	if ( g_pdwWriteFL )
	{
		IDirectSoundBuffer_Unlock( pDSBufFL, g_pdwWriteFL, g_dwSizeFL, NULL, 0 );
		g_pdwWriteFL = NULL;
	}
	else Com_Printf( "SNDEAX_UnlockBuffers: Buffer was not locked FL\n" );

	if ( g_pdwWriteFR )
	{
		IDirectSoundBuffer_Unlock( pDSBufFR, g_pdwWriteFR, g_dwSizeFR, NULL, 0 );
		g_pdwWriteFR = NULL;
	}
	else Com_Printf( "SNDEAX_UnlockBuffers: Buffer was not locked FR\n" );

	if ( g_pdwWriteRL )
	{
		IDirectSoundBuffer_Unlock( pDSBufRL, g_pdwWriteRL, g_dwSizeRL, NULL, 0 );
		g_pdwWriteRL = NULL;
	}
	else Com_Printf( "SNDEAX_UnlockBuffers: Buffer was not locked RL\n" );

	if ( g_pdwWriteRR )
	{
		IDirectSoundBuffer_Unlock( pDSBufRR, g_pdwWriteRR, g_dwSizeRR, NULL, 0 );
		g_pdwWriteRR = NULL;
	}
	else Com_Printf( "SNDEAX_UnlockBuffers: Buffer was not locked RR\n" );
}


/*
 */
void SNDEAX_ClearBuffers( void )
{
	SNDEAX_LockBuffers();
	if ( g_pdwWriteFL ) memset((char*)g_pdwWriteFL, 0, gSndBufSize/2 );
	if ( g_pdwWriteFR ) memset((char*)g_pdwWriteFR, 0, gSndBufSize/2 );
	if ( g_pdwWriteRL ) memset((char*)g_pdwWriteRL, 0, gSndBufSize/2 );
	if ( g_pdwWriteRR ) memset((char*)g_pdwWriteRR, 0, gSndBufSize/2 );
	SNDEAX_UnlockBuffers();
}

#endif
//////////////////////////////////////////////////////////////////////////////////////////////////////


