/*	dexp.c

This file is part of a program that implements a Software-Defined Radio.

Copyright (C) 2018, 2019 Warren Pratt, NR0V

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.

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.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

The author can be reached by email at

warren@wpratt.com

*/

#include "comm.h"

DEXP pdexp[4];

DELRING calc_delring (int rsize, int size, int delay, double* in, double* out)
{
	DELRING a = (DELRING) malloc0 (sizeof (delring));
	a->rsize = rsize;
	a->size = size;
	a->rdelay = delay;
	a->in = in;
	a->out = out;
	a->ring = (double *) malloc0 (a->rsize * sizeof (complex));
	a->inptr = a->rdelay;
	a->outptr = 0;
	return a;
}

void decalc_delring (DELRING a)
{
	_aligned_free (a->ring);
	_aligned_free (a);
}

void flush_delring (DELRING a)
{
	memset (a->ring, 0, a->rsize * sizeof (complex));
	a->inptr = a->rdelay;
	a->outptr = 0;
}

void xdelring (DELRING a)
{
	int first, second;
	// copy in
	if (a->size > (a->rsize - a->inptr))
	{
		first = a->rsize - a->inptr;
		second = a->size - first;
	}
	else
	{
		first = a->size;
		second = 0;
	}
	memcpy (a->ring + 2 * a->inptr, a->in, first * sizeof (complex));
	memcpy (a->ring, a->in + 2 * first, second * sizeof (complex));
	a->inptr = (a->inptr + a->size) % a->rsize;
	// copy out
	if (a->size > (a->rsize - a->outptr))
	{
		first = a->rsize - a->outptr;
		second = a->size - first;
	}
	else
	{
		first = a->size;
		second = 0;
	}
	memcpy (a->out, a->ring + 2 * a->outptr, first * sizeof (complex));
	memcpy (a->out + 2 * first, a->ring, second * sizeof (complex));
	a->outptr = (a->outptr + a->size) % a->rsize;
}

void calc_slews (DEXP a)
{
	int i;
	double delta, theta;
	delta = PI / (double)a->nattack;
	theta = 0.0;
	for (i = 0; i <= a->nattack; i++)
	{
		a->cattack[i] = a->low_gain + (1.0 - a->low_gain) * 0.5 * (1.0 - cos (theta));
		theta += delta;
	}
	delta = PI / (double)a->ndecay;
	theta = 0.0;
	for (i = 0; i <= a->ndecay; i++)
	{
		a->cdecay[i] = a->low_gain + (1.0 - a->low_gain) * 0.5 * (1.0 + cos (theta));
		theta += delta;
	}
}

void calc_buffs (DEXP a)
{
	a->trigsig	 = (double *)malloc0 (2 * a->size * sizeof(complex));	// allow for double-sized output of filter
	a->delsig	 = (double *)malloc0 (	  a->size * sizeof(complex));
	a->audbuffer = (double *)malloc0 (	  a->size * sizeof(complex));
}

void decalc_buffs (DEXP a)
{
	_aligned_free (a->audbuffer);
	_aligned_free (a->delsig);
	_aligned_free (a->trigsig);
}

void calc_dexp (DEXP a)
{
	// trigger signal preparation
	a->avm = exp(-1.0 / (a->rate * a->dettau));
	a->onem_avm = 1.0 - a->avm;
	a->avsig = 0.0;
	// level change
	a->nattack = (int)(a->tattack * a->rate);
	a->ndecay = (int)(a->tdecay * a->rate);
	a->cattack = (double *)malloc0((a->nattack + 1) * sizeof(double));
	a->cdecay = (double *)malloc0((a->ndecay + 1) * sizeof(double));
	a->low_gain = 1.0 / a->exp_ratio;
	calc_slews(a);
	// control
	a->state = 0;
	a->count = 0;
	a->hold_thresh = a->hysteresis_ratio * a->attack_thresh;	// hysteresis ratio < 1.0
	a->nhold = (int)(a->thold * a->rate);
	// vox
	a->vox_count = (int)(a->audelay * a->rate);
	// audio delay
	a->audring = calc_delring ((int)a->rate, a->size, (int)(a->audelay * a->rate), a->audbuffer, a->out);
}

