//
// Asura_MetaMusic_System.cpp
//
// Created by Richard Rice - 16/01/04
// 
// Copyright (C) 2004 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#if !defined(ASURA_SUPPORT_DYNAMIC_MUSIC_SYSTEM)

#include "Sound/Asura_MetaMusic_System.h"

#ifdef ASURA_USE_METAMUSICZONES
#include "Camera/Asura_Camera.h"
#endif //ASURA_USE_METAMUSICZONES
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_Stream.h"
#include "Core/Asura_String.h"
#include "Core/Asura_Timers.h"
#include "Core/Memory/Asura_ResourceAllocator.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_DebugOverlay.h"
#include "Network/Asura_Message.h"
#include "Sound/Asura_Music.h"
#ifdef ASURA_USE_METAMUSICZONES
#include "Sound/Asura_MetaMusic_Zone.h"
#include "Sound/Asura_MetaMusic_ZoneController.h"
#include "Zone/Asura_Zone_Controller.h"
#endif //ASURA_USE_METAMUSICZONES

#ifdef ASURA_TOOLS
#include "Tools_System.h"
#endif //ASURA_TOOLS


//////////////////////////////////////////////////////////////////////////////
//
// Asura_MetaMusic_Handler
//
//////////////////////////////////////////////////////////////////////////////

#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
Asura_MetaMusic_Handler*						Asura_MetaMusic_System::s_pxCurrentHandler	= NULL;		// Currently active handler.
Asura_TemplateHashTree<Asura_MetaMusic_Handler>	Asura_MetaMusic_System::s_xHandlerTree;					// Tree of all of the handlers available.
#else
Asura_MetaMusic_Handler							Asura_MetaMusic_System::s_xHandler;						// Single static-instance of a zone.
#endif


//////////////////////////////////////////////////////////////////////////////
//
// Asura_MetaMusic_System
//
//////////////////////////////////////////////////////////////////////////////

Asura_MetaMusic_TrackIndex Asura_MetaMusic_System::s_xCurrentTrack;
Asura_MetaMusic_TrackIndex Asura_MetaMusic_System::s_xLastTrackRequested;
Asura_MetaMusicState Asura_MetaMusic_System::s_eCurrentState;
Asura_Hash_ID Asura_MetaMusic_System::s_uCurrentFilenameHash;
float Asura_MetaMusic_System::s_fIdleTimer = 0.0f;

float Asura_MetaMusic_System::s_fTransitionPosition;
float Asura_MetaMusic_System::s_fPreviousPosition;

int Asura_MetaMusic_System::s_iQueuePosition = 0;
int Asura_MetaMusic_System::s_iQueueLength = 0;
int Asura_MetaMusic_System::s_iPauseCount = 0;
bool Asura_MetaMusic_System::s_bPausedMusicSystem = false;

Asura_MetaMusic_QueueEntry Asura_MetaMusic_System::s_axQueue[ASURA_METAMUSIC_MAXQUEUE];
const int iASURA_METAMUSIC_INGAME_SAVE_VERSION = 0;

const Asura_MetaMusic_File* Asura_MetaMusic_System::s_pxBestGuessFile = NULL;
Asura_MetaMusicState Asura_MetaMusic_System::s_xBestGuessState = ASURA_METAMUSIC_NOSTATE;

#ifdef ASURA_USE_METAMUSICZONES
u_int Asura_MetaMusic_System::s_uZone_TriggerLevel;
u_int Asura_MetaMusic_System::s_uZone_CurrentID;
u_int Asura_MetaMusic_System::s_uZone_CurrentHandler = ASURA_HASH_ID_UNSET;
float Asura_MetaMusic_System::s_fZone_Timer;
bool Asura_MetaMusic_System::s_bZone_HasStoredState;
u_int Asura_MetaMusic_System::s_uZone_StoredLastTrackHash;
u_int Asura_MetaMusic_System::s_uZone_StoredTrackHash;
Asura_MetaMusicState Asura_MetaMusic_System::s_eZone_StoredTrackState;
#endif //ASURA_USE_METAMUSICZONES

#if defined( ASURA_USE_DEBUGOVERLAY )
static const char* g_apszMetaMusicStateStrings[] =
{
	"Intro",
	"Ambient Outro",
	"Tension Outro",
	"Action Outro",
	"Ambient",
	"Ambient to Tension",
	"Ambient to Action",
	"Tension",
	"Tension to Ambient",
	"Tension to Action",
	"Action",
	"Action to Ambient",
	"Action to Tension",
};

#endif

//////////////////////////////////////////////////////////////////////////////
//
// Asura_MetaMusic_Handler
//
//////////////////////////////////////////////////////////////////////////////

Asura_MetaMusic_Handler::Asura_MetaMusic_Handler() :
	m_iNumberOfMusicTracks(0),
	m_pxMusicTracks(NULL),
	m_uZoneHashID(ASURA_HASH_ID_UNSET)
{
#if defined(ASURA_USE_METAMUSICZONES)
	// Initialise the zones...
	m_pxZoneController	= new Asura_MetaMusic_ZoneController;
#endif	// defined(ASURA_USE_METAMUSICZONES)
}

Asura_MetaMusic_Handler::~Asura_MetaMusic_Handler()
{
	Asura_ResourcePushType(ASURA_RESOURCE_META_MUSIC);
	{
		Asura_ResourceDeallocateArray(m_pxMusicTracks);
#if defined(ASURA_USE_METAMUSICZONES)
		delete(m_pxZoneController);
#endif	// defined(ASURA_USE_METAMUSICZONES)
	}
	Asura_ResourcePopType();
}

Asura_MetaMusic* Asura_MetaMusic_Handler::Initialise(const int iNumberOfTracks)
{
	Asura_ResourcePushType(ASURA_RESOURCE_META_MUSIC);
	{
		// Allocate the data...
		Asura_ResourceDeallocateArray(m_pxMusicTracks);
		Asura_ResourceAllocateArray(m_pxMusicTracks, iNumberOfTracks);

		m_iNumberOfMusicTracks	= iNumberOfTracks;
	}
	Asura_ResourcePopType();
	return(m_pxMusicTracks);
}

void Asura_MetaMusic_Handler::Reset (void)
{
	Asura_ResourcePushType(ASURA_RESOURCE_META_MUSIC);
	{
		Asura_ResourceDeallocateArray(m_pxMusicTracks);
		m_pxMusicTracks	= NULL;
		m_iNumberOfMusicTracks = 0;

#if defined(ASURA_USE_METAMUSICZONES)
		delete(m_pxZoneController);
		m_pxZoneController	= new Asura_MetaMusic_ZoneController;
#endif	// defined(ASURA_USE_METAMUSICZONES)
	}
	Asura_ResourcePopType();
}

#if defined(ASURA_TOOLS)

void Asura_MetaMusic_Handler::SetTracks(Asura_MetaMusic* pxTracks, const int iNumberOfTracks)
{
	Asura_ResourcePushType(ASURA_RESOURCE_META_MUSIC);
	{
		// Do we need to remove the data?
		if(m_pxMusicTracks != pxTracks)
		{
			Asura_ResourceDeallocateArray(m_pxMusicTracks);
		}

		// Set the variables...
		m_pxMusicTracks			= pxTracks;
		m_iNumberOfMusicTracks	= iNumberOfTracks;
	}
	Asura_ResourcePopType();
}

#endif	// defined(ASURA_TOOLS)

void Asura_MetaMusic_Handler::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	const int iVersion = iASURA_METAMUSIC_INGAME_SAVE_VERSION;
	xStream << iVersion;

	int iState;
	Asura_MetaMusic_TrackIndex xTrackIndex;

#ifdef ASURA_USE_METAMUSICZONES
	if(Asura_MetaMusic_System::s_bZone_HasStoredState)
	{
		//In a music zone Save the state that would be used if it wasn't for that.
		iState = Asura_MetaMusic_System::s_eZone_StoredTrackState;
		xTrackIndex = Asura_MetaMusic_System::GetTrackIndexFromHash(Asura_MetaMusic_System::s_uZone_StoredTrackHash);
	}
	else
#endif //ASURA_USE_METAMUSICZONES
		 if (Asura_MetaMusic_System::s_iQueueLength)
	{
		const Asura_MetaMusic_QueueEntry* pxLastQueueEntry = Asura_MetaMusic_System::GetFromQueue(Asura_MetaMusic_System::s_iQueueLength - 1);
		iState = pxLastQueueEntry->m_eState;
		xTrackIndex = pxLastQueueEntry->m_xTrackIndex;
	}
	else
	{
		iState = Asura_MetaMusic_System::s_eCurrentState;
		xTrackIndex = Asura_MetaMusic_System::s_xCurrentTrack;
	}

#if defined(ASURA_USE_STREAMING)
	if (xTrackIndex.GetHandlerHash() != GetZoneHashID())
	{
		// Track playing belongs to a different handler, ignore it.
		xTrackIndex.Reset();
	}
#endif //defined(ASURA_USE_STREAMING)

	xStream << iState;
	xStream << xTrackIndex.GetTrack();

#ifdef ASURA_USE_METAMUSICZONES
	//Need to save some in-game data from the zones as well
	const int iNumberOfZones = m_pxZoneController->GetNumberOfZones();
	xStream << iNumberOfZones;

	for(int i=0; i<iNumberOfZones; i++)
	{
		const Asura_MetaMusic_Zone* pxZone = static_cast< const Asura_MetaMusic_Zone* >( m_pxZoneController->GetZoneByIndex( i ) );
		pxZone->WriteInGameDataToChunkStream( xStream );
	}
#endif //ASURA_USE_METAMUSICZONES
}

void Asura_MetaMusic_Handler::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion;
	xStream >> iVersion;
	Asura_Assert(iVersion >= 0 && iVersion <= iASURA_METAMUSIC_INGAME_SAVE_VERSION, "Unhandled version");

	int iState, iTrackIndex;
	xStream >> iState;
	xStream >> iTrackIndex;

	Asura_MetaMusic_System::StopTrack(true);
	if (iTrackIndex != -1 &&
		iState != ASURA_METAMUSIC_NOSTATE)
	{
		const Asura_MetaMusic_TrackIndex xTrack(iTrackIndex, GetZoneHashID());
		Asura_MetaMusic_System::AddToQueue(xTrack, static_cast<Asura_MetaMusicState>(iState), true);
	}

#ifdef ASURA_USE_METAMUSICZONES
	int iNumberOfZones;
	xStream >> iNumberOfZones;

	// Number of zones must have stayed the same to restore them.
	if (iNumberOfZones == m_pxZoneController->GetNumberOfZones())
	{
		for (int i=0; i<iNumberOfZones; i++)
		{
			Asura_MetaMusic_Zone* pxZone = static_cast<Asura_MetaMusic_Zone*>( m_pxZoneController->GetZoneByIndex( i ) );
			pxZone->ReadInGameDataFromChunkStream( xStream );
		}
	}
