/*
 * ZamGEQ31 1/3 octave band graphic equaliser
 * Copyright (C) 2014  Damien Zammit <damien@zamaudio.com>
 *
 * 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 any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
 */

#include "ZamGEQ31Plugin.hpp"

START_NAMESPACE_DISTRHO

// -----------------------------------------------------------------------

ZamGEQ31Plugin::ZamGEQ31Plugin()
    : Plugin(paramCount, 1, 0) // 1 program, 0 states
{
    // set default values
    loadProgram(0);
}

// -----------------------------------------------------------------------
// Init

void ZamGEQ31Plugin::initParameter(uint32_t index, Parameter& parameter)
{
    switch (index)
    {
    case paramMaster:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "Master Gain";
        parameter.symbol     = "master";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -30.f;
        parameter.ranges.max = 30.f;
        break;
    case paramGain1:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "32Hz";
        parameter.symbol     = "band1";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain2:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "40Hz";
        parameter.symbol     = "band2";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain3:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "50Hz";
        parameter.symbol     = "band3";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain4:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "63Hz";
        parameter.symbol     = "band4";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain5:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "79Hz";
        parameter.symbol     = "band5";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain6:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "100Hz";
        parameter.symbol     = "band6";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain7:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "126Hz";
        parameter.symbol     = "band7";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain8:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "158Hz";
        parameter.symbol     = "band8";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain9:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "200Hz";
        parameter.symbol     = "band9";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain10:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "251Hz";
        parameter.symbol     = "band10";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain11:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "316Hz";
        parameter.symbol     = "band11";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain12:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "398Hz";
        parameter.symbol     = "band12";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain13:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "501Hz";
        parameter.symbol     = "band13";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain14:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "631Hz";
        parameter.symbol     = "band14";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain15:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "794Hz";
        parameter.symbol     = "band15";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain16:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "999Hz";
        parameter.symbol     = "band16";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain17:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "1257Hz";
        parameter.symbol     = "band17";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain18:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "1584Hz";
        parameter.symbol     = "band18";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain19:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "1997Hz";
        parameter.symbol     = "band19";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain20:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "2514Hz";
        parameter.symbol     = "band20";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain21:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "3165Hz";
        parameter.symbol     = "band21";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain22:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "3986Hz";
        parameter.symbol     = "band22";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain23:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "5017Hz";
        parameter.symbol     = "band23";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain24:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "6318Hz";
        parameter.symbol     = "band24";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain25:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "7963Hz";
        parameter.symbol     = "band25";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain26:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "10032Hz";
        parameter.symbol     = "band26";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain27:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "12662Hz";
        parameter.symbol     = "band27";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain28:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "16081Hz";
        parameter.symbol     = "band28";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramGain29:
        parameter.hints      = kParameterIsAutomable;
        parameter.name       = "20801Hz";
        parameter.symbol     = "band29";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    }
}

void ZamGEQ31Plugin::initProgramName(uint32_t index, String& programName)
{
    if (index != 0)
        return;

    programName = "Default";
}

// -----------------------------------------------------------------------
// Internal data

float ZamGEQ31Plugin::getParameterValue(uint32_t index) const
{
    switch (index)
    {
    case paramMaster:
        return master;
        break;
    case paramGain1:
        return gain[0];
        break;
    case paramGain2:
        return gain[1];
        break;
    case paramGain3:
        return gain[2];
        break;
    case paramGain4:
        return gain[3];
        break;
    case paramGain5:
        return gain[4];
        break;
    case paramGain6:
        return gain[5];
        break;
    case paramGain7:
        return gain[6];
        break;
    case paramGain8:
        return gain[7];
        break;
    case paramGain9:
        return gain[8];
        break;
    case paramGain10:
        return gain[9];
        break;
    case paramGain11:
        return gain[10];
        break;
    case paramGain12:
        return gain[11];
        break;
    case paramGain13:
        return gain[12];
        break;
    case paramGain14:
        return gain[13];
        break;
    case paramGain15:
        return gain[14];
        break;
    case paramGain16:
        return gain[15];
        break;
    case paramGain17:
        return gain[16];
        break;
    case paramGain18:
        return gain[17];
        break;
    case paramGain19:
        return gain[18];
        break;
    case paramGain20:
        return gain[19];
        break;
    case paramGain21:
        return gain[20];
        break;
    case paramGain22:
        return gain[21];
        break;
    case paramGain23:
        return gain[22];
        break;
    case paramGain24:
        return gain[23];
        break;
    case paramGain25:
        return gain[24];
        break;
    case paramGain26:
        return gain[25];
        break;
    case paramGain27:
        return gain[26];
        break;
    case paramGain28:
        return gain[27];
        break;
    case paramGain29:
        return gain[28];
        break;
    default:
        return 0.0f;
    }
}

