/*
  rakarrack - a guitar effects software

 Compressor.C  -  Compressor Effect
 Based on artscompressor.cc by Matthias Kretz <kretz@kde.org>
 Stefan Westerfeld <stefan@space.twc.de>

  Copyright (C) 2008-2010 Josep Andreu
  Author: Josep Andreu

        Patches:
        September 2009  Ryan Billing (a.k.a. Transmogrifox)
                --Modified DSP code to fix discontinuous gain change at threshold.
                --Improved automatic gain adjustment function
                --Improved handling of knee
                --Added support for user-adjustable knee
                --See inline comments

 This program is free software; you can redistribute it and/or modify
 it under the terms of version 2 of the GNU General Public License
 as published by the Free Software Foundation.

 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 (version 2) for more details.

 You should have received a copy of the GNU General Public License
 (version2)  along with this program; if not, write to the Free Software
 Foundation,
 Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

*/

#include "RRCompressor.h"

#include <math.h>

#define MIN_GAIN 0.00001f  // -100dB  This will help prevent evaluation of denormal numbers

RRCompressor::RRCompressor() {
    // RRCompressor::RRCompressor(float *efxoutl_, float *efxoutr_) {
    // efxoutl = efxoutl_;
    // efxoutr = efxoutr_;

    rvolume    = 0.0f;
    rvolume_db = 0.0f;
    lvolume    = 0.0f;
    lvolume_db = 0.0f;
    tthreshold = -24;
    tratio     = 4;
    toutput    = -10;
    tatt       = 20;
    trel       = 50;
    a_out      = 1;
    stereo     = 0;
    tknee      = 30;
    rgain      = 1.0f;
    rgain_old  = 1.0f;
    lgain      = 1.0f;
    lgain_old  = 1.0f;
    lgain_t    = 1.0f;
    rgain_t    = 1.0f;
    ratio      = 1.0;
    kpct       = 0.0f;
    peak       = 0;
    lpeak      = 0.0f;
    rpeak      = 0.0f;
    rell = relr = attr = attl = 1.0f;

    ltimer = rtimer = 0;
    hold            = (int)(SAMPLE_RATE * 0.0125);  // 12.5ms
    clipping        = 0;
    limit           = 0;
}

RRCompressor::~RRCompressor() {
}

void RRCompressor::cleanup() {
    lgain = rgain = 1.0f;
    lgain_old = rgain_old = 1.0f;
    rpeak                 = 0.0f;
    lpeak                 = 0.0f;
    limit                 = 0;
    clipping              = 0;
}

void RRCompressor::Compressor_Change(int np, int value) {
    switch (np) {
        case PARAM_THRESHOLD:
            tthreshold = value;
            thres_db   = (float)tthreshold;
            break;
        case PARAM_RATIO:
            tratio = value;
            ratio  = (float)tratio;
            break;
        case PARAM_OUTPUT:
            toutput = value;
            break;
        case PARAM_ATT:
            tatt = value;
            att  = cSAMPLE_RATE / (((float)value / 1000.0f) + cSAMPLE_RATE);
            attr = att;
            attl = att;
            break;
        case PARAM_REL:
            trel = value;
            rel  = cSAMPLE_RATE / (((float)value / 1000.0f) + cSAMPLE_RATE);
            rell = rel;
            relr = rel;
            break;
        case PARAM_A_OUT:
            a_out = value;
            break;
        case PARAM_KNEE:
            tknee = value;  // knee expressed a percentage of range between thresh and zero dB
            kpct  = (float)tknee / 100.1f;
            break;
        case PARAM_STEREO:
            stereo = value;
            break;
        case PARAM_PEAK:
            peak = value;
            break;
    }

    kratio = logf(ratio) / LOG_2;  //  Log base 2 relationship matches slope
    knee   = -kpct * thres_db;

    coeff_kratio = 1.0 / kratio;
    coeff_ratio  = 1.0 / ratio;
    coeff_knee   = 1.0 / knee;

    coeff_kk = knee * coeff_kratio;

    thres_mx  = thres_db + knee;  // This is the value of the input when the output is at t+k
    makeup    = -thres_db - knee / kratio + thres_mx / ratio;
    makeuplin = dB2rap(makeup);
    if (a_out)
        outlevel = dB2rap((float)toutput) * makeuplin;
    else
        outlevel = dB2rap((float)toutput);
}

