//===========================================================================//
// File:	l4audio.cc                                                          //
// Project: MUNGA           Brick: Audio Renderer									  //
// Contents: 																					  //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 01/30/95  ECH  Initial coding.                                            //
//---------------------------------------------------------------------------//
// Copyright (C) 1994, Virtual World Entertainment, Inc.                     //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include <mungal4.hpp>
#pragma hdrstop

#if !defined(L4AUDIO_HPP)
#	include <l4audio.hpp>
#endif

#if !defined(L4AUDLVL_HPP)
#	include <l4audlvl.hpp>
#endif

#if !defined(L4APP_HPP)
#	include <l4app.hpp>
#endif

#if !defined(L4AUDRND_HPP)
#	include <l4audrnd.hpp>
#endif

#if !defined(NAMELIST_HPP)
#	include <namelist.hpp>
#endif

//#############################################################################
//#######################    L4AudioSpatialization    #########################
//#############################################################################

//
//#############################################################################
//#############################################################################
//
L4AudioSpatialization::L4AudioSpatialization()
{
	azimuthOfSource = 0.0f;

	frontLeftScale = 0.0f;
	frontRightScale = 0.0f;
	rearLeftScale = 0.0f;
	rearRightScale = 0.0f;

	frontLeftDelay = 0.0f;
	frontRightDelay = 0.0f;
	rearLeftDelay = 0.0f;
	rearRightDelay = 0.0f;

	quadrant = Quadrant1;
}

//
//#############################################################################
//#############################################################################
//
L4AudioSpatialization::~L4AudioSpatialization()
{
}

//
//#############################################################################
//#############################################################################
//
Logical
	L4AudioSpatialization::TestInstance() const
{
	return True;
}

//
//#############################################################################
//#############################################################################
//
void
	L4AudioSpatialization::CalculateSpatialization(Radian azimuth_of_source)
{
	azimuthOfSource = azimuth_of_source;

	//
	//---------------------------------------------------------------
	// Make azimuth usable for channel volume calculations
	//---------------------------------------------------------------
	//
	#define DEG_90 	(90.0f*(RAD_PER_DEG))
	#define DEG_180 	(180.0f*(RAD_PER_DEG))
	#define DEG_360	(360.0f*(RAD_PER_DEG))

	azimuthOfSource = DEG_180 + azimuthOfSource;
	if (azimuthOfSource > DEG_180)
		azimuthOfSource = azimuthOfSource - DEG_360;
	Verify(
		azimuthOfSource <= DEG_180 &&
		azimuthOfSource >= -DEG_180
	);

	//
	//---------------------------------------------------------------
	// Channel volume scale and ITD calculations
	//---------------------------------------------------------------
	//

	//
	// Get audio head constants
	//
	L4AudioRenderer *audio_renderer;
	AudioHead *audio_head;

	Check(application);
	audio_renderer =
		Cast_Object(L4AudioRenderer*, application->GetAudioRenderer());
	Check(audio_renderer);
	audio_head = audio_renderer->GetAudioHead();
	Check(audio_head);

	//
	// Init channel volume scale variables
	//
	const Radian	azimuth_max = 45.0*RAD_PER_DEG; // HACK - should come from audio.ini
	Scalar			tangent_ratio;

	frontLeftScale = 0.0f;
	frontRightScale = 0.0f;
	rearLeftScale = 0.0f;
	rearRightScale = 0.0f;

	//
	// Init ITD parameters
	//
	const Scalar itd_delay = audio_head->GetITDDifference();

	frontLeftDelay = 0.0f;
	frontRightDelay = 0.0f;
	rearLeftDelay = 0.0f;
	rearRightDelay = 0.0f;

	//
	//---------------------------------------------------------------
	// Quadrant 1
	//---------------------------------------------------------------
	//
	if (
		azimuthOfSource > -azimuth_max &&
		azimuthOfSource < azimuth_max
	)
	{
		quadrant = Quadrant1;

		// volume scale
		Verify(!Small_Enough(tan(azimuth_max)));
		tangent_ratio =
			(tan(azimuthOfSource) / tan(azimuth_max)) * 0.5f;

		frontLeftScale = Sqrt(0.5f + tangent_ratio);
		frontRightScale = Sqrt(0.5f - tangent_ratio);
		Verify(frontLeftScale >= 0.0f && frontLeftScale <= 1.0f);
		Verify(frontRightScale >= 0.0f && frontRightScale <= 1.0f);

		// pitch offset
		if (azimuthOfSource > 0.0f)
		{
			frontLeftDelay = itd_delay * tangent_ratio * 2.0f;
		}
		else
		{
			frontRightDelay = itd_delay * -tangent_ratio * 2.0f;
		}
	}
	//
	//---------------------------------------------------------------
	// Quadrant 2
	//---------------------------------------------------------------
	//
	else if (
		azimuthOfSource > azimuth_max &&
		azimuthOfSource < (DEG_180 - azimuth_max)
	)
	{
		quadrant = Quadrant2;

		// volume scale
		azimuthOfSource = azimuthOfSource - DEG_90;

		Verify(!Small_Enough(tan(DEG_90 - azimuth_max)));
		tangent_ratio =
			(tan(azimuthOfSource) / tan(DEG_90 - azimuth_max)) * 0.5f;

		rearLeftScale = Sqrt(0.5f + tangent_ratio);
		frontLeftScale = Sqrt(0.5f - tangent_ratio);
		Verify(rearLeftScale >= 0.0f && rearLeftScale <= 1.0f);
		Verify(frontLeftScale >= 0.0f && frontLeftScale <= 1.0f);

		// pitch offset
		if (azimuthOfSource > 0.0f)
		{
			rearLeftDelay = itd_delay * tangent_ratio * 2.0f;
		}
		else
		{
			frontLeftDelay = itd_delay * -tangent_ratio * 2.0f;
		}
	}
	//
	//---------------------------------------------------------------
	// Quadrant 3
	//---------------------------------------------------------------
	//
	else if (
		azimuthOfSource > (DEG_180 - azimuth_max) ||
		azimuthOfSource < (-DEG_180 + azimuth_max)
	)
	{
		quadrant = Quadrant3;

		// volume scale
		azimuthOfSource = azimuthOfSource - DEG_180;

		Verify(!Small_Enough(tan(azimuth_max)));
		tangent_ratio =
			(tan(azimuthOfSource) / tan(azimuth_max)) * 0.5f;

		rearRightScale = Sqrt(0.5f + tangent_ratio);
		rearLeftScale = Sqrt(0.5f - tangent_ratio);
		Verify(rearRightScale >= 0.0f && rearRightScale <= 1.0f);
		Verify(rearLeftScale >= 0.0f && rearLeftScale <= 1.0f);

		// pitch offset
		if (azimuthOfSource > -azimuth_max)
		{
			rearLeftDelay = -(itd_delay * tangent_ratio * 2.0f);
		}
		else
		{
			rearRightDelay = -(itd_delay * -tangent_ratio * 2.0f);
		}

		#if 0
		Tell(
			"azimuthOfSource " << azimuthOfSource <<
			" rearRightDelay " << rearRightDelay <<
			" rearLeftDelay " << rearLeftDelay <<
			"\n"
		);
		#endif
	}
	//
	//---------------------------------------------------------------
	// Quadrant 4
	//---------------------------------------------------------------
	//
	else
	{
		quadrant = Quadrant4;

		Verify(
			azimuthOfSource > (-DEG_180 + azimuth_max) &&
			azimuthOfSource < -azimuth_max
		);

		// volume scale
		azimuthOfSource = azimuthOfSource + DEG_90;

		Verify(!Small_Enough(tan(DEG_90 - azimuth_max)));
		tangent_ratio =
			(tan(azimuthOfSource) / tan(DEG_90 - azimuth_max)) * 0.5f;

		frontRightScale = Sqrt(0.5f + tangent_ratio);
		rearRightScale = Sqrt(0.5f - tangent_ratio);
		Verify(frontRightScale >= 0.0f && frontRightScale <= 1.0f);
		Verify(rearRightScale >= 0.0f && rearRightScale <= 1.0f);

		// pitch offset
		if (azimuthOfSource > 0.0f)
		{
			frontRightDelay = itd_delay * tangent_ratio * 2.0f;
		}
		else
		{
			rearRightDelay = itd_delay * -tangent_ratio * 2.0f;
		}
	}

	#if 0
	Tell(
		"FL " << frontLeftScale <<
		" FR " << frontRightScale <<
		" RL " << rearLeftScale <<
		" RR " << rearRightScale <<
		"\n"
	);
	#endif

	#if 0
	Tell(
		"Quadrant " << quadrant <<
		" FL " << frontLeftDelay <<
		" FR " << frontRightDelay <<
		" RL " << rearLeftDelay <<
		" RR " << rearRightDelay <<
		"\n"
	);
	#endif

	Verify(frontLeftScale >= 0.0f && frontLeftScale <= 1.0f);
	Verify(frontRightScale >= 0.0f && frontRightScale <= 1.0f);
	Verify(rearLeftScale >= 0.0f && rearLeftScale <= 1.0f);
	Verify(rearRightScale >= 0.0f && rearRightScale <= 1.0f);
}

