#include <../source/TAL/objbase.h>

/*
 * The core FFT routines.
 */

/*
 * Copyright:
 *	Copyright(C) 1996-1999 Takuya OOURA
 * 	email: ooura@mmm.t.u-tokyo.ac.jp
 * 	download: http://momonga.t.u-tokyo.ac.jp/~ooura/fft.html
 * 	You may use, copy, modify this code for any purpose and
 *	without fee. You may distribute this ORIGINAL package.
 *
 *  UPD: http://www.kurims.kyoto-u.ac.jp/~ooura
 */

/*
Fast Fourier/Cosine/Sine Transform
	dimension   :one
	data length :power of 2
	decimation  :frequency
	radix       :4, 2
	data        :inplace
	table       :use
functions
	cdft: Complex Discrete Fourier Transform
	rdft: Real Discrete Fourier Transform
	ddct: Discrete Cosine Transform
	ddst: Discrete Sine Transform
	dfct: Cosine Transform of RDFT (Real Symmetric DFT)
	dfst: Sine Transform of RDFT (Real Anti-symmetric DFT)
function prototypes
	void cdft(int, int, vec_t *, int *, vec_t *);
	void rdft(int, int, vec_t *, int *, vec_t *);
	void ddct(int, int, vec_t *, int *, vec_t *);
	void ddst(int, int, vec_t *, int *, vec_t *);
	void dfct(int, vec_t *, vec_t *, int *, vec_t *);
	void dfst(int, vec_t *, vec_t *, int *, vec_t *);


-------- Complex DFT (Discrete Fourier Transform) --------
	[definition]
		<case1>
			X[k] = sum_j=0^n-1 x[j]*exp(2*pi*i*j*k/n), 0<=k<n
		<case2>
			X[k] = sum_j=0^n-1 x[j]*exp(-2*pi*i*j*k/n), 0<=k<n
		(notes: sum_j=0^n-1 is a summation from j=0 to n-1)
	[usage]
		<case1>
			ip[0] = 0; // first time only
			cdft(2*n, 1, a, ip, w);
		<case2>
			ip[0] = 0; // first time only
			cdft(2*n, -1, a, ip, w);
	[parameters]
		2*n            :data length (int)
						n >= 1, n = power of 2
		a[0...2*n-1]   :input/output data (vec_t *)
						input data
							a[2*j] = Re(x[j]), 
							a[2*j+1] = Im(x[j]), 0<=j<n
						output data
							a[2*k] = Re(X[k]), 
							a[2*k+1] = Im(X[k]), 0<=k<n
		ip[0...*]      :work area for bit reversal (int *)
						length of ip >= 2+sqrt(n)
						strictly, 
						length of ip >= 
							2+(1<<(int)(log(n+0.5)/log(2))/2).
						ip[0],ip[1] are pointers of the cos/sin table.
		w[0...n/2-1]   :cos/sin table (vec_t *)
						w[],ip[] are initialized if ip[0] == 0.
	[remark]
		Inverse of 
			cdft(2*n, -1, a, ip, w);
		is 
			cdft(2*n, 1, a, ip, w);
			for (j = 0; j <= 2 * n - 1; j++) {
				a[j] *= 1.0 / n;
			}
		.

-------- Real DFT / Inverse of Real DFT --------
	[definition]
		<case1> RDFT
			R[k] = sum_j=0^n-1 a[j]*cos(2*pi*j*k/n), 0<=k<=n/2
			I[k] = sum_j=0^n-1 a[j]*sin(2*pi*j*k/n), 0<k<n/2
		<case2> IRDFT (excluding scale)
			a[k] = (R[0] + R[n/2]*cos(pi*k))/2 + 
				   sum_j=1^n/2-1 R[j]*cos(2*pi*j*k/n) + 
				   sum_j=1^n/2-1 I[j]*sin(2*pi*j*k/n), 0<=k<n
	[usage]
		<case1>
			ip[0] = 0; // first time only
			rdft(n, 1, a, ip, w);
		<case2>
			ip[0] = 0; // first time only
			rdft(n, -1, a, ip, w);
	[parameters]
		n              :data length (int)
						n >= 2, n = power of 2
		a[0...n-1]     :input/output data (vec_t *)
						<case1>
							output data
								a[2*k] = R[k], 0<=k<n/2
								a[2*k+1] = I[k], 0<k<n/2
								a[1] = R[n/2]
						<case2>
							input data
								a[2*j] = R[j], 0<=j<n/2
								a[2*j+1] = I[j], 0<j<n/2
								a[1] = R[n/2]
		ip[0...*]      :work area for bit reversal (int *)
						length of ip >= 2+sqrt(n/2)
						strictly, 
						length of ip >= 
							2+(1<<(int)(log(n/2+0.5)/log(2))/2).
						ip[0],ip[1] are pointers of the cos/sin table.
		w[0...n/2-1]   :cos/sin table (vec_t *)
						w[],ip[] are initialized if ip[0] == 0.
	[remark]
		Inverse of 
			rdft(n, 1, a, ip, w);
		is 
			rdft(n, -1, a, ip, w);
			for (j = 0; j <= n - 1; j++) {
				a[j] *= 2.0 / n;
			}
		.

-------- DCT (Discrete Cosine Transform) / Inverse of DCT --------
	[definition]
		<case1> IDCT (excluding scale)
			C[k] = sum_j=0^n-1 a[j]*cos(pi*j*(k+1/2)/n), 0<=k<n
		<case2> DCT
			C[k] = sum_j=0^n-1 a[j]*cos(pi*(j+1/2)*k/n), 0<=k<n
	[usage]
		<case1>
			ip[0] = 0; // first time only
			ddct(n, 1, a, ip, w);
		<case2>
			ip[0] = 0; // first time only
			ddct(n, -1, a, ip, w);
	[parameters]
		n              :data length (int)
						n >= 2, n = power of 2
		a[0...n-1]     :input/output data (vec_t *)
						output data
							a[k] = C[k], 0<=k<n
		ip[0...*]      :work area for bit reversal (int *)
						length of ip >= 2+sqrt(n/2)
						strictly, 
						length of ip >= 
							2+(1<<(int)(log(n/2+0.5)/log(2))/2).
						ip[0],ip[1] are pointers of the cos/sin table.
		w[0...n*5/4-1] :cos/sin table (vec_t *)
						w[],ip[] are initialized if ip[0] == 0.
	[remark]
		Inverse of 
			ddct(n, -1, a, ip, w);
		is 
			a[0] *= 0.5;
			ddct(n, 1, a, ip, w);
			for (j = 0; j <= n - 1; j++) {
				a[j] *= 2.0 / n;
			}
		.


-------- DST (Discrete Sine Transform) / Inverse of DST --------
	[definition]
		<case1> IDST (excluding scale)
			S[k] = sum_j=1^n A[j]*sin(pi*j*(k+1/2)/n), 0<=k<n
		<case2> DST
			S[k] = sum_j=0^n-1 a[j]*sin(pi*(j+1/2)*k/n), 0<k<=n
	[usage]
		<case1>
			ip[0] = 0; // first time only
			ddst(n, 1, a, ip, w);
		<case2>
			ip[0] = 0; // first time only
			ddst(n, -1, a, ip, w);
	[parameters]
		n              :data length (int)
						n >= 2, n = power of 2
		a[0...n-1]     :input/output data (vec_t *)
						<case1>
							input data
								a[j] = A[j], 0<j<n
								a[0] = A[n]
							output data
								a[k] = S[k], 0<=k<n
						<case2>
							output data
								a[k] = S[k], 0<k<n
								a[0] = S[n]
		ip[0...*]      :work area for bit reversal (int *)
						length of ip >= 2+sqrt(n/2)
						strictly, 
						length of ip >= 
							2+(1<<(int)(log(n/2+0.5)/log(2))/2).
						ip[0],ip[1] are pointers of the cos/sin table.
		w[0...n*5/4-1] :cos/sin table (vec_t *)
						w[],ip[] are initialized if ip[0] == 0.
	[remark]
		Inverse of 
			ddst(n, -1, a, ip, w);
		is 
			a[0] *= 0.5;
			ddst(n, 1, a, ip, w);
			for (j = 0; j <= n - 1; j++) {
				a[j] *= 2.0 / n;
			}
		.


-------- Cosine Transform of RDFT (Real Symmetric DFT) --------
	[definition]
		C[k] = sum_j=0^n a[j]*cos(pi*j*k/n), 0<=k<=n
	[usage]
		ip[0] = 0; // first time only
		dfct(n, a, t, ip, w);
	[parameters]
		n              :data length - 1 (int)
						n >= 2, n = power of 2
		a[0...n]       :input/output data (vec_t *)
						output data
							a[k] = C[k], 0<=k<=n
		t[0...n/2]     :work area (vec_t *)
		ip[0...*]      :work area for bit reversal (int *)
						length of ip >= 2+sqrt(n/4)
						strictly, 
						length of ip >= 
							2+(1<<(int)(log(n/4+0.5)/log(2))/2).
						ip[0],ip[1] are pointers of the cos/sin table.
		w[0...n*5/8-1] :cos/sin table (vec_t *)
						w[],ip[] are initialized if ip[0] == 0.
	[remark]
		Inverse of 
			a[0] *= 0.5;
			a[n] *= 0.5;
			dfct(n, a, t, ip, w);
		is 
			a[0] *= 0.5;
			a[n] *= 0.5;
			dfct(n, a, t, ip, w);
			for (j = 0; j <= n; j++) {
				a[j] *= 2.0 / n;
			}
		.


-------- Sine Transform of RDFT (Real Anti-symmetric DFT) --------
	[definition]
		S[k] = sum_j=1^n-1 a[j]*sin(pi*j*k/n), 0<k<n
	[usage]
		ip[0] = 0; // first time only
		dfst(n, a, t, ip, w);
	[parameters]
		n              :data length + 1 (int)
						n >= 2, n = power of 2
		a[0...n-1]     :input/output data (vec_t *)
						output data
							a[k] = S[k], 0<k<n
						(a[0] is used for work area)
		t[0...n/2-1]   :work area (vec_t *)
		ip[0...*]      :work area for bit reversal (int *)
						length of ip >= 2+sqrt(n/4)
						strictly, 
						length of ip >= 
							2+(1<<(int)(log(n/4+0.5)/log(2))/2).
						ip[0],ip[1] are pointers of the cos/sin table.
		w[0...n*5/8-1] :cos/sin table (vec_t *)
						w[],ip[] are initialized if ip[0] == 0.
	[remark]
		Inverse of 
			dfst(n, a, t, ip, w);
		is 
			dfst(n, a, t, ip, w);
			for (j = 1; j <= n - 1; j++) {
				a[j] *= 2.0 / n;
			}
		.


Appendix :
	The cos/sin table is recalculated when the larger table required.
	w[] and ip[] are compatible with all routines.
*/

void makewt(int nw, int *ip, vec_t *w);
void makect(int nc, int *ip, vec_t *c);
void bitrv2(int n, int *ip, vec_t *a);
void bitrv2conj(int n, int *ip, vec_t *a);
void cftfsub(int n, vec_t *a, vec_t *w);
void cftbsub(int n, vec_t *a, vec_t *w);
void rftfsub(int n, vec_t *a, int nc, vec_t *c);
void rftbsub(int n, vec_t *a, int nc, vec_t *c);
void cft1st(int n, vec_t *a, vec_t *w);
void cftmdl(int n, int l, vec_t *a, vec_t *w);

