/*
  ZynAddSubFX - a software synthesizer

  Reverb.C - Reverberation effect
  Copyright (C) 2002-2005 Nasca Octavian Paul
  Author: Nasca Octavian Paul

  Modified for rakarrack by Josep Andreu

  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 (version 2)
  along with this program; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

*/

#include "Reverb_.h"

#ifdef SHR3D_SFX_CORE_RAKARRACK

#include <string.h>

/*TODO: EarlyReflections,Prdelay,Perbalance */

Reverb::Reverb()
  : lpf(2, 22000, 1, 0)
  , hpf(3, 20, 1, 0)
{
  rs_coeff = rs / (f32)REV_COMBS;

  for (i32 i = 0; i < REV_COMBS * 2; i++)
  {
    comblen[i] = 800 + (i32)(RND * 1400);
    combk[i] = 0;
    lpcomb[i] = 0;
    combfb[i] = -0.97f;
    comb[i] = NULL;
  }

  for (i32 i = 0; i < REV_APS * 2; i++)
  {
    aplen[i] = 500 + (i32)(RND * 500);
    apk[i] = 0;
    ap[i] = NULL;
  }

  idelay = NULL;

  setpreset(0);
  cleanup();			//do not call this before the comb initialisation
}

void Reverb::cleanup()
{
  for (i32 i = 0; i < REV_COMBS * 2; i++)
  {
    lpcomb[i] = 0.0;
    for (i32 j = 0; j < comblen[i]; j++)
      comb[i][j] = 0.0;
  }

  for (i32 i = 0; i < REV_APS * 2; i++)
    for (i32 j = 0; j < aplen[i]; j++)
      ap[i][j] = 0.0;

  if (idelay != NULL)
    for (i32 i = 0; i < idelaylen; i++)
      idelay[i] = 0.0;

  hpf.cleanup();
  lpf.cleanup();
}

void Reverb::processmono(i32 ch, f32* output, const i32 blockSize)
{
  //TODO: implement the high part from lohidamp

  for (i32 j = REV_COMBS * ch; j < REV_COMBS * (ch + 1); j++)
  {
    i32 ck = combk[j];
    i32 comblength = comblen[j];
    f32 lpcombj = lpcomb[j];

    for (i32 i = 0; i < blockSize; i++)
    {
      f32 fbout = comb[j][ck];
      fbout = fbout * combfb[j];
      fbout = fbout * (1.0f - lohifb) + (lpcombj * lohifb);
      lpcombj = fbout;

      comb[j][ck] = inputbuf[i] + fbout;
      output[i] += fbout;

      if ((++ck) >= comblength)
        ck = 0;
    }

    combk[j] = ck;
    lpcomb[j] = lpcombj;
  }

  for (i32 j = REV_APS * ch; j < REV_APS * (1 + ch); j++)
  {
    i32 ak = apk[j];
    i32 aplength = aplen[j];
    for (i32 i = 0; i < blockSize; i++)
    {
      const f32 tmp = ap[j][ak];
      ap[j][ak] = 0.7f * tmp + output[i];
      output[i] = tmp - 0.7f * ap[j][ak];
      if ((++ak) >= aplength)
        ak = 0;
    }
    apk[j] = ak;
  }
}

void Reverb::processBlock(const f32* const* inBlock, f32* const* outBlock, const i32 blockSize)
{
  memcpy(outBlock[0], inBlock[0], blockSize * sizeof(f32));
  memcpy(outBlock[1], inBlock[1], blockSize * sizeof(f32));

  for (i32 i = 0; i < blockSize; ++i)
  {
    inputbuf[i] = (inBlock[0][i] + inBlock[1][i]) * .5f;
    //Initial delay r
    if (idelay != NULL)
    {
      const f32 tmp = inputbuf[i] + idelay[idelayk] * idelayfb;
      inputbuf[i] = idelay[idelayk];
      idelay[idelayk] = tmp;
      idelayk++;
      if (idelayk >= idelaylen)
        idelayk = 0;
    }
  }

  lpf.filterout(inputbuf, blockSize);
  hpf.filterout(inputbuf, blockSize);

  processmono(0, outBlock[0], blockSize); //left
  processmono(1, outBlock[1], blockSize); //right

  const f32 lvol = rs_coeff * pan * 2.0f;
  const f32 rvol = rs_coeff * (1.0f - pan) * 2.0f;

  for (i32 i = 0; i < blockSize; i++)
  {
    outBlock[0][i] *= lvol;
    outBlock[1][i] *= rvol;
  }
}

