/* **********************************************************************
 * *                                                                    *
 * *      Simple Discrete Fourier Transform and Windowing Routines      *
 * *                                                                    *
 * **********************************************************************
 * File: libs/user/dft.c
 *
 *                        COPYRIGHT (c) 2003  BY
 *          SIGMET INCORPORATED, WESTFORD MASSACHUSETTS, U.S.A.
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <errno.h>

#include "sigtypes.h"
#include "dsp.h"
#include "user_lib.h"
#include "himath_lib.h"

/* ------------------------------
 * Set up cosine coefficients for a variety of different windows, and
 * return a gain correction for total power.
 */
FLT4 fSetWindowCoefficients
( SINT4 iWinType_a, SINT4 iSize_a, FLT4 fWinCoefs_a[3] )
{
  FLT4 fWinCoefs[3] = { 1.0, 0.0, 0.0 }, fScale ;

  switch( iWinType_a ) {
  case WIN_HAMMING:
    fWinCoefs[0] =  0.54 ; fWinCoefs[1] = -0.46 ; fWinCoefs[2] =  0.00 ;
    break ;

  case WIN_BLACKMAN:
    fWinCoefs[0] =  0.42 ; fWinCoefs[1] = -0.50 ; fWinCoefs[2] =  0.08 ;
    break ;

  case WIN_VONHANN:
    fWinCoefs[0] =  0.50 ; fWinCoefs[1] = -0.50 ; fWinCoefs[2] =  0.00 ;
    break ;

  case WIN_BLKMANEX:	 /* Exact Blackman window is size dependent */
    if( iSize_a >= 4 ) {
      FLT4 fAlpha = -0.5 / (1.0 + cos( (2*FPI) / (FLT4)(iSize_a - 1) ) ) ;
      fWinCoefs[0] = (fAlpha + 1.0) / 2.0 ;
      fWinCoefs[1] = -0.5 ;
      fWinCoefs[2] = -fAlpha / 2.0 ;
    }
  }
  /* Return coefficients if the caller actually seems to want them
   */
  if( fWinCoefs_a ) {
    fWinCoefs_a[0] = fWinCoefs[0] ;
    fWinCoefs_a[1] = fWinCoefs[1] ;
    fWinCoefs_a[2] = fWinCoefs[2] ;
  }
  /* Calculate and return the scale factor required to keep the peak
   * spectral height fixed for each choice of window.
   */
  fScale = 1.0 +
    ( SQUARE( fWinCoefs[1] / fWinCoefs[0] ) / 2.0 ) +
    ( SQUARE( fWinCoefs[2] / fWinCoefs[0] ) / 2.0 ) ;

  return( fScale ) ;
}

/* ------------------------------
 * Calculate the value of a window function of a given length at a
 * given point.  The window definition used here differs slightly from
 * the traditional definition in being offset by half a point.  This
 * makes the window both symmetric and nonzero everywhere, which are
 * both desirable qualities.  Also, the selected point is generalized
 * to allow non-integer values ranging from -0.5 to (ISIZE-1)+0.5,
 * i.e., half a point wider in each direction than the nominal range
 * of integer values.
 */
FLT4 fWindowValue( FLT4 fPoint_a, SINT4 iSize_a, const FLT4 fWinCoefs_a[3] )
{
  FLT4 fRotation = (0.5 + fPoint_a) / (FLT4)iSize_a ;  
  FLT4 fAngle = 2.0 * FPI * MAX( 0.0, MIN( 1.0, fRotation ) ) ;
  FLT4 fWinVal = ( ( fWinCoefs_a[0]                     ) +
		   ( fWinCoefs_a[1] * cos(      fAngle) ) +
		   ( fWinCoefs_a[2] * cos(2.0 * fAngle) ) ) ;
  return( fWinVal ) ;
}

/* ------------------------------
 * Given a window type, return a reasonable bound on the usable depth
 * (dynamic range) of spectra that are computed from it.
 */
FLT4 fWindowDepthDB( SINT4 iWinType_a )
{
  FLT4 fDepthDB ;
  switch( iWinType_a ) {
  default:
  case WIN_RECT    : fDepthDB = -13.0 ; break ;
  case WIN_HAMMING : fDepthDB = -42.0 ; break ;
  case WIN_VONHANN :
  case WIN_BLACKMAN:
  case WIN_BLKMANEX: fDepthDB = -57.0 ; break ;
  }
  return( fDepthDB ) ;
}

