// file: $isip_ifc/class/algo/Window/win_06.cc
// version: $Id: win_06.cc 10561 2006-04-21 04:12:06Z srinivas $
//

// isip include files
//
#include "Window.h"
#include "Bessel.h"
#include "Chebyshev.h"

// method: setConstants
//
// arguments: 
//  const VectorFloat& values: (input) vector of constants
//
// return: a boolean value indicating status
//
// this method sets the constants of the window
//
boolean Window::setConstants(const VectorFloat& values_a) {
  
  // make sure the correct number of coefficients are specified
  // for the given algorithm
  //
  if (algorithm_d == RECTANGULAR) {
    if (values_a.length() != DEF_RECT_CONSTANTS.length()) {
      return Error::handle(name(), L"setConstants", ERR, __FILE__, __LINE__);
    }
  }
  else if (algorithm_d == BARTLETT) {
    if (values_a.length() != DEF_BART_CONSTANTS.length()) {
      return Error::handle(name(), L"setConstants", ERR, __FILE__, __LINE__);
    }
  }
  else if (algorithm_d == BLACKMAN) {
    if (values_a.length() != DEF_BLCK_CONSTANTS.length()) {
      return Error::handle(name(), L"setConstants", ERR, __FILE__, __LINE__);
    }
  }
  else if (algorithm_d == DOLPH_CHEBYSHEV) {
    if (values_a.length() != DEF_DCHB_CONSTANTS.length()) {
      return Error::handle(name(), L"setConstants", ERR, __FILE__, __LINE__);
    }
  }
  else if (algorithm_d == GAUSSIAN) {
    if (values_a.length() != DEF_GAUS_CONSTANTS.length()) {
      return Error::handle(name(), L"setConstants", ERR, __FILE__, __LINE__);
    }
  }
  else if (algorithm_d == HAMMING) {
    if (values_a.length() != DEF_HAMM_CONSTANTS.length()) {
      return Error::handle(name(), L"setConstants", ERR, __FILE__, __LINE__);
    }
  }
  else if (algorithm_d == HANNING) {
    if (values_a.length() != DEF_HANN_CONSTANTS.length()) {
      return Error::handle(name(), L"setConstants", ERR, __FILE__, __LINE__);
    }
  }
  else if (algorithm_d == KAISER) {
    if (values_a.length() != DEF_KAIS_CONSTANTS.length()) {
      return Error::handle(name(), L"setConstants", ERR, __FILE__, __LINE__);
    }
  }
  else if (algorithm_d == LIFTER) {
    if (values_a.length() != DEF_LIFT_CONSTANTS.length()) {
      return Error::handle(name(), L"setConstants", ERR, __FILE__, __LINE__);
    }
  }
  else if (algorithm_d == CUSTOM) {
    return Error::handle(name(), L"setConstants", ERR, __FILE__, __LINE__);
  }
  else {
    return Error::handle(name(), L"setConstants", Error::ENUM,
			 __FILE__, __LINE__);
  }
  
  // set the constants
  //
  constants_d.assign(values_a);
  
  // make the window invalid
  //
  is_valid_d = false;
  
  // exit gracefully
  //
  return true;
}

// method: generateRectangular
//
// arguments: 
//  VectorFloat& output: (output) the window function
//  const VectorFloat& params: (input) window function parameters
//  long num_points: (input) number of points in the window
//
// return: a boolean value indicating status
//
// this method generates a rectangular window function:
//
//  w(n) = { G   n <= |M|
//         { 0   elsewhere
//
// there is one parameter allowed for this window:
//
//  params(0): G, the gain of the window
//
boolean Window::generateRectangular(VectorFloat& output_a,
				    const VectorFloat& params_a,
				    long num_points_a) const {
  
  // for even or odd number of points, do the same thing
  //
  return output_a.assign(num_points_a, params_a(0));
}