V_LOCAL void
oo_cdft(int n, int isgn, vec_t *a, int *ip, vec_t *w)
{
	if (n > (ip[0] << 2)) {
		makewt(n >> 2, ip, w);
	}
	if (n > 4) {
		if (isgn >= 0) {
			bitrv2(n, ip + 2, a);
			cftfsub(n, a, w);
		} else {
			bitrv2conj(n, ip + 2, a);
			cftbsub(n, a, w);
		}
	} else if (n == 4) {
		cftfsub(n, a, w);
	}
	/*
	 * FIXME: something _is_ wrong with definition of isgn == -1
	 * as a sign of _forward_ transform - V.S.
	 */
	if(isgn <= 0)
	{
		int j;
		vec_t scale = (vec_t)2.0 / n;
		for (j = 0; j < n; j++)
			a[j] *= scale;
	}
}

V_LOCAL void
oo_rdft(int n, int isgn, vec_t *a, int *ip, vec_t *w)
{
	int nw, nc;
	vec_t xi;

	nw = ip[0];
	if (n > (nw << 2)) {
		nw = n >> 2;
		makewt(nw, ip, w);
	}
	nc = ip[1];
	if (n > (nc << 2)) {
		nc = n >> 2;
		makect(nc, ip, w + nw);
	}
	if (isgn >= 0) {
		if (n > 4) {
			bitrv2(n, ip + 2, a);
			cftfsub(n, a, w);
			rftfsub(n, a, nc, w + nw);
		} else if (n == 4) {
			cftfsub(n, a, w);
		}
		xi = a[0] - a[1];
		a[0] += a[1];
		a[1] = xi;
	} else {
		a[1] = (vec_t)0.5 * (a[0] - a[1]);
		a[0] -= a[1];
		if (n > 4) {
			rftbsub(n, a, nc, w + nw);
			bitrv2(n, ip + 2, a);
			cftbsub(n, a, w);
		} else if (n == 4) {
			cftfsub(n, a, w);
		}
	}
	if(isgn < 0)
	{
		int j;
		vec_t scale = (vec_t)2.0 / n;
		for (j = 0; j < n; j++)
			a[j] *= scale;
	}
}

#if 0

void ddct(int n, int isgn, vec_t *a, int *ip, vec_t *w)
{
	void makewt(int nw, int *ip, vec_t *w);
	void makect(int nc, int *ip, vec_t *c);
	void bitrv2(int n, int *ip, vec_t *a);
	void cftfsub(int n, vec_t *a, vec_t *w);
	void cftbsub(int n, vec_t *a, vec_t *w);
	void rftfsub(int n, vec_t *a, int nc, vec_t *c);
	void rftbsub(int n, vec_t *a, int nc, vec_t *c);
	void dctsub(int n, vec_t *a, int nc, vec_t *c);
	int j, nw, nc;
	vec_t xr;

	nw = ip[0];
	if (n > (nw << 2)) {
		nw = n >> 2;
		makewt(nw, ip, w);
	}
	nc = ip[1];
	if (n > nc) {
		nc = n;
		makect(nc, ip, w + nw);
	}
	if (isgn < 0) {
		xr = a[n - 1];
		for (j = n - 2; j >= 2; j -= 2) {
			a[j + 1] = a[j] - a[j - 1];
			a[j] += a[j - 1];
		}
		a[1] = a[0] - xr;
		a[0] += xr;
		if (n > 4) {
			rftbsub(n, a, nc, w + nw);
			bitrv2(n, ip + 2, a);
			cftbsub(n, a, w);
		} else if (n == 4) {
			cftfsub(n, a, w);
		}
	}
	dctsub(n, a, nc, w + nw);
	if (isgn >= 0) {
		if (n > 4) {
			bitrv2(n, ip + 2, a);
			cftfsub(n, a, w);
			rftfsub(n, a, nc, w + nw);
		} else if (n == 4) {
			cftfsub(n, a, w);
		}
		xr = a[0] - a[1];
		a[0] += a[1];
		for (j = 2; j < n; j += 2) {
			a[j - 1] = a[j] - a[j + 1];
			a[j] += a[j + 1];
		}
		a[n - 1] = xr;
	}
}

void ddst(int n, int isgn, vec_t *a, int *ip, vec_t *w)
{
	void makewt(int nw, int *ip, vec_t *w);
	void makect(int nc, int *ip, vec_t *c);
	void bitrv2(int n, int *ip, vec_t *a);
	void cftfsub(int n, vec_t *a, vec_t *w);
	void cftbsub(int n, vec_t *a, vec_t *w);
	void rftfsub(int n, vec_t *a, int nc, vec_t *c);
	void rftbsub(int n, vec_t *a, int nc, vec_t *c);
	void dstsub(int n, vec_t *a, int nc, vec_t *c);
	int j, nw, nc;
	vec_t xr;

	nw = ip[0];
	if (n > (nw << 2)) {
		nw = n >> 2;
		makewt(nw, ip, w);
	}
	nc = ip[1];
	if (n > nc) {
		nc = n;
		makect(nc, ip, w + nw);
	}
	if (isgn < 0) {
		xr = a[n - 1];
		for (j = n - 2; j >= 2; j -= 2) {
			a[j + 1] = -a[j] - a[j - 1];
			a[j] -= a[j - 1];
		}
		a[1] = a[0] + xr;
		a[0] -= xr;
		if (n > 4) {
			rftbsub(n, a, nc, w + nw);
			bitrv2(n, ip + 2, a);
			cftbsub(n, a, w);
		} else if (n == 4) {
			cftfsub(n, a, w);
		}
	}
	dstsub(n, a, nc, w + nw);
	if (isgn >= 0) {
		if (n > 4) {
			bitrv2(n, ip + 2, a);
			cftfsub(n, a, w);
			rftfsub(n, a, nc, w + nw);
		} else if (n == 4) {
			cftfsub(n, a, w);
		}
		xr = a[0] - a[1];
		a[0] += a[1];
		for (j = 2; j < n; j += 2) {
			a[j - 1] = -a[j] - a[j + 1];
			a[j] -= a[j + 1];
		}
		a[n - 1] = -xr;
	}
}


void dfct(int n, vec_t *a, vec_t *t, int *ip, vec_t *w)
{
	void makewt(int nw, int *ip, vec_t *w);
	void makect(int nc, int *ip, vec_t *c);
	void bitrv2(int n, int *ip, vec_t *a);
	void cftfsub(int n, vec_t *a, vec_t *w);
	void rftfsub(int n, vec_t *a, int nc, vec_t *c);
	void dctsub(int n, vec_t *a, int nc, vec_t *c);
	int j, k, l, m, mh, nw, nc;
	vec_t xr, xi, yr, yi;

	nw = ip[0];
	if (n > (nw << 3)) {
		nw = n >> 3;
		makewt(nw, ip, w);
	}
	nc = ip[1];
	if (n > (nc << 1)) {
		nc = n >> 1;
		makect(nc, ip, w + nw);
	}
	m = n >> 1;
	yi = a[m];
	xi = a[0] + a[n];
	a[0] -= a[n];
	t[0] = xi - yi;
	t[m] = xi + yi;
	if (n > 2) {
		mh = m >> 1;
		for (j = 1; j < mh; j++) {
			k = m - j;
			xr = a[j] - a[n - j];
			xi = a[j] + a[n - j];
			yr = a[k] - a[n - k];
			yi = a[k] + a[n - k];
			a[j] = xr;
			a[k] = yr;
			t[j] = xi - yi;
			t[k] = xi + yi;
		}
		t[mh] = a[mh] + a[n - mh];
		a[mh] -= a[n - mh];
		dctsub(m, a, nc, w + nw);
		if (m > 4) {
			bitrv2(m, ip + 2, a);
			cftfsub(m, a, w);
			rftfsub(m, a, nc, w + nw);
		} else if (m == 4) {
			cftfsub(m, a, w);
		}
		a[n - 1] = a[0] - a[1];
		a[1] = a[0] + a[1];
		for (j = m - 2; j >= 2; j -= 2) {
			a[2 * j + 1] = a[j] + a[j + 1];
			a[2 * j - 1] = a[j] - a[j + 1];
		}
		l = 2;
		m = mh;
		while (m >= 2) {
			dctsub(m, t, nc, w + nw);
			if (m > 4) {
				bitrv2(m, ip + 2, t);
				cftfsub(m, t, w);
				rftfsub(m, t, nc, w + nw);
			} else if (m == 4) {
				cftfsub(m, t, w);
			}
			a[n - l] = t[0] - t[1];
			a[l] = t[0] + t[1];
			k = 0;
			for (j = 2; j < m; j += 2) {
				k += l << 2;
				a[k - l] = t[j] - t[j + 1];
				a[k + l] = t[j] + t[j + 1];
			}
			l <<= 1;
			mh = m >> 1;
			for (j = 0; j < mh; j++) {
				k = m - j;
				t[j] = t[m + k] - t[m + j];
				t[k] = t[m + k] + t[m + j];
			}
			t[mh] = t[m + mh];
			m = mh;
		}
		a[l] = t[0];
		a[n] = t[2] - t[1];
		a[0] = t[2] + t[1];
	} else {
		a[1] = a[0];
		a[2] = t[0];
		a[0] = t[1];
	}
}

void dfst(int n, vec_t *a, vec_t *t, int *ip, vec_t *w)
{
	void makewt(int nw, int *ip, vec_t *w);
	void makect(int nc, int *ip, vec_t *c);
	void bitrv2(int n, int *ip, vec_t *a);
	void cftfsub(int n, vec_t *a, vec_t *w);
	void rftfsub(int n, vec_t *a, int nc, vec_t *c);
	void dstsub(int n, vec_t *a, int nc, vec_t *c);
	int j, k, l, m, mh, nw, nc;
	vec_t xr, xi, yr, yi;

	nw = ip[0];
	if (n > (nw << 3)) {
		nw = n >> 3;
		makewt(nw, ip, w);
	}
	nc = ip[1];
	if (n > (nc << 1)) {
		nc = n >> 1;
		makect(nc, ip, w + nw);
	}
	if (n > 2) {
		m = n >> 1;
		mh = m >> 1;
		for (j = 1; j < mh; j++) {
			k = m - j;
			xr = a[j] + a[n - j];
			xi = a[j] - a[n - j];
			yr = a[k] + a[n - k];
			yi = a[k] - a[n - k];
			a[j] = xr;
			a[k] = yr;
			t[j] = xi + yi;
			t[k] = xi - yi;
		}
		t[0] = a[mh] - a[n - mh];
		a[mh] += a[n - mh];
		a[0] = a[m];
		dstsub(m, a, nc, w + nw);
		if (m > 4) {
			bitrv2(m, ip + 2, a);
			cftfsub(m, a, w);
			rftfsub(m, a, nc, w + nw);
		} else if (m == 4) {
			cftfsub(m, a, w);
		}
		a[n - 1] = a[1] - a[0];
		a[1] = a[0] + a[1];
		for (j = m - 2; j >= 2; j -= 2) {
			a[2 * j + 1] = a[j] - a[j + 1];
			a[2 * j - 1] = -a[j] - a[j + 1];
		}
		l = 2;
		m = mh;
		while (m >= 2) {
			dstsub(m, t, nc, w + nw);
			if (m > 4) {
				bitrv2(m, ip + 2, t);
				cftfsub(m, t, w);
				rftfsub(m, t, nc, w + nw);
			} else if (m == 4) {
				cftfsub(m, t, w);
			}
			a[n - l] = t[1] - t[0];
			a[l] = t[0] + t[1];
			k = 0;
			for (j = 2; j < m; j += 2) {
				k += l << 2;
				a[k - l] = -t[j] - t[j + 1];
				a[k + l] = t[j] - t[j + 1];
			}
			l <<= 1;
			mh = m >> 1;
			for (j = 1; j < mh; j++) {
				k = m - j;
				t[j] = t[m + k] + t[m + j];
				t[k] = t[m + k] - t[m + j];
			}
			t[0] = t[m + mh];
			m = mh;
		}
		a[l] = t[0];
	}
	a[0] = 0;
}

