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
|