/*	analyzer.c

This file is part of a program that implements a Spectrum Analyzer
used in conjunction with software-defined-radio hardware.

Copyright (C) 2012, 2013, 2014, 2016, 2023, 2025 Warren Pratt, NR0V
Copyright (C) 2012 David McQuate, WA8YWQ - Kaiser window & Bessel function added.

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"

DP pdisp[dMAX_DISPLAYS];

double bessi0(double x)
{
	double ax,ans;
	double y;

	if ((ax=fabs(x)) < 3.75) {
		y = x / 3.75,  y = y * y;
		ans = 1.0 + y * (3.5156229 + y * (3.0899424 +  y* (1.2067492
			+ y * (0.2659732 + y * (0.360768e-1 + y * 0.45813e-2)))));
	} else {
		y = 3.75 / ax;
		ans = (exp(ax) / sqrt(ax)) * (0.39894228 + y * (0.1328592e-1
			+ y * (0.225319e-2 + y * (-0.157565e-2 + y * (0.916281e-2
			+ y * (-0.2057706e-1 + y * (0.2635537e-1 + y * (-0.1647633e-1
			+ y * 0.392377e-2))))))));
	}
	return ans;
}

void new_window(int disp, int type, int size, double PiAlpha)
{
	DP a = pdisp[disp];
	int i;
	double arg0, arg1, cgsum, igsum;
	switch (type)
	{
	case 0:					// rectangular window
		{
			a->inv_coherent_gain = 1.0;
			igsum = (double)size;
			for (i = 0; i < size; i++)
				a->window[i] = a->inv_coherent_gain * 1.0;
			break;
		}
	case 1:					// blackman-harris window (4 term)
		{
			arg0 = 2.0 * PI / ((double)size - 1.0);
			cgsum = 0.0;
			igsum = 0.0;
			for (i = 0; i < size; i++)
			{
				arg1 = arg0 * (double)i;
				a->window[i] = 0.35875 - 0.48829 * cos(arg1) + 0.14128 * cos(2.0 * arg1) - 0.01168 * cos(3.0 * arg1);
				cgsum += a->window[i];
				igsum += a->window[i] * a->window[i];
			}
			a->inv_coherent_gain = (double)size / cgsum;
			for (i = 0; i < size; i++)
				a->window[i] *= a->inv_coherent_gain;
			break;
		}
	case 2:					// hann window
		{
			arg0 = 2.0 * PI / ((double)size - 1.0);
			cgsum = 0.0;
			igsum = 0.0;
			for (i = 0; i < size; i++)
			{
				a->window[i] = 0.5 * (1.0 - cos((double)i * arg0));
				cgsum += a->window[i];
				igsum += a->window[i] * a->window[i];
			}
			a->inv_coherent_gain = (double)size / cgsum;
			for (i = 0; i < size; i++)
				a->window[i] *= a->inv_coherent_gain;
			break;
		}
	case 3:					// flat-top window
		{
			arg0 = 2.0 * PI / ((double)size - 1.0);
			cgsum = 0.0;
			igsum = 0.0;
			for (i = 0; i < size; i++)
			{
				arg1 = arg0 * (double)i;
				a->window[i] = 0.21557895 - 0.41663158 * cos(arg1) + 0.277263158 * cos(2.0 * arg1) - 0.083578947 * cos(3.0 * arg1) + 0.006947368 * cos (4.0 * arg1);
				cgsum += a->window[i];
				igsum += a->window[i] * a->window[i];
			}
			a->inv_coherent_gain = (double)size / cgsum;
			for (i = 0; i < size; i++)
				a->window[i] *= a->inv_coherent_gain;
			break;
		}
	case 4:					// hamming window
		{
			arg0 = 2.0 * PI / ((double)size - 1.0);
			cgsum = 0.0;
			igsum = 0.0;
			for (i = 0; i < size; i++)
			{
				a->window[i] = (0.54 - 0.46 * cos((double)i * arg0));
				cgsum += a->window[i];
				igsum += a->window[i] * a->window[i];
			}
			a->inv_coherent_gain = (double)size / cgsum;
			for (i = 0; i < size; i++)
				a->window[i] *= a->inv_coherent_gain;
			break;
		}
	case 5:					// Kaiser window
		{	arg0 = bessi0(PiAlpha);
			arg1 = (double)(size - 1);
			cgsum = 0.0;
			igsum = 0.0;
			for (i = 0; i < size; ++i)
			{
				a->window[i] = bessi0(PiAlpha * sqrt(1.0 - pow(2.0 * (double)i / arg1 - 1.0, 2))) / arg0;
				cgsum += a->window[i];
				igsum += a->window[i] * a->window[i];
			}
			a->inv_coherent_gain = (double)size / cgsum;
			for (i = 0; i < size; i++)
				a->window[i] *= a->inv_coherent_gain;
			break;
		}
	case 6:					// Blackman-Harris window (7-term)
	default:
		{
			arg0 = 2.0 * PI / ((double)size - 1.0);
			cgsum = 0.0;
			igsum = 0.0;
			for (i = 0; i < size; ++i)
			{
				arg1 = cos (arg0 * (double)i);
				a->window[i]   =	+ 6.3964424114390378e-02
						+ arg1 *  ( - 2.3993864599352804e-01
						+ arg1 *  ( + 3.5015956323820469e-01
						+ arg1 *  ( - 2.4774111897080783e-01
						+ arg1 *  ( + 8.5438256055858031e-02
						+ arg1 *  ( - 1.2320203369293225e-02
						+ arg1 *  ( + 4.3778825791773474e-04 ))))));
				cgsum += a->window[i];
				igsum += a->window[i] * a->window[i];
			}
			a->inv_coherent_gain = (double)size / cgsum;
			for (i = 0; i < size; i++)
				a->window[i] *= a->inv_coherent_gain;
			break;
		}
	}
	a->inherent_power_gain = igsum / (double)size;
	a->inv_enb = 1.0 / (a->inherent_power_gain * a->inv_coherent_gain * a->inv_coherent_gain);
	// print_window_gain ("windows.txt", type, a->inv_coherent_gain, a->inherent_power_gain);
}

// spur elimination, REAL input data
void eliminate(int disp, int ss, int LO)
{
	DP a = pdisp[disp];
	int i, k, begin, end, ilim;
	double mag;

	if (ss == a->begin_ss)
		begin = a->fscL + a->clip;
	else
		begin = a->clip;
	if (ss == a->end_ss)
		end = a->out_size - 1 -a->clip - a->fscH;
	else
		end = a->out_size - 1 - a->clip;

	ilim = a->out_size - 1;

	if (a->flip[LO])
		for (i = ilim - begin, k = 0; i > ilim - end; i--, k++)
		{
			mag = (a->fft_out[ss][LO])[i][0] * (a->fft_out[ss][LO])[i][0] + (a->fft_out[ss][LO])[i][1] * (a->fft_out[ss][LO])[i][1];
			if ((a->spec_flag[ss] == 0) || (mag < (a->result[ss])[k]))
				(a->result[ss])[k] = mag;
		}
	else
		for (i = begin, k = 0; i < end; i++, k++)
		{
			mag = (a->fft_out[ss][LO])[i][0] * (a->fft_out[ss][LO])[i][0] + (a->fft_out[ss][LO])[i][1] * (a->fft_out[ss][LO])[i][1];
			if ((a->spec_flag[ss] == 0) || (mag < (a->result[ss])[k]))
				(a->result[ss])[k] = mag;
		}
	a->ss_bins[ss] = k;
}

// spur elimination, COMPLEX input data
void Celiminate(int disp, int ss, int LO)
{
	DP a = pdisp[disp];
	int i, k, begin0, end0, begin1, end1, ilim;
	double mag;

	if (ss == a->begin_ss)
	{
		begin0 = a->out_size / 2 + 1 + a->clip + a->fscL;
		if (begin0 > a->out_size)
			begin1 = begin0 - a->out_size;
		else
			begin1 = 0;
	}
	else
	{
		begin0 = a->out_size / 2 + 1 + a->clip;
		begin1 = 0;
	}
	if (ss == a->end_ss)
	{
		end1 = a->out_size / 2 - a->clip - a->fscH;
		if (end1 < 0)
			end0 = a->out_size + end1;
		else
			end0 = a->out_size;
	}
	else
	{
		end0 = a->out_size;
		end1 = a->out_size / 2 - a->clip;
	}

	ilim = a->out_size - 1;

	if (a->flip[LO])
	{
		for (i = ilim - begin0, k = 0; i > ilim - end0; i--, k++)
		{
			mag = (a->fft_out[ss][LO])[i][0] * (a->fft_out[ss][LO])[i][0] + (a->fft_out[ss][LO])[i][1] * (a->fft_out[ss][LO])[i][1];
			if ((a->spec_flag[ss] == 0) || (mag < (a->result[ss])[k]))
				(a->result[ss])[k] = mag;
		}
		for (i = ilim - begin1; i > ilim - end1; i--, k++)
		{
			mag = (a->fft_out[ss][LO])[i][0] * (a->fft_out[ss][LO])[i][0] + (a->fft_out[ss][LO])[i][1] * (a->fft_out[ss][LO])[i][1];
			if ((a->spec_flag[ss] == 0) || (mag < (a->result[ss])[k]))
				(a->result[ss])[k] = mag;
		}
	}
	else
	{
		for (i = begin0, k = 0; i < end0; i++, k++)
		{
			mag = (a->fft_out[ss][LO])[i][0] * (a->fft_out[ss][LO])[i][0] + (a->fft_out[ss][LO])[i][1] * (a->fft_out[ss][LO])[i][1];
			if ((a->spec_flag[ss] == 0) || (mag < (a->result[ss])[k]))
				(a->result[ss])[k] = mag;
		}
		for (i = begin1; i < end1; i++, k++)
		{
			mag = (a->fft_out[ss][LO])[i][0] * (a->fft_out[ss][LO])[i][0] + (a->fft_out[ss][LO])[i][1] * (a->fft_out[ss][LO])[i][1];
			if ((a->spec_flag[ss] == 0) || (mag < (a->result[ss])[k]))
				(a->result[ss])[k] = mag;
		}
	}
	a->ss_bins[ss] = k;
}

void detector ( int det_type,			// detector type
				int m,					// number of bins
				int num_pixels,			// number of output pixels
				double pix_per_bin,		// pixels per bin
				double bin_per_pix,		// bins per pixel
				double* bins,			// input buffer
				double* pixels,			// output buffer
				double inv_enb,			// inverse equivalent noise bandwidth
				double fsclipL,
				double fsclipH,
				double det_offset
				)
{
	int i, imin, ilim;
	int pix_count = 0;
	int rose, fell, next_pix_count, bcount, last_pix_count;
	double prev_maxi, mini, maxi, psum;
	if (pix_per_bin <= 1.0)
	{
		if (fsclipL == floor(fsclipL)) imin = 0;
		else  imin = 1;
		if (fsclipH == floor(fsclipH)) ilim = m;
		else  ilim = m - 1;
		switch (det_type)
		{

		case 0:		// positive peak
			for (i = 0; i < num_pixels; i++)
				pixels[i]	= - 1.0e300;

			for (i = imin; i < ilim; i++)
			{
				pix_count = (int)(det_offset + (double)i * pix_per_bin);
				if (pix_count >= num_pixels) pix_count = num_pixels - 1;
				if (bins[i] > pixels[pix_count])
					pixels[pix_count] = bins[i];
			}
			break;

		case 1:		// rosenfell
			rose		 = 0;
			fell		 = 0;
			mini		 = + 1.0e300;
			maxi		 = - 1.0e300;
			prev_maxi	 = - 1.0e300;

			for (i = imin; i < ilim; i++)		// for each FFT bin
			{
				// determine the pixel number that this FFT bin goes into
				pix_count = (int)(det_offset + (double)i * pix_per_bin);
				if (pix_count >= num_pixels) pix_count = num_pixels - 1;
				// determine the pixel number for the NEXT FFT bin
				next_pix_count = (int)((double)(i + 1) * pix_per_bin);
				// update the minimum and maximum of the set of bins within the pixel
				if (bins[i] <	mini)	  mini = bins[i];
				if (bins[i] >	maxi)	  maxi = bins[i];
				// if the next bin is also within the pixel && there is a next bin,
				//	  compare its value with the current bin and update rose and fell
				if (next_pix_count == pix_count && i < ilim - 1)
				{
					// NOTE:  when next_pix_count != pix_count, rose and fell do not get updated;
					//	  that's OK because we do NOT need to know if there's a rise or fall across bins
					if (bins[i + 1] > bins[i]) rose	   = 1;
					if (bins[i + 1] < bins[i]) fell	   = 1;
				}
				// if the next bin is NOT within the pixel || there is no next bin, finalize the pixel
				//	  value and reset parameters
				else
				{
					if (rose && fell)
						if (pix_count & 1)				// odd pixel
							pixels[pix_count] = max (prev_maxi, maxi);
						else							// even pixel
							pixels[pix_count] = mini;
					else
						pixels[pix_count] = maxi;
					rose = 0;
					fell = 0;
					prev_maxi = maxi;
					mini = + 1.0e300;
					maxi = - 1.0e300;
				}
			}
			break;

		case 2:		// average - adjusted for window's equivalent noise bandwidth
			psum = 0.0;
			bcount = 0;
			for (i = imin; i < ilim; i++)
			{
				last_pix_count = pix_count;
				pix_count = (int)(det_offset + (double)i * pix_per_bin);
				if (pix_count >= num_pixels) pix_count = num_pixels - 1;
				if (pix_count == last_pix_count)
				{
					psum += bins[i];
					bcount++;
				}
				else
				{
					pixels[last_pix_count] = psum / (double)bcount * inv_enb;
					psum = bins[i];
					bcount = 1;
				}
				if (i == ilim - 1)
				{
					pixels[pix_count] = psum / (double)bcount * inv_enb;
				}
			}
			break;

		case 3:		// sample - adjusted for window's equivalent noise bandwidth
			bcount = 0;
			for (i = imin; i < ilim; i++)
			{
				last_pix_count = pix_count;
				pix_count = (int)(det_offset + (double)i * pix_per_bin);
				if (pix_count >= num_pixels) pix_count = num_pixels - 1;
				if (pix_count == last_pix_count)
				{
					bcount++;
				}
				else
				{
					pixels[last_pix_count] = bins[i - bcount / 2 - 1] * inv_enb;
					bcount = 1;
				}
				if (i == ilim - 1)
				{
					pixels[pix_count] = bins[i - bcount / 2] * inv_enb;
				}
			}
			break;

		case 4:		// rms
			psum = 0.0;
			bcount = 0;
			for (i = imin; i < ilim; i++)
			{
				last_pix_count = pix_count;
				pix_count = (int)(det_offset + (double)i * pix_per_bin);
				if (pix_count >= num_pixels) pix_count = num_pixels - 1;
				if (pix_count == last_pix_count)
				{
					psum += bins[i] * bins[i];
					bcount++;
				}
				else
				{
					pixels[last_pix_count] = sqrt (psum / (double)bcount) * inv_enb;
					psum = bins[i] * bins[i];
					bcount = 1;
				}
				if (i == ilim - 1)
				{
					pixels[pix_count] = sqrt (psum / (double)bcount) * inv_enb;
				}
			}
			break;
		}
	}
	else
	{
		double frac;
		double pix_pos = fsclipL - floor(fsclipL);
		int ampl_comp = (det_type == 2) || (det_type == 3) || (det_type == 4);
		for (i = 1; i < m; i++)
		{
			while (pix_pos < ((double)i + 1.0e-06) && pix_count < num_pixels)
			{
				frac = pix_pos - (double)(i - 1);
				pixels[pix_count]	= bins[i - 1] * (1.0 - frac) + bins[i] * frac;
				if (ampl_comp) pixels[pix_count] *= inv_enb;
				pix_count++;
				pix_pos += bin_per_pix;
			}
		}
	}
	
}

void avenger (	int av_mode,				// averaging mode
				int num_pixels,				// number of pixels
				int* avail_frames,			// number of available frames for window averaging
				int num_average,			// number of frames to average within a window
				int* av_in_idx,				// in index for av_buff
				int* av_out_idx,			// out index for av_buff
				double av_backmult,			// multiplier for recursive averaging
				double scale,				// scale factor
				double* t_pixels,			// input buffer
				double* av_sum,				// history buffer for averaging
				double** av_buff,			// frame buffer for window averaging
				double* cd,					// correction factor buffer
				int norm,					// if TRUE, normalize to one Hz bandwidth
				double norm_oneHz,			// normalization factor to add
				dOUTREAL* pixels			// output buffer
	)
{
	int i;
	double factor;
	switch (av_mode)
	{
	case -1:	// peak-hold
		{
			for (i = 0; i < num_pixels; i++)
			{
				if (t_pixels[i] > av_sum[i])
					av_sum[i] = t_pixels[i];
				pixels[i] = (dOUTREAL)(10.0 * mlog10(scale * cd[i] * av_sum[i] + 1.0e-60));
			}
			break;
		}
	case 0:		// no averaging
	default:
		{
			for (i = 0; i < num_pixels; i++)
				pixels[i] = (dOUTREAL)(10.0 * mlog10(scale * cd[i] * t_pixels[i] + 1.0e-60));
			break;
		}
	case 1:		// weighted averaging of linear data
		{
			double onem_avb = 1.0 - av_backmult;
			for (i = 0; i < num_pixels; i++)
			{
				av_sum[i] = av_backmult * av_sum[i] + onem_avb * t_pixels[i];
				pixels[i] = (dOUTREAL)(10.0 * mlog10(scale * cd[i] * av_sum[i] + 1.0e-60));
			}
			break;
		}
	case 2:		// window averaging of linear data
		{
			if (*avail_frames < num_average)
			{
				factor = scale / (double)++(*avail_frames);
				for (i = 0; i < num_pixels; i++)
				{
					av_sum[i] += t_pixels[i];
					av_buff[*av_in_idx][i] = t_pixels[i];
					pixels[i] = (dOUTREAL)(10.0 * mlog10(cd[i] * av_sum[i] * factor + 1.0e-60));
				}
			}
			else
			{
				factor = scale / (double)(*avail_frames);
				for (i = 0; i < num_pixels; i++)
				{
					av_sum[i] += t_pixels[i] - (av_buff[*av_out_idx])[i];
					av_buff[*av_in_idx][i] = t_pixels[i];
					pixels[i] = (dOUTREAL)(10.0 * mlog10(cd[i] * av_sum[i] * factor + 1.0e-60));
				}
				if (++(*av_out_idx) == dMAX_AVERAGE)
						*av_out_idx = 0;
			}
			if (++(*av_in_idx) == dMAX_AVERAGE)
				*av_in_idx = 0;
			break;
		}
	case 3:		// weighted averaging of log data - looks nice, not accurate for time-varying signals
		{
			double onem_avb = 1.0 - av_backmult;
			for (i = 0; i < num_pixels; i++)
			{
				av_sum[i] = av_backmult * av_sum[i] + onem_avb * (10.0 * mlog10(scale * cd[i] * t_pixels[i] + 1e-60));
				pixels[i] = (dOUTREAL)av_sum[i];
			}
			break;
		}
	}
	if (norm)
		for (i = 0; i < num_pixels; i++)
			pixels[i] += (dOUTREAL)norm_oneHz;
}

void stitch(int disp)
{
	DP a = pdisp[disp];
	int i, j, k, n, m;
	double* ptr;

	// stitch
	m = 0;
	ptr = a->pre_av_out;
	for (n = a->begin_ss; n <= a->end_ss; n++)
	{
		memcpy(ptr, a->result[n], a->ss_bins[n] * sizeof(double));
		ptr += a->ss_bins[n];
		m += a->ss_bins[n];
	}
	for (i = 0; i < a->num_pixout; i++) // for each output
	{
		EnterCriticalSection(&a->ResampleSection);
		// if a detection of the same 'det_type' has already been done, use that result
		j = i - 1;
		k = i;
		while (j >= 0)
		{
			if (a->det_type[i] == a->det_type[j])
				k = j;
			j--;
		}
		if (k == i)
			// detect
			detector (a->det_type[i], m, a->num_pixels, a->pix_per_bin, a->bin_per_pix, a->pre_av_out,
				a->t_pixels[i], a->inv_enb, a->fsclipL, a->fsclipH, a->det_offset);
		else
			memcpy (a->t_pixels[i], a->t_pixels[k], a->num_pixels * sizeof (double));
		// average & convert to dBm
		avenger (a->av_mode[i], a->num_pixels, &a->avail_frames[i], a->num_average[i], &a->av_in_idx[i], &a->av_out_idx[i],
			a->av_backmult[i], a->scale, a->t_pixels[i], a->av_sum[i], a->av_buff[i], a->cd, a->normalize[i], a->norm_oneHz,
			a->pixels[i][a->w_pix_buff[i]]);
		LeaveCriticalSection(&a->ResampleSection);

		EnterCriticalSection(&a->PB_ControlsSection[i]);
			a->last_pix_buff[i] = a->w_pix_buff[i];
			while ((a->w_pix_buff[i] = (a->w_pix_buff[i] + 1) % dNUM_PIXEL_BUFFS) == a->r_pix_buff[i]);
		LeaveCriticalSection(&a->PB_ControlsSection[i]);
		InterlockedBitTestAndSet(&(a->pb_ready[i][a->last_pix_buff[i]]), 0);
	}
}

DWORD WINAPI spectra (void *pargs)
{
	int i, j;
	int disp = ((int)(uintptr_t)pargs) >> 12;
	int ss = (((int)(uintptr_t)pargs) >> 4) & 255;
	int LO = ((int)(uintptr_t)pargs) & 15;
	DP a = pdisp[disp];

	if (a->stop)
	{
		InterlockedDecrement(a->pnum_threads);
		return 0;
	}

	if ((ss >= a->begin_ss) && (ss <= a->end_ss))
	{
		for (i = 0; i < a->size; i++)
		{
			(a->fft_in[ss][LO])[i] = a->window[i] * (double)((a->I_samples[ss][LO])[a->IQO_idx[ss][LO]]);
			if(++a->IQO_idx[ss][LO] >= a->bsize)
				 a->IQO_idx[ss][LO] -= a->bsize;
		}

		if (a->stop)
		{
			InterlockedDecrement(a->pnum_threads);
			return 0;
		}
		fftw_execute (a->plan[ss][LO]);
	}
	if (a->stop)
	{
		InterlockedDecrement(a->pnum_threads);
		return 0;
	}

	EnterCriticalSection(&(a->EliminateSection[ss]));
	if ((ss >= a->begin_ss) && (ss <= a->end_ss))
		eliminate(disp, ss, LO);
	a->spec_flag[ss] |= 1 << LO;

	if (a->spec_flag[ss] == ((1 << a->num_fft) - 1))
	{
		a->spec_flag[ss] = 0;
		LeaveCriticalSection (&(a->EliminateSection[ss]));

		EnterCriticalSection (&a->StitchSection);
		a->stitch_flag |= ((uint64_t)1) << ss;

		if (a->stitch_flag == ((((uint64_t)1) << a->num_stitch) - 1))
		{
			a->stitch_flag = 0;
			LeaveCriticalSection(&a->StitchSection);
			for (j = 0; j < dMAX_STITCH; j++)
				for (i = 0; i < dMAX_NUM_FFT; i++)
					InterlockedBitTestAndReset(&(a->input_busy[j][i]), 0);
			stitch(disp);
		}
		else
			LeaveCriticalSection(&a->StitchSection);
	}
	else
		LeaveCriticalSection (&(a->EliminateSection[ss]));

	InterlockedDecrement(a->pnum_threads);
	return 1;
}

/********************************************************************************************************
*																										*
*							BEGIN CODE TO GET MAX FFT_BIN WITHIN A FREQ RANGE							*
*																										*
********************************************************************************************************/

