
///////////////////////////////////////////////////////////////////////////////
//
// File   : Tools_FacialAnimation_LipsyncConversion_Annosoft.cpp
// Author : Andy Tate
// Date   : 5-Aug-2008
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Include files...
////////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

#if defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)

// This file's header...
#include "FacialAnimation/LipsyncConversion/Tools_FacialAnimation_LipsyncConversion_Annosoft.h"

// Asura independent header-files...
#include "Debugging/Asura_Logfile.h"
#include "FileAccess/Asura_FileAccess.h"

// Middleware header-files...
#include "Annosoft/lib/liblipsync_license.h"

// Tools header-files...
#include "FacialAnimation/Tools_FacialAnimation_FaceAnimation.h"
#include "PC/FileSystem/Tools_FileSystem.h"


////////////////////////////////////////////////////////////////////////////////
// Constant declarations...
////////////////////////////////////////////////////////////////////////////////

#if 1
#define LOG_OUTPUT(s)
#else
#define LOG_OUTPUT(s)	Asura_OutputToDebugger_Retail(s)
#endif


////////////////////////////////////////////////////////////////////////////////
// Static variable declarations...
////////////////////////////////////////////////////////////////////////////////

char const* const Tools_FacialAnimation_LipsyncConversion_Annosoft::s_aszPhonemes[][2] =
{
	"x",	"<silence>"
	"IY",	"eat = IY + t"
	"IH",	"it = IH + t"
	"EH",	"Ed  = EH + d"
	"AE",	"at = AE + t"
	"AH",	"hut = h + AH + t"
	"UW",	"two = t + UW"
	"UH",	"hood = h + UH + d"
	"AA",	"odd = AA + d"
	"AO",	"ought = AO + t"
	"EY",	"ate = EY + t"
	"AY",	"hide = h + AY + d"
	"OY",	"toy = t + OY"
	"AW",	"cow = k + AW"
	"OW",	"oat = OW + t"
	"l",	"lee = l + IY"
	"r",	"read = r + IY + d"
	"y",	"yield = y + IY + l + d"
	"w",	"we + w + IY"
	"ER",	"hurt = h + ER + t"
	"m",	"me = m + IY"
	"n",	"knee = n + IY"
	"NG",	"ping = p + IH + NG"
	"CH",	"cheese = CH + IY + z"
	"j",	"gee = j + IY"
	"DH",	"thee = DH + IY"
	"b",	"be = b + IY"
	"d",	"dee = d + IY"
	"g",	"green = g + r + IY + n"
	"p",	"pee = p + IY"
	"t",	"tea = t + IY"
	"k",	"key = k + IY"
	"z",	"zee = z + IY"
	"ZH",	"seizure = s + IY + ZH + ER"
	"v",	"vee = v + IY"
	"f",	"fee = f + IY"
	"TH",	"theta = TH + EY + t + AH"
	"s",	"sea = s + IY"
	"SH",	"she = SH + IY"
	"h",	"he = h + IY"
};

const u_int	Tools_FacialAnimation_LipsyncConversion_Annosoft::s_uNumPhonemes	= sizeof(s_aszPhonemes) / sizeof(s_aszPhonemes[0]);


////////////////////////////////////////////////////////////////////////////////
// Private functions...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Protected functions...
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_LipsyncConversion_Annosoft::AnnosoftCommandSuccessful
// Author       : Andy Tate
// Date         : 5-Aug-2008
////////////////////////////////////////////////////////////////////////////////

bool Tools_FacialAnimation_LipsyncConversion_Annosoft::AnnosoftCommandSuccessful(const char* szOperationText,
																				 const serror xError)
{
	if(xError == kNoError)
	{
		return(true);
	}
	else
	{
		Asura_Assertf(false, ("Annosoft Error: \"%s\" - %s", GetLipsyncErrorMessage(xError), szOperationText));
		return(false);
	}
}