//#############################################################################
//#########################    L4AudioLocation    #############################
//#############################################################################

//
//#############################################################################
//#############################################################################
//
L4AudioLocation::L4AudioLocation(
	PlugStream *stream,
	Entity *entity
):
	AudioLocation(stream, entity)
{
	L4AudioLocationX();
}

//
//#############################################################################
//#############################################################################
//
void
	L4AudioLocation::L4AudioLocationX()
{
	//
	// Get audio renderer frame duration
	//
	Check(application)
	L4AudioRenderer *audio_renderer =
		Cast_Object(L4AudioRenderer*, application->GetAudioRenderer());
	Check(audio_renderer);

	Time frame_duration =
		audio_renderer->GetCalibrationFrameDuration();

	//
	// Init ITD pitch offset variables
	//
	lastITDFrameTime = Now();
	lastITDFrameTime -= frame_duration;

	currentFrontLeftDelay = 0.0f;
	currentFrontRightDelay = 0.0f;
	currentRearLeftDelay = 0.0f;
	currentRearRightDelay = 0.0f;

	frontLeftITDPitchOffset = 0.0f;
	frontRightITDPitchOffset = 0.0f;
	rearLeftITDPitchOffset = 0.0f;
	rearRightITDPitchOffset = 0.0f;
}

//
//#############################################################################
//#############################################################################
//
L4AudioLocation::~L4AudioLocation()
{
}

//
//#############################################################################
//#############################################################################
//
Logical
	L4AudioLocation::TestInstance() const
{
	AudioLocation::TestInstance();
	Check(&spatialization);
	return True;
}

//
//#############################################################################
//#############################################################################
//
void
	L4AudioLocation::UpdateSpatialModelImplementation(AudioHead *audio_head)
{
	Check(this);
	Check(audio_head);

	//
	//---------------------------------------------------------------
	// Call inherited method
	//---------------------------------------------------------------
	//
	AudioLocation::UpdateSpatialModelImplementation(audio_head);

	//
	//---------------------------------------------------------------
	// Catch case of head == source
	//---------------------------------------------------------------
	//
	if (IsHeadSource())
	{
		spatialization.frontLeftScale = 1.0f;
		spatialization.frontRightScale = 1.0f;
		spatialization.rearLeftScale = 1.0f;
		spatialization.rearRightScale = 1.0f;

		frontLeftITDPitchOffset = 0.0f;
		frontRightITDPitchOffset = 0.0f;
		rearLeftITDPitchOffset = 0.0f;
		rearRightITDPitchOffset = 0.0f;
		return;
	}

	//
	//---------------------------------------------------------------
	// Calculate channel spatialization
	//---------------------------------------------------------------
	//
	spatialization.CalculateSpatialization(GetAzimuthOfSource());

	//
	//---------------------------------------------------------------
	// Calculate ITD pitch offsets
	//---------------------------------------------------------------
	//
	const Scalar itd_pitch_offset_constant =
		0.003831f / 0.000002f; // period / delay // HACK - should come from audio head

	Time itd_delta_time;

	itd_delta_time = Now();
	itd_delta_time -= lastITDFrameTime;
	lastITDFrameTime = Now();

	frontLeftITDPitchOffset = 0.0f;
	frontRightITDPitchOffset = 0.0f;
	rearLeftITDPitchOffset = 0.0f;
	rearRightITDPitchOffset = 0.0f;

	if (!Small_Enough((Scalar)itd_delta_time))
	{
		if (currentFrontLeftDelay != spatialization.frontLeftDelay)
		{
			Verify(!Small_Enough((Scalar)itd_delta_time));

			frontLeftITDPitchOffset =
				itd_pitch_offset_constant *
					(spatialization.frontLeftDelay - currentFrontLeftDelay) /
						(Scalar)itd_delta_time;
			currentFrontLeftDelay = spatialization.frontLeftDelay;
		}
		if (currentFrontRightDelay != spatialization.frontRightDelay)
		{
			Verify(!Small_Enough((Scalar)itd_delta_time));

			frontRightITDPitchOffset =
				itd_pitch_offset_constant *
					(spatialization.frontRightDelay - currentFrontRightDelay) /
						(Scalar)itd_delta_time;
			currentFrontRightDelay = spatialization.frontRightDelay;
		}
		if (currentRearLeftDelay != spatialization.rearLeftDelay)
		{
			Verify(!Small_Enough((Scalar)itd_delta_time));

			rearLeftITDPitchOffset =
				itd_pitch_offset_constant *
					(spatialization.rearLeftDelay - currentRearLeftDelay) /
						(Scalar)itd_delta_time;
			currentRearLeftDelay = spatialization.rearLeftDelay;
		}
		if (currentRearRightDelay != spatialization.rearRightDelay)
		{
			Verify(!Small_Enough((Scalar)itd_delta_time));

			rearRightITDPitchOffset =
				itd_pitch_offset_constant *
					(spatialization.rearRightDelay - currentRearRightDelay) /
						(Scalar)itd_delta_time;
			currentRearRightDelay = spatialization.rearRightDelay;
		}
	}

	#if 0
	Tell(
		"Quadrant " << spatialization.quadrant <<
		" FL " << frontLeftITDPitchOffset <<
		" FR " << frontRightITDPitchOffset <<
		" RL " << rearLeftITDPitchOffset <<
		" RR " << rearRightITDPitchOffset <<
		"\n"
	);
	#endif

	//
	//---------------------------------------------------------------
	// Check for source "between ears"
	//---------------------------------------------------------------
	//
	const Scalar ear_radius =
		audio_head->GetDistanceBetweenEars() * 0.5;
	Scalar distance_to_source_listener_plane =
		GetDistanceToSourceListenerPlane();

	if (distance_to_source_listener_plane < ear_radius)
	{
		Verify(!Small_Enough(ear_radius));

		const Scalar proportion =
			0.5f *
				(ear_radius - distance_to_source_listener_plane) /
					ear_radius;

		Scalar powerA, powerB;

		#if DEBUG_LEVEL>0
			if (!(proportion >= 0.0f && proportion <= 1.0f))
			{
				Dump(ear_radius);
				Dump(distance_to_source_listener_plane);
				Dump(proportion);
			}
			Verify(proportion >= 0.0f && proportion <= 1.0f);
		#endif

		powerA = Sqrt(1.0f - proportion);
		powerB = Sqrt(proportion);
		Verify(powerA >= 0.0f && powerA <= 1.0f);
		Verify(powerB >= 0.0f && powerB <= 1.0f);

		switch (spatialization.quadrant)
		{
		case L4AudioSpatialization::Quadrant1:
			spatialization.rearLeftScale = spatialization.frontLeftScale * powerB;
			spatialization.rearRightScale = spatialization.frontRightScale * powerB;

			spatialization.frontLeftScale *=  powerA;
			spatialization.frontRightScale *= powerA;
			break;

		case L4AudioSpatialization::Quadrant2:
			spatialization.rearRightScale = spatialization.rearLeftScale * powerB;
			spatialization.frontRightScale = spatialization.frontLeftScale * powerB;

			spatialization.rearLeftScale *= powerA;
			spatialization.frontLeftScale *= powerA;
			break;

		case L4AudioSpatialization::Quadrant3:
			spatialization.frontRightScale = spatialization.rearRightScale * powerB;
			spatialization.frontLeftScale = spatialization.rearLeftScale * powerB;

			spatialization.rearRightScale *= powerA;
			spatialization.rearLeftScale *= powerA;
			break;

		case L4AudioSpatialization::Quadrant4:
			spatialization.frontLeftScale = spatialization.frontRightScale * powerB;
			spatialization.rearLeftScale = spatialization.rearRightScale * powerB;

			spatialization.frontRightScale *= powerA;
			spatialization.rearRightScale *= powerA;
			break;
		}

		#if 0
		Tell(
			"FL " << spatialization.frontLeftScale <<
			" FR " << spatialization.frontRightScale <<
			" RL " << spatialization.rearLeftScale <<
			" RR " << spatialization.rearRightScale <<
			"\n"
		);
		#endif
	}

	Verify(spatialization.frontLeftScale >= 0.0f && spatialization.frontLeftScale <= 1.0f);
	Verify(spatialization.frontRightScale >= 0.0f && spatialization.frontRightScale <= 1.0f);
	Verify(spatialization.rearLeftScale >= 0.0f && spatialization.rearLeftScale <= 1.0f);
	Verify(spatialization.rearRightScale >= 0.0f && spatialization.rearRightScale <= 1.0f);
}

