/*	calcc.c

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

Copyright (C) 2013, 2014, 2016, 2019, 2023 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

*/

#define _CRT_SECURE_NO_WARNINGS
#include "comm.h"

void size_calcc (CALCC a)
{	// for change in ints or spi
	int i;
	a->nsamps = a->ints * a->spi;

	a->tsamps = a->nsamps + a->npsamps;
	a->env_TX = (double*)malloc0(a->nsamps * sizeof(double));
	a->env_RX = (double*)malloc0(a->nsamps * sizeof(double));
	a->x = (double*)malloc0(a->tsamps * sizeof(double));
	a->ym = (double*)malloc0(a->tsamps * sizeof(double));
	a->yc = (double*)malloc0(a->tsamps * sizeof(double));
	a->ys = (double*)malloc0(a->tsamps * sizeof(double));
	a->cat = (double*)malloc0(4 * a->nsamps * sizeof(double));

	a->t	= (double *) malloc0 ((a->ints + 1) * sizeof(double));
	a->tmap = (double *) malloc0 ((a->ints + 1) * sizeof(double));
	for (i = 0; i < a->ints + 1; i++)
		a->t[i] = (double)i / (double)a->ints;

	a->cm = (double *) malloc0 (a->ints * 4 * sizeof(double));
	a->cc = (double *) malloc0 (a->ints * 4 * sizeof(double));
	a->cs = (double *) malloc0 (a->ints * 4 * sizeof(double));
	a->cm_old = (double *) malloc0 (a->ints * 4 * sizeof (double));

	a->rxs = (double *) malloc0 (a->nsamps * sizeof (complex));
	a->txs = (double *) malloc0 (a->nsamps * sizeof (complex));

	a->ccbld = create_builder(a->nsamps + a->npsamps, a->ints);

	a->ctrl.cpi = (int *) malloc0 (a->ints * sizeof (int));
	a->ctrl.sindex = (int *) malloc0 (a->ints * sizeof (int));
	a->ctrl.sbase = (int *) malloc0 (a->ints * sizeof (int));

	for (i = 0; i < a->ints; i++)
	{
		a->ctrl.cpi[i] = 0;
		a->ctrl.sindex[i] = 0;
		a->ctrl.sbase[i] = i * a->spi;
	}

	a->disp.x  = (double *) malloc0 (a->nsamps * sizeof (double));
	a->disp.ym = (double *) malloc0 (a->nsamps * sizeof (double));
	a->disp.yc = (double *) malloc0 (a->nsamps * sizeof (double));
	a->disp.ys = (double *) malloc0 (a->nsamps * sizeof (double));
	a->disp.cm = (double *) malloc0 (a->ints * 4 * sizeof(double));
	a->disp.cc = (double *) malloc0 (a->ints * 4 * sizeof(double));
	a->disp.cs = (double *) malloc0 (a->ints * 4 * sizeof(double));

	a->util.pm = (double *) malloc0 (4 * a->util.ints * sizeof(double));
	a->util.pc = (double *) malloc0 (4 * a->util.ints * sizeof(double));
	a->util.ps = (double *) malloc0 (4 * a->util.ints * sizeof(double));
}

void desize_calcc (CALCC a)
{
	_aligned_free(a->util.pm);
	_aligned_free(a->util.pc);
	_aligned_free(a->util.ps);

	_aligned_free (a->disp.cs);
	_aligned_free (a->disp.cc);
	_aligned_free (a->disp.cm);
	_aligned_free (a->disp.ys);
	_aligned_free (a->disp.yc);
	_aligned_free (a->disp.ym);
	_aligned_free (a->disp.x);

	_aligned_free (a->ctrl.sbase);
	_aligned_free (a->ctrl.sindex);
	_aligned_free (a->ctrl.cpi);
	destroy_builder(a->ccbld);
	_aligned_free (a->rxs);
	_aligned_free (a->txs);
	_aligned_free (a->cm_old);
	_aligned_free (a->cm);
	_aligned_free (a->cc);
	_aligned_free (a->cs);
	_aligned_free (a->tmap);
	_aligned_free (a->t);

	_aligned_free(a->cat);
	_aligned_free(a->x);
	_aligned_free(a->ym);
	_aligned_free(a->yc);
	_aligned_free(a->ys);
	_aligned_free(a->env_TX);
	_aligned_free(a->env_RX);
}

