/**
 * OpenAL cross platform audio library
 * Copyright (C) 1999-2000 by authors.
 * This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the
 *  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA  02111-1307, USA.
 * Or go to http://www.gnu.org/copyleft/lgpl.html
 */

#ifndef __MINGW32__
#define _CRT_SECURE_NO_DEPRECATE // get rid of sprintf security warnings on VS2005
#pragma comment(lib, "winmm.lib")
#endif

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include "../OpenAL32/include/alMain.h"
#include "AL/al.h"
#include "AL/alc.h"

///////////////////////////////////////////////////////
// DEBUG INFORMATION

#ifdef _DEBUG
 char szDebug[256];
#endif

///////////////////////////////////////////////////////


///////////////////////////////////////////////////////
// DEFINITIONS

#define SPEEDOFSOUNDMETRESPERSEC	(343.3f)
#define MAX_NUM_SOURCES				64
#define OUTPUT_BUFFER_SIZE			(32768*SWMIXER_OUTPUT_RATE/22050)

#define	TIMERINTERVAL	50

#ifndef DWORD_PTR_DEFINED
#define DWORD_PTR DWORD
#endif

#define sqr(a) ((a) * (a))
#define CLAMP(x, min, max) if (x < min)	x = min; else if (x > max) x = max;

#define LEVEL_TOLERANCE 100

///////////////////////////////////////////////////////


///////////////////////////////////////////////////////
// FUNCTION PROTOTYPES

// EAX Related
ALvoid ResetLocalFXParams(ALCcontext * ALCContext, ALuint ulSlotID);
void EAXFix(ALCcontext *context);

// Multimedia Timer Callback function prototype
static void CALLBACK WaveOutProc(HWAVEOUT hDevice,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2);
static void CALLBACK DirectSoundProc(UINT uID,UINT uReserved,DWORD_PTR dwUser,DWORD_PTR dwReserved1,DWORD_PTR dwReserved2);
void CALLBACK DirectSound3DProc(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2);
DWORD WINAPI ThreadProc(LPVOID lpParameter);

// Capture functions
static void CALLBACK WaveInProc(HWAVEIN hDevice,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2);
DWORD WINAPI CaptureThreadProc(LPVOID lpParameter);

ALvoid FillBuffer(ALsource *ALSource, ALubyte *pDestData, ALuint ulDestSize);

// Update Context functions
void UpdateSource(ALCcontext *ALContext, ALsource *ALSource);
void UpdateListener(ALCcontext *ALContext);

// DirectSound3D Device functions
ALuint GetMaxNumStereoBuffers(LPDIRECTSOUND lpDS);
ALuint GetMaxNum3DMonoBuffers(LPDIRECTSOUND lpDS);

// RollOff functions
void InitializeManualAttenuation(ALCcontext *pContext);
void SetDistanceModel(ALCcontext *pContext);
void SetNonEAXSourceLevels(ALCcontext *pContext, ALsource *pSource, ALuint ulFlags);

///////////////////////////////////////////////////////


///////////////////////////////////////////////////////
// STRING and EXTENSIONS

typedef struct ALCextension_struct
{
	ALCchar		*extName;
	ALvoid		*address;
} ALCextension;

typedef struct ALCfunction_struct
{
	ALCchar		*funcName;
	ALvoid		*address;
} ALCfunction;

static ALCextension alcExtensions[] = {
	{ "ALC_ENUMERATION_EXT",		(ALvoid *) NULL				},
	{ "ALC_EXT_CAPTURE",			(ALvoid *) NULL				},
	{ NULL,							(ALvoid *) NULL				} };

static ALCfunction  alcFunctions[] = {
	{ "alcCreateContext",           (ALvoid *) alcCreateContext         },
	{ "alcMakeContextCurrent",      (ALvoid *) alcMakeContextCurrent    },
	{ "alcProcessContext",          (ALvoid *) alcProcessContext        },
	{ "alcSuspendContext",          (ALvoid *) alcSuspendContext        },
	{ "alcDestroyContext",          (ALvoid *) alcDestroyContext        },
	{ "alcGetCurrentContext",       (ALvoid *) alcGetCurrentContext     },
	{ "alcGetContextsDevice",       (ALvoid *) alcGetContextsDevice     },
	{ "alcOpenDevice",              (ALvoid *) alcOpenDevice            },
	{ "alcCloseDevice",             (ALvoid *) alcCloseDevice           },
	{ "alcGetError",                (ALvoid *) alcGetError              },
	{ "alcIsExtensionPresent",      (ALvoid *) alcIsExtensionPresent    },
	{ "alcGetProcAddress",          (ALvoid *) alcGetProcAddress        },
	{ "alcGetEnumValue",            (ALvoid *) alcGetEnumValue          },
	{ "alcGetString",               (ALvoid *) alcGetString             },
	{ "alcGetIntegerv",             (ALvoid *) alcGetIntegerv           },
	{ "alcCaptureOpenDevice",		(ALvoid *) alcCaptureOpenDevice		},
	{ "alcCaptureCloseDevice",		(ALvoid *) alcCaptureCloseDevice	},
	{ "alcCaptureStart",			(ALvoid *) alcCaptureStart			},
	{ "alcCaptureStop",				(ALvoid *) alcCaptureStop			},
	{ "alcCaptureSamples",			(ALvoid *) alcCaptureSamples		},
	{ NULL,							(ALvoid *) NULL				} };

// Error strings
static ALCchar alcNoError[] = "No Error";
static ALCchar alcErrInvalidDevice[] = "Invalid Device";
static ALCchar alcErrInvalidContext[] = "Invalid Context";
static ALCchar alcErrInvalidEnum[] = "Invalid Enum";
static ALCchar alcErrInvalidValue[] = "Invalid Value";

// Context strings
static ALCchar alcDefaultDeviceSpecifier[] = "Generic Hardware";
static ALCchar alcDeviceList[] = "Generic Hardware\0Generic Software\0";
static ALCchar alcExtensionList[] = "ALC_ENUMERATION_EXT ALC_EXT_CAPTURE";

static ALCchar alcCaptureDefaultDeviceSpecifier[2048] = { 0 }; //  = "MMSYSTEM";
static ALCchar alcCaptureDeviceList[2048] = { 0 }; //  = "MMSYSTEM\0";

static ALCint alcMajorVersion = 1;
static ALCint alcMinorVersion = 1;

///////////////////////////////////////////////////////


///////////////////////////////////////////////////////
// Global Variables

// Critical Section data
extern CRITICAL_SECTION g_mutex;

// Context List
ALCcontext	*g_pContextList = NULL;
ALCuint		g_ulContextCount = 0;

// Context Error
static ALCenum g_eLastContextError = ALC_NO_ERROR;

// Capture Device List
ALCdevice *g_pCaptureDevice = NULL;

// EAX Related (need to be reset in alcOpenDevice)

typedef enum { NONE, EAX1, EAX2, EAX25, EAX3, EAX4 } EAXVERSION;

extern ALboolean bEAX2Initialized;
///////////////////////////////////////////////////////


///////////////////////////////////////////////////////
// ALC Related helper functions

/*
	IsContext

	Check pContext is a valid Context pointer
*/
ALCboolean IsContext(ALCcontext *pContext)
{
	ALCcontext *pTempContext;
	ALCboolean bReturn = AL_FALSE;

	pTempContext = g_pContextList;
	while (pTempContext)
	{
		if (pTempContext == pContext)
		{
			bReturn = AL_TRUE;
			break;
		}

		pTempContext = pTempContext->next;
	}

	return bReturn;
}


/*
	SetALCError

	Store latest ALC Error
*/
ALCvoid SetALCError(ALenum errorCode)
{
	g_eLastContextError = errorCode;
}


/*
	SuspendContext

	Thread-safe entry
*/
ALCvoid SuspendContext(ALCcontext *pContext)
{
	EnterCriticalSection(&g_mutex);
}


/*
	ProcessContext

	Thread-safe exit
*/
ALCvoid ProcessContext(ALCcontext *pContext)
{
	LeaveCriticalSection(&g_mutex);
}


/*
	InitContext

	Initialize Context variables
*/
ALvoid InitContext(ALCcontext *pContext)
{
	if (pContext)
	{
		//Lock context
		SuspendContext(pContext);
		//Initialise listener
		pContext->Listener.Gain=1.0f;
		pContext->Listener.Position[0]=0.0f;
		pContext->Listener.Position[1]=0.0f;
		pContext->Listener.Position[2]=0.0f;
		pContext->Listener.Velocity[0]=0.0f;
		pContext->Listener.Velocity[1]=0.0f;
		pContext->Listener.Velocity[2]=0.0f;
		pContext->Listener.Forward[0]=0.0f;
		pContext->Listener.Forward[1]=0.0f;
		pContext->Listener.Forward[2]=-1.0f;
		pContext->Listener.Up[0]=0.0f;
		pContext->Listener.Up[1]=1.0f;
		pContext->Listener.Up[2]=0.0f;

		pContext->Listener.EAX20LP.dwEnvironment = EAX_ENVIRONMENT_GENERIC;
		pContext->Listener.EAX20LP.flEnvironmentSize = EAXLISTENER_DEFAULTENVIRONMENTSIZE;
		pContext->Listener.EAX20LP.flEnvironmentDiffusion = EAXLISTENER_DEFAULTENVIRONMENTDIFFUSION;
		pContext->Listener.EAX20LP.lRoom = EAXLISTENER_MINROOM;
		pContext->Listener.EAX20LP.lRoomHF = EAXLISTENER_DEFAULTROOMHF;
		pContext->Listener.EAX20LP.flDecayTime = EAXLISTENER_DEFAULTDECAYTIME;
		pContext->Listener.EAX20LP.flDecayHFRatio = EAXLISTENER_DEFAULTDECAYHFRATIO;
		pContext->Listener.EAX20LP.lReflections = EAXLISTENER_DEFAULTREFLECTIONS;
		pContext->Listener.EAX20LP.flReflectionsDelay = EAXLISTENER_DEFAULTREFLECTIONSDELAY;
		pContext->Listener.EAX20LP.lReverb = EAXLISTENER_DEFAULTREVERB;
		pContext->Listener.EAX20LP.flReverbDelay = EAXLISTENER_DEFAULTREVERBDELAY;
		pContext->Listener.EAX20LP.flAirAbsorptionHF = EAXLISTENER_DEFAULTAIRABSORPTIONHF;
		pContext->Listener.EAX20LP.flRoomRolloffFactor = EAXLISTENER_DEFAULTROOMROLLOFFFACTOR;
		pContext->Listener.EAX20LP.dwFlags = EAXLISTENER_DEFAULTFLAGS;

		//Validate pContext
		pContext->LastError=AL_NO_ERROR;
		pContext->InUse=AL_FALSE;
		//Set output format
		pContext->Frequency=pContext->Device->Frequency;
		pContext->Channels=pContext->Device->Channels;
		pContext->Format=pContext->Device->Format;
		//Set globals
		pContext->DistanceModel=AL_INVERSE_DISTANCE_CLAMPED;
		pContext->DopplerFactor = 1.0f;
		pContext->DopplerVelocity = 1.0f;
		pContext->flSpeedOfSound = SPEEDOFSOUNDMETRESPERSEC;

		if (pContext->Device)
		{
			pContext->lNumStereoSources = 1;
			pContext->lNumMonoSources = pContext->Device->MaxNoOfSources - pContext->lNumStereoSources;
		}

		pContext->bUseManualAttenuation = AL_FALSE;
		
		// Initialize update to set all the Listener parameters
		pContext->Listener.update1 = LPOSITION | LVELOCITY | LORIENTATION | LDOPPLERFACTOR | LDOPPLERVELOCITY | LDISTANCEMODEL | LSPEEDOFSOUND;
		UpdateContext(pContext, ALLISTENER, 0);
			
		//Unlock pContext
		ProcessContext(pContext);
	}
}


/*
	ExitContext

	Clean up Context, destroy any remaining Sources
*/
ALCvoid ExitContext(ALCcontext *pContext)
{
	unsigned int i;
	ALsource *ALSource;
	ALsource *ALTempSource;

	if (IsContext(pContext))
	{
		//Lock context
		SuspendContext(pContext);

#ifdef _DEBUG
		if (pContext->SourceCount>0)
		{
			sprintf(szDebug,"OpenAL32 : alcDestroyContext() %d Source(s) NOT deleted\n", pContext->SourceCount);
			OutputDebugString(szDebug);
		}
#endif

		// Free all the Sources still remaining
		ALSource = pContext->Source;
		for (i = 0; i < pContext->SourceCount; i++)
		{
			if (ALSource->uservalue1)
			{
				IDirectSoundBuffer_Stop((LPDIRECTSOUNDBUFFER)ALSource->uservalue1);
				if (ALSource->uservalue3)
				{
					IKsPropertySet_Release((LPKSPROPERTYSET)ALSource->uservalue3);
					ALSource->uservalue3 = NULL;
				}
				if (ALSource->uservalue2)
				{
					IDirectSound3DBuffer_Release((LPDIRECTSOUND3DBUFFER)ALSource->uservalue2);
					ALSource->uservalue2 = NULL;
				}
				IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)ALSource->uservalue1);
				ALSource->uservalue1=NULL;
			}

			ALTempSource = ALSource->next;
            ALTHUNK_REMOVEENTRY(ALSource->source);
            memset(ALSource,0,sizeof(ALsource));
			free(ALSource);
			ALSource = ALTempSource;
		}

		// If we created the Permanent Source ( in EAXFix() ), then manually delete it now
		if (pContext->alPrivateSource)
		{
            ALSource=((ALsource *)ALTHUNK_LOOKUPENTRY(pContext->alPrivateSource));

			// Delete the Source
			if (ALSource->uservalue1)
			{
				IDirectSoundBuffer_Stop((LPDIRECTSOUNDBUFFER)ALSource->uservalue1);
				if (ALSource->uservalue3)
				{
					IKsPropertySet_Release((LPKSPROPERTYSET)ALSource->uservalue3);
					ALSource->uservalue3 = NULL;
				}
				if (ALSource->uservalue2)
				{
					IDirectSound3DBuffer_Release((LPDIRECTSOUND3DBUFFER)ALSource->uservalue2);
					ALSource->uservalue2 = NULL;
				}
				IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)ALSource->uservalue1);
				ALSource->uservalue1=NULL;
			}
            ALTHUNK_REMOVEENTRY(ALSource->source);
			memset(ALSource,0,sizeof(ALsource));
			free(ALSource);
		}

		//Invalidate context
		pContext->LastError=AL_NO_ERROR;
		pContext->InUse=AL_FALSE;

		//Unlock context
		ProcessContext(pContext);
	}
	else
		SetALCError(ALC_INVALID_CONTEXT);
}

///////////////////////////////////////////////////////


///////////////////////////////////////////////////////
// ALC Functions calls


