/*
 * Power Juice Plugin
 * Copyright (C) 2014 Andre Sklenar <andre.sklenar@gmail.com>, www.juicelab.cz
 *
 * 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 "PowerJuiceX2Plugin.hpp"
#include <cstdlib>
#include <algorithm> 

START_NAMESPACE_DISTRHO

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

PowerJuiceX2Plugin::PowerJuiceX2Plugin()
    : Plugin(paramCount, 1, 0) // 1 program, 0 states
{
    std::memset(&RMSStack, 0, sizeof(RMSStack));
    std::memset(&lookaheadStack, 0, sizeof(lookaheadStack));

    // set default values
    loadProgram(0);

    // reset
    deactivate();
}

PowerJuiceX2Plugin::~PowerJuiceX2Plugin()
{
	std::free(RMSStack.data);
	std::free(lookaheadStack.data);
}

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

void PowerJuiceX2Plugin::initParameter(uint32_t index, Parameter& parameter)
{
    switch (index)
    {
    case paramAttack:
        parameter.hints      = kParameterIsAutomatable;
        parameter.name       = "Attack";
        parameter.symbol     = "att";
        parameter.unit       = "ms";
        parameter.ranges.def = 20.0f;
        parameter.ranges.min = 0.1f;
        parameter.ranges.max = 1000.0f;
        break;
    case paramRelease:
        parameter.hints      = kParameterIsAutomatable;
        parameter.name       = "Release";
        parameter.symbol     = "rel";
        parameter.unit       = "ms";
        parameter.ranges.def = 200.0f;
        parameter.ranges.min = 0.1f;
        parameter.ranges.max = 1000.0f;
        break;
    case paramThreshold:
        parameter.hints      = kParameterIsAutomatable;
        parameter.name       = "Threshold";
        parameter.symbol     = "thr";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -60.0f;
        parameter.ranges.max = 0.0f;
        break;
    case paramRatio:
        parameter.hints      = kParameterIsAutomatable;
        parameter.name       = "Ratio";
        parameter.symbol     = "rat";
        parameter.unit       = "";
        parameter.ranges.def = 1.0f;
        parameter.ranges.min = 1.0f;
        parameter.ranges.max = 10.0f;
        break;
    case paramMakeup:
        parameter.hints      = kParameterIsAutomatable;
        parameter.name       = "Make-Up";
        parameter.symbol     = "mak";
        parameter.unit       = "";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = 0.0f;
        parameter.ranges.max = 20.0f;
        break;
    case paramMix:
        parameter.hints      = kParameterIsAutomatable;
        parameter.name       = "Mix";
        parameter.symbol     = "Mix";
        parameter.unit       = "";
        parameter.ranges.def = 1.0f;
        parameter.ranges.min = 0.0f;
        parameter.ranges.max = 1.0f;
        break;

    }
}

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

    programName = "Default";
}

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

float PowerJuiceX2Plugin::getParameterValue(uint32_t index) const
{
    switch (index)
    {
    case paramAttack:
        return attack;
    case paramRelease:
        return release;
    case paramThreshold:
        return threshold;
    case paramRatio:
        return ratio;
    case paramMakeup:
        return makeup;
    case paramMix:
        return mix;
    default:
        return 0.0f;
    }
}

void PowerJuiceX2Plugin::setParameterValue(uint32_t index, float value)
{
    switch (index)
    {
    case paramAttack:
        attack = value;
        attackSamples = getSampleRate()*(attack/1000.0f);
        break;
    case paramRelease:
        release = value;
        releaseSamples = getSampleRate()*(release/1000.0f);
        break;
    case paramThreshold:
        threshold = value;
        break;
    case paramRatio:
        ratio = value;
        break;
    case paramMakeup:
        makeup = value;
        makeupFloat = fromDB(makeup);
        break;
    case paramMix:
        mix = value;
        break;
    }
}

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

    /* Default parameter values */
    attack = 20.0f;
    release = 200.0f;
    threshold = 0.0f;
    ratio = 1.0f;
    makeup = 0.0f;
    mix = 1.0f;

    makeupFloat = fromDB(makeup);
    attackSamples = getSampleRate()*(attack/1000.0f);
    releaseSamples = getSampleRate()*(release/1000.0f);

	

    w = 563; //waveform plane size, size of the plane in pixels;
    w2 = 1126; //wavefowm array
    h = 121; //waveform plane height
    x = 27; //waveform plane positions
    y = 53;
    dc = 113; //0DC line y position
	
    /* Default variable values */
    averageCounter = 0;
    inputMax = 0.0f;

    balancer = 1.0f;
    GR = 1.0f;

	newRepaint = false;

    input.start = 0;
    rms.start = 0;
    gainReduction.start = 0;
    RMSStack.start = 0;
    lookaheadStack.start = 0;
    repaintSkip = 0;
    
    
    kFloatRMSStackCount = 400.0f/44100.0f*getSampleRate();
    std::free(RMSStack.data);
    RMSStack.data = (float*) calloc(kFloatRMSStackCount, sizeof(float));
    
    kFloatLookaheadStackCount = 800.0f/44100.0f*getSampleRate();
    std::free(lookaheadStack.data);
    lookaheadStack.data = (float*) calloc(kFloatLookaheadStackCount, sizeof(float));
    
    refreshSkip= 300.0f/44100.0f*getSampleRate();
    
    std::memset(rms.data, 0, sizeof(float)*kFloatStackCount);
    std::memset(gainReduction.data, 0, sizeof(float)*kFloatStackCount);
    std::memset(RMSStack.data, 0, sizeof(float)*kFloatRMSStackCount);
    std::memset(lookaheadStack.data, 0, sizeof(float)*kFloatLookaheadStackCount);
    
	for (int j=0; j < kFloatStackCount; ++j)
		history.rms[j] = h +y;
	for (int j=0; j < kFloatStackCount; ++j) 
		history.gainReduction[j] = h +y;

    activate();
    
}

