/*
  rakarrack - a guitar effects software

  StereoHarm.C  -  Stereo Harmonizer
  Copyright (C) 2008 Josep Andreu
  Author:  Josep Andreu

  Using Stephan M. Bernsee smbPitchShifter engine.

 This program is free software; you can redistribute it and/or modify
 it under the terms of version 2 of the GNU General Public License
 as published by the Free Software Foundation.

 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 (version 2) for more details.

 You should have received a copy of the GNU General Public License
 (version2)  along with this program; if not, write to the Free Software
 Foundation,
 Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

*/

#include "StereoHarm.h"

#ifdef SHR3D_SFX_CORE_RAKARRACK

#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE

#include <string.h>

#define PERIOD 128
inline f32 r__ratio[3];

StereoHarm::StereoHarm(i32 Quality, i32 DS, i32 uq, i32 dq)
  : U_Resample(dq)
  , D_Resample(uq)
{
  hq = Quality;
  adjust(DS);

  chromel = 0.0;
  chromer = 0.0;

  PSl = new PitchShifter(window, hq, nfSAMPLE_RATE);
  PSl->ratio = 1.0f;
  PSr = new PitchShifter(window, hq, nfSAMPLE_RATE);
  PSr->ratio = 1.0f;

  PMIDI = 0;
  mira = 0;
  setpreset(0);

  cleanup();
}

void StereoHarm::cleanup()
{
  mira = 0;
  chromel = 0;
  chromer = 0;
  memset(outil, 0, sizeof(f32) * nPERIOD);
  memset(outir, 0, sizeof(f32) * nPERIOD);
  memset(outol, 0, sizeof(f32) * nPERIOD);
  memset(outor, 0, sizeof(f32) * nPERIOD);
}

void StereoHarm::processBlock(const f32* const* inBlock, f32** outBlock, const i32 blockSize)
{
  f32* temp[] = { templ, tempr };
  if (DS_state != 0)
  {
    memcpy(templ, inBlock[0], sizeof(f32) * blockSize);
    memcpy(tempr, inBlock[1], sizeof(f32) * blockSize);
    U_Resample.processBlock(temp, outBlock, blockSize, u_up);
  }

  f32* outi[] = { outil, outir };
  for (i32 i = 0; i < nPERIOD; i++)
  {
    outil[i] = outBlock[0][i] * (1.0f - lrcross) + outBlock[1][i] * lrcross;
    if (outil[i] > 1.0)
      outil[i] = 1.0f;
    if (outil[i] < -1.0)
      outil[i] = -1.0f;

    outir[i] = outBlock[1][i] * (1.0f - lrcross) + outBlock[0][i] * lrcross;
    if (outir[i] > 1.0)
      outir[i] = 1.0f;
    if (outir[i] < -1.0)
      outir[i] = -1.0f;
  }

  if ((PMIDI) || (PSELECT))
  {
    PSl->ratio = r__ratio[1];
    PSr->ratio = r__ratio[2];
  }

  if (PSl->ratio != 1.0f)
    PSl->smbPitchShift(PSl->ratio, nPERIOD, window, hq, nfSAMPLE_RATE, outil, outol);
  else
    memcpy(outol, outil, sizeof(f32) * nPERIOD);


  if (PSr->ratio != 1.0f)
    PSr->smbPitchShift(PSr->ratio, nPERIOD, window, hq, nfSAMPLE_RATE, outir, outor);
  else
    memcpy(outor, outir, sizeof(f32) * nPERIOD);

  if (DS_state != 0)
  {
    D_Resample.processBlock(outi, temp, nPERIOD, u_down);
  }
  else
  {
    memcpy(templ, outol, sizeof(f32) * blockSize);
    memcpy(tempr, outor, sizeof(f32) * blockSize);
  }

  for (i32 i = 0; i < blockSize; i++)
  {
    outBlock[0][i] = templ[i] * gainl;
    outBlock[1][i] = tempr[i] * gainr;
  }
}

void StereoHarm::setvolume(i32 value)
{
  this->Pvolume = value;
  outvolume = (f32)Pvolume / 127.0f;
}

void StereoHarm::setgain(i32 chan, i32 value)
{
  switch (chan)
  {
  case 0:
    Pgainl = value;
    gainl = (f32)Pgainl / 127.0f;
    gainl *= 2.0;
    break;
  case 1:
    Pgainr = value;
    gainr = (f32)Pgainr / 127.0f;
    gainr *= 2.0;
    break;
  }
}

void StereoHarm::seti32erval(i32 chan, i32 value)
{
  switch (chan)
  {
  case 0:
    Pintervall = value;
    intervall = (f32)Pintervall - 12.0f;
    PSl->ratio = powf(2.0f, intervall / 12.0f) + chromel;
    if (Pintervall % 12 == 0)
      mira = 0;
    else
      mira = 1;
    break;

  case 1:
    Pintervalr = value;
    intervalr = (f32)Pintervalr - 12.0f;
    PSr->ratio = powf(2.0f, intervalr / 12.0f) + chromer;
    if (Pintervalr % 12 == 0)
      mira = 0;
    else
      mira = 1;
    break;
  }
}

