/*
  Rakarrack Audio FX

  Dual_Flange.C - Super Flanger
  Copyright (C) 2010 Ryan Billing
  Authors:
  Ryan Billing (a.k.a Transmogrifox)  --  Signal Processing
  Copyright (C) 2010 Ryan Billing

  Nasca Octavian Paul -- Remnants of ZynAddSubFX Echo.h structure and utility routines common to ZynSubFX source
  Copyright (C) 2002-2005 Nasca Octavian Paul

  Higher intensity flanging accomplished by picking two points out of the delay line to create a wider notch filter.

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

#ifdef SHR3D_SFX_CORE_RAKARRACK

DualFlange::DualFlange()
{
  period_const = 1.0f / fPERIOD;

  zcenter = (i32)floorf(0.5f * (fdepth + fwidth));
  base = 7.0f;		//sets curve of modulation to frequency relationship
  ibase = 1.0f / base;
  //default values
  rsA = 0.0f;
  rsB = 0.0f;
  lsA = 0.0f;
  lsB = 0.0f;
  setpreset(0);
  cleanup();
}

void DualFlange::cleanup()
{
  for (i32 i = 0; i < maxx_delay; i++)
  {
    ldelay[i] = 0.0;
    rdelay[i] = 0.0;
    zldelay[i] = 0.0;
    zrdelay[i] = 0.0;
  }

  //loop variables
  l = 0.0f;
  r = 0.0f;
  ldl = 0.0f;
  rdl = 0.0f;
  rflange0 = 0.0f;
  lflange0 = 0.0f;
  rflange1 = 0.0f;
  lflange1 = 0.0f;
  kl = 0;
  kr = 0;
  zl = 0;
  zr = 0;
}

void DualFlange::processBlock(const f32* const* inBlock, f32** outBlock, const i32 blockSize)
{
  //deal with LFO's
  i32 tmp0, tmp1;

  f32 lfol, lfor, lmod, rmod, lmodfreq, rmodfreq, rx0, rx1, lx0, lx1;
  f32 ldif0, ldif1, rdif0, rdif1;  //Difference between fractional delay and floor(fractional delay)
  f32 drA, drB, dlA, dlB;	//LFO inside the loop.

  lfo.effectlfoout(lfol, lfor);
  lmod = lfol;
  rmod = lfor;
  //  lmod = (powf (2.0f, lmod*LOG_FMAX) - 1.0f) * LFO_CONSTANT;  //2^x type sweep for musical interpretation of moving delay line.
  //  rmod = (powf (2.0f, rmod*LOG_FMAX) - 1.0f) * LFO_CONSTANT;

  lmodfreq = fdepth + fwidth * (powf(base, lmod) - 1.0f) * ibase;	//sets frequency of lowest notch. // 20 <= fdepth <= 4000 // 20 <= width <= 16000 //
  rmodfreq = fdepth + fwidth * (powf(base, rmod) - 1.0f) * ibase;

  if (lmodfreq > 10000.0f)
    lmodfreq = 10000.0f;
  else if (lmodfreq < 10.0f)
    lmodfreq = 10.0f;
  if (rmodfreq > 10000.0)
    rmodfreq = 10000.0f;
  else if (rmodfreq < 10.0f)
    rmodfreq = 10.0f;

  rflange0 = fSAMPLE_RATE * 0.5f / rmodfreq;		//Turn the notch frequency into a number for delay
  rflange1 = rflange0 * foffset;				//Set relationship of second delay line
  lflange0 = fSAMPLE_RATE * 0.5f / lmodfreq;
  lflange1 = lflange0 * foffset;

  //now is a delay expressed in number of samples.  Number here
  //will be fractional, but will use linear interpolation inside the loop to make a decent guess at 
  //the numbers between samples.

  rx0 = (rflange0 - oldrflange0) * period_const;  //amount to add each time around the loop.  Less processing of linear LFO interp inside the loop.
  rx1 = (rflange1 - oldrflange1) * period_const;
  lx0 = (lflange0 - oldlflange0) * period_const;
  lx1 = (lflange1 - oldlflange1) * period_const;
  // Now there is a fractional amount to add 

  drA = oldrflange0;
  drB = oldrflange1;
  dlA = oldlflange0;
  dlB = oldlflange1;
  // dr, dl variables are the LFO inside the loop.

  oldrflange0 = rflange0;
  oldrflange1 = rflange1;
  oldlflange0 = lflange0;
  oldlflange1 = lflange1;
  //lfo ready...

  for (i32 i = 0; i < blockSize; i++)
  {
    //Delay line utility
    ldl = ldelay[kl];
    rdl = rdelay[kr];
    l = ldl * flrcross + rdl * frlcross;
    r = rdl * flrcross + ldl * frlcross;
    ldl = l;
    rdl = r;
    ldl = inBlock[0][i] * lpan - ldl * ffb;
    rdl = inBlock[1][i] * rpan - rdl * ffb;


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

    if (Pzero)
    {
      //Offset zero reference delay
      zdl = zldelay[zl];
      zdr = zrdelay[zr];
      zldelay[zl] = inBlock[0][i];
      zrdelay[zr] = inBlock[1][i];
      if (--zl < 0)   //Cycle delay buffer in reverse so delay time can be indexed directly with addition 
        zl = zcenter;
      if (--zr < 0)
        zr = zcenter;
    }

    //End delay line management, start flanging:

    //Right Channel, delay A
    rdif0 = drA - floor(drA);
    tmp0 = (kr + (i32)floor(drA)) % maxx_delay;
    tmp1 = tmp0 + 1;
    if (tmp1 >= maxx_delay) tmp1 = 0;
    //rsA = rdelay[tmp0] + rdif0 * (rdelay[tmp1] - rdelay[tmp0] );	//here is the first right channel delay
    rsA = rdelay[tmp1] + rdif0 * (rdelay[tmp0] - rsA);	//All-pass interpolator

    //Right Channel, delay B	
    rdif1 = drB - floor(drB);
    tmp0 = (kr + (i32)floor(drB)) % maxx_delay;
    tmp1 = tmp0 + 1;
    if (tmp1 >= maxx_delay) tmp1 = 0;
    //rsB = rdelay[tmp0] + rdif1 * (rdelay[tmp1] - rdelay[tmp0]);	//here is the second right channel delay	
    rsB = rdelay[tmp1] + rdif1 * (rdelay[tmp0] - rsB);

    //Left Channel, delay A
    ldif0 = dlA - floor(dlA);
    tmp0 = (kl + (i32)floor(dlA)) % maxx_delay;
    tmp1 = tmp0 + 1;
    if (tmp1 >= maxx_delay) tmp1 = 0;
    //lsA = ldelay[tmp0] + ldif0 * (ldelay[tmp1] - ldelay[tmp0]);	//here is the first left channel delay
    lsA = ldelay[tmp1] + ldif0 * (ldelay[tmp0] - lsA);

    //Left Channel, delay B	
    ldif1 = dlB - floor(dlB);
    tmp0 = (kl + (i32)floor(dlB)) % maxx_delay;
    tmp1 = tmp0 + 1;
    if (tmp1 >= maxx_delay) tmp1 = 0;
    //lsB = ldelay[tmp0] + ldif1 * (ldelay[tmp1] - ldelay[tmp0]);	//here is the second leftt channel delay
    lsB = ldelay[tmp1] + ldif1 * (ldelay[tmp0] - lsB);
    //End flanging, next process outputs

    if (Pzero)
    {
      outBlock[0][i] = dry * inBlock[0][i] + fsubtract * wet * (fsubtract * (lsA + lsB) + zdl);    // Make final FX out mix
      outBlock[1][i] = dry * inBlock[1][i] + fsubtract * wet * (fsubtract * (rsA + rsB) + zdr);
    }
    else
    {
      outBlock[0][i] = dry * inBlock[0][i] + wet * fsubtract * (lsA + lsB);    // Make final FX out mix
      outBlock[1][i] = dry * inBlock[1][i] + wet * fsubtract * (rsA + rsB);
    }

    if (--kl < 0)   //Cycle delay buffer in reverse so delay time can be indexed directly with addition 
      kl = maxx_delay;
    if (--kr < 0)
      kr = maxx_delay;

    // Increment LFO
    drA += rx0;
    drB += rx1;
    dlA += lx0;
    dlB += lx1;
  }
}

void DualFlange::changepar(i32 npar, i32 value)
{
  switch (npar)
  {
  case 0:
    Pwetdry = value;
    dry = (f32)(Pwetdry + 64) / 128.0f;
    wet = 1.0f - dry;
    return;
  case 1:
    Ppanning = value;
    if (value < 0)
    {
      rpan = 1.0f + (f32)Ppanning / 64.0f;
      lpan = 1.0f;
    }
    else
    {
      lpan = 1.0f - (f32)Ppanning / 64.0f;
      rpan = 1.0f;
    }
    return;
  case 2:
    Plrcross = value;
    flrcross = (f32)Plrcross / 127.0f;
    frlcross = 1.0f - flrcross;	//keep this out of the DSP loop
    return;
  case 3:
    Pdepth = value;
    fdepth = (f32)Pdepth;
    zcenter = (i32)floor(0.5f * (fdepth + fwidth));
    return;
  case 4:
    Pwidth = value;
    fwidth = (f32)Pwidth;
    zcenter = (i32)floor(0.5f * (fdepth + fwidth));
    return;
  case 5:
    Poffset = value;
    foffset = 0.5f + (f32)Poffset / 255.0f;
    return;
  case 6:
    Pfb = value;
    ffb = (f32)Pfb / 64.5f;
    return;
  case 7:
    Phidamp = value;
    fhidamp = expf(-D_PI * (f32)Phidamp / fSAMPLE_RATE);
    return;
  case 8:
    Psubtract = value;
    fsubtract = 0.5f;
    if (Psubtract) fsubtract = -0.5f;  //In loop a mult by 0.5f is necessary, so this kills 2 birds with 1...
    return;
  case 9:
    Pzero = value;
    if (Pzero) fzero = 1.0f;
    return;
  case 10:
    lfo.Pfreq = value;
    lfo.updateparams();
    return;
  case 11:
    lfo.Pstereo = value;
    lfo.updateparams();
    return;
  case 12:
    lfo.PLFOtype = value;
    lfo.updateparams();
    return;
  case 13:
    lfo.Prandomness = value;
    lfo.updateparams();
    return;
  }
  ASSERT(false);
}

i32 DualFlange::getpar(i32 npar)
{
  switch (npar)
  {
  case 0:
    return Pwetdry;
  case 1:
    return Ppanning;
  case 2:
    return Plrcross;
  case 3:
    return Pdepth;
  case 4:
    return Pwidth;
  case 5:
    return Poffset;
  case 6:
    return Pfb;
  case 7:
    return Phidamp;
  case 8:
    return Psubtract;
  case 9:
    return Pzero;
  case 10:
    return lfo.Pfreq;
  case 11:
    return lfo.Pstereo;
  case 12:
    return lfo.PLFOtype;
  case 13:
    return lfo.Prandomness;
  }
  ASSERT(false);
  return 0;
}

void DualFlange::setpreset(i32 npreset)
{
  const i32 PRESET_SIZE = 14;
  i32 presets[][PRESET_SIZE] = {
    //Preset 1
    {-32, 0, 0, 110, 800, 10, -27, 16000, 1, 0, 24, 64, 1, 10},
    //Flange-Wha
    {0, 0, 64, 500, 3000, 50, -40, 8000, 1, 0, 196, 96, 0, 0},
    //FbFlange
    {0, 0, 64, 68, 75, 50, -50, 8000, 0, 1, 23, 96, 5, 0},
    //SoftFlange
    {-32, 0, 64, 60, 10, 100, 20, 16000, 0, 0, 16, 90, 4, 0},
    //Flanger
    {-32, 0, 64, 170, 1200, 50, 0, 16000, 1, 0, 68, 127, 0, 0},
    //Chorus 1
    {-15, 0, 0, 42, 12, 50, -10, 1500, 0, 0, 120, 0, 0, 20},
    //Chorus 2
    {-40, 0, 0, 35, 9, 67, 12, 4700, 1, 1, 160, 75, 0, 60},
    //Preset 8
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    //Preset 9
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  };

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

#endif // SHR3D_SFX_CORE_RAKARRACK
