//******************************************************************************************
template <class A> struct _ShiftApply
{
public:
	DtBlkFx* b;

	// effect data maintained between calls
	struct Data
	{
		// running phase change to fix angle
		long angle_fix;

		// 
		double angle_fix_per_samp;

		// previous start & stop bin fractions of fft len (rng is 0 .. 0.5f)
		float prev_bin_frac[2];
	};

	// freq fft length
	long fft_len;

	// bin shift distance
	long bin_shift;

	void run(
		A& process,
		const long src_bin[2],	// src start & stop bins
		cplxf phase_fix
	)
	// this is implemented as a template instead of using virtual functions to help optimizer
	//
	// return the change in power after applying effect
	{
		float* x = b->getFFTdat();
		float curr_amp = b->currFxParams().amp;
		long n_bins = src_bin[1]-src_bin[0];
		long start_bin = src_bin[0];
		long dest_bin = src_bin[0]+bin_shift;

		// adjust ranges if destination goes outside of fft data
		if(dest_bin < 1)  {
			start_bin += (1-dest_bin);
			n_bins -= (1-dest_bin);
			dest_bin = 1;
		}
		if(dest_bin+n_bins >= fft_len/2-1) n_bins = fft_len/2-1 - dest_bin;

		if(n_bins < 1) return;

		// change in pwr due to effect
		float pwr = 0;

		if(start_bin > dest_bin) {
			// process spectrum forwards
			pcplxf xs(x, fft_len, start_bin);
			pcplxf xd(x, fft_len, dest_bin);
			float *xe = xs.r+n_bins;
			for(; xs.r < xe; xs++, xd++) {
				pwr -= norm(*xd);
				process(*xs * phase_fix, xd, curr_amp);
				pwr += norm(*xd);
			}
		}
		else {
			// process spectrum in reverse
			pcplxf xs(x, fft_len, start_bin+n_bins-1);
			pcplxf xd(x, fft_len, dest_bin+n_bins-1);
			float *xe = xs.r-n_bins;
			
			for(; xs.r > xe; xs--, xd--) {
				pwr -= norm(*xd);
				process(*xs * phase_fix, xd, curr_amp);
				pwr += norm(*xd);
			}
		}

		// adjust pwr
		b->_total_out_pwr += pwr;
	}

	static void getOverlap(long out[2], const long in_a[2], const long in_b[2])
	{
		// get overlap, if no overlap range will be inverted
		out[0] = max(in_a[0], in_b[0]);
		out[1] = min(in_a[1], in_b[1]);
	}

	void sortRng(long out[2][2], long a, long b)
	{
		if(a <= b) {
			// normal case
			// out[0] is the range a to b
			// out[1] is at the end of the spectrum (fft_len/2)
			out[0][0] = a;
			out[1][0] = fft_len/2-1;
		}
		else {
			// inverted case
			// out[0] is the range 1 to b
			// out[1] is the range a to fft_len/2
			out[0][0] = 1;
			out[1][0] = a;
		}
		out[0][1] = b;
		out[1][1] = fft_len/2-1;
	}

	void run(A& process)
	//
	// expect "process" to contain operator (cplxf src, pcplxf dst, float amp)
	//
	// determine whether to process inside or outside of frequency range
	// template version to help optimizer
	//
	// return the change in power
	{
		// get effect data from blkfx
		DtBlkFx::FxParams& p = b->currFxParams();
		Data& d = *(Data*)&p.data[0];

		// see whether we should initialize the data
		if(p.changed) {
			DBG("_ShiftApply, clearing effect data");
			memset(&d, 0, sizeof(d));
		}

		fft_len = b->_freq_fft_n;
		float f_fft_len = (float)fft_len;
		float samp_rate = b->getSampleRate();
		float hz_shift = GetFreqShift(p.val);

		// calculate distance in number of bins to shift (positive is up)
		bin_shift = (long)(hz_shift/samp_rate*f_fft_len + 0.5f);

		// calculate phase fix based on how far we've moved since the prev blk
		d.angle_fix += (long)(d.angle_fix_per_samp * b->_next_blk_fwd_n);
		d.angle_fix &= sincos_table.IMASK;
		cplxf phase_fix = sincos_table[d.angle_fix];

		// find ranges to process
		long curr_rng[2][2];
		sortRng(curr_rng, p.bin[0], p.bin[1]);

		// find what the previous ranges were
		long prev_rng[2][2];
		sortRng(prev_rng, (long)(d.prev_bin_frac[0]*f_fft_len+0.5), (long)(d.prev_bin_frac[1]*f_fft_len+0.5));

		for(int i = 0; i < 2; i++) {
			// find which parts overlap with previous block and need phase correction
			// we may need to process in up to 3 parts - overlapping range either side
			// of current range plus a non-overlapping part in the middle

			// the phase correction doesn't work all that great if the shift frequency is changing
			// because beating will occur in overlapping section - the easiest fix is to reduce
			// overlap
			// a complex version would be to slide the the frequency in the time domain

			// get overlap with 2 previous ranges - these need phase correction
			long olap[2][2];
			getOverlap(olap[0], curr_rng[i], prev_rng[0]);
			getOverlap(olap[1], curr_rng[i], prev_rng[1]);

			// run processing on overlapping parts and find non-overlapping part
			long non_olap[2];
			if(olap[0][0] < olap[0][1]) {
				// overlap at the start
				run(process, olap[0], phase_fix);
				non_olap[0] = olap[0][1];
			}
			else non_olap[0] = curr_rng[i][0];

			if(olap[1][0] < olap[1][1]) {
				// overlap at the end
				run(process, olap[1], phase_fix);
				non_olap[1] = olap[1][0];
			}
			else non_olap[1] = curr_rng[i][1];

			// do the non-overlapping part with no phase correction
			run(process, non_olap, cplxf(1.0f, 0.0f));
		}

		// save the bin locations that we processed for next blk
		d.prev_bin_frac[0] = p.fbin[0]/f_fft_len;
		d.prev_bin_frac[1] = p.fbin[1]/f_fft_len;

		// work out the angle fix per sample for next blk
		d.angle_fix_per_samp = ((double)sincos_table.LEN * bin_shift) / f_fft_len;
	}
};
template <class A> void ShiftApply(DtBlkFx* b, A& process)
{
	_ShiftApply<A> a;
	a.b = b;
	a.run(process);
}

