/*
  ZynAddSubFX - a software synthesizer

  Distorsion.cpp - Distorsion effect
  Copyright (C) 2002-2005 Nasca Octavian Paul
  Author: Nasca Octavian Paul

  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 "Distorsion.h"
#include "../DSP/AnalogFilter.h"
#include "../Misc/WaveShapeSmps.h"
#include "../Misc/Allocator.h"
#include <cmath>
#include <rtosc/ports.h>
#include <rtosc/port-sugar.h>

namespace zyn {

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

rtosc::Ports Distorsion::ports = {
    {"preset::i", rProp(parameter)
                  rOptions(Overdrive 1, Overdrive 2, A. Exciter 1, A. Exciter 2, Guitar Amp,
                    Quantisize)
                  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(127), rPresetsAt(2, 64, 64)),
    rEffParPan(),
    rEffPar(Plrcross, 2, rShort("l/r"), rDefault(35), "Left/Right Crossover"),
    rEffPar(Pdrive,   3, rShort("drive"),
            rLinear(0, 127), rPresets(56, 29, 75, 85, 63, 88),
            "Input amplification"),
    rEffPar(Plevel,   4, rShort("output"), rPresets(70, 75, 80, 62, 75, 75),
            "Output amplification"),
    rEffPar(Ptype,    5, rShort("type"),
            rOptions(Arctangent, Asymmetric, Pow, Sine, Quantisize,
                     Zigzag, Limiter, Upper Limiter, Lower Limiter,
                     Inverse Limiter, Clip, Asym2, Pow2, sigmoid),
            rPresets(Arctangent, Asymmetric, Zigzag,
                     Asymmetric, Pow, Quantisize),
            "Distortion Shape"),
    rEffParTF(Pnegate, 6, rShort("neg"), rDefault(false), "Negate Signal"),
    rEffPar(Plpf, 7, rShort("lpf"),
            rPreset(0, 96), rPreset(4, 55), rDefault(127), "Low Pass Cutoff"),
    rEffPar(Phpf, 8, rShort("hpf"),
            rPreset(2, 105), rPreset(3, 118), rDefault(0), "High Pass Cutoff"),
    rEffParTF(Pstereo, 9, rShort("stereo"),
              rPresets(false, false, true, true, false, true), "Stereo"),
    rEffParTF(Pprefiltering, 10, rShort("p.filt"), rDefault(false),
              "Filtering before/after non-linearity"),
    {"waveform:", 0, 0, [](const char *, rtosc::RtData &d)
        {
            Distorsion  &dd = *(Distorsion*)d.obj;
            float        buffer[128];
            rtosc_arg_t  args[128];
            char         arg_str[128+1] = {0};

            for(int i=0; i<128; ++i)
                buffer[i] = 2*(i/128.0)-1;

            waveShapeSmps(sizeof(buffer)/sizeof(buffer[0]), buffer,
                    dd.Ptype + 1, dd.Pdrive);

            for(int i=0; i<128; ++i) {
                arg_str[i] = 'f';
                args[i].f  = buffer[i];
            }

            d.replyArray(d.loc, arg_str, args);
        }},
};
#undef rBegin
#undef rEnd
#undef rObject

Distorsion::Distorsion(EffectParams pars)
    :Effect(pars),
      Pvolume(50),
      Pdrive(90),
      Plevel(64),
      Ptype(0),
      Pnegate(0),
      Plpf(127),
      Phpf(0),
      Pstereo(0),
      Pprefiltering(0)
{
    lpfl = memory.alloc<AnalogFilter>(2, 22000, 1, 0, pars.srate, pars.bufsize);
    lpfr = memory.alloc<AnalogFilter>(2, 22000, 1, 0, pars.srate, pars.bufsize);
    hpfl = memory.alloc<AnalogFilter>(3, 20, 1, 0, pars.srate, pars.bufsize);
    hpfr = memory.alloc<AnalogFilter>(3, 20, 1, 0, pars.srate, pars.bufsize);
    setpreset(Ppreset);
    cleanup();
}

Distorsion::~Distorsion()
{
    memory.dealloc(lpfl);
    memory.dealloc(lpfr);
    memory.dealloc(hpfl);
    memory.dealloc(hpfr);
}

//Cleanup the effect
void Distorsion::cleanup(void)
{
    lpfl->cleanup();
    hpfl->cleanup();
    lpfr->cleanup();
    hpfr->cleanup();
}


//Apply the filters
void Distorsion::applyfilters(float *efxoutl, float *efxoutr)
{
    lpfl->filterout(efxoutl);
    hpfl->filterout(efxoutl);
    if(Pstereo != 0) { //stereo
        lpfr->filterout(efxoutr);
        hpfr->filterout(efxoutr);
    }
}


//Effect output
void Distorsion::out(const Stereo<float *> &smp)
{
    float inputvol = powf(5.0f, (Pdrive - 32.0f) / 127.0f);
    if(Pnegate)
        inputvol *= -1.0f;

    if(Pstereo) //Stereo
        for(int i = 0; i < buffersize; ++i) {
            efxoutl[i] = smp.l[i] * inputvol * pangainL;
            efxoutr[i] = smp.r[i] * inputvol * pangainR;
        }
    else //Mono
        for(int i = 0; i < buffersize; ++i)
            efxoutl[i] = (smp.l[i] * pangainL + smp.r[i] * pangainR) * inputvol;

    if(Pprefiltering)
        applyfilters(efxoutl, efxoutr);

    waveShapeSmps(buffersize, efxoutl, Ptype + 1, Pdrive);
    if(Pstereo)
        waveShapeSmps(buffersize, efxoutr, Ptype + 1, Pdrive);

    if(!Pprefiltering)
        applyfilters(efxoutl, efxoutr);

    if(!Pstereo)
        memcpy(efxoutr, efxoutl, bufferbytes);

    float level = dB2rap(60.0f * Plevel / 127.0f - 40.0f);
    for(int i = 0; i < buffersize; ++i) {
        float lout = efxoutl[i];
        float rout = efxoutr[i];
        float l    = lout * (1.0f - lrcross) + rout * lrcross;
        float r    = rout * (1.0f - lrcross) + lout * lrcross;
        lout = l;
        rout = r;

        efxoutl[i] = lout * 2.0f * level;
        efxoutr[i] = rout * 2.0f * level;
    }
}


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

    if(insertion == 0) {
        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 Distorsion::setlpf(unsigned char _Plpf)
{
    Plpf = _Plpf;
    float fr = expf(sqrtf(Plpf / 127.0f) * logf(25000.0f)) + 40.0f;
    lpfl->setfreq(fr);
    lpfr->setfreq(fr);
}

void Distorsion::sethpf(unsigned char _Phpf)
{
    Phpf = _Phpf;
    float fr = expf(sqrtf(Phpf / 127.0f) * logf(25000.0f)) + 20.0f;
    hpfl->setfreq(fr);
    hpfr->setfreq(fr);
}


void Distorsion::setpreset(unsigned char npreset)
{
    const int     PRESET_SIZE = 11;
    const int     NUM_PRESETS = 6;
    unsigned char presets[NUM_PRESETS][PRESET_SIZE] = {
        //Overdrive 1
        {127, 64, 35, 56, 70, 0, 0, 96,  0,   0, 0},
        //Overdrive 2
        {127, 64, 35, 29, 75, 1, 0, 127, 0,   0, 0},
        //A. Exciter 1
        {64,  64, 35, 75, 80, 5, 0, 127, 105, 1, 0},
        //A. Exciter 2
        {64,  64, 35, 85, 62, 1, 0, 127, 118, 1, 0},
        //Guitar Amp
        {127, 64, 35, 63, 75, 2, 0, 55,  0,   0, 0},
        //Quantisize
        {127, 64, 35, 88, 75, 4, 0, 127, 0,   1, 0}
    };

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


void Distorsion::changepar(int npar, unsigned char value)
{
    switch(npar) {
        case 0:
            setvolume(value);
            break;
        case 1:
            setpanning(value);
            break;
        case 2:
            setlrcross(value);
            break;
        case 3:
            Pdrive = value;
            break;
        case 4:
            Plevel = value;
            break;
        case 5:
            if(value > 13)
                Ptype = 13;  //this must be increased if more distorsion types are added
            else
                Ptype = value;
            break;
        case 6:
            if(value > 1)
                Pnegate = 1;
            else
                Pnegate = value;
            break;
        case 7:
            setlpf(value);
            break;
        case 8:
            sethpf(value);
            break;
        case 9:
            Pstereo = (value > 1) ? 1 : value;
            break;
        case 10:
            Pprefiltering = value;
            break;
    }
}

unsigned char Distorsion::getpar(int npar) const
{
    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 Ptype;
        case 6:  return Pnegate;
        case 7:  return Plpf;
        case 8:  return Phpf;
        case 9:  return Pstereo;
        case 10: return Pprefiltering;
        default: return 0; //in case of bogus parameter number
    }
}

}
