/*
 *  Copyright (c) 2007 - 2008 by Damien Di Fede <ddf@compartmental.net>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library 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 Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package ddf.minim.analysis;

import ddf.minim.Minim;

/**
 * DFT stands for Discrete Fourier Transform and is the most widely used Fourier
 * Transform. You will never want to use this class due to the fact that it is a
 * brute force implementation of the DFT and as such is quite slow. Use an FFT
 * instead. This exists primarily as a way to ensure that other implementations
 * of the DFT are working properly. This implementation expects an even
 * <code>timeSize</code> and will throw and IllegalArgumentException if this
 * is not the case.
 * 
 * @author Damien Di Fede
 * 
 * @see FourierTransform
 * @see FFT
 * @see <a href="http://www.dspguide.com/ch8.htm">The Discrete Fourier Transform</a>
 * 
 */
public class DFT extends FourierTransform
{
  /**
   * Constructs a DFT that expects audio buffers of length <code>timeSize</code> that 
   * have been recorded with a sample rate of <code>sampleRate</code>. Will throw an 
   * IllegalArgumentException if <code>timeSize</code> is not even.
   * 
   * @param timeSize the length of the audio buffers you plan to analyze
   * @param sampleRate the sample rate of the audio samples you plan to analyze
   */
  public DFT(int timeSize, float sampleRate)
  {
    super(timeSize, sampleRate);
    if (timeSize % 2 != 0)
      throw new IllegalArgumentException("DFT: timeSize must be even.");
    buildTrigTables();
  }

  protected void allocateArrays()
  {
    spectrum = new float[timeSize / 2 + 1];
    real = new float[timeSize / 2 + 1];
    imag = new float[timeSize / 2 + 1];
  }

  /**
   * Not currently implemented.
   */
  public void scaleBand(int i, float s)
  {
    // TODO implement this?
  }

  /**
   * Not currently implemented.
   */
  public void setBand(int i, float a)
  {
    // TODO implement this?
  }

  public void forward(float[] samples)
  {
    if (samples.length != timeSize)
    {
      Minim
          .error("DFT.forward: The length of the passed sample buffer must be equal to DFT.timeSize().");
      return;
    }
    doWindow(samples);
    int N = samples.length;
    for (int f = 0; f <= N / 2; f++)
    {
      real[f] = 0.0f;
      imag[f] = 0.0f;
      for (int t = 0; t < N; t++)
      {
        real[f] += samples[t] * cos(t * f);
        imag[f] += samples[t] * -sin(t * f);
      }
    }
    fillSpectrum();
  }

  public void inverse(float[] buffer)
  {
    int N = buffer.length;
    real[0] /= N;
    imag[0] = -imag[0] / (N / 2);
    real[N / 2] /= N;
    imag[N / 2] = -imag[0] / (N / 2);
    for (int i = 0; i < N / 2; i++)
    {
      real[i] /= (N / 2);
      imag[i] = -imag[i] / (N / 2);
    }
    for (int t = 0; t < N; t++)
    {
      buffer[t] = 0.0f;
      for (int f = 0; f < N / 2; f++)
      {
        buffer[t] += real[f] * cos(t * f) + imag[f] * sin(t * f);
      }
    }
  }

  // lookup table data and functions

  private float[] sinlookup;
  private float[] coslookup;

  private void buildTrigTables()
  {
    int N = spectrum.length * timeSize;
    sinlookup = new float[N];
    coslookup = new float[N];
    for (int i = 0; i < N; i++)
    {
      sinlookup[i] = (float) Math.sin(i * TWO_PI / timeSize);
      coslookup[i] = (float) Math.cos(i * TWO_PI / timeSize);
    }
  }

  private float sin(int i)
  {
    return sinlookup[i];
  }

  private float cos(int i)
  {
    return coslookup[i];
  }
}
