/* Copyright (c) 1991-2002 Doshita Lab. Speech Group, Kyoto University */
/* Copyright (c) 2000-2002 Speech and Acoustics Processing Lab., NAIST */

/* main.c --- main routine */

/* $Id: main.c,v 1.9 2002/11/06 16:56:41 ri Exp $ */

#define GLOBAL_VARIABLE_DEFINE	/* actually make global vars in global.h */
#include <julius.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>

HTK_Param *param;		/* parameter vector */

/* ---------- utility functions -----------------------------------------*/
/* int signal handler */
static RETSIGTYPE
my_sig_int()
{
  catch_intr_flag = TRUE;
  j_printerr("** catch sigint **\n");
}
/* start trap */
static RETSIGTYPE
sigint_trap()
{
  catch_intr_flag = FALSE;
  if (signal(SIGINT, my_sig_int) == SIG_ERR) {
    j_printerr("Warning: cannot set signal handler\n");
  }
}
/* end trap */
static RETSIGTYPE
sigint_untrap()
{
  if (signal(SIGINT, SIG_DFL) == SIG_ERR) {
    j_printerr("Warning: cannot set signal handler\n");
  }
}

#ifdef REPORT_MEMORY_USAGE
/* output memory usage (virtual / resident) by invoking "ps" */
/* for linux, sol2  */
static void
print_mem()
{
  char buf[200];
  sprintf(buf,"ps -o vsz,rss -p %d",getpid());
  system(buf);
  j_flushprint();
  fflush(stderr);
}
#endif
	  

/* --------------------- speech buffering ------------------ */
/* callback function for adin_go: store each segmented speech data to buffer */
/* will be used when NOT realtime mode */
static SP16 *overflowed_samples = NULL;
static int overflowed_samplenum;
/* return value:
   -1 ... error -> exit, terminate 
   0  ... continue reading
   1  ... segment detected -> force adin_go to segment (do not flush the cycle buffer)
*/
static int
adin_cut_callback_store_buffer(SP16 *now, int len)
{
  if (module_mode) {
    /* poll the command buffer for each input fragment */
    msock_check_and_process_command();
  }
  if (speechlen == 0) {		/* first part of a segment */
    if (module_mode) {
      /* tell module to start recording */
      msock_status_recstart();
      if (module_wants_terminate() ||/* TERMINATE ... force termination */
	  !module_is_active()) { /* PAUSE ... keep recording when *triggering */
	return(-2);
      }
    }
    if (overflowed_samples) {	/* last input was overflowed */
      /* restore last overflowed samples */
      memcpy(&(speech[0]), overflowed_samples, sizeof(SP16)*overflowed_samplenum);
      speechlen += overflowed_samplenum;
      free(overflowed_samples);
      overflowed_samples = NULL;
    }
  }
  if (speechlen + len > MAXSPEECHLEN) {
    /*j_printerr("Error: too long input (> %d samples)\n", MAXSPEECHLEN);*/
    j_printerr("Warning: too long input (> %d samples), segmented now\n", MAXSPEECHLEN);
    /* store the overflowed samples for next segment, and end segment */
    {
      int getlen, restlen;
      getlen = MAXSPEECHLEN - speechlen;
      restlen = len - getlen;
      overflowed_samples = (SP16 *)mymalloc(sizeof(SP16)*restlen);
      memcpy(overflowed_samples, &(now[getlen]), restlen * sizeof(SP16));
      if (record_dirname != NULL) {
	record_sample_write(&(now[getlen]), restlen);
      }
      overflowed_samplenum = restlen;
      memcpy(&(speech[speechlen]), now, getlen * sizeof(SP16));
      if (record_dirname != NULL) {
	record_sample_write(now, getlen);
      }
      speechlen += getlen;
    }
    return(1);			/* tell adin_go to end segment */
  }
  if (module_mode) {
    /* poll module command and terminate here if requested */
    if (module_wants_terminate()) {/* TERMINATE ... force termination */
      speechlen = 0;
      return(-2);
    }
  }
  /* store now[0..len] to speech[speechlen] */
  memcpy(&(speech[speechlen]), now, len * sizeof(SP16));
  if (record_dirname != NULL) {
    record_sample_write(now, len);
  }
  speechlen += len;
  return(0);			/* tell adin_go to continue reading */
}