// method: generateBartlett
//
// arguments: 
//  VectorFloat& output: (output) the window function
//  const VectorFloat& params: (input) window function parameters
//  long num_points: (input) number of points in the window
//
// return: a boolean value indicating status
//
// this method generates a Bartlett (triangular) window function:
//
//  w(n) = { G (1 - |n| / (M + 1))  n <= |M|
//         { 0                      elsewhere
//
// where M = numn_points_a / 2. a good reference for this window
// can be found at:
//
//  R.A. Roberts and C.T. Mullis, Digital Signal Processing,
//  Addison Wesley Publishing Company, Reading, Massachusetts, USA,
//  1987, pp. 183-188.
//
// there is one parameter allowed for this window:
//
//  params(0): G, the gain of the window
//
// for an odd number of points, the window is defined as above.
// for an even number of points, an interpolation technique is used.
//
boolean Window::generateBartlett(VectorFloat& output_a,
				 const VectorFloat& params_a,
				 long num_points_a) const {
  
  // compute some constants related to the length of the window
  //
  long N = num_points_a;
  long Nm1 = N - 1;
  long Nm2 = Nm1 - 1;
  float scale = (float)Nm2 / (float)Nm1;
  
  // compute the half-window length
  //
  long M = (N - 1) / 2;
  long Mp1 = M + 1;
  
  // create output space
  //
  output_a.setLength(N);
  
  // case 1: an odd number of points
  //
  if ((N % 2) != 0) {
    for (long n = -M; n <= M; n++) {
      output_a(n + M) = (float)params_a(0) * (1 - Integral::abs(n) / (float)(Mp1));
    }
  }
  
  // case 2: an even number of points
  //  resample an (N-1) point window
  //
  else {
    
    // generate a window for (N-1) points
    // sample it at a new time scale 
    //
    for (long n = 0; n < N; n++) {
      float scaled_index = -M + scale * n;
      output_a(n) = (float)params_a(0) *
	(1 - Integral::abs(scaled_index) / (float)(Mp1));
    }
  }    
  
  // exit gracefully
  //
  return true;
}

// method: generateBlackman
//
// arguments: 
//  VectorFloat& output: (output) the window function
//  const VectorFloat& params: (input) window function parameters
//  long num_points: (input) number of points in the window
//
// return: a boolean value indicating status
//
// this method generates a Blackman window function:
//
//  w(n) = { G (a1 + a2 * cos(2*PI*n / (2*M+1))
//         {       + (0.5 - a1) * cos(4*PI*n / (2*M+1))    n <= |M|
//         { 0                                             elsewhere
//
// a good reference for this window can be found at:
//
//  S.K. Mitra, Digital Signal Processing,
//  McGraw-Hill, Boston, Massachusetts, USA, 2001, pp. 452.
//
// there are three parameters allowed for this window:
//
//  params(0): G, the gain of the window
//  params(1): a1, the DC offset
//  params(2): a2, the first harmonic weight
//
// for an odd number of points, the window is defined as above.
// for an even number of points, an interpolation technique is used.
//
boolean Window::generateBlackman(VectorFloat& output_a,
				 const VectorFloat& params_a,
				 long num_points_a) const {
  
  // compute some constants related to the length of the window
  //
  long N = num_points_a;
  long Nm1 = N - 1;
  long Nm2 = Nm1 - 1;
  float scale = (float)Nm2 / (float)Nm1;
  
  // compute the half-window length
  //
  long M = (N - 1) / 2;
  long Mp1 = M + 1;
  
  // create output space
  //
  output_a.setLength(N);
  
  // case 1: an odd number of points
  //
  if ((N % 2) != 0) {
    for (long n = -M; n <= M; n++) {
      float arg1 = 2.0 * Integral::PI * (float)(n) / (float)Mp1;
      float arg2 = 2.0 * arg1;
      output_a(n + M) = (float)params_a(0) *
	((float)params_a(1) + (float)params_a(2) * Integral::cos(arg1) +
	 ((float)params_a(2) - (float)params_a(1)) * Integral::cos(arg2));
    }
  }
  
  // case 2: an even number of points
  //  resample an (N-1) point window
  //
  else {
    
    // generate a window for (N-1) points
    // sample it at a new time scale 
    //
    for (long n = 0; n < N; n++) {
      float scaled_index = -M + scale * n;
      float arg1 = 2.0 * Integral::PI * scaled_index / (float)Mp1;
      float arg2 = 2.0 * arg1;
      output_a(n) = (float)params_a(0) *
	((float)params_a(1) + (float)params_a(2) * Integral::cos(arg1) +
	 ((float)params_a(2) - (float)params_a(1)) * Integral::cos(arg2));
    }
  }
  
  // exit gracefully
  //
  return true;
}