#endif //ASURA_USE_METAMUSICZONES
}

Asura_MetaMusic_TrackIndex Asura_MetaMusic_Handler::GetTrackIndexFromHash(Asura_Hash_ID uHash) const
{
	for (int i=0; i<m_iNumberOfMusicTracks; i++)
	{
		if (m_pxMusicTracks[i].m_uNameHash == uHash)
		{
			return Asura_MetaMusic_TrackIndex(i, GetZoneHashID());
		}
	}
	// Failed to find track
	return Asura_MetaMusic_TrackIndex();
}

#if defined(ASURA_TOOLS)
const char* Asura_MetaMusic_Handler::GetTrackName(int iIndex) const
{
	Asura_Assert(iIndex>=0 && iIndex<m_iNumberOfMusicTracks,"MetaMusic: invalid track");
	return m_pxMusicTracks[iIndex].m_xName.GetString();
}

Asura_Hash_ID Asura_MetaMusic_Handler::GetTrackHash( int iIndex ) const
{
	if ( 0 <= iIndex && iIndex < m_iNumberOfMusicTracks )
	{
		return m_pxMusicTracks[ iIndex ].m_uNameHash;
	}
	else
	{
		return ASURA_HASH_ID_UNSET;
	}
}
#endif //#if defined(ASURA_TOOLS)

const char* Asura_MetaMusic_Handler::GetTrackName(const Asura_MetaMusic_TrackIndex& xIndex) const
{
	if (xIndex.GetHandlerHash() != GetZoneHashID())
	{
		Asura_Assert(0, "MetaMusic: wrong handler set");
		return 0;
	}

	const int iTrack = xIndex.GetTrack();
	if (iTrack>=0 && iTrack<m_iNumberOfMusicTracks)
	{
		return m_pxMusicTracks[iTrack].m_xName.GetString();
	}

	Asura_Assert(0,"MetaMusic: Invalid track index");
	return 0;
}

Asura_Hash_ID Asura_MetaMusic_Handler::GetTrackHash(const Asura_MetaMusic_TrackIndex& xIndex) const
{
	if (xIndex.GetHandlerHash() != GetZoneHashID())
	{
		Asura_Assert(0, "MetaMusic: wrong handler set");
		return ASURA_HASH_ID_UNSET;
	}

	const int iTrack = xIndex.GetTrack();
	if (iTrack>=0 && iTrack<m_iNumberOfMusicTracks)
	{
		return m_pxMusicTracks[iTrack].m_uNameHash;
	}
	
	Asura_Assert(0,"MetaMusic: Invalid track index");
	return ASURA_HASH_ID_UNSET;
}


const Asura_MetaMusic* Asura_MetaMusic_Handler::GetTrack(const Asura_MetaMusic_TrackIndex& xIndex) const
{
	if (xIndex.GetHandlerHash() != GetZoneHashID())
	{
		Asura_Assert(0, "MetaMusic: wrong handler set");
		return 0;
	}

	const int iTrack = xIndex.GetTrack();
	if (iTrack>=0 && iTrack<m_iNumberOfMusicTracks)
	{
		return &m_pxMusicTracks[iTrack];
	}

	Asura_Assert(0, "MetaMusic: Invalid track index");
	return 0;
}

//////////////////////////////////////////////////////////////////////////////
//
// Asura_MetaMusic_System
//
//////////////////////////////////////////////////////////////////////////////

void Asura_MetaMusic_System::Initialise()
{
	s_iQueuePosition = 0;
	s_iQueueLength = 0;
	s_fIdleTimer = 0;
	s_fTransitionPosition = -1.0f;
	s_eCurrentState = ASURA_METAMUSIC_NOSTATE;
	s_xLastTrackRequested.Reset();
	s_iPauseCount = 0;
	s_bPausedMusicSystem = false;

    #ifdef ASURA_USE_METAMUSICZONES
	s_uZone_TriggerLevel = 0;
	s_uZone_CurrentID = 0;
	s_uZone_CurrentHandler = ASURA_HASH_ID_UNSET;
	s_fZone_Timer = 0;

	s_bZone_HasStoredState = false;
	s_uZone_StoredLastTrackHash = 0;
	s_uZone_StoredTrackHash = 0;
	s_eZone_StoredTrackState = ASURA_METAMUSIC_NOSTATE;
    #endif //ASURA_USE_METAMUSICZONES


    #ifdef ASURA_USE_PROJECTMETAMUSIC
        Project_Initialise();
    #endif //ASURA_USE_METAMUSICZONES

	#if defined( ASURA_USE_DEBUGOVERLAY )
	if( !Asura_DebugOverlay::ModeIsRegistered( ASURA_DO_METAMUSIC ) )
	{
		Asura_DebugOverlay::RegisterMode( "MetaMusic", ASURA_DO_METAMUSIC, DoMetaMusicDebugOverlay, NULL );
	}	
	#endif
}

void Asura_MetaMusic_System::Shutdown()
{
	StopTrack(true);//(MKR - 29.08.06)

#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
	// Reset the entire handler tree...
	s_xHandlerTree.Reset();

	// Remove the current handler...
	s_pxCurrentHandler	= NULL;
#else
	Asura_MetaMusic_Handler& xHandler = GetCurrentHandler();

	// Reset the current instance...
	xHandler.Reset();
#endif	//defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)

	s_iPauseCount = 0;
	s_xCurrentTrack.Reset();
	s_uCurrentFilenameHash = ASURA_HASH_ID_UNSET;

	Asura_Music_System::ClearPreparedTrack();
	s_pxBestGuessFile = NULL;
	s_xBestGuessState = ASURA_METAMUSIC_NOSTATE;

#ifdef ASURA_USE_PROJECTMETAMUSIC
	Project_Shutdown();
#endif
}

void Asura_MetaMusic_System::Reset()
{
	if(s_bPausedMusicSystem)
	{
		Asura_Music_System::Unpause(false);
		s_bPausedMusicSystem = false;
	}

#ifdef ASURA_USE_MULTIPLE_ZONE_HANDLERS
	StopTrack(true);
	GetCurrentHandler().Reset();

	s_iPauseCount = 0;
	s_xCurrentTrack.Reset();
	s_uCurrentFilenameHash = ASURA_HASH_ID_UNSET;
#else
	Shutdown();
	Initialise();
#endif
}

#if defined( ASURA_USE_DEBUGOVERLAY )
void Asura_MetaMusic_System::DoMetaMusicDebugOverlay( void* const pxIgnore )
{
	Asura_DebugOverlay::DrawConsoleText( ASURA_DO_METAMUSIC, "Timer            : %.3f", Asura_Maths::FtoD(s_fIdleTimer));
	Asura_DebugOverlay::DrawConsoleText( ASURA_DO_METAMUSIC, "Position         : %.1f / %.1f", Asura_Maths::FtoD(Asura_Music_System::GetCurrentTrackPosition()), Asura_Maths::FtoD(Asura_Music_System::GetCurrentTrackLength()));
	Asura_DebugOverlay::DrawConsoleText( ASURA_DO_METAMUSIC, "State            : %s", (s_eCurrentState >= 0) ? g_apszMetaMusicStateStrings[s_eCurrentState] : "None");
	Asura_DebugOverlay::DrawConsoleText( ASURA_DO_METAMUSIC, "Best Guess State : %s", (s_xBestGuessState >= 0) ? g_apszMetaMusicStateStrings[s_xBestGuessState] : "None");
	
	if(Asura_MetaMusic_System::s_iQueueLength > 0)
	{
		Asura_DebugOverlay::DrawConsoleText( ASURA_DO_METAMUSIC, "- - - Queue - - -");

		for(int i=0;i<Asura_MetaMusic_System::s_iQueueLength;i++)
		{
			Asura_MetaMusicState eState = GetFromQueue(i)->m_eState;
			Asura_DebugOverlay::DrawConsoleText( ASURA_DO_METAMUSIC, "%d) %s",i,(eState >= 0) ? g_apszMetaMusicStateStrings[eState] : "None");
		}
	}
	
	Asura_DebugOverlay::DrawConsoleText( ASURA_DO_METAMUSIC, "- - - Music Tracks - - -");
	Asura_Music_System::DoMusicDebugOverlay(NULL);
}
#endif

#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_MetaMusic_System::CreateHandler
// Author       : Andy Tate
// Date         : 16-Mar-2006
//
// Description  : Attempts to create a meta-music handler with the specified
//				  hash-ID.
//
// Parameters   : The hash-ID of the handler to create.
//
// Return value : A pointer to the handler created.
////////////////////////////////////////////////////////////////////////////////

Asura_MetaMusic_Handler* Asura_MetaMusic_System::CreateHandler (const Asura_Hash_ID uHash)
{
	Asura_MetaMusic_Handler* pxHandler = s_xHandlerTree.GetDataByHash(uHash);

	// Do we already have this handler?
	if(pxHandler == NULL)
	{
		// No - create one...
		pxHandler = s_xHandlerTree.AddNode(uHash);
		if (pxHandler)
		{
			pxHandler->SetZoneHashID(uHash);
		}
	}
	else
	{
		// Yes - this shouldn't happen...
		Asura_Assert(false, "Already have an shape-handler with this hash-ID.");
	}

	// Return the handler...
	return(pxHandler);
}

/// End of function 'Asura_MetaMusic_System::CreateHandler'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_MetaMusic_System::DestroyHandler
// Author       : Andy Tate
// Date         : 16-Mar-2006
//
// Description  : Attempts to destroy the meta-music handler with the specified
//				  unique hash-ID.
//
// Parameters   : The hash-ID of the handler to destroy.
//
// Return value : "true" if destroyed, "false" if failed.
////////////////////////////////////////////////////////////////////////////////

bool Asura_MetaMusic_System::DestroyHandler (const Asura_Hash_ID uHash)
{
	// MGH 21/6/2006: Clean up references to this handler first...
#if defined(ASURA_USE_METAMUSICZONES)
	if (s_bZone_HasStoredState &&
		s_uZone_StoredTrackHash != ASURA_HASH_ID_UNSET)
	{
		const Asura_MetaMusic_TrackIndex xTrack = GetTrackIndexFromHash(s_uZone_StoredTrackHash);
		if (xTrack.GetHandlerHash() == uHash)
		{
			s_bZone_HasStoredState = false;
		}
	}

	if (s_uZone_CurrentHandler == uHash)
	{
		s_uZone_CurrentID = 0;
		s_uZone_CurrentHandler = ASURA_HASH_ID_UNSET;
		if (s_bZone_HasStoredState)
		{
			// Restore the previously stored state.
			MusicZone_RestoreState();
		}
	}
#endif //defined(ASURA_USE_METAMUSICZONES)
	if (s_xCurrentTrack.GetHandlerHash() == uHash)
	{
		StopTrack(true);
	}


	// Is this the current handler?
	if(s_pxCurrentHandler == s_xHandlerTree.GetDataByHash(uHash))
	{
		s_pxCurrentHandler	= NULL;
	}

	// Call the delete function...
	return s_xHandlerTree.DeleteNodeByHash(uHash);
}

