/*
 *  SphinxProject.cpp
 *  sphinxProject
 *
 *  Created by joshua noble on 3/12/08.
 *  thefactoryfactory.com
 *
 */

#include "SphinxController.h"


//-------------------------------------------------------------------------
// lame static method sign. b/c my C++ is lame and I can't pass &this->method

int receiveAudioBuffer(char *buffer, int bufferSize, void *data);
int receiveAudioBuffer(char *buffer, int bufferSize, void *data){

	SphinxController *sc = (SphinxController *)data; 
	sc->prepare_audio_data(buffer, bufferSize, data);
}

//-------------------------------------------------------------------------
SphinxController::SphinxController(){
	printf("SphinxController::SphinxController ");
	
	//this is for the callback so it can reference the class
	
	bufferSize			= SP_BUFFER_SIZE;
	bRecordAudio		= false;
	bDecodingStarted	= false;
}

//-------------------------------------------------------------------------
SphinxController::~SphinxController(){
	stopThread();
	stopDecoding();
}

//-------------------------------------------------------------------------
void SphinxController::stopDecoding()
{
	if( bDecodingStarted ){
		cout << "Stopping Sphinx-3 decoding..." << endl;
		s3_decode_end_utt(&decoder);			
		s3_decode_close(&decoder);
		fe_close(pSphinxFrontEnd);
		bDecodingStarted = false;
	}
}

//-------------------------------------------------------------------------
bool SphinxController::sphinxSetup(string config_file, float sampleRate, int deviceID){
	
	printf("\n\n\n\n -- ++ ==  SETUP -- ++ == \n\n\n\n");
	
	if( initSphinx(config_file) != 1){
		printf("SphinxController::sphinxSetup - sphinx failed to initialize!!!!\n");
		return false;
	}else{
		if( !initAudio(sampleRate, deviceID) ){
			printf("SphinxController::sphinxSetup - couldn't setup requested device and samplerate!\n");
			return false;
		}	
	}
	
	return true;
}

//-------------------------------------------------------------------------
bool SphinxController::initSphinx(string config_file)
{
	if (cmd_ln_parse_file(S3_DECODE_ARG_DEFS, ofToDataPath(config_file).c_str(), TRUE)  == -1) {
		printf("SphinxController::initSphinx - Bad arguments in file or File not found!\n\n ");
		return false;
	}
	
	// initialize the front end
	cout << "\n--- initialize front-end...\n" << endl;
	pSphinxFrontEnd = fe_init_auto(); 
		
	// initialize the decoder
	cout << "\n--- initializing decoder... \n" << endl;	
	if (s3_decode_init(&decoder) == -1) 
	{
		printf("\nSPhinxController::initSphinx - Decoder not initialized!\n");
		return false;
	}
		
	// Prepare for our one and only utterance, name it "simple"
	if (s3_decode_begin_utt(&decoder, "simple") == -1) 
	{
		printf("\nSphinxController::initSphinx - Failed to start decoding!\n");
		return false;
	}
	
	bDecodingStarted = true;
	
	printf("\n--- sphinx initalized!!! \n");
	return true;
}

//-------------------------------------------------------------------------
bool SphinxController::initAudio(float sampleRate, int deviceNumber){
	
	printf("SphinxController::initAudio");
	
	try {
		audio = new RtAudio();
		int bsize = bufferSize;
		audio->openStream(0, 0, deviceNumber, 1, RTAUDIO_FLOAT32, sampleRate, &bsize, 4);
	} 
	catch (RtError &error) {
		error.printMessage();
		printf("SphinxController::sphinxSetup rt audio messed up");
		return false;
	}
	try {
		//pass a pointer to our class so the callback knows which class is using it
		//will this work with multiple classes and multiple streams????
		audio->setStreamCallback(&receiveAudioBuffer, (void *)this);
		audio->startStream();
		printf("SphinxController::sphinxSetup audio stream started ok\n");
	} catch (RtError &error) {
		error.printMessage();
		printf("SphinxController::sphinxSetup rt audio stream messed up ");
		return false;
	}
	
	//------------------------------------------
	//lets setup our sample rate convertor!
	
	if ((src_state_ptr = src_new(2, 1, &samp_err_int_ptr)) == NULL){	
		printf ("\n\nError : src_new() failed : %s.\n\n", src_strerror (samp_err_int_ptr)) ;
		return false;
	} 
	
	//theo - we recalculate some of these dynamically in the prepare_audio_data function
	src_data.end_of_input		= 0;
	src_data.input_frames		= bufferSize;
	src_data.data_out			= float_sphinxSamplesOut;
	src_data.src_ratio			= 16000 / sampleRate;
	src_data.output_frames		= (float)bufferSize * src_data.src_ratio;
	
	printf("Audio Setup correctly on device %i at %fHz\n", deviceNumber, sampleRate);

	return true;
}

