/*
  ZynAddSubFX - a software synthesizer

  Phaser.cpp - Phasing and Approximate digital model of an analog JFET phaser.
               Analog modeling implemented by Ryan Billing aka Transmogrifox.
               DSP analog modeling theory & practice largely influenced by
               various CCRMA publications, particularly works by Julius O. Smith.
  Copyright (C) 2002-2005 Nasca Octavian Paul
  Copyright (C) 2009-2010 Ryan Billing
  Copyright (C) 2010-2010 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 <algorithm>
#include <rtosc/ports.h>
#include <rtosc/port-sugar.h>
#include "../Misc/Allocator.h"
#include "Phaser.h"
using namespace std;

namespace zyncarla {

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

#define ucharParamCb(pname) rBegin \
        rObject &p = *(rObject*)d.obj; \
        if(rtosc_narguments(msg)) \
            p.set##pname(rtosc_argument(msg, 0).i); \
        else \
            d.reply(d.loc, "i", p.P##pname); \
        rEnd
#define rParamPhaser(name, ...) \
  {STRINGIFY(P##name) "::i",  rProp(parameter) rMap(min, 0) rMap(max, 127) \
   rDefaultDepends(preset) DOC(__VA_ARGS__), NULL, ucharParamCb(name)}

rtosc::Ports Phaser::ports = {
    {"preset::i", rProp(parameter)
                  rOptions(Phaser 1, Phaser 2, Phaser 3, Phaser 4,
                           Phaser 5, Phaser 6,
                           APhaser 1, APhaser 2, APhaser 3, APhaser 4,
                           APhaser 5, APhaser 6)
                  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(64), rPreset(3, 39), rPreset(10, 25)),
    rEffParPan(),
    rEffPar(lfo.Pfreq,       2, rShort("freq"),
            rPresets(36, 35, 31, 22, 20, 53, 14, 14, 9, 14, 127, 1),
            "LFO frequency"),
    rEffPar(lfo.Prandomness, 3, rShort("rnd."),
            rPreset(5, 100), rPreset(7, 5), rPresetsAt(9, 10, 10, 10),
            rDefault(0), "LFO randomness"),
    rEffPar(lfo.PLFOtype,    4, rShort("type"),
            rPreset(4, tri), rPresetsAt(6, tri, tri), rPreset(11, tri),
            rDefault(sine),
            rOptions(sine, tri), "lfo shape"),
    rEffPar(lfo.Pstereo,     5, rShort("stereo"),
            rPresetsAt(1, 88, 66, 66, 110, 58), rDefault(64),
            "Left/right channel phase shift"),
    rEffPar(Pdepth,          6, rShort("depth"),
            rPresets(110, 40, 68, 67, 67, 37, 64, 70, 60, 45, 25, 70),
            "LFP depth"),
    rEffPar(Pfb,             7, rShort("fb"),
            rPresets(64, 64, 107, 10, 78, 78, 40, 40, 40, 80, 16, 40),
            "Feedback"),
    rEffPar(Pstages,         8, rLinear(1,12), rShort("stages"),
            rPresets(1, 3, 2, 5, 10, 3, 4, 6, 8, 7, 8, 12),
            ""),
    rParamPhaser(lrcross,       rShort("cross"),
            rPresetsAt(6, 10, 10, 10, 10, 100, 10) rDefault(0),
            "Channel routing"),
    rParamPhaser(offset,        rShort("off"),
            rPresetsAt(6, 10, 10, 10, 10, 100, 10) rDefault(0),
            "Offset"),
    rEffParTF(Poutsub,      10, rShort("sub"),
            rPreset(3, true), rPreset(9, true), rDefault(false),
            "Invert output"),
    rParamPhaser(phase,         rShort("phase"),
            rPresets(20, 20, 20, 20, 20, 20, 110, 110, 40, 110, 25, 110), ""),
    rParamPhaser(width,         rShort("width"),
            rPresets(20, 20, 20, 20, 20, 20, 110, 110, 40, 110, 25, 110), ""),
    rEffParTF(Phyper,       12, rShort("hyp."),
            rPresetsAt(6, true, true, false, true, false, true),
            rDefault(false), "Square the LFO"),
    rEffPar(Pdistortion,    13, rShort("distort"),
            rPresetsAt(6, 20, 20, 20, 20, 20, 20), rDefault(0),
            "Distortion"),
    rEffParTF(Panalog,      14, rShort("analog"),
            rPresetsAt(6, true, true, true, true, true, true), rDefault(false),
            "Use analog phaser"),
};
#undef rBegin
#undef rEnd
#undef rObject

#define PHASER_LFO_SHAPE 2
#define ONE_  0.99999f        // To prevent LFO ever reaching 1.0f for filter stability purposes
#define ZERO_ 0.00001f        // Same idea as above.

Phaser::Phaser(EffectParams pars)
    :Effect(pars), lfo(pars.srate, pars.bufsize), old(NULL), xn1(NULL),
      yn1(NULL), diff(0.0f), oldgain(0.0f), fb(0.0f)
{
    analog_setup();
    setpreset(Ppreset);
    cleanup();
}

void Phaser::analog_setup()
{
    //model mismatch between JFET devices
    offset[0]  = -0.2509303f;
    offset[1]  = 0.9408924f;
    offset[2]  = 0.998f;
    offset[3]  = -0.3486182f;
    offset[4]  = -0.2762545f;
    offset[5]  = -0.5215785f;
    offset[6]  = 0.2509303f;
    offset[7]  = -0.9408924f;
    offset[8]  = -0.998f;
    offset[9]  = 0.3486182f;
    offset[10] = 0.2762545f;
    offset[11] = 0.5215785f;

    barber = 0;  //Deactivate barber pole phasing by default

    mis       = 1.0f;
    Rmin      = 625.0f; // 2N5457 typical on resistance at Vgs = 0
    Rmax      = 22000.0f; // Resistor parallel to FET
    Rmx       = Rmin / Rmax;
    Rconst    = 1.0f + Rmx; // Handle parallel resistor relationship
    C         = 0.00000005f; // 50 nF
    CFs       = 2.0f * samplerate_f * C;
    invperiod = 1.0f / buffersize_f;
}

Phaser::~Phaser()
{
    memory.devalloc(old.l);
    memory.devalloc(old.r);
    memory.devalloc(xn1.l);
    memory.devalloc(xn1.r);
    memory.devalloc(yn1.l);
    memory.devalloc(yn1.r);
}

/*
 * Effect output
 */