/// End of function 'Asura_MetaMusic_System::DestroyHandler'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_MetaMusic_System::WriteHandlerLog
// Author       : Paul Cardy
// Date         : 20-12-07
//
// Description  : Writes a log of the contents of the supplied handler
//
// Parameters   : The hash-ID of the handler to log and the filename to log to
//
// Return value : None
////////////////////////////////////////////////////////////////////////////////

void Asura_MetaMusic_System::WriteHandlerReport(u_int uHandler, Asura_Stream& xLog)
{
	const u_int uLOGBUFFERSIZE = 64;
	char szLogBuffer[uLOGBUFFERSIZE];

	Asura_MetaMusic_Handler* pxHandler = s_xHandlerTree.GetDataByHash(uHandler);
	if (pxHandler)
	{
		if (pxHandler->GetNumberOfTracks())
		{
			Asura_PrintfToBfr(szLogBuffer, "MetaMusic:\t\t\t%d Tracks(s)\n", uLOGBUFFERSIZE, true, pxHandler->GetNumberOfTracks());
			xLog << szLogBuffer;
		}
		else
		{
			xLog << "MetaMusic:\t\t\tNone\n";
		}	
	}
	else
	{
		xLog << "MetaMusic:\t\t\tNo Handler\n";
	}
}

/// End of function 'Asura_MetaMusic_System::WriteHandlerLog'. ///

#endif	// defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)


void Asura_MetaMusic_System::Pause(bool bImmediate /*=true*/)
{
	if(!s_iPauseCount)
	{
		Asura_Music_System::Pause(bImmediate);
		s_bPausedMusicSystem = true;
	}
	s_iPauseCount++;
}

void Asura_MetaMusic_System::PauseAndReleaseSound(bool bImmediate)
{
	if(s_bPausedMusicSystem)
	{
		// Unpause music system before stopping it so as to keep count of pauses and unpauses the same.
		Asura_Music_System::Unpause(false);
	}

	if(bImmediate || s_bPausedMusicSystem)
	{
		// Stop dead.
		Asura_Music_System::Stop(0.0f);
	}
	else
	{
		// Stop with a short fade.
		Asura_Music_System::Stop();
	}
	s_bPausedMusicSystem = false;
	s_iPauseCount++;
}


void Asura_MetaMusic_System::Unpause()
{
	s_iPauseCount--;
	if(!s_iPauseCount)
	{
		if(s_bPausedMusicSystem)
		{
			// We paused the music system, so should just need to unpause it.
			Asura_Music_System::Unpause(false);
		}
		else
		{
			// The music was stopped, so we need to work out what to restart, if anything.
			bool bFoundTrack = false;
			for (int iPos = -1; iPos<s_iQueueLength; iPos++)
			{
				const Asura_MetaMusic_TrackIndex& xTrack = (iPos==-1) ? s_xCurrentTrack : GetFromQueue(iPos)->m_xTrackIndex;
				const Asura_MetaMusicState eState = (iPos==-1) ? s_eCurrentState : GetFromQueue(iPos)->m_eState;

				if (!IsLoopingState(eState) && eState != ASURA_METAMUSIC_INTRO) continue;
				if (!TrackHasState(xTrack, eState)) continue;
					
				// Adjust queue
				if (iPos == -1)
				{
					// Restarting the current track/state, so insert back in queue.
					AddToStartOfQueue(xTrack, eState, true);
				}
				else if (iPos>0)
				{
					// Continuing from further in the queue, so clear up to the correct point.
					s_iQueuePosition = (s_iQueuePosition+iPos)%ASURA_METAMUSIC_MAXQUEUE;
					s_iQueueLength -= (iPos);
				}
					
				bFoundTrack = true;
				break;					
			}
			if (!bFoundTrack)
			{
				// Failed to find a suitable track, so clear the queue completely.
			   ClearQueue();
			}

			// Start playing
			s_xCurrentTrack.Reset();
			s_eCurrentState = ASURA_METAMUSIC_NOSTATE;
			PlayNextInQueue();
		}
		s_bPausedMusicSystem = false;
	}

	if (s_iPauseCount<0)
	{
		s_iPauseCount = 0;
	}
}


void Asura_MetaMusic_System::HandleMessage(const Asura_Message* pxMessage)
{
    #ifdef ASURA_USE_PROJECTMETAMUSIC
	// See if the project wants to handle this.
	if ( Project_HandleMessage( pxMessage ) )
    {
        return;
    }
    #endif //ASURA_USE_PROJECTMETAMUSIC

	switch(pxMessage->GetMsgID())
	{
		case ASURA_MSG_METAMUSIC_PLAY:
		{
			const Asura_MsgData_MetaMusic_Play* pxData = static_cast
				<const Asura_MsgData_MetaMusic_Play*>(pxMessage->GetData());

			#ifdef ASURA_USE_METAMUSICZONES
			if (s_bZone_HasStoredState)
			{
				// Music controlled by a music zone.
				if (pxData->m_ucHighPriority)
				{
					// High priority message, so use the message straight away.
					MusicZone_RestoreState();
				}
				else
				{
					// Update the stored state.
					s_uZone_StoredLastTrackHash = pxData->m_uTrackID;
					s_uZone_StoredTrackHash = pxData->m_uTrackID;
					s_eZone_StoredTrackState = GetIntroLoopStateFromHash(s_uZone_StoredTrackHash);
					break;
				}
			}
			#endif //ASURA_USE_METAMUSICZONES
			
			const bool bHighPriority = (pxData->m_ucHighPriority ? true : false);
			if (pxData->m_uTrackID == ASURA_HASH_ID_UNSET)
			{
				Asura_MetaMusic_System::StopTrack(bHighPriority);
			}
			else
			{
				Asura_MetaMusic_System::PlayTrack(pxData->m_uTrackID, bHighPriority);
			}
			break;
		}
		case ASURA_MSG_METAMUSIC_CHANGESTATE:
		{
			const Asura_MsgData_MetaMusic_ChangeState* pxData = static_cast
				<const Asura_MsgData_MetaMusic_ChangeState*>(pxMessage->GetData());

			#ifdef ASURA_USE_METAMUSICZONES
			if(s_bZone_HasStoredState)
			{
				// Music controlled by a music zone.
				// Update the stored state.
				s_eZone_StoredTrackState = static_cast<Asura_MetaMusicState>(pxData->m_ucState);
				break;
			}
			#endif //ASURA_USE_METAMUSICZONES

			Asura_MetaMusic_System::ChangeState(static_cast<Asura_MetaMusicState>(pxData->m_ucState));
			break;
		}

		case ASURA_MSG_METAMUSIC_RESTARTLASTTRACK:
		{
			#ifdef ASURA_USE_METAMUSICZONES
			if(s_bZone_HasStoredState)
			{
				// Music controlled by a music zone.
				// Update the stored state.
				s_uZone_StoredTrackHash = s_uZone_StoredLastTrackHash;
				s_eZone_StoredTrackState = GetIntroLoopStateFromHash(s_uZone_StoredTrackHash);
				break;
			}
			#endif //ASURA_USE_METAMUSICZONES

			Asura_MetaMusic_System::RestartLastTrack();
			break;
		}

		#ifdef ASURA_USE_METAMUSICZONES
		case ASURA_MSG_METAMUSIC_ACTIVATEZONE:
		case ASURA_MSG_METAMUSIC_DEACTIVATEZONE:
		{
			Asura_Zone_Controller* const pxZoneController = GetZoneController();
			const Asura_MsgData_MetaMusic_Zone*	const pxData = static_cast<const Asura_MsgData_MetaMusic_Zone*>(pxMessage->GetData());

			// Find the zone and activate/deactivate it.
			Asura_MetaMusic_Zone* pxZone = static_cast<Asura_MetaMusic_Zone*>( pxZoneController->GetZoneByID(pxData->m_uZoneID) );
			if (pxZone)
			{
				pxZone->SetActive(pxMessage->GetMsgID()==ASURA_MSG_METAMUSIC_ACTIVATEZONE);
			}
			break;
		}
		#endif //ASURA_USE_METAMUSICZONES

		default:
			Asura_Assert(0,"Unexpected message sent to meta music system");
			break;
	}
}

void Asura_MetaMusic_System::UpdateForHandler(Asura_Hash_ID uHash, Asura_MetaMusic_Handler* pxHandler)
{
	#ifdef ASURA_USE_METAMUSICZONES
	UpdateMusicZones(*pxHandler);
	#endif //ASURA_USE_METAMUSICZONES
}