// This should probably move to another c file but for now ...
ALCAPI ALCdevice* ALCAPIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, ALCuint frequency, ALCenum format, ALCsizei SampleSize)
{
	ALCdevice *pDevice = NULL;
	WAVEINCAPS WaveInCaps;
	ALint i;
	ALint lNumDevs, lBufferSize;
	ALint lDeviceID = 0;

	if (!g_pCaptureDevice)
	{
		pDevice = malloc(sizeof(ALCdevice));
		if (pDevice)
		{
			if (SampleSize > 0)
			{
				//Initialise device structure
				memset(pDevice,0,sizeof(ALCdevice));
				//Validate device
				pDevice->bInUse=AL_TRUE;
				pDevice->bIsCaptureDevice=AL_TRUE;

				memset(&pDevice->wfexCaptureFormat,0,sizeof(WAVEFORMATEX));
				pDevice->wfexCaptureFormat.wFormatTag = WAVE_FORMAT_PCM;
				pDevice->wfexCaptureFormat.nChannels = (((format==AL_FORMAT_STEREO8)||(format==AL_FORMAT_STEREO16))?2:1);
				pDevice->wfexCaptureFormat.wBitsPerSample = (((format==AL_FORMAT_MONO16)||(format==AL_FORMAT_STEREO16))?16:8);
				pDevice->wfexCaptureFormat.nBlockAlign = pDevice->wfexCaptureFormat.nChannels * pDevice->wfexCaptureFormat.wBitsPerSample/8;
				pDevice->wfexCaptureFormat.nSamplesPerSec = frequency;
				pDevice->wfexCaptureFormat.nAvgBytesPerSec = pDevice->wfexCaptureFormat.nSamplesPerSec * pDevice->wfexCaptureFormat.nBlockAlign;
				pDevice->wfexCaptureFormat.cbSize=0;

				// Find the Device ID matching the deviceName if valid
				if (deviceName)
				{
					lNumDevs = waveInGetNumDevs();
					for (i = 0; i < lNumDevs; i++)
					{
						if (waveInGetDevCaps(i, &WaveInCaps, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR)
						{
							if (!strcmp(deviceName, WaveInCaps.szPname))
							{
								lDeviceID = i;
								break;
							}
						}
					}
				}

				if (waveInOpen(&pDevice->hWaveInHandle,lDeviceID,&pDevice->wfexCaptureFormat,(DWORD_PTR)&WaveInProc,(DWORD_PTR)pDevice,CALLBACK_FUNCTION)==MMSYSERR_NOERROR)
				{
					pDevice->hWaveInHdrEvent = CreateEvent(NULL, AL_TRUE, AL_FALSE, "WaveInAllHeadersReturned");
					if (pDevice->hWaveInHdrEvent != NULL)
					{
						pDevice->hWaveInThreadEvent = CreateEvent(NULL, AL_TRUE, AL_FALSE, "WaveInThreadDestroyed");
						if (pDevice->hWaveInThreadEvent != NULL)
						{
							pDevice->hWaveInThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)CaptureThreadProc,(LPVOID)pDevice,0,&pDevice->ulWaveInThreadID);
							if (pDevice->hWaveInThread != NULL)
							{
								// Allocate circular memory buffer for the captured audio
								pDevice->ulCapturedDataSize = SampleSize * pDevice->wfexCaptureFormat.nBlockAlign;

								// Make sure circular buffer is at least 100ms in size (and an exact multiple of
								// the block alignment
								if (pDevice->ulCapturedDataSize < (pDevice->wfexCaptureFormat.nAvgBytesPerSec / 10))
								{
									pDevice->ulCapturedDataSize = pDevice->wfexCaptureFormat.nAvgBytesPerSec / 10;
									pDevice->ulCapturedDataSize -= (pDevice->ulCapturedDataSize % pDevice->wfexCaptureFormat.nBlockAlign);
								}

								pDevice->pCapturedSampleData = (ALCchar*)malloc(pDevice->ulCapturedDataSize);
								pDevice->lWaveInBuffersCommitted=0;

								// Create 4 Buffers of 50ms each
								lBufferSize = pDevice->wfexCaptureFormat.nAvgBytesPerSec / 20;
								lBufferSize -= (lBufferSize % pDevice->wfexCaptureFormat.nBlockAlign);

								for (i=0;i<4;i++)
								{
									memset(&pDevice->WaveInBuffer[i],0,sizeof(WAVEHDR));
									pDevice->WaveInBuffer[i].dwBufferLength=lBufferSize;
									pDevice->WaveInBuffer[i].lpData=malloc(pDevice->WaveInBuffer[i].dwBufferLength);
									if (pDevice->WaveInBuffer[i].lpData)
										memset(pDevice->WaveInBuffer[i].lpData, 0, pDevice->WaveInBuffer[i].dwBufferLength);
									pDevice->WaveInBuffer[i].dwFlags=0;
									pDevice->WaveInBuffer[i].dwLoops=0;
									waveInPrepareHeader(pDevice->hWaveInHandle, &pDevice->WaveInBuffer[i], sizeof(WAVEHDR));
									waveInAddBuffer(pDevice->hWaveInHandle, &pDevice->WaveInBuffer[i], sizeof(WAVEHDR));
									pDevice->lWaveInBuffersCommitted++;
								}

								pDevice->ulReadCapturedDataPos = 0;
								pDevice->ulWriteCapturedDataPos = 0;

								g_pCaptureDevice = pDevice;

								if (waveInGetDevCaps(lDeviceID, &WaveInCaps, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR)
									strcpy(g_pCaptureDevice->szDeviceName, WaveInCaps.szPname);
							}
						}
					}
				}

				if (!g_pCaptureDevice)
				{
					if (pDevice->hWaveInThreadEvent)
					{
						CloseHandle(pDevice->hWaveInThreadEvent);
						pDevice->hWaveInThreadEvent = NULL;
					}

					if (pDevice->hWaveInHdrEvent)
					{
						CloseHandle(pDevice->hWaveInHdrEvent);
						pDevice->hWaveInHdrEvent = NULL;
					}

					if (pDevice->hWaveInHandle)
					{
						waveInClose(pDevice->hWaveInHandle);
						pDevice->hWaveInHandle = NULL;
					}
				}
			}
			else
			{
				SetALCError(ALC_INVALID_VALUE);
			}
		}
		else
		{
			SetALCError(ALC_OUT_OF_MEMORY);
		}
	}

	return pDevice;
}

ALCAPI ALCboolean ALCAPIENTRY alcCaptureCloseDevice(ALCdevice *pDevice)
{
	ALint i;
	ALCboolean bReturn = ALC_FALSE;

	if ((pDevice)&&(pDevice->bIsCaptureDevice))
	{		
		// Call waveOutReset to shutdown wave device
		pDevice->bWaveInShutdown = AL_TRUE;
		waveInReset(pDevice->hWaveInHandle);

		// Wait for signal that all Wave Buffers have returned
		WaitForSingleObjectEx(pDevice->hWaveInHdrEvent, 5000, FALSE);

		// Wait for signal that Wave Thread has been destroyed
		WaitForSingleObjectEx(pDevice->hWaveInThreadEvent, 5000, FALSE);

		// Release the wave buffers
		for (i=0;i<4;i++)
		{
			waveInUnprepareHeader(pDevice->hWaveInHandle, &pDevice->WaveInBuffer[i], sizeof(WAVEHDR));
			free(pDevice->WaveInBuffer[i].lpData);
		}
	
		// Free Audio Buffer data
		free(pDevice->pCapturedSampleData);
		pDevice->pCapturedSampleData = NULL;

		// Close the Wave device
		waveInClose(pDevice->hWaveInHandle);
		pDevice->hWaveInHandle = 0;

		CloseHandle(pDevice->hWaveInThread);
		pDevice->hWaveInThread = 0;

		if (pDevice->hWaveInHdrEvent)
		{
			CloseHandle(pDevice->hWaveInHdrEvent);
			pDevice->hWaveInHdrEvent = 0;
		}

		if (pDevice->hWaveInThreadEvent)
		{
			CloseHandle(pDevice->hWaveInThreadEvent);
			pDevice->hWaveInThreadEvent = 0;
		}

		g_pCaptureDevice = NULL;

		bReturn = ALC_TRUE;
	}
	else
	{
		SetALCError(ALC_INVALID_DEVICE);
	}

	return bReturn;
}

ALCAPI void ALCAPIENTRY alcCaptureStart(ALCdevice *pDevice)
{
	if ((pDevice)&&(pDevice->bIsCaptureDevice))
		waveInStart(pDevice->hWaveInHandle);
	else
		SetALCError(ALC_INVALID_DEVICE);
}

ALCAPI void ALCAPIENTRY alcCaptureStop(ALCdevice *pDevice)
{
	if ((pDevice)&&(pDevice->bIsCaptureDevice))
		waveInStop(pDevice->hWaveInHandle);
	else
		SetALCError(ALC_INVALID_DEVICE);
}

ALCAPI void ALCAPIENTRY alcCaptureSamples(ALCdevice *pDevice, ALCvoid *pBuffer, ALCsizei lSamples)
{
	ALuint	ulCapturedSamples;
	ALuint	ulBytes, ulBytesToCopy;
	ALuint	ulReadOffset;
	ALuint	ulSamples = (unsigned long)lSamples;

	if ((pDevice) && (pDevice->bIsCaptureDevice))
	{
		SuspendContext(NULL);

		// Check that we have the requested numbers of Samples
		ulCapturedSamples = (pDevice->ulWriteCapturedDataPos - pDevice->ulReadCapturedDataPos) / pDevice->wfexCaptureFormat.nBlockAlign;

		if (ulSamples <= ulCapturedSamples)
		{
			ulBytes = ulSamples * pDevice->wfexCaptureFormat.nBlockAlign;

			// Get Read Offset
			ulReadOffset = (pDevice->ulReadCapturedDataPos % pDevice->ulCapturedDataSize);

			// Check for wrap-around condition
			if ((ulReadOffset + ulBytes) > pDevice->ulCapturedDataSize)
			{
				// Copy data from last Read position to end of data
				ulBytesToCopy = pDevice->ulCapturedDataSize - ulReadOffset;
				memcpy(pBuffer, pDevice->pCapturedSampleData + ulReadOffset, ulBytesToCopy);

				// Copy rest of the data from the start of the captured data
				memcpy(((char *)pBuffer) + ulBytesToCopy, pDevice->pCapturedSampleData, ulBytes - ulBytesToCopy);
			}
			else
			{
				// Copy data from the read position in the captured data
				memcpy(pBuffer, pDevice->pCapturedSampleData + ulReadOffset, ulBytes);
			}

			// Update Read Position
			pDevice->ulReadCapturedDataPos += ulBytes;
		}
		else
		{
			SetALCError(ALC_INVALID_VALUE);
		}

		ProcessContext(NULL);
	}
	else
	{
		SetALCError(ALC_INVALID_DEVICE);
	}
}

/*
	WaveInProc

	Posts a message to 'CaptureThreadProc' everytime a WaveIn Buffer is completed and
	returns to the application (with more data)
*/
static void CALLBACK WaveInProc(HWAVEIN hDevice,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2)
{
	ALCdevice *pDevice;

	pDevice = (ALCdevice *)dwInstance;
	if ((uMsg==WIM_DATA) && (pDevice))
	{
		// Decrement number of buffers in use
		pDevice->lWaveInBuffersCommitted--;

		if (pDevice->bWaveInShutdown == AL_FALSE)
		{
			// Notify Wave Processor Thread that a Wave Header has returned
			PostThreadMessage(pDevice->ulWaveInThreadID,uMsg,0,dwParam1);
		}
		else
		{
			if (pDevice->lWaveInBuffersCommitted == 0)
			{
				// Signal Wave Buffers Returned event
				if (pDevice->hWaveInHdrEvent)
					SetEvent(pDevice->hWaveInHdrEvent);
				
				// Post 'Quit' Message to WaveIn Processor Thread
				PostThreadMessage(pDevice->ulWaveInThreadID,WM_QUIT,0,0);
			}
		}
	}
}

/*
	CaptureThreadProc

	Used by "MMSYSTEM" Device.  Called when a WaveIn buffer had been filled with new
	audio data.
*/
DWORD WINAPI CaptureThreadProc(LPVOID lpParameter)
{
	ALCdevice *pDevice;
	LPWAVEHDR pWaveHdr;
	MSG msg;
	ALuint ulOffset, ulMaxSize, ulSection;

	pDevice = (ALCdevice*)lpParameter;

	while (GetMessage(&msg, NULL, 0, 0))
	{
		if ((msg.message==WIM_DATA)&&(!pDevice->bWaveInShutdown))
		{
			SuspendContext(NULL);

			pWaveHdr = ((LPWAVEHDR)msg.lParam);

			// Calculate offset in local buffer to write data to
			ulOffset = pDevice->ulWriteCapturedDataPos % pDevice->ulCapturedDataSize;

			if ((ulOffset + pWaveHdr->dwBytesRecorded) > pDevice->ulCapturedDataSize)
			{
				ulSection = pDevice->ulCapturedDataSize - ulOffset;
				memcpy(pDevice->pCapturedSampleData + ulOffset, pWaveHdr->lpData, ulSection);
				memcpy(pDevice->pCapturedSampleData, pWaveHdr->lpData + ulSection, pWaveHdr->dwBytesRecorded - ulSection);
			}
			else
			{
				memcpy(pDevice->pCapturedSampleData + ulOffset, pWaveHdr->lpData, pWaveHdr->dwBytesRecorded);
			}

			pDevice->ulWriteCapturedDataPos += pWaveHdr->dwBytesRecorded;

			if (pDevice->ulWriteCapturedDataPos > (pDevice->ulReadCapturedDataPos + pDevice->ulCapturedDataSize))
			{
				// Application has not read enough audio data from the capture buffer so data has been
				// overwritten.  Reset ReadPosition.
				pDevice->ulReadCapturedDataPos = pDevice->ulWriteCapturedDataPos - pDevice->ulCapturedDataSize;
			}

			// To prevent an over-flow prevent the offset values from getting too large
			ulMaxSize = pDevice->ulCapturedDataSize << 4;
			if ((pDevice->ulReadCapturedDataPos > ulMaxSize) && (pDevice->ulWriteCapturedDataPos > ulMaxSize))
			{
				pDevice->ulReadCapturedDataPos -= ulMaxSize;
				pDevice->ulWriteCapturedDataPos -= ulMaxSize;
			}

			// Send buffer back to capture more data
			waveInAddBuffer(pDevice->hWaveInHandle,pWaveHdr,sizeof(WAVEHDR));
			pDevice->lWaveInBuffersCommitted++;

			ProcessContext(NULL);
		}
	}

	// Signal Wave Thread completed event
	if (pDevice->hWaveInThreadEvent)
		SetEvent(pDevice->hWaveInThreadEvent);

	ExitThread(0);

	return 0;
}

/*
	alcGetError

	Return last ALC generated error code
*/
ALCAPI ALCenum ALCAPIENTRY alcGetError(ALCdevice *device)
{
	ALCenum errorCode;

	errorCode = g_eLastContextError;
	g_eLastContextError = ALC_NO_ERROR;
	return errorCode;
}


/*
	alcSuspendContext

	Not functional
*/
ALCAPI ALCvoid ALCAPIENTRY alcSuspendContext(ALCcontext *pContext)
{
	// Not a lot happens here !
}


/*
	alcProcessContext

	Not functional
*/
ALCAPI ALCvoid ALCAPIENTRY alcProcessContext(ALCcontext *pContext)
{
	// Not a lot happens here !
}


/*
	alcGetString

	Returns information about the Device, and error strings
*/
ALCAPI const ALCchar* ALCAPIENTRY alcGetString(ALCdevice *pDevice,ALCenum param)
{
	WAVEINCAPS WaveInCaps;
	const ALCchar *value = NULL;
	ALint lNumDevs, lLoop;
	ALCchar *szDeviceList;

	switch (param)
	{
	case ALC_NO_ERROR:
		value=alcNoError;
		break;

	case ALC_INVALID_ENUM:
		value=alcErrInvalidEnum;
		break;

	case ALC_INVALID_VALUE:
		value=alcErrInvalidValue;
		break;

	case ALC_INVALID_DEVICE:
		value=alcErrInvalidDevice;
		break;

	case ALC_INVALID_CONTEXT:
		value=alcErrInvalidContext;
		break;

	case ALC_DEFAULT_DEVICE_SPECIFIER:
		value = alcDefaultDeviceSpecifier;
		break;

	case ALC_DEVICE_SPECIFIER:
		if (pDevice)
			value = pDevice->szDeviceName;
		else
			value = alcDeviceList;
		break;

	case ALC_CAPTURE_DEVICE_SPECIFIER:
		if (pDevice)
		{
			value = pDevice->szDeviceName;
		}
		else
		{
			szDeviceList = alcCaptureDeviceList;

			lNumDevs = waveInGetNumDevs();
			for (lLoop = 0; lLoop < lNumDevs; lLoop++)
			{
				if (waveInGetDevCaps(lLoop, &WaveInCaps, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR)
				{
					sprintf(szDeviceList, "%s", WaveInCaps.szPname);
					szDeviceList += strlen(szDeviceList) + 1;
				}
			}

			value = alcCaptureDeviceList;
		}
		break;

	case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER:
		if (waveInGetNumDevs())
		{
			if (waveInGetDevCaps(0, &WaveInCaps, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR)
				sprintf(alcCaptureDefaultDeviceSpecifier, "%s", WaveInCaps.szPname);
		}
		value = alcCaptureDefaultDeviceSpecifier;
		break;

	case ALC_EXTENSIONS:
		value = alcExtensionList;
		break;

	default:
		SetALCError(ALC_INVALID_ENUM);
		break;
	}

	return value;
}


/*
	alcGetIntegerv

	Returns information about the Device and the version of Open AL
*/
ALCAPI ALCvoid ALCAPIENTRY alcGetIntegerv(ALCdevice *device,ALCenum param,ALsizei size,ALCint *data)
{
	ALCcontext *Context;
	ALint lCapturedBytes;

	if ((device)&&(device->bIsCaptureDevice))
	{
		SuspendContext(NULL);

		// Capture device
		switch (param)
		{
		case ALC_CAPTURE_SAMPLES:
			if ((size) && (data))
			{
				lCapturedBytes = (device->ulWriteCapturedDataPos - device->ulReadCapturedDataPos);
				*data = lCapturedBytes / device->wfexCaptureFormat.nBlockAlign;
			}
			else
			{
				SetALCError(ALC_INVALID_VALUE);
			}
			break;

		default:
			SetALCError(ALC_INVALID_ENUM);
			break;
		}

		ProcessContext(NULL);
	}
	else
	{
		Context=alcGetCurrentContext();
		if (Context)
		{
			SuspendContext(Context);

			if (data)
			{
				// Playback Device
				switch (param)
				{
				case ALC_MAJOR_VERSION:
					if (size)
						*data = alcMajorVersion;
					else
						SetALCError(ALC_INVALID_VALUE);
					break;

				case ALC_MINOR_VERSION:
					if (size)
						*data = alcMinorVersion;
					else
						SetALCError(ALC_INVALID_VALUE);
					break;

				case ALC_ATTRIBUTES_SIZE:
					if (device)
					{
						if (size)
							*data = 11;
						else
							SetALCError(ALC_INVALID_VALUE);
					}
					else
					{
						SetALCError(ALC_INVALID_DEVICE);
					}
					break;

				case ALC_ALL_ATTRIBUTES:
					if (device)
					{
						if (size >= 11)
						{
							data[0] = ALC_FREQUENCY;
							if (device->lpDS3DListener)
								data[1] = 44100;
							else
								data[1] = SWMIXER_OUTPUT_RATE;

							data[2] = ALC_REFRESH;
							if (device->lpDS3DListener)
								data[3] = 20;
							else
								data[3] = 40;

							data[4] = ALC_SYNC;
							data[5] = AL_FALSE;

							data[6] = ALC_MONO_SOURCES;
							data[7] = Context->lNumMonoSources;

							data[8] = ALC_STEREO_SOURCES;
							data[9] = Context->lNumStereoSources;

							data[10] = 0;
						}
						else
						{
							SetALCError(ALC_INVALID_VALUE);
						}
					}
					else
					{
						SetALCError(ALC_INVALID_DEVICE);
					}
					break;

				default:
					SetALCError(ALC_INVALID_ENUM);
					break;
				}
			}
			else
			{
				SetALCError(ALC_INVALID_VALUE);
			}

			ProcessContext(Context);
		}
		else
		{
			// Invalid Context
			SetALCError(ALC_INVALID_CONTEXT);
		}
	}

	return;
}


/*
	alcIsExtensionPresent

	Determines if there is support for a particular extension
*/
ALCAPI ALCboolean ALCAPIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName)
{
	ALsizei i = 0;
	ALCboolean bResult = ALC_FALSE;

	if (extName)
	{
		while ((alcExtensions[i].extName)&&(_stricmp(alcExtensions[i].extName,extName)))
			i++;

		if (alcExtensions[i].extName)
			bResult = ALC_TRUE;
	}
	else
	{
		SetALCError(ALC_INVALID_VALUE);
	}

	return bResult;
}


/*
	alcGetProcAddress

	Retrieves the function address for a particular extension function
*/
ALCAPI ALCvoid *  ALCAPIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName)
{
	ALsizei i = 0;
	ALCvoid *pFunction = NULL;

	if (funcName)
	{
		while ((alcFunctions[i].funcName)&&(strcmp(alcFunctions[i].funcName,funcName)))
			i++;
		pFunction = alcFunctions[i].address;
	}
	else
	{
		SetALCError(ALC_INVALID_VALUE);
	}

	return pFunction;
}