void ZamGEQ31Plugin::setParameterValue(uint32_t index, float value)
{
    switch (index)
    {
    case paramMaster:
        master = value;
        break;
    case paramGain1:
        gain[0] = value;
        break;
    case paramGain2:
        gain[1] = value;
        break;
    case paramGain3:
        gain[2] = value;
        break;
    case paramGain4:
        gain[3] = value;
        break;
    case paramGain5:
        gain[4] = value;
        break;
    case paramGain6:
        gain[5] = value;
        break;
    case paramGain7:
        gain[6] = value;
        break;
    case paramGain8:
        gain[7] = value;
        break;
    case paramGain9:
        gain[8] = value;
        break;
    case paramGain10:
        gain[9] = value;
        break;
    case paramGain11:
        gain[10] = value;
        break;
    case paramGain12:
        gain[11] = value;
        break;
    case paramGain13:
        gain[12] = value;
        break;
    case paramGain14:
        gain[13] = value;
        break;
    case paramGain15:
        gain[14] = value;
        break;
    case paramGain16:
        gain[15] = value;
        break;
    case paramGain17:
        gain[16] = value;
        break;
    case paramGain18:
        gain[17] = value;
        break;
    case paramGain19:
        gain[18] = value;
        break;
    case paramGain20:
        gain[19] = value;
        break;
    case paramGain21:
        gain[20] = value;
        break;
    case paramGain22:
        gain[21] = value;
        break;
    case paramGain23:
        gain[22] = value;
        break;
    case paramGain24:
        gain[23] = value;
        break;
    case paramGain25:
        gain[24] = value;
        break;
    case paramGain26:
        gain[25] = value;
        break;
    case paramGain27:
        gain[26] = value;
        break;
    case paramGain28:
        gain[27] = value;
        break;
    case paramGain29:
        gain[28] = value;
        break;
    }
}

void ZamGEQ31Plugin::geq(int i, float srate, float g)
{
	int mm;
	float w, omegaB, omegaM, glinear, alpham;
	// 2dB tolerance
	//const int stack[29] = {	2, 1, 1, 1, 1, 1, 1, 1,
	//			1, 1, 1, 1, 1, 1, 1, 1,
	//			1, 1, 1, 1, 1, 1, 1, 1,
	//			1, 1, 1, 2, 3 };
	
	// 0.15dB tolerance
	const int stack[29] = {	8, 8, 8, 8, 8, 8, 9, 9,
				9, 9, 9, 9, 9, 9, 9, 9,
				9, 9, 9, 9, 9, 9, 9, 10,
				11, 12, 15, 20, 20 };

	w = 2. * M_PI / srate;
	omegaB = w * (omegaU[i] - omegaL[i]);
	omegaM = 2. * atan(sqrt(tan(w*omegaU[i]/2.) * tan(w*omegaL[i]/2.)));

	glinear = from_dB(g);
	m[i] = 2. * stack[i];
	
	k[i] = (1. / pow(glinear, (1. / (2. * m[i])))) * tan(omegaB / 2.);
	v[i] = pow(glinear, (1. / m[i])) - 1;
	a[i] = cos(omegaM);

	for (mm = 1; mm <= m[i] / 2; mm++) {
		alpham = (0.5 - (2. * mm - 1) / (2. * m[i])) * M_PI;
		cm[i][mm] = cos(alpham);
		a0m[i][mm] = 1. / (1. + 2. * k[i]*cm[i][mm] + k[i]*k[i]);
	}
}

double ZamGEQ31Plugin::run_filter(int i, double in)
{
	int j;
        double out, a1out, a2out, v1, v2;
        in = sanitize_denormal(in);
	a1out = 0.;
	a2out = 0.;
	v1 = v2 = 0.;
	out = in;

	// higher order
	for (j = 1; j <= m[i] / 2; j++) {

		// allpass 1
		a1out = a[i]*(w11[i][j] + a[i]*w12[i][j]) - w12[i][j];

		// allpass 2
		a2out = a[i]*(w21[i][j] + a[i]*w22[i][j]) - w22[i][j];

		// intermediate values
		v1 = (a2out - 2. * a1out) + k[i]*(2. * (-cm[i][j]) * a2out +
			k[i]*(2. * a1out + a2out));
		v2 = a2out + 2. * a1out;

		// 4th order output section
		out = in + v[i] * 2. * (-cm[i][j])*(-a0m[i][j]*(k[i]*in - v1) + a2out) +
			v[i]*(2. + v[i])*k[i]*(a0m[i][j]*(k[i]*in - v1) + v2);
		
		// update state
		w12[i][j] = w11[i][j] + a[i]*w12[i][j];
		w11[i][j] = a0m[i][j]*(k[i]*in - v1);
		w22[i][j] = w21[i][j] + a[i]*w22[i][j];
		w21[i][j] = a1out;
		
		in = out;
	}
	return out;
}

