
/*
 * UNPUBLISHED -- Rights  reserved  under  the  copyright  laws  of the 
 * United States.  Use  of a copyright notice is precautionary only and 
 * does not imply publication or disclosure.                            
 *                                                                      
 * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION 
 * OF    VICARIOUS   VISIONS,  INC.    ANY  DUPLICATION,  MODIFICATION, 
 * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR 
 * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC.
 */

// leave this as first line for PCH reasons...
//
#include "../server/exe_headers.h"


#include "win_local.h"

#include "../client/openal/al.h"
#include "../client/openal/alc.h"

#include <dsound.h>
//#include <dsstdfx.h>
#include "snd_fx_img.h"

#include <cmath>
#include <deque>
#include <map>

#define QAL_STREAM_WAIT_TIME (500)
#define QAL_MAX_STREAM_PACKETS (2)

// About 1 second of audio at 44100, stereo, ADPCM
#define QAL_STREAM_PACKET_SIZE (44136)

// Un-comment to enable 5-channel 3-d sound mixing
//#define _FIVE_CHANNEL

extern HANDLE Sys_FileStreamMutex;
extern const char* Sys_GetFileCodeName(int code);

/***********************************************
*
* OpenAL STATE - Main container for all AL objects
*
************************************************/

struct QALState
{
	IDirectSound8*		m_SoundObject;
	LPDSEFFECTIMAGEDESC	m_ImageDesc;

	ALuint m_MemoryUsed;
	ALenum m_Error;
	FLOAT m_Gain;

	struct ListenerInfo
	{
		D3DXVECTOR3 m_Position;
		D3DXMATRIX m_LTM;
	};
	typedef std::map<ALuint, ListenerInfo*> listener_t;
	listener_t m_Listeners;
	ALuint m_NextListener;

	struct SourceInfo
	{
		typedef std::map<ALuint, IDirectSoundBuffer*> voice_t;
		voice_t m_Voices;
		
		ALuint m_Buffer;
		
		FLOAT m_Gain;
		bool m_GainDirty;

		bool m_Loop;
		
		bool m_Is3d;
		D3DXVECTOR3 m_Position;
	};
	typedef std::map<ALuint, SourceInfo*> source_t;
	source_t m_Sources;
	ALuint m_NextSource;

	struct BufferInfo
	{
		void* m_Data;
		DWORD m_DataOffset;
		XBOXADPCMWAVEFORMAT m_WAVFormat;
		
		DWORD m_Freq;
		DWORD m_Size;
		
		bool m_Valid;
	};
	typedef std::map<ALuint, BufferInfo*> buffer_t;
	buffer_t m_Buffers;
	ALuint m_NextBuffer;

	struct StreamInfo
	{
		IDirectSoundStream* m_pVoice;
		XFileMediaObject* m_pFile;

		unsigned int m_StartTime;

		bool m_Open;
		bool m_Playing;
		bool m_Valid;
		
		FLOAT m_Gain;
		bool m_GainDirty;

		bool m_Looping;

		void* m_pPacketBuffer;
		DWORD m_PacketStatus[QAL_MAX_STREAM_PACKETS];
		DWORD m_CurrentPacket;

		HANDLE m_Thread;
		HANDLE m_Mutex;
		HANDLE m_QueueLen;

		enum RequestType
		{
			REQ_NOP,
			REQ_PLAY,
			REQ_STOP,
			REQ_SHUTDOWN,
		};

		struct Request
		{
			RequestType m_Type;
			DWORD m_Data[3];
		};
		
		typedef std::deque<Request> queue_t;
		queue_t *m_Queue;
	};
	StreamInfo m_Stream;
};

static QALState* s_pState = NULL;

/***********************************************
*
* HACK - Voice initialization needs this
*
************************************************/

LPDSEFFECTIMAGEDESC getEffectsImageDesc(void)
{
	return s_pState->m_ImageDesc;
}


/***********************************************
*
* DEVICES AND CONTEXTS
*
************************************************/

ALCdevice* alcOpenDevice(ALCubyte *deviceName)
{
	if (s_pState) return NULL;
	s_pState = new QALState;
	s_pState->m_Stream.m_Queue = new QALState::StreamInfo::queue_t;

	s_pState->m_Gain = 1.f;
	s_pState->m_Error = AL_NO_ERROR;
	s_pState->m_MemoryUsed = 0;
	s_pState->m_NextBuffer = 1;
	s_pState->m_NextListener = 1;
	s_pState->m_NextSource = 1;
	s_pState->m_Stream.m_Valid = false;
	
	// init the sound hardware
	if (DirectSoundCreate(NULL, &s_pState->m_SoundObject, NULL) != DS_OK)
	{
		delete s_pState;
		return NULL;
	}

	DirectSoundUseFullHRTF();

	// download effects image to hardware
	void* image;
	int len = FS_ReadFile("sound/dsstdfx.bin", &image);
	if (len <= 0)
	{
		delete s_pState;
		return NULL;
	}
	
	DSEFFECTIMAGELOC effect;
	effect.dwI3DL2ReverbIndex = GraphI3DL2_I3DL2Reverb;
	effect.dwCrosstalkIndex = GraphXTalk_XTalk;
	s_pState->m_SoundObject->DownloadEffectsImage(image, len, &effect, &s_pState->m_ImageDesc);

	Z_Free(image);

	// setup default reverb
	DSI3DL2LISTENER reverb = { DSI3DL2_ENVIRONMENT_PRESET_NOREVERB };
	s_pState->m_SoundObject->SetI3DL2Listener(&reverb, DS3D_DEFERRED);

	return (ALCdevice*)s_pState->m_SoundObject;
}

