/*
  ZynAddSubFX - a software synthesizer

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

namespace zyn {

using rtosc::RtData;
#define rObject EQ
#define rBegin [](const char *msg, RtData &d) {\
    rObject *obj = (rObject*)d.obj;
#define rEQ(offset) \
    int nfilt = atoi(msg-2); \
    int id    = 10+nfilt*5+offset; \
    if(rtosc_narguments(msg)) \
        obj->changepar(id, rtosc_argument(msg,0).i);\
    else \
        d.reply(d.loc, "i", obj->getpar(id))

#define rEnd }

static rtosc::Ports filterports {
    {"Ptype::i", rProp(parameter) rOptions(Off, LP1, HP1, LP2,
            HP2, BP, notch, peak, l.shelf, h.shelf)
        rShort("type") rDoc("Filter Type"), 0,
        rBegin;
        rEQ(0);
        rEnd},
    {"Pfreq::i", rProp(parameter) rMap(min, 0) rMap(max, 127)
        rShort("freq"), 0,
        rBegin;
        rEQ(1);
        rEnd},
    {"Pgain::i", rProp(parameter) rMap(min, 0) rMap(max, 127)
        rShort("gain"), 0,
        rBegin;
        rEQ(2);
        rEnd},
    {"Pq::i",    rProp(parameter) rMap(min, 0) rMap(max, 127)
        rShort("q") rDoc("Resonance/Bandwidth"), 0,
        rBegin;
        rEQ(3);
        rEnd},
    {"Pstages::i", rProp(parameter) rMap(min, 0) rMap(max, 4)
        rShort("stages") rDoc("Additional filter stages"), 0,
        rBegin;
        rEQ(4);
        rEnd},
};

rtosc::Ports EQ::ports = {
    {"filter#8/", 0, &filterports,
        rBegin;
        (void)obj;
        SNIP;
        filterports.dispatch(msg, d);
        rEnd},
    {"coeff:", rProp(internal) rDoc("Get equalizer Coefficients"), NULL,
        [](const char *, rtosc::RtData &d)
        {
            EQ *eq = (EQ*)d.obj;
            float a[MAX_EQ_BANDS*MAX_FILTER_STAGES*3];
            float b[MAX_EQ_BANDS*MAX_FILTER_STAGES*3];
            memset(a, 0, sizeof(a));
            memset(b, 0, sizeof(b));
            eq->getFilter(a,b);

            char        type[MAX_EQ_BANDS*MAX_FILTER_STAGES*3*2+1] = {};
            rtosc_arg_t  val[MAX_EQ_BANDS*MAX_FILTER_STAGES*3*2] = {};
            for(int i=0; i<MAX_EQ_BANDS*MAX_FILTER_STAGES*3; ++i) {
                int stride = MAX_EQ_BANDS*MAX_FILTER_STAGES*3;
                type[i]  = type[i+stride] = 'f';
                val[i].f = b[i];
                val[i+stride].f = a[i];
            }
            d.replyArray(d.loc, type, val);
        }},
};

#undef rObject
#undef rBegin
#undef rEnd

EQ::EQ(EffectParams pars)
    :Effect(pars)
{
    for(int i = 0; i < MAX_EQ_BANDS; ++i) {
        filter[i].l = memory.alloc<AnalogFilter>(6, 1000.0f, 1.0f, 0, pars.srate, pars.bufsize);
        filter[i].r = memory.alloc<AnalogFilter>(6, 1000.0f, 1.0f, 0, pars.srate, pars.bufsize);
    }
    //default values
    Pvolume = 50;

    setpreset(Ppreset);
    cleanup();
}

EQ::~EQ()
{
       for(int i = 0; i < MAX_EQ_BANDS; ++i) {
           memory.dealloc(filter[i].l);
           memory.dealloc(filter[i].r);
       }
}

// Cleanup the effect
void EQ::cleanup(void)
{
    for(int i = 0; i < MAX_EQ_BANDS; ++i) {
        filter[i].l->cleanup();
        filter[i].r->cleanup();
    }
}

//Effect output
void EQ::out(const Stereo<float *> &smp)
{
    for(int i = 0; i < buffersize; ++i) {
        efxoutl[i] = smp.l[i] * volume;
        efxoutr[i] = smp.r[i] * volume;
    }

    for(int i = 0; i < MAX_EQ_BANDS; ++i) {
        if(filter[i].Ptype == 0)
            continue;
        filter[i].l->filterout(efxoutl);
        filter[i].r->filterout(efxoutr);
    }
}


//Parameter control
void EQ::setvolume(unsigned char _Pvolume)
{
    Pvolume   = _Pvolume;
    outvolume = powf(0.005f, (1.0f - Pvolume / 127.0f)) * 10.0f;
    volume    = (!insertion) ? 1.0f : outvolume;
}

unsigned char EQ::getpresetpar(unsigned char npreset, unsigned int npar)
{
#define PRESET_SIZE 1
#define NUM_PRESETS 2
    static const unsigned char presets[NUM_PRESETS][PRESET_SIZE] = {
        {67}, //EQ 1
        {67}  //EQ 2
    };
    if(npreset < NUM_PRESETS && npar < PRESET_SIZE) {
        return presets[npreset][npar];
    } else if (npar >= 10 && npar < (10 + MAX_EQ_BANDS * 5)) {
        static const unsigned char bp_preset[5] = { 0, 64, 64, 64, 0 };
        return bp_preset[npar % 5];
    }
    return 0;
}

void EQ::setpreset(unsigned char npreset)
{

    if(npreset >= NUM_PRESETS)
        npreset = NUM_PRESETS - 1;
    for(int n = 0; n != 128; n++)
        changepar(n, getpresetpar(npreset, n));
    Ppreset = npreset;
}


void EQ::changepar(int npar, unsigned char value)
{
    switch(npar) {
        case 0:
            setvolume(value);
            break;
    }
    if(npar < 10)
        return;

    int nb = (npar - 10) / 5; //number of the band (filter)
    if(nb >= MAX_EQ_BANDS)
        return;
    int bp = npar % 5; //band paramenter

    float tmp;
    switch(bp) {
        case 0:
            filter[nb].Ptype = value;
            if(value > 9)
                filter[nb].Ptype = 0;  //has to be changed if more filters will be added
            if(filter[nb].Ptype != 0) {
                filter[nb].l->settype(value - 1);
                filter[nb].r->settype(value - 1);
            }
            break;
        case 1:
            filter[nb].Pfreq = value;
            tmp = 600.0f * powf(30.0f, (value - 64.0f) / 64.0f);
            filter[nb].l->setfreq(tmp);
            filter[nb].r->setfreq(tmp);
            break;
        case 2:
            filter[nb].Pgain = value;
            tmp = 30.0f * (value - 64.0f) / 64.0f;
            filter[nb].l->setgain(tmp);
            filter[nb].r->setgain(tmp);
            break;
        case 3:
            filter[nb].Pq = value;
            tmp = powf(30.0f, (value - 64.0f) / 64.0f);
            filter[nb].l->setq(tmp);
            filter[nb].r->setq(tmp);
            break;
        case 4:
            filter[nb].Pstages = value;
            if(value >= MAX_FILTER_STAGES)
                filter[nb].Pstages = MAX_FILTER_STAGES - 1;
            filter[nb].l->setstages(value);
            filter[nb].r->setstages(value);
            break;
    }
}

unsigned char EQ::getpar(int npar) const
{
    switch(npar) {
        case 0:
            return Pvolume;
            break;
    }

    if(npar < 10)
        return 0;

    int nb = (npar - 10) / 5; //number of the band (filter)
    if(nb >= MAX_EQ_BANDS)
        return 0;
    int bp = npar % 5; //band paramenter
    switch(bp) {
        case 0:
            return filter[nb].Ptype;
            break;
        case 1:
            return filter[nb].Pfreq;
            break;
        case 2:
            return filter[nb].Pgain;
            break;
        case 3:
            return filter[nb].Pq;
            break;
        case 4:
            return filter[nb].Pstages;
            break;
        default: return 0; //in case of bogus parameter number
    }
}


float EQ::getfreqresponse(float freq)
{
    float resp = 1.0f;
    for(int i = 0; i < MAX_EQ_BANDS; ++i) {
        if(filter[i].Ptype == 0)
            continue;
        resp *= filter[i].l->H(freq);
    }
    return rap2dB(resp * outvolume);
}

//Not exactly the most efficient manner to derive the total taps, but it should
//be fast enough in practice
void EQ::getFilter(float *a, float *b) const
{
    a[0] = 1;
    b[0] = 1;
    off_t off=0;
    for(int i = 0; i < MAX_EQ_BANDS; ++i) {
        auto &F = filter[i];
        if(F.Ptype == 0)
            continue;
        const float Fb[3] = {F.l->coeff.c[0], F.l->coeff.c[1], F.l->coeff.c[2]};
        const float Fa[3] = {1.0f, -F.l->coeff.d[1], -F.l->coeff.d[2]};

        for(int j=0; j<F.Pstages+1; ++j) {
            for(int k=0; k<3; ++k) {
                a[off] = Fa[k];
                b[off] = Fb[k];
                off++;
            }
        }
    }
}

}
