#include "../Symphony/RuSymphony_Base.h"

#include "../Core/Stream/RuStreamIO.h"
#include "../Engine/Manager/RuEngine_ResourceManager_Base.h"

#pragma managed(push, off)

// ************************************************************************************************************************************************************

ruRTTI_DEFINE_SUBCLASS(CRuSymphony, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuSymphony, "CRuSymphony", "IRuObject")
ruCLASSGUID_DEFINE(CRuSymphony, 0x6136F6FF, 0x768844e4, 0x9B1E91BE, 0x694C20AD)

// ************************************************************************************************************************************************************

CRuSymphony::SoundDescriptor::SoundDescriptor()
:	m_resourceName(NULL),
	m_soundDataSize(0),
	m_soundData(NULL)
{
}

CRuSymphony::SoundDescriptor::~SoundDescriptor()
{
	delete[] m_resourceName;
	delete[] m_soundData;
}

// ************************************************************************************************************************************************************

CRuSymphony::CRuSymphony()
:	m_digDriver(0),
	m_numSampleHandles(64),
	m_sampleDescriptors(NULL),
	m_soundDescriptorCacheSize(16777216),
	m_defaults_3D_MinDist(1.0f),
	m_defaults_3D_MaxDist(480.0f),
	m_defaults_3D_Rolloff(0.10f)
{
}

CRuSymphony::~CRuSymphony()
{
	delete[] m_sampleDescriptors;
}

CRuEvent &CRuSymphony::Event_PlaybackFinished()
{
	return m_event_PlaybackFinished;
}

BOOL CRuSymphony::Startup()
{
	// Set MSS redistributables directory
	AIL_set_redist_directory("redist");

	// Startup
	AIL_startup();

	// Open digital sound driver
	m_digDriver = AIL_open_digital_driver(44100, 16, MSS_MC_STEREO, 0);

	if(m_digDriver == 0)
	{
		return FALSE;
	}

	// Find DSP filters
	if(AIL_find_filter("Volume Ramp Filter", &m_volumeRampFilter) == 0)
	{
	}

	// Get the speaker configuration
	AIL_speaker_configuration(m_digDriver, &m_numPhysicalChannels, &m_numLogicalChannels, &m_falloffPower, &m_channelSpec);

	// Allocate array to store sample handles
	m_sampleDescriptors = ruNEW SampleDescriptor [m_numSampleHandles];

	// Populate sample handles
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		m_sampleDescriptors[i].m_sampleHandle = AIL_allocate_sample_handle(m_digDriver);
		m_sampleDescriptors[i].m_sampleData = NULL;
		m_sampleDescriptors[i].m_sampleDataSize = 0;
		m_sampleDescriptors[i].m_fadingOut = FALSE;
		m_sampleDescriptors[i].m_loop = ruAUDIOLOOPMODE_NONE;
		m_sampleDescriptors[i].m_slaveSampleDesc = NULL;
		m_sampleDescriptors[i].m_enable3DPositioning = FALSE;
		m_sampleDescriptors[i].m_pinToListener = FALSE;
	}

	AIL_set_3D_distance_factor(m_digDriver, 0.1f);
	AIL_set_3D_rolloff_factor(m_digDriver, 1.0f);

	// Start timers
//	AIL_start_timer(m_loopingTimer);

	return TRUE;
}

BOOL CRuSymphony::Shutdown()
{
	// Shutdown
	AIL_shutdown();

	ClearSoundDescriptors();

	return TRUE;
}

const CRuVector3 &CRuSymphony::GetListenerPosition()
{
	return m_listenerPosition;
}

BOOL CRuSymphony::SetListenerPosition(const CRuVector3 &listenerPosition)
{
	if(m_digDriver == 0)
		return FALSE;

	AIL_lock();

	// Offset by the actor height... (TEST)
	CRuVector3 effectiveListenerPosition = listenerPosition;// + CRuVector3(0.0f, 16.0f, 0.0f);

	// Save old position
	CRuVector3 oldListenerPosition = m_listenerPosition;

	// Update listener position
	m_listenerPosition = effectiveListenerPosition;

	// Iterate through and process pinned 3D sounds
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		// Find pinned 3D sounds
		if(m_sampleDescriptors[i].m_enable3DPositioning && m_sampleDescriptors[i].m_pinToListener)
		{
			// If the sample is playing, update the pinned sound's absolute position
			U32 sampleStatus = AIL_sample_status(m_sampleDescriptors[i].m_sampleHandle);

			if(sampleStatus != SMP_DONE && sampleStatus != SMP_STOPPED)
			{
				CRuVector3 relativePosition = m_sampleDescriptors[i].m_3dPosition - oldListenerPosition;
				m_sampleDescriptors[i].m_3dPosition = relativePosition + m_listenerPosition;
			}
		}
	}

	AIL_unlock();

	return TRUE;
}