ALCvoid alcCloseDevice(ALCdevice *device)
{
	// shutdown the sound hardware
	s_pState->m_SoundObject->Release();
	
	delete s_pState;
	s_pState = NULL;
}

ALCcontext* alcCreateContext(ALCdevice *device,ALCint *attrList)
{
	return (ALCcontext*)1;
}

ALCboolean alcMakeContextCurrent(ALCcontext *context)
{
	return true;
}

ALCcontext* alcGetCurrentContext(ALCvoid)
{
	return (ALCcontext*)1;
}

ALCdevice* alcGetContextsDevice(ALCcontext *context)
{
	if (!s_pState) return NULL;
	return (ALCdevice*)s_pState->m_SoundObject;
}

ALCvoid alcDestroyContext(ALCcontext *context)
{
}

ALCenum	alcGetError(ALCdevice *device)
{
	return ALC_NO_ERROR;
}




/***********************************************
*
* LISTENERS
*
************************************************/

ALvoid alGenListeners( ALsizei n, ALuint* listeners )
{
	while (n--)
	{
		QALState::ListenerInfo* info = new QALState::ListenerInfo;

		info->m_Position.x = 0.f;
		info->m_Position.y = 0.f;
		info->m_Position.z = 0.f;

		D3DXMatrixIdentity(&info->m_LTM);
		
		s_pState->m_Listeners[s_pState->m_NextListener] = info;
		listeners[n] = s_pState->m_NextListener++;
	}
}

ALvoid alDeleteListeners( ALsizei n, ALuint* listeners )
{
	while (n--)
	{
		QALState::listener_t::iterator i = 
			s_pState->m_Listeners.find(listeners[n]);
		
		if (i != s_pState->m_Listeners.end())
		{
			delete i->second;
			s_pState->m_Listeners.erase(i);
		}
	}
}

ALvoid alListenerfv( ALuint listener, ALenum param, ALfloat* values )
{
	assert(s_pState->m_Listeners.find(listener) != 
		s_pState->m_Listeners.end());
	
	QALState::ListenerInfo* info = s_pState->m_Listeners[listener];
	D3DXVECTOR3 right;
	D3DXMATRIX trans;
	FLOAT det;
	
	switch (param)
	{
	case AL_POSITION:
		info->m_Position.x = values[0];
		info->m_Position.y = values[1];
		info->m_Position.z = values[2];

		// translation
		D3DXMatrixTranslation(&trans, -values[0], -values[1], -values[2]);
		D3DXMatrixMultiply(&info->m_LTM, &trans, &info->m_LTM);
		break;

	case AL_ORIENTATION:
		D3DXMatrixIdentity(&info->m_LTM);

		// at vector
		info->m_LTM(2, 0) = values[0];
		info->m_LTM(2, 1) = values[1];
		info->m_LTM(2, 2) = values[2];

		// up vector
		info->m_LTM(1, 0) = values[3];
		info->m_LTM(1, 1) = values[4];
		info->m_LTM(1, 2) = values[5];
		
		// Hack. We switched the sign on values[2] up above, need to do that here
		D3DXVec3Cross(&right, (D3DXVECTOR3*)&values[0], (D3DXVECTOR3*)&values[3]);
		
		// right vector
		info->m_LTM(0, 0) = right.x;
		info->m_LTM(0, 1) = right.y;
		info->m_LTM(0, 2) = right.z;

		// convert to local space transform
		D3DXMatrixInverse(&info->m_LTM, &det, &info->m_LTM);

		// translation
		D3DXMatrixTranslation(&trans, 
			-info->m_Position.x, -info->m_Position.y, -info->m_Position.z);
		D3DXMatrixMultiply(&info->m_LTM, &trans, &info->m_LTM);
		break;
	}
}




/***********************************************
*
* SOURCES
*
************************************************/

