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

/* search_bestfirst_v1.c --- hypothesis handling and scoring functions
   for backscan algorithm */

/* $Id: search_bestfirst_v1.c,v 1.7 2002/10/15 07:17:46 ri Exp $ */

#include <julius.h>
#ifndef PASS2_STRICT_IWCD

#undef TCD			/* enable debug messages */

/**********************************************************************/
/************ 仮説ノードの基本操作                         ************/
/************ basic functions for hypothesis node handling ************/
/**********************************************************************/

/* node を解放 */
/* free node */
void
free_node(NODE *node)
{
  if (node == NULL) return;
  free(node->g);
  if (ccd_flag) free(node->g_prev);

  free(node);
}

/* src から dst へコピー */
/* copy node from 'src' to 'dst' */
NODE *
cpy_node(NODE *dst, NODE *src)
{
  
  dst->next = src->next;
  dst->prev = src->prev;
  memcpy(dst->g, src->g, sizeof(LOGPROB) * peseqlen);
  memcpy(dst->seq, src->seq, sizeof(WORD_ID) * MAXSEQNUM);
#ifdef WORD_ALIGNMENT
  memcpy(dst->seq, src->seq, sizeof(short) * MAXSEQNUM);
#endif  
  dst->seqnum = src->seqnum;
  dst->score = src->score;
  dst->bestt = src->bestt;
  dst->estimated_next_t = src->estimated_next_t;
  dst->endflag = src->endflag;
#ifdef USE_DFA
  dst->state = src->state;
#endif
  dst->tre = src->tre;
  if (ccd_flag) {
    memcpy(dst->g_prev, src->g_prev, sizeof(LOGPROB)*peseqlen);
    dst->last_ph = src->last_ph;
    dst->last_ph_sp_attached = src->last_ph_sp_attached;
#ifdef USE_NGRAM
    dst->lscore = src->lscore;
#endif
  }
#ifdef USE_NGRAM
  dst->totallscore = src->totallscore;
#endif
  dst->final_g = src->final_g;
  return(dst);
}

/* 新たな仮説ノードを割り付け */
/* allocate new node */
NODE *
newnode()
{
  NODE *tmp;
  int i;

  if ((tmp=(NODE *)mymalloc(sizeof(NODE)))==NULL) {
    j_error("can't malloc\n");
  }
  /*bzero(tmp,sizeof(NODE));*/
  tmp->next=NULL;
  tmp->prev=NULL;
  tmp->g = (LOGPROB *)mymalloc(sizeof(LOGPROB)*peseqlen);
  tmp->last_ph = NULL;
  tmp->last_ph_sp_attached = FALSE;
  if (ccd_flag) {
    tmp->g_prev = (LOGPROB *)mymalloc(sizeof(LOGPROB)*peseqlen);
#ifdef USE_NGRAM
    tmp->lscore = LOG_ZERO;
    tmp->totallscore = LOG_ZERO;
#endif
  }
  tmp->endflag = FALSE;
  tmp->seqnum = 0;
  for(i=0;i<peseqlen;i++) {
    tmp->g[i] = LOG_ZERO;
    if (ccd_flag) tmp->g_prev[i] = LOG_ZERO;
  }
  tmp->final_g = LOG_ZERO;

  return(tmp);
}


/**********************************************************************/
/************ 前向きトレリス展開と尤度計算             ****************/
/************ expand trellis and update forward score *****************/
/**********************************************************************/

static LOGPROB **wordtrellis;	/* buffer for computing viterbi of a word */
static LOGPROB *g;
static HMM_Logical **phmmseq;
static int phmmlen_max;
static boolean *has_sp;

/* １単語分のトレリス計算用のワークエリア wordtrellis[t][]を確保 */
/* allocate work area 'wordtrellis[t][]' for trellis computation of a word */
void
malloc_wordtrellis()
{
  int i;
  int maxwn;
  LOGPROB *p;

  maxwn = winfo->maxwn + 10;
  wordtrellis = (LOGPROB **)mymalloc(sizeof(LOGPROB *) * peseqlen);
  p = (LOGPROB *)mymalloc(sizeof(LOGPROB) * peseqlen * maxwn);
  for (i=0;i<peseqlen;i++) {
    wordtrellis[i] = p + i * maxwn;
  }
  wordtrellis[0][maxwn] = 1.0;

  g = (LOGPROB *)mymalloc(sizeof(LOGPROB) * peseqlen);

  phmmlen_max = winfo->maxwlen + 2;
  phmmseq = (HMM_Logical **)mymalloc(sizeof(HMM_Logical *) * phmmlen_max);
  has_sp = (boolean *)mymalloc(sizeof(boolean) * phmmlen_max);
}
/* 上記のワークエリア wordtrellis[t][] を解放 */
/* free the 'wordtrellis[t][]' */
void
free_wordtrellis()
{
  free(wordtrellis[0]);
  free(wordtrellis);
  free(g);
  free(phmmseq);
  free(has_sp);
}