void Asura_MetaMusic_System::Update()
{
	if (s_iPauseCount)
	{
		return;
	}
	// Find out what track the music system is actually playing, to see if
	// it has been changed by something else.
	Asura_Hash_ID uPlayingHash = Asura_Music_System::GetCurrentTrackHash();
	if(s_uCurrentFilenameHash && uPlayingHash && s_uCurrentFilenameHash!=uPlayingHash)
	{
		// Looks like something else has started instead, so ignore what we have queued up.
		s_uCurrentFilenameHash = ASURA_HASH_ID_UNSET;
		s_xCurrentTrack.Reset();
		s_eCurrentState = ASURA_METAMUSIC_NOSTATE;
		ClearQueue();
		return;
	}

    #ifdef ASURA_USE_PROJECTMETAMUSIC
	Project_Update();
    #endif //ASURA_USE_PROJECTMETAMUSIC

#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
	// Update all of the handlers...
	s_xHandlerTree.Traverse(UpdateForHandler);
#else
	// Update the instance...
	UpdateForHandler(ASURA_HASH_ID_UNSET, &s_xHandler);
#endif	//defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)

	const Asura_MetaMusic* pxCurrentTrack = 0;
	if (!s_xCurrentTrack.IsNull())
	{
#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
		const Asura_MetaMusic_Handler* pxCurrentTrackHandler = GetHandler(s_xCurrentTrack.GetHandlerHash());
		if (pxCurrentTrackHandler)
		{
			pxCurrentTrack = pxCurrentTrackHandler->GetTrack(s_xCurrentTrack);
		}
#else
		pxCurrentTrack = s_xHandler.GetTrack(s_xCurrentTrack);
#endif	//defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
	}

	if (s_iQueueLength)
	{
		if (!pxCurrentTrack || !uPlayingHash)
		{
			// Not playing anything, and have some tracks queued up, so move on to the next.
			PlayNextInQueue();
		}
		else if (!Asura_Music_System::IsCurrentTrackFading())
		{
			// Have tracks in queue, and we aren't still in the middle of a previous fade.
			// So check to see when we should enter the next state.
			const float fCurrentPosition = Asura_Music_System::GetCurrentTrackPosition();
			if (s_fTransitionPosition<0.0f)
			{
				// Need to determine when the transition should be done.
				// Find out the next entry that will be played.
				Asura_MetaMusic_TrackIndex xNextTrack;
				const Asura_MetaMusic_QueueEntry* pxQueue = GetNextInQueueWithFiles();
				if (pxQueue)
				{
					xNextTrack = pxQueue->m_xTrackIndex;
				}

				const Asura_MetaMusicState eNextState = pxQueue ? pxQueue->m_eState : ASURA_METAMUSIC_NOSTATE;
				const bool bImmediate = GetFromQueue(0)->m_bImmediate;

				// Get the fade time.
				const float fFadeTime = GetFadeTime(s_xCurrentTrack, s_eCurrentState, xNextTrack, eNextState, bImmediate);

				if (bImmediate)
				{
					const float fBeatTimer = pxCurrentTrack ? pxCurrentTrack->GetBeatLoopTime(s_eCurrentState) : 0.0f;
					// For high priority transitions, we just want to go up to the next point defined
					// by the loop beats.
					if (fBeatTimer)
					{
						// Find the transition point just below the current point.
						float fTransitionPoint = Asura_Maths::FloatToInt_Chop(fCurrentPosition/fBeatTimer)*fBeatTimer;
						// If we are more than a little past it, go for the next one instead.
						if (fTransitionPoint+0.05f < fCurrentPosition)
						{
							fTransitionPoint += fBeatTimer;
						}
						
						s_fTransitionPosition = fTransitionPoint;
						// If not looping, then make sure there is enough time left in the track.
						if (!Asura_Music_System::IsCurrentTrackLooping())
						{
							const float fTrackLength = Asura_Music_System::GetCurrentTrackLength();
							Asura_Assert(fTrackLength<10000.0f, "Unexpectedly long track length... being played as looping when it shouldn't be?");

							// KOR - 16/03/2006 - on PSP, there's a gap between play and the track length being filled out.
							// Only set transition time if we've got a valid track length.
							if (fTrackLength > 0.f)
							{
								s_fTransitionPosition = Asura_Maths::Min(fTransitionPoint,fTrackLength - fFadeTime);
							}
						}
					}
					else
					{
						s_fTransitionPosition = fCurrentPosition;
					}
				}
				else
				{
					const float fTrackLength = Asura_Music_System::GetCurrentTrackLength();
					Asura_Assert(fTrackLength<10000.0f,"Unexpectedly long track length... being played as looping when it shouldn't be?");

					// KOR - 16/03/2006 - on PSP, there's a gap between play and the track length being filled out.
					// Only set transition time if we've got a valid track length.
					if (fTrackLength > 0.f)
					{
						//If the transition isn't immediate , then it should happen just before the track ends
						s_fTransitionPosition = fTrackLength - fFadeTime;
					}
				}
				// Set previous position now, since we are ony bothering to update it while waiting for
				// a transition.
				s_fPreviousPosition = fCurrentPosition;

			}
			// Play next if we have reached our transition time, or if it looks like we have overshot and looped.
			// KOR - 16/03/2006 - if s_fTransitionPosition is negative then it still needs initialising
			if (s_fTransitionPosition >= 0.f && (fCurrentPosition >= s_fTransitionPosition || fCurrentPosition< s_fPreviousPosition))
			{
				// Time to start transition into next entry in queue.
				PlayNextInQueue();
			}
			s_fPreviousPosition = fCurrentPosition;
		}
	}
	else if (pxCurrentTrack)
	{
		// Wait for fade in to finish before checking for fade out. RAR 11/10/2005
		if (!Asura_Music_System::IsCurrentTrackFading())
		{
			if (!Asura_Music_System::IsCurrentTrackLooping())
			{
				if (uPlayingHash)
				{
					// Nothing else in queue ,and not in looping state.
					// See if we are near enough to the end of the current track to start fade-out.

					const float fCurrentPosition = Asura_Music_System::GetCurrentTrackPosition();
					const float fTrackLength = Asura_Music_System::GetCurrentTrackLength();
					Asura_Assert(fTrackLength<10000.0f,"Unexpectedly long track length... being played as looping when it shouldn't be?");

					const float fTimeLeft = fTrackLength - fCurrentPosition;

					const float fFadeTime = pxCurrentTrack->m_axStates[GetOutroState(s_xCurrentTrack,s_eCurrentState)].m_fFadeOutTime;

					if (fTimeLeft <= fFadeTime)
					{
						// Fade out
						Asura_Music_System::Stop(fTimeLeft);
						// Set uPlayingHash to 0, so the current track etc will be reset below.
						uPlayingHash = 0;
					}
				}

				if (!uPlayingHash)
				{
					// No longer playing anything ,so reset the current track.
					s_uCurrentFilenameHash = ASURA_HASH_ID_UNSET;
					s_xCurrentTrack.Reset();
					s_eCurrentState = ASURA_METAMUSIC_NOSTATE;
				}
			}
		}
	}

	if (pxCurrentTrack && IsLoopingState(s_eCurrentState))
	{
		s_fIdleTimer += Asura_Timers::GetGameTimeSlice();
		if (pxCurrentTrack->m_fDuration && s_fIdleTimer>pxCurrentTrack->m_fDuration)
		{
			// Time to stop
			StopTrack(false);
			s_fIdleTimer = 0;
		}
	}
	else
	{
		s_fIdleTimer = 0;
	}
}

void Asura_MetaMusic_System::PlayTrack(Asura_Hash_ID uHash, bool bHighPriority)
{
	// Search for the track to play.
	const Asura_MetaMusic_TrackIndex xIndex = GetTrackIndexFromHash(uHash);
	if (xIndex.IsNull())
	{
		Asura_Assert(0,"Failed to find music track to play from hash value");
		return;
	}
 	PlayTrackByIndex(xIndex, bHighPriority);
}


void Asura_MetaMusic_System::PlayTrackByIndex(const Asura_MetaMusic_TrackIndex& xTrackIndex, bool bHighPriority)
{
	Asura_Assert(!xTrackIndex.IsNull(), "MetaMusic: No track specified");
	
	s_xLastTrackRequested = xTrackIndex;
	
	// Are we already playing this track?
	if (xTrackIndex == s_xCurrentTrack && !IsOutroState(s_eCurrentState))
	{
		// No need to change track in this case.
		// However, we may need to check for any queued up changes from this track.
		for(int i=0; i<s_iQueueLength; i++)
		{
			const Asura_MetaMusic_QueueEntry* pxQueue = GetFromQueue(i);
			if (IsOutroState(pxQueue->m_eState) || pxQueue->m_xTrackIndex != xTrackIndex)
			{
				// Clear this part of the queue.
				ClearQueue(i);
				break;
			}
		}

		// Make sure it will end up in a loop state.
		// This is needed to avoid cases where a track is in intro, is then stopped, and then
		// started again before having left the intro in the first place.
		const Asura_MetaMusicState eFinalState = (s_iQueueLength) ? GetFromQueue(s_iQueueLength-1)->m_eState : s_eCurrentState;
		if (!IsLoopingState(eFinalState))
		{
			Asura_MetaMusicState eLoopState;
			switch(eFinalState)
			{
				case ASURA_METAMUSIC_INTRO:
					eLoopState = GetIntroLoopState(xTrackIndex);
					break;

				case ASURA_METAMUSIC_TENSION_TO_AMBIENT:
				case ASURA_METAMUSIC_ACTION_TO_AMBIENT:
					eLoopState = ASURA_METAMUSIC_AMBIENT;
					break;

				case ASURA_METAMUSIC_AMBIENT_TO_TENSION:
				case ASURA_METAMUSIC_ACTION_TO_TENSION:
					eLoopState = ASURA_METAMUSIC_TENSION;
					break;

				case ASURA_METAMUSIC_AMBIENT_TO_ACTION:
				case ASURA_METAMUSIC_TENSION_TO_ACTION:
					eLoopState = ASURA_METAMUSIC_ACTION;
					break;

				default :
					// Shouldn't be any outro or loop state.
					// May also be impossible to be any transition state, but undecided on that.
					Asura_Assert(0,"Unexpected state");
					eLoopState = GetIntroLoopState(xTrackIndex);
					break;
			}

			AddToQueue(xTrackIndex, eLoopState, false);
		}

		s_fIdleTimer = 0;
		return;
	}

	// Are we scheduled to play this track already?
	for (int i=0; i<s_iQueueLength; i++)
	{
		const Asura_MetaMusic_QueueEntry* pxQueue = GetFromQueue(i);
		if (pxQueue->m_xTrackIndex == xTrackIndex)
		{
			// Future change to this track already taken care of.
			return;
		}
	}

	StopTrack(bHighPriority);

	// Add the intro for the new track.
	AddToQueue(xTrackIndex, ASURA_METAMUSIC_INTRO, false);
	// Add default looping state to queue.
	AddToQueue(xTrackIndex, GetIntroLoopState(xTrackIndex), false);
}

void Asura_MetaMusic_System::RestartLastTrack()
{
	if (s_xCurrentTrack.IsNull() && !s_xLastTrackRequested.IsNull())
	{
		PlayTrackByIndex(s_xLastTrackRequested, false);
	}
}

