////////////////////////////////////////////////////////////////////////
// This file is part of the SndObj library
//
// 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// Copyright (c)Victor Lazzarini, 1997-2004
// See License.txt for a disclaimer of all warranties
// and licensing information

/////////////////////////////////////////////////
// PVA.cpp: Phase Vocoder Analysis class
//
//           Victor Lazzarini, 2003
/////////////////////////////////////////////////
#include "PVA.h"

PVA::PVA(){
  m_rotcount = 0;
  m_phases = new double[m_halfsize];
  memset(m_phases, 0, sizeof(double)*m_halfsize);
  m_factor = m_sr/(m_hopsize*TWOPI);
}

PVA::PVA(Table* window, SndObj* input, double scale,
     int fftsize, int hopsize, double sr)
  :FFT(window, input, scale, fftsize, hopsize, sr){
  m_rotcount = 0;
  m_phases = new double[m_halfsize];
  memset(m_phases, 0, sizeof(double)*m_halfsize);
  m_factor = m_sr/(m_hopsize*TWOPI);
}

PVA::~PVA(){
  delete[] m_phases;
}

int
PVA::Set(const char* mess, double value){
  switch(FindMsg(mess)){

  case 22:
    SetFFTSize((int) value);
    return 1;

  case 23:
    SetHopSize((int) value);
    return 1;

  default:
    return  FFT::Set(mess, value);
  }
}

void
PVA::SetFFTSize(int fftsize){
  m_rotcount = 0;
  FFT::SetFFTSize(fftsize);
}

void
PVA::SetHopSize(int hopsize){
  m_rotcount = 0;
  m_factor = m_sr/(hopsize*TWOPI);
  FFT::SetFFTSize(hopsize);
}

void
PVA::pvanalysis(double* signal){
  double re, im, pha, diff;

  memcpy(m_fftIn, &signal[0], sizeof(double) * m_fftsize);
  fftw_execute(m_plan);

  m_output[0] = m_fftOut[0][0] / m_norm;
  m_output[1] = m_fftOut[0][1] / m_norm;

  int i = 2;
  for(int bin = 1; bin < m_halfsize; bin++){
    re = (m_fftOut[bin][0] * 2) / m_norm;
    im = (m_fftOut[bin][1] * 2) / m_norm;

    if((m_output[i] = sqrt((re*re)+(im*im))) == 0.f){
      diff = 0.f;
    }
    else {
      pha = atan2(im,re);
      diff = pha - m_phases[bin];
      m_phases[bin] = (double) pha;

      while(diff > PI) diff -= TWOPI;
      while(diff < -PI) diff += TWOPI;
    }
    m_output[i+1] = (double) (diff * m_factor) + (bin * m_fund);
    i += 2;
  }
}

short
PVA::DoProcess(){
  if(!m_error){
    if(m_input){
      if(m_enable){
        int i; double sig = 0.f;
        for(m_vecpos = 0; m_vecpos < m_hopsize; m_vecpos++){
          // signal input
          sig = m_input->Output(m_vecpos);

          // distribute to the signal fftframes and apply the window
          // according to a time pointer (kept by counter[n])
          // input is also rotated according to the input time.
          for(i=0;i < m_frames; i++){
            m_sigframe[i][m_rotcount]= (double) sig*m_table->Lookup(m_counter[i]);
            m_counter[i]++;
          }
          m_rotcount++;
        }

        m_rotcount %= m_fftsize;
        // every vecsize samples
        // set the current fftframe to be transformed
        m_cur--; if(m_cur<0) m_cur = m_frames-1;

        // phase vocoder analysis
        pvanalysis(m_sigframe[m_cur]);

        // zero the current fftframe time pointer
        m_counter[m_cur] = 0;
        return 1;
      }
      else{ // if disabled, reset the fftframes
        for(m_vecpos =0; m_vecpos < m_hopsize; m_vecpos++)
          m_output[m_vecpos] = 0.f;
        return 1;
      }
    }
    else {
      m_error = 3;
      return 0;
    }
  }
  else
    return 0;
}