/*
  ZynAddSubFX - a software synthesizer

  Echo.cpp - Echo effect
  Copyright (C) 2002-2005 Nasca Octavian Paul
  Copyright (C) 2009-2010 Mark McCurry
  Author: Nasca Octavian Paul
          Mark McCurry

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.
*/

#include <cmath>
#include <rtosc/ports.h>
#include <rtosc/port-sugar.h>
#include "../Misc/Allocator.h"
#include "Echo.h"

#define MAX_DELAY 2

namespace zyncarla {

#define rObject Echo
#define rBegin [](const char *msg, rtosc::RtData &d) {
#define rEnd }

rtosc::Ports Echo::ports = {
    {"preset::i", rOptions(Echo 1, Echo 2, Echo 3, Simple Echo, Canyon, Panning Echo 1,
            Panning Echo 2, Panning Echo 3, Feedback Echo)
                  rProp(parameter)
                  rDoc("Instrument Presets"), 0,
                  rBegin;
                  rObject *o = (rObject*)d.obj;
                  if(rtosc_narguments(msg))
                      o->setpreset(rtosc_argument(msg, 0).i);
                  else
                      d.reply(d.loc, "i", o->Ppreset);
                  rEnd},
    rEffParVol(rDefault(67), rPresetsAt(6, 81, 81, 62)),
    rEffParPan(rPresetsAt(2, 75, 60, 60, 64, 60, 60, 64)),
    rEffPar(Pdelay,   2, rShort("delay"),
            rPresets(35, 21, 60, 44, 102, 44, 46, 26, 28),
            "Length of Echo"),
    rEffPar(Plrdelay, 3, rShort("lr delay"),
            rPresetsAt(4, 50, 17, 118, 100, 64), rDefault(64),
            "Difference In Left/Right Delay"),
    rEffPar(Plrcross, 4, rShort("cross"),
            rPresetsAt(5, 0, 100, 127, 100), rDefault(30),
            "Left/Right Crossover"),
    rEffPar(Pfb,      5, rShort("feedback"),
            rPresets(59, 59, 59, 0, 82, 82, 68, 67, 90),
            "Echo Feedback"),
    rEffPar(Phidamp,  6, rShort("damp"),
            rPresets(0, 0, 10, 0, 48, 24, 18, 36, 55),
            "Dampen High Frequencies"),
};
#undef rBegin
#undef rEnd
#undef rObject

Echo::Echo(EffectParams pars)
    :Effect(pars),
      Pvolume(50),
      Pdelay(60),
      Plrdelay(100),
      Pfb(40),
      Phidamp(60),
      delayTime(1),
      lrdelay(0),
      avgDelay(0),
      delay(memory.valloc<float>(MAX_DELAY * pars.srate),
            memory.valloc<float>(MAX_DELAY * pars.srate)),
      old(0.0f),
      pos(0),
      delta(1),
      ndelta(1)
{
    initdelays();
    setpreset(Ppreset);
}

Echo::~Echo()
{
    memory.devalloc(delay.l);
    memory.devalloc(delay.r);
}

//Cleanup the effect
void Echo::cleanup(void)
{
    memset(delay.l, 0, MAX_DELAY * samplerate * sizeof(float));
    memset(delay.r, 0, MAX_DELAY * samplerate * sizeof(float));
    old = Stereo<float>(0.0f);
}

inline int max(int a, int b)
{
    return a > b ? a : b;
}

//Initialize the delays
void Echo::initdelays(void)
{
    cleanup();
    //number of seconds to delay left chan
    float dl = avgDelay - lrdelay;

    //number of seconds to delay right chan
    float dr = avgDelay + lrdelay;

    ndelta.l = max(1, (int) (dl * samplerate));
    ndelta.r = max(1, (int) (dr * samplerate));
    delta = ndelta;
}

//Effect output
void Echo::out(const Stereo<float *> &input)
{
    for(int i = 0; i < buffersize; ++i) {
        float ldl = delay.l[pos.l];
        float rdl = delay.r[pos.r];
        ldl = ldl * (1.0f - lrcross) + rdl * lrcross;
        rdl = rdl * (1.0f - lrcross) + ldl * lrcross;

        efxoutl[i] = ldl * 2.0f;
        efxoutr[i] = rdl * 2.0f;

        ldl = input.l[i] * pangainL - ldl * fb;
        rdl = input.r[i] * pangainR - rdl * fb;

        //LowPass Filter
        old.l = delay.l[(pos.l + delta.l) % (MAX_DELAY * samplerate)] =
                    ldl * hidamp + old.l * (1.0f - hidamp);
        old.r = delay.r[(pos.r + delta.r) % (MAX_DELAY * samplerate)] =
                    rdl * hidamp + old.r * (1.0f - hidamp);

        //increment
        ++pos.l; // += delta.l;
        ++pos.r; // += delta.r;

        //ensure that pos is still in bounds
        pos.l %= MAX_DELAY * samplerate;
        pos.r %= MAX_DELAY * samplerate;

        //adjust delay if needed
        delta.l = (15 * delta.l + ndelta.l) / 16;
        delta.r = (15 * delta.r + ndelta.r) / 16;
    }
}


//Parameter control
void Echo::setvolume(unsigned char _Pvolume)
{
    Pvolume = _Pvolume;

    if(insertion == 0) {
        if (Pvolume == 0) {
            outvolume = 0.0f;
        } else {
            outvolume = powf(0.01f, (1.0f - Pvolume / 127.0f)) * 4.0f;
        }
        volume    = 1.0f;
    }
    else
        volume = outvolume = Pvolume / 127.0f;
    if(Pvolume == 0)
        cleanup();
}

void Echo::setdelay(unsigned char _Pdelay)
{
    Pdelay   = _Pdelay;
    avgDelay = (Pdelay / 127.0f * 1.5f); //0 .. 1.5 sec
    initdelays();
}

void Echo::setlrdelay(unsigned char _Plrdelay)
{
    float tmp;
    Plrdelay = _Plrdelay;
    tmp      =
        (powf(2.0f, fabsf(Plrdelay - 64.0f) / 64.0f * 9.0f) - 1.0f) / 1000.0f;
    if(Plrdelay < 64.0f)
        tmp = -tmp;
    lrdelay = tmp;
    initdelays();
}

void Echo::setfb(unsigned char _Pfb)
{
    Pfb = _Pfb;
    fb  = Pfb / 128.0f;
}

void Echo::sethidamp(unsigned char _Phidamp)
{
    Phidamp = _Phidamp;
    hidamp  = 1.0f - Phidamp / 127.0f;
}

void Echo::setpreset(unsigned char npreset)
{
    const int     PRESET_SIZE = 7;
    const int     NUM_PRESETS = 9;
    unsigned char presets[NUM_PRESETS][PRESET_SIZE] = {
        {67, 64, 35,  64,  30,  59, 0 }, //Echo 1
        {67, 64, 21,  64,  30,  59, 0 }, //Echo 2
        {67, 75, 60,  64,  30,  59, 10}, //Echo 3
        {67, 60, 44,  64,  30,  0,  0 }, //Simple Echo
        {67, 60, 102, 50,  30,  82, 48}, //Canyon
        {67, 64, 44,  17,  0,   82, 24}, //Panning Echo 1
        {81, 60, 46,  118, 100, 68, 18}, //Panning Echo 2
        {81, 60, 26,  100, 127, 67, 36}, //Panning Echo 3
        {62, 64, 28,  64,  100, 90, 55}  //Feedback Echo
    };

    if(npreset >= NUM_PRESETS)
        npreset = NUM_PRESETS - 1;
    for(int n = 0; n < PRESET_SIZE; ++n)
        changepar(n, presets[npreset][n]);
    if(insertion)
        setvolume(presets[npreset][0] / 2);  //lower the volume if this is insertion effect
    Ppreset = npreset;
}


void Echo::changepar(int npar, unsigned char value)
{
    switch(npar) {
        case 0:
            setvolume(value);
            break;
        case 1:
            setpanning(value);
            break;
        case 2:
            setdelay(value);
            break;
        case 3:
            setlrdelay(value);
            break;
        case 4:
            setlrcross(value);
            break;
        case 5:
            setfb(value);
            break;
        case 6:
            sethidamp(value);
            break;
    }
}

unsigned char Echo::getpar(int npar) const
{
    switch(npar) {
        case 0:  return Pvolume;
        case 1:  return Ppanning;
        case 2:  return Pdelay;
        case 3:  return Plrdelay;
        case 4:  return Plrcross;
        case 5:  return Pfb;
        case 6:  return Phidamp;
        default: return 0; // in case of bogus parameter number
    }
}

}