CALCC create_calcc (int channel, int runcal, int size, int rate, int ints, int spi, double hw_scale,
	double moxdelay, double loopdelay, double ptol, int mox, int solidmox, int pin, int map, int stbl,
	int npsamps, double alpha)
{
	CALCC a = (CALCC) malloc0 (sizeof (calcc));
	a->channel = channel;
	a->runcal = runcal;
	a->size = size;
	a->rate = rate;
	a->ints = ints;
	a->spi = spi;
	a->hw_scale = hw_scale;
	a->ctrl.moxdelay = moxdelay;
	a->ctrl.loopdelay = loopdelay;
	a->ptol = ptol;
	a->mox = mox;
	a->solidmox = solidmox;
	a->pin = pin;
	a->map = map;
	a->stbl = stbl;
	a->npsamps = npsamps;
	a->alpha = alpha;

	a->info	 = (int *) malloc0 (16 * sizeof (int));
	a->binfo = (int *) malloc0 (16 * sizeof (int));

	a->ctrl.state = 0;
	a->ctrl.reset = 0;
	a->ctrl.automode = 0;
	a->ctrl.mancal = 0;
	a->ctrl.turnon = 0;
	a->ctrl.moxsamps = (int)(a->rate * a->ctrl.moxdelay);
	a->ctrl.moxcount = 0;
	a->ctrl.count = 0;
	a->ctrl.full_ints = 0;
	a->ctrl.calcinprogress = 0;
	a->ctrl.calcdone = 0;
	a->ctrl.waitsamps = (int)(a->rate * a->ctrl.loopdelay);
	a->ctrl.waitcount = 0;
	a->ctrl.running = 0;
	a->ctrl.current_state = 0;
	InitializeCriticalSectionAndSpinCount (&txa[a->channel].calcc.cs_update, 2500);
	InitializeCriticalSectionAndSpinCount (&a->ctrl.cs_SafeToEnd, 2500);
	a->rxdelay = create_delay (
		1,											// run
		0,											// size				[stuff later]
		0,											// input buffer		[stuff later]
		0,											// output buffer	[stuff later]
		a->rate,									// sample rate
		20.0e-09,									// delta (delay stepsize)
		0.0);										// delay
	a->txdelay = create_delay (
		1,											// run
		0,											// size				[stuff later]
		0,											// input buffer		[stuff later]
		0,											// output buffer	[stuff later]
		a->rate,									// sample rate
		20.0e-09,									// delta (delay stepsize)
		0.0);										// delay
	
	InitializeCriticalSectionAndSpinCount (&a->disp.cs_disp, 2500);
	a->util.ints = a->ints;
	a->util.channel = a->channel;

	size_calcc (a);

	a->temprx = (double*)malloc0(2048 * sizeof(complex));														// remove later
	a->temptx = (double*)malloc0(2048 * sizeof(complex));														// remove later

	// correction save and restore threads
	InterlockedBitTestAndReset(&a->savecorr_bypass, 0);
	a->Sem_SaveCorr = CreateSemaphore(0, 0, 1, 0);
	_beginthread(PSSaveCorrection, 0, (void*)a);
	InterlockedBitTestAndReset(&a->restcorr_bypass, 0);
	a->Sem_RestCorr = CreateSemaphore(0, 0, 1, 0);
	_beginthread(PSRestoreCorrection, 0, (void*)a);
	InterlockedBitTestAndReset(&a->calccorr_bypass, 0);
	a->Sem_CalcCorr = CreateSemaphore(0, 0, 1, 0);
	_beginthread(doPSCalcCorrection, 0, (void*)a);
	InterlockedBitTestAndReset(&a->turnoff_bypass, 0);
	a->Sem_TurnOff = CreateSemaphore(0, 0, 1, 0);
	_beginthread(doPSTurnoff, 0, (void*)a);

	return a;
}

void destroy_calcc (CALCC a)
{
	// correction save and restore threads
	InterlockedBitTestAndReset(&txa[a->channel].iqc.p1->busy, 0);
	Sleep(10);
	InterlockedBitTestAndSet(&a->savecorr_bypass, 0);
	ReleaseSemaphore(a->Sem_SaveCorr, 1, 0);
	while (InterlockedAnd(&a->savecorr_bypass, 0xffffffff)) Sleep(1);
	CloseHandle(a->Sem_SaveCorr);
	InterlockedBitTestAndSet(&a->restcorr_bypass, 0);
	ReleaseSemaphore(a->Sem_RestCorr, 1, 0);
	while (InterlockedAnd(&a->restcorr_bypass, 0xffffffff)) Sleep(1);
	CloseHandle(a->Sem_RestCorr);
	InterlockedBitTestAndSet(&a->calccorr_bypass, 0);
	ReleaseSemaphore(a->Sem_CalcCorr, 1, 0);
	while (InterlockedAnd(&a->calccorr_bypass, 0xffffffff)) Sleep(1);
	CloseHandle(a->Sem_CalcCorr);
	InterlockedBitTestAndSet(&a->turnoff_bypass, 0);
	ReleaseSemaphore(a->Sem_TurnOff, 1, 0);
	while (InterlockedAnd(&a->turnoff_bypass, 0xffffffff)) Sleep(1);
	CloseHandle(a->Sem_TurnOff);

	_aligned_free (a->temptx);																						// remove later
	_aligned_free (a->temprx);																						// remove later
	desize_calcc (a);
	DeleteCriticalSection (&a->disp.cs_disp);
	destroy_delay (a->txdelay);
	destroy_delay (a->rxdelay);
	DeleteCriticalSection (&a->ctrl.cs_SafeToEnd);
	DeleteCriticalSection (&txa[a->channel].calcc.cs_update);
	_aligned_free (a->binfo);
	_aligned_free (a->info);

	_aligned_free (a);
}

void flush_calcc (CALCC a)
{
	flush_delay (a->rxdelay);
	flush_delay (a->txdelay);
}