/* 最終状態への遷移の確率の最大値を返す */
/* return the maximum transition log probability to final state */
static LOGPROB
get_max_out_arc(HTK_HMM_Trans *tr, int state_num)
{
  LOGPROB max_a;
  int afrom;
  LOGPROB a;
  
  max_a = LOG_ZERO;
  for (afrom = 0; afrom < state_num - 1; afrom++) {
    a = tr->a[afrom][state_num-1];
    if (max_a < a) max_a = a;
  }
  return(max_a);
}
static LOGPROB
max_out_arc(HMM_Logical *l)
{
  return(get_max_out_arc(hmm_logical_trans(l), hmm_logical_state_num(l)));
}


/**********************************************************************/
/************ 仮説の前向き尤度計算                  *******************/
/************ compute forward score of a hypothesis *******************/
/**********************************************************************/

/* --- 第2パス探索(RL)における単語間triphoneの扱い ---

   この search_bestfirst_v1.c では，デコーディングの高速化のため
   次単語とその前の単語の接続点について
   「単語間音素コンテキストの遅延処理」を行なう:
   (a) 新仮説の生成(next_word())では，次単語の最後の音素の右コンテキストのみ
       が考慮される．
   (b) その単語間の完全な音素環境依存性は，その仮説がいったんスタックに
       入った後もう一度 POP されたときに scan_word() にて改めて計算する．

   仮説生成時にはすべての生成仮説に対して依存計算を行なず，あとでスコアが
   高く POP された仮説についてのみ再計算を行なう．このため処理が高速化される．
   ただし仮説スコア計算(next_word())において次単語接続部分の環境依存性が
   考慮されないので, スコアに若干の誤差が生じる可能性がある．
   
   実装について:
   (1) next_word() では，次単語の最後の音素のみを右コンテキスト(=展開元
       単語の最初の音素)を考慮して変化させ，トレリス接続点の出力確率を求める．
   (2) scan_word() では，新単語部分ともう１つ前の単語の最初の音素を変化
       させ，scan する．そのため新単語部分だけでなく，そのもう一音素前まで
       scan の対象となる．この "1-phoneme backscan" を行なうため,
       各仮説ノードは最終HMM状態の前向きスコア (NODEにおける g[]) だけでなく，
       その backscan 開始点(もう１つ前の単語の最初の音素の手前)のスコア
       も保存しておく必要がある (NODE における g_prev[])．

   なお，１音素のみからなる単語では backscan 開始点と単語境界が重なることを
   考慮する必要があるため，実装はもう少し複雑になる．
 */

/* --- handling of cross-word triphone in 2nd pass search (in RL direction) ---
   
   Here in search_bestfirst_v1.c, we use "delayed cross-word context handling"
   method for connection of next word and last word of the hypothesis
   for speeding up decoding:
   (a) only right context of the tail phone in the next word is considered
       when generating a new hypothesis (next_word()).
   (b) the whole context dependency will be fully computed when the
       hypothesis is once pushed to stack and later popped in scan_word().
   This method avoid computing full context-dependency handling for all
   generated hypothesis in next_word(), and only re-compute it after
   primising ones are popped from stack later.  This speeds up decoding.
   But since the context dependency is not considered in the total hypothesis
   score (computed in next_word()).

   To realize this,
   (1) in nextword(), the tail phone in the new word is modified considering
       the right context (= head phone in the last word of source hypothesis),
       and the outprob on the connection point between backtrellis and forward
       trellis is computed using the triphone.
   (2) In scan_word(), not only the new word but also the head phone in the
       previous word should be modified and re-scanned.
       To realize this '1-phoneme backscan' procedure, hypothesis nodes
       have to keep forward scores not only at the last HMM state (g[] in
       NODE), but also at the backscan restart point (= before the head
       phone in the previous word, g_prev[] in NODE).
       
   Note that the actual implementation becomes a little more complicated
   to handle 1-phoneme words...

*/

/* 文仮説の前向き尤度を更新する
   最後の1単語の前向きトレリスを計算する */
