//===========================================================================//
// File:	AudioRenderer.cpp                                                //
// Project: MUNGA           Brick: Video Renderer 	                         //
// Contents: Abstracted Base Video Renderer								     //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 03/04/97  JTR  Initial coding.                                            //
//---------------------------------------------------------------------------//
// Copyright (C) 1997, Virtual World Entertainment, Inc.                     //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include "AdeptHeaders.hpp"

#include "SpatializedCommand.hpp"
#include "AudioRenderer.hpp"
#include "SpatializedChannel.hpp"
#include "AudioSample.hpp"
#include "Interface.hpp"

//##########################################################################
//#########################    SpatializedCommand    #######################
//##########################################################################

SpatializedCommand::ClassData*
	SpatializedCommand::DefaultData = NULL;

MemoryBlock*
	SpatializedCommand::s_AllocatedMemory = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void SpatializedCommand::InitializeClass(
		size_t block_count,
		size_t block_delta
	)
{
	Verify(!s_AllocatedMemory);
	s_AllocatedMemory =
		new MemoryBlock(
			sizeof(SpatializedCommand),
			block_count,
			block_delta,
			"SpatializedCommand",
			g_LibraryHeap
		);
	Register_Object(s_AllocatedMemory);

	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			SpatializedCommandClassID,
			"Adept::SpatializedCommand",
			BaseClass::DefaultData
		);
	Register_Object(DefaultData);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void SpatializedCommand::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;

	Unregister_Object(s_AllocatedMemory);
	delete s_AllocatedMemory;
	s_AllocatedMemory = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void SpatializedCommand::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