/// End of function 'Tools_FacialAnimation_LipsyncConversion_Annosoft::AnnosoftCommandSuccessful'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_LipsyncConversion_Annosoft::InitialiseAnimationFromMarkerList
// Author       : Andy Tate
// Date         : 6-Aug-2008
////////////////////////////////////////////////////////////////////////////////

void Tools_FacialAnimation_LipsyncConversion_Annosoft::InitialiseAnimationFromMarkerList(ISyncResultsCollection& xMarkerList,
																						 Tools_FacialAnimation_FaceAnimation& xAnimation)
{
	const CSyncMarker*	pxEndMarker	= xMarkerList.end();

	// Do Something with the results. The IPhnMixtureArticulator can also do some great things with the data...
	for(const CSyncMarker* pxCurrentMarker = xMarkerList.begin(); (pxCurrentMarker != pxEndMarker); pxCurrentMarker++)
	{
		switch(pxCurrentMarker->type)
		{
			case CSyncMarker::phoneme:
			{
				const long	lStartMS	= (pxCurrentMarker->milliStart < 0) ? 0 : pxCurrentMarker->milliStart;
				const long	lEndMS		= (pxCurrentMarker->milliEnd < 0) ? 0 : pxCurrentMarker->milliEnd;

				if(lStartMS < lEndMS)
				{
					const char*	szPhoneme	= pxCurrentMarker->szPhoneme;
					const float	fWeight		= pxCurrentMarker->intensity;

					LOG_OUTPUT(("- \"%s\" %d -> %d @ %.3f\n", szPhoneme, lStartMS, lEndMS, fWeight));
					xAnimation.AddPhoneme(szPhoneme, static_cast<u_int>(lStartMS), static_cast<u_int>(lEndMS), fWeight);
				}
				break;
			}

			default:
			{
				Asura_Assert(false, "Unexpected marker-type found when creating animation from .WAV-file.");
				break;
			}
		}		// End of switch.
	}
}

/// End of function 'Tools_FacialAnimation_LipsyncConversion_Annosoft::InitialiseAnimationFromMarkerList'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_LipsyncConversion_Annosoft::InitialiseAnimationFromArticulation
// Author       : Andy Tate
// Date         : 6-Aug-2008
////////////////////////////////////////////////////////////////////////////////

void Tools_FacialAnimation_LipsyncConversion_Annosoft::InitialiseAnimationFromArticulation(IPhnMixtureArticulator& xArticulator,
																						   Tools_FacialAnimation_FaceAnimation& xAnimation)
{
	const CPhonemeMixtureArticulation*	pxEndPhoneme	= xArticulator.end();

	// Do Something with the results. The IPhnMixtureArticulator can also do some great things with the data...
	for(const CPhonemeMixtureArticulation* pxCurrentPhoneme = xArticulator.begin(); (pxCurrentPhoneme != pxEndPhoneme); pxCurrentPhoneme++)
	{
		const long	lStartMS	= (pxCurrentPhoneme->msStart < 0) ? 0 : pxCurrentPhoneme->msStart;
		const long	lEndMS		= (pxCurrentPhoneme->msEnd < 0) ? 0 : pxCurrentPhoneme->msEnd;

		if(lStartMS < lEndMS)
		{
			const u_int										uNumConstituents		= pxCurrentPhoneme->m_nConstituents;
			const CPhonemeMixtureArticulation::t_phoneme*	pxCurrentConstituent	= pxCurrentPhoneme->m_constituents;

			LOG_OUTPUT(("- %d -> %d:", lStartMS, lEndMS));
			for(u_int u = 0; u < uNumConstituents; u++, pxCurrentConstituent++)
			{
				const char*	szPhoneme	= pxCurrentConstituent->strPhoneme;
				const float	fWeight		= pxCurrentConstituent->weight;

				LOG_OUTPUT((" (\"%s\" @ %.3f)", szPhoneme, fWeight));
				xAnimation.AddPhoneme(szPhoneme, static_cast<u_int>(lStartMS), static_cast<u_int>(lEndMS), fWeight);
			}
			LOG_OUTPUT(("\n"));
		}
	}
}

