/*
  MBDist.C - Distorsion effect

  ZynAddSubFX - a software synthesizer
  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 "DistBand.h"

#ifdef SHR3D_SFX_CORE_RAKARRACK

#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE

#include <string.h>

/*
 * Waveshape (this is called by OscilGen::waveshape and Distorsion::process)
 */

DistBand::DistBand()
  : lpf1l(2, 500.0f, .7071f, 0)
  , lpf1r(2, 500.0f, .7071f, 0)
  , hpf1l(3, 500.0f, .7071f, 0)
  , hpf1r(3, 500.0f, .7071f, 0)
  , lpf2l(2, 2500.0f, .7071f, 0)
  , lpf2r(2, 2500.0f, .7071f, 0)
  , hpf2l(3, 2500.0f, .7071f, 0)
  , hpf2r(3, 2500.0f, .7071f, 0)
  , DCl(3, 30, 1, 0)
  , DCr(3, 30, 1, 0)
{

  DCl.setfreq(30.0f);
  DCr.setfreq(30.0f);

  setpreset(0);
  cleanup();
}

void DistBand::cleanup()
{
  lpf1l.cleanup();
  hpf1l.cleanup();
  lpf1r.cleanup();
  hpf1r.cleanup();
  lpf2l.cleanup();
  hpf2l.cleanup();
  lpf2r.cleanup();
  hpf2r.cleanup();
  DCl.cleanup();
  DCr.cleanup();
}

void DistBand::processBlock(const f32* const* inBlock, f32** outBlock, const i32 blockSize)
{
  f32 inputvol = powf(5.0f, ((f32)Pdrive - 32.0f) / 127.0f);
  if (Pnegate != 0)
    inputvol *= -1.0f;

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

  memcpy(lowl, outBlock[0], sizeof(f32) * blockSize);
  memcpy(midl, outBlock[0], sizeof(f32) * blockSize);
  memcpy(highl, outBlock[0], sizeof(f32) * blockSize);

  lpf1l.filterout(lowl, blockSize);
  hpf1l.filterout(midl, blockSize);
  lpf2l.filterout(midl, blockSize);
  hpf2l.filterout(highl, blockSize);

  if (volL > 0)
    mbwshape1l.waveshapesmps(blockSize, lowl, PtypeL, PdriveL, 1);
  if (volM > 0)
    mbwshape2l.waveshapesmps(blockSize, midl, PtypeM, PdriveM, 1);
  if (volH > 0)
    mbwshape3l.waveshapesmps(blockSize, highl, PtypeH, PdriveH, 1);

  if (Pstereo)
  {
    memcpy(lowr, outBlock[1], sizeof(f32) * blockSize);
    memcpy(midr, outBlock[1], sizeof(f32) * blockSize);
    memcpy(highr, outBlock[1], sizeof(f32) * blockSize);

    lpf1r.filterout(lowr, blockSize);
    hpf1r.filterout(midr, blockSize);
    lpf2r.filterout(midr, blockSize);
    hpf2r.filterout(highr, blockSize);

    if (volL > 0)
      mbwshape1r.waveshapesmps(blockSize, lowr, PtypeL, PdriveL, 1);
    if (volM > 0)
      mbwshape2r.waveshapesmps(blockSize, midr, PtypeM, PdriveM, 1);
    if (volH > 0)
      mbwshape3r.waveshapesmps(blockSize, highr, PtypeH, PdriveH, 1);
  }

  for (i32 i = 0; i < blockSize; i++)
  {
    outBlock[0][i] = lowl[i] * volL + midl[i] * volM + highl[i] * volH;
    if (Pstereo) outBlock[1][i] = lowr[i] * volL + midr[i] * volM + highr[i] * volH;
  }

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

  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);
  }

  DCr.filterout(outBlock[1], blockSize);
  DCl.filterout(outBlock[0], blockSize);
}

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

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

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

void DistBand::setCross1(i32 value)
{
  Cross1 = value;
  lpf1l.setfreq((f32)value);
  lpf1r.setfreq((f32)value);
  hpf1l.setfreq((f32)value);
  hpf1r.setfreq((f32)value);
}

void DistBand::setCross2(i32 value)
{
  Cross2 = value;
  hpf2l.setfreq((f32)value);
  hpf2r.setfreq((f32)value);
  lpf2l.setfreq((f32)value);
  lpf2r.setfreq((f32)value);
}

void DistBand::setpreset(i32 npreset)
{
  const i32 PRESET_SIZE = 15;
  i32 presets[][PRESET_SIZE] = {
    //Saturation
    {0, 64, 0, 41, 64, 26, 19, 26, 41, 20, 35, 0, 400, 1200, 0},
    //Dist 1
    {0, 64, 64, 20, 64, 0, 14, 13, 38, 49, 40, 0, 288, 1315, 0},
    //Soft
    {0, 64, 0, 32, 64, 6, 13, 6, 50, 70, 50, 0, 400, 1800, 0},
    //Modulated
    {0, 64, 0, 36, 64, 18, 17, 18, 40, 70, 30, 0, 500, 2200, 0},
    //Crunch
    {0, 64, 0, 24, 64, 19, 14, 19, 30, 80, 30, 0, 800, 1800, 0},
    //Dist 2
    {0, 64, 0, 64, 64, 22, 27, 22, 25, 50, 25, 0, 440, 1500, 0},
    //Dist 3
    {0, 64, 0, 64, 64, 27, 22, 27, 50, 69, 50, 0, 800, 1200, 0},
    //Dist 4
    {0, 64, 0, 30, 64, 19, 25, 26, 20, 51, 83, 0, 329, 800, 0}
  };

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

  cleanup();
}

void DistBand::changepar(i32 npar, i32 value)
{
  switch (npar)
  {
  case 0:
    setvolume(value);
    return;
  case 1:
    setpanning(value);
    return;
  case 2:
    setlrcross(value);
    return;
  case 3:
    Pdrive = value;
    PdriveL = (i32)((f32)Pdrive * volL);
    PdriveM = (i32)((f32)Pdrive * volM);
    PdriveH = (i32)((f32)Pdrive * volH);
    return;
  case 4:
    Plevel = value;
    return;
  case 5:
    PtypeL = value;
    return;
  case 6:
    PtypeM = value;
    return;
  case 7:
    PtypeH = value;
    return;
  case 8:
    PvolL = value;
    volL = (f32)value / 100.0f;
    PdriveL = (i32)((f32)Pdrive * volL);
    return;
  case 9:
    PvolM = value;
    volM = (f32)value / 100.0f;
    PdriveM = (i32)((f32)Pdrive * volM);
    return;
  case 10:
    PvolH = value;
    volH = (f32)value / 100.0f;
    PdriveH = (i32)((f32)Pdrive * volH);
    return;
  case 11:
    Pnegate = value;
    return;
  case 12:
    setCross1(value);
    return;
  case 13:
    setCross2(value);
    return;
  case 14:
    Pstereo = value;
    return;
  }
  ASSERT(false);
}

i32 DistBand::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 PtypeL;
  case 6:
    return PtypeM;
  case 7:
    return PtypeH;
  case 8:
    return PvolL;
  case 9:
    return PvolM;
  case 10:
    return PvolH;
  case 11:
    return Pnegate;
  case 12:
    return Cross1;
  case 13:
    return Cross2;
  case 14:
    return Pstereo;
  }
  ASSERT(false);
  return 0;
}

#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE

#endif // SHR3D_SFX_CORE_RAKARRACK