// Call in XCreateAnalyzer(...)
// This gets initialized for each 'disp' that is set up.
void Init_DetectMaxBin(int disp)
{
	DP a = pdisp[disp];
	InitializeCriticalSectionAndSpinCount(&a->cs_dmb, 2500);
	a->dmb_run = 0;
	a->dmb_disp = disp;
	a->dmb_ss = 0;
	a->dmb_LO = 0;
	a->dmb_rate = 48000.0;
	a->dmb_fLow = 0.0;
	a->dmb_fHigh = 0.0;
	a->dmb_begin0 = 0;
	a->dmb_end0 = -1;
	a->dmb_begin1 = 0;
	a->dmb_end1 = -1;
	a->dmb_max_dB = -400.0;
}

// Call in DestroyAnalyzer(...)
void Destroy_DetectMaxBin(int disp)
{
	DP a = pdisp[disp];
	DeleteCriticalSection(&a->cs_dmb);
}

// Called from SetupDetectMaxBin(...) AND anytime 'size' changes, e.g., in SetAnalyzer(...)
void calc_dmb(int disp, int size)
{
	DP a = pdisp[disp];
	double bin_spacing, min_freq, max_freq;
	bin_spacing = a->dmb_rate / size;
	min_freq = -a->dmb_rate * (double)((size / 2 - 1)) / (double)size;
	max_freq = -min_freq;
	if (a->dmb_fLow < min_freq) a->dmb_fLow = min_freq;
	if (a->dmb_fLow > max_freq) a->dmb_fLow = max_freq;
	if (a->dmb_fHigh < min_freq) a->dmb_fHigh = min_freq;
	if (a->dmb_fHigh > max_freq) a->dmb_fHigh = max_freq;

	if (a->dmb_fLow < 0.0 && a->dmb_fHigh < 0.0)
	{
		a->dmb_begin0 = size - (int)ceil(-a->dmb_fLow / bin_spacing);
		// Example:	 1024, 48K, -2700 => 966
		// Example:	 1024, 48K, -23953.125==min_freq => 513
		// NOTE:  since dmb_fLow < 0.0, begin0 will never be greater than a->size - 1
		a->dmb_end0 = size - (int)ceil(-a->dmb_fHigh / bin_spacing);
		// Example:	 1024, 48K, -300  => 1017
		a->dmb_begin1 = 0;
		a->dmb_end1 = -1;
	}
	else if (a->dmb_fLow >= 0.0 && a->dmb_fHigh >= 0.0)
	{
		a->dmb_begin0 = (int)round(a->dmb_fLow / bin_spacing);
		// Example:	 1024, 48K, +300  => 6
		a->dmb_end0 = (int)round(a->dmb_fHigh / bin_spacing);
		// Example:	 1024, 48K, +2700 => 58
		// Example:	 1024, 48K, 23953.125==max_freq => 511
		a->dmb_begin1 = 0;
		a->dmb_end1 = -1;
	}
	else	// fLow < 0.0 && fHigh >= 0.0
	{
		a->dmb_begin0 = size - (int)ceil(-a->dmb_fLow / bin_spacing);
		a->dmb_end0 = size - 1;
		a->dmb_begin1 = 0;
		a->dmb_end1 = (int)round(a->dmb_fHigh / bin_spacing);
	}
	if (a->dmb_tau > 0.0 && a->dmb_frame_rate > 0)
		a->dmb_decay = exp(-1.0 / (a->dmb_tau * (double)a->dmb_frame_rate));
	else
		a->dmb_decay = 0.0;
	a->dmb_max_dB = -400.0;
}