/*
	alcGetEnumValue

	Get the value for a particular ALC Enumerated Value
	Calls alGetEnumValue in alExtension.c to process request
*/
ALCAPI ALCenum ALCAPIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName)
{
	ALCenum eResult = AL_NONE;

	if (enumName)
		eResult = alGetEnumValue(enumName);
	else
		SetALCError(ALC_INVALID_VALUE);

	return eResult;
}


/*
	alcCreateContext

	Create and attach a Context to a particular Device.
*/
ALCAPI ALCcontext* ALCAPIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList)
{
	ALCcontext *ALContext = NULL;
	ALboolean	bDeviceHasContext = AL_FALSE;
	ALuint		ulAttributeIndex, ulRequestedStereoSources;

	if ((device)&&(!device->bIsCaptureDevice))
	{
		// Reset Context Last Error code
		g_eLastContextError = ALC_NO_ERROR;

		// Check if this Device already has been assigned a Context (current implementation
		// only allows one Context per Device)
		ALContext = g_pContextList;
		while (ALContext)
		{
			if (ALContext->Device == device)
			{
				bDeviceHasContext = AL_TRUE;
				break;
			}
			ALContext = ALContext->next;
		}

		if (!bDeviceHasContext)
		{
			if (!g_pContextList)
			{
				g_pContextList = malloc(sizeof(ALCcontext));
				
				if (g_pContextList)
				{
					memset(g_pContextList, 0, sizeof(ALCcontext));
					g_pContextList->Device = device;
					InitContext(g_pContextList);
					g_ulContextCount++;
				}

				ALContext = g_pContextList;
			}
			else
			{
				ALContext = g_pContextList;
				while (ALContext->next)
					ALContext=ALContext->next;
				if (ALContext)
				{
					ALContext->next=malloc(sizeof(ALCcontext));
					if (ALContext->next)
					{
						memset(ALContext->next,0,sizeof(ALCcontext));
						ALContext->next->previous=ALContext;
						ALContext->next->Device=device;
						InitContext(ALContext->next);
						g_ulContextCount++;
					}
					ALContext=ALContext->next;
				}
			}

			// Check for Voice Count attributes
			if (attrList)
			{
				ulAttributeIndex = 0;
				while ((ulAttributeIndex < 10) && (attrList[ulAttributeIndex]))
				{
					if (attrList[ulAttributeIndex] == ALC_STEREO_SOURCES)
					{
						ulRequestedStereoSources = attrList[ulAttributeIndex + 1];

						if (ALContext->Device->lpDS3DListener)
						{
							// As we have already reserved a voice, add it back to the max Source count
							ALContext->Device->MaxNoOfSources++;

							if ((ulRequestedStereoSources * 2) > ALContext->Device->MaxNoOfSources)
								ulRequestedStereoSources = ALContext->Device->MaxNoOfSources / 2;

							ALContext->Device->MaxNoOfSources -= ulRequestedStereoSources;
							ALContext->lNumStereoSources = ulRequestedStereoSources;
							ALContext->lNumMonoSources = ALContext->Device->MaxNoOfSources - ALContext->lNumStereoSources;
						}
						else
						{
							if (ulRequestedStereoSources > ALContext->Device->MaxNoOfSources)
								ulRequestedStereoSources = ALContext->Device->MaxNoOfSources;

							ALContext->lNumStereoSources = ulRequestedStereoSources;
							ALContext->lNumMonoSources = ALContext->Device->MaxNoOfSources - ALContext->lNumStereoSources;
						}
						break;
					}

					ulAttributeIndex += 2;
				}
			}
		}
		else
		{
			SetALCError(ALC_INVALID_VALUE);
			ALContext = NULL;
		}
	}
	else
	{
		SetALCError(ALC_INVALID_DEVICE);
	}

	return ALContext;
}


/*
	alcDestroyContext

	Remove a Context
*/
ALCAPI ALCvoid ALCAPIENTRY alcDestroyContext(ALCcontext *context)
{
	ALCcontext *ALContext;

	if (IsContext(context))
	{
		// Lock context
		SuspendContext(context);
		ALContext=((ALCcontext *)context);
		ExitContext(ALContext);
		if (ALContext->previous)
			ALContext->previous->next=ALContext->next;
		else
			g_pContextList = ALContext->next;
		if (ALContext->next)
			ALContext->next->previous=ALContext->previous;
		memset(ALContext,0,sizeof(ALCcontext));
		g_ulContextCount--;
		// Unlock context
		ProcessContext(context);
		// Free memory (MUST do this after ProcessContext)
		free(ALContext);
	}
	else
	{
		SetALCError(ALC_INVALID_CONTEXT);
	}
}


/*
	alcGetCurrentContext

	Returns the currently active Context
*/
ALCAPI ALCcontext * ALCAPIENTRY alcGetCurrentContext(ALCvoid)
{
	ALCcontext *pContext = NULL;

	pContext = g_pContextList;
	while ((pContext) && (!pContext->InUse))
		pContext = pContext->next;

	return pContext;
}


/*
	alcGetContextsDevice

	Returns the Device that a particular Context is attached to
*/
ALCAPI ALCdevice* ALCAPIENTRY alcGetContextsDevice(ALCcontext *pContext)
{
	ALCdevice *pDevice = NULL;
	ALCcontext *pTempContext = NULL;

	pTempContext = pContext;
	if (IsContext(pTempContext))
	{
		SuspendContext(pTempContext);
		pDevice = pTempContext->Device;
		ProcessContext(pTempContext);
	}
	else
	{
		SetALCError(ALC_INVALID_CONTEXT);
	}

	return pDevice;
}


/*
	alcMakeContextCurrent

	Makes the given Context the active Context
*/
ALCAPI ALCboolean ALCAPIENTRY alcMakeContextCurrent(ALCcontext *context)
{
	ALCcontext *ALContext;
	ALboolean bReturn = AL_TRUE;
	
	// context must be a valid Context or NULL
	if ((IsContext(context)) || (context == NULL))
	{
		if (ALContext=alcGetCurrentContext())
		{
			SuspendContext(ALContext);
			ALContext->InUse=AL_FALSE;
			ProcessContext(ALContext);
		}

		if ((ALContext=context) && (ALContext->Device))
		{
			SuspendContext(ALContext);
			ALContext->InUse=AL_TRUE;

			// If we are using the DS3D Device then we may need to work-around a problem with the
			// FCS Audigy WDM drivers (see EAXFix() for more details)
			if (ALContext->Device->lpDS3DListener)
			{
				EAXFix(context);
			}

			ProcessContext(ALContext);
		}
	}
	else
	{
		SetALCError(ALC_INVALID_CONTEXT);
		bReturn = AL_FALSE;
	}

	return bReturn;
}


/*
	alcOpenDevice

	Open the Device specified.  Current options are "Generic Hardware", "Generic Software", and for legacy
	"DirectSound3D", "DirectSound" and "MMSYSTEM"
*/
ALCAPI ALCdevice* ALCAPIENTRY alcOpenDevice(const ALCchar *deviceName)
{
	DSBUFFERDESC DSBDescription;
	WAVEFORMATEX OutputType;
	TIMECAPS timeCaps;
	ALCdevice *device=NULL;
	ALint vmode=1;
	ALint i;
	DSCAPS dsCaps;
	ALvoid *lpPart1, *lpPart2;
	ALuint dwSize1, dwSize2;
	ALboolean bUseDS;
	ALboolean bUseDS3D;
	ALboolean bDeviceFound = AL_FALSE;
	ALint numSources;

	bUseDS = AL_FALSE;
	bUseDS3D = AL_FALSE;

	if (deviceName)
	{
		if ((!strcmp(deviceName,"DirectSound3D")) || (!strcmp(deviceName,"Generic Hardware")))
			bUseDS3D = AL_TRUE;
		else if ((!strcmp(deviceName, "DirectSound")) || (!strcmp(deviceName, "Generic Software")))
			bUseDS = AL_TRUE;
	}
	else
	{
		// If no device name is specified, we will attempt to use DS3D
		bUseDS3D = AL_TRUE;
	}

	device=malloc(sizeof(ALCdevice));
	if (device)
	{
		//Initialise device structure
		memset(device,0,sizeof(ALCdevice));
		//Validate device
		device->bInUse=AL_TRUE;
		device->bIsCaptureDevice=AL_FALSE;
		//Set output format
		device->Frequency=SWMIXER_OUTPUT_RATE;
		device->Channels=2;
		device->Format=AL_FORMAT_STEREO16;
		//Wave-Out properties
		device->bWaveShutdown = AL_FALSE;
		device->hWaveHdrEvent = NULL;
		device->hWaveThreadEvent = NULL;
		device->hWaveThread = NULL;
		device->ulWaveThreadID = 0;
		device->lWaveBuffersCommitted = 0;
		//Reset EAX Variables
		bEAX2Initialized = AL_FALSE;
		//Platform specific
		memset(&OutputType,0,sizeof(WAVEFORMATEX));
		OutputType.wFormatTag=WAVE_FORMAT_PCM;
        OutputType.nChannels=(WORD)device->Channels;
		OutputType.wBitsPerSample=(((device->Format==AL_FORMAT_MONO16)||(device->Format==AL_FORMAT_STEREO16))?16:8);
		OutputType.nBlockAlign=OutputType.nChannels*OutputType.wBitsPerSample/8;
		OutputType.nSamplesPerSec=device->Frequency;
		OutputType.nAvgBytesPerSec=OutputType.nSamplesPerSec*OutputType.nBlockAlign;
		OutputType.cbSize=0;
		//Initialise requested device

		if (bUseDS3D)
		{
			//Init COM
			CoInitialize(NULL);
			//DirectSound Init code
			if (CoCreateInstance(&CLSID_DirectSound,NULL,CLSCTX_INPROC_SERVER,&IID_IDirectSound,&(device->lpDS))==S_OK)
			{
				if (IDirectSound_Initialize(device->lpDS,NULL)==DS_OK)
				{
					if (IDirectSound_SetCooperativeLevel(device->lpDS,GetForegroundWindow(),DSSCL_PRIORITY)==DS_OK)
					{
						memset(&DSBDescription,0,sizeof(DSBUFFERDESC));
						DSBDescription.dwSize=sizeof(DSBUFFERDESC);
						DSBDescription.dwFlags=DSBCAPS_PRIMARYBUFFER|DSBCAPS_CTRL3D|DSBCAPS_CTRLVOLUME;
						if (IDirectSound_CreateSoundBuffer(device->lpDS,&DSBDescription,&device->DSpbuffer,NULL)==DS_OK)
						{
							if (IDirectSoundBuffer_SetFormat(device->DSpbuffer,&OutputType)==DS_OK)
							{
								if (IDirectSoundBuffer_QueryInterface(device->DSpbuffer,&IID_IDirectSound3DListener,&device->lpDS3DListener)==DS_OK)
								{
									memset(&dsCaps, 0, sizeof(DSCAPS));
									dsCaps.dwSize = sizeof(DSCAPS);
									if (IDirectSound_GetCaps(device->lpDS, &dsCaps) == DS_OK)
									{
										// Check that is an accelerated DS device
										if (!(dsCaps.dwFlags & DSCAPS_EMULDRIVER))
										{
											numSources = GetMaxNum3DMonoBuffers(device->lpDS);

											// To enable the 'DirectSound3D' device, the audio card MUST support
											// at least 16 voices.  (If not, then the device selection drops through
											// to the 'DirectSound' device).
											if (numSources >= 16)
											{
												device->MaxNoOfSources = numSources - 2; // Reserved one for a Stereo Source, and one for EAXFix

												if ((deviceName) && (!strcmp(deviceName,"DirectSound3D")))
													strcpy(device->szDeviceName, "DirectSound3D");
												else
													strcpy(device->szDeviceName, "Generic Hardware");

												// Set-up Timer for serving streaming buffers
												device->ulDS3DTimerInterval = TIMERINTERVAL;
												device->ulDS3DTimerID = 0;

												// Get the Timer Capabilities of the system
												timeGetDevCaps(&timeCaps, sizeof(TIMECAPS));

												// If desired accuracy is not available, then just go with the best that we have
												if (timeCaps.wPeriodMin > device->ulDS3DTimerInterval)
													device->ulDS3DTimerInterval = timeCaps.wPeriodMin;

												// Begin Time Period
												timeBeginPeriod(device->ulDS3DTimerInterval);

												bDeviceFound = AL_TRUE;
											}
										}
									}
								}
							}
						}
					}
				}
			}

			if (!bDeviceFound)
			{
				if (device->lpDS3DListener)
				{
					IDirectSound3DListener_Release(device->lpDS3DListener);
					device->lpDS3DListener=NULL;
				}

				if (device->DSpbuffer)
				{
					IDirectSoundBuffer_Release(device->DSpbuffer);
					device->DSpbuffer=NULL;
				}

				if (device->lpDS)
				{
					IDirectSound_Release(device->lpDS);
					device->lpDS = NULL;
				}

				// Failed to initialize DirectSound3D device - so fall back to DirectSound device
				bUseDS = AL_TRUE;
			}
		}

		if (bUseDS)
		{
			//Init COM
			CoInitialize(NULL);
			//DirectSound Init code
			if (CoCreateInstance(&CLSID_DirectSound,NULL,CLSCTX_INPROC_SERVER,&IID_IDirectSound,&device->lpDS)==S_OK)
			{
				if (IDirectSound_Initialize(device->lpDS,NULL)==DS_OK)
				{
					if (IDirectSound_SetCooperativeLevel(device->lpDS,GetForegroundWindow(),DSSCL_PRIORITY)==DS_OK)
					{
						memset(&DSBDescription,0,sizeof(DSBUFFERDESC));
						DSBDescription.dwSize=sizeof(DSBUFFERDESC);
						DSBDescription.dwFlags=DSBCAPS_PRIMARYBUFFER;
						if (IDirectSound_CreateSoundBuffer(device->lpDS,&DSBDescription,&device->DSpbuffer,NULL)==DS_OK)
						{
							if (IDirectSoundBuffer_SetFormat(device->DSpbuffer,&OutputType)==DS_OK)
							{
								memset(&DSBDescription,0,sizeof(DSBUFFERDESC));
								DSBDescription.dwSize=sizeof(DSBUFFERDESC);
								DSBDescription.dwFlags=DSBCAPS_GLOBALFOCUS|DSBCAPS_GETCURRENTPOSITION2;
								DSBDescription.dwBufferBytes=OUTPUT_BUFFER_SIZE;
								DSBDescription.lpwfxFormat=&OutputType;
								if (IDirectSound_CreateSoundBuffer(device->lpDS,&DSBDescription,&device->DSsbuffer,NULL)==DS_OK)
								{
									// Check that is a hardware accelerated DS device
									memset(&dsCaps, 0, sizeof(DSCAPS));
									dsCaps.dwSize = sizeof(DSCAPS);
									if (IDirectSound_GetCaps(device->lpDS, &dsCaps) == DS_OK)
									{
										// Check that is an accelerated DS device
										if (!(dsCaps.dwFlags & DSCAPS_EMULDRIVER))
										{
											if (IDirectSoundBuffer_Lock(device->DSsbuffer,0,0,&lpPart1, &dwSize1, &lpPart2, &dwSize2, DSBLOCK_ENTIREBUFFER ) == DS_OK)
											{
												memset(lpPart1, 0, dwSize1);
												IDirectSoundBuffer_Unlock(device->DSsbuffer,lpPart1, dwSize1, lpPart1, dwSize2);
											}
											if (IDirectSoundBuffer_Play(device->DSsbuffer,0,0,DSBPLAY_LOOPING)==DS_OK)
											{
                                                device->ulDSTimerID = timeSetEvent(25,0,(LPTIMECALLBACK)DirectSoundProc,(DWORD)device,(UINT)TIME_CALLBACK_FUNCTION|TIME_PERIODIC);
												device->MaxNoOfSources = 256;

												if ((deviceName) && (!strcmp(deviceName,"DirectSound")))
													strcpy(device->szDeviceName, "DirectSound");
												else
													strcpy(device->szDeviceName, "Generic Software");

												bDeviceFound = AL_TRUE;
											}
										}
									}
								}
							}
						}
					}
				}
			}

			if (!bDeviceFound)
			{
				if (device->DSsbuffer)
				{
					IDirectSoundBuffer_Release(device->DSsbuffer);
					device->DSsbuffer=NULL;
				}

				if (device->DSpbuffer)
				{
					IDirectSoundBuffer_Release(device->DSpbuffer);
					device->DSpbuffer = NULL;
				}
				
				if (device->lpDS)
				{
					IDirectSound_Release(device->lpDS);
					device->lpDS = NULL;
				}
			}
		}
		
		if (!bDeviceFound)
		{
			// Fallback to WaveOut code
			if (waveOutOpen(&device->hWaveHandle,WAVE_MAPPER,&OutputType,0,0,WAVE_FORMAT_DIRECT_QUERY)==MMSYSERR_NOERROR)
			{
                if (waveOutOpen(&device->hWaveHandle,WAVE_MAPPER,&OutputType,(DWORD_PTR)&WaveOutProc,(DWORD_PTR)device,CALLBACK_FUNCTION)==MMSYSERR_NOERROR)
				{
					device->hWaveHdrEvent = CreateEvent(NULL, AL_TRUE, AL_FALSE, "WaveOutAllHeadersReturned");
					if (device->hWaveHdrEvent != NULL)
					{
						device->hWaveThreadEvent = CreateEvent(NULL, AL_TRUE, AL_FALSE, "WaveOutThreadDestroyed");
						if (device->hWaveThreadEvent != NULL)
						{
							device->hWaveThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,(LPVOID)device,0,&device->ulWaveThreadID);
							if (device->hWaveThread != NULL)
							{
								device->MaxNoOfSources = 256;
								// Setup Windows Multimedia driver buffers and start playing
								for (i=0;i<NUMWAVEBUFFERS;i++)
								{
									memset(&device->buffer[i],0,sizeof(WAVEHDR));
									device->buffer[i].dwBufferLength=((OutputType.nAvgBytesPerSec/16)&0xfffffff0);
									device->buffer[i].lpData=malloc(device->buffer[i].dwBufferLength);
									if (device->buffer[i].lpData)
										memset(device->buffer[i].lpData, 0, device->buffer[i].dwBufferLength);
									device->buffer[i].dwFlags=0;
									device->buffer[i].dwLoops=0;
									waveOutPrepareHeader(device->hWaveHandle,&device->buffer[i],sizeof(WAVEHDR));
									if (waveOutWrite(device->hWaveHandle,&device->buffer[i],sizeof(WAVEHDR))!=MMSYSERR_NOERROR)
									{
										waveOutUnprepareHeader(device->hWaveHandle,&device->buffer[i],sizeof(WAVEHDR));
										free(device->buffer[i].lpData);
									}
									else
										device->lWaveBuffersCommitted++;
								}
								strcpy(device->szDeviceName, "Generic Software Compatibility Mode");
								bDeviceFound = AL_TRUE;
							}
						}
					}
				}
			}

			if (!bDeviceFound)
			{
				if (device->hWaveThreadEvent)
				{
					CloseHandle(device->hWaveThreadEvent);
					device->hWaveThreadEvent = NULL;
				}

				if (device->hWaveHdrEvent)
				{
					CloseHandle(device->hWaveHdrEvent);
					device->hWaveHdrEvent = NULL;
				}

				if (device->hWaveHandle)
				{
					waveOutClose(device->hWaveHandle);
					device->hWaveHandle = NULL;
				}
			}
		}

		if (!bDeviceFound)
		{
			// No suitable output device found
			free(device);
			device = NULL;
		}
	}

	return device;
}


