//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=====================================================================================//

#include "audio_pch.h"
#include <dsound.h>
#pragma warning(disable : 4201)		// nameless struct/union
#include <ks.h>
// Fix for VS 2010 build errors copied from Dota
#if !defined( NEW_DXSDK ) && ( _MSC_VER >= 1600 )
#undef KSDATAFORMAT_SUBTYPE_WAVEFORMATEX
//#undef KSDATAFORMAT_SUBTYPE_PCM
#undef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
#endif
#include <ksmedia.h>
#include "iprediction.h"
#include "eax.h"
#include "tier0/icommandline.h"
#include "video//ivideoservices.h"
#include "sys_dll.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

extern bool snd_firsttime;

extern void DEBUG_StartSoundMeasure(int type, int samplecount );
extern void DEBUG_StopSoundMeasure(int type, int samplecount );

// legacy support
extern ConVar sxroom_off;
extern ConVar sxroom_type;
extern ConVar sxroomwater_type;
extern float sxroom_typeprev;

extern HWND* pmainwindow;

typedef enum {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat;

#define SECONDARY_BUFFER_SIZE			0x10000		// output buffer size in bytes
#define SECONDARY_BUFFER_SIZE_SURROUND	0x04000		// output buffer size in bytes, one per channel

#if !defined( NEW_DXSDK )
// hack - need to include latest dsound.h
#undef DSSPEAKER_5POINT1
#undef DSSPEAKER_7POINT1
#undef DSSPEAKER_7POINT1_SURROUND
#undef DSSPEAKER_5POINT1_SURROUND
#define DSSPEAKER_5POINT1		6
#define DSSPEAKER_7POINT1		7
#define DSSPEAKER_7POINT1_SURROUND 8
#define DSSPEAKER_5POINT1_SURROUND 9
#endif

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

extern void ReleaseSurround(void);
extern bool MIX_ScaleChannelVolume( paintbuffer_t *ppaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans );
void OnSndSurroundCvarChanged( IConVar *var, const char *pOldString, float flOldValue );
void OnSndSurroundLegacyChanged( IConVar *var, const char *pOldString, float flOldValue );
void OnSndVarChanged( IConVar *var, const char *pOldString, float flOldValue );

static LPDIRECTSOUND pDS = NULL;
static LPDIRECTSOUNDBUFFER pDSBuf = NULL, pDSPBuf = NULL;

static GUID IID_IDirectSound3DBufferDef = {0x279AFA86, 0x4981, 0x11CE, {0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60}};
static ConVar windows_speaker_config("windows_speaker_config", "-1", FCVAR_ARCHIVE);
static DWORD g_ForcedSpeakerConfig = 0;

extern ConVar snd_mute_losefocus;

//-----------------------------------------------------------------------------
// Purpose: Implementation of direct sound
//-----------------------------------------------------------------------------
class CAudioDirectSound : public CAudioDeviceBase
{
public:
	~CAudioDirectSound( void );
	bool		IsActive( void ) { return true; }
	bool		Init( void );
	void		Shutdown( void );
	void		Pause( void );
	void		UnPause( void );
	float		MixDryVolume( void );
	bool		Should3DMix( void );
	void		StopAllSounds( void );

	int			PaintBegin( float mixAheadTime, int soundtime, int paintedtime );
	void		PaintEnd( void );

	int			GetOutputPosition( void );
	void		ClearBuffer( void );
	void		UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up );

	void		ChannelReset( int entnum, int channelIndex, float distanceMod );
	void		TransferSamples( int end );

	const char *DeviceName( void );
	int			DeviceChannels( void )		{ return m_deviceChannels; }
	int			DeviceSampleBits( void )	{ return m_deviceSampleBits; }
	int			DeviceSampleBytes( void )	{ return m_deviceSampleBits/8; }
	int			DeviceDmaSpeed( void )		{ return m_deviceDmaSpeed; }
	int			DeviceSampleCount( void )	{ return m_deviceSampleCount; }

	bool		IsInterleaved() { return m_isInterleaved; }

	// Singleton object
	static		CAudioDirectSound *m_pSingleton;

private:
	void		DetectWindowsSpeakerSetup();
	bool		LockDSBuffer( LPDIRECTSOUNDBUFFER pBuffer, DWORD **pdwWriteBuffer, DWORD *pdwSizeBuffer, const char *pBufferName, int lockFlags = 0 );
	bool		IsUsingBufferPerSpeaker();

	sndinitstat SNDDMA_InitDirect( void );
	bool		SNDDMA_InitInterleaved( LPDIRECTSOUND lpDS, WAVEFORMATEX* lpFormat, int channelCount );
	bool		SNDDMA_InitSurround(LPDIRECTSOUND lpDS, WAVEFORMATEX* lpFormat, DSBCAPS* lpdsbc, int cchan);
	void		S_TransferSurround16( portable_samplepair_t *pfront, portable_samplepair_t *prear, portable_samplepair_t *pcenter, int lpaintedtime, int endtime, int cchan);
	void		S_TransferSurround16Interleaved( const portable_samplepair_t *pfront, const portable_samplepair_t *prear, const portable_samplepair_t *pcenter, int lpaintedtime, int endtime);
	void		S_TransferSurround16Interleaved_FullLock( const portable_samplepair_t *pfront, const portable_samplepair_t *prear, const portable_samplepair_t *pcenter, int lpaintedtime, int endtime);

	int			m_deviceChannels;					// channels per hardware output buffer (1 for quad/5.1, 2 for stereo)
	int			m_deviceSampleBits;					// bits per sample (16)
	int			m_deviceSampleCount;				// count of mono samples in output buffer
	int			m_deviceDmaSpeed;					// samples per second per output buffer
	int			m_bufferSizeBytes;					// size of a single hardware output buffer, in bytes
	
	DWORD		m_outputBufferStartOffset;						// output buffer playback starting byte offset
	HINSTANCE	m_hInstDS;
	bool		m_isInterleaved;
};

CAudioDirectSound *CAudioDirectSound::m_pSingleton = NULL;

LPDIRECTSOUNDBUFFER pDSBufFL = NULL;
LPDIRECTSOUNDBUFFER pDSBufFR = NULL;
LPDIRECTSOUNDBUFFER pDSBufRL = NULL;
LPDIRECTSOUNDBUFFER pDSBufRR = NULL;
LPDIRECTSOUNDBUFFER pDSBufFC = NULL;
LPDIRECTSOUND3DBUFFER pDSBuf3DFL = NULL;
LPDIRECTSOUND3DBUFFER pDSBuf3DFR = NULL;
LPDIRECTSOUND3DBUFFER pDSBuf3DRL = NULL;
LPDIRECTSOUND3DBUFFER pDSBuf3DRR = NULL;
LPDIRECTSOUND3DBUFFER pDSBuf3DFC = NULL;

// ----------------------------------------------------------------------------- //
// Helpers.
// ----------------------------------------------------------------------------- //


CAudioDirectSound::~CAudioDirectSound( void )
{
	m_pSingleton = NULL;
}

bool CAudioDirectSound::Init( void )
{
	m_hInstDS = NULL;

	static bool first = true;
	if ( first )
	{
		snd_surround.InstallChangeCallback( &OnSndSurroundCvarChanged );
		snd_legacy_surround.InstallChangeCallback( &OnSndSurroundLegacyChanged );
		snd_mute_losefocus.InstallChangeCallback( &OnSndVarChanged );
		first = false;
	}

	if ( SNDDMA_InitDirect() == SIS_SUCCESS)
	{
		if ( g_pVideo != NULL )
		{
			g_pVideo->SoundDeviceCommand( VideoSoundDeviceOperation::SET_DIRECT_SOUND_DEVICE, pDS );
		}

		return true;
	}

	return false;
}

void CAudioDirectSound::Shutdown( void )
{
	ReleaseSurround();

	if (pDSBuf)
	{
		pDSBuf->Stop();
		pDSBuf->Release();
	}

	// only release primary buffer if it's not also the mixing buffer we just released
	if (pDSPBuf && (pDSBuf != pDSPBuf))
	{
		pDSPBuf->Release();
	}

	if (pDS)
	{
		pDS->SetCooperativeLevel(*pmainwindow, DSSCL_NORMAL);
		pDS->Release();
	}

	pDS = NULL;
	pDSBuf = NULL;
	pDSPBuf = NULL;

	if ( m_hInstDS )
	{
		FreeLibrary( m_hInstDS );
		m_hInstDS = NULL;
	}

	if ( this == CAudioDirectSound::m_pSingleton )
	{
		CAudioDirectSound::m_pSingleton = NULL;
	}
}