/// End of function 'Tools_FacialAnimation_LipsyncConversion_Annosoft::InitialiseAnimationFromArticulation'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_LipsyncConversion_Annosoft::InitialiseAnimationFromArticulationEnvelopes
// Author       : Andy Tate
// Date         : 6-Aug-2008
////////////////////////////////////////////////////////////////////////////////

void Tools_FacialAnimation_LipsyncConversion_Annosoft::InitialiseAnimationFromArticulationEnvelopes(IPhnMixtureArticulator& xArticulator,
																									Tools_FacialAnimation_FaceAnimation& xAnimation)
{
	const CPhonemeArticulationEnvelop*	pxEndPhoneme	= xArticulator.env_end();

	for(const CPhonemeArticulationEnvelop* pxCurrentPhoneme = xArticulator.env_begin(); pxCurrentPhoneme != pxEndPhoneme; pxCurrentPhoneme++)
	{
		const char*									szPhoneme			= pxCurrentPhoneme->strPhoneme;
		const u_int									uNumEnvelopes		= pxCurrentPhoneme->n_env;
		const CPhonemeArticulationEnvelop::t_env*	pxCurrentEnvelope	= pxCurrentPhoneme->env;

		LOG_OUTPUT(("- \"%s\":", szPhoneme));
		for(u_int u = 0; u < uNumEnvelopes; u++, pxCurrentEnvelope++)
		{
			const long	lStartMS	= (pxCurrentEnvelope->msStart < 0) ? 0 : pxCurrentEnvelope->msStart;
			const long	lEndMS		= (pxCurrentEnvelope->msEnd < 0) ? 0 : pxCurrentEnvelope->msEnd;

			if(lStartMS < lEndMS)
			{
				const float	fWeight	= pxCurrentEnvelope->weight;

				LOG_OUTPUT((" (%d -> %d @ %.3f)", lStartMS, lEndMS, fWeight));
				xAnimation.AddPhoneme(szPhoneme, static_cast<u_int>(lStartMS), static_cast<u_int>(lEndMS), fWeight);
			}
		}
		LOG_OUTPUT(("\n"));
	}
}

/// End of function 'Tools_FacialAnimation_LipsyncConversion_Annosoft::InitialiseAnimationFromArticulationEnvelopes'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_LipsyncConversion_Annosoft::CreateAnimation
// Author       : Andy Tate
// Date         : 5-Aug-2008
////////////////////////////////////////////////////////////////////////////////

Tools_FacialAnimation_FaceAnimation* Tools_FacialAnimation_LipsyncConversion_Annosoft::CreateAnimation(const char* szFaceTypeName,
																									   const char* szBaseFacePoseName,
																									   const char* szWAVFilename,
																									   ISyncResultsCollection& xMarkerList)
{
	Tools_FacialAnimation_FaceAnimation*	pxNewAnimation	= PARENT::BeginAnimationCreation(szFaceTypeName, szBaseFacePoseName, szWAVFilename);

	if(pxNewAnimation == NULL)
	{
		return(NULL);
	}
	else
	{
		IPhnMixtureArticulator*	pxArticulator	= NULL;
		serror					xError			= ::CreatePhonemeArticulator(&pxArticulator, 0);

		LOG_OUTPUT(("Creating facial-animation from \"%s\"...\n", szWAVFilename));

		if(AnnosoftCommandSuccessful("CreatePhonemeArticulator()", xError) && (pxArticulator != NULL))
		{
			// Generate our articulation data...
			xError	= pxArticulator->GenerateArticulations(&xMarkerList, NULL);

			if(AnnosoftCommandSuccessful("GenerateArticulations()", xError))
			{
#if 0
				xError	= pxArticulator->GenerateEnvelopsFromArticulations();

				if(AnnosoftCommandSuccessful("GenerateEnvelopsFromArticulations", xError))
				{
					InitialiseAnimationFromArticulationEnvelopes(*pxArticulator, *pxNewAnimation);
				}
				else
				{
					InitialiseAnimationFromArticulation(*pxArticulator, *pxNewAnimation);
				}
#else
				InitialiseAnimationFromArticulation(*pxArticulator, *pxNewAnimation);
#endif
			}
			else
			{
				InitialiseAnimationFromMarkerList(xMarkerList, *pxNewAnimation);
			}
		}
		else
		{
			InitialiseAnimationFromMarkerList(xMarkerList, *pxNewAnimation);
		}

		EndAnimationCreation(*pxNewAnimation);
		return(pxNewAnimation);
	}
}