void Asura_MetaMusic_System::StopTrack(bool bHighPriority)
{
	if (s_xCurrentTrack.IsNull())
	{
		ClearQueue(); // In case anything has been queued up while paused.
		return;
	}
	
	if (!bHighPriority)
	{
		// Search for a suitable place to end track.
		for (int iPos=-1; iPos<s_iQueueLength; iPos++)
		{
			const Asura_MetaMusic_TrackIndex& xTrack = (iPos==-1) ? s_xCurrentTrack : GetFromQueue(iPos)->m_xTrackIndex;
			const Asura_MetaMusicState eState = (iPos==-1) ? s_eCurrentState : GetFromQueue(iPos)->m_eState;
			
			if (IsLoopingState(eState))
			{
				// In a looping state.
				// Do an immediate transition to outro (unless flagged to go to end of loop).
				const Asura_MetaMusic* const pxMusic = GetTrack(xTrack);
				//Asura_Assert(pxMusic, "MetaMusic: null track pointer");
				const bool bPlayToEndOfLoop = (pxMusic && pxMusic->ShouldLoopingStatePlayToEndOfLoop(eState));

				ClearQueue(iPos+1);
				AddToQueue(xTrack, GetOutroState(xTrack,eState), !bPlayToEndOfLoop);
				return;
			}
			else if (IsTransitionState(eState) || eState == ASURA_METAMUSIC_INTRO)
			{
				// In transition.
				// Allow transition to get to end , and then go to outro
				// (unless there isn't enough time for fade to take place).
				bool bImmediate = false;
				if (eState == ASURA_METAMUSIC_INTRO && !TrackHasState(xTrack, GetIntroLoopState(xTrack)))
				{
					// Intro state in piece that has no looping state to follow, so use immediate transition.
					bImmediate = true;
				}
				const Asura_MetaMusicState eOutroState = GetOutroState(xTrack, eState);
				const bool bHasOutro = TrackHasState(xTrack, eOutroState);
				Asura_MetaMusic_TrackIndex xOutroTrack;
				if (bHasOutro)
				{
					xOutroTrack = xTrack;
				}

				if (iPos>=0 || IsTimeLeftForFade(xOutroTrack, eOutroState, bImmediate))
				{
					ClearQueue(iPos+1);
					AddToQueue(xTrack, eOutroState, bImmediate);
					return;
				}
			}
			else
			{
				Asura_Assert(IsOutroState(eState), "Unexpected state");
				// Already in outro, so just let it play to end.
				ClearQueue(iPos+1);
				return;
			}
		}
	}

	// High priority 'Stop', or there is no suitable place to play outro so just stop.
	ClearQueue();
	if (bHighPriority)
	{
		Asura_Music_System::Stop( 0.0f );
		s_xCurrentTrack.Reset();
		s_eCurrentState = ASURA_METAMUSIC_NOSTATE;
		s_uCurrentFilenameHash = ASURA_HASH_ID_UNSET;
	}
	else
	{
		PlayNextInQueue(); // Will cause track to stop using appropriate outro time.
	}
}

void Asura_MetaMusic_System::ChangeState(Asura_MetaMusicState eDestState)
{
	if (eDestState != ASURA_METAMUSIC_AMBIENT && eDestState != ASURA_METAMUSIC_TENSION && eDestState != ASURA_METAMUSIC_ACTION)
	{
		Asura_Assert(0,"Destination state must be ambient , tension or action");
		return;
	}
	s_fIdleTimer = 0; // Regardless of what happens, reset the idle timer.

	// If there are already some changes queued up, then we may need to clear some or all of them.
	// We want to keep any changes in track, but clear out any ambient/tension/action state changes.
	Asura_MetaMusicState eFromState = ASURA_METAMUSIC_NOSTATE;
	Asura_MetaMusicState eFinalState;
	Asura_MetaMusic_TrackIndex xFromTrack;

	if (s_iQueueLength)
	{
		xFromTrack  = GetFromQueue(s_iQueueLength-1)->m_xTrackIndex;
		eFinalState = GetFromQueue(s_iQueueLength-1)->m_eState;
	}
	else
	{
		xFromTrack  = s_xCurrentTrack;
		eFinalState = s_eCurrentState;
	}

	if (IsOutroState(eFinalState) || eFinalState == ASURA_METAMUSIC_NOSTATE)
	{
		// If destination state is an outro, then the music is in the process
		// of stopping completely, so state changes should be ignored. RAR 30/06/2005
		// Also ignore ignore if no music is playing, and none is queued to be played
		// (when eFinalState == ASURA_METAMUSIC_NOSTATE). RAR 26/09/2005
		return;
	}

	Asura_Assert(!xFromTrack.IsNull(), "MetaMusic: invalid track");
	const Asura_MetaMusic* pxTrack = GetTrack(xFromTrack);
	Asura_Assert(pxTrack, "MetaMusic: null track pointer");
	if (!pxTrack)
	{
		return;
	}

	Asura_MetaMusicState eToState = ASURA_METAMUSIC_NOSTATE;

	// Determine whether the music actually has the requested state.
	if (pxTrack->HasState(eDestState))
	{
		eToState = eDestState;
	}
	else
	{
		// If destination was action or ambient, allow tension instead.
		if (eDestState == ASURA_METAMUSIC_ACTION || eDestState == ASURA_METAMUSIC_AMBIENT)
		{
			if (pxTrack->HasState(ASURA_METAMUSIC_TENSION))
			{
				eToState = ASURA_METAMUSIC_TENSION;
			}
		}
	}

	if (eToState == ASURA_METAMUSIC_NOSTATE)
	{
		return;
	}


	// Search for position in final track, from which we can do the state change.
	for (int iPos = -1; iPos<s_iQueueLength; iPos++)
	{
		const Asura_MetaMusic_TrackIndex xTrack = (iPos==-1) ? s_xCurrentTrack : GetFromQueue(iPos)->m_xTrackIndex;
		const Asura_MetaMusicState eState = (iPos==-1) ? s_eCurrentState : GetFromQueue(iPos)->m_eState;

		if (xTrack == xFromTrack)
		{
			switch(eState)
			{
				case ASURA_METAMUSIC_INTRO:
					eFromState = GetIntroLoopState(xTrack);
					break;

				case ASURA_METAMUSIC_AMBIENT:
				case ASURA_METAMUSIC_TENSION_TO_AMBIENT:
				case ASURA_METAMUSIC_ACTION_TO_AMBIENT:
					eFromState = ASURA_METAMUSIC_AMBIENT;
					break;

				case ASURA_METAMUSIC_TENSION:
				case ASURA_METAMUSIC_AMBIENT_TO_TENSION:
				case ASURA_METAMUSIC_ACTION_TO_TENSION:
					eFromState = ASURA_METAMUSIC_TENSION;
					break;

				case ASURA_METAMUSIC_ACTION:
				case ASURA_METAMUSIC_AMBIENT_TO_ACTION:
				case ASURA_METAMUSIC_TENSION_TO_ACTION:
					eFromState = ASURA_METAMUSIC_ACTION;
					break;

				case ASURA_METAMUSIC_AMBIENTOUTRO:
				case ASURA_METAMUSIC_TENSIONOUTRO:
				case ASURA_METAMUSIC_ACTIONOUTRO:
				default:
					// Need to check further.
					break;
			}

			if (eFromState != ASURA_METAMUSIC_NOSTATE)
			{
				if (eState == eToState)
				{
					// Change to desired state already queued up.
					ClearQueue(iPos+1);
					return;
				}
				else
				{
					// Get the transition state
					Asura_MetaMusicState eTransitionState = ASURA_METAMUSIC_NOSTATE;
					if (eFromState != eToState)
					{
						eTransitionState = GetTransitionState(eFromState, eToState);
					}
					
					const Asura_MetaMusic* const pxMusic = GetTrack(xTrack);
					Asura_Assert(pxMusic, "MetaMusic: Null music pointer");
					
					bool bImmediate = IsLoopingState(eState) && pxMusic && !pxMusic->ShouldLoopingStatePlayToEndOfLoop(eState);
										
					ClearQueue(iPos+1);

					// Add transition state if needed.
					if (eFromState != eToState)
					{
						AddToQueue(xFromTrack, eTransitionState, bImmediate);
						bImmediate = false;
					}
					// Add the looping state.
					AddToQueue(xFromTrack, eToState, bImmediate);
					return;
				}
			}
		}
	}

	// Failed to find a point to do the state change from, so skip it
	// (may happen if track was in the process of ending).
}


void Asura_MetaMusic_System::PlayNextInQueue()
{

	// Take the immediate setting from the first queue entry, even if there are no files for that state.
	bool bImmediate = true;
	if (s_iQueueLength)
	{
		bImmediate = GetFromQueue(0)->m_bImmediate;
	}
	
	while(s_iQueueLength)
	{
		// Get the current queue entry
		const Asura_MetaMusic_QueueEntry* const pxQueue = &s_axQueue[s_iQueuePosition];
		// and advance to the next one.
		s_iQueueLength--;
		s_iQueuePosition = (s_iQueuePosition+1)%ASURA_METAMUSIC_MAXQUEUE;

		const Asura_MetaMusic* pxNextTrack = GetTrack(pxQueue->m_xTrackIndex);
		Asura_Assert(pxNextTrack, "MetaMusic: null track pointer");

		// Need to get the name of a sound file in the state we are changing to.
		const Asura_MetaMusic_File* pxFile = 0;
		if(pxNextTrack)
		{
			if(s_xBestGuessState == pxQueue->m_eState)
			{
				//We have already prepared a file for this state, hooray
				pxFile = s_pxBestGuessFile;
			}
			else
			{
				pxFile = pxNextTrack->GetFileInState(pxQueue->m_eState);
			}
		}
		
		if (pxFile)
		{
			// Determine fade time.
			float fFade = GetFadeTime(s_xCurrentTrack, s_eCurrentState, pxQueue->m_xTrackIndex, pxQueue->m_eState, bImmediate);

			// If currently playing a track, make sure that the fade time is no greater than the time left.
			if (Asura_Music_System::GetCurrentTrackHash())
			{
				Asura_Music_TrackID eTrack;
				bool bLoop;
				Asura_Music_System::GetCurrentTrackData(eTrack, bLoop);
				
				if (!bLoop)
				{
					const float fTrackLength = Asura_Music_System::GetCurrentTrackLength();
					Asura_Assert(fTrackLength<10000.0f,"Unexpectedly long track length... being played as looping when it shouldn't be?");

					const float fTimeLeft = fTrackLength - Asura_Music_System::GetCurrentTrackPosition();

					if (fFade>fTimeLeft) fFade = fTimeLeft;
					if (fFade<0) fFade = 0;
				}
			}
			
			//DF 27/03/08
			// Crash dump reported a null filename string. Error checking to prevent crash
			if ( pxFile->m_xFilename.GetString() != NULL )
			{
				s_xCurrentTrack = pxQueue->m_xTrackIndex;
				s_eCurrentState = pxQueue->m_eState;
				s_fTransitionPosition = -1.0f;
			
				s_uCurrentFilenameHash = Asura_GetHashID(pxFile->m_xFilename.GetString());
				
				const float fVolume = pxNextTrack->m_fVolume * pxFile->m_fVolume;
				const bool bLoop = ShouldStateBeLooped(s_xCurrentTrack, s_eCurrentState);
				
				Asura_Music_System::Play(pxFile->m_xFilename.GetString(), fVolume, bLoop, fFade);
			
				if (IsLoopingState(s_eCurrentState) && !bLoop && !s_iQueueLength)
				{
					// In a looping state, but playing as non-looped so that a new
					// track can be played after each loop. Add the state back
					// into the queue.
					AddToQueue(s_xCurrentTrack,s_eCurrentState,false);
				}
			}

			//Clear our best guess
			Asura_Music_System::ClearPreparedTrack();
			s_xBestGuessState = ASURA_METAMUSIC_NOSTATE;
			s_pxBestGuessFile = NULL;

			if(s_iQueueLength > 0)
			{
				//Update our best guess for the next track
				const Asura_MetaMusic_QueueEntry* pxQueueNext = &s_axQueue[s_iQueuePosition];
				if(pxQueueNext)
				{
					const Asura_MetaMusic* pxNextTrack = GetTrack(pxQueueNext->m_xTrackIndex);

					s_xBestGuessState = pxQueueNext->m_eState;
					s_pxBestGuessFile = pxNextTrack->GetFileInState(pxQueueNext->m_eState);
					if(s_pxBestGuessFile)
					{
						Asura_Music_System::Prepare(s_pxBestGuessFile->m_xFilename.GetString());
					}
				}
			}
			return;		
		}
		// No luck, try next entry in queue.
	}
	
	// Failed to find anything else to play, so just clean up.
	const Asura_MetaMusic_TrackIndex xNullTrack;
	const float fFade = GetFadeTime(s_xCurrentTrack, s_eCurrentState, xNullTrack, ASURA_METAMUSIC_NOSTATE, bImmediate);
	Asura_Music_System::Stop(fFade);
	s_xCurrentTrack.Reset();
	s_eCurrentState = ASURA_METAMUSIC_NOSTATE;
	s_uCurrentFilenameHash = ASURA_HASH_ID_UNSET;
	s_fIdleTimer = 0;
	s_fTransitionPosition = -1.0f;

}