void scheck(CALCC a)
{
	int i, j, k;
	double v, dx, out, x, xold;
	int intm1 = a->ints - 1;
	const double diff_thresh = 0.05;
	a->binfo[6] = 0x0000;

	for (i = 0; i < 4 * a->ints; i++)
	{
		if (isnan (a->cm[i])) a->binfo[6] |= 0x0001;
		if (isnan (a->cc[i])) a->binfo[6] |= 0x0001;
		if (isnan (a->cs[i])) a->binfo[6] |= 0x0001;
	}

	for (i = 0; i < a->ints; i++)
		if ((a->cm[4 * i + 0] == 0.0) && (a->cm[4 * i + 1] == 0.0) &&
			(a->cm[4 * i + 2] == 0.0) && (a->cm[4 * i + 3] == 0.0)) a->binfo[6] |= 0x0002;

	for (i = 0; i < a->ints; i++)
	{
		for (j = 0; j < 4; j++)
		{
			k = 4 * i + j;
			v = (double)k / (4.0 * (double)a->ints);
			dx = (a->t[i + 1] - a->t[i]) * (double)j / 4.0;
			out = v * (a->cm[4 * i + 0] + dx * (a->cm[4 * i + 1] + dx * (a->cm[4 * i + 2] + dx * a->cm[4 * i + 3])));
			if (out > 1.0)
				a->binfo[6] |= 0x0004;
			if (out < 0.0) a->binfo[6] |= 0x0010;
		}
	}

	dx = a->t[a->ints] - a->t[intm1];
	x = a->cm[4 * intm1 + 0] + dx * (a->cm[4 * intm1 + 1] + dx * (a->cm[4 * intm1 + 2] + dx * a->cm[4 * intm1 + 3]));

	if (x > 1.07)	// VALUE
		a->binfo[6] |= 0x0008;
	if (x < 0.0) a->binfo[6] |= 0x0020;

	for (i = 4; i < a->ints; i++)
		if (fabs (a->cm[4 * i + 0] - a->cm_old[4 * i + 0]) > diff_thresh) a->binfo[6] |= 0x0040;
	xold = a->cm_old[4 * intm1 + 0] + dx * (a->cm_old[4 * intm1 + 1] + dx * (a->cm_old[4 * intm1 + 2] + dx * a->cm_old[4 * intm1 + 3]));
	if (fabs (x - xold) > diff_thresh) a->binfo[6] |= 0x0040;
	memcpy (a->cm_old, a->cm, a->ints * 4 * sizeof(double));
}

void rxscheck (int rints, double* tvec, double* coef, int* info)
{
	int i, j, k;
	int rintsm1 = rints - 1;
	double v, dx, out;
	*info = 0x0000;
	for (i = 0; i < 4 * rints; i++)
		if (isnan (coef[i])) *info |= 0x0001;
	for (i = 0; i < rints; i++)
		if ((coef[4 * i + 0] == 0.0) && (coef[4 * i + 1] == 0.0) && (coef[4 * i + 2] == 0.0) && (coef[4 * i + 3] == 0.0))
			*info |= 0x0002;
	for (i = 0; i < rints; i++)
	{
		for (j = 0; j < 4; j++)
		{
			k = 4 * i + j;
			v = (double)k / (4.0 * (double)rints);
			dx = (tvec[i + 1] - tvec[i]) * (double)j / 4.0;
			out = v * (coef[4 * i + 0] + dx * (coef[4 * i + 1] + dx * (coef[4 * i + 2] + dx * coef[4 * i + 3])));
			if (out > 1.0)	// potentially use hw_scale here
				*info |= 0x0004;
			if (out < 0.0) *info |= 0x0010;
		}
	}
	dx = tvec[rints] - tvec[rints - 1];
	out = coef[4 * rintsm1 + 0] + dx * (coef[4 * rintsm1 + 1] + dx * (coef[4 * rintsm1 + 2] + dx * coef[4 * rintsm1 + 3]));
	if (out > 1.07) *info |= 0x0008;
	if (out < 0.00) *info |= 0x0020;
}