struct _ShiftAddEffect
{
	inline void operator ()(cplxf src, pcplxf dst, float curr_amp) { *dst = src * curr_amp + *dst; }
};




//******************************************************************************************
struct WeedProcess : public AmpProcess
{
	//
	float out_pwr;

	// threshold to weed above/below
	float thresh;

	// width of weed
	float width;

	// whether to weed below (true) threshold or above (false)
	bool remove_low;

	struct Data
	{
		float thresh_param;
		float width_param;
	};
	WeedProcess(DtBlkFx* b) : AmpProcess(b, /*amp not used*/0)
	{
		out_pwr = 0;

		DtBlkFx::FxParams& params = b->currFxParams();
		Data& d = *(Data*)params.data[0];
		if(params.prev_type != ??)
		{
			d.thresh = 0.75f;
			d.width = 0.2f;
		}

		// scale threshold: -1 .. 1
		if(params.type == ??)
			d.thresh_param = params.val;
		else
			d.width_param = params.val;

		thresh = d.thresh_param*2.0f-1.0f; 

		// find a weeding threshold
		if(thresh >= 0) remove_low = true;
		else {
			remove_low = false;
			thresh = 1+thresh;
		}

		// arbitrary scaling on threshold
		thresh *= thresh*24.0f*b->_total_out_pwr/(float)b->_freq_fft_n;
	}

	void run(long b0, long b1)
	// weed out bins with magnitude lower than the weed threshold
	{
		if(remove_low) {
			// remove stuff below the threshold
			long width = 0;
			long dist = 0;
			pcplxf d(x1);
			pcplxf_range x(/*AmpProcess::*/x1, b0, b1+1);
			while(!x.empty()) {
				float t0 = norm(*x);
				/*AmpProcess::*/in_pwr += t0;
				if(t0 < thresh) {
					if(dist > width) {
						*d = cplxf(0,0);
					}
					*x = 
					x++;
				}
				else {
					while(dist) {
						t0 = norm(*d);
						in_pwr += t0;
						out_pwr += t0;
						d.r++;
						dist--;
					}

					out_pwr += t0;

					// don't touch the next "width" bins
					long i = width;
					while(i && !x.empty()) {
						t0 = norm(*x);
						in_pwr += t0;
						out_pwr += t0;
						x++;
						i--;
					}

					x++;
					d = x;
				}
			}
		}
		else {
			// remove high
			for(pcplxf_range x(/*AmpProcess::*/x1, b0, b1+1); !x.empty(); x++) {
				float t0 = norm(*x);
				/*AmpProcess::*/in_pwr += t0;
				if(t0 >= thresh) *x = cplxf(0,0);
				else out_pwr += t0;
			}
		}
	}

	float pwrChg() { return out_pwr-/*AmpProcess::*/in_pwr; }
};



