/*	siphon.c

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

Copyright (C) 2013, 2024 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@pratt.one

*/

#include "comm.h"

void build_window (SIPHON a)
{
	int i;
	double arg0, cosphi;
	double sum, scale;
	arg0 = 2.0 * PI / ((double)a->fftsize - 1.0);
	sum = 0.0;
	for (i = 0; i < a->fftsize; i++)
	{
		cosphi = cos (arg0 * (double)i);
		a->window[i] =	+ 6.3964424114390378e-02
		  + cosphi *  ( - 2.3993864599352804e-01
		  + cosphi *  ( + 3.5015956323820469e-01
		  + cosphi *  ( - 2.4774111897080783e-01
		  + cosphi *  ( + 8.5438256055858031e-02
		  + cosphi *  ( - 1.2320203369293225e-02
		  + cosphi *  ( + 4.3778825791773474e-04 ))))));
		sum += a->window[i];
	}
	scale = 1.0 / sum;
	for (i = 0; i < a->fftsize; i++)
		a->window[i] *= scale;
}

SIPHON create_siphon (int run, int position, int mode, int disp, int insize,
	double* in, int sipsize, int fftsize, int specmode)
{
	SIPHON a = (SIPHON) malloc0 (sizeof (siphon));
	a->run = run;
	a->position = position;
	a->mode = mode;
	a->disp = disp;
	a->insize = insize;
	a->in = in;
	a->sipsize = sipsize;	// NOTE:  sipsize MUST BE A POWER OF TWO!!
	a->fftsize = fftsize;
	a->specmode = specmode;
	a->sipbuff = (double *) malloc0 (a->sipsize * sizeof (complex));
	a->idx = 0;
	a->sipout  = (double *) malloc0 (a->sipsize * sizeof (complex));
	a->specout = (double *) malloc0 (a->fftsize * sizeof (complex));
	a->sipplan = fftw_plan_dft_1d (a->fftsize, (fftw_complex *)a->sipout, (fftw_complex *)a->specout, FFTW_FORWARD, FFTW_PATIENT);
	a->window  = (double *) malloc0 (a->fftsize * sizeof (complex));
	InitializeCriticalSectionAndSpinCount(&a->update, 2500);
	build_window (a);
	a->n_alloc_disps = 0;
	a->alloc_run  = (int*) malloc0 (dMAX_DISPLAYS * sizeof(int));
	a->alloc_disp = (int*) malloc0 (dMAX_DISPLAYS * sizeof(int));
	return a;
}

void destroy_siphon (SIPHON a)
{
	_aligned_free (a->alloc_disp);
	_aligned_free (a->alloc_run);
	DeleteCriticalSection (&a->update);
	fftw_destroy_plan (a->sipplan);
	_aligned_free (a->window);
	_aligned_free (a->specout);
	_aligned_free (a->sipout);
	_aligned_free (a->sipbuff);
	_aligned_free (a);
}

void flush_siphon (SIPHON a)
{
	memset (a->sipbuff, 0, a->sipsize * sizeof (complex));
	memset (a->sipout , 0, a->sipsize * sizeof (complex));
	memset (a->specout, 0, a->fftsize * sizeof (complex));
	a->idx = 0;
}

void xsiphon (SIPHON a, int pos)
{
	int first, second, i;
	EnterCriticalSection(&a->update);
	if (a->run && a->position == pos)
	{
		switch (a->mode)
		{
		case 0:
			if (a->insize >= a->sipsize)
				memcpy (a->sipbuff, &(a->in[2 * (a->insize - a->sipsize)]), a->sipsize * sizeof (complex));
			else
			{
				if (a->insize > (a->sipsize - a->idx))
				{
					first = a->sipsize - a->idx;
					second = a->insize - first;
				}
				else
				{
					first = a->insize;
					second = 0;
				}
				memcpy (a->sipbuff + 2 * a->idx, a->in, first * sizeof (complex));
				memcpy (a->sipbuff, a->in + 2 * first, second * sizeof (complex));
				if ((a->idx += a->insize) >= a->sipsize) a->idx -= a->sipsize;
			}
			break;
		case 1:
			Spectrum0 (1, a->disp, 0, 0, a->in);
			for (i = 0; i < a->n_alloc_disps; i++)
				Spectrum0 (a->alloc_run[i], a->alloc_disp[i], 0, 0, a->in);
			break;
		}
	}
	LeaveCriticalSection(&a->update);
}