#endif

/* -------- initializing routines -------- */


#include <math.h>

void makewt(int nw, int *ip, vec_t *w)
{
	void bitrv2(int n, int *ip, vec_t *a);
	int j, nwh;
	vec_t delta, x, y;

	ip[0] = nw;
	ip[1] = 1;
	if (nw > 2) {
		nwh = nw >> 1;
		delta = M_PI4 / nwh;
		w[0] = 1;
		w[1] = 0;
		w[nwh] = (vec_t)cos(delta * nwh);
		w[nwh + 1] = w[nwh];
		if (nwh > 2) {
			for (j = 2; j < nwh; j += 2) {
				x = (vec_t)cos(delta * j);
				y = (vec_t)sin(delta * j);
				w[j] = x;
				w[j + 1] = y;
				w[nw - j] = y;
				w[nw - j + 1] = x;
			}
			bitrv2(nw, ip + 2, w);
		}
	}
}

void makect(int nc, int *ip, vec_t *c)
{
	int j, nch;
	vec_t delta;

	ip[1] = nc;
	if (nc > 1) {
		nch = nc >> 1;
		delta = M_PI4 / nch;
		c[0] = (vec_t)cos(delta * nch);
		c[nch] = (vec_t)0.5 * c[0];
		for (j = 1; j < nch; j++) {
			c[j] = (vec_t)(0.5 * cos(delta * j));
			c[nc - j] = (vec_t)(0.5 * sin(delta * j));
		}
	}
}

/* -------- child routines -------- */

void bitrv2(int n, int *ip, vec_t *a)
{
	int j, j1, k, k1, l, m, m2;
	vec_t xr, xi, yr, yi;

	ip[0] = 0;
	l = n;
	m = 1;
	while ((m << 3) < l) {
		l >>= 1;
		for (j = 0; j < m; j++) {
			ip[m + j] = ip[j] + l;
		}
		m <<= 1;
	}
	m2 = 2 * m;
	if ((m << 3) == l) {
		for (k = 0; k < m; k++) {
			for (j = 0; j < k; j++) {
				j1 = 2 * j + ip[k];
				k1 = 2 * k + ip[j];
				xr = a[j1];
				xi = a[j1 + 1];
				yr = a[k1];
				yi = a[k1 + 1];
				a[j1] = yr;
				a[j1 + 1] = yi;
				a[k1] = xr;
				a[k1 + 1] = xi;
				j1 += m2;
				k1 += 2 * m2;
				xr = a[j1];
				xi = a[j1 + 1];
				yr = a[k1];
				yi = a[k1 + 1];
				a[j1] = yr;
				a[j1 + 1] = yi;
				a[k1] = xr;
				a[k1 + 1] = xi;
				j1 += m2;
				k1 -= m2;
				xr = a[j1];
				xi = a[j1 + 1];
				yr = a[k1];
				yi = a[k1 + 1];
				a[j1] = yr;
				a[j1 + 1] = yi;
				a[k1] = xr;
				a[k1 + 1] = xi;
				j1 += m2;
				k1 += 2 * m2;
				xr = a[j1];
				xi = a[j1 + 1];
				yr = a[k1];
				yi = a[k1 + 1];
				a[j1] = yr;
				a[j1 + 1] = yi;
				a[k1] = xr;
				a[k1 + 1] = xi;
			}
			j1 = 2 * k + m2 + ip[k];
			k1 = j1 + m2;
			xr = a[j1];
			xi = a[j1 + 1];
			yr = a[k1];
			yi = a[k1 + 1];
			a[j1] = yr;
			a[j1 + 1] = yi;
			a[k1] = xr;
			a[k1 + 1] = xi;
		}
	} else {
		for (k = 1; k < m; k++) {
			for (j = 0; j < k; j++) {
				j1 = 2 * j + ip[k];
				k1 = 2 * k + ip[j];
				xr = a[j1];
				xi = a[j1 + 1];
				yr = a[k1];
				yi = a[k1 + 1];
				a[j1] = yr;
				a[j1 + 1] = yi;
				a[k1] = xr;
				a[k1 + 1] = xi;
				j1 += m2;
				k1 += m2;
				xr = a[j1];
				xi = a[j1 + 1];
				yr = a[k1];
				yi = a[k1 + 1];
				a[j1] = yr;
				a[j1 + 1] = yi;
				a[k1] = xr;
				a[k1 + 1] = xi;
			}
		}
	}
}

void bitrv2conj(int n, int *ip, vec_t *a)
{
	int j, j1, k, k1, l, m, m2;
	vec_t xr, xi, yr, yi;

	ip[0] = 0;
	l = n;
	m = 1;
	while ((m << 3) < l) {
		l >>= 1;
		for (j = 0; j < m; j++) {
			ip[m + j] = ip[j] + l;
		}
		m <<= 1;
	}
	m2 = 2 * m;
	if ((m << 3) == l) {
		for (k = 0; k < m; k++) {
			for (j = 0; j < k; j++) {
				j1 = 2 * j + ip[k];
				k1 = 2 * k + ip[j];
				xr = a[j1];
				xi = -a[j1 + 1];
				yr = a[k1];
				yi = -a[k1 + 1];
				a[j1] = yr;
				a[j1 + 1] = yi;
				a[k1] = xr;
				a[k1 + 1] = xi;
				j1 += m2;
				k1 += 2 * m2;
				xr = a[j1];
				xi = -a[j1 + 1];
				yr = a[k1];
				yi = -a[k1 + 1];
				a[j1] = yr;
				a[j1 + 1] = yi;
				a[k1] = xr;
				a[k1 + 1] = xi;
				j1 += m2;
				k1 -= m2;
				xr = a[j1];
				xi = -a[j1 + 1];
				yr = a[k1];
				yi = -a[k1 + 1];
				a[j1] = yr;
				a[j1 + 1] = yi;
				a[k1] = xr;
				a[k1 + 1] = xi;
				j1 += m2;
				k1 += 2 * m2;
				xr = a[j1];
				xi = -a[j1 + 1];
				yr = a[k1];
				yi = -a[k1 + 1];
				a[j1] = yr;
				a[j1 + 1] = yi;
				a[k1] = xr;
				a[k1 + 1] = xi;
			}
			k1 = 2 * k + ip[k];
			a[k1 + 1] = -a[k1 + 1];
			j1 = k1 + m2;
			k1 = j1 + m2;
			xr = a[j1];
			xi = -a[j1 + 1];
			yr = a[k1];
			yi = -a[k1 + 1];
			a[j1] = yr;
			a[j1 + 1] = yi;
			a[k1] = xr;
			a[k1 + 1] = xi;
			k1 += m2;
			a[k1 + 1] = -a[k1 + 1];
		}
	} else {
		a[1] = -a[1];
		a[m2 + 1] = -a[m2 + 1];
		for (k = 1; k < m; k++) {
			for (j = 0; j < k; j++) {
				j1 = 2 * j + ip[k];
				k1 = 2 * k + ip[j];
				xr = a[j1];
				xi = -a[j1 + 1];
				yr = a[k1];
				yi = -a[k1 + 1];
				a[j1] = yr;
				a[j1 + 1] = yi;
				a[k1] = xr;
				a[k1 + 1] = xi;
				j1 += m2;
				k1 += m2;
				xr = a[j1];
				xi = -a[j1 + 1];
				yr = a[k1];
				yi = -a[k1 + 1];
				a[j1] = yr;
				a[j1 + 1] = yi;
				a[k1] = xr;
				a[k1 + 1] = xi;
			}
			k1 = 2 * k + ip[k];
			a[k1 + 1] = -a[k1 + 1];
			a[k1 + m2 + 1] = -a[k1 + m2 + 1];
		}
	}
}

void cftfsub(int n, vec_t *a, vec_t *w)
{
	int j, j1, j2, j3, l;
	vec_t x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;

	l = 2;
	if (n > 8) {
		cft1st(n, a, w);
		l = 8;
		while ((l << 2) < n) {
			cftmdl(n, l, a, w);
			l <<= 2;
		}
	}
	if ((l << 2) == n) {
		for (j = 0; j < l; j += 2) {
			j1 = j + l;
			j2 = j1 + l;
			j3 = j2 + l;
			x0r = a[j] + a[j1];
			x0i = a[j + 1] + a[j1 + 1];
			x1r = a[j] - a[j1];
			x1i = a[j + 1] - a[j1 + 1];
			x2r = a[j2] + a[j3];
			x2i = a[j2 + 1] + a[j3 + 1];
			x3r = a[j2] - a[j3];
			x3i = a[j2 + 1] - a[j3 + 1];
			a[j] = x0r + x2r;
			a[j + 1] = x0i + x2i;
			a[j2] = x0r - x2r;
			a[j2 + 1] = x0i - x2i;
			a[j1] = x1r - x3i;
			a[j1 + 1] = x1i + x3r;
			a[j3] = x1r + x3i;
			a[j3 + 1] = x1i - x3r;
		}
	} else {
		for (j = 0; j < l; j += 2) {
			j1 = j + l;
			x0r = a[j] - a[j1];
			x0i = a[j + 1] - a[j1 + 1];
			a[j] += a[j1];
			a[j + 1] += a[j1 + 1];
			a[j1] = x0r;
			a[j1 + 1] = x0i;
		}
	}
}

void cftbsub(int n, vec_t *a, vec_t *w)
{
	void cft1st(int n, vec_t *a, vec_t *w);
	void cftmdl(int n, int l, vec_t *a, vec_t *w);
	int j, j1, j2, j3, l;
	vec_t x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;

	l = 2;
	if (n > 8) {
		cft1st(n, a, w);
		l = 8;
		while ((l << 2) < n) {
			cftmdl(n, l, a, w);
			l <<= 2;
		}
	}
	if ((l << 2) == n) {
		for (j = 0; j < l; j += 2) {
			j1 = j + l;
			j2 = j1 + l;
			j3 = j2 + l;
			x0r = a[j] + a[j1];
			x0i = -a[j + 1] - a[j1 + 1];
			x1r = a[j] - a[j1];
			x1i = -a[j + 1] + a[j1 + 1];
			x2r = a[j2] + a[j3];
			x2i = a[j2 + 1] + a[j3 + 1];
			x3r = a[j2] - a[j3];
			x3i = a[j2 + 1] - a[j3 + 1];
			a[j] = x0r + x2r;
			a[j + 1] = x0i - x2i;
			a[j2] = x0r - x2r;
			a[j2 + 1] = x0i + x2i;
			a[j1] = x1r - x3i;
			a[j1 + 1] = x1i - x3r;
			a[j3] = x1r + x3i;
			a[j3 + 1] = x1i + x3r;
		}
	} else {
		for (j = 0; j < l; j += 2) {
			j1 = j + l;
			x0r = a[j] - a[j1];
			x0i = -a[j + 1] + a[j1 + 1];
			a[j] += a[j1];
			a[j + 1] = -a[j + 1] - a[j1 + 1];
			a[j1] = x0r;
			a[j1 + 1] = x0i;
		}
	}
}