void Asura_MetaMusic_System::AddToQueue(const Asura_MetaMusic_TrackIndex& xTrackIndex, Asura_MetaMusicState eState, bool bImmediate)
{
	if (s_iQueueLength>=ASURA_METAMUSIC_MAXQUEUE)
	{
		Asura_Assert(0,"MetaMusic: Queue too long");
		return;
	}
	Asura_MetaMusic_QueueEntry* const pxQueue = GetFromQueue(s_iQueueLength);
	pxQueue->m_xTrackIndex = xTrackIndex;
	pxQueue->m_eState = eState;
	pxQueue->m_bImmediate = bImmediate;
	s_iQueueLength++;
}

void Asura_MetaMusic_System::AddToStartOfQueue(const Asura_MetaMusic_TrackIndex& xTrackIndex, Asura_MetaMusicState eState, bool bImmediate)
{
	if (s_iQueueLength>=ASURA_METAMUSIC_MAXQUEUE)
	{
		Asura_Assert(0,"MetaMusic: Queue too long");
		return;
	}
	// Move queue position back one.
	s_iQueuePosition = (s_iQueuePosition+ASURA_METAMUSIC_MAXQUEUE-1)%ASURA_METAMUSIC_MAXQUEUE;

	// Insert at the new zeroth entry.
	Asura_MetaMusic_QueueEntry* const pxQueue = GetFromQueue(0);
	pxQueue->m_xTrackIndex = xTrackIndex;
	pxQueue->m_eState = eState;
	pxQueue->m_bImmediate = bImmediate;
	s_iQueueLength++;
}


void Asura_MetaMusic_System::ClearQueue(int iFrom /*= 0*/)
{
	s_iQueueLength = Asura_Maths::Min(iFrom,s_iQueueLength);
	// If queue is being totally cleared, then also clear the variables for the next transition change.
	if (!s_iQueueLength)
	{
		s_fTransitionPosition = -1.0f;
	}
}

const Asura_MetaMusic_QueueEntry* Asura_MetaMusic_System::GetNextInQueueWithFiles()
{
	for (int i=0; i<s_iQueueLength; i++)
	{
		const int iIndex = (s_iQueuePosition+i)%ASURA_METAMUSIC_MAXQUEUE;

		const Asura_MetaMusic_QueueEntry* const pxQueue = &s_axQueue[iIndex];
		Asura_Assert(!pxQueue->m_xTrackIndex.IsNull(), "MetaMusic: invalid track index");

		const Asura_MetaMusic* pxNextTrack = GetTrack(pxQueue->m_xTrackIndex);
		Asura_Assert(pxNextTrack, "MetaMusic: null track pointer");

		if (pxNextTrack && pxNextTrack->HasState(pxQueue->m_eState))
		{
			return pxQueue;
		}
	}

	// None of the states in the queue have files associated with them.
	return 0;
}

bool Asura_MetaMusic_System::IsLoopingState(Asura_MetaMusicState eState)
{
	return	(eState == ASURA_METAMUSIC_AMBIENT) ||
			(eState == ASURA_METAMUSIC_TENSION) ||
			(eState == ASURA_METAMUSIC_ACTION);
}

bool Asura_MetaMusic_System::IsOutroState(Asura_MetaMusicState eState)
{
	return	(eState == ASURA_METAMUSIC_AMBIENTOUTRO) ||
			(eState == ASURA_METAMUSIC_TENSIONOUTRO) ||
			(eState == ASURA_METAMUSIC_ACTIONOUTRO);
}

bool Asura_MetaMusic_System::IsTransitionState(Asura_MetaMusicState eState)
{
	return	(eState == ASURA_METAMUSIC_AMBIENT_TO_TENSION) ||
			(eState == ASURA_METAMUSIC_AMBIENT_TO_ACTION) ||
			(eState == ASURA_METAMUSIC_TENSION_TO_AMBIENT) ||
			(eState == ASURA_METAMUSIC_TENSION_TO_ACTION) ||
			(eState == ASURA_METAMUSIC_ACTION_TO_AMBIENT) ||
			(eState == ASURA_METAMUSIC_ACTION_TO_TENSION);
}

void Asura_MetaMusic_System::CalculateFinalDummyState( Asura_Hash_ID&        uLastTrackHashID,
                                                       Asura_Hash_ID&        uTrackHashID,
                                                       Asura_MetaMusicState& eState )
{
    // This is not perfectly accurate, as we cannot predict if any messages will come in whilst
    // the queue is being handled in real time.

    uLastTrackHashID = GetTrackHash( s_xLastTrackRequested );
	if (s_iQueueLength)
	{
		// Take the state from the last entry in the queue.
        const Asura_MetaMusic_QueueEntry* const pxQueueEntry = GetFromQueue(s_iQueueLength-1);
        uTrackHashID     = GetTrackHash( pxQueueEntry->m_xTrackIndex );
        eState           = pxQueueEntry->m_eState;
		
	}
	else
	{
    	uTrackHashID     = GetTrackHash( s_xCurrentTrack );
    	eState           = s_eCurrentState;
	}

	if (IsOutroState(eState))
	{
		// Final state is an outro state, so heading towards having no track playing.
		uTrackHashID = ASURA_HASH_ID_UNSET;
		eState = ASURA_METAMUSIC_NOSTATE;
	}
}

void Asura_MetaMusic_System::UpdateDummyState( Asura_Hash_ID& uLastTrackHashID,
                                               Asura_Hash_ID& uTrackHashID,
                                               Asura_MetaMusicState& eState,
                                               const class Asura_Message* pxMessage )
{
    // Update the dummy state to what it would be if the message were acted upon.
    switch ( pxMessage->GetMsgID() )
    {
        case ASURA_MSG_METAMUSIC_PLAY:
        {
            const Asura_MsgData_MetaMusic_Play* pxData =
                static_cast< const Asura_MsgData_MetaMusic_Play* >( pxMessage->GetData() );

            // Play command only does something if it is going to a different track.
            if (pxData->m_uTrackID != uTrackHashID)
			{
            	uLastTrackHashID = pxData->m_uTrackID;
            	uTrackHashID     = pxData->m_uTrackID;
            	eState           = GetIntroLoopStateFromHash(uTrackHashID);
			}
        }
		break;

        case ASURA_MSG_METAMUSIC_CHANGESTATE:
        {
            const Asura_MsgData_MetaMusic_ChangeState* pxData =
                static_cast< const Asura_MsgData_MetaMusic_ChangeState* >( pxMessage->GetData() );

            eState = static_cast< Asura_MetaMusicState >( pxData->m_ucState );
        }
		break;

        case ASURA_MSG_METAMUSIC_RESTARTLASTTRACK:
        {
			if (uLastTrackHashID != uTrackHashID)
			{
				// 'Play' the most recently requested track.
				uTrackHashID = uLastTrackHashID;
            	eState = GetIntroLoopStateFromHash(uTrackHashID);
			}
        }
		break;

        default:
        {
            Asura_Assert( false, "Asura_MetaMusic_System::UpdateDummyState - unhandled message" );
        }
		break;
    }
}

void Asura_MetaMusic_System::RestoreSystemState( const Asura_Hash_ID uLastTrackHashID,
                                                 const Asura_Hash_ID uTrackHashID,
                                                 const Asura_MetaMusicState eState )
{
    if ( uTrackHashID == ASURA_HASH_ID_UNSET )
    {
        Asura_MetaMusic_System::StopTrack( false );
    }
    else
    {
        Asura_MetaMusic_System::PlayTrack( uTrackHashID, false );

        if ( IsLoopingState(eState) )
        {
            Asura_MetaMusic_System::ChangeState( eState );
        }
    }
    s_xLastTrackRequested = GetTrackIndexFromHash( uLastTrackHashID );
}

Asura_MetaMusicState Asura_MetaMusic_System::GetOutroState(const Asura_MetaMusic_TrackIndex& xTrack, Asura_MetaMusicState eCurrentState)
{
	if (eCurrentState == ASURA_METAMUSIC_INTRO)
	{
		// Intro state may be ambient,tension or action.
		eCurrentState = GetIntroLoopState(xTrack);
	}

	switch (eCurrentState)
	{
		case ASURA_METAMUSIC_AMBIENT:
		case ASURA_METAMUSIC_TENSION_TO_AMBIENT:
		case ASURA_METAMUSIC_ACTION_TO_AMBIENT:
		case ASURA_METAMUSIC_AMBIENTOUTRO:
			return ASURA_METAMUSIC_AMBIENTOUTRO;

		case ASURA_METAMUSIC_TENSION:
		case ASURA_METAMUSIC_AMBIENT_TO_TENSION:
		case ASURA_METAMUSIC_ACTION_TO_TENSION:
		case ASURA_METAMUSIC_TENSIONOUTRO:
			return ASURA_METAMUSIC_TENSIONOUTRO;

		case ASURA_METAMUSIC_ACTION:
		case ASURA_METAMUSIC_AMBIENT_TO_ACTION:
		case ASURA_METAMUSIC_TENSION_TO_ACTION:
		case ASURA_METAMUSIC_ACTIONOUTRO:
			return ASURA_METAMUSIC_ACTIONOUTRO;

		default:
			Asura_Assert(0,"Unexpected state");
			return ASURA_METAMUSIC_AMBIENTOUTRO;
	}
}