void calc (CALCC a)
{
	int i;
	double norm;
	for (i = 0; i < a->nsamps; i++)
	{
		a->env_TX[i] = sqrt (a->txs[2 * i + 0] * a->txs[2 * i + 0] + a->txs[2 * i + 1] * a->txs[2 * i + 1]);
		a->env_RX[i] = sqrt (a->rxs[2 * i + 0] * a->rxs[2 * i + 0] + a->rxs[2 * i + 1] * a->rxs[2 * i + 1]);
	}
	{
		int rints, ix;
		double dx;
		double tvec[3];
		double txrxcoefs[4 * 2];
		double rx_scale;
		if (a->ints < 16) rints = 1;
		else			  rints = 2;
		ix = rints - 1;
		for (i = 0; i <= rints; i++)
			tvec[i] = (double)i / (double)rints / a->hw_scale;
		dx = tvec[rints] - tvec[rints - 1];
		xbuilder(a->ccbld, a->nsamps, a->env_TX, a->env_RX, rints, tvec, &(a->binfo[0]), txrxcoefs, a->ptol);
		rxscheck (rints, tvec, txrxcoefs, &a->binfo[7]);
		if ((a->binfo[0] == 0) && (a->binfo[7] == 0))
			rx_scale = 1.0 / (txrxcoefs[4 * ix + 0] + dx * (txrxcoefs[4 * ix + 1] + dx * (txrxcoefs[4 * ix + 2] + dx * txrxcoefs[4 * ix + 3])));
		else
		{
			a->scOK = 0;
			goto cleanup;
		}
		if (a->stbl && _InterlockedAnd (&a->ctrl.running, 1))
			a->rx_scale = a->alpha * a->rx_scale + (1.0 - a->alpha) * rx_scale;
		else
			a->rx_scale = rx_scale;
	}

	a->binfo[4] = (int)(256.0 * (a->hw_scale / a->rx_scale));
	a->binfo[5]++;

	if (a->pin) // regress
	{
		const double slope = 0.001;
		double max_rx;
		for (i = 0; i < a->nsamps; i++)
		{
			max_rx = (1.0 - slope + slope * a->hw_scale * a->env_TX[i]) / a->rx_scale;
			if (a->env_RX[i] > max_rx)
				a->env_RX[i] = max_rx;
		}
	}

	for (i = 0; i < a->nsamps; i++)
	{
		norm = a->env_TX[i] * a->env_RX[i];
		a->x[i]	 = a->rx_scale * a->env_RX[i];
		a->ym[i] = (a->hw_scale * a->env_TX[i]) / (a->rx_scale * a->env_RX[i]);
		a->yc[i] = (+ a->txs[2 * i + 0] * a->rxs[2 * i + 0] + a->txs[2 * i + 1] * a->rxs[2 * i + 1]) / norm;
		a->ys[i] = (- a->txs[2 * i + 0] * a->rxs[2 * i + 1] + a->txs[2 * i + 1] * a->rxs[2 * i + 0]) / norm;
		if (a->stbl && _InterlockedAnd (&a->ctrl.running, 1) && a->scOK)
		{
			int k;
			double dx, ymo, yco, yso;
			if ((k = (int)(a->x[i] * a->ints)) > a->ints - 1) k = a->ints - 1;
			dx = a->x[i] - a->t[k];
			ymo = a->cm[4 * k + 0] + dx * (a->cm[4 * k + 1] + dx * (a->cm[4 * k + 2] + dx * a->cm[4 * k + 3]));
			yco = a->cc[4 * k + 0] + dx * (a->cc[4 * k + 1] + dx * (a->cc[4 * k + 2] + dx * a->cc[4 * k + 3]));
			yso = a->cs[4 * k + 0] + dx * (a->cs[4 * k + 1] + dx * (a->cs[4 * k + 2] + dx * a->cs[4 * k + 3]));
			a->ym[i] = a->alpha * ymo + (1.0 - a->alpha) * a->ym[i];
			a->yc[i] = a->alpha * yco + (1.0 - a->alpha) * a->yc[i];
			a->ys[i] = a->alpha * yso + (1.0 - a->alpha) * a->ys[i];
		}
	}

	if (a->pin) // pin
	{
		const double mval = 1.0e+00 - 1.0e-10;
		double cval, sval;
		for (i = 0; i < a->nsamps; i++)
		{
			a->cat[4 * i + 0] = a->x[i];
			a->cat[4 * i + 1] = a->ym[i];
			a->cat[4 * i + 2] = a->yc[i];
			a->cat[4 * i + 3] = a->ys[i];
		}
		qsort(a->cat, a->nsamps, 4 * sizeof(double), fcompare);
		for (i = 0; i < a->nsamps; i++)
		{
			a->x[i]	 = a->cat[4 * i + 0];
			a->ym[i] = a->cat[4 * i + 1];
			a->yc[i] = a->cat[4 * i + 2];
			a->ys[i] = a->cat[4 * i + 3];
		}
		cval = 0.0;
		sval = 0.0;
		for (i = a->nsamps - 1; i > a->nsamps - 17; i--)
		{
			cval += a->yc[i];
			sval += a->ys[i];
		}
		cval /= 16.0;
		sval /= 16.0;
		for (i = a->nsamps; i < a->tsamps; i++)
		{
			a->x[i]	 = mval;
			a->ym[i] = mval;
			a->yc[i] = cval;
			a->ys[i] = sval;
		}
		xbuilder(a->ccbld, a->tsamps, a->x, a->ym, a->ints, a->t, &(a->binfo[1]), a->cm, a->ptol);
		xbuilder(a->ccbld, a->tsamps, a->x, a->yc, a->ints, a->t, &(a->binfo[2]), a->cc, a->ptol);
		xbuilder(a->ccbld, a->tsamps, a->x, a->ys, a->ints, a->t, &(a->binfo[3]), a->cs, a->ptol);
	}
	else
	{
		xbuilder(a->ccbld, a->nsamps, a->x, a->ym, a->ints, a->t, &(a->binfo[1]), a->cm, a->ptol);
		xbuilder(a->ccbld, a->nsamps, a->x, a->yc, a->ints, a->t, &(a->binfo[2]), a->cc, a->ptol);
		xbuilder(a->ccbld, a->nsamps, a->x, a->ys, a->ints, a->t, &(a->binfo[3]), a->cs, a->ptol);
	}

	if (a->pin) // tune
	{
		int k = a->ints - 1;
		double dx = a->t[a->ints] - a->t[a->ints - 1];
		double sf = 1.0 / (a->cm[4 * k + 0] + dx * (a->cm[4 * k + 1] + dx * (a->cm[4 * k + 2] + dx * a->cm[4 * k + 3])));
		for (i = 0; i < 4 * a->ints; i++)
			a->cm[i] *= sf;
	}

	scheck (a);
	a->scOK = ((a->binfo[0] == 0) && (a->binfo[1] == 0) && (a->binfo[2] == 0) && (a->binfo[3] == 0) && (a->binfo[6] == 0));

	if (a->scOK)	// map calc
	{
		for (i = 0; i < a->ints; i++)
			a->tmap[i] = a->cm[4 * i] * a->t[i];
		a->tmap[a->ints] = 1.0;
		a->convex = ((a->tmap[a->ints] - a->tmap[a->ints - 1]) > (a->t[a->ints] - a->t[a->ints - 1]));
	}

	EnterCriticalSection (&a->disp.cs_disp);
	memcpy(a->disp.x, a->x,	 a->nsamps * sizeof (double));
	memcpy(a->disp.ym, a->ym, a->nsamps * sizeof (double));
	memcpy(a->disp.yc, a->yc, a->nsamps * sizeof (double));
	memcpy(a->disp.ys, a->ys, a->nsamps * sizeof (double));
	if (a->scOK)
	{
		memcpy(a->disp.cm, a->cm, a->ints * 4 * sizeof (double));
		memcpy(a->disp.cc, a->cc, a->ints * 4 * sizeof (double));
		memcpy(a->disp.cs, a->cs, a->ints * 4 * sizeof (double));
	}
	else
	{
		memset(a->disp.cm, 0, a->ints * 4 * sizeof (double));
		memset(a->disp.cc, 0, a->ints * 4 * sizeof (double));
		memset(a->disp.cs, 0, a->ints * 4 * sizeof (double));
	}
	LeaveCriticalSection (&a->disp.cs_disp);
cleanup:
	return;
}