BOOL CRuSymphony::SetListenerOrientation(const CRuVector3 &listenerForward, const CRuVector3 &listenerUp)
{
	// Update listener orientation
	m_listenerForward = listenerForward;
	m_listenerUp = listenerUp;
/*
	char moo[200];
	sprintf(moo, "%f %f %f\n", listenerForward.m_x, listenerForward.m_y, listenerForward.m_z);
	OutputDebugString(moo);
*/
	return TRUE;
}

BOOL CRuSymphony::SetDefault3DSoundDistances(REAL minDist, REAL maxDist)
{
	if(m_digDriver == 0)
		return FALSE;

	AIL_lock();

	m_defaults_3D_MinDist = minDist;
	m_defaults_3D_MaxDist = maxDist;

	AIL_unlock();

	return TRUE;
}

BOOL CRuSymphony::SetDefault3DSoundRolloff(REAL rolloff)
{
	if(m_digDriver == 0)
		return FALSE;

	AIL_lock();

	m_defaults_3D_Rolloff = rolloff;

	AIL_unlock();

	return TRUE;
}

REAL CRuSymphony::GetDefault3DMaxDist()
{
	return m_defaults_3D_MaxDist;
}

PTRVALUE CRuSymphony::PlaySound(const char *soundPath, INT32 soundType, REAL volumeLevel, BOOL loop)
{
	if(m_digDriver == 0)
		return 0;

	AIL_lock();

	SoundDescriptor *soundDesc = GetSoundDescriptor(soundPath);

	if(soundDesc)
	{
		SampleDescriptor *sampleDesc = GetAvailableSampleDescriptor();

		if(sampleDesc)
		{
			// Fill sample descriptor with sound data
			sampleDesc->m_sampleData = soundDesc->m_soundData;
			sampleDesc->m_sampleDataSize = soundDesc->m_soundDataSize;

			// Disable 3D positioning
			sampleDesc->m_enable3DPositioning = FALSE;

			// Store pointer to sound descriptor
			sampleDesc->m_soundDescriptor = soundDesc;

			// Store sound type
			sampleDesc->m_soundType = soundType;

			// Feed sample data to MSS
			INT32 parseCode = AIL_set_named_sample_file(sampleDesc->m_sampleHandle, soundPath, sampleDesc->m_sampleData, sampleDesc->m_sampleDataSize, 0);

			AIL_set_sample_loop_count(sampleDesc->m_sampleHandle, loop ? 0 : 1);

			// Set volume levels
			AIL_set_sample_volume_pan(sampleDesc->m_sampleHandle, volumeLevel, 0.5f);

			// Install ramp filter on sample
			parseCode = AIL_set_sample_processor(sampleDesc->m_sampleHandle, SP_FILTER_0, m_volumeRampFilter);

			// Ramp volume from zero
			VolumeRamp(sampleDesc->m_sampleHandle, 0.0f, 1.0f, 250.0f);

			// Start sample playback
			AIL_start_sample(sampleDesc->m_sampleHandle);
		}
		else
		{
			char debugString[1024];
			sprintf(debugString, "Cannot allocate sample descriptor while attempting to play %s.\n", soundPath);
			OutputDebugString(debugString);
		}

		AIL_unlock();

		return reinterpret_cast<PTRVALUE>(sampleDesc);
	}

	AIL_unlock();

	return 0;
}

