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

/* paramselect.c --- select which parameter component to use */

/* $Id: paramselect.c,v 1.2 2002/09/11 22:01:50 ri Exp $ */

/*                   example1: MFCC_E_D_Z (26)-> MFCC_E_D_N_Z (25)*/
/*                   example2: MFCC_E_D_A (39)-> MFCC_E_D (26) */
/* NOTE: can only exclude part of vectors, can't generate new one */

#include <sent/stddefs.h>
#include <sent/htk_param.h>
#include <sent/htk_hmm.h>

/*
   F_ENERGY     0x0040      _E log energy coef. 
   F_ENERGY_SUP 0x0080      _N (with _E) suppress absolute energy 
   F_DELTA      0x0100      _D delta (first-order regression) coef. 
   F_ACCL       0x0200      _A (with _D) acceleration (second-order) coef. 
   F_COMPRESS   0x0400      _C compressed 
   F_CEPNORM    0x0800      _Z cepstral mean normalization 
   F_CHECKSUM   0x1000      _K CRC checksum added 
   F_ZEROTH     0x2000      _0 (with MFCC) 0'th cepstral parameter
   */



/* selection algorithm --- mark-based: */
/* 1. initialize (=0) mark for each vector element */
/* 2. compare parameter type which is present and needed, and */
/*    mark each element which is not needed as EXCLUDE(=1) */
/* 3. allocate new parameter area and copy only NOT marked element */
/* '-DDEBUG' displays the process on stdout */

/* vector mark & delete */
static int *vmark;		/* mark vector */
static int vlen;		/* length of above */
static int vnewlen;		/* selected new length */

/* initialize vmark */
static void
init_mark(param)
     HTK_Param *param;
{
  int i;
  vmark = (int *)mymalloc(sizeof(int) * param->veclen);
  vlen = param->veclen;
  for (i=0;i<vlen;i++) {
    vmark[i] = 0;
  }
}
/* free vmark */
static void
free_mark()
{
  free(vmark);
}

/* mark part of vectors ([loc..(loc+len-1)] as exclude) */
static void
mark_exclude_vector(loc, len)
     int loc;
     int len;
{
  int i;
#ifdef DEBUG
  j_printf("delmark: %d-%d\n",loc, loc+len-1);
#endif
  for (i=0;i<len;i++) {
    if (loc + i >= vlen) {
      j_error("delmark buffer exceeded!!\n");
    }
    vmark[loc+i] = 1;
  }
#ifdef DEBUG
  j_printf("now :");
  for (i=0;i<vlen;i++) {
    if (vmark[i] == 1) {
      j_printf("-");
    } else {
      j_printf("O");
    }
  }
  j_printf("\n");
#endif
}

/* execute exclusion */
/* copy vectors from src to new according to vmark status */
static void
exec_exclude_vectors(new, src)
     HTK_Param *new;
     HTK_Param *src;
{
  int i,loc, t;
  /* src ---(vmark)--> new */

  /* new length */
  new->veclen = vnewlen;
#ifdef DEBUG
  j_printf("new length = %d\n", new->veclen);
#endif

  /* malloc */
  new->samplenum = src->samplenum;
  new->parvec = (VECT **)mymalloc(sizeof(VECT *) * new->samplenum);
  
  for(t = 0; t < src->samplenum; t++) {
    new->parvec[t] = (VECT *)mymalloc(sizeof(VECT) * new->veclen);
    loc = 0;
    for (i=0;i<src->veclen;i++) {
      if (vmark[i] == 0) {	/* not delete == copy */
	new->parvec[t][loc] = src->parvec[t][i];
	loc++;
      }
    }
  }
}

/* for in-line one-vector execution */
/* modify the vector, not copy */
int
exec_exclude_one_vector(VECT *vec, int len)
{
  int i,loc;
  
  loc = 0;
  for (i=0;i<len;i++) {
    if (vmark[i] == 0) {	/* not delete == copy */
      vec[loc] = vec[i];
      loc++;
    }
  }
  return(loc);
}



