// file: $isip/class/algo/Correlation/corr_05.cc
// version: $Id: corr_05.cc 8165 2002-05-31 21:57:33Z picone $
//

// isip include files
//
#include "Correlation.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 selects the appropriate computation methods according
// to the input data type
//
boolean Correlation::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;

  // 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);

    // branch on algorithm: autocorrelation only has one input,
    // crosscorrelation has two inputs
    //
    if (input_a(c)(0).getDataType() == AlgorithmData::VECTOR_FLOAT) {

      // call AlgorithmData::makeVectorFloat to force the output vector for
      // this channel to be a VectorFloat.
      // call AlgorithmData::getVectorFloat on the input for this channel to
      // check that the input is already a VectorFloat and return that
      // vector.
      //
      res &= compute(output_a(c).makeVectorFloat(),
		     input_a(c),
		     input_a(c)(0).getCoefType(),
		     c);
    }

    // for crosscorrelation the input should be a combination of two
    // VectorFloat's -- one for each signal to obtain the correlation
    // between.
    //
    else if (input_a(c)(0).getDataType() == AlgorithmData::COMBINATION) {

      // two input VectorFloat signals are expected here
      //
      if (input_a(c)(0).getCombination().length() != 2) {
	return Error::handle(name(), L"apply", ERR_INPUT, __FILE__, __LINE__);
      }

      // force the output to be a vector through
      // AlgorithmData::makeVectorFloat. for each of the two input signals
      // call getVectorFloat to obtain the VectorFloat signal.
      //
      res &= compute(output_a(c).makeVectorFloat(),
		     input_a(c)(0).getCombination()(0).getVectorFloat(),
		     input_a(c)(0).getCombination()(1).getVectorFloat(),
		     input_a(c)(0).getCoefType(),
		     c);
    }

    // error condition
    //
    else {
      return Error::handle(name(), L"apply", ERR_UNKTYP, __FILE__, __LINE__);
    }

    // set the coefficient type for the output
    //
    // branch on algorithm:
    //  Algorithm: AUTO or AUTO_TAPERED
    //
    if ((algorithm_d == AUTO) || (algorithm_d == AUTO_TAPERED)) {
      output_a(c).setCoefType(AlgorithmData::CORRELATION);
    }

    //  Algorithm: CROSS or CONV
    //
    else {
      output_a(c).setCoefType(AlgorithmData::SIGNAL);
    }
  }

  // finish the debugging output
  //
  displayFinish(this);

  // exit gracefully
  //
  return res;
}