PTRVALUE CRuSymphony::Play3DSound(const char *soundPath, INT32 soundType, REAL volumeLevel, BOOL loop, const CRuVector3 &position, BOOL pinToListener)
{
	PTRVALUE soundHandle = 0;

	if(m_digDriver == 0)
		return 0;

	AIL_lock();

	// Verify that the specified position is not too far away to hear (with some room for error)
	REAL maxThreshold = m_defaults_3D_MaxDist * 1.10f;
	REAL distance = (position - m_listenerPosition).Magnitude();

	if(distance < maxThreshold)
	{
		SoundDescriptor *soundDesc = GetSoundDescriptor(soundPath);

		if(soundDesc)
		{
			SampleDescriptor *sampleDesc = GetAvailableSampleDescriptor();

			if(sampleDesc)
			{
				// Fill sample descriptor with sound data
				sampleDesc->m_sampleData = soundDesc->m_soundData;
				sampleDesc->m_sampleDataSize = soundDesc->m_soundDataSize;

				// Store pointer to sound descriptor
				sampleDesc->m_soundDescriptor = soundDesc;

				// Store sound type
				sampleDesc->m_soundType = soundType;
				sampleDesc->m_volumeModifier = volumeLevel;

				// Enable 3D positioning
				sampleDesc->m_enable3DPositioning = TRUE;
				sampleDesc->m_pinToListener = pinToListener;
				sampleDesc->m_3dPosition = position;

				// Feed sample data to MSS
				INT32 parseCode = AIL_set_named_sample_file(sampleDesc->m_sampleHandle, soundPath, sampleDesc->m_sampleData, sampleDesc->m_sampleDataSize, 0);

				// Handle looping
				sampleDesc->m_loop = loop ? ruAUDIOLOOPMODE_LOOP_250MS : ruAUDIOLOOPMODE_NONE;
				if(sampleDesc->m_loop != ruAUDIOLOOPMODE_NONE)
				{
					AIL_set_sample_loop_count(sampleDesc->m_sampleHandle, 0);
				}
				else
				{
					AIL_set_sample_loop_count(sampleDesc->m_sampleHandle, 1);
				}

				// Install ramp filter on sample
				AIL_set_sample_processor(sampleDesc->m_sampleHandle, SP_FILTER_0, m_volumeRampFilter);

				// Ramp volume from zero
				VolumeRamp(sampleDesc->m_sampleHandle, 0.0f, 1.0f, 250.0f);

				// Start sample playback
				AIL_start_sample(sampleDesc->m_sampleHandle);

				// Update channel levels
				Update3DSoundChannelLevels(reinterpret_cast<PTRVALUE>(sampleDesc));

				soundHandle = reinterpret_cast<PTRVALUE>(sampleDesc);
			}
			else
			{
				char debugString[1024];
				sprintf(debugString, "Cannot allocate sample descriptor while attempting to play %s.\n", soundPath);
				OutputDebugString(debugString);
			}
		}
	}

	AIL_unlock();

	return soundHandle;
}

CRuVector3 CRuSymphony::Get3DSoundPosition(PTRVALUE soundHandle)
{
	// Cast sound handle back to a pointer to sample descriptor
	SampleDescriptor *sampleDesc = reinterpret_cast<SampleDescriptor *>(soundHandle);

	// Iterate through all playing handles
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		if(&m_sampleDescriptors[i] == sampleDesc)
		{
			return m_sampleDescriptors[i].m_3dPosition;
		}
	}

	return CRuVector3(0.0f, 0.0f, 0.0f);
}

BOOL CRuSymphony::Set3DSoundPosition(PTRVALUE soundHandle, const CRuVector3 &position)
{
	BOOL retVal = FALSE;

	if(m_digDriver == 0)
		return retVal;

	AIL_lock();

	// Cast sound handle back to a pointer to sample descriptor
	SampleDescriptor *sampleDesc = reinterpret_cast<SampleDescriptor *>(soundHandle);

	// Iterate through all playing handles
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		if(&m_sampleDescriptors[i] == sampleDesc)
		{
			m_sampleDescriptors[i].m_3dPosition = position;
			retVal = TRUE;
			break;
		}
	}

	AIL_unlock();

	return retVal;
}

PTRVALUE CRuSymphony::PlaySoundByPath(const char *soundPath, BOOL loop, REAL volumeLevel, INT32 soundType)
{
	return PlaySound(soundPath, soundType, volumeLevel, loop);
}

PTRVALUE CRuSymphony::Play3DSoundByPath(const char *soundPath, REAL x, REAL y, REAL z, RuAudioLoopMode loop, BOOL pinToListener, INT32 soundType, REAL volumeLevel)
{
	return Play3DSound(soundPath, soundType, volumeLevel, loop != ruAUDIOLOOPMODE_NONE, CRuVector3(x, y, z), pinToListener);
}