// method: generateDolphChebyshev
//
// arguments: 
//  VectorFloat& output: (output) the window function
//  const VectorFloat& params: (input) window function parameters
//  long num_points: (input) number of points in the window
//
// return: a boolean value indicating status
//
// this method generates a Dolph-Chebyshev window function:
//
//  w(n) = { G/(2M+1) * [1/gamma + 
//         {             2 * sum Cheb(beta*k*PI/(2M+1)) *
//         {             cos(2*n*k*PI/(2M+1))]                  n <= |M|
//         { 0                                                  elsewhere
//
//  there are a couple of important auxiliary equations:
//
//   alpha_s = (2*M * 2.285 * (float)dw + 16.4) / 2.056
//
//  where dw is the transition bandwidth in normalized frequency.
//  using alpha_s, we compute gamma and beta (needed above):
//
//   gamma = 10 ** (-alpha_s / 20.0)
//   beta = cosh((1/2M) * acosh(1/gamma))
//
// a good reference for this window can be found at:
//
//  S.K. Mitra, Digital Signal Processing,
//  McGraw-Hill, Boston, Massachusetts, USA, 2001, pp. 456.
//
// there are two parameters allowed for this window:
//
//  params(0): G, the gain of the window
//  params(1): dw, the normalized transition bandwidth
//
// for an odd number of points, the window is defined as above.
// for an even number of points, an interpolation technique is used.
//
boolean Window::generateDolphChebyshev(VectorFloat& output_a,
				       const VectorFloat& params_a,
				       long num_points_a) const {
  
  // compute some constants related to the length of the window
  //
  long N = num_points_a;
  long Nm1 = N - 1;
  long Nm2 = Nm1 - 1;
  float scale = (float)Nm2 / (float)Nm1;
  
  // compute the half-window length
  //
  long M = (N - 1) / 2;
  
  // create output space
  //
  output_a.setLength(num_points_a);
  
  // in this approach, the constant represents the transition bandwidth.
  // the window length is supplied by the user. we compute the appropriate
  // constants to satisfy this relationship.
  //
  float A = (2*M * 2.285 * (float)params_a(1) + 16.4) / 2.056;
  float gamma = Integral::pow(10.0, -A / Integral::DB_MAG_SCALE_FACTOR);
  float gamma_inv = 1.0 / gamma;
  
  // from gamma and M, we compute beta
  //
  float beta = Integral::cosh(Integral::acosh(gamma_inv) / (float)(2*M));
  
  // case 1: an odd number of points
  //
  if ((N % 2) != 0) {
    for (long n = -M; n <= M; n++) {
      
      // compute the inner summation
      //
      float sum = 0;
      for (long k = 1; k < M; k++) {
	float arg1 = (float)k * Integral::PI / (float)(N);
	float arg2 = 2 * n * arg1;
	float val;
	Chebyshev::compute(val, beta * Integral::cos(arg1), k);
	sum += val * Integral::cos(arg2);
      }
      
      // save the output value
      //
      output_a(n + M) = params_a(0) / (float)(N) * (gamma_inv + 2 * sum);
    }
  }
  
  // case 2: an even number of points
  //  use a resampling strategy
  //
  else {
    for (long n = 0; n < N; n++) {
      float scaled_index = -M + scale * n;
      
      // compute the inner summation
      //
      float sum = 0;
      for (long k = 1; k < M; k++) {
	float arg1 = (float)k * Integral::PI / (float)(Nm1);
	float arg2 = 2 * scaled_index * arg1;
	float val;
	Chebyshev::compute(val, beta * Integral::cos(arg1), k);
	sum += val * Integral::cos(arg2);
      }
      
      // save the output value
      //
      output_a(n) = params_a(0) / (float)(Nm1) * (gamma_inv + 2 * sum);
    }
  }
  
  // exit gracefully
  //
  return true;
}