SpatializedCommand* SpatializedCommand::Create(
	int type,
	const ResourceID &hint_ID,
	Stuff::Scalar priority,
	Stuff::Scalar volume,
	Stuff::Scalar min_restart,
	Stuff::Scalar min_replace,
	const Point3D &position,
	const Vector3D &velocity,
	Stuff::Scalar min_deviation,
	Stuff::Scalar near_clip,
	Stuff::Scalar min_range,
	Stuff::Scalar max_range,
	Stuff::Scalar far_clip
)
{
	return new SpatializedCommand(
		DefaultData,
		type,
		hint_ID,
		priority,
		volume,
		min_restart,
		min_replace,
		position,
		velocity,
		min_deviation,
		near_clip,
		min_range,
		max_range,
		far_clip
	);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
SpatializedCommand::SpatializedCommand(
	ClassData *class_data,
	int type,
	const ResourceID &sample_ID,
	Stuff::Scalar priority,
	Stuff::Scalar volume,
	Stuff::Scalar min_restart,
	Stuff::Scalar min_replace,
	const Point3D &position,
	const Vector3D &velocity,
	Stuff::Scalar min_deviation,
	Stuff::Scalar near_clip,
	Stuff::Scalar min_range,
	Stuff::Scalar max_range,
	Stuff::Scalar far_clip
):
	AudioCommand(class_data, type, sample_ID, priority, volume, min_restart, min_replace),
	m_position(position),
	m_velocity(velocity),
	m_minDeviation(min_deviation),
	m_nearClip(near_clip),
	m_minRange(min_range),
	m_maxRange(max_range),
	m_farClip(far_clip)
{
	Check_Pointer(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
SpatializedCommand::~SpatializedCommand()
{
	Check_Object(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool SpatializedCommand::ConnectToChannel(AudioSample *sample)
{
	Check_Object(this);
	Check_Object(sample);
	AUDIO_RENDER("Execute::NewCommands::SpatializedCommand");

	//
	//---------------------------
	// Compute the command energy
	//---------------------------
	//
	SpatializedChannel *candidate = NULL;
	Scalar energy = m_energy = ComputeEnergy(NULL);
	Time now = gos_GetElapsedTime();

	//
	//-----------------------------------------------------------------
	// If we aren't going to loop, see if we can be immediately clipped
	//-----------------------------------------------------------------
	//
	if (m_bearing.range<m_nearClip || m_bearing.range>m_farClip)
	{
		if (m_playMode == gosAudio_PlayOnce)
			delete this;
		return false;
	}

	//
	//----------------------------------------------------------------------------
	// First look through the active channels to see if we can ignore this command
	//----------------------------------------------------------------------------
	//
	ChainIteratorOf<AudioChannel*> active_channels(&sample->m_activeChannels);
	AudioChannel *channel;
	if (m_playMode == gosAudio_PlayOnce)
	{
		while ((channel = active_channels.ReadAndNext()) != NULL)
		{
			SpatializedChannel *spatial = Cast_Object(SpatializedChannel*, channel);

			//
			//----------------------------------------------------------------------------------
			// We now need to calculate the angles to the ear from the old site and the new one.
			// If the sound is too different from the one we are looking at, just go on to the
			// next sound
			//----------------------------------------------------------------------------------
			//
			Scalar bear_diff = Fabs(spatial->m_bearing.yaw - m_bearing.yaw);
			SpatializedCommand *existing_command = Cast_Object(SpatializedCommand*, spatial->m_command);
			Check_Object(existing_command);
			if (bear_diff > existing_command->m_minDeviation)
				continue;

			//
			//-----------------------------------------------
			// If the existing sound could replace us, let it
			//-----------------------------------------------
			//
			Verify(m_playMode != gosAudio_Loop);
			if (channel->m_startTime + existing_command->m_minRestartDelay >= now)
			{
				delete this;
				return false;
			}

			//
			//------------------------------------------------------------------------------
			// If we are not lower priority than the existing sound, check to see if it is a
			// candidate for replacement
			//------------------------------------------------------------------------------
			//
			if (existing_command->m_priority >= m_priority)
			{
				if (channel->m_startTime + existing_command->m_minReplaceDelay <= now)
				{
					Scalar temp = channel->m_energy;
					if (temp <= energy)
					{
						candidate = spatial;
						energy = temp;
					}
				}
			}
		}
	}

	//
	//------------------------------------------------------
	// Look for an inactive spatial hooked up to this sample
	//------------------------------------------------------
	//
	ChainIteratorOf<AudioChannel*> inactive_channels(&sample->m_inactiveChannels);
	if ((channel = inactive_channels.GetCurrent()) != NULL)
	{
		Verify(channel->m_playMode != gosAudio_Loop);
		channel->Activate(this);
		Verify(channel->m_startTime == now);
		return true;
	}

	//
	//-------------------------------------------
	// Replace the best candidate if there is one
	//-------------------------------------------
	//
	if (candidate)
	{
		Check_Object(candidate);
		Verify(candidate->m_playMode != gosAudio_Loop);
		candidate->Stop();
		candidate->Activate(this);
		Verify(candidate->m_startTime == now);
		return true;
	}

	//
	//------------------------------------------------------------------------------------
	// Now we search the inactive channel list for our sound type and use the earliest one
	//------------------------------------------------------------------------------------
	//
	Check_Object(AudioRenderer::Instance);
	ChannelType &type = AudioRenderer::Instance->m_channelTypes[m_type];
	ChainIteratorOf<AudioChannel*> free_channels(&type.m_inactiveChannels);
	if ((channel = free_channels.GetCurrent()) != NULL)
	{
		Check_Object(channel);
		Verify(channel->m_playMode != gosAudio_Loop);
		channel->SetSample(sample);
		channel->Activate(this);
		Verify(channel->m_startTime == now);
		return true;
	}

	//
	//----------------------------------------------------------------------------------
	// Now we search the active channel list for our sound type and use the earliest one
	// that also matches the playmode - loops steal loops and one-shots steal one-shots
	//----------------------------------------------------------------------------------
	//
	ChainIteratorOf<AudioChannel*> used_channels(&type.m_activeChannels);
	while ((channel = used_channels.ReadAndNext()) != NULL)
	{
		Check_Object(channel);
		if (channel->m_playMode == m_playMode)
		{
			channel->Stop();
			Verify(channel->m_playMode != gosAudio_Loop);
			if (channel->m_sample != sample)
				channel->SetSample(sample);
			channel->Activate(this);
			Verify(channel->m_startTime == now);
			return true;
		}
	}

	//
	//----------------------------------------------------------------------------------
	// Now we search the active channel list for our sound type and use the earliest one
	//----------------------------------------------------------------------------------
	//
	used_channels.First();
	channel = used_channels.GetCurrent();
	Check_Object(channel);
	Verify(channel->m_playMode != gosAudio_Loop);
	channel->Stop();
	if (channel->m_sample != sample)
		channel->SetSample(sample);
	channel->Activate(this);
	Verify(channel->m_startTime == now);
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool SpatializedCommand::AddLoop()
{
	Check_Object(this);

	//
	//-------------------------------------------------
	// Hook us up to the loop chain of our channel type
	//-------------------------------------------------
	//
	Check_Object(AudioRenderer::Instance);
	ChannelType &type = AudioRenderer::Instance->m_channelTypes[m_type];
	type.m_loopCommands.Add(this);
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Stuff::Scalar SpatializedCommand::ComputeEnergy(AudioChannel *channel)
{
	Check_Object(this);

	//
	//----------------------------------------------------------------------------
	// If we haven't been assigned a channel yet, only distance affects our energy
	//----------------------------------------------------------------------------
	//
	Scalar energy = 0.0f;
	if (!channel)
	{
		LinearMatrix4D ear_to_world = AudioRenderer::Instance->GetInterface()->GetLocalToWorld();
		Point3D ear_in_world(ear_to_world);
		Vector3D ear_to_command;
		ear_to_command.Subtract(m_position, ear_in_world);
		m_bearing = ear_to_command;
		Scalar energy = 0.0f;
		if (m_bearing.range >= m_nearClip && m_bearing.range <= m_farClip)
		{
			if (m_bearing.range >= m_minRange)
			{
				if (m_bearing.range >= m_maxRange)
					energy = 1.0f;
				else
					energy = (m_maxRange - m_minRange - m_bearing.range) / (m_maxRange - m_minRange);
			}
		}
		return energy;
	}

	//
	//----------------------------------------------------------------
	// Otherwise, our energy decreases over the lifetime of the sample
	//----------------------------------------------------------------
	//
	SpatializedChannel *spatial = Cast_Object(SpatializedChannel*, channel);
	m_bearing = spatial->m_bearing;
	if (m_bearing.range >= m_nearClip && m_bearing.range <= m_farClip)
	{
		if (m_bearing.range >= m_minRange)
		{
			if (m_bearing.range >= m_maxRange)
				energy = 1.0f;
			else
				energy = (m_maxRange - m_minRange - m_bearing.range) / (m_maxRange - m_minRange);
		}
	}
	AudioSample *sample = spatial->m_sample;
	Check_Object(sample);
	energy *= 1.0f - (static_cast<Scalar>(gos_GetElapsedTime() - channel->m_startTime)/sample->m_sampleInfo.fDuration);
	return energy;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void SpatializedCommand::SetPosition(const Point3D &position)
{
	Check_Object(this);
	m_position = position;
	if (m_channel)
	{
		SpatializedChannel *channel = Cast_Object(SpatializedChannel*, m_channel);
		channel->SetPosition(position);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void SpatializedCommand::SetVelocity(const Vector3D &velocity)
{
	Check_Object(this);
	m_velocity = velocity;
	if (m_channel)
	{
		SpatializedChannel *channel = Cast_Object(SpatializedChannel*, m_channel);
		channel->SetVelocity(velocity);
	}
}