//////////////////////////////////////////////////////////////////////////////
//
// Asura_PS2_VoiceRecognition.cpp
//
// Copyright (c) 2004 Rebellion. All rights reserved.
//
// Description:
//
//     Asura interface for ScanSoft voice recognition SDK
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Input/Asura_PS2_VoiceRecognition.h"

#if ASURA_PS2_VOICERECOG_SDK == ASURA_PS2_VOICERECOG_SCANSOFT

// Asura independent headers
#include "Core/Asura_String.h"
#include "Core/Asura_UserInput.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"

// Asura PS2 headers
#include "Core/Asura_PS2_OS.h"
#include "Input/Asura_PlatformInput.h"

// Middleware headers
#include "ScanSoft/gsapi.h"

// D E B U G V A R S /////////////////////////////////////////////////////////
DEBUGVAR bool dbg_bPrintVoiceRecogResults = false;
DEBUGVAR bool dbg_bPrintVoiceRecogPartialResults = false;
DEBUGVAR bool dbg_bPrintSoundLevels = false;
DEBUGVAR int dbg_iVoiceEnergyThreshold = -1500;

// G L O B A L   V A R I A B L E S ///////////////////////////////////////////
static GSAPI_ERROR g_xScansoftError = GSAPI_ERR_NOTINIT;
static GSAPI_HENGINE g_hEngine = 0;
static u_char* g_pucLangData = 0;
static u_char* g_pucCTXData = 0;
static u_char* g_pucWRDData = 0;
//static u_char* g_pucGCDData = 0;
static const char* g_szLanguageFile = "misc\\vrecog\\enu300.lng";//eng200.lng";
static const char* g_szCTXFile = "misc\\vrecog\\enu3.ctx";//eng.ctx";
static const char* g_szWRDFile = "misc\\vrecog\\enu3.wrd";//eng.wrd";
static bool g_bReachedDetectionThreshold;

// S T A T I C   C L A S S   D A T A /////////////////////////////////////////
int Asura_PS2_VoiceRecognition::s_iTalkButtonID = ASURA_PS2_KEYID_PAD1_R3;


// C L A S S E S /////////////////////////////////////////////////////////////
class Asura_PS2_VoiceRecognition_Internal : public Asura_PS2_VoiceRecognition
{
public:
	static void cbResult(GSAPI_HENGINE hEngine, GSAPI_ACTION Action, GSAPI_RESULT* pxResult, LH_U32 u32UserData);
	static void cbNotify(GSAPI_HENGINE hEngine, GSAPI_NOTIFY_TYPE NotifyType, LH_U32 wParam, LH_U32 lParam);
};

// C L A S S   F U N C T I O N S /////////////////////////////////////////////
void Asura_PS2_VoiceRecognition::Initialise( void )
{
	Asura_Assert(g_hEngine == 0, "already initialised");

	// Load the files required by the voice recognition engine
	g_pucLangData = Asura_PS2_OS::LoadFileAligned(g_szLanguageFile, ASURA_ALIGN_64_BYTE);
	g_pucCTXData = Asura_PS2_OS::LoadFileAligned(g_szCTXFile, ASURA_ALIGN_64_BYTE);
	g_pucWRDData = Asura_PS2_OS::LoadFileAligned(g_szWRDFile, ASURA_ALIGN_64_BYTE);
	//g_pucGCDData = Asura_PS2_OS::LoadFileAligned(ASURA_PS2_VR_GCDFILE, ASURA_ALIGN_64_BYTE);
	
	Asura_OutputToDebugger_Retail(("Loading voice recognition files %s (%s), %s (%s), %s (%s)\n",
		g_szLanguageFile, g_pucLangData ? "ok" : "failed",
		g_szCTXFile, g_pucCTXData ?  "ok" : "failed",
		g_szWRDFile, g_pucWRDData ?  "ok" : "failed" ));

	Asura_Assert(g_pucLangData && g_pucCTXData && g_pucWRDData, "loading 1 or more voice recognition files failed");

	LH_U32* puLng = reinterpret_cast<LH_U32*>(g_pucLangData);
	LH_U32* puCtx = reinterpret_cast<LH_U32*>(g_pucCTXData);
	LH_U32* puWrd = reinterpret_cast<LH_U32*>(g_pucWRDData);
	LH_U32* puGcd = NULL;
	
	// initialise the API
	g_xScansoftError = gsapi_Init(Asura_PS2_VoiceRecognition_Internal::cbResult,0);
	
	// register the Notify callback
	if (GSAPI_SUCC(g_xScansoftError)) gsapi_NotifySetCallback(Asura_PS2_VoiceRecognition_Internal::cbNotify);

	// register the language, which will be used for all recogniser instances
	if (GSAPI_SUCC(g_xScansoftError)) g_xScansoftError = gsapi_LanguageLoadBuffer(puLng, 0);

	// create a recogniser; this also initialises the Logitech audio library for the USB microphone
	if (GSAPI_SUCC(g_xScansoftError)) g_xScansoftError = gsapi_EngineOpen(0, &g_hEngine);

	// set the mode to be push-to-talk. 
	// set engine mode to be Single, which requires that gsapi_EngineStart must be called before
	// a new recognition will begin.
	if (GSAPI_SUCC(g_xScansoftError)) g_xScansoftError = gsapi_EngineSetMode(g_hEngine, GSAPI_MODE_SINGLE);
	// set the intial context
	if (GSAPI_SUCC(g_xScansoftError)) g_xScansoftError = gsapi_EngineSetImmediateContext( g_hEngine, puCtx, puGcd, puWrd);
	// set auto detection of start and of trailing silence to both be off.
	if (GSAPI_SUCC(g_xScansoftError)) g_xScansoftError = gsapi_EngineSetParam(g_hEngine, GSAPI_PARAM_START_ENABLE, 0);
	if (GSAPI_SUCC(g_xScansoftError)) g_xScansoftError = gsapi_EngineSetParam(g_hEngine, GSAPI_PARAM_TS_ENABLE, 0);

	CheckForErrors();
}

