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

/* m_fusion.c --- initialize all models, work area and parameters
                  to make up system */

/* $Id: m_fusion.c,v 1.11 2003/01/06 16:10:39 ri Exp $ */

#include <julius.h>

/* initialize acoustic HMM */
static void
initialize_HMM()
{
  /* allocate new hmminfo */
  hmminfo = hmminfo_new();
  /* load hmmdefs */
  init_hmminfo(hmminfo, hmmfilename, mapfilename);
  /* check training parameter type */
  /* for direct speech input,
     only MFCC_{0|E}_D_Z[_N] with {25|26} dimension is supported */
  if (speech_input != SP_MFCFILE) {
    if ((hmminfo->opt.param_type == param_str2code("MFCC_E_D_Z") && hmminfo->opt.vec_size == 26) || (hmminfo->opt.param_type == param_str2code("MFCC_E_D_N_Z") && hmminfo->opt.vec_size == 25)) {
      c0_required = FALSE;
    } else if ((hmminfo->opt.param_type == param_str2code("MFCC_0_D_Z") && hmminfo->opt.vec_size == 26) || (hmminfo->opt.param_type == param_str2code("MFCC_0_D_N_Z") && hmminfo->opt.vec_size == 25)) {
      c0_required = TRUE;
    } else {
      j_printerr("Error: for direct speech input, only 25(26)-dim. MFCC_{0|E}_D[_N]_Z is supported\n");
      j_error("Error: with this HMM, input must be given in HTK parameter file via \"-input mfcfile\"\n");
    }
  }
  /* check if tied_mixture */
  if (hmminfo->is_tied_mixture && hmminfo->codebooknum <= 0) {
    j_error("%s: this tied-mixture model has no codebook!?\n", EXECNAME);
  }
#ifdef PASS1_IWCD
  /* make state clusters of same context for inter-word triphone approx. */
  if (hmminfo->is_triphone) {
    j_printerr("Making pseudo bi/mono-phone for IW-triphone...");
    if (make_cdset(hmminfo) == FALSE) {
      j_error("\nError: failed to make context-dependent state set\n");
    }
    j_printerr("done\n");
  }
#endif

  /* find short pause model and set to hmminfo->sp */
  htk_hmm_set_pause_model(hmminfo, spmodel_name);

  /* set flag for context dependent handling (if not specified in command arg)*/
  if (!ccd_flag_force) {
    if (hmminfo->is_triphone) {
      ccd_flag = TRUE;
    } else {
      ccd_flag = FALSE;
    }
  }
  /* set which iwcd1 method to use */
  if (iwcdavg_flag) {
    hmminfo->prefer_cdset_avg = TRUE;
  } else {
    hmminfo->prefer_cdset_avg = FALSE;
  }

  /* find short-pause model */
  if (enable_iwsp) {
    if (hmminfo->sp == NULL) {
      j_error("cannot find short pause model \"%s\" in hmmdefs\n", spmodel_name);
    }
    hmminfo->iwsp_penalty = iwsp_penalty;
  }
}

/* initialize HMM for Gaussian Mixture Selection */
static void
initialize_GSHMM()
{
  j_printerr("Reading GS HMMs:\n");
  hmm_gs = hmminfo_new();
  init_hmminfo(hmm_gs, hmm_gs_filename, NULL);
}

/* initialize word dictionary */
static void
initialize_dict()
{

  winfo = word_info_new();
  if ( ! 
#ifdef MONOTREE
      /* leave winfo monophone for 1st pass lexicon tree */
       init_voca(winfo, dictfilename, hmminfo, TRUE, forcedict_flag)
#else 
       init_voca(winfo, dictfilename, hmminfo, FALSE, forcedict_flag)
#endif
       ) {
    j_error("ERROR: failed to read dictionary, terminated\n");
  }

#ifdef USE_NGRAM
  /* if necessary, append a IW-sp word to the dict if "-iwspword" specified */
  if (enable_iwspword) {
    if (
#ifdef MONOTREE
	voca_append_htkdict(iwspentry, winfo, hmminfo, TRUE)
#else 
	voca_append_htkdict(iwspentry, winfo, hmminfo, FALSE)
#endif
	== FALSE) {
      j_error("Error: failed to make IW-sp word entry \"%s\"\n", iwspentry);
    } else {
      j_printerr("1 IW-sp word entry added\n");
    }
  }
  /* set {head,tail}_silwid */
  winfo->head_silwid = voca_lookup_wid(head_silname, winfo);
  if (winfo->head_silwid == WORD_INVALID) { /* not exist */
    j_error("ERROR: head sil word \"%s\" not exist in voca", head_silname);
  }
  winfo->tail_silwid = voca_lookup_wid(tail_silname, winfo);
  if (winfo->tail_silwid == WORD_INVALID) { /* not exist */
    j_error("ERROR: tail sil word \"%s\" not exist in voca", tail_silname);
  }
#endif
  
#ifdef PASS1_IWCD
  if (triphone_check_flag && hmminfo->is_triphone) {
    /* go into interactive triphone HMM check mode */
    hmm_check(hmminfo, winfo);
  }
#endif


}


#ifdef USE_NGRAM


