//===========================================================================//
// 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 "AudioCommand.hpp"
#include "AudioRenderer.hpp"
#include "AudioChannel.hpp"
#include "AudioSample.hpp"

//##########################################################################
//############################    AudioCommand    ##########################
//##########################################################################

AudioCommand::ClassData*
	AudioCommand::DefaultData = NULL;

MemoryBlock*
	AudioCommand::s_AllocatedMemory = NULL;

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

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

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

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AudioCommand* AudioCommand::Create(
	int type,
	const ResourceID &hint_ID,
	Stuff::Scalar priority,
	Stuff::Scalar volume,
	Stuff::Scalar min_restart,
	Stuff::Scalar min_replace
)
{
	return new AudioCommand(
		DefaultData,
		type,
		hint_ID,
		priority,
		volume,
		min_restart,
		min_replace
	);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AudioCommand* AudioCommand::Create(
	int type,
	const char *name,
	Stuff::Scalar priority,
	Stuff::Scalar volume,
	Stuff::Scalar min_restart,
	Stuff::Scalar min_replace
)
{
	Resource hint(MString("Audio\\")+name);
	if (!hint.DoesResourceExist())
		return NULL;
	return new AudioCommand(
		DefaultData,
		type,
		hint.GetResourceID(),
		priority,
		volume,
		min_restart,
		min_replace
	);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AudioCommand::AudioCommand(
	ClassData *class_data,
	int type,
	const ResourceID &sample_ID,
	Stuff::Scalar priority,
	Stuff::Scalar volume,
	Stuff::Scalar min_restart,
	Stuff::Scalar min_replace
):
	Plug(class_data),
	m_type(type),
	m_handleResourceID(sample_ID),
	m_priority(priority),
	m_volume(volume),
	m_minRestartDelay(min_restart),
	m_minReplaceDelay(min_replace),
	m_channel(NULL),
	m_playMode(gosAudio_Stop)
{
	Check_Pointer(this);
	Verify((unsigned)type < AudioRenderer::Instance->m_channelTypes.GetLength());
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void AudioCommand::Play(gosAudio_PlayMode play_mode)
{
	Check_Object(this);
	Check_Object(AudioRenderer::Instance);

	//
	//---------------------------------------------------------------------
	// Grab the sample (loading it if necessary), and figure out the delays
	//---------------------------------------------------------------------
	//
	m_playMode = play_mode;
	AudioSample *sample = AudioRenderer::Instance->UseSample(m_handleResourceID);
	Check_Object(sample);
	sample->m_commands.Add(this);

	//
	//------------------------------------------------------------------------------------
	// If this is going to be a loop, add it to the loop list.  If nothing is available to
	// play it now, just return
	//------------------------------------------------------------------------------------
	//
	if (play_mode == gosAudio_Loop)
	{
		if (!AddLoop())
			return;
	}

	//
	//---------------------------------------------
	// Put the loop command in the audio buffer now
	//---------------------------------------------
	//
	AudioRenderer::Instance->AddNewCommand(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void AudioCommand::Stop()
{
	Check_Object(this);

	//
	//--------------------------------------------------------------------------------------------
	// If we still have a channel, check the play mode.  If we are play once, stopping the channel
	// will delete the command
	//--------------------------------------------------------------------------------------------
	//
	if (m_channel)
	{
		if (m_playMode == gosAudio_PlayOnce)
		{
			Check_Object(m_channel);
			m_channel->Stop();
		}

		//
		//---------------------------------------------------------------------------------------------
		// We are a loop, so the stop command just put us on the loop queue.  We need to kill ourselves
		// to truly stop the sound
		//---------------------------------------------------------------------------------------------
		//
		else
		{
			Verify(m_playMode == gosAudio_Loop);
			Check_Object(m_channel);
			m_channel->Stop();
			Check_Object(this);
			delete this;
		}
	}

	//
	//-----------------------------------------------------------
	// We don't have a channel, so we need to destroy the command
	//-----------------------------------------------------------
	//
	else
		delete this;
}

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

	//
	//---------------------------------------------------------------------------------
	// If this is a queued type, go ahead and queue it immediately if there are no free
	// channels
	//---------------------------------------------------------------------------------
	//
	Check_Object(AudioRenderer::Instance);
	ChannelType &type = AudioRenderer::Instance->m_channelTypes[m_type];
	if (type.IsQueued() && type.m_inactiveChannels.IsEmpty())
	{
		type.m_queuedCommands.Add(this);
		return false;
	}

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

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

			//
			//---------------------------------------------
			// Can we merge the sound with an existing one?
			//---------------------------------------------
			//
			if (
				existing_command->m_volume >= m_volume
				 && existing_command->m_priority <= m_priority
				 && channel->m_startTime + existing_command->m_minRestartDelay >= now
			)
			{
				delete this;
				return false;
			}

			//
			//------------------------------------------------------------
			// Check to see if this channel is a candidate for replacement
			//------------------------------------------------------------
			//
			if (
				existing_command->m_volume <= m_volume
				 && existing_command->m_priority >= m_priority
				 && channel->m_startTime + existing_command->m_minReplaceDelay <= now
			)
			{
				Scalar temp = channel->m_energy;
				if (temp <= energy)
				{
					candidate = channel;
					energy = temp;
				}
			}
		}
	}

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

	//
	//-------------------------------------------
	// Replace the best candidate if there is one
	//-------------------------------------------
	//
	if (candidate)
	{
		Check_Object(candidate);
		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
	//------------------------------------------------------------------------------------
	//
	ChainIteratorOf<AudioChannel*> free_channels(&type.m_inactiveChannels);
	if ((channel = free_channels.GetCurrent()) != NULL)
	{
		Check_Object(channel);
		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();
			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);
	channel->Stop();
	if (channel->m_sample != sample)
		channel->SetSample(sample);
	channel->Activate(this);
	Verify(channel->m_startTime == now);
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool AudioCommand::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 AudioCommand::ComputeEnergy(AudioChannel *channel)
{
	Check_Object(this);

	//
	//--------------------------------------------------------------
	// If we haven't been assigned a channel yet, our energy is full
	//--------------------------------------------------------------
	//
	if (!channel)
		return 1.0f;

	//
	//----------------------------------------------------------------
	// Otherwise, our energy decreases over the lifetime of the sample
	//----------------------------------------------------------------
	//
	Check_Object(channel);
	AudioSample *sample = channel->m_sample;
	Check_Object(sample);
	return 1.0f - (static_cast<Scalar>(gos_GetElapsedTime() - channel->m_startTime)/sample->m_sampleInfo.fDuration);
}
