// changed loading impulse response .wav files via libsndfile to use hard coded audio data instead.

/*

  Convolotron.C - Convolotron effect
  Author: Ryam Billing & Jospe 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 "Convolotron.h"

#ifdef SHR3D_SFX_CORE_RAKARRACK

#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE

#ifdef SHR3D_SFX_CORE_RAKARRACK_CONVOLOTRON

#include "../../../data.h"

#include <string.h>

Convolotron::Convolotron(i32 DS, i32 uq, i32 dq)
  : M_Resample(0)
  , U_Resample(dq) //Downsample, uses sinc interpolation for bandlimiting to avoid aliasing
  , D_Resample(uq)
{
  //default values
  adjust(DS, blockSize());

  maxx_size = (i32)(nfSAMPLE_RATE * convlength);  //just to get the max memory allocated
  buf = (f32*)malloc(sizeof(f32) * maxx_size);
  rbuf = (f32*)malloc(sizeof(f32) * maxx_size);
  lxn = (f32*)malloc(sizeof(f32) * maxx_size);
  maxx_size--;
  offset = 0;

  setpreset(0);
}

void Convolotron::adjust(i32 DS, const i32 blockSize)
{
  DS_state = DS;

  switch (DS)
  {
  case 0:
    nPERIOD = blockSize;
    nSAMPLE_RATE = SAMPLE_RATE;
    nfSAMPLE_RATE = fSAMPLE_RATE;
    break;
  case 1:
    nPERIOD = lrintf(f32(blockSize) * 96000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 96000;
    nfSAMPLE_RATE = 96000.0f;
    break;
  case 2:
    nPERIOD = lrintf(f32(blockSize) * 48000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 48000;
    nfSAMPLE_RATE = 48000.0f;
    break;
  case 3:
    nPERIOD = lrintf(f32(blockSize) * 44100.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 44100;
    nfSAMPLE_RATE = 44100.0f;
    break;
  case 4:
    nPERIOD = lrintf(f32(blockSize) * 32000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 32000;
    nfSAMPLE_RATE = 32000.0f;
    break;
  case 5:
    nPERIOD = lrintf(f32(blockSize) * 22050.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 22050;
    nfSAMPLE_RATE = 22050.0f;
    break;
  case 6:
    nPERIOD = lrintf(f32(blockSize) * 16000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 16000;
    nfSAMPLE_RATE = 16000.0f;
    break;
  case 7:
    nPERIOD = lrintf(f32(blockSize) * 12000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 12000;
    nfSAMPLE_RATE = 12000.0f;
    break;
  case 8:
    nPERIOD = lrintf(f32(blockSize) * 8000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 8000;
    nfSAMPLE_RATE = 8000.0f;
    break;
  case 9:
    nPERIOD = lrintf(f32(blockSize) * 4000.0f / fSAMPLE_RATE);
    nSAMPLE_RATE = 4000;
    nfSAMPLE_RATE = 4000.0f;
    break;
  }
  u_up = (f64)nPERIOD / (f64)blockSize;
  u_down = (f64)blockSize / (f64)nPERIOD;
}

void Convolotron::processBlock(f32** inBlock, f32** outBlock, const i32 blockSize)
{
  if (DS_state != 0)
  {
    memcpy(templ, inBlock[0], sizeof(f32) * blockSize);
    memcpy(tempr, inBlock[1], sizeof(f32) * blockSize);
    f32* temp[2] = { templ, tempr };
    U_Resample.processBlock(temp, inBlock, blockSize, u_up);
  }

  for (i32 i = 0; i < nPERIOD; i++)
  {
    f32 l = inBlock[0][i] + inBlock[1][i] + feedback;
    oldl = l * hidamp + oldl * (alpha_hidamp);  //apply damping while I'm in the loop
    lxn[offset] = oldl;

    //Convolve left channel
    f32 lyn = 0.0f;
    i32 xindex = offset;

    for (i32 j = 0; j < length; j++)
    {
      if (--xindex < 0) xindex = maxx_size;		//length of lxn is maxx_size.  
      lyn += buf[j] * lxn[xindex];		//this is all there is to convolution
    }

    feedback = fb * lyn;
    templ[i] = lyn * levpanl;
    tempr[i] = lyn * levpanr;

    if (++offset > maxx_size) offset = 0;
  }

  if (DS_state != 0)
  {
    f32* temp[2] = { templ, tempr };
    D_Resample.processBlock(temp, outBlock, nPERIOD, u_down);
  }
  else
  {
    memcpy(outBlock[0], templ, sizeof(f32) * blockSize);
    memcpy(outBlock[1], tempr, sizeof(f32) * blockSize);
  }
}

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

void Convolotron::setpanning(i32 Ppanning_)
{
  Ppanning = Ppanning_;
  lpanning = ((f32)Ppanning_ + 0.5f) / 127.0f;
  rpanning = 1.0f - lpanning;
  levpanl = lpanning * level * 2.0f;
  levpanr = rpanning * level * 2.0f;
}

static i32 getConvolotronData(i32 index, const f32*& data)
{
  switch (index)
  {
  case 0:
    data = Data::Convolotron::impulseResponse0;
    return ARRAY_SIZE(Data::Convolotron::impulseResponse0);
  case 1:
    data = Data::Convolotron::impulseResponse1;
    return ARRAY_SIZE(Data::Convolotron::impulseResponse1);
  case 2:
    data = Data::Convolotron::impulseResponse2;
    return ARRAY_SIZE(Data::Convolotron::impulseResponse2);
  case 3:
    data = Data::Convolotron::impulseResponse3;
    return ARRAY_SIZE(Data::Convolotron::impulseResponse3);
  case 4:
    data = Data::Convolotron::impulseResponse4;
    return ARRAY_SIZE(Data::Convolotron::impulseResponse4);
  case 5:
    data = Data::Convolotron::impulseResponse5;
    return ARRAY_SIZE(Data::Convolotron::impulseResponse5);
  case 6:
    data = Data::Convolotron::impulseResponse6;
    return ARRAY_SIZE(Data::Convolotron::impulseResponse6);
  case 7:
    data = Data::Convolotron::impulseResponse7;
    return ARRAY_SIZE(Data::Convolotron::impulseResponse7);
  case 8:
    data = Data::Convolotron::impulseResponse8;
    return ARRAY_SIZE(Data::Convolotron::impulseResponse8);
  }
  ASSERT(false);
  return 0;
}

void Convolotron::setProfile(const i32 index)
{
  ASSERT(index >= 0);
  ASSERT(index <= 8);

  offset = 0;
  maxx_read = maxx_size / 2;
  memset(buf, 0, sizeof(f32) * maxx_size);
  memset(rbuf, 0, sizeof(f32) * maxx_size);

  const f32* impulseResponse = nullptr;
  real_len = getConvolotronData(index, impulseResponse);

  if (44100 != nSAMPLE_RATE)
  {
    const f64 sr_ratio = (f64)nSAMPLE_RATE / 44100.0;
    M_Resample.mono_out(impulseResponse, rbuf, real_len, sr_ratio, lrint((f64)real_len * sr_ratio));
    real_len = lrintf((f32)real_len * (f32)sr_ratio);
  }
  else
  {
    memcpy(rbuf, impulseResponse, real_len * sizeof(f32));
  }

  process_rbuf();
}

void Convolotron::process_rbuf()
{
  i32 ii, j, N, N2;
  f32 tailfader, alpha, a0, a1, a2, Nm1p, Nm1pp, IRpowa, IRpowb, ngain, maxamp;
  memset(buf, 0, sizeof(f32) * real_len);

  if (length > real_len) length = real_len;
  /*Blackman Window function
  wn = a0 - a1*cos(2*pi*n/(N-1)) + a2 * cos(4*PI*n/(N-1)
  a0 = (1 - alpha)/2; a1 = 0.5; a2 = alpha/2
  */
  alpha = 0.16f;
  a0 = 0.5f * (1.0f - alpha);
  a1 = 0.5f;
  a2 = 0.5f * alpha;
  N = length;
  N2 = length / 2;
  Nm1p = D_PI / ((f32)(N - 1));
  Nm1pp = 4.0f * PI_ / ((f32)(N - 1));

  for (ii = 0; ii < length; ii++)
  {
    if (ii < N2)
    {
      tailfader = 1.0f;
    }
    else
    {
      tailfader = a0 - a1 * cosf(ii * Nm1p) + a2 * cosf(ii * Nm1pp);   //Calculate Blackman Window for right half of IR
    }

    buf[ii] = rbuf[ii] * tailfader;   //Apply window function
  }

  memcpy(buf, rbuf, real_len * sizeof(f32));

  IRpowa = IRpowb = maxamp = 0.0f;
  //compute IR signal power
  for (j = 0; j < maxx_read; j++)
  {
    IRpowa += fabsf(rbuf[j]);
    if (maxamp < fabsf(buf[j])) maxamp = fabsf(buf[j]);   //find maximum level to normalize	

    if (j < length)
    {
      IRpowb += fabsf(buf[j]);
    }
  }

  //if(maxamp < 0.3f) maxamp = 0.3f;
  ngain = IRpowa / IRpowb;
  if (ngain > maxx_read)
    ngain = f32(maxx_read);

  for (j = 0; j < length; j++)
    buf[j] *= ngain;
}