/// End of function 'Tools_FacialAnimation_LipsyncConversion_Annosoft::CreateAnimation'. ///



////////////////////////////////////////////////////////////////////////////////
// Public functions...
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_LipsyncConversion_Annosoft::Tools_FacialAnimation_LipsyncConversion_Annosoft
// Author       : Andy Tate
// Date         : 5-Aug-2008
////////////////////////////////////////////////////////////////////////////////

Tools_FacialAnimation_LipsyncConversion_Annosoft::Tools_FacialAnimation_LipsyncConversion_Annosoft(const char* szUserName,
																								   const char* szCompanyName,
																								   const char* szLicenseKey)
: m_szHmmFilename("FacialAnimation\\Annosoft\\models\\HiQ40_20_38generic.hmm")
, m_szTrigrammerFilename("FacialAnimation\\Annosoft\\dicts\\anno.trig")
, m_pxHmm(NULL)
{
	ILibLipsyncRegistrar*	pxRegistrar;

	serror					xError	= LipsyncGetInfoMgr(&pxRegistrar);

	if(AnnosoftCommandSuccessful("LipsyncGetInfoMgr()", xError))
	{
		// Initialise Annosoft...
		pxRegistrar->SetUserName(szUserName);
		pxRegistrar->SetCompanyName(szCompanyName);
		pxRegistrar->SetLicenseKey(szLicenseKey);

		pxRegistrar->Release();
	}
}

/// End of function 'Tools_FacialAnimation_LipsyncConversion_Annosoft::Tools_FacialAnimation_LipsyncConversion_Annosoft'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_LipsyncConversion_Annosoft::~Tools_FacialAnimation_LipsyncConversion_Annosoft
// Author       : Andy Tate
// Date         : 5-Aug-2008
////////////////////////////////////////////////////////////////////////////////

Tools_FacialAnimation_LipsyncConversion_Annosoft::~Tools_FacialAnimation_LipsyncConversion_Annosoft()
{
	if(IsConversionInProgress())
	{
		EndConversion();
	}
}

/// End of function 'Tools_FacialAnimation_LipsyncConversion_Annosoft::~Tools_FacialAnimation_LipsyncConversion_Annosoft'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_LipsyncConversion_Annosoft::BeginConversion
// Author       : Andy Tate
// Date         : 5-Aug-2008
////////////////////////////////////////////////////////////////////////////////

bool Tools_FacialAnimation_LipsyncConversion_Annosoft::BeginConversion(void)
{
	const Asura_Handle	iFileHandle	= Tools_FileSystem::Open_CheckNetwork(m_szHmmFilename);

	if(iFileHandle == ASURA_INVALID_HANDLE)
	{
		Asura_Assertf(false, ("Failed to open Annosoft file \"%s\".", m_szHmmFilename));
		return(false);
	}
	else
	{
		const u_int	uFileSize	= Asura_FileAccess::GetFileSize(iFileHandle);
		char*		pcHmmData	= new char [uFileSize];

		serror		xError;

		// Read the data into memory...
		Asura_FileAccess::ReadData(iFileHandle, pcHmmData, uFileSize);
		Asura_FileAccess::Close(iFileHandle);

		xError	= ::CreateAccHMM(pcHmmData, uFileSize, "", &m_pxHmm);

		delete[](pcHmmData);

		if(AnnosoftCommandSuccessful("CreateAccHMM()", xError))
		{
			serror	xError;

			// Successfully created our acoustic, now load the "trigrammer"...
			xError	= ::LoadTrigramFileIntoHMM(m_pxHmm, m_szTrigrammerFilename, true);

			if(AnnosoftCommandSuccessful("LoadTrigramFileIntoHMM()", xError))
			{
				return(true);
			}
			else
			{
				EndConversion();
				return(false);
			}
		}
		else
		{
			return(false);
		}
	}
}