//#############################################################################
//#########################    AudioChannelSet    #############################
//#############################################################################

AudioChannelSet AudioChannelSet::Null;

//
//#############################################################################
//#############################################################################
//
AudioChannelSet::AudioChannelSet()
{
	for (int i = 0; i < AudioChannelSetSize; i++)
	{
		channels[i] = NULL;
		enables[i] = False;
	}
}

//
//#############################################################################
//#############################################################################
//
Logical
	AudioChannelSet::TestInstance() const
{
	for (int i = 0; i < AudioChannelSetSize; i++)
	{
		if (channels[i] != NULL)
		{
			Verify(enables[i] == True);
			Check(channels[i]);
		}
	}
	return True;
}

//
//#############################################################################
//#############################################################################
//
void
	AudioChannelSet::EnableAll()
{
	//
	// Must set all channels to null and all enables to True
	//
	for (int i = 0; i < AudioChannelSetSize; i++)
	{
		channels[i] = NULL;
		enables[i] = True;
	}
}

//
//#############################################################################
//#############################################################################
//
void
	AudioChannelSet::ReleaseAll()
{
	//
	// Must release all channels without changing enable values
	//
	for (int i = 0; i < AudioChannelSetSize; i++)
	{
		if (channels[i] != NULL)
		{
			Check(channels[i]);
			channels[i]->Release();
			channels[i] = NULL;
		}
	}
}

//#############################################################################
//##########################    L4AudioSource    ##############################
//#############################################################################

//
//#############################################################################
//#############################################################################
//
L4AudioSource::L4AudioSource(
	PlugStream *stream,
	Entity *entity
):
	AudioSource(stream, entity)
{
	L4AudioSourceX();
}

//
//#############################################################################
//#############################################################################
//
void
	L4AudioSource::BuildFromPage(
		PlugStream *stream,
		NameList *name_list,
		ClassID class_ID,
		ObjectID object_ID
	)
{
	AudioSource::BuildFromPage(stream, name_list, class_ID, object_ID);
}

//
//#############################################################################
//#############################################################################
//
void
	L4AudioSource::L4AudioSourceX()
{
}

//
//#############################################################################
//#############################################################################
//
L4AudioSource::~L4AudioSource()
{
}

//
//#############################################################################
//#############################################################################
//
Logical
	L4AudioSource::TestInstance() const
{
	AudioSource::TestInstance();
	Check(&channelSet);
	return True;
}

//
//#############################################################################
//#############################################################################
//
void
	L4AudioSource::StopMessageImplementation()
{
	Check(this);

	AudioSource::StopMessageImplementation();

	//
	// Request stop from renderer
	//
	Check(application);
	Cast_Object(L4AudioRenderer*, application->GetAudioRenderer())->
		StopRequest(this);
}

//
//#############################################################################
//#############################################################################
//
void
	L4AudioSource::SetAudioChannelSet(const AudioChannelSet &audio_channel_set)
{
	Check(this);
	channelSet = audio_channel_set;
}

//
//#############################################################################
//#############################################################################
//
const AudioChannelSet*
	L4AudioSource::GetAudioChannelSet()
{
	Check(this);
	return &channelSet;
}

//
//#############################################################################
//#############################################################################
//
void
	L4AudioSource::ReleaseChannels()
{
	Check(this);
	channelSet.ReleaseAll();
}

//#############################################################################
//########################    DirectPatchSource    ############################
//#############################################################################

//
//#############################################################################
//#############################################################################
//
DirectPatchSource::DirectPatchSource(
	PlugStream *stream,
	Entity *entity
):
	L4AudioSource(stream, entity)
{
	MemoryStream_Read(stream, &audioPosition);
	channel = NULL;
}

//
//#############################################################################
//#############################################################################
//
void
	DirectPatchSource::BuildFromPage(
		PlugStream *stream,
		NameList *name_list,
		ClassID class_ID,
		ObjectID object_ID
	)
{
	L4AudioSource::BuildFromPage(stream, name_list, class_ID, object_ID);
	MEM_STRM_WRITE_ENTRY(*stream, name_list, Enumeration, position);
}

//
//#############################################################################
//#############################################################################
//
DirectPatchSource::~DirectPatchSource()
{
}

//
//#############################################################################
//#############################################################################
//
Logical
	DirectPatchSource::TestInstance() const
{
	L4AudioSource::TestInstance();
	if (channel != NULL)
	{
		Check(channel);
	}
	return True;
}

//
//#############################################################################
//#############################################################################
//
void
	DirectPatchSource::StartMessageImplementation()
{
	Check(this);

	L4AudioSource::StartMessageImplementation();

	//
	//--------------------------------------------------------------------------
	// Init channel set to requested channels
	//--------------------------------------------------------------------------
	//
	channelSet = AudioChannelSet::Null;
	switch (audioPosition)
	{
	case FrontDirectPatchPosition:
	case FrontLeftDirectPatchPosition:
	case FrontRightDirectPatchPosition:
		channelSet.EnableFrontLeft(True);
		break;

	case RearDirectPatchPosition:
	case RearLeftDirectPatchPosition:
	case RearRightDirectPatchPosition:
		channelSet.EnableRearLeft(True);
		break;
	}

	//
	//--------------------------------------------------------------------------
	// Request start from renderer
	//--------------------------------------------------------------------------
	//
	Check(application);
	Cast_Object(L4AudioRenderer*, application->GetAudioRenderer())->
		StartRequest(this);
}