/* guess base parameter vector length */
int
guess_basenum(HTK_Param *p, short qualtype)
{
  int size;
  int compnum;
  
  compnum = 1 + ((qualtype & F_DELTA) ? 1 : 0) + ((qualtype & F_ACCL) ? 1 : 0);
  
  size = p->veclen;
  if (p->header.samptype & F_ENERGY_SUP) size += 1;
  if ((size % compnum) != 0) {
    j_printerr("ERROR: illegal vector length (should not happen)!\n");
    return -1;
  }
  size /= compnum;
  if (p->header.samptype & F_ENERGY) size -= 1;
  if (p->header.samptype & F_ZEROTH) size -= 1;

  return(size);
}

/* compare source param type and required type in HTK format, and set mark */
/* can add: _N */
/* can sub: _E_D_A_0 */
static short src_type;
boolean
select_param_vmark(HTK_Param *src, short dst_type_arg)
{
  short dst_type;
  short del_type, add_type;
  int basenum, pb[3],pe[3],p0[3]; /* location */
  int i, len;
  char srcstr[80], dststr[80], buf[80];

  src_type = src->header.samptype & ~(F_COMPRESS | F_CHECKSUM);
  src_type &= ~(F_BASEMASK);	/* only qualifier code needed */
  srcstr[0] = '\0';
  param_qualcode2str(srcstr, src_type, FALSE);
  dst_type = dst_type_arg & ~(F_COMPRESS | F_CHECKSUM);
  dst_type &= ~(F_BASEMASK);	/* only qualifier code needed */
  dststr[0] = '\0';
  param_qualcode2str(dststr, dst_type, FALSE);

#ifdef DEBUG
  j_printf("try to select qualifiers: %s -> %s\n", srcstr, dststr);
#endif

  if (dst_type == F_ERR_INVALID) {
    j_printerr("ERROR: unknown parameter kind for selection: %s\n", dststr);
    return(FALSE);
  }
  
  /* guess base coefficient num */
  basenum = guess_basenum(src, src_type);
  if (basenum < 0) {		/* error */
    return(FALSE);
  }
#ifdef DEBUG
  j_printf("base num = %d\n", basenum);
#endif

  /* determine which component to use */
  del_type = src_type & (~(dst_type));
  add_type = (~(src_type)) & dst_type;

  init_mark(src);

  /* vector layout for exclusion*/
  pb[0] = 0;
  if ((src_type & F_ENERGY) && (src_type & F_ZEROTH)){
    p0[0] = basenum;
    pe[0] = basenum + 1;
    len = basenum + 2;
  } else if ((src_type & F_ENERGY) || (src_type & F_ZEROTH)){
    p0[0] = pe[0] = basenum;
    len = basenum + 1;
  } else {
    p0[0] = pe[0] = 0;
    len = basenum;
  }
  for (i=1;i<3;i++) {
    pb[i] = pb[i-1] + len;
    pe[i] = pe[i-1] + len;
    p0[i] = p0[i-1] + len;
  }
  if (src_type & F_ENERGY_SUP) {
    pe[0] = 0;
    for (i=1;i<3;i++) {
      pb[i]--;
      pe[i]--;
      p0[i]--;
    }
  }
  
  /* modification begin */
  /* qualifier addition: "_N" */
#ifdef DEBUG
  buf[0] = '\0';
  j_printf("try to add: %s\n", param_qualcode2str(buf, add_type, FALSE));
#endif
  
  if (add_type & F_ENERGY_SUP) {
    if (src_type & F_ENERGY) {
      mark_exclude_vector(pe[0], 1);
      src_type = src_type | F_ENERGY_SUP;
    } else if (src_type & F_ZEROTH) {
      mark_exclude_vector(p0[0], 1);
      src_type = src_type | F_ENERGY_SUP;
    } else {
      j_printerr("WARNING: \"_N\" needs \"_E\" or \"_0\". ignored\n");
    }
    add_type = add_type & (~(F_ENERGY_SUP)); /* set to 0 */
  }
  if (add_type != 0) {		/* others left */
    buf[0] = '\0';
    j_printerr("WARNING: can do only parameter exclusion. qualifiers %s ignored\n", param_qualcode2str(buf, add_type, FALSE));
  }
  
  /* qualifier excludeion: "_D","_A","_0","_E" */
#ifdef DEBUG
  buf[0] = '\0';
  j_printf("try to del: %s\n", param_qualcode2str(buf, del_type, FALSE));
#endif

  if (del_type & F_DELTA) del_type |= F_ACCL;
  /* mark delete vector */
  if (del_type & F_ACCL) {
    mark_exclude_vector(pb[2], len);
    src_type &= ~(F_ACCL);
    del_type &= ~(F_ACCL);
  }
  if (del_type & F_DELTA) {
    mark_exclude_vector(pb[1], len);
    src_type &= ~(F_DELTA);
    del_type &= ~(F_DELTA);
  }
  
  if (del_type & F_ENERGY) {
    mark_exclude_vector(pe[2], 1);
    mark_exclude_vector(pe[1], 1);
    if (!(src_type & F_ENERGY_SUP)) {
      mark_exclude_vector(pe[0], 1);
    }
    src_type &= ~(F_ENERGY | F_ENERGY_SUP);
    del_type &= ~(F_ENERGY | F_ENERGY_SUP);
  }
  if (del_type & F_ZEROTH) {
    mark_exclude_vector(p0[2], 1);
    mark_exclude_vector(p0[1], 1);
    if (!(src_type & F_ENERGY_SUP)) {
      mark_exclude_vector(p0[0], 1);
    }
    src_type &= ~(F_ZEROTH | F_ENERGY_SUP);
    del_type &= ~(F_ZEROTH | F_ENERGY_SUP);
  }
  
  if (del_type != 0) {		/* left */
    buf[0] = '\0';
    j_printerr("WARNING: cannot exclude qualifiers %s. selection ignored\n", param_qualcode2str(buf, del_type, FALSE));
  }

  vnewlen = 0;
  for (i=0;i<vlen;i++) {
    if (vmark[i] == 0) vnewlen++;
  }

  return(TRUE);
  
}


