Chris@0: /* Chris@0: * Free FFT and convolution (JavaScript) Chris@0: * Chris@0: * Copyright (c) 2014 Project Nayuki Chris@0: * http://www.nayuki.io/page/free-small-fft-in-multiple-languages Chris@0: * Chris@0: * (MIT License) Chris@0: * Permission is hereby granted, free of charge, to any person obtaining a copy of Chris@0: * this software and associated documentation files (the "Software"), to deal in Chris@0: * the Software without restriction, including without limitation the rights to Chris@0: * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of Chris@0: * the Software, and to permit persons to whom the Software is furnished to do so, Chris@0: * subject to the following conditions: Chris@0: * - The above copyright notice and this permission notice shall be included in Chris@0: * all copies or substantial portions of the Software. Chris@0: * - The Software is provided "as is", without warranty of any kind, express or Chris@0: * implied, including but not limited to the warranties of merchantability, Chris@0: * fitness for a particular purpose and noninfringement. In no event shall the Chris@0: * authors or copyright holders be liable for any claim, damages or other Chris@0: * liability, whether in an action of contract, tort or otherwise, arising from, Chris@0: * out of or in connection with the Software or the use or other dealings in the Chris@0: * Software. Chris@0: */ Chris@0: Chris@0: "use strict"; Chris@0: Chris@0: Chris@0: /* Chris@0: * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector. Chris@0: * The vector can have any length. This is a wrapper function. Chris@0: */ Chris@0: function transform(real, imag) { Chris@0: if (real.length != imag.length) Chris@0: throw "Mismatched lengths"; Chris@0: Chris@0: var n = real.length; Chris@0: if (n == 0) Chris@0: return; Chris@0: else if ((n & (n - 1)) == 0) // Is power of 2 Chris@0: transformRadix2(real, imag); Chris@0: else // More complicated algorithm for arbitrary sizes Chris@0: transformBluestein(real, imag); Chris@0: } Chris@0: Chris@0: Chris@0: /* Chris@0: * Computes the inverse discrete Fourier transform (IDFT) of the given complex vector, storing the result back into the vector. Chris@0: * 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: */ Chris@0: function inverseTransform(real, imag) { Chris@0: transform(imag, real); Chris@0: } Chris@0: Chris@0: Chris@0: /* Chris@0: * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector. Chris@0: * The vector's length must be a power of 2. Uses the Cooley-Tukey decimation-in-time radix-2 algorithm. Chris@0: */ Chris@0: function transformRadix2(real, imag) { Chris@0: // Initialization Chris@0: if (real.length != imag.length) Chris@0: throw "Mismatched lengths"; Chris@0: var n = real.length; Chris@0: if (n == 1) // Trivial transform Chris@0: return; Chris@0: var levels = -1; Chris@0: for (var i = 0; i < 32; i++) { Chris@0: if (1 << i == n) Chris@0: levels = i; // Equal to log2(n) Chris@0: } Chris@0: if (levels == -1) Chris@0: throw "Length is not a power of 2"; Chris@0: var cosTable = new Array(n / 2); Chris@0: var sinTable = new Array(n / 2); Chris@0: for (var i = 0; i < n / 2; i++) { Chris@0: cosTable[i] = Math.cos(2 * Math.PI * i / n); Chris@0: sinTable[i] = Math.sin(2 * Math.PI * i / n); Chris@0: } Chris@0: Chris@0: // Bit-reversed addressing permutation Chris@0: for (var i = 0; i < n; i++) { Chris@0: var j = reverseBits(i, levels); Chris@0: if (j > i) { Chris@0: var temp = real[i]; Chris@0: real[i] = real[j]; Chris@0: real[j] = temp; Chris@0: temp = imag[i]; Chris@0: imag[i] = imag[j]; Chris@0: imag[j] = temp; Chris@0: } Chris@0: } Chris@0: Chris@0: // Cooley-Tukey decimation-in-time radix-2 FFT Chris@0: for (var size = 2; size <= n; size *= 2) { Chris@0: var halfsize = size / 2; Chris@0: var tablestep = n / size; Chris@0: for (var i = 0; i < n; i += size) { Chris@0: for (var j = i, k = 0; j < i + halfsize; j++, k += tablestep) { Chris@0: var tpre = real[j+halfsize] * cosTable[k] + imag[j+halfsize] * sinTable[k]; Chris@0: var tpim = -real[j+halfsize] * sinTable[k] + imag[j+halfsize] * cosTable[k]; Chris@0: real[j + halfsize] = real[j] - tpre; Chris@0: imag[j + halfsize] = imag[j] - tpim; Chris@0: real[j] += tpre; Chris@0: imag[j] += tpim; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: // Returns the integer whose value is the reverse of the lowest 'bits' bits of the integer 'x'. Chris@0: function reverseBits(x, bits) { Chris@0: var y = 0; Chris@0: for (var i = 0; i < bits; i++) { Chris@0: y = (y << 1) | (x & 1); Chris@0: x >>>= 1; Chris@0: } Chris@0: return y; Chris@0: } Chris@0: } Chris@0: Chris@0: Chris@0: /* Chris@0: * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector. Chris@0: * The vector can have any length. This requires the convolution function, which in turn requires the radix-2 FFT function. Chris@0: * Uses Bluestein's chirp z-transform algorithm. Chris@0: */ Chris@0: function transformBluestein(real, imag) { Chris@0: // Find a power-of-2 convolution length m such that m >= n * 2 + 1 Chris@0: if (real.length != imag.length) Chris@0: throw "Mismatched lengths"; Chris@0: var n = real.length; Chris@0: var m = 1; Chris@0: while (m < n * 2 + 1) Chris@0: m *= 2; Chris@0: Chris@0: // Trignometric tables Chris@0: var cosTable = new Array(n); Chris@0: var sinTable = new Array(n); Chris@0: for (var i = 0; i < n; i++) { Chris@0: var j = i * i % (n * 2); // This is more accurate than j = i * i Chris@0: cosTable[i] = Math.cos(Math.PI * j / n); Chris@0: sinTable[i] = Math.sin(Math.PI * j / n); Chris@0: } Chris@0: Chris@0: // Temporary vectors and preprocessing Chris@0: var areal = new Array(m); Chris@0: var aimag = new Array(m); Chris@0: for (var i = 0; i < n; i++) { Chris@0: areal[i] = real[i] * cosTable[i] + imag[i] * sinTable[i]; Chris@0: aimag[i] = -real[i] * sinTable[i] + imag[i] * cosTable[i]; Chris@0: } Chris@0: for (var i = n; i < m; i++) Chris@0: areal[i] = aimag[i] = 0; Chris@0: var breal = new Array(m); Chris@0: var bimag = new Array(m); Chris@0: breal[0] = cosTable[0]; Chris@0: bimag[0] = sinTable[0]; Chris@0: for (var i = 1; i < n; i++) { Chris@0: breal[i] = breal[m - i] = cosTable[i]; Chris@0: bimag[i] = bimag[m - i] = sinTable[i]; Chris@0: } Chris@0: for (var i = n; i <= m - n; i++) Chris@0: breal[i] = bimag[i] = 0; Chris@0: Chris@0: // Convolution Chris@0: var creal = new Array(m); Chris@0: var cimag = new Array(m); Chris@0: convolveComplex(areal, aimag, breal, bimag, creal, cimag); Chris@0: Chris@0: // Postprocessing Chris@0: for (var i = 0; i < n; i++) { Chris@0: real[i] = creal[i] * cosTable[i] + cimag[i] * sinTable[i]; Chris@0: imag[i] = -creal[i] * sinTable[i] + cimag[i] * cosTable[i]; Chris@0: } Chris@0: } Chris@0: Chris@0: Chris@0: /* Chris@0: * Computes the circular convolution of the given real vectors. Each vector's length must be the same. Chris@0: */ Chris@0: function convolveReal(x, y, out) { Chris@0: if (x.length != y.length || x.length != out.length) Chris@0: throw "Mismatched lengths"; Chris@0: var zeros = new Array(x.length); Chris@0: for (var i = 0; i < zeros.length; i++) Chris@0: zeros[i] = 0; Chris@0: convolveComplex(x, zeros, y, zeros.slice(0), out, zeros.slice(0)); Chris@0: } Chris@0: Chris@0: Chris@0: /* Chris@0: * Computes the circular convolution of the given complex vectors. Each vector's length must be the same. Chris@0: */ Chris@0: function convolveComplex(xreal, ximag, yreal, yimag, outreal, outimag) { Chris@0: if (xreal.length != ximag.length || xreal.length != yreal.length || yreal.length != yimag.length || xreal.length != outreal.length || outreal.length != outimag.length) Chris@0: throw "Mismatched lengths"; Chris@0: Chris@0: var n = xreal.length; Chris@0: xreal = xreal.slice(0); Chris@0: ximag = ximag.slice(0); Chris@0: yreal = yreal.slice(0); Chris@0: yimag = yimag.slice(0); Chris@0: Chris@0: transform(xreal, ximag); Chris@0: transform(yreal, yimag); Chris@0: for (var i = 0; i < n; i++) { Chris@0: var temp = xreal[i] * yreal[i] - ximag[i] * yimag[i]; Chris@0: ximag[i] = ximag[i] * yreal[i] + xreal[i] * yimag[i]; Chris@0: xreal[i] = temp; Chris@0: } Chris@0: inverseTransform(xreal, ximag); Chris@0: for (var i = 0; i < n; i++) { // Scaling (because this FFT implementation omits it) Chris@0: outreal[i] = xreal[i] / n; Chris@0: outimag[i] = ximag[i] / n; Chris@0: } Chris@0: }