//
//#############################################################################
//#############################################################################
//
void
	DirectPatchSource::StartImplementation()
{
	Check(this);

	//
	//--------------------------------------------------------------------------
	// Call inherited method
	//--------------------------------------------------------------------------
	//
	L4AudioSource::StartImplementation();

	//
	//--------------------------------------------------------------------------
	// Remember the channel
	//--------------------------------------------------------------------------
	//
	Verify(channel == NULL);
	switch (audioPosition)
	{
	case FrontDirectPatchPosition:
	case FrontLeftDirectPatchPosition:
	case FrontRightDirectPatchPosition:
		channel = channelSet.GetFrontLeft();
		break;

	case RearDirectPatchPosition:
	case RearLeftDirectPatchPosition:
	case RearRightDirectPatchPosition:
		channel = channelSet.GetRearLeft();
		break;
	}
	Check(channel);

	//
	//--------------------------------------------------------------------------
	// Setup the channel
	//--------------------------------------------------------------------------
	//
	ExecuteWatchers();

	//
	// Turn the volume down while setting up the channel
	//
	channel->SendController(MIDI_VOLUME_CONTROL, MIDI_MIN_CONTROL_VALUE);

	//
	// Set the channel to the bank and program
	//
	PatchResource *patch_resource;

	patch_resource = Cast_Object(PatchResource*, GetAudioResource());
	Check(patch_resource);
	patch_resource->SetDistance(GetDistanceToSource());
	patch_resource->SetupPatch(channel);

	//
	// Set the channel to default control values
	//
	channel->SendController(MIDI_REVERB_CONTROL, MIDI_MIN_CONTROL_VALUE);
	channel->SendController(MIDI_CHORUS_CONTROL, MIDI_MIN_CONTROL_VALUE);

	switch (audioPosition)
	{
	case FrontDirectPatchPosition:
	case RearDirectPatchPosition:
		channel->SendController(MIDI_PAN_CONTROL, MIDI_CENTER_PAN_VALUE);
		break;
	case FrontLeftDirectPatchPosition:
	case RearLeftDirectPatchPosition:
		channel->SendController(MIDI_PAN_CONTROL, MIDI_LEFT_PAN_VALUE);
		break;
	case FrontRightDirectPatchPosition:
	case RearRightDirectPatchPosition:
		channel->SendController(MIDI_PAN_CONTROL, MIDI_RIGHT_PAN_VALUE);
		break;
	}

	//
	// Set midi history to default control values
	//
	lastMIDIVolume = MIDI_MIN_CONTROL_VALUE;
	lastMIDIPitchBend = 0; // Set by force update
	lastMIDIFilterCutoff = MIDI_MAX_CONTROL_VALUE; // Set by patch load

	//
	//--------------------------------------------------------------------------
	// Calculate attack time, velocity, and execute model
	//--------------------------------------------------------------------------
	//
	MIDIValue midi_velocity =
		CalculateSourceAttackVolumeScale() *
		(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;

	if (UseSourceAttackTime())
	{
		MIDINRPNValue midi_attack_time =
			CalculateSourceAttackTime() *
			(Scalar)AWE_VOL_ATTACK_TIME_RANGE + 0.5f;

		channel->SendNRPN(AWE_VOL_ATTACK_TIME_NRPN, midi_attack_time);
	}

	ExecuteModel(True);

	//
	//--------------------------------------------------------------------------
	// Start note
	//--------------------------------------------------------------------------
	//
	channel->SendNoteOn(GetCurrentNoteValue(), midi_velocity);
	#if 0
		Tell("On " << (int)GetCurrentNoteValue() << "\n");
	#endif
}

//
//#############################################################################
//#############################################################################
//
void
	DirectPatchSource::StopImplementation()
{
	Check(this);

	//
	// Stop note
	//
	Check(channel);
	channel->SendNoteOff(GetCurrentNoteValue(), MIDI_MIN_CONTROL_VALUE);
	channel = NULL;
}

//
//#############################################################################
//#############################################################################
//
void
	DirectPatchSource::ExecuteModel(Logical force_update)
{
	Check(this);

	//
	//--------------------------------------------------------------------------
	// Apply pitch offset
	//--------------------------------------------------------------------------
	//
	const MIDIPitchBend 	pitch_resolution = 12; // HACK - should come from audio.ini
	AudioPitchCents		pitch_offset;
	MIDIPitchBend 			midi_pitch_bend;
	MIDIPitchBend 			pitch_difference;

	pitch_offset = CalculateSourcePitchOffset();
	midi_pitch_bend = (Scalar)pitch_offset * MIDI_PITCH_PER_CENTS + 0.5f;
	Clamp(midi_pitch_bend, -MIDI_PITCH_BEND_MAX, MIDI_PITCH_BEND_MAX);
	pitch_difference = lastMIDIPitchBend - midi_pitch_bend;

	if ((Abs(pitch_difference) >= pitch_resolution) || force_update)
	{
		lastMIDIPitchBend = midi_pitch_bend;
		Check(channel);
		channel->SendPitchBend(midi_pitch_bend);
	}

	//
	//--------------------------------------------------------------------------
	// Apply filter scale
	//--------------------------------------------------------------------------
	//
	if (UseSourceBrightnessScale())
	{
		const MIDINRPNValue 	filter_resolution = 2;// HACK - should come from audio.ini
		Scalar					filter_scale;
		PatchResource			*patch_resource;
		MIDINRPNValue 			max_midi_filter_cutoff;
		MIDINRPNValue 			midi_filter_cutoff;
		MIDINRPNValue 			filter_difference;

		filter_scale = CalculateSourceBrightnessScale();
		patch_resource = Cast_Object(PatchResource*, GetAudioResource());
		max_midi_filter_cutoff = patch_resource->GetMaxMIDIFilterCutoff();
		midi_filter_cutoff = filter_scale * (Scalar)max_midi_filter_cutoff + 0.5f;
		filter_difference = lastMIDIFilterCutoff - midi_filter_cutoff;

		if (Abs(filter_difference) >= filter_resolution)
		{
			lastMIDIFilterCutoff = midi_filter_cutoff;
			Check(channel);
			channel->SendNRPN(AWE_FILTER_CUTOFF_NRPN, midi_filter_cutoff);
		}
	}

	//
	//--------------------------------------------------------------------------
	// Apply volume scale
	//--------------------------------------------------------------------------
	//
	Scalar				volume_scale;
	const MIDIValue 	volume_resolution = 2; // HACK - should come from audio.ini
	MIDIValue 			midi_volume;
	int 					volume_difference;

	volume_scale = CalculateSourceVolumeScale();
	midi_volume = volume_scale * (Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;
	volume_difference = lastMIDIVolume - midi_volume;

	if (Abs(volume_difference) >= volume_resolution)
	{
		lastMIDIVolume = midi_volume;
		Check(channel);
		channel->SendController(MIDI_VOLUME_CONTROL, midi_volume);
	}
}

//#############################################################################
//######################    Dynamic3DPatchSource    ###########################
//#############################################################################

//
//#############################################################################
//#############################################################################
//
Dynamic3DPatchSource::Dynamic3DPatchSource(
	PlugStream *stream,
	Entity *entity
):
	L4AudioSource(stream, entity)
{
}

//
//#############################################################################
//#############################################################################
//
Dynamic3DPatchSource::~Dynamic3DPatchSource()
{
}

//
//#############################################################################
//#############################################################################
//
Logical
	Dynamic3DPatchSource::TestInstance() const
{
	L4AudioSource::TestInstance();
	return True;
}

//
//#############################################################################
//#############################################################################
//
void
	Dynamic3DPatchSource::StartMessageImplementation()
{
	Check(this);

	L4AudioSource::StartMessageImplementation();

	//
	// Init channel set to requested channels
	//
	channelSet.EnableAll();

	//
	// Request start from renderer
	//
	Check(application);
	Cast_Object(L4AudioRenderer*, application->GetAudioRenderer())->
		StartRequest(this);
}

//
//#############################################################################
//#############################################################################
//
void
	Dynamic3DPatchSource::StartImplementation()
{
	Check(this);

	//
	//--------------------------------------------------------------------------
	// Call inherited method
	//--------------------------------------------------------------------------
	//
	L4AudioSource::StartImplementation();

	//
	//--------------------------------------------------------------------------
	// Get application, renderer and head pointers
	//--------------------------------------------------------------------------
	//
	L4AudioRenderer *audio_renderer;
	AudioHead *audio_head;

	Check(application);
	audio_renderer =
		Cast_Object(L4AudioRenderer*, application->GetAudioRenderer());
	Check(audio_renderer);
	audio_head = audio_renderer->GetAudioHead();
	Check(audio_head);

	//
	//--------------------------------------------------------------------------
	// Setup the channel
	//--------------------------------------------------------------------------
	//
	ExecuteWatchers();

	//
	// Turn the volume down while setting up the channels
	//
	int i;
	AudioChannel *channel;

	for (i = 0; i < AudioChannelSetSize; i++)
	{
		channel = channelSet.GetNth(i);
		Check(channel);
		channel->SendController(MIDI_VOLUME_CONTROL, MIDI_MIN_CONTROL_VALUE);
	}

	//
	// Set the channels to the bank and program
	//
	PatchResource *patch_resource;

	patch_resource = Cast_Object(PatchResource*, GetAudioResource());
	Check(patch_resource);
	patch_resource->SetDistance(GetDistanceToSource());
	for (i = 0; i < AudioChannelSetSize; i++)
	{
		channel = channelSet.GetNth(i);
		patch_resource->SetupPatch(channel);
	}

	//
	// Set the channels to default control values
	//
	MIDIValue midi_reverb_level =
		audio_head->GetGlobalReverbScale() *
		(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;

	for (i = 0; i < AudioChannelSetSize; i++)
	{
		channel = channelSet.GetNth(i);
		Check(channel);
		channel->SendController(MIDI_REVERB_CONTROL, midi_reverb_level);
		channel->SendController(MIDI_CHORUS_CONTROL, MIDI_MIN_CONTROL_VALUE);
	}

	//
	// Set the channels to correct pan
	//
	channel = channelSet.GetFrontLeft();
	Check(channel);
	channel->SendController(MIDI_PAN_CONTROL, MIDI_LEFT_PAN_VALUE);

	channel = channelSet.GetFrontRight();
	Check(channel);
	channel->SendController(MIDI_PAN_CONTROL, MIDI_RIGHT_PAN_VALUE);

	channel = channelSet.GetRearLeft();
	Check(channel);
	channel->SendController(MIDI_PAN_CONTROL, MIDI_LEFT_PAN_VALUE);

	channel = channelSet.GetRearRight();
	Check(channel);
	channel->SendController(MIDI_PAN_CONTROL, MIDI_RIGHT_PAN_VALUE);

	//
	// Set midi history to default control values
	//
	lastFrontLeftMIDIVolume = MIDI_MIN_CONTROL_VALUE;
	lastFrontRightMIDIVolume = MIDI_MIN_CONTROL_VALUE;
	lastRearLeftMIDIVolume = MIDI_MIN_CONTROL_VALUE;
	lastRearRightMIDIVolume = MIDI_MIN_CONTROL_VALUE;
	lastFrontLeftMIDIPitchBend = 0; // Set by force update
	lastFrontRightMIDIPitchBend = 0; // Set by force update
	lastRearLeftMIDIPitchBend = 0; // Set by force update
	lastRearRightMIDIPitchBend = 0; // Set by force update
	lastMIDIFilterCutoff = MIDI_MAX_CONTROL_VALUE; // Set by patch

	//
	//--------------------------------------------------------------------------
	// Calculate attack time, velocity, and execute model
	//--------------------------------------------------------------------------
	//
	MIDIValue midi_velocity =
		CalculateSourceAttackVolumeScale() *
		(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;

	if (UseSourceAttackTime())
	{
		MIDINRPNValue midi_attack_time =
			CalculateSourceAttackTime() *
			(Scalar)AWE_VOL_ATTACK_TIME_RANGE + 0.5f;

		for (i = 0; i < AudioChannelSetSize; i++)
		{
			channel = channelSet.GetNth(i);
			Check(channel);
			channel->SendNRPN(AWE_VOL_ATTACK_TIME_NRPN, midi_attack_time);
		}
	}

	ExecuteModel(True);

	//
	//--------------------------------------------------------------------------
	// Start note
	//--------------------------------------------------------------------------
	//
	for (i = 0; i < AudioChannelSetSize; i++)
	{
		channel = channelSet.GetNth(i);
		Check(channel);
		channel->SendNoteOn(GetCurrentNoteValue(), midi_velocity);
	}
}

//
//#############################################################################
//#############################################################################
//
void
	Dynamic3DPatchSource::StopImplementation()
{
	Check(this);

	//
	// Stop note
	//
	AudioChannel *channel;
	for (int i = 0; i < AudioChannelSetSize; i++)
	{
		channel = channelSet.GetNth(i);
		Check(channel);
		channel->SendNoteOff(GetCurrentNoteValue(), MIDI_MIN_CONTROL_VALUE);
	}
}

//
//#############################################################################
//#############################################################################
//
void
	Dynamic3DPatchSource::ExecuteModel(Logical force_update)
{
	Check(this);

	//
	//--------------------------------------------------------------------------
	// Call inherited method
	//--------------------------------------------------------------------------
	//
	L4AudioSource::Execute();

	//
	//--------------------------------------------------------------------------
	// Get audio resource
	//--------------------------------------------------------------------------
	//
	PatchResource *patch_resource;

	patch_resource = Cast_Object(PatchResource*, GetAudioResource());
	Check(patch_resource);

	//
	//--------------------------------------------------------------------------
	// Get audio location
	//--------------------------------------------------------------------------
	//
	L4AudioLocation *audio_location;

	audio_location = Cast_Object(L4AudioLocation*, GetAudioLocation());
	Check(audio_location);

	//
	//--------------------------------------------------------------------------
	// Render spatial model
	//--------------------------------------------------------------------------
	//
	AudioChannel *front_left_channel = channelSet.GetFrontLeft();
	AudioChannel *front_right_channel = channelSet.GetFrontRight();
	AudioChannel *rear_left_channel = channelSet.GetRearLeft();
	AudioChannel *rear_right_channel = channelSet.GetRearRight();

	Check(front_left_channel);
	Check(front_right_channel);
	Check(rear_left_channel);
	Check(rear_right_channel);

	//
	// Apply distance attenuation to volume scale
	//
	Scalar volume_scale = CalculateSourceVolumeScale();

	//
	// Apply speaker scaling to volume and calculate MIDI volume
	//
	Scalar volume_temp;
	MIDIValue front_left_midi_volume;
	MIDIValue front_right_midi_volume;
	MIDIValue rear_left_midi_volume;
	MIDIValue rear_right_midi_volume;

	volume_temp = volume_scale * (Scalar)MIDI_MAX_CONTROL_VALUE;

	front_left_midi_volume =
		audio_location->GetFrontLeftScale() *
		volume_temp + 0.5f;

	front_right_midi_volume =
		audio_location->GetFrontRightScale() *
		volume_temp + 0.5f;

	rear_left_midi_volume =
		audio_location->GetRearLeftScale() *
		volume_temp + 0.5f;

	rear_right_midi_volume =
		audio_location->GetRearRightScale() *
		volume_temp + 0.5f;

	//
	// Apply Doppler offset to pitch
	//
	Scalar pitch_offset;

	pitch_offset = CalculateSourcePitchOffset();
	pitch_offset += audio_location->GetDopplerCents();

	//
	// Apply ITD pitch offset and calculate MIDI pitch bend
	//
	MIDIPitchBend front_left_midi_pitch_bend;
	MIDIPitchBend front_right_midi_pitch_bend;
	MIDIPitchBend rear_left_midi_pitch_bend;
	MIDIPitchBend rear_right_midi_pitch_bend;

	front_left_midi_pitch_bend =
		(pitch_offset + audio_location->GetFrontLeftITDPitchOffset()) *
		MIDI_PITCH_PER_CENTS + 0.5f;

	front_right_midi_pitch_bend =
		(pitch_offset + audio_location->GetFrontRightITDPitchOffset()) *
		MIDI_PITCH_PER_CENTS + 0.5f;

	rear_left_midi_pitch_bend =
		(pitch_offset + audio_location->GetRearLeftITDPitchOffset()) *
		MIDI_PITCH_PER_CENTS + 0.5f;

	rear_right_midi_pitch_bend =
		(pitch_offset + audio_location->GetRearRightITDPitchOffset()) *
		MIDI_PITCH_PER_CENTS + 0.5f;

	Clamp(front_left_midi_pitch_bend, -MIDI_PITCH_BEND_MAX, MIDI_PITCH_BEND_MAX);
	Clamp(front_right_midi_pitch_bend, -MIDI_PITCH_BEND_MAX, MIDI_PITCH_BEND_MAX);
	Clamp(rear_left_midi_pitch_bend, -MIDI_PITCH_BEND_MAX, MIDI_PITCH_BEND_MAX);
	Clamp(rear_right_midi_pitch_bend, -MIDI_PITCH_BEND_MAX, MIDI_PITCH_BEND_MAX);

	//
	// Apply high frequency scale to cutoff and calculate NRPN cutoff
	//
	Scalar filter_scale;
	MIDINRPNValue max_midi_filter_cutoff;
	MIDINRPNValue midi_filter_cutoff;

	filter_scale = CalculateSourceBrightnessScale();
	filter_scale *= audio_location->GetHighFreqCutoffScale();

	max_midi_filter_cutoff = patch_resource->GetMaxMIDIFilterCutoff();
	midi_filter_cutoff = filter_scale * (Scalar)max_midi_filter_cutoff + 0.5f;

	//
	//--------------------------------------------------------------------------
	// Send pitch bend
	//--------------------------------------------------------------------------
	//
	const MIDIPitchBend pitch_resolution = 2;
	MIDIPitchBend pitch_difference;

	pitch_difference = front_left_midi_pitch_bend - lastFrontLeftMIDIPitchBend;
	if (Abs(pitch_difference) >= pitch_resolution || force_update)
	{
		lastFrontLeftMIDIPitchBend = front_left_midi_pitch_bend;
		front_left_channel->SendPitchBend(front_left_midi_pitch_bend);
	}

	pitch_difference = front_right_midi_pitch_bend - lastFrontRightMIDIPitchBend;
	if (Abs(pitch_difference) >= pitch_resolution || force_update)
	{
		lastFrontRightMIDIPitchBend = front_right_midi_pitch_bend;
		front_right_channel->SendPitchBend(front_right_midi_pitch_bend);
	}

	pitch_difference = rear_left_midi_pitch_bend - lastRearLeftMIDIPitchBend;
	if (Abs(pitch_difference) >= pitch_resolution || force_update)
	{
		lastRearLeftMIDIPitchBend = rear_left_midi_pitch_bend;
		rear_left_channel->SendPitchBend(rear_left_midi_pitch_bend);
	}

	pitch_difference = rear_right_midi_pitch_bend - lastRearRightMIDIPitchBend;
	if (Abs(pitch_difference) >= pitch_resolution || force_update)
	{
		lastRearRightMIDIPitchBend = rear_right_midi_pitch_bend;
		rear_right_channel->SendPitchBend(rear_right_midi_pitch_bend);
	}

	//
	//--------------------------------------------------------------------------
	// Send MIDI volume
	//--------------------------------------------------------------------------
	//
	Logical front_left_turned_on = False;
	Logical front_right_turned_on = False;
	Logical rear_left_turned_on = False;
	Logical rear_right_turned_on = False;

	if (front_left_midi_volume != lastFrontLeftMIDIVolume)
	{
		if (lastFrontLeftMIDIVolume == 0)
			front_left_turned_on = True;

		lastFrontLeftMIDIVolume = front_left_midi_volume;
		Check(front_left_channel);
		front_left_channel->SendController(
			MIDI_VOLUME_CONTROL,
			front_left_midi_volume
		);
	}

	if (front_right_midi_volume != lastFrontRightMIDIVolume)
	{
		if (lastFrontRightMIDIVolume == 0)
			front_right_turned_on = True;

		lastFrontRightMIDIVolume = front_right_midi_volume;
		Check(front_right_channel);
		front_right_channel->SendController(
			MIDI_VOLUME_CONTROL,
			front_right_midi_volume
		);
	}

	if (rear_left_midi_volume != lastRearLeftMIDIVolume)
	{
		if (lastRearLeftMIDIVolume == 0)
			rear_left_turned_on = True;

		lastRearLeftMIDIVolume = rear_left_midi_volume;
		Check(rear_left_channel);
		rear_left_channel->SendController(
			MIDI_VOLUME_CONTROL,
			rear_left_midi_volume
		);
	}

	if (rear_right_midi_volume != lastRearRightMIDIVolume)
	{
		if (lastRearRightMIDIVolume == 0)
			rear_right_turned_on = True;

		lastRearRightMIDIVolume = rear_right_midi_volume;
		Check(rear_right_channel);
		rear_right_channel->SendController(
			MIDI_VOLUME_CONTROL,
			rear_right_midi_volume
		);
	}

	//
	//--------------------------------------------------------------------------
	// Send NRPN filter cutoff
	//--------------------------------------------------------------------------
	//
	const MIDIValue filter_resolution = 4;
	MIDINRPNValue filter_difference;
	Logical filter_changed;

	filter_difference = midi_filter_cutoff - lastMIDIFilterCutoff;
	filter_changed = (Abs(filter_difference) >= filter_resolution);

	if (
		front_left_turned_on ||
		(filter_changed && front_left_midi_volume > 0)
	)
	{
		lastMIDIFilterCutoff = midi_filter_cutoff;
		Check(front_left_channel);
		front_left_channel->SendNRPN(
			AWE_FILTER_CUTOFF_NRPN,
			midi_filter_cutoff
		);
	}
	if (
		front_right_turned_on ||
		(filter_changed && front_right_midi_volume > 0)
	)
	{
		lastMIDIFilterCutoff = midi_filter_cutoff;
		Check(front_right_channel);
		front_right_channel->SendNRPN(
			AWE_FILTER_CUTOFF_NRPN,
			midi_filter_cutoff
		);
	}
	if (
		rear_left_turned_on ||
		(filter_changed && rear_left_midi_volume > 0)
	)
	{
		lastMIDIFilterCutoff = midi_filter_cutoff;
		Check(rear_left_channel);
		rear_left_channel->SendNRPN(
			AWE_FILTER_CUTOFF_NRPN,
			midi_filter_cutoff
		);
	}
	if (
		rear_right_turned_on ||
		(filter_changed && rear_right_midi_volume > 0)
	)
	{
		lastMIDIFilterCutoff = midi_filter_cutoff;
		Check(rear_right_channel);
		rear_right_channel->SendNRPN(
			AWE_FILTER_CUTOFF_NRPN,
			midi_filter_cutoff
		);
	}
}

//
//#############################################################################
//#############################################################################
//
AudioControlValue
	Dynamic3DPatchSource::CalculateSourceVolumeScale()
{
	Check(this);
	
	//
	// Call inherited method to calculate volume scale
	//
	Scalar 
		volume_scale = L4AudioSource::CalculateSourceVolumeScale();
	
	//
	// Update the spatial model that will result in the value
	// for distance related volume attenuation
	//
	Check(application);
	Check(application->GetAudioRenderer());
	UpdateSpatialModel(application->GetAudioRenderer()->GetAudioHead());

	//
	// Apply distance attenuation to the volume scale
	//	
	Check(GetAudioLocation());
	volume_scale *= GetAudioLocation()->GetDistanceVolumeScale();
	return volume_scale;
}

//#############################################################################
//#######################    Static3DPatchSource    ###########################
//#############################################################################

//
//#############################################################################
//#############################################################################
//
Static3DPatchSource::Static3DPatchSource(
	PlugStream *stream,
	Entity *entity
):
	L4AudioSource(stream, entity),
	position(0.0f, 0.0f, 0.0f)
{
	MemoryStream_Read(stream, &useInternalSpatialization);
}

//
//#############################################################################
//#############################################################################
//
Static3DPatchSource::~Static3DPatchSource()
{
}

//
//#############################################################################
//#############################################################################
//
void
	Static3DPatchSource::BuildFromPage(
		PlugStream *stream,
		NameList *name_list,
		ClassID class_ID,
		ObjectID object_ID
	)
{
	L4AudioSource::BuildFromPage(stream, name_list, class_ID, object_ID);

	if (name_list->FindData("use_internal") != NULL)
	{
		MEM_STRM_WRITE_ENTRY(*stream, name_list, Logical, use_internal);
	}
	else
	{
		Logical use_internal = True;
		MemoryStream_Write(stream, &use_internal);
	}
}

//
//#############################################################################
//#############################################################################
//
Logical
	Static3DPatchSource::TestInstance() const
{
	L4AudioSource::TestInstance();
	if (useInternalSpatialization)
	{
		Check(&internalSpatialization);
	}
	return True;
}

//
//#############################################################################
//#############################################################################
//
void
	Static3DPatchSource::SetPosition(const Vector3D &vector3D)
{
	position = vector3D;
}

//
//#############################################################################
//#############################################################################
//
void
	Static3DPatchSource::StartMessageImplementation()
{
	Check(this);

	L4AudioSource::StartMessageImplementation();

	//
	//--------------------------------------------------------------------------
	// Get audio renderer
	//--------------------------------------------------------------------------
	//
	L4AudioRenderer *audio_renderer;

	Check(application);
	audio_renderer =
		Cast_Object(L4AudioRenderer*, application->GetAudioRenderer());
	Check(audio_renderer);

	//
	//--------------------------------------------------------------------------
	// Update spatial parameters
	//--------------------------------------------------------------------------
	//
	if (useInternalSpatialization)
	{
		//
		//-----------------------------------------------------------------------
		// Calculate azimuth of position
		//-----------------------------------------------------------------------
		//
		Radian azimuth_of_source;

		if (Small_Enough(position.x) && Small_Enough(position.z))
		{
			azimuth_of_source = 0.0f;
		}
		else
		{
			Verify(!(Small_Enough(position.x) && Small_Enough(position.z)));
			azimuth_of_source = Arctan(position.x, position.z);
		}
		Verify(azimuth_of_source <= PI && azimuth_of_source >= -PI);

		internalSpatialization.CalculateSpatialization(azimuth_of_source);
	}
	else
	{
		UpdateSpatialModel(audio_renderer->GetAudioHead());
		Cast_Object(L4AudioLocation*, GetAudioLocation())->GetSpatialization(
			&internalSpatialization
		);
	}

	//
	//--------------------------------------------------------------------------
	// Init channel set to required channels
	//--------------------------------------------------------------------------
	//
	channelSet.EnableFrontLeft(False);
	channelSet.EnableFrontRight(False);
	channelSet.EnableRearLeft(False);
	channelSet.EnableRearRight(False);

	switch (GetQuadrant())
	{
	case L4AudioSpatialization::Quadrant1:
		channelSet.EnableFrontLeft(True);
		break;

	case L4AudioSpatialization::Quadrant2:
		channelSet.EnableFrontLeft(True);
		channelSet.EnableRearLeft(True);
		break;

	case L4AudioSpatialization::Quadrant3:
		channelSet.EnableRearLeft(True);
		break;

	case L4AudioSpatialization::Quadrant4:
		channelSet.EnableFrontRight(True);
		channelSet.EnableRearRight(True);
		break;
	}

	//
	//--------------------------------------------------------------------------
	// Request start from renderer
	//--------------------------------------------------------------------------
	//
	audio_renderer->StartRequest(this);
}

//
//#############################################################################
//#############################################################################
//
void
	Static3DPatchSource::StartImplementation()
{
	Check(this);

	L4AudioSource::StartImplementation();

	//
	//--------------------------------------------------------------------------
	// Get audio head constants
	//--------------------------------------------------------------------------
	//
	L4AudioRenderer *audio_renderer;
	AudioHead *audio_head;

	Check(application);
	audio_renderer =
		Cast_Object(L4AudioRenderer*, application->GetAudioRenderer());
	Check(audio_renderer);
	audio_head = audio_renderer->GetAudioHead();
	Check(audio_head);

	//
	//--------------------------------------------------------------------------
	// Setup the channel
	//--------------------------------------------------------------------------
	//
	ExecuteWatchers();

	//
	// Turn the volume down while setting up the channels
	//
	int i;
	AudioChannel *channel;

	for (i = 0; i < AudioChannelSetSize; i++)
	{
		if ((channel = channelSet.GetNth(i)) != NULL)
		{
			Check(channel);
			channel->SendController(MIDI_VOLUME_CONTROL, MIDI_MIN_CONTROL_VALUE);
		}
	}

	//
	// Set the channels to the bank and program
	//
	PatchResource *patch_resource;

	patch_resource = Cast_Object(PatchResource*, GetAudioResource());
	Check(patch_resource);
	patch_resource->SetDistance(GetDistanceToSource());
	for (i = 0; i < AudioChannelSetSize; i++)
	{
		if ((channel = channelSet.GetNth(i)) != NULL)
		{
			patch_resource->SetupPatch(channel);
		}
	}

	//
	// Set the channels to default control values
	//
	MIDIValue midi_reverb_level =
		audio_head->GetGlobalReverbScale() *
		(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;

	for (i = 0; i < AudioChannelSetSize; i++)
	{
		if ((channel = channelSet.GetNth(i)) != NULL)
		{
			Check(channel);
			channel->SendController(MIDI_REVERB_CONTROL, midi_reverb_level);
			channel->SendController(MIDI_CHORUS_CONTROL, MIDI_MIN_CONTROL_VALUE);
		}
	}

	//
	// Set the channels to correct pan
	//
	switch (GetQuadrant())
	{
	case L4AudioSpatialization::Quadrant1:
		{
			const Radian azimuth_max =
				45.0*RAD_PER_DEG; // HACK - should come from head
			const Scalar slope =
				-(Scalar)MIDI_MAX_CONTROL_VALUE / (2.0f * azimuth_max);
			const Scalar intercept =
				(MIDI_MAX_CONTROL_VALUE * 0.5f);
			MIDIValue
				midi_pan;

			midi_pan = slope * GetAzimuthOfSource() + intercept;
			Check(channelSet.GetFrontLeft());
			channelSet.GetFrontLeft()->SendController(
				MIDI_PAN_CONTROL,
				midi_pan
			);
		}
		break;

	case L4AudioSpatialization::Quadrant2:
		{
			Check(channelSet.GetFrontLeft());
			Check(channelSet.GetRearLeft());
			channelSet.GetFrontLeft()->SendController(
				MIDI_PAN_CONTROL,
				MIDI_MIN_CONTROL_VALUE
			);
			channelSet.GetRearLeft()->SendController(
				MIDI_PAN_CONTROL,
				MIDI_MIN_CONTROL_VALUE
			);
		}
		break;

	case L4AudioSpatialization::Quadrant3:
		{
			const Radian azimuth_max =
				45.0*RAD_PER_DEG; // HACK - should come from head
			const Scalar slope =
				(Scalar)MIDI_MAX_CONTROL_VALUE / (2.0f * azimuth_max);
			const Scalar intercept =
				(MIDI_MAX_CONTROL_VALUE * 0.5f);
			MIDIValue
				midi_pan;

			midi_pan = slope * GetAzimuthOfSource() + intercept;
			Check(channelSet.GetRearLeft());
			channelSet.GetRearLeft()->SendController(
				MIDI_PAN_CONTROL,
				midi_pan
			);
		}
		break;

	case L4AudioSpatialization::Quadrant4:
		{
			Check(channelSet.GetFrontRight());
			Check(channelSet.GetRearRight());
			channelSet.GetFrontRight()->SendController(
				MIDI_PAN_CONTROL,
				MIDI_MAX_CONTROL_VALUE
			);
			channelSet.GetRearRight()->SendController(
				MIDI_PAN_CONTROL,
				MIDI_MAX_CONTROL_VALUE
			);
		}
		break;
	}

	//
	// Set midi history to default control values
	//
	lastFrontLeftMIDIVolume = MIDI_MIN_CONTROL_VALUE;
	lastFrontRightMIDIVolume = MIDI_MIN_CONTROL_VALUE;
	lastRearLeftMIDIVolume = MIDI_MIN_CONTROL_VALUE;
	lastRearRightMIDIVolume = MIDI_MIN_CONTROL_VALUE;
	lastMIDIPitchBend = 0;  // Set by force update
	lastMIDIFilterCutoff = MIDI_MAX_CONTROL_VALUE; // Set by patch

	//
	//--------------------------------------------------------------------------
	// Calculate attack time, velocity, and execute model
	//--------------------------------------------------------------------------
	//
	MIDIValue midi_velocity =
		CalculateSourceAttackVolumeScale() *
		(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;

	if (UseSourceAttackTime())
	{
		MIDINRPNValue midi_attack_time =
			CalculateSourceAttackTime() *
			(Scalar)AWE_VOL_ATTACK_TIME_RANGE + 0.5f;

		for (i = 0; i < AudioChannelSetSize; i++)
		{
			if ((channel = channelSet.GetNth(i)) != NULL)
			{
				Check(channel);
				channel->SendNRPN(AWE_VOL_ATTACK_TIME_NRPN, midi_attack_time);
			}
		}
	}

	ExecuteModel(True);

	//
	//--------------------------------------------------------------------------
	// Start note
	//--------------------------------------------------------------------------
	//
	for (i = 0; i < AudioChannelSetSize; i++)
	{
		if ((channel = channelSet.GetNth(i)) != NULL)
		{
			Check(channel);
			channel->SendNoteOn(GetCurrentNoteValue(), midi_velocity);
		}
	}
}

//
//#############################################################################
//#############################################################################
//
void
	Static3DPatchSource::StopImplementation()
{
	Check(this);

	//
	//------------------------------------------------------------
	// Stop note
	//------------------------------------------------------------
	//
	AudioChannel *channel;

	for (int i = 0; i < AudioChannelSetSize; i++)
	{
		if ((channel = channelSet.GetNth(i)) != NULL)
		{
			Check(channel);
			channel->SendNoteOff(GetCurrentNoteValue(), MIDI_MIN_CONTROL_VALUE);
		}
	}
}

//
//#############################################################################
//#############################################################################
//
void
	Static3DPatchSource::ExecuteModel(Logical force_update)
{
	Check(this);

	//
	//--------------------------------------------------------------------------
	// Get audio resource
	//--------------------------------------------------------------------------
	//
	PatchResource *patch_resource;

	patch_resource = Cast_Object(PatchResource*, GetAudioResource());
	Check(patch_resource);

	//
	//--------------------------------------------------------------------------
	// Render spatial model
	//--------------------------------------------------------------------------
	//
	AudioChannel *front_left_channel = channelSet.GetFrontLeft();
	AudioChannel *front_right_channel = channelSet.GetFrontRight();
	AudioChannel *rear_left_channel = channelSet.GetRearLeft();
	AudioChannel *rear_right_channel = channelSet.GetRearRight();

	//
	// Apply volume scale offset
	//
	Scalar volume_scale = CalculateSourceVolumeScale();

	//
	// Apply pitch offset and calculate MIDI pitch bend
	//
	Scalar pitch_offset;
	MIDIPitchBend midi_pitch_bend;

	pitch_offset = CalculateSourcePitchOffset();
	midi_pitch_bend = pitch_offset * MIDI_PITCH_PER_CENTS + 0.5f;
	Clamp(midi_pitch_bend, -MIDI_PITCH_BEND_MAX, MIDI_PITCH_BEND_MAX);

	//
	// Apply high frequency scale and calculate NRPN filter cutoff
	//
	Scalar filter_scale;
	MIDINRPNValue max_midi_filter_cutoff;
	MIDINRPNValue midi_filter_cutoff;

	filter_scale = CalculateSourceBrightnessScale();
	max_midi_filter_cutoff = patch_resource->GetMaxMIDIFilterCutoff();
	midi_filter_cutoff = filter_scale * (Scalar)max_midi_filter_cutoff + 0.5f;

	//
	//--------------------------------------------------------------------------
	// Send pitch bend
	//--------------------------------------------------------------------------
	//
	const MIDIPitchBend pitch_resolution = 2;
	MIDIPitchBend pitch_difference = midi_pitch_bend - lastMIDIPitchBend;

	if (Abs(pitch_difference) >= pitch_resolution || force_update)
	{
		lastMIDIPitchBend = midi_pitch_bend;

		switch (GetQuadrant())
		{
		case L4AudioSpatialization::Quadrant1:
			Check(front_left_channel);
			front_left_channel->SendPitchBend(midi_pitch_bend);
			break;

		case L4AudioSpatialization::Quadrant2:
			Check(front_left_channel);
			front_left_channel->SendPitchBend(midi_pitch_bend);
			Check(rear_left_channel);
			rear_left_channel->SendPitchBend(midi_pitch_bend);
			break;

		case L4AudioSpatialization::Quadrant3:
			Check(rear_left_channel);
			rear_left_channel->SendPitchBend(midi_pitch_bend);
			break;

		case L4AudioSpatialization::Quadrant4:
			Check(front_right_channel);
			front_right_channel->SendPitchBend(midi_pitch_bend);
			Check(rear_right_channel);
			rear_right_channel->SendPitchBend(midi_pitch_bend);
			break;
		}
	}

	//
	//--------------------------------------------------------------------------
	// Send NRPN filter cutoff
	//--------------------------------------------------------------------------
	//
	if (UseSourceBrightnessScale())
	{
		const MIDIValue filter_resolution = 2;
		MIDINRPNValue filter_difference;

		filter_difference = midi_filter_cutoff - lastMIDIFilterCutoff;

		if (Abs(filter_difference) >= filter_resolution)
		{
			lastMIDIFilterCutoff = midi_filter_cutoff;

			switch (GetQuadrant())
			{
			case L4AudioSpatialization::Quadrant1:
				Check(front_left_channel);
				front_left_channel->SendNRPN(
					AWE_FILTER_CUTOFF_NRPN,
					midi_filter_cutoff
				);
				break;

			case L4AudioSpatialization::Quadrant2:
				Check(front_left_channel);
				front_left_channel->SendNRPN(
					AWE_FILTER_CUTOFF_NRPN,
					midi_filter_cutoff
				);
				Check(rear_left_channel);
				rear_left_channel->SendNRPN(
					AWE_FILTER_CUTOFF_NRPN,
					midi_filter_cutoff
				);
				break;

			case L4AudioSpatialization::Quadrant3:
				Check(rear_left_channel);
				rear_left_channel->SendNRPN(
					AWE_FILTER_CUTOFF_NRPN,
					midi_filter_cutoff
				);
				break;

			case L4AudioSpatialization::Quadrant4:
				Check(front_right_channel);
				front_right_channel->SendNRPN(
					AWE_FILTER_CUTOFF_NRPN,
					midi_filter_cutoff
				);
				Check(rear_right_channel);
				rear_right_channel->SendNRPN(
					AWE_FILTER_CUTOFF_NRPN,
					midi_filter_cutoff
				);
				break;
			}
		}
	}

	//
	//--------------------------------------------------------------------------
	// Send MIDI volume
	//--------------------------------------------------------------------------
	//
	switch (GetQuadrant())
	{
	case L4AudioSpatialization::Quadrant1:
		{
			MIDIValue front_left_midi_volume =
				volume_scale *
				(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;

			if (front_left_midi_volume != lastFrontLeftMIDIVolume)
			{
				lastFrontLeftMIDIVolume = front_left_midi_volume;
				Check(front_left_channel);
				front_left_channel->SendController(
					MIDI_VOLUME_CONTROL,
					front_left_midi_volume
				);
			}
		}
		break;

	case L4AudioSpatialization::Quadrant2:
		{
			MIDIValue front_left_midi_volume =
				volume_scale * GetFrontLeftScale() *
				(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;

			MIDIValue rear_left_midi_volume =
				volume_scale * GetRearLeftScale() *
				(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;

			if (front_left_midi_volume != lastFrontLeftMIDIVolume)
			{
				lastFrontLeftMIDIVolume = front_left_midi_volume;
				Check(front_left_channel);
				front_left_channel->SendController(
					MIDI_VOLUME_CONTROL,
					front_left_midi_volume
				);
			}
			if (rear_left_midi_volume != lastRearLeftMIDIVolume)
			{
				lastRearLeftMIDIVolume = rear_left_midi_volume;
				Check(rear_left_channel);
				rear_left_channel->SendController(
					MIDI_VOLUME_CONTROL,
					rear_left_midi_volume
				);
			}
		}
		break;

	case L4AudioSpatialization::Quadrant3:
		{
			MIDIValue rear_left_midi_volume =
				volume_scale *
				(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;

			if (rear_left_midi_volume != lastRearLeftMIDIVolume)
			{
				lastRearLeftMIDIVolume = rear_left_midi_volume;
				Check(rear_left_channel);
				rear_left_channel->SendController(
					MIDI_VOLUME_CONTROL,
					rear_left_midi_volume
				);
			}
		}
		break;

	case L4AudioSpatialization::Quadrant4:
		{
			MIDIValue front_right_midi_volume =
				volume_scale * GetFrontRightScale() *
				(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;

			MIDIValue rear_right_midi_volume =
				volume_scale * GetRearRightScale() *
				(Scalar)MIDI_MAX_CONTROL_VALUE + 0.5f;

			if (front_right_midi_volume != lastFrontRightMIDIVolume)
			{
				lastFrontRightMIDIVolume = front_right_midi_volume;
				Check(front_right_channel);
				front_right_channel->SendController(
					MIDI_VOLUME_CONTROL,
					front_right_midi_volume
				);
			}
			if (rear_right_midi_volume != lastRearRightMIDIVolume)
			{
				lastRearRightMIDIVolume = rear_right_midi_volume;
				Check(rear_right_channel);
				rear_right_channel->SendController(
					MIDI_VOLUME_CONTROL,
					rear_right_midi_volume
				);
			}
		}
		break;
	}
}