void decalc_dexp (DEXP a)
{
	decalc_delring (a->audring);
	_aligned_free (a->cdecay);
	_aligned_free (a->cattack);
}

void calc_filter (DEXP a)
{
	double* impulse;
	// 2.0 gain on filter is somewhat arbitrarily chosen to get trigger input similar to that without the filter, knowing
	//	  that for any reasonable use of the filter there will be a reduction in trigger signal.
	impulse = fir_bandpass (a->nc, a->low_cut, a->high_cut, a->rate, a->wintype, 1, 2.0/(double)(2 * a->size));
	// print_impulse ("scf.txt", a->nc, impulse, 1, 0);
	a->p = create_fircore (a->size, a->in, a->trigsig, a->nc, 1, impulse);
	_aligned_free (impulse);
	a->scdring = calc_delring (a->size + a->nc / 2, a->size, a->nc / 64, a->in, a->delsig);
}

void decalc_filter (DEXP a)
{
	destroy_fircore (a->p);
	decalc_delring (a->scdring);
}

void calc_antivox(DEXP a)
{
	a->antivox_mult = exp(-1.0 / (a->antivox_rate * a->antivox_tau));
	a->antivox_onemmult = 1.0 - a->antivox_mult;
	a->antivox_data = (double *) malloc0 (a->antivox_size * sizeof (complex));
}

void decalc_antivox(DEXP a)
{
	_aligned_free (a->antivox_data);
}

PORT
void create_dexp (int id, int run_dexp, int size, double* in, double* out, int rate, double dettau, double tattack, double tdecay,
	double thold, double exp_ratio, double hyst_ratio, double attack_thresh, int nc, int wtype, double lowcut, double highcut,
	int run_filt, int run_vox, int run_audelay, double audelay, void (__stdcall *pushvox)(int id, int active),
	int antivox_run, int antivox_size, int antivox_rate, double antivox_gain, double antivox_tau)
{
	DEXP a = (DEXP) malloc0 (sizeof (dexp));
	a->id = id;
	a->run_dexp = run_dexp;
	a->size = size;
	a->in = in;
	a->out = out;
	a->rate = (double)rate;
	a->dettau = dettau;
	a->tattack = tattack;
	a->tdecay = tdecay;
	a->thold = thold;
	a->exp_ratio = exp_ratio;
	a->hysteresis_ratio = hyst_ratio;
	a->attack_thresh = attack_thresh;
	a->nc = nc;
	a->wintype = wtype;
	a->low_cut = lowcut;
	a->high_cut = highcut;
	a->run_filt = run_filt;
	a->run_vox = run_vox;
	a->run_audelay = run_audelay;
	a->audelay = audelay;
	a->pushvox = pushvox;
	a->antivox_run = antivox_run;
	a->antivox_size = antivox_size;
	a->antivox_rate = (double)antivox_rate;
	a->antivox_gain = antivox_gain;
	a->antivox_tau = antivox_tau;
	calc_buffs (a);
	calc_dexp (a);
	calc_filter (a);
	calc_antivox (a);
	InitializeCriticalSectionAndSpinCount(&a->cs_update, 2500);
	pdexp[id] = a;
	return;
}

PORT
void destroy_dexp (int id)
{
	DEXP a = pdexp[id];
	DeleteCriticalSection (&a->cs_update);
	decalc_antivox (a);
	decalc_filter (a);
	decalc_dexp (a);
	decalc_buffs (a);
	_aligned_free (a);
}