/*
	alcCloseDevice

	Close the specified Device
*/
ALCAPI ALCboolean ALCAPIENTRY alcCloseDevice(ALCdevice *pDevice)
{
	ALint i;
	ALCboolean bReturn = ALC_FALSE;

	if ((pDevice)&&(!pDevice->bIsCaptureDevice))
	{
		// Stop and release the DS3D timer
		if (pDevice->ulDS3DTimerID != 0)
			timeKillEvent(pDevice->ulDS3DTimerID);

		// End Timer Period
		timeEndPeriod(pDevice->ulDS3DTimerInterval);

		// Stop and release the DS timer
		if (pDevice->ulDSTimerID)
			timeKillEvent(pDevice->ulDSTimerID);

		// Wait ... just in case any timer events happen
		Sleep(100);

		EnterCriticalSection(&g_mutex);

		//Platform specific exit
		if (pDevice->lpDS)
		{
			if (pDevice->lpDS3DListener)
				IDirectSound3DListener_Release(pDevice->lpDS3DListener);
			if (pDevice->DSsbuffer)
				IDirectSoundBuffer_Release(pDevice->DSsbuffer);
			if (pDevice->DSpbuffer)
				IDirectSoundBuffer_Release(pDevice->DSpbuffer);
			if (pDevice->lpDS)
				IDirectSound_Release(pDevice->lpDS);
			//Deinit COM
			CoUninitialize();
		}
		else
		{
			pDevice->bWaveShutdown = AL_TRUE;

			// Wait for signal that all Wave Buffers have returned
			WaitForSingleObjectEx(pDevice->hWaveHdrEvent, 5000, FALSE);

			// Wait for signal that Wave Thread has been destroyed
			WaitForSingleObjectEx(pDevice->hWaveThreadEvent, 5000, FALSE);
			
			// Call waveOutReset to shutdown wave device
			waveOutReset(pDevice->hWaveHandle);

			// Release them all
			for (i=0;i<NUMWAVEBUFFERS;i++)
			{
				waveOutUnprepareHeader(pDevice->hWaveHandle,&pDevice->buffer[i],sizeof(WAVEHDR));
				free(pDevice->buffer[i].lpData);
			}

			// Close the Wave device
			waveOutClose(pDevice->hWaveHandle);
			pDevice->hWaveHandle = 0;

			CloseHandle(pDevice->hWaveThread);
			pDevice->hWaveThread = 0;

			if (pDevice->hWaveHdrEvent)
			{
				CloseHandle(pDevice->hWaveHdrEvent);
				pDevice->hWaveHdrEvent = 0;
			}

			if (pDevice->hWaveThreadEvent)
			{
				CloseHandle(pDevice->hWaveThreadEvent);
				pDevice->hWaveThreadEvent = 0;
			}
		}

		//Release device structure
		LeaveCriticalSection(&g_mutex);
		memset(pDevice,0,sizeof(ALCdevice));
		free(pDevice);
		
		bReturn = ALC_TRUE;
	}
	else
	{
		SetALCError(ALC_INVALID_DEVICE);
	}

	return bReturn;
}
///////////////////////////////////////////////////////


///////////////////////////////////////////////////////
// ALC Device Specific Functions


/*
	WaveOutProc

	Used by "MMSYSTEM" device.  Posts a message to 'ThreadProc' everytime a WaveOut Buffer is completed and
	returns to the application (for more data)
*/
static void CALLBACK WaveOutProc(HWAVEOUT hDevice,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2)
{
	ALCdevice *pDevice;

	pDevice = (ALCdevice *)dwInstance;
	if ((uMsg==WOM_DONE) && (pDevice))
	{
		// Decrement number of buffers in use
		pDevice->lWaveBuffersCommitted--;

		if (pDevice->bWaveShutdown == AL_FALSE)
		{
			// Notify Wave Processor Thread that a Wave Header has returned
			PostThreadMessage(pDevice->ulWaveThreadID,uMsg,0,dwParam1);
		}
		else
		{
			if (pDevice->lWaveBuffersCommitted == 0)
			{
				// Signal Wave Buffers Returned event
				if (pDevice->hWaveHdrEvent)
					SetEvent(pDevice->hWaveHdrEvent);
				
				// Post 'Quit' Message to Wave Processor Thread
				PostThreadMessage(pDevice->ulWaveThreadID,WM_QUIT,0,0);
			}
		}
	}
}


/*
	ThreadProc

	Used by "MMSYSTEM" Device.  Called when a WaveOut buffer needs to be filled with new
	audio data.
*/
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	ALCcontext *pContext;
	ALCdevice *pDevice;
	LPWAVEHDR WaveHdr;
	MSG msg;

	pDevice = (ALCdevice*)lpParameter;

	while (GetMessage(&msg, NULL, 0, 0))
	{
		if ((msg.message==WOM_DONE)&&(!pDevice->bWaveShutdown))
		{
			// See if there is a Context using the WaveOut Device
			pContext = g_pContextList;
			while (pContext)
			{
				if (pContext->Device == pDevice)
					break;

				pContext = pContext->next;
			}

			WaveHdr=((LPWAVEHDR)msg.lParam);
			if (pContext)
			{
				SuspendContext(pContext);
				aluMixData(pContext,WaveHdr->lpData,WaveHdr->dwBufferLength,pContext->Format);
				ProcessContext(pContext);
			}
			else
			{
				memset(WaveHdr->lpData,0,WaveHdr->dwBufferLength);
			}
			
			waveOutWrite(pDevice->hWaveHandle,WaveHdr,sizeof(WAVEHDR));
			pDevice->lWaveBuffersCommitted++;
		}
	}

	// Signal Wave Thread completed event
	if (pDevice->hWaveThreadEvent)
		SetEvent(pDevice->hWaveThreadEvent);

	ExitThread(0);

	return 0;
}


/*
	DirectSoundProc

	Used by the "DirectSound" device to service the streaming DS Buffer.  Mixes new audio data to the
	buffer.
*/
static void CALLBACK DirectSoundProc(UINT uID,UINT uReserved,DWORD_PTR dwUser,DWORD_PTR dwReserved1,DWORD_PTR dwReserved2)
{
	static DWORD OldWriteCursor=0;
	DWORD PlayCursor,WriteCursor;
	BYTE *WritePtr1,*WritePtr2;
	DWORD WriteCnt1,WriteCnt2;
	WAVEFORMATEX OutputType;
	ALCcontext *pContext;
	ALCdevice *pDevice;
	DWORD BytesPlayed;
	HRESULT DSRes;

	pContext = alcGetCurrentContext();
	SuspendContext(pContext);

	// Get Device pointer from dwUser data
	pDevice = (ALCdevice *)dwUser;

	// See if there is a Context using this Device
	pContext = g_pContextList;
	while (pContext)
	{
		if (pContext->Device == pDevice)
			break;
		pContext = pContext->next;
	}

	// Get current play and write cursors
	IDirectSoundBuffer_GetCurrentPosition(pDevice->DSsbuffer,&PlayCursor,&WriteCursor);
	if (!OldWriteCursor) OldWriteCursor=WriteCursor-PlayCursor;

	// Get the output format and figure the number of bytes played (block aligned)
	IDirectSoundBuffer_GetFormat(pDevice->DSsbuffer,&OutputType,sizeof(WAVEFORMATEX),NULL);
	BytesPlayed=((((WriteCursor<OldWriteCursor)?(OUTPUT_BUFFER_SIZE+WriteCursor-OldWriteCursor):(WriteCursor-OldWriteCursor))/OutputType.nBlockAlign)*OutputType.nBlockAlign);

	// Lock output buffer started at 40msec in front of the old write cursor (15msec in front of the actual write cursor)
	DSRes=IDirectSoundBuffer_Lock(pDevice->DSsbuffer,(OldWriteCursor+(OutputType.nSamplesPerSec/25)*OutputType.nBlockAlign)%OUTPUT_BUFFER_SIZE,BytesPlayed,&WritePtr1,&WriteCnt1,&WritePtr2,&WriteCnt2,0);

	// If the buffer is lost, restore it, play and lock
	if (DSRes==DSERR_BUFFERLOST)
	{
		IDirectSoundBuffer_Restore(pDevice->DSsbuffer);
		IDirectSoundBuffer_Play(pDevice->DSsbuffer,0,0,DSBPLAY_LOOPING);
		DSRes=IDirectSoundBuffer_Lock(pDevice->DSsbuffer,(OldWriteCursor+(OutputType.nSamplesPerSec/25)*OutputType.nBlockAlign)%OUTPUT_BUFFER_SIZE,BytesPlayed,&WritePtr1,&WriteCnt1,&WritePtr2,&WriteCnt2,0);
	}

	// Successfully locked the output buffer
	if (DSRes==DS_OK)
	{
		// If we have a active context, mix data directly into output buffer otherwise fill with silence
		if (pContext)
		{
			if (WritePtr1)
				aluMixData(pContext,WritePtr1,WriteCnt1,pContext->Format);
			if (WritePtr2)
				aluMixData(pContext,WritePtr2,WriteCnt2,pContext->Format);
		}
		else
		{
			if (WritePtr1)
				memset(WritePtr1,0,WriteCnt1);
			if (WritePtr2)
				memset(WritePtr2,0,WriteCnt2);
		}

		// Unlock output buffer only when successfully locked
		IDirectSoundBuffer_Unlock(pDevice->DSsbuffer,WritePtr1,WriteCnt1,WritePtr2,WriteCnt2);
	}
	
	// Update old write cursor location
	OldWriteCursor=((OldWriteCursor+BytesPlayed)%OUTPUT_BUFFER_SIZE);

	ProcessContext(pContext);
}


