// file: $isip/class/algo/Calculus/calc_05.cc
// version: $Id: calc_05.cc 8226 2002-06-25 01:42:42Z picone $
//

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

// method: apply
//
// arguments:
//  Vector<AlgorithmData>& output: (output) output data
//  const Vector< CircularBuffer<AlgorithmData> >& input: (input) input data
//
// return: a boolean value indicating status
//
// this method calls the appropriate computation method
//
boolean Calculus::apply(Vector<AlgorithmData>& output_a,
			const Vector< CircularBuffer<AlgorithmData> >&
			input_a) {
  
  // determine the number of input channels and force the output to be
  // that number
  //
  long len = input_a.length();
  output_a.setLength(len);
  boolean res = true;
  frame_index_d++;
  
  // start the debugging output
  //
  displayStart(this);
  
  // loop over the channels and call the compute method
  //
  for (long c = 0; c < len; c++) {

    // display the channel number
    //
    displayChannel(c);
    
    if (input_a(c)(0).getDataType() != AlgorithmData::VECTOR_FLOAT) {
      return Error::handle(name(), L"apply", ERR_UNSUPA, __FILE__, __LINE__);
    }

    // cmode_d: FRAME_INTERNAL
    //
    if (cmode_d == FRAME_INTERNAL) {
      
      // call AlgorithmData::makeVectorFloat to force the output vector for
      // this channel to be a VectorFloat, use the
      // CircularBuffer<AlgorithmData> directly on the input for this
      // channel
      //          
      res &= computeInternal(output_a(c).makeVectorFloat(), input_a(c));
    }
    
    // cmode_d: CROSS_FRAME
    //
    else if (cmode_d == CROSS_FRAME) {
      
      // call AlgorithmData::makeVector to force the output vector for
      // this channel to be a VectorFloat, use the
      // CircularBuffer<AlgorithmData> directly on the input for this
      // channel
      //          
      res &= computeCross(output_a(c).makeVectorFloat(), input_a(c));
    }
    
    // mode_d: ACCUMULATE
    //
    else {
      return Error::handle(name(), L"apply",
			   ERR_UNSUPM, __FILE__, __LINE__);
    }    
    // set the coefficient type for the output
    //    
    output_a(c).setCoefType(input_a(c)(0).getCoefType());
  }
  
  // finish the debugging output
  //
  displayFinish(this);
  
  // exit gracefully
  //
  return res;
}

// method: compute
//
// arguments:
//  VectorFloat& output: (output) output data
//  const VectorFloat& input: (input) input data
//  AlgorithmData::COEF_TYPE input_coef_type: (input) type of input
//  long channel_index: (input) channel index
//
// return: a boolean value indicating status
//
// this method performs appropriate calculus operation on input vector.
// note that none of the algorithms or implementations currently
// require memory to be maintained between calls, so channel_index is
// not used.
//
  boolean Calculus::compute(VectorFloat& output_a, const VectorFloat& input_a,
			  AlgorithmData::COEF_TYPE input_coef_type_a,
			  long channel_index_a) {

  // declare local variables
  //
  boolean status = false;
  
  // check valid flag
  //
  if (!is_valid_d) {
    init();
  }

  // branch on algorithm:
  //  Algorithm: DIFFERENTIATION
  //
  if (algorithm_d == DIFFERENTIATION) {

    // branch on implementation:
    //
    //  Implementation: REGRESSION
    //
    if (implementation_d == REGRESSION) {
      status = computeDifRegression(output_a, input_a);
    }

    //  Implementation: CENTRAL_DEFFERENCE
    //
    else if (implementation_d == CENTRAL_DIFFERENCE) {
      status = computeDifCentral(output_a, input_a);
    }

    //  Implementation: BACKWARD_DIFFERENCE
    //
    else if (implementation_d == BACKWARD_DIFFERENCE) {
      status = computeDifBackward(output_a, input_a);
    }

    // else: unknown condition
    //
    else {
      return Error::handle(name(), L"compute",
			   ERR_UNKIMP, __FILE__, __LINE__);
    }
  }

  //  Algorithm: INTEGRATION
  //
  else if (algorithm_d == INTEGRATION) {
    return Error::handle(name(), L"compute",
			 ERR_UNSUPA, __FILE__, __LINE__);
  }  

  // check algorithm: unknown
  //
  else {
    return Error::handle(name(), L"compute",
			 ERR_UNKALG, __FILE__, __LINE__);
  }  

  // exit gracefully
  //
  return status;
}