/* compute forward score by expanding trellis of the last word */
void
scan_word(NODE *now, HTK_Param *param)
{
  int   i,t;
  HMM *whmm;
  A_CELL *ac;
  WORD_ID word;
  LOGPROB tmpmax, tmptmp, score1;
  int startt = 0;
  int wordhmmnum;
  LOGPROB tmpmax_store, store_point_maxarc;
  int phmmlen;
  HMM_Logical *ret, *wend;
  int store_point = -1;
  int crossword_point = 0;
  boolean back_rescan = FALSE;
  boolean node_exist_p;
  
  /* ----------------------- prepare HMM ----------------------- */

  if (ccd_flag) {
    /* 直前の音素があれば，そこまでさかのぼって scan する */
    /* if there are any last phone, enable backscan */
    if (now->last_ph == NULL) {
      /* initial score: now->g[] */
      /* scan range: phones in now->seq[now->seqnum-1] */
      back_rescan = FALSE;
    } else {
      /* initial score: now->g_prev[] (1-phone before)*/
      /* scan range: phones in now->seq[now->seqnum-1] + now->last_ph */
      back_rescan = TRUE;
    }
  }
#ifdef TCD
  if (now->last_ph != NULL) {
    j_printf("inherited last_ph: %s", (now->last_ph)->name);
    if (now->last_ph_sp_attached) j_printf(" (sp attached)");
    j_printf("\n");
  } else {
    j_printf("no last_ph inherited\n");
  }
#endif

  /* scan 範囲分のHMMを準備 */
  /* prepare HMM of the scan range */
  word = now->seq[now->seqnum-1];

  if (ccd_flag) {

    if (back_rescan) {
      
      /* scan range: phones in now->seq[now->seqnum-1] + now->last_ph */
      
      phmmlen = winfo->wlen[word] + 1;
      if (phmmlen > phmmlen_max) {
	j_error("short of phmmlen\n");
      }
      for (i=0;i<phmmlen - 2;i++) {
	phmmseq[i] = winfo->wseq[word][i];
	has_sp[i] = FALSE;
      }
      /* 最終単語と last_ph 間の単語間triphoneを考慮 */
      /* consider cross-word context dependency between the last word and now->last_ph */
      wend = winfo->wseq[word][winfo->wlen[word]-1];
      ret = get_right_context_HMM(wend, now->last_ph->name, hmminfo);
      if (ret == NULL) {	/* triphone not found */
	/* fallback to the original bi/mono-phone */
	/* error if the original is pseudo phone (not explicitly defined
	   in hmmdefs/hmmlist) */
	/* exception: word with 1 phone (triphone may exist in the next expansion */
	if (winfo->wlen[word] > 1 && wend->is_pseudo) {
	  error_missing_right_triphone(wend, now->last_ph->name);
	}
	phmmseq[phmmlen-2] = wend;
      } else {
	phmmseq[phmmlen-2] = ret;
      }
     
      ret = get_left_context_HMM(now->last_ph, wend->name, hmminfo);
      if (ret == NULL) {
	/* fallback to the original bi/mono-phone */
	/* error if the original is pseudo phone (not explicitly defined
	   in hmmdefs/hmmlist) */
	if (now->last_ph->is_pseudo) {
	  error_missing_left_triphone(now->last_ph, wend->name);
	}
	phmmseq[phmmlen-1] = now->last_ph;
      } else {
	phmmseq[phmmlen-1] = ret;
      }

      has_sp[phmmlen-2] = has_sp[phmmlen-1] = FALSE;
      if (enable_iwsp) {
	has_sp[phmmlen-2] = TRUE;
	if (now->last_ph_sp_attached) {
	  has_sp[phmmlen-1] = TRUE;
	}
      }


#ifdef TCD
      j_printf("w=");
      for(i=0;i<winfo->wlen[word];i++) {
	j_printf(" %s",(winfo->wseq[word][i])->name);
	if (has_sp[i]) j_printf("(sp)");
      }
      j_printf(" | %s", (now->last_ph)->name);
      if (now->last_ph_sp_attached) j_printf("(sp)");
      j_printf("\nscan for:");
      for (i=0;i<phmmlen;i++) {
	j_printf(" %s", phmmseq[i]->name);
	if (has_sp[i]) j_printf("(sp)");
      }
      j_printf("\n");
#endif

      /* 単語HMMを作る */
      /* make word HMM */
      whmm = new_make_word_hmm(hmminfo, phmmseq, phmmlen, has_sp);
      
      /* backscan なので，計算前の g[] 初期値は now->g_prev[] を使用 */
      /* As backscan enabled, the initial forward score g[] is set by
	 now->g_prev[] */
      for (t=0;t<peseqlen;t++) {
	g[t]=now->g_prev[t];

      }
      
      /* 次段用のg_prevを格納するノード位置を設定 */
      /* set where to store scores as new g_prev[] for the next backscan
	 in the HMM */
      store_point = hmm_logical_state_num(phmmseq[0]) - 2;
      store_point_maxarc = max_out_arc(phmmseq[0]);
      if (enable_iwsp && has_sp[0]) {
	store_point += hmm_logical_state_num(hmminfo->sp) - 2;
	if (store_point_maxarc < max_out_arc(hmminfo->sp)) {
	  store_point_maxarc = max_out_arc(hmminfo->sp);
	}
      }

      /* scan中に直前単語とこの単語をまたぐ場所を設定 */
      /* set where is the connection point of the last word in the HMM */
      crossword_point = whmm->len - hmm_logical_state_num(phmmseq[phmmlen-1]);
      if (enable_iwsp && has_sp[phmmlen-1]) {
	crossword_point -= hmm_logical_state_num(hmminfo->sp) - 2;
      }
      
    } else {			/* not backscan mode */
      
      /* scan range: phones in now->seq[now->seqnum-1] */
      
#ifdef TCD
      j_printf("scan(org):");
      for (i=0;i<winfo->wlen[word];i++) {
	j_printf(" %s", (winfo->wseq[word][i])->name);
      }
      j_printf("\n");
#endif
      
      /* 単語HMMを作る */
      /* make word HMM */
      for(i=0;i<winfo->wlen[word];i++) {
	has_sp[i] = FALSE;
      }
      if (enable_iwsp) {
	has_sp[winfo->wlen[word]-1] = TRUE;
      }
     
      whmm = new_make_word_hmm(hmminfo, winfo->wseq[word], winfo->wlen[word], has_sp);
      
      /* 計算前の g[] 初期値は now->g[] を使用 */
      /* the initial forward score g[] is set by now->g[] */
      for (t=0;t<peseqlen;t++) {
	g[t]=now->g[t];
      }
      
      /* 次段用のg_prevを格納するノード位置を設定 */
      /* set where to store scores as new g_prev[] for the next backscan
	 in the HMM */
      store_point = hmm_logical_state_num(winfo->wseq[word][0]) - 2;
      store_point_maxarc = max_out_arc(winfo->wseq[word][0]);
      if (enable_iwsp && has_sp[0]) {
	store_point += hmm_logical_state_num(hmminfo->sp) - 2;
	if (store_point_maxarc < max_out_arc(hmminfo->sp)) {
	  store_point_maxarc = max_out_arc(hmminfo->sp);
	}
      }

      /* scan中に直前単語とこの単語をまたぐ場所は，なし */
      /* the connection point of the last word is not exist in the HMM */
      crossword_point = -1;
    }
    
  } else {			/* ccd_flag == FALSE */

    /* 音素環境非依存の場合は単純に最終単語分の HMM を作成 */
    /* for monophone: simple make HMM for the last word */
    for(i=0;i<winfo->wlen[word];i++) {
      has_sp[i] = FALSE;
    }
    if (enable_iwsp) {
      has_sp[winfo->wlen[word]-1] = TRUE;
    }
    whmm = new_make_word_hmm(hmminfo, winfo->wseq[word], winfo->wlen[word], has_sp);

    /* 計算前の g[] 初期値は now->g[] を使用 */
    /* the initial forward score g[] is set by now->g[] */
    for (t=0;t<peseqlen;t++) {
      g[t]=now->g[t];
    }
    
  }

#ifdef TCD
  j_printf("whmm len	  = %d\n",whmm->len);
  j_printf("crossword_point = %d\n", crossword_point);
  j_printf("g[] store point = %d\n", store_point);
#endif

  wordhmmnum = whmm->len;
  if (wordhmmnum >= winfo->maxwn + 10) {
    j_error("scan_word: word too long\n");
  }

  /* ----------------------- do scan ----------------------- */
#if 0
  printf("-------\n");
  for(t=0;t<peseqlen;t++) {
    printf("%d %f %f\n",t,framemaxscore[t],g[t]);
  }
  printf("-------\n");
#endif
  
  /* scan開始点を検索 -> starttへ*/
  /* search for the start frame -> set to startt */
  for(t = peseqlen-1; t >=0 ; t--) {
    if (
#ifdef SCAN_BEAM
	g[t] > framemaxscore[t] - scan_beam_thres &&
#endif
	g[t] > LOG_ZERO) {
      break;
    }
  }
  if (t < 0) {			/* no node has score > LOG_ZERO */
    for(t=0;t<peseqlen;t++) {
      if (ccd_flag) now->g_prev[t] = LOG_ZERO;
      now->g[t] = LOG_ZERO;
    }
    goto end_of_scan;
  }
  startt = t;
  
  /* clear [startt+1..peseqlen-1] */
  for(t=peseqlen-1;t>startt;t--) {
    if (ccd_flag) now->g_prev[t] = LOG_ZERO;
    now->g[t] = LOG_ZERO;
  }
  
  /* メインループ: startt から始まり 0 に向かって Viterbi 計算 */
  /* main loop: start from [startt], and compute Viterbi toward [0] */
  for(t=startt;t>=0;t--) {
    
    node_exist_p = FALSE;	/* TRUE if there is at least 1 survived node in this frame */

    /* 端のノード [t][wordhmmnum-1]は g[] を参照する */
    /* the edge node [t][wordhmmnum-1] is equal to g[] */

    /* ノード [t][wordhmmnum-2..0] についてトレリスを計算 */
    /* expand trellis for node [t][wordhmmnum-2..0] */
    tmpmax_store = LOG_ZERO;
    
    for(i=wordhmmnum-2;i>=0;i--) {

      if (ccd_flag) {

	/* 最尤パスと最尤スコア tmpmax を見つける */
	/* tmpmax2 は次回用 g_prev[] のための最大値(自己遷移を除いた最大値) */
	/* find most likely path and the max score 'tmpmax' */
	/* 'tmpmax2' is max score excluding self transition, for next g_prev[] */
	tmpmax = LOG_ZERO;
	for (ac=whmm->state[i].ac;ac;ac=ac->next) {
	  if (ac->arc == wordhmmnum-1) score1 = g[t];
	  else if (t + 1 > startt) score1 = LOG_ZERO;
	  else score1 = wordtrellis[t+1][ac->arc];
	  score1 += ac->a;
	  if (i <= crossword_point && ac->arc > crossword_point) {
	    /* これは単語を越える遷移 (backscan 実行時) */
	    /* this is a transition across word (when backscan is enabled) */
#ifdef USE_NGRAM
	    score1 += now->lscore; /* add N-gram LM score */
#else
	    score1 += penalty2;	/* add DFA insertion penalty */
#endif
	  }
	  if (tmpmax < score1) tmpmax = score1;
	  if (i <= store_point && ac->arc > store_point) {
	    if (tmpmax_store < score1) tmpmax_store = score1;
	  }
	}

	/* スコアエンベロープチェック: 一定幅外なら落とす */
	/* check if score of this node is within the score envelope */
	if (
#ifdef SCAN_BEAM
	    tmpmax <= framemaxscore[t] - scan_beam_thres ||
#endif
	    tmpmax <= LOG_ZERO
	    ) {  /* invalid node */
	  wordtrellis[t][i] = LOG_ZERO;
	} else { /* survived node */
	  node_exist_p = TRUE;	/* at least one node survive in this frame */
	  wordtrellis[t][i] = tmpmax;
	  if (i > 0) {
	    /* compute output probability */
	    wordtrellis[t][i] += outprob(t, &(whmm->state[i]), param);
	  }
	}
	
      } else {			/* not triphone */

	/* backscan 無し: store_point, crossword_point は無関係 */
	/* no backscan: store_point, crossword_point ignored */
	tmpmax = LOG_ZERO;
	for (ac=whmm->state[i].ac;ac;ac=ac->next) {
	  if (ac->arc == wordhmmnum-1) score1 = g[t];
	  else if (t + 1 > startt) score1 = LOG_ZERO;
	  else score1 = wordtrellis[t+1][ac->arc];
	  score1 += ac->a;
	  if (tmpmax < score1) tmpmax = score1;
	}

	/* スコアエンベロープチェック: 一定幅外なら落とす */
	/* check if score of this node is within the score envelope */
	if (
#ifdef SCAN_BEAM
	    tmpmax <= framemaxscore[t] - scan_beam_thres ||
#endif
	    tmpmax <= LOG_ZERO
	    ) {
	  /* invalid node */
	  wordtrellis[t][i] = LOG_ZERO;
	} else {
	  /* survived node */
	  node_exist_p = TRUE;
	  /* score of node [t][i] has been determined here */
	  wordtrellis[t][i] = tmpmax;
	  if (i > 0) {
	    /* compute output probability */
	    wordtrellis[t][i] += outprob(t, &(whmm->state[i]), param);
	  } 
	}
	
      }
    } /* end of node loop */

    /* 時間 t のViterbi計算終了．前向きスコアはscanした単語の始端 */
    /* Viterbi end for frame [t].  the forward score is the score of word
       beginning scanned */
    now->g[t] = wordtrellis[t][0];

    /* triphone 時, 次段のために store_point のデータをg_prevに保存 */
    /* store the scores crossing the store_point to g_prev, for next scan */
    if (ccd_flag) {
      /* the max arc crossing the store_point always selected as tmpmax_score */ 
      tmpmax_store -= store_point_maxarc;
      if (tmpmax_store < LOG_ZERO) tmpmax_store = LOG_ZERO;
      now->g_prev[t] = tmpmax_store;
    }

    /* scanした単語の第１パスでの始端時刻より先まで t が進んでおり，かつ
       この t においてスコアエンベロープによって生き残ったノードが一つも
       無かったならば,このフレームで計算を打ち切りそれ以上先([0..t-1])は
       計算しない */
    /* if frame 't' already reached the beginning frame of scanned word
       in 1st pass and no node was survived in this frame (all nodes pruned
       by score envelope), terminate computation at this frame and
       do not computer further frame ([0..t-1]). */
    if (t < now->estimated_next_t && (!node_exist_p)) {
      /* clear the rest scores */
      for (i=t-1;i>=0;i--) {
	now->g[i] = LOG_ZERO;
	if (ccd_flag) now->g_prev[i] = LOG_ZERO;
      }
      /* terminate loop */
      break;
    }
    
  } /* end of time loop */
  
  if (debug2_flag) j_printf("scanned: [%3d-%3d]\n",t+1,startt);

 end_of_scan:
  /* 前向きスコアの最終値を計算 (状態 0 から時間 0 への遷移) */
  /* compute the total forward score (transition from state 0 to frame 0 */
  tmpmax = LOG_ZERO;
  for(ac=whmm->state[0].ac;ac;ac=ac->next) {
    score1 = wordtrellis[0][ac->arc] + ac->a;
    if (tmpmax < score1) tmpmax = score1;
  }
  now->final_g = score1;

  /* 次回 backscan のための情報格納 */
  /* store data for next backscan */
  if (ccd_flag) {
    if (store_point == wordhmmnum - 2) {
      /* last_ph無し，かつ単語の音素長=1の場合、次回の scan_word() で
	 単語全体がもう一度再計算される．この場合,
	 g_prev は，このscan_wordを開始する前のスコアを入れておく必要がある */
      /* if there was no 'last_ph' and the scanned word consists of only
	 1 phone, the whole word should be re-computed in the future scan_word().
	 So the next 'g_prev[]' should be the initial forward scores
	 before we begin Viterbi (= g[t]). */
      for (t = startt; t>=0; t--) {
	now->g_prev[t] = g[t];
      }
    }
    /* 次回のために now->last_ph を更新 */
    /* update 'now->last_ph' for future scan_word() */
    if (back_rescan) {
      now->last_ph = phmmseq[0];
    } else {
      now->last_ph = winfo->wseq[word][0];
    }
    now->last_ph_sp_attached = has_sp[0];
  }

  /* free work area */
  free_hmm(whmm);
#ifdef TCD
  if (ccd_flag) {
    j_printf("last_ph = %s", (now->last_ph)->name);
    if (now->last_ph_sp_attached) j_printf(" (sp attached)");
    j_printf("\n");
  }
#endif
}