static void _wavSetFormat(XBOXADPCMWAVEFORMAT* wav, ALenum format, ALsizei freq)
{
	switch (format)
	{
	case AL_FORMAT_MONO4:
		wav->wfx.wFormatTag = WAVE_FORMAT_XBOX_ADPCM;
		wav->wfx.nChannels = 1;
		wav->wfx.nSamplesPerSec = freq;
		wav->wfx.nBlockAlign = 36 * wav->wfx.nChannels;
		wav->wfx.nAvgBytesPerSec = wav->wfx.nSamplesPerSec * wav->wfx.nBlockAlign / 64;
		wav->wfx.wBitsPerSample = 4;
		wav->wfx.cbSize = sizeof(XBOXADPCMWAVEFORMAT) - sizeof(WAVEFORMATEX);
		wav->wSamplesPerBlock = 64;
		break;

	case AL_FORMAT_STEREO4:
		wav->wfx.wFormatTag = WAVE_FORMAT_XBOX_ADPCM;
		wav->wfx.nChannels = 2;
		wav->wfx.nSamplesPerSec = freq;
		wav->wfx.nBlockAlign = 36 * wav->wfx.nChannels;
		wav->wfx.nAvgBytesPerSec = wav->wfx.nSamplesPerSec * wav->wfx.nBlockAlign / 64;
		wav->wfx.wBitsPerSample = 4;
		wav->wfx.cbSize = sizeof(XBOXADPCMWAVEFORMAT) - sizeof(WAVEFORMATEX);
		wav->wSamplesPerBlock = 64;
		break;

	case AL_FORMAT_MONO8:
	case AL_FORMAT_STEREO8:
	case AL_FORMAT_MONO16:
	case AL_FORMAT_STEREO16:
	default:
		assert(0);
		break;
	}
}

static int _genSource(bool is3d)
{
	// alloc a new source
	QALState::SourceInfo* sinfo = new QALState::SourceInfo;

	// describe the voice
	XBOXADPCMWAVEFORMAT wav;
	_wavSetFormat(&wav, AL_FORMAT_MONO4, 22050);
	
	DSBUFFERDESC desc;
	desc.dwSize = sizeof(desc);
	if (is3d) desc.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_MUTE3DATMAXDISTANCE;
	else desc.dwFlags = 0;
	desc.dwBufferBytes = 0;
	desc.lpwfxFormat = (WAVEFORMATEX*)&wav;
	desc.lpMixBins = NULL;
	desc.dwInputMixBin = 0;

	// create voice for all listeners
	for (QALState::listener_t::iterator l = s_pState->m_Listeners.begin();
	l != s_pState->m_Listeners.end(); ++l)
	{
		// create the voice
		IDirectSoundBuffer* voice;
		if (s_pState->m_SoundObject->CreateSoundBuffer(&desc, &voice, NULL) != DS_OK)
		{
			s_pState->m_Error = AL_OUT_OF_MEMORY;
			return false;
		}

		sinfo->m_Voices[l->first] = voice;

		// only create a single voice for 2d sounds
		if (!is3d) break;
	}

	// setup some defaults
	sinfo->m_Buffer = 0;

	sinfo->m_Gain = 1.f;
	sinfo->m_GainDirty = true;
	sinfo->m_Loop = false;

	sinfo->m_Is3d = is3d;
	sinfo->m_Position.x = 0.f;
	sinfo->m_Position.y = 0.f;
	sinfo->m_Position.z = 0.f;
	
	s_pState->m_Sources[s_pState->m_NextSource] = sinfo;

	return true;
}

static void _attachBuffer(ALuint source, ALuint buffer)
{
	assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end());
	assert(s_pState->m_Buffers.find(buffer) != s_pState->m_Buffers.end());
	
	QALState::SourceInfo* sinfo = s_pState->m_Sources[source];
	QALState::BufferInfo* binfo = s_pState->m_Buffers[buffer];
	
	// setup voices for all listeners
	for (QALState::SourceInfo::voice_t::iterator v = sinfo->m_Voices.begin(); 
	v != sinfo->m_Voices.end(); ++v)
	{
		v->second->SetFormat((WAVEFORMATEX*)&binfo->m_WAVFormat);

#ifdef _FIVE_CHANNEL
		DSMIXBINVOLUMEPAIR dsmbvp[6] = {
			DSMIXBINVOLUMEPAIRS_DEFAULT_5CHANNEL_3D,
		};
		DSMIXBINS dsmb;
		dsmb.dwMixBinCount = 6;
		dsmb.lpMixBinVolumePairs = dsmbvp;
		
		v->second->SetMixBins(&dsmb);
#endif

		v->second->SetBufferData((char*)binfo->m_Data + binfo->m_DataOffset, binfo->m_Size);
	}
	
	sinfo->m_Buffer = buffer;
}

static void _dettachBuffer(ALuint source)
{
	assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end());
	
	QALState::SourceInfo* info = s_pState->m_Sources[source];

	// clear buffer on voices
	for (QALState::SourceInfo::voice_t::iterator v = info->m_Voices.begin(); 
	v != info->m_Voices.end(); ++v)
	{
		v->second->Stop();
		v->second->SetBufferData(NULL, 0);
	}

	info->m_Buffer = 0;
}