int RRCompressor::getpar(int np) {
    switch (np) {
        case PARAM_THRESHOLD:
            return (tthreshold);
            break;
        case PARAM_RATIO:
            return (tratio);
            break;
        case PARAM_OUTPUT:
            return (toutput);
            break;
        case PARAM_ATT:
            return (tatt);
            break;
        case PARAM_REL:
            return (trel);
            break;
        case PARAM_A_OUT:
            return (a_out);
            break;
        case PARAM_KNEE:
            return (tknee);
            break;
        case PARAM_STEREO:
            return (stereo);
            break;
        case PARAM_PEAK:
            return (peak);
            break;
    }

    return (0);
}

void RRCompressor::Compressor_Change_Preset(int npreset) {
    // void RRCompressor::Compressor_Change_Preset(int dgui, int npreset) {
    const int PRESET_SIZE                       = 10;
    const int NUM_PRESETS                       = 7;
    int       presets[NUM_PRESETS][PRESET_SIZE] = {
        // 2:1
        {-30, 2, -6, 20, 120, 1, 0, 0, 0},
        // 4:1
        {-26, 4, -8, 20, 120, 1, 10, 0, 0},
        // 8:1
        {-24, 8, -12, 20, 35, 1, 30, 0, 0},
        // Final Limiter
        {-1, 15, 0, 5, 250, 0, 0, 1, 1},
        // HarmonicEnhancer
        {-20, 15, -3, 5, 50, 0, 0, 1, 1},
        // Band CompBand
        {-3, 2, 0, 5, 50, 1, 0, 1, 0},
        // End CompBand
        {-60, 2, 0, 10, 500, 1, 0, 1, 1},

    };

    // if ((dgui) && (npreset > 2)) {
    //     Fpre->ReadPreset(1, npreset - 2);
    //     for (int n = 1; n < PRESET_SIZE; n++)
    //         Compressor_Change(n, pdata[n - 1]);

    // } else {
    for (int n = 0; n < PRESET_SIZE; n++) {
        Compressor_Change(n, presets[npreset][n]);
    }
    // }
}