/**************************************************************************/
/*** 新仮説の展開とヒューリスティックを繋いだ全体スコアを計算           ***/
/*** expand new hypothesis and compute the total score (with heuristic) ***/
/**************************************************************************/

/* now に次単語 nword を接続して新しい仮説 new を生成し，その
   (ヒューリスティックを含む)仮説スコアを求める */
/* generate a new hypothesis 'new' by connecting next word 'nword' to
   the source hypothesis 'now', and compute the total score of 'new' with
   heuristics */
void
next_word(NODE *now, NODE *new,	NEXTWORD *nword, HTK_Param *param, BACKTRELLIS *backtrellis)
{
  int   t;
  HMM_Logical *newphone;
  int lastword;
  int   i;
  int   startt;
  int word;
  LOGPROB totalscore, tmpp;
  TRELLIS_ATOM *tre;

  new->score = LOG_ZERO;

  word = nword->id;
  lastword=now->seq[now->seqnum-1];

  /* 単語並び、DFA状態番号、言語スコアを継承・更新 */
  /* inherit and update word sequence, DFA state and total LM score */
  for (i=0;i< now->seqnum;i++){	
    new->seq[i] = now->seq[i];
  }
  new->seq[i] = word;
  new->seqnum = now->seqnum+1;
#ifdef USE_DFA
  new->state = nword->next_state;
#endif
#ifdef USE_NGRAM
  new->totallscore = now->totallscore + nword->lscore;
#endif
  new->final_g = now->final_g;

  if (ccd_flag) {
    
    /* 展開単語の接続点の音素HMMをnewphoneにセットする．
       元仮説 now との単語間の音素環境依存性を考慮する */
    /* set the triphone at the connection point to 'newphone', considering
       cross-word context dependency to 'now' */
    newphone = get_right_context_HMM(winfo->wseq[word][winfo->wlen[word]-1], now->last_ph->name, hmminfo);
    if (newphone == NULL) {	/* triphone not found */
      /* fallback to the original bi/mono-phone */
      /* error if the original is pseudo phone (not explicitly defined
	 in hmmdefs/hmmlist) */
      /* exception: word with 1 phone (triphone may exist in the next expansion */
      if (winfo->wlen[word] > 1 && winfo->wseq[word][winfo->wlen[word]-1]->is_pseudo){
	error_missing_right_triphone(winfo->wseq[word][winfo->wlen[word]-1], now->last_ph->name);
      }
      newphone = winfo->wseq[word][winfo->wlen[word]-1];
    }
    
    /* 元仮説をscanした時の末端音素HMM -> 新仮説の直前音素HMM */
    /* inherit last_ph */
    new->last_ph = now->last_ph;
    new->last_ph_sp_attached = now->last_ph_sp_attached;
    
#ifdef USE_NGRAM
    /* set current LM score */
    new->lscore = nword->lscore;
#endif
    
    /* backscan用接続ポイントのスコア g_prev[] をコピー */
    /* copy g_prev[] that are scores at backscan connection point */
    for (t=0;t<peseqlen;t++) {
      new->g_prev[t] = now->g_prev[t];
    }
    
  } else {			/* not triphone */
    
    /* 展開単語の接続(=終端)の音素HMMをnewphoneにセット */
    /* set the phone at the connection point to 'newphone' */
    newphone = winfo->wseq[word][winfo->wlen[word]-1];
  }

  /***************************************************************************/
  /* 前向き(第２パス),後ろ向き(第１パス)トレリスを接続し最尤接続点を見つける */
  /* connect forward/backward trellis to look for the best connection time   */
  /***************************************************************************/
  
  startt = peseqlen-1;

#ifdef WORD_GRAPH
  /*-----------------------------------------------------------------*/
  /* 単語グラフモードでは同じフレームの同じ単語でも直前単語毎にスコ
     アが違う．bestt は 固定 */
  /* there are several trellis word with the same word ID in the same
     frame, with different word context in WORD_GRAPH mode.  And the
     best t is fixed (no re-esimation) */
  /*-----------------------------------------------------------------*/

  /* update new->g[t] */
  for(t=startt;t>=0;t--) {
    new->g[t] = now->g[t]
#ifdef USE_NGRAM
      + nword->lscore
#else
      + penalty2
#endif
      ;
  }
  
  /* 最尤接続点は, 最後に参照したTRELLIS_ATOMの終端に固定 */
  /* the best connection point is fixed to the end time of last trellis word (no re-alignment */
  new->bestt = (nword->tre)->endtime;
  /* 次回の展開単語決定のための推定始端時間は，上記の始端 */
  /* the estimated next beginning frame for the next word expansion
     corresponds to the endtime above (this is also fixed) */
  new->estimated_next_t = (nword->tre)->begintime - 1;

  /* 出力確率を tmpp に計算 */
  /* compute output prob to 'tmpp' */
#if 0
  if (newphone->is_pseudo) {
    tmpp = outprob_cd(new->bestt, &(newphone->body.pseudo->stateset[newphone->body.pseudo->state_num-2]), param);
  } else {
    tmpp = outprob_state(new->bestt, newphone->body.defined->s[newphone->body.defined->state_num-2], param);
  }
#endif

  /* 仮説全体のスコアを計算 */
  new->score = new->g[new->bestt] + (nword->tre)->backscore /*+ tmpp*/;
  
  /* 展開単語のトレリス上の情報を伝搬 */
  new->tre  =  nword->tre;
  
#else  /* not WORD_GRAPH */

  /*-----------------------------------------------------------------*/
  /* 単語トレリスを探して, 次単語の最尤接続点を発見する */
  /* determine the best connection time of the new word, seeking the word
     trellis */
  /*-----------------------------------------------------------------*/

  /* update new->g[t] */
  for(t=startt;t>=0;t--) {
    new->g[t] = now->g[t]
#ifdef USE_NGRAM
      + nword->lscore
#else
      + penalty2
#endif
      ;
  }

#ifdef USE_DFA
  if (!looktrellis_flag) {
    /* すべてのフレームにわたって最尤を探す */
    /* search for best trellis word throughout all frame */
    for(t = startt; t >= 0; t--) {
      tre = bt_binsearch_atom(backtrellis, t, (WORD_ID) word);
      if (tre == NULL) continue;
#if 0
      if (newphone->is_pseudo) {
	tmpp = outprob_cd(t, &(newphone->body.pseudo->stateset[newphone->body.pseudo->state_num-2]), param);
      } else {
	tmpp = outprob_state(t, newphone->body.defined->s[newphone->body.defined->state_num-2], param);
      }
#endif
      totalscore = new->g[t] + tre->backscore /*+ tmpp*/;
      if (new->score < totalscore) {
	new->score = totalscore;
	new->bestt = t;
	new->estimated_next_t = tre->begintime - 1;
      }
    }
  } else {
#endif /* USE_DFA */
      
  /* この展開単語のトレリス上の終端時間の前後のみスキャンする
     前後に連続して存在するフレームについてのみ計算 */
  /* search for best trellis word only around the estimated time */
  /* 1. search forward */
  for(t = (nword->tre)->endtime; t >= 0; t--) {
    tre = bt_binsearch_atom(backtrellis, t, (WORD_ID) word);
    if (tre == NULL) break;	/* go to 2 if the trellis word disappear */
#if 0
    if (newphone->is_pseudo) {
      tmpp = outprob_cd(t, &(newphone->body.pseudo->stateset[newphone->body.pseudo->state_num-2]), param);
    } else {
      tmpp = outprob_state(t, newphone->body.defined->s[newphone->body.defined->state_num-2], param);
    }
#endif
    totalscore = new->g[t] + tre->backscore /*+ tmpp*/;
    if (new->score < totalscore) {
      new->score = totalscore;
      new->bestt = t;
      new->estimated_next_t = tre->begintime - 1;
    }
  }
  /* 2. search backward */
  for(t = (nword->tre)->endtime + 1; t <= startt; t++) {
    tre = bt_binsearch_atom(backtrellis, t, (WORD_ID) word);
    if (tre == NULL) break;	/* end if the trellis word disapper */
#if 0
    if (newphone->is_pseudo) {
      tmpp = outprob_cd(t, &(newphone->body.pseudo->stateset[newphone->body.pseudo->state_num-2]), param);
    } else {
      tmpp = outprob_state(t, newphone->body.defined->s[newphone->body.defined->state_num-2], param);
    }
#endif
    totalscore = new->g[t] + tre->backscore /*+ tmpp*/;
    if (new->score < totalscore) {
      new->score = totalscore;
      new->bestt = t;
      new->estimated_next_t = tre->begintime - 1;
    }
  }

#ifdef USE_DFA
  }
#endif
  
#endif /* WORD_GRAPH */

}