void cft1st(int n, vec_t *a, vec_t *w)
{
	int j, k1, k2;
	vec_t wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;
	vec_t x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;

	x0r = a[0] + a[2];
	x0i = a[1] + a[3];
	x1r = a[0] - a[2];
	x1i = a[1] - a[3];
	x2r = a[4] + a[6];
	x2i = a[5] + a[7];
	x3r = a[4] - a[6];
	x3i = a[5] - a[7];
	a[0] = x0r + x2r;
	a[1] = x0i + x2i;
	a[4] = x0r - x2r;
	a[5] = x0i - x2i;
	a[2] = x1r - x3i;
	a[3] = x1i + x3r;
	a[6] = x1r + x3i;
	a[7] = x1i - x3r;
	wk1r = w[2];
	x0r = a[8] + a[10];
	x0i = a[9] + a[11];
	x1r = a[8] - a[10];
	x1i = a[9] - a[11];
	x2r = a[12] + a[14];
	x2i = a[13] + a[15];
	x3r = a[12] - a[14];
	x3i = a[13] - a[15];
	a[8] = x0r + x2r;
	a[9] = x0i + x2i;
	a[12] = x2i - x0i;
	a[13] = x0r - x2r;
	x0r = x1r - x3i;
	x0i = x1i + x3r;
	a[10] = wk1r * (x0r - x0i);
	a[11] = wk1r * (x0r + x0i);
	x0r = x3i + x1r;
	x0i = x3r - x1i;
	a[14] = wk1r * (x0i - x0r);
	a[15] = wk1r * (x0i + x0r);
	k1 = 0;
	for (j = 16; j < n; j += 16) {
		k1 += 2;
		k2 = 2 * k1;
		wk2r = w[k1];
		wk2i = w[k1 + 1];
		wk1r = w[k2];
		wk1i = w[k2 + 1];
		wk3r = wk1r - 2 * wk2i * wk1i;
		wk3i = 2 * wk2i * wk1r - wk1i;
		x0r = a[j] + a[j + 2];
		x0i = a[j + 1] + a[j + 3];
		x1r = a[j] - a[j + 2];
		x1i = a[j + 1] - a[j + 3];
		x2r = a[j + 4] + a[j + 6];
		x2i = a[j + 5] + a[j + 7];
		x3r = a[j + 4] - a[j + 6];
		x3i = a[j + 5] - a[j + 7];
		a[j] = x0r + x2r;
		a[j + 1] = x0i + x2i;
		x0r -= x2r;
		x0i -= x2i;
		a[j + 4] = wk2r * x0r - wk2i * x0i;
		a[j + 5] = wk2r * x0i + wk2i * x0r;
		x0r = x1r - x3i;
		x0i = x1i + x3r;
		a[j + 2] = wk1r * x0r - wk1i * x0i;
		a[j + 3] = wk1r * x0i + wk1i * x0r;
		x0r = x1r + x3i;
		x0i = x1i - x3r;
		a[j + 6] = wk3r * x0r - wk3i * x0i;
		a[j + 7] = wk3r * x0i + wk3i * x0r;
		wk1r = w[k2 + 2];
		wk1i = w[k2 + 3];
		wk3r = wk1r - 2 * wk2r * wk1i;
		wk3i = 2 * wk2r * wk1r - wk1i;
		x0r = a[j + 8] + a[j + 10];
		x0i = a[j + 9] + a[j + 11];
		x1r = a[j + 8] - a[j + 10];
		x1i = a[j + 9] - a[j + 11];
		x2r = a[j + 12] + a[j + 14];
		x2i = a[j + 13] + a[j + 15];
		x3r = a[j + 12] - a[j + 14];
		x3i = a[j + 13] - a[j + 15];
		a[j + 8] = x0r + x2r;
		a[j + 9] = x0i + x2i;
		x0r -= x2r;
		x0i -= x2i;
		a[j + 12] = -wk2i * x0r - wk2r * x0i;
		a[j + 13] = -wk2i * x0i + wk2r * x0r;
		x0r = x1r - x3i;
		x0i = x1i + x3r;
		a[j + 10] = wk1r * x0r - wk1i * x0i;
		a[j + 11] = wk1r * x0i + wk1i * x0r;
		x0r = x1r + x3i;
		x0i = x1i - x3r;
		a[j + 14] = wk3r * x0r - wk3i * x0i;
		a[j + 15] = wk3r * x0i + wk3i * x0r;
	}
}

void cftmdl(int n, int l, vec_t *a, vec_t *w)
{
	int j, j1, j2, j3, k, k1, k2, m, m2;
	vec_t wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;
	vec_t x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;

	m = l << 2;
	for (j = 0; j < l; j += 2) {
		j1 = j + l;
		j2 = j1 + l;
		j3 = j2 + l;
		x0r = a[j] + a[j1];
		x0i = a[j + 1] + a[j1 + 1];
		x1r = a[j] - a[j1];
		x1i = a[j + 1] - a[j1 + 1];
		x2r = a[j2] + a[j3];
		x2i = a[j2 + 1] + a[j3 + 1];
		x3r = a[j2] - a[j3];
		x3i = a[j2 + 1] - a[j3 + 1];
		a[j] = x0r + x2r;
		a[j + 1] = x0i + x2i;
		a[j2] = x0r - x2r;
		a[j2 + 1] = x0i - x2i;
		a[j1] = x1r - x3i;
		a[j1 + 1] = x1i + x3r;
		a[j3] = x1r + x3i;
		a[j3 + 1] = x1i - x3r;
	}
	wk1r = w[2];
	for (j = m; j < l + m; j += 2) {
		j1 = j + l;
		j2 = j1 + l;
		j3 = j2 + l;
		x0r = a[j] + a[j1];
		x0i = a[j + 1] + a[j1 + 1];
		x1r = a[j] - a[j1];
		x1i = a[j + 1] - a[j1 + 1];
		x2r = a[j2] + a[j3];
		x2i = a[j2 + 1] + a[j3 + 1];
		x3r = a[j2] - a[j3];
		x3i = a[j2 + 1] - a[j3 + 1];
		a[j] = x0r + x2r;
		a[j + 1] = x0i + x2i;
		a[j2] = x2i - x0i;
		a[j2 + 1] = x0r - x2r;
		x0r = x1r - x3i;
		x0i = x1i + x3r;
		a[j1] = wk1r * (x0r - x0i);
		a[j1 + 1] = wk1r * (x0r + x0i);
		x0r = x3i + x1r;
		x0i = x3r - x1i;
		a[j3] = wk1r * (x0i - x0r);
		a[j3 + 1] = wk1r * (x0i + x0r);
	}
	k1 = 0;
	m2 = 2 * m;
	for (k = m2; k < n; k += m2) {
		k1 += 2;
		k2 = 2 * k1;
		wk2r = w[k1];
		wk2i = w[k1 + 1];
		wk1r = w[k2];
		wk1i = w[k2 + 1];
		wk3r = wk1r - 2 * wk2i * wk1i;
		wk3i = 2 * wk2i * wk1r - wk1i;
		for (j = k; j < l + k; j += 2) {
			j1 = j + l;
			j2 = j1 + l;
			j3 = j2 + l;
			x0r = a[j] + a[j1];
			x0i = a[j + 1] + a[j1 + 1];
			x1r = a[j] - a[j1];
			x1i = a[j + 1] - a[j1 + 1];
			x2r = a[j2] + a[j3];
			x2i = a[j2 + 1] + a[j3 + 1];
			x3r = a[j2] - a[j3];
			x3i = a[j2 + 1] - a[j3 + 1];
			a[j] = x0r + x2r;
			a[j + 1] = x0i + x2i;
			x0r -= x2r;
			x0i -= x2i;
			a[j2] = wk2r * x0r - wk2i * x0i;
			a[j2 + 1] = wk2r * x0i + wk2i * x0r;
			x0r = x1r - x3i;
			x0i = x1i + x3r;
			a[j1] = wk1r * x0r - wk1i * x0i;
			a[j1 + 1] = wk1r * x0i + wk1i * x0r;
			x0r = x1r + x3i;
			x0i = x1i - x3r;
			a[j3] = wk3r * x0r - wk3i * x0i;
			a[j3 + 1] = wk3r * x0i + wk3i * x0r;
		}
		wk1r = w[k2 + 2];
		wk1i = w[k2 + 3];
		wk3r = wk1r - 2 * wk2r * wk1i;
		wk3i = 2 * wk2r * wk1r - wk1i;
		for (j = k + m; j < l + (k + m); j += 2) {
			j1 = j + l;
			j2 = j1 + l;
			j3 = j2 + l;
			x0r = a[j] + a[j1];
			x0i = a[j + 1] + a[j1 + 1];
			x1r = a[j] - a[j1];
			x1i = a[j + 1] - a[j1 + 1];
			x2r = a[j2] + a[j3];
			x2i = a[j2 + 1] + a[j3 + 1];
			x3r = a[j2] - a[j3];
			x3i = a[j2 + 1] - a[j3 + 1];
			a[j] = x0r + x2r;
			a[j + 1] = x0i + x2i;
			x0r -= x2r;
			x0i -= x2i;
			a[j2] = -wk2i * x0r - wk2r * x0i;
			a[j2 + 1] = -wk2i * x0i + wk2r * x0r;
			x0r = x1r - x3i;
			x0i = x1i + x3r;
			a[j1] = wk1r * x0r - wk1i * x0i;
			a[j1 + 1] = wk1r * x0i + wk1i * x0r;
			x0r = x1r + x3i;
			x0i = x1i - x3r;
			a[j3] = wk3r * x0r - wk3i * x0i;
			a[j3 + 1] = wk3r * x0i + wk3i * x0r;
		}
	}
}

void rftfsub(int n, vec_t *a, int nc, vec_t *c)
{
	int j, k, kk, ks, m;
	vec_t wkr, wki, xr, xi, yr, yi;

	m = n >> 1;
	ks = 2 * nc / m;
	kk = 0;
	for (j = 2; j < m; j += 2) {
		k = n - j;
		kk += ks;
		wkr = (vec_t)0.5 - c[nc - kk];
		wki = c[kk];
		xr = a[j] - a[k];
		xi = a[j + 1] + a[k + 1];
		yr = wkr * xr - wki * xi;
		yi = wkr * xi + wki * xr;
		a[j] -= yr;
		a[j + 1] -= yi;
		a[k] += yr;
		a[k + 1] -= yi;
	}
}

void rftbsub(int n, vec_t *a, int nc, vec_t *c)
{
	int j, k, kk, ks, m;
	vec_t wkr, wki, xr, xi, yr, yi;

	a[1] = -a[1];
	m = n >> 1;
	ks = 2 * nc / m;
	kk = 0;
	for (j = 2; j < m; j += 2) {
		k = n - j;
		kk += ks;
		wkr = (vec_t)0.5 - c[nc - kk];
		wki = c[kk];
		xr = a[j] - a[k];
		xi = a[j + 1] + a[k + 1];
		yr = wkr * xr + wki * xi;
		yi = wkr * xi - wki * xr;
		a[j] -= yr;
		a[j + 1] = yi - a[j + 1];
		a[k] += yr;
		a[k + 1] = yi - a[k + 1];
	}
	a[m + 1] = -a[m + 1];
}

