/*
 *
 *    soniK digital audio editor
 *    Copyright (C) 2003-2006  Robert Walker <rob@tenfoot.org.uk>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
#include "sonik_sigproc.h"

#include <cmath>
#include <gsl/gsl_fft_real.h>

#include <iostream>

//
//
// Simple statistics
//
//

//
//
// Windowing
//
//

/**
 * Get name of window function
 */
QString Sonik::windowToString(WindowFunction f)
{
  switch (f)
  {
    case Sonik::RECTANGULAR:  return QString("rectangular"); break;
    case Sonik::TRIANGULAR:   return QString("triangular");  break;
    case Sonik::BARTLETT:     return QString("bartlett");    break;
    case Sonik::BLACKMAN:     return QString("blackman");    break;
    case Sonik::CHEBYSHEV:    return QString("chebyshev");   break;
    case Sonik::HAMMING:      return QString("hamming");     break;
    case Sonik::HANN:         return QString("hann");        break;
    case Sonik::KAISER:       return QString("kaiser");      break;
    case Sonik::UNKNOWN:
    default:                  return QString::null;          break;
  }
}

/**
 * Get window function from name
 */
Sonik::WindowFunction Sonik::stringToWindow(const QString& s)
{
  QString l = s.lower();
  if (l == "rectangular")
    return RECTANGULAR;
  else if (l == "triangular")
    return TRIANGULAR;
  else if (l == "bartlett")
    return BARTLETT;
  else if (l == "blackman")
    return BLACKMAN;
  else if (l == "chebyshev")
    return CHEBYSHEV;
  else if (l == "hamming")
    return HAMMING;
  else if (l == "hann")
    return HANN;
  else if (l == "kaiser")
    return KAISER;
  else
    return UNKNOWN;
}

/**
 * Generate a window function
 */
void Sonik::makeWindow(Sonik::Sample* buf, size_t len, WindowFunction f)
{
  switch (f)
  {
    case Sonik::RECTANGULAR: Sonik::makeWindowRectangular(buf, len); break;
    case Sonik::TRIANGULAR:  Sonik::makeWindowTriangular(buf, len);  break;
    case Sonik::BARTLETT:    Sonik::makeWindowBartlett(buf, len);    break;
    case Sonik::BLACKMAN:    Sonik::makeWindowBlackman(buf, len);    break;
    case Sonik::CHEBYSHEV:   Sonik::makeWindowChebyshev(buf, len);   break;
    case Sonik::HAMMING:     Sonik::makeWindowHamming(buf, len);     break;
    case Sonik::HANN:        Sonik::makeWindowHann(buf, len);        break;
    case Sonik::KAISER:      Sonik::makeWindowKaiser(buf, len);      break;
    case Sonik::UNKNOWN:
    default:                                                         break;
  }
}

/**
 * Generate a window function
 */
void Sonik::makeWindow(auto_buffer<Sample>& buf, WindowFunction f)
{
  makeWindow(buf.data(), buf.size(), f);
}

/**
 * Generate a rectangular window function
 */
void Sonik::makeWindowRectangular(Sonik::Sample* buf, size_t len)
{
  if (len == 0)
    return;

  for (size_t i = 0; i < len; ++i)
    buf[i] = 1.0;
}

/**
 * Generate a triangular window function
 */
void Sonik::makeWindowTriangular(Sonik::Sample* buf, size_t len)
{
  if (len == 0)
    return;

  double k = 2.0/(len + len % 2);
  double j = -((double)len-1)/(len + len % 2);
  for (size_t i = 0; i < len/2; ++i, j += k)
    buf[i] = 1.0 + j;
  for (size_t i = len/2; i < len; ++i, j += k)
    buf[i] = 1.0 - j;
}

/**
 * Generate a bartlett window function
 */
