/**
 * projectM -- Milkdrop-esque visualisation SDK
 * Copyright (C)2003-2004 projectM Team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * See 'LICENSE.txt' included within this release
 *
 */
/**
 * $Id: PCM.c,v 1.3 2006/03/13 20:35:26 psperl Exp $
 *
 * Takes sound data from wherever and hands it back out.
 * Returns PCM Data or spectrum data, or the derivative of the PCM data
 */

#include <stdlib.h>
#include <stdio.h>

#include "Common.hpp"
#include "wipemalloc.h"
#include "fftsg.h"
#include "PCM.hpp"
#include <cassert>

int PCM::maxsamples = 2048;

//initPCM(int samples)
//
//Initializes the PCM buffer to
// number of samples specified.
#include <iostream>
PCM::PCM() {
    _initPCM( 2048 );

    #ifdef DEBUG
    std::cerr << "[PCM] MAX SAMPLES:" << maxsamples << std::endl;
    #endif
  }

void PCM::_initPCM(int samples) {
  int i;

    waveSmoothing = 0;

  //Allocate memory for PCM data buffer
    assert(samples == 2048);
  PCMd = (float **)wipemalloc(2 * sizeof(float *));
  PCMd[0] = (float *)wipemalloc(samples * sizeof(float));
  PCMd[1] = (float *)wipemalloc(samples * sizeof(float));

  //maxsamples=samples;
  newsamples=0;
    numsamples = maxsamples;

  //Initialize buffers to 0
  for (i=0;i<samples;i++)
    {
      PCMd[0][i]=0;
      PCMd[1][i]=0;
    }

  start=0;

  //Allocate FFT workspace
  // per rdft() documentation
  //    length of ip >= 2+sqrt(n) and length of w == n/2
#if FFT_LENGTH > 1024
#error update this code
#endif
  w  = (double *)wipemalloc(FFT_LENGTH/2*sizeof(double));
  ip = (int *)wipemalloc(34 * sizeof(int));
  ip[0]=0;

    /** PCM data */
//    this->maxsamples = 2048;
//    this->numsamples = 0;
//    this->pcmdataL = NULL;
//    this->pcmdataR = NULL;

    /** Allocate PCM data structures */
    pcmdataL=(float *)wipemalloc(this->maxsamples*sizeof(float));
    pcmdataR=(float *)wipemalloc(this->maxsamples*sizeof(float));

}

PCM::~PCM() {

	free(pcmdataL);
	free(pcmdataR);
	free(w);
	free(ip);

	free(PCMd[0]);
	free(PCMd[1]);
	free(PCMd);

}

#include <iostream>

void PCM::addPCMfloat(const float *PCMdata, int samples)
{
  int i,j;

  for(i=0;i<samples;i++)
    {
      j=i+start;

      if (PCMdata[i] != 0 ) {

	PCMd[0][j%maxsamples] = PCMdata[i];
	PCMd[1][j%maxsamples] = PCMdata[i];

      }
      else
	{
	  PCMd[0][j % maxsamples] = 0;
	  PCMd[1][j % maxsamples] = 0;
	}
    }

  start+=samples;
  start=start%maxsamples;

 newsamples+=samples;
 if (newsamples>maxsamples) newsamples=maxsamples;
  numsamples = getPCMnew(pcmdataR,1,0,waveSmoothing,0,0);
    getPCMnew(pcmdataL,0,0,waveSmoothing,0,1);
    getPCM(vdataL,FFT_LENGTH,0,1,0,0);
    getPCM(vdataR,FFT_LENGTH,1,1,0,0);
}


void PCM::addPCMfloat_2ch(const float *PCMdata, int samples)
{
    int i,j;

    for(i=0;i<samples;i+=2)
    {
        j=(i/2)+start;
        PCMd[0][j%maxsamples] = PCMdata[i];
        PCMd[1][j%maxsamples] = PCMdata[i+1];
    }

    start+=samples/2;
    start=start%maxsamples;

    newsamples+=samples/2;
    if (newsamples>maxsamples)
        newsamples=maxsamples;
    numsamples = getPCMnew(pcmdataR,1,0,waveSmoothing,0,0);
    getPCMnew(pcmdataL,0,0,waveSmoothing,0,1);
    getPCM(vdataL,FFT_LENGTH,0,1,0,0);
    getPCM(vdataR,FFT_LENGTH,1,1,0,0);
}