// Total number of samples that have played out to hardware
// for current output buffer (ie: from buffer offset start).
// return playback position within output playback buffer:
// the output units are dependant on the device channels
// so the ouput units for a 2 channel device are as 16 bit LR pairs
// and the output unit for a 1 channel device are as 16 bit mono samples.
// take into account the original start position within the buffer, and 
// calculate difference between current position (with buffer wrap) and 
// start position.
int	CAudioDirectSound::GetOutputPosition( void )
{
	int samp16;
	int start, current;
	DWORD dwCurrent;

	// get size in bytes of output buffer
	const int size_bytes = m_bufferSizeBytes; 
	if ( IsUsingBufferPerSpeaker() )
	{
		// mono output buffers
		// get byte offset of playback cursor in Front Left output buffer
		pDSBufFL->GetCurrentPosition(&dwCurrent, NULL);

		start = (int) m_outputBufferStartOffset;
		current = (int) dwCurrent;
	} 
	else
	{
		// multi-channel interleavd output buffer 
		// get byte offset of playback cursor in output buffer
		pDSBuf->GetCurrentPosition(&dwCurrent, NULL);

		start = (int) m_outputBufferStartOffset;
		current = (int) dwCurrent;
	}

	// get 16 bit samples played, relative to buffer starting offset
	if (current > start)
	{
		// get difference & convert to 16 bit mono samples
		samp16 = (current - start) >> SAMPLE_16BIT_SHIFT;
	}
	else
	{
		// get difference (with buffer wrap) convert to 16 bit mono samples
		samp16 = ((size_bytes - start) + current) >> SAMPLE_16BIT_SHIFT;
	}

	int outputPosition = samp16 / DeviceChannels();

	return outputPosition;
}

void CAudioDirectSound::Pause( void )
{
	if (pDSBuf)
	{
		pDSBuf->Stop();
	}

	if ( pDSBufFL ) pDSBufFL->Stop(); 
	if ( pDSBufFR ) pDSBufFR->Stop(); 
	if ( pDSBufRL ) pDSBufRL->Stop(); 
	if ( pDSBufRR ) pDSBufRR->Stop(); 
	if ( pDSBufFC ) pDSBufFC->Stop(); 
}


void CAudioDirectSound::UnPause( void )
{
	if (pDSBuf)
		pDSBuf->Play(0, 0, DSBPLAY_LOOPING);

	if (pDSBufFL) pDSBufFL->Play(0, 0, DSBPLAY_LOOPING); 
	if (pDSBufFR) pDSBufFR->Play(0, 0, DSBPLAY_LOOPING); 
	if (pDSBufRL) pDSBufRL->Play(0, 0, DSBPLAY_LOOPING); 
	if (pDSBufRR) pDSBufRR->Play( 0, 0, DSBPLAY_LOOPING); 
	if (pDSBufFC) pDSBufFC->Play( 0, 0, DSBPLAY_LOOPING); 
}


float CAudioDirectSound::MixDryVolume( void )
{
	return 0;
}


bool CAudioDirectSound::Should3DMix( void )
{
	if ( m_bSurround )
		return true;
	return false;
}


IAudioDevice *Audio_CreateDirectSoundDevice( void )
{
	if ( !CAudioDirectSound::m_pSingleton )
		CAudioDirectSound::m_pSingleton = new CAudioDirectSound;

	if ( CAudioDirectSound::m_pSingleton->Init() )
	{
		if (snd_firsttime)
			DevMsg ("DirectSound initialized\n");

		return CAudioDirectSound::m_pSingleton;
	}

	DevMsg ("DirectSound failed to init\n");

	delete CAudioDirectSound::m_pSingleton;
	CAudioDirectSound::m_pSingleton = NULL;

	return NULL;
}

int CAudioDirectSound::PaintBegin( float mixAheadTime, int soundtime, int lpaintedtime )
{
	//  soundtime - total full samples that have been played out to hardware at dmaspeed
	//  paintedtime - total full samples that have been mixed at speed
	//  endtime - target for full samples in mixahead buffer at speed
	//  samps - size of output buffer in full samples
	
	int mixaheadtime = mixAheadTime * DeviceDmaSpeed();
	int endtime = soundtime + mixaheadtime;

	if ( endtime <= lpaintedtime )
		return endtime;

	uint nSamples = endtime - lpaintedtime;
	if ( nSamples & 0x3 )
	{
		// The difference between endtime and painted time should align on 
		// boundaries of 4 samples.  This is important when upsampling from 11khz -> 44khz.
		nSamples += (4 - (nSamples & 3));
	}
	// clamp to min 512 samples per mix
	if ( nSamples > 0 && nSamples < 512 )
	{
		nSamples = 512;
	}
	endtime = lpaintedtime + nSamples;

	int fullsamps = DeviceSampleCount() / DeviceChannels();
	if ( (endtime - soundtime) > fullsamps)
	{
		endtime = soundtime + fullsamps;
		endtime += (4 - (endtime & 3));
	}

	DWORD	dwStatus;

	// If using surround, there are 4 or 5 different buffers being used and the pDSBuf is NULL.
	if ( IsUsingBufferPerSpeaker() ) 
	{
		if (pDSBufFL->GetStatus(&dwStatus) != DS_OK)
			Msg ("Couldn't get SURROUND FL sound buffer status\n");
		
		if (dwStatus & DSBSTATUS_BUFFERLOST)
			pDSBufFL->Restore();
		
		if (!(dwStatus & DSBSTATUS_PLAYING))
			pDSBufFL->Play(0, 0, DSBPLAY_LOOPING);

		if (pDSBufFR->GetStatus(&dwStatus) != DS_OK)
			Msg ("Couldn't get SURROUND FR sound buffer status\n");
		
		if (dwStatus & DSBSTATUS_BUFFERLOST)
			pDSBufFR->Restore();
		
		if (!(dwStatus & DSBSTATUS_PLAYING))
			pDSBufFR->Play(0, 0, DSBPLAY_LOOPING);

		if (pDSBufRL->GetStatus(&dwStatus) != DS_OK)
			Msg ("Couldn't get SURROUND RL sound buffer status\n");
		
		if (dwStatus & DSBSTATUS_BUFFERLOST)
			pDSBufRL->Restore();
		
		if (!(dwStatus & DSBSTATUS_PLAYING))
			pDSBufRL->Play(0, 0, DSBPLAY_LOOPING);

		if (pDSBufRR->GetStatus(&dwStatus) != DS_OK)
			Msg ("Couldn't get SURROUND RR sound buffer status\n");
		
		if (dwStatus & DSBSTATUS_BUFFERLOST)
			pDSBufRR->Restore();
		
		if (!(dwStatus & DSBSTATUS_PLAYING))
			pDSBufRR->Play(0, 0, DSBPLAY_LOOPING);

		if ( m_bSurroundCenter )
		{
			if (pDSBufFC->GetStatus(&dwStatus) != DS_OK)
				Msg ("Couldn't get SURROUND FC sound buffer status\n");
			
			if (dwStatus & DSBSTATUS_BUFFERLOST)
				pDSBufFC->Restore();
			
			if (!(dwStatus & DSBSTATUS_PLAYING))
				pDSBufFC->Play(0, 0, DSBPLAY_LOOPING);
		}
	}
	else if (pDSBuf)
	{
		if ( pDSBuf->GetStatus (&dwStatus) != DS_OK )
			Msg("Couldn't get sound buffer status\n");

		if ( dwStatus & DSBSTATUS_BUFFERLOST )
			pDSBuf->Restore();

		if ( !(dwStatus & DSBSTATUS_PLAYING) )
			pDSBuf->Play(0, 0, DSBPLAY_LOOPING);
	}

	return endtime;
}


void CAudioDirectSound::PaintEnd( void )
{
}