static void _sourceSetRefDist(QALState::SourceInfo* info, FLOAT value)
{
	for (QALState::SourceInfo::voice_t::iterator v = info->m_Voices.begin(); 
	v != info->m_Voices.end(); ++v)
	{
		// In order to prevent debug DX from complaining that
		// the max dist is greater than the min dist, I clear
		// the min dist _before_ setting the max.  Ug.
		v->second->SetMinDistance(1, DS3D_DEFERRED);

		// New algorithm - ref dist is supposed to be dist at which sound is 1/2 volume,
		// which happens at double min distance in DS, thus: (reverted)
		v->second->SetMaxDistance(value * 10.f, DS3D_DEFERRED);
		v->second->SetMinDistance(value, DS3D_DEFERRED);
//		v->second->SetMinDistance(value / 2.f, DS3D_DEFERRED);
	}
}

ALvoid alGenSources2D( ALsizei n, ALuint* sources )
{
	while (n--)
	{
		if (!_genSource(false)) break;
		sources[n] = s_pState->m_NextSource++;
	}
}

ALvoid alGenSources3D( ALsizei n, ALuint* sources )
{
	while (n--)
	{
		if (!_genSource(true)) break;
		sources[n] = s_pState->m_NextSource++;
	}
}

ALvoid alDeleteSources( ALsizei n, ALuint* sources )
{
	while (n--)
	{
		QALState::source_t::iterator i = 
			s_pState->m_Sources.find(sources[n]);

		if (i != s_pState->m_Sources.end())
		{
			QALState::SourceInfo* info = i->second;

			// stop using any buffers
			_dettachBuffer(sources[n]);			

			// free associated voices
			for (QALState::SourceInfo::voice_t::iterator v = info->m_Voices.begin(); 
			v != info->m_Voices.end(); ++v)
			{
				v->second->Release();
			}
			
			delete info;
			s_pState->m_Sources.erase(i);
		}
	}
}

ALvoid alSourcei( ALuint source, ALenum param, ALint value )
{
	assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end());

	switch (param)
	{
	case AL_LOOPING:
		s_pState->m_Sources[source]->m_Loop = value;
		break;

	case AL_BUFFER:
		if (value) _attachBuffer(source, value);
		break;

	default:
		assert(0);
		break;
	}
}

ALvoid alSourcef( ALuint source, ALenum param, ALfloat value )
{
	assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end());
	
	QALState::SourceInfo* info = s_pState->m_Sources[source];

	switch (param)
	{
	case AL_REFERENCE_DISTANCE:
		_sourceSetRefDist(info, value);
		break;
	case AL_GAIN:
		info->m_Gain = value;
		info->m_GainDirty = true;
		break;
	default:
		assert(0);
		break;
	}
}

ALvoid alSourcefv( ALuint source, ALenum param, ALfloat* values )
{
	assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end());
	
	QALState::SourceInfo* info = s_pState->m_Sources[source];

	switch (param)
	{
	case AL_POSITION:
		assert(info->m_Is3d);
		info->m_Position.x = values[0];
		info->m_Position.y = values[1];
		info->m_Position.z = values[2];
		break;
	default:
		assert(0);
		break;
	}
}

ALvoid alSourceStop( ALuint source )
{
	assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end());
	
	QALState::SourceInfo* info = s_pState->m_Sources[source];

	// stop playing for all listeners
	for (QALState::SourceInfo::voice_t::iterator v = info->m_Voices.begin(); 
	v != info->m_Voices.end(); ++v)
	{
		v->second->Stop();
		
		DWORD status = 1;		// Wait for voice to turn off
		do { 
			v->second->GetStatus(&status);
		} while (status != 0);
			
	}
}

ALvoid alSourcePlay( ALuint source )
{
	assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end());
	
	QALState::SourceInfo* info = s_pState->m_Sources[source];

	if (!info->m_Buffer)
	{
		return;
	}

	// start playing for all listeners
	for (QALState::SourceInfo::voice_t::iterator v = info->m_Voices.begin(); 
	v != info->m_Voices.end(); ++v)
	{
		v->second->SetCurrentPosition(0);
		v->second->Play(0, 0, info->m_Loop ? DSBPLAY_LOOPING : 0);
	}
}

ALvoid alGetSourcei( ALuint source, ALenum param, ALint* value )
{
	assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end());
	
	QALState::SourceInfo* info = s_pState->m_Sources[source];

	switch (param)
	{
	case AL_SOURCE_STATE:
		{
			DWORD status;
			info->m_Voices.begin()->second->GetStatus(&status);
			*value = (status & DSBSTATUS_PLAYING) ? AL_PLAYING : AL_STOPPED;
		}
		break;
	default:
		assert(0);
		break;
	}
}




