// file: $isip_ifc/class/sp/SignalDetector/sigd_09.cc
// version: $Id: sigd_09.cc 10210 2005-08-12 20:24:51Z stanley $
//

// isip include files
//
# include "SignalDetector.h"


// method: cbUttInProgress
//
// arguments: 
//  long channel_index: (input) audio channel index
//
// return: a boolean vlaue indicating whether we have a start of utterance
//
// a start of utterance is defined as the point at which we have a
// run of SIGN states. this does not mean this is the final start time
// of the utterance (which is found from some postprocessing). it simply
// means we are confident there is an utterance in progress.
//
boolean SignalDetector::cbUttInProgress(long channel_index_a) {
  
  // make sure there the number of frames between the current frame
  // and the release frame is more than the utterance_minimum_duration_d
  //
  if((egy_cur_d(channel_index_a) - egy_rlse_d(channel_index_a)) < utterance_minimum_sample_duration_d) {
    return false;
  }
  
  // start from the egy_cur_d
  //
  long nf = egy_cur_d(channel_index_a);
  
  // try to find a run of signal states of duration utterance_minimum_duration_d
  // backwards from egy_cur_d
  //
  for(long counter = 0;counter<utterance_minimum_sample_duration_d; counter++) {
    
    if (nf == egy_rlse_d(channel_index_a)) {
      return Error::handle(name(), L"Error in circular buffer in cbUttInProgress function",Error::ARG,__FILE__, __LINE__);
    }
    
    if(egy_d(channel_index_a)(nf - egy_rlse_d(channel_index_a)) != SM_STATE_SIGN) {
      return false;
    }
    nf--;
  }
  
  // return gracefully
  //
  return true;
}

// method: cbUttNotInProgress
//
// arguments:
//  long channel_index: (input) audio channel index
//
// return: a boolean indicating whether we have a noise state
//
// this method is normally called after an utterance has been in progress
// to determine when the end of utterance has occurred.
//
boolean SignalDetector::cbUttNotInProgress(long channel_index_a) {
  
  // make sure that the number of frames between the egy_cur_d and
  // egy_rlse_d is greater than the minimum utterance seperation
  //
  if((egy_cur_d(channel_index_a) - egy_rlse_d(channel_index_a)) < utterance_minimum_sample_separation_d) {
    
    return false;
  }
  
  // start from the current frame
  //
  long nf = egy_cur_d(channel_index_a);
  
  // try to find a run of noise states equal to minimum utterance
  // separation backwards starting from the current frame
  //
  for(long counter = 0;counter<utterance_minimum_sample_separation_d; counter++) {
    
    if (nf == egy_rlse_d(channel_index_a)) {
      return Error::handle(name(), L"Error in circular buffer in cbUttNotInProgress function",Error::ARG,__FILE__, __LINE__);      
    }
    if(egy_d(channel_index_a)(nf - egy_rlse_d(channel_index_a)) != SM_STATE_NOIS) {
      return false;
    }
    nf--;
  }
  
  // return gracefully
  //
  return true;
  
}

// method: cbScanForPattern
//
// arguments:
//  Vector<Long> pattern: (input) pattern to be matched
//  long length: (input) length of the pattern
//  long index: (input) position from which to start scan 
//  long channel_index: (input) channel index 
//
// return: an long indicating the position of the match
//  a value less than zero indicates no match
//
// this method scans the circular buffer for the first occurrence of the
// pattern supplied as an argument.
//
long SignalDetector::cbScanForPattern(Vector<Long> pattern_a, long length_a , long index_a , long channel_index_a) {
  
  long pointer = index_a;
  
  long nf_stop = egy_rlse_d(channel_index_a);
  
  // make sure the number of frames between the release frame and the
  // "index" frame is more or equal to the length_a
  //
  for (long nf = 0; nf < length_a; nf++) {
    nf_stop++;
    if (nf_stop == pointer)
      {
	return (long)-1;
      }
    
  }
  
  // start from the user given index and go towards the release frame
  // trying to find a match for the pattern
  //
  for (long nf = pointer ; nf >= nf_stop; nf--) {
    
    long num_matches = 0;
    long n = nf;
    for (long counter = 0; counter < length_a; counter++) {
      
      // match the frame state to that of the pattern
      //
      if(egy_d(channel_index_a)(n-egy_rlse_d(channel_index_a))== pattern_a (counter)) {
	num_matches++;
      }
      n--;
      
      if (n < egy_rlse_d(channel_index_a)) {
	return Error::handle(name(), L"Exception in the circular buffer in cbScanForPattern function",Error::ARG,__FILE__, __LINE__);
      }
      
    }
    //check to see if a pattern is found
    //
    if (num_matches == length_a) {
      
      return nf;
    }
  }
  return -1; 
}