/// End of function 'Tools_FacialAnimation_LipsyncConversion_Annosoft::BeginConversion'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_LipsyncConversion_Annosoft::ConvertTextless
// Author       : Andy Tate
// Date         : 5-Aug-2008
////////////////////////////////////////////////////////////////////////////////

Tools_FacialAnimation_FaceAnimation* Tools_FacialAnimation_LipsyncConversion_Annosoft::ConvertTextless(const char* szFaceTypeName,
																									   const char* szBaseFacePoseName,
																									   const char* szWAVFilename)
{
	if(IsConversionInProgress())
	{
		IObservationStream*						pxObservationStream	= NULL;
		ITextlessPhnRecognizer*					pxPhonemeRecogniser	= NULL;
		ISyncResultsCollection*					pxMarkerList		= NULL;
		Tools_FacialAnimation_FaceAnimation*	pxAnimation			= NULL;

		serror									xError;

		// Create the Observation stream based on our audio file...
		xError	= ::CreateObservationStreamFromAudioFile(szWAVFilename, m_pxHmm, &pxObservationStream);

		if(AnnosoftCommandSuccessful("CreateObservationStreamFromAudioFile()", xError))
		{
			// Create the text-less phoneme recogniser...
			xError	= ::CreateTextlessPhnRecognizer(&pxPhonemeRecogniser);

			if(AnnosoftCommandSuccessful("CreateTextlessPhnRecognizer()", xError))
			{
				// Create the ISyncResultsCollection. The recognition results will be returned in this collection object for us to use...
				xError	= ::CreateSyncResultsCollection(ISyncResultsCollection::opt_intensity_as_morph, &pxMarkerList);

				if(AnnosoftCommandSuccessful("CreateSyncResultsCollection()", xError))
				{
					// Perform the recognition...
					xError	= pxPhonemeRecogniser->RecognizePhonemes(pxObservationStream, m_pxHmm, NULL, pxMarkerList);

					if(AnnosoftCommandSuccessful("RecognizePhonemes()", xError))
					{
						// Initialisation was successful - time to create the animation itself...
						pxAnimation	= CreateAnimation(szFaceTypeName, szBaseFacePoseName, szWAVFilename, *pxMarkerList);
					}
				}
			}
		}

		// Clean-up...
		DestroySyncResultsCollection(pxMarkerList);
		DestroyTextlessPhnRecognizer(pxPhonemeRecogniser);
		DestroyObservationStream(pxObservationStream);
		return(pxAnimation);
	}
	else
	{
		Asura_Assert(false, "We haven't begun conversion.");
		return(NULL);
	}
}

/// End of function 'Tools_FacialAnimation_LipsyncConversion_Annosoft::ConvertTextless'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_LipsyncConversion_Annosoft::EndConversion
// Author       : Andy Tate
// Date         : 5-Aug-2008
////////////////////////////////////////////////////////////////////////////////

bool Tools_FacialAnimation_LipsyncConversion_Annosoft::EndConversion(void)
{
	if(m_pxHmm == NULL)
	{
		return(false);
	}
	else
	{
		DestroyAccHMM(m_pxHmm);
		m_pxHmm	= NULL;
		return(true);
	}
}

/// End of function 'Tools_FacialAnimation_LipsyncConversion_Annosoft::EndConversion'. ///

#endif	//defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)


/// End of file. ///