void Asura_PS2_VoiceRecognition::Update( void )
{
	if (GSAPI_SUCC(g_xScansoftError)) 
	{	
		if (Asura_UserInput::DebouncedKeyPressed(s_iTalkButtonID) &&
			Project_ShouldListen())
		{
			g_bReachedDetectionThreshold = false;
			g_xScansoftError = gsapi_EngineStart(g_hEngine);
		}

		if (Asura_UserInput::DebouncedKeyNotPressed(s_iTalkButtonID))
		{
			g_xScansoftError = gsapi_EngineStop(g_hEngine);
		}

		gsapi_ExecServer(0);

		CheckForErrors();
	}
	
}

void Asura_PS2_VoiceRecognition::Reset( void )
{
	// not sure if we need to do anything here.
}

void Asura_PS2_VoiceRecognition::Shutdown( void )
{
	if (GSAPI_SUCC(g_xScansoftError)) g_xScansoftError = gsapi_EngineClose(g_hEngine);
	if (GSAPI_SUCC(g_xScansoftError)) g_xScansoftError = gsapi_Close();

	CheckForErrors();

	g_hEngine = 0;

	Asura_DeallocateAlign(g_pucLangData);
	g_pucLangData = 0;
	Asura_DeallocateAlign(g_pucCTXData);
	g_pucCTXData = 0;
	Asura_DeallocateAlign(g_pucWRDData);
	g_pucWRDData = 0;
//	Asura_DeallocateAlign(g_pucGCDData);
//	g_pucGCDData = 0;
}

void Asura_PS2_VoiceRecognition::SetTalkButton( int iKeyID )
{
	// first stop the engine, in case the old button happened to be held down
	if (GSAPI_SUCC(g_xScansoftError)) 
	{	
		// ignore errors, it might not be started.
		gsapi_EngineStop(g_hEngine);
	}
	s_iTalkButtonID = iKeyID;
}

void Asura_PS2_VoiceRecognition::SetLanguage(const char* szLanguageFile)
{
	Asura_Assert(!IsInitialised(), "can't change voice recognition language once it's initialised");
	g_szLanguageFile = szLanguageFile;
}

void Asura_PS2_VoiceRecognition::SetGrammar(const char* szCTX, const char* szWRD)
{
	Asura_Assert(!IsInitialised(), "can't change voice recognition grammar once it's initialised");
	g_szCTXFile = szCTX;
	g_szWRDFile = szWRD;
}

const char* Asura_PS2_VoiceRecognition::GetLanguageFile( void )
{
	return g_szLanguageFile;
}

bool Asura_PS2_VoiceRecognition::IsInitialised( void )
{
	return g_hEngine != 0;
}

void Asura_PS2_VoiceRecognition::CheckForErrors( void )
{
	if (GSAPI_FAIL(g_xScansoftError))
	{
		const char* szError = gsapi_ErrorGetString(g_xScansoftError);
		Asura_Assertf(false, ("%8x : %s", g_xScansoftError, szError));
	}
}



// S T A T I C   F U N C T I O N S ///////////////////////////////////////////
void Asura_PS2_VoiceRecognition_Internal::cbResult(GSAPI_HENGINE hEngine, GSAPI_ACTION Action, GSAPI_RESULT* pxResult, LH_U32 u32UserData)
{
	if (dbg_bPrintVoiceRecogResults)
	{
		static char const* const strResultType[] = {"final", "premature", "timeout", "rejected"};
		GSAPI_ERROR Error = 0;
		int i;
		char* szResult;
		
		if (pxResult->u16NbrWords > 0) 
		{
			printf("Result (type %d - %s):",
			pxResult->Type,strResultType[pxResult->Type]);
			for(i = 0; i < pxResult->u16NbrWords; i++) 
			{
				Error = gsapi_GetResultString(hEngine, pxResult->pWordIds[i], &szResult);
				if (GSAPI_SUCC(Error)) 
				{
					Asura_OutputToDebugger(("%s ", szResult));
				} 
				else 
				{
					break;
				}
			}
		}
		Asura_OutputToDebugger(("(%d)\n", pxResult->u16Conf));
	}

	if (pxResult->Type == GSAPI_RESULTTYPE_FINAL)
	{
		GSAPI_ERROR Error = 0;
		char acBuffer[256] = "";
		// NB the API returns .S for the 1st and last words for some reason.
		if (pxResult->u16NbrWords > 2) 
		{
			// string the words together into acBuffer
			for(int i = 1; i < pxResult->u16NbrWords - 1; ++i) 
			{
				char* szResult;
				Error = gsapi_GetResultString(hEngine, pxResult->pWordIds[i], &szResult);
				if (GSAPI_SUCC(Error)) 
				{
					Asura_PrintfToBfr(acBuffer, (i == 1) ? "%s%s" : "%s %s", 256, true, acBuffer, szResult);
				} 
				else 
				{
					break;
				}
			}
		}

		Project_ProcessUtterance( Action, acBuffer );
	}
		
}