// method: cbSmoothSignal
//
// arguments:
//  long channel_index: (input) audio channel index
//
// return: a boolean indicating the status
//
// this method removes short bursts of noise that do not meet the
// duration constraints.
//
// this algorithm works as follows:
//  (1) checks from the current position if there are min_utt_dur consecutive
//      frames assigned to state SIGN
//  (2) scans backward to find another min_utt_dur consecutive frames
//      assigned to state SIGN previous to the frames in step (1)
//  (3) if the time difference between these two groups of frames
//      is less than min_utt_sep, assign them to SIGN
//  
boolean SignalDetector::cbSmoothSignal(long channel_index_a) {
  
  Vector<Long> temp;
  long write_pointer = egy_cur_d(channel_index_a);
  
  // start from the current frame and go backward towards the release
  // frame and try to find a run of signla states that is less than
  // the minimum utterance duration
  //
  for (long counter = 0; counter <utterance_minimum_sample_duration_d; counter++) {
    
    if(egy_d(channel_index_a)(write_pointer - egy_rlse_d(channel_index_a)) != SM_STATE_SIGN) {
      return false;
    }
    write_pointer--;
    if (write_pointer == egy_rlse_d(channel_index_a)) {
      return false;
    }  
  }
  
  while (write_pointer != egy_rlse_d(channel_index_a)) {
    
    if (egy_d(channel_index_a)(write_pointer - egy_rlse_d(channel_index_a)) != SM_STATE_SIGN) {
      break;
    } 
    write_pointer--;
  }
  if (write_pointer == egy_rlse_d(channel_index_a)) {
    return false;
  }
  
  // try to find a match for sig_patn_d pattern starting from the
  // write_pointer index
  //
  long match_index = cbScanForPattern(sig_patn_d , sig_patn_len_d, write_pointer, channel_index_a);
  
  // if no match is found return false
  //
  if (match_index < 0) {
    return false;
  }
  
  // get the difference between the match index and the write pointer
  //
  long index_difference = abs(write_pointer - match_index) ;
  
  // make sure the difference is less than the minimum utterance
  // separation
  //
  if (index_difference < utterance_minimum_sample_separation_d) {
    
    // read the data into a temporary vector
    //
    long length = egy_d(channel_index_a).getNumForward();
    egy_d(channel_index_a).getElement(temp, (long)(length + 1), 0);
    
    while (index_difference > 0) {
      
      // set the state to signal state
      //
      temp(write_pointer - egy_rlse_d(channel_index_a)) = SM_STATE_SIGN;
      index_difference--;
      write_pointer--;
    }
    
    // write back the vector into the circular buffer
    //
    CircularBuffer<Long> temp_egy_d(egy_len_d);
    for (long i = 0; i < egy_rlse_d(channel_index_a); i++) {
      temp_egy_d.append((Long)0);
    }
    temp_egy_d.append(temp);
    temp_egy_d.setRead(egy_rlse_d(channel_index_a));
    egy_d(channel_index_a).assign(temp_egy_d);
  }
  
  // return gracefully
  //
  return true;
  
}


