Chris@0
|
1 /*
|
Chris@0
|
2 * Free FFT and convolution (JavaScript)
|
Chris@0
|
3 *
|
Chris@0
|
4 * Copyright (c) 2014 Project Nayuki
|
Chris@0
|
5 * http://www.nayuki.io/page/free-small-fft-in-multiple-languages
|
Chris@0
|
6 *
|
Chris@0
|
7 * (MIT License)
|
Chris@0
|
8 * Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Chris@0
|
9 * this software and associated documentation files (the "Software"), to deal in
|
Chris@0
|
10 * the Software without restriction, including without limitation the rights to
|
Chris@0
|
11 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
Chris@0
|
12 * the Software, and to permit persons to whom the Software is furnished to do so,
|
Chris@0
|
13 * subject to the following conditions:
|
Chris@0
|
14 * - The above copyright notice and this permission notice shall be included in
|
Chris@0
|
15 * all copies or substantial portions of the Software.
|
Chris@0
|
16 * - The Software is provided "as is", without warranty of any kind, express or
|
Chris@0
|
17 * implied, including but not limited to the warranties of merchantability,
|
Chris@0
|
18 * fitness for a particular purpose and noninfringement. In no event shall the
|
Chris@0
|
19 * authors or copyright holders be liable for any claim, damages or other
|
Chris@0
|
20 * liability, whether in an action of contract, tort or otherwise, arising from,
|
Chris@0
|
21 * out of or in connection with the Software or the use or other dealings in the
|
Chris@0
|
22 * Software.
|
Chris@0
|
23 */
|
Chris@0
|
24
|
Chris@0
|
25 "use strict";
|
Chris@0
|
26
|
Chris@0
|
27
|
Chris@0
|
28 /*
|
Chris@0
|
29 * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
|
Chris@0
|
30 * The vector can have any length. This is a wrapper function.
|
Chris@0
|
31 */
|
Chris@0
|
32 function transform(real, imag) {
|
Chris@0
|
33 if (real.length != imag.length)
|
Chris@0
|
34 throw "Mismatched lengths";
|
Chris@0
|
35
|
Chris@0
|
36 var n = real.length;
|
Chris@0
|
37 if (n == 0)
|
Chris@0
|
38 return;
|
Chris@0
|
39 else if ((n & (n - 1)) == 0) // Is power of 2
|
Chris@0
|
40 transformRadix2(real, imag);
|
Chris@0
|
41 else // More complicated algorithm for arbitrary sizes
|
Chris@0
|
42 transformBluestein(real, imag);
|
Chris@0
|
43 }
|
Chris@0
|
44
|
Chris@0
|
45
|
Chris@0
|
46 /*
|
Chris@0
|
47 * Computes the inverse discrete Fourier transform (IDFT) of the given complex vector, storing the result back into the vector.
|
Chris@0
|
48 * The vector can have any length. This is a wrapper function. This transform does not perform scaling, so the inverse is not a true inverse.
|
Chris@0
|
49 */
|
Chris@0
|
50 function inverseTransform(real, imag) {
|
Chris@0
|
51 transform(imag, real);
|
Chris@0
|
52 }
|
Chris@0
|
53
|
Chris@0
|
54
|
Chris@0
|
55 /*
|
Chris@0
|
56 * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
|
Chris@0
|
57 * The vector's length must be a power of 2. Uses the Cooley-Tukey decimation-in-time radix-2 algorithm.
|
Chris@0
|
58 */
|
Chris@0
|
59 function transformRadix2(real, imag) {
|
Chris@0
|
60 // Initialization
|
Chris@0
|
61 if (real.length != imag.length)
|
Chris@0
|
62 throw "Mismatched lengths";
|
Chris@0
|
63 var n = real.length;
|
Chris@0
|
64 if (n == 1) // Trivial transform
|
Chris@0
|
65 return;
|
Chris@0
|
66 var levels = -1;
|
Chris@0
|
67 for (var i = 0; i < 32; i++) {
|
Chris@0
|
68 if (1 << i == n)
|
Chris@0
|
69 levels = i; // Equal to log2(n)
|
Chris@0
|
70 }
|
Chris@0
|
71 if (levels == -1)
|
Chris@0
|
72 throw "Length is not a power of 2";
|
Chris@0
|
73 var cosTable = new Array(n / 2);
|
Chris@0
|
74 var sinTable = new Array(n / 2);
|
Chris@0
|
75 for (var i = 0; i < n / 2; i++) {
|
Chris@0
|
76 cosTable[i] = Math.cos(2 * Math.PI * i / n);
|
Chris@0
|
77 sinTable[i] = Math.sin(2 * Math.PI * i / n);
|
Chris@0
|
78 }
|
Chris@0
|
79
|
Chris@0
|
80 // Bit-reversed addressing permutation
|
Chris@0
|
81 for (var i = 0; i < n; i++) {
|
Chris@0
|
82 var j = reverseBits(i, levels);
|
Chris@0
|
83 if (j > i) {
|
Chris@0
|
84 var temp = real[i];
|
Chris@0
|
85 real[i] = real[j];
|
Chris@0
|
86 real[j] = temp;
|
Chris@0
|
87 temp = imag[i];
|
Chris@0
|
88 imag[i] = imag[j];
|
Chris@0
|
89 imag[j] = temp;
|
Chris@0
|
90 }
|
Chris@0
|
91 }
|
Chris@0
|
92
|
Chris@0
|
93 // Cooley-Tukey decimation-in-time radix-2 FFT
|
Chris@0
|
94 for (var size = 2; size <= n; size *= 2) {
|
Chris@0
|
95 var halfsize = size / 2;
|
Chris@0
|
96 var tablestep = n / size;
|
Chris@0
|
97 for (var i = 0; i < n; i += size) {
|
Chris@0
|
98 for (var j = i, k = 0; j < i + halfsize; j++, k += tablestep) {
|
Chris@0
|
99 var tpre = real[j+halfsize] * cosTable[k] + imag[j+halfsize] * sinTable[k];
|
Chris@0
|
100 var tpim = -real[j+halfsize] * sinTable[k] + imag[j+halfsize] * cosTable[k];
|
Chris@0
|
101 real[j + halfsize] = real[j] - tpre;
|
Chris@0
|
102 imag[j + halfsize] = imag[j] - tpim;
|
Chris@0
|
103 real[j] += tpre;
|
Chris@0
|
104 imag[j] += tpim;
|
Chris@0
|
105 }
|
Chris@0
|
106 }
|
Chris@0
|
107 }
|
Chris@0
|
108
|
Chris@0
|
109 // Returns the integer whose value is the reverse of the lowest 'bits' bits of the integer 'x'.
|
Chris@0
|
110 function reverseBits(x, bits) {
|
Chris@0
|
111 var y = 0;
|
Chris@0
|
112 for (var i = 0; i < bits; i++) {
|
Chris@0
|
113 y = (y << 1) | (x & 1);
|
Chris@0
|
114 x >>>= 1;
|
Chris@0
|
115 }
|
Chris@0
|
116 return y;
|
Chris@0
|
117 }
|
Chris@0
|
118 }
|
Chris@0
|
119
|
Chris@0
|
120
|
Chris@0
|
121 /*
|
Chris@0
|
122 * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
|
Chris@0
|
123 * The vector can have any length. This requires the convolution function, which in turn requires the radix-2 FFT function.
|
Chris@0
|
124 * Uses Bluestein's chirp z-transform algorithm.
|
Chris@0
|
125 */
|
Chris@0
|
126 function transformBluestein(real, imag) {
|
Chris@0
|
127 // Find a power-of-2 convolution length m such that m >= n * 2 + 1
|
Chris@0
|
128 if (real.length != imag.length)
|
Chris@0
|
129 throw "Mismatched lengths";
|
Chris@0
|
130 var n = real.length;
|
Chris@0
|
131 var m = 1;
|
Chris@0
|
132 while (m < n * 2 + 1)
|
Chris@0
|
133 m *= 2;
|
Chris@0
|
134
|
Chris@0
|
135 // Trignometric tables
|
Chris@0
|
136 var cosTable = new Array(n);
|
Chris@0
|
137 var sinTable = new Array(n);
|
Chris@0
|
138 for (var i = 0; i < n; i++) {
|
Chris@0
|
139 var j = i * i % (n * 2); // This is more accurate than j = i * i
|
Chris@0
|
140 cosTable[i] = Math.cos(Math.PI * j / n);
|
Chris@0
|
141 sinTable[i] = Math.sin(Math.PI * j / n);
|
Chris@0
|
142 }
|
Chris@0
|
143
|
Chris@0
|
144 // Temporary vectors and preprocessing
|
Chris@0
|
145 var areal = new Array(m);
|
Chris@0
|
146 var aimag = new Array(m);
|
Chris@0
|
147 for (var i = 0; i < n; i++) {
|
Chris@0
|
148 areal[i] = real[i] * cosTable[i] + imag[i] * sinTable[i];
|
Chris@0
|
149 aimag[i] = -real[i] * sinTable[i] + imag[i] * cosTable[i];
|
Chris@0
|
150 }
|
Chris@0
|
151 for (var i = n; i < m; i++)
|
Chris@0
|
152 areal[i] = aimag[i] = 0;
|
Chris@0
|
153 var breal = new Array(m);
|
Chris@0
|
154 var bimag = new Array(m);
|
Chris@0
|
155 breal[0] = cosTable[0];
|
Chris@0
|
156 bimag[0] = sinTable[0];
|
Chris@0
|
157 for (var i = 1; i < n; i++) {
|
Chris@0
|
158 breal[i] = breal[m - i] = cosTable[i];
|
Chris@0
|
159 bimag[i] = bimag[m - i] = sinTable[i];
|
Chris@0
|
160 }
|
Chris@0
|
161 for (var i = n; i <= m - n; i++)
|
Chris@0
|
162 breal[i] = bimag[i] = 0;
|
Chris@0
|
163
|
Chris@0
|
164 // Convolution
|
Chris@0
|
165 var creal = new Array(m);
|
Chris@0
|
166 var cimag = new Array(m);
|
Chris@0
|
167 convolveComplex(areal, aimag, breal, bimag, creal, cimag);
|
Chris@0
|
168
|
Chris@0
|
169 // Postprocessing
|
Chris@0
|
170 for (var i = 0; i < n; i++) {
|
Chris@0
|
171 real[i] = creal[i] * cosTable[i] + cimag[i] * sinTable[i];
|
Chris@0
|
172 imag[i] = -creal[i] * sinTable[i] + cimag[i] * cosTable[i];
|
Chris@0
|
173 }
|
Chris@0
|
174 }
|
Chris@0
|
175
|
Chris@0
|
176
|
Chris@0
|
177 /*
|
Chris@0
|
178 * Computes the circular convolution of the given real vectors. Each vector's length must be the same.
|
Chris@0
|
179 */
|
Chris@0
|
180 function convolveReal(x, y, out) {
|
Chris@0
|
181 if (x.length != y.length || x.length != out.length)
|
Chris@0
|
182 throw "Mismatched lengths";
|
Chris@0
|
183 var zeros = new Array(x.length);
|
Chris@0
|
184 for (var i = 0; i < zeros.length; i++)
|
Chris@0
|
185 zeros[i] = 0;
|
Chris@0
|
186 convolveComplex(x, zeros, y, zeros.slice(0), out, zeros.slice(0));
|
Chris@0
|
187 }
|
Chris@0
|
188
|
Chris@0
|
189
|
Chris@0
|
190 /*
|
Chris@0
|
191 * Computes the circular convolution of the given complex vectors. Each vector's length must be the same.
|
Chris@0
|
192 */
|
Chris@0
|
193 function convolveComplex(xreal, ximag, yreal, yimag, outreal, outimag) {
|
Chris@0
|
194 if (xreal.length != ximag.length || xreal.length != yreal.length || yreal.length != yimag.length || xreal.length != outreal.length || outreal.length != outimag.length)
|
Chris@0
|
195 throw "Mismatched lengths";
|
Chris@0
|
196
|
Chris@0
|
197 var n = xreal.length;
|
Chris@0
|
198 xreal = xreal.slice(0);
|
Chris@0
|
199 ximag = ximag.slice(0);
|
Chris@0
|
200 yreal = yreal.slice(0);
|
Chris@0
|
201 yimag = yimag.slice(0);
|
Chris@0
|
202
|
Chris@0
|
203 transform(xreal, ximag);
|
Chris@0
|
204 transform(yreal, yimag);
|
Chris@0
|
205 for (var i = 0; i < n; i++) {
|
Chris@0
|
206 var temp = xreal[i] * yreal[i] - ximag[i] * yimag[i];
|
Chris@0
|
207 ximag[i] = ximag[i] * yreal[i] + xreal[i] * yimag[i];
|
Chris@0
|
208 xreal[i] = temp;
|
Chris@0
|
209 }
|
Chris@0
|
210 inverseTransform(xreal, ximag);
|
Chris@0
|
211 for (var i = 0; i < n; i++) { // Scaling (because this FFT implementation omits it)
|
Chris@0
|
212 outreal[i] = xreal[i] / n;
|
Chris@0
|
213 outimag[i] = ximag[i] / n;
|
Chris@0
|
214 }
|
Chris@0
|
215 }
|