
/**
   1D and 2D FFT routines.
   
   @author Morgan McGuire (reformulation of 
     <A HREF="mailto:jdtaft@nauticom.net">Jeffrey Taft</A>'s 
     <A HREF="http://www.nauticom.net/www/jdtaft/JavaFFT.htm">1D Java FFT code</A>)
   

    
   The algorithms work on arrays of complex numbers.  These are
   represented as array[REAL][x] is the real part of a[x] and
   array[IMAG][x] is the imaginary part.  This representation is
   chosen because signal processing operations typically separate
   the real and imaginary parts for processing.
 */
public class FFT {

    static public final int REAL = 0;
    static public final int IMAG = 1;

    /**
       Complex magnitude index.
     */
    static public final int MAGNT = 0;

    /**
       Complex phase index.
     */
    static public final int PHASE = 0;

    /**
       Computes the conjugate of an array (i.e. negates the imaginary part)
       in place and returns the array.
       
       @param array[2][n]
     */
    public static float[][] conjugate(float array[][]) {
        float imag[] = array[IMAG];
        
        for (int i = imag.length - 1; i >= 0; i--) {
            imag[i] = -imag[i];
        }

        return array;
    }

    /**
       Converts an array in REAL/IMAG format to MAGNT/PHASE format in place
       and returns the array.
     */
    public static float[][] toMagntPhase(float array[][]) {
        for (int i = array[REAL].length - 1; i >= 0; i--) {
            float x_r = array[REAL][i];
            float x_i = array[IMAG][i];

            array[MAGNT][i] = (float)Math.sqrt(x_r * x_r + x_i * x_i);
            array[PHASE][i] = (float)Math.atan2(x_i, x_r);
        }

        return array;
    }


    /**
       Converts an array in MAGNT/PHASE format to REAL/IMAG format in
       place and returns the array.  */
    public static float[][] toRealImag(float array[][]) {
        for (int i = array[MAGNT].length - 1; i >= 0; i--) {
            float x_m = array[MAGNT][i];
            float x_p = array[PHASE][i];

            array[REAL][i] = (float)(x_m * Math.cos(x_p));
            array[IMAG][i] = (float)(x_m * Math.sin(x_p));
        }

        return array;
    }

    /**
       Performs a 2D complex inverse FFT in place and returns the input array.

       @param array[2][width * height] Real and imaginary parts of a
       2D array stored in 1D row-major order.  Width and height must each
       be powers of two.
     */
    public static float[][] ifft_2d(float array[][], int width, int height) {
        // ifft(x) = fft(conjugate(x));

        return fft_2d(conjugate(array), width, height);
    }

    /**
       Performs a 2D complex FFT in place and returns the input array.

       @param array[2][width * height] Real and imaginary parts of a
       2D array stored in 1D row-major order.  Width and height must each
       be powers of two.
    */
    public static float[][] fft_2d(float array[][], int width, int height) {
        if (array.length != 2) {
            throw new IllegalArgumentException("Array must contain a real and an imaginary array.");
        }

        if (array[REAL].length != width * height) {
            throw new IllegalArgumentException("Real and imaginary arrays must have length width * height");
        }
        
        // Row FFTs
        {
            float subarray[][] = new float[2][width];
            for (int r = 0; r < height; r++) {
                // Grab the row
                System.arraycopy(array[REAL], r * width, subarray[REAL], 0, width);
                System.arraycopy(array[IMAG], r * width, subarray[IMAG], 0, width);

                // in place 1D FFT
                fft_1d(subarray);

                // Copy back
                System.arraycopy(subarray[REAL], 0, array[REAL], r * width, width);
                System.arraycopy(subarray[IMAG], 0, array[IMAG], r * width, width);
            }
        }

        // Col FFTs
        {
            float subarray[][] = new float[2][height];
            for (int c = 0; c < width; c++) {

                // Grab the column
                for (int r = 0; r < height; r++) {
                    subarray[REAL][r] = array[REAL][r * width + c];
                    subarray[IMAG][r] = array[IMAG][r * width + c];
                }

                // in place 1D FFT
                fft_1d(subarray);

                // Put the column back
                for (int r = 0; r < height; r++) {
                    array[REAL][r * width + c] = subarray[REAL][r];
                    array[IMAG][r * width + c] = subarray[IMAG][r];
                }
            }
        }

        // Return the input array.
        return array;
    }

    /**
       Mutates the original array to contain the real part of the FFT and
       creates a new array for the imaginary part.
     */
    public static float[][] fft_1d(float array[]) {
        float x[][] = new float[2][array.length];
        x[REAL] = array;
        x[IMAG] = new float[array.length];
        return fft_1d(x);
    }

    /**
       Performs a 1D FFT in place and returns the input array.
       
       @param array[2][n] Real and imaginary input.  n must be a power of two.
    */
    public static float[][] fft_1d(float array[][]) {

        // Real part of the input/output
        float real[];

        // Imaginary part of the input/output
        float imag[];

        // Length of the input
        int n;
	n = array[REAL].length;

        // Check the array length
        {
            double exp = Math.log(n) / Math.log(2);
            if (((int)exp) != exp) {
                throw new IllegalArgumentException("Array length must be a power of two (n = " + n + ")");
            }
        }

	float  t_real, t_imag;
	int     ln, nv2, k, l, j, ip, i;

        real = array[REAL];
        imag = array[IMAG];

    	ln = (int)(Math.log((float) n) / Math.log(2) + 0.5);
    	nv2 = n / 2;
    	j = 1;
 	for (i = 1; i < n; i++) {
            if (i < j) {
                t_real = real[i - 1];
                t_imag = imag[i - 1];
                real[i - 1] = real[j - 1];
                imag[i - 1] = imag[j - 1];
                real[j - 1] = t_real;
                imag[j - 1] = t_imag;
            }

            k = nv2;
            while (k < j) {
                j = j - k;
                k = k / 2;
            }
            j = j + k;
    	}

        // loops through stages
 	for (l = 1; l <= ln; l++) {	
            float le = (int)(Math.exp((float)l * Math.log(2)) + 0.5);
            float le1 = le / 2;

            float u_real = 1.0f;
            float u_imag = 0.0f;

            // Complex roots of unity
            float w_real = (float) Math.cos(Math.PI / (double)le1);
            float w_imag = (float)-Math.sin(Math.PI / (double)le1);

            // loops through 1/2 twiddle values per stage
            for (j = 1; j <= le1; j++) {

                // loops through points per 1/2 twiddle
                for (i = j; i <= n; i += le) {
                    ip = (int)(i + le1);
                    t_real = real[ip - 1] * u_real - u_imag * imag[ip - 1];
                    t_imag = imag[ip - 1] * u_real + u_imag * real[ip - 1];
                    
                    real[ip - 1] = real[i - 1] - t_real;
                    imag[ip - 1] = imag[i - 1] - t_imag; 
                    
                    real[i - 1]  = real[i - 1] + t_real;
                    imag[i - 1]  = imag[i - 1] + t_imag;  
                }
                t_real = u_real * w_real - w_imag * u_imag;
                u_imag = w_real * u_imag + w_imag * u_real;
                u_real = t_real;
            } 
    	}  
	return array;
    }
}