void Reverb::setvolume(i32 Pvolume_)
{
  Pvolume = Pvolume_;
  outvolume = (f32)Pvolume_ / 127.0f;
  if (Pvolume_ == 0)
    cleanup();
}

void Reverb::setpan(i32 Ppan_)
{
  Ppan = Ppan_;
  pan = (f32)Ppan_ / 127.0f;
}

void Reverb::settime(i32 Ptime_)
{
  Ptime = Ptime_;
  const f32 t = powf(60.0f, (f32)Ptime_ / 127.0f) - 0.97f;

  for (i32 i = 0; i < REV_COMBS * 2; i++)
  {
    combfb[i] = -expf((f32)comblen[i] / fSAMPLE_RATE * logf(0.001f) / t);
    //the feedback is negative because it removes the DC
  }
}

void Reverb::setlohidamp(i32 Plohidamp_)
{
  if (Plohidamp_ < 64)
    Plohidamp_ = 64;		//remove this when the high part from lohidamp will be added

  Plohidamp = Plohidamp_;
  if (Plohidamp_ == 64)
  {
    lohidamptype = 0;
    lohifb = 0.0;
  }
  else
  {
    if (Plohidamp_ < 64)
      lohidamptype = 1;
    if (Plohidamp_ > 64)
      lohidamptype = 2;
    const f32 x = fabsf((f32)(Plohidamp_ - 64) / 64.1f);
    lohifb = x * x;
  }
}

void Reverb::setidelay(i32 Pidelay_)
{
  Pidelay = Pidelay_;
  const f32 delay = powf(50.0f * (f32)Pidelay_ / 127.0f, 2.0f) - 1.0f;

  if (idelay != NULL)
    delete (idelay);
  idelay = NULL;

  idelaylen = lrintf(fSAMPLE_RATE * delay / 1000.0f);
  if (idelaylen > 1)
  {
    idelayk = 0;
    idelay = new f32[idelaylen];
    for (i32 i = 0; i < idelaylen; i++)
      idelay[i] = 0.0;
  }
}

void Reverb::setidelayfb(i32 Pidelayfb_)
{
  Pidelayfb = Pidelayfb_;
  idelayfb = (f32)Pidelayfb_ / 128.0f;
}

void Reverb::sethpf(i32 value)
{
  Phpf = value;
  f32 fr = (f32)Phpf;
  hpf.setfreq(fr);
}

void Reverb::setlpf(i32 value)
{
  Plpf = value;
  f32 fr = (f32)Plpf;
  lpf.setfreq(fr);
}

void Reverb::settype(i32 Ptype_)
{
  const i32 NUM_TYPES = 2;
  i32 combtunings[NUM_TYPES][REV_COMBS] = {
    //this is unused (for random)
    {0, 0, 0, 0, 0, 0, 0, 0},
    //Freeverb by Jezar at Dreampoint
    {1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617}
  };
  i32 aptunings[NUM_TYPES][REV_APS] = {
    //this is unused (for random)
    {0, 0, 0, 0},
    //Freeverb by Jezar at Dreampoint
    {225, 341, 441, 556}
  };

  if (Ptype_ >= NUM_TYPES)
    Ptype_ = NUM_TYPES - 1;
  Ptype = Ptype_;

  f32 tmp;
  for (i32 i = 0; i < REV_COMBS * 2; i++)
  {
    if (Ptype_ == 0)
      tmp = 800.0f + (f32)(RND * 1400.0f);
    else
      tmp = (f32)combtunings[Ptype_][i % REV_COMBS];
    tmp *= roomsize;
    if (i > REV_COMBS)
      tmp += 23.0f;
    tmp *= fSAMPLE_RATE / 44100.0f;	//adjust the combs according to the samplerate
    if (tmp < 10)
      tmp = 10;

    comblen[i] = lrintf(tmp);
    combk[i] = 0;
    lpcomb[i] = 0;
    if (comb[i] != NULL)
      delete comb[i];
    comb[i] = new f32[comblen[i]];
  }

  for (i32 i = 0; i < REV_APS * 2; i++)
  {
    if (Ptype_ == 0)
      tmp = 500.0f + (f32)(RND * 500.0f);
    else
      tmp = (f32)aptunings[Ptype_][i % REV_APS];
    tmp *= roomsize;
    if (i > REV_APS)
      tmp += 23.0f;
    tmp *= fSAMPLE_RATE / 44100.0f;	//adjust the combs according to the samplerate
    if (tmp < 10)
      tmp = 10;
    aplen[i] = lrintf(tmp);
    apk[i] = 0;
    if (ap[i] != NULL)
      delete ap[i];
    ap[i] = new f32[aplen[i]];
  }
  settime(Ptime);
  cleanup();
}

