/*
  ZynAddSubFX - a software synthesizer

  Chorus.C - Chorus and Flange effects
  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 "Chorus_.h"

#ifdef SHR3D_SFX_CORE_RAKARRACK

#define MAX_CHORUS_DELAY 250.0f	//ms

Chorus::Chorus()
{
  maxdelay = lrintf(MAX_CHORUS_DELAY / 1000.0 * SAMPLE_RATE);
  delayl = new f32[maxdelay];
  delayr = new f32[maxdelay];

  setpreset(0);

  lfo.effectlfoout(lfol, lfor);
  dl2 = getdelay(lfol);
  dr2 = getdelay(lfor);
  cleanup();
}

/*
 * get the delay value in samples; xlfo is the current lfo value
 */
f32 Chorus::getdelay(f32 xlfo)
{
  f32 result;
  if (Pflangemode == 0)
    result = (delay + xlfo * depth) * fSAMPLE_RATE;
  else
    result = 0;

  //check if it is too big delay(caused bu errornous setdelay() and setdepth()    
  if ((result + 0.5) >= maxdelay)
  {
    fprintf(stderr, "%s",
      "WARNING: Chorus.C::getdelay(..) too big delay (see setdelay and setdepth funcs.)\n");
    printf("%f %d\n", result, maxdelay);
    result = (f32)maxdelay - 1.0f;
  }
  return result;
}

/*
 * Apply the effect
 */
void Chorus::processBlock(const f32* const* inBlock, f32** outBlock, const i32 blockSize)
{
  dl1 = dl2;
  dr1 = dr2;
  lfo.effectlfoout(lfol, lfor);

  dl2 = getdelay(lfol);
  dr2 = getdelay(lfor);

  for (i32 i = 0; i < blockSize; i++)
  {
    f32 inl = inBlock[0][i];
    f32 inr = inBlock[1][i];
    //LRcross
    f32 l = inl;
    f32 r = inr;
    inl = l * (1.0f - lrcross) + r * lrcross;
    inr = r * (1.0f - lrcross) + l * lrcross;

    //Left channel

    //compute the delay in samples using linear interpolation between the lfo delays
    mdel = (dl1 * (f32)(blockSize - i) + dl2 * (f32)i) / f32(blockSize);
    if (++dlk >= maxdelay)
      dlk = 0;
    f32 tmp = (f32)dlk - mdel + (f32)maxdelay * 2.0f;	//where should I get the sample from

    F2I(tmp, dlhi);
    dlhi %= maxdelay;

    dlhi2 = (dlhi - 1 + maxdelay) % maxdelay;
    dllo = 1.0f - fmodf(tmp, 1.0f);
    outBlock[0][i] = delayl[dlhi2] * dllo + delayl[dlhi] * (1.0f - dllo);
    delayl[dlk] = inl + outBlock[0][i] * fb;

    //Right channel

    //compute the delay in samples using linear interpolation between the lfo delays
    mdel = (dr1 * (f32)(blockSize - i) + dr2 * (f32)i) / f32(blockSize);
    if (++drk >= maxdelay)
      drk = 0;
    tmp = (f32)drk - mdel + (f32)maxdelay * 2.0f;	//where should I get the sample from

    F2I(tmp, dlhi);
    dlhi %= maxdelay;

    dlhi2 = (dlhi - 1 + maxdelay) % maxdelay;
    dllo = 1.0f - fmodf(tmp, 1.0f);
    outBlock[1][i] = delayr[dlhi2] * dllo + delayr[dlhi] * (1.0f - dllo);
    delayr[dlk] = inr + outBlock[1][i] * fb;
  }

  if (Poutsub != 0)
  {
    for (i32 i = 0; i < blockSize; i++)
    {
      outBlock[0][i] *= -1.0f;
      outBlock[1][i] *= -1.0f;
    }
  }

  for (i32 i = 0; i < blockSize; i++)
  {
    outBlock[0][i] *= panning;
    outBlock[1][i] *= (1.0f - panning);
  }
}

/*
 * Cleanup the effect
 */