void setBuffers_siphon (SIPHON a, double* in)
{
	a->in = in;
}

void setSamplerate_siphon (SIPHON a, int rate)
{
	flush_siphon (a);
}

void setSize_siphon (SIPHON a, int size)
{
	a->insize = size;
	flush_siphon (a);
}

void suck (SIPHON a)
{
	if (a->outsize <= a->sipsize)
	{
		int mask = a->sipsize - 1;
		int j = (a->idx - a->outsize) & mask;
		int size = a->sipsize - j;
		if (size >= a->outsize)
			memcpy (a->sipout, &(a->sipbuff[2 * j]), a->outsize * sizeof (complex));
		else
		{
			memcpy (a->sipout, &(a->sipbuff[2 * j]), size * sizeof (complex));
			memcpy (&(a->sipout[2 * size]), a->sipbuff, (a->outsize - size) * sizeof (complex));
		}
	}
}

void sip_spectrum (SIPHON a)
{
	int i;
	for (i = 0; i < a->fftsize; i++)
	{
		a->sipout[2 * i + 0] *= a->window[i];
		a->sipout[2 * i + 1] *= a->window[i];
	}
	fftw_execute (a->sipplan);
}

/********************************************************************************************************
*																										*
*											RXA Properties												*
*																										*
********************************************************************************************************/

PORT
void RXAGetaSipF (int channel, float* out, int size)
{	// return raw samples as floats
	SIPHON a= rxa[channel].sip1.p;
	int i;
	EnterCriticalSection (&a->update);
	a->outsize = size;
	suck (a);
	LeaveCriticalSection (&a->update);
	for (i = 0; i < size; i++)
	{
		out[i] = (float)a->sipout[2 * i + 0];
	}
}

PORT
void RXAGetaSipF1 (int channel, float* out, int size)
{	// return raw samples as floats
	SIPHON a= rxa[channel].sip1.p;
	int i;
	EnterCriticalSection (&a->update);
	a->outsize = size;
	suck (a);
	LeaveCriticalSection (&a->update);
	for (i = 0; i < size; i++)
	{
		out[2 * i + 0] = (float)a->sipout[2 * i + 0];
		out[2 * i + 1] = (float)a->sipout[2 * i + 1];
	}
}

/********************************************************************************************************
*																										*
*											TXA Properties												*
*																										*
********************************************************************************************************/

PORT
void TXASetSipPosition (int channel, int pos)
{
	SIPHON a = txa[channel].sip1.p;
	EnterCriticalSection (&a->update);
	a->position = pos;
	LeaveCriticalSection (&a->update);
}

PORT
void TXASetSipMode (int channel, int mode)
{
	SIPHON a = txa[channel].sip1.p;
	EnterCriticalSection (&a->update);
	a->mode = mode;
	LeaveCriticalSection (&a->update);
}

PORT
void TXASetSipDisplay (int channel, int disp)
{
	SIPHON a = txa[channel].sip1.p;
	EnterCriticalSection (&a->update);
	a->disp = disp;
	LeaveCriticalSection (&a->update);
}

PORT
void TXAGetaSipF (int channel, float* out, int size)
{	// return raw samples as floats
	SIPHON a = txa[channel].sip1.p;
	int i;
	EnterCriticalSection (&a->update);
	a->outsize = size;
	suck (a);
	LeaveCriticalSection (&a->update);
	for (i = 0; i < size; i++)
	{
		out[i] = (float)a->sipout[2 * i + 0];
	}
}

PORT
void TXAGetaSipF1 (int channel, float* out, int size)
{	// return raw samples as floats
	SIPHON a = txa[channel].sip1.p;
	int i;
	EnterCriticalSection (&a->update);
	a->outsize = size;
	suck (a);
	LeaveCriticalSection (&a->update);
	for (i = 0; i < size; i++)
	{
		out[2 * i + 0] = (float)a->sipout[2 * i + 0];
		out[2 * i + 1] = (float)a->sipout[2 * i + 1];
	}
}