void Reverb::setroomsize(i32 Proomsize_)
{
  if (Proomsize_ == 0)
    Proomsize_ = 64;		//this is because the older versions consider roomsize=0
  Proomsize = Proomsize_;
  roomsize = ((f32)Proomsize_ - 64.0f) / 64.0f;
  if (roomsize > 0.0)
    roomsize *= 2.0f;
  roomsize = powf(10.0f, roomsize);
  rs = sqrtf(roomsize);
  rs_coeff = rs / (f32)REV_COMBS;
  settype(Ptype);
}

void Reverb::setpreset(i32 npreset)
{
  const i32 PRESET_SIZE = 10;
  i32 presets[][PRESET_SIZE] = {
    //Cathedral1
    {80, 64, 63, 24, 0, 4002, 27, 83, 1, 64},
    //Cathedral2
    {80, 64, 69, 35, 0, 25040, 21, 71, 0, 64},
    //Cathedral3
    {80, 64, 69, 24, 0,  25040, 2417, 78, 1, 85},
    //Hall1
    {90, 64, 51, 10, 0, 25040, 81, 78, 1, 64},
    //Hall2
    {90, 64, 53, 20, 0, 25040, 2417, 71, 1, 64},
    //Room1
    {100, 64, 33, 0, 0, 25040, 21, 106, 0, 30},
    //Room2
    {100, 64, 21, 26, 0, 1223, 21, 77, 1, 45},
    //Basement
    {110, 64, 14, 0, 0, 25040, 27, 71, 0, 25},
    //Tunnel
    {85, 80, 84, 20, 42, 652, 21, 78, 1, 105},
    //Echoed1
    {95, 64, 26, 60, 71, 14722, 21, 64, 1, 64},
    //Echoed2
    {90, 64, 40, 88, 71, 14722, 21, 88, 1, 64},
    //VeryLong1
    {90, 64, 93, 15, 0, 14722, 21, 77, 0, 95},
    //VeryLong2
    {90, 64, 111, 30, 0, 14722, 5058, 74, 1, 80}
  };

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

void Reverb::changepar(i32 npar, i32 value)
{
  switch (npar)
  {
  case 0:
    setvolume(value);
    return;
  case 1:
    setpan(value);
    return;
  case 2:
    settime(value);
    return;
  case 3:
    setidelay(value);
    return;
  case 4:
    setidelayfb(value);
    return;
  case 5:
    setlpf(value);
    return;
  case 6:
    sethpf(value);
    return;
  case 7:
    setlohidamp(value);
    return;
  case 8:
    settype(value);
    return;
  case 9:
    setroomsize(value);
    return;
  }
  ASSERT(false);
}

i32 Reverb::getpar(i32 npar)
{
  switch (npar)
  {
  case 0:
    return Pvolume;
  case 1:
    return Ppan;
  case 2:
    return Ptime;
  case 3:
    return Pidelay;
  case 4:
    return (Pidelayfb);
  case 5:
    return Plpf;
  case 6:
    return Phpf;
  case 7:
    return Plohidamp;
  case 8:
    return Ptype;
  case 9:
    return Proomsize;
  }
  ASSERT(false);
  return 0;
}

#endif // SHR3D_SFX_CORE_RAKARRACK