void CAudioDirectSound::ClearBuffer( void )
{
	int		clear;

	DWORD	dwSizeFL, dwSizeFR, dwSizeRL, dwSizeRR, dwSizeFC;
	char	*pDataFL, *pDataFR, *pDataRL, *pDataRR, *pDataFC;

	dwSizeFC = 0;		// compiler warning
	pDataFC = NULL;

	if ( IsUsingBufferPerSpeaker() )
	{
		int		SURROUNDreps;
		HRESULT	SURROUNDhresult;
		SURROUNDreps = 0;

		if ( !pDSBufFL && !pDSBufFR && !pDSBufRL && !pDSBufRR && !pDSBufFC )
			return;

		while ((SURROUNDhresult = pDSBufFL->Lock(0, m_bufferSizeBytes, (void**)&pDataFL, &dwSizeFL, NULL, NULL, 0)) != DS_OK)
		{
			if (SURROUNDhresult != DSERR_BUFFERLOST)
			{
				Msg ("S_ClearBuffer: DS::Lock FL Sound Buffer Failed\n");
				S_Shutdown ();
				return;
			}

			if (++SURROUNDreps > 10000)
			{
				Msg ("S_ClearBuffer: DS: couldn't restore FL buffer\n");
				S_Shutdown ();
				return;
			}
		}

		SURROUNDreps = 0;
		while ((SURROUNDhresult = pDSBufFR->Lock(0, m_bufferSizeBytes, (void**)&pDataFR, &dwSizeFR, NULL, NULL, 0)) != DS_OK)
		{
			if (SURROUNDhresult != DSERR_BUFFERLOST)
			{
				Msg ("S_ClearBuffer: DS::Lock FR Sound Buffer Failed\n");
				S_Shutdown ();
				return;
			}

			if (++SURROUNDreps > 10000)
			{
				Msg ("S_ClearBuffer: DS: couldn't restore FR buffer\n");
				S_Shutdown ();
				return;
			}
		}

		SURROUNDreps = 0;
		while ((SURROUNDhresult = pDSBufRL->Lock(0, m_bufferSizeBytes, (void**)&pDataRL, &dwSizeRL, NULL, NULL, 0)) != DS_OK)
		{
			if (SURROUNDhresult != DSERR_BUFFERLOST)
			{
				Msg ("S_ClearBuffer: DS::Lock RL Sound Buffer Failed\n");
				S_Shutdown ();
				return;
			}

			if (++SURROUNDreps > 10000)
			{
				Msg ("S_ClearBuffer: DS: couldn't restore RL buffer\n");
				S_Shutdown ();
				return;
			}
		}

		SURROUNDreps = 0;
		while ((SURROUNDhresult = pDSBufRR->Lock(0, m_bufferSizeBytes, (void**)&pDataRR, &dwSizeRR, NULL, NULL, 0)) != DS_OK)
		{
			if (SURROUNDhresult != DSERR_BUFFERLOST)
			{
				Msg ("S_ClearBuffer: DS::Lock RR Sound Buffer Failed\n");
				S_Shutdown ();
				return;
			}

			if (++SURROUNDreps > 10000)
			{
				Msg ("S_ClearBuffer: DS: couldn't restore RR buffer\n");
				S_Shutdown ();
				return;
			}
		}

		if (m_bSurroundCenter)
		{
			SURROUNDreps = 0;
			while ((SURROUNDhresult = pDSBufFC->Lock(0, m_bufferSizeBytes, (void**)&pDataFC, &dwSizeFC, NULL, NULL, 0)) != DS_OK)
			{
				if (SURROUNDhresult != DSERR_BUFFERLOST)
				{
					Msg ("S_ClearBuffer: DS::Lock FC Sound Buffer Failed\n");
					S_Shutdown ();
					return;
				}

				if (++SURROUNDreps > 10000)
				{
					Msg ("S_ClearBuffer: DS: couldn't restore FC buffer\n");
					S_Shutdown ();
					return;
				}
			}
		}

		Q_memset(pDataFL, 0, m_bufferSizeBytes);
		Q_memset(pDataFR, 0, m_bufferSizeBytes);
		Q_memset(pDataRL, 0, m_bufferSizeBytes);
		Q_memset(pDataRR, 0, m_bufferSizeBytes);

		if (m_bSurroundCenter)
			Q_memset(pDataFC, 0, m_bufferSizeBytes);

		pDSBufFL->Unlock(pDataFL, dwSizeFL, NULL, 0);
		pDSBufFR->Unlock(pDataFR, dwSizeFR, NULL, 0);
		pDSBufRL->Unlock(pDataRL, dwSizeRL, NULL, 0);
		pDSBufRR->Unlock(pDataRR, dwSizeRR, NULL, 0);

		if (m_bSurroundCenter)
			pDSBufFC->Unlock(pDataFC, dwSizeFC, NULL, 0);

		return;
	}
		
	if ( !pDSBuf )
		return;

	if ( DeviceSampleBits() == 8 )
		clear = 0x80;
	else
		clear = 0;

	if (pDSBuf)
	{
		DWORD	dwSize;
		DWORD	*pData;
		int		reps;
		HRESULT	hresult;

		reps = 0;
		while ((hresult = pDSBuf->Lock(0, m_bufferSizeBytes, (void**)&pData, &dwSize, NULL, NULL, 0)) != DS_OK)
		{
			if (hresult != DSERR_BUFFERLOST)
			{
				Msg("S_ClearBuffer: DS::Lock Sound Buffer Failed\n");
				S_Shutdown();
				return;
			}

			if (++reps > 10000)
			{
				Msg("S_ClearBuffer: DS: couldn't restore buffer\n");
				S_Shutdown();
				return;
			}
		}

		Q_memset(pData, clear, dwSize);

		pDSBuf->Unlock(pData, dwSize, NULL, 0);
	}
}

void CAudioDirectSound::StopAllSounds( void )
{
}

bool CAudioDirectSound::SNDDMA_InitInterleaved( LPDIRECTSOUND lpDS, WAVEFORMATEX* lpFormat, int channelCount )
{
	WAVEFORMATEXTENSIBLE    wfx = { 0 } ;     // DirectSoundBuffer wave format (extensible)

    // set the channel mask and number of channels based on the command line parameter
    if(channelCount == 2)
    {
        wfx.Format.nChannels = 2;
        wfx.dwChannelMask = KSAUDIO_SPEAKER_STEREO;   // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
    }
    else if(channelCount == 4)
    {
        wfx.Format.nChannels = 4;
        wfx.dwChannelMask = KSAUDIO_SPEAKER_QUAD;     // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
    }
    else if(channelCount == 6)
    {
        wfx.Format.nChannels = 6;
        wfx.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;  // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
    }
    else
    {
        return false;
    }

    // setup the extensible structure
    wfx.Format.wFormatTag             = WAVE_FORMAT_EXTENSIBLE; 
  //wfx.Format.nChannels              = SET ABOVE 
    wfx.Format.nSamplesPerSec         = lpFormat->nSamplesPerSec;
    wfx.Format.wBitsPerSample         = lpFormat->wBitsPerSample; 
    wfx.Format.nBlockAlign            = wfx.Format.wBitsPerSample / 8 * wfx.Format.nChannels;
    wfx.Format.nAvgBytesPerSec        = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
    wfx.Format.cbSize                 = 22; // size from after this to end of extensible struct. sizeof(WORD + DWORD + GUID)
    wfx.Samples.wValidBitsPerSample   = lpFormat->wBitsPerSample;
  //wfx.dwChannelMask                 = SET ABOVE BASED ON COMMAND LINE PARAMETERS
    wfx.SubFormat                     = KSDATAFORMAT_SUBTYPE_PCM;

    // setup the DirectSound
    DSBUFFERDESC            dsbdesc = { 0 };  // DirectSoundBuffer descriptor	
    dsbdesc.dwSize = sizeof(DSBUFFERDESC);
	dsbdesc.dwFlags = 0;

    dsbdesc.dwBufferBytes = SECONDARY_BUFFER_SIZE_SURROUND * channelCount;
 
    dsbdesc.lpwfxFormat = (WAVEFORMATEX*)&wfx;
	bool bSuccess = false;
	for ( int i = 0; i < 3; i++ )
	{
		switch(i)
		{
		case 0:
			dsbdesc.dwFlags = DSBCAPS_LOCHARDWARE;
			break;
		case 1:
			dsbdesc.dwFlags = DSBCAPS_LOCSOFTWARE;
			break;
		case 2:
			dsbdesc.dwFlags = 0;
			break;
		}
		if ( !snd_mute_losefocus.GetBool() )
		{
			dsbdesc.dwFlags |= DSBCAPS_GLOBALFOCUS;
		}

		if(!FAILED(lpDS->CreateSoundBuffer(&dsbdesc, &pDSBuf, NULL)))
		{
			bSuccess = true;
			break;
		}
	}
	if ( !bSuccess )
		return false;

	DWORD dwSize = 0, dwWrite;
	DWORD *pBuffer = 0;
	if ( !LockDSBuffer( pDSBuf, &pBuffer, &dwSize, "DS_INTERLEAVED", DSBLOCK_ENTIREBUFFER ) )
		return false;

	m_deviceChannels = wfx.Format.nChannels;
	m_deviceSampleBits = wfx.Format.wBitsPerSample;
	m_deviceDmaSpeed = wfx.Format.nSamplesPerSec;
	m_bufferSizeBytes = dsbdesc.dwBufferBytes;
	m_isInterleaved = true;

	Q_memset( pBuffer, 0, dwSize );

	pDSBuf->Unlock(pBuffer, dwSize, NULL, 0);
	
	// Make sure mixer is active (this was moved after the zeroing to avoid popping on startup -- at least when using the dx9.0b debug .dlls)
	pDSBuf->Play(0, 0, DSBPLAY_LOOPING);

	pDSBuf->Stop();
	pDSBuf->GetCurrentPosition(&m_outputBufferStartOffset, &dwWrite);

	pDSBuf->Play(0, 0, DSBPLAY_LOOPING);

	return true;
}

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