// Call from console, for each 'disp' for which this function is desired.
// Call for initial setup and anytime one of the parameters changes.
// run:			Set to '1' if this is being used; cycles can be saved by setting to
//				'0' if this is not currently in use.
// disp:		Display identifier number.
// ss:			Set to '0' for Thetis use.
// LO:			Set to '0' for Thetis use.
// rate:		Sample_rate of display data, e.g., '192000.0'.
// fLow:		Lowest frequency of frequency range to evaluate, referenced to center_frequency
//				to which DDC is tuned.	For example, for LSB, not using CTUN, this might be
//				'-3000.0'.
// fHigh:		Highest frequency of frequency range to evaluate, referenced to center_frequency
//				to which DDC is tuned.	Example, LSB:  '-300.0'.
// tau:			Metering time constant in seconds.	E.g., '0.5'.
// frame_rate:	Display frame_rate currently in use.  E.g., '60'.
PORT
void SetupDetectMaxBin(int run, int disp, int ss, int LO, double rate,
	double fLow, double fHigh, double tau, int frame_rate)
{
	// We only allow setup for one (ss,LO) pair at a time for each 'disp'.
	DP a = pdisp[disp];
	
	a->dmb_run = run;
	a->dmb_disp = disp;
	a->dmb_ss = ss;
	a->dmb_LO = LO;
	a->dmb_rate = rate;
	a->dmb_fLow = fLow;
	a->dmb_fHigh = fHigh;
	a->dmb_tau = tau;
	a->dmb_frame_rate = frame_rate;

	calc_dmb(a->dmb_disp, a->size);
}