BOOL CRuSymphony::SetMasterSoundVolume(REAL volumeLevel)
{
	if(m_digDriver == 0)
		return FALSE;

	AIL_lock();

	AIL_set_digital_master_volume_level(m_digDriver, volumeLevel);

	AIL_unlock();

	return TRUE;
}

BOOL CRuSymphony::AdjustSoundVolumeByType(INT32 soundType, REAL volumeLevel)
{
	if(m_digDriver == 0)
		return FALSE;

	AIL_lock();

	// Iterate through all playing handles
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		if(m_sampleDescriptors[i].m_soundType == soundType)
		{
			INT32 sampleStatus = AIL_sample_status(m_sampleDescriptors[i].m_sampleHandle);
			if(sampleStatus != SMP_DONE && sampleStatus != SMP_STOPPED)
			{
				AIL_set_sample_volume_pan(m_sampleDescriptors[i].m_sampleHandle, volumeLevel, 0.5f);
			}
		}
	}

	AIL_unlock();

	return TRUE;
}

BOOL CRuSymphony::FadeOutSound(PTRVALUE soundHandle, REAL fadeOutTimeMS)
{
	if(m_digDriver == 0)
		return FALSE;

	AIL_lock();

	// Cast sound handle back to a pointer to sample descriptor
	SampleDescriptor *sampleDesc = reinterpret_cast<SampleDescriptor *>(soundHandle);

	// Find a matching sample descriptor
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		if(&m_sampleDescriptors[i] == sampleDesc)
		{
			/*
			char debugString[1024];
			sprintf(debugString, "Stopping sound %s.\n", sampleDesc->m_soundDescriptor->m_resourceName);
			OutputDebugString(debugString);
			*/

			if(VolumeRamp(m_sampleDescriptors[i].m_sampleHandle, 1.0f, 0.0f, fadeOutTimeMS))
			{
				m_sampleDescriptors[i].m_fadingOut = TRUE;

				if(m_sampleDescriptors[i].m_slaveSampleDesc)
				{
					AIL_unlock();
					return FadeOutSound(reinterpret_cast<PTRVALUE>(m_sampleDescriptors[i].m_slaveSampleDesc));
				}

				AIL_unlock();
				return TRUE;
			}

			AIL_unlock();
			return FALSE;
		}
	}

	AIL_unlock();
	return FALSE;
}

BOOL CRuSymphony::StopSound(PTRVALUE soundHandle)
{
	if(m_digDriver == 0)
		return TRUE;

	AIL_lock();

	// Cast sound handle back to a pointer to sample descriptor
	SampleDescriptor *deletedSampleDesc = reinterpret_cast<SampleDescriptor *>(soundHandle);

	// Find a matching sample descriptor
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		if(&m_sampleDescriptors[i] == deletedSampleDesc)
		{
			// Stop sample playback
			AIL_stop_sample(m_sampleDescriptors[i].m_sampleHandle);
/*
			// Debug
			char mooStr[512];
			sprintf(mooStr, "Stopped sample index %d in use, %s\n", i, m_sampleDescriptors[i].m_soundDescriptor->m_resourceName);
			OutputDebugString(mooStr);
*/
			// Stop the sample's slave sample as well
			if(m_sampleDescriptors[i].m_slaveSampleDesc)
			{
				StopSound(reinterpret_cast<PTRVALUE>(m_sampleDescriptors[i].m_slaveSampleDesc));
			}

			AIL_unlock();
			return TRUE;
		}
	}

	AIL_unlock();
	return FALSE;
}

void CRuSymphony::EnumeratePlayingSounds()
{
	if(m_digDriver == 0)
		return;

	AIL_lock();

	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		U32 sampleStatus = AIL_sample_status(m_sampleDescriptors[i].m_sampleHandle);

		if(sampleStatus != SMP_DONE && sampleStatus != SMP_STOPPED)
		{
			char mooStr[512];
			sprintf(mooStr, "Sample index %d in use, %s\n", i, m_sampleDescriptors[i].m_soundDescriptor->m_resourceName);
			OutputDebugString(mooStr);
		}
	}

	AIL_unlock();
}