void Sonik::makeWindowBartlett(Sonik::Sample* buf, size_t len)
{
  if (len == 0)
    return;

  if (len == 1)
    buf[0] = 1.0;
  else
  {
    double k = 2.0/(len-1.0);
    double j = 0.0;
    for (size_t i = 0; i < len/2; ++i, j += k)
      buf[i] = j;
    for (size_t i = len/2; i < len; ++i, j += k)
      buf[i] = 2 - j;
  }
}

/**
 * Generate a blackman window function
 */
void Sonik::makeWindowBlackman(Sonik::Sample* buf, size_t len)
{
  if (len == 0)
    return;

  double k1 = 2*M_PI/(len-1);
  double k2 = 4*M_PI/(len-1);
  for (size_t i = 0; i < len; ++i)
    buf[i] = 0.42 - 0.5 * cos(k1*i) + 0.08 * cos(k2*i);
}

/**
 * Generate a chebyshev window function
 */
void Sonik::makeWindowChebyshev(Sonik::Sample* buf, size_t len, double ripple)
{
  if (len == 0)
    return;

  for (size_t i = 0; i < len; ++i)
    buf[i] = 1.0;
}

/**
 * Generate a hamming window function
 */
void Sonik::makeWindowHamming(Sonik::Sample* buf, size_t len)
{
  if (len == 0)
    return;

  double k = 2*M_PI/(len-1);
  for (size_t i = 0; i < len; ++i)
    buf[i] = 0.54 - 0.46 * cos(k*i);
}

/**
 * Generate a hann window function
 */
void Sonik::makeWindowHann(Sonik::Sample* buf, size_t len)
{
  if (len == 0)
    return;

  double k = 2*M_PI/(len-1);
  for (size_t i = 0; i < len; ++i)
    buf[i] = 0.5 - 0.5 * cos(k*i);
}

/**
 * Generate a kaiser window function
 */
void Sonik::makeWindowKaiser(Sonik::Sample* buf, size_t len, double beta)
{
  if (len == 0)
    return;

  for (size_t i = 0; i < len; ++i)
    buf[i] = 1.0;
}

//
//
// Fourier transforms
//
//

Sonik::FFTParms::~FFTParms()
{
}

namespace Sonik
{
  struct FFTParmsImpl : public FFTParms
  {
    FFTParmsImpl(size_t len)
      : table(gsl_fft_real_wavetable_alloc(len)),
        work(gsl_fft_real_workspace_alloc(len)),
        fftData(new double[len])
    {
    }

    virtual ~FFTParmsImpl()
    {
      gsl_fft_real_wavetable_free(table);
      gsl_fft_real_workspace_free(work);
      delete[] fftData;
    }

    gsl_fft_real_wavetable* table;
    gsl_fft_real_workspace* work;
    double*                 fftData;
  };
}

/**
 * Create parameter struct for repeated FFTs
 */
Sonik::FFTParms* Sonik::createFFTParms(size_t len)
{
  return new FFTParmsImpl(len);
}

/**
 * Calculate fast fourier transform of buf
 */
void Sonik::fft(const Sonik::Sample* buf,
                Sonik::ComplexSample* out,
                size_t len,
                Sonik::FFTParms* parms)
{
  if (len == 0)
    return;

  // Create parameter struct if necessary
  Sonik::FFTParmsImpl* p;
  if (parms == 0)
    p = new Sonik::FFTParmsImpl(len);
  else
    p = static_cast<Sonik::FFTParmsImpl*>(parms);

  for (size_t i = 0; i < len; ++i)
    p->fftData[i] = buf[i];

  gsl_fft_real_transform(p->fftData, 1, len, p->table, p->work);

  out[0].real = p->fftData[0];
  out[0].imag = 0.0;

  size_t i;
  for (i = 1; i < len-i; ++i)
  {
    out[i].real = p->fftData[2 * i - 1];
    out[i].imag = p->fftData[2 * i];
  }

  if ((len&0x1) == 0)
  {
    out[len>>1].real = p->fftData[len-1];
    out[len>>1].imag = 0.0;
  }

  // Delete parameter struct if necessary
  if (parms == 0)
    delete p;
}