void Asura_PS2_VoiceRecognition_Internal::cbNotify(GSAPI_HENGINE hEngine, GSAPI_NOTIFY_TYPE NotifyType, LH_U32 wParam, LH_U32 lParam)
{
	if (NotifyType == GSAPI_NOTIFY_ASRRESULT)
	{
		if (dbg_bPrintVoiceRecogPartialResults)
		{
			GSAPI_RESULT* pxResult = reinterpret_cast<GSAPI_RESULT*>(wParam);
			if (pxResult)
			{
				u_int uConfidenceThreshold = lParam;

				static char const* const strResultType[] = {"final", "premature", "timeout", "rejected"};
				GSAPI_ERROR Error = 0;
				int i;
				char* szResult;
				
				if (pxResult->u16NbrWords > 0) 
				{
					printf("Result (type %d - %s):",
					pxResult->Type,strResultType[pxResult->Type]);
					for(i = 0; i < pxResult->u16NbrWords; i++) 
					{
						Error = gsapi_GetResultString(hEngine, pxResult->pWordIds[i], &szResult);
						if (GSAPI_SUCC(Error)) 
						{
							Asura_OutputToDebugger(("%s ", szResult));
						} 
						else 
						{
							break;
						}
					}
				}
				Asura_OutputToDebugger(("(%u < %u)\n", pxResult->u16Conf, uConfidenceThreshold));
			}
		}

		// only call it a failed utterance if sound level reached a threshold
		if (g_bReachedDetectionThreshold)
		{
			Project_HandleFailedUtterance();
		}
	}
	else if (NotifyType == GSAPI_NOTIFY_ENERGYLEVEL)
	{
		// expecting valid between -7200 and 1800
		LH_S32 iLevel = static_cast<LH_S32>(wParam);
		if (dbg_bPrintSoundLevels)
		{
			Asura_OutputToDebugger(("sound level(%d)\n", iLevel));
		}
		if (iLevel > dbg_iVoiceEnergyThreshold)
		{
			g_bReachedDetectionThreshold = true;
		}
	}
}

#else

// Asura independent headers
#include "Core/Asura_Maths.h"
#include "Core/Asura_String.h"
#include "Core/Asura_UserInput.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"

// Asura PS2 headers
#include "Core/Asura_PS2_OS.h"
#include "Input/Asura_PlatformInput.h"

// Middleware headers
#include "Fonix/VoiceCmds.h"
#include "Logitech/liblgaud/liblgaud.h"

//////////////////////////////////////////////////////////////////////////
//
// class Asura_PS2_VoiceRecognition_Fonix
//
//////////////////////////////////////////////////////////////////////////

// this helper class is based on the sample code from the Fonix SDK (which in turn is based
// on a Logitech sample).
// (MH - 07.12.2004) TODO: put this lot in a packed ASR file...
static const char* g_szVocabFile = "misc\\vrecog\\bhdengus.vcb";
static const char* g_szNeuralNetFile = "misc\\vrecog\\bhdengus.vnn";
static const char* g_szUserDataFile = "misc\\vrecog\\bhdengus.usr";
static const char* g_szWordListFile = "misc\\vrecog\\bhdengus.txt";
static const u_int uMAX_RECOGNISED_WORDS = 16;
DEBUGVAR float dbg_fMIN_CONFIDENCE = 10.0f;

#define IN_SAMPLING_FREQUENCY 11025
#define IN_SAMPLING_BITS 16
#define IN_SAMPLING_CHANNELS 1
#define IN_SPOOLERBUF 10 * IN_SAMPLING_FREQUENCY * IN_SAMPLING_CHANNELS * (IN_SAMPLING_BITS/8)

#define RECORDLENGTH 10     // milliseconds  
#define READCHUNK 9000      // enough to hold more than a few frames of audio

#define DEFAULT_RECORD_VOLUME 100

// Voice processing: 11025Hz, mono, 16 bit samples, 1000 packets, 10ms each 
#define BYTES_PER_SAMPLE   (2)

typedef void(*Asura_Fonix_RecognisedCallback)(u_int uAction, const char* szString);
typedef void(*Asura_Fonix_FailedCallback)( void );