BOOL CRuSymphony::Update(REAL elapsedTime)
{
	if(m_digDriver == 0)
		return FALSE;

	// Iterate through all sample handles and update their fade out time
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		if(m_sampleDescriptors[i].m_fadingOut)
		{
			if(SoundVolumeRampIsZero(reinterpret_cast<PTRVALUE>(&m_sampleDescriptors[i])))
			{
				// Stop sample playback
				AIL_stop_sample(m_sampleDescriptors[i].m_sampleHandle);
			}
		}

		// Check sample handles which have sample data assigned to see which ones have finished playback
		if(m_sampleDescriptors[i].m_sampleData)
		{
			INT32 sampleStatus = AIL_sample_status(m_sampleDescriptors[i].m_sampleHandle);
			if(sampleStatus == SMP_DONE || sampleStatus == SMP_STOPPED)
			{
				// Trigger playback finished event
				RuSymphonyEvent_PlaybackFinished_Args playbackFinishedArgs(reinterpret_cast<PTRVALUE>(&m_sampleDescriptors[i]));
				m_event_PlaybackFinished.Trigger(&playbackFinishedArgs);

				// Clear sample data
				m_sampleDescriptors[i].m_sampleData = NULL;
				m_sampleDescriptors[i].m_sampleDataSize = 0;
			}
		}

		// Update 3D sound channel level
		Update3DSoundChannelLevels(reinterpret_cast<PTRVALUE>(&m_sampleDescriptors[i]));
	}

	// Garbage collect stale sound descriptors
	m_gcElapsedTime = elapsedTime;
	GCSoundDescriptors();

	for(INT32 i = 0; i < m_gcSoundDescriptors.Count(); ++i)
	{
/*
		// Balar
		OutputDebugString("Collecting ");
		OutputDebugString(m_gcSoundDescriptors[i]->m_resourceName);
		OutputDebugString("\n");
*/
		// Remove from sound descriptor tree
		m_soundDescriptors.Remove(m_gcSoundDescriptors[i]->m_resourceName);

		// Delete
		delete m_gcSoundDescriptors[i];
	}

	m_gcSoundDescriptors.Clear();

	return TRUE;
}

BOOL CRuSymphony::ClearUnusedSoundDescriptors()
{
	if(m_digDriver == 0)
		return FALSE;

	// Iterate through all sound descriptors
	m_gcSoundDescriptors.Clear();
	m_soundDescriptors.ForAllElements(boost::bind(&CRuSymphony::ClearUnusedSoundDescriptorsCallback, this, _1, _2));

	for(INT32 i = 0; i < m_gcSoundDescriptors.Count(); ++i)
	{
		// Remove from sound descriptor tree
		m_soundDescriptors.Remove(m_gcSoundDescriptors[i]->m_resourceName);

		// Delete
		delete m_gcSoundDescriptors[i];
	}

	m_gcSoundDescriptors.Clear();

	return TRUE;
}

CRuSymphony::SampleDescriptor *CRuSymphony::GetAvailableSampleDescriptor()
{
	if(m_digDriver == 0)
		return NULL;

	AIL_lock();

	// Find an available sample handle
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		if(m_sampleDescriptors[i].m_sampleData == NULL)
		{
			// Reset sampler structure
			m_sampleDescriptors[i].m_fadingOut = FALSE;
			m_sampleDescriptors[i].m_loop = ruAUDIOLOOPMODE_NONE;
			m_sampleDescriptors[i].m_slaveSampleDesc = NULL;

			AIL_unlock();
			return &m_sampleDescriptors[i];
		}
	}

	OutputDebugString("SYMPHONY: No Sample Descriptors Available!\n");

	AIL_unlock();
	return NULL;
}

CRuSymphony::SoundDescriptor *CRuSymphony::GetSoundDescriptor(const char *soundPath)
{
	if(m_digDriver == 0)
		return FALSE;

	AIL_lock();

	// Make sure the sound path is valid
	if(strlen(soundPath) == 0)
	{
		AIL_unlock();
		return NULL;
	}

	// Attempt to retrieve sound descriptor from the cache
	SoundDescriptor *soundDesc = NULL;

	if(m_soundDescriptors.Get(soundPath, soundDesc))
	{
		// Reset time since last use
		soundDesc->m_timeSinceLastUse = 0.0f;

		// Unlock and return
		AIL_unlock();
		return soundDesc;
	}

	// Load sound stream via resource manager
	IRuStream *sampleStream = g_ruResourceManager->LoadStream(soundPath);
	if(sampleStream)
	{
		// Create new sound descriptor
		soundDesc = ruNEW SoundDescriptor();

		soundDesc->m_resourceName = ruNEW char [strlen(soundPath) + 1];
		strcpy(soundDesc->m_resourceName, soundPath);

		soundDesc->m_soundDataSize = sampleStream->GetStreamSize();
		soundDesc->m_soundData = ruNEW BYTE [soundDesc->m_soundDataSize];
		soundDesc->m_timeSinceLastUse = 0.0f;

		sampleStream->Read(soundDesc->m_soundData, soundDesc->m_soundDataSize);

		delete sampleStream;

		// Insert sound descriptor into tree
		m_soundDescriptors.Add(soundDesc->m_resourceName, soundDesc);
	}

	AIL_unlock();
	return soundDesc;
}