//*************************************************************************************************
void AutoHarmEffect(ProcessArgs& a)
// auto amplify loudest harmonic
{
	DtBlkFx* b = a.b;
	DtBlkFx::FxParams& p = b->currFxParams();
	long start_bin = p.bin[0];
	long stop_bin = p.bin[1];

	// default to processing entire range
	long process_stop_bin = b->_freq_fft_n/2-1;

	if(start_bin > stop_bin) {
		// if freqA < freqB, processing only occurs in the range specified
		swap(start_bin, stop_bin);
		process_stop_bin = stop_bin;
	}

	// find the bin having the highest power in the freq range specified
	pcplxf x(b->getFFTdat(), b->_freq_fft_n, start_bin);
	float* xe = &(b->_x1[stop_bin]);

	float* x_pos = 0;
	float max_pwr = 0.0f;
	for(; x.r < xe; x++) {
		float t = norm(*x);
		if(t > max_pwr) {
			max_pwr = t;
			x_pos = x.r;
		}
	}
	
	if(max_pwr == 0.0f) return; // nothing found...

	// find position of harmonic
	float harmonic = (float)(x_pos-b->getFFTdat());

	DtBlkFx::FxParams& params = a.b->currFxParams();
	HarmMaskProcess<AmpProcess> harm(
		AmpProcess(a.b, params.amp),	//
		harmonic,	// cent
		p.val
	);
	run(a.b, harm, 1, process_stop_bin);
}




//-------------------------------------------------------------------------------------------------
template <class T> class InvMaskProcess : public MaskProcessBase<T>
//
// invert ranges processed
//
{
public:

	void init(T& process, long min_bin, long max_bin)
	{
		/*MaskProcessBase::*/_process = &process;

		// bin ranges are the same as SplitMaskProcess
		if(params.bin[0] <= params.bin[1]) {
			// include region
			_incl_bin[0] = params.bin[0];
			_incl_bin[1] = params.bin[1];
			_excl_bin[0] = -1;
			_excl_bin[1] = -1;
		}
		else {
			// exclude region
			_incl_bin[0] = 1;
			_incl_bin[1] = b->_freq_fft_n/2-1;
			_excl_bin[0] = params.bin[1];
			_excl_bin[1] = params.bin[0];
		}
	}

	InvMaskProcess() {}

	InvMaskProcess(T& process, DtBlkFx* b, DtBlkFx::FxParams& params)
		{ init(process, b, params); }

	// inverted ranges are limited to the include (incl) bin range
	long _incl_bin[2];

	// inverted ranges will not contain the excluded (excl) bin range
	long _excl_bin[2];

	long _prev_bin;

	// return true if "bin" is within the exclusion range
	bool inExcl(long bin) { return bin >= _excl_bin[0] && bin <= _excl_bin[1]; }

	// prepare to run
	void prepare()
	{
		_process->prepare();

		if(reverse()) {
			_prev_bin = _incl_bin[1];
			if(inExcl(prev_bin)) _prev_bin = _excl_bin[0]-1;
		}
		else {
			_prev_bin = _incl_bin[0];
			if(inExcl(prev_bin)) _prev_bin = _excl_bin[1]+1;
		}
	}

	// do the processing
	void run(long b0, long b1)
	{
		if(inExcl(b0)) b0 = _excl_bin[0];
		if(inExcl(b1)) b1 = _excl_bin[1];
		if(reverse()) {
			long v0 = max(b1+1, _incl_bin[0]);
			if(v0 <= prev_bin) {
				_process->run(v0, _prev_bin);
				_prev_bin = b0-1;
			}
		}
		else {
			long v1 = min(b0-1, _incl_bin[1]);
			if(prev_bin <= v1) {
				_process->run(_prev_bin, v1);
				_prev_bin = b1+1;
			}
		}
	}

	// processing complete
	void done()
	{
		if(reverse()) {
			if(_incl_bin[0] <= _prev_bin) _process->run(_incl_bin[0], _prev_bin);
		}
		else {
			if(_prev_bin <= _incl_bin[1]) _process->run(_prev_bin, _incl_bin[1]);
		}
		_process->done();
	}
};




// frequency phase match calculation

		float fft_len = (float)b->_freq_fft_n;
		// calculate distance in number of bins to shift (positive is up)
		_bin_shift = (long)(
			GetFreqShift(params.val)/b->getSampleRate()*fft_len + 0.5f
		);

		// calculate phase fix based on how far we've moved since the prev blk
		d.angle_fix += (long)(d.angle_fix_per_samp * b->_next_blk_fwd_n);
		d.angle_fix &= sincos_table.IMASK;
		_phase_fix_amp = sincos_table[d.angle_fix] * AmpProcess::_amp;

		// work out the angle fix per sample for next blk
		d.angle_fix_per_samp = ((double)sincos_table.LEN * _bin_shift) / fft_len;