class Asura_PS2_VoiceRecognition_Fonix
{
	enum VoiceRecog_State
	{
		VOICE_NOT_CONNECTED,	// No communicator on this port
		VOICE_IDLE,				// Not doing any speech recognition [waiting for push-to-talk button]
		VOICE_LISTENING,		// Currently accepting wave input into the recognizer
		VOICE_FINISHING,		// Hit end-of-speech, and finishing processing on buffered data [don't send more wave samples]
		VOICE_STOPPING,			// Requested to stop listening, but still have data to process off the buffer.
		VOICE_UNAVAILABLE		// Voice recognition could not be initialized.
	};

	public:


		static bool Initialise(Asura_Fonix_RecognisedCallback pfRecognisedCallback, Asura_Fonix_FailedCallback pfFailedCallback);
		static void Update();
		static void Shutdown();

		static void StartListening();
		static void StopListening();

	private:
		
		// logitech library management
		static bool InitLogitech();
		static void ShutdownLogitech();
		static int StartRecording();
		static int StopRecording();
		
		// initialisation and shutdown routines
		static bool LoadRecognitionSupport();
		static void ParseWordList( u_int uNumBytes );
		static bool FindAndOpenHeadsetDevice();
		static void CloseDevice();
		static void InitAudioDevice(int usb_device, int mode);

		// internal update processing routines

		// static data
		static Asura_Fonix_RecognisedCallback	s_pfRecognisedCallback;
		static Asura_Fonix_FailedCallback		s_pfFailedCallback;
		
		static int				s_iUSBDevice;
		static lgAudDeviceDesc	s_xUSBDeviceDesc;

		static VoiceRecog_State	s_eState;
		static u_char*			s_pucSpooler;
		static u_char*			s_pucReadPtr;


		static FnxVoicePtr		s_pFnxVoice;       /* Main (common) recognition structure. */
		static FnxVocabPtr		s_pFnxVocab;       /* Recognition vocabulary */
		static FnxVoiceUserPtr	s_pFnxVoiceUser;
		static u_char*			s_pucWordList;		// location of plain text file.
		static char*			s_aszPhrases[uMAX_RECOGNISED_WORDS];	// array of strings

		static bool				s_bInitialisedOK;
};

Asura_Fonix_RecognisedCallback	Asura_PS2_VoiceRecognition_Fonix::s_pfRecognisedCallback;
Asura_Fonix_FailedCallback		Asura_PS2_VoiceRecognition_Fonix::s_pfFailedCallback;

int					Asura_PS2_VoiceRecognition_Fonix::s_iUSBDevice = LGAUD_INVALID_DEVICE;
lgAudDeviceDesc		Asura_PS2_VoiceRecognition_Fonix::s_xUSBDeviceDesc;

Asura_PS2_VoiceRecognition_Fonix::VoiceRecog_State	Asura_PS2_VoiceRecognition_Fonix::s_eState;
u_char*				Asura_PS2_VoiceRecognition_Fonix::s_pucSpooler;
u_char*				Asura_PS2_VoiceRecognition_Fonix::s_pucReadPtr;
					
FnxVoicePtr			Asura_PS2_VoiceRecognition_Fonix::s_pFnxVoice;       /* Main (common) recognition structure. */
FnxVocabPtr			Asura_PS2_VoiceRecognition_Fonix::s_pFnxVocab;       /* Recognition vocabulary */
FnxVoiceUserPtr		Asura_PS2_VoiceRecognition_Fonix::s_pFnxVoiceUser;
u_char*				Asura_PS2_VoiceRecognition_Fonix::s_pucWordList;		// location of plain text file.
char*				Asura_PS2_VoiceRecognition_Fonix::s_aszPhrases[uMAX_RECOGNISED_WORDS];	// array of strings
bool				Asura_PS2_VoiceRecognition_Fonix::s_bInitialisedOK;

bool Asura_PS2_VoiceRecognition_Fonix::Initialise(Asura_Fonix_RecognisedCallback pfRecognisedCallback, Asura_Fonix_FailedCallback pfFailedCallback)
{
	bool bOK = InitLogitech();
	if (bOK)
	{
		// try to connect a headset
		FindAndOpenHeadsetDevice();
	}

	if (bOK)
	{
		bOK = LoadRecognitionSupport();
	}

	if (bOK)
	{
		// Create our internal global buffers
		s_pucSpooler = reinterpret_cast<u_char*>(Asura_Allocate(IN_SPOOLERBUF));
		if (s_pucSpooler)
		{
			memset(s_pucSpooler, 0, IN_SPOOLERBUF);
		}
		s_pucReadPtr = s_pucSpooler;
	}

	s_pfRecognisedCallback = pfRecognisedCallback;
	s_pfFailedCallback = pfFailedCallback;
	
	s_bInitialisedOK = bOK;

	return bOK;
}