PORT
void TXASetSipSpecmode (int channel, int mode)
{
	SIPHON a = txa[channel].sip1.p;
	if (mode == 0)
		InterlockedBitTestAndReset (&a->specmode, 0);
	else
		InterlockedBitTestAndSet   (&a->specmode, 0);
}

PORT
void TXAGetSpecF1 (int channel, float* out)
{	// return spectrum magnitudes in dB
	SIPHON a = txa[channel].sip1.p;
	int i, j, mid, m, n;
	EnterCriticalSection (&a->update);
	a->outsize = a->fftsize;
	suck (a);
	LeaveCriticalSection (&a->update);
	sip_spectrum (a);
	mid = a->fftsize / 2;
	if (!InterlockedAnd (&a->specmode, 1))
		// swap the halves of the spectrum
		for (i = 0, j = mid; i < mid; i++, j++)
		{
			out[i] = (float)(10.0 * mlog10 (a->specout[2 * j + 0] * a->specout[2 * j + 0] + a->specout[2 * j + 1] * a->specout[2 * j + 1] + 1.0e-60));
			out[j] = (float)(10.0 * mlog10 (a->specout[2 * i + 0] * a->specout[2 * i + 0] + a->specout[2 * i + 1] * a->specout[2 * i + 1] + 1.0e-60));
		}
	else
		// mirror each half of the spectrum in-place
		for (i = 0, j = mid - 1, m = mid, n = a->fftsize - 1; i < mid; i++, j--, m++, n--)
		{
			out[i] = (float)(10.0 * mlog10 (a->specout[2 * j + 0] * a->specout[2 * j + 0] + a->specout[2 * j + 1] * a->specout[2 * j + 1] + 1.0e-60));
			out[m] = (float)(10.0 * mlog10 (a->specout[2 * n + 0] * a->specout[2 * n + 0] + a->specout[2 * n + 1] * a->specout[2 * n + 1] + 1.0e-60));
		}
}

PORT
void TXASetSipAllocDisps (int channel, int n_alloc_disps, int* alloc_run, int* alloc_disp)
{
	SIPHON a = txa[channel].sip1.p;
	int i;
	EnterCriticalSection(&a->update);
	a->n_alloc_disps = n_alloc_disps;
	for (i = 0; i < a->n_alloc_disps; i++)
	{
		a->alloc_run[i]	 = alloc_run[i];
		a->alloc_disp[i] = alloc_disp[i];
	}
	LeaveCriticalSection(&a->update);
}

/********************************************************************************************************
*																										*
*										CALLS FOR EXTERNAL USE											*
*																										*
********************************************************************************************************/

#define MAX_EXT_SIPHONS (2)									// maximum number of Siphons called from outside wdsp
__declspec (align (16)) SIPHON psiphon[MAX_EXT_SIPHONS];	// array of pointers for Siphons used EXTERNAL to wdsp


PORT
void create_siphonEXT (int id, int run, int insize, int sipsize, int fftsize, int specmode)
{
	psiphon[id] = create_siphon (run, 0, 0, 0, insize, 0, sipsize, fftsize, specmode);
}

PORT
void destroy_siphonEXT (int id)
{
	destroy_siphon (psiphon[id]);
}

PORT
void flush_siphonEXT (int id)
{
	flush_siphon (psiphon[id]);
}

PORT
void xsiphonEXT (int id, double* buff)
{
	SIPHON a = psiphon[id];
	a->in = buff;
	xsiphon (a, 0);
}

PORT
void GetaSipF1EXT (int id, float* out, int size)
{	// return raw samples as floats
	SIPHON a = psiphon[id];
	int i;
	EnterCriticalSection (&a->update);
	a->outsize = size;
	suck (a);
	LeaveCriticalSection (&a->update);
	for (i = 0; i < size; i++)
	{
		out[2 * i + 0] = (float)a->sipout[2 * i + 0];
		out[2 * i + 1] = (float)a->sipout[2 * i + 1];
	}
}

PORT
void SetSiphonInsize (int id, int size)
{
	SIPHON a = psiphon[id];
	EnterCriticalSection (&a->update);
	a->insize = size;
	LeaveCriticalSection (&a->update);
}