/* extracts needed parameter vector specified in dst_type_arg from src, and */
/* returns newly allocated parameter structure */
HTK_Param *
new_select_param_kind(HTK_Param *src, short dst_type_arg)
{
  HTK_Param *new;

  /* prepare new parameter */
  new = new_param();
  /* mark to determine operation */
  if (select_param_vmark(src, dst_type_arg) == FALSE) return(NULL);
  /* execute deletion (copy needed to new param)*/
  exec_exclude_vectors(new, src);
  
  /* copy & set header info */
  new->header.samplenum = src->header.samplenum;
  new->header.wshift = src->header.wshift;
  new->header.sampsize = new->veclen * sizeof(VECT);
  new->header.samptype = src_type | (src->header.samptype & F_BASEMASK);
  
#ifdef DEBUG
  j_printf("new param made: %s\n", param_code2str(buf, new->header.samptype, FALSE));
#endif
  
  free_mark();

  return(new);
}

/* top function: */
/* compare types for given parameter 'param' and HMM definition 'hmminfo' */
/* if type is not same, try to adjust by vector selection */
/* vflag is verbose flag (if TRUE, output verbose messages) */
/* returns NULL on paramtype error */
HTK_Param *
new_param_check_and_adjust(HTK_HMM_INFO *hmminfo, HTK_Param *param, boolean vflag)
{
  HTK_Param *selected_param;
  char pbuf[80],hbuf[80];
  
  param_code2str(pbuf, param->header.samptype & ~(F_COMPRESS | F_CHECKSUM), FALSE);
  param_code2str(hbuf, hmminfo->opt.param_type, FALSE);  
  if (!check_param_basetype(hmminfo, param)) {
    /* error if base type not match */
    j_printerr("Error: incompatible parameter type\n");
    j_printerr("  HMM  trained  by %s(%d)\n", hbuf, hmminfo->opt.vec_size);
    j_printerr("input parameter is %s(%d)\n", pbuf, param->veclen);
    return NULL;
  }
  if (!check_param_coherence(hmminfo, param)) {
    /* try to select needed parameter vector */
    if (vflag) j_printerr("attach %s", pbuf);
    selected_param = new_select_param_kind(param, hmminfo->opt.param_type);
    if (selected_param == NULL) {
      if (vflag) j_printerr("->%s failed\n", hbuf);
      j_printerr("Error: incompatible parameter type\n");
      j_printerr("  HMM  trained  by %s(%d)\n", hbuf, hmminfo->opt.vec_size);
      j_printerr("input parameter is %s(%d)\n", pbuf, param->veclen);
      return NULL;
    }
    param_code2str(pbuf, selected_param->header.samptype, FALSE);
    if (vflag) j_printerr("->%s\n", pbuf);
    free_param(param);
    return(selected_param);
  }
  return(param);
}