/***********************************************
*
* BUFFERS
*
************************************************/

ALvoid alGenBuffers( ALsizei n, ALuint* buffers )
{
	while (n--)
	{
		QALState::BufferInfo* info = new QALState::BufferInfo;

		info->m_Valid = false;

		s_pState->m_Buffers[s_pState->m_NextBuffer] = info;
		buffers[n] = s_pState->m_NextBuffer++;
	}
}

ALvoid alDeleteBuffers( ALsizei n, ALuint* buffers )
{
	while (n--)
	{
		QALState::buffer_t::iterator b = 
			s_pState->m_Buffers.find(buffers[n]);

		// check if the buffer exists
		if (b != s_pState->m_Buffers.end())
		{
			QALState::BufferInfo* binfo = b->second;
			
			if (binfo->m_Valid)
			{
				// dettach buffer from any sources using it (may block)
				for (QALState::source_t::iterator s = s_pState->m_Sources.begin();
				s != s_pState->m_Sources.end(); ++s)
				{
					QALState::SourceInfo* sinfo = s->second;
					if (sinfo->m_Buffer == buffers[n])
					{
						_dettachBuffer(s->first);
					}
				}
			
				// free the memory
				Z_Free(binfo->m_Data);
				s_pState->m_MemoryUsed -= binfo->m_Size;
			}
		
			delete b->second;
			s_pState->m_Buffers.erase(b);
		}
	}
}

ALvoid alBufferData( ALuint buffer, ALenum format, ALvoid* data, ALsizei size, ALsizei freq )
{
	assert(s_pState->m_Buffers.find(buffer) != s_pState->m_Buffers.end());
	
	QALState::BufferInfo* info = s_pState->m_Buffers[buffer];
	
	// if this buffer has been used before, clear the old data
	if (info->m_Valid)
	{
		Z_Free(info->m_Data);
		s_pState->m_MemoryUsed -= info->m_Size;
		info->m_Valid = false;
	}

	info->m_Data = data;

	// assume we have a wave file...
	WAVEFORMATEX* wav = (WAVEFORMATEX*)((char*)data + 20);
	info->m_DataOffset = 20 + sizeof(WAVEFORMATEX) + wav->cbSize + 8;

	info->m_Size = size;
	s_pState->m_MemoryUsed += info->m_Size;

	_wavSetFormat(&info->m_WAVFormat, format, freq);
	
	info->m_Valid = true;
}


/***********************************************
*
* STREAMS
*
************************************************/

static int _streamFromFile(void)
{
	DWORD total = 0;
	DWORD used = 0;
	
	// setup a media packet for reading from the file
	XMEDIAPACKET xmp;
	ZeroMemory(&xmp, sizeof(xmp));
	xmp.pvBuffer = (BYTE *)s_pState->m_Stream.m_pPacketBuffer +
		(QAL_STREAM_PACKET_SIZE * s_pState->m_Stream.m_CurrentPacket);
	xmp.dwMaxSize = QAL_STREAM_PACKET_SIZE;
	xmp.pdwCompletedSize = &used;
	
	WaitForSingleObject(Sys_FileStreamMutex, INFINITE);

	// loop until we have a full packet of data
	while (total < QAL_STREAM_PACKET_SIZE)
	{
		if (DS_OK != s_pState->m_Stream.m_pFile->Process(NULL, &xmp))
		{
			ReleaseMutex(Sys_FileStreamMutex);
			return -1;
		}
		
		total += used;
		
		// did we get enough data?
		if (used < xmp.dwMaxSize)
		{
			if (s_pState->m_Stream.m_Looping)
			{
				// must have reached the end of the file, loop back
				// around to the beginning and get more data
				xmp.pvBuffer  = (BYTE*)xmp.pvBuffer + used;
				xmp.dwMaxSize = xmp.dwMaxSize - used;
				
				if (DS_OK != s_pState->m_Stream.m_pFile->Seek(
					0, FILE_BEGIN, NULL))
				{
					ReleaseMutex(Sys_FileStreamMutex);
					return -1;
				}
			}
			else
			{
				// reached end, finish up
				s_pState->m_Stream.m_Playing = false;
				ReleaseMutex(Sys_FileStreamMutex);
				return used;
			}
		}
	}

	ReleaseMutex(Sys_FileStreamMutex);

	return QAL_STREAM_PACKET_SIZE;
}

static void _streamToVoice(int size)
{
	// setup a packet with the current data
	XMEDIAPACKET xmp;
	ZeroMemory(&xmp, sizeof(xmp));
	xmp.pvBuffer = (BYTE *)s_pState->m_Stream.m_pPacketBuffer +
		(QAL_STREAM_PACKET_SIZE * s_pState->m_Stream.m_CurrentPacket);
	xmp.dwMaxSize = size;
	xmp.pdwStatus = &s_pState->m_Stream.m_PacketStatus[
		s_pState->m_Stream.m_CurrentPacket];

	// sent to the voice
	s_pState->m_Stream.m_pVoice->Process(&xmp, NULL);

	// make sure we're playing
	s_pState->m_Stream.m_pVoice->Pause(DSSTREAMPAUSE_RESUME);
	if (s_pState->m_Stream.m_StartTime == 0)
	{
		s_pState->m_Stream.m_StartTime = Sys_Milliseconds();
	}
}

