/*
  ZynAddSubFX - a software synthesizer

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

  Modified for rakarrack by Josep Andreu
  Reverse Echo effect by Transmogrifox
  Echo Direct patch From: Arnout Engelen <arnouten@bzzt.net>

  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 "Echo.h"

#ifdef SHR3D_SFX_CORE_RAKARRACK

Echo::Echo()
{
  //default values
  Srate_Attack_Coeff = 1.0f / (fSAMPLE_RATE * ATTACK);

  setpreset(0);
  cleanup();
}

void Echo::cleanup()
{
  i32 i;
  for (i = 0; i < maxx_delay; i++)
    ldelay[i] = 0.0;
  for (i = 0; i < maxx_delay; i++)
    rdelay[i] = 0.0;
  oldl = 0.0;
  oldr = 0.0;
}

void Echo::initdelays()
{
  kl = 0;
  kr = 0;

  dl = delay - lrdelay;
  if (dl < 1)
    dl = 1;
  dr = delay + lrdelay;
  if (dr < 1)
    dr = 1;

  rvkl = dl - 1;
  rvkr = dr - 1;
  Srate_Attack_Coeff = 15.0f / (dl + dr);   // Set swell time to 1/10th of average delay time 

  for (i32 i = dl; i < maxx_delay; i++)
    ldelay[i] = 0.0;
  for (i32 i = dr; i < maxx_delay; i++)
    rdelay[i] = 0.0;

  oldl = 0.0;
  oldr = 0.0;
}

void Echo::processBlock(const f32* const* inBlock, f32** outBlock, const i32 blockSize)
{
  i32 i;
  f32 l, r, ldl, rdl, ldlout, rdlout, rswell, lswell;

  for (i = 0; i < blockSize; i++)
  {
    ldl = ldelay[kl];
    rdl = rdelay[kr];
    l = ldl * (1.0f - lrcross) + rdl * lrcross;
    r = rdl * (1.0f - lrcross) + ldl * lrcross;
    ldl = l;
    rdl = r;

    ldlout = -ldl * fb;
    rdlout = -rdl * fb;
    if (!Pdirect)
    {
      ldlout = ldl = inBlock[0][i] * panning + ldlout;
      rdlout = rdl = inBlock[1][i] * (1.0f - panning) + rdlout;
    }
    else
    {
      ldl = inBlock[0][i] * panning + ldlout;
      rdl = inBlock[1][i] * (1.0f - panning) + rdlout;
    }

    if (reverse > 0.0)
    {
      lswell = (f32)(abs(kl - rvkl)) * Srate_Attack_Coeff;
      if (lswell <= PI_)
      {
        lswell = 0.5f * (1.0f - cosf(lswell));  //Clickless transition
        outBlock[0][i] = reverse * (ldelay[rvkl] * lswell + ldelay[rvfl] * (1.0f - lswell)) + (ldlout * (1 - reverse));   //Volume ducking near zero crossing.     
      }
      else
      {
        outBlock[0][i] = (ldelay[rvkl] * reverse) + (ldlout * (1 - reverse));
      }

      rswell = (f32)(abs(kr - rvkr)) * Srate_Attack_Coeff;
      if (rswell <= PI_)
      {
        rswell = 0.5f * (1.0f - cosf(rswell));   //Clickless transition 
        outBlock[1][i] = reverse * (rdelay[rvkr] * rswell + rdelay[rvfr] * (1.0f - rswell)) + (rdlout * (1 - reverse));  //Volume ducking near zero crossing.
      }
      else
      {
        outBlock[1][i] = (rdelay[rvkr] * reverse) + (rdlout * (1 - reverse));
      }
    }
    else
    {
      outBlock[0][i] = ldlout;
      outBlock[1][i] = rdlout;
    }

    //LowPass Filter
    ldelay[kl] = ldl = ldl * hidamp + oldl * (1.0f - hidamp);
    rdelay[kr] = rdl = rdl * hidamp + oldr * (1.0f - hidamp);
    oldl = ldl + DENORMAL_GUARD;
    oldr = rdl + DENORMAL_GUARD;

    if (++kl >= dl)
      kl = 0;
    if (++kr >= dr)
      kr = 0;
    rvkl = dl - 1 - kl;
    rvkr = dr - 1 - kr;

    rvfl = dl + fade - kl;
    rvfr = dr + fade - kr;
    //Safety checks to avoid addressing data outside of buffer
    if (rvfl > dl) rvfl = rvfl - dl;
    if (rvfl < 0) rvfl = 0;
    if (rvfr > dr) rvfr = rvfr - dr;
    if (rvfr < 0) rvfr = 0;
  }
}

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

void Echo::setpanning(i32 Ppanning_)
{
  Ppanning = Ppanning_;
  panning = ((f32)Ppanning_ + 0.5f) / 127.0f;
}

void Echo::setreverse(i32 Preverse_)
{
  Preverse = Preverse_;
  reverse = (f32)Preverse_ / 127.0f;
}

void Echo::Tempo2Delay(i32 value)
{
  Pdelay = i32(60.0f / (f32)value * 1000.0f);
  delay = i32((f32)Pdelay / 1000.0f * fSAMPLE_RATE);
  if ((u32)delay > (SAMPLE_RATE * MAX_DELAY)) delay = SAMPLE_RATE * MAX_DELAY;
  initdelays();
}

void Echo::setdelay(i32 Pdelay_)
{
  Pdelay = Pdelay_;
  delay = Pdelay_;
  if (delay < 10) delay = 10;
  if (delay > MAX_DELAY * 1000) delay = 1000 * MAX_DELAY;  //Constrains 10ms ... MAX_DELAY
  delay = 1 + lrintf(((f32)delay / 1000.0f) * fSAMPLE_RATE);

  initdelays();
}

void Echo::setlrdelay(i32 Plrdelay_)
{
  Plrdelay = Plrdelay_;
  f32 tmp =
    (powf(2.0, fabsf((f32)Plrdelay_ - 64.0f) / 64.0f * 9.0f) -
      1.0f) / 1000.0f * fSAMPLE_RATE;
  if (Plrdelay_ < 64.0)
    tmp = -tmp;
  lrdelay = lrintf(tmp);
  initdelays();
}

void Echo::setlrcross(i32 Plrcross_)
{
  Plrcross = Plrcross_;
  lrcross = (f32)Plrcross_ / 127.0f * 1.0f;
}

void Echo::setfb(i32 Pfb_)
{
  Pfb = Pfb_;
  fb = (f32)Pfb_ / 128.0f;
}

void Echo::sethidamp(i32 Phidamp_)
{
  Phidamp = Phidamp_;
  hidamp = 1.0f - (f32)Phidamp_ / 127.0f;
}

void Echo::setdirect(i32 Pdirect_)
{
  if (Pdirect_ > 0)
    Pdirect_ = 1;
  Pdirect = Pdirect_;
}

void Echo::setpreset(i32 npreset)
{
  const i32 PRESET_SIZE = 9;
  i32 presets[][PRESET_SIZE] = {
    //Echo 1
    {67, 64, 565, 64, 30, 59, 0, 127, 0},
    //Echo 2
    {67, 64, 357, 64, 30, 59, 0, 64, 0},
    //Echo 3
    {67, 75, 955, 64, 30, 59, 10, 0, 0},
    //Simple Echo
    {67, 60, 705, 64, 30, 0, 0, 0, 0},
    //Canyon
    {67, 60, 1610, 50, 30, 82, 48, 0, 0},
    //Panning Echo 1
    {67, 64, 705, 17, 0, 82, 24, 0, 0},
    //Panning Echo 2
    {81, 60, 737, 118, 100, 68, 18, 0, 0},
    //Panning Echo 3
    {81, 60, 472, 100, 127, 67, 36, 0, 0},
    //Feedback Echo
    {62, 64, 456, 64, 100, 90, 55, 0, 0}
  };

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

void Echo::changepar(i32 npar, i32 value)
{
  switch (npar)
  {
  case 0:
    setvolume(value);
    return;
  case 1:
    setpanning(value);
    return;
  case 2:
    setdelay(value);
    return;
  case 3:
    setlrdelay(value);
    return;
  case 4:
    setlrcross(value);
    return;
  case 5:
    setfb(value);
    return;
  case 6:
    sethidamp(value);
    return;
  case 7:
    setreverse(value);
    return;
  case 8:
    setdirect(value);
    return;
  }
}

i32 Echo::getpar(i32 npar)
{
  switch (npar)
  {
  case 0:
    return Pvolume;
  case 1:
    return Ppanning;
  case 2:
    return Pdelay;
  case 3:
    return Plrdelay;
  case 4:
    return Plrcross;
  case 5:
    return Pfb;
  case 6:
    return Phidamp;
  case 7:
    return Preverse;
  case 8:
    return Pdirect;
  }
  ASSERT(false);
  return (0);
}

#endif // SHR3D_SFX_CORE_RAKARRACK