float PowerJuiceX2Plugin::getRMSHistory(int n) {
    return history.rms[n];
}

bool PowerJuiceX2Plugin::repaintNeeded() {
	return newRepaint;
}

float PowerJuiceX2Plugin::getGainReductionHistory(int n) {
	if (n == kFloatStackCount-1) {
		newRepaint = false;
		//printf("falsing!\n");
	}
    return history.gainReduction[n];
}

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

void PowerJuiceX2Plugin::activate()
{
}

void PowerJuiceX2Plugin::deactivate()
{
    // all values to zero
}

void PowerJuiceX2Plugin::run(const float** inputs, float** outputs, uint32_t frames)
{
    const float* in1 = inputs[0];
    const float* in2 = inputs[1];
    float* out1 = outputs[0];
    float* out2 = outputs[1];
    float sum;
    float data;
    float difference;


    for (uint32_t i=0; i < frames; i++) {

        sum = 0.0f;
        data = 0.0f;
        difference = 0;
        //sanitizeDenormal(in1[i]); // FIXME - you cannot modify inputs
        //sanitizeDenormal(in2[i]); // FIXME - you cannot modify inputs

        /*   compute last RMS   */

        //store audio samples in an RMS buffer line
        RMSStack.data[RMSStack.start++] = std::max(in1[i], in2[i]);
	   
        if (RMSStack.start == kFloatRMSStackCount)
                RMSStack.start = 0;
        //compute RMS over last kFloatRMSStackCount samples
        for (int j=0; j < kFloatRMSStackCount; ++j) {
            data = RMSStack.data[(RMSStack.start+j) % kFloatRMSStackCount];
            sum += data * data;
        }
	   
        //root mean SQUARE
          float RMS = sqrt(sum / kFloatRMSStackCount);
        sanitizeDenormal(RMS);
		

        /*   compute gain reduction if needed   */
        float RMSDB = toDB(RMS);
	   
        if (RMSDB>threshold) {
            //attack stage
            float difference = (RMSDB-threshold);
		  
            //sanitizeDenormal(difference);
            targetGR = difference - difference/ratio;
            if (targetGR>difference/(ratio/4.0f)) {
                targetGR = difference - difference/(ratio*1.5f);
                //more power!
            }
		  //
            if (GR<targetGR) {
                //approach targetGR at attackSamples rate
                GR -= (GR-targetGR)/(attackSamples);
            } else {
                //approach targetGR at releaseSamples rate
                GR -= (GR-targetGR)/releaseSamples;
            }
		  
            sanitizeDenormal(GR);
        } else {
            //release stage
            //approach targetGR at releaseSamples rate, targetGR = 0.0f
            GR -= GR/releaseSamples;
        }
		
        //store audio in lookahead buffer
	   
        lookaheadStack.data[lookaheadStack.start++] = std::max(in1[i], in2[i]);
	   //printf("rms\n");
        if (lookaheadStack.start == kFloatLookaheadStackCount)
                lookaheadStack.start = 0;
		
        if (++averageCounter >= refreshSkip) {
		  
            //add relevant values to the shared memory
            rms.data[rms.start++] = RMSDB;
            gainReduction.data[gainReduction.start++] = GR;
		  

            //rewind stack reading heads if needed
            if (rms.start == kFloatStackCount)
                rms.start = 0;
            if (gainReduction.start == kFloatStackCount)
                gainReduction.start = 0;


            //saving in gfx format, for speed
            //share memory

            for (int j=0; j < kFloatStackCount; ++j)
                history.rms[j] = -toIEC(rms.data[(rms.start+j) % kFloatStackCount])/200*h +h +y;
            for (int j=0; j < kFloatStackCount; ++j) {
                history.gainReduction[j] = -toIEC(-gainReduction.data[(gainReduction.start+j) % kFloatStackCount])/200*h +h +y;
			
		  }
			
			repaintSkip++;
			if (repaintSkip>5) {
				repaintSkip = 0;
				newRepaint = true;
			}
			
            averageCounter = 0;
            inputMax = 0.0f;
        }

        /* compress, mix, done. */
	   float realGR = fromDB(-GR);
        float compressedSignal1 = in1[i]*realGR;
	   float compressedSignal2 = in2[i]*realGR;
        out1[i] = (compressedSignal1*makeupFloat*mix)+in1[i]*(1-mix);
	   out2[i] = (compressedSignal2*makeupFloat*mix)+in2[i]*(1-mix);
    }
}

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

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

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

END_NAMESPACE_DISTRHO