void dctsub(int n, vec_t *a, int nc, vec_t *c)
{
	int j, k, kk, ks, m;
	vec_t wkr, wki, xr;

	m = n >> 1;
	ks = nc / n;
	kk = 0;
	for (j = 1; j < m; j++) {
		k = n - j;
		kk += ks;
		wkr = c[kk] - c[nc - kk];
		wki = c[kk] + c[nc - kk];
		xr = wki * a[j] - wkr * a[k];
		a[j] = wkr * a[j] + wki * a[k];
		a[k] = xr;
	}
	a[m] *= c[0];
}

void dstsub(int n, vec_t *a, int nc, vec_t *c)
{
	int j, k, kk, ks, m;
	vec_t wkr, wki, xr;

	m = n >> 1;
	ks = nc / n;
	kk = 0;
	for (j = 1; j < m; j++) {
		k = n - j;
		kk += ks;
		wkr = c[kk] - c[nc - kk];
		wki = c[kk] + c[nc - kk];
		xr = wki * a[k] - wkr * a[j];
		a[k] = wkr * a[k] + wki * a[j];
		a[j] = xr;
	}
	a[m] *= c[0];
}

/*
 * Wrapper objects
 */
typedef struct fft_context_s
{
	libv_object_t base;
	void (*transform)(int, int, vec_t *, int *, vec_t *);
	int pwr, len, sig_forward, sig_inverse;
	int *bitrev;
	vec_t *sintab;
}	fft_context_t, *fft_context_p;

#ifdef	CHECKED_BUILD

V_LOCAL char *fftc_name = "FFT context";

#define	FFTC_MAGIC	0x46465443 /* FFTC */

#endif	/* CHECKED_BUILD */

#define	GET_FFTC(handle,caller)	\
	(fft_context_p)(libvIsHandle(handle,caller,fftc_name,FFTC_MAGIC))

V_LOCAL u32_t V_CALL
destroy_fft_context(vhandle_t handle)
{
	fft_context_p self = (fft_context_p)handle;
	UNREF_EXIT(self);
	if(self->bitrev)
		memFree(self->bitrev);
	if(self->sintab)
		memFree(self->sintab);
	memFreeObject(self);
	return 0;
}

vhandle_t	V_GLOBAL
fftCreateContext(u32_t pwr,vboolean fft_real)
{
	fft_context_p self;
	if((pwr < 2) || (pwr >= 31))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"fftCreateContext(%u,%u): 'pwr' must be in range [1..30].\n",
			pwr,(u32_t)fft_real);
#endif	/* CHECKED_BUILD */
		return LIBV_INVALID_HANDLE;
	}
	ALLOC_OBJECT(self,fft_context_t,0,FFTC_MAGIC);
	self->base.destroy = destroy_fft_context;
	self->pwr = pwr;
	self->len = (int)(1 << pwr);
	self->bitrev = (int*)memAllocZ(self->len * sizeof(int));
	self->sintab = (vec_t*)memAllocZ(self->len * sizeof(vec_t));
	if(!self->bitrev || !self->sintab)
	{
		destroy_fft_context(self);
		return LIBV_INVALID_HANDLE;
	}
	if(fft_real)
	{
		u32_t nw = self->len >> 2;
		self->transform = oo_rdft;
		self->sig_forward = 1;
		self->sig_inverse = -1;
		makewt(nw,self->bitrev,self->sintab);
		makect(nw,self->bitrev,self->sintab + nw);
	}
	else
	{
		self->len = self->len << 1;
		/*
		 * see note above.
		 */
		self->transform = oo_cdft;
		self->sig_forward = 1;
		self->sig_inverse = -1;
		makewt(self->len >> 2,self->bitrev,self->sintab);
	}
	return self;
}

vboolean	V_GLOBAL
fftForward(vhandle_t context,vec_t *samples)
{
	fft_context_p self = GET_FFTC(context,"fftForward");
	if(!self)
		return false;
	self->transform(self->len,self->sig_forward,samples,
		self->bitrev,self->sintab);
	return true;
}

vboolean	V_GLOBAL
fftInverse(vhandle_t context,vec_t *samples)
{
	fft_context_p self = GET_FFTC(context,"fftInverse");
	if(!self)
		return false;
	self->transform(self->len,self->sig_inverse,samples,
		self->bitrev,self->sintab);
	return true;
}

vboolean	V_GLOBAL
fftGetLen(vhandle_t context,u32_t *len_ret)
{
	fft_context_p self = GET_FFTC(context,"fftGetLen");
	if(!self)
		return false;
	*len_ret = (u32_t)self->len;
	return true;
}

void V_GLOBAL
fftDestroyContext(vhandle_t handle)
{
	libvCloseHandle(handle,"fftDestroyContext",fftc_name,FFTC_MAGIC);
}

/*
 * Context for 2D real FFT
 */
typedef struct fft_rvm_context_s
{
	libv_object_t base;
	int row_pwr, row_len;
	int col_pwr, col_len;
	int *row_bitrev;
	int *col_bitrev;
	vec_t *row_sintab;
	vec_t *col_sintab;
	vec_t *cplx_col;
}	fft_rvm_context_t, *fft_rvm_context_p;

#ifdef	CHECKED_BUILD

V_LOCAL char *fft2_name = "Real 2D FFT context";

#define	FFT2_MAGIC	0x46465432 /* FFT2 */

#endif	/* CHECKED_BUILD */

#define	GET_FFT2(handle,caller)	\
	(fft_rvm_context_p)(libvIsHandle(handle,caller,fft2_name,FFT2_MAGIC))

V_LOCAL u32_t V_CALL
destroy_rvm_fft_context(vhandle_t handle)
{
	fft_rvm_context_p self = (fft_rvm_context_p)handle;
	UNREF_EXIT(self);
	if(self->row_bitrev)
		memFree(self->row_bitrev);
	if(self->col_bitrev)
		memFree(self->col_bitrev);
	if(self->row_sintab)
		memFree(self->row_sintab);
	if(self->col_sintab)
		memFree(self->col_sintab);
	if(self->cplx_col)
		memFree(self->cplx_col);
	memFreeObject(self);
	return 0;
}

V_LOCAL u32_t
int_log2(u32_t val)
{
	u32_t pwr = 1;
	V_ASSERT(IS_POWER_TWO(val));
	while((u32_t)(1 << pwr) < val)
		pwr++;
	return pwr;
}

vhandle_t	V_GLOBAL
fftRVMCreateContext(u32_t num_rows,u32_t num_cols)
{
	CALLER_NAME(fftRVMCreateContext)
	u32_t nw;
	fft_rvm_context_p self;
	if(!IS_POWER_TWO(num_rows) || !IS_POWER_TWO(num_cols))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s(%u,%u): both dimensions must be powers of 2.\n",
			caller_name,num_rows,num_cols);
#endif	/* CHECKED_BUILD */
		return LIBV_INVALID_HANDLE;
	}
	if(num_cols < 2)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s(%u,%u): number of columns must be greater than 1.\n",
			caller_name,num_rows,num_cols);
#endif	/* CHECKED_BUILD */
		return LIBV_INVALID_HANDLE;
	}
	ALLOC_OBJECT(self,fft_rvm_context_t,0,FFT2_MAGIC);
	self->base.destroy = destroy_rvm_fft_context;
	/*
	 * We have to create two transforms: real transform of len
	 * _'num_cols'_ for each row, and complex transform of len
	 * _'num_rows'_ for each column.
	 */
	self->row_len = (int)num_cols;
	self->col_len = (int)num_rows;
	self->row_pwr = int_log2(num_rows);
	self->col_pwr = int_log2(num_cols);
	self->row_bitrev = (int*)memAllocZ(self->row_len * sizeof(int));
	self->row_sintab = (vec_t*)memAllocZ(self->row_len * sizeof(vec_t));
	self->col_bitrev = (int*)memAllocZ(self->col_len * sizeof(int));
	self->col_sintab = (vec_t*)memAllocZ(self->col_len * sizeof(vec_t));
	if(!self->row_bitrev || !self->row_sintab ||
		!self->col_bitrev || !self->col_sintab)
	{
		destroy_rvm_fft_context(self);
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s(%u,%u): out of memory.\n",
			caller_name,num_rows,num_cols);
#endif	/* CHECKED_BUILD */
		return LIBV_INVALID_HANDLE;
	}
	/* Real row transform of len 'num_cols' */
	nw = self->row_len >> 2;
	makewt(nw,self->row_bitrev,self->row_sintab);
	makect(nw,self->row_bitrev,self->row_sintab + nw);
	/* Complex column transform of len 'num_rows' */
	self->col_len = self->col_len << 1;
	self->cplx_col = (vec_p)memAllocZ(self->col_len * sizeof(vec_t));
	makewt(self->col_len >> 2,self->col_bitrev,self->col_sintab);
	return self;
}

vboolean	V_GLOBAL
fftRVMGetSize(vhandle_t context,
	u32_p target_num_rows,u32_p target_num_cols)
{
	fft_rvm_context_p self = GET_FFT2(context,"fftRVMGetSize");
	if(!self)
		return false;
	if(target_num_rows)
		*target_num_rows = (u32_t)self->col_len >> 1;
	if(target_num_cols)
		*target_num_cols = (u32_t)self->row_len;
	return true;
}

void V_CALL
rvmUnpackCplxCol(vec_p cplx,matrix_p data,u32_t col)
{
	u32_t i, n = data->rows;
	vec_p *cell = data->cell;
	for(i = 0;i < n;i++)
	{
		*cplx++ = cell[i][col];
		*cplx++ = cell[i][col+1];
	}
}

void V_CALL
rvmPackCplxCol(matrix_p data,vec_p cplx,u32_t col)
{
	u32_t i, n = data->rows;
	vec_p *cell = data->cell;
	for(i = 0;i < n;i++)
	{
		cell[i][col] = *cplx++;
		cell[i][col+1] = *cplx++;
	}
}

/*
 * Though the output of this function contains _all_ necessary information
 * to restore the original real-valued matrix, the processing logic and
 * placement of data are _not_ obvious.
 *
 * At call, 'rvm' contains real-valued elements (as the name suggests).
 * The transform is two-pass. At first, we compute real FT of each row;
 * we got _positive_ spatial frequencies only. Each frequency, _except_
 * first and second ones, is a complex value and occupies two consecutive
 * columns. Thus, to compute transform in second dimension, we need
 * to do _complex_ FT of each _pair_ of columns (Re(z) in even, Im(z) in
 * odd). Such FT returns both positive (from zero to highest) and negative
 * (from -highest to -nearest_to_zero) frequencies of each column.
 *     However, the complex FT transform, being applied to the 1st pair of
 * columns, returns _mixed_ values of _positive only_ frequencies of the
 * _first_ and _last_ columns. So, do _not_ try to manipulate the first
 * and second columns directly, unless you are know what you doing.
 * Use the provided helper routines instead.
 *
 * Again, please note that there are _no_ negative row frequencies for
 * zero column frequency (for the first pair of columns).
 */