// Call this function in 'Cspectra(...)', after the FFT.
void DetectMaxBin(int disp, int ss, int LO)
{
	DP a = pdisp[disp];
	int i;
	double mag, dmb_max;
	double dmb_max_dB;
	// If 'run' is set and the FFT Output is from the correct disp, ss, LO ...
	if (a->dmb_run && disp == a->dmb_disp && ss == a->dmb_ss && LO == a->dmb_LO)
	{
		fftw_complex* fft_out = a->fft_out[ss][LO];
		dmb_max = 1.0e-60;
		EnterCriticalSection(&a->cs_dmb);
		for (i = a->dmb_begin0; i <= a->dmb_end0; i++)
		{
			mag = fft_out[i][0] * fft_out[i][0] + fft_out[i][1] * fft_out[i][1];
			if (mag > dmb_max) dmb_max = mag;
		}
		for (i = a->dmb_begin1; i <= a->dmb_end1; i++)
		{
			mag = fft_out[i][0] * fft_out[i][0] + fft_out[i][1] * fft_out[i][1];
			if (mag > dmb_max) dmb_max = mag;
		}

		a->dmb_max_dB -= fabs((1.0 - a->dmb_decay) * a->dmb_max_dB);
		dmb_max_dB = 10.0 * mlog10(a->scale * dmb_max);
		if (dmb_max_dB > a->dmb_max_dB) a->dmb_max_dB = dmb_max_dB;
		LeaveCriticalSection(&a->cs_dmb);
		// for test only.
		// printf("Max Bin = %.5e\n", a->dmb_max_dB);
	}
}

// Call from console, for each 'disp' for which this function is desired.
// Always returns the value from the most recent display frame.
PORT
double GetDetectMaxBin(int disp)
{
	DP a = pdisp[disp];
	double dmb_max_dB;
	EnterCriticalSection(&a->cs_dmb);
	dmb_max_dB = a->dmb_max_dB;
	LeaveCriticalSection(&a->cs_dmb);
	return dmb_max_dB;
}

/********************************************************************************************************
*																										*
*							END CODE TO GET MAXIMUM FFT_BIN WITHIN A FREQ RANGE							*
*																										*
********************************************************************************************************/

DWORD WINAPI Cspectra (void *pargs)
{
	int i, j;
	int disp = ((int)(uintptr_t)pargs) >> 12;
	int ss = (((int)(uintptr_t)pargs) >> 4) & 255;
	int LO = ((int)(uintptr_t)pargs) & 15;
	DP a = pdisp[disp];
	int trans_size = a->size * sizeof(double);

	if (a->stop)
	{
		InterlockedDecrement(a->pnum_threads);
		return 0;
	}

	if ((ss >= a->begin_ss) && (ss <= a->end_ss))
	{
		for (i = 0; i < a->size; i++)
		{
			(a->Cfft_in[ss][LO])[i][0] = a->window[i] * (double)((a->I_samples[ss][LO])[a->IQO_idx[ss][LO]]);
			(a->Cfft_in[ss][LO])[i][1] = a->window[i] * (double)((a->Q_samples[ss][LO])[a->IQO_idx[ss][LO]]);
			if(++a->IQO_idx[ss][LO] >= a->bsize)
				 a->IQO_idx[ss][LO] -= a->bsize;
		}

		if (a->stop)
		{
			InterlockedDecrement(a->pnum_threads);
			return 0;
		}
		fftw_execute (a->Cplan[ss][LO]);

		// Detect value of Max FFT Bin in a freq range
		DetectMaxBin(disp, ss, LO);
		//

	}

	if (a->stop)
	{
		InterlockedDecrement(a->pnum_threads);
		return 0;
	}

	if (InterlockedBitTestAndReset(&(a->snap[ss][LO]), 0))
	{
		memcpy((char *)(a->snap_buff[ss][LO]), (char *)(a->fft_out[ss][LO]) + trans_size, trans_size);
		memcpy((char *)(a->snap_buff[ss][LO]) + trans_size, (char *)(a->fft_out[ss][LO]), trans_size);
		SetEvent(a->hSnapEvent[ss][LO]);
	}

	EnterCriticalSection(&(a->EliminateSection[ss]));
	if ((ss >= a->begin_ss) && (ss <= a->end_ss))
		Celiminate(disp, ss, LO);
	a->spec_flag[ss] |= 1 << LO;

	if (a->spec_flag[ss] == ((1 << a->num_fft) - 1))
	{
		a->spec_flag[ss] = 0;
		LeaveCriticalSection (&(a->EliminateSection[ss]));

		EnterCriticalSection (&a->StitchSection);
		a->stitch_flag |= ((uint64_t)1) << ss;

		if (a->stitch_flag == ((((uint64_t)1) << a->num_stitch) - 1))
		{
			a->stitch_flag = 0;
			LeaveCriticalSection(&a->StitchSection);
			for (j = 0; j < dMAX_STITCH; j++)
				for (i = 0; i < dMAX_NUM_FFT; i++)
					InterlockedBitTestAndReset(&(a->input_busy[j][i]), 0);
			stitch(disp);
		}
		else
			LeaveCriticalSection(&a->StitchSection);
	}
	else
		LeaveCriticalSection (&(a->EliminateSection[ss]));

	InterlockedDecrement(a->pnum_threads);
	return 1;
}

void interpolate(int disp, int set, double fmin, double fmax, int num_pixels)
{
	DP a = pdisp[disp];
	int i;
	double f;
	int n = a->n_freqs[set];
	int k;
	int kmin = 0;
	int kmax = n - 1;
	int kdelta;
	double dx;
	double mag;

	for (i = 0; i < num_pixels; i++)
	{
		f = fmin + (double)i * (fmax - fmin) / (double)(num_pixels - 1);
		
		if (f < (a->freqs[set])[0])
			k = 0;
		else if (f > (a->freqs[set])[n - 1])
			k = n - 2;
		else
		{
			kdelta = 1;

			while (f < (a->freqs[set])[kmin])
			{
				kmin = max(0, kmin - kdelta);
				kdelta += kdelta;
			}
			while (f > (a->freqs[set])[kmax])
			{
				kmax = min(n - 1, kmax + kdelta);
				kdelta += kdelta;
			}
			k = (kmin + kmax) / 2;
			while ((kmax - kmin) > 1)
			{
				k = (kmin + kmax) / 2;
				if (f > (a->freqs[set])[k])
					kmin = k;
				else
					kmax = k--;
			}
		}

		dx = f - (a->freqs[set])[k];

		mag = (((a->ac3[set][0])[k] * dx + (a->ac2[set][0])[k]) * dx + (a->ac1[set][0])[k]) * dx + (a->ac0[set][0])[k];
		a->cd[i] = mag * mag;
	}
	return;
}