// method: generateGaussian
//
// arguments: 
//  VectorFloat& output: (output) the window function
//  const VectorFloat& params: (input) window function parameters
//  long num_points: (input) number of points in the window
//
// return: a boolean value indicating status
//
// this method generates a Gaussian window function:
//
//                            2                  2
//  w(n) = { G * exp (-0.5 * k * (tan(theta_0/2))  ,           n <= |M|
//         { 0                                                 elsewhere
//
// theta_0 is the width of the main lobe in radians, and is related
// to the frequency response of the window.
// a good reference for this window can be found at:
//
//  R.A. Roberts and C.T. Mullis, Digital Signal Processing,
//  Addison Wesley Publishing Company, Reading, Massachusetts, USA,
//  1987, pp. 183-188.
//
// there are two parameters allowed for this window:
//
//  params(0): G, the gain of the window
//  params(1): theta_0, the main lobe width
//
// for an odd number of points, the window is defined as above.
// for an even number of points, an interpolation technique is used.
//
boolean Window::generateGaussian(VectorFloat& output_a,
				 const VectorFloat& params_a,
				 long num_points_a) const {
  
  // compute some constants related to the length of the window
  //
  long N = num_points_a;
  long Nm1 = N - 1;
  long Nm2 = Nm1 - 1;
  float scale = (float)Nm2 / (float)Nm1;
  
  // compute the half-window length
  //
  long M = (N - 1) / 2;
  
  // define some useful constants related to the Gaussian function
  //
  float theta_0 = params_a(1);
  float arg1 = (float)(theta_0 / 2.0);
  float arg2 = Integral::tan(arg1);
  
  // create output space
  //
  output_a.setLength(num_points_a);
  
  // case 1: an odd number of points
  //
  if ((N % 2) != 0) {
    
    // loop over all elements
    //
    for (long n = -M; n <= M; n++) {
      output_a(n + M) = (float)params_a(0) *
	Integral::exp(-0.5 * (float)n * (float)n * arg2 * arg2);
    }
  }
  
  // case 2: an even number of points
  //  use a resampling strategy
  //
  else {
    for (long n = 0; n < N; n++) {
      float scaled_index = -M + scale * n;
      output_a(n) = (float)params_a(0) *
	Integral::exp(-0.5 * (float)scaled_index * (float)scaled_index *
		      arg2 * arg2);
    }
  }
  
  // exit gracefully
  //
  return true;
}

// method: generateGeneralizedHanning
//
// arguments: 
//  VectorFloat& output: (output) the window function
//  const VectorFloat& params: (input) window function parameters
//  long num_points: (input) number of points in the window
//
// return: a boolean value indicating status
//
// this method generates a generalized Hanning window function:
//
//  w(n) = { G * (alpha - (1 - alpha) cos (2 * PI * n / (N-1)))  0 <= n < N
//         { 0                                                   elsewhere
//
// the constant alpha can be used to implement a variety of windows,
// including a Hamming (alpha = 0.54) and Hanning (alpha = 0.50).
// a good reference for this window can be found at:
//
//  L.R. Rabiner and R.W. Schafer, Digital Processing of Speech Signals,
//  Prentice-Hall, Englewood Cliffs, New Jersey, USA, 1978, pp. 121.
//
// there are two parameters allowed for this window:
//
//  params(0): G, the gain of the window
//  params(1): alpha, the main lobe width
//
// this window works the same way for both odd and even samples.
//
boolean Window::generateGeneralizedHanning(VectorFloat& output_a,
					   const VectorFloat& params_a,
					   long num_points_a) const {
  
  // compute some constants related to the length of the window
  //
  long N = num_points_a;
  long Nm1 = N - 1;
  
  // create output space
  //
  output_a.setLength(N);
  
  // loop over all elements
  //
  for (long n = 0; n < N; n++) {
    float arg1 = 2.0 * Integral::PI * (float)n / (float)(Nm1);
    output_a(n) = (float)params_a(0) * ((float)params_a(1) -
				 (1 - (float)params_a(1)) * Integral::cos(arg1));
  }
  
  // exit gracefully
  //
  return true;
}