Asura_MetaMusicState Asura_MetaMusic_System::GetTransitionState(Asura_MetaMusicState eCurrentState, Asura_MetaMusicState eNextState)
{
	if (eCurrentState == ASURA_METAMUSIC_AMBIENT)
	{
		if (eNextState == ASURA_METAMUSIC_TENSION)
		{
			return ASURA_METAMUSIC_AMBIENT_TO_TENSION;
		}
		else if (eNextState == ASURA_METAMUSIC_ACTION)
		{
			return ASURA_METAMUSIC_AMBIENT_TO_ACTION;
		}
		else
		{
			Asura_Assert(0,"MetaMusic: Unexpected state for GetTransionState");
		}
	}
	else if (eCurrentState == ASURA_METAMUSIC_TENSION)
	{
		if (eNextState == ASURA_METAMUSIC_AMBIENT)
		{
			return ASURA_METAMUSIC_TENSION_TO_AMBIENT;
		}
		else if (eNextState == ASURA_METAMUSIC_ACTION)
		{
			return ASURA_METAMUSIC_TENSION_TO_ACTION;
		}
		else
		{
			Asura_Assert(0,"MetaMusic: Unexpected state for GetTransionState");
		}
	}
	else if (eCurrentState == ASURA_METAMUSIC_ACTION)
	{
		if (eNextState == ASURA_METAMUSIC_TENSION)
		{
			return ASURA_METAMUSIC_ACTION_TO_TENSION;
		}
		else if (eNextState == ASURA_METAMUSIC_AMBIENT)
		{
			return ASURA_METAMUSIC_ACTION_TO_AMBIENT;
		}
		else
		{
			Asura_Assert(0,"MetaMusic: Unexpected state for GetTransionState");
		}
	}
	else
	{
		Asura_Assert(0,"MetaMusic: Unexpected state for GetTransionState");
	}

	return ASURA_METAMUSIC_AMBIENT_TO_TENSION;
}

Asura_MetaMusicState Asura_MetaMusic_System::GetIntroLoopState(const Asura_MetaMusic_TrackIndex& xTrack)
{
	Asura_Assert(!xTrack.IsNull(), "MetaMusic: invalid track");

	const Asura_MetaMusic* const pxTrack = GetTrack(xTrack);
	Asura_Assert(pxTrack,"MetaMusic: null track pointer");

	// If no track then just return ambient for a safe value.
	return pxTrack ? pxTrack->m_eIntroLoopState : ASURA_METAMUSIC_AMBIENT;
}

Asura_MetaMusicState Asura_MetaMusic_System::GetIntroLoopStateFromHash(Asura_Hash_ID uTrackHash)
{
	if (uTrackHash)
	{
		const Asura_MetaMusic_TrackIndex xTrackIndex = GetTrackIndexFromHash(uTrackHash);
		if (!xTrackIndex.IsNull())
		{
			return GetIntroLoopState(xTrackIndex);
		}
	}

	// No track, just return ambient for a safe value.
	return ASURA_METAMUSIC_AMBIENT;
}

bool Asura_MetaMusic_System::ShouldStateBeLooped(const Asura_MetaMusic_TrackIndex& xTrack, Asura_MetaMusicState eState)
{
	Asura_Assert(!xTrack.IsNull(), "MetaMusic: invalid track");

	const Asura_MetaMusic* const pxTrack = GetTrack(xTrack);
	Asura_Assert(pxTrack,"MetaMusic: null track pointer");

	return pxTrack ? pxTrack->ShouldStateBeLooped(eState) : false;
}


float Asura_MetaMusic_System::GetFadeTime(const Asura_MetaMusic_TrackIndex& xCurrentTrack, Asura_MetaMusicState eCurrentState, const Asura_MetaMusic_TrackIndex& xNextTrack, Asura_MetaMusicState eNextState, bool bImmediate)
{
	const Asura_MetaMusic*	pxCurrentTrack	= (!xCurrentTrack.IsNull()) ? GetTrack(xCurrentTrack) : NULL;
	const Asura_MetaMusic*	pxNextTrack		= (!xNextTrack.IsNull()) ? GetTrack(xNextTrack) : NULL;

	if (!pxCurrentTrack && !pxNextTrack)
	{
		return 0; // Both pointers are null.
	}

	if (pxCurrentTrack != pxNextTrack)
	{
		// Transition between tracks
		if (!pxCurrentTrack)
		{
			// No previous track... use the intro fade in time.
			return pxNextTrack->m_axStates[ASURA_METAMUSIC_INTRO].m_fFadeInTime;
		}
		else if (!pxNextTrack)
		{
			// Next track doesn't exist, so use the fade out time from the outro.
			return pxCurrentTrack->m_axStates[GetOutroState(xCurrentTrack, eCurrentState)].m_fFadeOutTime;
		}
		else
		{
			// Both current and next track exist.
			// Use of intro or outro fade time depends on whether we are letting it reach the
			// end of the outro.
			if (bImmediate)
			{
				return pxNextTrack->m_axStates[ASURA_METAMUSIC_INTRO].m_fFadeInTime;
			}
			else
			{
				return pxCurrentTrack->m_axStates[GetOutroState(xCurrentTrack, eCurrentState)].m_fFadeOutTime;
			}
		}
	}

	if (IsLoopingState(eCurrentState))
	{
		if (IsLoopingState(eNextState))
		{
			if (eCurrentState == eNextState)
			{
				// Going to another track in the same looping state, so use the crossfade (fade in) time.
				return pxCurrentTrack->m_axStates[eCurrentState].m_fFadeInTime;
			}
			else
			{
				// Going from one looping state direct to another (because no track in transition state)
				// so we need to get the crossfade time for that transition.
				// The crossfade time is equivalent to the fade in time for the transition state.
				const Asura_MetaMusicState eTransition = GetTransitionState(eCurrentState, eNextState);
				return pxCurrentTrack->m_axStates[eTransition].m_fFadeInTime;
			}
		}
		else
		{
			// Going from looping state to non-looping
			// use the fade in time of the next state.
			return pxCurrentTrack->m_axStates[eNextState].m_fFadeInTime;
		}
	}
	else
	{
		// Going from a non-looping state.
		if (bImmediate)
		{
			// Going from middle of track, so use next track's fade in time.
			Asura_Assert(!IsLoopingState(eNextState),"Not expecting an immediate transition to a looping state");
			return pxCurrentTrack->m_axStates[eNextState].m_fFadeInTime;
		}
		else
		{
			// Going from end of track, so use this track's fade out time.
			return pxCurrentTrack->m_axStates[eCurrentState].m_fFadeOutTime;
		}
	}
}

bool Asura_MetaMusic_System::IsTimeLeftForFade(const Asura_MetaMusic_TrackIndex& xTrack, Asura_MetaMusicState eState, bool bImmediate)
{
	// If  no track playing, then this is somewhat irrelevant.
	if (!Asura_Music_System::GetCurrentTrackHash()) return true;
	
	// If track is looping, then it's fine.
	Asura_Music_TrackID eTrack;
	bool bLoop;
	Asura_Music_System::GetCurrentTrackData(eTrack, bLoop);
	if (bLoop) return true;
	
	// Get time needed for fade
	const float fFadeTime = GetFadeTime(s_xCurrentTrack, s_eCurrentState, xTrack, eState, bImmediate);
	// and time left.
	const float fTrackLength = Asura_Music_System::GetCurrentTrackLength();
	Asura_Assert(fTrackLength<10000.0f,"Unexpectedly long track length... being played as looping when it shouldn't be?");
	const float fTimeLeft = fTrackLength - Asura_Music_System::GetCurrentTrackPosition();

	return fFadeTime<=fTimeLeft;
}

bool Asura_MetaMusic_System::TrackHasState(const Asura_MetaMusic_TrackIndex& xTrack, Asura_MetaMusicState eState)
{
	Asura_Assert(!xTrack.IsNull(), "MetaMusic: invalid track");

	const Asura_MetaMusic* const pxTrack = GetTrack(xTrack);
	Asura_Assert(pxTrack,"MetaMusic: null track pointer");

	return pxTrack ? pxTrack->HasState(eState) : false;
}

void Asura_MetaMusic_System::ClearBestGuessFile ()
{
	s_pxBestGuessFile = NULL;
	s_xBestGuessState = ASURA_METAMUSIC_NOSTATE;	
}

#ifdef ASURA_USE_METAMUSICZONES
void Asura_MetaMusic_System::UpdateMusicZones(Asura_MetaMusic_Handler& xHandler)
{
	const Asura_Zone_Controller* const pxZoneController = xHandler.GetZoneController();
	const Asura_MetaMusic_Zone* pxZone = static_cast< const Asura_MetaMusic_Zone* >( pxZoneController->GetZoneFromPosition( *Asura_Camera::GetPosition() ) );

	#ifdef ASURA_TOOLS
	if (Tools_System::IsActive())
	{
		// Disabling zones while in tools for the moment.
		// May re-enable later when I add something to fake the number of NPCs, and also
		// provide a way of seeing volumes, and the music queue at the same time.
		// RAR 02/11/2005
		pxZone = 0;
	}
	#endif //ASURA_TOOLS

	// If we're not in a zone, or we're in the default zone (which is not a valid zone for our purposes).
    if ( pxZone == NULL || pxZone->IsDefaultZone() )
    {
		// MGH 19/6/2006: We may be in a zone associated with a different handler. If so do nothing.
		if (xHandler.GetZoneHashID() == s_uZone_CurrentHandler)
		{
			s_uZone_CurrentID = 0;
			s_uZone_CurrentHandler = ASURA_HASH_ID_UNSET;
			if (s_bZone_HasStoredState)
			{
				// Restore the previously stored state.
				MusicZone_RestoreState();
			}
		}
	}
	else
	{
		const int iNumNPCs = Project_GetNumberOfAttackingNPCs();
	
		s_fZone_Timer += Asura_Timers::GetGameTimeSlice();
		if (pxZone->GetZoneID() != s_uZone_CurrentID ||
			xHandler.GetZoneHashID() != s_uZone_CurrentHandler)
		{
			// Moved to a new zone.
			s_uZone_CurrentID = pxZone->GetZoneID();
			s_uZone_CurrentHandler = xHandler.GetZoneHashID(); 
			s_uZone_TriggerLevel = 0;
			s_fZone_Timer = 0;
		}
		const u_int uTriggerLevel = pxZone->GetTriggerLevel(iNumNPCs, s_uZone_TriggerLevel, s_fZone_Timer);
		if (uTriggerLevel == 0)
		{
			if (s_bZone_HasStoredState)
			{
				// Restore the previously stored state.
				MusicZone_RestoreState();
			}
		}
		else if (uTriggerLevel != s_uZone_TriggerLevel)
		{
			// Changed trigger level.
			// If it's increasing, then we always use it. If it's decreasing, we ignore
			// it if there isn't a stored state (which will happen if a high priority
			// message has taken control of things).
			if (uTriggerLevel > s_uZone_TriggerLevel || s_bZone_HasStoredState)
			{
				if (!s_bZone_HasStoredState)
				{
					MusicZone_StoreState();
				}
				pxZone->Trigger(uTriggerLevel);
			}
			s_fZone_Timer = 0; // Reset the timer regardless of whether it was used or not.
		}
	
		s_uZone_TriggerLevel = uTriggerLevel;
	}
}