BOOL CRuSymphony::IsSoundDescriptorInUse(CRuSymphony::SoundDescriptor *soundDesc)
{
	if(m_digDriver == 0)
		return FALSE;

	AIL_lock();

	// Iterate through all sample handles
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		// If the sound is playing through an active sample, return true
		INT32 sampleStatus = AIL_sample_status(m_sampleDescriptors[i].m_sampleHandle);

		if((sampleStatus != SMP_DONE) && (sampleStatus != SMP_STOPPED))
		{
			if(m_sampleDescriptors[i].m_sampleData == soundDesc->m_soundData)
			{
				AIL_unlock();
				return TRUE;
			}
		}
	}

	AIL_unlock();
	return FALSE;
}

BOOL CRuSymphony::VolumeRamp(HSAMPLE sampleHandle, REAL level0, REAL level1, REAL duration)
{
	if(m_digDriver == 0)
		return FALSE;

	AIL_lock();

	S32 result;
	F32 rampTime = 0.0f;
	F32 rampLevel = level0;

	result = AIL_sample_stage_property(sampleHandle, SP_FILTER_0, "Ramp Time", 0, &rampTime, 0);
	result = AIL_sample_stage_property(sampleHandle, SP_FILTER_0, "Ramp To", 0, &rampLevel, 0);

	rampTime = duration;
	rampLevel = level1;
	result = AIL_sample_stage_property(sampleHandle, SP_FILTER_0, "Ramp Time", 0, &rampTime, 0);
	result = AIL_sample_stage_property(sampleHandle, SP_FILTER_0, "Ramp To", 0, &rampLevel, 0);

	AIL_unlock();
	return TRUE;
}

BOOL CRuSymphony::SoundVolumeRampIsZero(PTRVALUE soundHandle)
{
	if(m_digDriver == 0)
		return TRUE;

	AIL_lock();

	// Cast sound handle back to a pointer to sample descriptor
	SampleDescriptor *sampleDesc = reinterpret_cast<SampleDescriptor *>(soundHandle);

	// Find a matching sample descriptor
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		if(&m_sampleDescriptors[i] == sampleDesc)
		{
			F32 rampLevel = 1.0f;

			AIL_sample_stage_property(m_sampleDescriptors[i].m_sampleHandle, SP_FILTER_0, "Ramp At", &rampLevel, 0, 0);

			if(rampLevel <= 0.01f)
			{
				if(m_sampleDescriptors[i].m_slaveSampleDesc)
				{
					// Yeah.. this does not work... the sound volume ramp level is never both zero.. so we rely on using the master sample's ramp level only
//					return SoundVolumeRampIsZero(reinterpret_cast<PTRVALUE>(m_sampleDescriptors[i].m_slaveSampleDesc));
				}

				AIL_unlock();
				return TRUE;
			}

			AIL_unlock();
			return FALSE;
		}
	}

	AIL_unlock();
	return FALSE;
}