PORT
void flush_dexp (int id)
{
	DEXP a = pdexp[id];
	memset (a->audbuffer, 0, a->size * sizeof (complex));
	memset (a->trigsig, 0, a->size * sizeof (complex));
	memset (a->delsig,	0, a->size * sizeof (complex));
	a->avsig = 0.0;
	a->state = 0;
	a->count = 0;
	flush_fircore (a->p);
	flush_delring (a->scdring);
	flush_delring (a->audring);
}

enum _dexpstate
{
	DEXP_LOW,
	DEXP_ATTACK,
	DEXP_HIGH,
	DEXP_HOLD,
	DEXP_DECAY
};

PORT
void xdexp (int id)
{
	DEXP a = pdexp[id];
	int i;
	double sig, gain, asig;
	double max = 0.0;
	EnterCriticalSection (&a->cs_update);

	// ******* BEGIN SIDE-CHANNEL FILTER *******
	if (a->run_filt)
	{
		xdelring (a->scdring);		// input is 'a->in'; output is 'a->delsig'
		xfircore (a->p);			// input is 'a->in'; output is 'a->trigsig'
	}
	else
	{
		memcpy (a->delsig,	a->in, a->size * sizeof (complex));
		memcpy (a->trigsig, a->in, a->size * sizeof (complex));
	}
	// ******* END SIDE-CHANNEL FILTER *******

	// ******* CALCULATE ANTIVOX LEVEL *******
	if (a->state == DEXP_LOW && a->antivox_new != 0)
	{
		// if VOX is currently NOT triggered, and, if we have new antivox data to process
		for (i = 0; i < a->antivox_size; i++)
		{
			sig = sqrt (a->antivox_data[2 * i + 0] * a->antivox_data[2 * i + 0] + a->antivox_data[2 * i + 1] * a->antivox_data[2 * i + 1]);
			a->antivox_level = a->antivox_mult * a->antivox_level + a->antivox_onemmult * sig;
		}
		// set the new_data flag to zero
		a->antivox_new = 0;
	}
	// ******* END CALCULATE ANTIVOX LEVEL *******

	// ******* BEGIN DEXP *******
	// uses 'a->trigsig' as trigger signal; uses 'a->delsig' as audio input
	// 'a->audbuffer' is audio output
	// DEXP code runs continuously so it can be used to trigger VOX also.
	for (i = 0; i < a->size; i++)
	{
		sig = sqrt (a->trigsig[2 * i + 0] * a->trigsig[2 * i + 0] + a->trigsig[2 * i + 1] * a->trigsig[2 * i + 1]);
		a->avsig = a->avm * a->avsig + a->onem_avm * sig;
		if (a->avsig > max)	 max = a->avsig;
		switch (a->state)
		{
		case DEXP_LOW:
			if (a->antivox_run)
				asig = a->avsig - a->antivox_gain * a->antivox_level;
			else
				asig = a->avsig;
			if (asig > a->attack_thresh)
			{
				a->state = DEXP_ATTACK;
				a->count = a->nattack;
			}
			a->audbuffer[2 * i + 0] = a->low_gain * a->delsig[2 * i + 0];
			a->audbuffer[2 * i + 1] = a->low_gain * a->delsig[2 * i + 1];

			// ******* BEGIN VOX *******
			// If we're going to attack, turn on VOX immediately.
			// Prepare 'vox_count' for the next turnoff too.
			if (a->run_vox && a->state == DEXP_ATTACK)
			{
				(a->pushvox)(a->id, 1);
				// Set vox_count for delay IF the audio delay is also enabled.
				if (a->run_audelay)
					a->vox_count = (int)(a->audelay * a->rate);
				else
					a->vox_count = 1;
			}
			// If we're sitting in this state and the delayed vox count expires, turn OFF VOX.
			else if (a->run_vox && --(a->vox_count) == 0)
				(a->pushvox)(a->id, 0);
			// Don't let 'vox_count' keep counting down.
			else if (a->vox_count < 0)
				a->vox_count = 0;
			// ******* END VOX *******

			break;
		case DEXP_ATTACK:
			gain = a->cattack[a->nattack - a->count];
			a->audbuffer[2 * i + 0] = a->delsig[2 * i + 0] * gain;
			a->audbuffer[2 * i + 1] = a->delsig[2 * i + 1] * gain;
			if (a->count-- == 0)
				a->state = DEXP_HIGH;
			break;
		case DEXP_HIGH:
			if (a->avsig < a->hold_thresh)
			{
				a->state = DEXP_HOLD;
				a->count = a->nhold;
			}
			a->audbuffer[2 * i + 0] = a->delsig[2 * i + 0];
			a->audbuffer[2 * i + 1] = a->delsig[2 * i + 1];
			break;
		case DEXP_HOLD:
			a->audbuffer[2 * i + 0] = a->delsig[2 * i + 0];
			a->audbuffer[2 * i + 1] = a->delsig[2 * i + 1];
			if (a->avsig > a->attack_thresh)
				a->state = DEXP_HIGH;
			else if (a->count-- == 0)
			{
				a->state = DEXP_DECAY;
				a->count = a->ndecay;
			}
			break;
		case DEXP_DECAY:
			gain = a->cdecay[a->ndecay - a->count];
			a->audbuffer[2 * i + 0] = a->delsig[2 * i + 0] * gain;
			a->audbuffer[2 * i + 1] = a->delsig[2 * i + 1] * gain;
			if (a->count-- == 0)
				a->state = DEXP_LOW;
			break;
		}
	}
	a->peak = max;
	// If DEXP functionality is set to OFF, copy its input to overwrite its output.
	if (!a->run_dexp)
		memcpy (a->audbuffer, a->delsig, a->size * sizeof (complex));
	// ******* END DEXP *******

	// ******* BEGIN AUDIO DELAY *******
	if (a->run_audelay)
		xdelring (a->audring);		// uses 'a->audbuffer' as audio input; uses 'a->out' as audio output
	else
		memcpy (a->out, a->audbuffer, a->size * sizeof (complex));
	// ******* END AUDIO DELAY *******

	LeaveCriticalSection (&a->cs_update);
}