static void _streamFill(void)
{
	// do we have any free packets?
	if (XMEDIAPACKET_STATUS_PENDING !=
		s_pState->m_Stream.m_PacketStatus[s_pState->m_Stream.m_CurrentPacket])
	{
		// get some data
		int size = _streamFromFile();
		if (size > 0)
		{
			_streamToVoice(size);
	
			// next packet...
			++s_pState->m_Stream.m_CurrentPacket;
			s_pState->m_Stream.m_CurrentPacket %= QAL_MAX_STREAM_PACKETS;
		}

		if (!s_pState->m_Stream.m_Playing)
		{
			// Non-looping stream finished playback
			s_pState->m_Stream.m_pVoice->Discontinuity();
		}
	}
}

static void _streamOpen(DWORD file, DWORD offset, bool loop)
{
	if (s_pState->m_Stream.m_Open)
	{
		// if a stream is current playing, interrupt it
		s_pState->m_Stream.m_pVoice->Flush();
		s_pState->m_Stream.m_pFile->Release();
		s_pState->m_Stream.m_Playing = false;
		s_pState->m_Stream.m_Open = false;
	}

	const char* name = Sys_GetFileCodeName(file);
	
	WaitForSingleObject(Sys_FileStreamMutex, INFINITE);

	// open the file for streaming
	LPCWAVEFORMATEX fmt;
	if (DS_OK == XWaveFileCreateMediaObject(
		name, &fmt, &s_pState->m_Stream.m_pFile))
	{
		// set the voice based on the file format
		s_pState->m_Stream.m_pVoice->SetFormat(fmt);

#ifdef _FIVE_CHANNEL
		DSMIXBINVOLUMEPAIR dsmbvp[6] = {
			DSMIXBINVOLUMEPAIRS_DEFAULT_5CHANNEL_3D,
		};
		DSMIXBINS dsmb;
		dsmb.dwMixBinCount = 6;
		dsmb.lpMixBinVolumePairs = dsmbvp;

		s_pState->m_Stream.m_pVoice->SetMixBins(&dsmb);
#endif

		// seek the requested start position
		s_pState->m_Stream.m_pFile->Seek(RoundDown(offset, 72), 
			FILE_BEGIN, NULL);
		
		s_pState->m_Stream.m_StartTime = 0;
		s_pState->m_Stream.m_Looping = loop;
		s_pState->m_Stream.m_Playing = true;
		s_pState->m_Stream.m_Open = true;
	}

	ReleaseMutex(Sys_FileStreamMutex);
}

static void _streamClose(void)
{
	if (s_pState->m_Stream.m_Open)
	{
		// stop the stream
		s_pState->m_Stream.m_pVoice->Flush();
		s_pState->m_Stream.m_pFile->Release();
		s_pState->m_Stream.m_Playing = false;
		s_pState->m_Stream.m_Open = false;
	}
}

static DWORD WINAPI _streamThread(LPVOID lpParameter)
{
	for (;;)
	{
		QALState::StreamInfo* strm = &s_pState->m_Stream;
		QALState::StreamInfo::Request req;
		
		// Wait for the queue to fill
		WaitForSingleObject(strm->m_QueueLen, QAL_STREAM_WAIT_TIME);
		
		// Grab the next request
		WaitForSingleObject(strm->m_Mutex, INFINITE);
		if (!strm->m_Queue->empty())
		{
			req = strm->m_Queue->front();
			strm->m_Queue->pop_front();
		}
		else
		{
			req.m_Type = QALState::StreamInfo::REQ_NOP;
		}
		ReleaseMutex(strm->m_Mutex);

		// Process request
		switch (req.m_Type)
		{
		case QALState::StreamInfo::REQ_PLAY:
			_streamOpen(req.m_Data[0], req.m_Data[1], req.m_Data[2]);
			break;

		case QALState::StreamInfo::REQ_STOP:
			_streamClose();
			break;

		case QALState::StreamInfo::REQ_SHUTDOWN:
			ExitThread(0);
			break;

		case QALState::StreamInfo::REQ_NOP:
			break;
		}

		// fill the stream with data
		if (strm->m_Open && strm->m_Playing)
		{
			_streamFill();
		}
	}
}