BOOL CRuSymphony::Update3DSoundChannelLevels(PTRVALUE soundHandle)
{
	if(m_digDriver == 0)
		return FALSE;

	AIL_lock();

	// Cast sound handle back to a pointer to sample descriptor
	SampleDescriptor *sampleDesc = reinterpret_cast<SampleDescriptor *>(soundHandle);

	// Make sure the sample playing is a 3D sample
	if(sampleDesc->m_enable3DPositioning)
	{
		// Get sample playback status
		U32 sampleStatus = AIL_sample_status(sampleDesc->m_sampleHandle);

		if(sampleStatus == SMP_PLAYING)
		{
			// Calculate 3D channel levels
			MSSVECTOR3D srcPos = { sampleDesc->m_3dPosition.m_x, sampleDesc->m_3dPosition.m_y, sampleDesc->m_3dPosition.m_z };
			MSSVECTOR3D srcFace = { 1.0f, 0.0f, 0.0f };
			MSSVECTOR3D srcUp = { 0.0f, 1.0f, 0.0f };
			MSSVECTOR3D listenPos = { m_listenerPosition.m_x, m_listenerPosition.m_y, m_listenerPosition.m_z };
			MSSVECTOR3D listenFace = { m_listenerForward.m_x, m_listenerForward.m_y, m_listenerForward.m_z };
			MSSVECTOR3D listenUp = { m_listenerUp.m_x, m_listenerUp.m_y, m_listenerUp.m_z };

			REAL channelLevels[MAX_SPEAKERS];
			INT32 channelsWriten = AIL_calculate_3D_channel_levels(m_digDriver, channelLevels, &srcPos, &srcFace, &srcUp, 360.0f, 360.0f, 1.0f, m_defaults_3D_MaxDist, m_defaults_3D_MinDist, &listenPos, &listenFace, &listenUp, m_defaults_3D_Rolloff, NULL, NULL);

			// If the sound source is very close to the listener, it should always be centered
			REAL dist = (sampleDesc->m_3dPosition - m_listenerPosition).Magnitude();
			if(dist < 0.01f)
			{
				REAL volMax = 0.0f;

				for(INT32 i = 0; i < channelsWriten; ++i)
				{
					volMax = max(volMax, channelLevels[i]);
				}

				for(INT32 i = 0; i < channelsWriten; ++i)
				{
					channelLevels[i] = volMax;
				}
			}

			// Modulate channel levels by the volume modifier
			for(INT32 i = 0; i < channelsWriten; ++i)
			{
				channelLevels[i] *= sampleDesc->m_volumeModifier;
			}

			// Set channel levels
			AIL_set_sample_channel_levels(sampleDesc->m_sampleHandle, channelLevels, channelsWriten);
		}
	}

	AIL_unlock();
	return TRUE;
}

BOOL CRuSymphony::ClearUnusedSoundDescriptorsCallback(const void *key, void *data)
{
	SoundDescriptor *soundDesc = *static_cast<SoundDescriptor **>(data);

	// If sound descriptor is not in use, add it to the list of descriptors to be garbage collected
	if(!IsSoundDescriptorInUse(soundDesc))
	{
		m_gcSoundDescriptors.Add(soundDesc);
	}

	return TRUE;
}

BOOL CRuSymphony::GCSoundDescriptors()
{
	if(m_digDriver == 0)
		return FALSE;

	// Iterate through each sound descriptor
	m_soundDescriptors.ForAllElements(boost::bind(&CRuSymphony::GCSoundDescriptorsCallback, this, _1, _2));

	return TRUE;
}

BOOL CRuSymphony::GCSoundDescriptorsCallback(const void *key, void *data)
{
	if(m_digDriver == 0)
		return FALSE;

	SoundDescriptor *soundDesc = *static_cast<SoundDescriptor **>(data);

	// Update time stamp
	soundDesc->m_timeSinceLastUse += m_gcElapsedTime;

	// If time since last use exceeds threshold, add it to the list of descriptors to be garbage collected
	if(soundDesc->m_timeSinceLastUse > 300.0f && !IsSoundDescriptorInUse(soundDesc))
	{
		m_gcSoundDescriptors.Add(soundDesc);
	}

	return TRUE;
}

BOOL CRuSymphony::ClearSoundDescriptors()
{
	if(m_digDriver == 0)
		return TRUE;

	// Iterate through each sound descriptor and destroy
	m_soundDescriptors.ForAllElements(boost::bind(&CRuSymphony::ClearSoundDescriptorsCallback, this, _1, _2));

	// Clear all texture entries
	m_soundDescriptors.Clear();

	return TRUE;
}

BOOL CRuSymphony::ClearSoundDescriptorsCallback(const void *key, void *data)
{
	SoundDescriptor *soundDesc = *((SoundDescriptor **) data);
	delete soundDesc;

	return TRUE;
}

// ************************************************************************************************************************************************************

CRuSymphony *g_ruSymphony = NULL;

// ************************************************************************************************************************************************************

#pragma managed(pop)