// method: compute
//
// arguments:
//  VectorFloat& output: (output) output data
//  const CircularBuffer<AlgorithmData>& input: (input) input data
//  AlgorithmData::COEF_TYPE coef_type: (input) coeff type of input signal
//  long channel_index: (input) the channel index of input signal
//
// return: a boolean value containing status
//
// this method applies the specified window on the input channel
//
boolean Correlation::compute(VectorFloat& output_a,
			const CircularBuffer<AlgorithmData>& input_a,
			AlgorithmData::COEF_TYPE coef_type_a,
			long channel_index_a) {

  // for FRAME_INTERNAL mode, we just process what data we have
  //
  if (cmode_d == FRAME_INTERNAL) {
    return compute(output_a, input_a(0).getVectorFloat(), coef_type_a,
		   channel_index_a);
  }
  
  // for other modes, we error
  //
  else if (cmode_d != CROSS_FRAME) {
    return Error::handle(name(), L"compute",
			 ERR_UNSUPM, __FILE__, __LINE__);
  }

  // make sure there is enough data
  //
  if ((input_a.getNumForward() < getLeadingPad()) ||
      (input_a.getNumBackward() < getTrailingPad())) {
    return Error::handle(name(), L"compute", ERR_DATA, __FILE__, __LINE__);
  }

  // precompute some useful constants
  //
  double frame_nsamp = frame_dur_d * sample_freq_d;
  long order = order_d;

  double num_frames = order / frame_nsamp;
  long frame = (long)Integral::ceil(num_frames);
  
  VectorFloat buf;

  // algorithm: AUTO
  //
  if (algorithm_d == AUTO) {
    
    // this is the number of samples not included from the first frame
    //
    long index = frame ;
    long first_offset = frame * (long)Integral::ceil(frame_nsamp) - order;
    long first_nelem = (long)Integral::ceil(frame_nsamp) - first_offset;
    double num_left = order + frame_nsamp;
    
    // loop over the remaining data
    //
    while (num_left > 0) {
      
      // for the first frame, grab only the non-zero samples
      //
      if (index == frame) {
	buf.move(input_a(-index).getVectorFloat(),
		 first_nelem, first_offset, 0);
	num_left -= first_nelem;
      }
      
      // for all other frames, we copy the entire frame
      //
      else {
	buf.concat(input_a(-index).getVectorFloat());
	num_left -= frame_nsamp;
      }

      // decrement the frame counter
      //
      index--;
    }
    
    // apply the correlation function
    //
    if (implementation_d == FACTORED) {
      return computeAutoFactoredFromSignal(output_a, buf);
    }
    else if (implementation_d == UNFACTORED) {
      return computeAutoUnfactoredFromSignal(output_a, buf);  
    }
    else
      return Error::handle(name(), L"compute",
			   ERR_UNSUPI, __FILE__, __LINE__);
  }

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

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

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

// 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 selects the appropriate computational method to compute
// autocorrelation.
//
boolean Correlation::compute(VectorFloat& output_a,
			     const VectorFloat& input_a,
			     AlgorithmData::COEF_TYPE input_coef_type_a,
			     long channel_index_a) {

  // declare local variable
  //
  boolean status = false;

  // algorithm: AUTO or AUTO_TAPERED
  //
  if ((algorithm_d == AUTO) || (algorithm_d == AUTO_TAPERED)) {

    // input coefficient type: linear prediction coefficients
    //
    if (input_coef_type_a == AlgorithmData::PREDICTION) {
      status = computeAutoFromPrediction(output_a, input_a);
    }
    else if (input_coef_type_a == AlgorithmData::REFLECTION) {
      status = computeAutoFromReflection(output_a, input_a);
    }

    // else: signals
    //
    else if (input_coef_type_a == AlgorithmData::SIGNAL) {

      // check the implementation
      //
      if (implementation_d == FACTORED) {
	status = computeAutoFactoredFromSignal(output_a, input_a);
      }
      else if (implementation_d == UNFACTORED) {
	status = computeAutoUnfactoredFromSignal(output_a, input_a);
      }
      else {
	return Error::handle(name(), L"compute",
			     ERR_UNKIMP, __FILE__, __LINE__);
      }
    }
      
    // error: unknown coefficient type
    //
    else {
      return Error::handle(name(), L"compute",
			   ERR_UNCTYP, __FILE__, __LINE__);
    }
  }

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

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

  // exit gracefully
  //
  return status;
}

// method: compute
//
// arguments:
//  VectorFloat& output: (output) output data
//  const VectorFloat& input1: (input) input data
//  const VectorFloat& input2: (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 selects the appropriate computational method to compute
// cross-correlation and convolution
//
boolean Correlation::compute(VectorFloat& output_a,
			     const VectorFloat& input1_a,
			     const VectorFloat& input2_a,
			     AlgorithmData::COEF_TYPE input_coef_type_a,
			     long channel_index_a) {
  
  // declare local variable
  //
  boolean status = false;
  
  // algorithm: CROSS
  //
  if (algorithm_d == CROSS) {

    // implementation: UNFACTORED
    //
    if (implementation_d == UNFACTORED) {
      status = computeCross(output_a, input1_a, input2_a);
    }

    // check known but unsupported implementations
    //
    else if (implementation_d == FACTORED) {
      return Error::handle(name(), L"compute",
			   ERR_UNSUPA, __FILE__, __LINE__);
    }

    // error: unknown implementation
    //
    else {
      return Error::handle(name(), L"compute",
			   ERR_UNKIMP, __FILE__, __LINE__);
    }
  }
  
  // algorithm: CONVOLUTION
  //
  else if (algorithm_d == CONV) {

    // implementation: UNFACTORED
    //
    if (implementation_d == UNFACTORED) {
      status = computeConv(output_a, input1_a, input2_a);
    }

    // check known but unsupported implementations
    //
    else if (implementation_d == CIRCULAR) {
      return Error::handle(name(), L"compute",
			   ERR_UNSUPA, __FILE__, __LINE__);
    }

    // error: unknown implementation
    //
    else {
      return Error::handle(name(), L"compute",
			   ERR_UNKIMP, __FILE__, __LINE__);
    }
  }
  
  // error: unknown algorithm
  //
  else {
    status = Error::handle(name(), L"compute",
			   ERR_UNKALG, __FILE__, __LINE__);
  }

  // possibly display the data. note that we generate an input vector
  // if the debug level warrants that it will actually be output by
  // the AlgorithmBase method -- if not we just pass in a dummy value.
  //
  if (debug_level_d >= Integral::DETAILED) {
    Vector<VectorFloat> input(2);
    input(0).assign(input1_a);
    input(1).assign(input2_a);
    display(output_a, input, name());
  }
  else {
    display(output_a, input1_a, name());
  }
  
  // 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 channel_index: (input) channel index
//
// return: a boolean value indicating status
//
// this method selects the appropriate computational method for
// the complex data type.
//
boolean Correlation::compute(VectorComplexFloat& output_a,
			     const VectorComplexFloat& input_a,
			     AlgorithmData::COEF_TYPE input_coef_type_a,
			     long channel_index_a) {
  
  // this method is not implemented yet
  //
  return Error::handle(name(), L"compute", Error::NOT_IMPLEM,
		       __FILE__, __LINE__);      
}

// method: compute
//
// arguments:
//  VectorComplexFloat& output: (output) output data
//  const VectorComplexFloat& input1: (input) input data
//  const VectorComplexFloat& input2: (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 selects the appropriate computational method for
// complex data type
//
boolean Correlation::compute(VectorComplexFloat& output_a,
			     const VectorComplexFloat& input1_a,
			     const VectorComplexFloat& input2_a,
			     AlgorithmData::COEF_TYPE input_coef_type_a,
			     long channel_index_a) {

  // this method is not implemented yet
  //
  return Error::handle(name(), L"compute", Error::NOT_IMPLEM,
		       __FILE__, __LINE__);      
}

// method: computeAutoFactoredFromSignal
//
// arguments:
//  VectorFloat& output: (output) Correlation coefficients
//  const VectorFloat& input: (input) input data
//
// return: a boolean value indicating status
//
// this method computes the Autocorrelation function using a factored
// Correlation computation (for sampled data). see:
//
//  J.D. Markel and A.H. Gray, Jr.,
//  Linear Prediction of Speech, Springer-Verlag Berlin Heidelberg,
//  New York, New York, USA, pp. 218, 1976.
//
// see figure 9.2 for a description of the algorithm. note that for
// this algorithm to work, the order must be less than the length/3
//
boolean Correlation::computeAutoFactoredFromSignal(VectorFloat& output_a,
						   const VectorFloat& input_a) {

  // declare local variable
  //
  long order = order_d;
  long N = input_a.length();
  boolean status = false;
  
  // resize the output
  //
  if (!output_a.setLength(order + 1)) {
    return Error::handle(name(), L"computeAutoFactoredFromSignal",
			 ERR, __FILE__, __LINE__);
  }
  
  // algorithm: AUTO
  //  in this approach, we consider the sum from order to N-1
  //
  if (algorithm_d == AUTO) {

    // compute the zeroth term explicitly over the range [order,N]
    //
    double sum = 0.0;
    for (long i = order; i < N; i++) {
      sum += input_a(i) * input_a(i);
    }
    output_a(0) = sum;

    // compute lags on the range [1,order]:
    //  loop over the order
    //
    for (long i = 1; i <= order; i++) {

      // reset the accumulators
      //
      long next = order;
      long last = next;
      sum = 0.0;

      // make sure we don't overrun the end of the vector
      //
      while (last < N) {

	// loop over all partial sums in this block.
	// make sure we don't overrun the edge of the vector
	//
	long np  = next;
	long npm = next - i;
	long npp = next + i;

	for (long j = 0; j < i; j++) {
	  if (npp < N) {
	    sum += input_a(np++) * (input_a(npm++) + input_a(npp++));
	  }
	}

	// increase counters
	//
	next += 2*i;
	last = next + 2*i;
      }
      
      // accumulate the leftover stuff (products that can no longer be grouped
      // because they are too close to the edge of the frame
      //
      long nmi = next - i;
      for (long n = next; n < N; n++) {
	sum += input_a(n) * input_a(nmi++);
      }

      // output the value
      //
      output_a(i) = sum;
    }

    // everything is ok
    //
    status = true;
  }

  // algorithm: AUTO_TAPERED
  //  note that this implementation follows the Markel and Gray reference
  //  above (pg. 217) very closely.
  //
  else {

    // check the order
    //
    if ((order_d < (long)1) ||
	(order_d > (input_a.length() / 3))) {
      return Error::handle(name(), L"computeAutoFactoredFromSignal",
			   ERR_ORDER, __FILE__, __LINE__);
    }  
    
    // declare some local variables
    //
    long mp = (long)order_d + 1;
    long n = input_a.length();
    double rr;
    double tmp;
    
    long i, j, k, im1, mod, nterm, kstrt, kstop, kk;
    long kl = (long)0;
    long kr = (long)0;
    long j_start;
    
    // initialize
    //
    rr = 0.0;
    for (i = 0; i < n; i++) {
      tmp = (double)input_a(i);
      rr += tmp*tmp;
    }
    output_a((long)0) = rr;
    
    // loop over all lags
    //
    for (i = 1; i < mp; i++) {
      
      // compute a new value for rr
      //
      im1 = i;
      mod = im1 + im1;
      nterm = n - im1;
      
      rr = 0.0;
      j_start = i + 1;
      
      for (j = j_start; j <= mod; j++) {
	kr = j - im1;
	for (k = j; k <= nterm; k += mod) {
	  kl = kr;
	  kr = k + im1;
	  rr += input_a(k - 1) * (input_a(kl - 1) + input_a(kr - 1));
	}
      }
      
      // check if we are done
      //
      if (kr != n) {
	
	// compute new start/stop indices
	//
	if ((n - kr) > (long)i) {
	  kstrt = n - mod + 1;
	  kstop = kr;
	}
	else {
	  kstrt = kl + i + 1;
	  kstop = nterm;
	}
	
	// adjust rr
	//
	for (long k = kstrt - 1; k < kstop; k++) {
	  kk = k + im1;
	  rr += input_a(k) * input_a(kk);
	}      
      }
      
      // save output value
      //
      output_a(i) = rr;
    }
  }    

  // normalize:
  //  case: LENGTH
  //  divide by the number of terms used to compute each lag (note for
  //  the tapered algorithm, this depends on the lag
  //
  if (normalization_d == LENGTH) {
    if (N == 0) {
      return Error::handle(name(), L"computeAutoFactoredFromSignal",
			   Error::ARG, __FILE__, __LINE__);
    }
    if (algorithm_d == AUTO) {
      status = output_a.mult(1.0 / ((float)N - (float)order));
    }
    else if (algorithm_d == AUTO_TAPERED) {
      for (long k = 0; k < order; k++) {
	output_a(k) /= N - k;
      }
      status = true;
    }
  }

  // normalize:
  //  case: UNIT_ENERGY
  //  divide by the zeroth order lag
  //
  else if (normalization_d == UNIT_ENERGY) {
    if (output_a(0) == (float)0) {
      return Error::handle(name(), L"computeAutoFactoredFromSignal",
			   Error::ARG, __FILE__, __LINE__);
    }
    output_a.mult(1.0 / output_a(0));
  }

  // exit gracefully
  //
  return status;  
}

// method: computeAutoUnfactoredFromSignal
//
// arguments:
//  VectorFloat& output: (output) Correlation coefficients
//  const VectorFloat& input: (input) input data
//
// return: a boolean value indicating status
//
// this method compute the Correlation function using an unfactored
// computation (for sampled data). see:
//
//  J.D. Markel and A.H. Gray, Jr.,
//  Linear Prediction of Speech, Springer-Verlag Berlin Heidelberg,
//  New York, New York, USA, pp. 14, 1976.
//
// see equation 1.23b.
//
boolean Correlation::computeAutoUnfactoredFromSignal(VectorFloat& output_a,
						     const VectorFloat& input_a) {

  // declare local variables
  //
  boolean status = true;
  long order = order_d;
  long last_index = input_a.length();

  // check order
  //
  if (order_d < (long)1) {
    return Error::handle(name(), L"computeAutoUnfactoredFromSignal",
			 ERR_ORDER, __FILE__, __LINE__);
  }
  else if (last_index < (order + 1)) {
    return Error::handle(name(), L"computeAutoUnfactoredFromSignal",
			 ERR_INSUFD, __FILE__, __LINE__);
  }    
  
  // create output space
  //
  if (!output_a.setLength((long)order_d + 1)) {
    return Error::handle(name(), L"computeAutoUnfactoredFromSignal", ERR,
			 __FILE__, __LINE__, Error::WARNING);
  }
  
  //  loop over all lags
  //
  for (long i = 0; i <= order; i++) {

    // set the starting sample depending on the mode:
    //  AUTO: starting sample is always order_d
    //  AUTO_TAPERED: starting sample is dependent on the lag
    //
    double sum = 0.0;
    long first_index = order;
    if (algorithm_d == AUTO_TAPERED) {
      first_index = i;
    }

    for (long n = first_index; n < last_index; n++) {
      sum += (double)input_a(n) * (double)input_a(n - i);
    }

    // save the output value
    //
    output_a(i) = sum;
  }

  // normalize:
  //  case: LENGTH
  //  divide by the number of terms used to compute each lag (note for
  //  the tapered algorithm, this depends on the lag
  //
  if (normalization_d == LENGTH) {
    if (last_index == 0) {
      return Error::handle(name(), L"computeAutoUnfactoredFromSignal",
			   Error::ARG, __FILE__, __LINE__);
    }
    if (algorithm_d == AUTO) {
      status = output_a.mult(1.0 / ((float)last_index - (float)order));
    }
    else if (algorithm_d == AUTO_TAPERED) {
      for (long k = 0; k < order; k++) {
	output_a(k) /= last_index - k;
      }
      status = true;
    }
  }

  // normalize:
  //  case: UNIT_ENERGY
  //  divide by the zeroth order lag
  //
  else if (normalization_d == UNIT_ENERGY) {
    if (output_a(0) == (float)0) {
      return Error::handle(name(), L"computeAutoUnfactoredFromSignal",
			   Error::ARG, __FILE__, __LINE__);
    }
    output_a.mult(1.0 / output_a(0));
  }

  // exit gracefully
  //
  return true;
}

// method: computeAutoFromPrediction
//
// arguments:
//  VectorFloat& output: (output) Correlation coefficients
//  const VectorFloat& input: (input) input data
//
// return: a boolean value indicating status
//
// this method computes the Autocorrelation coefficients from
// predictor coefficients using Factored algorithm (for linear
// predictor coefficients). see:
//
//  L. Rabiner and R. Schafer,
//  Digital Processing of Speech Signals,
//  Prentice-Hall, Englewood Cliffs, New Jersey, USA, pp. 443, 1976.
//
// see equation 8.128. this algorithm returns normalized autocorrelation
// coefficients (r(0) = 1.0);
//
boolean Correlation::computeAutoFromPrediction(VectorFloat& output_a,
					       const VectorFloat& input_a) {

  // convert to reflection coefficients
  //
  VectorFloat rc;
  convertFromPrediction(rc, input_a);

  // convert to autocorrelation
  //
  return computeAutoFromReflection(output_a, rc);
}

// method: computeAutoFromReflection
//
// arguments:
//  VectorFloat& output: (output) Correlation coefficients
//  const VectorFloat& input: (input) input data
//
// return: a boolean value indicating status
//
// this method computes the Autocorrelation coefficients from
// reflection coefficients. See:
//
//  L. Rabiner and R. Schafer,
//  Digital Processing of Speech Signals,
//  Prentice-Hall, Englewood Cliffs, New Jersey, USA, pp. 443, 1976.
//
// see equation 8.128. this algorithm returns normalized autocorrelation
// coefficients (r(0) = 1.0);
//
boolean Correlation::computeAutoFromReflection(VectorFloat& output_a,
					       const VectorFloat& input_a) {

  // declare local variables
  //
  long i, j;					// loop counters
  long lp_order = order_d;			// analysis order
  long j_bckwd;				        // a backwards index
  long middle_index;				// inner loop limit
  double sum;					// accumulator
  double rc_reg;				// register
  double egy_error;				// normalized error
  VectorFloat pc(lp_order + 1);		        // intermediate storage
  
  // create output space
  //
  if (!output_a.setLength(lp_order + 1)) {
    return Error::handle(name(), L"computeAutoFromReflection", ERR,
			 __FILE__, __LINE__, Error::WARNING);
  }
  
  // initialization
  //
  egy_error = 1.0;
  output_a(0) = egy_error;
  pc(0) = egy_error;
  
  // do the first step manually
  //
  sum = input_a(0);
  output_a(1) = -(float)input_a(0) * egy_error;
  pc(1) = sum;
  egy_error *= (1 - sum * sum);
  
  // recursion
  //
  for (i = 2; i <= lp_order; i++) {
    
    // compute the next reflection coefficient
    //
    sum = 0;
    for (j = 1; j < i; j++) {
      sum += pc(j) * output_a(i - j);
    }
    rc_reg = input_a(i - 1);
    output_a(i) = -rc_reg * egy_error - sum;
    
    // compute the new prediction coefficients:
    //  note that an algorithm implemented in
    //  Markel and Gray, Linear Prediction of Speech, pp. 219,
    //  is used here to minimize storage.
    //
    pc(i) = rc_reg;
    j_bckwd = i - 1;
    middle_index = i / 2;
    for (j = 1; j <= middle_index; j++) {
      sum = (float)pc(j_bckwd) + rc_reg*pc(j);
      pc(j) = (float)pc(j) + rc_reg*pc(i - j);
      pc(j_bckwd) = sum;
      j_bckwd--;
    }
    
    // compute new error
    //
    egy_error *= 1.0 - rc_reg * rc_reg;
  }
  
  // exit gracefully
  //
  return true;    
}

// method: convertFromPrediction
//
// arguments:
//  VectorFloat& output: (output) LP reflection coefficients
//  const VectorFloat& input: (input) LP predictor coefficients
//
// return: a boolean value indicating status
//
// this method converts linear prediction reflection coefficients
// to predictor coefficients. See:
//
//  J.D. Markel and A.H. Gray, Jr.,
//  Linear Prediction of Speech, Springer-Verlag Berlin Heidelberg,
//  New York, New York, USA, pp. 96, 1976.
//
boolean Correlation::convertFromPrediction(VectorFloat& output_a,
					   const VectorFloat& input_a) {

  // copy the input (so we don't destroy it)
  // and create space for the output data
  //
  VectorFloat a(input_a);
  output_a.setLength(order_d);

  // create additional scratch space
  //
  VectorFloat b(input_a.length());

  // declare some local counters
  //
  long m = order_d;
  long mr;
  long j, k;

  float d;
  float a_tmp;

  // Loop Over All Orders
  //
  for (j = 0; j < m; j++) {

    // establish preliminary counters
    //
    mr = m - j;
    a_tmp = a(mr);
    d = 1.0 - a_tmp*a_tmp;

    // store a() in reverse order
    //
    for (k = 0; k < mr; k++) {
      b(k) = a(mr - k);
    }

    // compute the new predictor coefficients
    //
    for (k = 0; k < mr; k++) {

      // check for a divide by zero error
      //
      if (d == 0) {
	return Error::handle(name(), L"convertFromPrediction",
			     Error::ARG, __FILE__, __LINE__);
      }
      a(k) = (a(k) - a_tmp * b(k)) / d;
    }
    
    // save the output value
    //
    output_a(mr - 1) = a_tmp;

    // stability check
    //
    if ((a_tmp > 1.0) || (a_tmp < -1.0)) {
      return Error::handle(name(), L"convertFromPrediction",
			   ERR_STABLE, __FILE__, __LINE__, Error::WARNING);
    }
  }
  
  // exit gracefully
  //
  return true;
}

// method: computeCross
//
// arguments:
//  VectorFloat& output: (output) Correlation coefficients
//  const VectorFloat& ref: (input) reference signal
//  const VectorFloat& tst: (input) test signal
//
// return: a boolean value indicating status
//
// this method performs crosscorrelation between two signals. note that
// this implementation is not symmetric - the first signal is taken as
// the reference and the length of the correlation window is taken
// as the length of the second signal.
//
boolean Correlation::computeCross(VectorFloat& output_a,
				  const VectorFloat& ref_a,
				  const VectorFloat& tst_a) {

  // declare local variables
  //
  long input_len = ref_a.length();
  long tst_len = tst_a.length();
  order_d = input_len;
  
  // check the lengths
  //
  if (tst_len > input_len) {
    return Error::handle(name(), L"computeCross",
			 ERR_INSUFD, __FILE__, __LINE__);
  }
  
  // create output space
  //
  if (!output_a.setLength(order_d)) {
    return Error::handle(name(), L"computeCross", ERR,
			 __FILE__, __LINE__);
  }
  
  // calculate the output
  //
  for (long lag_index = 0; lag_index < order_d; lag_index++) {
	
      // declare local variables
      //
      double sum = 0.0;
	
      // loop over possible samples
      //
      long last_index = (long)Integral::min(tst_len, input_len - lag_index);

      for (long sample_index = 0;
	   sample_index < last_index; sample_index++) {
	  
	// accumulate the dot product
	//
	sum += (double)ref_a(lag_index + sample_index) *
	  (double)tst_a(sample_index);
      }

      // output sum
      //
      output_a(lag_index) = sum;
    }
  
  // normalization type: LENGTH
  //
  if (normalization_d == LENGTH) {

    // check for a divide by zero error
    //
    if (tst_len == 0) {
      return Error::handle(name(), L"computeCross",
			   Error::ARG, __FILE__, __LINE__);
    }
    
    // perform LENGTH normalization and exit gracefully
    //
    float temp = (float)1 / ((float)tst_len);
    return output_a.mult(temp);
  }

  // normalization type: UNIT_ENERGY
  //
  else if (normalization_d == UNIT_ENERGY) {

    // check for a divide by zero error
    //
    float sum_t = Integral::sqrt(tst_a.sumSquare());
    float sum_r = Integral::sqrt(ref_a.sumSquare());

    if ((sum_t == 0) || (sum_r == 0)) {
      return output_a.mult(0.0);
    }
    
    // perform UNIT_ENERGY normalization and exit gracefully
    //
    float temp = (float)1 / Integral::sqrt(ref_a.sumSquare() *
					   tst_a.sumSquare());
    return output_a.mult(temp);
  }

  // exit gracefully
  //
  return true;
}

// method: computeConv
//
// arguments:
//  VectorFloat& output: (output) convolution output
//  const VectorFloat& input: (input) input data, input signal
//  const VectorFloat& filter: (input) input data, filter signal
//
// return: a boolean value indicating status
//
// this method computes the convolution between two signals. see:
//
//  John G. Proakis, Dimitris G. Manolakis,
//  Digital Signal Processing: Principles, Algorithms, and Applications,
//  Second Edition, Macmillan Publishing Company, New York, USA,
//  pp. 75-76, 1992
//
// particularly equation 2.3.17.
//
boolean Correlation::computeConv(VectorFloat& output_a,
				 const VectorFloat& input_a,
				 const VectorFloat& filter_a) {

  // declare local variables
  //
  boolean status = true;
  long input_len = input_a.length();
  long filter_len = filter_a.length();
  
  // check order
  //
  if ((input_len <= 0) || (filter_len <= 0)) {
    return Error::handle(name(), L"computeConv",
			 ERR_INSUFD, __FILE__, __LINE__);
  }
  
  // create output space
  //
  if (!output_a.setLength(input_len)) {
    return Error::handle(name(), L"computeConv",
			 ERR, __FILE__, __LINE__);
  }
  output_a.clear(Integral::RETAIN);

  // loop through all necessary input signal samples
  //
  for (long output_index = 0; output_index < input_len; output_index++) {

    // loop through all necessary filter signal samples
    //
    long last_filter_index = (long)Integral::min(filter_len, output_index + 1);

    for (long filter_index = 0;
	 filter_index < last_filter_index; filter_index++) {

      // sum the product of the input signal and filter signal
      //
      output_a(output_index) += input_a(output_index - filter_index) *
	filter_a(filter_index);
    }
  }
  
  // normalization type: LENGTH
  //
  if (normalization_d == LENGTH) {

    // check for a divide by zero error
    //
    if (filter_len == 0) {
      return Error::handle(name(), L"computeConv",
			   Error::ARG, __FILE__, __LINE__);
    }
    
    // perform LENGTH normalization and exit gracefully
    //
    float temp = (float)1 / ((float)filter_len);
    return output_a.mult(temp);
  }

  // normalization type: UNIT_ENERGY
  //
  else if (normalization_d == UNIT_ENERGY) {

    // check for a divide by zero error
    //
    if ((Integral::sqrt(filter_a.sumSquare() *
			input_a.sumSquare())) == 0) {
      return Error::handle(name(), L"computeConv",
			   Error::ARG, __FILE__, __LINE__);
    }
			
    // perform UNIT_ENERGY normalization and exit gracefully
    //
    float temp = (float)1 / Integral::sqrt(filter_a.sumSquare() *
					   input_a.sumSquare());
    status = output_a.mult(temp);
  }

  // exit gracefully
  //
  return status;
}