void StereoHarm::setchrome(i32 chan, i32 value)
{
  f32 max, min;
  max = 0.0;
  min = 0.0;

  switch (chan)
  {
  case 0:
    max = powf(2.0f, (intervall + 1.0f) / 12.0f);
    min = powf(2.0f, (intervall - 1.0f) / 12.0f);
    break;
  case 1:
    max = powf(2.0f, (intervalr + 1.0f) / 12.0f);
    min = powf(2.0f, (intervalr - 1.0f) / 12.0f);
    break;
  }

  if (max > 2.0)
    max = 2.0f;
  if (min < 0.5)
    min = 0.5f;

  switch (chan)
  {
  case 0:
    Pchromel = value;
    chromel = (max - min) / 4000.0f * (f32)value;
    PSl->ratio = powf(2.0f, intervall / 12.0f) + chromel;
    break;
  case 1:
    Pchromer = value;
    chromer = (max - min) / 4000.0f * (f32)value;
    PSr->ratio = powf(2.0f, intervalr / 12.0f) + chromer;
    break;
  }
}

void StereoHarm::setMIDI(i32 value)
{
  this->PMIDI = value;
}

void StereoHarm::adjust(i32 DS)
{
  DS_state = DS;

  switch (DS)
  {
  case 0:
    nPERIOD = PERIOD;
    nSAMPLE_RATE = SAMPLE_RATE;
    nfSAMPLE_RATE = fSAMPLE_RATE;
    window = 2048;
    break;
  case 1:
    nPERIOD = lrintf(fPERIOD * 96000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 96000;
    nfSAMPLE_RATE = 96000.0f;
    window = 2048;
    break;
  case 2:
    nPERIOD = lrintf(fPERIOD * 48000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 48000;
    nfSAMPLE_RATE = 48000.0f;
    window = 2048;
    break;
  case 3:
    nPERIOD = lrintf(fPERIOD * 44100.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 44100;
    nfSAMPLE_RATE = 44100.0f;
    window = 2048;
    break;
  case 4:
    nPERIOD = lrintf(fPERIOD * 32000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 32000;
    nfSAMPLE_RATE = 32000.0f;
    window = 2048;
    break;
  case 5:
    nPERIOD = lrintf(fPERIOD * 22050.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 22050;
    nfSAMPLE_RATE = 22050.0f;
    window = 1024;
    break;
  case 6:
    nPERIOD = lrintf(fPERIOD * 16000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 16000;
    nfSAMPLE_RATE = 16000.0f;
    window = 1024;
    break;
  case 7:
    nPERIOD = lrintf(fPERIOD * 12000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 12000;
    nfSAMPLE_RATE = 12000.0f;
    window = 512;
    break;
  case 8:
    nPERIOD = lrintf(fPERIOD * 8000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 8000;
    nfSAMPLE_RATE = 8000.0f;
    window = 512;
    break;
  case 9:
    nPERIOD = lrintf(fPERIOD * 4000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 4000;
    nfSAMPLE_RATE = 4000.0f;
    window = 256;
    break;
  }

  u_up = (f64)nPERIOD / (f64)PERIOD;
  u_down = (f64)PERIOD / (f64)nPERIOD;
}

void StereoHarm::setlrcross(i32 value)
{
  Plrcross = value;
  lrcross = (f32)Plrcross / 127.0f;
}

void StereoHarm::setpreset(i32 npreset)
{
  const i32 PRESET_SIZE = 12;
  const i32 NUM_PRESETS = 4;
  i32 presets[NUM_PRESETS][PRESET_SIZE] = {
    //Plain
    {64, 64, 12, 0, 64, 12, 0, 0, 0, 0, 0, 64},
    //Octavador
    {64, 64, 0, 0, 64, 0, 0, 0, 0, 0, 0, 64},
    //Chorus
    {64, 64, 12, 80, 64, 12, -80, 0, 0, 0, 0, 64},
    //Chorus
    {64, 64, 12, 280, 64, 12, -280, 0, 0, 0, 0, 64}
  };

  cleanup();

  for (i32 n = 0; n < PRESET_SIZE; n++)
    changepar(n, presets[npreset][n]);
}

void StereoHarm::changepar(i32 npar, i32 value)
{
  switch (npar)
  {
  case 0:
    setvolume(value);
    return;
  case 1:
    setgain(0, value);
    return;
  case 2:
    seti32erval(0, value);
    return;
  case 3:
    setchrome(0, value);
    return;
  case 4:
    setgain(1, value);
    return;
  case 5:
    seti32erval(1, value);
    return;
  case 6:
    setchrome(1, value);
    return;
  case 7:
    PSELECT = value;;
    return;
  case 8:
    Pnote = value;
    return;
  case 9:
    Ptype = value;
    if (Ptype == 0)
    {
      setchrome(0, Pchromel);
      setchrome(1, Pchromer);
    }
    return;
  case 10:
    setMIDI(value);
    return;
  case 11:
    setlrcross(value);
    return;
  }
  ASSERT(false);
}

i32 StereoHarm::getpar(i32 npar)
{
  switch (npar)
  {
  case 0:
    return Pvolume;
  case 1:
    return Pgainl;
  case 2:
    return Pintervall;
  case 3:
    return Pchromel;
  case 4:
    return Pgainr;
  case 5:
    return Pintervalr;
  case 6:
    return Pchromer;
  case 7:
    return PSELECT;
  case 8:
    return Pnote;
  case 9:
    return Ptype;
  case 10:
    return PMIDI;
  case 11:
    return Plrcross;
  }
  ASSERT(false);
  return 0;
}

#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE

#endif // SHR3D_SFX_CORE_RAKARRACK
