annotate at/ofai/music/audio/FFT.java @ 5:bcb4c9697967 tip

Add README and CITATION files
author Chris Cannam
date Tue, 03 Dec 2013 12:58:05 +0000
parents 4c3f5bc01c97
children
rev   line source
Chris@2 1 /*
Chris@2 2 Copyright (C) 2001, 2006 by Simon Dixon
Chris@2 3
Chris@2 4 This program is free software; you can redistribute it and/or modify
Chris@2 5 it under the terms of the GNU General Public License as published by
Chris@2 6 the Free Software Foundation; either version 2 of the License, or
Chris@2 7 (at your option) any later version.
Chris@2 8
Chris@2 9 This program is distributed in the hope that it will be useful,
Chris@2 10 but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@2 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@2 12 GNU General Public License for more details.
Chris@2 13
Chris@2 14 You should have received a copy of the GNU General Public License along
Chris@2 15 with this program (the file gpl.txt); if not, download it from
Chris@2 16 http://www.gnu.org/licenses/gpl.txt or write to the
Chris@2 17 Free Software Foundation, Inc.,
Chris@2 18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Chris@2 19 */
Chris@2 20
Chris@2 21 package at.ofai.music.audio;
Chris@2 22
Chris@2 23 /** Class for computing a windowed fast Fourier transform.
Chris@2 24 * Implements some of the window functions for the STFT from
Chris@2 25 * Harris (1978), Proc. IEEE, 66, 1, 51-83.
Chris@2 26 */
Chris@2 27 public class FFT {
Chris@2 28
Chris@2 29 /** used in {@link FFT#fft(double[], double[], int)} to specify
Chris@2 30 * a forward Fourier transform */
Chris@2 31 public static final int FORWARD = -1;
Chris@2 32 /** used in {@link FFT#fft(double[], double[], int)} to specify
Chris@2 33 * an inverse Fourier transform */
Chris@2 34 public static final int REVERSE = 1;
Chris@2 35 /** used in {@link FFT#makeWindow(int,int,int)} to specify a
Chris@2 36 * rectangular window function */
Chris@2 37 public static final int RECT = 0;
Chris@2 38 /** used in {@link FFT#makeWindow(int,int,int)} to specify a
Chris@2 39 * Hamming window function */
Chris@2 40 public static final int HAMMING = 1;
Chris@2 41 /** used in {@link FFT#makeWindow(int,int,int)} to specify a
Chris@2 42 * 61-dB 3-sample Blackman-Harris window function */
Chris@2 43 public static final int BH3 = 2;
Chris@2 44 /** used in {@link FFT#makeWindow(int,int,int)} to specify a
Chris@2 45 * 74-dB 4-sample Blackman-Harris window function */
Chris@2 46 public static final int BH4 = 3;
Chris@2 47 /** used in {@link FFT#makeWindow(int,int,int)} to specify a
Chris@2 48 * minimum 3-sample Blackman-Harris window function */
Chris@2 49 public static final int BH3MIN = 4;
Chris@2 50 /** used in {@link FFT#makeWindow(int,int,int)} to specify a
Chris@2 51 * minimum 4-sample Blackman-Harris window function */
Chris@2 52 public static final int BH4MIN = 5;
Chris@2 53 /** used in {@link FFT#makeWindow(int,int,int)} to specify a
Chris@2 54 * Gaussian window function */
Chris@2 55 public static final int GAUSS = 6;
Chris@2 56 static final double twoPI = 2 * Math.PI;
Chris@2 57
Chris@2 58 /** The FFT method. Calculation is inline, for complex data stored
Chris@2 59 * in 2 separate arrays. Length of input data must be a power of two.
Chris@2 60 * @param re the real part of the complex input and output data
Chris@2 61 * @param im the imaginary part of the complex input and output data
Chris@2 62 * @param direction the direction of the Fourier transform (FORWARD or
Chris@2 63 * REVERSE)
Chris@2 64 * @throws IllegalArgumentException if the length of the input data is
Chris@2 65 * not a power of 2
Chris@2 66 */
Chris@2 67 public static void fft(double re[], double im[], int direction) {
Chris@2 68 int n = re.length;
Chris@2 69 int bits = (int)Math.rint(Math.log(n) / Math.log(2));
Chris@2 70 if (n != (1 << bits))
Chris@2 71 throw new IllegalArgumentException("FFT data must be power of 2");
Chris@2 72 int localN;
Chris@2 73 int j = 0;
Chris@2 74 for (int i = 0; i < n-1; i++) {
Chris@2 75 if (i < j) {
Chris@2 76 double temp = re[j];
Chris@2 77 re[j] = re[i];
Chris@2 78 re[i] = temp;
Chris@2 79 temp = im[j];
Chris@2 80 im[j] = im[i];
Chris@2 81 im[i] = temp;
Chris@2 82 }
Chris@2 83 int k = n / 2;
Chris@2 84 while ((k >= 1) && (k - 1 < j)) {
Chris@2 85 j = j - k;
Chris@2 86 k = k / 2;
Chris@2 87 }
Chris@2 88 j = j + k;
Chris@2 89 }
Chris@2 90 for(int m = 1; m <= bits; m++) {
Chris@2 91 localN = 1 << m;
Chris@2 92 double Wjk_r = 1;
Chris@2 93 double Wjk_i = 0;
Chris@2 94 double theta = twoPI / localN;
Chris@2 95 double Wj_r = Math.cos(theta);
Chris@2 96 double Wj_i = direction * Math.sin(theta);
Chris@2 97 int nby2 = localN / 2;
Chris@2 98 for (j = 0; j < nby2; j++) {
Chris@2 99 for (int k = j; k < n; k += localN) {
Chris@2 100 int id = k + nby2;
Chris@2 101 double tempr = Wjk_r * re[id] - Wjk_i * im[id];
Chris@2 102 double tempi = Wjk_r * im[id] + Wjk_i * re[id];
Chris@2 103 re[id] = re[k] - tempr;
Chris@2 104 im[id] = im[k] - tempi;
Chris@2 105 re[k] += tempr;
Chris@2 106 im[k] += tempi;
Chris@2 107 }
Chris@2 108 double wtemp = Wjk_r;
Chris@2 109 Wjk_r = Wj_r * Wjk_r - Wj_i * Wjk_i;
Chris@2 110 Wjk_i = Wj_r * Wjk_i + Wj_i * wtemp;
Chris@2 111 }
Chris@2 112 }
Chris@2 113 } // fft()
Chris@2 114
Chris@2 115 /** Computes the power spectrum of a real sequence (in place).
Chris@2 116 * @param re the real input and output data; length must be a power of 2
Chris@2 117 */
Chris@2 118 public static void powerFFT(double[] re) {
Chris@2 119 double[] im = new double[re.length];
Chris@2 120 fft(re, im, FORWARD);
Chris@2 121 for (int i = 0; i < re.length; i++)
Chris@2 122 re[i] = re[i] * re[i] + im[i] * im[i];
Chris@2 123 } // powerFFT()
Chris@2 124
Chris@2 125 /** Converts a real power sequence from to magnitude representation,
Chris@2 126 * by computing the square root of each value.
Chris@2 127 * @param re the real input (power) and output (magnitude) data; length
Chris@2 128 * must be a power of 2
Chris@2 129 */
Chris@2 130 public static void toMagnitude(double[] re) {
Chris@2 131 for (int i = 0; i < re.length; i++)
Chris@2 132 re[i] = Math.sqrt(re[i]);
Chris@2 133 } // toMagnitude()
Chris@2 134
Chris@2 135 /** Computes the magnitude spectrum of a real sequence (in place).
Chris@2 136 * @param re the real input and output data; length must be a power of 2
Chris@2 137 */
Chris@2 138 public static void magnitudeFFT(double[] re) {
Chris@2 139 powerFFT(re);
Chris@2 140 toMagnitude(re);
Chris@2 141 } // magnitudeFFT()
Chris@2 142
Chris@2 143 /** Computes a complex (or real if im[] == {0,...}) FFT and converts
Chris@2 144 * the results to polar coordinates (power and phase). Both arrays
Chris@2 145 * must be the same length, which is a power of 2.
Chris@2 146 * @param re the real part of the input data and the power of the output
Chris@2 147 * data
Chris@2 148 * @param im the imaginary part of the input data and the phase of the
Chris@2 149 * output data
Chris@2 150 */
Chris@2 151 public static void powerPhaseFFT(double[] re, double[] im) {
Chris@2 152 fft(re, im, FORWARD);
Chris@2 153 for (int i = 0; i < re.length; i++) {
Chris@2 154 double pow = re[i] * re[i] + im[i] * im[i];
Chris@2 155 im[i] = Math.atan2(im[i], re[i]);
Chris@2 156 re[i] = pow;
Chris@2 157 }
Chris@2 158 } // powerPhaseFFT()
Chris@2 159
Chris@2 160 /** Inline computation of the inverse FFT given spectral input data
Chris@2 161 * in polar coordinates (power and phase).
Chris@2 162 * Both arrays must be the same length, which is a power of 2.
Chris@2 163 * @param pow the power of the spectral input data (and real part of the
Chris@2 164 * output data)
Chris@2 165 * @param ph the phase of the spectral input data (and the imaginary part
Chris@2 166 * of the output data)
Chris@2 167 */
Chris@2 168 public static void powerPhaseIFFT(double[] pow, double[] ph) {
Chris@2 169 toMagnitude(pow);
Chris@2 170 for (int i = 0; i < pow.length; i++) {
Chris@2 171 double re = pow[i] * Math.cos(ph[i]);
Chris@2 172 ph[i] = pow[i] * Math.sin(ph[i]);
Chris@2 173 pow[i] = re;
Chris@2 174 }
Chris@2 175 fft(pow, ph, REVERSE);
Chris@2 176 } // powerPhaseIFFT()
Chris@2 177
Chris@2 178 /** Computes a complex (or real if im[] == {0,...}) FFT and converts
Chris@2 179 * the results to polar coordinates (magnitude and phase). Both arrays
Chris@2 180 * must be the same length, which is a power of 2.
Chris@2 181 * @param re the real part of the input data and the magnitude of the
Chris@2 182 * output data
Chris@2 183 * @param im the imaginary part of the input data and the phase of the
Chris@2 184 * output data
Chris@2 185 */
Chris@2 186 public static void magnitudePhaseFFT(double[] re, double[] im) {
Chris@2 187 powerPhaseFFT(re, im);
Chris@2 188 toMagnitude(re);
Chris@2 189 } // magnitudePhaseFFT()
Chris@2 190
Chris@2 191
Chris@2 192 /** Fill an array with the values of a standard Hamming window function
Chris@2 193 * @param data the array to be filled
Chris@2 194 * @param size the number of non zero values; if the array is larger than
Chris@2 195 * this, it is zero-padded symmetrically at both ends
Chris@2 196 */
Chris@2 197 static void hamming(double[] data, int size) {
Chris@2 198 int start = (data.length - size) / 2;
Chris@2 199 int stop = (data.length + size) / 2;
Chris@2 200 double scale = 1.0 / (double)size / 0.54;
Chris@2 201 double factor = twoPI / (double)size;
Chris@2 202 for (int i = 0; start < stop; start++, i++)
Chris@2 203 data[i] = scale * (25.0/46.0 - 21.0/46.0 * Math.cos(factor * i));
Chris@2 204 } // hamming()
Chris@2 205
Chris@2 206 /** Fill an array with the values of a minimum 4-sample Blackman-Harris
Chris@2 207 * window function
Chris@2 208 * @param data the array to be filled
Chris@2 209 * @param size the number of non zero values; if the array is larger than
Chris@2 210 * this, it is zero-padded symmetrically at both ends
Chris@2 211 */
Chris@2 212 static void blackmanHarris4sMin(double[] data, int size) {
Chris@2 213 int start = (data.length - size) / 2;
Chris@2 214 int stop = (data.length + size) / 2;
Chris@2 215 double scale = 1.0 / (double)size / 0.36;
Chris@2 216 for (int i = 0; start < stop; start++, i++)
Chris@2 217 data[i] = scale * ( 0.35875 -
Chris@2 218 0.48829 * Math.cos(twoPI * i / size) +
Chris@2 219 0.14128 * Math.cos(2 * twoPI * i / size) -
Chris@2 220 0.01168 * Math.cos(3 * twoPI * i / size));
Chris@2 221 } // blackmanHarris4sMin()
Chris@2 222
Chris@2 223 /** Fill an array with the values of a 74-dB 4-sample Blackman-Harris
Chris@2 224 * window function
Chris@2 225 * @param data the array to be filled
Chris@2 226 * @param size the number of non zero values; if the array is larger than
Chris@2 227 * this, it is zero-padded symmetrically at both ends
Chris@2 228 */
Chris@2 229 static void blackmanHarris4s(double[] data, int size) {
Chris@2 230 int start = (data.length - size) / 2;
Chris@2 231 int stop = (data.length + size) / 2;
Chris@2 232 double scale = 1.0 / (double)size / 0.4;
Chris@2 233 for (int i = 0; start < stop; start++, i++)
Chris@2 234 data[i] = scale * ( 0.40217 -
Chris@2 235 0.49703 * Math.cos(twoPI * i / size) +
Chris@2 236 0.09392 * Math.cos(2 * twoPI * i / size) -
Chris@2 237 0.00183 * Math.cos(3 * twoPI * i / size));
Chris@2 238 } // blackmanHarris4s()
Chris@2 239
Chris@2 240 /** Fill an array with the values of a minimum 3-sample Blackman-Harris
Chris@2 241 * window function
Chris@2 242 * @param data the array to be filled
Chris@2 243 * @param size the number of non zero values; if the array is larger than
Chris@2 244 * this, it is zero-padded symmetrically at both ends
Chris@2 245 */
Chris@2 246 static void blackmanHarris3sMin(double[] data, int size) {
Chris@2 247 int start = (data.length - size) / 2;
Chris@2 248 int stop = (data.length + size) / 2;
Chris@2 249 double scale = 1.0 / (double) size / 0.42;
Chris@2 250 for (int i = 0; start < stop; start++, i++)
Chris@2 251 data[i] = scale * ( 0.42323 -
Chris@2 252 0.49755 * Math.cos(twoPI * i / size) +
Chris@2 253 0.07922 * Math.cos(2 * twoPI * i / size));
Chris@2 254 } // blackmanHarris3sMin()
Chris@2 255
Chris@2 256 /** Fill an array with the values of a 61-dB 3-sample Blackman-Harris
Chris@2 257 * window function
Chris@2 258 * @param data the array to be filled
Chris@2 259 * @param size the number of non zero values; if the array is larger than
Chris@2 260 * this, it is zero-padded symmetrically at both ends
Chris@2 261 */
Chris@2 262 static void blackmanHarris3s(double[] data, int size) {
Chris@2 263 int start = (data.length - size) / 2;
Chris@2 264 int stop = (data.length + size) / 2;
Chris@2 265 double scale = 1.0 / (double) size / 0.45;
Chris@2 266 for (int i = 0; start < stop; start++, i++)
Chris@2 267 data[i] = scale * ( 0.44959 -
Chris@2 268 0.49364 * Math.cos(twoPI * i / size) +
Chris@2 269 0.05677 * Math.cos(2 * twoPI * i / size));
Chris@2 270 } // blackmanHarris3s()
Chris@2 271
Chris@2 272 /** Fill an array with the values of a Gaussian window function
Chris@2 273 * @param data the array to be filled
Chris@2 274 * @param size the number of non zero values; if the array is larger than
Chris@2 275 * this, it is zero-padded symmetrically at both ends
Chris@2 276 */
Chris@2 277 static void gauss(double[] data, int size) { // ?? between 61/3 and 74/4 BHW
Chris@2 278 int start = (data.length - size) / 2;
Chris@2 279 int stop = (data.length + size) / 2;
Chris@2 280 double delta = 5.0 / size;
Chris@2 281 double x = (1 - size) / 2.0 * delta;
Chris@2 282 double c = -Math.PI * Math.exp(1.0) / 10.0;
Chris@2 283 double sum = 0;
Chris@2 284 for (int i = start; i < stop; i++) {
Chris@2 285 data[i] = Math.exp(c * x * x);
Chris@2 286 x += delta;
Chris@2 287 sum += data[i];
Chris@2 288 }
Chris@2 289 for (int i = start; i < stop; i++)
Chris@2 290 data[i] /= sum;
Chris@2 291 } // gauss()
Chris@2 292
Chris@2 293 /** Fill an array with the values of a rectangular window function
Chris@2 294 * @param data the array to be filled
Chris@2 295 * @param size the number of non zero values; if the array is larger than
Chris@2 296 * this, it is zero-padded symmetrically at both ends
Chris@2 297 */
Chris@2 298 static void rectangle(double[] data, int size) {
Chris@2 299 int start = (data.length - size) / 2;
Chris@2 300 int stop = (data.length + size) / 2;
Chris@2 301 for (int i = start; i < stop; i++)
Chris@2 302 data[i] = 1.0 / (double) size;
Chris@2 303 } // rectangle()
Chris@2 304
Chris@2 305 /** Returns an array of values of a normalised smooth window function,
Chris@2 306 * as used for performing a short time Fourier transform (STFT).
Chris@2 307 * All functions are normalised by length and coherent gain.
Chris@2 308 * More information on characteristics of these functions can be found
Chris@2 309 * in F.J. Harris (1978), On the Use of Windows for Harmonic Analysis
Chris@2 310 * with the Discrete Fourier Transform, <em>Proceedings of the IEEE</em>,
Chris@2 311 * 66, 1, 51-83.
Chris@2 312 * @param choice the choice of window function, one of the constants
Chris@2 313 * defined above
Chris@2 314 * @param size the size of the returned array
Chris@2 315 * @param support the number of non-zero values in the array
Chris@2 316 * @return the array containing the values of the window function
Chris@2 317 */
Chris@2 318 public static double[] makeWindow(int choice, int size, int support) {
Chris@2 319 double[] data = new double[size];
Chris@2 320 if (support > size)
Chris@2 321 support = size;
Chris@2 322 switch (choice) {
Chris@2 323 case RECT: rectangle(data, support); break;
Chris@2 324 case HAMMING: hamming(data, support); break;
Chris@2 325 case BH3: blackmanHarris3s(data, support); break;
Chris@2 326 case BH4: blackmanHarris4s(data, support); break;
Chris@2 327 case BH3MIN: blackmanHarris3sMin(data, support); break;
Chris@2 328 case BH4MIN: blackmanHarris4sMin(data, support); break;
Chris@2 329 case GAUSS: gauss(data, support); break;
Chris@2 330 default: rectangle(data, support); break;
Chris@2 331 }
Chris@2 332 return data;
Chris@2 333 } // makeWindow()
Chris@2 334
Chris@2 335 /** Applies a window function to an array of data, storing the result in
Chris@2 336 * the data array.
Chris@2 337 * Performs a dot product of the data and window arrays.
Chris@2 338 * @param data the array of input data, also used for output
Chris@2 339 * @param window the values of the window function to be applied to data
Chris@2 340 */
Chris@2 341 public static void applyWindow(double[] data, double[] window) {
Chris@2 342 for (int i = 0; i < data.length; i++)
Chris@2 343 data[i] *= window[i];
Chris@2 344 } // applyWindow()
Chris@2 345
Chris@2 346 /** Unit test of the FFT class.
Chris@2 347 * Performs a forward and inverse FFT on a 1MB array of random values
Chris@2 348 * and checks how closely the values are preserved.
Chris@2 349 * @param args ignored
Chris@2 350 */
Chris@2 351 public static void main(String[] args) {
Chris@2 352 final int SZ = 1024 * 1024;
Chris@2 353 double[] r1 = new double[SZ];
Chris@2 354 double[] i1 = new double[SZ];
Chris@2 355 double[] r2 = new double[SZ];
Chris@2 356 double[] i2 = new double[SZ];
Chris@2 357 for (int j = 0; j < SZ; j++) {
Chris@2 358 r1[j] = r2[j] = Math.random();
Chris@2 359 i1[j] = i2[j] = Math.random();
Chris@2 360 }
Chris@2 361 System.out.println("start");
Chris@2 362 fft(r2, i2, FORWARD);
Chris@2 363 System.out.println("reverse");
Chris@2 364 fft(r2, i2, REVERSE);
Chris@2 365 System.out.println("result");
Chris@2 366 double err = 0;
Chris@2 367 for (int j = 0; j < SZ; j++)
Chris@2 368 err += Math.abs(r1[j] - r2[j] / SZ) + Math.abs(i1[j] - i2[j] / SZ);
Chris@2 369 System.out.printf( "Err: %12.10f Av: %12.10f\n", err, err / SZ);
Chris@2 370 } // main()
Chris@2 371
Chris@2 372 } // class FFT