void Asura_PS2_VoiceRecognition_Fonix::Shutdown()
{
	CloseDevice();
	ShutdownLogitech();

	if (s_pFnxVoice)
	{
		Asura_DeallocateAlign( s_pFnxVoice );
		s_pFnxVoice = NULL;
	}
	if (s_pFnxVocab)
	{
		Asura_DeallocateAlign( s_pFnxVocab );
		s_pFnxVocab = NULL;
	}
	if (s_pFnxVoiceUser)
	{
		Asura_DeallocateAlign( s_pFnxVoiceUser );
		s_pFnxVoiceUser = NULL;
	}
	if (s_pucWordList)
	{
		Asura_DeallocateAlign( s_pucWordList );
		s_pucWordList = NULL;
	}
	if (s_pucSpooler)
	{
		Asura_Deallocate(s_pucSpooler);
		s_pucSpooler = NULL;
	}	
	for (u_int u = 0; u < uMAX_RECOGNISED_WORDS; ++u)
	{
		s_aszPhrases[u] = NULL;
	}

	s_pfRecognisedCallback = NULL;
	s_pfFailedCallback = NULL;

	s_bInitialisedOK = false;
}


static void* lgAudMalloc(size_t uNumBytes)
{
	return Asura_Allocate(uNumBytes);
}

static void lgAudFree(void* pMem)
{
	Asura_Deallocate(pMem);
}

bool Asura_PS2_VoiceRecognition_Fonix::InitLogitech()
{
#ifdef ASURA_PS2_SUPPORT_USB_HEADSET
	int iResult = lgAudInit(lgAudMalloc, lgAudFree);
	if(LGAUD_FAILED(iResult))
	{
		Asura_Assert(false, "lgAudInit failed");
		return false;
	}
	return true;
#else
	return false;
#endif //ASURA_PS2_SUPPORT_USB_HEADSET
	
}

void Asura_PS2_VoiceRecognition_Fonix::ShutdownLogitech()
{
#ifdef ASURA_PS2_SUPPORT_USB_HEADSET
	lgAudDeInit();
#endif //#ifdef ASURA_PS2_SUPPORT_USB_HEADSET
}

//////////////////////////////////////////////////////////////////////
// FindAndOpenHeadsetDevice
//
// Returns the first attached headset device
//////////////////////////////////////////////////////////////////////
bool Asura_PS2_VoiceRecognition_Fonix::FindAndOpenHeadsetDevice()
{
#ifdef ASURA_PS2_SUPPORT_USB_HEADSET
   Asura_Assert(s_iUSBDevice == LGAUD_INVALID_DEVICE, "already have a headset");
	if (s_iUSBDevice != LGAUD_INVALID_DEVICE)
	{
		return true;
	}

    int ret = LGAUD_SUCCESS;
    int iEnumhint;

    s_iUSBDevice = LGAUD_INVALID_DEVICE;

    if(LGAUD_SUCCEEDED(lgAudEnumHint(&iEnumhint)))
    {
        if (iEnumhint == LGAUD_HINT_ENUMNEEDED)
		{
			// something changed, try enumerating a headset
			// for simplicity, we just pick the first
			if( LGAUD_SUCCESS == lgAudEnumerate(0, &s_xUSBDeviceDesc))
			{
				lgAudOpenParam openParam;
				openParam.Mode = /*LGAUD_MODE_PLAYBACK | */LGAUD_MODE_RECORDING;
            
				/*
				openParam.PlaybackFormat.Channels = OUT_SAMPLING_CHANNELS;
				openParam.PlaybackFormat.BitResolution = OUT_SAMPLING_BITS;
				openParam.PlaybackFormat.SamplingRate = OUT_SAMPLING_FREQUENCY;
				openParam.PlaybackFormat.BufferMilliseconds = PLAYBACKLENGTH;
				*/            
				openParam.RecordingFormat.Channels = IN_SAMPLING_CHANNELS;
				openParam.RecordingFormat.BitResolution = IN_SAMPLING_BITS;
				openParam.RecordingFormat.SamplingRate = IN_SAMPLING_FREQUENCY;
				openParam.RecordingFormat.BufferMilliseconds = RECORDLENGTH;
            
				if ((ret = lgAudOpen(0, &openParam, &s_iUSBDevice)) == LGAUD_SUCCESS)
				{
                
	//              printf("Opened USB Audio Device\n");
	//              ShowDeviceSupport();
					printf("\n\n");

					/*
					printf("Playback: opened device %d with format %dHz, %dbit, %dchannel, %dms buffer\n",
						   s_iUSBDevice,
						   openParam.PlaybackFormat.SamplingRate,
						   openParam.PlaybackFormat.BitResolution,
						   openParam.PlaybackFormat.Channels,
						   openParam.PlaybackFormat.BufferMilliseconds);
					*/
					printf("Record: opened device %d with format %dHz, %dbit, %dchannel, %dms buffer\n",
						   s_iUSBDevice,
						   openParam.RecordingFormat.SamplingRate,
						   openParam.RecordingFormat.BitResolution,
						   openParam.RecordingFormat.Channels,
						   openParam.RecordingFormat.BufferMilliseconds);
					printf("\n\n");
                
					//InitAudioDevice(s_iUSBDevice, LGAUD_MODE_PLAYBACK);
					InitAudioDevice(s_iUSBDevice, LGAUD_MODE_RECORDING);
                
					return true;
				}
				else
				{
					printf("ERROR: lgAudOpen failed (0x%x)\n", ret);
				}
			}
		}
    }
#endif //#ifdef ASURA_PS2_SUPPORT_USB_HEADSET
    return false;
    
}