void Phaser::out(const Stereo<float *> &input)
{
    if(Panalog)
        AnalogPhase(input);
    else
        normalPhase(input);
}

void Phaser::AnalogPhase(const Stereo<float *> &input)
{
    Stereo<float> gain(0.0f), lfoVal(0.0f), mod(0.0f), g(0.0f), b(0.0f), hpf(
        0.0f);

    lfo.effectlfoout(&lfoVal.l, &lfoVal.r);
    mod.l = lfoVal.l * width + (depth - 0.5f);
    mod.r = lfoVal.r * width + (depth - 0.5f);

    mod.l = limit(mod.l, ZERO_, ONE_);
    mod.r = limit(mod.r, ZERO_, ONE_);

    if(Phyper) {
        //Triangle wave squared is approximately sin on bottom, tri on top
        //Result is exponential sweep more akin to filter in synth with
        //exponential generator circuitry.
        mod.l *= mod.l;
        mod.r *= mod.r;
    }

    //g.l,g.r is Vp - Vgs. Typical FET drain-source resistance follows constant/[1-sqrt(Vp - Vgs)]
    mod.l = sqrtf(1.0f - mod.l);
    mod.r = sqrtf(1.0f - mod.r);

    diff.r = (mod.r - oldgain.r) * invperiod;
    diff.l = (mod.l - oldgain.l) * invperiod;

    g = oldgain;
    oldgain = mod;

    for(int i = 0; i < buffersize; ++i) {
        g.l += diff.l; // Linear interpolation between LFO samples
        g.r += diff.r;

        Stereo<float> xn(input.l[i] * pangainL, input.r[i] * pangainR);

        if(barber) {
            g.l += 0.25;
            g.l -= floorf(g.l);
            g.r += 0.25;
            g.r -= floorf(g.r);
        }

        xn.l = applyPhase(xn.l, g.l, fb.l, hpf.l, yn1.l, xn1.l);
        xn.r = applyPhase(xn.r, g.r, fb.r, hpf.r, yn1.r, xn1.r);


        fb.l = xn.l * feedback;
        fb.r = xn.r * feedback;
        efxoutl[i] = xn.l;
        efxoutr[i] = xn.r;
    }

    if(Poutsub) {
        invSignal(efxoutl, buffersize);
        invSignal(efxoutr, buffersize);
    }
}