void Convolotron::sethidamp(i32 Phidamp_)
{
  Phidamp = Phidamp_;
  hidamp = 1.0f - (f32)Phidamp_ / 127.1f;
  alpha_hidamp = 1.0f - hidamp;
}

void Convolotron::setpreset(i32 npreset)
{
  ASSERT(npreset >= 0);
  ASSERT(npreset <= 3);

  const i32 PRESET_SIZE = 11;
  i32 presets[][PRESET_SIZE] = {
    //Convolotron 1
    {0, 64, 1, 100, 0, 64, 30, 64, 0, 0, 0},
    //Convolotron 2
    {0, 64, 1, 100, 0, 64, 30, 64, 1, 0, 0},
    //Convolotron 3
    {0, 75, 1, 100, 0, 64, 30, 64, 2, 0, 0},
    //Convolotron 4
    {0, 60, 1, 100, 0, 64, 30, 64, 3, 0, 0}
  };

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

void Convolotron::changepar(i32 npar, i32 value)
{
  switch (npar)
  {
  case 0:
    setvolume(value);
    return;
  case 1:
    setpanning(value);
    return;
  case 2:
    return;
  case 3:
    Plength = value;
    convlength = ((f32)Plength) / 1000.0f;                   //time in seconds
    length = (i32)(nfSAMPLE_RATE * convlength);        //time in samples
    if (real_len > 0)
      process_rbuf();
    return;
  case 4:
    return;
  case 5:
    return;
  case 6:
    sethidamp(value);
    return;
  case 7:
    Plevel = value;
    level = dB2rap(60.0f * (f32)Plevel / 127.0f - 40.0f);
    levpanl = lpanning * level * 2.0f;
    levpanr = rpanning * level * 2.0f;
    return;
  case 8:
    setProfile(value);
    return;
  case 9:
    return;
  case 10:
    Pfb = value;
    if (Pfb < 0)
      fb = (f32).1f * value / 250.0f * .15f;
    else
      fb = (f32).1f * value / 500.0f * .15f;
    return;
  }
  ASSERT(false);
}

i32 Convolotron::getpar(i32 npar)
{
  switch (npar)
  {
  case 0:
    return Pvolume;
  case 1:
    return Ppanning;
  case 2:
    return 0;
  case 3:
    return Plength;
  case 4:
    return 0;
  case 5:
    return 0;
  case 6:
    return Phidamp;
  case 7:
    return Plevel;
  case 8:
    return 0;
  case 9:
    return 0;
  case 10:
    return Pfb;
  }
  ASSERT(false);
  return 0;
}

#endif // SHR3D_SFX_CORE_RAKARRACK_CONVOLOTRON

#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE

#endif // SHR3D_SFX_CORE_RAKARRACK