//////////////////////////////////////////////////////////////////////
// CloseDevice
// 
// Closes handle to attached device
//////////////////////////////////////////////////////////////////////
void Asura_PS2_VoiceRecognition_Fonix::CloseDevice()
{
#ifdef ASURA_PS2_SUPPORT_USB_HEADSET
    lgAudClose(s_iUSBDevice);

    s_iUSBDevice = LGAUD_INVALID_DEVICE;
#endif //#ifdef ASURA_PS2_SUPPORT_USB_HEADSET
}


//////////////////////////////////////////////////////////////////////
// InitAudioDevice
//
// Initializes the attached device
//////////////////////////////////////////////////////////////////////
void Asura_PS2_VoiceRecognition_Fonix::InitAudioDevice(int usb_device, int mode)
{
#ifdef ASURA_PS2_SUPPORT_USB_HEADSET
    switch(mode)
    {
    case LGAUD_MODE_RECORDING:
        lgAudSetRecordingVolume(usb_device, LGAUD_CH_BOTH, DEFAULT_RECORD_VOLUME);
        break;
//  case LGAUD_MODE_PLAYBACK:
//      lgAudSetPlaybackVolume(usb_device, LGAUD_CH_BOTH, g_playback_volume);
//      break;
    default:
        break;
    }
#endif //#ifdef ASURA_PS2_SUPPORT_USB_HEADSET
}

bool Asura_PS2_VoiceRecognition_Fonix::LoadRecognitionSupport()
{
	// Load the common voice recognition information.
	s_pFnxVocab = reinterpret_cast<FnxVocabPtr*>(Asura_PS2_OS::LoadFileAligned( g_szVocabFile, ASURA_ALIGN_QUADWORD ));
	s_pFnxVoice = reinterpret_cast<FnxVoicePtr*>(Asura_PS2_OS::LoadFileAligned( g_szNeuralNetFile, ASURA_ALIGN_QUADWORD ));
	s_pFnxVoiceUser = reinterpret_cast<FnxVoiceUserPtr*>(Asura_PS2_OS::LoadFileAligned( g_szUserDataFile, ASURA_ALIGN_QUADWORD ));
	u_int uWordListSize;
	s_pucWordList = Asura_PS2_OS::LoadFileAligned( g_szWordListFile, ASURA_ALIGN_QUADWORD, &uWordListSize );

	if ( (!s_pFnxVocab) || (!s_pFnxVoice) || (!s_pucWordList) || (!s_pFnxVoiceUser) )
	{
		Asura_Assert(false, "failed to load voice recog files");
		return false;
	}

	// Initialize the common voice recognition block (or set m_err to FNX_NULL_POINTER if the read failed)
	int iErr  = FnxVoiceInit(s_pFnxVoice);

	if (!iErr)
	{
		// Load vocabulary
		iErr = FnxVocabInit(s_pFnxVocab);
	}

	// load the word list that was used to create this vocab
	if (!iErr)
	{
		ParseWordList(uWordListSize);
	}

	/*******************************************/
	// Initialize user-specific ASR information
	s_eState = VOICE_UNAVAILABLE; // unless initialization is successful.
	if (!iErr)
	{
	   {
		  int i;
		  iErr = FnxVoiceUserInit(s_pFnxVoice, s_pFnxVoiceUser);
		  if (!iErr)
		  {
			 // Select the one and only vocabulary
			 iErr = FnxSelectXVocabs(s_pFnxVoiceUser, &s_pFnxVocab, 1);
		  }

		  // Start out in the idle state
		  s_eState = VOICE_IDLE;
	   }

	}
	return iErr == 0;
}


// ParseWordList() - function to setup the s_aszPhrases array from the word list text file 
// loaded into s_pucWordList. Each entry will point to the start of a phrase but no new memory 
// is allocated - the strings are NULL-terminated in-place.
void Asura_PS2_VoiceRecognition_Fonix::ParseWordList( u_int uNumBytes )
{
	Asura_Assert(s_pucWordList, "no word list!!!");
	u_int uNumWords = 0;

	u_char* pucCur = s_pucWordList;
	u_char* const pucEndOfData = s_pucWordList + uNumBytes;
	while (pucCur < pucEndOfData)
	{
		int iEntryNum;
		if (sscanf( reinterpret_cast<char*>(pucCur), "%d\t", &iEntryNum ) == 1)
		{
			Asura_Assert(uNumWords < uMAX_RECOGNISED_WORDS, "too many words");
			if (uNumWords >= uMAX_RECOGNISED_WORDS)
			{
				return;
			}

			// this is an entry, check the entry number is as expected, and store a 
			// pointer to the string
			Asura_Assert(iEntryNum == int(uNumWords + 1), "unexpected number in sequence of words in vocab list");
			// skip to start of string
			while (*pucCur++ != '\t')
			{}
			// skip any further whitespace
			while ( (pucCur < pucEndOfData) && (*pucCur == '\t' || *pucCur == ' ') )
			{
				++pucCur;
			}

			s_aszPhrases[uNumWords++] = reinterpret_cast<char*>(pucCur);

			// get newline to replace it with zero
			while ( (pucCur < pucEndOfData) && (*pucCur != '\n') && (*pucCur != '\r') )
			{
				++pucCur;
			}

			Asura_Assert(pucCur != pucEndOfData, "need newline at end of file");
			if (pucCur == pucEndOfData)
			{
				pucCur = pucEndOfData - 1;
			}
			
			// terminate string and advance to next line
			*pucCur++ = 0;

			// skip extra newlines (or CR/LF)
			while ( (pucCur < pucEndOfData) && (*pucCur == '\n' || *pucCur == '\r') )
			{
				++pucCur;
			}

		}
		else
		{
			// find end of line
			while (pucCur < pucEndOfData)
			{
				u_char ucChar = *pucCur++;
				if ((ucChar == '\n') || (ucChar == '\r'))
				{
					break;
				}
			}
			// skip extra newlines (or CR/LF)
			while ( (pucCur < pucEndOfData) && (*pucCur == '\n' || *pucCur == '\r') )
			{
				++pucCur;
			}

		}
	}

}