float Phaser::applyPhase(float x, float g, float fb,
                         float &hpf, float *yn1, float *xn1)
{
    for(int j = 0; j < Pstages; ++j) { //Phasing routine
        mis = 1.0f + offsetpct * offset[j];

        //This is symmetrical.
        //FET is not, so this deviates slightly, however sym dist. is
        //better sounding than a real FET.
        float d = (1.0f + 2.0f * (0.25f + g) * hpf * hpf * distortion) * mis;
        Rconst = 1.0f + mis * Rmx;

        // This is 1/R. R is being modulated to control filter fc.
        float b    = (Rconst - g) / (d * Rmin);
        float gain = (CFs - b) / (CFs + b);
        yn1[j] = gain * (x + yn1[j]) - xn1[j];

        //high pass filter:
        //Distortion depends on the high-pass part of the AP stage.
        hpf = yn1[j] + (1.0f - gain) * xn1[j];

        xn1[j] = x;
        x      = yn1[j];
        if(j == 1)
            x += fb;  //Insert feedback after first phase stage
    }
    return x;
}
void Phaser::normalPhase(const Stereo<float *> &input)
{
    Stereo<float> gain(0.0f), lfoVal(0.0f);

    lfo.effectlfoout(&lfoVal.l, &lfoVal.r);
    gain.l =
        (expf(lfoVal.l
              * PHASER_LFO_SHAPE) - 1) / (expf(PHASER_LFO_SHAPE) - 1.0f);
    gain.r =
        (expf(lfoVal.r
              * PHASER_LFO_SHAPE) - 1) / (expf(PHASER_LFO_SHAPE) - 1.0f);

    gain.l = 1.0f - phase * (1.0f - depth) - (1.0f - phase) * gain.l * depth;
    gain.r = 1.0f - phase * (1.0f - depth) - (1.0f - phase) * gain.r * depth;

    gain.l = limit(gain.l, ZERO_, ONE_);
    gain.r = limit(gain.r, ZERO_, ONE_);

    for(int i = 0; i < buffersize; ++i) {
        float x  = (float) i / buffersize_f;
        float x1 = 1.0f - x;
        //TODO think about making panning an external feature
        Stereo<float> xn(input.l[i] * pangainL + fb.l,
                         input.r[i] * pangainR + fb.r);

        Stereo<float> g(gain.l * x + oldgain.l * x1,
                        gain.r * x + oldgain.r * x1);

        xn.l = applyPhase(xn.l, g.l, old.l);
        xn.r = applyPhase(xn.r, g.r, old.r);

        //Left/Right crossing
        crossover(xn.l, xn.r, lrcross);

        fb.l = xn.l * feedback;
        fb.r = xn.r * feedback;
        efxoutl[i] = xn.l;
        efxoutr[i] = xn.r;
    }

    oldgain = gain;

    if(Poutsub) {
        invSignal(efxoutl, buffersize);
        invSignal(efxoutr, buffersize);
    }
}

float Phaser::applyPhase(float x, float g, float *old)
{
    for(int j = 0; j < Pstages * 2; ++j) { //Phasing routine
        float tmp = old[j];
        old[j] = g * tmp + x;
        x      = tmp - g * old[j];
    }
    return x;
}

/*
 * Cleanup the effect
 */
void Phaser::cleanup()
{
    fb = oldgain = Stereo<float>(0.0f);
    for(int i = 0; i < Pstages * 2; ++i) {
        old.l[i] = 0.0f;
        old.r[i] = 0.0f;
    }
    for(int i = 0; i < Pstages; ++i) {
        xn1.l[i] = 0.0f;
        yn1.l[i] = 0.0f;
        xn1.r[i] = 0.0f;
        yn1.r[i] = 0.0f;
    }
}

/*
 * Parameter control
 */
void Phaser::setwidth(unsigned char Pwidth)
{
    this->Pwidth = Pwidth;
    width = ((float)Pwidth / 127.0f);
}

void Phaser::setfb(unsigned char Pfb)
{
    this->Pfb = Pfb;
    feedback  = (float) (Pfb - 64) / 64.2f;
}

void Phaser::setvolume(unsigned char Pvolume)
{
    this->Pvolume = Pvolume;
    outvolume     = Pvolume / 127.0f;
    if(insertion == 0)
        volume = 1.0f;
    else
        volume = outvolume;
}

void Phaser::setdistortion(unsigned char Pdistortion)
{
    this->Pdistortion = Pdistortion;
    distortion = (float)Pdistortion / 127.0f;
}

void Phaser::setoffset(unsigned char Poffset)
{
    this->Poffset = Poffset;
    offsetpct     = (float)Poffset / 127.0f;
}

void Phaser::setstages(unsigned char Pstages_)
{
    memory.devalloc(old.l);
    memory.devalloc(old.r);
    memory.devalloc(xn1.l);
    memory.devalloc(xn1.r);
    memory.devalloc(yn1.l);
    memory.devalloc(yn1.r);

    Pstages = limit<int>(Pstages_, 1, MAX_PHASER_STAGES);

    old = Stereo<float *>(memory.valloc<float>(Pstages * 2),
                          memory.valloc<float>(Pstages * 2));

    xn1 = Stereo<float *>(memory.valloc<float>(Pstages),
                          memory.valloc<float>(Pstages));

    yn1 = Stereo<float *>(memory.valloc<float>(Pstages),
                          memory.valloc<float>(Pstages));

    cleanup();
}