/**********************************************************************/
/********** 初期仮説の生成                 ****************************/
/********** generate an initial hypothesis ****************************/
/**********************************************************************/
void
start_word(
     NODE *new,
     NEXTWORD *nword,
     HTK_Param *param,
     BACKTRELLIS *backtrellis)
{
  HMM_Logical *newphone;
  WORD_ID word;
  TRELLIS_ATOM *tre = NULL;
  LOGPROB tmpp;
  int t;

  /* initialize data */
  word = nword->id;
  new->score = LOG_ZERO;
  new->seqnum = 1;
  new->seq[0] = word;

#ifdef USE_DFA
  new->state = nword->next_state;
#endif
#ifdef USE_NGRAM
  new->totallscore = nword->lscore;
#endif  

  /* cross-word triphone handling is not needed on startup */
  newphone = winfo->wseq[word][winfo->wlen[word]-1];
  if (ccd_flag) {
    new->last_ph = NULL;
    new->last_ph_sp_attached = FALSE;
#ifdef USE_NGRAM
    new->lscore = nword->lscore;
#endif
  }
  
#ifdef USE_NGRAM
  new->g[peseqlen-1] = nword->lscore;
#else  /* USE_DFA */
  new->g[peseqlen-1] = 0;
#endif
  
#ifdef WORD_GRAPH
#if 0
  if (newphone->is_pseudo) {
    tmpp = outprob_cd(peseqlen-1, &(newphone->body.pseudo->stateset[newphone->body.pseudo->state_num-2]), param);
  } else {
    tmpp = outprob_state(peseqlen-1, newphone->body.defined->s[newphone->body.defined->state_num-2], param);
  }
#endif
  
  new->score = new->g[peseqlen-1] + (nword->tre)->backscore /*+ tmpp*/;
  new->tre  =  nword->tre;
  new->bestt = peseqlen-1;
  new->estimated_next_t = (nword->tre)->begintime - 1;
  
#else

  for (t=peseqlen-1; t>=0; t--) {
    tre = bt_binsearch_atom(backtrellis, t, word);
    if (tre != NULL) {
      new->bestt = t;
#if 0
      if (newphone->is_pseudo) {
	tmpp = outprob_cd(peseqlen-1, &(newphone->body.pseudo->stateset[newphone->body.pseudo->state_num-2]), param);
      } else {
	tmpp = outprob_state(peseqlen-1, newphone->body.defined->s[newphone->body.defined->state_num-2], param);
      }
#endif 
      new->score = new->g[peseqlen-1] + tre->backscore /*+ tmpp*/;
      new->estimated_next_t = tre->begintime - 1;
      break;
    }
  }
  if (tre == NULL) {		/* no word in backtrellis */
    new->score = LOG_ZERO;
  }
#endif /* WORD_GRAPH */
}



/* 最終仮説のスコア計算 */
/* generate final hypothesis from 'now' to 'new' */
void
last_next_word(NODE *now, NODE *new, HTK_Param *param)
{
  cpy_node(new, now);
  /* 最終スコアを設定 */
  /* update the final score */
  new->score = now->final_g;
}


#endif /* PASS2_STRICT_IWCD */
