/*
  ZynAddSubFX - a software synthesizer

  ModFilter.cpp - Modulated Filter
  Copyright (C) 2016 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 "ModFilter.h"
#include "Envelope.h"
#include "LFO.h"
#include "../Misc/Util.h"
#include "../Misc/Allocator.h"
#include "../Params/FilterParams.h"
#include "../DSP/Filter.h"
#include "../DSP/SVFilter.h"
#include "../DSP/AnalogFilter.h"
#include "../DSP/FormantFilter.h"
#include "../DSP/MoogFilter.h"
#include "../DSP/CombFilter.h"
#include <cassert>

namespace zyn {

ModFilter::ModFilter(const FilterParams &pars_,
                     const SYNTH_T      &synth_,
                     const AbsTime      &time_,
                           Allocator    &alloc_,
                           bool         stereo,
                           float        notefreq)
    :pars(pars_), synth(synth_), time(time_), alloc(alloc_),
    noteFreq(notefreq),
    left(nullptr),
    right(nullptr),
    env(nullptr),
    lfo(nullptr)
{
    tracking = pars.getfreqtracking(notefreq);
    baseQ    = pars.getq();
    baseFreq = pars.getfreq();

    left = Filter::generate(alloc, &pars,
            synth.samplerate, synth.buffersize);

    if(stereo)
        right = Filter::generate(alloc, &pars,
                synth.samplerate, synth.buffersize);
}

ModFilter::~ModFilter(void)
{
    alloc.dealloc(left);
    alloc.dealloc(right);
}

void ModFilter::addMod(LFO &lfo_)
{
    lfo = &lfo_;
}

void ModFilter::addMod(Envelope &env_)
{
    env = &env_;
}

//Recompute Filter Parameters
void ModFilter::update(float relfreq, float relq)
{
    if(pars.last_update_timestamp == time.time()) {
        paramUpdate(left);
        if(right)
            paramUpdate(right);

        baseFreq = pars.getfreq();
        baseQ    = pars.getq();
        tracking = pars.getfreqtracking(noteFreq);
    }

    //Controller Free Center Frequency
    const float Fc = baseFreq
                   + sense
                   + (env ? env->envout() : 0)
                   + (lfo ? lfo->lfoout() : 0);

    const float Fc_mod = Fc + relfreq + tracking;

    //Convert into Hz
    const float Fc_Hz = Filter::getrealfreq(Fc_mod);

    const float q = baseQ * relq;

    left->setfreq_and_q(Fc_Hz, q);
    if(right)
        right->setfreq_and_q(Fc_Hz, q);
}

void ModFilter::updateNoteFreq(float noteFreq_)
{
    noteFreq = noteFreq_;
    tracking = pars.getfreqtracking(noteFreq_);
}

void ModFilter::updateSense(float velocity, uint8_t scale,
        uint8_t func)
{
    const float velScale = scale / 127.0f;
    sense = velScale * 6.0f * (VelF(velocity, func) - 1);
}

void ModFilter::filter(float *l, float *r)
{
    if(left && l)
        left->filterout(l);
    if(right && r)
        right->filterout(r);
}

static int current_category(Filter *f)
{
    if(dynamic_cast<AnalogFilter*>(f))
        return 0;
    else if(dynamic_cast<FormantFilter*>(f))
        return 1;
    else if(dynamic_cast<SVFilter*>(f))
        return 2;
    else if(dynamic_cast<MoogFilter*>(f))
        return 3;
    else if(dynamic_cast<CombFilter*>(f))
        return 4;

    assert(false);
    return -1;
}

void ModFilter::paramUpdate(Filter *&f)
{
    //Common parameters
    baseQ    = pars.getq();
    baseFreq = pars.getfreq();

    if(current_category(f) != pars.Pcategory) {
        alloc.dealloc(f);
        f = Filter::generate(alloc, &pars,
                synth.samplerate, synth.buffersize);
        return;
    }

    if(auto *sv = dynamic_cast<SVFilter*>(f))
        svParamUpdate(*sv);
    else if(auto *an = dynamic_cast<AnalogFilter*>(f))
        anParamUpdate(*an);
    else if(auto *mg = dynamic_cast<MoogFilter*>(f))
        mgParamUpdate(*mg);
    else if(auto *cb = dynamic_cast<CombFilter*>(f))
        cbParamUpdate(*cb);
}

void ModFilter::svParamUpdate(SVFilter &sv)
{
    sv.settype(pars.Ptype);
    sv.setstages(pars.Pstages);
}

void ModFilter::anParamUpdate(AnalogFilter &an)
{
    an.settype(pars.Ptype);
    an.setstages(pars.Pstages);
    an.setgain(pars.getgain());
}

void ModFilter::mgParamUpdate(MoogFilter &mg)
{
    mg.settype(pars.Ptype);
    mg.setgain(pars.getgain());
}

void ModFilter::cbParamUpdate(CombFilter &cb)
{
    cb.settype(pars.Ptype);
    cb.setgain(pars.getgain());
}
}