void Phaser::setphase(unsigned char Pphase)
{
    this->Pphase = Pphase;
    phase = (Pphase / 127.0f);
}

void Phaser::setdepth(unsigned char Pdepth)
{
    this->Pdepth = Pdepth;
    depth = (float)(Pdepth) / 127.0f;
}


void Phaser::setpreset(unsigned char npreset)
{
    const int     PRESET_SIZE = 15;
    const int     NUM_PRESETS = 12;
    unsigned char presets[NUM_PRESETS][PRESET_SIZE] = {
        //Phaser
        //0   1    2    3  4   5     6   7   8    9 10   11 12  13 14
        {64, 64, 36,  0,   0, 64,  110, 64,  1,  0,   0, 20,
         0, 0,
         0 },
        {64, 64, 35,  0,   0, 88,  40,  64,  3,  0,   0, 20, 0,  0,
         0 },
        {64, 64, 31,  0,   0, 66,  68,  107, 2,  0,   0, 20, 0,  0,
         0 },
        {39, 64, 22,  0,   0, 66,  67,  10,  5,  0,   1, 20, 0,  0,
         0 },
        {64, 64, 20,  0,   1, 110, 67,  78,  10, 0,   0, 20, 0,  0,
         0 },
        {64, 64, 53,  100, 0, 58,  37,  78,  3,  0,   0, 20, 0,  0,
         0 },
        //APhaser
        //0   1    2   3   4   5     6   7   8    9 10   11 12  13 14
        {64, 64, 14,  0,   1, 64,  64,  40,  4,  10,  0, 110,1,  20,
         1 },
        {64, 64, 14,  5,   1, 64,  70,  40,  6,  10,  0, 110,1,  20,
         1 },
        {64, 64, 9,   0,   0, 64,  60,  40,  8,  10,  0, 40, 0,  20,
         1 },
        {64, 64, 14,  10,  0, 64,  45,  80,  7,  10,  1, 110,1,  20,
         1 },
        {25, 64, 127, 10,  0, 64,  25,  16,  8,  100, 0, 25, 0,  20,
         1 },
        {64, 64, 1,   10,  1, 64,  70,  40,  12, 10,  0, 110,1,  20,
         1 }
    };
    if(npreset >= NUM_PRESETS)
        npreset = NUM_PRESETS - 1;
    for(int n = 0; n < PRESET_SIZE; ++n)
        changepar(n, presets[npreset][n]);
    Ppreset = npreset;
}


void Phaser::changepar(int npar, unsigned char value)
{
    switch(npar) {
        case 0:
            setvolume(value);
            break;
        case 1:
            setpanning(value);
            break;
        case 2:
            lfo.Pfreq = value;
            lfo.updateparams();
            break;
        case 3:
            lfo.Prandomness = value;
            lfo.updateparams();
            break;
        case 4:
            lfo.PLFOtype = value;
            lfo.updateparams();
            barber = (2 == value);
            break;
        case 5:
            lfo.Pstereo = value;
            lfo.updateparams();
            break;
        case 6:
            setdepth(value);
            break;
        case 7:
            setfb(value);
            break;
        case 8:
            setstages(value);
            break;
        case 9:
            setlrcross(value);
            setoffset(value);
            break;
        case 10:
            Poutsub = min((int)value, 1);
            break;
        case 11:
            setphase(value);
            setwidth(value);
            break;
        case 12:
            Phyper = min((int)value, 1);
            break;
        case 13:
            setdistortion(value);
            break;
        case 14:
            Panalog = value;
            break;
    }
}

unsigned char Phaser::getpar(int npar) const
{
    switch(npar) {
        case 0:  return Pvolume;
        case 1:  return Ppanning;
        case 2:  return lfo.Pfreq;
        case 3:  return lfo.Prandomness;
        case 4:  return lfo.PLFOtype;
        case 5:  return lfo.Pstereo;
        case 6:  return Pdepth;
        case 7:  return Pfb;
        case 8:  return Pstages;
        case 9:  return Plrcross;
            return Poffset;      //same
        case 10: return Poutsub;
        case 11: return Pphase;
            return Pwidth;      //same
        case 12: return Phyper;
        case 13: return Pdistortion;
        case 14: return Panalog;
        default: return 0;
    }
}

}
