/*
  Rakarrack Audio FX
  Valve DSP Code based Steve Harris valve LADSPA plugin(swh-plugins).
  ZynAddSubFX effect structure - Copyright (C) 2002-2005 Nasca Octavian Paul
  Modified and adapted for rakarrack by Josep Andreu

  Valve.C - Distorsion effect

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

#ifdef SHR3D_SFX_CORE_RAKARRACK

#include <string.h>

#define LN2R 1.442695041f

Valve::Valve()
  : lpfl(2, 22000.0f, 1.0f, 0)
  , lpfr(2, 22000.0f, 1.0f, 0)
  , hpfl(3, 20.0f, 1.0f, 0)
  , hpfr(3, 20.0f, 1.0f, 0)
  , harm(rm, 20.0f, 20000.0f, 1.0f)
{
  //default values
  Pvolume = 50;
  Plrcross = 40;
  Pdrive = 90;
  Plevel = 64;
  Pnegate = 0;
  Plpf = 127;
  Phpf = 0;
  Q_q = 64;
  Ped = 0;
  Pstereo = 0;
  Pprefiltering = 0;
  q = 0.0f;
  dist = 0.0f;
  setlpf(127);
  sethpf(1);
  atk = 1.0f - 40.0f / fSAMPLE_RATE;

  harm.calcula_mag(rm);

  setpreset(0);
  init_coefs();
  cleanup();
}

void Valve::cleanup()
{
  lpfl.cleanup();
  hpfl.cleanup();
  lpfr.cleanup();
  hpfr.cleanup();
  otml = 0.0f;
  itml = 0.0f;
  otmr = 0.0f;
  itmr = 0.0f;
}

void Valve::applyfilters(f32** inOutBlock, const i32 blockSize)
{
  lpfl.filterout(inOutBlock[0], blockSize);
  hpfl.filterout(inOutBlock[0], blockSize);

  if (Pstereo != 0)
  {				//stereo
    lpfr.filterout(inOutBlock[1], blockSize);
    hpfr.filterout(inOutBlock[1], blockSize);
  }
}

f32 Valve::Wshape(f32 x)
{
  if (x < factor)
    return(x);
  if (x > factor)
    return(factor + (x - factor) / powf(1.0f + ((x - factor) / (1.0f - factor)), 2.0f));
  if (x > 1.0f)
    return((factor + 1.0f) * .5f);
  return 0.0f;
}

void Valve::processBlock(const f32* const* inBlock, f32** outBlock, const i32 blockSize)
{
  f32  fx;

  if (Pstereo != 0)
  {				//Stereo
    for (i32 i = 0; i < blockSize; i++)
    {
      outBlock[0][i] = inBlock[0][i] * inputvol;
      outBlock[1][i] = inBlock[1][i] * inputvol;
    }
  }
  else
  {
    for (i32 i = 0; i < blockSize; i++)
    {
      outBlock[0][i] =
        (inBlock[0][i] + inBlock[1][i]) * inputvol;
    }
  }

  harm.harm_out(outBlock, blockSize);

  if (Pprefiltering != 0)
    applyfilters(outBlock, blockSize);

  if (Ped)
  {
    for (i32 i = 0; i < blockSize; i++)
    {
      outBlock[0][i] = Wshape(outBlock[0][i]);
      if (Pstereo != 0) outBlock[1][i] = Wshape(outBlock[1][i]);
    }
  }

  for (i32 i = 0; i < blockSize; i++) //soft limiting to 3.0 (max)
  {
    fx = outBlock[0][i];
    if (fx > 1.0f) fx = 3.0f - 2.0f / sqrtf(fx);
    outBlock[0][i] = fx;
    fx = outBlock[1][i];
    if (fx > 1.0f) fx = 3.0f - 2.0f / sqrtf(fx);
    outBlock[1][i] = fx;
  }

  if (q == 0.0f)
  {
    for (i32 i = 0; i < blockSize; i++)
    {
      if (outBlock[0][i] == q) fx = fdist;
      else fx = outBlock[0][i] / (1.0f - powf(2.0f, -dist * outBlock[0][i] * LN2R));
      otml = atk * otml + fx - itml;
      itml = fx;
      outBlock[0][i] = otml;
    }
  }
  else
  {
    for (i32 i = 0; i < blockSize; i++)
    {
      if (outBlock[0][i] == q) fx = fdist + qcoef;
      else fx = (outBlock[0][i] - q) / (1.0f - powf(2.0f, -dist * (outBlock[0][i] - q) * LN2R)) + qcoef;
      otml = atk * otml + fx - itml;
      itml = fx;
      outBlock[0][i] = otml;

    }
  }

  if (Pstereo != 0)
  {
    if (q == 0.0f)
    {
      for (i32 i = 0; i < blockSize; i++)
      {
        if (outBlock[1][i] == q) fx = fdist;
        else fx = outBlock[1][i] / (1.0f - powf(2.0f, -dist * outBlock[1][i] * LN2R));
        otmr = atk * otmr + fx - itmr;
        itmr = fx;
        outBlock[1][i] = otmr;

      }
    }
    else
    {
      for (i32 i = 0; i < blockSize; i++)
      {
        if (outBlock[1][i] == q) fx = fdist + qcoef;
        else fx = (outBlock[1][i] - q) / (1.0f - powf(2.0f, -dist * (outBlock[1][i] - q) * LN2R)) + qcoef;
        otmr = atk * otmr + fx - itmr;
        itmr = fx;
        outBlock[1][i] = otmr;
      }
    }
  }

  if (Pprefiltering == 0)
    applyfilters(outBlock, blockSize);

  if (Pstereo == 0)
    memcpy(outBlock[1], outBlock[0], blockSize * sizeof(f32));

  const f32 level = dB2rap(60.0f * (f32)Plevel / 127.0f - 40.0f);

  for (i32 i = 0; i < blockSize; i++)
  {
    const f32 lout = outBlock[0][i];
    const f32 rout = outBlock[1][i];

    const f32 l = lout * (1.0f - lrcross) + rout * lrcross;
    const f32 r = rout * (1.0f - lrcross) + lout * lrcross;

    outBlock[0][i] = l * 2.0f * level * panning;
    outBlock[1][i] = r * 2.0f * level * (1.0f - panning);
  }
}

void Valve::init_coefs()
{
  coef = 1.0f / (1.0f - powf(2.0f, dist * q * LN2R));
  qcoef = q * coef;
  fdist = 1.0f / dist;
  inputvol = powf(4.0f, ((f32)Pdrive - 32.0f) / 127.0f);
  if (Pnegate != 0)
    inputvol *= -1.0f;
}

void Valve::setvolume(i32 Pvolume_)
{
  Pvolume = Pvolume_;

  outvolume = (f32)Pvolume_ / 127.0f;
  if (Pvolume_ == 0)
    cleanup();
}

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

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

void Valve::setlpf(i32 value)
{
  Plpf = value;
  f32 fr = (f32)Plpf;

  lpfl.setfreq(fr);
  lpfr.setfreq(fr);
}

void Valve::sethpf(i32 value)
{
  Phpf = value;
  f32 fr = (f32)Phpf;

  hpfl.setfreq(fr);
  hpfr.setfreq(fr);

  //Prefiltering of 51 is approx 630 Hz. 50 - 60 generally good for OD pedal.
}

void Valve::setpresence(i32 value)
{
  f32 freq = 5.0f * (100.0f - (f32)value);
  f32 nvol = (f32)(value * .01f);

  harm.set_freqh(1, freq);
  harm.set_vol(1, nvol);
}

void Valve::setpreset(i32 npreset)
{
  const i32 PRESET_SIZE = 13;
  i32 presets[][PRESET_SIZE] = {
    //Valve 1
    {0, 64, 64, 127, 64, 0, 5841, 61, 1, 0, 69, 1, 84},
    //Valve 2
    {0, 64, 64, 127, 64, 0, 5078, 61, 1, 0, 112, 0, 30},
    //Valve 3
    {0, 64, 35, 80, 64, 1, 3134, 358, 1, 1, 100, 1, 30}
  };

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

  cleanup();
}

void Valve::changepar(i32 npar, i32 value)
{
  switch (npar)
  {
  case 0:
    setvolume(value);
    break;
  case 1:
    setpanning(value);
    break;
  case 2:
    setlrcross(value);
    break;
  case 3:
    Pdrive = value;
    dist = (f32)Pdrive / 127.0f * 40.0f + .5f;
    break;
  case 4:
    Plevel = value;
    break;
  case 5:
    if (value > 1)
      value = 1;
    Pnegate = value;
    break;
  case 6:
    setlpf(value);
    break;
  case 7:
    sethpf(value);
    break;
  case 8:
    if (value > 1)
      value = 1;
    Pstereo = value;
    break;
  case 9:
    Pprefiltering = value;
    break;
  case 10:
    Q_q = value;
    q = (f32)Q_q / 127.0f - 1.0f;
    factor = 1.0f - ((f32)Q_q / 128.0f);
    break;
  case 11:
    Ped = value;
    break;
  case 12:
    Presence = value;
    setpresence(value);
    break;
  default:
    unreachable();
  }
  init_coefs();
}

i32 Valve::getpar(i32 npar)
{
  switch (npar)
  {
  case 0:
    return (Pvolume);
  case 1:
    return (Ppanning);
  case 2:
    return (Plrcross);
  case 3:
    return (Pdrive);
  case 4:
    return (Plevel);
  case 5:
    return (Pnegate);
  case 6:
    return (Plpf);
  case 7:
    return (Phpf);
  case 8:
    return (Pstereo);
  case 9:
    return (Pprefiltering);
  case 10:
    return (Q_q);
  case 11:
    return (Ped);
  case 12:
    return (Presence);
  }
  ASSERT(false);
  return 0;
}

#endif // SHR3D_SFX_CORE_RAKARRACK