// method: cbSmoothNoise
//
// arguments:
//  long channel_index: (input) audio channel index
//
// return: a boolean indicating the status
//
// this method removes short bursts of energy that do not meet the
// duration constraints and occur within a run of noise.
//
// this algorithm works as follows:
//  (1) checks from the current position if there are min_utt_dur consecutive
//      frames assigned to state NOIS
//  (2) scans backward to find another min_utt_dur consecutive frames
//      assigned to state NOIS previous to the frames in step (1)
//  (3) if the time difference between these two groups of frames
//      is less than min_utt_dur, assign them to NOIS
//
boolean SignalDetector::cbSmoothNoise(long channel_index_a) {
  
  
  Vector<Long> temp;
  long write_pointer = egy_cur_d(channel_index_a);
  
  // make sure there are min_utt_dur consecutive frames
  //
  for (long counter = 0; counter < utterance_minimum_sample_duration_d; counter++) {
    
    if (egy_d(channel_index_a)(write_pointer - egy_rlse_d(channel_index_a)) != SM_STATE_NOIS) {
      return false;
    }
    write_pointer--;
    if (write_pointer == egy_rlse_d(channel_index_a)) {
      return false;
    }  
  }
  
  while (write_pointer != egy_rlse_d(channel_index_a)) {
    
    if (egy_d(channel_index_a)(write_pointer - egy_rlse_d(channel_index_a)) != SM_STATE_NOIS) {
      break;
      
    } 
    write_pointer--;
  }
  if (write_pointer == egy_rlse_d(channel_index_a)) {
    return false;
  }
  
  // scan backward to find another minimum uterance duration of
  // consecutive frames
  //
  long match_index = cbScanForPattern(nse_patn_d , utterance_minimum_sample_duration_d, write_pointer, channel_index_a);
  
  if (match_index < 0) {
    return false;
  }
  
  // see if the difference between them is less than min_utt_dur
  //
  long index_difference = abs(write_pointer - match_index) ;
  
  // if the difference is less than min_utt_dur assign the states to
  // noise
  //
  if (index_difference < utterance_minimum_sample_duration_d) {
    
    long length = egy_d(channel_index_a).getNumForward();
    egy_d(channel_index_a).getElement(temp, (long)(length + 1), 0);
    
    while (index_difference > 0) {
      
      // read the circular buffer into an temporary array
      //
      temp(write_pointer - egy_rlse_d(channel_index_a))= SM_STATE_NOIS;
      index_difference--;
      write_pointer--;
    }
    
    // write back the modified vector to the circular buffer
    //
    CircularBuffer<Long> temp_egy_d(egy_len_d);
    for (long i = 0; i < egy_rlse_d(channel_index_a); i++) {
      temp_egy_d.append((Long)0);
    }
    temp_egy_d.append(temp);
    temp_egy_d.setRead(egy_rlse_d(channel_index_a));
    egy_d(channel_index_a).assign(temp_egy_d);
  }
  return true;
  
}

// method: cbRemoveShortNoiseBursts
//
// arguments:
//  long channel_index: (input) audio channel index
//
// return: a boolean indicating the status
//
// this method removes short bursts of noise that do not meet the
// duration constraints. note that this method starts from the
// back of the circular buffer (egy_rlse_d).
//
// this algorithm works as follows:
//  (1) finds a run of states that toggle between NOIS and NTOS
//  (2) assigns them to NOIS
//
boolean SignalDetector::cbRemoveShortNoiseBursts(long channel_index_a) {
  
  Vector<Long> temp;
  
  long index = egy_cur_d(channel_index_a) -
    utterance_minimum_sample_separation_d;
  
  if ((index - egy_rlse_d(channel_index_a)) < 0) {
    return false;
  }
  
  // read the circular buffer into an temporary array
  //
  long length = egy_d(channel_index_a).getNumForward();
  egy_d(channel_index_a).getElement(temp, (long)(length + 1), 0);  
  
  // assign the ntos states to noise state
  //
  while (index != egy_rlse_d(channel_index_a)) {
    if (temp(index - egy_rlse_d(channel_index_a)) == SM_STATE_NTOS) {
      temp(index - egy_rlse_d(channel_index_a)) = SM_STATE_NOIS;
    }
    index--;
  }
  
  // write the modified vector back to the circular buffer
  //
  CircularBuffer<Long> temp_egy_d(egy_len_d);
  for (long i = 0; i < egy_rlse_d(channel_index_a); i++) {
    temp_egy_d.append((Long)0);
  }
  temp_egy_d.append(temp);
  temp_egy_d.setRead(egy_rlse_d(channel_index_a));
  egy_d(channel_index_a).assign(temp_egy_d);  
  
  // return gracefully
  //
  return true;
}