int build_interpolants(int disp, int set, int n, int m, double *x, double (*y)[dMAX_M])
{
	DP a = pdisp[disp];
	double dx[dMAX_N];
	double idx[dMAX_N];
	double dmain[dMAX_N];
	double dsub[dMAX_N];
	double dsup[dMAX_N];
	double d[dMAX_N][dMAX_M];
	double S[dMAX_N][dMAX_M];
	double b[dMAX_N];
	double v[dMAX_N][dMAX_M];
	double tmp;
	int i, j;
	if (n < 3) return -1;
	for (i = 0; i < n - 1; i++)
	{
		dx[i] = x[i + 1] - x[i];

		if (dx[i] < 1e-30)
			return -1;
		idx[i] = 1.0 / dx[i];
	}

	for (i = 1; i <= n - 2; i++)
	{
		if (i == 1)
		{
			dsub[i] = 0.0;
			dmain[i] = 3.0 * dx[i - 1] + 2.0 * dx[i];
			dsup[i] = dx[i];
		}
		else if (i == (n - 2))
		{
			dsub[i] = dx[i - 1];
			dmain[i] = 2.0 * dx[i - 1] + 3.0 * dx[i];
			dsup[i] = 0.0;
		}
		else
		{
			dsub[i] = dx[i - 1];
			dmain[i] = 2.0 * (dx[i - 1] + dx[i]);
			dsup[i] = dx[i];
		}
		for (j = 0; j < m; j++)
			d[i][j] = 6.0 * ((y[i + 1][j] - y[i][j]) * idx[i] - (y[i][j] - y[i - 1][j]) * idx[i - 1]);
	}

	b[1] = dmain[1];
	for (j = 0; j < m; j++)
		v[1][j] = d[1][j];

	for (i = 2; i <= n - 2; i++)
	{
		tmp = dsub[i] / b[i - 1];
		b[i] = dmain[i] - tmp * dsup[i - 1];
		for (j = 0; j < m; j++)
			v[i][j] = d[i][j] - tmp * v[i - 1][j];
	}

	for (j = 0; j < m; j++)
		S[n - 2][j] = v[n - 2][j] / b[n - 2];

	for (i = n - 3; i >= 1; i--)
		for (j = 0; j < m; j++)
			S[i][j] = (v[i][j] - dsup[i] * S[i + 1][j]) / b[i];

	for (j = 0; j < m; j++)
	{
		S[0][j] = S[1][j];
		S[n - 1][j] = S[n - 2][j];
	}

	for (i = 0; i < n - 1; i++)
		for (j = 0; j < m; j++)
		{
			(a->ac3[set][j])[i] = (S[i + 1][j] - S[i][j]) / (6.0 * dx[i]);
			(a->ac2[set][j])[i] = 0.5 * S[i][j];
			(a->ac1[set][j])[i] = (y[i + 1][j] - y[i][j]) * idx[i] - (2.0 * dx[i] * S[i][j] + dx[i] * S[i + 1][j]) / 6.0;
			(a->ac0[set][j])[i] = y[i][j];
		}
	return 0;
}

void __cdecl sendbuf(void *arg)
{
	DP a = pdisp[(int)(uintptr_t)arg];
	while(!a->end_dispatcher)
	{
		for (a->ss = 0; a->ss < a->num_stitch; a->ss++)
			for (a->LO = 0; a->LO < a->num_fft; a->LO++)
			{
				if (!_InterlockedAnd(&(a->input_busy[a->ss][a->LO]), 1) && _InterlockedAnd(&(a->buff_ready[a->ss][a->LO]), 1))
				{
					InterlockedBitTestAndSet(&(a->input_busy[a->ss][a->LO]), 0);

					a->IQO_idx[a->ss][a->LO] = a->IQout_index[a->ss][a->LO];
					
					InterlockedIncrement(a->pnum_threads);
					if (a->type == 0)
						QueueUserWorkItem(spectra, (void *)(((uintptr_t)arg << 12) + (a->ss << 4) + a->LO), 0);
					else
						QueueUserWorkItem(Cspectra, (void *)(((uintptr_t)arg << 12) + (a->ss << 4) + a->LO), 0);

					if((a->IQout_index[a->ss][a->LO] += a->incr) >= a->bsize)
						a->IQout_index[a->ss][a->LO] -= a->bsize;

					EnterCriticalSection(&(a->BufferControlSection[a->ss][a->LO]));
					if ((a->have_samples[a->ss][a->LO] -= a->incr) < a->size)
						InterlockedBitTestAndReset(&(a->buff_ready[a->ss][a->LO]), 0);
					LeaveCriticalSection(&(a->BufferControlSection[a->ss][a->LO]));
				}
			}
		Sleep(1);
	}
	InterlockedBitTestAndReset(&a->dispatcher, 0);
	_endthread();
}

void CalcBandwidthNormalization (DP a)
{
	double bin_width;
	if (a->sample_rate <= 0 || a->size <= 0) { a->norm_oneHz = 0.0; return; }
	bin_width = (double)a->sample_rate / (double)a->size;
	if (bin_width <= 0.0) { a->norm_oneHz = 0.0; return; }
	a->norm_oneHz = 10.0 * mlog10 (1.0 / bin_width);
}

PORT
void ResetPixelBuffers(int disp)
{
	//[2.10.2]MW0LGE reset all the pixel and average buffers
	DP a = pdisp[disp];
	int i, j, k;

	EnterCriticalSection(&a->SetAnalyzerSection);
	EnterCriticalSection(&a->ResampleSection);
	for (i = 0; i < dMAX_PIXOUTS; i++)
	{
		for (j = 0; j < dMAX_PIXELS; j++)
			a->t_pixels[i][j] = 0.0;
		for (j = 0; j < dMAX_AVERAGE; j++)
			for (k = 0; k < dMAX_PIXELS; k++)
				a->av_buff[i][j][k] = 0.0f;
		switch (a->av_mode[i])
		{
		case 1:
			for (j = 0; j < dMAX_PIXELS; j++)
				a->av_sum[i][j] = 1.0e-12;
			break;
		case 2:
			//done below
			//a->avail_frames[i] = 0;
			//a->av_in_idx[i] = 0;
			//a->av_out_idx[i] = 0;
			break;
		case 3:
			for (j = 0; j < dMAX_PIXELS; j++)
				a->av_sum[i][j] = -160.0;
			break;
		default:
			memset((void*)a->av_sum[i], 0, sizeof(double) * dMAX_PIXELS);
			break;
		}
		a->avail_frames[i] = 0;
		a->av_in_idx[i] = 0;
		a->av_out_idx[i] = 0;
		EnterCriticalSection(&a->PB_ControlsSection[i]);
		a->w_pix_buff[i] = 0;
		a->r_pix_buff[i] = 0;
		a->last_pix_buff[i] = 0;
		for (j = 0; j < dNUM_PIXEL_BUFFS; j++)
			a->pb_ready[i][j] = 0;
		LeaveCriticalSection(&a->PB_ControlsSection[i]);
	}
	memset((void*)a->pre_av_out, 0, sizeof(double) * a->max_size * a->max_stitch);
	LeaveCriticalSection(&a->ResampleSection);
	EnterCriticalSection(&a->StitchSection);
	for (i = 0; i < dMAX_STITCH; i++)
		for (j = 0; j < dMAX_NUM_FFT; j++)
			a->input_busy[i][j] = 0;
	for (i = 0; i < dMAX_STITCH; i++)
		a->spec_flag[i] = 0;
	a->stitch_flag = 0;
	a->ss = 0;
	a->LO = 0;
	for (i = 0; i < dMAX_STITCH; i++)
		for (j = 0; j < dMAX_NUM_FFT; j++)
		{
			EnterCriticalSection(&(a->BufferControlSection[i][j]));
			a->buff_ready[i][j] = 0;
			a->have_samples[i][j] = 0;
			a->IQin_index[i][j] = 0;
			a->IQout_index[i][j] = 0;
			LeaveCriticalSection(&(a->BufferControlSection[i][j]));
		}
	LeaveCriticalSection(&a->StitchSection);
	LeaveCriticalSection(&a->SetAnalyzerSection);
}