void PCM::addPCM16Data(const short* pcm_data, short samples)  {
   int i, j;

   for (i = 0; i < samples; ++i) {
      j=i+start;
      PCMd[0][j % maxsamples]=(pcm_data[i * 2 + 0]/16384.0);
      PCMd[1][j % maxsamples]=(pcm_data[i * 2 + 1]/16384.0);
   }

   start = (start + samples) % maxsamples;

   newsamples+=samples;
   if (newsamples>maxsamples) newsamples=maxsamples;
     numsamples = getPCMnew(pcmdataR,1,0,waveSmoothing,0,0);
    getPCMnew(pcmdataL,0,0,waveSmoothing,0,1);
    getPCM(vdataL,FFT_LENGTH,0,1,0,0);
    getPCM(vdataR,FFT_LENGTH,1,1,0,0);
}


void PCM::addPCM16(short PCMdata[2][512])
{
  int i,j;
  int samples=512;

	 for(i=0;i<samples;i++)
	   {
	     j=i+start;
         if ( PCMdata[0][i] != 0 && PCMdata[1][i] != 0 ) {
	         PCMd[0][j%maxsamples]=(PCMdata[0][i]/16384.0);
	         PCMd[1][j%maxsamples]=(PCMdata[1][i]/16384.0);
          } else {
             PCMd[0][j % maxsamples] = (float)0;
             PCMd[1][j % maxsamples] = (float)0;
          }
	   }

	 // printf("Added %d samples %d %d %f\n",samples,start,(start+samples)%maxsamples,PCM[0][start+10]);

 start+=samples;
 start=start%maxsamples;

 newsamples+=samples;
 if (newsamples>maxsamples) newsamples=maxsamples;

    numsamples = getPCMnew(pcmdataR,1,0,waveSmoothing,0,0);
    getPCMnew(pcmdataL,0,0,waveSmoothing,0,1);
    getPCM(vdataL,FFT_LENGTH,0,1,0,0);
    getPCM(vdataR,FFT_LENGTH,1,1,0,0);
}


void PCM::addPCM8( unsigned char PCMdata[2][1024])
{
  int i,j;
  int samples=1024;


	 for(i=0;i<samples;i++)
	   {
	     j=i+start;
         if ( PCMdata[0][i] != 0 && PCMdata[1][i] != 0 ) {
	         PCMd[0][j%maxsamples]=( (float)( PCMdata[0][i] - 128.0 ) / 64 );
	         PCMd[1][j%maxsamples]=( (float)( PCMdata[1][i] - 128.0 ) / 64 );
          } else {
             PCMd[0][j % maxsamples] = 0;
             PCMd[1][j % maxsamples] = 0;
          }
	   }


	 // printf("Added %d samples %d %d %f\n",samples,start,(start+samples)%maxsamples,PCM[0][start+10]);

 start+=samples;
 start=start%maxsamples;

 newsamples+=samples;
 if (newsamples>maxsamples) newsamples=maxsamples;
    numsamples = getPCMnew(pcmdataR,1,0,waveSmoothing,0,0);
    getPCMnew(pcmdataL,0,0,waveSmoothing,0,1);
    getPCM(vdataL,FFT_LENGTH,0,1,0,0);
    getPCM(vdataR,FFT_LENGTH,1,1,0,0);
}

