/*
Copyright (C) 2001, 2006 by Simon Dixon
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 (the file gpl.txt); if not, download it from
http://www.gnu.org/licenses/gpl.txt or write to the
Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package at.ofai.music.audio;
/** Class for computing a windowed fast Fourier transform.
* Implements some of the window functions for the STFT from
* Harris (1978), Proc. IEEE, 66, 1, 51-83.
*/
public class FFT {
/** used in {@link FFT#fft(double[], double[], int)} to specify
* a forward Fourier transform */
public static final int FORWARD = -1;
/** used in {@link FFT#fft(double[], double[], int)} to specify
* an inverse Fourier transform */
public static final int REVERSE = 1;
/** used in {@link FFT#makeWindow(int,int,int)} to specify a
* rectangular window function */
public static final int RECT = 0;
/** used in {@link FFT#makeWindow(int,int,int)} to specify a
* Hamming window function */
public static final int HAMMING = 1;
/** used in {@link FFT#makeWindow(int,int,int)} to specify a
* 61-dB 3-sample Blackman-Harris window function */
public static final int BH3 = 2;
/** used in {@link FFT#makeWindow(int,int,int)} to specify a
* 74-dB 4-sample Blackman-Harris window function */
public static final int BH4 = 3;
/** used in {@link FFT#makeWindow(int,int,int)} to specify a
* minimum 3-sample Blackman-Harris window function */
public static final int BH3MIN = 4;
/** used in {@link FFT#makeWindow(int,int,int)} to specify a
* minimum 4-sample Blackman-Harris window function */
public static final int BH4MIN = 5;
/** used in {@link FFT#makeWindow(int,int,int)} to specify a
* Gaussian window function */
public static final int GAUSS = 6;
static final double twoPI = 2 * Math.PI;
/** The FFT method. Calculation is inline, for complex data stored
* in 2 separate arrays. Length of input data must be a power of two.
* @param re the real part of the complex input and output data
* @param im the imaginary part of the complex input and output data
* @param direction the direction of the Fourier transform (FORWARD or
* REVERSE)
* @throws IllegalArgumentException if the length of the input data is
* not a power of 2
*/
public static void fft(double re[], double im[], int direction) {
int n = re.length;
int bits = (int)Math.rint(Math.log(n) / Math.log(2));
if (n != (1 << bits))
throw new IllegalArgumentException("FFT data must be power of 2");
int localN;
int j = 0;
for (int i = 0; i < n-1; i++) {
if (i < j) {
double temp = re[j];
re[j] = re[i];
re[i] = temp;
temp = im[j];
im[j] = im[i];
im[i] = temp;
}
int k = n / 2;
while ((k >= 1) && (k - 1 < j)) {
j = j - k;
k = k / 2;
}
j = j + k;
}
for(int m = 1; m <= bits; m++) {
localN = 1 << m;
double Wjk_r = 1;
double Wjk_i = 0;
double theta = twoPI / localN;
double Wj_r = Math.cos(theta);
double Wj_i = direction * Math.sin(theta);
int nby2 = localN / 2;
for (j = 0; j < nby2; j++) {
for (int k = j; k < n; k += localN) {
int id = k + nby2;
double tempr = Wjk_r * re[id] - Wjk_i * im[id];
double tempi = Wjk_r * im[id] + Wjk_i * re[id];
re[id] = re[k] - tempr;
im[id] = im[k] - tempi;
re[k] += tempr;
im[k] += tempi;
}
double wtemp = Wjk_r;
Wjk_r = Wj_r * Wjk_r - Wj_i * Wjk_i;
Wjk_i = Wj_r * Wjk_i + Wj_i * wtemp;
}
}
} // fft()
/** Computes the power spectrum of a real sequence (in place).
* @param re the real input and output data; length must be a power of 2
*/
public static void powerFFT(double[] re) {
double[] im = new double[re.length];
fft(re, im, FORWARD);
for (int i = 0; i < re.length; i++)
re[i] = re[i] * re[i] + im[i] * im[i];
} // powerFFT()
/** Converts a real power sequence from to magnitude representation,
* by computing the square root of each value.
* @param re the real input (power) and output (magnitude) data; length
* must be a power of 2
*/
public static void toMagnitude(double[] re) {
for (int i = 0; i < re.length; i++)
re[i] = Math.sqrt(re[i]);
} // toMagnitude()
/** Computes the magnitude spectrum of a real sequence (in place).
* @param re the real input and output data; length must be a power of 2
*/
public static void magnitudeFFT(double[] re) {
powerFFT(re);
toMagnitude(re);
} // magnitudeFFT()
/** Computes a complex (or real if im[] == {0,...}) FFT and converts
* the results to polar coordinates (power and phase). Both arrays
* must be the same length, which is a power of 2.
* @param re the real part of the input data and the power of the output
* data
* @param im the imaginary part of the input data and the phase of the
* output data
*/
public static void powerPhaseFFT(double[] re, double[] im) {
fft(re, im, FORWARD);
for (int i = 0; i < re.length; i++) {
double pow = re[i] * re[i] + im[i] * im[i];
im[i] = Math.atan2(im[i], re[i]);
re[i] = pow;
}
} // powerPhaseFFT()
/** Inline computation of the inverse FFT given spectral input data
* in polar coordinates (power and phase).
* Both arrays must be the same length, which is a power of 2.
* @param pow the power of the spectral input data (and real part of the
* output data)
* @param ph the phase of the spectral input data (and the imaginary part
* of the output data)
*/
public static void powerPhaseIFFT(double[] pow, double[] ph) {
toMagnitude(pow);
for (int i = 0; i < pow.length; i++) {
double re = pow[i] * Math.cos(ph[i]);
ph[i] = pow[i] * Math.sin(ph[i]);
pow[i] = re;
}
fft(pow, ph, REVERSE);
} // powerPhaseIFFT()
/** Computes a complex (or real if im[] == {0,...}) FFT and converts
* the results to polar coordinates (magnitude and phase). Both arrays
* must be the same length, which is a power of 2.
* @param re the real part of the input data and the magnitude of the
* output data
* @param im the imaginary part of the input data and the phase of the
* output data
*/
public static void magnitudePhaseFFT(double[] re, double[] im) {
powerPhaseFFT(re, im);
toMagnitude(re);
} // magnitudePhaseFFT()
/** Fill an array with the values of a standard Hamming window function
* @param data the array to be filled
* @param size the number of non zero values; if the array is larger than
* this, it is zero-padded symmetrically at both ends
*/
static void hamming(double[] data, int size) {
int start = (data.length - size) / 2;
int stop = (data.length + size) / 2;
double scale = 1.0 / (double)size / 0.54;
double factor = twoPI / (double)size;
for (int i = 0; start < stop; start++, i++)
data[i] = scale * (25.0/46.0 - 21.0/46.0 * Math.cos(factor * i));
} // hamming()
/** Fill an array with the values of a minimum 4-sample Blackman-Harris
* window function
* @param data the array to be filled
* @param size the number of non zero values; if the array is larger than
* this, it is zero-padded symmetrically at both ends
*/
static void blackmanHarris4sMin(double[] data, int size) {
int start = (data.length - size) / 2;
int stop = (data.length + size) / 2;
double scale = 1.0 / (double)size / 0.36;
for (int i = 0; start < stop; start++, i++)
data[i] = scale * ( 0.35875 -
0.48829 * Math.cos(twoPI * i / size) +
0.14128 * Math.cos(2 * twoPI * i / size) -
0.01168 * Math.cos(3 * twoPI * i / size));
} // blackmanHarris4sMin()
/** Fill an array with the values of a 74-dB 4-sample Blackman-Harris
* window function
* @param data the array to be filled
* @param size the number of non zero values; if the array is larger than
* this, it is zero-padded symmetrically at both ends
*/
static void blackmanHarris4s(double[] data, int size) {
int start = (data.length - size) / 2;
int stop = (data.length + size) / 2;
double scale = 1.0 / (double)size / 0.4;
for (int i = 0; start < stop; start++, i++)
data[i] = scale * ( 0.40217 -
0.49703 * Math.cos(twoPI * i / size) +
0.09392 * Math.cos(2 * twoPI * i / size) -
0.00183 * Math.cos(3 * twoPI * i / size));
} // blackmanHarris4s()
/** Fill an array with the values of a minimum 3-sample Blackman-Harris
* window function
* @param data the array to be filled
* @param size the number of non zero values; if the array is larger than
* this, it is zero-padded symmetrically at both ends
*/
static void blackmanHarris3sMin(double[] data, int size) {
int start = (data.length - size) / 2;
int stop = (data.length + size) / 2;
double scale = 1.0 / (double) size / 0.42;
for (int i = 0; start < stop; start++, i++)
data[i] = scale * ( 0.42323 -
0.49755 * Math.cos(twoPI * i / size) +
0.07922 * Math.cos(2 * twoPI * i / size));
} // blackmanHarris3sMin()
/** Fill an array with the values of a 61-dB 3-sample Blackman-Harris
* window function
* @param data the array to be filled
* @param size the number of non zero values; if the array is larger than
* this, it is zero-padded symmetrically at both ends
*/
static void blackmanHarris3s(double[] data, int size) {
int start = (data.length - size) / 2;
int stop = (data.length + size) / 2;
double scale = 1.0 / (double) size / 0.45;
for (int i = 0; start < stop; start++, i++)
data[i] = scale * ( 0.44959 -
0.49364 * Math.cos(twoPI * i / size) +
0.05677 * Math.cos(2 * twoPI * i / size));
} // blackmanHarris3s()
/** Fill an array with the values of a Gaussian window function
* @param data the array to be filled
* @param size the number of non zero values; if the array is larger than
* this, it is zero-padded symmetrically at both ends
*/
static void gauss(double[] data, int size) { // ?? between 61/3 and 74/4 BHW
int start = (data.length - size) / 2;
int stop = (data.length + size) / 2;
double delta = 5.0 / size;
double x = (1 - size) / 2.0 * delta;
double c = -Math.PI * Math.exp(1.0) / 10.0;
double sum = 0;
for (int i = start; i < stop; i++) {
data[i] = Math.exp(c * x * x);
x += delta;
sum += data[i];
}
for (int i = start; i < stop; i++)
data[i] /= sum;
} // gauss()
/** Fill an array with the values of a rectangular window function
* @param data the array to be filled
* @param size the number of non zero values; if the array is larger than
* this, it is zero-padded symmetrically at both ends
*/
static void rectangle(double[] data, int size) {
int start = (data.length - size) / 2;
int stop = (data.length + size) / 2;
for (int i = start; i < stop; i++)
data[i] = 1.0 / (double) size;
} // rectangle()
/** Returns an array of values of a normalised smooth window function,
* as used for performing a short time Fourier transform (STFT).
* All functions are normalised by length and coherent gain.
* More information on characteristics of these functions can be found
* in F.J. Harris (1978), On the Use of Windows for Harmonic Analysis
* with the Discrete Fourier Transform, <em>Proceedings of the IEEE</em>,
* 66, 1, 51-83.
* @param choice the choice of window function, one of the constants
* defined above
* @param size the size of the returned array
* @param support the number of non-zero values in the array
* @return the array containing the values of the window function
*/
public static double[] makeWindow(int choice, int size, int support) {
double[] data = new double[size];
if (support > size)
support = size;
switch (choice) {
case RECT: rectangle(data, support); break;
case HAMMING: hamming(data, support); break;
case BH3: blackmanHarris3s(data, support); break;
case BH4: blackmanHarris4s(data, support); break;
case BH3MIN: blackmanHarris3sMin(data, support); break;
case BH4MIN: blackmanHarris4sMin(data, support); break;
case GAUSS: gauss(data, support); break;
default: rectangle(data, support); break;
}
return data;
} // makeWindow()
/** Applies a window function to an array of data, storing the result in
* the data array.
* Performs a dot product of the data and window arrays.
* @param data the array of input data, also used for output
* @param window the values of the window function to be applied to data
*/
public static void applyWindow(double[] data, double[] window) {
for (int i = 0; i < data.length; i++)
data[i] *= window[i];
} // applyWindow()
/** Unit test of the FFT class.
* Performs a forward and inverse FFT on a 1MB array of random values
* and checks how closely the values are preserved.
* @param args ignored
*/
public static void main(String[] args) {
final int SZ = 1024 * 1024;
double[] r1 = new double[SZ];
double[] i1 = new double[SZ];
double[] r2 = new double[SZ];
double[] i2 = new double[SZ];
for (int j = 0; j < SZ; j++) {
r1[j] = r2[j] = Math.random();
i1[j] = i2[j] = Math.random();
}
System.out.println("start");
fft(r2, i2, FORWARD);
System.out.println("reverse");
fft(r2, i2, REVERSE);
System.out.println("result");
double err = 0;
for (int j = 0; j < SZ; j++)
err += Math.abs(r1[j] - r2[j] / SZ) + Math.abs(i1[j] - i2[j] / SZ);
System.out.printf( "Err: %12.10f Av: %12.10f\n", err, err / SZ);
} // main()
} // class FFT