PORT	
void SetAnalyzer (	int disp,			// display identifier
					int n_pixout,		// pixel output identifier
					int n_fft,			// number of LO frequencies = number of ffts used in elimination
					int typ,			// 0 for real input data (I only); 1 for complex input data (I & Q)
					int *flp,			// vector with one elt for each LO frequency, 1 if high-side LO, 0 otherwise
					int sz,				// size of the fft, i.e., number of input samples
					int bf_sz,			// number of samples transferred for each OpenBuffer()/CloseBuffer()
					int win_type,		// integer specifying which window function to use
					double pi,			// PiAlpha parameter for Kaiser window
					int ovrlp,			// number of samples each fft (other than the first) is to re-use from the previous
					int clp,			// number of fft output bins to be clipped from EACH side of each sub-span
					double fscLin,		// number of bins to clip from low end of entire span
					double fscHin,		// number of bins to clip from high end of entire span
					int n_pix,			// number of pixel values to return.  may be either <= or > number of bins
					int n_stch,			// number of sub-spans to concatenate to form a complete span
					int calset,			// identifier of which set of calibration data to use
					double fmin,		// frequency at first pixel value
					double fmax,		// frequency at last pixel value
					int max_w
				 )
{
	WDSP_FPE_GUARD;
	DP a = pdisp[disp];
	int i, j;

	EnterCriticalSection(&a->SetAnalyzerSection);
	a->end_dispatcher = 1;
	while (InterlockedAnd(&a->dispatcher, 1))
		Sleep(1);
	a->stop = 1;
	while (_InterlockedAnd(a->pnum_threads, 1023))
		Sleep(1);
	a->num_pixout = n_pixout;
	a->num_fft = n_fft;
	a->type = typ;
	a->buff_size = bf_sz;
	for (i = 0; i < a->num_fft; i++)
		a->flip[i] = *(flp + i);
	a->overlap = ovrlp;
	a->clip = clp;
	a->fsclipL = fscLin;
	a->fsclipH = fscHin;
	a->num_stitch = n_stch;

	if (sz != a->size)
	{
		for (i = 0; i < a->max_stitch; i++)
			for (j = 0; j < a->max_num_fft; j++)
			{
				if (a->plan[i][j])		fftw_destroy_plan (a->plan[i][j]);
				if (a->Cplan[i][j])		fftw_destroy_plan (a->Cplan[i][j]);
				a->plan[i][j] = fftw_plan_dft_r2c_1d(sz, a->fft_in[i][j], a->fft_out[i][j], FFTW_PATIENT);
				a->Cplan[i][j] = fftw_plan_dft_1d(sz, a->Cfft_in[i][j], a->fft_out[i][j], FFTW_FORWARD, FFTW_PATIENT);
			}

		// Setup DetectMaxBin for a 'size' change.
		calc_dmb(disp, sz);
		//

	}

	if ((sz != a->size) || (win_type != a->window_type) || (pi != a->PiAlpha))
		new_window(disp, win_type, sz, pi);

	a->size = sz;
	a->window_type = win_type;
	a->PiAlpha = pi;
	a->max_writeahead = max_w;
	CalcBandwidthNormalization (a);
	if (((fmin != a->f_min) || (fmax != a->f_max)) && ((fmin == 0.0) && (fmax == 0.0)))
		for (i = 0; i < dMAX_PIXELS; i++)
			a->cd[i] = 1.0;

	if (((fmax != 0.0) || (fmin != 0.0)) && ((n_pix != a->num_pixels) || (fmin != a->f_min) || (fmax != a->f_max) || (calset != a->cal_set) || a->cal_changed))
		interpolate(disp, calset, fmin, fmax, n_pix);

	a->incr = a->size - a->overlap;
	a->num_pixels = n_pix;
	a->f_min = fmin;
	a->f_max = fmax;
	a->cal_set = calset;
	a->cal_changed = 0;

	if (a->type == 0)
	{
		a->out_size = a->size / 2 + 1;
		a->scale = 4.0 / ((double)a->size * (double)a->size);
	}
	else
	{
		a->out_size = a->size;
		a->scale = 1.0 / ((double)a->size * (double)a->size);
	}

	a->begin_ss = 0;
	a->end_ss = a->num_stitch - 1;
	a->fscL = (int)a->fsclipL;
	a->fscH = (int)a->fsclipH;
	while (a->fscL >= (a->out_size - 1 - 2 * a->clip))
	{
		a->fscL -= a->out_size - 1 - 2 * a->clip;
		a->ss_bins[a->begin_ss] = 0;
		a->begin_ss++;
	}
	while (a->fscH >= (a->out_size - 1 - 2 * a->clip))
	{
		a->fscH -= a->out_size - 1 - 2 * a->clip;
		a->ss_bins[a->end_ss] = 0;
		a->end_ss--;
	}

	a->pix_per_bin = (double)a->num_pixels / ((double)(a->num_stitch * (a->out_size - 1 - 2 * a->clip)) - a->fsclipL - a->fsclipH - 1.0);
	a->det_offset = -a->pix_per_bin * (a->fsclipL - floor(a->fsclipL));
	a->bin_per_pix = ((double)(a->num_stitch * (a->out_size - 1 - 2 * a->clip)) - 1.0 - a->fsclipL - a->fsclipH) / ((double)a->num_pixels - 1.0);

	for (i = 0; i < dMAX_STITCH; i++)
		for (j = 0; j < dMAX_NUM_FFT; j++)
			a->input_busy[i][j] = 0;

	for (i = 0; i < dMAX_STITCH; i++)
		a->spec_flag[i] = 0;
	a->stitch_flag = 0;
	for (i = 0; i < dMAX_PIXOUTS; i++)
	{
		a->w_pix_buff[i] = 0;
		a->r_pix_buff[i] = 0;
		a->last_pix_buff[i] = 0;
		for (j = 0; j < dNUM_PIXEL_BUFFS; j++)
			a->pb_ready[i][j] = 0;
	}
	a->ss = 0;
	a->LO = 0;
	for (i = 0; i < dMAX_STITCH; i++)
		for (j = 0; j < dMAX_NUM_FFT; j++)
		{
			a->buff_ready[i][j] = 0;
			a->have_samples[i][j] = 0;
			a->IQin_index[i][j] = 0;
			a->IQout_index[i][j] = 0;
		}

	a->stop = 0;
	a->end_dispatcher = 0;
	LeaveCriticalSection(&a->SetAnalyzerSection);
}

PORT
void XCreateAnalyzer(	int disp,
						int *success,
						int m_size,
						int m_num_fft,
						int m_stitch,
						char *app_data_path
						)
{
	WDSP_FPE_GUARD;
	int i, j;
	DP a = (DP) malloc0 (sizeof(dp));
	pdisp[disp] = a;

	a->max_size = m_size;
	a->max_num_fft = m_num_fft;
	a->max_stitch = m_stitch;
	
	a->pnum_threads = (LONG*) malloc0 (sizeof (LONG));

	for (i = 0; i < a->max_stitch; i++)
		for (j = 0; j < a->max_num_fft; j++)
		{
			a->hSnapEvent[i][j] = CreateEvent(NULL, FALSE, FALSE, TEXT("snap"));
			a->snap[i][j] = 0;
		}
	InitializeCriticalSectionAndSpinCount(&a->ResampleSection, 0);
	InitializeCriticalSectionAndSpinCount(&a->SetAnalyzerSection, 0);
	InitializeCriticalSectionAndSpinCount(&a->StitchSection, 0);
	for (i = 0; i < dMAX_PIXOUTS; i++)
		InitializeCriticalSectionAndSpinCount(&a->PB_ControlsSection[i], 0);
	for (i = 0; i < dMAX_STITCH; i++)
	{
		InitializeCriticalSectionAndSpinCount(&(a->EliminateSection[i]), 0);
		for (j = 0; j < dMAX_NUM_FFT; j++)
			InitializeCriticalSectionAndSpinCount(&(a->BufferControlSection[i][j]), 0);
	}

	a->window = (double*) malloc0 (sizeof(double) * a->max_size);

	for (i = 0; i < a->max_stitch; i++)
	{
		a->result[i] = (double*) malloc0 (sizeof(double) * a->max_size);

	}
	for (i = 0; i < a->max_stitch; i++)
		for (j = 0; j < a->max_num_fft; j++)
		{
			a->plan[i][j] = 0;
			a->Cplan[i][j] = 0;
			a->fft_in[i][j]	  = (double*) malloc0 (sizeof(double) * a->max_size);
			a->Cfft_in[i][j]  = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * a->max_size);
			a->fft_out[i][j]  = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * a->max_size);
		}
	a->pre_av_out = (double*) malloc0 (sizeof(double) * a->max_size * a->max_stitch);
	for (i = 0; i < dMAX_PIXOUTS; i++)
	{
		a->det_type[i] = 0;
		a->av_mode[i] = 0;
		a->av_sum[i] = (double*) malloc0 (sizeof(double) * dMAX_PIXELS);
		for (j = 0; j < dMAX_AVERAGE; j++)
			a->av_buff[i][j] = (double*) malloc0 (sizeof(double) * dMAX_PIXELS);
		a->t_pixels[i] = (double*) malloc0 (sizeof(double) * dMAX_PIXELS);
		for (j = 0; j < dNUM_PIXEL_BUFFS; j++)
			a->pixels[i][j] = (dOUTREAL*) malloc0 (sizeof(dOUTREAL) * dMAX_PIXELS);
	}
	
	a->cd = (double*) malloc0 (sizeof(double) * dMAX_PIXELS);
	for (j = 0; j < dMAX_PIXELS; j++)
		a->cd[j] = 1.0;
	for (i = 0; i < dMAX_CAL_SETS; i++)
	{
		a->freqs[i] = (double*) malloc0 (sizeof(double) * dMAX_N);
		for (j = 0; j < dMAX_M; j++)
		{
			a->ac3[i][j] = (double*) malloc0 (sizeof(double) * dMAX_N);
			a->ac2[i][j] = (double*) malloc0 (sizeof(double) * dMAX_N);
			a->ac1[i][j] = (double*) malloc0 (sizeof(double) * dMAX_N);
			a->ac0[i][j] = (double*) malloc0 (sizeof(double) * dMAX_N);
		}
	}
	
	a->size = -1;
	a->window_type = -1;
	a->num_pixels = -1;
	a->cal_set = -1;
	a->f_min = -1.0;
	a->f_max = -1.0;

	a->bsize = a->max_size * dSAMP_BUFF_MULT;
	for (i = 0; i < a->max_stitch; i++)
		for (j = 0; j < a->max_num_fft; j++)
		{
			a->I_samples[i][j] = (dINREAL*) malloc0 (sizeof(dINREAL) * a->bsize);
			a->Q_samples[i][j] = (dINREAL*) malloc0 (sizeof(dINREAL) * a->bsize);
		}

	// Initialize DetectMaxBin functionality
	Init_DetectMaxBin(disp);
	// for test only.
	// SetupDetectMaxBin(1, 0, 0, 0, 192000.0, -3000.0, -300.0, 0.5, 60);
	//

	*success = 0;
}