/* initialize word N-gram */
static void
initialize_ngram()
{
  /* allocate new */
  ngram = ngram_info_new();
  /* load LM */
  if (ngram_filename != NULL) {	/* binary format */
    init_ngram_bin(ngram, ngram_filename);
  } else {			/* ARPA format */
    init_ngram_arpa(ngram, ngram_filename_lr_arpa, ngram_filename_rl_arpa);
  }

  /* map dict item to N-gram entry */
  make_voca_ref(ngram, winfo);
}

#ifdef CLASS_NGRAM
static void
initialize_class_ngram()
{
  /* allocate new */
  class_ngram = ngram_info_new();
  /* load LM */
  init_ngram_bin(class_ngram, class_bingram_file);
  /* map ordinal N-gram word ID to class ID */
  make_class_ref(class_ngram, winfo);
}  
#endif



#else  /* USE_DFA */


static void
initialize_dfa()
{
  /* allocate new */
  dfa = dfa_info_new();
  /* load DFA grammar */
  init_dfa(dfa, dfa_filename);
  /* the rest preparation is done in multigram.c */
  /* make_dfa_voca_ref(dfa, winfo);
  dfa_find_pause_word(dfa, winfo, hmminfo);
  extract_cpair(dfa);*/
}


#endif /* USE_NGRAM */


/* set params whose default will change by models and not specified in arg */
static void
configure_param()
{
#ifdef USE_NGRAM
  /* set default lm parameter */
  if (!lmp_specified) set_lm_weight();
  if (!lmp2_specified) set_lm_weight2();
  if (lmp_specified != lmp2_specified) {
    j_printerr("Warning: only -lmp or -lmp2 specified, LM weights may be unbalanced\n");
  }
#endif
  /* select Gaussian pruning function */
  if (gprune_method == GPRUNE_SEL_UNDEF) {/* set default if not specified */
    if (hmminfo->is_tied_mixture) {
#ifdef GPRUNE_DEFAULT_SAFE
      gprune_method = GPRUNE_SEL_SAFE;
#elif GPRUNE_DEFAULT_HEURISTIC
      gprune_method = GPRUNE_SEL_HEURISTIC;
#elif GPRUNE_DEFAULT_BEAM
      gprune_method = GPRUNE_SEL_BEAM;
#endif
    } else {
      /* currently, pruning is available for only tied-mixture model */
      gprune_method = GPRUNE_SEL_NONE;
    }
  }
}

/* setup result output function */
void
select_result_output()
{
  switch(result_output) {
  case SP_RESULT_TTY: setup_result_tty(); break; /* in result_tty.c */
  case SP_RESULT_MSOCK: setup_result_msock(); break; /* in result_msock.c */
  default:
    j_printerr("Internal Error: no such result output device: id = %d\n", result_output);
    break;
  }
}



/**********************************************************************/
/* main setup function: read in all models and build datas to prepare for
   recognition.  Should be called after all parameters are prepared.
 */
void
final_fusion()
{
  VERMES("###### build up system\n");

  /* stage 1: load models */
  initialize_HMM();
  if (hmm_gs_filename != NULL) initialize_GSHMM();
  initialize_dict();
#ifdef USE_NGRAM
  initialize_ngram();
#ifdef CLASS_NGRAM
  initialize_class_ngram();
#endif
#else
  initialize_dfa();
#endif

  /* stage 2: fixate params */
  /* set params whose default will change by models and not specified in arg */
  configure_param();

  /* stage 3: build lexicon tree */
#ifdef USE_DFA

  /* regist the initial grams and setup */
  multigram_add(dfa, winfo);
  multigram_exec();

#else  /* ~USE_DFA */

  wchmm = wchmm_new();
  wchmm->ngram = ngram;
  wchmm->winfo = winfo;
  wchmm->hmminfo = hmminfo;
#ifdef CATEGORY_TREE
  if (old_tree_function_flag) {
    build_wchmm(wchmm);
  } else {
    build_wchmm2(wchmm);
  }
#else
  build_wchmm2(wchmm);
#endif /* CATEGORY_TREE */
  /* guess beam width from models, when not specified */
  if (trellis_beam_width <= 0) set_beam_width();
  if (trellis_beam_width > wchmm->n) trellis_beam_width = wchmm->n;

#endif			/* USE_DFA */

#ifdef MONOTREE
  /* after building tree lexocon, */
  /* convert monophone to triphone in winfo for 2nd pass */
  if (hmminfo->is_triphone) {
    j_printerr("convert monophone dictionary to word-internal triphone...");
    if (voca_mono2tri(winfo, hmminfo) == FALSE) {
      j_error("failed\n");
    }
    j_printerr("done\n");
  }
#endif
  
  /* stage 4: setup output function */
  if (hmm_gs_filename != NULL) {/* with GMS */
    outprob_init(hmminfo, hmm_gs, gs_statenum, gprune_method, mixnum_thres);
  } else {
    outprob_init(hmminfo, NULL, 0, gprune_method, mixnum_thres);
  }

  /* stage 5: initialize work area and misc. */
  bt_init(&backtrellis);	/* backtrellis initialization */
#ifdef USE_NGRAM
  max_successor_cache_init(wchmm);	/* initialize cache for factoring */
#endif
  if (realtime_flag) {
    RealTimeInit();		/* prepare for 1st pass pipeline processing */
  }
  /* setup result output function */
  select_result_output();

  /* finished! */
  VERMES("All init successfully done\n\n");
}