void Chorus::cleanup()
{
  for (i32 i = 0; i < maxdelay; i++)
  {
    delayl[i] = 0.0;
    delayr[i] = 0.0;
  }
}

/*
 * Parameter control
 */
void Chorus::setdepth(i32 Pdepth_)
{
  Pdepth = Pdepth_;
  depth = (powf(8.0f, ((f32)Pdepth_ / 127.0f) * 2.0f) - 1.0f) / 1000.0f;	//seconds
}

void Chorus::setdelay(i32 Pdelay_)
{
  Pdelay = Pdelay_;
  delay = (powf(10.0f, ((f32)Pdelay_ / 127.0f) * 2.0f) - 1.0f) / 1000.0f;	//seconds
}

void Chorus::setfb(i32 Pfb_)
{
  Pfb = Pfb_;
  fb = ((f32)Pfb_ - 64.0f) / 64.1f;
}

void Chorus::setvolume(i32 Pvolume_)
{
  Pvolume = Pvolume_;
  outvolume = (f32)Pvolume_ / 127.0f;
}

void Chorus::setpanning(i32 Ppanning_)
{
  Ppanning = Ppanning_;
  panning = ((f32)Ppanning_ + .5f) / 127.0f;
}

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

void Chorus::setpreset(i32 npreset)
{
  const i32 PRESET_SIZE = 12;
  i32 presets[][PRESET_SIZE] = {
    //Chorus1
    {64, 64, 33, 0, 0, 90, 40, 85, 64, 119, 0, 0},
    //Chorus2
    {64, 64, 19, 0, 0, 98, 56, 90, 64, 19, 0, 0},
    //Chorus3
    {64, 64, 7, 0, 1, 42, 97, 95, 90, 127, 0, 0},
    //Celeste1
    {64, 64, 1, 0, 0, 42, 115, 18, 90, 127, 0, 0},
    //Celeste2
    {64, 64, 7, 117, 0, 50, 115, 9, 31, 127, 0, 1},
    //Flange1
    {64, 64, 39, 0, 0, 60, 23, 3, 62, 0, 0, 0},
    //Flange2
    {64, 64, 9, 34, 1, 40, 35, 3, 109, 0, 0, 0},
    //Flange3
    {64, 64, 31, 34, 1, 94, 35, 3, 54, 0, 0, 1},
    //Flange4
    {64, 64, 14, 0, 1, 62, 12, 19, 97, 0, 0, 0},
    //Flange5
    {64, 64, 34, 105, 0, 24, 39, 19, 17, 0, 0, 1}
  };

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

void Chorus::changepar(i32 npar, i32 value)
{
  switch (npar)
  {
  case 0:
    setvolume(value);
    return;
  case 1:
    setpanning(value);
    return;
  case 2:
    lfo.Pfreq = value;
    lfo.updateparams();
    return;
  case 3:
    lfo.Prandomness = value;
    lfo.updateparams();
    return;
  case 4:
    lfo.PLFOtype = value;
    lfo.updateparams();
    return;
  case 5:
    lfo.Pstereo = value;
    lfo.updateparams();
    return;
  case 6:
    setdepth(value);
    return;
  case 7:
    setdelay(value);
    return;
  case 8:
    setfb(value);
    return;
  case 9:
    setlrcross(value);
    return;
  case 10:
    if (value > 1)
      value = 1;
    Pflangemode = value;
    return;
  case 11:
    if (value > 1)
      value = 1;
    Poutsub = value;
    return;
  }
  ASSERT(false);
}

i32 Chorus::getpar(i32 npar)
{
  switch (npar)
  {
  case 0:
    return Pvolume;
  case 1:
    return Ppanning;
  case 2:
    return lfo.Pfreq;
  case 3:
    return lfo.Prandomness;
  case 4:
    return lfo.PLFOtype;
  case 5:
    return lfo.Pstereo;
  case 6:
    return Pdepth;
  case 7:
    return Pdelay;
  case 8:
    return Pfb;
  case 9:
    return Plrcross;
  case 10:
    return Pflangemode;
  case 11:
    return Poutsub;
  }
  ASSERT(false);
  return 0;
}

#endif // SHR3D_SFX_CORE_RAKARRACK