/*
	DirectSound3DProc

	Used by "DirectSound3D" device.  Used to service all the streaming Direct Sound 3D Buffers.
*/
void CALLBACK DirectSound3DProc(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
{
	ALuint loop;
	ALuint PlayCursor, WriteCursor, DataToLock;
    ALuint DataSize;
	ALuint Part1Size, Part2Size;
	ALuint DataCount;
	ALuint BytesPlayed, BufferSize;
	ALuint NewTime;
	ALint  BytesPlayedSinceLastTimer;
	ALvoid *lpPart1, *lpPart2;
	ALsource *ALSource;
	ALuint	ulSourceCount;
	ALCdevice *pDevice;
	ALCcontext *pContext;
	ALbufferlistitem *ALBufferListItem;

	pContext = alcGetCurrentContext();
	SuspendContext(pContext);

	// Get Device pointer from dwUser data
	pDevice = (ALCdevice *)dwUser;

	// See if there is a Context using the "DirectSound3D" Device
	pContext = g_pContextList;
	while (pContext)
	{
		if (pContext->Device == pDevice)
			break;
		pContext = pContext->next;
	}

	if (pContext)
	{
		ALSource = pContext->Source;
		ulSourceCount = pContext->SourceCount;
	}
	else
	{
		ulSourceCount = 0;
	}

	// Process each playing source
	for (loop=0;loop < ulSourceCount;loop++)
	{
		if (ALSource->DSBufferPlaying)
		{
			// Get position in DS Buffer
			IDirectSoundBuffer_GetCurrentPosition((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, &PlayCursor, &WriteCursor);

			// Get current time
			// if time elapsed > duration of circular buffer
			//	 bytesplayed += 88200
			//   oldwritecursor = writecursor
			// oldtime = current time
			NewTime = timeGetTime();

			if ((NewTime - ALSource->OldTime) > ALSource->BufferDuration)
			{
				// Buffer has wrapped around due to lack of Time Callbacks !
				BytesPlayedSinceLastTimer = 88200;
				ALSource->OldWriteCursor = WriteCursor;
			}
			else
			{
				// Calculate amount of data played since last Timer event
				if (ALSource->OldPlayCursor > PlayCursor)
					BytesPlayedSinceLastTimer = ((88200 - ALSource->OldPlayCursor) + PlayCursor);
				else
					BytesPlayedSinceLastTimer = (PlayCursor - ALSource->OldPlayCursor);
			}

			ALSource->lBytesPlayed += BytesPlayedSinceLastTimer;
			ALSource->OldTime = NewTime;

			// Lock buffer from Old Write cursor to current Play cursor
			if (ALSource->OldWriteCursor > PlayCursor)
				DataToLock = (88200 - ALSource->OldWriteCursor) + PlayCursor;
			else
				DataToLock = PlayCursor - ALSource->OldWriteCursor;

			// If Source is STOPPED, copy silence into DS Buffer, PAUSED buffers are physically stopped
			if (ALSource->CurrentState == AL_STOPPED)
			{
				// Check if we have already filled the buffer with silence
				if (ALSource->Silence < 88200)
				{
					IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, ALSource->OldWriteCursor, DataToLock, &lpPart1, &Part1Size, &lpPart2, &Part2Size, 0);

					if (lpPart1)
					{
						memset(lpPart1, 0, Part1Size);
						ALSource->Silence += Part1Size;
					}

					if (lpPart2)
					{
						memset(lpPart2, 0, Part2Size);
						ALSource->Silence += Part2Size;
					}

					IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, lpPart1, Part1Size, lpPart2, Part2Size);
				}

				// Update Old Write Cursor
				ALSource->OldWriteCursor += DataToLock;
				if (ALSource->OldWriteCursor >= 88200)
					ALSource->OldWriteCursor -= 88200;

				// Update Old Play Cursor
				ALSource->OldPlayCursor = PlayCursor;

				// Move on to next Source
				ALSource = ALSource->next;
				continue;
			}

			// Update current buffer variable

			if (ALSource->lBytesPlayed > 0)
			{
				// Find position in queue
				BytesPlayed = ALSource->lBytesPlayed;

				if (BytesPlayed >= ALSource->TotalBufferDataSize)
				{
					if (ALSource->bLooping == AL_TRUE)
					{
						BytesPlayed = (BytesPlayed % ALSource->TotalBufferDataSize);
					}
					else
					{
						// Not looping ... must have played too much data !
						BytesPlayed = ALSource->TotalBufferDataSize;
					}
				}

				ALBufferListItem = ALSource->queue;
				DataSize = 0;
				while (ALBufferListItem != NULL)
				{
					if (ALBufferListItem->buffer)
						BufferSize = ((ALbuffer*)(ALTHUNK_LOOKUPENTRY(ALBufferListItem->buffer)))->size;
					else
						BufferSize = 0;
					DataSize += BufferSize;
					if (DataSize >= BytesPlayed)
						break;
					else
						ALBufferListItem = ALBufferListItem->next;
				}

				// Record current BufferID
				ALSource->ulBufferID = ALBufferListItem->buffer;
			}

			// If we are not looping, check if any buffers in the queue have finished playing
			// Also check if the Source has now finished playing
			if (ALSource->bLooping == AL_FALSE)
			{
				DataCount = 0;
				ALSource->BuffersProcessed = 0;
				ALBufferListItem = ALSource->queue;
				while (ALBufferListItem)
				{
					if (ALBufferListItem->buffer)
                        DataSize = ((ALbuffer*)ALTHUNK_LOOKUPENTRY(ALBufferListItem->buffer))->size;
					else
						DataSize = 0;

					DataCount += DataSize;
					if ((ALint)DataCount <= ALSource->lBytesPlayed)
					{
						// Buffer has been played
						ALBufferListItem->bufferstate = PROCESSED;
						ALSource->BuffersProcessed++;
						ALBufferListItem = ALBufferListItem->next;
					}
					else
						ALBufferListItem = NULL;
				}

				// Check if finished - if so stop source !
				if (ALSource->lBytesPlayed >= (ALint)ALSource->TotalBufferDataSize)
				{
					ALSource->state = AL_STOPPED;
					ALSource->CurrentState = AL_STOPPED;

					// Make sure current buffer points to last queued buffer
					ALBufferListItem = ALSource->queue;
					while (ALBufferListItem)
					{
						if (ALBufferListItem->next == NULL)
							ALSource->ulBufferID = ALBufferListItem->buffer;
		
						ALBufferListItem = ALBufferListItem->next;
					}

					// Move on to next source
					ALSource = ALSource->next;
					continue;
				}
			}

			if (SUCCEEDED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, ALSource->OldWriteCursor, DataToLock, &lpPart1, &Part1Size, &lpPart2, &Part2Size, 0)))
			{
				FillBuffer(ALSource, (ALubyte *)lpPart1, Part1Size);
				FillBuffer(ALSource, (ALubyte *)lpPart2, Part2Size);

				ALSource->OldPlayCursor = PlayCursor;

				IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, lpPart1, Part1Size, lpPart2, Part2Size);
			}
		}

		ALSource = ALSource->next;
	}

	ProcessContext(pContext);

	return;
}


/*
	FillBuffer

	Used by "DirectSound3D" device.  Fills the given AL Source with new audio data from its
	queue of AL Buffers, or silence if the queue is completed
*/
ALvoid FillBuffer(ALsource *ALSource, ALubyte *pDestData, ALuint ulDestSize)
{
	ALbufferlistitem *ALBufferListItem;
	ALuint BytesWritten, BuffersToSkip, BufferID;
	ALuint DataSize, DataLeft;
	ALuint i;
	ALvoid *Data;

	if ((pDestData) && (ulDestSize > 0) && (ulDestSize <= 88200))
	{
		if (ALSource->FinishedQueue)
		{
			memset(pDestData, 0, ulDestSize);
			ALSource->SilenceAdded += ulDestSize;
			ALSource->OldWriteCursor += ulDestSize;
			if (ALSource->OldWriteCursor >= 88200)
				ALSource->OldWriteCursor -= 88200;
		}
		else
		{
			// Find position in buffer queue
			BuffersToSkip = ALSource->BuffersAddedToDSBuffer;

			if (BuffersToSkip >= ALSource->BuffersInQueue)
				BuffersToSkip = BuffersToSkip % ALSource->BuffersInQueue;

			ALBufferListItem = ALSource->queue;
			for (i = 0; i < BuffersToSkip; i++)
			{
				ALBufferListItem = ALBufferListItem->next;
			}

			BytesWritten = 0;
			BufferID = ALBufferListItem->buffer;

			while (AL_TRUE)
			{
				// Copy audio data from Open AL Buffer(s) into DS buffer
							
				// Find out how much data is left in current Open AL Buffer
				if (BufferID)
				{
					Data = (ALvoid*)(((ALbuffer*)ALTHUNK_LOOKUPENTRY(BufferID))->data);
					DataSize = ((ALbuffer*)ALTHUNK_LOOKUPENTRY(BufferID))->size;
				}
				else
				{
					Data = 0;
					DataSize = 0;
				}

				if (DataSize == 0)
				{
					DataLeft = 0;
				}
				else
				{
					if (ALSource->BufferPosition > DataSize)
						ALSource->BufferPosition = 0;

					DataLeft = DataSize - ALSource->BufferPosition;
				}

				if (DataLeft > (ulDestSize - BytesWritten))
				{
					// Copy (ulDestSize - BytesWritten) bytes to Direct Sound buffer
					memcpy(pDestData + BytesWritten, (ALubyte*)Data + ALSource->BufferPosition, ulDestSize - BytesWritten);

					ALSource->FinishedQueue = AL_FALSE;	// More data to follow ...
					ALSource->BufferPosition += (ulDestSize - BytesWritten);		// Record position in buffer data
					BytesWritten += (ulDestSize - BytesWritten);
					break;
				}
				else
				{
					// Not enough data in buffer to fill DS buffer so just copy as much data as possible
					if ((Data) && (DataLeft > 0))
					{
						memcpy(pDestData + BytesWritten, (ALubyte*)Data + ALSource->BufferPosition, DataLeft);
					}
					BytesWritten += DataLeft;

					ALSource->BuffersAddedToDSBuffer++;
					ALSource->BufferPosition = 0;

					// Get next valid buffer ID
					ALBufferListItem = ALBufferListItem->next;
							
					if (ALBufferListItem == NULL)
					{
						// No more buffers - check for looping flag
						if (ALSource->bLooping == AL_TRUE)
						{
							// Looping
							ALBufferListItem = ALSource->queue;
						}
						else
						{
							// Not looping and no more buffers
							ALSource->FinishedQueue = AL_TRUE;
							break;
						}
					}

					BufferID = ALBufferListItem->buffer;
				}
			}

			if (BytesWritten < ulDestSize)
			{
				// Fill the rest of the buffer with silence
				memset(pDestData + BytesWritten, 0, ulDestSize - BytesWritten);

				ALSource->SilenceAdded += (ulDestSize - BytesWritten);
			}

			ALSource->OldWriteCursor += ulDestSize;
			if (ALSource->OldWriteCursor >= 88200)
				ALSource->OldWriteCursor -= 88200;
		}
	}
}


/*
	UpdateContext

	Used by Direct Sound 3D device for any updates to the Listener or any of the Sources
*/
ALCvoid UpdateContext(ALCcontext *context, ALuint type, ALuint name)
{
	ALCcontext *ALContext;
	ALsource *ALSource;
	
	ALContext = context;
    ALSource = (ALsource*)ALTHUNK_LOOKUPENTRY(name);

	SuspendContext(ALContext);

	//Platform specific context updating
	if ((ALContext->Device->lpDS)&&(ALContext->Device->lpDS3DListener))
	{
		// Check if we need to update a Source
		if ( (type == ALSOURCE) && (alIsSource(name)) && (ALSource->update1) )
		{
			// First check for any Open AL Updates (e.g Position, Velocity, Looping etc ...)
			if (ALSource->update1)
				UpdateSource(ALContext, ALSource);

			// If we need to actually start playing the sound, do it now
			if ((ALSource->play) && (ALSource->uservalue1))
			{
				// Start playing the DS Streaming buffer (always looping)
				ALSource->OldTime = timeGetTime();
				IDirectSoundBuffer_Play((LPDIRECTSOUNDBUFFER)ALSource->uservalue1,0,0,DSBPLAY_LOOPING);
				ALSource->play = AL_FALSE;
				ALSource->DSBufferPlaying = AL_TRUE;
			}
		}

		// Check for Listener related updates
		if ((type == ALLISTENER) && (ALContext->Device->lpDS3DListener))
		{
			// Update any Open AL Listener Properties (e.g Position, Velocity, Orientation etc ...)
			if (ALContext->Listener.update1)
				UpdateListener(ALContext);
		}

	}

	ProcessContext(ALContext);
}