vboolean	V_GLOBAL
fftRVMForward(vhandle_t context,matrix_p rvm)
{
	CALLER_NAME(fftRVMForward)
	u32_t i;
	fft_rvm_context_p self = GET_FFT2(context,caller_name);
	if(!self)
		return false;
	if((rvm->rows << 1) != (u32_t)self->col_len ||
		rvm->cols != (u32_t)self->row_len)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s(): expected [%u,%u] matrix, got [%u,%u].\n",
			caller_name,
			(u32_t)self->row_len, (u32_t)self->col_len >> 1,
			rvm->rows,rvm->cols);
#endif	/* CHECKED_BUILD */
		return false;
	}
	/* real transform of each row */
	for(i = 0;i < rvm->rows;i++)
		oo_rdft(self->row_len,1,rvm->cell[i],
			self->row_bitrev,self->row_sintab);
	/* complex transform of each (complex) column */
	for(i = 0;i < rvm->cols;i += 2)
	{
		rvmUnpackCplxCol(self->cplx_col,rvm,i);
		oo_cdft(self->col_len,1,self->cplx_col,
			self->col_bitrev,self->col_sintab);
		rvmPackCplxCol(rvm,self->cplx_col,i);
	}
	return true;
}

/*
 * Inverses the result of fftRVMForward().
 */
vboolean	V_GLOBAL
fftRVMInverse(vhandle_t context,matrix_p rvmft)
{
	CALLER_NAME(fftRVMInverse)
	u32_t i;
	fft_rvm_context_p self = GET_FFT2(context,caller_name);
	if(!self)
		return false;
	if((rvmft->rows << 1) != (u32_t)self->col_len ||
		rvmft->cols != (u32_t)self->row_len)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s(): expected [%u,%u] matrix, got [%u,%u].\n",
			caller_name,
			(u32_t)self->row_len, (u32_t)self->col_len >> 1,
			rvmft->rows,rvmft->cols);
#endif	/* CHECKED_BUILD */
		return false;
	}
	/* complex transform of each (complex) column */
	for(i = 0;i < rvmft->cols;i += 2)
	{
		rvmUnpackCplxCol(self->cplx_col,rvmft,i);
		oo_cdft(self->col_len,-1,self->cplx_col,
			self->col_bitrev,self->col_sintab);
		rvmPackCplxCol(rvmft,self->cplx_col,i);
	}
	/* real transform of each row */
	for(i = 0;i < rvmft->rows;i++)
		oo_rdft(self->row_len,-1,rvmft->cell[i],
			self->row_bitrev,self->row_sintab);
	return true;
}

void V_GLOBAL
fftRVMDestroyContext(vhandle_t handle)
{
	libvCloseHandle(handle,"fftRVMDestroyContext",fft2_name,FFT2_MAGIC);
}

/*
 * The idea is from Numerical Recipes.
 */

V_LOCAL void
unpack_twoffts(vec_p freq_zero,vec_p freq_middle,vec_p raw,u32_t n)
{
	u32_t j;
	u32_t nn2 = n + n;
	u32_t nn3 = 1 + nn2;
	vec_t rep, rem, aip, aim;
	freq_zero[0] = raw[0];
	freq_middle[0] = raw[1];
	for(j = 2;j < n;j += 2)
	{
		rep = (vec_t)0.5 * (raw[j] + raw[nn2 - j]);
		rem = (vec_t)0.5 * (raw[j] - raw[nn2 - j]);
		aip = (vec_t)0.5 * (raw[j + 1] + raw[nn3 - j]);
		aim = (vec_t)0.5 * (raw[j + 1] - raw[nn3 - j]);
		freq_zero[j] = rep;
		freq_middle[j] = aip;
		freq_zero[j + 1] = aim;
		freq_middle[j + 1] = -rem;
	}
	/* j == n + 2 */
	rep = (vec_t)0.5 * (raw[j] + raw[nn2 - j]); /* rem == 0 */
	aip = (vec_t)0.5 * (raw[j + 1] + raw[nn3 - j]); /* aim == 0 */
	freq_zero[1] = rep;
	freq_middle[1] = aip;
}

V_LOCAL	void
pack_twoffts(vec_p raw,vec_p freq_zero,vec_p freq_middle,u32_t n)
{
	u32_t j;
	u32_t nn2 = n + n;
	u32_t nn3 = 1 + nn2;
	vec_t rep, rem, aip, aim;
	raw[0] = freq_zero[0];
	raw[1] = freq_middle[0];
	for(j = 2;j < n;j += 2)
	{
		rep = freq_zero[j];
		aip = freq_middle[j];
		aim = freq_zero[j + 1];
		rem = - freq_middle[j + 1];
		raw[j] = rep + rem;
		raw[nn2 - j] = rep - rem;
		raw[j + 1] = aip + aim;
		raw[nn3 - j] = aip - aim;
	}
	/* j == n + 2 */
	rep = freq_zero[1];
	aip = freq_middle[1];
	raw[j] = rep;
	raw[nn2 - j] = rep;
	raw[j + 1] = aip;
	raw[nn3 - j] = aip;
}

/*
 * Unpacks the combination of two real-valued Fourier
 * transforms corresponding to zero and middle frequencies.
 * Length of 'zero' and 'middle' vectors must be either 
 * equal, or _two_ elements greater than rvmft->rows. In later
 * case, the real and imaginary parts of transforms will be
 * stored in even (0,2,...) and odd (1,3,...) elements,
 * respectively; the properties of FFT guarantee that
 * 	zero[1] = middle[1] = zero[ft->rows + 1] = middle[ft->rows + 1] = 0.
 * In former case, first two elements of both 'zero' and 'middle'
 * will contain _real_ parts of zero (1st) and middle (last)
 * elements.
 */

void	V_GLOBAL
matRVMFTUnpackZeroColumn(vector_p zero,vector_p middle,matrix_p rvmft)
{
	CALLER_NAME(matRVMFTUnpackZeroColumn)
	vec_p raw;
	if(!zero || !middle || !rvmft ||
		zero->size < rvmft->rows || middle->size < rvmft->rows ||
		!IS_POWER_TWO(rvmft->rows) || !IS_POWER_TWO(rvmft->cols))
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return;
	}
	raw = (vec_p)memAlloc(rvmft->rows * 2 * sizeof(vec_t));
	rvmUnpackCplxCol(raw,rvmft,0);
	unpack_twoffts(zero->elements,middle->elements,raw,rvmft->rows);
	if(zero->size == (rvmft->rows + 2))
	{
		zero->elements[rvmft->rows] = zero->elements[1];
		zero->elements[1] = zero->elements[rvmft->rows + 1] = 0;
	}
	if(middle->size == (rvmft->rows + 2))
	{
		middle->elements[rvmft->rows] = middle->elements[1];
		middle->elements[1] = middle->elements[rvmft->rows + 1] = 0;
	}
	memFree(raw);
}

/*
 * Packs the result returned by previous routine back into
 * rvm Fourier transform.
 */

void	V_GLOBAL
matRVMFTPackZeroColumn(matrix_p rvmft,vector_p zero,vector_p middle)
{
	CALLER_NAME(matRVMFTPackZeroColumn)
	vec_p raw, tmp_zero, tmp_middle;
	if(!zero || !middle || !rvmft ||
		zero->size < rvmft->rows || middle->size < rvmft->rows ||
		!IS_POWER_TWO(rvmft->rows) || !IS_POWER_TWO(rvmft->cols))
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return;
	}
	raw = (vec_p)memAlloc(rvmft->rows * 4 * sizeof(vec_t));
	if(zero->size == (rvmft->rows + 2))
	{
		tmp_zero = raw + rvmft->rows * 2;
		memCopy(tmp_zero,zero->elements,rvmft->rows * sizeof(vec_t));
		tmp_zero[1] = zero->elements[rvmft->rows];
	}
	else
		tmp_zero = zero->elements;
	if(middle->size == (rvmft->rows + 2))
	{
		tmp_middle = raw + rvmft->rows * 3;
		memCopy(tmp_middle,middle->elements,rvmft->rows * sizeof(vec_t));
		tmp_middle[1] = middle->elements[rvmft->rows];
	}
	else
		tmp_middle = middle->elements;
	pack_twoffts(raw,zero->elements,middle->elements,rvmft->rows);
	rvmPackCplxCol(rvmft,raw,0);
	memFree(raw);
}

void	V_GLOBAL
matRVMFTMultiply(matrix_p dest,matrix_p multiplier)
{
	CALLER_NAME(matRVMFTMultiply)
	vec_p raw, zero, middle;
	vec_p rawm, zerom, middlem;
	vec_t re1, re2, im1, im2;
	u32_t r, c;
	if(!dest || !multiplier ||
		dest->rows != multiplier->rows || dest->cols != multiplier->cols ||
		!IS_POWER_TWO(dest->rows) || !IS_POWER_TWO(dest->cols))
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return;
	}
	/*
	 * Extract and unpack zero column frequencies
	 */
	raw = (vec_p)memAlloc(dest->rows * 8 * sizeof(vec_t));
	zero = raw + dest->rows * 2;
	middle = zero + dest->rows;
	rawm = middle + dest->rows;
	zerom = rawm + dest->rows * 2;
	middlem = zerom + dest->rows;
	rvmUnpackCplxCol(raw,dest,0);
	rvmUnpackCplxCol(rawm,multiplier,0);
	unpack_twoffts(zero,middle,raw,dest->rows);
	unpack_twoffts(zerom,middlem,rawm,dest->rows);
	/*
	 * Multiply first two real elements
	 */
	zero[0] *= zerom[0];
	middle[0] *= middlem[0];
	zero[1] *= zerom[1];
	middle[1] *= middlem[1];
	/*
	 * The other elements are complex
	 */
	for(r = 2;r < dest->rows;r += 2)
	{
		re1 = zero[r];
		im1 = zero[r + 1];
		re2 = zerom[r];
		im2 = zerom[r + 1];
		zero[r] = re1 * re2 - im1 * im2;
		zero[r + 1] = im1 * re2 + re1 * im2;
		re1 = middle[r];
		im1 = middle[r + 1];
		re2 = middlem[r];
		im2 = middlem[r + 1];
		middle[r] = re1 * re2 - im1 * im2;
		middle[r + 1] = im1 * re2 + re1 * im2;
	}
	/*
	 * Pack zero frequency and reinsert it in 'dest' again
	 */
	pack_twoffts(raw,zero,middle,dest->rows);
	rvmPackCplxCol(dest,raw,0);
	memFree(raw);
	/* the other is straightforward */
	for(r = 0;r < dest->rows;r++)
	{
		for(c = 2;c < dest->cols;c += 2)
		{
			re1 = dest->cell[r][c];
			im1 = dest->cell[r][c + 1];
			re2 = multiplier->cell[r][c];
			im2 = multiplier->cell[r][c + 1];
			dest->cell[r][c] = re1 * re2 - im1 * im2;
			dest->cell[r][c + 1] = im1 * re2 + re1 * im2;
		}
	}
}

/*
 * matRVMFTDivide()
 *
 * The FLOAT_ZERO_EPSILON may be too large; but by coincidence,
 * current value (1e-6) working _good_ for deconvolutions.
 */