void ZamGEQ31Plugin::loadProgram(uint32_t index)
{
    if (index != 0)
        return;

    int i;
    /* Default parameter values */
    for (i = 0; i < 29; i++) {
    	gain[i] = 0.0f;
    }

    omegaL[0] = 28.2;
    omegaL[1] = 35.5;
    omegaL[2] = 44.7;
    omegaL[3] = 56.2;
    omegaL[4] = 70.8;
    omegaL[5] = 89.1;
    omegaL[6] = 112.;
    omegaL[7] = 141.;
    omegaL[8] = 178.;
    omegaL[9] = 224.;
    omegaL[10] = 282.;
    omegaL[11] = 355.;
    omegaL[12] = 447.;
    omegaL[13] = 562.;
    omegaL[14] = 708.;
    omegaL[15] = 891.;
    omegaL[16] = 1120.;
    omegaL[17] = 1410.;
    omegaL[18] = 1780.;
    omegaL[19] = 2240.;
    omegaL[20] = 2820.;
    omegaL[21] = 3550.;
    omegaL[22] = 4470.;
    omegaL[23] = 5620.;
    omegaL[24] = 7080.;
    omegaL[25] = 8910.;
    omegaL[26] = 11200.;
    omegaL[27] = 14100.;
    omegaL[28] = 17800.;

    omegaU[0] = 35.5;
    omegaU[1] = 44.7;
    omegaU[2] = 56.2;
    omegaU[3] = 70.8;
    omegaU[4] = 89.1;
    omegaU[5] = 112.;
    omegaU[6] = 141.;
    omegaU[7] = 178.;
    omegaU[8] = 224.;
    omegaU[9] = 282.;
    omegaU[10] = 355.;
    omegaU[11] = 447.;
    omegaU[12] = 562.;
    omegaU[13] = 708.;
    omegaU[14] = 891.;
    omegaU[15] = 1120.;
    omegaU[16] = 1410.;
    omegaU[17] = 1780.;
    omegaU[18] = 2240.;
    omegaU[19] = 2820.;
    omegaU[20] = 3550.;
    omegaU[21] = 4470.;
    omegaU[22] = 5620.;
    omegaU[23] = 7080.;
    omegaU[24] = 8910.;
    omegaU[25] = 11200.;
    omegaU[26] = 14100.;
    omegaU[27] = 17800.;
    omegaU[28] = 22400.;

    master = 0.f;
    float srate = getSampleRate();
    for (i = 0; i < 29; ++i) {
        geq(i, srate, 0.);
        gainold[i] = 0.;
    }

    /* reset filter values */
    activate();
}

// -----------------------------------------------------------------------
// Process

void ZamGEQ31Plugin::activate()
{
    int i, j;
    for (i = 0; i < 29; i++) {
        for (j = 0; j < 21; j++) {
            w11[i][j] = 0.;
            w12[i][j] = 0.;
            w21[i][j] = 0.;
            w22[i][j] = 0.;
        }
    }

}

void ZamGEQ31Plugin::run(const float** inputs, float** outputs, uint32_t frames)
{
	float srate = getSampleRate();
	
	uint32_t i, j;
	for (i = 0; i < 29; i++) {
		if (gain[i] != gainold[i]) {
			geq(i, srate, gain[i]);
			gainold[i] = gain[i];
		}
	}

	for (i = 0; i < frames; i++) {
		double tmp, filtered;
		double in = inputs[0][i];
		in = sanitize_denormal(in);
		tmp = in;
		filtered = tmp;

		for (j = 0; j < 29; j++) {
			if (gain[j] != 0.f) {
				filtered = run_filter(j, tmp);
				tmp = filtered;
			}
		}
		outputs[0][i] = inputs[0][i];
		outputs[0][i] = (float) (filtered * from_dB(master));
	}
}

Plugin* createPlugin()
{
    return new ZamGEQ31Plugin();
}

// -----------------------------------------------------------------------

END_NAMESPACE_DISTRHO