// method: generateKaiser
//
// arguments: 
//  VectorFloat& output: (output) the window function
//  const VectorFloat& params: (input) window function parameters
//  long num_points: (input) number of points in the window
//
// return: a boolean value indicating status
//
// this method generates a Kaiser window function:
//
//                                   2
//  w(n) = { I (beta * sqrt(1 - (n/M) ) / I (beta)             n <= |M|
//            0                            0
//       = { 0                                                 elsewhere
//
//  there are a couple of important auxiliary design equations:
//
//   alpha_s = 2.285 * N * dw + 8
//
//  where dw is the transition bandwidth in normalized radian frequency and
//  N is the window length. from alpha_a, we compute beta using the
//  following heuristically derived equations:
//
//          { 0.1102(alpha_s - 8.7), 		     alpha_s > 50
//   beta = { 0.5842(alpha_s - 21)**0.4 +
//                0.07886(alpha_s - 21)              21 <= alpha_s <= 50
//          { 0                                      alpha_s < 21
//
// a good reference for this window can be found at:
//
//  S.K. Mitra, Digital Signal Processing,
//  McGraw-Hill, Boston, Massachusetts, USA, 2001, pp. 456.
//
// there are two parameters allowed for this window:
//
//  params(0): G, the gain of the window
//  params(1): dw, the normalized transition bandwidth
//
// for an odd number of points, the window is defined as above.
// for an even number of points, an interpolation technique is used.
//
boolean Window::generateKaiser(VectorFloat& output_a,
			       const VectorFloat& params_a,
			       long num_points_a) const {
  
  // compute some constants related to the length of the window
  //
  long N = num_points_a;
  long Nm1 = N - 1;
  long Nm2 = Nm1 - 1;
  float scale = (float)Nm2 / (float)Nm1;
  
  // compute the half-window length
  //
  long M = (N - 1) / 2;
  
  // create output space
  //
  output_a.setLength(N);
  
  // convert the transition bandwidth (delta_w) and window length (M) to
  // an attenuation in dB (alpha_s)
  //
  float alpha_s = N * 2.285 * params_a(1) + 8.0;
    
  // convert the attenuation to beta
  //
  float beta;

  if (alpha_s >= 50.0) {
    beta = 0.1102 * (alpha_s - 8.7);
  }
  else if ((alpha_s > 21) && (alpha_s < 50)) {
    beta = 0.5842 * Integral::pow(alpha_s - 21.0, 0.4) +
      0.07886 * (alpha_s - 21.0);
  }
  else {
    return Error::handle(name(), L"generateKaiser",
			 ERR_PRM, __FILE__, __LINE__);
  }
  
  // precompute the normalization factor
  //
  float num, denom;
  Bessel::compute(denom, beta, 0);
    
  // case 1: an odd number of points
  //
  if ((N % 2) != 0) {
    for (long n = -M; n <= M; n++) {

      // compute the argument and then the Bessel function value
      //
      float n_M = (float)n / (float)M;
      float arg1 = beta * Integral::sqrt(1.0 - n_M * n_M);
      Bessel::compute(num, arg1, 0);

      // save the output value
      //
      output_a(n + M) = params_a(0) * num / denom;
    }
  }

  // case 2: an even number of points
  //  use a resampling technique
  //
  else {
    for (long n = 0; n < N; n++) {

      // compute the argument and then the Bessel function value
      //
      float scaled_index = -M + scale * n;
      float n_M = Integral::min(scaled_index / (float)M, 1.0);
      float arg1 = beta * Integral::sqrt(1.0 - n_M * n_M);
      Bessel::compute(num, arg1, 0);

      // save the output value
      //
      output_a(n) = params_a(0) * num / denom;
    }
  }
  // exit gracefully
  //
  return true;
}