// method: cbRemoveLongNoiseBursts
//
// arguments:
//  long channel_index: (input) audio channel index
// return: a boolean indicating the status
//
// this method removes long bursts of noise. note that:
// 
//  - it works forward in time starting at egy_rlse_d
//  - it can only remove utterance_minimum_sample_separation_d
//    frames of noise when it finds twice this much
// 
boolean SignalDetector::cbRemoveLongNoiseBursts(long channel_index_a) {
  
  // set the variable to twice as that utterance_minimum_separation_d
  //
  long num_states_req = 2 * utterance_minimum_sample_separation_d;
  long nf = egy_rlse_d(channel_index_a);  
  
  // try to find a run of noise states twice as that of the minimum
  // utterance separation
  //
  for (long counter = 0; counter < num_states_req; counter++) {
    
    if (egy_rlse_d(channel_index_a) == (Long) 0) {
      if (egy_d(channel_index_a)(nf) != SM_STATE_NOIS) {
	return false;
      } 
    }
    
    else {
      if (egy_d(channel_index_a)(nf - egy_rlse_d(channel_index_a))
	  != SM_STATE_NOIS) {
	return false;
      }
    }
    nf++;
    
    // id current frame is reached return false
    //
    if (nf == egy_cur_d(channel_index_a)) {
      return false;
    }
    
  }
  
  // if a run of noise states equal to twice that of the minimum
  // utterance separation is found move teh release pointer by a
  // utterance_minimum_sample_separation_d length
  //
  egy_d(channel_index_a).setRead(utterance_minimum_sample_separation_d);
  egy_rlse_d(channel_index_a) = egy_rlse_d(channel_index_a) + utterance_minimum_sample_separation_d;
  
  // return gracefully
  //
  return true;
}



// method: cbSmoothTransitions
//
// arguments:
//  long channel_index: (input) audio channel index
//
// return: a boolean indicating the status
//
// this method turns short bursts of energy transitions that occur prior to
// min_utt_dur of SIGN states into either NOIS or SIGN.
//
// this algorithm works as follows:
//
//  (1) checks from the current position if there are min_utt_dur consecutive
//      frames assigned to state SIGN.  the time at which these SIGN state
//      first occur is called 't2'.
//  (2) scans backward to find another min_utt_dur consecutive frames
//      assigned to state NOIS previous to the frames in step (1).  the end of
//      these NOIS states is called 't1'.
//  (3) for all frames between t1 and t2, a sum is accumulated, with a SIGN
//      state adding 1, NOIS state adding 0, and transition states adding 1/2.
//  (4) if this sum is greater that the total number of NOIS states in this
//      interval, then all state in this interval are changed to SIGN, else
//      all states are changed to NOIS.
//
boolean SignalDetector::cbSmoothTransitions(long channel_index_a) {
  
  Vector<Long> temp;
  long pointer = egy_cur_d(channel_index_a);
  long counter = 0;
  
  // check from the current position for a run of signal states of
  // minimum utterance duration
  //
  while ( counter < utterance_minimum_sample_duration_d) {
    if (egy_d(channel_index_a)(pointer - egy_rlse_d(channel_index_a))
	!= SM_STATE_SIGN) {
      return false;
    }
    counter++;
    pointer--;
    if (pointer < 0) {
      return false;
    }
    
  }
  long t2 = pointer;
  
  // scan backwards to another run of noise states
  //
  long t1 = cbScanForPattern(nse_patn_d, utterance_minimum_sample_separation_d, pointer, channel_index_a);
  if (t1 < 0) {
    return false;
  }
  
  // a good explanation for the logic below can be found above in this
  // functions header
  //
  double sum_sign = 0.0;
  double sum_nois = 0.0;
  for (counter = t1; counter < t2; counter++) {
    if (egy_d(channel_index_a)(counter - egy_rlse_d(channel_index_a)) == SM_STATE_SIGN){
      sum_sign += SM_WGT_SIGN;
    }
    else if (egy_d(channel_index_a)(counter - egy_rlse_d(channel_index_a)) == SM_STATE_NOIS) {
      sum_nois += SM_WGT_NOIS;
    }
    else {
      sum_sign += SM_WGT_TRAN;
    }
  }
  
  // make a decision on whether it is signal or noise
  //
  long new_val;
  
  if (sum_sign > sum_nois) {
    new_val = SM_STATE_SIGN;
  }
  else {
    new_val = SM_STATE_NOIS;
  }
  
  // set the frames to the new value
  //
  t1++;
  t2++;
  long length = egy_d(channel_index_a).getNumForward();
  egy_d(channel_index_a).getElement(temp, (long)(length + 1), 0);  
  
  for (counter = t1; counter < t2; counter++) {
    temp(counter - egy_rlse_d(channel_index_a)) = new_val;
  }
  
  // write the temporary vector back to the circular buffer
  //
  CircularBuffer<Long> temp_egy_d(egy_len_d);
  for (long i = 0; i < egy_rlse_d(channel_index_a); i++) {
    temp_egy_d.append((Long)0);
  }
  temp_egy_d.append(temp);
  temp_egy_d.setRead(egy_rlse_d(channel_index_a));
  egy_d(channel_index_a).assign(temp_egy_d);
  
  // exit gracefully
  //
  return true;
}