/* ------------------------------
 * Brute force routine to compute a Discrete Fourier Transform.  Use
 * this only when you need the full generality of continuous spectrum
 * sizes (can't use FFT routines) and platform independence (can't use
 * Intel IPP DFT routines).
 */
MESSAGE dft_complex
( const FLT4 *freal_in_a,	/* Real input data */
  const FLT4 *fimag_in_a,	/* Imaginary input data */
  SINT4 inc_in_a,		/* Increment between input values */
  FLT4 *freal_out_a,		/* Real and imaginary output data */
  FLT4 *fimag_out_a,		/*   (okay to overlap input data) */
  SINT4 inc_out_a,		/* Increment between output values */
  SINT4 iSize_a,		/* Transform size */
  UINT4 iflags_a )		/* Control Flags (from FFT_xxx) */
{
  FLT4 *pSinCosReIm, *pSinTab, *pCosTab, *pReRslt, *pImRslt ;
  SINT4 ii, iSpec ;

  /* Check for illegal sizes, and try to allocate the memory that
   * we'll use to hold sin/cos lookup tables and final transform
   * results.  We can't share the sin/cos tables because the input
   * size may not be divisible by four.
   */
  if( iSize_a <= 0 )
    return( iMessageMod_va( USER_ZLEN_FILE, 1, "dft_complex()" ) ) ;
  if( ! ( pSinCosReIm = malloc( iSize_a * 4 * sizeof(FLT4) ) ) )
    return( errno_to_message_va( errno, 1, "dft_complex()" ) ) ;

  pSinTab = &pSinCosReIm[ 0           ] ;
  pCosTab = &pSinCosReIm[ iSize_a     ] ;
  pReRslt = &pSinCosReIm[ iSize_a * 2 ] ;
  pImRslt = &pSinCosReIm[ iSize_a * 3 ] ;

  /* Create the Sin and Cos lookup tables for this transform.  Set the
   * scale factors according to whether we're doing an inverse and/or
   * scalled transform.
   */
  {
    FLT8 fAngScl = (2.0 * FPI) / (FLT8)iSize_a ;
    FLT4 fSinScl = 1.0, fCosScl = 1.0 ;
    if( iflags_a & FFT_INVERSE ) fSinScl *= -1.0 ;
    if( iflags_a & FFT_SCALE ) {
      fSinScl /= (FLT4)iSize_a ; fCosScl /= (FLT4)iSize_a ;
    }
    for( ii=0 ; ii < iSize_a ; ii++ ) {
      FLT8 fAngle =  fAngScl * (FLT8)ii ;
      pSinTab[ii] = ( fSinScl * sin( fAngle ) ) ;
      pCosTab[ii] = ( fCosScl * cos( fAngle ) ) ;
    }
  }
  /* Crank the DFT, leaving the results in temporary memory
   */
  for( iSpec=0 ; iSpec < iSize_a ; iSpec++ ) {
    FLT8 fReal = 0.0, fImag = 0.0 ; SINT4 iIn = 0 ;

    for( ii=0 ; ii < iSize_a ; ii++ ) {
      SINT4 iAngle = (ii * iSpec) % iSize_a ;

      fReal += ( ( freal_in_a[ iIn ] * pCosTab[ iAngle ] ) -
		 ( fimag_in_a[ iIn ] * pSinTab[ iAngle ] ) ) ;

      fImag += ( ( freal_in_a[ iIn ] * pSinTab[ iAngle ] ) +
		 ( fimag_in_a[ iIn ] * pCosTab[ iAngle ] ) ) ;

      iIn += inc_in_a ;
    }
    pReRslt[ iSpec ] = (FLT4)fReal ;
    pImRslt[ iSpec ] = (FLT4)fImag ;
  }

  /* Copy the transform back to the caller, and free the temporary
   * memory.
   */
  for( iSpec=0 ; iSpec < iSize_a ; iSpec++ ) {
    SINT4 iOut = ( iSpec * inc_out_a ) ;
    freal_out_a[ iOut ] = pReRslt[ iSpec ] ;
    fimag_out_a[ iOut ] = pImRslt[ iSpec ] ;
  }
  free( pSinCosReIm ) ;
  return( SS_NORMAL ) ;
}