/*
	UpdateSource

	Used by "DirectSound3D" device to handles updates to AL Sources (including generation and destruction)
*/
void UpdateSource(ALCcontext *ALContext, ALsource *ALSource)
{
	WAVEFORMATEX OutputType;
	DSBUFFERDESC DSBDescription;
	ALfloat Dir[3], Pos[3], Vel[3];
	ALuint	DataPlayed, DataCount, TotalDataSize;
	ALint	BufferSize, DataCommitted;
	ALint	Relative;
    ALuint	Freq, State, outerAngle, innerAngle;
	ALfloat maxDist, minDist;
	ALvoid	*lpPart1, *lpPart2;
	ALuint	Part1Size, Part2Size, DataSize;
	ALuint	DataToCopy, PlayCursor, WriteCursor;
    ALuint	BufferID;
	ALbufferlistitem *ALBufferListItem;
	ALbufferlistitem *ALBufferListTemp;
	ALint	volume;
	ALuint BytesPlayedSinceLastTime, CursorGap, DataToLock;
	ALboolean bServiceNow;
	ALfloat	flVelFactor;

	// Check if the Source is being Destroyed
	if (ALSource->update1 == SDELETE)
	{
		// Destroy source
		if (ALSource->uservalue1)
		{
			IDirectSoundBuffer_Stop((LPDIRECTSOUNDBUFFER)ALSource->uservalue1);
			if (ALSource->uservalue3)
			{
				IKsPropertySet_Release((LPKSPROPERTYSET)ALSource->uservalue3);
				ALSource->uservalue3 = NULL;
			}
			if (ALSource->uservalue2)
			{
				IDirectSound3DBuffer_Release((LPDIRECTSOUND3DBUFFER)ALSource->uservalue2);
				ALSource->uservalue2 = NULL;
			}
			IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)ALSource->uservalue1);
			ALSource->uservalue1=NULL;
		}

		ALSource->update1 &= ~SDELETE;
		if (ALSource->update1 == 0)
			return;
	}

	// Check if we need to generate a new Source
	if (ALSource->update1 & SGENERATESOURCE)
	{
		// Create a streaming DS buffer - 16bit mono 44.1KHz, 1 second in length
		memset(&DSBDescription,0,sizeof(DSBUFFERDESC));
		DSBDescription.dwSize=sizeof(DSBUFFERDESC);
		DSBDescription.dwFlags=DSBCAPS_CTRLVOLUME|DSBCAPS_CTRLFREQUENCY|DSBCAPS_CTRL3D|DSBCAPS_GLOBALFOCUS|
			DSBCAPS_GETCURRENTPOSITION2|DSBCAPS_LOCHARDWARE;
		DSBDescription.dwBufferBytes=88200;
		DSBDescription.lpwfxFormat=&OutputType;
		memset(&OutputType,0,sizeof(WAVEFORMATEX));
		OutputType.wFormatTag=WAVE_FORMAT_PCM;
		OutputType.nChannels=1;
		OutputType.wBitsPerSample=16;
		OutputType.nBlockAlign=2;
		OutputType.nSamplesPerSec=44100;
		OutputType.nAvgBytesPerSec=88200;
		OutputType.cbSize=0;
		ALSource->DSFrequency=44100;

		if (IDirectSound_CreateSoundBuffer(ALContext->Device->lpDS,&DSBDescription,(LPDIRECTSOUNDBUFFER *)&ALSource->uservalue1,NULL)==DS_OK)
		{
			IDirectSoundBuffer_SetCurrentPosition((LPDIRECTSOUNDBUFFER)ALSource->uservalue1,0);

			// Get 3D Interface
			if (IDirectSoundBuffer_QueryInterface((LPDIRECTSOUNDBUFFER)ALSource->uservalue1,&IID_IDirectSound3DBuffer,(LPUNKNOWN *)&ALSource->uservalue2)==DS_OK)
			{
				// Get Property Set Interface
				IDirectSound3DBuffer_QueryInterface((LPDIRECTSOUND3DBUFFER)ALSource->uservalue2,&IID_IKsPropertySet,(LPUNKNOWN *)&ALSource->uservalue3);

				ALSource->SourceType = SOURCE3D;

				// Set Volume
				if (ALContext->bUseManualAttenuation)
				{
					SetSourceLevel(ALSource, 0);
				}
				else
				{
					volume = ALSource->lVolume + ALContext->Listener.lVolume;
					CLAMP(volume, -10000, 0)
					IDirectSoundBuffer_SetVolume((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, volume);
				}
			}
			else
			{
				// Failed creation of 3D interface, so release the buffer
				IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)ALSource->uservalue1);
				ALSource->uservalue1 = NULL;
			}
		}

		ALSource->update1 &= ~SGENERATESOURCE;
		if (ALSource->update1 == 0)
				return;
	}

	// Check if we need to Stop, Start, Pause, or Resume a Source
	if (ALSource->update1 & STATE)
	{
		State = ALSource->state;

		switch (State)
		{
			case AL_INITIAL:
				break;

			case AL_PLAYING:
				if (ALSource->uservalue1 == NULL)
				{
					ALSource->play = AL_FALSE;
					ALSource->state = AL_STOPPED;
					break;
				}

				if (ALSource->play)
				{
					if (ALSource->CurrentState == AL_PAUSED)
					{
						// If paused, just restart the DS buffer, and nothing else
						IDirectSoundBuffer_Play((LPDIRECTSOUNDBUFFER)ALSource->uservalue1,0,0,DSBPLAY_LOOPING);
						ALSource->DSBufferPlaying = AL_TRUE;
						ALSource->CurrentState = AL_PLAYING;
						ALSource->play = AL_FALSE;
						// Make sure we reset the LastTime
						ALSource->OldTime = timeGetTime();
						break;
					}

					// Skip over any Buffers marked as PROCESSED (this can happen if the Source has been given
					// an Offset position)
					ALBufferListItem = ALSource->queue;
					while ((ALBufferListItem) && (ALBufferListItem->bufferstate == PROCESSED))
						ALBufferListItem = ALBufferListItem->next;
					
					// Mark any NULL or zero-length buffers at the current play position as PROCESSED
					while (ALBufferListItem)
					{
						if (ALBufferListItem->buffer)
                            BufferSize = ((ALbuffer*)ALTHUNK_LOOKUPENTRY(ALBufferListItem->buffer))->size;
						else
							BufferSize = 0;
						if (BufferSize == 0)
						{
							// Skip over this buffer (and mark as processed)
							ALBufferListItem->bufferstate = PROCESSED;
							ALSource->BuffersProcessed++;
							ALBufferListItem = ALBufferListItem->next;
							ALSource->BuffersAddedToDSBuffer++;
						}
						else
						{
							// Found a valid buffer
							break;
						}
					}

					// Start multimedia timer (if not already in progress)
					if (ALContext->Device->ulDS3DTimerID == 0)
                        ALContext->Device->ulDS3DTimerID = timeSetEvent(ALContext->Device->ulDS3DTimerInterval, 0, (LPTIMECALLBACK)&DirectSound3DProc, (DWORD)ALContext->Device, (UINT)TIME_CALLBACK_FUNCTION | TIME_PERIODIC);

					// Update current buffer variable
					BufferID = ALBufferListItem->buffer;
                    ALSource->ulBufferID = BufferID;

					// Set Direct Sound buffer to frequency of current Open AL buffer multiplied by desired Pitch
                    Freq = ((ALbuffer*)ALTHUNK_LOOKUPENTRY(BufferID))->frequency;
					
					if (ALSource->DSFrequency != (unsigned long)(Freq*ALSource->flPitch))
					{
						if (SUCCEEDED(IDirectSoundBuffer_SetFrequency((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, (unsigned long)(Freq*ALSource->flPitch))))
							ALSource->DSFrequency = (unsigned long)(Freq*ALSource->flPitch);
					}

					// Record duration of the DS circular buffer
					if (ALSource->SourceType == SOURCE3D)
						ALSource->BufferDuration = 44100000.f / (float)(ALSource->DSFrequency);
					else
						ALSource->BufferDuration = 22050000.f / (float)(ALSource->DSFrequency);


					if (ALSource->DSBufferPlaying)
					{
						// Lock as much of the Buffer as possible (from write cursor to play cursor)
						IDirectSoundBuffer_GetCurrentPosition((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, &PlayCursor, &WriteCursor);
						
						if (PlayCursor > WriteCursor)
						{
							DataToCopy = PlayCursor - WriteCursor;
							ALSource->lBytesPlayed -= ((88200 - PlayCursor) + WriteCursor);
						}
						else
						{
							DataToCopy = ((88200 - WriteCursor) + PlayCursor);
							ALSource->lBytesPlayed -= WriteCursor - PlayCursor;
						}

						if (SUCCEEDED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1,WriteCursor,DataToCopy,&lpPart1,&Part1Size,&lpPart2,&Part2Size,0)))
						{
							// This is just for the debug output in the FillBuffer routine
							ALSource->OldWriteCursor = WriteCursor;

							FillBuffer(ALSource, lpPart1, Part1Size);
							FillBuffer(ALSource, lpPart2, Part2Size);

							// We will have filled the whole buffer (minus gap between play and write cursors) with data
							// up to the new Play Cursor.  So set OldWrite and OldPlay to current Play cursor, for the next
							// timer callback to correctly service this source.
							ALSource->OldPlayCursor = PlayCursor;
							ALSource->OldWriteCursor = PlayCursor;

							IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, lpPart1, Part1Size, lpPart2, Part2Size);
						}

						ALSource->play = AL_FALSE;
						// Make sure we reset the LastTime
						ALSource->OldTime = timeGetTime();
					}
					else
					{
						if (SUCCEEDED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1,0,0,&lpPart1,&Part1Size,0,0,DSBLOCK_ENTIREBUFFER)))
						{
							FillBuffer(ALSource, lpPart1, Part1Size);

							IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1,lpPart1,Part1Size,0,0);
						}
					}
				}

				ALSource->CurrentState = AL_PLAYING;
				break;

			case AL_PAUSED:
				if (ALSource->uservalue1)
				{
					if (ALSource->CurrentState != AL_PAUSED)
					{
						ALSource->CurrentState = AL_PAUSED;
						IDirectSoundBuffer_Stop((LPDIRECTSOUNDBUFFER)ALSource->uservalue1);
						ALSource->DSBufferPlaying = AL_FALSE;
					}
				}
				break;

			case AL_STOPPED:
				if (ALSource->uservalue1)
				{
					if (ALSource->CurrentState != AL_STOPPED)
					{
						if (ALSource->CurrentState == AL_PAUSED)
						{
							// DSBuffer is already stopped, so just re-set position to 0
							IDirectSoundBuffer_SetCurrentPosition((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, 0);
							ALSource->OldWriteCursor = 0;
							ALSource->OldPlayCursor = 0;
						}
						else
						{
							// Lock as much of buffer as possible, and fill with silence 
							// So that the DSBuffer does not need to be stopped
							IDirectSoundBuffer_GetCurrentPosition((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, &PlayCursor, &WriteCursor);
							
							if (PlayCursor > WriteCursor)
								DataToCopy = PlayCursor - WriteCursor;
							else
								DataToCopy = ((88200 - WriteCursor) + PlayCursor);

							if (SUCCEEDED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1,WriteCursor,DataToCopy,&lpPart1,&Part1Size,&lpPart2,&Part2Size,0)))
							{
								if (lpPart1)
								{
									memset(lpPart1, 0, Part1Size);
									ALSource->OldWriteCursor += Part1Size;
								}
								
								if (lpPart2)
								{
									memset(lpPart2, 0, Part2Size);
									ALSource->OldWriteCursor += Part2Size;
								}

								// Update Old Play and Old Write Cursors
								if (ALSource->OldWriteCursor >= 88200)
									ALSource->OldWriteCursor -= 88200;
								
								ALSource->OldPlayCursor = PlayCursor;

								IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, lpPart1, Part1Size, lpPart2, Part2Size);
							}
						}

						ALSource->CurrentState = AL_STOPPED;

						// Mark all buffers in queue as PROCESSED
						ALSource->BuffersProcessed = ALSource->BuffersInQueue;

						ALBufferListItem= ALSource->queue;
						while (ALBufferListItem != NULL)
						{
							ALBufferListItem->bufferstate = PROCESSED;
							ALBufferListItem = ALBufferListItem->next;
						}
					}
				}
				break;
		}

		// End of STATE update
		ALSource->update1 &= ~STATE;
		if (ALSource->update1 == 0)
			return;
	}


	if (ALSource->update1 & OFFSET)
	{
		ALSource->FinishedQueue = AL_FALSE;

		// Lock as much of buffer as possible, and fill with data from the new Byte Offset
		IDirectSoundBuffer_GetCurrentPosition((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, &PlayCursor, &WriteCursor);
		
		if (PlayCursor > WriteCursor)
			DataToCopy = PlayCursor - WriteCursor;
		else
			DataToCopy = ((88200 - WriteCursor) + PlayCursor);

		ALSource->lBytesPlayed -= (88200 - DataToCopy);

		if (SUCCEEDED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1,WriteCursor,DataToCopy,&lpPart1,&Part1Size,&lpPart2,&Part2Size,0)))
		{
			// This is just for the debug output in the FillBuffer routine
			ALSource->OldWriteCursor = WriteCursor;

			FillBuffer(ALSource, lpPart1, Part1Size);
			FillBuffer(ALSource, lpPart2, Part2Size);

			// We will have filled the whole buffer (minus gap between play and write cursors) with data
			// up to the new Play Cursor.  So set OldWrite and OldPlay to current Play cursor, for the next
			// timer callback to correctly service this source.
			ALSource->OldPlayCursor = PlayCursor;
			ALSource->OldWriteCursor = PlayCursor;

			IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, lpPart1, Part1Size, lpPart2, Part2Size);
		}

		ALSource->update1 &= ~OFFSET;
		if (ALSource->update1 == 0)
			return;
	}

	// Check if we need to update the 3D Position of the Source
	if (ALSource->update1 & POSITION)
	{
		if (ALSource->uservalue2)
		{
			Pos[0] = ALSource->vPosition[0];
			Pos[1] = ALSource->vPosition[1];
			Pos[2] = -ALSource->vPosition[2];
			IDirectSound3DBuffer_SetPosition((LPDIRECTSOUND3DBUFFER)ALSource->uservalue2,Pos[0],Pos[1],Pos[2],DS3D_IMMEDIATE);

			// Manual attenation
			if (ALContext->bUseManualAttenuation)
			{
				// Calculate distance between Source and Listener
				if (ALSource->bHeadRelative)
				{
					ALSource->flDistance =
						(float)sqrt(sqr(ALSource->vPosition[0]) +
									sqr(ALSource->vPosition[1]) +
									sqr(ALSource->vPosition[2]));
				}
				else
				{
					ALSource->flDistance =
						(float)sqrt(sqr(ALContext->Listener.Position[0] - ALSource->vPosition[0]) +
									sqr(ALContext->Listener.Position[1] - ALSource->vPosition[1]) +
									sqr(ALContext->Listener.Position[2] - ALSource->vPosition[2]));
				}
				
				SetSourceLevel(ALSource, LEVELFLAG_RECALCULATE_ATTENUATION);
			}
		}

		ALSource->update1 &= ~POSITION;
		if (ALSource->update1 == 0)
			return;
	}


	// Check if we need to adjust the velocity of the Source
	if (ALSource->update1 & VELOCITY)
	{
		if (ALSource->uservalue2)
		{
			flVelFactor = SPEEDOFSOUNDMETRESPERSEC / ALContext->flSpeedOfSound;

			Vel[0] = ALSource->vVelocity[0] * flVelFactor;
			Vel[1] = ALSource->vVelocity[1] * flVelFactor;
			Vel[2] = -ALSource->vVelocity[2] * flVelFactor;
			IDirectSound3DBuffer_SetVelocity((LPDIRECTSOUND3DBUFFER)ALSource->uservalue2,Vel[0],Vel[1],Vel[2],DS3D_IMMEDIATE);
		}

		ALSource->update1 &= ~VELOCITY;
		if (ALSource->update1 == 0)
			return;
	}


	// Check if we need to adjust the Orientation of the Source
	if (ALSource->update1 & ORIENTATION)
	{
		if (ALSource->uservalue2)
		{
			Dir[0] = ALSource->vOrientation[0];
			Dir[1] = ALSource->vOrientation[1];
			Dir[2] = -ALSource->vOrientation[2];
			IDirectSound3DBuffer_SetConeOrientation((LPDIRECTSOUND3DBUFFER)ALSource->uservalue2,Dir[0],Dir[1],Dir[2],DS3D_IMMEDIATE);
		}

		ALSource->update1 &= ~ORIENTATION;
		if (ALSource->update1 == 0)
			return;
	}

	
	// Check if any Buffers have been added to this Source's queue
	if (ALSource->update1 & SQUEUE)
	{
		bServiceNow = AL_FALSE;

		if ((ALSource->uservalue1) && ((ALSource->state == AL_PLAYING) || (ALSource->state == AL_PAUSED)))
		{
			// Some buffer(s) have been added to the queue

			// If silence has been added, then we need to overwrite the silence with new audio
			// data from the buffers in the queue
			if (ALSource->SilenceAdded > 0)
			{
				// Need to determine if it is safe / possible to write the new queued data on to the end of the
				// last valid data.   This is only possible, if the Play Cursor has not gone past the end of the
				// valid data minus the gap between the Play and Write cursors.
				// If the data can't be added at the end, it should be written to the current Write Cursor and
				// BytesPlayed adjusted accordingly.

				IDirectSoundBuffer_GetCurrentPosition((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, &PlayCursor, &WriteCursor);

				if (WriteCursor >= PlayCursor)
					CursorGap = WriteCursor - PlayCursor;
				else
					CursorGap = (88200 - PlayCursor) + WriteCursor;

				if (PlayCursor >= ALSource->OldPlayCursor)
					BytesPlayedSinceLastTime = PlayCursor - ALSource->OldPlayCursor;
				else
					BytesPlayedSinceLastTime = (88200 - ALSource->OldPlayCursor) + PlayCursor;

				ALSource->lBytesPlayed += BytesPlayedSinceLastTime;

				if (ALSource->lBytesPlayed >= (ALint)(ALSource->TotalBufferDataSize - CursorGap))
				{
					ALSource->OldWriteCursor = WriteCursor;

					// If we have played more than the TotalBufferDataSize then set lBytesPlayed
					// to the total size minus the gap between the Play and Write cursors (because new data will
					// be written to Write Cursor)

					// Otherwise, we need a minor correction to BytesPlayed equal to the gap between the WriteCursor
					// and the end of the valid data (otherwise this will be included in BytesPlayed)

					if (ALSource->lBytesPlayed > (ALint)ALSource->TotalBufferDataSize)
						ALSource->lBytesPlayed = ALSource->TotalBufferDataSize - CursorGap;
					else
						ALSource->lBytesPlayed -= (CursorGap - (ALSource->TotalBufferDataSize - ALSource->lBytesPlayed));
				}
				else
				{
					if (ALSource->SilenceAdded > ALSource->OldWriteCursor)
					{
						ALSource->OldWriteCursor = (88200 - ALSource->SilenceAdded + ALSource->OldWriteCursor);
					}
					else
						ALSource->OldWriteCursor -= ALSource->SilenceAdded;
				}

				// Read position from next buffer should be set to 0
				ALSource->BufferPosition = 0;

				// We have overwritten the silent data, so reset variable
				ALSource->SilenceAdded = 0;

				// Make sure that the we haven't finished processing the queue !
				ALSource->FinishedQueue = AL_FALSE;

				// Need to copy new data to the (new) Write cursor immediately
				bServiceNow = AL_TRUE;
			}
			else if (ALSource->bLooping == AL_TRUE)
			{
				// Need to copy new data to the (new) Write cursor immediately
				bServiceNow = AL_TRUE;

				// Get position in DS Buffer
				IDirectSoundBuffer_GetCurrentPosition((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, &PlayCursor, &WriteCursor);

				// Calculate amount of data played
				if (ALSource->OldPlayCursor > PlayCursor)
					ALSource->lBytesPlayed += ((88200 - ALSource->OldPlayCursor) + PlayCursor);
				else
					ALSource->lBytesPlayed += (PlayCursor - ALSource->OldPlayCursor);

				// BytesPlayed and BuffersAddedToDSBuffer are always MOD'ed against the total data size, and
				// number of buffers in queue respectively.  These calculates will go wrong if the queue has completed
				// a loop, so we need to re-set them here (using the old values of TotalBufferData and BuffersInQueue)
				// so they effectively are set to values as if the queue hasn't looped (yet)
				if (ALSource->lBytesPlayed > 0)
					ALSource->lBytesPlayed = (ALSource->lBytesPlayed % ALSource->TotalBufferDataSize);

				ALSource->BuffersAddedToDSBuffer = (ALSource->BuffersAddedToDSBuffer % (ALSource->BuffersInQueue - ALSource->NumBuffersAddedToQueue));
				
				// If the current queue is smaller than 88200, it will be necessary to overwrite some
				// data that has been copied into the DS Buffer.   Re-set OldWriteCursor to end of current
				// queue loop, and update BufferPosition and BuffersAddedToDSBuffer, so in the TimerCallback
				// the new queued buffers will be added at the correct location
				if ((ALSource->TotalBufferDataSize - ALSource->lBytesPlayed) < 88200)
				{
					// Need to find position to write new data
					ALSource->OldWriteCursor = PlayCursor + (ALSource->TotalBufferDataSize - ALSource->lBytesPlayed);

					if (ALSource->OldWriteCursor >= 88200)
						ALSource->OldWriteCursor -= 88200;

					// Make sure that ALSource->OldWriteCursor is NOT between the current PlayCursor and WriteCursor
					if (WriteCursor > PlayCursor)
					{
						while ((ALSource->OldWriteCursor >= PlayCursor) && (ALSource->OldWriteCursor <= WriteCursor))
						{
							ALSource->OldWriteCursor += ALSource->TotalBufferDataSize;
							if (ALSource->OldWriteCursor >= 88200)
								ALSource->OldWriteCursor -= 88200;

							ALSource->lBytesPlayed -= ALSource->TotalBufferDataSize;
						}
					}
					else
					{
						while ((ALSource->OldWriteCursor >= PlayCursor) && (ALSource->OldWriteCursor <= WriteCursor))
						{
							ALSource->OldWriteCursor += ALSource->TotalBufferDataSize;
							if (ALSource->OldWriteCursor >= 88200)
								ALSource->OldWriteCursor -= 88200;

							ALSource->lBytesPlayed -= ALSource->TotalBufferDataSize;
						}
					}

					// Read position from next buffer should be set to 0
					ALSource->BufferPosition = 0;

					ALSource->BuffersAddedToDSBuffer = ALSource->BuffersInQueue - ALSource->NumBuffersAddedToQueue;

					// Make sure that the we haven't finished processing the queue !
					ALSource->FinishedQueue = AL_FALSE;
				}
			}
		}

		ALSource->TotalBufferDataSize += ALSource->SizeOfBufferDataAddedToQueue;
		ALSource->SizeOfBufferDataAddedToQueue = 0;
		ALSource->NumBuffersAddedToQueue = 0;

		if (bServiceNow)
		{
			// Lock buffer from Old Write cursor to current Play cursor
			if (ALSource->OldWriteCursor > PlayCursor)
				DataToLock = (88200 - ALSource->OldWriteCursor) + PlayCursor;
			else
				DataToLock = PlayCursor - ALSource->OldWriteCursor;

			if (SUCCEEDED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, ALSource->OldWriteCursor, DataToLock, &lpPart1, &Part1Size, &lpPart2, &Part2Size, 0)))
			{
				FillBuffer(ALSource, (ALubyte *)lpPart1, Part1Size);
				FillBuffer(ALSource, (ALubyte *)lpPart2, Part2Size);

				ALSource->OldPlayCursor = PlayCursor;

				IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, lpPart1, Part1Size, lpPart2, Part2Size);
			}
		}

		ALSource->update1 &= ~SQUEUE;
		if (ALSource->update1 == 0)
			return;
	}

	// Check if any Buffers have been removed from this Source's Queue
	if (ALSource->update1 & SUNQUEUE)
	{
		// Some number of buffers have been removed from the queue

		// We need to update some variables to correctly reflect the new queue

		// The number of BuffersAddedToDSBuffers must be decreased by the number of buffers
		// removed from the queue (or else the Timer function will think we are further through
		// the list than we are)

		// The amount of DataPlayed must be decreased by the total size of the data in the buffers
		// removed from the queue (or the amount of data still to play (TotalDataSize - DataPlayed)
		// will be incorrect)
		if ((ALSource->uservalue1) && (ALSource->state == AL_PLAYING))
		{
			ALSource->lBytesPlayed -= ALSource->SizeOfBufferDataRemovedFromQueue;
		}
		ALSource->TotalBufferDataSize -= ALSource->SizeOfBufferDataRemovedFromQueue;
		ALSource->NumBuffersRemovedFromQueue = 0;
		ALSource->SizeOfBufferDataRemovedFromQueue = 0;

		// If we're not playing then reset current buffer (it may have changed)
		if (ALSource->state != AL_PLAYING)
		{
			if (ALSource->queue)
				BufferID = ALSource->queue->buffer;
			else
				BufferID = 0;

            ALSource->ulBufferID = BufferID;
		}

		ALSource->update1 &= ~SUNQUEUE;
		if (ALSource->update1 == 0)
			return;
	}

	// Check if we need to adjust the volume of the Source
	if (ALSource->update1 & VOLUME)
	{
		if (ALSource->uservalue1)
		{
			ALSource->lVolume = LinearGainToMB(ALSource->flGain);

			if (ALContext->bUseManualAttenuation)
			{
				SetSourceLevel(ALSource, 0);
			}
			else
			{
				volume = ALSource->lVolume + ALContext->Listener.lVolume;
				CLAMP(volume, -10000, 0)
				IDirectSoundBuffer_SetVolume((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, volume);
			}
		}

		ALSource->update1 &= ~VOLUME;
		if (ALSource->update1 == 0)
			return;
	}


	// Check if we need to adjust the frequency of the Source
	if (ALSource->update1 & FREQUENCY)
	{
		if (ALSource->uservalue1)
		{
            BufferID = ALSource->ulBufferID;
			if (BufferID == 0)
				Freq = 44100;
			else
                Freq = ((ALbuffer*)ALTHUNK_LOOKUPENTRY(BufferID))->frequency;

			if (SUCCEEDED(IDirectSoundBuffer_SetFrequency((LPDIRECTSOUNDBUFFER)ALSource->uservalue1,(unsigned long)(Freq*ALSource->flPitch))))
				ALSource->DSFrequency = (unsigned long)(Freq*ALSource->flPitch);
			
			// Update duration of the DS circular buffer
			if (ALSource->SourceType == SOURCE3D)
				ALSource->BufferDuration = 44100000.f / (float)(ALSource->DSFrequency);
			else
				ALSource->BufferDuration = 22050000.f / (float)(ALSource->DSFrequency);
		}

		ALSource->update1 &= ~FREQUENCY;
		if (ALSource->update1 == 0)
			return;
	}


	// Check if we need to adjust the Min Distance of the Source
	if (ALSource->update1 & MINDIST)
	{
		if (ALSource->uservalue2)
		{
			minDist = ALSource->flRefDistance;
			IDirectSound3DBuffer_SetMinDistance((LPDIRECTSOUND3DBUFFER)ALSource->uservalue2,minDist,DS3D_IMMEDIATE);

			// Manual attenuation
			if (ALContext->bUseManualAttenuation)
				SetSourceLevel(ALSource, LEVELFLAG_RECALCULATE_ATTENUATION);
		}

		ALSource->update1 &= ~MINDIST;
		if (ALSource->update1 == 0)
			return;
	}


	// Check if we need to adjust the Max Distance of the Source
	if (ALSource->update1 & MAXDIST)
	{
		if (ALSource->uservalue2)
		{
			maxDist = ALSource->flMaxDistance;
			IDirectSound3DBuffer_SetMaxDistance((LPDIRECTSOUND3DBUFFER)ALSource->uservalue2,maxDist,DS3D_IMMEDIATE);

			// Manual attenuation
			if (ALContext->bUseManualAttenuation)
				SetSourceLevel(ALSource, LEVELFLAG_RECALCULATE_ATTENUATION);
		}

		ALSource->update1 &= ~MAXDIST;
		if (ALSource->update1 == 0)
			return;
	}


	// Check if we need to adjust the Cone Outside Volume of the Source
	if (ALSource->update1 & CONEOUTSIDEVOLUME)
	{
		if (ALSource->uservalue2)
		{
			volume = LinearGainToMB(ALSource->flOuterGain);

			IDirectSound3DBuffer_SetConeOutsideVolume((LPDIRECTSOUND3DBUFFER)ALSource->uservalue2, volume,DS3D_IMMEDIATE);
		}

		ALSource->update1 &= ~CONEOUTSIDEVOLUME;
		if (ALSource->update1 == 0)
			return;
	}


	// Check if we need to update the Roll-Off Factor of the Source
	if (ALSource->update1 & ROLLOFFFACTOR)
	{
		if (ALSource->uservalue2)
		{
			// Manual attenuation
			SetSourceLevel(ALSource, LEVELFLAG_RECALCULATE_ATTENUATION);
		}

		ALSource->update1 &= ~ROLLOFFFACTOR;
		if (ALSource->update1 == 0)
			return;
	}


	// Check if we need to update Min Gain
	if (ALSource->update1 & MINGAIN)
	{
		ALSource->lMinVolume = LinearGainToMB(ALSource->flMinGain);

		// Manual attenuation
		SetSourceLevel(ALSource, 0);

		ALSource->update1 &= ~MINGAIN;
		if (ALSource->update1 == 0)
			return;
	}


	// Check if we need to update Max Gain
	if (ALSource->update1 & MAXGAIN)
	{
		ALSource->lMaxVolume = LinearGainToMB(ALSource->flMaxGain);

		// Manual attenuation
		SetSourceLevel(ALSource, 0);

		ALSource->update1 &= ~MAXGAIN;
		if (ALSource->update1 == 0)
			return;
	}
	

	// Check if we need to update the 3D Processing Mode (Head Relative)
	if (ALSource->update1 & MODE)
	{
		if (ALSource->uservalue2)
		{
			Relative = ALSource->bHeadRelative ? DS3DMODE_HEADRELATIVE : DS3DMODE_NORMAL;
			IDirectSound3DBuffer_SetMode((LPDIRECTSOUND3DBUFFER)ALSource->uservalue2,Relative,DS3D_IMMEDIATE);

			// Manual attenation
			if (ALContext->bUseManualAttenuation)
			{
				// Calculate distance between Source and Listener
				if (ALSource->bHeadRelative)
				{
					ALSource->flDistance =
						(float)sqrt(sqr(ALSource->vPosition[0]) +
									sqr(ALSource->vPosition[1]) +
									sqr(ALSource->vPosition[2]));
				}
				else
				{
					ALSource->flDistance =
						(float)sqrt(sqr(ALContext->Listener.Position[0] - ALSource->vPosition[0]) +
									sqr(ALContext->Listener.Position[1] - ALSource->vPosition[1]) +
									sqr(ALContext->Listener.Position[2] - ALSource->vPosition[2]));
				}
				
				SetSourceLevel(ALSource, LEVELFLAG_RECALCULATE_ATTENUATION);
			}
		}

		ALSource->update1 &= ~MODE;
		if (ALSource->update1 == 0)
			return;
	}


	// Check if we ne need to update the Cone Angles for the Source
	if (ALSource->update1 & CONEANGLES)
	{
		if (ALSource->uservalue2)
		{
			innerAngle = (ALuint)ALSource->flInnerAngle;
			outerAngle = (ALuint)ALSource->flOuterAngle;

			IDirectSound3DBuffer_SetConeAngles((LPDIRECTSOUND3DBUFFER)ALSource->uservalue2,innerAngle,outerAngle,DS3D_IMMEDIATE);
		}

		ALSource->update1 &= ~CONEANGLES;
		if (ALSource->update1 == 0)
			return;
	}


	// Check if Looping has been enabled / disabled
	if (ALSource->update1 & LOOPED)
	{
		bServiceNow = AL_FALSE;

		// Only has an effect if the Source is playing
		if ((ALSource->uservalue1) && ((ALSource->state == AL_PLAYING) || (ALSource->state == AL_PAUSED)))
		{
			// Find out whether Looping has been enabled or disabled
			if (ALSource->bLooping == AL_TRUE)
			{
				// Looping enabled !

				// All buffers in queue will be needed again, so their state needs to be upgraded
				// to PENDING, and the number of buffers processed set to 0
				ALSource->BuffersProcessed = 0;

				// Set all Buffer states to PENDING
				ALBufferListTemp = ALSource->queue;
				while (ALBufferListTemp != NULL)
				{
					ALBufferListTemp->bufferstate = PENDING;
					ALBufferListTemp = ALBufferListTemp->next;
				}

				// If we have added silence after the valid data, then we need to set the new
				// write position back to the end of the valid data
				if (ALSource->SilenceAdded > 0)
				{
					if (ALSource->OldWriteCursor < ALSource->SilenceAdded)
						ALSource->OldWriteCursor += 88200;

					ALSource->OldWriteCursor -= ALSource->SilenceAdded;

					ALSource->BufferPosition = 0;

					ALSource->SilenceAdded = 0;

					IDirectSoundBuffer_GetCurrentPosition((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, &PlayCursor, &WriteCursor);

					// Calculate amount of data played
					if (ALSource->OldPlayCursor > PlayCursor)
						ALSource->lBytesPlayed += ((88200 - ALSource->OldPlayCursor) + PlayCursor);
					else
						ALSource->lBytesPlayed += (PlayCursor - ALSource->OldPlayCursor);
					
					bServiceNow = AL_TRUE;
				}

				ALSource->FinishedQueue = AL_FALSE;
			}
			else
			{
				// Looping disabled !

				// We need to calculate how much data is still to be played
				IDirectSoundBuffer_GetCurrentPosition((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, &PlayCursor, &WriteCursor);
				
				// Calculate amount of data played
				if (ALSource->OldPlayCursor > PlayCursor)
					ALSource->lBytesPlayed += ((88200 - ALSource->OldPlayCursor) + PlayCursor);
				else
					ALSource->lBytesPlayed += (PlayCursor - ALSource->OldPlayCursor);

				// Calculate how much data is left to play for the current iteration of the looping data
				TotalDataSize = ALSource->TotalBufferDataSize;

				if (ALSource->lBytesPlayed > (ALint)TotalDataSize)
					ALSource->lBytesPlayed = (ALSource->lBytesPlayed % TotalDataSize);

				ALSource->BuffersAddedToDSBuffer = (ALSource->BuffersAddedToDSBuffer % ALSource->BuffersInQueue);

				if (WriteCursor >= PlayCursor)
					DataCommitted = WriteCursor - PlayCursor;
				else
					DataCommitted = (88200 - PlayCursor) + WriteCursor;

				if (DataCommitted > (ALint)(ALSource->TotalBufferDataSize - ALSource->lBytesPlayed))
				{
					// Data for the next iteration of the loop has already been committed
					ALSource->lBytesPlayed -= TotalDataSize;
				}
				else
				{
					DataPlayed = ALSource->lBytesPlayed;
					DataCount = 0;
					ALSource->BuffersProcessed = 0;

					ALBufferListItem = ALSource->queue;
					while (ALBufferListItem != NULL)
					{
						if (ALBufferListItem->buffer)
	                        DataSize = ((ALbuffer*)ALTHUNK_LOOKUPENTRY(ALBufferListItem->buffer))->size;
						else
							DataSize = 0;

						DataCount += DataSize;
						if (DataCount < DataPlayed)
						{
							ALBufferListItem->bufferstate = PROCESSED;
							ALBufferListItem = ALBufferListItem->next;
							ALSource->BuffersProcessed++;
						}
						else
							ALBufferListItem = NULL;
					}
				}

				if ((ALSource->TotalBufferDataSize - ALSource->lBytesPlayed) < 88200)
				{
					// Need to move Write Cursor to end of valid data (so silence can be added after it)
					ALSource->OldWriteCursor = PlayCursor + (ALSource->TotalBufferDataSize - ALSource->lBytesPlayed);

					if (ALSource->OldWriteCursor >= 88200)
						ALSource->OldWriteCursor -= 88200;

					ALSource->FinishedQueue = AL_TRUE;
				}

				bServiceNow = AL_TRUE;
			}

			if (bServiceNow)
			{
				// Lock buffer from Old Write cursor to current Play cursor
				if (ALSource->OldWriteCursor > PlayCursor)
					DataToLock = (88200 - ALSource->OldWriteCursor) + PlayCursor;
				else
					DataToLock = PlayCursor - ALSource->OldWriteCursor;

				if (SUCCEEDED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, ALSource->OldWriteCursor, DataToLock, &lpPart1, &Part1Size, &lpPart2, &Part2Size, 0)))
				{
					FillBuffer(ALSource, (ALubyte *)lpPart1, Part1Size);
					FillBuffer(ALSource, (ALubyte *)lpPart2, Part2Size);

					ALSource->OldPlayCursor = PlayCursor;

					IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)ALSource->uservalue1, lpPart1, Part1Size, lpPart2, Part2Size);
				}
			}
		}

		ALSource->update1 &= ~LOOPED;
		if (ALSource->update1 == 0)
			return;
	}

	return;
}