static void _postStreamRequest(const QALState::StreamInfo::Request& req)
{
	// Add request to queue
	WaitForSingleObject(s_pState->m_Stream.m_Mutex, INFINITE);
	s_pState->m_Stream.m_Queue->push_back(req);
	ReleaseMutex(s_pState->m_Stream.m_Mutex);

	// Let thread know it has one more pending request
	ReleaseSemaphore(s_pState->m_Stream.m_QueueLen, 1, NULL);

	// Give the stream thread some CPU
	Sleep(0);
}

void Sys_StreamRequestQueueClear(void)
{
	WaitForSingleObject(s_pState->m_Stream.m_Mutex, INFINITE);
	delete s_pState->m_Stream.m_Queue;
	s_pState->m_Stream.m_Queue = new QALState::StreamInfo::queue_t;
	ReleaseMutex(s_pState->m_Stream.m_Mutex);
}

ALvoid alGenStream( ALvoid )
{
	assert(!s_pState->m_Stream.m_Valid);
	
	// describe the stream
	XBOXADPCMWAVEFORMAT wav;
	_wavSetFormat(&wav, AL_FORMAT_STEREO4, 44100);
	
	DSSTREAMDESC desc;
	ZeroMemory(&desc, sizeof(desc));
	desc.dwMaxAttachedPackets = QAL_MAX_STREAM_PACKETS;
	desc.lpwfxFormat = (WAVEFORMATEX*)&wav;
	
	// create a voice for the stream
	if (s_pState->m_SoundObject->CreateSoundStream(&desc, 
		&s_pState->m_Stream.m_pVoice, NULL) != DS_OK)
	{
		s_pState->m_Error = AL_OUT_OF_MEMORY;
		return;
	}

	// get some memory to hold the stream data
	s_pState->m_Stream.m_pPacketBuffer = 
		XPhysicalAlloc(QAL_MAX_STREAM_PACKETS * QAL_STREAM_PACKET_SIZE,
		MAXULONG_PTR, 0, PAGE_READWRITE | PAGE_NOCACHE);

	// setup some defaults
	s_pState->m_Stream.m_Gain = 1.f;
	s_pState->m_Stream.m_GainDirty = true;

	s_pState->m_Stream.m_CurrentPacket = 0;
	for (int p = 0; p < QAL_MAX_STREAM_PACKETS; ++p)
	{
		s_pState->m_Stream.m_PacketStatus[p] = XMEDIAPACKET_STATUS_SUCCESS;
	}

	s_pState->m_Stream.m_Open = false;
	s_pState->m_Stream.m_Playing = false;
	s_pState->m_Stream.m_Valid = true;

	// setup a thread to service the stream (keep blocking IO out
	// of the main thread)
	s_pState->m_Stream.m_QueueLen = CreateSemaphore(NULL, 0, 256, NULL);
	s_pState->m_Stream.m_Mutex = CreateMutex(NULL, FALSE, NULL);
	s_pState->m_Stream.m_Thread = CreateThread(NULL, 0, 
		_streamThread, NULL, 0, NULL );
}

ALvoid alDeleteStream( ALvoid )
{
	assert(s_pState->m_Stream.m_Valid);
	
	// stop the audio
	alStreamStop();

	// kill the thread
	QALState::StreamInfo::Request req;
	req.m_Type = QALState::StreamInfo::REQ_SHUTDOWN;
	_postStreamRequest(req);
	
	// Wait for thread to close
	WaitForSingleObject(s_pState->m_Stream.m_Thread, INFINITE);

	// thread handles
	CloseHandle(s_pState->m_Stream.m_Thread);
	CloseHandle(s_pState->m_Stream.m_Mutex);
	CloseHandle(s_pState->m_Stream.m_QueueLen);

	// release the stream
	s_pState->m_Stream.m_pVoice->Release();
	XPhysicalFree(s_pState->m_Stream.m_pPacketBuffer);

	s_pState->m_Stream.m_Valid = false;
}

ALvoid alStreamStop( ALvoid )
{
	assert(s_pState->m_Stream.m_Valid);

	QALState::StreamInfo::Request req;
	req.m_Type = QALState::StreamInfo::REQ_STOP;
	_postStreamRequest(req);	
}

ALvoid alStreamPlay( ALsizei offset, ALint file, ALint loop )
{
	assert(s_pState->m_Stream.m_Valid);

	QALState::StreamInfo::Request req;
	req.m_Type = QALState::StreamInfo::REQ_PLAY;
	req.m_Data[0] = file;
	req.m_Data[1] = offset;
	req.m_Data[2] = loop;
	_postStreamRequest(req);

	s_pState->m_Stream.m_Playing = true;
}

ALvoid alStreamf( ALenum param, ALfloat value )
{
	assert(s_pState->m_Stream.m_Valid);

	switch (param)
	{
	case AL_GAIN:
		s_pState->m_Stream.m_Gain = value;
		s_pState->m_Stream.m_GainDirty = true;
		break;
	default:
		assert(0);
		break;
	}
}