void Asura_PS2_VoiceRecognition_Fonix::Update()
{
	if (!s_bInitialisedOK) { return; }

	if (s_iUSBDevice == LGAUD_INVALID_DEVICE)
	{
		bool bOK = FindAndOpenHeadsetDevice();
		if (!bOK) return;
	}

    // Are we recording?
    if ( (s_eState == VOICE_LISTENING) || (s_eState == VOICE_FINISHING) || (s_eState == VOICE_STOPPING) )
    {
        // collect our data at the read point
		// read up to READCHUNK bytes (or remaining bytes in our spooler buffer if it's smaller).
        int iBytesToRead = Asura_Maths::Min(READCHUNK, (int)((s_pucSpooler + IN_SPOOLERBUF) - s_pucReadPtr));
        if (iBytesToRead > 0)
        {
            int iLgAudReadResult = lgAudRead(s_iUSBDevice, LGAUD_BLOCKMODE_NOT_BLOCKING, s_pucReadPtr, &iBytesToRead);

			if (LGAUD_SUCCEEDED(iLgAudReadResult))
            {
				DWORD uFonixResult;
				HRESULT iErr;

				// Submit the copied data to the speech recognizer
				if (!iBytesToRead)
				{
					if ((s_eState == VOICE_FINISHING) || (s_eState == VOICE_STOPPING))
					{
						// Provide time to finish doing recognition, but don't send more data
						iErr = FnxVoiceRecognize(s_pFnxVoice, s_pFnxVoiceUser, NULL, 0, &uFonixResult);
						if (iErr)
						{
							Asura_OutputToDebugger(("FnxVoiceRecognize returned error %d\n", iErr));
						}
					}

				}
				else 
				{
					// Send in some wave data and do some processing 
					iErr = FnxVoiceRecognize(s_pFnxVoice, s_pFnxVoiceUser, reinterpret_cast<short *>(s_pucReadPtr), 
						iBytesToRead/BYTES_PER_SAMPLE, &uFonixResult);
					if (iErr)
					{
						Asura_OutputToDebugger(("FnxVoiceRecognize returned error %d\n", iErr));
					}
					
					if (uFonixResult & END_OF_SPEECH)
					{
						s_eState = VOICE_FINISHING;
						StopRecording();
					}

// (MH - 08.12.2004) - could display something to show it's working.
//						if (uFonixResult & SPEECH_DETECTED)
//						{
//							bSpeechDetected = TRUE;
//						}
					
				}
					
				if ( (uFonixResult & RESULTS_AVAILABLE) || (s_eState == VOICE_STOPPING) )
				{
					int iNBest;
					DWORD* pdwTempWordID = NULL;
					float* pfTempConfidence = NULL;


					// Processing has completed on the speech, so get the results
					iErr = FnxVoiceGetResults(s_pFnxVoice, s_pFnxVoiceUser, &pdwTempWordID, &pfTempConfidence, &iNBest);
					if (iErr)
					{
						Asura_OutputToDebugger(("FnxVoiceGetResults returned error %d\n", iErr));
					}
					else
					{
						bool bRecognised = (iNBest > 0);
						if (bRecognised)
						{
							Asura_Assert(iNBest == 1, "should have recognised one word");
							Asura_Assert(pdwTempWordID && pfTempConfidence, "FnxVoiceGetResults didn't fill out arrays");
							if (pdwTempWordID && pfTempConfidence)
							{
								if (*pfTempConfidence < dbg_fMIN_CONFIDENCE)
								{
									bRecognised = false;
								}
								else
								{
									u_int uArrayIndex = *pdwTempWordID - 1;
									Asura_Assert(uArrayIndex < uMAX_RECOGNISED_WORDS, "word ID out of range");
									if (uArrayIndex < uMAX_RECOGNISED_WORDS)
									{
										Asura_OutputToDebugger(("Recognized phrase %s. Confidence = %f\n", 
											s_aszPhrases[uArrayIndex], Asura_Maths::FtoD(*pfTempConfidence)));

										s_pfRecognisedCallback( *pdwTempWordID, s_aszPhrases[uArrayIndex] );
									}
								}
							}
						}

						if (!bRecognised)
						{
							s_pfFailedCallback();
						}
					}

					s_eState = VOICE_IDLE;

				} // end if (uFonixResult & RESULTS_AVAILABLE)

                // update the read ptr
                s_pucReadPtr += iBytesToRead;

            }
            else
            {
                Asura_OutputToDebugger(("lgAudRead failed with code %x (LGAUD_ERR_DEVICE_LOST == 0x80000006)\n", iLgAudReadResult ));
                // probably lost device
				CloseDevice();
            }
        }
    }
    
}