/*
	UpdateListener

	Used by "DirectSound3D" device to handle 3D Listener updates
*/
void UpdateListener(ALCcontext *ALContext)
{
	ALfloat		Pos[3],Vel[3], Ori[6];
	ALfloat		flDistanceFactor, flVelFactor;
	ALint		volume;
	ALsource	*pSourceList;

	if (ALContext->Listener.update1 & LVOLUME)
	{
		ALContext->Listener.lVolume = LinearGainToMB(ALContext->Listener.Gain);

		if (ALContext->bUseManualAttenuation)
		{
			pSourceList = ALContext->Source;
			while (pSourceList)
			{
				if (pSourceList->uservalue1)
					SetSourceLevel(pSourceList, 0);

				pSourceList = pSourceList->next;
			}
		}
		else
		{
			pSourceList = ALContext->Source;
			while (pSourceList)
			{
				if (pSourceList->uservalue1)
				{
					volume = pSourceList->lVolume + ALContext->Listener.lVolume;
					CLAMP(volume, -10000, 0)
					IDirectSoundBuffer_SetVolume((LPDIRECTSOUNDBUFFER)pSourceList->uservalue1, volume);
				}

				pSourceList = pSourceList->next;
			}
		}

		ALContext->Listener.update1 &= ~LVOLUME;
		if (ALContext->Listener.update1 == 0)
			return;
	}

	if (ALContext->Listener.update1 & LPOSITION)
	{
		Pos[0] = ALContext->Listener.Position[0];
		Pos[1] = ALContext->Listener.Position[1];
		Pos[2] = -ALContext->Listener.Position[2];
		IDirectSound3DListener_SetPosition(ALContext->Device->lpDS3DListener, Pos[0], Pos[1], Pos[2],DS3D_IMMEDIATE);

		// Manual attenuation
		if (ALContext->bUseManualAttenuation)
		{
			pSourceList = ALContext->Source;
			while (pSourceList)
			{
				// Calculate distance between Source and Listener (only affects 3D non-Head Relative sounds)
				if ((pSourceList->uservalue2) && (pSourceList->bHeadRelative == AL_FALSE))
				{
					pSourceList->flDistance =
						(float)sqrt(sqr(ALContext->Listener.Position[0] - pSourceList->vPosition[0]) +
									sqr(ALContext->Listener.Position[1] - pSourceList->vPosition[1]) +
									sqr(ALContext->Listener.Position[2] - pSourceList->vPosition[2]));
				}

				SetSourceLevel(pSourceList, LEVELFLAG_RECALCULATE_ATTENUATION);
				pSourceList = pSourceList->next;
			}
		}

		ALContext->Listener.update1 &= ~LPOSITION;
		if (ALContext->Listener.update1 == 0)
			return;
	}

	if (ALContext->Listener.update1 & LVELOCITY)
	{
		flVelFactor = SPEEDOFSOUNDMETRESPERSEC / ALContext->flSpeedOfSound;
		Vel[0] = ALContext->Listener.Velocity[0] * flVelFactor;
		Vel[1] = ALContext->Listener.Velocity[1] * flVelFactor;
		Vel[2] = -ALContext->Listener.Velocity[2] * flVelFactor;
		IDirectSound3DListener_SetVelocity(ALContext->Device->lpDS3DListener, Vel[0], Vel[1], Vel[2], DS3D_IMMEDIATE);
		ALContext->Listener.update1 &= ~LVELOCITY;
		if (ALContext->Listener.update1 == 0)
			return;
	}

	if (ALContext->Listener.update1 & LORIENTATION)
	{
		Ori[0] = ALContext->Listener.Forward[0];
		Ori[1] = ALContext->Listener.Forward[1];
		Ori[2] = -ALContext->Listener.Forward[2];
		Ori[3] = ALContext->Listener.Up[0];
		Ori[4] = ALContext->Listener.Up[1];
		Ori[5] = -ALContext->Listener.Up[2];
		IDirectSound3DListener_SetOrientation(ALContext->Device->lpDS3DListener, Ori[0], Ori[1], Ori[2], Ori[3], Ori[4], Ori[5], DS3D_IMMEDIATE);
		ALContext->Listener.update1 &= ~LORIENTATION;
		if (ALContext->Listener.update1 == 0)
			return;
	}

	if (ALContext->Listener.update1 & LDOPPLERFACTOR)
	{
		IDirectSound3DListener_SetDopplerFactor(ALContext->Device->lpDS3DListener,ALContext->DopplerFactor,DS3D_IMMEDIATE);
		ALContext->Listener.update1 &= ~LDOPPLERFACTOR;
		if (ALContext->Listener.update1 == 0)
			return;
	}

	if (ALContext->Listener.update1 & LDOPPLERVELOCITY)
	{
		// Doppler Velocity is used to set the speed of sound in units per second
		// DS3D uses Distance Factor to relate units to real world coordinates (metres)
		// Therefore need to convert Doppler Velocity into DS3D Distance Factor
		flDistanceFactor = 1.0f / ALContext->DopplerVelocity;
		IDirectSound3DListener_SetDistanceFactor(ALContext->Device->lpDS3DListener, flDistanceFactor, DS3D_IMMEDIATE);
		ALContext->Listener.update1 &= ~LDOPPLERVELOCITY;
		if (ALContext->Listener.update1 == 0)
			return;
	}

	if (ALContext->Listener.update1 & LSPEEDOFSOUND)
	{
		// Multiply the Listener and all Source velocities by 343.3 / SpeedOfSound
		flVelFactor = SPEEDOFSOUNDMETRESPERSEC / ALContext->flSpeedOfSound;

		// Listener
		Vel[0] = ALContext->Listener.Velocity[0] * flVelFactor;
		Vel[1] = ALContext->Listener.Velocity[1] * flVelFactor;
		Vel[2] = -ALContext->Listener.Velocity[2] * flVelFactor;
		IDirectSound3DListener_SetVelocity(ALContext->Device->lpDS3DListener, Vel[0], Vel[1], Vel[2], DS3D_IMMEDIATE);

		// Sources
		pSourceList = ALContext->Source;
		while (pSourceList)
		{
			if (pSourceList->uservalue2)
			{
				Vel[0] = pSourceList->vVelocity[0] * flVelFactor;
				Vel[1] = pSourceList->vVelocity[1] * flVelFactor;
				Vel[2] = -pSourceList->vVelocity[2] * flVelFactor;
				IDirectSound3DBuffer_SetVelocity((LPDIRECTSOUND3DBUFFER)pSourceList->uservalue2, Vel[0], Vel[1], Vel[2], DS3D_IMMEDIATE);
			}
			pSourceList = pSourceList->next;
		}

		ALContext->Listener.update1 &= ~LSPEEDOFSOUND;
		if (ALContext->Listener.update1 == 0)
			return;
	}

	if (ALContext->Listener.update1 & LDISTANCEMODEL)
	{
		SetDistanceModel(ALContext);
		ALContext->Listener.update1 &= ~LDISTANCEMODEL;
		if (ALContext->Listener.update1 == 0)
			return;
	}

	return;
}