// method: compute
//
// arguments:
//  VectorComplexFloat& output: (output) output data
//  const VectorComplexFloat& input: (input) input data
//  AlgorithmData::COEF_TYPE input_coef_type: (input) type of input
//  long index: (input) channel index
//
// return: a boolean value indicating status
//
// this method performs the appropriate calculus operation on the input vector
//
boolean Calculus::compute(VectorComplexFloat& output_a,
			  const VectorComplexFloat& input_a,
			  AlgorithmData::COEF_TYPE input_coef_type_a,
			  long index_a) {
  return Error::handle(name(), L"compute", ERR_UNSUPA, __FILE__, __LINE__);
}  

// method: computeInternal
//
// arguments:
//  VectorFloat& output: (output) output vector
//  const CircularBuffer<AlgorithmData>& input: (input) input vectors
//  AlgorithmData::COEF_TYPE input_coef_type: (input) type of input
//  long channel_index: (input) channel index
//
// return: a boolean value indicating status
//
// this method transfers data from the circular buffer to the
// lower level compute method
//
boolean Calculus::computeInternal(VectorFloat& output_a,
				  const CircularBuffer<AlgorithmData>& input_a,
				  AlgorithmData::COEF_TYPE input_coef_type_a,
				  long channel_index_a) {
  
  // check that the type of data is correct
  //
  if (input_a(0).getDataType() != AlgorithmData::VECTOR_FLOAT) {
    return Error::handle(name(), L"computeInternal", ERR_UNKTYP, __FILE__, __LINE__);
  }
    
  // create space for the output
  //
  long num_coeffs = input_a(0).getVectorFloat().length();
  output_a.setLength(num_coeffs);
    
  // check how much past and future data is in the buffer, and whether
  // this is adequate. note that the data must span from the interval:
  //
  //  [offset_d, offset_d+num_elements-1]
  //
  // getNumBackward and getNumForward retrieve this information from
  // the circular buffer. the calling class, typically Algorithm,
  // has the responsibility of guaranteeing there is enough data in
  // the circular buffer
  //
  if (offset_d < (-1 * input_a.getNumBackward())) {
    return Error::handle(name(), L"computeInternal", ERR_INSUFD,
			 __FILE__, __LINE__);
  }
  else if ((offset_d + num_elements_d - 1) > input_a.getNumForward()) {
    return Error::handle(name(), L"computeInternal", ERR_INSUFD,
			 __FILE__, __LINE__);
  }
    
  // declare some scratch buffers:
  //  a vector to hold frame-spanning data
  //  a vector to accept output from compute()
  //
  VectorFloat data(num_elements_d);
  VectorFloat result(1);

  // loop over the number of coefficients
  //
  for (long i = 0; i < num_coeffs; i++) {
      
    // copy data onto the data buffer
    //
    for (long k = 0; k < num_elements_d; k++) {
	
      // fetch the data backward in time
      //
      if (i +  k < delta_win_d) {
	data(k) = (input_a(-1).getVectorFloat())(num_coeffs -
						 delta_win_d + i + k);
      }
      else if ( (i > num_coeffs - 1 - delta_win_d) &&
		(i + k - delta_win_d > num_coeffs - 1) &&
		(k > delta_win_d)) {
	data(k) = (input_a(1).getVectorFloat())(i + k -
						num_coeffs - delta_win_d);
      }
      else {
	data(k) = (input_a(0).getVectorFloat())(i + k - delta_win_d);
      }
    }

    // compute the algorithm: we now have the temporal data in a vector
    //  and can process it
    //
    if (!compute(result, data, input_coef_type_a)) {
      return Error::handle(name(), L"computeInternal",
			   ERR_COMPF, __FILE__, __LINE__, Error::WARNING);
    }

    // save the result into the output buffer and move to the next element
    //
    output_a(i) = result(0);
  }

  // possibly display the data
  //
  display(output_a, input_a, name());

  // exit gracefully
  //
  return true;
}