PORT
void SendCBPushDexpVox (int id, void (__stdcall *pushvox)(int id, int active))
{
	// Call to set the address of the callback to operate VOX.
	DEXP a = pdexp[id];
	a->pushvox = pushvox;
}

PORT
void SetDEXPRun (int id, int run)
{
	// run != 0, puts dexp in the audio processing path; otherwise, it's only used to trigger VOX
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	a->run_dexp = run;
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPSize (int id, int size)
{
	// There are some constraints on the input/output buffer sizes.
	//	 * must be a power-of-two
	//	 * must be less than or equal to 'nc', the number of filter coefficients, which is also a power-of-two
	//	 * must be less than 'rate' samples because of the sizing of 'audring'
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_filter (a);
	decalc_dexp (a);
	decalc_buffs (a);
	a->size = size;
	calc_buffs (a);
	calc_dexp (a);
	calc_filter (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPIOBuffers (int id, double* in, double* out)
{
	// Sets the input/output buffers.  They can be the same.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_filter (a);
	decalc_dexp (a);
	a->in = in;
	a->out = out;
	calc_dexp (a);
	calc_filter (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPRate (int id, double rate)
{
	// Sets the sample rate.
	// This is used for timing and filter calculations as well as sizing 'audring'.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_filter (a);
	decalc_dexp (a);
	a->rate = rate;
	calc_dexp (a);
	calc_filter (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPDetectorTau (int id, double tau)
{
	// Time-constant for smoothing the signal for detection (seconds).
	// 0.01 seconds is a good starting point to try.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_dexp (a);
	a->dettau = tau;
	calc_dexp (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPAttackTime (int id, double time)
{
	// Set attack time, seconds.
	// 0.002 - 0.100 should be a good range.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_dexp (a);
	a->tattack = time;
	calc_dexp (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPReleaseTime (int id, double time)
{
	// Set release time, seconds.
	// 0.002 - 0.999 should be a good range.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_dexp (a);
	a->tdecay = time;
	calc_dexp (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPHoldTime (int id, double time)
{
	// Set hold time, seconds.
	// 0.000 - 2.000 should be a good range.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_dexp (a);
	a->thold = time;
	calc_dexp (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPExpansionRatio (int id, double ratio)
{
	// Set expansion ratio.	 High_gain = 1.0; Low_gain = 1.0/exp_ratio.
	// Range of 1.0 - 30.0 should be good.	Could use dB:  0.0 - 30.0dB.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_dexp (a);
	a->exp_ratio = ratio;
	calc_dexp (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPHysteresisRatio (int id, double ratio)
{
	// Set Hysteresis Ratio.  Hold_thresh = hysteresis_ratio * Attack_thresh.
	// Expose to operator in dB:  0.0dB - 9.9dB should be good (1.000 - 0.320).
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_dexp (a);
	a->hysteresis_ratio = ratio;
	calc_dexp (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPAttackThreshold (int id, double thresh)
{
	// Set attack threshold.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_dexp (a);
	a->attack_thresh = thresh;
	calc_dexp (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPFilterTaps (int id, int taps)
{
	// Set number of taps.	Must be a power of two and an even multiple of 'size'.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_filter (a);
	a->nc = taps;
	calc_filter (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPWindowType (int id, int type)
{
	// Set filter window type.
	//	 * 0 - 4-term Blackman-Harris.
	//	 * 1 - 7-term Blackman-Harris.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_filter (a);
	a->wintype = type;
	calc_filter (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPLowCut (int id, double lowcut)
{
	// Set side-channel filter low_cut (Hertz).
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_filter (a);
	a->low_cut = lowcut;
	calc_filter (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPHighCut (int id, double highcut)
{
	// Set side-channel filter high_cut (Hertz).
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_filter (a);
	a->high_cut = highcut;
	calc_filter (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPRunSideChannelFilter (int id, int run)
{
	// Turn OFF/ON the side-channel filter and its compensating delay.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	a->run_filt = run;
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPRunVox (int id, int run)
{
	// Turn OFF/ON calls to 'pushvox(...)'.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	a->run_vox = run;
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPRunAudioDelay (int id, int run)
{
	// Turn OFF/ON audio delay line.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	a->run_audelay = run;
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetDEXPAudioDelay (int id, double delay)
{
	// Set the audio delay, seconds.
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_dexp (a);
	a->audelay = delay;
	calc_dexp (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void GetDEXPPeakSignal (int id, double* peak)
{
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	*peak = a->peak;
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetAntiVOXRun (int id, int run)
{
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	a->antivox_run = run;
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetAntiVOXSize (int id, int size)
{
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_antivox(a);
	a->antivox_size = size;
	calc_antivox(a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetAntiVOXRate (int id, double rate)
{
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_antivox(a);
	a->antivox_rate = rate;
	calc_antivox(a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetAntiVOXGain (int id, double gain)
{
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	a->antivox_gain = gain;
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SetAntiVOXDetectorTau (int id, double tau)
{
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	decalc_antivox (a);
	a->antivox_tau = tau;
	calc_antivox (a);
	LeaveCriticalSection (&a->cs_update);
}

PORT
void SendAntiVOXData (int id, int nsamples, double* data)
{
	// note:  'nsamples' is not used as it has been previously specified
	DEXP a = pdexp[id];
	EnterCriticalSection (&a->cs_update);
	memcpy (a->antivox_data, data, a->antivox_size * sizeof (complex));
	a->antivox_new = 1;
	LeaveCriticalSection (&a->cs_update);
}