void __cdecl doPSCalcCorrection (void *arg)
{
	CALCC a = (CALCC)arg;
	while (!InterlockedAnd(&a->calccorr_bypass, 0xffffffff))
	{
		WaitForSingleObject(a->Sem_CalcCorr, INFINITE);
		if (!InterlockedAnd(&a->calccorr_bypass, 0xffffffff))
		{
			calc(a);
			if (a->scOK)
			{
				EnterCriticalSection (&a->ctrl.cs_SafeToEnd);
				if (!InterlockedBitTestAndSet(&a->ctrl.running, 0))
					SetTXAiqcStart(a->channel, a->cm, a->cc, a->cs);
				else
					SetTXAiqcSwap(a->channel, a->cm, a->cc, a->cs);
				LeaveCriticalSection(&a->ctrl.cs_SafeToEnd);
			}
			InterlockedBitTestAndSet(&a->ctrl.calcdone, 0);
		}
	}
	InterlockedBitTestAndReset(&a->calccorr_bypass, 0);
}

void __cdecl doPSTurnoff (void *arg)
{
	CALCC a = (CALCC)arg;
	while (!InterlockedAnd(&a->turnoff_bypass, 0xffffffff))
	{
		WaitForSingleObject(a->Sem_TurnOff, INFINITE);
		if (!InterlockedAnd(&a->turnoff_bypass, 0xffffffff))
		{
			EnterCriticalSection(&a->ctrl.cs_SafeToEnd);
			SetTXAiqcEnd(a->channel);
			LeaveCriticalSection(&a->ctrl.cs_SafeToEnd);
		}
	}
	InterlockedBitTestAndReset(&a->turnoff_bypass, 0);
}

enum _calcc_state
{
	LRESET,
	LWAIT,
	LMOXDELAY,
	LSETUP,
	LCOLLECT,
	MOXCHECK,
	LCALC,
	LDELAY,
	LSTAYON,
	LTURNON
};

void __cdecl PSSaveCorrection (void *pargs)
{
	int i, k;
	CALCC a = (CALCC)pargs;
	while (!InterlockedAnd(&a->savecorr_bypass, 0xffffffff))
	{
		WaitForSingleObject(a->Sem_SaveCorr, INFINITE);
		if (!InterlockedAnd(&a->savecorr_bypass, 0xffffffff))
		{
			FILE* file = fopen(a->util.savefile, "w");
			if (file)
			{
				GetTXAiqcValues(a->util.channel, a->util.pm, a->util.pc, a->util.ps);
				for (i = 0; i < a->util.ints; i++)
				{
					for (k = 0; k < 4; k++)
						fprintf(file, "%.17e\t", a->util.pm[4 * i + k]);
					fprintf(file, "\n");
					for (k = 0; k < 4; k++)
						fprintf(file, "%.17e\t", a->util.pc[4 * i + k]);
					fprintf(file, "\n");
					for (k = 0; k < 4; k++)
						fprintf(file, "%.17e\t", a->util.ps[4 * i + k]);
					fprintf(file, "\n\n");
				}
				fflush(file);
				fclose(file);
			}
		}
	}
	InterlockedBitTestAndReset(&a->savecorr_bypass, 0);
}

void __cdecl PSRestoreCorrection(void *pargs)
{
	int i, k;
	CALCC a = (CALCC)pargs;
	while (!InterlockedAnd(&a->restcorr_bypass, 0xffffffff))
	{
		WaitForSingleObject(a->Sem_RestCorr, INFINITE);
		if (!InterlockedAnd(&a->restcorr_bypass, 0xffffffff))
		{
			FILE* file = fopen(a->util.restfile, "r");
			if (file)
			{
				int error = 0;
				for (i = 0; i < a->util.ints; i++)
				{
					// no additional reads after first error occurs
					for (k = 0; k < 4; k++)
						if (error == 0 && fscanf(file, "%le", &(a->util.pm[4 * i + k])) != 1) error = 1;
					for (k = 0; k < 4; k++)
						if (error == 0 && fscanf(file, "%le", &(a->util.pc[4 * i + k])) != 1) error = 1;
					for (k = 0; k < 4; k++)
						if (error == 0 && fscanf(file, "%le", &(a->util.ps[4 * i + k])) != 1) error = 1;
				}
				fclose(file);
				if (!error)
				{
					if (!InterlockedBitTestAndSet(&a->ctrl.running, 0))
						SetTXAiqcStart(a->channel, a->util.pm, a->util.pc, a->util.ps);
					else
						SetTXAiqcSwap(a->channel, a->util.pm, a->util.pc, a->util.ps);
				}
			}
		}
	}
	InterlockedBitTestAndReset(&a->restcorr_bypass, 0);
}


/********************************************************************************************************
*																										*
*										  Public Functions												*
*																										*
********************************************************************************************************/

