/* Audio Library for Teensy 3.X
 * Dynamics Processor (Gate, Compressor & Limiter)
 * Copyright (c) 2018, Marc Paquette (marc@dacsystemes.com)
 * Based on analyse_rms, effect_envelope & mixer objects by Paul Stoffregen
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef effect_dynamics_h_
#define effect_dynamics_h_

#include "Arduino.h"
#include "AudioStream.h"

#define MIN_DB	-110.0f
#define MAX_DB	0.0f

#define MIN_T	0.03f  //Roughly 1 block
#define MAX_T	4.00f

#define RATIO_OFF		1.0f
#define RATIO_INFINITY	60.0f

class AudioEffectDynamics : public AudioStream
{
public:
	AudioEffectDynamics(void) : AudioStream(1, inputQueueArray) {
		
		gate();
		compression();
		limit();
		autoMakeupGain();
		
		gatedb = MIN_DB;
		compdb = MIN_DB;
		limitdb = MIN_DB;
	}

	//Sets the gate parameters.
	//threshold is in dbFS
	//attack & release are in seconds
	void gate(float threshold = -50.0f, float attack = MIN_T, float release = 0.3f, float hysterisis = 6.0f) {
		
		gateEnabled = threshold > MIN_DB;
		
		gateThresholdOpen = constrain(threshold, MIN_DB, MAX_DB);
		gateThresholdClose = gateThresholdOpen - constrain(hysterisis, 0.0f, 6.0f);
		
		float gateAttackTime = constrain(attack, MIN_T, MAX_T);
		float gateReleaseTime = constrain(release, MIN_T, MAX_T);
		
		aGateAttack = timeToAlpha(gateAttackTime);
		aOneMinusGateAttack = 1.0f - aGateAttack;
		aGateRelease = timeToAlpha(gateReleaseTime);
		aOneMinusGateRelease = 1.0f - aGateRelease;
	}

	//Sets the compression parameters.
	//threshold & kneeWidth are in db(FS)
	//attack and release are in seconds
	//ratio is expressed as x:1 i.e. 1 for no compression, 60 for brickwall limiting
	//Set kneeWidth to 0 for hard knee
	void compression(float threshold = -40.0f, float attack = MIN_T, float release = 0.5f, float ratio = 35.0f, float kneeWidth = 6.0f) {
		
		compEnabled = threshold < MAX_DB;
		
		compThreshold = constrain(threshold, MIN_DB, MAX_DB);
		float compAttackTime = constrain(attack, MIN_T, MAX_T);
		float compReleaseTime = constrain(release, MIN_T, MAX_T);
		compRatio = 1.0f / constrain(abs(ratio), RATIO_OFF, RATIO_INFINITY);
		float compKneeWidth = constrain(abs(kneeWidth), 0.0f, 32.0f);
		computeMakeupGain();
		
		aCompAttack = timeToAlpha(compAttackTime);
		aOneMinusCompAttack = 1.0f - aCompAttack;
		aCompRelease = timeToAlpha(compReleaseTime);
		aOneMinusCompRelease = 1.0f - aCompRelease;
		aHalfKneeWidth = compKneeWidth / 2.0f;
		aTwoKneeWidth = 1.0f / (compKneeWidth * 2.0f);
		aKneeRatio = compRatio - 1.0f;
		aLowKnee = compThreshold - aHalfKneeWidth;
		aHighKnee = compThreshold + aHalfKneeWidth;
	}

	//Sets the hard limiter parameters
	//threshold is in dbFS
	//attack & release are in seconds
	void limit(float threshold = -3.0f, float attack = MIN_T, float release = MIN_T) {
		
		limiterEnabled = threshold < MAX_DB;
		
		limitThreshold = constrain(threshold, MIN_DB, MAX_DB);
		float limitAttackTime = constrain(attack, MIN_T, MAX_T);
		float limitReleaseTime = constrain(release, MIN_T, MAX_T); 

		computeMakeupGain();
		
		aLimitAttack = timeToAlpha(limitAttackTime);
		aOneMinusLimitAttack = 1.0f - aLimitAttack;
		aLimitRelease = timeToAlpha(limitReleaseTime);
	}
	
	//Enables automatic makeup gain setting
	//headroom is in dbFS
	void autoMakeupGain(float headroom = 6.0f) {
		
		mgAutoEnabled = true;
		mgHeadroom = constrain(headroom, 0.0f, 60.0f);
		computeMakeupGain();
	}
	
	//Sets a fixed makeup gain value.
	//gain is in dbFS
	void makeupGain(float gain = 0.0f) {
		
		mgAutoEnabled = false;
		makeupdb = constrain(gain, -12.0f, 24.0f);
	}
	
private:
	audio_block_t *inputQueueArray[1];

	bool gateEnabled = false;
	float gateThresholdOpen;
	float gateThresholdClose;
	float gatedb;

	bool compEnabled = false;
	float compThreshold;
	float compRatio;
	float compdb;

	bool limiterEnabled = false;
	float limitThreshold;
	float limitdb;

	bool mgAutoEnabled;
	float mgHeadroom;
	float makeupdb;
	
	float aGateAttack;
	float aOneMinusGateAttack;
	float aGateRelease;
	float aOneMinusGateRelease;
	float aHalfKneeWidth;
	float aTwoKneeWidth;
	float aKneeRatio;
	float aLowKnee;
	float aHighKnee;
	float aCompAttack;
	float aOneMinusCompAttack;
	float aCompRelease;
	float aOneMinusCompRelease;
	float aLimitAttack;
	float aOneMinusLimitAttack;
	float aLimitRelease;
	
	int32_t last_mult;
	
	void computeMakeupGain() {
		if (mgAutoEnabled) {
			makeupdb = -compThreshold + (compThreshold * compRatio) + limitThreshold - mgHeadroom;
		}
	}
	
	//Computes smoothing time constants for a 10% to 90% change
	float timeToAlpha(float time) {
		return expf(-0.9542f / (((float)AUDIO_SAMPLE_RATE_EXACT / (float)AUDIO_BLOCK_SAMPLES) * time));
	}
	
	virtual void update(void);
};

#endif