void RRCompressor::out(float *efxoutl, float *efxoutr) {
    int i;

    for (i = 0; i < PERIOD; i++) {
        float rdelta = 0.0f;
        float ldelta = 0.0f;

        // Right Channel
        if (peak) {
            if (rtimer > hold) {
                rpeak *= 0.9998f;  // The magic number corresponds to ~0.1s based on T/(RC + T),
                rtimer--;
            }
            if (ltimer > hold) {
                lpeak *= 0.9998f;  // leaky peak detector.
                ltimer--;          // keeps the timer from eventually exceeding max int & rolling over
            }
            ltimer++;
            rtimer++;
            if (rpeak < fabs(efxoutr[i])) {
                rpeak  = fabs(efxoutr[i]);
                rtimer = 0;
            }
            if (lpeak < fabs(efxoutl[i])) {
                lpeak  = fabs(efxoutl[i]);
                ltimer = 0;
            }
            if (lpeak > 20.0f) {
                lpeak = 20.0f;
            }
            if (rpeak > 20.0f) {
                rpeak = 20.0f;  // keeps limiter from getting locked up when signal levels go way out of bounds (like hundreds)
            }
        } else {
            rpeak = efxoutr[i];
            lpeak = efxoutl[i];
        }

        if (stereo) {
            rdelta = fabsf(rpeak);
            if (rvolume < 0.9f) {
                attr = att;
                relr = rel;
            } else if (rvolume < 1.0f) {
                attr = att + ((1.0f - att) * (rvolume - 0.9f) * 10.0f);  // dynamically change attack time for limiting mode
                relr = rel / (1.0f + (rvolume - 0.9f) * 9.0f);           // release time gets longer when signal is above limiting
            } else {
                attr = 1.0f;
                relr = rel * 0.1f;
            }

            if (rdelta > rvolume)
                rvolume = attr * rdelta + (1.0f - attr) * rvolume;
            else
                rvolume = relr * rdelta + (1.0f - relr) * rvolume;

            rvolume_db = rap2dB(rvolume);
            if (rvolume_db < thres_db) {
                rgain = outlevel;
            } else if (rvolume_db < thres_mx) {
                // Dynamic ratio that depends on volume.  As can be seen, ratio starts
                // at something negligibly larger than 1 once volume exceeds thres, and increases toward selected
                //  ratio by the time it has reached thres_mx.  --Transmogrifox

                eratio = 1.0f + (kratio - 1.0f) * (rvolume_db - thres_db) * coeff_knee;
                rgain  = outlevel * dB2rap(thres_db + (rvolume_db - thres_db) / eratio - rvolume_db);
            } else {
                rgain = outlevel * dB2rap(thres_db + coeff_kk + (rvolume_db - thres_mx) * coeff_ratio - rvolume_db);
                limit = 1;
            }

            if (rgain < MIN_GAIN)
                rgain = MIN_GAIN;
            rgain_t = .4f * rgain + .6f * rgain_old;
        };

        // Left Channel
        if (stereo) {
            ldelta = fabsf(lpeak);
        } else {
            ldelta = 0.5f * (fabsf(lpeak) + fabsf(rpeak));
        };  // It's not as efficient to check twice, but it's small expense worth code clarity

        if (lvolume < 0.9f) {
            attl = att;
            rell = rel;
        } else if (lvolume < 1.0f) {
            attl = att + ((1.0f - att) * (lvolume - 0.9f) * 10.0f);  // dynamically change attack time for limiting mode
            rell = rel / (1.0f + (lvolume - 0.9f) * 9.0f);           // release time gets longer when signal is above limiting
        } else {
            attl = 1.0f;
            rell = rel * 0.1f;
        }

        if (ldelta > lvolume)
            lvolume = attl * ldelta + (1.0f - attl) * lvolume;
        else
            lvolume = rell * ldelta + (1.0f - rell) * lvolume;

        lvolume_db = rap2dB(lvolume);

        if (lvolume_db < thres_db) {
            lgain = outlevel;
        } else if (lvolume_db < thres_mx)  // knee region
        {
            eratio = 1.0f + (kratio - 1.0f) * (lvolume_db - thres_db) * coeff_knee;
            lgain  = outlevel * dB2rap(thres_db + (lvolume_db - thres_db) / eratio - lvolume_db);
        } else {
            lgain = outlevel * dB2rap(thres_db + coeff_kk + (lvolume_db - thres_mx) * coeff_ratio - lvolume_db);
            limit = 1;
        }

        if (lgain < MIN_GAIN)
            lgain = MIN_GAIN;
        lgain_t = .4f * lgain + .6f * lgain_old;

        if (stereo) {
            efxoutl[i] *= lgain_t;
            efxoutr[i] *= rgain_t;
            rgain_old = rgain;
            lgain_old = lgain;
        } else {
            efxoutl[i] *= lgain_t;
            efxoutr[i] *= lgain_t;
            lgain_old = lgain;
        }

        if (peak) {
            if (efxoutl[i] > 0.999f) {  // output hard limiting
                efxoutl[i] = 0.999f;
                clipping   = 1;
            }
            if (efxoutl[i] < -0.999f) {
                efxoutl[i] = -0.999f;
                clipping   = 1;
            }
            if (efxoutr[i] > 0.999f) {
                efxoutr[i] = 0.999f;
                clipping   = 1;
            }
            if (efxoutr[i] < -0.999f) {
                efxoutr[i] = -0.999f;
                clipping   = 1;
            }
            // highly probably there is a more elegant way to do that, but what the hey...
        }
    }
}