/*

  Echotron.C - Echotron effect
  Author: Ryan Billing & Josep Andreu

  Adapted effect structure of ZynAddSubFX - a software synthesizer
  Author: Nasca Octavian Paul

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

#ifdef SHR3D_SFX_CORE_RAKARRACK

#include "../common/RBFilter.h"

#include <string.h>

Echotron::Echotron()
  : lpfl(0, 800, 1, 0)
  , lpfr(0, 800, 1, 0)
{
  initparams = 0;
  //default values

  for (i32 i = 0; i < ECHOTRON_MAXFILTERS; i++)
  {
    const f32 center = 500;
    const f32 qq = 1.0f;
    filterbank[i].sfreq = center;
    filterbank[i].sq = qq;
    filterbank[i].sLP = 0.25f;
    filterbank[i].sBP = -1.0f;
    filterbank[i].sHP = 0.5f;
    filterbank[i].sStg = 1.0f;
    filterbank[i].l = new RBFilter(0, center, qq, 0);
    filterbank[i].r = new RBFilter(0, center, qq, 0);

    filterbank[i].l->setmix(1, filterbank[i].sLP, filterbank[i].sBP, filterbank[i].sHP);
    filterbank[i].r->setmix(1, filterbank[i].sLP, filterbank[i].sBP, filterbank[i].sHP);
  }

  setpreset(0);
  cleanup();
}

void Echotron::cleanup()
{
  memset(lxn, 0, sizeof(f32) * maxx_size);
  memset(rxn, 0, sizeof(f32) * maxx_size);

  lpfl.cleanup();
  lpfr.cleanup();
}

void Echotron::processBlock(const f32* const* inBlock, f32** outBlock, const i32 blockSize)
{
  if (Pmoddly || Pmodfilts)
    modulate_delay();
  else
    interpl = interpr = 0;

  f32 tmpmodl = oldldmod;
  f32 tmpmodr = oldrdmod;
  i32 intmodl, intmodr;

  for (i32 i = 0; i < blockSize; i++)
  {
    tmpmodl += interpl;
    tmpmodr += interpr;

    intmodl = lrintf(tmpmodl);
    intmodr = lrintf(tmpmodr);

    const f32 l = lpfl.filterout_s(inBlock[0][i] + lfeedback);  //High Freq damping 
    const f32 r = lpfr.filterout_s(inBlock[1][i] + rfeedback);

    lxn[offset] = l;
    rxn[offset] = r;

    //Convolve 
    f32 lyn = 0.0f;
    f32 ryn = 0.0f;

    if (Pfilters)
    {
      i32 j = 0;
      for (i32 k = 0; k < Plength; k++)
      {
        i32 lxindex = offset + ltime[k] + intmodl;
        i32 rxindex = offset + rtime[k] + intmodr;
        if (lxindex >= maxx_size)
          lxindex -= maxx_size;
        if (rxindex >= maxx_size)
          rxindex -= maxx_size;
        if ((iStages[k] >= 0) && (j < ECHOTRON_MAXFILTERS))
        {
          lyn += filterbank[j].l->filterout_s(lxn[lxindex]) * ldata[k];		//filter each tap specified
          ryn += filterbank[j].r->filterout_s(rxn[rxindex]) * rdata[k];
          j++;
        }
        else
        {
          lyn += lxn[lxindex] * ldata[k];
          ryn += rxn[rxindex] * rdata[k];
        }
      }
    }
    else
    {
      for (i32 k = 0; k < Plength; k++)
      {
        i32 lxindex = offset + ltime[k] + intmodl;
        i32 rxindex = offset + rtime[k] + intmodr;
        if (lxindex >= maxx_size) lxindex -= maxx_size;
        if (rxindex >= maxx_size) rxindex -= maxx_size;
        lyn += lxn[lxindex] * ldata[k];
        ryn += rxn[rxindex] * rdata[k];
      }
    }

    lfeedback = (lrcross * ryn + ilrcross * lyn) * lpanning;
    rfeedback = (lrcross * lyn + ilrcross * ryn) * rpanning;
    outBlock[0][i] = lfeedback;
    outBlock[1][i] = rfeedback;
    lfeedback *= fb;
    rfeedback *= fb;

    offset--;
    if (offset < 0)
      offset = maxx_size;
  }

  if (initparams)
    init_params();
}

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

void Echotron::setpanning(i32 value)
{
  Ppanning = value;
  rpanning = ((f32)Ppanning) / 64.0f;
  lpanning = 2.0f - rpanning;
  lpanning = 10.0f * powf(lpanning, 4);
  rpanning = 10.0f * powf(rpanning, 4);
  lpanning = 1.0f - 1.0f / (lpanning + 1.0f);
  rpanning = 1.0f - 1.0f / (rpanning + 1.0f);
  lpanning *= 1.1f;
  rpanning *= 1.1f;
  if (lpanning > 1.0f)
    lpanning = 1.0f;
  if (rpanning > 1.0f)
    rpanning = 1.0f;
}

static const char* getEchotronData(i32 index, f32& subdiv_fmod, f32& subdiv_dmod, i32& f_qmode)
{
  switch (index)
  {
  case 0:
    subdiv_fmod = 0.5f;
    subdiv_dmod = 1.0f;
    f_qmode = 1;
    // Pan Time Level LP BP HP Freq Q Stages
    return R"(-1.0	0.5	1.0	0.5	-0.25	0.125	550	4.0	1
1.0	0.75	1.0	0.0	-0.25	0.75	950	4.0	1
-0.85	1.0	.707	0.5	-0.25	0.125	750	4.0	1
0.85	1.25	.707	0.0	-0.25	0.5	750	4.0	1
)";
  case 1:
    subdiv_fmod = 0.5f;
    subdiv_dmod = 0.5f;
    f_qmode = 0;
    return R"(-1.0	0.10	1.0	0.5	-0.25	0.125	550	4.6	1
1.0	0.20	1.0	0.0	-0.25	0.75	950	4.6	1
-0.5	0.30	1.0	0.5	-0.25	0.125	750	4.6	1
)";
  case 2:
    subdiv_fmod = 0.5f;
    subdiv_dmod = 0.5f;
    f_qmode = 0;
    return R"(0.0	0.00006	-1.75	0.5	0.0	0.0	10000	0.5	1
0.0	0.00012	2.0	0.0	0.0	0.5	5000	0.5	1
0.5	0.0001	0.5	0.0	-0.25	0.5	2500	1.0	1
-0.5	0.00011	0.5	0.5	-0.25	0.0	2000	1.0	1
0.0	0.375	1.0	0.0	-0.25	0.5	450	4.6	1
)";
  case 3:
    subdiv_fmod = 0.5f;
    subdiv_dmod = 0.5f;
    f_qmode = 0;
    return R"(0.0     0.00025 0.95    0.0     1.0     0.0     1000    30      1
0.0     0.000125        -0.95   0.0     1.0     0.0     1000    30      1
0.0     0.0000625       0.95    0.0     1.0     0.0     2000    30      1
0.0     0.000031        -0.95   0.0     1.0     0.0     4000    30      1
0.0     0.000015        0.95    0.0     1.0     0.0     6000    30      1
0.0     0.0000075       -0.95   0.0     1.0     0.0     8000    30      1
0.0     0.0000032       0.95    0.0     1.0     0.0     16000   30     1
0.0     0.0005  -0.95   0.0     1.0     0.0     500     30      1
)";
  case 4:
    subdiv_fmod = 0.5f;
    subdiv_dmod = 0.5f;
    f_qmode = 1;
    return R"(0.0	0.0005	1.0	0.0	0.5	0.0	750	2.5	4
-1.0	0.5	1.0	0.5	-0.25	0.125	825	4.6	1
1.0	0.75	1.0	0.0	-0.25	0.75	550	4.6	1
-0.85	1.0	1.0	0.5	-0.25	0.125	750	4.6	2
0.85	1.25	1.0	0.0	-0.25	0.5	450	4.6	2
)";
  case 5:
    subdiv_fmod = 0.5f;
    subdiv_dmod = 0.5f;
    f_qmode = 0;
    return R"(0.0	0.025	1.0	0.0	1.0	0.0	750	2.5	4
-1.0	0.5	1.0	1.0	-0.5	0.25	825	4.6	1
1.0	0.75	1.0	0.0	-0.5	1.25	550	4.6	1
-0.85	1.0	1.0	1.0	-0.5	0.25	750	4.6	2
0.85	1.25	1.0	0.0	-0.5	1.0	450	4.6	2
)";
  case 6:
    subdiv_fmod = 0.5f;
    subdiv_dmod = 0.5f;
    f_qmode = 0;
    return R"(0.0	0.005	0.25	0.0	1.0	0.0	700	3.65045	1
0.0	0.005	-0.125	0.0	1.0	0.0	300.0	3.65045	1
0.0	0.005	-0.125	0.0	1.0	0.0	1000.0	3.0	1
0.0	0.005	0.25	1.0	1.0	1.0	550	1.0	0
)";
  case 7:
    subdiv_fmod = 0.5f;
    subdiv_dmod = 2.0f;
    f_qmode = 0;
    return R"(-0.75	0.0025	0.15	1.0	-1.0	1.0	50	1	1
0.75	0.0025	-0.15	1.0	-1.0	1.0	100	1	1
-1	0.02	0.5	1.0	-1.0	1.0	3200	1	1
1	0.025	0.5	1.0	-1.0	1.0	200	1	1
0.0	0.030	0.7	1.0	-1.0	1.2	400	1	1
0.0	0.035	-0.4	1.0	-1.0	1.0	800	1	1
0.0	0.040	0.25	1.0	-1.0	1.5	1600	1	1
)";
  case 8:
    subdiv_fmod = 0.125f;
    subdiv_dmod = 50.0f;
    f_qmode = 0;
    return R"(-1.0	0.375	1.0	0.75	-0.25	1.25	550	4.0	1
0.5	0.75	1.0	1.0	-0.15	0.5	750	4.0	1
-0.85	0.5	0.5	1.0	-0.25	1.0	950	4.0	1
0.85	0.875	0.5	1.0	-0.25	1.0	1250	4.0	1
-0.5	1.0	0.25	1.0	-0.15	0.5	1550	4.0	1
1.0	1.25	0.25	0.75	-0.25	1.25	1850	4.0	1
)";
  case 9:
    subdiv_fmod = 1.0f;
    subdiv_dmod = 2.0f;
    f_qmode = 1;
    return R"(1.0	0.00507583	1.0	0.0	1.0	0.0	49.2531	2.66145	1
1.0	0.00347029	1.0	0.0	1.0	0.0	72.0402	2.66145	1
1.0	0.0023726	1.0	0.0	1.0	0.0	105.37	2.66145	1
1.0	0.00162212	1.0	0.0	1.0	0.0	154.119	2.66145	1
1.0	0.00110902	1.0	0.0	1.0	0.0	225.423	2.66145	1
1.0	0.000758228	1.0	0.0	1.0	0.0	329.716	2.66145	1
1.0	0.000518392	1.0	0.0	1.0	0.0	482.261	2.66144	1
1.0	0.000354419	1.0	0.0	1.0	0.0	705.38	2.66145	1
1.0	0.000242312	1.0	0.0	1.0	0.0	1031.73	2.66145	1
1.0	0.000165666	1.0	0.0	1.0	0.0	1509.06	2.66144	1
1.0	0.000113264	1.0	0.0	1.0	0.0	2207.23	2.66145	1
1.0	0.0000774375	1.0	0.0	1.0	0.0	3228.41	2.66145	1
1.0	0.0000529431	1.0	0.0	1.0	0.0	4722.05	2.66145	1
1.0	0.0000361966	1.0	0.0	1.0	0.0	6906.72	2.66144	1
1.0	0.0000247472	1.0	0.0	1.0	0.0	10102.1	2.66145	1
)";
  case 10:
    subdiv_fmod = 0.5f;
    subdiv_dmod = 5.0f;
    f_qmode = 1;
    return R"(-0.5	0.5	1.0	1.0	0.0	0.0	3000	1.0	1
0.5	1.0	0.93	1.0	0.0	0.0	1500	1.0	1
0.0	1.5	0.86	1.0	0.0	0.0	750	1.0	1
0.75	2.0	0.8	1.0	0.0	0.0	550	1.0	1
-0.75	2.5	0.75	1.0	0.0	0.0	550	1.0	1
)";
  }
  ASSERT(false);
  return {};
}

void Echotron::setProfile(i32 index)
{
  Filenum = index;
  f32 tPan = 0.0f;
  f32 tTime = 0.0f;
  f32 tLevel = 0.0f;
  f32 tLP = 0.0f;
  f32 tBP = 0.0f;
  f32 tHP = 0.0f;
  f32 tFreq = 20.0f;
  f32 tQ = 1.0f;
  i32 tiStages = 0;

  const char* csvData = getEchotronData(index, subdiv_fmod, subdiv_dmod, f_qmode);

  i32 count = 0;
  while (*csvData != '\0')
  {
    sscanf(csvData, "%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%d", &tPan, &tTime, &tLevel, &tLP, &tBP, &tHP, &tFreq, &tQ, &tiStages);

    if ((tPan < -1.0f) || (tPan > 1.0f))
    {
      ASSERT(false);
      break;
    }
    else
      fPan[count] = tPan;

    if ((tTime < -6.0) || (tTime > 6.0f))
    {
      ASSERT(false);
      break;
    }
    else
      fTime[count] = fabs(tTime);

    if ((tLevel < -10.0f) || (tLevel > 10.0f))
    {
      ASSERT(false);
      break;
    }
    else
      fLevel[count] = tLevel;

    if ((tLP < -2.0f) || (tLP > 2.0f))
    {
      ASSERT(false);
      break;
    }
    else
      fLP[count] = tLP;

    if ((tBP < -2.0f) || (tBP > 2.0f))
    {
      ASSERT(false);
      break;
    }
    else
      fBP[count] = tBP;

    if ((tHP < -2.0f) || (tHP > 2.0f))
    {
      ASSERT(false);
      break;
    }
    else
      fHP[count] = tHP;

    if ((tFreq < 20.0f) || (tFreq > 26000.0f))
    {
      ASSERT(false);
      break;
    }
    else
      fFreq[count] = tFreq;

    if ((tQ < 0.0) || (tQ > 300.0f))
    {
      ASSERT(false);
      break;
    }
    else
      fQ[count] = tQ;

    if ((tiStages < 0) || (tiStages > MAX_FILTER_STAGES))
    {
      ASSERT(false);
      break;
    }
    else
      iStages[count] = tiStages - 1;     //check in main loop if <0, then skip filter  

    ++count;

    while (*csvData != '\n')
      ++csvData;
    ++csvData;
  }

  if (!Pchange)
    Plength = count;

  cleanup();
  init_params();
}

void Echotron::init_params()
{
  f32 hSR = fSAMPLE_RATE * 0.5f;
  f32 tmp_time;
  f32 tpanl, tpanr;
  f32 tmptempo;
  i32 tfcnt = 0;

  initparams = 0;
  depth = ((f32)(Pdepth - 64)) / 64.0f;
  dlyrange = 0.008f * powf(2.0f, 4.5f * depth);
  width = ((f32)Pwidth) / 127.0f;

  tmptempo = (f32)Ptempo;
  lfo.Pfreq = lrintf(subdiv_fmod * tmptempo);
  dlfo.Pfreq = lrintf(subdiv_dmod * tmptempo);

  for (i32 i = 0; i < Plength; i++)
  {
    tmp_time = lrintf(fTime[i] * tempo_coeff * fSAMPLE_RATE);
    if (tmp_time < maxx_size)
      rtime[i] = tmp_time; else rtime[i] = maxx_size;

    ltime[i] = rtime[i];

    if (fPan[i] >= 0.0f)
    {
      tpanr = 1.0;
      tpanl = 1.0f - fPan[i];
    }
    else
    {
      tpanl = 1.0;
      tpanr = 1.0f + fPan[i];
    }

    ldata[i] = fLevel[i] * tpanl;
    rdata[i] = fLevel[i] * tpanr;

    if ((tfcnt < ECHOTRON_MAXFILTERS) && (iStages[i] >= 0))
    {
      i32 Freq = fFreq[i] * powf(2.0f, depth * 4.5f);
      if (Freq < 20.0)
        Freq = 20.0f;
      if (Freq > i32(hSR))
        Freq = i32(hSR);
      filterbank[tfcnt].l->setfreq_and_q(Freq, fQ[i]);
      filterbank[tfcnt].r->setfreq_and_q(Freq, fQ[i]);
      filterbank[tfcnt].l->setstages(iStages[i]);
      filterbank[tfcnt].r->setstages(iStages[i]);
      filterbank[tfcnt].l->setmix(1, fLP[i], fBP[i], fHP[i]);
      filterbank[tfcnt].r->setmix(1, fLP[i], fBP[i], fHP[i]);
      filterbank[tfcnt].l->setmode(f_qmode);
      filterbank[tfcnt].r->setmode(f_qmode);
      tfcnt++;
    }
  }
}

void Echotron::modulate_delay()
{
  f32 lfol, lfor;
  lfo.effectlfoout(lfol, lfor);

  f32 dlfol, dlfor;
  dlfo.effectlfoout(dlfol, dlfor);

  if (Pmodfilts)
  {
    const f32 lfmod = powf(2.0f, (lfol * width + 0.25f + depth) * 4.5f);
    const f32 rfmod = powf(2.0f, (lfor * width + 0.25f + depth) * 4.5f);
    for (i32 i = 0; i < ECHOTRON_MAXFILTERS; i++)
    {
      filterbank[i].l->setfreq(lfmod * fFreq[i]);
      filterbank[i].r->setfreq(rfmod * fFreq[i]);
    }
  }

  if (Pmoddly)
  {
    oldldmod = ldmod;
    oldrdmod = rdmod;
    ldmod = width * dlfol;
    rdmod = width * dlfor;

    ldmod = lrintf(dlyrange * tempo_coeff * fSAMPLE_RATE * ldmod);
    rdmod = lrintf(dlyrange * tempo_coeff * fSAMPLE_RATE * rdmod);

    const f32 fperiod = 1.0f / fPERIOD;
    interpl = (ldmod - oldldmod) * fperiod;
    interpr = (rdmod - oldrdmod) * fperiod;
  }
  else
  {
    oldldmod = 0.0f;
    oldrdmod = 0.0f;
    ldmod = 0.0f;
    rdmod = 0.0f;
    interpl = 0.0f;
    interpr = 0.0f;
  }
}

void Echotron::sethidamp(i32 Phidamp_)
{
  Phidamp = Phidamp_;
  hidamp = 1.0f - (f32)Phidamp_ / 127.1f;
  f32 fr = 20.0f * powf(2.0, hidamp * 10.0f);
  lpfl.setfreq(fr);
  lpfr.setfreq(fr);
}

void Echotron::setfb(i32 value)
{
  fb = (f32)value / 64.0f;
}

void Echotron::setpreset(i32 npreset)
{
  const i32 PRESET_SIZE = 16;
  i32 presets[][PRESET_SIZE] = {
    //Summer
    {64, 45, 34, 4, 0, 76, 3, 41, 0, 96, -13, 64, 1, 1, 1, 1},
    //Ambience
    {96, 64, 16, 4, 0, 180, 50, 64, 1, 96, -4, 64, 1, 0, 0, 0},
    //Arranjer
    {64, 64, 10, 4, 0, 400, 32, 64, 1, 96, -8, 64, 1, 0, 0, 0},
    //Suction
    {0, 47, 28, 8, 0, 92, 0, 64, 3, 32, 0, 64, 1, 1, 1, 1},
    //SucFlange
    {64, 36, 93, 8, 0, 81, 0, 64, 3, 32, 0, 64, 1, 0, 1, 1}
  };

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

void Echotron::changepar(i32 npar, i32 value)
{
  switch (npar)
  {
  case 0:
    setvolume(value);
    return;
  case 1:
    Pdepth = value;
    initparams = 1;
    return;
  case 2:
    Pwidth = value;
    initparams = 1;
    return;
  case 3:
    Plength = value;
    if (Plength > 127) Plength = 127;
    initparams = 1;
    return;
  case 4:
    return;
  case 5:
    Ptempo = value;
    {
      const f32 tmptempo = (f32)Ptempo;
      tempo_coeff = 60.0f / tmptempo;
      lfo.Pfreq = lrintf(subdiv_fmod * tmptempo);
      dlfo.Pfreq = lrintf(subdiv_dmod * tmptempo);
    }
    lfo.updateparams();
    initparams = 1;
    return;
  case 6:
    sethidamp(value);
    return;
  case 7:
    Plrcross = value;
    lrcross = f32(Plrcross - 64) / 64.0f;
    ilrcross = 1.0f - abs(lrcross);
    return;
  case 8:
    setProfile(value);
    return;
  case 9:
    lfo.Pstereo = value;
    dlfo.Pstereo = value;
    lfo.updateparams();
    dlfo.updateparams();
    return;
  case 10:
    Pfb = value;
    setfb(value);
    return;
  case 11:
    setpanning(value);
    return;
  case 12:
    Pmoddly = value;//delay modulation on/off
    return;
  case 13:
    Pmodfilts = value;//filter modulation on/off
    if (!Pmodfilts)
      initparams = 1;
    return;
  case 14:
    //LFO Type      
    lfo.PLFOtype = value;
    lfo.updateparams();
    dlfo.PLFOtype = value;
    dlfo.updateparams();
    return;
  case 15:
    Pfilters = value;//Pfilters
    return;
  }
  ASSERT(false);
}

i32 Echotron::getpar(i32 npar)
{
  switch (npar)
  {
  case 0:
    return Pvolume;
  case 1:
    return Pdepth;
  case 2:
    return Pwidth;
  case 3:
    return Plength;
  case 4:
    return 0;
  case 5:
    return Ptempo;
  case 6:
    return Phidamp;
  case 7:
    return Plrcross;
  case 8:
    return Filenum;
  case 9:
    return lfo.Pstereo;
  case 10:
    return Pfb;
  case 11:
    return Ppanning;
  case 12:
    return Pmoddly;  //modulate delays
  case 13:
    return Pmodfilts; //modulate filters
  case 14:
    return lfo.PLFOtype;
  case 15:
    return Pfilters;  //Filter delay line on/off
  }
  ASSERT(false);
  return 0;
}

#endif // SHR3D_SFX_CORE_RAKARRACK