PORT
void pscc (int channel, int size, double* tx, double* rx)
{
	int i, n, m;
	double env;
	CALCC a;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a = txa[channel].calcc.p;
	if (a->runcal)
	{
		a->size = size;
		if (InterlockedAnd (&a->mox, 1) && (a->txdelay->tdelay != 0.0 || a->rxdelay->tdelay != 0.0))
		{
			SetDelayBuffs (a->rxdelay, a->size, rx, rx);
			xdelay (a->rxdelay);
			SetDelayBuffs (a->txdelay, a->size, tx, tx);
			xdelay (a->txdelay);
		}
		a->info[15] = a->ctrl.state;

		switch (a->ctrl.state)
		{
			case LRESET:
				InterlockedExchange (&a->ctrl.current_state, LRESET);
				a->ctrl.reset = 0;
				if (!a->ctrl.turnon)
					if (InterlockedBitTestAndReset(&a->ctrl.running, 0))
						ReleaseSemaphore(a->Sem_TurnOff, 1, 0);
				a->info[14] = 0;
				a->ctrl.env_maxtx = 0.0;
				a->ctrl.bs_count = 0;
				if (a->ctrl.turnon)
					a->ctrl.state = LTURNON;
				else if (a->ctrl.automode || a->ctrl.mancal)
					a->ctrl.state = LWAIT;
				break;
			case LWAIT:
				InterlockedExchange (&a->ctrl.current_state, LWAIT);
				a->ctrl.mancal = 0;
				a->ctrl.moxcount = 0;
				if (a->ctrl.reset)
					a->ctrl.state = LRESET;
				else if (a->ctrl.turnon)
					a->ctrl.state = LTURNON;
				else if (InterlockedAnd (&a->mox, 1))
				{
					a->ctrl.state = LMOXDELAY;
					InterlockedBitTestAndSet (&a->solidmox, 0);
				}
				break;
			case LMOXDELAY:
				InterlockedExchange (&a->ctrl.current_state, LMOXDELAY);
				a->ctrl.moxcount += a->size;
				if (a->ctrl.reset)
					a->ctrl.state = LRESET;
				else if (a->ctrl.turnon)
					a->ctrl.state = LTURNON;
				else if (!InterlockedAnd (&a->mox, 1) || !InterlockedAnd (&a->solidmox, 1))
					a->ctrl.state = LWAIT;
				else if ((a->ctrl.moxcount - a->size) >= a->ctrl.moxsamps)
					a->ctrl.state = LSETUP;
				break;
			case LSETUP:
				InterlockedExchange (&a->ctrl.current_state, LSETUP);
				a->ctrl.count = 0;
				for (i = 0; i < a->ints; i++)
				{
					a->ctrl.cpi[i] = 0;
					a->ctrl.sindex[i] = 0;
				}
				a->ctrl.full_ints = 0;
				a->ctrl.waitcount = 0;
				
				if (a->ctrl.reset)
					a->ctrl.state = LRESET;
				else if (a->ctrl.turnon)
					a->ctrl.state = LTURNON;
				else if (InterlockedAnd (&a->mox, 1) && InterlockedAnd (&a->solidmox, 1))
				{
					a->ctrl.state = LCOLLECT;
					SetTXAiqcDogCount (channel, a->info[13] = 0);
				}
				else
					a->ctrl.state = LWAIT;
				break;
			case LCOLLECT:
				InterlockedExchange (&a->ctrl.current_state, LCOLLECT);
				for (i = 0; i < a->size; i++)
				{
					env = sqrt(tx[2 * i + 0] * tx[2 * i + 0] + tx[2 * i + 1] * tx[2 * i + 1]);
					if (env > a->ctrl.env_maxtx)
						a->ctrl.env_maxtx = env;
					if ((env *= a->hw_scale) <= 1.0)
					{
						if (env == 1.0)
							n = a->ints - 1;
						else if (a->map && _InterlockedAnd (&a->ctrl.running, 1) && a->convex)
						{
							int nmin = 0;
							int nmax = a->ints;
							n = (nmin + nmax) / 2;
							while (nmax - nmin > 1)
							{
								n = (nmin + nmax) / 2;
								if (env >= a->tmap[n])
									nmin = n;
								else
									nmax = n;
							}
							if (env < a->tmap[n]) n--;
						}
						else
							n = (int)(env * (double)a->ints);
						m = a->ctrl.sbase[n] + a->ctrl.sindex[n];
						a->txs[2 * m + 0] = tx[2 * i + 0];
						a->txs[2 * m + 1] = tx[2 * i + 1];
						a->rxs[2 * m + 0] = rx[2 * i + 0];
						a->rxs[2 * m + 1] = rx[2 * i + 1];
						if (++a->ctrl.sindex[n] == a->spi) a->ctrl.sindex[n] = 0;
						if (a->ctrl.cpi[n] != a->spi)
							if (++a->ctrl.cpi[n] == a->spi) a->ctrl.full_ints++;
						++a->ctrl.count;
					}
				}
				GetTXAiqcDogCount (channel, &a->info[13]);
				if (a->ctrl.reset)
					a->ctrl.state = LRESET;
				else if (a->ctrl.turnon)
					a->ctrl.state = LTURNON;
				else if (!InterlockedAnd (&a->mox, 1) || !InterlockedAnd (&a->solidmox, 1))
					a->ctrl.state = LWAIT;
				else if (a->ctrl.full_ints == a->ints)
					a->ctrl.state = MOXCHECK;
				else if (a->info[13] >= 6)
					a->ctrl.state = LRESET;
				else if (a->ctrl.count >= 4 * a->rate)
				{
					a->ctrl.count = 0;
					for (i = 0; i < a->ints; i++)
					{
						a->ctrl.cpi[i] = 0;
						a->ctrl.sindex[i] = 0;
					}
					a->ctrl.full_ints = 0;
				}
				break;
			case MOXCHECK:
				InterlockedExchange (&a->ctrl.current_state, MOXCHECK);
				if (a->ctrl.reset)
					a->ctrl.state = LRESET;
				else if (a->ctrl.turnon)
					a->ctrl.state = LTURNON;
				else if (!InterlockedAnd (&a->mox, 1) || !InterlockedAnd (&a->solidmox, 1))
					a->ctrl.state = LWAIT;
				else
					a->ctrl.state = LCALC;
				break;
			case LCALC:
				InterlockedExchange (&a->ctrl.current_state, LCALC);
				if (!a->ctrl.calcinprogress)	
				{
					a->ctrl.calcinprogress = 1;
					ReleaseSemaphore(a->Sem_CalcCorr, 1, 0);
				}

				if (InterlockedBitTestAndReset(&a->ctrl.calcdone, 0))
				{
					memcpy (a->info, a->binfo, 8 * sizeof (int));
					a->info[14] = _InterlockedAnd (&a->ctrl.running, 1);
					a->ctrl.calcinprogress = 0;
					if (a->ctrl.reset)
						a->ctrl.state = LRESET;
					else if (a->ctrl.turnon)
						a->ctrl.state = LTURNON;
					else if (a->scOK)
					{
						a->ctrl.bs_count = 0;
						a->ctrl.state = LDELAY;
					}
					else if (++(a->ctrl.bs_count) >= 2)
						a->ctrl.state = LRESET;
					else if (InterlockedAnd (&a->mox, 1) && InterlockedAnd (&a->solidmox, 1))
						a->ctrl.state = LSETUP;
					else a->ctrl.state = LWAIT;
				}
				break;
			case LDELAY:
				InterlockedExchange (&a->ctrl.current_state, LDELAY);
				a->ctrl.waitcount += a->size;
				if (a->ctrl.reset)
					a->ctrl.state = LRESET;
				else if (a->ctrl.turnon)
					a->ctrl.state = LTURNON;
				else if ((a->ctrl.waitcount - a->size) >= a->ctrl.waitsamps)
				{
					if (a->ctrl.automode)
					{
						if (InterlockedAnd (&a->mox, 1) && InterlockedAnd (&a->solidmox, 1))
							a->ctrl.state = LSETUP;
						else
							a->ctrl.state = LWAIT;
					}
					else
						a->ctrl.state = LSTAYON;
				}
				break;
			case LSTAYON:
				InterlockedExchange (&a->ctrl.current_state, LSTAYON);
				if (a->ctrl.reset || a->ctrl.automode || a->ctrl.mancal)
					a->ctrl.state = LRESET;
				break;
			case LTURNON:
				InterlockedExchange (&a->ctrl.current_state, LTURNON);
				a->ctrl.turnon = 0;
				a->ctrl.automode = 0;
				a->info[14] = 1;
				a->ctrl.state = LSTAYON;
				break;
		}
	}
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void psccF (int channel, int size, float *Itxbuff, float *Qtxbuff, float *Irxbuff, float *Qrxbuff, int mox, int solidmox)
{
	int i;
	CALCC a;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a = txa[channel].calcc.p;
	// a->mox = mox;
	// a->solidmox = solidmox;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
	for (i = 0; i < size; i++)
	{
		a->temptx[2 * i + 0] = (double)Itxbuff[i];
		a->temptx[2 * i + 1] = (double)Qtxbuff[i];
		a->temprx[2 * i + 0] = (double)Irxbuff[i];
		a->temprx[2 * i + 1] = (double)Qrxbuff[i];
	}
	pscc (channel, size, a->temptx, a->temprx);
}

PORT
void PSSaveCorr (int channel, char* filename)
{
	CALCC a;
	int i = 0;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a = txa[channel].calcc.p;
	while (a->util.savefile[i++] = *filename++);
	ReleaseSemaphore(a->Sem_SaveCorr, 1, 0);
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void PSRestoreCorr (int channel, char* filename)
{
	CALCC a;
	int i = 0;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a = txa[channel].calcc.p;
	while (a->util.restfile[i++] = *filename++);
	a->ctrl.turnon = 1;
	ReleaseSemaphore(a->Sem_RestCorr, 1, 0);
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

/********************************************************************************************************
*																										*
*											  Properties												*
*																										*
********************************************************************************************************/

PORT
void SetPSRunCal (int channel, int run)
{
	CALCC a;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a = txa[channel].calcc.p;
	a->runcal = run;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void SetPSMox (int channel, int mox)
{
	CALCC a = txa[channel].calcc.p;;
	if (mox)
		InterlockedBitTestAndSet (&a->mox, 0);
	else
	{
		InterlockedBitTestAndReset (&a->mox, 0);
		InterlockedBitTestAndReset (&a->solidmox, 0);
	}
}

PORT
void GetPSInfo (int channel, int *info)
{
	CALCC a;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a = txa[channel].calcc.p;
	memcpy (info, a->info, 16 * sizeof(int));
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void SetPSReset (int channel, int reset)
{
	CALCC a;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a = txa[channel].calcc.p;
	a->ctrl.reset = reset;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void SetPSMancal (int channel, int mancal)
{
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	txa[channel].calcc.p->ctrl.mancal = mancal;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void SetPSAutomode (int channel, int automode)
{
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	txa[channel].calcc.p->ctrl.automode = automode;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void SetPSTurnon (int channel, int turnon)
{
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	txa[channel].calcc.p->ctrl.turnon = turnon;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void SetPSControl (int channel, int reset, int mancal, int automode, int turnon)
{
	CALCC a;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a = txa[channel].calcc.p;
	a->ctrl.reset = reset;
	a->ctrl.mancal = mancal;
	a->ctrl.automode = automode;
	a->ctrl.turnon = turnon;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void SetPSLoopDelay (int channel, double delay)
{
	CALCC a;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a = txa[channel].calcc.p;
	a->ctrl.loopdelay = delay;
	a->ctrl.waitsamps = (int)(a->rate * a->ctrl.loopdelay);
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void SetPSMoxDelay (int channel, double delay)
{
	CALCC a;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a = txa[channel].calcc.p;
	a->ctrl.moxdelay = delay;
	a->ctrl.moxsamps = (int)(a->rate * a->ctrl.moxdelay);
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
double SetPSTXDelay (int channel, double delay)
{
	CALCC a;
	double adelay;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a = txa[channel].calcc.p;
	a->txdel = delay;
	if (a->txdel >= 0.0)
	{
		adelay = SetDelayValue (a->txdelay, a->txdel);
		SetDelayValue (a->rxdelay, 0.0);
	}
	else
	{
		adelay = -SetDelayValue (a->rxdelay, -a->txdel);
		SetDelayValue (a->txdelay, 0.0);
	}
	//adelay = SetDelayValue (a->txdelay, a->txdel);
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
	return adelay;
}

PORT
void SetPSHWPeak (int channel, double peak)
{
	CALCC a;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a = txa[channel].calcc.p;
	a->hw_scale = 1.0 / peak;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void GetPSHWPeak (int channel, double* peak)
{
EnterCriticalSection (&txa[channel].calcc.cs_update);
*peak = 1.0 / txa[channel].calcc.p->hw_scale;
LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void GetPSMaxTX (int channel, double* maxtx)
{
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	*maxtx = txa[channel].calcc.p->ctrl.env_maxtx;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void SetPSPtol (int channel, double ptol)
{
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	txa[channel].calcc.p->ptol = ptol;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void GetPSDisp (int channel, double* x, double* ym, double* yc, double* ys, double* cm, double* cc, double* cs)
{
	CALCC a = txa[channel].calcc.p;
	EnterCriticalSection (&a->disp.cs_disp);
	memcpy (x,	a->disp.x,	a->nsamps * sizeof (double));
	memcpy (ym, a->disp.ym, a->nsamps * sizeof (double));
	memcpy (yc, a->disp.yc, a->nsamps * sizeof (double));
	memcpy (ys, a->disp.ys, a->nsamps * sizeof (double));
	memcpy (cm, a->disp.cm, a->ints * 4 * sizeof (double));
	memcpy (cc, a->disp.cc, a->ints * 4 * sizeof (double));
	memcpy (cs, a->disp.cs, a->ints * 4 * sizeof (double));
	LeaveCriticalSection (&a->disp.cs_disp);
}

PORT
void SetPSFeedbackRate (int channel, int rate)
{
	CALCC a = txa[channel].calcc.p;
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	a->rate = rate;
	a->ctrl.moxsamps = (int)(a->rate * a->ctrl.moxdelay);
	a->ctrl.waitsamps = (int)(a->rate * a->ctrl.loopdelay);
	destroy_delay (a->txdelay);
	destroy_delay (a->rxdelay);
	a->rxdelay = create_delay (
		1,											// run
		0,											// size				[stuff later]
		0,											// input buffer		[stuff later]
		0,											// output buffer	[stuff later]
		a->rate,									// sample rate
		20.0e-09,									// delta (delay stepsize)
		0.0);										// delay
	a->txdelay = create_delay (
		1,											// run
		0,											// size				[stuff later]
		0,											// input buffer		[stuff later]
		0,											// output buffer	[stuff later]
		a->rate,									// sample rate
		20.0e-09,									// delta (delay stepsize)
		a->txdel);									// delay
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void SetPSPinMode (int channel, int pin)
{
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	txa[channel].calcc.p->pin = pin;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void SetPSMapMode (int channel, int map)
{
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	txa[channel].calcc.p->map = map;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

PORT
void SetPSStabilize (int channel, int stbl)
{
	EnterCriticalSection (&txa[channel].calcc.cs_update);
	txa[channel].calcc.p->stbl = stbl;
	LeaveCriticalSection (&txa[channel].calcc.cs_update);
}

void ForceShutDown (CALCC a, IQC b, int timeout)
{
	a->runcal = 0;										// close pscc() gate
	InterlockedBitTestAndReset (&b->run, 0);			// close xiqc() gate
	Sleep (timeout);									// wait for anything possibly running to clear
	a->ctrl.state = LRESET;								// set next_state
	InterlockedBitTestAndReset (&a->ctrl.running, 0);	// set running = 0
	InterlockedBitTestAndReset (&b->busy, 0);			// set busy = 0 so turnoff thread can finish & terminate
	InterlockedBitTestAndReset (&a->ctrl.calcdone, 0);
	a->info[14] = 0;
	a->ctrl.env_maxtx = 0.0;
	a->ctrl.bs_count = 0;
}

PORT
void SetPSIntsAndSpi (int channel, int ints, int spi)
{
	CALCC a = txa[channel].calcc.p;
	IQC	  b = txa[channel].iqc.p1;
	if (b->ints != ints || b->dog.spi != spi || a->ints != ints || a->spi != spi)
	{
		// SHUT-DOWN
		const int timeout = 50;
		int runcal	 = a->runcal;
		int mancal	 = a->ctrl.mancal;
		int automode = a->ctrl.automode;
		int turnon	 = a->ctrl.turnon;
		int count = 0;
		SetPSControl (a->channel, 1, 0, 0, 0);
		while (count++ < timeout && (LRESET != _InterlockedAnd (&a->ctrl.current_state, 0xFFFFFFFF)
			|| _InterlockedAnd (&a->ctrl.running, 1) || _InterlockedAnd (&b->run, 1)))
			Sleep (1);						// wait for normal shutdown (when samples are flowing)
		if (LRESET != _InterlockedAnd (&a->ctrl.current_state, 0xFFFFFFFF)
			|| _InterlockedAnd (&a->ctrl.running, 1) || _InterlockedAnd (&b->run, 1))
			ForceShutDown(a, b, timeout);	// apparently no sammples flowing; force shutdown.
		// MAKE CHANGES
		desize_iqc (b);
		desize_calcc (a);
		b->ints = ints;
		b->dog.spi = spi;
		a->ints = ints;
		a->spi = spi;
		size_calcc (a);
		size_iqc (b);
		// START-UP
		SetPSControl (a->channel, 1, mancal, automode, turnon);
		a->runcal = runcal;
	}
}