// method: cbRemoveShortSignalBursts
//
// arguments:
//  long channel_index: (input) audio channel index
//
// return: a boolean indicating the status
//
// this method removes short bursts of signal that do not meet the
// duration constraints.
//
// this algorithm works as follows:
//  (1) checks from the current position if there are utterance_minimum_separation_dur
//      consecutive frames assigned to state NOIS
//  (2) scans backward to find the next min_sep_dur consecutive frames
//      assigned to NOIS states
//  (3) checks if the time difference between these two groups of frames
//      is less than min_utt_sep, assigns them to NOIS
//
boolean SignalDetector::cbRemoveShortSignalBursts(long channel_index_a) {
  
  // check from the current position for a run of noise states
  //
  long nf = egy_cur_d(channel_index_a);
  for (long counter = 0; counter < utterance_minimum_sample_separation_d; counter++) {
    if (egy_d(channel_index_a)(nf - egy_rlse_d(channel_index_a)) != SM_STATE_NOIS) {
      return false;
    }
    nf--;
    if (nf == egy_rlse_d(channel_index_a)) {
      return false;
    }
  }
  
  // check if the next state is non-NOIS
  //
  long start_index = nf - 1;
  if ((start_index == egy_rlse_d(channel_index_a)) ||
      (egy_d(channel_index_a)(start_index - egy_rlse_d(channel_index_a))
       == SM_STATE_NOIS)) {
    return false;
  }
  
  // find the next run of NOIS states
  //
  long end_index = start_index;
  long last_index = egy_rlse_d(channel_index_a) + utterance_minimum_sample_separation_d;
  
  while (end_index != last_index) {
    
    // count the number of frames that are assigned to the state NOIS
    //
    long num_states_nois = 0;
    nf = end_index;
    long counter = 0;
    
    while (counter < utterance_minimum_sample_separation_d) {
      if (egy_d(channel_index_a)(nf - egy_rlse_d(channel_index_a))
	  != SM_STATE_NOIS) {
	break;
      }
      else {
	num_states_nois++;
      }
      
      // update counters
      //
      counter++;
      nf--;
    }
    
    // check if we have a match
    //
    if (num_states_nois == utterance_minimum_sample_separation_d) {
      
      // check the duration of the burst
      //
      if (abs(start_index - end_index) < utterance_minimum_sample_duration_d) {
	
	// assign the states to NOIS
	//
	while (start_index != end_index) {
	  egy_d(channel_index_a)(start_index - egy_rlse_d(channel_index_a)) = SM_STATE_NOIS;
	  start_index--;
	}
	
	// exit
	//
	return true;
      }
    }
    
    // decrement counter
    //
    end_index--;
  }
  
  // exit gracefully - no bursts found
  //
  return false;
}