void Asura_MetaMusic_System::MusicZone_StoreState()
{
	if (s_bZone_HasStoredState) return;

	CalculateFinalDummyState(s_uZone_StoredLastTrackHash, s_uZone_StoredTrackHash, s_eZone_StoredTrackState);
	s_bZone_HasStoredState = true;
}

void Asura_MetaMusic_System::MusicZone_RestoreState()
{
	if(!s_bZone_HasStoredState) return;
	
	RestoreSystemState(s_uZone_StoredLastTrackHash, s_uZone_StoredTrackHash, s_eZone_StoredTrackState);
	s_bZone_HasStoredState = false;

}
#endif //ASURA_USE_METAMUSICZONES


/////////////////////////////////////////////////////////////////////////////
/*-----------------**
** Asura_MetaMusic **
**-----------------*/
Asura_MetaMusic::~Asura_MetaMusic()
{
	Asura_ResourcePushType(ASURA_RESOURCE_META_MUSIC);
	{
		Asura_ResourceDeallocateArray(m_pxFiles);
	}
	Asura_ResourcePopType();
}


bool Asura_MetaMusic::HasState(Asura_MetaMusicState eState) const 
{
	return m_axStates[eState].m_iNumberOfFiles != 0;
}

#if 0 //Disabled because of issues implementing on XBox. RAR 06/02/2005
bool Asura_MetaMusic::ShouldLoopingStatePlayToEndOfLoop(Asura_MetaMusicState eState)
{
	switch(eState)
	{
		case ASURA_METAMUSIC_AMBIENT:
			return m_bPlayAmbientToEndOfLoop != 0; 
		case ASURA_METAMUSIC_TENSION:
			return m_bPlayTensionToEndOfLoop != 0; 
		case ASURA_METAMUSIC_ACTION:
			return m_bPlayActionToEndOfLoop != 0; 
		
		default :
			break;
	}

	return true;
}
#endif

const Asura_MetaMusic_File* Asura_MetaMusic::GetFileInState(Asura_MetaMusicState eState) const
{
	if (!m_axStates[eState].m_iNumberOfFiles) return 0;

	// Pick a random index in this state
	const int iIndex = (Asura_Random::GetIntU()%m_axStates[eState].m_iNumberOfFiles) + m_axStates[eState].m_iFirstIndex;

	Asura_Assert(iIndex>=0 && iIndex<m_iNumberOfFiles,"MetaMusic: Invalid index");

	return m_pxFiles[iIndex].m_xFilename.IsBlank() ? 0 : &m_pxFiles[iIndex];
}

float Asura_MetaMusic::GetBeatLoopTime(Asura_MetaMusicState eCurrentState) const
{
	switch (eCurrentState)
	{

		case ASURA_METAMUSIC_AMBIENT:
			if(!m_fAmbientBeatsPerMinute) return 0;
			return (60.0f/m_fAmbientBeatsPerMinute)*m_fAmbientLoopBeats;

		case ASURA_METAMUSIC_TENSION:
			if(!m_fTensionBeatsPerMinute) return 0;
			return (60.0f/m_fTensionBeatsPerMinute)*m_fTensionLoopBeats;

		case ASURA_METAMUSIC_ACTION:
			if(!m_fActionBeatsPerMinute) return 0;
			return (60.0f/m_fActionBeatsPerMinute)*m_fActionLoopBeats;

		default:
			return 0.0f;
	}
}

bool Asura_MetaMusic::ShouldStateBeLooped(Asura_MetaMusicState eState) const
{
	if (Asura_MetaMusic_System::IsLoopingState(eState))
	{
		if (m_bChooseNewFileAfterEachLoop && m_axStates[eState].m_iNumberOfFiles > 1)
		{
			// In a looping state, but picking a new piece after each loop.
			// So play as non-looped to allow this.
 			return false;
		}
		return true;
	}
	return false;
}

#ifdef ASURA_TOOLS
void Asura_MetaMusic::Initialise(const char* pszName)
{
	m_xName = pszName;
	m_uNameHash = Asura_GetHashID(pszName);
	m_fDuration = 0.0f;
	m_fAmbientBeatsPerMinute = 0;
	m_fAmbientLoopBeats = 0;
	m_fTensionBeatsPerMinute = 0;
	m_fTensionLoopBeats = 0;
	m_fActionBeatsPerMinute = 0;
	m_fActionLoopBeats = 0;
	m_fVolume = 1.0f;
	for (int i=0; i<ASURA_METAMUSIC_NUMSTATES; i++)
	{
		m_axStates[i].m_iFirstIndex = 0;
		m_axStates[i].m_iNumberOfFiles = 0;
		m_axStates[i].m_fFadeInTime = 0.0f;
		m_axStates[i].m_fFadeOutTime = 0.0f;
	}

	m_iNumberOfFiles = 0;
	m_pxFiles = 0;

	m_uFlags = 0;
	m_eIntroLoopState = ASURA_METAMUSIC_AMBIENT;
}
#endif //ASURA_TOOLS

float Asura_MetaMusic_System::GetBeatTimer()
{
	const float fCurrentPosition = Asura_Music_System::GetCurrentTrackPosition();
	const Asura_MetaMusic* pxCurrentTrack = 0;
	if(!s_xCurrentTrack.IsNull())
	{
		pxCurrentTrack = GetTrack(s_xCurrentTrack);
	}
	
	if (pxCurrentTrack)
	{
		float fBPM = 0.0f;
		float fLoopBeats = 0.0f;
		switch (s_eCurrentState)
		{
			case ASURA_METAMUSIC_AMBIENT:
			{
				fBPM = pxCurrentTrack->m_fAmbientBeatsPerMinute;
				fLoopBeats = pxCurrentTrack->m_fAmbientLoopBeats;
				break;
			}

			case ASURA_METAMUSIC_TENSION:
			{
				fBPM = pxCurrentTrack->m_fTensionBeatsPerMinute;
				fLoopBeats = pxCurrentTrack->m_fTensionLoopBeats;
				break;
			}

			case ASURA_METAMUSIC_ACTION:
			{
				fBPM = pxCurrentTrack->m_fActionBeatsPerMinute;
				fLoopBeats = pxCurrentTrack->m_fActionLoopBeats;
				break;
			}
			default:
			{
				break;
			}
		}

		if (fLoopBeats > 0.0f)
		{
			const float fBeats = Asura_Maths::FloatMod(fCurrentPosition * fBPM / 60.0f, fLoopBeats);
			return fBeats; 
		}
	}
	return 0.0f;
}

////////////////////////
// GetHandlerForTrack //
////////////////////////
// Find the handler that corresponds to this track.
Asura_MetaMusic_Handler* Asura_MetaMusic_System::GetHandlerForTrack(const Asura_MetaMusic_TrackIndex& xIndex)
{
#if defined(ASURA_USE_STREAMING)
	if (xIndex.IsNull()) return 0;

	const Asura_Hash_ID uHandlerHash = xIndex.GetHandlerHash();
	
	Asura_MetaMusic_Handler* pxHandler = &GetCurrentHandler();
	if (!pxHandler || pxHandler->GetZoneHashID() != uHandlerHash)
	{
		pxHandler = s_xHandlerTree.GetDataByHash(uHandlerHash);
	}
	Asura_Assert(pxHandler, "MetaMusic: couldn't find handler");
	return pxHandler;
#else
	// Only one handler in non-streaming builds.
	return &GetCurrentHandler();
#endif //defined(ASURA_USE_STREAMING)
}


#if defined(ASURA_USE_STREAMING)
//////////////////////////////
// Asura_MetaMusic_UserData //
//////////////////////////////
class Asura_MetaMusic_UserData
{
public:
	Asura_MetaMusic_UserData(Asura_Hash_ID uHash) : m_uTrackHash(uHash) {}

	Asura_Hash_ID m_uTrackHash; // Input
	Asura_MetaMusic_TrackIndex m_xTrackIndex; // Output
};

////////////////////////////////////
// GetTrackIndexFromHash_Callback //
////////////////////////////////////
// Return true to continue, false to stop traversal.
bool Asura_MetaMusic_System::GetTrackIndexFromHash_Callback(Asura_Hash_ID uHash, const Asura_MetaMusic_Handler* pxHandler, void* pUserData)
{
	Asura_MetaMusic_UserData* const pxUserData = static_cast<Asura_MetaMusic_UserData*>(pUserData);
	if (!pxUserData) return false; // Give up.

	pxUserData->m_xTrackIndex = pxHandler->GetTrackIndexFromHash(pxUserData->m_uTrackHash);

	// Continue the traversal if track is not set.
	return pxUserData->m_xTrackIndex.IsNull();
}
#endif //#if defined(ASURA_USE_STREAMING)

///////////////////////////
// GetTrackIndexFromHash //
///////////////////////////
Asura_MetaMusic_TrackIndex Asura_MetaMusic_System::GetTrackIndexFromHash(Asura_Hash_ID uHash)
{
#if defined(ASURA_USE_STREAMING)
	// Search the handlers looking for one with the correct track hash.
	Asura_MetaMusic_UserData xUserData(uHash);
	s_xHandlerTree.Traverse(GetTrackIndexFromHash_Callback, &xUserData);
	return xUserData.m_xTrackIndex;
#else
	return GetCurrentHandler().GetTrackIndexFromHash(uHash);
#endif
}

#endif	//!defined(ASURA_SUPPORT_DYNAMIC_MUSIC_SYSTEM)