/* return malloced next file name from inputlist (return NULL on error) */
static char *
mfcfilelist_nextfile()
{
  static FILE *mfclist = NULL;	/* filelist of MFC file (-filelist) */
  static char *buf;
  int newline;
  /* filelist of rawfile is handled within adin_go() */
  
  if (mfclist == NULL) {	/* not opened yet */
    if ((mfclist = fopen(inputlist_filename, "r")) == NULL) { /* open error */
      j_error("inputlist open error\n");
    }
  }
  buf = mymalloc(MAXLINELEN);
  while(fgets(buf, MAXLINELEN, mfclist) != NULL) {
    newline = strlen(buf) - 1;
    if (buf[newline] == '\n') buf[newline] = '\0'; /* chop newline character */
    if (buf[0] == '\0') continue; /* skip blank line */
    if (buf[0] == '#') continue; /* skip commented-out line */
    /* read a filename */
    return buf;
  }
  /* end of inputfile list */
  fclose(mfclist);
  mfclist = NULL;
  return NULL;
}

				       
/* ------------------------------------------------------------------- */
/* ------------- Main Recognition Loop ------------------------------- */
/* ------------------------------------------------------------------- */
void
main_recognition_loop()
{
  char *speechfilename;	/* pathname of speech file or MFCC file */
  HTK_Param *selected_param;
  int ret;
  int file_counter;

  /***************************/
  /* Model/IO initialization */
  /***************************/
  /* initialize and standby audio input device */
  adin_initialize();
  
  /* initialize all models, work area, parameters to bootup system */
  final_fusion();

  /* print out system information */
  print_info();

  /* reset file count */
  file_counter = 0;
  
  /***************************/
  /***************************/
  /** Main Recognition Loop **/
  /***************************/
  /***************************/
  for (;;) {

    j_printf("\n");
    if (verbose_flag) j_printf("------\n");
    j_flushprint();

    /*********************/
    /* open input stream */
    /*********************/
    if (speech_input == SP_MFCFILE) {
      /*******************************************/
      /* read pre-analyzed parameter file (MFCC) */
      /*******************************************/
      VERMES("### read analyzed parameter\n");
      /* from MFCC parameter file (in HTK format) */
      if (inputlist_filename != NULL) {	/* has filename list */
	speechfilename = mfcfilelist_nextfile();
      } else {
	speechfilename = get_line("enter MFCC filename->");
      }
      if (speechfilename == NULL) {
	/* end */
	j_printerr("%d files processed\n", file_counter);
#ifdef REPORT_MEMORY_USAGE
	print_mem();
#endif
	j_exit();
      }
      if (verbose_flag) j_printf("\ninput MFCC file: %s\n",speechfilename);
      /* read parameter file */
      param = new_param();
      if (rdparam(speechfilename, param) == FALSE) {
	j_printerr("error in reading MFCC file: %s\n",speechfilename);
	free(speechfilename);
	free_param(param);
	continue;
      }
      /* check and strip invalid frames */
      if (strip_zero_sample) {
	param_strip_zero(param);
      }
      free(speechfilename);
    } else {			/* raw speech input */
      VERMES("### read waveform input\n");
      /* begin A/D input */
      if (adin_begin() == FALSE) {
	/* failed to begin stream, terminate */
	if (speech_input == SP_RAWFILE) {
	  j_printerr("%d files processed\n", file_counter);
	  j_exit();  /* end of file list */
	} else if (speech_input == SP_STDIN) {
	  j_exit();  /* end of input */
	} else {
	  j_error("failed to begin input stream\n");
	}
      }
    }
    
    /*********************************/
    /* do recognition for each input */
    /*********************************/
    while (1) {

    start_recog:

      if (module_mode) {
	/* process module command */
	/* If recognition is running (active), commands are polled only once
	   here, and if any, process the command, and continue the recognition.
	   If recognition is sleeping (inactive), wait here for any command to
	   come, and process them until recognition is activated by the
	   commands
	 */
	if (module_is_active()) {
	  /* process is active, check the command buffer and process if any */
	  msock_check_and_process_command();
	}
	module_reset_reload();	/* reset reload flag here */
	while (! module_is_active()) {    
	  /* now sleeping, wait for another command */
	  /* we will stop here and wait for another command */
	  /* until status turns to active */
	  msock_process_command();
	}
#ifdef USE_DFA
	/* check for grammar to change */
	multigram_exec();
	if (dfa == NULL || winfo == NULL) { /* stop when no grammar found */
	  msock_exec_command("PAUSE");
	  goto start_recog;
	}
#endif
      }

      if (speech_input == SP_MFCFILE) {
	/************************/
	/* parameter file input */
	/************************/
	/* whole input is already read, so set input status to end of stream */
	/* and jump to the start point of 1st pass */
	ret = 0;
      } else {
	/***********************/
	/* raw wave data input */
	/***********************/
	/* if needed, begin recording the incoming speech data to a file */
	if (record_dirname != NULL) {
	  record_sample_open();
	}
	if (realtime_flag) {
	  /****************************/
	  /* SPECIAL: realtime 1st-pass */
	  /****************************/
	  /* store, analysis and search in a pipeline (realtime_1stpass.c) */
	  /* after this part, directly jump to the beginning of the 2nd pass */
#ifdef SP_BREAK_CURRENT_FRAME
	  if (rest_param) {
	    /* last was segmented by short pause */
	    /* the margin segment in the last input should be re-processed first */
	    ret = RealTimeResume();
	    if (ret < 0) {		/* error end in the margin */
	      j_error("error in resuming last fragment\n"); /* exit now! */
	    }
	    if (ret != 1) {	/* if segmented again in the margin, not process the rest */
	      if (module_mode) {
		/* tell module to start recording, and command check in adin */
		msock_status_recready();
		ret = adin_go(RealTimePipeLine, msock_check_in_adin);
	      } else {
		/* process the incoming input as normal */
		ret = adin_go(RealTimePipeLine, NULL);
	      }
	      if (ret < 0) {		/* error end in adin_go */
		if (module_mode && ret == -2) {	/* terminated by module */
		  goto start_recog;
		}
		j_error("error in adin_go\n");          /* exit now! */
	      }
	    }
	    
	  } else {
	    /* last was not segmented, process the incoming input  */
#endif
	    RealTimePipeLinePrepare();
	    if (module_mode) {
	      /* tell module to start recording, and command check in adin */
	      msock_status_recready();
	      ret = adin_go(RealTimePipeLine, msock_check_in_adin);
	    } else {
	      ret = adin_go(RealTimePipeLine, NULL); /* process the incoming input */
	    }
	    if (ret < 0) {		/* error end in adin_go */
	      if (module_mode && ret == -2) {	/* terminated by module */
		goto start_recog;
	      }
	      j_error("error in adin_go\n");            /* exit now! */
	    }
#ifdef SP_BREAK_CURRENT_FRAME
	  }
#endif
	  /* last procedure of 1st-pass */
	  param = RealTimeParam(&backmax);
	  if (module_mode) {
	    /* if terminate signal has been received, discard this input */
	    if (module_wants_terminate()) goto end_recog;
	    /* tell module that recording is stopped */
	    msock_status_recend();
	  }
	  /* end of 1st pass, jump to 2nd pass */
	  goto end_1pass;
	  
	}
	
	/********************************/
	/* store raw samples & analysis */
	/********************************/
#ifdef SP_BREAK_CURRENT_FRAME
	if (rest_param == NULL) { /* no segment left */
#endif
	  speechlen = 0;
	  if (module_mode) {
	    /* tell module to start recording */
	    msock_status_recready();
	    ret = adin_go(adin_cut_callback_store_buffer, msock_check_in_adin);
	  } else {
	    ret = adin_go(adin_cut_callback_store_buffer, NULL);
	  }
	  if (ret < 0) {		/* error end in adin_go */
	    if (module_mode && ret == -2) {	/* terminated by module */
	      goto start_recog;
	    }
	    j_error("error in adin_go\n");              /* exit now! */
	  }
	  /* tell module that recording is stopped */
	  if (module_mode) msock_status_recend();
      
	  j_printf("%d samples (%.2f sec.)\n", speechlen, (float)speechlen / (float) smpFreq);
	  if (speechlen == 0) j_exit();	/* terminate when no input */
      
	  /**********************************/
	  /* acoustic analysis and encoding */
	  /**********************************/
	  VERMES("### speech analysis (waveform -> MFCC)\n");
	  /* subtract zero mean to remove DC offset */
	  /* sub_zmean(speech, speechlen); */
	  /* wav2mfcc() has no ZMEANSOURCE */
	  param = new_wav2mfcc(speech, speechlen);
	  if (param == NULL) continue;

	  /* if terminate signal has been received, cancel this input */
	  if (module_mode && module_wants_terminate()) goto end_recog;

#ifdef SP_BREAK_CURRENT_FRAME
	}
#endif
      }	/* end of raw speech data input and analysis */
      
      /********************************/
      /* check the analized parameter */
      /********************************/
      if (verbose_flag) {
	put_param_info(param);
      }
      /* parameter type check --- compare the type to that of HMM,
	 and adjust them if necessary */
      if (paramtype_check_flag) {
	/* return param itself or new malloced param */
	selected_param = new_param_check_and_adjust(hmminfo, param, verbose_flag);
	if (selected_param == NULL) { /* failed */
	  free_param(param);
	  continue;
	}
	param = selected_param;
      }


      /******************************************************/
      /* 1st-pass --- backward search to compute heuristics */
      /******************************************************/
#ifdef USE_NGRAM
      VERMES("### Recognition: 1st pass (LR beam with 2-gram)\n");
#else
      VERMES("### Recognition: 1st pass (LR beam with word-pair grammar)\n");
#endif
/* 
 * #ifdef WPAIR
 *     VERMES("with word-pair approx. ");
 * #else
 *     VERMES("with 1-best approx. ");
 * #endif
 *     VERMES("generating ");
 * #ifdef WORD_GRAPH
 *     VERMES("word graph\n");
 * #else
 *     VERMES("back trellis\n");
 * #endif
 */

      if (!realtime_flag) {
	/* prepare for outprob cache for each HMM state and time frame */
	outprob_prepare(param->samplenum);
      }

      if (module_mode) {
	/* if terminate signal has been received, cancel this input */
	if (module_wants_terminate()) goto end_recog;
      }

      /* execute computation of left-to-right backtrellis */
      get_back_trellis(param, wchmm, &backtrellis, &backmax);

    end_1pass:
      /* with realtime_flag, it joins at this point */

      /* if [-1pass] is specified, terminate search here */
      if (compute_only_1pass) {
	goto end_recog;
      }

      /* if backtrellis function returns with bad status, terminate search */
      if (backmax == LOG_ZERO) {
	/* j_printerr("Terminate 2nd pass.\n"); */
	result_pass2((NODE *)NULL, 0, wchmm->winfo);/* print NULL result */
	goto end_recog;
      }

      /* if terminate signal has been received, cancel this input */
      if (module_mode && module_wants_terminate()) goto end_recog;

      /***********************************************/
      /* 2nd-pass --- forward search with heuristics */
      /***********************************************/
#ifdef SCAN_BEAM
      /* prepare score envelope for 2nd pass */
      framemaxscore = (LOGPROB *)mymalloc(sizeof(LOGPROB)*backtrellis.framelen);
      envl_init(backtrellis.framelen);
#endif /* SCAN_BEAM */
    
#ifdef USE_NGRAM
      VERMES("### Recognition: 2nd pass (RL heuristic best-first with 3-gram)\n");
#else
      VERMES("### Recognition: 2nd pass (RL heuristic best-first with DFA)\n");
#endif

      /* execute stack-decoding search */
      wchmm_fbs(param, &backtrellis, backmax, stack_size, nbest, hypo_overflow);

#ifdef SCAN_BEAM
      free(framemaxscore);
#endif

    end_recog:
      /**********************/
      /* end of recognition */
      /**********************/
      free_param(param);

      /* if needed, close the recording files */
      if (record_dirname != NULL) {
	record_sample_close();
      }

      /* count number of processed files */
      if (speech_input == SP_MFCFILE || speech_input == SP_RAWFILE) {
	file_counter++;
      }

      VERMES("\n");

#ifdef SP_BREAK_CURRENT_FRAME
      /* param is now shrinked to hold only the processed input, and */
      /* the rests are holded in (newly allocated) "rest_param" */
      /* if this is the last segment, rest_param is NULL */
      if (rest_param != NULL) {
	/* process the rest parameters in the next loop */
	VERMES("<<<resuming>>>\n");
	param = rest_param;
      } else {
	/* input has reached end of stream loop, terminate program */
	if (ret <= 0) break;
      }
#else
      /* input has reached end of stream loop, terminate program */
      if (ret <= 0) break;
#endif
    } /* end of stream loop */

    if (speech_input != SP_MFCFILE) {
      adin_end();
    }
  }

}


/* ------------------------------------------------------------------- */
/* ------------- The Main Routine ------------------------------------ */
/* ------------------------------------------------------------------- */
int
main(int argc, char *argv[])
{
  /*************************/
  /* System initialization */
  /*************************/
  /* initialize, set some default parameter before all */
  system_bootup();
  /* parse options and set variables */
  opt_parse(argc,argv,NULL);

  /* check option values */
  check_specs();

  /*********************/
  /* Server/Standalone */
  /*********************/
  if (module_mode) {
    /* server mode */
    /* main_recognition_loop() will be called in main_module_loop() */
    main_module_loop();
  } else {
    /* standalone mode */
    main_recognition_loop();
  }
}

/* for object link error by WORD_GRAPH defined */
#ifdef WORD_GRAPH
void verify_WORD_GRAPH_define(){}
#else
void verify_WORD_GRAPH_undefine(){}
#endif
#ifdef WPAIR
void verify_WPAIR_define(){}
#else
void verify_WPAIR_undefine(){}
#endif