void	V_GLOBAL
matRVMFTDivide(matrix_p dest,matrix_p divisor)
{
	CALLER_NAME(matRVMFTDivide)
	vec_p raw, zero, middle;
	vec_p rawm, zerom, middlem;
	vec_t re1, re2, im1, im2, rd_mag;
	u32_t r, c;
	if(!dest || !divisor ||
		dest->rows != divisor->rows || dest->cols != divisor->cols ||
		!IS_POWER_TWO(dest->rows) || !IS_POWER_TWO(dest->cols))
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return;
	}
	/*
	 * Extract and unpack zero column frequencies
	 */
	raw = (vec_p)memAlloc(dest->rows * 8 * sizeof(vec_t));
	zero = raw + dest->rows * 2;
	middle = zero + dest->rows;
	rawm = middle + dest->rows;
	zerom = rawm + dest->rows * 2;
	middlem = zerom + dest->rows;
	rvmUnpackCplxCol(raw,dest,0);
	rvmUnpackCplxCol(rawm,divisor,0);
	unpack_twoffts(zero,middle,raw,dest->rows);
	unpack_twoffts(zerom,middlem,rawm,dest->rows);
	/*
	 * Divide first two real elements
	 */
	if(!IsZero(zerom[0]))
		zero[0] /= zerom[0];
	if(!IsZero(middlem[0]))
		middle[0] /= middlem[0];
	if(!IsZero(zerom[1]))
		zero[1] /= zerom[1];
	if(!IsZero(middlem[1]))
		middle[1] /= middlem[1];
	/*
	 * The other elements are complex
	 */
	for(r = 2;r < dest->rows;r += 2)
	{
		re1 = zero[r];
		im1 = zero[r + 1];
		re2 = zerom[r];
		im2 = zerom[r + 1];
		rd_mag = re2 * re2 + im2 * im2;
		if(IsZero(rd_mag))
			continue;
		rd_mag = 1 / rd_mag;
		zero[r] = rd_mag * (re1 * re2 + im1 * im2);
		zero[r + 1] = rd_mag * (im1 * re2 - re1 * im2);

		re1 = middle[r];
		im1 = middle[r + 1];
		re2 = middlem[r];
		im2 = middlem[r + 1];
		rd_mag = re2 * re2 + im2 * im2;
		if(IsZero(rd_mag))
			continue;
		rd_mag = 1 / rd_mag;
		middle[r] = rd_mag * (re1 * re2 + im1 * im2);
		middle[r + 1] = rd_mag * (im1 * re2 - re1 * im2);
	}
	/*
	 * Pack zero frequency and reinsert it in 'dest' again
	 */
	pack_twoffts(raw,zero,middle,dest->rows);
	rvmPackCplxCol(dest,raw,0);
	memFree(raw);
	/* the other is straightforward */
	for(r = 0;r < dest->rows;r++)
	{
		for(c = 2;c < dest->cols;c += 2)
		{
			re1 = dest->cell[r][c];
			im1 = dest->cell[r][c + 1];
			re2 = divisor->cell[r][c];
			im2 = divisor->cell[r][c + 1];
			rd_mag = re2 * re2 + im2 * im2;
			if(IsZero(rd_mag))
				continue;
			rd_mag = 1 / rd_mag;
			dest->cell[r][c] = rd_mag * (re1 * re2 + im1 * im2);
			dest->cell[r][c + 1] = rd_mag * (im1 * re2 - re1 * im2);
		}
	}
}

/* in either case, at least one-element rectangle will be returned */
V_LOCAL void
mat_clamp_rect(u32_t *left,u32_t *top,u32_t *right,u32_t *bottom,
	u32_t max_right,u32_t max_bottom)
{
	if(*right > max_right)
		*right = max_right;
	if(*bottom > max_bottom)
		*bottom = max_bottom;
	if(*left > *right)
		*left = *right;
	if(*top > *bottom)
		*top = *bottom;
}

/*
 * Walks through all elements of transform, calling the
 * callback_proc for each (Re,Im) pair.
 */
vboolean	V_GLOBAL
matRVMFTWalkCartesian(matrix_p rvmft,
	u32_t col_freq_start,
	u32_t row_freq_start,
	u32_t col_freq_end,
	u32_t row_freq_end,
	cplx_cart_transform_t callback_proc,void_p callback_arg)
{
	CALLER_NAME(matRVMFTWalkCartesian)
	u32_t row_ft_len, col_ft_len, r, nc, ftr, ftc, nc_init, row_freq_limit;
	vec_p raw, zero, middle;
	vec_t reim[2];
	if(!rvmft || !callback_proc ||
		!IS_POWER_TWO(rvmft->rows) || !IS_POWER_TWO(rvmft->cols))
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return false;
	}
	/* Clamp the ranges to positive part of Fourier transform */
	row_ft_len = rvmft->rows >> 1;
	col_ft_len = rvmft->cols >> 1;
	mat_clamp_rect(&col_freq_start,&row_freq_start,&col_freq_end,
		&row_freq_end,col_ft_len,row_ft_len);
	/*
	 * Extract and unpack zero frequencies
	 */
	raw = (vec_p)memAlloc(rvmft->rows * 4 * sizeof(vec_t));
	zero = raw + rvmft->rows * 2;
	middle = zero + rvmft->rows;
	rvmUnpackCplxCol(raw,rvmft,0);
	unpack_twoffts(zero,middle,raw,rvmft->rows);
	/*
	 * Walk though zero column
	 */
	if(!col_freq_start)
	{
		if(!row_freq_start)
		{
			reim[0] = zero[0];
			reim[1] = 0;
			if(!(*callback_proc)(callback_arg,0,0,false,reim))
				goto outer_break;
			zero[0] = reim[0];
		}
		for(r = 2; r < rvmft->rows; r += 2)
			if(!(*callback_proc)(callback_arg,r >> 1,0,false,zero + r))
				goto outer_break;
		if(row_freq_end == row_ft_len)
		{
			reim[0] = zero[1];
			reim[1] = 0;
			if(!(*callback_proc)(callback_arg,row_ft_len,0,false,reim))
				goto outer_break;
			zero[1] = reim[0];
		}
		col_freq_start++;
	}
	if(col_freq_end == col_ft_len)
	{
		if(!row_freq_start)
		{
			reim[0] = middle[0];
			reim[1] = 0;
			if(!(*callback_proc)(callback_arg,0,col_ft_len,false,reim))
				goto outer_break;
			middle[0] = reim[0];
		}
		for(r = 2; r < rvmft->rows; r += 2)
			if(!(*callback_proc)(callback_arg,r >> 1,col_ft_len,false,middle + r))
				goto outer_break;
		if(row_freq_end == row_ft_len)
		{
			reim[0] = middle[1];
			reim[1] = 0;
			if(!(*callback_proc)(callback_arg,row_ft_len,col_ft_len,false,reim))
				goto outer_break;
			middle[1] = reim[0];
		}
		col_freq_end--;
	}
	/*
	 * Pack zero frequency and reinsert it in 'dest' again
	 */
	pack_twoffts(raw,zero,middle,rvmft->rows);
	rvmPackCplxCol(rvmft,raw,0);
	memFree(raw);
	raw = NULL;
	/* walk through other part */
	if(row_freq_end == row_ft_len)
		row_freq_limit = row_freq_end - 1;
	else
		row_freq_limit = row_freq_end;
	nc_init = col_freq_end - col_freq_start + 1;
	/* for zero row frequency */
	if(!row_freq_start)
	{
		nc = nc_init;
		ftc = col_freq_start << 1;
		for(;nc--;ftc += 2)
		{
			if(!callback_proc(callback_arg,0,ftc >> 1,false,&rvmft->cell[0][ftc]))
				goto outer_break;
		}
		row_freq_start++;
	}
	/* for non-zero positive and negative row frequencies */
	for(r = row_freq_start;r <= row_freq_limit;r++)
	{
		ftr = rvmft->rows - r;
		nc = nc_init;
		ftc = col_freq_start << 1;
		for(;nc--;ftc += 2)
		{
			if(!callback_proc(callback_arg,r,ftc >> 1,false,&rvmft->cell[r][ftc]))
				goto outer_break;
			if(!callback_proc(callback_arg,r,ftc >> 1,true,&rvmft->cell[ftr][ftc]))
				goto outer_break;
		}
	}
	/* for highest negative row frequency */
	if(r == row_ft_len)
	{
		nc = nc_init;
		ftc = col_freq_start << 1;
		for(;nc--;ftc += 2)
		{
			if(!callback_proc(callback_arg,r,ftc >> 1,true,&rvmft->cell[r][ftc]))
				goto outer_break;
		}
	}
	return true;
outer_break:;
	if(raw)
		memFree(raw);
	return false;
}

/*
 * Walks through all elements of two transforms, calling the
 * callback_proc for each (Re1,Im1);(Re2,Im2) pairs.
 */