// method: computeCross
//
// arguments:
//  VectorFloat& output: (output) output vector
//  const CircularBuffer<AlgorithmData>& input: (input) input vectors
//  AlgorithmData::COEF_TYPE input_coef_type: (input) type of input
//  long channel_index: (input) channel index
//
// return: a boolean value indicating status
//
// this method transfers data from the circular buffer to the
// lower level compute method
//
boolean Calculus::computeCross(VectorFloat& output_a,
			       const CircularBuffer<AlgorithmData>& input_a,
			       AlgorithmData::COEF_TYPE input_coef_type_a,
			       long channel_index_a) {
  
  // check that the type of data is correct
  //
  if (input_a(0).getDataType() != AlgorithmData::VECTOR_FLOAT) {
    return Error::handle(name(), L"computeCross",
			 ERR_UNKTYP, __FILE__, __LINE__);
  }
    
  // create space for the output
  //
  long num_coeffs = input_a(0).getVectorFloat().length();
  output_a.setLength(num_coeffs);
    
  // check how much past and future data is in the buffer, and whether
  // this is adequate. note that the data must span from the interval:
  //
  //  [offset_d, offset_d+num_elements-1]
  //
  // getNumBackward and getNumForward retrieve this information from
  // the circular buffer. the calling class, typically Algorithm,
  // has the responsibility of guaranteeing there is enough data in
  // the circular buffer
  //
  if (offset_d < (-1 * input_a.getNumBackward())) {
    return Error::handle(name(), L"computeCross",
			 ERR_INSUFD, __FILE__, __LINE__);
  }
  else if ((offset_d + num_elements_d - 1) > input_a.getNumForward()) {
    return Error::handle(name(), L"computeCross",
			 ERR_INSUFD, __FILE__, __LINE__);
  }
    
  // declare some scratch buffers:
  //  a vector to hold frame-spanning data
  //  a vector to accept output from compute()
  //
  VectorFloat data(num_elements_d);
  VectorFloat result(1);    
  long number_frame = (long)(Integral::round(signal_duration_d / frame_dur_d));

  // loop over the number of coefficients
  //
  for (long i = 0; i < num_coeffs; i++) {

    long j = offset_d;

    // copy data onto the data buffer
    //
    for (long k = 0; k < num_elements_d; k++) {

      // fetch the data backward in time
      //
      if ((k + frame_index_d + offset_d) < 0) {
	data(k) = (input_a(0 - frame_index_d).getVectorFloat())(i);
	j++;
      }
      else if ((k + frame_index_d + offset_d) > number_frame - 1) {
	data(k) =
	  (input_a(number_frame - 1 - frame_index_d ).getVectorFloat())(i);
      }
      else {	
	data(k) = (input_a(j++).getVectorFloat())(i);
      }
    }
    
    // compute the algorithm: we now have the temporal data in a vector
    //  and can process it
    //
    if (!compute(result, data, input_coef_type_a)) {
      return Error::handle(name(), L"computeCross",
			   ERR_COMPF, __FILE__, __LINE__, Error::WARNING);
    }

    // save the result into the output buffer and move to the next element
    //
    output_a(i) = result(0);
  }

  // possibly display the data
  //
  display(output_a, input_a, name());

  // exit gracefully
  //
  return true;
}


// method: computeDifCentral
//
// arguments:
//  VectorFloat& output: (output) output data
//  const VectorFloat& input: (input) input data
//
// return: a boolean value indicating status
//
// this method computes a temporal derivative using a central difference.
// two pertinent references are:
//
//  J.G. Proakis and D.G. Manolakis, Digital Signal Processing (Third Edition)
//  Prentice-Hall, Upper Saddle River, New Jersey, USA, 1996.
//
// and:
//
//  J. Picone, "Signal Processing in Speech Recognition," Proceedings
//  of the Speech Recognition System Training Workshop, Institute
//  for Signal and Information Processing, Mississippi State University,
//  May 2000 (see http://www.isip.msstate.edu/conferences/srstw00/program/
//  session_04/signal_processing/index.html).
//
boolean Calculus::computeDifCentral(VectorFloat& output_a,
				    const VectorFloat& input_a) {

  // check the order: we currently only support a first-order difference
  //
  if (order_d != (long)1) {
    return Error::handle(name(), L"computeDifCentral",
			 ERR_ORDER, __FILE__, __LINE__);
  }

  // check the length
  //
  else if (input_a.length() != (2 * delta_win_d + 1)) {
    return Error::handle(name(), L"computeDifCentral",
			 ERR_INSUFD, __FILE__, __LINE__);
  }
  
  // compute the numerator of the difference
  //
  long N = (long)delta_win_d;
  double num = input_a(N + delta_win_d) - input_a(N - delta_win_d);

  // exit gracefully: return the value
  //
  output_a.setLength(1);

  if (denom_d != (double)0.0) {
    output_a(0) = num * denom_d;
    return true;
  }    
  else {
    return Error::handle(name(), L"computeDifCentral",
			 ERR_ZERODENOM, __FILE__, __LINE__, Error::WARNING);
  }
}