void PCM::addPCM8_512( const unsigned char PCMdata[2][512])
{
  int i,j;
  int samples=512;


	 for(i=0;i<samples;i++)
	   {
	     j=i+start;
         if ( PCMdata[0][i] != 0 && PCMdata[1][i] != 0 ) {
	         PCMd[0][j%maxsamples]=( (float)( PCMdata[0][i] - 128.0 ) / 64 );
	         PCMd[1][j%maxsamples]=( (float)( PCMdata[1][i] - 128.0 ) / 64 );
          } else {
             PCMd[0][j % maxsamples] = 0;
             PCMd[1][j % maxsamples] = 0;
          }
	   }


	 // printf("Added %d samples %d %d %f\n",samples,start,(start+samples)%maxsamples,PCM[0][start+10]);

 start+=samples;
 start=start%maxsamples;

 newsamples+=samples;
 if (newsamples>maxsamples) newsamples=maxsamples;
    numsamples = getPCMnew(pcmdataR,1,0,waveSmoothing,0,0);
    getPCMnew(pcmdataL,0,0,waveSmoothing,0,1);
    getPCM(vdataL,FFT_LENGTH,0,1,0,0);
    getPCM(vdataR,FFT_LENGTH,1,1,0,0);
}


//puts sound data requested at provided pointer
//
//samples is number of PCM samples to return
//freq = 0 gives PCM data
//freq = 1 gives FFT data
//smoothing is the smoothing coefficient

//returned values are normalized from -1 to 1

void PCM::getPCM(float *PCMdata, int samples, int channel, int freq, float smoothing, int derive)
{
   if (smoothing == 0)
     {
       for (int i = 0; i < samples; i++)
       {
           int index = start - 1 - i;
           if (index < 0)
               index = maxsamples + index;
           PCMdata[i] = PCMd[channel][index];
       }
     }
   else
     {
       int index=start-1;

       if (index<0)
         index=maxsamples+index;

       PCMdata[0] = PCMd[channel][index];

       for (int i = 1; i < samples; i++)
       {
           index = start - 1 - i;
           if (index < 0)
               index = maxsamples + index;
           PCMdata[i] = (1 - smoothing) * PCMd[channel][index] + smoothing * PCMdata[i - 1];
       }
     }

   //return derivative of PCM data
   if (derive)
     {
       for(int i=0;i<samples-1;i++)
         {
           PCMdata[i]=PCMdata[i]-PCMdata[i+1];
	     }
       PCMdata[samples-1]=0;
     }

   //return frequency data instead of PCM (perform FFT)

   if (freq)
     {
       // NOTE some presets set bSpectrum=1 and samples!=2^n, not sure what rdft() does with that
       assert(samples <= 1024);
       samples = std::min(1024,samples);
       double temppcm[1024];
       for (int i=0;i<samples;i++)
         {temppcm[i]=(double)PCMdata[i];}
       rdft(samples, 1, temppcm, ip, w);
       for (int j=0;j<samples;j++)
         {PCMdata[j]=(float)temppcm[j];}
     }
}

//getPCMnew
//
//Like getPCM except it returns all new samples in the buffer
//the actual return value is the number of samples, up to maxsamples.
//the passed pointer, PCMData, must bee able to hold up to maxsamples

int PCM::getPCMnew(float *PCMdata, int channel, int freq, float smoothing, int derive, int reset)
{
   int i,index;

   index=start-1;

   if (index<0) index=maxsamples+index;

   PCMdata[0]=PCMd[channel][index];
   for(i=1;i<newsamples;i++)
     {
       index=start-1-i;
       if (index<0) index=maxsamples+index;

       PCMdata[i]=(1-smoothing)*PCMd[channel][index]+smoothing*PCMdata[i-1];
     }

   //return derivative of PCM data
   if(derive)
     {
       for(i=0;i<newsamples-1;i++)
	 {
	   PCMdata[i]=PCMdata[i]-PCMdata[i+1];
	 }
       PCMdata[newsamples-1]=0;
     }

   //return frequency data instead of PCM (perform FFT)
   //   if (freq) rdft(samples, 1, PCMdata, ip, w);
   i=newsamples;
   if (reset)  newsamples=0;

   return i;
}

//Free stuff
void PCM::freePCM() {
  free(PCMd[0]);
  free(PCMd[1]);
  free(PCMd);
  free(ip);
  free(w);

  PCMd = NULL;
  ip = NULL;
  w = NULL;
}
