changeset 25:66f9fd5ac611

Bring in some more of the third-party code
author Chris Cannam
date Wed, 07 Oct 2015 13:46:38 +0100
parents e705de983b67
children 9af60df83c6f
files .hgsub .hgsubstate fft/fft.js/LICENSE fft/fft.js/Makefile fft/fft.js/README.md fft/fft.js/lib/complex.js fft/fft.js/lib/node.erb.js fft/fft.js/lib/node.js fft/fft.js/lib/real.js fft/fft.js/package.json fft/fft.js/src/complex.erb.js fft/fft.js/src/complex.rb fft/fft.js/src/node.erb.js fft/fft.js/src/real.erb.js fft/fft.js/src/real.rb fft/fft.js/test.html fft/index.html fft/jsfft/LICENSE fft/jsfft/README.md fft/jsfft/example/example.css fft/jsfft/example/index.html fft/jsfft/lib/complex_array.js fft/jsfft/lib/fft.js fft/jsfft/lib/fft_image.js fft/jsfft/package.json fft/jsfft/test/test-complex_array.js fft/jsfft/test/test-fft.js fft/jsfft/test/test-fft_image.js fft/jsfft/test/test_helper.js fft/test.html
diffstat 30 files changed, 3501 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsub	Wed Oct 07 08:57:11 2015 +0100
+++ b/.hgsub	Wed Oct 07 13:46:38 2015 +0100
@@ -1,4 +0,0 @@
-fft-test/fft.js = [git]https://github.com/JensNockert/fft.js
-fft-test/timbre.js = [git]https://github.com/mohayonao/timbre.js
-fft-test/jsfft = [git]https://github.com/dntj/jsfft
-fft-test/dsp.js = [git]https://github.com/corbanbrook/dsp.js
--- a/.hgsubstate	Wed Oct 07 08:57:11 2015 +0100
+++ b/.hgsubstate	Wed Oct 07 13:46:38 2015 +0100
@@ -1,4 +0,0 @@
-a7b2e97b1385a43083e50ed6dc81d697f0e57e28 fft-test/dsp.js
-dd96e6bec5464fe6b30c91ddbfdef88ea9d70bd1 fft-test/fft.js
-b82257673c97f5f4bfc12fe445dcf1cf2169b46e fft-test/jsfft
-7d94de567ea1d758599b1e29a11afff304d44c55 fft-test/timbre.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/LICENSE	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,20 @@
+/* Copyright (c) 2012, Jens Nockert <jens@ofmlabs.org>, Jussi Kalliokoski <jussi@ofmlabs.org>
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met: 
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/Makefile	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,18 @@
+TARGETS := lib/complex.js lib/node.erb.js lib/node.js
+
+all: $(TARGETS)
+node: lib/node.js
+
+lib/complex.js: src/complex.erb.js
+	erb $^ > $@
+
+lib/node.erb.js: src/node.erb.js
+	erb $^ > $@
+
+lib/node.js: lib/node.erb.js
+	erb $^ > $@
+
+clean:
+	rm -rf $(TARGETS)
+
+.PHONY: all clean
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/README.md	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,41 @@
+fft.js
+================================================================================
+
+FFT in JavaScript, it works, I think.
+
+No promises, but I tested it against Wolfram Alpha once, and it was reasonably accurate.
+
+There are optimized kernels for prime factors, 2, 3, 4, so if you want high performance, use lengths that are a factor of those.
+
+Notice that the DFT is not normalized, so `ifft(fft(x)) / n ~= x`
+
+
+Usage
+---------------------------------------------------------------------------------
+
+```javascript
+
+/* Create a new FFT object */
+
+var fft = new FFT.complex(n, inverse)
+
+/* Output and input should be float arrays (of the right length), type is either 'complex' (default) or 'real' */
+fft.process(output, outputOffset, outputStride, input, inputOffset, inputStride, type)
+
+/* Or the simplified interface, which just sets the offsets to 0, and the strides to 1 */
+fft.simple(output, input, type)
+
+```
+
+
+Installing via npm
+---------------------------------------------------------------------------------
+
+You can also install via npm, the name is `fft` in the registy.
+
+
+Credits
+---------------------------------------------------------------------------------
+
+I was too lazy to calculate the butterflies myself, so they are inspired by [kissfft](http://sourceforge.net/projects/kissfft/), which is a small library for doing discrete fourier transforms.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/lib/complex.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,292 @@
+/* Copyright (c) 2012, Jens Nockert <jens@ofmlabs.org>, Jussi Kalliokoski <jussi@ofmlabs.org>
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met: 
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+if (!FFT) {
+	var FFT = {}
+}
+
+void function (namespace) {
+	"use strict"
+	
+	function butterfly2(output, outputOffset, outputStride, fStride, state, m) {
+		var t = state.twiddle
+		
+		for (var i = 0; i < m; i++) {
+			var s0_r = output[2 * ((outputOffset) + (outputStride) * (i))], s0_i = output[2 * ((outputOffset) + (outputStride) * (i)) + 1]
+			var s1_r = output[2 * ((outputOffset) + (outputStride) * (i + m))], s1_i = output[2 * ((outputOffset) + (outputStride) * (i + m)) + 1]
+			
+			var t1_r = t[2 * ((0) + (fStride) * (i))], t1_i = t[2 * ((0) + (fStride) * (i)) + 1]
+			
+			var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r
+			
+			var r0_r = s0_r + v1_r, r0_i = s0_i + v1_i
+			var r1_r = s0_r - v1_r, r1_i = s0_i - v1_i
+			
+			output[2 * ((outputOffset) + (outputStride) * (i))] = r0_r, output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = r0_i
+			output[2 * ((outputOffset) + (outputStride) * (i + m))] = r1_r, output[2 * ((outputOffset) + (outputStride) * (i + m)) + 1] = r1_i
+		}
+	}
+	
+	function butterfly3(output, outputOffset, outputStride, fStride, state, m) {
+		var t = state.twiddle
+		var m1 = m, m2 = 2 * m
+		var fStride1 = fStride, fStride2 = 2 * fStride
+		
+		var e = t[2 * ((0) + (fStride) * (m)) + 1]
+		
+		for (var i = 0; i < m; i++) {
+			var s0_r = output[2 * ((outputOffset) + (outputStride) * (i))], s0_i = output[2 * ((outputOffset) + (outputStride) * (i)) + 1]
+			
+			var s1_r = output[2 * ((outputOffset) + (outputStride) * (i + m1))], s1_i = output[2 * ((outputOffset) + (outputStride) * (i + m1)) + 1]
+			var t1_r = t[2 * ((0) + (fStride1) * (i))], t1_i = t[2 * ((0) + (fStride1) * (i)) + 1]
+			var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r
+			
+			var s2_r = output[2 * ((outputOffset) + (outputStride) * (i + m2))], s2_i = output[2 * ((outputOffset) + (outputStride) * (i + m2)) + 1]
+			var t2_r = t[2 * ((0) + (fStride2) * (i))], t2_i = t[2 * ((0) + (fStride2) * (i)) + 1]
+			var v2_r = s2_r * t2_r - s2_i * t2_i, v2_i = s2_r * t2_i + s2_i * t2_r
+			
+			var i0_r = v1_r + v2_r, i0_i = v1_i + v2_i
+			
+			var r0_r = s0_r + i0_r, r0_i = s0_i + i0_i
+			output[2 * ((outputOffset) + (outputStride) * (i))] = r0_r, output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = r0_i
+			
+			var i1_r = s0_r - i0_r * 0.5
+			var i1_i = s0_i - i0_i * 0.5
+			
+			var i2_r = (v1_r - v2_r) * e
+			var i2_i = (v1_i - v2_i) * e
+			
+			var r1_r = i1_r - i2_i
+			var r1_i = i1_i + i2_r
+			output[2 * ((outputOffset) + (outputStride) * (i + m1))] = r1_r, output[2 * ((outputOffset) + (outputStride) * (i + m1)) + 1] = r1_i
+			
+			var r2_r = i1_r + i2_i
+			var r2_i = i1_i - i2_r
+			output[2 * ((outputOffset) + (outputStride) * (i + m2))] = r2_r, output[2 * ((outputOffset) + (outputStride) * (i + m2)) + 1] = r2_i
+		}
+	}
+	
+	function butterfly4(output, outputOffset, outputStride, fStride, state, m) {
+		var t = state.twiddle
+		var m1 = m, m2 = 2 * m, m3 = 3 * m
+		var fStride1 = fStride, fStride2 = 2 * fStride, fStride3 = 3 * fStride
+		
+		for (var i = 0; i < m; i++) {
+			var s0_r = output[2 * ((outputOffset) + (outputStride) * (i))], s0_i = output[2 * ((outputOffset) + (outputStride) * (i)) + 1]
+			
+			var s1_r = output[2 * ((outputOffset) + (outputStride) * (i + m1))], s1_i = output[2 * ((outputOffset) + (outputStride) * (i + m1)) + 1]
+			var t1_r = t[2 * ((0) + (fStride1) * (i))], t1_i = t[2 * ((0) + (fStride1) * (i)) + 1]
+			var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r
+			
+			var s2_r = output[2 * ((outputOffset) + (outputStride) * (i + m2))], s2_i = output[2 * ((outputOffset) + (outputStride) * (i + m2)) + 1]
+			var t2_r = t[2 * ((0) + (fStride2) * (i))], t2_i = t[2 * ((0) + (fStride2) * (i)) + 1]
+			var v2_r = s2_r * t2_r - s2_i * t2_i, v2_i = s2_r * t2_i + s2_i * t2_r
+			
+			var s3_r = output[2 * ((outputOffset) + (outputStride) * (i + m3))], s3_i = output[2 * ((outputOffset) + (outputStride) * (i + m3)) + 1]
+			var t3_r = t[2 * ((0) + (fStride3) * (i))], t3_i = t[2 * ((0) + (fStride3) * (i)) + 1]
+			var v3_r = s3_r * t3_r - s3_i * t3_i, v3_i = s3_r * t3_i + s3_i * t3_r
+			
+			var i0_r = s0_r + v2_r, i0_i = s0_i + v2_i
+			var i1_r = s0_r - v2_r, i1_i = s0_i - v2_i
+			var i2_r = v1_r + v3_r, i2_i = v1_i + v3_i
+			var i3_r = v1_r - v3_r, i3_i = v1_i - v3_i
+			
+			var r0_r = i0_r + i2_r, r0_i = i0_i + i2_i
+			
+			if (state.inverse) {
+				var r1_r = i1_r - i3_i
+				var r1_i = i1_i + i3_r
+			} else {
+				var r1_r = i1_r + i3_i
+				var r1_i = i1_i - i3_r
+			}
+			
+			var r2_r = i0_r - i2_r, r2_i = i0_i - i2_i
+			
+			if (state.inverse) {
+				var r3_r = i1_r + i3_i
+				var r3_i = i1_i - i3_r
+			} else {
+				var r3_r = i1_r - i3_i
+				var r3_i = i1_i + i3_r
+			}
+			
+			output[2 * ((outputOffset) + (outputStride) * (i))] = r0_r, output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = r0_i
+			output[2 * ((outputOffset) + (outputStride) * (i + m1))] = r1_r, output[2 * ((outputOffset) + (outputStride) * (i + m1)) + 1] = r1_i
+			output[2 * ((outputOffset) + (outputStride) * (i + m2))] = r2_r, output[2 * ((outputOffset) + (outputStride) * (i + m2)) + 1] = r2_i
+			output[2 * ((outputOffset) + (outputStride) * (i + m3))] = r3_r, output[2 * ((outputOffset) + (outputStride) * (i + m3)) + 1] = r3_i
+		}
+	}
+	
+	function butterfly(output, outputOffset, outputStride, fStride, state, m, p) {
+		var t = state.twiddle, n = state.n, scratch = new Float64Array(2 * p)
+		
+		for (var u = 0; u < m; u++) {
+			for (var q1 = 0, k = u; q1 < p; q1++, k += m) {
+				var x0_r = output[2 * ((outputOffset) + (outputStride) * (k))], x0_i = output[2 * ((outputOffset) + (outputStride) * (k)) + 1]
+				scratch[2 * (q1)] = x0_r, scratch[2 * (q1) + 1] = x0_i
+			}
+			
+			for (var q1 = 0, k = u; q1 < p; q1++, k += m) {
+				var tOffset = 0
+				
+				var x0_r = scratch[2 * (0)], x0_i = scratch[2 * (0) + 1]
+				output[2 * ((outputOffset) + (outputStride) * (k))] = x0_r, output[2 * ((outputOffset) + (outputStride) * (k)) + 1] = x0_i
+				
+				for (var q = 1; q < p; q++) {
+					tOffset = (tOffset + fStride * k) % n
+					
+					var s0_r = output[2 * ((outputOffset) + (outputStride) * (k))], s0_i = output[2 * ((outputOffset) + (outputStride) * (k)) + 1]
+					
+					var s1_r = scratch[2 * (q)], s1_i = scratch[2 * (q) + 1]
+					var t1_r = t[2 * (tOffset)], t1_i = t[2 * (tOffset) + 1]
+					var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r
+					
+					var r0_r = s0_r + v1_r, r0_i = s0_i + v1_i
+					output[2 * ((outputOffset) + (outputStride) * (k))] = r0_r, output[2 * ((outputOffset) + (outputStride) * (k)) + 1] = r0_i
+				}
+			}
+		}
+	}
+	
+	function work(output, outputOffset, outputStride, f, fOffset, fStride, inputStride, factors, state) {
+		var p = factors.shift()
+		var m = factors.shift()
+		
+		if (m == 1) {
+			for (var i = 0; i < p * m; i++) {
+				var x0_r = f[2 * ((fOffset) + (fStride * inputStride) * (i))], x0_i = f[2 * ((fOffset) + (fStride * inputStride) * (i)) + 1]
+				output[2 * ((outputOffset) + (outputStride) * (i))] = x0_r, output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = x0_i
+			}
+		} else {
+			for (var i = 0; i < p; i++) {
+				work(output, outputOffset + outputStride * i * m, outputStride, f, fOffset + i * fStride * inputStride, fStride * p, inputStride, factors.slice(), state)
+			}
+		}
+		
+		switch (p) {
+			case 2: butterfly2(output, outputOffset, outputStride, fStride, state, m); break
+			case 3: butterfly3(output, outputOffset, outputStride, fStride, state, m); break
+			case 4: butterfly4(output, outputOffset, outputStride, fStride, state, m); break
+			default: butterfly(output, outputOffset, outputStride, fStride, state, m, p); break
+		}
+	}
+	
+	var complex = function (n, inverse) {
+		if (arguments.length < 2) {
+			throw new RangeError("You didn't pass enough arguments, passed `" + arguments.length + "'")
+		}
+		
+		var n = ~~n, inverse = !!inverse
+		
+		if (n < 1) {
+			throw new RangeError("n is outside range, should be positive integer, was `" + n + "'")
+		}
+		
+		var state = {
+			n: n,
+			inverse: inverse,
+			
+			factors: [],
+			twiddle: new Float64Array(2 * n),
+			scratch: new Float64Array(2 * n)
+		}
+		
+		var t = state.twiddle, theta = 2 * Math.PI / n
+		
+		for (var i = 0; i < n; i++) {
+			if (inverse) {
+				var phase =  theta * i
+			} else {
+				var phase = -theta * i
+			}
+			
+			t[2 * (i)] = Math.cos(phase)
+			t[2 * (i) + 1] = Math.sin(phase)
+		}
+		
+		var p = 4, v = Math.floor(Math.sqrt(n))
+		
+		while (n > 1) {
+			while (n % p) {
+				switch (p) {
+					case 4: p = 2; break
+					case 2: p = 3; break
+					default: p += 2; break
+				}
+				
+				if (p > v) {
+					p = n
+				}
+			}
+			
+			n /= p
+			
+			state.factors.push(p)
+			state.factors.push(n)
+		}
+		
+		this.state = state
+	}
+	
+	complex.prototype.simple = function (output, input, t) {
+		this.process(output, 0, 1, input, 0, 1, t)
+	}
+	
+	complex.prototype.process = function(output, outputOffset, outputStride, input, inputOffset, inputStride, t) {
+		var outputStride = ~~outputStride, inputStride = ~~inputStride
+		
+		var type = t == 'real' ? t : 'complex'
+		
+		if (outputStride < 1) {
+			throw new RangeError("outputStride is outside range, should be positive integer, was `" + outputStride + "'")
+		}
+		
+		if (inputStride < 1) {
+			throw new RangeError("inputStride is outside range, should be positive integer, was `" + inputStride + "'")
+		}
+		
+		if (type == 'real') {
+			for (var i = 0; i < this.state.n; i++) {
+				var x0_r = input[inputOffset + inputStride * i]
+				var x0_i = 0.0
+				
+				this.state.scratch[2 * (i)] = x0_r, this.state.scratch[2 * (i) + 1] = x0_i
+			}
+			
+			work(output, outputOffset, outputStride, this.state.scratch, 0, 1, 1, this.state.factors.slice(), this.state)
+		} else {
+			if (input == output) {
+				work(this.state.scratch, 0, 1, input, inputOffset, 1, inputStride, this.state.factors.slice(), this.state)
+				
+				for (var i = 0; i < this.state.n; i++) {
+					var x0_r = this.state.scratch[2 * (i)], x0_i = this.state.scratch[2 * (i) + 1]
+					
+					output[2 * ((outputOffset) + (outputStride) * (i))] = x0_r, output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = x0_i
+				}
+			} else {
+				work(output, outputOffset, outputStride, input, inputOffset, 1, inputStride, this.state.factors.slice(), this.state)
+			}
+		}
+	}
+	
+	namespace.complex = complex
+}(FFT)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/lib/node.erb.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,275 @@
+<%= $:.unshift('.'); require "#{File.dirname(__FILE__)}/../src/complex.rb"; File.read "#{File.dirname(__FILE__)}/../LICENSE" %>
+
+if (!FFT) {
+	var FFT = {}
+}
+
+void function (namespace) {
+	"use strict"
+	
+	function butterfly2(output, outputOffset, outputStride, fStride, state, m) {
+		var t = state.twiddle
+		
+		for (var i = 0; i < m; i++) {
+			<%= load('s0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			<%= load('s1', 'output', 'outputOffset', 'i + m', 'outputStride') %>
+			
+			<%= load('t1', 't', 0, 'i', 'fStride') %>
+			
+			<%= cmul('v1', 's1', 't1') %>
+			
+			<%= cadd('r0', 's0', 'v1') %>
+			<%= csub('r1', 's0', 'v1') %>
+			
+			<%= store('r0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			<%= store('r1', 'output', 'outputOffset', 'i + m', 'outputStride') %>
+		}
+	}
+	
+	function butterfly3(output, outputOffset, outputStride, fStride, state, m) {
+		var t = state.twiddle
+		var m1 = m, m2 = 2 * m
+		var fStride1 = fStride, fStride2 = 2 * fStride
+		
+		var e = <%= imag('t', 0, 'm', 'fStride') %>
+		
+		for (var i = 0; i < m; i++) {
+			<%= load('s0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			
+			<%= load('s1', 'output', 'outputOffset', 'i + m1', 'outputStride') %>
+			<%= load('t1', 't', 0, 'i', 'fStride1') %>
+			<%= cmul('v1', 's1', 't1') %>
+			
+			<%= load('s2', 'output', 'outputOffset', 'i + m2', 'outputStride') %>
+			<%= load('t2', 't', 0, 'i', 'fStride2') %>
+			<%= cmul('v2', 's2', 't2') %>
+			
+			<%= cadd('i0', 'v1', 'v2') %>
+			
+			<%= cadd('r0', 's0', 'i0') %>
+			<%= store('r0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			
+			var i1_r = s0_r - i0_r * 0.5
+			var i1_i = s0_i - i0_i * 0.5
+			
+			var i2_r = (v1_r - v2_r) * e
+			var i2_i = (v1_i - v2_i) * e
+			
+			var r1_r = i1_r - i2_i
+			var r1_i = i1_i + i2_r
+			<%= store('r1', 'output', 'outputOffset', 'i + m1', 'outputStride') %>
+			
+			var r2_r = i1_r + i2_i
+			var r2_i = i1_i - i2_r
+			<%= store('r2', 'output', 'outputOffset', 'i + m2', 'outputStride') %>
+		}
+	}
+	
+	function butterfly4(output, outputOffset, outputStride, fStride, state, m) {
+		var t = state.twiddle
+		var m1 = m, m2 = 2 * m, m3 = 3 * m
+		var fStride1 = fStride, fStride2 = 2 * fStride, fStride3 = 3 * fStride
+		
+		for (var i = 0; i < m; i++) {
+			<%= load('s0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			
+			<%= load('s1', 'output', 'outputOffset', 'i + m1', 'outputStride') %>
+			<%= load('t1', 't', 0, 'i', 'fStride1') %>
+			<%= cmul('v1', 's1', 't1') %>
+			
+			<%= load('s2', 'output', 'outputOffset', 'i + m2', 'outputStride') %>
+			<%= load('t2', 't', 0, 'i', 'fStride2') %>
+			<%= cmul('v2', 's2', 't2') %>
+			
+			<%= load('s3', 'output', 'outputOffset', 'i + m3', 'outputStride') %>
+			<%= load('t3', 't', 0, 'i', 'fStride3') %>
+			<%= cmul('v3', 's3', 't3') %>
+			
+			<%= cadd('i0', 's0', 'v2') %>
+			<%= csub('i1', 's0', 'v2') %>
+			<%= cadd('i2', 'v1', 'v3') %>
+			<%= csub('i3', 'v1', 'v3') %>
+			
+			<%= cadd('r0', 'i0', 'i2') %>
+			
+			if (state.inverse) {
+				var r1_r = i1_r - i3_i
+				var r1_i = i1_i + i3_r
+			} else {
+				var r1_r = i1_r + i3_i
+				var r1_i = i1_i - i3_r
+			}
+			
+			<%= csub('r2', 'i0', 'i2') %>
+			
+			if (state.inverse) {
+				var r3_r = i1_r + i3_i
+				var r3_i = i1_i - i3_r
+			} else {
+				var r3_r = i1_r - i3_i
+				var r3_i = i1_i + i3_r
+			}
+			
+			<%= store('r0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			<%= store('r1', 'output', 'outputOffset', 'i + m1', 'outputStride') %>
+			<%= store('r2', 'output', 'outputOffset', 'i + m2', 'outputStride') %>
+			<%= store('r3', 'output', 'outputOffset', 'i + m3', 'outputStride') %>
+		}
+	}
+	
+	function butterfly(output, outputOffset, outputStride, fStride, state, m, p) {
+		var t = state.twiddle, n = state.n, scratch = new Float64Array(2 * p)
+		
+		for (var u = 0; u < m; u++) {
+			for (var q1 = 0, k = u; q1 < p; q1++, k += m) {
+				<%= load('x0', 'output', 'outputOffset', 'k', 'outputStride') %>
+				<%= store('x0', 'scratch', 'q1') %>
+			}
+			
+			for (var q1 = 0, k = u; q1 < p; q1++, k += m) {
+				var tOffset = 0
+				
+				<%= load('x0', 'scratch', 0) %>
+				<%= store('x0', 'output', 'outputOffset', 'k', 'outputStride') %>
+				
+				for (var q = 1; q < p; q++) {
+					tOffset = (tOffset + fStride * k) % n
+					
+					<%= load('s0', 'output', 'outputOffset', 'k', 'outputStride') %>
+					
+					<%= load('s1', 'scratch', 'q') %>
+					<%= load('t1', 't', 'tOffset') %>
+					<%= cmul('v1', 's1', 't1') %>
+					
+					<%= cadd('r0', 's0', 'v1') %>
+					<%= store('r0', 'output', 'outputOffset', 'k', 'outputStride') %>
+				}
+			}
+		}
+	}
+	
+	function work(output, outputOffset, outputStride, f, fOffset, fStride, inputStride, factors, state) {
+		var p = factors.shift()
+		var m = factors.shift()
+		
+		if (m == 1) {
+			for (var i = 0; i < p * m; i++) {
+				<%= load('x0', 'f', 'fOffset', 'i', 'fStride * inputStride') %>
+				<%= store('x0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			}
+		} else {
+			for (var i = 0; i < p; i++) {
+				work(output, outputOffset + outputStride * i * m, outputStride, f, fOffset + i * fStride * inputStride, fStride * p, inputStride, factors.slice(), state)
+			}
+		}
+		
+		switch (p) {
+			case 2: butterfly2(output, outputOffset, outputStride, fStride, state, m); break
+			case 3: butterfly3(output, outputOffset, outputStride, fStride, state, m); break
+			case 4: butterfly4(output, outputOffset, outputStride, fStride, state, m); break
+			default: butterfly(output, outputOffset, outputStride, fStride, state, m, p); break
+		}
+	}
+	
+	var complex = function (n, inverse) {
+		if (arguments.length < 2) {
+			throw new RangeError("You didn't pass enough arguments, passed `" + arguments.length + "'")
+		}
+		
+		var n = ~~n, inverse = !!inverse
+		
+		if (n < 1) {
+			throw new RangeError("n is outside range, should be positive integer, was `" + n + "'")
+		}
+		
+		var state = {
+			n: n,
+			inverse: inverse,
+			
+			factors: [],
+			twiddle: new Float64Array(2 * n),
+			scratch: new Float64Array(2 * n)
+		}
+		
+		var t = state.twiddle, theta = 2 * Math.PI / n
+		
+		for (var i = 0; i < n; i++) {
+			if (inverse) {
+				var phase =  theta * i
+			} else {
+				var phase = -theta * i
+			}
+			
+			<%= real('t', 'i') %> = Math.cos(phase)
+			<%= imag('t', 'i') %> = Math.sin(phase)
+		}
+		
+		var p = 4, v = Math.floor(Math.sqrt(n))
+		
+		while (n > 1) {
+			while (n % p) {
+				switch (p) {
+					case 4: p = 2; break
+					case 2: p = 3; break
+					default: p += 2; break
+				}
+				
+				if (p > v) {
+					p = n
+				}
+			}
+			
+			n /= p
+			
+			state.factors.push(p)
+			state.factors.push(n)
+		}
+		
+		this.state = state
+	}
+	
+	complex.prototype.simple = function (output, input, t) {
+		this.process(output, 0, 1, input, 0, 1, t)
+	}
+	
+	complex.prototype.process = function(output, outputOffset, outputStride, input, inputOffset, inputStride, t) {
+		var outputStride = ~~outputStride, inputStride = ~~inputStride
+		
+		var type = t == 'real' ? t : 'complex'
+		
+		if (outputStride < 1) {
+			throw new RangeError("outputStride is outside range, should be positive integer, was `" + outputStride + "'")
+		}
+		
+		if (inputStride < 1) {
+			throw new RangeError("inputStride is outside range, should be positive integer, was `" + inputStride + "'")
+		}
+		
+		if (type == 'real') {
+			for (var i = 0; i < this.state.n; i++) {
+				var x0_r = input[inputOffset + inputStride * i]
+				var x0_i = 0.0
+				
+				<%= store('x0', 'this.state.scratch', 'i') %>
+			}
+			
+			work(output, outputOffset, outputStride, this.state.scratch, 0, 1, 1, this.state.factors.slice(), this.state)
+		} else {
+			if (input == output) {
+				work(this.state.scratch, 0, 1, input, inputOffset, 1, inputStride, this.state.factors.slice(), this.state)
+				
+				for (var i = 0; i < this.state.n; i++) {
+					<%= load('x0', 'this.state.scratch', 'i') %>
+					
+					<%= store('x0', 'output', 'outputOffset', 'i', 'outputStride') %>
+				}
+			} else {
+				work(output, outputOffset, outputStride, input, inputOffset, 1, inputStride, this.state.factors.slice(), this.state)
+			}
+		}
+	}
+	
+	namespace.complex = complex
+}(FFT)
+
+module.exports = FFT
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/lib/node.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,294 @@
+/* Copyright (c) 2012, Jens Nockert <jens@ofmlabs.org>, Jussi Kalliokoski <jussi@ofmlabs.org>
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met: 
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+if (!FFT) {
+	var FFT = {}
+}
+
+void function (namespace) {
+	"use strict"
+	
+	function butterfly2(output, outputOffset, outputStride, fStride, state, m) {
+		var t = state.twiddle
+		
+		for (var i = 0; i < m; i++) {
+			var s0_r = output[2 * ((outputOffset) + (outputStride) * (i))], s0_i = output[2 * ((outputOffset) + (outputStride) * (i)) + 1]
+			var s1_r = output[2 * ((outputOffset) + (outputStride) * (i + m))], s1_i = output[2 * ((outputOffset) + (outputStride) * (i + m)) + 1]
+			
+			var t1_r = t[2 * ((0) + (fStride) * (i))], t1_i = t[2 * ((0) + (fStride) * (i)) + 1]
+			
+			var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r
+			
+			var r0_r = s0_r + v1_r, r0_i = s0_i + v1_i
+			var r1_r = s0_r - v1_r, r1_i = s0_i - v1_i
+			
+			output[2 * ((outputOffset) + (outputStride) * (i))] = r0_r, output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = r0_i
+			output[2 * ((outputOffset) + (outputStride) * (i + m))] = r1_r, output[2 * ((outputOffset) + (outputStride) * (i + m)) + 1] = r1_i
+		}
+	}
+	
+	function butterfly3(output, outputOffset, outputStride, fStride, state, m) {
+		var t = state.twiddle
+		var m1 = m, m2 = 2 * m
+		var fStride1 = fStride, fStride2 = 2 * fStride
+		
+		var e = t[2 * ((0) + (fStride) * (m)) + 1]
+		
+		for (var i = 0; i < m; i++) {
+			var s0_r = output[2 * ((outputOffset) + (outputStride) * (i))], s0_i = output[2 * ((outputOffset) + (outputStride) * (i)) + 1]
+			
+			var s1_r = output[2 * ((outputOffset) + (outputStride) * (i + m1))], s1_i = output[2 * ((outputOffset) + (outputStride) * (i + m1)) + 1]
+			var t1_r = t[2 * ((0) + (fStride1) * (i))], t1_i = t[2 * ((0) + (fStride1) * (i)) + 1]
+			var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r
+			
+			var s2_r = output[2 * ((outputOffset) + (outputStride) * (i + m2))], s2_i = output[2 * ((outputOffset) + (outputStride) * (i + m2)) + 1]
+			var t2_r = t[2 * ((0) + (fStride2) * (i))], t2_i = t[2 * ((0) + (fStride2) * (i)) + 1]
+			var v2_r = s2_r * t2_r - s2_i * t2_i, v2_i = s2_r * t2_i + s2_i * t2_r
+			
+			var i0_r = v1_r + v2_r, i0_i = v1_i + v2_i
+			
+			var r0_r = s0_r + i0_r, r0_i = s0_i + i0_i
+			output[2 * ((outputOffset) + (outputStride) * (i))] = r0_r, output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = r0_i
+			
+			var i1_r = s0_r - i0_r * 0.5
+			var i1_i = s0_i - i0_i * 0.5
+			
+			var i2_r = (v1_r - v2_r) * e
+			var i2_i = (v1_i - v2_i) * e
+			
+			var r1_r = i1_r - i2_i
+			var r1_i = i1_i + i2_r
+			output[2 * ((outputOffset) + (outputStride) * (i + m1))] = r1_r, output[2 * ((outputOffset) + (outputStride) * (i + m1)) + 1] = r1_i
+			
+			var r2_r = i1_r + i2_i
+			var r2_i = i1_i - i2_r
+			output[2 * ((outputOffset) + (outputStride) * (i + m2))] = r2_r, output[2 * ((outputOffset) + (outputStride) * (i + m2)) + 1] = r2_i
+		}
+	}
+	
+	function butterfly4(output, outputOffset, outputStride, fStride, state, m) {
+		var t = state.twiddle
+		var m1 = m, m2 = 2 * m, m3 = 3 * m
+		var fStride1 = fStride, fStride2 = 2 * fStride, fStride3 = 3 * fStride
+		
+		for (var i = 0; i < m; i++) {
+			var s0_r = output[2 * ((outputOffset) + (outputStride) * (i))], s0_i = output[2 * ((outputOffset) + (outputStride) * (i)) + 1]
+			
+			var s1_r = output[2 * ((outputOffset) + (outputStride) * (i + m1))], s1_i = output[2 * ((outputOffset) + (outputStride) * (i + m1)) + 1]
+			var t1_r = t[2 * ((0) + (fStride1) * (i))], t1_i = t[2 * ((0) + (fStride1) * (i)) + 1]
+			var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r
+			
+			var s2_r = output[2 * ((outputOffset) + (outputStride) * (i + m2))], s2_i = output[2 * ((outputOffset) + (outputStride) * (i + m2)) + 1]
+			var t2_r = t[2 * ((0) + (fStride2) * (i))], t2_i = t[2 * ((0) + (fStride2) * (i)) + 1]
+			var v2_r = s2_r * t2_r - s2_i * t2_i, v2_i = s2_r * t2_i + s2_i * t2_r
+			
+			var s3_r = output[2 * ((outputOffset) + (outputStride) * (i + m3))], s3_i = output[2 * ((outputOffset) + (outputStride) * (i + m3)) + 1]
+			var t3_r = t[2 * ((0) + (fStride3) * (i))], t3_i = t[2 * ((0) + (fStride3) * (i)) + 1]
+			var v3_r = s3_r * t3_r - s3_i * t3_i, v3_i = s3_r * t3_i + s3_i * t3_r
+			
+			var i0_r = s0_r + v2_r, i0_i = s0_i + v2_i
+			var i1_r = s0_r - v2_r, i1_i = s0_i - v2_i
+			var i2_r = v1_r + v3_r, i2_i = v1_i + v3_i
+			var i3_r = v1_r - v3_r, i3_i = v1_i - v3_i
+			
+			var r0_r = i0_r + i2_r, r0_i = i0_i + i2_i
+			
+			if (state.inverse) {
+				var r1_r = i1_r - i3_i
+				var r1_i = i1_i + i3_r
+			} else {
+				var r1_r = i1_r + i3_i
+				var r1_i = i1_i - i3_r
+			}
+			
+			var r2_r = i0_r - i2_r, r2_i = i0_i - i2_i
+			
+			if (state.inverse) {
+				var r3_r = i1_r + i3_i
+				var r3_i = i1_i - i3_r
+			} else {
+				var r3_r = i1_r - i3_i
+				var r3_i = i1_i + i3_r
+			}
+			
+			output[2 * ((outputOffset) + (outputStride) * (i))] = r0_r, output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = r0_i
+			output[2 * ((outputOffset) + (outputStride) * (i + m1))] = r1_r, output[2 * ((outputOffset) + (outputStride) * (i + m1)) + 1] = r1_i
+			output[2 * ((outputOffset) + (outputStride) * (i + m2))] = r2_r, output[2 * ((outputOffset) + (outputStride) * (i + m2)) + 1] = r2_i
+			output[2 * ((outputOffset) + (outputStride) * (i + m3))] = r3_r, output[2 * ((outputOffset) + (outputStride) * (i + m3)) + 1] = r3_i
+		}
+	}
+	
+	function butterfly(output, outputOffset, outputStride, fStride, state, m, p) {
+		var t = state.twiddle, n = state.n, scratch = new Float64Array(2 * p)
+		
+		for (var u = 0; u < m; u++) {
+			for (var q1 = 0, k = u; q1 < p; q1++, k += m) {
+				var x0_r = output[2 * ((outputOffset) + (outputStride) * (k))], x0_i = output[2 * ((outputOffset) + (outputStride) * (k)) + 1]
+				scratch[2 * (q1)] = x0_r, scratch[2 * (q1) + 1] = x0_i
+			}
+			
+			for (var q1 = 0, k = u; q1 < p; q1++, k += m) {
+				var tOffset = 0
+				
+				var x0_r = scratch[2 * (0)], x0_i = scratch[2 * (0) + 1]
+				output[2 * ((outputOffset) + (outputStride) * (k))] = x0_r, output[2 * ((outputOffset) + (outputStride) * (k)) + 1] = x0_i
+				
+				for (var q = 1; q < p; q++) {
+					tOffset = (tOffset + fStride * k) % n
+					
+					var s0_r = output[2 * ((outputOffset) + (outputStride) * (k))], s0_i = output[2 * ((outputOffset) + (outputStride) * (k)) + 1]
+					
+					var s1_r = scratch[2 * (q)], s1_i = scratch[2 * (q) + 1]
+					var t1_r = t[2 * (tOffset)], t1_i = t[2 * (tOffset) + 1]
+					var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r
+					
+					var r0_r = s0_r + v1_r, r0_i = s0_i + v1_i
+					output[2 * ((outputOffset) + (outputStride) * (k))] = r0_r, output[2 * ((outputOffset) + (outputStride) * (k)) + 1] = r0_i
+				}
+			}
+		}
+	}
+	
+	function work(output, outputOffset, outputStride, f, fOffset, fStride, inputStride, factors, state) {
+		var p = factors.shift()
+		var m = factors.shift()
+		
+		if (m == 1) {
+			for (var i = 0; i < p * m; i++) {
+				var x0_r = f[2 * ((fOffset) + (fStride * inputStride) * (i))], x0_i = f[2 * ((fOffset) + (fStride * inputStride) * (i)) + 1]
+				output[2 * ((outputOffset) + (outputStride) * (i))] = x0_r, output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = x0_i
+			}
+		} else {
+			for (var i = 0; i < p; i++) {
+				work(output, outputOffset + outputStride * i * m, outputStride, f, fOffset + i * fStride * inputStride, fStride * p, inputStride, factors.slice(), state)
+			}
+		}
+		
+		switch (p) {
+			case 2: butterfly2(output, outputOffset, outputStride, fStride, state, m); break
+			case 3: butterfly3(output, outputOffset, outputStride, fStride, state, m); break
+			case 4: butterfly4(output, outputOffset, outputStride, fStride, state, m); break
+			default: butterfly(output, outputOffset, outputStride, fStride, state, m, p); break
+		}
+	}
+	
+	var complex = function (n, inverse) {
+		if (arguments.length < 2) {
+			throw new RangeError("You didn't pass enough arguments, passed `" + arguments.length + "'")
+		}
+		
+		var n = ~~n, inverse = !!inverse
+		
+		if (n < 1) {
+			throw new RangeError("n is outside range, should be positive integer, was `" + n + "'")
+		}
+		
+		var state = {
+			n: n,
+			inverse: inverse,
+			
+			factors: [],
+			twiddle: new Float64Array(2 * n),
+			scratch: new Float64Array(2 * n)
+		}
+		
+		var t = state.twiddle, theta = 2 * Math.PI / n
+		
+		for (var i = 0; i < n; i++) {
+			if (inverse) {
+				var phase =  theta * i
+			} else {
+				var phase = -theta * i
+			}
+			
+			t[2 * (i)] = Math.cos(phase)
+			t[2 * (i) + 1] = Math.sin(phase)
+		}
+		
+		var p = 4, v = Math.floor(Math.sqrt(n))
+		
+		while (n > 1) {
+			while (n % p) {
+				switch (p) {
+					case 4: p = 2; break
+					case 2: p = 3; break
+					default: p += 2; break
+				}
+				
+				if (p > v) {
+					p = n
+				}
+			}
+			
+			n /= p
+			
+			state.factors.push(p)
+			state.factors.push(n)
+		}
+		
+		this.state = state
+	}
+	
+	complex.prototype.simple = function (output, input, t) {
+		this.process(output, 0, 1, input, 0, 1, t)
+	}
+	
+	complex.prototype.process = function(output, outputOffset, outputStride, input, inputOffset, inputStride, t) {
+		var outputStride = ~~outputStride, inputStride = ~~inputStride
+		
+		var type = t == 'real' ? t : 'complex'
+		
+		if (outputStride < 1) {
+			throw new RangeError("outputStride is outside range, should be positive integer, was `" + outputStride + "'")
+		}
+		
+		if (inputStride < 1) {
+			throw new RangeError("inputStride is outside range, should be positive integer, was `" + inputStride + "'")
+		}
+		
+		if (type == 'real') {
+			for (var i = 0; i < this.state.n; i++) {
+				var x0_r = input[inputOffset + inputStride * i]
+				var x0_i = 0.0
+				
+				this.state.scratch[2 * (i)] = x0_r, this.state.scratch[2 * (i) + 1] = x0_i
+			}
+			
+			work(output, outputOffset, outputStride, this.state.scratch, 0, 1, 1, this.state.factors.slice(), this.state)
+		} else {
+			if (input == output) {
+				work(this.state.scratch, 0, 1, input, inputOffset, 1, inputStride, this.state.factors.slice(), this.state)
+				
+				for (var i = 0; i < this.state.n; i++) {
+					var x0_r = this.state.scratch[2 * (i)], x0_i = this.state.scratch[2 * (i) + 1]
+					
+					output[2 * ((outputOffset) + (outputStride) * (i))] = x0_r, output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = x0_i
+				}
+			} else {
+				work(output, outputOffset, outputStride, input, inputOffset, 1, inputStride, this.state.factors.slice(), this.state)
+			}
+		}
+	}
+	
+	namespace.complex = complex
+}(FFT)
+
+module.exports = FFT
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/lib/real.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,300 @@
+/* Copyright (c) 2012, Jens Nockert <jens@ofmlabs.org>, Jussi Kalliokoski <jussi@ofmlabs.org>
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met: 
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+if (!FFT) {
+	var FFT = {}
+}
+
+void function (namespace) {
+	"use strict"
+	
+	function forwardButterfly2(output, outputOffset, outputStride, input, inputOffset, inputStride, product, n, twiddle, fStride) {
+		var m = n / 2, q = n / product, old = product / 2
+		
+		for (var i = 0; i < q; i++) {
+			var a0 = old * i
+			var a1 = a0 + m
+			
+			var s0 = input[inputOffset + inputStride * a0]
+			var s1 = input[inputOffset + inputStride * a1]
+			
+			var r0 = s0 + s1
+			var r1 = s0 - s1
+			
+			var a0 = product * i
+			var a1 = a0 + product - 1
+			
+			output[outputOffset + outputStride * a0] = r0
+			output[outputOffset + outputStride * a1] = r1
+		}
+		
+		if (old == 1) { return }
+		
+		for (var i = 0; i < old / 2; i++) {
+			var t1_r = twiddle[2 * ((-1) + (i))], t1_i = twiddle[2 * ((-1) + (i)) + 1]
+			
+			for (var j = 0; j < q; j++) {
+				var a0 = j * old + 2 * i - 1
+				var a1 = a0 + m
+				
+				var s0_r = input[2 * ((inputOffset) + (inputStride) * (a0))], s0_i = input[2 * ((inputOffset) + (inputStride) * (a0)) + 1]
+				
+				var s1_r = input[2 * ((inputOffset) + (inputStride) * (a1))], s1_i = input[2 * ((inputOffset) + (inputStride) * (a1)) + 1]
+				var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r
+				
+				var r0_r = s0_r + v1_r, r0_i = s0_i + v1_i
+				var r1_r = s0_r - v1_r, r1_i = s0_i - v1_i; r1_i = -r1_i
+				
+				var a0 = j * product + 2 * i - 1
+				var a1 = (j - 1) * product - 2 * i - 1
+				
+				output[2 * ((outputOffset) + (outputStride) * (a0))] = r0_r, output[2 * ((outputOffset) + (outputStride) * (a0)) + 1] = r0_i
+				output[2 * ((outputOffset) + (outputStride) * (a1))] = r1_r, output[2 * ((outputOffset) + (outputStride) * (a1)) + 1] = r1_i
+			}
+		}
+		
+		if (old % 2 == 1) { return }
+		
+		for (var i = 0; i < q; i++) {
+			var a0 = (i + 1) * old - 1
+			var a1 = a0 + m
+			
+			var r0_r =  input[2 * ((inputOffset) + (inputStride) * (a0))]
+			var r1_i = -input[2 * ((inputOffset) + (inputStride) * (a1))]
+			
+			var a0 = i * product + old - 1
+			
+			output[2 * ((outputOffset) + (outputStride) * (a0))] = r0_r, output[2 * ((outputOffset) + (outputStride) * (a0)) + 1] = r0_i
+		}
+	}
+	
+	function backwardButterfly2(output, outputOffset, outputStride, input, inputOffset, inputStride, product, n, twiddle, fStride) {
+		var m = n / 2, q = n / product, old = product / 2
+		
+		for (var i = 0; i < q; i++) {
+			var a0 = (2 * i) * q
+			var a1 = (2 * i + 2) * q - 1
+			
+			var s0 = input[inputOffset + inputStride * a0]
+			var s1 = input[inputOffset + inputStride * a1]
+			
+			var r0 = s0 + s1
+			var r1 = s0 - s1
+			
+			var a0 = q * i
+			var a1 = q * i + m
+			
+			output[outputOffset + outputStride * a0] = r0
+			output[outputOffset + outputStride * a1] = r1
+		}
+		
+		if (q == 1) { return }
+		
+		for (var i = 0; i < q / 2; i++) {
+			var t1_r = twiddle[2 * ((-1) + (i))], t1_i = twiddle[2 * ((-1) + (i)) + 1]
+			
+			for (var j = 0; j < old; j++) {
+				var a0 = 2 * j * q + 2 * i - 1
+				var a1 = 2 * (j + 1) * q - 2 * i - 1
+				
+				var s0_r = input[2 * ((inputOffset) + (inputStride) * (a0))], s0_i = input[2 * ((inputOffset) + (inputStride) * (a0)) + 1]
+				var s1_r = input[2 * ((inputOffset) + (inputStride) * (a1))], s1_i = input[2 * ((inputOffset) + (inputStride) * (a1)) + 1]
+				
+				var r0_r = s0_r + s1_r
+				var r0_i = s0_i - s1_i
+				
+				var v1_r = s0_r - s1_r
+				var v1_i = s0_i + s1_i
+				
+				var r1_r = v1_r * t1_r - v1_i * t1_i, r1_i = v1_r * t1_i + v1_i * t1_r
+				
+				var a0 = j * q + 2 * i - 1
+				var a1 = a0 + m
+				
+				output[2 * ((outputOffset) + (outputStride) * (a0))] = r0_r, output[2 * ((outputOffset) + (outputStride) * (a0)) + 1] = r0_i
+				output[2 * ((outputOffset) + (outputStride) * (a1))] = r1_r, output[2 * ((outputOffset) + (outputStride) * (a1)) + 1] = r1_i
+			}
+		}
+		
+		if (q % 2 == 1) { return }
+		
+		for (var i = 0; i < q; i++) {
+			var a0 = 2 * (i + 1) * q - 1
+			
+			var r0_r = input[2 * ((inputOffset) + (inputStride) * (a0))], r0_i = input[2 * ((inputOffset) + (inputStride) * (a0)) + 1]
+			
+			input[2 * ((inputOffset) + (inputStride) * (a0))] =  2 * r0_r
+			input[2 * ((inputOffset) + (inputStride) * (a1)) + 1] = -2 * r0_i
+		}
+	}
+	
+	function work(output, outputOffset, outputStride, f, fOffset, fStride, inputStride, factors, state) {
+		var p = factors.shift()
+		var m = factors.shift()
+		
+		if (m == 1) {
+			for (var i = 0; i < p * m; i++) {
+				var x0_r = f[2 * ((fOffset) + (fStride * inputStride) * (i))], x0_i = f[2 * ((fOffset) + (fStride * inputStride) * (i)) + 1]
+				output[2 * ((outputOffset) + (outputStride) * (i))] = x0_r, output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = x0_i
+			}
+		} else {
+			for (var i = 0; i < p; i++) {
+				work(output, outputOffset + outputStride * i * m, outputStride, f, fOffset + i * fStride * inputStride, fStride * p, inputStride, factors.slice(), state)
+			}
+		}
+		
+		switch (p) {
+			case 2: butterfly2(output, outputOffset, outputStride, fStride, state, m); break
+			case 3: butterfly3(output, outputOffset, outputStride, fStride, state, m); break
+			case 4: butterfly4(output, outputOffset, outputStride, fStride, state, m); break
+			default: butterfly(output, outputOffset, outputStride, fStride, state, m, p); break
+		}
+	}
+	
+	var real = function (n, inverse) {
+		var n = ~~n, inverse = !!inverse
+		
+		if (n < 1) {
+			throw new RangeError("n is outside range, should be positive integer, was `" + n + "'")
+		}
+		
+		var state = {
+			n: n,
+			inverse: inverse,
+			
+			factors: [],
+			twiddle: [],
+			scratch: new Float64Array(n)
+		}
+		
+		var t = new Float64Array(n)
+		
+		var p = 4, v = Math.floor(Math.sqrt(n))
+		
+		while (n > 1) {
+			while (n % p) {
+				switch (p) {
+					case 4: p = 2; break
+					case 2: p = 3; break
+					default: p += 2; break
+				}
+				
+				if (p > v) {
+					p = n
+				}
+			}
+			
+			n /= p
+			
+			state.factors.push(p)
+		}
+		
+		var theta = 2 * Math.PI / n, product = 1, twiddle = new Float64Array(n)
+			
+		for (var i = 0, t = 0; i < state.factors.length; i++) {
+			var phase = theta * i, factor = state.factors[i]
+			
+			var old = product, product = product * factor, q = n / product
+			
+			state.twiddle.push(new Float64Array(twiddle, t))
+			
+			if (inverse) {
+				var counter = q, multiplier = old
+			} else {
+				var counter = old, multiplier = q
+			}
+			
+			for (var j = 1; j < factor; j++) {
+				var m = 0
+					
+				for (var k = 1; k < counter / 2; k++, t++) {
+					m = (m + j * multiplier) % n
+						
+					var phase = theta * m
+						
+					t[2 * (i)] = Math.cos(phase)
+					t[2 * (i) + 1] = Math.sin(phase)
+				}
+			}
+		}
+		
+		this.state = state
+	}
+	
+	real.prototype.process = function(output, outputStride, input, inputStride) {
+		var outputStride = ~~outputStride, inputStride = ~~inputStride
+		
+		if (outputStride < 1) {
+			throw new RangeError("outputStride is outside range, should be positive integer, was `" + outputStride + "'")
+		}
+		
+		if (inputStride < 1) {
+			throw new RangeError("inputStride is outside range, should be positive integer, was `" + inputStride + "'")
+		}
+		
+		var product = 1, state = 0, inverse = this.state.inverse
+		
+		var n = this.state.n, factors = this.state.factors
+		var twiddle = this.state.twiddle, scratch = this.state.scratch
+		
+		for (var i = 0; i < factors.length; i++) {
+			var factor = factors[i], old = product, product = product * factor
+			
+			var q = n / product, fStride = Math.ceil(old / 2) - 1
+			
+			if (state == 0) {
+				var inBuffer = input, inStride = inputStride
+				
+				if (this.state.factors.length % 2 == 0) {
+					var outBuffer = scratch, outStride = 1, state = 1
+				} else {
+					var outBuffer = output, outStride = outputStride, state = 2
+				}
+			} else if (state == 1) {
+				var inBuffer = scratch, inStride = 1, outBuffer = output, outStride = outputStride, state = 2
+			} else if (state == 2) {
+				var inBuffer = output, inStride = outputStride, outBuffer = scratch, outStride = 1, state = 1
+			} else {
+				throw new RangeError("state somehow is not in the range (0 .. 2)")
+			}
+			
+			if (inverse) {
+				switch (factor) {
+				case 2: backwardButterfly2(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				case 3: backwardButterfly3(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				case 4: backwardButterfly3(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				case 5: backwardButterfly3(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				default: backwardButterfly(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				}
+			} else {
+				switch (factor) {
+				case 2: forwardButterfly2(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				case 3: forwardButterfly3(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				case 4: forwardButterfly3(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				case 5: forwardButterfly3(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				default: forwardButterfly(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				}
+			}
+		}
+	}
+	
+	namespace.real = real
+}(FFT)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/package.json	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,20 @@
+{
+	"name":		"fft",
+	"authors":	["Jens Nockert", "Jussi Kalliokoski"],
+	"version":	"0.2.1",
+	"description":	"A Fast Fourier Transform library for JS.",
+	"repository":	{
+		"type":		"git",
+		"url":		"http://github.com/JensNockert/fft.js"
+	},
+	"engines":	{
+		"node":		">=0.6.0-0"
+	},
+	"licenses":	[{
+		"type":		"FreeBSD"
+	}],
+	"bugs":		{
+		"url":		"http://github.com/JensNockert/fft.js/issues"
+	},
+	"main":		"./lib/node.js"
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/src/complex.erb.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,273 @@
+<%= $:.unshift('.'); require "#{File.dirname(__FILE__)}/../src/complex.rb"; File.read "#{File.dirname(__FILE__)}/../LICENSE" %>
+
+if (!FFT) {
+	var FFT = {}
+}
+
+void function (namespace) {
+	"use strict"
+	
+	function butterfly2(output, outputOffset, outputStride, fStride, state, m) {
+		var t = state.twiddle
+		
+		for (var i = 0; i < m; i++) {
+			<%= load('s0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			<%= load('s1', 'output', 'outputOffset', 'i + m', 'outputStride') %>
+			
+			<%= load('t1', 't', 0, 'i', 'fStride') %>
+			
+			<%= cmul('v1', 's1', 't1') %>
+			
+			<%= cadd('r0', 's0', 'v1') %>
+			<%= csub('r1', 's0', 'v1') %>
+			
+			<%= store('r0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			<%= store('r1', 'output', 'outputOffset', 'i + m', 'outputStride') %>
+		}
+	}
+	
+	function butterfly3(output, outputOffset, outputStride, fStride, state, m) {
+		var t = state.twiddle
+		var m1 = m, m2 = 2 * m
+		var fStride1 = fStride, fStride2 = 2 * fStride
+		
+		var e = <%= imag('t', 0, 'm', 'fStride') %>
+		
+		for (var i = 0; i < m; i++) {
+			<%= load('s0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			
+			<%= load('s1', 'output', 'outputOffset', 'i + m1', 'outputStride') %>
+			<%= load('t1', 't', 0, 'i', 'fStride1') %>
+			<%= cmul('v1', 's1', 't1') %>
+			
+			<%= load('s2', 'output', 'outputOffset', 'i + m2', 'outputStride') %>
+			<%= load('t2', 't', 0, 'i', 'fStride2') %>
+			<%= cmul('v2', 's2', 't2') %>
+			
+			<%= cadd('i0', 'v1', 'v2') %>
+			
+			<%= cadd('r0', 's0', 'i0') %>
+			<%= store('r0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			
+			var i1_r = s0_r - i0_r * 0.5
+			var i1_i = s0_i - i0_i * 0.5
+			
+			var i2_r = (v1_r - v2_r) * e
+			var i2_i = (v1_i - v2_i) * e
+			
+			var r1_r = i1_r - i2_i
+			var r1_i = i1_i + i2_r
+			<%= store('r1', 'output', 'outputOffset', 'i + m1', 'outputStride') %>
+			
+			var r2_r = i1_r + i2_i
+			var r2_i = i1_i - i2_r
+			<%= store('r2', 'output', 'outputOffset', 'i + m2', 'outputStride') %>
+		}
+	}
+	
+	function butterfly4(output, outputOffset, outputStride, fStride, state, m) {
+		var t = state.twiddle
+		var m1 = m, m2 = 2 * m, m3 = 3 * m
+		var fStride1 = fStride, fStride2 = 2 * fStride, fStride3 = 3 * fStride
+		
+		for (var i = 0; i < m; i++) {
+			<%= load('s0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			
+			<%= load('s1', 'output', 'outputOffset', 'i + m1', 'outputStride') %>
+			<%= load('t1', 't', 0, 'i', 'fStride1') %>
+			<%= cmul('v1', 's1', 't1') %>
+			
+			<%= load('s2', 'output', 'outputOffset', 'i + m2', 'outputStride') %>
+			<%= load('t2', 't', 0, 'i', 'fStride2') %>
+			<%= cmul('v2', 's2', 't2') %>
+			
+			<%= load('s3', 'output', 'outputOffset', 'i + m3', 'outputStride') %>
+			<%= load('t3', 't', 0, 'i', 'fStride3') %>
+			<%= cmul('v3', 's3', 't3') %>
+			
+			<%= cadd('i0', 's0', 'v2') %>
+			<%= csub('i1', 's0', 'v2') %>
+			<%= cadd('i2', 'v1', 'v3') %>
+			<%= csub('i3', 'v1', 'v3') %>
+			
+			<%= cadd('r0', 'i0', 'i2') %>
+			
+			if (state.inverse) {
+				var r1_r = i1_r - i3_i
+				var r1_i = i1_i + i3_r
+			} else {
+				var r1_r = i1_r + i3_i
+				var r1_i = i1_i - i3_r
+			}
+			
+			<%= csub('r2', 'i0', 'i2') %>
+			
+			if (state.inverse) {
+				var r3_r = i1_r + i3_i
+				var r3_i = i1_i - i3_r
+			} else {
+				var r3_r = i1_r - i3_i
+				var r3_i = i1_i + i3_r
+			}
+			
+			<%= store('r0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			<%= store('r1', 'output', 'outputOffset', 'i + m1', 'outputStride') %>
+			<%= store('r2', 'output', 'outputOffset', 'i + m2', 'outputStride') %>
+			<%= store('r3', 'output', 'outputOffset', 'i + m3', 'outputStride') %>
+		}
+	}
+	
+	function butterfly(output, outputOffset, outputStride, fStride, state, m, p) {
+		var t = state.twiddle, n = state.n, scratch = new Float64Array(2 * p)
+		
+		for (var u = 0; u < m; u++) {
+			for (var q1 = 0, k = u; q1 < p; q1++, k += m) {
+				<%= load('x0', 'output', 'outputOffset', 'k', 'outputStride') %>
+				<%= store('x0', 'scratch', 'q1') %>
+			}
+			
+			for (var q1 = 0, k = u; q1 < p; q1++, k += m) {
+				var tOffset = 0
+				
+				<%= load('x0', 'scratch', 0) %>
+				<%= store('x0', 'output', 'outputOffset', 'k', 'outputStride') %>
+				
+				for (var q = 1; q < p; q++) {
+					tOffset = (tOffset + fStride * k) % n
+					
+					<%= load('s0', 'output', 'outputOffset', 'k', 'outputStride') %>
+					
+					<%= load('s1', 'scratch', 'q') %>
+					<%= load('t1', 't', 'tOffset') %>
+					<%= cmul('v1', 's1', 't1') %>
+					
+					<%= cadd('r0', 's0', 'v1') %>
+					<%= store('r0', 'output', 'outputOffset', 'k', 'outputStride') %>
+				}
+			}
+		}
+	}
+	
+	function work(output, outputOffset, outputStride, f, fOffset, fStride, inputStride, factors, state) {
+		var p = factors.shift()
+		var m = factors.shift()
+		
+		if (m == 1) {
+			for (var i = 0; i < p * m; i++) {
+				<%= load('x0', 'f', 'fOffset', 'i', 'fStride * inputStride') %>
+				<%= store('x0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			}
+		} else {
+			for (var i = 0; i < p; i++) {
+				work(output, outputOffset + outputStride * i * m, outputStride, f, fOffset + i * fStride * inputStride, fStride * p, inputStride, factors.slice(), state)
+			}
+		}
+		
+		switch (p) {
+			case 2: butterfly2(output, outputOffset, outputStride, fStride, state, m); break
+			case 3: butterfly3(output, outputOffset, outputStride, fStride, state, m); break
+			case 4: butterfly4(output, outputOffset, outputStride, fStride, state, m); break
+			default: butterfly(output, outputOffset, outputStride, fStride, state, m, p); break
+		}
+	}
+	
+	var complex = function (n, inverse) {
+		if (arguments.length < 2) {
+			throw new RangeError("You didn't pass enough arguments, passed `" + arguments.length + "'")
+		}
+		
+		var n = ~~n, inverse = !!inverse
+		
+		if (n < 1) {
+			throw new RangeError("n is outside range, should be positive integer, was `" + n + "'")
+		}
+		
+		var state = {
+			n: n,
+			inverse: inverse,
+			
+			factors: [],
+			twiddle: new Float64Array(2 * n),
+			scratch: new Float64Array(2 * n)
+		}
+		
+		var t = state.twiddle, theta = 2 * Math.PI / n
+		
+		for (var i = 0; i < n; i++) {
+			if (inverse) {
+				var phase =  theta * i
+			} else {
+				var phase = -theta * i
+			}
+			
+			<%= real('t', 'i') %> = Math.cos(phase)
+			<%= imag('t', 'i') %> = Math.sin(phase)
+		}
+		
+		var p = 4, v = Math.floor(Math.sqrt(n))
+		
+		while (n > 1) {
+			while (n % p) {
+				switch (p) {
+					case 4: p = 2; break
+					case 2: p = 3; break
+					default: p += 2; break
+				}
+				
+				if (p > v) {
+					p = n
+				}
+			}
+			
+			n /= p
+			
+			state.factors.push(p)
+			state.factors.push(n)
+		}
+		
+		this.state = state
+	}
+	
+	complex.prototype.simple = function (output, input, t) {
+		this.process(output, 0, 1, input, 0, 1, t)
+	}
+	
+	complex.prototype.process = function(output, outputOffset, outputStride, input, inputOffset, inputStride, t) {
+		var outputStride = ~~outputStride, inputStride = ~~inputStride
+		
+		var type = t == 'real' ? t : 'complex'
+		
+		if (outputStride < 1) {
+			throw new RangeError("outputStride is outside range, should be positive integer, was `" + outputStride + "'")
+		}
+		
+		if (inputStride < 1) {
+			throw new RangeError("inputStride is outside range, should be positive integer, was `" + inputStride + "'")
+		}
+		
+		if (type == 'real') {
+			for (var i = 0; i < this.state.n; i++) {
+				var x0_r = input[inputOffset + inputStride * i]
+				var x0_i = 0.0
+				
+				<%= store('x0', 'this.state.scratch', 'i') %>
+			}
+			
+			work(output, outputOffset, outputStride, this.state.scratch, 0, 1, 1, this.state.factors.slice(), this.state)
+		} else {
+			if (input == output) {
+				work(this.state.scratch, 0, 1, input, inputOffset, 1, inputStride, this.state.factors.slice(), this.state)
+				
+				for (var i = 0; i < this.state.n; i++) {
+					<%= load('x0', 'this.state.scratch', 'i') %>
+					
+					<%= store('x0', 'output', 'outputOffset', 'i', 'outputStride') %>
+				}
+			} else {
+				work(output, outputOffset, outputStride, input, inputOffset, 1, inputStride, this.state.factors.slice(), this.state)
+			}
+		}
+	}
+	
+	namespace.complex = complex
+}(FFT)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/src/complex.rb	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,43 @@
+def real(x, offset = 0, i = nil, stride = nil)
+  if stride
+    "#{x}[2 * ((#{offset}) + (#{stride}) * (#{i}))]"
+  elsif i
+    "#{x}[2 * ((#{offset}) + (#{i}))]"
+  elsif offset
+    "#{x}[2 * (#{offset})]"
+  else
+    "#{x}[0]"
+  end
+end
+
+def imag(x, offset = 0, i = nil, stride = nil)
+  if stride
+    "#{x}[2 * ((#{offset}) + (#{stride}) * (#{i})) + 1]"
+  elsif i
+    "#{x}[2 * ((#{offset}) + (#{i})) + 1]"
+  elsif offset
+    "#{x}[2 * (#{offset}) + 1]"
+  else
+    "#{x}[1]"
+  end
+end
+
+def load(value, x, offset = 0, i = nil, stride = nil)
+  "var #{value}_r = #{real(x, offset, i, stride)}, #{value}_i = #{imag(x, offset, i, stride)}"
+end
+
+def store(value, x, offset = 0, i = nil, stride = nil)
+  "#{real(x, offset, i, stride)} = #{value}_r, #{imag(x, offset, i, stride)} = #{value}_i"
+end
+
+def cadd(result, a, b)
+  "var #{result}_r = #{a}_r + #{b}_r, #{result}_i = #{a}_i + #{b}_i"
+end
+
+def csub(result, a, b)
+  "var #{result}_r = #{a}_r - #{b}_r, #{result}_i = #{a}_i - #{b}_i"
+end
+
+def cmul(result, a, b)
+  "var #{result}_r = #{a}_r * #{b}_r - #{a}_i * #{b}_i, #{result}_i = #{a}_r * #{b}_i + #{a}_i * #{b}_r"
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/src/node.erb.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,2 @@
+<%= File.read "#{File.dirname(__FILE__)}/../src/complex.erb.js" %>
+module.exports = FFT
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/src/real.erb.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,541 @@
+<%= $:.unshift('.'); require "#{File.dirname(__FILE__)}/../src/complex.rb"; File.read "#{File.dirname(__FILE__)}/../LICENSE" %>
+
+if (!FFT) {
+	var FFT = {}
+}
+
+void function (namespace) {
+	"use strict"
+	
+	function forwardButterfly2(output, outputOffset, outputStride, input, inputOffset, inputStride, product, n, twiddle, fStride) {
+		var m = n / 2, q = n / product, old = product / 2
+		
+		for (var i = 0; i < q; i++) {
+			var a0 = old * i
+			var a1 = a0 + m
+			
+			var s0 = input[inputOffset + inputStride * a0]
+			var s1 = input[inputOffset + inputStride * a1]
+			
+			var r0 = s0 + s1
+			var r1 = s0 - s1
+			
+			var a0 = product * i
+			var a1 = a0 + product - 1
+			
+			output[outputOffset + outputStride * a0] = r0
+			output[outputOffset + outputStride * a1] = r1
+		}
+		
+		if (old == 1) { return }
+		
+		for (var i = 0; i < old / 2; i++) {
+			<%= load('t1', 'twiddle', -1, 'i') %>
+			
+			for (var j = 0; j < q; j++) {
+				var a0 = j * old + 2 * i - 1
+				var a1 = a0 + m
+				
+				<%= load('s0', 'input', 'inputOffset', 'a0', 'inputStride') %>
+				
+				<%= load('s1', 'input', 'inputOffset', 'a1', 'inputStride') %>
+				<%= cmul('v1', 's1', 't1') %>
+				
+				<%= cadd('r0', 's0', 'v1') %>
+				<%= csub('r1', 's0', 'v1') %>; r1_i = -r1_i
+				
+				var a0 = j * product + 2 * i - 1
+				var a1 = (j - 1) * product - 2 * i - 1
+				
+				<%= store('r0', 'output', 'outputOffset', 'a0', 'outputStride') %>
+				<%= store('r1', 'output', 'outputOffset', 'a1', 'outputStride') %>
+			}
+		}
+		
+		if (old % 2 == 1) { return }
+		
+		for (var i = 0; i < q; i++) {
+			var a0 = (i + 1) * old - 1
+			var a1 = a0 + m
+			
+			var r0_r =  <%= real('input', 'inputOffset', 'a0', 'inputStride') %>
+			var r1_i = -<%= real('input', 'inputOffset', 'a1', 'inputStride') %>
+			
+			var a0 = i * product + old - 1
+			
+			<%= store('r0', 'output', 'outputOffset', 'a0', 'outputStride') %>
+		}
+	}
+	
+	function forwardButterfly(output, outputOffset, outputStride, input, inputOffset, inputStride, product, n, twiddle, fStride, factor) {
+		var m = n / 2, q = n / product, old = product / 2
+		
+		var theta = 2.0 * Math.PI / factor
+		
+		var theta_r =  Math.cos(theta)
+		var theta_i = -Math.sin(theta)
+		
+		for (var i = 0; i < q; i++) {
+			var d_r = 1.0
+			var d_i = 0.0
+			
+			for (var j = 0; j <= Math.floor(factor / 2); j++) {
+				var sum_r = 0.0
+				var sum_i = 0.0
+				
+				var w_r = 1.0
+				var w_i = 0.0
+				
+				if (j > 0) {
+					<%= cmul('t0', 'd', 'theta') %>
+					
+					d_r = t0_r
+					d_i = t0_i
+				}
+				
+				for (var k = 0; k < factor; k++) {
+					var z = <%= real('input', 'inputOffset', 'i * old + k * m', 'inputStride') %>
+					
+					if (k > 0) {
+						<%= cmul('t0', 'd', 'w') %>
+						
+						d_r = t0_r
+						d_i = t0_i
+					}
+					
+					/* TODO: Use Kahan summation..? */
+					s_r += w_r * z
+					s_i += w_i * z
+				}
+				
+				if (j == 0) {
+					var a0 = product * i
+					
+					output[outputOffset + outputStride * a0] = sum_r
+				} else if (j < factor / 2) {
+					var a0 = product * i
+					
+					output[outputOffset + outputStride * a0] = sum_r
+				} else if (j == factor / 2) {
+					
+				}
+			}
+	        for (e1 = 0; e1 <= factor - e1; e1++)
+	          {
+	            if (e1 == 0)
+	              {
+	                const size_t to0 = product * k1;
+	                VECTOR(out,ostride,to0) = sum_real;
+	              }
+	            else if (e1 < factor - e1)
+	              {
+	                const size_t to0 = k1 * product + 2 * e1 * product_1 - 1;
+	                VECTOR(out,ostride,to0) = sum_real;
+	                VECTOR(out,ostride,to0 + 1) = sum_imag;
+	              }
+	            else if (e1 == factor - e1)
+	              {
+	                const size_t to0 = k1 * product + 2 * e1 * product_1 - 1;
+	                VECTOR(out,ostride,to0) = sum_real;
+	              }
+
+	          }
+			
+		}
+		
+		if (old == 1) { return }
+		
+		for (var i = 0; i < old / 2; i++) {
+			
+		}
+		
+		if (old % 2 == 1) { return }
+		
+		var t_arg = Math.PI / factor
+		
+		var t_r =  cos(t_arg)
+		var t_i = -sin(t_arg)
+		
+		for (var i = 0; i < q; i++) {
+			
+		}
+	}
+
+    if (product_1 == 1)
+      return;
+
+    for (k = 1; k < (product_1 + 1) / 2; k++)
+      {
+        for (k1 = 0; k1 < q; k1++)
+          {
+
+            ATOMIC dw_real = 1.0, dw_imag = 0.0;
+
+            for (e1 = 0; e1 < factor; e1++)
+              {
+                ATOMIC sum_real = 0.0, sum_imag = 0.0;
+
+                ATOMIC w_real = 1.0, w_imag = 0.0;
+
+                if (e1 > 0)
+                  {
+                    const ATOMIC tmp_real = dw_real * cos_d_theta + dw_imag * sin_d_theta;
+                    const ATOMIC tmp_imag = -dw_real * sin_d_theta + dw_imag * cos_d_theta;
+                    dw_real = tmp_real;
+                    dw_imag = tmp_imag;
+                  }
+
+                for (e2 = 0; e2 < factor; e2++)
+                  {
+
+                    int tskip = (product_1 + 1) / 2 - 1;
+                    const size_t from0 = k1 * product_1 + 2 * k + e2 * m - 1;
+                    ATOMIC tw_real, tw_imag;
+                    ATOMIC z_real, z_imag;
+
+                    if (e2 == 0)
+                      {
+                        tw_real = 1.0;
+                        tw_imag = 0.0;
+                      }
+                    else
+                      {
+                        const size_t t_index = (k - 1) + (e2 - 1) * tskip;
+                        tw_real = GSL_REAL(twiddle[t_index]);
+                        tw_imag = -GSL_IMAG(twiddle[t_index]);
+                      }
+
+                    {
+                      const ATOMIC f0_real = VECTOR(in,istride,from0);
+                      const ATOMIC f0_imag = VECTOR(in,istride,from0 + 1);
+
+                      z_real = tw_real * f0_real - tw_imag * f0_imag;
+                      z_imag = tw_real * f0_imag + tw_imag * f0_real;
+                    }
+
+                    if (e2 > 0)
+                      {
+                        const ATOMIC tmp_real = dw_real * w_real - dw_imag * w_imag;
+                        const ATOMIC tmp_imag = dw_real * w_imag + dw_imag * w_real;
+                        w_real = tmp_real;
+                        w_imag = tmp_imag;
+                      }
+
+                    sum_real += w_real * z_real - w_imag * z_imag;
+                    sum_imag += w_real * z_imag + w_imag * z_real;
+                  }
+
+                if (e1 < factor - e1)
+                  {
+                    const size_t to0 = k1 * product - 1 + 2 * e1 * product_1 + 2 * k;
+                    VECTOR(out,ostride,to0) = sum_real;
+                    VECTOR(out,ostride,to0 + 1) = sum_imag;
+                  }
+                else
+                  {
+                    const size_t to0 = k1 * product - 1 + 2 * (factor - e1) * product_1 - 2 * k;
+                    VECTOR(out,ostride,to0) = sum_real;
+                    VECTOR(out,ostride,to0 + 1) = -sum_imag;
+                  }
+
+              }
+          }
+      }
+
+
+    if (product_1 % 2 == 1)
+      return;
+
+    {
+      double tw_arg = M_PI / ((double) factor);
+      ATOMIC cos_tw_arg = cos (tw_arg);
+      ATOMIC sin_tw_arg = -sin (tw_arg);
+
+      for (k1 = 0; k1 < q; k1++)
+        {
+          ATOMIC dw_real = 1.0, dw_imag = 0.0;
+
+          for (e1 = 0; e1 < factor; e1++)
+            {
+              ATOMIC z_real, z_imag;
+
+              ATOMIC sum_real = 0.0;
+              ATOMIC sum_imag = 0.0;
+
+              ATOMIC w_real = 1.0, w_imag = 0.0;
+              ATOMIC tw_real = 1.0, tw_imag = 0.0;
+
+              if (e1 > 0)
+                {
+                  ATOMIC t_real = dw_real * cos_d_theta + dw_imag * sin_d_theta;
+                  ATOMIC t_imag = -dw_real * sin_d_theta + dw_imag * cos_d_theta;
+                  dw_real = t_real;
+                  dw_imag = t_imag;
+                }
+
+              for (e2 = 0; e2 < factor; e2++)
+                {
+
+                  if (e2 > 0)
+                    {
+                      ATOMIC tmp_real = tw_real * cos_tw_arg - tw_imag * sin_tw_arg;
+                      ATOMIC tmp_imag = tw_real * sin_tw_arg + tw_imag * cos_tw_arg;
+                      tw_real = tmp_real;
+                      tw_imag = tmp_imag;
+                    }
+
+                  if (e2 > 0)
+                    {
+                      ATOMIC tmp_real = dw_real * w_real - dw_imag * w_imag;
+                      ATOMIC tmp_imag = dw_real * w_imag + dw_imag * w_real;
+                      w_real = tmp_real;
+                      w_imag = tmp_imag;
+                    }
+
+
+                  {
+                    const size_t from0 = k1 * product_1 + 2 * k + e2 * m - 1;
+                    const ATOMIC f0_real = VECTOR(in,istride,from0);
+                    z_real = tw_real * f0_real;
+                    z_imag = tw_imag * f0_real;
+                  }
+
+                  sum_real += w_real * z_real - w_imag * z_imag;
+                  sum_imag += w_real * z_imag + w_imag * z_real;
+                }
+
+              if (e1 + 1 < factor - e1)
+                {
+                  const size_t to0 = k1 * product - 1 + 2 * e1 * product_1 + 2 * k;
+                  VECTOR(out,ostride,to0) = sum_real;
+                  VECTOR(out,ostride,to0 + 1) = sum_imag;
+                }
+              else if (e1 + 1 == factor - e1)
+                {
+                  const size_t to0 = k1 * product - 1 + 2 * e1 * product_1 + 2 * k;
+                  VECTOR(out,ostride,to0) = sum_real;
+                }
+              else
+                {
+                  const size_t to0 = k1 * product - 1 + 2 * (factor - e1) * product_1 - 2 * k;
+                  VECTOR(out,ostride,to0) = sum_real;
+                  VECTOR(out,ostride,to0 + 1) = -sum_imag;
+                }
+
+            }
+        }
+    }
+    return;
+	
+	function backwardButterfly2(output, outputOffset, outputStride, input, inputOffset, inputStride, product, n, twiddle, fStride) {
+		var m = n / 2, q = n / product, old = product / 2
+		
+		for (var i = 0; i < q; i++) {
+			var a0 = (2 * i) * q
+			var a1 = (2 * i + 2) * q - 1
+			
+			var s0 = input[inputOffset + inputStride * a0]
+			var s1 = input[inputOffset + inputStride * a1]
+			
+			var r0 = s0 + s1
+			var r1 = s0 - s1
+			
+			var a0 = q * i
+			var a1 = q * i + m
+			
+			output[outputOffset + outputStride * a0] = r0
+			output[outputOffset + outputStride * a1] = r1
+		}
+		
+		if (q == 1) { return }
+		
+		for (var i = 0; i < q / 2; i++) {
+			<%= load('t1', 'twiddle', -1, 'i') %>
+			
+			for (var j = 0; j < old; j++) {
+				var a0 = 2 * j * q + 2 * i - 1
+				var a1 = 2 * (j + 1) * q - 2 * i - 1
+				
+				<%= load('s0', 'input', 'inputOffset', 'a0', 'inputStride') %>
+				<%= load('s1', 'input', 'inputOffset', 'a1', 'inputStride') %>
+				
+				var r0_r = s0_r + s1_r
+				var r0_i = s0_i - s1_i
+				
+				var v1_r = s0_r - s1_r
+				var v1_i = s0_i + s1_i
+				
+				<%= cmul('r1', 'v1', 't1') %>
+				
+				var a0 = j * q + 2 * i - 1
+				var a1 = a0 + m
+				
+				<%= store('r0', 'output', 'outputOffset', 'a0', 'outputStride') %>
+				<%= store('r1', 'output', 'outputOffset', 'a1', 'outputStride') %>
+			}
+		}
+		
+		if (q % 2 == 1) { return }
+		
+		for (var i = 0; i < q; i++) {
+			var a0 = 2 * (i + 1) * q - 1
+			
+			<%= load('r0', 'input', 'inputOffset', 'a0', 'inputStride') %>
+			
+			<%= real('input', 'inputOffset', 'a0', 'inputStride') %> =  2 * r0_r
+			<%= imag('input', 'inputOffset', 'a1', 'inputStride') %> = -2 * r0_i
+		}
+	}
+	
+	function work(output, outputOffset, outputStride, f, fOffset, fStride, inputStride, factors, state) {
+		var p = factors.shift()
+		var m = factors.shift()
+		
+		if (m == 1) {
+			for (var i = 0; i < p * m; i++) {
+				<%= load('x0', 'f', 'fOffset', 'i', 'fStride * inputStride') %>
+				<%= store('x0', 'output', 'outputOffset', 'i', 'outputStride') %>
+			}
+		} else {
+			for (var i = 0; i < p; i++) {
+				work(output, outputOffset + outputStride * i * m, outputStride, f, fOffset + i * fStride * inputStride, fStride * p, inputStride, factors.slice(), state)
+			}
+		}
+		
+		switch (p) {
+			case 2: butterfly2(output, outputOffset, outputStride, fStride, state, m); break
+			case 3: butterfly3(output, outputOffset, outputStride, fStride, state, m); break
+			case 4: butterfly4(output, outputOffset, outputStride, fStride, state, m); break
+			default: butterfly(output, outputOffset, outputStride, fStride, state, m, p); break
+		}
+	}
+	
+	var real = function (n, inverse) {
+		var n = ~~n, inverse = !!inverse
+		
+		if (n < 1) {
+			throw new RangeError("n is outside range, should be positive integer, was `" + n + "'")
+		}
+		
+		var state = {
+			n: n,
+			inverse: inverse,
+			
+			factors: [],
+			twiddle: [],
+			scratch: new Float64Array(n)
+		}
+		
+		var t = new Float64Array(n)
+		
+		var p = 4, v = Math.floor(Math.sqrt(n))
+		
+		while (n > 1) {
+			while (n % p) {
+				switch (p) {
+					case 4: p = 2; break
+					case 2: p = 3; break
+					default: p += 2; break
+				}
+				
+				if (p > v) {
+					p = n
+				}
+			}
+			
+			n /= p
+			
+			state.factors.push(p)
+		}
+		
+		var theta = 2 * Math.PI / n, product = 1, twiddle = new Float64Array(n)
+			
+		for (var i = 0, t = 0; i < state.factors.length; i++) {
+			var phase = theta * i, factor = state.factors[i]
+			
+			var old = product, product = product * factor, q = n / product
+			
+			state.twiddle.push(new Float64Array(twiddle, t))
+			
+			if (inverse) {
+				var counter = q, multiplier = old
+			} else {
+				var counter = old, multiplier = q
+			}
+			
+			for (var j = 1; j < factor; j++) {
+				var m = 0
+					
+				for (var k = 1; k < counter / 2; k++, t++) {
+					m = (m + j * multiplier) % n
+						
+					var phase = theta * m
+						
+					<%= real('t', 'i') %> = Math.cos(phase)
+					<%= imag('t', 'i') %> = Math.sin(phase)
+				}
+			}
+		}
+		
+		this.state = state
+	}
+	
+	real.prototype.process = function(output, outputStride, input, inputStride) {
+		var outputStride = ~~outputStride, inputStride = ~~inputStride
+		
+		if (outputStride < 1) {
+			throw new RangeError("outputStride is outside range, should be positive integer, was `" + outputStride + "'")
+		}
+		
+		if (inputStride < 1) {
+			throw new RangeError("inputStride is outside range, should be positive integer, was `" + inputStride + "'")
+		}
+		
+		var product = 1, state = 0, inverse = this.state.inverse
+		
+		var n = this.state.n, factors = this.state.factors
+		var twiddle = this.state.twiddle, scratch = this.state.scratch
+		
+		for (var i = 0; i < factors.length; i++) {
+			var factor = factors[i], old = product, product = product * factor
+			
+			var q = n / product, fStride = Math.ceil(old / 2) - 1
+			
+			if (state == 0) {
+				var inBuffer = input, inStride = inputStride
+				
+				if (this.state.factors.length % 2 == 0) {
+					var outBuffer = scratch, outStride = 1, state = 1
+				} else {
+					var outBuffer = output, outStride = outputStride, state = 2
+				}
+			} else if (state == 1) {
+				var inBuffer = scratch, inStride = 1, outBuffer = output, outStride = outputStride, state = 2
+			} else if (state == 2) {
+				var inBuffer = output, inStride = outputStride, outBuffer = scratch, outStride = 1, state = 1
+			} else {
+				throw new RangeError("state somehow is not in the range (0 .. 2)")
+			}
+			
+			if (inverse) {
+				switch (factor) {
+				case 2: backwardButterfly2(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				case 3: backwardButterfly3(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				case 4: backwardButterfly3(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				case 5: backwardButterfly3(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				default: backwardButterfly(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride, factor); break
+				}
+			} else {
+				switch (factor) {
+				case 2: forwardButterfly2(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				case 3: forwardButterfly3(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				case 4: forwardButterfly3(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				case 5: forwardButterfly3(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride); break
+				default: forwardButterfly(outBuffer, 0, outStride, inBuffer, 0, inStride, product, n, twiddle[i], fStride, factor); break
+				}
+			}
+		}
+	}
+	
+	namespace.real = real
+}(FFT)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/src/real.rb	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,47 @@
+def load(offset = 0, i = nil, stride = nil)
+  "#{real(x, offset, i, stride)}"
+end
+
+def creal(x, offset = 0, i = nil, stride = nil)
+  if stride
+    "#{x}[2 * ((#{offset}) + (#{stride}) * (#{i}))]"
+  elsif i
+    "#{x}[2 * ((#{offset}) + (#{i}))]"
+  elsif offset
+    "#{x}[2 * (#{offset})]"
+  else
+    "#{x}[0]"
+  end
+end
+
+def cimag(x, offset = 0, i = nil, stride = nil)
+  if stride
+    "#{x}[2 * ((#{offset}) + (#{stride}) * (#{i})) + 1]"
+  elsif i
+    "#{x}[2 * ((#{offset}) + (#{i})) + 1]"
+  elsif offset
+    "#{x}[2 * (#{offset}) + 1]"
+  else
+    "#{x}[1]"
+  end
+end
+
+def cload(value, x, offset = 0, i = nil, stride = nil)
+  "var #{value}_r = #{real(x, offset, i, stride)}, #{value}_i = #{imag(x, offset, i, stride)}"
+end
+
+def cstore(value, x, offset = 0, i = nil, stride = nil)
+  "#{real(x, offset, i, stride)} = #{value}_r, #{imag(x, offset, i, stride)} = #{value}_i"
+end
+
+def cadd(result, a, b)
+  "var #{result}_r = #{a}_r + #{b}_r, #{result}_i = #{a}_i + #{b}_i"
+end
+
+def csub(result, a, b)
+  "var #{result}_r = #{a}_r - #{b}_r, #{result}_i = #{a}_i - #{b}_i"
+end
+
+def cmul(result, a, b)
+  "var #{result}_r = #{a}_r * #{b}_r - #{a}_i * #{b}_i, #{result}_i = #{a}_r * #{b}_i + #{a}_i * #{b}_r"
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/fft.js/test.html	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,171 @@
+<!DOCTYPE html>
+
+<head>
+	<title>fft.js</title>
+	<script src='lib/real.js'></script>
+	<script src='lib/complex.js'></script>
+	<script>
+	
+	function kahanDiff(x, xOffset, xStride, y, yOffset, yStride, n, run) {
+		var sum = 0.0, compensation = 0.0
+		
+		for (var i = 0; i < x.length / run / xStride; i++) {
+			for (var j = 0; j < run; j++) {
+				var v = Math.abs(x[xOffset + run * xStride * i + j] - y[yOffset + run * yStride * i + j] / n) - compensation
+			
+				var t = sum + v
+			
+				compensation = (t - sum) - v
+			
+				sum = t
+				
+				if (isNaN(sum)) {
+					debugger
+				}
+			}
+		}
+		
+		return sum
+	}
+	
+	function kahanStrideDiff(x, xOffset, xStride, run) {
+		var sum = 0.0, compensation = 0.0
+		
+		for (var i = 0; i < x.length / run / xStride; i++) {
+			for (var j = run; j < run * xStride; j++) {
+				var v = Math.abs(x[xOffset + run * xStride * i + j]) - compensation
+			
+				var t = sum + v
+			
+				compensation = (t - sum) - v
+			
+				sum = t
+				
+				if (isNaN(sum)) {
+					debugger
+				}
+			}
+		}
+		
+		return sum
+	}
+	
+	function kahanOffsetDiff(x, xOffset) {
+		var sum = 0.0, compensation = 0.0
+		
+		for (var i = 0; i < xOffset; i++) {
+			var v = Math.abs(x[i]) - compensation
+			
+			var t = sum + v
+			
+			compensation = (t - sum) - v
+			
+			sum = t
+				
+			if (isNaN(sum)) {
+				debugger
+			}
+		}
+		
+		return sum
+	}
+	
+	function testComplex(n) {
+		var i = new Float64Array(2 * n), o1 = new Float64Array(2 * n), o2 = new Float64Array(2 * n)
+		var fft = new FFT.complex(n, false), ifft = new FFT.complex(n, true)
+		
+		for (var j = 0; j < (2 * n); j++) {
+			i[j] = Math.random()
+		}
+		
+		fft.process(o1, 0, 1, i, 0,  1)
+		ifft.process(o2, 0, 1, o1, 0, 1)
+		
+		return kahanDiff(i, 0, 1, o2, 0, 1, n, 2)
+	}
+	
+	function testComplexStride(n) {
+		var iStride = ~~(12 * Math.random()) + 1, o1Stride = ~~(16 * Math.random()) + 1, o2Stride = ~~(5 * Math.random()) + 1
+		
+		var i = new Float64Array(2 * iStride * n), o1 = new Float64Array(2 * o1Stride * n), o2 = new Float64Array(2 * o2Stride * n)
+		var fft = new FFT.complex(n, false), ifft = new FFT.complex(n, true)
+		
+		for (var j = 0; j < n; j++) {
+			i[2 * j * iStride + 0] = Math.random()
+			i[2 * j * iStride + 1] = Math.random()
+		}
+		
+		fft.process(o1, 0, o1Stride, i, 0, iStride)
+		ifft.process(o2, 0, o2Stride, o1, 0, o1Stride)
+		
+		return [kahanDiff(i, 0, iStride, o2, 0, o2Stride, n, 2), kahanStrideDiff(o2, 0, o2Stride, 2)]
+	}
+	
+	function testRealToComplex(n) {
+		var i = new Float64Array(n), o1 = new Float64Array(2 * n), o2 = new Float64Array(2 * n)
+		var fft = new FFT.complex(n, false), ifft = new FFT.complex(n, true)
+		
+		for (var j = 0; j < n; j++) {
+			i[j] = Math.random()
+		}
+		
+		fft.simple(o1, i, 'real')
+		ifft.simple(o2, o1)
+		
+		return [kahanDiff(i, 0, 1, o2, 0, 2, n, 1), kahanStrideDiff(o2, 2, 1)]
+	}
+	
+	function testRealToComplexWithOffset(n) {
+		var iOffset = ~~(12 * Math.random()), o1Offset = ~~(12 * Math.random()), o2Offset = ~~(12 * Math.random())
+		
+		var i = new Float64Array(iOffset + n), o1 = new Float64Array(o1Offset + 2 * n), o2 = new Float64Array(o2Offset + 2 * n)
+		var fft = new FFT.complex(n, false), ifft = new FFT.complex(n, true)
+		
+		for (var j = 0; j < n; j++) {
+			i[iOffset + j] = Math.random()
+		}
+		
+		fft.process(o1, o1Offset, 1, i, iOffset, 1, 'real')
+		ifft.process(o2, o2Offset, 1, o1, o1Offset, 1)
+		
+		return [kahanDiff(i, 1, o2, 2, n, 1), kahanStrideDiff(o2, 2, 1), kahanOffsetDiff(o1, o1Offset), kahanOffsetDiff(o2, o2Offset)]
+	}
+	
+	
+	var count = 0
+	for (var i = 2; i < 100; i++) {
+		var v = testComplex(i)
+		if (isNaN(v) || v > 1e-12) {
+			console.log('Complex', i, v), count++
+		}
+	}
+	
+	for (var i = 2; i < 100; i++) {
+		var v = testComplexStride(i)
+		if (isNaN(v[0]) || v[0] > 1e-12 || v[1] > 0) {
+			console.log('Complex Strided', i, v), count++
+		}
+	}
+	
+	for (var i = 2; i < 100; i++) {
+		var v = testRealToComplex(i)
+		if (isNaN(v[0]) || isNaN(v[1]) || v[0] > 1e-12 || v[1] > 1e-12) {
+			console.log('Real to Complex', i, v), count++
+		}
+	}
+	
+	for (var i = 2; i < 100; i++) {
+		var v = testRealToComplexWithOffset(i)
+		if (isNaN(v[0]) || isNaN(v[1]) || isNaN(v[2]) || isNaN(v[3]) || v[0] > 1e-12 || v[1] > 1e-12 || v[2] > 0 || v[3] > 0) {
+			console.log('Real to Complex Offset', i, v), count++
+		}
+	}
+	
+	var f0 = new Float64Array(32), r0 = new Float64Array(64), fft = new FFT.complex(32, false); f0[0] = 1, f0[31] = 1
+	fft.simple(r0, f0, 'real')
+	
+	console.log(r0)
+	
+	console.log("Failcount", count)
+	</script>
+</head>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/index.html	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,73 @@
+<html>
+  <head>
+    
+  <meta charset="UTF-8">
+
+  <style type="text/css">
+    body { margin: 5%; }
+    table, td, th { border: 0.1em solid #e0e0e0; border-collapse: collapse }
+    td, th { padding: 0.5em }
+  </style>
+  
+  <script src="nayuki/fft.js"></script>
+  <script src="nayuki-obj/fft.js"></script>
+  <script src="fft.js/lib/complex.js"></script>
+  <script src="jsfft/lib/complex_array.js"></script>
+  <script src="jsfft/lib/fft.js"></script>
+  <script src="cross/Cross.js"></script>
+  <script src="cross/FFT.js"></script>
+  <script src="kissfft/KissFFT.js"></script>
+  <script src="kissfft/FFT.js"></script>
+  <script src="fftw/FFTW.js"></script>
+  <script src="fftw/FFT.js"></script>
+  <script src="test.js"></script>
+
+  </head>
+<body>
+
+  <h3>Results</h3>
+
+  <p id="test-description"></p>
+  
+  <table>
+    <tr>
+      <th>Implementation</th><th>Result</th><th>Time (first half)</th><th>Time (second half)</th><th>Rate (second half)</th>
+    </tr>
+    <tr>
+      <td>Nayuki</td><td id="nayuki-result"></td><td id="nayuki-1"></td><td id="nayuki-2"></td><td id="nayuki-itr"></td>
+    </tr><tr> 
+      <td>Nayuki (obj)</td><td id="nayukiobj-result"></td><td id="nayukiobj-1"></td><td id="nayukiobj-2"></td><td id="nayukiobj-itr"></td>
+    </tr><tr> 
+      <td>Nockert</td><td id="nockert-result"></td><td id="nockert-1"></td><td id="nockert-2"></td><td id="nockert-itr"></td>
+    </tr><tr>
+      <td>Dntj</td><td id="dntj-result"></td><td id="dntj-1"></td><td id="dntj-2"></td><td id="dntj-itr"></td>
+    </tr><tr>
+      <td>Cross</td><td id="cross-result"></td><td id="cross-1"></td><td id="cross-2"></td><td id="cross-itr"></td>
+    </tr><tr>
+      <td>KissFFT</td><td id="kissfft-result"></td><td id="kissfft-1"></td><td id="kissfft-2"></td><td id="kissfft-itr"></td>
+    </tr><tr>
+      <td>FFTW</td><td id="fftw-result"></td><td id="fftw-1"></td><td id="fftw-2"></td><td id="fftw-itr"></td>
+    </tr>
+  </table>
+
+  <h3>Notes</h3>
+
+  <ul>
+    <li><b>Nayuki</b>: in-place single-precision complex-complex. Around 7kb.</li>
+    <li><b>Nayuki (obj)</b>: Nayuki with the sin/cos tables pre-calculated on object construction. Around 4kb.</li>
+    <li><b>Nockert</b>: double-precision real-complex. Around 25kb.</li>
+    <li><b>Dntj</b>: double-precision complex-complex. Forward
+    transform is scaled and I've scaled it back again here, which may
+    introduce rounding error. Around 10kb.</li>
+    <li><b>Cross</b>: double-precision real-complex in C, compiled
+    with Emscripten. This is considered a slow implementation amongst
+    native code ones. Around 60kb.</li>
+    <li><b>KissFFT</b>: single-precision real-complex in C, compiled
+    with Emscripten. A reasonably sophisticated implementation. Around
+    70kb.</li>
+    <li><b>FFTW</b>: single-precision real-complex in C, compiled with
+    Emscripten. GPL licensed. Around 3Mb.</li>
+  </ul>
+  
+</body>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/jsfft/LICENSE	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2012 Nick Jones
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/jsfft/README.md	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,45 @@
+# jsfft
+
+Small, efficient Javascript FFT implementation for node or the browser.
+
+## Usage
+
+JSFFT ships with a **complex_array** and a **fft** module.
+
+```javascript
+var data = new complex_array.ComplexArray(512)
+// Use the in-place mapper to populate the data.
+data.map(function(value, i, n) {
+  value.real = (i > n/3 && i < 2*n/3) ? 1 : 0
+})
+```
+
+Including the **fft** module attaches FFT methods to ComplexArray.  FFT and
+InvFFT perform in-place transforms on the underlying data:
+
+```javascript
+var frequencies = data.FFT()
+// Implement a low-pass filter using the in-place mapper.
+frequencies.map(function(frequency, i, n) {
+  if (i > n/5 && i < 4*n/5) {
+    frequency.real = 0
+    frequency.imag = 0
+  }
+})
+```
+
+Alternatively, frequency-space filters can be implemented via the frequencyMap:
+
+```javascript
+var filtered = data.frequencyMap(function(frequency, i, n) {
+  if (i > n/5 && i < 4*n/5) {
+    frequency.real = 0
+    frequency.imag = 0
+  }
+})
+```
+
+## Other Implementations
+
+[DSP](https://github.com/corbanbrook/dsp.js) is a full featured Digital Signal
+Processing library in JS which includes a JS FFT implementation.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/jsfft/example/example.css	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,34 @@
+body {
+  font-family: sans-serif;
+}
+
+canvas {
+  background-color: #eee;
+}
+
+div.panel {
+  float: left;
+  width: 312px;
+  height: 312px;
+  border: 1px solid #ccc;
+  margin: 1%;
+}
+
+div.panel div.example {
+  width: 256px;
+  height: 128px;
+  left: 28px;
+  position: relative;
+}
+
+div.explanation {
+  left: 0;
+  right: 0;
+  height: 50%;
+  padding: 20px 10px 0 10px;
+  overflow: scroll;
+}
+
+pre, code {
+  font-size: 85%;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/jsfft/example/index.html	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <script src="../lib/complex_array.js"></script>
+  <script src="../lib/fft.js"></script>
+  <link rel="stylesheet" href="example.css">
+  <title></title>
+</head>
+<body>
+  <div class="panel">
+    <div id="original" class="example"></div>
+    <div class="explanation">
+      Sample data
+      <pre>
+var data = new ComplexArray(128)
+data.map(function(value, i, n) {
+  value.real = (i > n/3 &amp;&amp; i &lt; 2*n/3) ? 1 : 0
+})
+      </pre>
+    </div>
+  </div>
+  <div class="panel">
+    <div id="fft" class="example"></div>
+    <div class="explanation">
+      Transform (in place):<br>
+      <code>
+        data.FFT()
+      </code>
+    </div>
+  </div>
+  <div  class="panel">
+    <div id="fft_filtered" class="example"></div>
+    <div class="explanation">
+      Simple low pass filter:
+      <pre>
+data.map(function(freq, i, n) {
+  if (i > n/5 &amp;&amp; i &lt; 4*n/5) {
+    freq.real = 0
+    freq.imag = 0
+  }
+})
+      </pre>
+    </div>
+  </div>
+  <div class="panel">
+    <div id="original_filtered" class="example"></div>
+    <div class="explanation">
+      Transform back:<br>
+      <code>
+        data.InvFFT()
+      </code>
+    </div>
+  </div>
+  <div class="panel">
+    <div id="all_in_one" class="example"></div>
+    <div class="explanation">
+      ... or all in one step
+      <pre>
+data.frequencyMap(function(freq, i, n) {
+  if (i > n/5 &amp;&amp; i &lt; 4*n/5) {
+    freq.real = 0
+    freq.imag = 0
+  }
+})
+      </pre>
+    </div>
+  </div>
+  <script>
+    !function() {
+      function drawToCanvas(element_id, data) {
+        var
+          element = document.getElementById(element_id)
+          canvas = document.createElement('canvas'),
+          context = canvas.getContext('2d'),
+          width = element.clientWidth,
+          height = element.clientHeight,
+          n = data.length
+
+        canvas.width = width
+        canvas.height = height
+        element.appendChild(canvas)
+
+        context.strokeStyle = 'blue'
+        context.beginPath()
+        data.forEach(function(c_value, i) {
+          context.lineTo(
+            i * width / n,
+            height/2 * (1.5 - c_value.real)
+          )
+        })
+        context.stroke()
+      }
+
+      window.onload = function() {
+        var
+          data = new complex_array.ComplexArray(128)
+
+        data.map(function(value, i, n) {
+          value.real = (i > n/3 && i < 2*n/3) ? 1 : 0
+        })
+
+        drawToCanvas('original', data)
+
+        data.FFT()
+        drawToCanvas('fft', data)
+        data.map(function(freq, i, n) {
+          if (i > n/5 && i < 4*n/5) {
+            freq.real = 0
+            freq.imag = 0
+          }
+        })
+        drawToCanvas('fft_filtered', data)
+        drawToCanvas('original_filtered', data.InvFFT())
+
+        drawToCanvas('all_in_one', data.frequencyMap(function(freq, i, n) {
+          if (i > n/5 && i < 4*n/5) {
+            freq.real = 0
+            freq.imag = 0
+          }
+        }))
+      }
+    }()
+  </script>
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/jsfft/lib/complex_array.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,116 @@
+'use strict';
+
+!function(exports, undefined) {
+
+  var
+    // If the typed array is unspecified, use this.
+    DefaultArrayType = Float32Array,
+    // Simple math functions we need.
+    sqrt = Math.sqrt,
+    sqr = function(number) {return Math.pow(number, 2)},
+    // Internal convenience copies of the exported functions
+    isComplexArray,
+    ComplexArray
+
+  exports.isComplexArray = isComplexArray = function(obj) {
+    return obj !== undefined &&
+      obj.hasOwnProperty !== undefined &&
+      obj.hasOwnProperty('real') &&
+      obj.hasOwnProperty('imag')
+  }
+
+  exports.ComplexArray = ComplexArray = function(other, opt_array_type){
+    if (isComplexArray(other)) {
+      // Copy constuctor.
+      this.ArrayType = other.ArrayType
+      this.real = new this.ArrayType(other.real)
+      this.imag = new this.ArrayType(other.imag)
+    } else {
+      this.ArrayType = opt_array_type || DefaultArrayType
+      // other can be either an array or a number.
+      this.real = new this.ArrayType(other)
+      this.imag = new this.ArrayType(this.real.length)
+    }
+
+    this.length = this.real.length
+  }
+
+  ComplexArray.prototype.toString = function() {
+    var components = []
+
+    this.forEach(function(c_value, i) {
+      components.push(
+        '(' +
+        c_value.real.toFixed(2) + ',' +
+        c_value.imag.toFixed(2) +
+        ')'
+      )
+    })
+
+    return '[' + components.join(',') + ']'
+  }
+
+  // In-place mapper.
+  ComplexArray.prototype.map = function(mapper) {
+    var
+      i,
+      n = this.length,
+      // For GC efficiency, pass a single c_value object to the mapper.
+      c_value = {}
+
+    for (i = 0; i < n; i++) {
+      c_value.real = this.real[i]
+      c_value.imag = this.imag[i]
+      mapper(c_value, i, n)
+      this.real[i] = c_value.real
+      this.imag[i] = c_value.imag
+    }
+
+    return this
+  }
+
+  ComplexArray.prototype.forEach = function(iterator) {
+    var
+      i,
+      n = this.length,
+      // For consistency with .map.
+      c_value = {}
+
+    for (i = 0; i < n; i++) {
+      c_value.real = this.real[i]
+      c_value.imag = this.imag[i]
+      iterator(c_value, i, n)
+    }
+  }
+
+  ComplexArray.prototype.conjugate = function() {
+    return (new ComplexArray(this)).map(function(value) {
+      value.imag *= -1
+    })
+  }
+
+  // Helper so we can make ArrayType objects returned have similar interfaces
+  //   to ComplexArrays.
+  function iterable(obj) {
+    if (!obj.forEach)
+      obj.forEach = function(iterator) {
+        var i, n = this.length
+
+        for (i = 0; i < n; i++)
+          iterator(this[i], i, n)
+      }
+
+    return obj
+  }
+
+  ComplexArray.prototype.magnitude = function() {
+    var mags = new this.ArrayType(this.length)
+
+    this.forEach(function(value, i) {
+      mags[i] = sqrt(sqr(value.real) + sqr(value.imag))
+    })
+
+    // ArrayType will not necessarily be iterable: make it so.
+    return iterable(mags)
+  }
+}(typeof exports === 'undefined' && (this.complex_array = {}) || exports)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/jsfft/lib/fft.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,225 @@
+'use strict';
+
+!function(exports, complex_array) {
+
+  var
+    ComplexArray = complex_array.ComplexArray,
+    // Math constants and functions we need.
+    PI = Math.PI,
+    SQRT1_2 = Math.SQRT1_2,
+    sqrt = Math.sqrt,
+    cos = Math.cos,
+    sin = Math.sin
+
+  ComplexArray.prototype.FFT = function() {
+    return FFT(this, false)
+  }
+
+  exports.FFT = function(input) {
+    return ensureComplexArray(input).FFT()
+  }
+
+  ComplexArray.prototype.InvFFT = function() {
+    return FFT(this, true)
+  }
+
+  exports.InvFFT = function(input) {
+    return ensureComplexArray(input).InvFFT()
+  }
+
+  // Applies a frequency-space filter to input, and returns the real-space
+  // filtered input.
+  // filterer accepts freq, i, n and modifies freq.real and freq.imag.
+  ComplexArray.prototype.frequencyMap = function(filterer) {
+    return this.FFT().map(filterer).InvFFT()
+  }
+
+  exports.frequencyMap = function(input, filterer) {
+    return ensureComplexArray(input).frequencyMap(filterer)
+  }
+
+  function ensureComplexArray(input) {
+    return complex_array.isComplexArray(input) && input ||
+        new ComplexArray(input)
+  }
+
+  function FFT(input, inverse) {
+    var n = input.length
+
+    if (n & (n - 1)) {
+      return FFT_Recursive(input, inverse)
+    } else {
+      return FFT_2_Iterative(input, inverse)
+    }
+  }
+
+  function FFT_Recursive(input, inverse) {
+    var
+      n = input.length,
+      // Counters.
+      i, j,
+      output,
+      // Complex multiplier and its delta.
+      f_r, f_i, del_f_r, del_f_i,
+      // Lowest divisor and remainder.
+      p, m,
+      normalisation,
+      recursive_result,
+      _swap, _real, _imag
+
+    if (n === 1) {
+      return input
+    }
+
+    output = new ComplexArray(n, input.ArrayType)
+
+    // Use the lowest odd factor, so we are able to use FFT_2_Iterative in the
+    // recursive transforms optimally.
+    p = LowestOddFactor(n)
+    m = n / p
+    normalisation = 1 / sqrt(p)
+    recursive_result = new ComplexArray(m, input.ArrayType)
+
+    // Loops go like O(n Σ p_i), where p_i are the prime factors of n.
+    // for a power of a prime, p, this reduces to O(n p log_p n)
+    for(j = 0; j < p; j++) {
+      for(i = 0; i < m; i++) {
+        recursive_result.real[i] = input.real[i * p + j]
+        recursive_result.imag[i] = input.imag[i * p + j]
+      }
+      // Don't go deeper unless necessary to save allocs.
+      if (m > 1) {
+        recursive_result = FFT(recursive_result, inverse)
+      }
+
+      del_f_r = cos(2*PI*j/n)
+      del_f_i = (inverse ? -1 : 1) * sin(2*PI*j/n)
+      f_r = 1
+      f_i = 0
+
+      for(i = 0; i < n; i++) {
+        _real = recursive_result.real[i % m]
+        _imag = recursive_result.imag[i % m]
+
+        output.real[i] += f_r * _real - f_i * _imag
+        output.imag[i] += f_r * _imag + f_i * _real
+
+        _swap = f_r * del_f_r - f_i * del_f_i
+        f_i = f_r * del_f_i + f_i * del_f_r
+        f_r = _swap
+      }
+    }
+
+    // Copy back to input to match FFT_2_Iterative in-placeness
+    // TODO: faster way of making this in-place?
+    for(i = 0; i < n; i++) {
+      input.real[i] = normalisation * output.real[i]
+      input.imag[i] = normalisation * output.imag[i]
+    }
+
+    return input
+  }
+
+  function FFT_2_Iterative(input, inverse) {
+    var
+      n = input.length,
+      // Counters.
+      i, j,
+      output, output_r, output_i,
+      // Complex multiplier and its delta.
+      f_r, f_i, del_f_r, del_f_i, temp,
+      // Temporary loop variables.
+      l_index, r_index,
+      left_r, left_i, right_r, right_i,
+      // width of each sub-array for which we're iteratively calculating FFT.
+      width
+
+    output = BitReverseComplexArray(input)
+    output_r = output.real
+    output_i = output.imag
+    // Loops go like O(n log n):
+    //   width ~ log n; i,j ~ n
+    width = 1
+    while (width < n) {
+      del_f_r = cos(PI/width)
+      del_f_i = (inverse ? -1 : 1) * sin(PI/width)
+      for (i = 0; i < n/(2*width); i++) {
+        f_r = 1
+        f_i = 0
+        for (j = 0; j < width; j++) {
+          l_index = 2*i*width + j
+          r_index = l_index + width
+
+          left_r = output_r[l_index]
+          left_i = output_i[l_index]
+          right_r = f_r * output_r[r_index] - f_i * output_i[r_index]
+          right_i = f_i * output_r[r_index] + f_r * output_i[r_index]
+
+          output_r[l_index] = SQRT1_2 * (left_r + right_r)
+          output_i[l_index] = SQRT1_2 * (left_i + right_i)
+          output_r[r_index] = SQRT1_2 * (left_r - right_r)
+          output_i[r_index] = SQRT1_2 * (left_i - right_i)
+          temp = f_r * del_f_r - f_i * del_f_i
+          f_i = f_r * del_f_i + f_i * del_f_r
+          f_r = temp
+        }
+      }
+      width <<= 1
+    }
+
+    return output
+  }
+
+  function BitReverseIndex(index, n) {
+    var bitreversed_index = 0
+
+    while (n > 1) {
+      bitreversed_index <<= 1
+      bitreversed_index += index & 1
+      index >>= 1
+      n >>= 1
+    }
+    return bitreversed_index
+  }
+
+  function BitReverseComplexArray(array) {
+    var n = array.length,
+        flips = {},
+        swap,
+        i
+
+    for(i = 0; i < n; i++) {
+      var r_i = BitReverseIndex(i, n)
+
+      if (flips.hasOwnProperty(i) || flips.hasOwnProperty(r_i)) continue
+
+      swap = array.real[r_i]
+      array.real[r_i] = array.real[i]
+      array.real[i] = swap
+
+      swap = array.imag[r_i]
+      array.imag[r_i] = array.imag[i]
+      array.imag[i] = swap
+
+      flips[i] = flips[r_i] = true
+    }
+
+    return array
+  }
+
+  function LowestOddFactor(n) {
+    var factor = 3,
+        sqrt_n = sqrt(n)
+
+    while(factor <= sqrt_n) {
+      if (n % factor === 0) return factor
+      factor = factor + 2
+    }
+    return n
+  }
+
+}(
+  typeof exports === 'undefined' && (this.fft = {}) || exports,
+  typeof require === 'undefined' && (this.complex_array) ||
+    require('./complex_array')
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/jsfft/lib/fft_image.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,87 @@
+'use strict';
+
+!function(complex_array, fft) {
+
+  var ComplexArray = complex_array.ComplexArray
+
+  fft.FFTImageDataRGBA = function(data, nx, ny) {
+    var rgb = splitRGB(data)
+
+    return mergeRGB(
+      FFT2D(new ComplexArray(rgb[0], Float32Array), nx, ny),
+      FFT2D(new ComplexArray(rgb[1], Float32Array), nx, ny),
+      FFT2D(new ComplexArray(rgb[2], Float32Array), nx, ny)
+    )
+  }
+
+  function splitRGB(data) {
+    var n = data.length / 4,
+        r = new Uint8ClampedArray(n),
+        g = new Uint8ClampedArray(n),
+        b = new Uint8ClampedArray(n),
+        i
+
+    for(i = 0; i < n; i++) {
+      r[i] = data[4 * i    ]
+      g[i] = data[4 * i + 1]
+      b[i] = data[4 * i + 2]
+    }
+
+    return [r, g, b]
+  }
+
+  function mergeRGB(r, g, b) {
+    var n = r.length,
+        output = new ComplexArray(n * 4),
+        i
+
+    for(i = 0; i < n; i++) {
+      output.real[4 * i    ] = r.real[i]
+      output.imag[4 * i    ] = r.imag[i]
+      output.real[4 * i + 1] = g.real[i]
+      output.imag[4 * i + 1] = g.imag[i]
+      output.real[4 * i + 2] = b.real[i]
+      output.imag[4 * i + 2] = b.imag[i]
+    }
+
+    return output
+  }
+
+  function FFT2D(input, nx, ny, inverse) {
+    var i, j,
+        transform = inverse ? 'InvFFT' : 'FFT',
+        output = new ComplexArray(input.length, input.ArrayType),
+        row = new ComplexArray(nx, input.ArrayType),
+        col = new ComplexArray(ny, input.ArrayType)
+
+    for(j = 0; j < ny; j++) {
+      row.map(function(v, i) {
+        v.real = input.real[i + j * nx]
+        v.imag = input.imag[i + j * nx]
+      })
+      row[transform]().forEach(function(v, i) {
+        output.real[i + j * nx] = v.real
+        output.imag[i + j * nx] = v.imag
+      })
+    }
+
+    for(i = 0; i < nx; i++) {
+      col.map(function(v, j) {
+        v.real = output.real[i + j * nx]
+        v.imag = output.imag[i + j * nx]
+      })
+      col[transform]().forEach(function(v, j) {
+        output.real[i + j * nx] = v.real
+        output.imag[i + j * nx] = v.imag
+      })
+    }
+
+    return output
+  }
+
+}(
+  typeof require === 'undefined' && (this.complex_array) ||
+    require('./complex_array'),
+  typeof require === 'undefined' && (this.fft) ||
+    require('./fft')
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/jsfft/package.json	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,23 @@
+{
+  "name": "jsfft",
+  "version": "0.0.1",
+  "author": "Nick Jones",
+  "description": "A lightweight FFT implementation for Javascript",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/dntj/jsfft.git"
+  },
+  "engines": {
+    "node": "*"
+  },
+
+  "main": "./lib/fft",
+
+  "scripts": {
+    "test": "mocha"
+  },
+
+  "devDependencies": {
+    "mocha": ">=1.1.0"
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/jsfft/test/test-complex_array.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,125 @@
+var assert = require('assert'),
+  complex_array = require('../lib/complex_array'),
+  ComplexArray = complex_array.ComplexArray
+
+function assertArrayEquals(first, second) {
+  var message = first + ' != ' + second
+
+  first.forEach(function(item, i) {
+    assert.equal(item, second[i], message)
+  })
+}
+
+describe('isComplexArray', function() {
+  it('should correctly identify complex arrays', function() {
+    assert.ok(!complex_array.isComplexArray(1))
+    assert.ok(!complex_array.isComplexArray([1,2,3]))
+    assert.ok(!complex_array.isComplexArray('string'))
+
+    assert.ok(complex_array.isComplexArray(new ComplexArray(1)))
+  })
+})
+
+describe('ComplexArray', function() {
+  describe('#__constructor__()', function() {
+    it('should construct from a number', function() {
+      var a = new ComplexArray(10)
+      assert.equal(10, a.real.length)
+      assert.equal(10, a.imag.length)
+      assert.equal(0, a.real[0])
+      assert.equal(0, a.imag[0])
+    })
+
+    it('should construct from a number with a type', function() {
+      var a = new ComplexArray(10, Int32Array)
+      assert.equal(Int32Array, a.ArrayType)
+      assert.equal(10, a.real.length)
+      assert.equal(10, a.imag.length)
+      assert.equal(0, a.real[0])
+      assert.equal(0, a.imag[0])
+    })
+
+    it('should contruct from a real array', function() {
+      var a = new ComplexArray([1, 2])
+      assertArrayEquals([1, 2], a.real)
+      assertArrayEquals([0, 0], a.imag)
+    })
+
+    it('should contruct from a real array with a type', function() {
+      var a = new ComplexArray([1, 2], Int32Array)
+      assert.equal(Int32Array, a.ArrayType)
+      assertArrayEquals([1, 2], a.real)
+      assertArrayEquals([0, 0], a.imag)
+    })
+
+    it('should contruct from another complex array', function() {
+      var a = new ComplexArray(new ComplexArray([1, 2]))
+      assertArrayEquals([1, 2], a.real)
+      assertArrayEquals([0, 0], a.imag)
+    })
+  })
+
+  describe('#map()', function() {
+    it('should alter all values', function() {
+      var a = new ComplexArray([1, 2])
+
+      a.map(function(value, i) {
+        value.real *= 10
+        value.imag = i
+      })
+      assertArrayEquals([10, 20], a.real)
+      assertArrayEquals([0, 1], a.imag)
+    })
+  })
+
+  describe('#forEach()', function() {
+    it('should touch every value', function() {
+      var
+        a = new ComplexArray([1, 2]),
+        sum = 0
+
+      a.imag[0] = 4
+      a.imag[1] = 8
+      a.forEach(function(value, i) {
+        sum += value.real
+        sum += value.imag
+      })
+      assert.equal(15, sum)
+    })
+  })
+
+  describe('#conjugate()', function() {
+    it('should multiply a number', function() {
+      var
+        a = new ComplexArray([1, 2]),
+        b
+
+      a.imag[0] = 1
+      a.imag[1] = -2
+      b = a.conjugate()
+      assertArrayEquals([1, 2], b.real)
+      assertArrayEquals([-1, 2], b.imag)
+    })
+  })
+
+  describe('#magnitude()', function() {
+    it('should give the an array of magnitudes', function() {
+      var a = new ComplexArray([1, 3])
+
+      a.imag[0] = 0
+      a.imag[1] = 4
+      assertArrayEquals([1, 5], a.magnitude())
+    })
+
+    it('should return an iterable ArrayType object', function() {
+      var
+        sum = 0,
+        a = new ComplexArray([1, 2])
+
+      a.magnitude().forEach(function(value, i) {
+        sum += value
+      })
+      assert.equal(3, sum)
+    })
+  })
+})
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/jsfft/test/test-fft.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,136 @@
+require('./test_helper')
+
+var ComplexArray = require('../lib/complex_array').ComplexArray
+
+describe('fft', function() {
+  describe('#FFT()', function() {
+    describe('on N=4 Arrays', function() {
+      it('should return a single frequency given a constant array', function() {
+        assertFFTMatches([1, 1, 1, 1], new ComplexArray([2, 0, 0, 0]))
+      })
+
+      it('should return flat with a delta function input', function() {
+        assertFFTMatches([1, 0, 0, 0], new ComplexArray([0.5, 0.5, 0.5, 0.5]))
+      })
+
+      it('should return a single high freq', function() {
+        assertFFTMatches([1, -1, 1, -1], new ComplexArray([0, 0, 2, 0]))
+      })
+
+      it('should return a single low freq', function() {
+        assertFFTMatches([1, 0, -1, -0], new ComplexArray([0, 1, 0, 1]))
+      })
+
+      it('should return a high freq and DC', function() {
+        assertFFTMatches([1, 0, 1, 0], new ComplexArray([1, 0, 1, 0]))
+      })
+    })
+
+    describe('on N=6 Arrays', function() {
+      it('should return a single frequency given a constant array', function() {
+        assertFFTMatches(
+          [1, 1, 1, 1, 1, 1],
+          new ComplexArray([Math.sqrt(6), 0, 0, 0, 0, 0])
+        )
+      })
+
+      it('should return flat with a delta function input', function() {
+        var a = 1 / Math.sqrt(6)
+        assertFFTMatches(
+          [1, 0, 0, 0, 0, 0],
+          new ComplexArray([a, a, a, a, a, a])
+        )
+      })
+    })
+
+    describe('on N=`prime` Arrays', function() {
+      it('should match the DFT', function() {
+        var a = new ComplexArray(13)
+
+        a.map(function(value) {
+          value.real = Math.random()
+          value.imag = Math.random()
+        })
+
+        assertFFTMatchesDFT(a)
+      })
+    })
+
+    describe('on N=512 Arrays', function() {
+      it('should match the DFT', function() {
+        var a = new ComplexArray(512)
+
+        a.map(function(value) {
+          value.real = Math.random()
+          value.imag = Math.random()
+        })
+
+        assertFFTMatchesDFT(a)
+      })
+    })
+
+    describe('on N=900 Arrays', function() {
+      it('should match the DFT', function() {
+        var a = new ComplexArray(900)
+
+        a.map(function(value) {
+          value.real = Math.random()
+          value.imag = Math.random()
+        })
+
+        assertFFTMatchesDFT(a)
+      })
+    })
+  })
+
+  describe('#frequencyMap()', function() {
+    it('should not modify the original', function() {
+      var
+        original = new ComplexArray([1, 2, 3, 4]),
+        filtered = original.frequencyMap(function() {})
+
+      assertComplexArraysAlmostEqual(original, filtered)
+    })
+
+    it('should halve the original', function() {
+      var
+        original = new ComplexArray([1, 2, 3, 4]),
+        filtered = original.frequencyMap(function(value, i) {
+          value.real /= 2
+          value.imag /= 2
+        })
+
+      assertComplexArraysAlmostEqual(
+          new ComplexArray([0.5, 1, 1.5, 2]), filtered)
+    })
+
+    it('should return zeroed ComplexArray', function() {
+      var
+        original = new ComplexArray([1, 2, 3, 4]),
+        filtered = original.frequencyMap(function(value, i) {
+          value.real = value.imag = 0
+        })
+
+      assertComplexArraysAlmostEqual(new ComplexArray([0, 0, 0, 0]), filtered)
+    })
+
+    it('should shift the original', function() {
+      var
+        original = new ComplexArray([1, 2, 3, 4]),
+        filtered = original.frequencyMap(function(value, i) {
+          var
+            // Multiply by a phase to shift the original.
+            real_multiplier = i % 2 ? 0 : (1 - i),
+            imag_multiplier = i % 2 ? (2 - i) : 0,
+            swap_real = value.real,
+            swap_imag = value.imag
+
+          value.real = real_multiplier * swap_real - imag_multiplier * swap_imag
+          value.imag = real_multiplier * swap_imag + imag_multiplier * swap_real
+        })
+
+      assertComplexArraysAlmostEqual(new ComplexArray([4, 1, 2, 3]), filtered)
+    })
+  })
+})
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/jsfft/test/test-fft_image.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,70 @@
+require('../lib/fft_image')
+require('./test_helper')
+
+var assert = require('assert'),
+    fft_lib = require('../lib/fft'),
+    complex_array_lib = require('../lib/complex_array'),
+    ComplexArray = complex_array_lib.ComplexArray
+
+function randomImageData(n) {
+  var array = new Uint8ClampedArray(n),
+      i
+
+  for(i = 0; i < n; i++) {
+    array[i] = Math.random() * 128
+  }
+  return array
+}
+
+function mergeRGBA(r, g, b, a) {
+  var n = r.length,
+      output = new Uint8ClampedArray(4 * n),
+      i
+
+  for(i = 0; i < n; i++) {
+    output[4 * i    ] = r[i]
+    output[4 * i + 1] = g[i]
+    output[4 * i + 2] = b[i]
+    output[4 * i + 3] = a[i]
+  }
+  return output
+}
+
+function mergeComplexRGBA(r, g, b, a) {
+  var n = r.length,
+      output = new ComplexArray(4 * n),
+      i
+
+  for(i = 0; i < n; i++) {
+    output.real[4 * i    ] = r.real[i]
+    output.imag[4 * i    ] = r.imag[i]
+    output.real[4 * i + 1] = g.real[i]
+    output.imag[4 * i + 1] = g.imag[i]
+    output.real[4 * i + 2] = b.real[i]
+    output.imag[4 * i + 2] = b.imag[i]
+    output.real[4 * i + 3] = a.real[i]
+    output.imag[4 * i + 3] = a.imag[i]
+  }
+
+  return output
+}
+
+describe('fft', function() {
+  describe('#FFTImageDataRGBA()', function() {
+    it('transforms independent channels', function() {
+      var n = 48,
+          r = randomImageData(n),
+          g = randomImageData(n),
+          b = randomImageData(n),
+          a = new Uint8ClampedArray(n),
+          data = mergeRGBA(r, g, b, a),
+          expected = mergeComplexRGBA(DFT(r), DFT(g), DFT(b), DFT(a)),
+          output = fft_lib.FFTImageDataRGBA(data, n, 1)
+
+      assertComplexArraysAlmostEqual(expected, output)
+    })
+
+    xit('transforms in 2D')
+  })
+})
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fft/jsfft/test/test_helper.js	Wed Oct 07 13:46:38 2015 +0100
@@ -0,0 +1,83 @@
+!function() {
+
+  var EPSILON = 1e-4,
+      assert = require('assert'),
+      complex_array_lib = require('../lib/complex_array'),
+      fft_lib = require('../lib/fft'),
+      ComplexArray = complex_array_lib.ComplexArray,
+      isComplexArray = complex_array_lib.isComplexArray,
+      PI = Math.PI,
+      SQRT2 = Math.SQRT2,
+      SQRT1_2 = Math.SQRT1_2,
+      cos = Math.cos,
+      sin = Math.sin,
+      sqrt = Math.sqrt
+
+  global.assertComplexArraysAlmostEqual = function(first, second) {
+    var message = second + ' != ' + first
+
+    assert.equal(first.length, second.length, message)
+
+    first.forEach(function(value, i) {
+      assertApproximatelyEqual(value.real, second.real[i], message)
+      assertApproximatelyEqual(value.imag, second.imag[i], message)
+    })
+  }
+
+  global.assertFFTMatches = function(original, expected) {
+    var transformed, copy
+
+    if (!isComplexArray(expected)) {
+      throw TypeError('expected match should be a ComplexArray')
+    }
+
+    copy = new ComplexArray(original)
+    transformed = fft_lib.FFT(original)
+    assertComplexArraysAlmostEqual(expected, transformed)
+    assertComplexArraysAlmostEqual(copy, fft_lib.InvFFT(transformed))
+  }
+
+   global.assertFFTMatchesDFT = function(input) {
+    input = new ComplexArray(input)
+
+    assertComplexArraysAlmostEqual(DFT(input), fft_lib.FFT(input))
+  }
+
+  global.DFT = function(input) {
+    var n = input.length,
+        amplitude = 1 / sqrt(n),
+        output = new ComplexArray(input),
+        phase = {real: 0, imag: 0},
+        delta = {real: 0, imag: 0},
+        i, j,
+        _swap
+
+    if (!isComplexArray(input)) {
+      input = new ComplexArray(input)
+    }
+
+    for(i = 0; i < n; i++) {
+      output.real[i] = 0, output.imag[i] = 0
+      phase.real = 1, phase.imag = 0
+      delta.real = cos(2*PI*i/n), delta.imag = sin(2*PI*i/n)
+
+      for(j = 0; j < n; j++) {
+        output.real[i] += phase.real * input.real[j] - phase.imag * input.imag[j]
+        output.imag[i] += phase.real * input.imag[j] + phase.imag * input.real[j]
+        _swap = phase.real
+        phase.real = phase.real * delta.real - phase.imag * delta.imag
+        phase.imag = _swap * delta.imag + phase.imag * delta.real
+      }
+      output.real[i] *= amplitude
+      output.imag[i] *= amplitude
+    }
+
+    return output
+  }
+
+  function assertApproximatelyEqual(first, second, message) {
+    var delta = Math.abs(first - second)
+    assert.ok(delta < EPSILON, message)
+  }
+
+}()
--- a/fft/test.html	Wed Oct 07 08:57:11 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-<html>
-  <head>
-    
-  <meta charset="UTF-8">
-
-  <style type="text/css">
-    body { margin: 5%; }
-    table, td, th { border: 0.1em solid #e0e0e0; border-collapse: collapse }
-    td, th { padding: 0.5em }
-  </style>
-  
-  <script src="nayuki/fft.js"></script>
-  <script src="nayuki-obj/fft.js"></script>
-  <script src="fft.js/lib/complex.js"></script>
-  <script src="jsfft/lib/complex_array.js"></script>
-  <script src="jsfft/lib/fft.js"></script>
-  <script src="cross/Cross.js"></script>
-  <script src="cross/FFT.js"></script>
-  <script src="kissfft/KissFFT.js"></script>
-  <script src="kissfft/FFT.js"></script>
-  <script src="fftw/FFTW.js"></script>
-  <script src="fftw/FFT.js"></script>
-  <script src="test.js"></script>
-
-  </head>
-<body>
-
-  <h3>Results</h3>
-
-  <p id="test-description"></p>
-  
-  <table>
-    <tr>
-      <th>Implementation</th><th>Result</th><th>Time (first half)</th><th>Time (second half)</th><th>Rate (second half)</th>
-    </tr>
-    <tr>
-      <td>Nayuki</td><td id="nayuki-result"></td><td id="nayuki-1"></td><td id="nayuki-2"></td><td id="nayuki-itr"></td>
-    </tr><tr> 
-      <td>Nayuki (obj)</td><td id="nayukiobj-result"></td><td id="nayukiobj-1"></td><td id="nayukiobj-2"></td><td id="nayukiobj-itr"></td>
-    </tr><tr> 
-      <td>Nockert</td><td id="nockert-result"></td><td id="nockert-1"></td><td id="nockert-2"></td><td id="nockert-itr"></td>
-    </tr><tr>
-      <td>Dntj</td><td id="dntj-result"></td><td id="dntj-1"></td><td id="dntj-2"></td><td id="dntj-itr"></td>
-    </tr><tr>
-      <td>Cross</td><td id="cross-result"></td><td id="cross-1"></td><td id="cross-2"></td><td id="cross-itr"></td>
-    </tr><tr>
-      <td>KissFFT</td><td id="kissfft-result"></td><td id="kissfft-1"></td><td id="kissfft-2"></td><td id="kissfft-itr"></td>
-    </tr><tr>
-      <td>FFTW</td><td id="fftw-result"></td><td id="fftw-1"></td><td id="fftw-2"></td><td id="fftw-itr"></td>
-    </tr>
-  </table>
-
-  <h3>Notes</h3>
-
-  <ul>
-    <li><b>Nayuki</b>: in-place single-precision complex-complex. Around 7kb.</li>
-    <li><b>Nayuki (obj)</b>: Nayuki with the sin/cos tables pre-calculated on object construction. Around 4kb.</li>
-    <li><b>Nockert</b>: double-precision real-complex. Around 25kb.</li>
-    <li><b>Dntj</b>: double-precision complex-complex. Forward
-    transform is scaled and I've scaled it back again here, which may
-    introduce rounding error. Around 10kb.</li>
-    <li><b>Cross</b>: double-precision real-complex in C, compiled
-    with Emscripten. This is considered a slow implementation amongst
-    native code ones. Around 60kb.</li>
-    <li><b>KissFFT</b>: single-precision real-complex in C, compiled
-    with Emscripten. A reasonably sophisticated implementation. Around
-    70kb.</li>
-    <li><b>FFTW</b>: single-precision real-complex in C, compiled with
-    Emscripten. GPL licensed. Around 3Mb.</li>
-  </ul>
-  
-</body>
-