// method: computeDifBackward
//
// arguments:
//  VectorFloat& output: (output) output data
//  const VectorFloat& input: (input) input data
//
// return: a boolean value indicating status
//
// this method computes a temporal derivative using a simple
// backward difference. two pertinent references are:
//
//  J.G. Proakis and D.G. Manolakis, Digital Signal Processing (Third Edition)
//  Prentice-Hall, Upper Saddle River, New Jersey, USA, 1996.
//
// and:
//
//  J. Picone, "Signal Processing in Speech Recognition," Proceedings
//  of the Speech Recognition System Training Workshop, Institute
//  for Signal and Information Processing, Mississippi State University,
//  May 2000 (see http://www.isip.msstate.edu/conferences/srstw00/program/
//  session_04/signal_processing/index.html).
//
boolean Calculus::computeDifBackward(VectorFloat& output_a,
				     const VectorFloat& input_a) {

  // check the order: we currently only support a first-order difference
  //
  if (order_d != (long)1) {
    return Error::handle(name(), L"computeDifBackward",
			 ERR_ORDER, __FILE__, __LINE__);
  }

  // check the length
  //
  else if (input_a.length() != (delta_win_d + (long)1)) {
    return Error::handle(name(), L"computeDifBackward",
			 ERR_INSUFD, __FILE__, __LINE__);
  }
  
  // compute the numerator of the difference
  //
  long N = (long)delta_win_d;
  double num = input_a(N) - input_a(0);

  // exit gracefully: return the value
  //
  output_a.setLength(1);

  if (denom_d != (double)0.0) {
    output_a(0) = num * denom_d;
    return true;
  }    
  else {
    return Error::handle(name(), L"computeDifBackward",
			 ERR_ZERODENOM, __FILE__, __LINE__, Error::WARNING);
  }
}

// method: computeDifRegression
//
// arguments:
//  VectorFloat& output: (output) output data
//  const VectorFloat& input: (input) input data
//
// return: a boolean value indicating status
//
// this method computes a temporal derivative using a regression
// approximation. Two pertinent references are:
//
//  F.K. Soong and A.E. Rosenburg, "On the Use of Instantaneous and
//  Transitional Spectral Information in Speaker Recognition," Proceedings
//  of the International Conference on Acoustics, Speech, and Signal
//  Processing, Tokyo, Japan, pp. 877-880, April 1986.
//
// and:
//
//  J. Picone, "Signal Processing in Speech Recognition," Proceedings
//  of the Speech Recognition System Training Workshop, Institute
//  for Signal and Information Processing, Mississippi State University,
//  May 2000 (see http://www.isip.msstate.edu/conferences/srstw00/program/
//  session_04/signal_processing/index.html).
//
boolean Calculus::computeDifRegression(VectorFloat& output_a,
				       const VectorFloat& input_a) {
  
  // check the order: we currently only support a first-order regression
  //
  if (order_d != (long)1) {
    return Error::handle(name(), L"computeDifRegression",
			 ERR_ORDER, __FILE__, __LINE__);
  }

  // check the length
  //
  else if (input_a.length() != (2 * delta_win_d + 1)) {
    return Error::handle(name(), L"computeDifRegression",
			 ERR_INSUFD, __FILE__, __LINE__);
  }

  // compute the numerator of the regression formula 
  //
  double num = 0.0;
  long N = (long)delta_win_d;
  
  for (long i = 1; i <= N; i++) {
    num += (double)i * (input_a(N + i) - input_a(N - i));
  }

  // exit gracefully: return the value
  //
  output_a.setLength(1);

  if (denom_d != (double)0.0) {
    output_a(0) = num * denom_d;
    return true;
  }    
  else {
    return Error::handle(name(), L"computeDifRegression",
			 ERR_ZERODENOM, __FILE__, __LINE__, Error::WARNING);
  }
}