ALvoid alGetStreamf( ALenum param, ALfloat* value )
{
	assert(s_pState->m_Stream.m_Valid);

	switch (param)
	{
	case AL_TIME:
		if (s_pState->m_Stream.m_Open && s_pState->m_Stream.m_StartTime)
		{
			*value = (float)(Sys_Milliseconds() - 
				s_pState->m_Stream.m_StartTime) / 1000.f;
		}
		else
		{
			*value = 0.f;
		}
		break;
	default:
		assert(0);
		break;
	}
}

ALvoid alGetStreami( ALenum param, ALint* value )
{
	assert(s_pState->m_Stream.m_Valid);

	switch (param)
	{
	case AL_SOURCE_STATE:
		*value = s_pState->m_Stream.m_Playing ? AL_PLAYING : AL_STOPPED;
		break;
	default:
		assert(0);
		break;
	}
}



/***********************************************
*
* ADDITIONAL FUNCTIONS
*
************************************************/

static void _updateVoiceGain(IDirectSoundBuffer* voice, FLOAT gain)
{
	// compute aggregate gain
	FLOAT g = s_pState->m_Gain * gain;
	
	if (g <= 0.f)
	{
		// mute the sound
		voice->SetVolume(DSBVOLUME_MIN);
	}
	else
	{
		// convert to dB
		g = 20.f * log10(g);

		if(g < -100.0f) {
			g = -100.0f;
		}

		// set the volume
		voice->SetVolume(g * 100.f);
	}
}

static void _updateVoicePos(IDirectSoundBuffer* voice, D3DXVECTOR3* pos,
	QALState::ListenerInfo* listener)
{
	// get source pos in listener space
	D3DXVECTOR4 lpos;
	D3DXVec3Transform(&lpos, pos, &listener->m_LTM);

	voice->SetPosition(lpos.x, lpos.y, lpos.z, DS3D_DEFERRED);
}

static void _updateSource(QALState::SourceInfo* source)
{
	// loop through all the voices at this source
	for (QALState::SourceInfo::voice_t::iterator v = source->m_Voices.begin(); 
	v != source->m_Voices.end(); ++v)
	{
		// update the gain
		if (source->m_GainDirty)
		{
			_updateVoiceGain(v->second, source->m_Gain);
		}

		// update position
		if (source->m_Is3d)
		{
			// get the listener for this voice
			QALState::listener_t::iterator l = s_pState->m_Listeners.find(v->first);

			if (l != s_pState->m_Listeners.end())
			{
				_updateVoicePos(
					v->second,
					&source->m_Position, 
					l->second);
			}
		}
	}

	source->m_GainDirty = false;
}

static void _updateStream(void)
{
	if (s_pState->m_Stream.m_Open && s_pState->m_Stream.m_GainDirty)
	{
		// compute aggregate gain
		FLOAT g = s_pState->m_Gain * s_pState->m_Stream.m_Gain;
		if (g <= 0.f)
		{
			// mute the sound
			s_pState->m_Stream.m_pVoice->SetVolume(DSBVOLUME_MIN);
		}
		else
		{
			// convert to dB
			g = 20.f * log10(g);

			if(g < -100.0f) {
				g = -100.0f;
			}

			// set the volume
			s_pState->m_Stream.m_pVoice->SetVolume(g * 100.f);
		}

		s_pState->m_Stream.m_GainDirty = false;
	}
}

ALenum alGetError( ALvoid )
{
	ALenum error = s_pState->m_Error;
	s_pState->m_Error = AL_NO_ERROR;
	return error;
}

ALvoid alUpdate( ALvoid )
{
	DirectSoundDoWork();

	// update sources
	for (QALState::source_t::iterator i = s_pState->m_Sources.begin();
	i != s_pState->m_Sources.end(); ++i)
	{
		QALState::SourceInfo* info = i->second;
		
		// 3d sounds and dirty sources must be updated
		if (info->m_Is3d || info->m_GainDirty)
		{
			// only playing sources should be updated
			DWORD status;
			info->m_Voices.begin()->second->GetStatus(&status);

			if (status & DSBSTATUS_PLAYING)
			{
				_updateSource(info);
			}
		}
	}

	// update stream
	_updateStream();

	s_pState->m_SoundObject->CommitDeferredSettings();
}

ALvoid alGeti( ALenum param, ALint* value )
{
	switch (param)
	{
	case AL_MEMORY_USED:
		*value = s_pState->m_MemoryUsed;
		break;

	default:
		assert(0);
	}
}

ALvoid alGain( ALfloat value )
{
	s_pState->m_Gain = value;

	// set gain dirty for all sources
	for (QALState::source_t::iterator i = s_pState->m_Sources.begin();
	i != s_pState->m_Sources.end(); ++i)
	{
		i->second->m_GainDirty = true;
	}

	// set gain dirty for stream
	s_pState->m_Stream.m_GainDirty = true;
}