Direct-Sound support
==================
*/
sndinitstat CAudioDirectSound::SNDDMA_InitDirect( void )
{
	DSBUFFERDESC	dsbuf;
	DSBCAPS			dsbcaps;
	DWORD			dwSize, dwWrite;
	WAVEFORMATEX	format;
	WAVEFORMATEX	pformat; 
	HRESULT			hresult;
	void			*lpData = NULL;
	bool			primary_format_set = false;
	int				pri_channels = 2;
	
	if (!m_hInstDS)
	{
		m_hInstDS = LoadLibrary("dsound.dll");
		if (m_hInstDS == NULL)
		{
			Warning( "Couldn't load dsound.dll\n");
			return SIS_FAILURE;
		}

		pDirectSoundCreate = (long (__stdcall *)(struct _GUID *,struct IDirectSound ** ,struct IUnknown *))GetProcAddress(m_hInstDS,"DirectSoundCreate");
		if (!pDirectSoundCreate)
		{
			Warning( "Couldn't get DS proc addr\n");
			return SIS_FAILURE;
		}
	}

	while ((hresult = pDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK)
	{
		if (hresult != DSERR_ALLOCATED)
		{
			DevMsg ("DirectSound create failed\n");
			return SIS_FAILURE;
		}

		return SIS_NOTAVAIL;
	}

	// get snd_surround value from window settings
	DetectWindowsSpeakerSetup();

	m_bSurround = false;
	m_bSurroundCenter = false;
	m_bHeadphone = false;
	m_isInterleaved = false;

	switch ( snd_surround.GetInt() )
	{
	case 0:
		m_bHeadphone = true;	// stereo headphone
		pri_channels = 2;		// primary buffer mixes stereo input data
		break;
	default:
	case 2:
		pri_channels = 2;		// primary buffer mixes stereo input data
		break;					// no surround
	case 4:
		m_bSurround = true;		// quad surround
		pri_channels = 1;		// primary buffer mixes 3d mono input data
		break;
	case 5:
	case 7:
		m_bSurround = true;		// 5.1 surround
		m_bSurroundCenter = true;
		pri_channels = 1;		// primary buffer mixes 3d mono input data
		break;
	}

	m_deviceChannels   = pri_channels;		// secondary buffers should have same # channels as primary
	m_deviceSampleBits = 16;				// hardware bits per sample
	m_deviceDmaSpeed   = SOUND_DMA_SPEED;	// hardware playback rate

	Q_memset( &format, 0, sizeof(format) );
	format.wFormatTag		= WAVE_FORMAT_PCM;
    format.nChannels		= pri_channels;			
    format.wBitsPerSample	= m_deviceSampleBits;
    format.nSamplesPerSec	= m_deviceDmaSpeed;
    format.nBlockAlign		= format.nChannels * format.wBitsPerSample / 8;
    format.cbSize			= 0;
    format.nAvgBytesPerSec	= format.nSamplesPerSec * format.nBlockAlign; 

	DSCAPS dscaps;
	Q_memset( &dscaps, 0, sizeof(dscaps) );
	dscaps.dwSize = sizeof(dscaps);
	if (DS_OK != pDS->GetCaps(&dscaps))
	{
		Warning( "Couldn't get DS caps\n");
	}

	if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
	{
		Warning( "No DirectSound driver installed\n");
		Shutdown();
		return SIS_FAILURE;
	}

	if (DS_OK != pDS->SetCooperativeLevel(*pmainwindow, DSSCL_EXCLUSIVE))
	{
		Warning( "Set coop level failed\n");
		Shutdown();
		return SIS_FAILURE;
	}

	// get access to the primary buffer, if possible, so we can set the
	// sound hardware format
	Q_memset( &dsbuf, 0, sizeof(dsbuf) );
	dsbuf.dwSize = sizeof(DSBUFFERDESC);
	dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
	if ( snd_legacy_surround.GetBool() || m_bSurround )
	{
		dsbuf.dwFlags |= DSBCAPS_CTRL3D;
	}
	dsbuf.dwBufferBytes = 0;
	dsbuf.lpwfxFormat = NULL;

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

	if ( !CommandLine()->CheckParm("-snoforceformat"))
	{
		if (DS_OK == pDS->CreateSoundBuffer(&dsbuf, &pDSPBuf, NULL))
		{
			pformat = format;

			if (DS_OK != pDSPBuf->SetFormat(&pformat))
			{
				if (snd_firsttime)
					DevMsg ("Set primary sound buffer format: no\n");
			}
			else
			{
				if (snd_firsttime)
					DevMsg ("Set primary sound buffer format: yes\n");

				primary_format_set = true;
			}
		}
	}

	if ( m_bSurround )
	{
		// try to init surround
		m_bSurround = false;
		if ( snd_legacy_surround.GetBool() )
		{
			if (snd_surround.GetInt() == 4) 
			{
				// attempt to init 4 channel surround
				m_bSurround = SNDDMA_InitSurround(pDS, &format, &dsbcaps, 4);
			}
			else if (snd_surround.GetInt() == 5 || snd_surround.GetInt() == 7) 
			{
				// attempt to init 5 channel surround
				m_bSurroundCenter = SNDDMA_InitSurround(pDS, &format, &dsbcaps, 5);
				m_bSurround = m_bSurroundCenter;
			}
		}
		if ( !m_bSurround )
		{
			pri_channels = 6;
			if ( snd_surround.GetInt() < 5 )
			{
				pri_channels = 4;
			}
	
			m_bSurround = SNDDMA_InitInterleaved( pDS, &format, pri_channels );
		}
	}

	if ( !m_bSurround )
	{
		// snd_surround.SetValue( 0 );
		if ( !primary_format_set || !CommandLine()->CheckParm ("-primarysound") )
		{
			// create the secondary buffer we'll actually work with
			Q_memset( &dsbuf, 0, sizeof(dsbuf) );
			dsbuf.dwSize = sizeof(DSBUFFERDESC);
			dsbuf.dwFlags = DSBCAPS_LOCSOFTWARE;		// NOTE: don't use CTRLFREQUENCY (slow)
			dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
			dsbuf.lpwfxFormat = &format;
			if ( !snd_mute_losefocus.GetBool() )
			{
				dsbuf.dwFlags |= DSBCAPS_GLOBALFOCUS;
			}

			if (DS_OK != pDS->CreateSoundBuffer(&dsbuf, &pDSBuf, NULL))
			{
				Warning( "DS:CreateSoundBuffer Failed");
				Shutdown();
				return SIS_FAILURE;
			}

			m_deviceChannels   = format.nChannels;
			m_deviceSampleBits = format.wBitsPerSample;
			m_deviceDmaSpeed   = format.nSamplesPerSec;

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

			if (DS_OK != pDSBuf->GetCaps( &dsbcaps ))
			{
				Warning( "DS:GetCaps failed\n");
				Shutdown();
				return SIS_FAILURE;
			}

			if ( snd_firsttime )
				DevMsg ("Using secondary sound buffer\n");
		}
		else
		{
			if (DS_OK != pDS->SetCooperativeLevel(*pmainwindow, DSSCL_WRITEPRIMARY))
			{
				Warning( "Set coop level failed\n");
				Shutdown();
				return SIS_FAILURE;
			}

			Q_memset(&dsbcaps, 0, sizeof(dsbcaps));
			dsbcaps.dwSize = sizeof(dsbcaps);
			if (DS_OK != pDSPBuf->GetCaps(&dsbcaps))
			{
				Msg ("DS:GetCaps failed\n");
				return SIS_FAILURE;
			}

			pDSBuf = pDSPBuf;
			DevMsg ("Using primary sound buffer\n");
		}

		if ( snd_firsttime )
		{
			DevMsg("   %d channel(s)\n"
						   "   %d bits/sample\n"
						   "   %d samples/sec\n",
						   DeviceChannels(), DeviceSampleBits(), DeviceDmaSpeed());
		}

		// initialize the buffer
		m_bufferSizeBytes = dsbcaps.dwBufferBytes;
		int reps = 0;
		while ((hresult = pDSBuf->Lock(0, m_bufferSizeBytes, (void**)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
		{
			if (hresult != DSERR_BUFFERLOST)
			{
				Warning( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n");
				Shutdown();
				return SIS_FAILURE;
			}

			if (++reps > 10000)
			{
				Warning( "SNDDMA_InitDirect: DS: couldn't restore buffer\n");
				Shutdown();
				return SIS_FAILURE;
			}
		}

		Q_memset( lpData, 0, dwSize );
		pDSBuf->Unlock(lpData, dwSize, NULL, 0);
		
		// Make sure mixer is active (this was moved after the zeroing to avoid popping on startup -- at least when using the dx9.0b debug .dlls)
		pDSBuf->Play(0, 0, DSBPLAY_LOOPING);

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

		pDSBuf->Stop();

		pDSBuf->GetCurrentPosition(&m_outputBufferStartOffset, &dwWrite);

		pDSBuf->Play(0, 0, DSBPLAY_LOOPING);
	}

	// number of mono samples output buffer may hold
	m_deviceSampleCount = m_bufferSizeBytes/(DeviceSampleBytes());

	return SIS_SUCCESS;

}

static DWORD GetSpeakerConfigForSurroundMode( int surroundMode, const char **pConfigDesc )
{
	DWORD newSpeakerConfig = DSSPEAKER_STEREO;
	const char *speakerConfigDesc = "";

	switch ( surroundMode )
	{
	case 0:
		newSpeakerConfig = DSSPEAKER_HEADPHONE;
		speakerConfigDesc = "headphone";
		break;

	case 2:
	default:
		newSpeakerConfig = DSSPEAKER_STEREO;
		speakerConfigDesc = "stereo speaker";
		break;

	case 4:
		newSpeakerConfig = DSSPEAKER_QUAD;
		speakerConfigDesc = "quad speaker";
		break;

	case 5:
		newSpeakerConfig = DSSPEAKER_5POINT1;
		speakerConfigDesc = "5.1 speaker";
		break;

	case 7:
		newSpeakerConfig = DSSPEAKER_7POINT1;
		speakerConfigDesc = "7.1 speaker";
		break;
	}
	if ( pConfigDesc )
	{
		*pConfigDesc = speakerConfigDesc;
	}
	return newSpeakerConfig;
}

// Read the speaker config from windows
static DWORD GetWindowsSpeakerConfig()
{
	DWORD speaker_config = windows_speaker_config.GetInt();
	if ( windows_speaker_config.GetInt() < 0 )
	{
		speaker_config = DSSPEAKER_STEREO;
		if (DS_OK == pDS->GetSpeakerConfig( &speaker_config ))
		{
			// split out settings
			speaker_config = DSSPEAKER_CONFIG(speaker_config);
			if ( speaker_config == DSSPEAKER_7POINT1_SURROUND )
				speaker_config = DSSPEAKER_7POINT1;
			if ( speaker_config == DSSPEAKER_5POINT1_SURROUND)
				speaker_config = DSSPEAKER_5POINT1;
		}
		windows_speaker_config.SetValue((int)speaker_config);
	}

	return speaker_config;
}

// Writes snd_surround convar given a directsound speaker config
static void SetSurroundModeFromSpeakerConfig( DWORD speakerConfig )
{
	// set the cvar to be the windows setting
	switch (speakerConfig)
	{
	case DSSPEAKER_HEADPHONE:
		snd_surround.SetValue(0);
		break;

	case DSSPEAKER_MONO:
	case DSSPEAKER_STEREO:
	default:
		snd_surround.SetValue( 2 );
		break;

	case DSSPEAKER_QUAD:
		snd_surround.SetValue(4);
		break;

	case DSSPEAKER_5POINT1:
		snd_surround.SetValue(5);
		break;

	case DSSPEAKER_7POINT1:
		snd_surround.SetValue(7);
		break;
	}
}
/*
 Sets the snd_surround_speakers cvar based on the windows setting
*/

void CAudioDirectSound::DetectWindowsSpeakerSetup()
{
	// detect speaker settings from windows
	DWORD speaker_config = GetWindowsSpeakerConfig();
	SetSurroundModeFromSpeakerConfig(speaker_config);

	// DEBUG
	if (speaker_config == DSSPEAKER_MONO)
		DevMsg( "DS:mono configuration detected\n");

	if (speaker_config == DSSPEAKER_HEADPHONE)
		DevMsg( "DS:headphone configuration detected\n");

	if (speaker_config == DSSPEAKER_STEREO)
		DevMsg( "DS:stereo speaker configuration detected\n");

	if (speaker_config == DSSPEAKER_QUAD)
		DevMsg( "DS:quad speaker configuration detected\n");

	if (speaker_config == DSSPEAKER_SURROUND)
		DevMsg( "DS:surround speaker configuration detected\n");

	if (speaker_config == DSSPEAKER_5POINT1)
		DevMsg( "DS:5.1 speaker configuration detected\n");

	if (speaker_config == DSSPEAKER_7POINT1)
		DevMsg( "DS:7.1 speaker configuration detected\n");
}

/*
 Updates windows settings based on snd_surround_speakers cvar changing
 This should only happen if the user has changed it via the console or the UI
 Changes won't take effect until the engine has restarted
*/
void OnSndSurroundCvarChanged( IConVar *pVar, const char *pOldString, float flOldValue )
{
	// if the old value is -1, we're setting this from the detect routine for the first time
	// no need to reset the device
	if (!pDS || flOldValue == -1 )
		return;

	// get the user's previous speaker config
	DWORD speaker_config = GetWindowsSpeakerConfig();

	// get the new config
	DWORD newSpeakerConfig = 0;
	const char *speakerConfigDesc = "";

	ConVarRef var( pVar );
	newSpeakerConfig = GetSpeakerConfigForSurroundMode( var.GetInt(), &speakerConfigDesc );
	// make sure the config has changed
	if (newSpeakerConfig == speaker_config)
		return;

	// set new configuration
	windows_speaker_config.SetValue( (int)newSpeakerConfig );

	Msg("Speaker configuration has been changed to %s.\n", speakerConfigDesc);

	// restart sound system so it takes effect
	g_pSoundServices->RestartSoundSystem();
}

void OnSndSurroundLegacyChanged( IConVar *pVar, const char *pOldString, float flOldValue )
{
	if ( pDS && CAudioDirectSound::m_pSingleton )
	{
		ConVarRef var( pVar );
		// should either be interleaved or have legacy surround set, not both
		if ( CAudioDirectSound::m_pSingleton->IsInterleaved() == var.GetBool() )
		{
			Msg( "Legacy Surround %s.\n", var.GetBool() ? "enabled" : "disabled" );
			// restart sound system so it takes effect
			g_pSoundServices->RestartSoundSystem();
		}
	}
}

void OnSndVarChanged( IConVar *pVar, const char *pOldString, float flOldValue )
{
	ConVarRef var(pVar);
	// restart sound system so the change takes effect
	if ( var.GetInt() != int(flOldValue) )
	{
		g_pSoundServices->RestartSoundSystem();
	}
}

/*
 Release all Surround buffer pointers
*/
void ReleaseSurround(void)
{
	if ( pDSBuf3DFL != NULL )
	{
		pDSBuf3DFL->Release();
		pDSBuf3DFL = NULL;
	}

	if ( pDSBuf3DFR != NULL)
	{
		pDSBuf3DFR->Release();
		pDSBuf3DFR = NULL;
	}

	if ( pDSBuf3DRL != NULL )
	{
		pDSBuf3DRL->Release();
		pDSBuf3DRL = NULL;
	}

	if ( pDSBuf3DRR != NULL )
	{	
		pDSBuf3DRR->Release();
		pDSBuf3DRR = NULL;
	}

	if ( pDSBufFL != NULL )
	{
		pDSBufFL->Release();
		pDSBufFL = NULL;
	}

	if ( pDSBufFR != NULL )
	{
		pDSBufFR->Release();
		pDSBufFR = NULL;
	}

	if ( pDSBufRL != NULL )
	{
		pDSBufRL->Release();
		pDSBufRL = NULL;
	}

	if ( pDSBufRR != NULL )
	{
		pDSBufRR->Release();
		pDSBufRR = NULL;
	}

	if ( pDSBufFC != NULL )
	{
		pDSBufFC->Release();
		pDSBufFC = NULL;
	}
}

void DEBUG_DS_FillSquare( void *lpData, DWORD dwSize )
{
	short *lpshort = (short *)lpData;
	DWORD j = min((DWORD)10000, dwSize/2);
 
	for (DWORD i = 0; i < j; i++)
		lpshort[i] = 8000;
}

void DEBUG_DS_FillSquare2( void *lpData, DWORD dwSize )
{
	short *lpshort = (short *)lpData;
	DWORD j = min((DWORD)1000, dwSize/2);
 
	for (DWORD i = 0; i < j; i++)
		lpshort[i] = 16000;
}

// helper to set default buffer params
void DS3D_SetBufferParams( LPDIRECTSOUND3DBUFFER pDSBuf3D, D3DVECTOR *pbpos, D3DVECTOR *pbdir )
{
	DS3DBUFFER bparm;
	D3DVECTOR bvel;
	D3DVECTOR bpos, bdir;
	HRESULT hr;
	
	bvel.x = 0.0f; bvel.y = 0.0f; bvel.z = 0.0f;
	bpos = *pbpos;
	bdir = *pbdir;

	bparm.dwSize = sizeof(DS3DBUFFER);

	hr = pDSBuf3D->GetAllParameters( &bparm );

	bparm.vPosition = bpos;
	bparm.vVelocity = bvel;
	bparm.dwInsideConeAngle = 5.0;						// narrow cones for each speaker
	bparm.dwOutsideConeAngle = 10.0;	
	bparm.vConeOrientation = bdir;
	bparm.lConeOutsideVolume = DSBVOLUME_MIN;
	bparm.flMinDistance = 100.0;		// no rolloff (until > 2.0 meter distance)
	bparm.flMaxDistance = DS3D_DEFAULTMAXDISTANCE;
	bparm.dwMode = DS3DMODE_NORMAL;

	hr = pDSBuf3D->SetAllParameters( &bparm, DS3D_DEFERRED );
}

// Initialization for Surround sound support (4 channel or 5 channel). 
// Creates 4 or 5 mono 3D buffers to be used as Front Left, (Front Center), Front Right, Rear Left, Rear Right
bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND lpDS, WAVEFORMATEX* lpFormat, DSBCAPS* lpdsbc, int cchan)
{
	DSBUFFERDESC	dsbuf;
	WAVEFORMATEX wvex;
	DWORD dwSize, dwWrite;
	int reps;
	HRESULT hresult;
	void			*lpData = NULL;

	if ( lpDS == NULL ) return FALSE;
 
	// Force format to mono channel

	memcpy(&wvex, lpFormat, sizeof(WAVEFORMATEX));
	wvex.nChannels = 1;
	wvex.nBlockAlign = wvex.nChannels * wvex.wBitsPerSample / 8;
	wvex.nAvgBytesPerSec = wvex.nSamplesPerSec	* wvex.nBlockAlign; 

	memset (&dsbuf, 0, sizeof(dsbuf));
	dsbuf.dwSize = sizeof(DSBUFFERDESC);
														 // NOTE: LOCHARDWARE causes SB AWE64 to crash in it's DSOUND driver
	dsbuf.dwFlags = DSBCAPS_CTRL3D;						 // don't use CTRLFREQUENCY (slow)
	if ( !snd_mute_losefocus.GetBool() )
	{
		dsbuf.dwFlags |= DSBCAPS_GLOBALFOCUS;
	}

	// reserve space for each buffer

	dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE_SURROUND;	

	dsbuf.lpwfxFormat = &wvex;

	// create 4 mono buffers FL, FR, RL, RR

	if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &pDSBufFL, NULL))
	{
		Warning( "DS:CreateSoundBuffer for 3d front left failed");
		ReleaseSurround();
		return FALSE;
	}

	if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &pDSBufFR, NULL))
	{
		Warning( "DS:CreateSoundBuffer for 3d front right failed");
		ReleaseSurround();
		return FALSE;
	}

	if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &pDSBufRL, NULL))
	{
		Warning( "DS:CreateSoundBuffer for 3d rear left failed");
		ReleaseSurround();
		return FALSE;
	}

	if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &pDSBufRR, NULL))
	{
		Warning( "DS:CreateSoundBuffer for 3d rear right failed");
		ReleaseSurround();
		return FALSE;
	}

	// create center channel

	if (cchan == 5)
	{
		if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &pDSBufFC, NULL))
		{
			Warning( "DS:CreateSoundBuffer for 3d front center failed");
			ReleaseSurround();
			return FALSE;
		}
	}

	// Try to get 4 or 5 3D buffers from the mono DS buffers

	if (DS_OK != pDSBufFL->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DFL))
	{
		Warning( "DS:Query 3DBuffer for 3d front left failed");
		ReleaseSurround();
		return FALSE;
	}

	if (DS_OK != pDSBufFR->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DFR))
	{
		Warning( "DS:Query 3DBuffer for 3d front right failed");
		ReleaseSurround();
		return FALSE;
	}

	if (DS_OK != pDSBufRL->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DRL))
	{
		Warning( "DS:Query 3DBuffer for 3d rear left failed");
		ReleaseSurround();
		return FALSE;
	}

	if (DS_OK != pDSBufRR->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DRR))
	{
		Warning( "DS:Query 3DBuffer for 3d rear right failed");
		ReleaseSurround();
		return FALSE;
	}

	if (cchan == 5)
	{
		if (DS_OK != pDSBufFC->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DFC)) 
		{
			Warning( "DS:Query 3DBuffer for 3d front center failed");
			ReleaseSurround();
			return FALSE;
		}
	}

	// set listener position & orientation.
	// DS uses left handed coord system: +x is right, +y is up, +z is forward

	HRESULT hr;

	IDirectSound3DListener *plistener = NULL;
	
	hr = pDSPBuf->QueryInterface(IID_IDirectSound3DListener, (void**)&plistener);
	if (plistener)
	{
		DS3DLISTENER lparm;
		lparm.dwSize = sizeof(DS3DLISTENER);

		hr = plistener->GetAllParameters( &lparm );	

		hr = plistener->SetOrientation( 0.0f,0.0f,1.0f, 0.0f,1.0f,0.0f, DS3D_IMMEDIATE); // frontx,y,z topx,y,z
		hr = plistener->SetPosition(0.0f, 0.0f, 0.0f, DS3D_IMMEDIATE);	
	}
	else
	{
		Warning( "DS: failed to get 3D listener interface.");
		ReleaseSurround();
		return FALSE;
	}

	// set 3d buffer position and orientation params

	D3DVECTOR bpos, bdir;

	bpos.x = -1.0; bpos.y = 0.0; bpos.z = 1.0;				// FL
	bdir.x =  1.0; bdir.y = 0.0; bdir.z = -1.0;
	
	DS3D_SetBufferParams( pDSBuf3DFL, &bpos, &bdir );
	
	bpos.x = 1.0; bpos.y = 0.0; bpos.z = 1.0;				// FR
	bdir.x = -1.0; bdir.y = 0.0; bdir.z = -1.0;
	
	DS3D_SetBufferParams( pDSBuf3DFR, &bpos, &bdir );

	bpos.x = -1.0; bpos.y = 0.0; bpos.z = -1.0;				// RL
	bdir.x = 1.0; bdir.y = 0.0; bdir.z = 1.0;
	
	DS3D_SetBufferParams( pDSBuf3DRL, &bpos, &bdir );

	bpos.x = 1.0; bpos.y = 0.0; bpos.z = -1.0;				// RR
	bdir.x = -1.0; bdir.y = 0.0; bdir.z = 1.0;
	
	DS3D_SetBufferParams( pDSBuf3DRR, &bpos, &bdir );

	if (cchan == 5)
	{
		bpos.x = 0.0; bpos.y = 0.0; bpos.z = 1.0;			// FC
		bdir.x = 0.0; bdir.y = 0.0; bdir.z = -1.0;
	
		DS3D_SetBufferParams( pDSBuf3DFC, &bpos, &bdir );
	}

	// commit all buffer param settings

	hr = plistener->CommitDeferredSettings();

	m_deviceChannels = 1;				// 1 mono 3d output buffer
	m_deviceSampleBits = lpFormat->wBitsPerSample;
	m_deviceDmaSpeed = lpFormat->nSamplesPerSec;

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

	if (DS_OK != pDSBufFL->GetCaps (lpdsbc))
	{
		Warning( "DS:GetCaps failed for 3d sound buffer\n");
		ReleaseSurround();
		return FALSE;
	}

	pDSBufFL->Play(0, 0, DSBPLAY_LOOPING);
	pDSBufFR->Play(0, 0, DSBPLAY_LOOPING);
	pDSBufRL->Play(0, 0, DSBPLAY_LOOPING);
	pDSBufRR->Play(0, 0, DSBPLAY_LOOPING);

	if (cchan == 5)
		pDSBufFC->Play(0, 0, DSBPLAY_LOOPING);

	if (snd_firsttime)
		DevMsg("   %d channel(s)\n"
					"   %d bits/sample\n"
					"   %d samples/sec\n",
					cchan, DeviceSampleBits(), DeviceDmaSpeed());

	m_bufferSizeBytes = lpdsbc->dwBufferBytes;

	// Test everything just like in the normal initialization.
	if (cchan == 5)
	{
		reps = 0;
		while ((hresult = pDSBufFC->Lock(0, lpdsbc->dwBufferBytes, (void**)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
		{
			if (hresult != DSERR_BUFFERLOST)
			{
				Warning( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed for FC\n");
				ReleaseSurround();
				return FALSE;
			}

			if (++reps > 10000)
			{
				Warning( "SNDDMA_InitDirect: DS: couldn't restore buffer for FC\n");
				ReleaseSurround();
				return FALSE;
			}
		}
		memset(lpData, 0, dwSize);
//		DEBUG_DS_FillSquare( lpData, dwSize );
		pDSBufFC->Unlock(lpData, dwSize, NULL, 0);
	}

	reps = 0;
	while ((hresult = pDSBufFL->Lock(0, lpdsbc->dwBufferBytes, (void**)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
	{
		if (hresult != DSERR_BUFFERLOST)
		{
			Warning( "SNDDMA_InitSurround: DS::Lock Sound Buffer Failed for 3d FL\n");
			ReleaseSurround();
			return FALSE;
		}

		if (++reps > 10000)
		{
			Warning( "SNDDMA_InitSurround: DS: couldn't restore buffer for 3d FL\n");
			ReleaseSurround();
			return FALSE;
		}
	}
	memset(lpData, 0, dwSize);
//	DEBUG_DS_FillSquare( lpData, dwSize );
	pDSBufFL->Unlock(lpData, dwSize, NULL, 0);

	reps = 0;
	while ((hresult = pDSBufFR->Lock(0, lpdsbc->dwBufferBytes, (void**)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
	{
		if (hresult != DSERR_BUFFERLOST)
		{
			Warning( "SNDDMA_InitSurround: DS::Lock Sound Buffer Failed for 3d FR\n");
			ReleaseSurround();
			return FALSE;
		}

		if (++reps > 10000)
		{
			Warning( "SNDDMA_InitSurround: DS: couldn't restore buffer for FR\n");
			ReleaseSurround();
			return FALSE;
		}
	}
	memset(lpData, 0, dwSize);
//	DEBUG_DS_FillSquare( lpData, dwSize );
	pDSBufFR->Unlock(lpData, dwSize, NULL, 0);

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

		if (++reps > 10000)
		{
			Warning( "SNDDMA_InitDirect: DS: couldn't restore buffer for RL\n");
			ReleaseSurround();
			return FALSE;
		}
	}
	memset(lpData, 0, dwSize);
//	DEBUG_DS_FillSquare( lpData, dwSize );
	pDSBufRL->Unlock(lpData, dwSize, NULL, 0);

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

		if (++reps > 10000)
		{
			Warning( "SNDDMA_InitDirect: DS: couldn't restore buffer for RR\n");
			ReleaseSurround();
			return FALSE;
		}
	}
	memset(lpData, 0, dwSize);
//	DEBUG_DS_FillSquare( lpData, dwSize );
	pDSBufRR->Unlock(lpData, dwSize, NULL, 0);

	lpData = NULL; // this is invalid now

	// OK Stop and get our positions and were good to go.
	pDSBufFL->Stop();
	pDSBufFR->Stop();
	pDSBufRL->Stop();
	pDSBufRR->Stop();
	if (cchan == 5) 
		pDSBufFC->Stop();

	// get hardware playback position, store it, syncronize all buffers to FL

	pDSBufFL->GetCurrentPosition(&m_outputBufferStartOffset, &dwWrite);
	pDSBufFR->SetCurrentPosition(m_outputBufferStartOffset);
	pDSBufRL->SetCurrentPosition(m_outputBufferStartOffset);
	pDSBufRR->SetCurrentPosition(m_outputBufferStartOffset);
	if (cchan == 5) 
		pDSBufFC->SetCurrentPosition(m_outputBufferStartOffset);

	pDSBufFL->Play(0, 0, DSBPLAY_LOOPING);
	pDSBufFR->Play(0, 0, DSBPLAY_LOOPING);
	pDSBufRL->Play(0, 0, DSBPLAY_LOOPING);
	pDSBufRR->Play(0, 0, DSBPLAY_LOOPING);
	if (cchan == 5) 
		pDSBufFC->Play(0, 0, DSBPLAY_LOOPING);

	if (snd_firsttime)
		Warning( "3d surround sound initialization successful\n");

	return TRUE;
}

void CAudioDirectSound::UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up )
{
}

void CAudioDirectSound::ChannelReset( int entnum, int channelIndex, float distanceMod )
{
}

const char *CAudioDirectSound::DeviceName( void )
{ 
	if ( m_bSurroundCenter )
		return "5 Channel Surround";

	if ( m_bSurround )
		return "4 Channel Surround";

	return "Direct Sound"; 
}

// use the partial buffer locking code in stereo as well - not available when recording a movie
ConVar snd_lockpartial("snd_lockpartial","1");

// Transfer up to a full paintbuffer (PAINTBUFFER_SIZE) of stereo samples
// out to the directsound secondary buffer(s).
// For 4 or 5 ch surround, there are 4 or 5 mono 16 bit secondary DS streaming buffers.
// For stereo speakers, there is one stereo 16 bit secondary DS streaming buffer.

void CAudioDirectSound::TransferSamples( int end )
{
	int		lpaintedtime = g_paintedtime;
	int		endtime = end;
	
	// When Surround is enabled, divert to 4 or 5 chan xfer scheme.
	if ( m_bSurround )
	{		
		if ( m_isInterleaved )
		{
			S_TransferSurround16Interleaved( PAINTBUFFER, REARPAINTBUFFER, CENTERPAINTBUFFER, lpaintedtime, endtime);
		}
		else
		{
			int	cchan = ( m_bSurroundCenter ? 5 : 4);

			S_TransferSurround16( PAINTBUFFER, REARPAINTBUFFER, CENTERPAINTBUFFER, lpaintedtime, endtime, cchan);
		}
		return;
	}
	else if ( snd_lockpartial.GetBool() && DeviceChannels() == 2 && DeviceSampleBits() == 16 && !SND_IsRecording() )
	{
		S_TransferSurround16Interleaved( PAINTBUFFER, NULL, NULL, lpaintedtime, endtime );
	}
	else
	{
		DWORD *pBuffer = NULL;
		DWORD dwSize = 0;
		if ( !LockDSBuffer( pDSBuf, &pBuffer, &dwSize, "DS_STEREO" ) )
		{
			S_Shutdown();
			S_Startup();
			return;
		}
		if ( pBuffer )
		{
			if ( DeviceChannels() == 2 && DeviceSampleBits() == 16 )
			{
				S_TransferStereo16( pBuffer, PAINTBUFFER, lpaintedtime, endtime );
			}
			else
			{
				// UNDONE: obsolete - no 8 bit mono output supported
				S_TransferPaintBuffer( pBuffer, PAINTBUFFER, lpaintedtime, endtime );
			}
			pDSBuf->Unlock( pBuffer, dwSize, NULL, 0 );
		}
	}
}

bool CAudioDirectSound::IsUsingBufferPerSpeaker() 
{ 
	return m_bSurround && !m_isInterleaved; 
}

bool CAudioDirectSound::LockDSBuffer( LPDIRECTSOUNDBUFFER pBuffer, DWORD **pdwWriteBuffer, DWORD *pdwSizeBuffer, const char *pBufferName, int lockFlags )
{
	if ( !pBuffer )
		return false;
	HRESULT hr;
	int reps = 0;
	while ((hr = pBuffer->Lock(0, m_bufferSizeBytes, (void**)pdwWriteBuffer, pdwSizeBuffer, 
		NULL, NULL, lockFlags)) != DS_OK)
	{
		if (hr != DSERR_BUFFERLOST)
		{
			Msg ("DS::Lock Sound Buffer Failed %s\n", pBufferName);
			return false;
		}

		if (++reps > 10000)
		{
			Msg ("DS:: couldn't restore buffer %s\n", pBufferName);
			return false;
		}
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////////////////////////////
// Given front, rear and center stereo paintbuffers, split samples into 4 or 5 mono directsound buffers (FL, FC, FR, RL, RR)
void CAudioDirectSound::S_TransferSurround16( portable_samplepair_t *pfront, portable_samplepair_t *prear, portable_samplepair_t *pcenter, int lpaintedtime, int endtime, int cchan)
{
	int		lpos;
	DWORD *pdwWriteFL=NULL, *pdwWriteFR=NULL, *pdwWriteRL=NULL, *pdwWriteRR=NULL, *pdwWriteFC=NULL;
	DWORD dwSizeFL=0, dwSizeFR=0, dwSizeRL=0, dwSizeRR=0, dwSizeFC=0;
	int i, j, *snd_p, *snd_rp, *snd_cp, volumeFactor;
	short	*snd_out_fleft, *snd_out_fright, *snd_out_rleft, *snd_out_rright, *snd_out_fcenter;

	pdwWriteFC = NULL;			// compiler warning
	dwSizeFC = 0;
	snd_out_fcenter = NULL;

	volumeFactor = S_GetMasterVolume() * 256;

	// lock all 4 or 5 mono directsound buffers FL, FR, RL, RR, FC
	if ( !LockDSBuffer( pDSBufFL, &pdwWriteFL, &dwSizeFL, "FL" ) ||
		!LockDSBuffer( pDSBufFR, &pdwWriteFR, &dwSizeFR, "FR" ) ||
		!LockDSBuffer( pDSBufRL, &pdwWriteRL, &dwSizeRL, "RL" ) ||
		!LockDSBuffer( pDSBufRR, &pdwWriteRR, &dwSizeRR, "RR" ) )
	{
		S_Shutdown();
		S_Startup();
		return;
	}

	if (cchan == 5 && !LockDSBuffer( pDSBufFC, &pdwWriteFC, &dwSizeFC, "FC" ))
	{
		S_Shutdown ();
		S_Startup ();
		return;
	}

	// take stereo front and rear paintbuffers, and center paintbuffer if provided,
	// and copy samples into the 4 or 5 mono directsound buffers

	snd_rp = (int *)prear;
	snd_cp = (int *)pcenter;
	snd_p = (int *)pfront;
	
	int linearCount;							 // space in output buffer for linearCount mono samples
	int sampleMonoCount = DeviceSampleCount();	 // number of mono samples per output buffer (was;(DeviceSampleCount()>>1))
	int sampleMask = sampleMonoCount - 1;
	
	// paintedtime - number of full samples that have played since start
	// endtime - number of full samples to play to - endtime is g_soundtime + mixahead samples

	while (lpaintedtime < endtime)
	{														
		lpos = lpaintedtime & sampleMask;		// lpos is next output position in output buffer

		linearCount = sampleMonoCount - lpos;		

		// limit output count to requested number of samples

		if (linearCount > endtime - lpaintedtime)		
			linearCount = endtime - lpaintedtime;		

		snd_out_fleft = (short *)pdwWriteFL + lpos;
		snd_out_fright = (short *)pdwWriteFR + lpos;
		snd_out_rleft = (short *)pdwWriteRL + lpos;
		snd_out_rright = (short *)pdwWriteRR + lpos;

		if (cchan == 5)
			snd_out_fcenter = (short *)pdwWriteFC + lpos;

		// for 16 bit sample in the front and rear stereo paintbuffers, copy
		// into the 4 or 5 FR, FL, RL, RR, FC directsound paintbuffers

		for (i=0, j= 0 ; i<linearCount ; i++, j+=2)
		{
			snd_out_fleft[i]  = (snd_p[j]*volumeFactor)>>8;		 
			snd_out_fright[i] = (snd_p[j + 1]*volumeFactor)>>8;
			snd_out_rleft[i]  = (snd_rp[j]*volumeFactor)>>8;
			snd_out_rright[i] = (snd_rp[j + 1]*volumeFactor)>>8;
		}

		// copy front center buffer (mono) data to center chan directsound paintbuffer

		if (cchan == 5)
		{
			for (i=0, j=0 ; i<linearCount ; i++, j+=2)
			{
				snd_out_fcenter[i] = (snd_cp[j]*volumeFactor)>>8;
				
			}
		}

		snd_p += linearCount << 1;
		snd_rp += linearCount << 1;
		snd_cp += linearCount << 1;

		lpaintedtime += linearCount;
	}

	pDSBufFL->Unlock(pdwWriteFL, dwSizeFL, NULL, 0);
	pDSBufFR->Unlock(pdwWriteFR, dwSizeFR, NULL, 0);
	pDSBufRL->Unlock(pdwWriteRL, dwSizeRL, NULL, 0);
	pDSBufRR->Unlock(pdwWriteRR, dwSizeRR, NULL, 0);

	if (cchan == 5) 
		pDSBufFC->Unlock(pdwWriteFC, dwSizeFC, NULL, 0);
}

struct surround_transfer_t
{
	int paintedtime;
	int	linearCount;
	int sampleMask;
	int channelCount;
	int	*snd_p;
	int	*snd_rp;
	int *snd_cp;
	short *pOutput;
};

static void TransferSamplesToSurroundBuffer( int outputCount, surround_transfer_t &transfer )
{
	int i, j;
	int volumeFactor = S_GetMasterVolume() * 256;

	if ( transfer.channelCount == 2 )
	{
		for (i=0, j=0; i<outputCount ; i++, j+=2)
		{
			transfer.pOutput[0]  = (transfer.snd_p[j]*volumeFactor)>>8;		// FL
			transfer.pOutput[1] = (transfer.snd_p[j + 1]*volumeFactor)>>8;	// FR
			transfer.pOutput += 2;
		}
	}
	// no center channel, 4 channel surround
	else if ( transfer.channelCount == 4 )
	{
		for (i=0, j=0; i<outputCount ; i++, j+=2)
		{
			transfer.pOutput[0]  = (transfer.snd_p[j]*volumeFactor)>>8;		// FL
			transfer.pOutput[1] = (transfer.snd_p[j + 1]*volumeFactor)>>8;	// FR
			transfer.pOutput[2]  = (transfer.snd_rp[j]*volumeFactor)>>8;		// RL
			transfer.pOutput[3] = (transfer.snd_rp[j + 1]*volumeFactor)>>8;	// RR
			transfer.pOutput += 4;
			//Assert( baseOffset <= (DeviceSampleCount()) );
		}
	}
	else
	{
		Assert(transfer.snd_cp);
		// 6 channel / 5.1
		for (i=0, j=0 ; i<outputCount ; i++, j+=2)
		{
			transfer.pOutput[0]  = (transfer.snd_p[j]*volumeFactor)>>8;		// FL
			transfer.pOutput[1] = (transfer.snd_p[j + 1]*volumeFactor)>>8;	// FR

			transfer.pOutput[2]  = (transfer.snd_cp[j]*volumeFactor)>>8;		// Center

			transfer.pOutput[3]  = 0;

			transfer.pOutput[4]  = (transfer.snd_rp[j]*volumeFactor)>>8;		// RL
			transfer.pOutput[5] = (transfer.snd_rp[j + 1]*volumeFactor)>>8;	// RR
			
#if 0
			// average channels into the subwoofer, let the sub filter the output
			// NOTE: avg l/r rear to do 2 shifts instead of divide by 5
			int sumFront = (int)transfer.pOutput[0] + (int)transfer.pOutput[1] + (int)transfer.pOutput[2];
			int sumRear = (int)transfer.pOutput[4] + (int)transfer.pOutput[5];
			transfer.pOutput[3]  = (sumFront + (sumRear>>1)) >> 2;
#endif

			transfer.pOutput += 6;
			//Assert( baseOffset <= (DeviceSampleCount()) );
		}
	}

	transfer.snd_p += outputCount << 1;
	if ( transfer.snd_rp )
	{
		transfer.snd_rp += outputCount << 1;
	}
	if ( transfer.snd_cp )
	{
		transfer.snd_cp += outputCount << 1;
	}

	transfer.paintedtime += outputCount;
	transfer.linearCount -= outputCount;

}

void CAudioDirectSound::S_TransferSurround16Interleaved_FullLock( const portable_samplepair_t *pfront, const portable_samplepair_t *prear, const portable_samplepair_t *pcenter, int lpaintedtime, int endtime )
{
	int		lpos;
	DWORD *pdwWrite = NULL;
	DWORD dwSize = 0;
	int i, j, *snd_p, *snd_rp, *snd_cp, volumeFactor;

	volumeFactor = S_GetMasterVolume() * 256;
	int channelCount = m_bSurroundCenter ? 5 : 4;
	if ( DeviceChannels() == 2 )
	{
		channelCount = 2;
	}

	// lock single interleaved buffer
	if ( !LockDSBuffer( pDSBuf, &pdwWrite, &dwSize, "DS_INTERLEAVED" ) )
	{
		S_Shutdown ();
		S_Startup ();
		return;
	}

	// take stereo front and rear paintbuffers, and center paintbuffer if provided,
	// and copy samples into the 4 or 5 mono directsound buffers

	snd_rp = (int *)prear;
	snd_cp = (int *)pcenter;
	snd_p = (int *)pfront;

	int linearCount;							 // space in output buffer for linearCount mono samples
	int sampleMonoCount = m_bufferSizeBytes/(DeviceSampleBytes()*DeviceChannels());	 // number of mono samples per output buffer (was;(DeviceSampleCount()>>1))
	int sampleMask = sampleMonoCount - 1;

	// paintedtime - number of full samples that have played since start
	// endtime - number of full samples to play to - endtime is g_soundtime + mixahead samples

	short *pOutput = (short *)pdwWrite;
	while (lpaintedtime < endtime)
	{														
		lpos = lpaintedtime & sampleMask;		// lpos is next output position in output buffer

		linearCount = sampleMonoCount - lpos;		

		// limit output count to requested number of samples

		if (linearCount > endtime - lpaintedtime)		
			linearCount = endtime - lpaintedtime;		

		if ( channelCount == 4 )
		{
			int baseOffset = lpos * channelCount;
			for (i=0, j= 0 ; i<linearCount ; i++, j+=2)
			{
				pOutput[baseOffset+0]  = (snd_p[j]*volumeFactor)>>8;		// FL
				pOutput[baseOffset+1] = (snd_p[j + 1]*volumeFactor)>>8;	// FR
				pOutput[baseOffset+2]  = (snd_rp[j]*volumeFactor)>>8;		// RL
				pOutput[baseOffset+3] = (snd_rp[j + 1]*volumeFactor)>>8;	// RR
				baseOffset += 4;
			}
		}
		else
		{
			Assert(channelCount==5); // 6 channel / 5.1
			int baseOffset = lpos * 6;
			for (i=0, j= 0 ; i<linearCount ; i++, j+=2)
			{
				pOutput[baseOffset+0]  = (snd_p[j]*volumeFactor)>>8;		// FL
				pOutput[baseOffset+1] = (snd_p[j + 1]*volumeFactor)>>8;	// FR

				pOutput[baseOffset+2]  = (snd_cp[j]*volumeFactor)>>8;		// Center
				// NOTE: Let the hardware mix the sub from the main channels since
				//		 we don't have any sub-specific sounds, or direct sub-addressing
				pOutput[baseOffset+3]  = 0;

				pOutput[baseOffset+4]  = (snd_rp[j]*volumeFactor)>>8;		// RL
				pOutput[baseOffset+5] = (snd_rp[j + 1]*volumeFactor)>>8;	// RR


				baseOffset += 6;
			}
		}

		snd_p += linearCount << 1;
		snd_rp += linearCount << 1;
		snd_cp += linearCount << 1;

		lpaintedtime += linearCount;
	}

	pDSBuf->Unlock(pdwWrite, dwSize, NULL, 0);
}

void CAudioDirectSound::S_TransferSurround16Interleaved( const portable_samplepair_t *pfront, const portable_samplepair_t *prear, const portable_samplepair_t *pcenter, int lpaintedtime, int endtime )
{
	if ( !pDSBuf )
		return;
	if ( !snd_lockpartial.GetBool() )
	{
		S_TransferSurround16Interleaved_FullLock( pfront, prear, pcenter, lpaintedtime, endtime );
		return;
	}
	// take stereo front and rear paintbuffers, and center paintbuffer if provided,
	// and copy samples into the 4 or 5 mono directsound buffers

	surround_transfer_t transfer;
	transfer.snd_rp = (int *)prear;
	transfer.snd_cp = (int *)pcenter;
	transfer.snd_p = (int *)pfront;
	
	int sampleMonoCount = DeviceSampleCount()/DeviceChannels();	 // number of full samples per output buffer
	Assert(IsPowerOfTwo(sampleMonoCount));
	transfer.sampleMask = sampleMonoCount - 1;
	transfer.paintedtime = lpaintedtime;
	transfer.linearCount = endtime - lpaintedtime;
	// paintedtime - number of full samples that have played since start
	// endtime - number of full samples to play to - endtime is g_soundtime + mixahead samples
	int channelCount = m_bSurroundCenter ? 6 : 4;
	if ( DeviceChannels() == 2 )
	{
		channelCount = 2;
	}
	transfer.channelCount = channelCount;
	void *pBuffer0=NULL;
	void *pBuffer1=NULL;
	DWORD size0, size1;
	int lpos = transfer.paintedtime & transfer.sampleMask;		// lpos is next output position in output buffer

	int offset = lpos*2*channelCount;
	int lockSize = transfer.linearCount*2*channelCount;
	int reps = 0;
	HRESULT hr;
	while ( (hr = pDSBuf->Lock( offset, lockSize, &pBuffer0, &size0, &pBuffer1, &size1, 0 )) != DS_OK )
	{
		if ( hr == DSERR_BUFFERLOST )
		{
			if ( ++reps < 10000 )
				continue;
		}
		Msg ("DS::Lock Sound Buffer Failed\n");
		return;
	}

	if ( pBuffer0 )
	{
		transfer.pOutput = (short *)pBuffer0;
		TransferSamplesToSurroundBuffer( size0 / (channelCount*2), transfer );
	}
	if ( pBuffer1 )
	{
		transfer.pOutput = (short *)pBuffer1;
		TransferSamplesToSurroundBuffer( size1 / (channelCount*2), transfer );
	}
	pDSBuf->Unlock(pBuffer0, size0, pBuffer1, size1);
}