// method: generateLifter
//
// arguments: 
//  VectorFloat& output: (output) the window function
//  const VectorFloat& params: (input) window function parameters
//  long num_points: (input) number of points in the window
//
// return: a boolean value indicating status
//
// this method generates a raised sine, often called a lifter,
// window function:
//
//  w(n) = { G * (1 + h * sin(n * PI / L))          1 <= n <= L
//         { 0                                      elsewhere
//
// the constant h is normally set to L/2. note that L != N.
// a good reference for this window can be found at:
//
//  L. Rabiner and B.H. Juang, Fundamentals of Speech Recognition,
//  Prentice-Hall, Englewood Cliffs, New Jersey, USA, 1993, pp. 169.
//
// there are two parameters allowed for this window:
//
//  params(0): G, the gain of the window
//  params(1): L, the raised sine frequency
//  params(2): h, the raised sine scale factor
//
boolean Window::generateLifter(VectorFloat& output_a,
			       const VectorFloat& params_a,
			       long num_points_a) const {
  
  // compute some constants related to the length of the window
  //
  long N = num_points_a;
  float L = params_a(1);
  float h = params_a(2);

  // create output space
  //
  output_a.setLength(N);
  output_a(0) = 0.0;
  
  // loop over all elements
  //
  for (long n = 1; n < N; n++) {

    // check the limits
    //
    if (n <= L) {
      float arg1 = (float)n * Integral::PI / L;
      output_a(n) = (float)params_a(0) * (1 + h * Integral::sin(arg1));
    }
    else {
      output_a(n) = 0;
    }
  }
  
  // exit gracefully
  //
  return true;
}

// method: getLeadingPad
//
// arguments: none 
//
// return: a long value indicating leading pad (in units of frames)
//
long Window::getLeadingPad() const {

  // in FRAME_INTERNAL mode, we only deal with the current frame
  //
  if (cmode_d == FRAME_INTERNAL) {
    return 0;
  }
  
  // for a right-aligned window, there is no leading pad
  //
  if (alignment_d == RIGHT) {
    return 0;
  }

  // for a left-aligned window, we need to compute the overlap
  // (fixed a previous bug by doing calculations in num samples instead of seconds)
  //
  long frame_nsamp = (long)Integral::round(frame_dur_d * sample_freq_d);
  long duration_nsamp = (long)Integral::round(duration_d * sample_freq_d);
  float overlap = (duration_nsamp - frame_nsamp) / (float)frame_nsamp;
  //float overlap = (duration_d - frame_dur_d) / frame_dur_d;

  if (alignment_d == LEFT) {
    return (long)Integral::ceil(overlap);
  }

  // for a center-aligned window, we need to compute the overlap
  //
  else if (alignment_d == CENTER) {
    return (long)Integral::ceil(overlap / 2);
  }

  // else: exit ungracefully
  //
  else {
    return Error::handle(name(), L"getLeadingPad",
			 ERR_PRM, __FILE__, __LINE__);
  }
}

// method: getTrailingPad
//
// arguments: none 
//
// return: a long value indicating the trailing pad (in units of frames)
//
long Window::getTrailingPad() const {

  // in FRAME-INTERNAL mode, we only deal with the current frame
  //
  if (cmode_d == FRAME_INTERNAL) {
    return 0;
  }
  
  // for a right-aligned window, there is no leading pad
  //
  if (alignment_d == LEFT) {
    return 0;
  }

  // for a right-aligned window, we need to compute the overlap
  // (fixed a previous bug by doing calculations in num samples instead of seconds)
  //
  long frame_nsamp = (long)Integral::round(frame_dur_d * sample_freq_d);
  long duration_nsamp = (long)Integral::round(duration_d * sample_freq_d);
  float overlap = (duration_nsamp - frame_nsamp) / (float)frame_nsamp;
  //float overlap = (duration_d - frame_dur_d) / frame_dur_d;

  if (alignment_d == RIGHT) {
    return (long)Integral::ceil(overlap);
  }

  // for a center-aligned window, we need to compute the overlap
  //
  else if (alignment_d == CENTER) {
    return (long)Integral::ceil(overlap / 2);
  }

  // else: exit ungracefully
  //
  else {
    return Error::handle(name(), L"getTrailingPad", ERR_PRM,
			 __FILE__, __LINE__);
  }
}