//------------------------------------------------------------------------------
void SphinxController::threadedFunction(){
	
	lock();
	
	float32** ppFrames		= NULL;	
	int16 * intpt			= (int16*)inputPtr;
	int numFramesToProcess	= nFrames;
	int numSamples			= nSamples;

//we can use this to check num samples per frame - at the moment it is 160
//	int samplesNeeded	= 0;
//	int sampleShift		= 0;
//	fe_get_input_size(pSphinxFrontEnd, &samplesNeeded, &sampleShift);
//	printf("samplesNeeded is %i \n", samplesNeeded);
	
	int t1 = ofGetElapsedTimeMillis();
	
	// Do the front-end and recognition processing of this block of samples				
	if (numFramesToProcess > 0){	
	
		fe_process_utt(pSphinxFrontEnd, intpt, numSamples, &ppFrames, &numFramesToProcess);
		
		int t2 = ofGetElapsedTimeMillis();
			printf("fe_process_utt %i \n", t2 - t1);
		
		if (ppFrames != NULL) {
			s3_decode_process(&decoder, ppFrames, numFramesToProcess);
			int t3 = ofGetElapsedTimeMillis();
			printf("s3_decode_process %i\n", t3 - t2);
			
			ckd_free_2d((void **) ppFrames);
		}else{
			printf("ppFrames is null - returning\n");
			unlock();
			return;
		}
	}
	
	int t4 = ofGetElapsedTimeMillis();

    char *str, *uttid;
	if (S3_DECODE_SUCCESS == s3_decode_hypothesis(&decoder, &uttid, &str, NULL)) {
		int t5 = ofGetElapsedTimeMillis();
	
		printf("s3_decode_hypothesis %i\n", t5 - t4);
	
		cout << "Partial hypothesis: " << str << endl;
		receiveHypothesis(str);
	}else{
		printf("s3_decode_hypothesis -- no success!\n");
	}

	//printf("done\n");	
	unlock();
}

//------------------------------------------------------------------------------
int SphinxController::prepare_audio_data(char *buffer, int bufferSize, void *data){
	
	int err = 0;

	//get a ptr to the buffer (cast to float coz that is what it really is)
	src_data.data_in = (float *)buffer;
	
	//covert to 16,000Hz
	err = src_process(src_state_ptr, &src_data);
	if( err != 0 ){
		printf("SphinxController::prepare_audio_data - src_process error!\n");
	}
	
	//add the converted data to a queue of samples waiting to be processed 
	for (int i = 0; i < src_data.output_frames; i++) {
		
		//theo - to covert the floating point -1.0 to 1.0 range to the range of a short
		//(which is the same as an int) we need to multiply the floating point value
		//by the max amount a short can hold. a short can hold between -32768 to 32767 
		//so we just multiply by 32767 
		convertedData[i] = (short) ( float_sphinxSamplesOut[i] * 32767.0 );
		sphinxSamples.push_back(convertedData[i]); 
	}
		
	//theo - every SP_BUFFER_SIZE samples in results in 92 samples out
	//92 samples is a very short amount of time so we only process
	//the audio after recieving 40 buffers of 92 samples which aprox = 230 ms
	// if you do 92 * 10 then it will send the data more quickly but results might not be so accurate
	// if you send the data 92 * 80 then it will take longer to get back the results. 
	// 92 * 40 seems to work well - but this was just a guess so feel free to play around

	int currentSampleIndex = sphinxSamples.size();	 
	if( currentSampleIndex >= ( 92 * 40 ) ){
	
		//we now copy over the data to be processed and kick off a thread to do the processing
		if(bDecodingStarted)processAudioChunk();
		
		//clear the buffered audio data - we don't need it anymore!
		sphinxSamples.clear();
	}
	
	return err;
}

//------------------------------------------------------------------------------
void SphinxController::processAudioChunk(){		
	
		bool areWeLocked = lock();
			
			nSamples = sphinxSamples.size();
					
			//theo - s3decoder needs to know how many frames it is getting
			//by default 1 frame is 160 samples - so we calculate the number of frames
			//by dividing the total samples by 160
			nFrames = nSamples / 160;
					
			//copy the data over from the vector to the c array 
			//must be a faster way to do this no?
			for(int i = 0; i < nSamples; i++){
				processMe[i] = sphinxSamples[i];
			}
			
			//tell the thread where to find the audio data!
			inputPtr = processMe;
					
		if(areWeLocked)unlock();
		
		startThread(true, false);

}

//be careful here as anything happening here is called by
//the threadedFunction - so is part of the threaded process

//-----------------------------------------------------------------------------
void SphinxController::receiveHypothesis(char* hypothesis){
	current_hypo = (string) hypothesis;
	//pApplication->decoded(current_hypo);
}







//void recordCurrentAudio(){
//	//	if(bRecordAudio){
////	
////			short buffer[sphinxSamples.size()];
////	
////			for (int i = 0; i < sphinxSamples.size(); i++) {
////				buffer[i] = sphinxSamples[i];
////			}
////	
////			FILE * pFile;
////			pFile = fopen ( ofToDataPath("sound.raw").c_str() , "wb" );
////			fwrite ( (char *)buffer , 1 , sizeof(buffer) , pFile );
////			fclose (pFile);
////			bRecordAudio = false;	
////	}
//	
//}