PORT
void DestroyAnalyzer(int disp)
{
	DP a = pdisp[disp];
	int i, j;

	a->end_dispatcher = 1;
	while (InterlockedAnd(&a->dispatcher, 1))
		Sleep(1);

	for (i = 0; i < a->max_stitch; i++)
		for (j = 0; j < a->max_num_fft; j++)
		{
			_aligned_free  (a->I_samples[i][j]);
			_aligned_free  (a->Q_samples[i][j]);
		}
	
	for (i = 0; i < dMAX_CAL_SETS; i++)
	{
		_aligned_free (a->freqs[i]);
		for (j = 0; j < dMAX_M; j++)
		{
			_aligned_free  (a->ac3[i][j]);
			_aligned_free  (a->ac2[i][j]);
			_aligned_free  (a->ac1[i][j]);
			_aligned_free  (a->ac0[i][j]);
		}
	}
	_aligned_free (a->cd);
	
	for (i = 0; i < dMAX_PIXOUTS; i++)
	{
		for (j = 0; j < dNUM_PIXEL_BUFFS; j++)
			_aligned_free (a->pixels[i][j]);
		_aligned_free  (a->t_pixels[i]);
		for (j = 0; j < dMAX_AVERAGE; j++)
			_aligned_free (a->av_buff[i][j]);
		_aligned_free (a->av_sum[i]);
	}
	
	_aligned_free (a->pre_av_out);
	for (i = 0; i < a->max_stitch; i++)
		for (j = 0; j < a->max_num_fft; j++)
		{
			fftw_destroy_plan (a->plan[i][j]);
			fftw_destroy_plan (a->Cplan[i][j]);
			fftw_free (a->Cfft_in[i][j]);
			_aligned_free (a->fft_in[i][j]);
			fftw_free (a->fft_out[i][j]);
		}
	
	for (i = 0; i < a->max_stitch; i++)
		_aligned_free (a->result[i]);
	_aligned_free (a->window);

	for (i = 0; i < dMAX_STITCH; i++)
	{
		DeleteCriticalSection(&(a->EliminateSection[i]));
		for (j = 0; j < dMAX_NUM_FFT; j++)
			DeleteCriticalSection(&(a->BufferControlSection[i][j]));
	}
	for (i = 0; i < dMAX_PIXOUTS; i++)
		DeleteCriticalSection(&a->PB_ControlsSection[i]);
	DeleteCriticalSection(&a->StitchSection);
	DeleteCriticalSection(&a->SetAnalyzerSection);
	DeleteCriticalSection(&a->ResampleSection);

	for (i = 0; i < a->max_stitch; i++)
		for (j = 0; j < a->max_num_fft; j++)
			CloseHandle(a->hSnapEvent[i][j]);

	_aligned_free ((void *) a->pnum_threads);

	// Destroy DetectMaxBin functionality.
	Destroy_DetectMaxBin(disp);
	//

	_aligned_free (a);
}

PORT
void GetPixels	(	int disp,
					int pixout,
					dOUTREAL *pix,		//if new pixel values avail, copies to pix and sets flag = 1
					int *flag			//else, returns 0 (try again later)
				)
{
	DP a = pdisp[disp];
	EnterCriticalSection(&a->PB_ControlsSection[pixout]);
		a->r_pix_buff[pixout] = a->last_pix_buff[pixout];
	LeaveCriticalSection(&a->PB_ControlsSection[pixout]);

	if (_InterlockedAnd(&(a->pb_ready[pixout][a->r_pix_buff[pixout]]), 1))
	{
		memcpy (pix, a->pixels[pixout][a->r_pix_buff[pixout]], a->num_pixels * sizeof(dOUTREAL));
		*flag = 1;
		InterlockedBitTestAndReset(&(a->pb_ready[pixout][a->r_pix_buff[pixout]]), 0);
	}
	else
		*flag = 0;
}

PORT
void SnapSpectrum(	int disp,
					int ss,
					int LO,
					double *snap_buff)
{
	DP a = pdisp[disp];
	a->snap_buff[ss][LO] = snap_buff;
	InterlockedBitTestAndSet(&(a->snap[ss][LO]), 0);
	WaitForSingleObject(a->hSnapEvent[ss][LO], INFINITE);
}

PORT
void SnapSpectrumTimeout(	int disp,
							int ss,
							int LO,
							double* snap_buff,
							DWORD timeout,
							int* flag)
{
	DP a = pdisp[disp];
	a->snap_buff[ss][LO] = snap_buff;
	ResetEvent(a->hSnapEvent[ss][LO]);
	InterlockedBitTestAndSet(&(a->snap[ss][LO]), 0);
	if (!WaitForSingleObject(a->hSnapEvent[ss][LO], timeout))
		*flag = 1;
	else
	{
		InterlockedBitTestAndReset(&(a->snap[ss][LO]), 0);
		*flag = 0;
	}
}

int calcompare (const void * a, const void * b)
{
	if (*(double*)a < *(double*)b)
		return -1;
	else if (*(double*)a == *(double*)b)
		return 0;
	else
		return 1;
}

PORT
void SetCalibration (	int disp,
						int set_num,				//identifier for this calibration data set
						int n_points,				//number of calibration points in the set
						double (*cal)[dMAX_M+1]		//pointer to the calibration table, first
					)								//	 column is frequency, add'l columns are
													//	 data for variables being calibrated
{
	DP a = pdisp[disp];
	int i, j;
	int k = 0;
	double y [dMAX_N][dMAX_M];

	qsort (cal, n_points, (dMAX_M+1) * sizeof(double), calcompare);

	for (i = 0; i < n_points; i++)
	{
		if ((i == n_points - 1) || (cal[i][0] != cal[i + 1][0]))
		{
			(a->freqs[set_num])[k] = cal[i][0];
			for (j = 0; j < dMAX_M; j++)
				y[k][j] = cal[i][j + 1];
			k++;
		}
	}

	a->n_freqs[set_num] = k;

	build_interpolants(disp, set_num, a->n_freqs[set_num], dMAX_M, a->freqs[set_num], y);
	a->cal_changed = 1;
}

PORT
void OpenBuffer(int disp, int ss, int LO, void **Ipointer, void **Qpointer)
{
	DP a = pdisp[disp];
	EnterCriticalSection(&a->SetAnalyzerSection);
	*Ipointer = &((a->I_samples[ss][LO])[a->IQin_index[ss][LO]]);
	*Qpointer = &((a->Q_samples[ss][LO])[a->IQin_index[ss][LO]]);
	LeaveCriticalSection(&a->SetAnalyzerSection);
}

PORT
void CloseBuffer(int disp, int ss, int LO)
{
	DP a = pdisp[disp];
	EnterCriticalSection(&a->SetAnalyzerSection);
	EnterCriticalSection(&(a->BufferControlSection[ss][LO]));
		if (a->have_samples[ss][LO] > a->max_writeahead)
			{
				//if we're receiving samples too much faster than we're consuming them, skip some
				if ((a->IQout_index[ss][LO] += a->have_samples[ss][LO] - a->max_writeahead) >= a->bsize)
						a->IQout_index[ss][LO] -= a->bsize;
				a->have_samples[ss][LO] = a->max_writeahead;
			}
		if ((a->have_samples[ss][LO] += a->buff_size) >= a->size)
			InterlockedBitTestAndSet(&(a->buff_ready[ss][LO]), 0);
	LeaveCriticalSection(&(a->BufferControlSection[ss][LO]));
	if((a->IQin_index[ss][LO] += a->buff_size) >= a->bsize) //REQUIRES buff_size IS A SUB-MULTIPLE OF SIZE OF INPUT SAMPLE BUFFS!
		a->IQin_index[ss][LO] = 0;

	if (!InterlockedAnd(&a->dispatcher, 1))
	{
		InterlockedBitTestAndSet (&a->dispatcher, 0);
		LeaveCriticalSection(&a->SetAnalyzerSection);
		_beginthread(sendbuf, 0, (void *)(uintptr_t)disp);
	}
	else
		LeaveCriticalSection(&a->SetAnalyzerSection);
}