int Asura_PS2_VoiceRecognition_Fonix::StartRecording()
{
	if (!s_bInitialisedOK) 
	{ 
		return LGAUD_ERROR; 
	}

    if (s_iUSBDevice == LGAUD_INVALID_DEVICE)
	{
        return LGAUD_ERROR;
	}

    int iResult = lgAudStartRecording(s_iUSBDevice);
	if (LGAUD_SUCCEEDED(iResult))
    {        
        Asura_OutputToDebugger(("Start Recording\n"));

        // test data.
        memset(s_pucSpooler, 0, IN_SPOOLERBUF);
        s_pucReadPtr = s_pucSpooler;
        s_eState = VOICE_LISTENING;

		// clear any partial recognition results
		int iErr = FnxVoiceGetResults(s_pFnxVoice, s_pFnxVoiceUser, 0, 0, 0);
		if (iErr)
		{
			Asura_OutputToDebugger(("FnxVoiceGetResults returned error %d\n", iErr));
		}
		return LGAUD_SUCCESS;
    }
    else
    {
        // we must have lost our device
		Asura_OutputToDebugger(("lgAudStartRecording failed with code %x (LGAUD_ERR_DEVICE_LOST == 0x80000006)\n", iResult ));
        CloseDevice();
    }

    return iResult;
}

int Asura_PS2_VoiceRecognition_Fonix::StopRecording()
{
	if (!s_bInitialisedOK) 
	{ 
		return LGAUD_ERROR; 
	}

    if(s_iUSBDevice == LGAUD_INVALID_DEVICE)
	{
        return LGAUD_ERROR;
    }

	if (s_eState == VOICE_IDLE)
	{
		return LGAUD_SUCCESS;
	}

    int iResult = lgAudStopRecording(s_iUSBDevice);

  	if (LGAUD_FAILED(iResult))
	{
		Asura_OutputToDebugger(("lgAudStopRecording failed with code %x (LGAUD_ERR_DEVICE_LOST == 0x80000006)\n", iResult ));
	}
	else
	{
		Asura_OutputToDebugger(("Stop Recording\n"));
    }
    
    return iResult;
}

void Asura_PS2_VoiceRecognition_Fonix::StartListening()
{
	StartRecording();
}

void Asura_PS2_VoiceRecognition_Fonix::StopListening()
{
	if (s_eState == VOICE_LISTENING)
	{
		s_eState = VOICE_STOPPING;
	}
	StopRecording();
}

//////////////////////////////////////////////////////////////////////////
//
// class Asura_PS2_VoiceRecognition
//
//////////////////////////////////////////////////////////////////////////
int Asura_PS2_VoiceRecognition::s_iTalkButtonID = ASURA_PS2_KEYID_PAD1_R3;

void Asura_PS2_VoiceRecognition::Initialise( void )
{
	Asura_PS2_VoiceRecognition_Fonix::Initialise(Project_ProcessUtterance, Project_HandleFailedUtterance);
}

void Asura_PS2_VoiceRecognition::SetLanguageFiles(const char* szVocabFile,
												  const char* szNeuralNetFile,
												  const char* szUserDataFile,
												  const char* szWordListFile)
{
	g_szVocabFile = szVocabFile;
	g_szNeuralNetFile = szNeuralNetFile;
	g_szUserDataFile = szUserDataFile;
	g_szWordListFile = szWordListFile;
}

void Asura_PS2_VoiceRecognition::Update( void )
{
	if (Asura_UserInput::DebouncedKeyPressed(s_iTalkButtonID))
	{
		Asura_PS2_VoiceRecognition_Fonix::StartListening();
	}

	if (Asura_UserInput::DebouncedKeyNotPressed(s_iTalkButtonID))
	{
		Asura_PS2_VoiceRecognition_Fonix::StopListening();
	}

	Asura_PS2_VoiceRecognition_Fonix::Update();
}

void Asura_PS2_VoiceRecognition::Reset( void )
{}

void Asura_PS2_VoiceRecognition::Shutdown( void )
{
	Asura_PS2_VoiceRecognition_Fonix::Shutdown();
}

// the button which must be held to capture speech can be changed at any time
void Asura_PS2_VoiceRecognition::SetTalkButton( int iKeyID )
{
	// first stop the engine, in case the old button happened to be held down
	Asura_PS2_VoiceRecognition_Fonix::StopListening();

	s_iTalkButtonID = iKeyID;
}

#endif