vboolean	V_GLOBAL
matRVMFTWalkCartesianPair(matrix_p rvmft1,matrix_p rvmft2,
	u32_t col_freq_start,
	u32_t row_freq_start,
	u32_t col_freq_end,
	u32_t row_freq_end,
	cplx_cart_transform_pair_t callback_proc,void_p callback_arg)
{
	CALLER_NAME(matRVMFTWalkCartesianPair)
	u32_t row_ft_len, col_ft_len, r, nc, ftr, ftc, nc_init, row_freq_limit;
	vec_p raw1, zero1, middle1, raw2, zero2, middle2;
	vec_t reim1[2], reim2[2];
	if(!rvmft1 || !rvmft2 || !callback_proc ||
		!IS_POWER_TWO(rvmft1->rows) || !IS_POWER_TWO(rvmft1->cols) ||
		rvmft1->rows != rvmft2->rows || rvmft1->cols != rvmft2->cols)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return false;
	}
	/* Clamp the ranges to positive part of Fourier transform */
	row_ft_len = rvmft1->rows >> 1;
	col_ft_len = rvmft1->cols >> 1;
	mat_clamp_rect(&col_freq_start,&row_freq_start,&col_freq_end,
		&row_freq_end,col_ft_len,row_ft_len);
	/*
	 * Extract and unpack zero frequencies
	 */
	raw1 = (vec_p)memAlloc(rvmft1->rows * 8 * sizeof(vec_t));
	zero1 = raw1 + rvmft1->rows * 2;
	middle1 = zero1 + rvmft1->rows;
	raw2 = middle1 + rvmft1->rows;
	zero2 = raw2 + rvmft1->rows * 2;
	middle2 = zero2 + rvmft1->rows;
	rvmUnpackCplxCol(raw1,rvmft1,0);
	unpack_twoffts(zero1,middle1,raw1,rvmft1->rows);
	rvmUnpackCplxCol(raw2,rvmft2,0);
	unpack_twoffts(zero2,middle2,raw2,rvmft1->rows);
	/*
	 * Walk though zero column
	 */
	if(!col_freq_start)
	{
		if(!row_freq_start)
		{
			reim1[0] = zero1[0];
			reim2[0] = zero2[0];
			reim1[1] = reim2[1] = 0;
			if(!(*callback_proc)(callback_arg,0,0,false,reim1,reim2))
				goto outer_break;
			zero1[0] = reim1[0];
			zero2[0] = reim2[0];
		}
		for(r = 2; r < rvmft1->rows; r += 2)
			if(!(*callback_proc)(callback_arg,r >> 1,0,false,zero1 + r,zero2 + r))
				goto outer_break;
		if(row_freq_end == row_ft_len)
		{
			reim1[0] = zero1[1];
			reim2[0] = zero2[1];
			reim1[1] = reim2[1] = 0;
			if(!(*callback_proc)(callback_arg,row_ft_len,0,false,reim1,reim2))
				goto outer_break;
			zero1[1] = reim1[0];
			zero2[1] = reim2[0];
		}
		col_freq_start++;
	}
	if(col_freq_end == col_ft_len)
	{
		if(!row_freq_start)
		{
			reim1[0] = middle1[0];
			reim2[0] = middle2[0];
			reim1[1] = reim2[1] = 0;
			if(!(*callback_proc)(callback_arg,0,col_ft_len,false,reim1,reim2))
				goto outer_break;
			middle1[0] = reim1[0];
			middle2[0] = reim2[0];
		}
		for(r = 2; r < rvmft1->rows; r += 2)
			if(!(*callback_proc)(callback_arg,r >> 1,col_ft_len,false,middle1 + r,middle2 + r))
				goto outer_break;
		if(row_freq_end == row_ft_len)
		{
			reim1[0] = middle1[1];
			reim2[0] = middle2[1];
			reim1[1] = reim2[1] = 0;
			if(!(*callback_proc)(callback_arg,row_ft_len,col_ft_len,false,reim1,reim2))
				goto outer_break;
			middle1[1] = reim1[0];
			middle2[1] = reim2[0];
		}
		col_freq_end--;
	}
	/*
	 * Pack zero frequency and reinsert it in 'dest' again
	 */
	pack_twoffts(raw1,zero1,middle1,rvmft1->rows);
	rvmPackCplxCol(rvmft1,raw1,0);
	pack_twoffts(raw2,zero2,middle2,rvmft1->rows);
	rvmPackCplxCol(rvmft2,raw2,0);
	memFree(raw1);
	raw1 = NULL;
	/* walk through other part */
	if(row_freq_end == row_ft_len)
		row_freq_limit = row_freq_end - 1;
	else
		row_freq_limit = row_freq_end;
	nc_init = col_freq_end - col_freq_start + 1;
	/* for zero row frequency */
	if(!row_freq_start)
	{
		nc = nc_init;
		ftc = col_freq_start << 1;
		for(;nc--;ftc += 2)
		{
			if(!callback_proc(callback_arg,0,ftc >> 1,false,
					&rvmft1->cell[0][ftc],&rvmft2->cell[0][ftc]))
				goto outer_break;
		}
		row_freq_start++;
	}
	/* for non-zero positive and negative row frequencies */
	for(r = row_freq_start;r <= row_freq_limit;r++)
	{
		ftr = rvmft1->rows - r;
		nc = nc_init;
		ftc = col_freq_start << 1;
		for(;nc--;ftc += 2)
		{
			if(!callback_proc(callback_arg,r,ftc >> 1,false,
					&rvmft1->cell[r][ftc],&rvmft2->cell[r][ftc]))
				goto outer_break;
			if(!callback_proc(callback_arg,r,ftc >> 1,true,
					&rvmft1->cell[ftr][ftc],&rvmft2->cell[ftr][ftc]))
				goto outer_break;
		}
	}
	/* for highest negative row frequency */
	if(r == row_ft_len)
	{
		nc = nc_init;
		ftc = col_freq_start << 1;
		for(;nc--;ftc += 2)
		{
			if(!callback_proc(callback_arg,r,ftc >> 1,true,
					&rvmft1->cell[r][ftc],&rvmft2->cell[r][ftc]))
				goto outer_break;
		}
	}
	return true;
outer_break:;
	if(raw1)
		memFree(raw1);
	return false;
}

typedef struct rvmft_recall_polar_s
{
	vboolean (V_CALL *pcb)(void_p,u32_t,u32_t,vboolean,vec_p);
	void_p pcb_arg;
}	rvmft_recall_polar_t, *rvmft_recall_polar_p;

V_LOCAL vboolean V_CALL
rvmft_polar_recall(void_p arg,u32_t rowf,u32_t colf,vboolean negrowf,vec_p reim)
{
	vboolean result;
	rvmft_recall_polar_p recall = (rvmft_recall_polar_p)arg;
	vec_t magphase[2];
	magphase[0] = (vec_t)hypot(reim[0],reim[1]);
	if(IsZero(reim[0]))
	{
		if(reim[1] < 0)
			magphase[1] = -M_PI2;
		else
			magphase[1] = M_PI2;
	}
	else
		magphase[1] = (vec_t)atan2(reim[1], reim[0]);
	result = (*recall->pcb)(recall->pcb_arg,rowf,colf,negrowf,magphase);
	reim[0] = magphase[0] * (vec_t)cos(magphase[1]);
	reim[1] = magphase[0] * (vec_t)sin(magphase[1]);
	return result;
}

typedef struct rvmft_recall_polar_pair_s
{
	vboolean (V_CALL *pcb)(void_p,u32_t,u32_t,vboolean,vec_p,vec_p);
	void_p pcb_arg;
}	rvmft_recall_polar_pair_t, *rvmft_recall_polar_pair_p;

V_LOCAL vboolean V_CALL
rvmft_polar_pair_recall(void_p arg,u32_t rowf,u32_t colf,
	vboolean negrowf,vec_p reim1,vec_p reim2)
{
	vboolean result;
	rvmft_recall_polar_pair_p recall = (rvmft_recall_polar_pair_p)arg;
	vec_t magphase1[2], magphase2[2];
	magphase1[0] = (vec_t)hypot(reim1[0],reim1[1]);
	if(IsZero(reim1[0]))
	{
		if(reim1[1] < 0)
			magphase1[1] = -M_PI2;
		else
			magphase1[1] = M_PI2;
	}
	else
		magphase1[1] = (vec_t)atan2(reim1[1], reim1[0]);
	magphase2[0] = (vec_t)hypot(reim2[0],reim2[1]);
	if(IsZero(reim2[0]))
	{
		if(reim2[1] < 0)
			magphase2[1] = -M_PI2;
		else
			magphase2[1] = M_PI2;
	}
	else
		magphase2[1] = (vec_t)atan2(reim2[1], reim2[0]);
	result = (*recall->pcb)(recall->pcb_arg,rowf,colf,negrowf,
		magphase1,magphase2);
	reim1[0] = magphase1[0] * (vec_t)cos(magphase1[1]);
	reim1[1] = magphase1[0] * (vec_t)sin(magphase1[1]);
	reim2[0] = magphase2[0] * (vec_t)cos(magphase2[1]);
	reim2[1] = magphase2[0] * (vec_t)sin(magphase2[1]);
	return result;
}

vboolean	V_GLOBAL
matRVMFTWalkPolar(matrix_p rvmft,
	u32_t col_freq_start,
	u32_t row_freq_start,
	u32_t col_freq_end,
	u32_t row_freq_end,
	cplx_polar_transform_t callback_proc,void_p callback_arg)
{
	rvmft_recall_polar_t recall;
	recall.pcb = callback_proc;
	recall.pcb_arg = callback_arg;
	return matRVMFTWalkCartesian(rvmft,col_freq_start,row_freq_start,
		col_freq_end,row_freq_end,rvmft_polar_recall,&recall);
}

vboolean	V_GLOBAL
matRVMFTWalkPolarPair(matrix_p rvmft1,matrix_p rvmft2,
	u32_t col_freq_start,
	u32_t row_freq_start,
	u32_t col_freq_end,
	u32_t row_freq_end,
	cplx_polar_transform_pair_t callback_proc,void_p callback_arg)
{
	rvmft_recall_polar_pair_t recall;
	recall.pcb = callback_proc;
	recall.pcb_arg = callback_arg;
	return matRVMFTWalkCartesianPair(rvmft1,rvmft2,
		col_freq_start,row_freq_start,
		col_freq_end,row_freq_end,
		rvmft_polar_pair_recall,&recall);
}

/*
 * The filtering routines below are _not_ fast, but the implementation
 * is very clear. Let they be.
 */

typedef struct rvmft_filter_s
{
	u32_t	cutoff_lower, cutoff_upper;
}	rvmft_filter_t, *rvmft_filter_p;

#include <../source/PAL/argsused.h>
V_LOCAL vboolean V_CALL
rvmft_lo_pass_callback(void_p arg,u32_t row,u32_t col,vboolean negf,vec_p reim)
{
	rvmft_filter_p filt = (rvmft_filter_p)arg;
	if(row > filt->cutoff_upper || col > filt->cutoff_upper)
		reim[0] = reim[1] = 0;
	return true;
}

#include <../source/PAL/argsused.h>
V_LOCAL vboolean V_CALL
rvmft_hi_pass_callback(void_p arg,u32_t row,u32_t col,vboolean negf,vec_p reim)
{
	rvmft_filter_p filt = (rvmft_filter_p)arg;
	if(row < filt->cutoff_lower || col < filt->cutoff_lower)
		reim[0] = reim[1] = 0;
	return true;
}

#include <../source/PAL/argsused.h>
V_LOCAL vboolean V_CALL
rvmft_band_pass_callback(void_p arg,u32_t row,u32_t col,vboolean negf,vec_p reim)
{
	rvmft_filter_p filt = (rvmft_filter_p)arg;
	if(row < filt->cutoff_lower || col < filt->cutoff_lower ||
		row > filt->cutoff_upper || col > filt->cutoff_upper)
		reim[0] = reim[1] = 0;
	return true;
}

#include <../source/PAL/argsused.h>
V_LOCAL vboolean V_CALL
rvmft_band_reject_callback(void_p arg,u32_t row,u32_t col,vboolean negf,vec_p reim)
{
	rvmft_filter_p filt = (rvmft_filter_p)arg;
	if(row < filt->cutoff_lower || col < filt->cutoff_lower ||
		row > filt->cutoff_upper || col > filt->cutoff_upper)
		return true;
	reim[0] = reim[1] = 0;
	return true;
}

void	V_GLOBAL
matRVMFTLowPass(matrix_p rvmft,u32_t cutoff)
{
	rvmft_filter_t filt;
	filt.cutoff_upper = cutoff;
	matRVMFTWalkCartesian(rvmft,0,0,rvmft->cols,rvmft->rows,
		rvmft_lo_pass_callback,&filt);
}

void	V_GLOBAL
matRVMFTHighPass(matrix_p rvmft,u32_t cutoff)
{
	rvmft_filter_t filt;
	filt.cutoff_lower = cutoff;
	matRVMFTWalkCartesian(rvmft,0,0,rvmft->cols,rvmft->rows,
		rvmft_hi_pass_callback,&filt);
}

void	V_GLOBAL
matRVMFTBandPass(matrix_p rvmft,u32_t cutoff_lower,u32_t cutoff_upper)
{
	rvmft_filter_t filt;
	filt.cutoff_lower = cutoff_lower;
	filt.cutoff_upper = cutoff_upper;
	matRVMFTWalkCartesian(rvmft,0,0,rvmft->cols,rvmft->rows,
		rvmft_band_pass_callback,&filt);
}

void	V_GLOBAL
matRVMFTBandReject(matrix_p rvmft,u32_t cutoff_lower,u32_t cutoff_upper)
{
	rvmft_filter_t filt;
	filt.cutoff_lower = cutoff_lower;
	filt.cutoff_upper = cutoff_upper;
	matRVMFTWalkCartesian(rvmft,0,0,rvmft->cols,rvmft->rows,
		rvmft_band_reject_callback,&filt);
}