PORT
void Spectrum(int disp, int ss, int LO, dINREAL* pI, dINREAL* pQ)
{
	dINREAL *Ipointer;
	dINREAL *Qpointer;
	DP a = pdisp[disp];
	EnterCriticalSection(&a->SetAnalyzerSection);
	Ipointer = &((a->I_samples[ss][LO])[a->IQin_index[ss][LO]]);
	Qpointer = &((a->Q_samples[ss][LO])[a->IQin_index[ss][LO]]);
	LeaveCriticalSection(&a->SetAnalyzerSection);

	memcpy(Ipointer, pI, a->buff_size * sizeof(dINREAL));
	memcpy(Qpointer, pQ, a->buff_size * sizeof(dINREAL));

	EnterCriticalSection(&a->SetAnalyzerSection);
	EnterCriticalSection(&(a->BufferControlSection[ss][LO]));
		if (a->have_samples[ss][LO] > a->max_writeahead)
			{
				//if we're receiving samples too much faster than we're consuming them, skip some
				if ((a->IQout_index[ss][LO] += a->have_samples[ss][LO] - a->max_writeahead) >= a->bsize)
						a->IQout_index[ss][LO] -= a->bsize;
				a->have_samples[ss][LO] = a->max_writeahead;
			}
		if ((a->have_samples[ss][LO] += a->buff_size) >= a->size)
			InterlockedBitTestAndSet(&(a->buff_ready[ss][LO]), 0);
	LeaveCriticalSection(&(a->BufferControlSection[ss][LO]));
	if((a->IQin_index[ss][LO] += a->buff_size) >= a->bsize) //REQUIRES buff_size IS A SUB-MULTIPLE OF SIZE OF INPUT SAMPLE BUFFS!
		a->IQin_index[ss][LO] = 0;

	if (!InterlockedAnd(&a->dispatcher, 1))
	{
		InterlockedBitTestAndSet(&a->dispatcher, 0);
		LeaveCriticalSection(&a->SetAnalyzerSection);
		_beginthread(sendbuf, 0, (void *)(uintptr_t)disp);
	}
	else
		LeaveCriticalSection(&a->SetAnalyzerSection);
}

PORT
void Spectrum2(int run, int disp, int ss, int LO, dINREAL* pbuff)
{
	if (run)
	{
		int i;
		dINREAL *Ipointer;
		dINREAL *Qpointer;
		DP a = pdisp[disp];
		EnterCriticalSection(&a->SetAnalyzerSection);
		Ipointer = &((a->I_samples[ss][LO])[a->IQin_index[ss][LO]]);
		Qpointer = &((a->Q_samples[ss][LO])[a->IQin_index[ss][LO]]);
		LeaveCriticalSection(&a->SetAnalyzerSection);

		for (i = 0; i < a->buff_size; i++)
		{
			Ipointer[i] = pbuff[2 * i + 1];
			Qpointer[i] = pbuff[2 * i + 0];
		}

		EnterCriticalSection(&a->SetAnalyzerSection);
		EnterCriticalSection(&(a->BufferControlSection[ss][LO]));
			if (a->have_samples[ss][LO] > a->max_writeahead)
				{
					//if we're receiving samples too much faster than we're consuming them, skip some
					if ((a->IQout_index[ss][LO] += a->have_samples[ss][LO] - a->max_writeahead) >= a->bsize)
							a->IQout_index[ss][LO] -= a->bsize;
					a->have_samples[ss][LO] = a->max_writeahead;
				}
			if ((a->have_samples[ss][LO] += a->buff_size) >= a->size)
				InterlockedBitTestAndSet(&(a->buff_ready[ss][LO]), 0);
		LeaveCriticalSection(&(a->BufferControlSection[ss][LO]));
		if((a->IQin_index[ss][LO] += a->buff_size) >= a->bsize) //REQUIRES buff_size IS A SUB-MULTIPLE OF SIZE OF INPUT SAMPLE BUFFS!
			a->IQin_index[ss][LO] = 0;

		if (!InterlockedAnd(&a->dispatcher, 1))
		{
			InterlockedBitTestAndSet(&a->dispatcher, 0);
			LeaveCriticalSection(&a->SetAnalyzerSection);
			_beginthread(sendbuf, 0, (void *)(uintptr_t)disp);
		}
		else
			LeaveCriticalSection(&a->SetAnalyzerSection);
	}
}

PORT
void Spectrum0(int run, int disp, int ss, int LO, double* pbuff)
{
	if (run)
	{
		int i;
		dINREAL *Ipointer;
		dINREAL *Qpointer;
		DP a = pdisp[disp];
		EnterCriticalSection(&a->SetAnalyzerSection);
		Ipointer = &((a->I_samples[ss][LO])[a->IQin_index[ss][LO]]);
		Qpointer = &((a->Q_samples[ss][LO])[a->IQin_index[ss][LO]]);
		LeaveCriticalSection(&a->SetAnalyzerSection);

		for (i = 0; i < a->buff_size; i++)
		{
			Ipointer[i] = (dINREAL)pbuff[2 * i + 1];
			Qpointer[i] = (dINREAL)pbuff[2 * i + 0];
		}

		EnterCriticalSection(&a->SetAnalyzerSection);
		EnterCriticalSection(&(a->BufferControlSection[ss][LO]));
			if (a->have_samples[ss][LO] > a->max_writeahead)
				{
					//if we're receiving samples too much faster than we're consuming them, skip some
					if ((a->IQout_index[ss][LO] += a->have_samples[ss][LO] - a->max_writeahead) >= a->bsize)
						a->IQout_index[ss][LO] -= a->bsize;
					a->have_samples[ss][LO] = a->max_writeahead;
				}
			if ((a->have_samples[ss][LO] += a->buff_size) >= a->size)
				InterlockedBitTestAndSet(&(a->buff_ready[ss][LO]), 0);
		LeaveCriticalSection(&(a->BufferControlSection[ss][LO]));
		if((a->IQin_index[ss][LO] += a->buff_size) >= a->bsize) //REQUIRES buff_size IS A SUB-MULTIPLE OF SIZE OF INPUT SAMPLE BUFFS!
			a->IQin_index[ss][LO] = 0;

		if (!InterlockedAnd(&a->dispatcher, 1))
		{
			InterlockedBitTestAndSet(&a->dispatcher, 0);
			LeaveCriticalSection(&a->SetAnalyzerSection);
			_beginthread(sendbuf, 0, (void *)(uintptr_t)disp);
		}
		else
			LeaveCriticalSection(&a->SetAnalyzerSection);
	}
}

PORT
void SetDisplayDetectorMode (int disp, int pixout, int mode)
{
	DP a = pdisp[disp];
	if (a->det_type[pixout] != mode)
	{
		EnterCriticalSection (&a->ResampleSection);
		a->det_type[pixout] = mode;
		LeaveCriticalSection (&a->ResampleSection);
	}
}

PORT
void SetDisplayAverageMode (int disp, int pixout, int mode)
{
	int i;
	DP a = pdisp[disp];
	if (a->av_mode[pixout] != mode)
	{
		EnterCriticalSection (&a->ResampleSection);
		a->av_mode[pixout] = mode;
		switch (mode)
		{
		case 1:
			for (i = 0; i < dMAX_PIXELS; i++)
				a->av_sum[pixout][i] = 1.0e-12;
			break;
		case 2:
			a->avail_frames[pixout] = 0;
			a->av_in_idx[pixout] = 0;
			a->av_out_idx[pixout] = 0;
			break;
		case 3:
			for (i = 0; i < dMAX_PIXELS; i++)
				a->av_sum[pixout][i] = -160.0;
			break;
		default:
			memset ((void *)a->av_sum[pixout], 0, sizeof(double) * dMAX_PIXELS);
			break;
		}
		LeaveCriticalSection (&a->ResampleSection);
	}
}

PORT
void SetDisplayNumAverage (int disp, int pixout, int num)
{
	DP a = pdisp[disp];
	if (a->num_average[pixout] != num)
	{
		EnterCriticalSection (&a->ResampleSection);
		a->num_average[pixout] = num;
		a->avail_frames[pixout] = 0;
		a->av_in_idx[pixout] = 0;
		a->av_out_idx[pixout] = 0;
		LeaveCriticalSection (&a->ResampleSection);
	}
}

PORT
void SetDisplayAvBackmult (int disp, int pixout, double mult)
{
	DP a = pdisp[disp];
	if (a->av_backmult[pixout] != mult)
	{
		EnterCriticalSection (&a->ResampleSection);
		a->av_backmult[pixout] = mult;
		LeaveCriticalSection (&a->ResampleSection);
	}
}

PORT
void SetDisplaySampleRate (int disp, int rate)
{
	DP a = pdisp[disp];
	if (a->sample_rate != rate)
	{
		EnterCriticalSection (&a->ResampleSection);
		a->sample_rate = rate;
		CalcBandwidthNormalization (a);
		LeaveCriticalSection (&a->ResampleSection);
	}
}

PORT
void SetDisplayNormOneHz (int disp, int pixout, int norm)
{
	DP a = pdisp[disp];
	if (a->normalize[pixout] != norm)
	{
		EnterCriticalSection (&a->ResampleSection);
		a->normalize[pixout] = norm;
		LeaveCriticalSection (&a->ResampleSection);
	}
}

PORT
double GetDisplayENB (int disp)
{
	DP a = pdisp[disp];
	double enb;
	EnterCriticalSection(&a->SetAnalyzerSection);
	enb = 1.0 / a->inv_enb;
	LeaveCriticalSection(&a->SetAnalyzerSection);
	return enb;
}