/*
	EAXFix

	Used by "DirectSound3D" device to fix an Audigy & Live Driver bug
*/
void EAXFix(ALCcontext *context)
{
	ALuint alDummySource;
	ALboolean bEAX = AL_FALSE;

	if (context->alPrivateSource)
		return;

	// Generate a dummy Source
	alGenSources(1, &alDummySource);

	// Query for EAX 2.0 support (this function will make an EAX 2.0 Set() call if EAX 2.0 is detected)
	bEAX = alIsExtensionPresent((ALubyte*)"EAX2.0");

	if (bEAX)
	{
		// Need to generate Permanent Source
		alGenSources(1, &(context->alPrivateSource));
	}

	// Delete dummy Source
	alDeleteSources(1, &alDummySource);

	// Update Context Source variables
	if (context->alPrivateSource)
	{
		context->Source = NULL;
		context->SourceCount = 0;
	}
}

void InitializeManualAttenuation(ALCcontext *pContext)
{
	ALsource	*pSourceList;
	ALfloat		flRollOffFactor;
	ALuint		i;

#ifdef _DEBUG
	OutputDebugString("Initializing Manual Attenuation !\n");
#endif
		
	pContext->bUseManualAttenuation = AL_TRUE;

	// Set Global RollOff Factor to 0
	flRollOffFactor = 0.0f;
	IDirectSound3DListener_SetRolloffFactor (pContext->Device->lpDS3DListener, flRollOffFactor, DS3D_IMMEDIATE);

	// Calculate level for every Source
	pSourceList = pContext->Source;
	for (i = 0; i < pContext->SourceCount; i++)
	{
		// Calculate distance between Source and Listener
		if (pSourceList->SourceType == SOURCE2D)
		{
			pSourceList->flDistance = 0;
		}
		else if (pSourceList->bHeadRelative)
		{
			pSourceList->flDistance =
				(float)sqrt(sqr(pSourceList->vPosition[0]) +
							sqr(pSourceList->vPosition[1]) +
							sqr(pSourceList->vPosition[2]));
		}
		else
		{
			pSourceList->flDistance =
				(float)sqrt(sqr(pContext->Listener.Position[0] - pSourceList->vPosition[0]) +
							sqr(pContext->Listener.Position[1] - pSourceList->vPosition[1]) +
							sqr(pContext->Listener.Position[2] - pSourceList->vPosition[2]));
		}

		SetSourceLevel(pSourceList, LEVELFLAG_RECALCULATE_ATTENUATION | LEVELFLAG_FORCE_EAX_CALL);
		pSourceList = pSourceList->next;
	}
}

void SetDistanceModel(ALCcontext *pContext)
{
	ALsource	*pSourceList;
	ALfloat		flRollOffFactor;
	ALuint		i;

	if (pContext->bUseManualAttenuation)
	{
		pSourceList = pContext->Source;
		for (i = 0; i < pContext->SourceCount; i++)
		{
			SetSourceLevel(pSourceList, LEVELFLAG_RECALCULATE_ATTENUATION);
			pSourceList = pSourceList->next;
		}
	}
	else
	{
		if (pContext->DistanceModel == AL_INVERSE_DISTANCE_CLAMPED)
		{
			// Set Global RollOff Factor to 1.0
			flRollOffFactor = 1.0f;
			IDirectSound3DListener_SetRolloffFactor (pContext->Device->lpDS3DListener, flRollOffFactor, DS3D_IMMEDIATE);
		}
		else
		{
			// Has to be emulated manually
			InitializeManualAttenuation(pContext);
		}
	}
}

void SetSourceLevel(ALsource *pSource, ALuint ulFlags)
{
	ALCcontext	*pContext;

	pContext = alcGetCurrentContext();
	if (pContext)
	{
		if (pContext->bUseManualAttenuation)
		{
			if (pSource->uservalue1)
				SetNonEAXSourceLevels(pContext, pSource, ulFlags);
		}
		else if ((pSource) && ((pSource->flRollOffFactor != 1.0f) ||
			(pSource->flMinGain != 0.0f) ||
			(pSource->flMaxGain != 1.0f)))
		{
			InitializeManualAttenuation(pContext);
		}
	}
}

void SetNonEAXSourceLevels(ALCcontext *pContext, ALsource *pSource, ALuint ulFlags)
{
	long lVolume;
	float flDistance;

	if (ulFlags & LEVELFLAG_RECALCULATE_ATTENUATION)
	{
		if (pSource->SourceType == SOURCE3D)
		{
			// Get the Source->Listener Distance (and clamp if appropriate)
			switch (pContext->DistanceModel)
			{
			case AL_INVERSE_DISTANCE_CLAMPED:
			case AL_LINEAR_DISTANCE_CLAMPED:
			case AL_EXPONENT_DISTANCE_CLAMPED:
				flDistance = max(pSource->flDistance, pSource->flRefDistance);
				flDistance = min(flDistance, pSource->flMaxDistance);
				break;

			case AL_INVERSE_DISTANCE:
			case AL_LINEAR_DISTANCE:
			case AL_EXPONENT_DISTANCE:
				flDistance = pSource->flDistance;
				break;

			case AL_NONE:
			default:
				flDistance = 0.0f;
				break;
			}

			// Default is no attenuation
			pSource->lAttenuationVolume = 0;

			// Calculate attenuation based on Distance and DistanceModel
			switch (pContext->DistanceModel)
			{
			case AL_INVERSE_DISTANCE:
				if (pSource->flRefDistance > 0.0f)
				{
					if ((((flDistance - pSource->flRefDistance) / pSource->flRefDistance) * pSource->flRollOffFactor) > -1.0f)
						pSource->lAttenuationVolume = (ALint)(-2000 * log10(1.0f + (((flDistance - pSource->flRefDistance) / pSource->flRefDistance) * pSource->flRollOffFactor)));
					else
						pSource->lAttenuationVolume = 10000;
				}
				break;

			case AL_INVERSE_DISTANCE_CLAMPED:
				if ((pSource->flMaxDistance >= pSource->flRefDistance) && (pSource->flRefDistance > 0.0f))
				{
					if ((((flDistance - pSource->flRefDistance) / pSource->flRefDistance) * pSource->flRollOffFactor) > -1.0f)
						pSource->lAttenuationVolume = (ALint)(-2000 * log10(1.0f + (((flDistance - pSource->flRefDistance) / pSource->flRefDistance) * pSource->flRollOffFactor)));
					else
						pSource->lAttenuationVolume = 10000;
				}
				break;

			case AL_LINEAR_DISTANCE:
				if (pSource->flMaxDistance != pSource->flRefDistance)
					pSource->lAttenuationVolume = LinearGainToMB(1.0f - (pSource->flRollOffFactor * (flDistance - pSource->flRefDistance) / (pSource->flMaxDistance - pSource->flRefDistance)));
				break;

			case AL_LINEAR_DISTANCE_CLAMPED:
				// NOTE : For clamped models, Max Distance must be greater than or equal to Ref Distance, but in this case
				//        the values cannot be equal (otherwise a divide by zero will occur)
				if (pSource->flMaxDistance > pSource->flRefDistance)
					pSource->lAttenuationVolume = LinearGainToMB(1.0f - (pSource->flRollOffFactor * (flDistance - pSource->flRefDistance) / (pSource->flMaxDistance - pSource->flRefDistance)));
				break;

			case AL_EXPONENT_DISTANCE:
				if ((flDistance > 0.0f) && (pSource->flRefDistance > 0.0f))
					pSource->lAttenuationVolume = LinearGainToMB((ALfloat)pow(flDistance / pSource->flRefDistance, -pSource->flRollOffFactor));
				break;

			case AL_EXPONENT_DISTANCE_CLAMPED:
				if ((pSource->flMaxDistance >= pSource->flRefDistance) && (flDistance > 0.0f) && (pSource->flRefDistance > 0.0f))
					pSource->lAttenuationVolume = LinearGainToMB((ALfloat)pow(flDistance / pSource->flRefDistance, -pSource->flRollOffFactor));
				break;

			case AL_NONE:
			default:
				pSource->lAttenuationVolume = 0;
				break;
			}
		}
		else
		{
			pSource->lAttenuationVolume = 0;
		}
	}

	// Add Source's Volume (preserving a silent source)
	lVolume = (pSource->lVolume == -10000) ? -10000 : pSource->lVolume + pSource->lAttenuationVolume;

	// Clamp to Min/Max Volume
	lVolume = min(lVolume, pSource->lMaxVolume);
	lVolume = max(lVolume, pSource->lMinVolume);

	// Add Listener Volume
	lVolume += pContext->Listener.lVolume;

	// Clamp in range
	CLAMP(lVolume, -10000, 0)

	// Set if actually different by more than LEVEL_TOLERANCE
	if ((ulFlags & LEVELFLAG_FORCE_EAX_CALL) || (abs(lVolume - pSource->lFinalVolume) > LEVEL_TOLERANCE))
	{
		pSource->lFinalVolume = lVolume;
		IDirectSoundBuffer_SetVolume((LPDIRECTSOUNDBUFFER)pSource->uservalue1, lVolume);
	}
}


/*
	LinearGainToMB

	Helper function to convert a floating point amplitude (range 0.0 to 1.0) to Millibels
*/
ALint LinearGainToMB(float flGain)
{
	if (flGain > 0)
		return (long)(2000.0*log10(flGain));
	else
		return -10000;
}


/*
	GetMaxNumStereoBuffers
	
	Determine how many HARDWARE Stereo buffers can be successfully created
*/
ALuint GetMaxNumStereoBuffers(LPDIRECTSOUND lpDS)
{
	LPDIRECTSOUNDBUFFER lpDSB[MAX_NUM_SOURCES];
	DSBUFFERDESC DSBDescription;
	WAVEFORMATEX	OutputType;
	ALuint loop;
	ALuint numBuffers = 0;

	// Set Caps
	memset(&DSBDescription,0,sizeof(DSBUFFERDESC));
	DSBDescription.dwSize=sizeof(DSBUFFERDESC);
	DSBDescription.dwFlags=DSBCAPS_CTRLVOLUME|DSBCAPS_CTRLFREQUENCY|DSBCAPS_GLOBALFOCUS|
		DSBCAPS_GETCURRENTPOSITION2|DSBCAPS_LOCHARDWARE;
	DSBDescription.dwBufferBytes=88200;
	DSBDescription.lpwfxFormat=&OutputType;
	memset(&OutputType,0,sizeof(WAVEFORMATEX));
	OutputType.wFormatTag=WAVE_FORMAT_PCM;
	OutputType.nChannels=2;
	OutputType.wBitsPerSample=16;
	OutputType.nBlockAlign=4;
	OutputType.nSamplesPerSec=44100;
	OutputType.nAvgBytesPerSec=176400;
	OutputType.cbSize=0;

	for (loop = 0; loop < MAX_NUM_SOURCES; loop++)
	{
		if (IDirectSound_CreateSoundBuffer(lpDS,&DSBDescription,&lpDSB[loop],NULL)==DS_OK)
			numBuffers++;
		else
			break;
	}

	// Release all the buffers
	for (loop = 0; loop < numBuffers; loop++)
	{
		if (lpDSB[loop])
		{
			IDirectSoundBuffer_Release(lpDSB[loop]);
			lpDSB[loop] = NULL;
		}
	}

	return numBuffers;
}


/*
	GetMaxNum3DMonoBuffers

	Determine how many HARDWARE mono 3D buffers can be successfully created
*/
ALuint GetMaxNum3DMonoBuffers(LPDIRECTSOUND lpDS)
{
	LPDIRECTSOUNDBUFFER lpDSB[MAX_NUM_SOURCES] = { NULL };
	LPDIRECTSOUND3DBUFFER lpDS3DB[MAX_NUM_SOURCES] = { NULL };
	DSBUFFERDESC DSBDescription;
	WAVEFORMATEX	OutputType;
	ALuint loop;
	ALuint numBuffers = 0;

	// Set Caps
	memset(&DSBDescription,0,sizeof(DSBUFFERDESC));
	DSBDescription.dwSize=sizeof(DSBUFFERDESC);
	DSBDescription.dwFlags=DSBCAPS_CTRL3D|DSBCAPS_CTRLVOLUME|DSBCAPS_CTRLFREQUENCY|DSBCAPS_GLOBALFOCUS|
		DSBCAPS_GETCURRENTPOSITION2|DSBCAPS_LOCHARDWARE;
	DSBDescription.dwBufferBytes=88200;
	DSBDescription.lpwfxFormat=&OutputType;
	memset(&OutputType,0,sizeof(WAVEFORMATEX));
	OutputType.wFormatTag=WAVE_FORMAT_PCM;
	OutputType.nChannels=1;
	OutputType.wBitsPerSample=16;
	OutputType.nBlockAlign=2;
	OutputType.nSamplesPerSec=44100;
	OutputType.nAvgBytesPerSec=88200;
	OutputType.cbSize=0;
	
	for (loop = 0; loop < MAX_NUM_SOURCES; loop++)
	{
		if (IDirectSound_CreateSoundBuffer(lpDS,&DSBDescription,&lpDSB[loop],NULL)==DS_OK)
		{
			if (IDirectSoundBuffer_QueryInterface(lpDSB[loop], &IID_IDirectSound3DBuffer, &lpDS3DB[loop])==DS_OK)
			{
				numBuffers++;
			}
			else
			{
				break;
			}
		}
		else
		{
			break;
		}
	}

	// Release all the buffers
	for (loop = 0; loop < numBuffers; loop++)
	{
		if (lpDS3DB[loop])
		{
			IDirectSound3DBuffer_Release(lpDS3DB[loop]);
			lpDS3DB[loop] = NULL;
		}

		if (lpDSB[loop])
		{
			IDirectSoundBuffer_Release(lpDSB[loop]);
			lpDSB[loop] = NULL;
		}
	}

	return numBuffers;
}
