flocking
Version:
Creative audio synthesis for the Web
1,070 lines (926 loc) • 33.6 kB
JavaScript
// -*- mode: javascript; tab-width: 2; indent-tabs-mode: nil; -*-
//------------------------------------------------------------------------------
// Web Array Math API - JavaScript polyfill
//
// Copyright(c) 2013 Marcus Geelnard
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the
// use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not claim
// that you wrote the original software. If you use this software in a
// product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//------------------------------------------------------------------------------
"use strict";
//------------------------------------------------------------------------------
// interface ArrayMath
//------------------------------------------------------------------------------
(function () {
var context = typeof (window) !== "undefined" ? window :
typeof (self) !== "undefined" ? self :
typeof module !== "undefined" && module.exports ? module.exports :
global;
if (context.ArrayMath) return;
var ArrayMath = {};
ArrayMath.add = function (dst, x, y) {
var k;
if (x instanceof Float32Array)
for (k = Math.min(dst.length, x.length, y.length) - 1; k >= 0; --k)
dst[k] = x[k] + y[k];
else
for (k = Math.min(dst.length, y.length) - 1; k >= 0; --k)
dst[k] = x + y[k];
};
ArrayMath.sub = function (dst, x, y) {
var k;
if (x instanceof Float32Array)
for (k = Math.min(dst.length, x.length, y.length) - 1; k >= 0; --k)
dst[k] = x[k] - y[k];
else
for (k = Math.min(dst.length, y.length) - 1; k >= 0; --k)
dst[k] = x - y[k];
};
ArrayMath.mul = function (dst, x, y) {
var k;
if (x instanceof Float32Array)
for (k = Math.min(dst.length, x.length, y.length) - 1; k >= 0; --k)
dst[k] = x[k] * y[k];
else
for (k = Math.min(dst.length, y.length) - 1; k >= 0; --k)
dst[k] = x * y[k];
};
ArrayMath.mulCplx = function (dstReal, dstImag, xReal, xImag, yReal, yImag) {
var k, xr, xi, yr, yi;
if (xReal instanceof Float32Array)
for (k = Math.min(dstReal.length, dstImag.length, xReal.length, xImag.length, yReal.length, yImag.length) - 1; k >= 0; --k) {
xr = xReal[k], xi = xImag[k], yr = yReal[k], yi = yImag[k];
dstReal[k] = xr * yr - xi * yi;
dstImag[k] = xr * yi + xi * yr;
}
else
for (k = Math.min(dstReal.length, dstImag.length, yReal.length, yImag.length) - 1; k >= 0; --k) {
yr = yReal[k], yi = yImag[k];
dstReal[k] = xReal * yr - xImag * yi;
dstImag[k] = xReal * yi + xImag * yr;
}
};
ArrayMath.div = function (dst, x, y) {
var k;
if (x instanceof Float32Array)
for (k = Math.min(dst.length, x.length, y.length) - 1; k >= 0; --k)
dst[k] = x[k] / y[k];
else
for (k = Math.min(dst.length, y.length) - 1; k >= 0; --k)
dst[k] = x / y[k];
};
ArrayMath.divCplx = function (dstReal, dstImag, xReal, xImag, yReal, yImag) {
var k, xr, xi, yr, yi, denom;
if (xReal instanceof Float32Array)
for (k = Math.min(dstReal.length, dstImag.length, xReal.length, xImag.length, yReal.length, yImag.length) - 1; k >= 0; --k) {
xr = xReal[k], xi = xImag[k], yr = yReal[k], yi = yImag[k];
denom = 1 / (yr * yr + yi * yi);
dstReal[k] = (xr * yr + xi * yi) * denom;
dstImag[k] = (xi * yr - xr * yi) * denom;
}
else {
for (k = Math.min(dstReal.length, dstImag.length, yReal.length, yImag.length) - 1; k >= 0; --k) {
yr = yReal[k], yi = yImag[k];
denom = 1 / (yr * yr + yi * yi);
dstReal[k] = (xReal * yr + xImag * yi) * denom;
dstImag[k] = (xImag * yr - xReal * yi) * denom;
}
}
};
ArrayMath.madd = function (dst, x, y, z) {
var k;
if (x instanceof Float32Array)
for (k = Math.min(dst.length, x.length, y.length, z.length) - 1; k >= 0; --k)
dst[k] = x[k] * y[k] + z[k];
else
for (k = Math.min(dst.length, y.length, z.length) - 1; k >= 0; --k)
dst[k] = x * y[k] + z[k];
};
ArrayMath.abs = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.abs(x[k]);
};
ArrayMath.absCplx = function (dst, real, imag) {
for (var k = Math.min(dst.length, real.length, imag.length) - 1; k >= 0; --k)
dst[k] = Math.sqrt(real[k] * real[k] + imag[k] * imag[k]);
};
ArrayMath.acos = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.acos(x[k]);
};
ArrayMath.asin = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.asin(x[k]);
};
ArrayMath.atan = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.atan(x[k]);
};
ArrayMath.atan2 = function (dst, y, x) {
for (var k = Math.min(dst.length, x.length, y.length) - 1; k >= 0; --k)
dst[k] = Math.atan2(y[k], x[k]);
};
ArrayMath.ceil = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.ceil(x[k]);
};
ArrayMath.cos = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.cos(x[k]);
};
ArrayMath.exp = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.exp(x[k]);
};
ArrayMath.floor = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.floor(x[k]);
};
ArrayMath.log = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.log(x[k]);
};
ArrayMath.max = function (x) {
var ret = -Infinity;
for (var k = x.length - 1; k >= 0; --k) {
var val = x[k];
if (val > ret)
ret = val;
}
return ret;
};
ArrayMath.min = function (x) {
var ret = Infinity;
for (var k = x.length - 1; k >= 0; --k) {
var val = x[k];
if (val < ret)
ret = val;
}
return ret;
};
ArrayMath.pow = function (dst, x, y) {
var k;
if (y instanceof Float32Array)
for (k = Math.min(dst.length, x.length, y.length) - 1; k >= 0; --k)
dst[k] = Math.pow(x[k], y[k]);
else {
for (k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.pow(x[k], y);
}
};
ArrayMath.random = function (dst, low, high) {
if (!low)
low = 0;
if (isNaN(parseFloat(high)))
high = 1;
var scale = high - low;
for (var k = dst.length - 1; k >= 0; --k)
dst[k] = Math.random() * scale + low;
};
ArrayMath.round = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.round(x[k]);
};
ArrayMath.sin = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.sin(x[k]);
};
ArrayMath.sqrt = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.sqrt(x[k]);
};
ArrayMath.tan = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = Math.tan(x[k]);
};
ArrayMath.clamp = function (dst, x, xMin, xMax) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k) {
var val = x[k];
dst[k] = val < xMin ? xMin : val > xMax ? xMax : val;
}
};
ArrayMath.fract = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k) {
var val = x[k];
dst[k] = val - Math.floor(val);
}
};
ArrayMath.fill = function (dst, value) {
for (var k = dst.length - 1; k >= 0; --k) {
dst[k] = value;
}
};
ArrayMath.ramp = function (dst, first, last) {
var maxIdx = dst.length - 1;
if (maxIdx >= 0)
dst[0] = first;
if (maxIdx > 0) {
var step = (last - first) / maxIdx;
for (var k = 1; k <= maxIdx; ++k)
dst[k] = first + step * k;
}
};
ArrayMath.sign = function (dst, x) {
for (var k = Math.min(dst.length, x.length) - 1; k >= 0; --k)
dst[k] = x[k] < 0 ? -1 : 1;
};
ArrayMath.sum = function (x) {
// TODO(m): We should use pairwise summation or similar here.
var ret = 0;
for (var k = x.length - 1; k >= 0; --k)
ret += x[k];
return ret;
};
ArrayMath.sampleLinear = function (dst, x, t) {
var xLen = x.length, maxIdx = xLen - 1;
for (var k = Math.min(dst.length, t.length) - 1; k >= 0; --k) {
var t2 = t[k];
t2 = t2 < 0 ? 0 : t2 > maxIdx ? maxIdx : t2;
var idx = Math.floor(t2);
var w = t2 - idx;
var p1 = x[idx];
var p2 = x[idx < maxIdx ? idx + 1 : maxIdx];
dst[k] = p1 + w * (p2 - p1);
}
};
ArrayMath.sampleLinearRepeat = function (dst, x, t) {
var xLen = x.length, maxIdx = xLen - 1;
for (var k = Math.min(dst.length, t.length) - 1; k >= 0; --k) {
var t2 = t[k];
t2 = t2 - Math.floor(t2/xLen) * xLen;
var idx = Math.floor(t2);
var w = t2 - idx;
var p1 = x[idx];
var p2 = x[idx < maxIdx ? idx + 1 : 0];
dst[k] = p1 + w * (p2 - p1);
}
};
ArrayMath.sampleCubic = function (dst, x, t) {
var xLen = x.length, maxIdx = xLen - 1;
for (var k = Math.min(dst.length, t.length) - 1; k >= 0; --k) {
var t2 = t[k];
t2 = t2 < 0 ? 0 : t2 > maxIdx ? maxIdx : t2;
var idx = Math.floor(t2);
var w = t2 - idx;
var w2 = w * w;
var w3 = w2 * w;
var h2 = -2*w3 + 3*w2;
var h1 = 1 - h2;
var h4 = w3 - w2;
var h3 = h4 - w2 + w;
var p1 = x[idx > 0 ? idx - 1 : 0];
var p2 = x[idx];
var p3 = x[idx < maxIdx ? idx + 1 : maxIdx];
var p4 = x[idx < maxIdx - 1 ? idx + 2 : maxIdx];
dst[k] = h1 * p2 + h2 * p3 + 0.5 * (h3 * (p3 - p1) + h4 * (p4 - p2));
}
};
ArrayMath.sampleCubicRepeat = function (dst, x, t) {
var xLen = x.length, maxIdx = xLen - 1;
for (var k = Math.min(dst.length, t.length) - 1; k >= 0; --k) {
var t2 = t[k];
t2 = t2 - Math.floor(t2/xLen) * xLen;
var idx = Math.floor(t2);
var w = t2 - idx;
var w2 = w * w;
var w3 = w2 * w;
var h2 = -2*w3 + 3*w2;
var h1 = 1 - h2;
var h4 = w3 - w2;
var h3 = h4 - w2 + w;
var p1 = x[idx > 0 ? idx - 1 : maxIdx];
var p2 = x[idx];
var p3 = x[idx < maxIdx ? idx + 1 : 0];
var p4 = x[idx < maxIdx - 1 ? idx + 2 : (idx + 2 - Math.floor((idx + 2)/xLen) * xLen)];
dst[k] = h1 * p2 + h2 * p3 + 0.5 * (h3 * (p3 - p1) + h4 * (p4 - p2));
}
};
ArrayMath.pack = function (dst, offset, stride, src1, src2, src3, src4) {
var dstCount = Math.floor(Math.max(0, (dst.length - offset)) / stride);
var count = Math.min(dstCount, src1.length);
if (src2) {
var count = Math.min(count, src2.length);
if (src3) {
var count = Math.min(count, src3.length);
if (src4) {
var count = Math.min(count, src4.length);
for (var k = 0; k < count; ++k) {
dst[offset] = src1[k];
dst[offset + 1] = src2[k];
dst[offset + 2] = src3[k];
dst[offset + 3] = src4[k];
offset += stride;
}
}
else
for (var k = 0; k < count; ++k) {
dst[offset] = src1[k];
dst[offset + 1] = src2[k];
dst[offset + 2] = src3[k];
offset += stride;
}
}
else
for (var k = 0; k < count; ++k) {
dst[offset] = src1[k];
dst[offset + 1] = src2[k];
offset += stride;
}
}
else
for (var k = 0; k < count; ++k) {
dst[offset] = src1[k];
offset += stride;
}
};
ArrayMath.unpack = function (src, offset, stride, dst1, dst2, dst3, dst4) {
var srcCount = Math.floor(Math.max(0, (src.length - offset)) / stride);
var count = Math.min(srcCount, dst1.length);
if (dst2) {
var count = Math.min(count, dst2.length);
if (dst3) {
var count = Math.min(count, dst3.length);
if (dst4) {
var count = Math.min(count, dst4.length);
for (var k = 0; k < count; ++k) {
dst1[k] = src[offset];
dst2[k] = src[offset + 1];
dst3[k] = src[offset + 2];
dst4[k] = src[offset + 3];
offset += stride;
}
}
else
for (var k = 0; k < count; ++k) {
dst1[k] = src[offset];
dst2[k] = src[offset + 1];
dst3[k] = src[offset + 2];
offset += stride;
}
}
else
for (var k = 0; k < count; ++k) {
dst1[k] = src[offset];
dst2[k] = src[offset + 1];
offset += stride;
}
}
else
for (var k = 0; k < count; ++k) {
dst1[k] = src[offset];
offset += stride;
}
};
context.ArrayMath = ArrayMath;
})();
//------------------------------------------------------------------------------
// interface Filter
//------------------------------------------------------------------------------
(function () {
var context = typeof (window) !== "undefined" ? window :
typeof (self) !== "undefined" ? self :
typeof module !== "undefined" && module.exports ? module.exports :
global;
if (context.Filter) return;
var Filter = function (bSize, aSize) {
if (isNaN(parseFloat(bSize)) || !isFinite(bSize))
bSize = 1;
if (!aSize)
aSize = 0;
this._b = new Float32Array(bSize);
this._b[0] = 1;
this._a = new Float32Array(aSize);
this._bHist = new Float32Array(bSize);
this._aHist = new Float32Array(aSize);
};
Filter.prototype.filter = function (dst, x) {
// Put commonly accessed objects and properties in local variables
var a = this._a, aLen = a.length,
b = this._b, bLen = b.length,
aHist = this._aHist, bHist = this._bHist,
xLen = x.length, dstLen = dst.length;
// Perform run-in part using the history (slow)
var bHistRunIn = bLen - 1;
var aHistRunIn = aLen;
var k;
for (k = 0; (bHistRunIn || aHistRunIn) && k < xLen; ++k) {
var m, noHistLen;
// FIR part
noHistLen = bLen - bHistRunIn;
bHistRunIn && bHistRunIn--;
var res = b[0] * x[k];
for (m = 1; m < noHistLen; ++m)
res += b[m] * x[k - m];
for (; m < bLen; ++m)
res += b[m] * bHist[m - noHistLen];
// Recursive part
noHistLen = aLen - aHistRunIn;
aHistRunIn && aHistRunIn--;
for (m = 0; m < noHistLen; ++m)
res -= a[m] * dst[k - 1 - m];
for (; m < aLen; ++m)
res -= a[m] * aHist[m - noHistLen];
dst[k] = res;
}
// Perform history-free part (fast)
if (bLen == 3 && aLen == 2) {
// Optimized special case: biquad filter
var b0 = b[0], b1 = b[1], b2 = b[2], a1 = a[0], a2 = a[1];
var x0 = x[k-1], x1 = x[k-2], x2;
var y0 = dst[k-1], y1 = dst[k-2], y2;
for (; k < xLen; ++k) {
x2 = x1;
x1 = x0;
x0 = x[k];
y2 = y1;
y1 = y0;
y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
dst[k] = y0;
}
}
else {
// Generic case
for (; k < xLen; ++k) {
var m;
// FIR part
var res = b[0] * x[k];
for (m = 1; m < bLen; ++m)
res += b[m] * x[k - m];
// Recursive part
for (m = 0; m < aLen; ++m)
res -= a[m] * dst[k - 1 - m];
dst[k] = res;
}
}
// Update history state
var histCopy = Math.min(bLen - 1, xLen);
for (k = bLen - 2; k >= histCopy; --k)
bHist[k] = bHist[k - histCopy];
for (k = 0; k < histCopy; ++k)
bHist[k] = x[xLen - 1 - k];
histCopy = Math.min(aLen, dstLen);
for (k = aLen - 1; k >= histCopy; --k)
aHist[k] = aHist[k - histCopy];
for (k = 0; k < histCopy; ++k)
aHist[k] = dst[xLen - 1 - k];
};
Filter.prototype.clearHistory = function () {
for (var k = this._bHist.length - 1; k >= 0; --k)
this._bHist[k] = 0;
for (var k = this._aHist.length - 1; k >= 0; --k)
this._aHist[k] = 0;
};
Filter.prototype.setB = function (values) {
var len = Math.min(this._b.length, values.length);
for (var k = 0; k < len; ++k)
this._b[k] = values[k];
};
Filter.prototype.setA = function (values) {
var len = Math.min(this._a.length, values.length);
for (var k = 0; k < len; ++k)
this._a[k] = values[k];
};
context.Filter = Filter;
})();
//------------------------------------------------------------------------------
// interface FFT
//
// NOTE: This is essentially a hand-translation of the C language Kiss FFT
// library, copyright by Mark Borgerding, relicensed with permission from the
// author.
//
// The algorithm implements mixed radix FFT and supports transforms of any size
// (not just powers of 2). For optimal performance, use sizes that can be
// factorized into factors 2, 3, 4 and 5.
//------------------------------------------------------------------------------
(function () {
var context = typeof (window) !== "undefined" ? window :
typeof (self) !== "undefined" ? self :
typeof module !== "undefined" && module.exports ? module.exports :
global;
if (context.FFT) return;
var butterfly2 = function (outRe, outIm, outIdx, stride, twRe, twIm, m) {
var scratch0Re, scratch0Im,
out0Re, out0Im, out1Re, out1Im,
tRe, tIm;
var tw1 = 0,
idx0 = outIdx,
idx1 = outIdx + m;
var scale = 0.7071067811865475; // sqrt(1/2)
var idx0End = idx0 + m;
while (idx0 < idx0End) {
// out0 = out[idx0] / sqrt(2)
out0Re = outRe[idx0] * scale;
out0Im = outIm[idx0] * scale;
// out1 = out[idx1] / sqrt(2)
out1Re = outRe[idx1] * scale;
out1Im = outIm[idx1] * scale;
// scratch0 = out1 * tw[tw1]
tRe = twRe[tw1]; tIm = twIm[tw1];
scratch0Re = out1Re * tRe - out1Im * tIm;
scratch0Im = out1Re * tIm + out1Im * tRe;
// out[idx1] = out0 - scratch0
outRe[idx1] = out0Re - scratch0Re;
outIm[idx1] = out0Im - scratch0Im;
// out[idx0] = out0 + scratch0
outRe[idx0] = out0Re + scratch0Re;
outIm[idx0] = out0Im + scratch0Im;
tw1 += stride;
++idx0; ++idx1;
}
};
var butterfly3 = function (outRe, outIm, outIdx, stride, twRe, twIm, m) {
var scratch0Re, scratch0Im, scratch1Re, scratch1Im,
scratch2Re, scratch2Im, scratch3Re, scratch3Im,
out0Re, out0Im, out1Re, out1Im, out2Re, out2Im,
tRe, tIm;
var tw1 = 0,
tw2 = 0,
stride2 = 2 * stride,
idx0 = outIdx,
idx1 = outIdx + m,
idx2 = outIdx + 2 * m;
var epi3Im = twIm[stride*m];
var scale = 0.5773502691896258; // sqrt(1/3)
var idx0End = idx0 + m;
while (idx0 < idx0End) {
// out0 = out[idx0] / sqrt(3)
out0Re = outRe[idx0] * scale;
out0Im = outIm[idx0] * scale;
// out1 = out[idx1] / sqrt(3)
out1Re = outRe[idx1] * scale;
out1Im = outIm[idx1] * scale;
// out2 = out[idx2] / sqrt(3)
out2Re = outRe[idx2] * scale;
out2Im = outIm[idx2] * scale;
// scratch1 = out1 * tw[tw1]
tRe = twRe[tw1]; tIm = twIm[tw1];
scratch1Re = out1Re * tRe - out1Im * tIm;
scratch1Im = out1Re * tIm + out1Im * tRe;
// scratch2 = out2 * tw[tw2]
tRe = twRe[tw2]; tIm = twIm[tw2];
scratch2Re = out2Re * tRe - out2Im * tIm;
scratch2Im = out2Re * tIm + out2Im * tRe;
// scratch3 = scratch1 + scratch2
scratch3Re = scratch1Re + scratch2Re;
scratch3Im = scratch1Im + scratch2Im;
// scratch0 = scratch1 - scratch2
scratch0Re = scratch1Re - scratch2Re;
scratch0Im = scratch1Im - scratch2Im;
// out1 = out0 - scratch3 / 2
out1Re = out0Re - scratch3Re * 0.5;
out1Im = out0Im - scratch3Im * 0.5;
// scratch0 *= epi3.i
scratch0Re *= epi3Im;
scratch0Im *= epi3Im;
// out[idx0] = out0 + scratch3
outRe[idx0] = out0Re + scratch3Re;
outIm[idx0] = out0Im + scratch3Im;
outRe[idx2] = out1Re + scratch0Im;
outIm[idx2] = out1Im - scratch0Re;
outRe[idx1] = out1Re - scratch0Im;
outIm[idx1] = out1Im + scratch0Re;
tw1 += stride; tw2 += stride2;
++idx0; ++idx1; ++idx2;
}
};
var butterfly4 = function (outRe, outIm, outIdx, stride, twRe, twIm, m, inverse) {
var scratch0Re, scratch0Im, scratch1Re, scratch1Im, scratch2Re, scratch2Im,
scratch3Re, scratch3Im, scratch4Re, scratch4Im, scratch5Re, scratch5Im,
out0Re, out0Im, out1Re, out1Im, out2Re, out2Im, out3Re, out3Im,
tRe, tIm;
var tw1 = 0,
tw2 = 0,
tw3 = 0,
stride2 = 2 * stride,
stride3 = 3 * stride,
idx0 = outIdx,
idx1 = outIdx + m,
idx2 = outIdx + 2 * m,
idx3 = outIdx + 3 * m;
var scale = 0.5; // sqrt(1/4)
var idx0End = idx0 + m;
while (idx0 < idx0End) {
// out0 = out[idx0] / sqrt(4)
out0Re = outRe[idx0] * scale;
out0Im = outIm[idx0] * scale;
// out1 = out[idx1] / sqrt(4)
out1Re = outRe[idx1] * scale;
out1Im = outIm[idx1] * scale;
// out2 = out[idx2] / sqrt(4)
out2Re = outRe[idx2] * scale;
out2Im = outIm[idx2] * scale;
// out3 = out[idx3] / sqrt(4)
out3Re = outRe[idx3] * scale;
out3Im = outIm[idx3] * scale;
// scratch0 = out1 * tw[tw1]
tRe = twRe[tw1]; tIm = twIm[tw1];
scratch0Re = out1Re * tRe - out1Im * tIm;
scratch0Im = out1Re * tIm + out1Im * tRe;
// scratch1 = out2 * tw[tw2]
tRe = twRe[tw2]; tIm = twIm[tw2];
scratch1Re = out2Re * tRe - out2Im * tIm;
scratch1Im = out2Re * tIm + out2Im * tRe;
// scratch2 = out3 * tw[tw3]
tRe = twRe[tw3]; tIm = twIm[tw3];
scratch2Re = out3Re * tRe - out3Im * tIm;
scratch2Im = out3Re * tIm + out3Im * tRe;
// scratch5 = out0 - scratch1
scratch5Re = out0Re - scratch1Re;
scratch5Im = out0Im - scratch1Im;
// out0 += scratch1
out0Re += scratch1Re;
out0Im += scratch1Im;
// scratch3 = scratch0 + scratch2
scratch3Re = scratch0Re + scratch2Re;
scratch3Im = scratch0Im + scratch2Im;
// scratch4 = scratch0 - scratch2
scratch4Re = scratch0Re - scratch2Re;
scratch4Im = scratch0Im - scratch2Im;
// out[idx2] = out0 - scratch3
outRe[idx2] = out0Re - scratch3Re;
outIm[idx2] = out0Im - scratch3Im;
// out[idx0] = out0 + scratch3
outRe[idx0] = out0Re + scratch3Re;
outIm[idx0] = out0Im + scratch3Im;
if (inverse) {
outRe[idx1] = scratch5Re - scratch4Im;
outIm[idx1] = scratch5Im + scratch4Re;
outRe[idx3] = scratch5Re + scratch4Im;
outIm[idx3] = scratch5Im - scratch4Re;
}
else {
outRe[idx1] = scratch5Re + scratch4Im;
outIm[idx1] = scratch5Im - scratch4Re;
outRe[idx3] = scratch5Re - scratch4Im;
outIm[idx3] = scratch5Im + scratch4Re;
}
tw1 += stride; tw2 += stride2; tw3 += stride3;
++idx0; ++idx1; ++idx2; ++idx3;
}
};
var butterfly5 = function (outRe, outIm, outIdx, stride, twRe, twIm, m) {
var scratch0Re, scratch0Im, scratch1Re, scratch1Im, scratch2Re, scratch2Im,
scratch3Re, scratch3Im, scratch4Re, scratch4Im, scratch5Re, scratch5Im,
scratch6Re, scratch6Im, scratch7Re, scratch7Im, scratch8Re, scratch8Im,
scratch9Re, scratch9Im, scratch10Re, scratch10Im, scratch11Re, scratch11Im,
scratch12Re, scratch12Im,
out0Re, out0Im, out1Re, out1Im, out2Re, out2Im, out3Re, out3Im, out4Re, out4Im,
tRe, tIm;
var tw1 = 0,
tw2 = 0,
tw3 = 0,
tw4 = 0,
stride2 = 2 * stride,
stride3 = 3 * stride,
stride4 = 4 * stride;
var idx0 = outIdx,
idx1 = outIdx + m,
idx2 = outIdx + 2 * m,
idx3 = outIdx + 3 * m,
idx4 = outIdx + 4 * m;
// ya = tw[stride*m];
var yaRe = twRe[stride * m],
yaIm = twIm[stride * m];
// yb = tw[stride*2*m];
var ybRe = twRe[stride * 2 * m],
ybIm = twIm[stride * 2 * m];
var scale = 0.4472135954999579; // sqrt(1/5)
var idx0End = idx0 + m;
while (idx0 < idx0End) {
// out0 = out[idx0] / sqrt(5)
out0Re = outRe[idx0] * scale;
out0Im = outIm[idx0] * scale;
// out1 = out[idx1] / sqrt(5)
out1Re = outRe[idx1] * scale;
out1Im = outIm[idx1] * scale;
// out2 = out[idx2] / sqrt(5)
out2Re = outRe[idx2] * scale;
out2Im = outIm[idx2] * scale;
// out3 = out[idx3] / sqrt(5)
out3Re = outRe[idx3] * scale;
out3Im = outIm[idx3] * scale;
// out4 = out[idx4] / sqrt(5)
out4Re = outRe[idx4] * scale;
out4Im = outIm[idx4] * scale;
// scratch0 = out0;
scratch0Re = out0Re;
scratch0Im = out0Im;
// scratch1 = out1 * tw[tw1]
tRe = twRe[tw1]; tIm = twIm[tw1];
scratch1Re = out1Re * tRe - out1Im * tIm;
scratch1Im = out1Re * tIm + out1Im * tRe;
// scratch2 = out2 * tw[tw2]
tRe = twRe[tw2]; tIm = twIm[tw2];
scratch2Re = out2Re * tRe - out2Im * tIm;
scratch2Im = out2Re * tIm + out2Im * tRe;
// scratch3 = out3 * tw[tw3]
tRe = twRe[tw3]; tIm = twIm[tw3];
scratch3Re = out3Re * tRe - out3Im * tIm;
scratch3Im = out3Re * tIm + out3Im * tRe;
// scratch4 = out4 * tw[tw4]
tRe = twRe[tw4]; tIm = twIm[tw4];
scratch4Re = out4Re * tRe - out4Im * tIm;
scratch4Im = out4Re * tIm + out4Im * tRe;
// scratch7 = scratch1 + scratch4
scratch7Re = scratch1Re + scratch4Re;
scratch7Im = scratch1Im + scratch4Im;
// scratch10 = scratch1 - scratch4
scratch10Re = scratch1Re - scratch4Re;
scratch10Im = scratch1Im - scratch4Im;
// scratch8 = scratch2 + scratch2
scratch8Re = scratch2Re + scratch3Re;
scratch8Im = scratch2Im + scratch3Im;
// scratch9 = scratch2 - scratch3
scratch9Re = scratch2Re - scratch3Re;
scratch9Im = scratch2Im - scratch3Im;
// out[idx0] = out0 + scratch7 + scratch8
outRe[idx0] = out0Re + scratch7Re + scratch8Re;
outIm[idx0] = out0Im + scratch7Im + scratch8Im;
scratch5Re = scratch0Re + scratch7Re * yaRe + scratch8Re * ybRe;
scratch5Im = scratch0Im + scratch7Im * yaRe + scratch8Im * ybRe;
scratch6Re = scratch10Im * yaIm + scratch9Im * ybIm;
scratch6Im = -scratch10Re * yaIm - scratch9Re * ybIm;
// out[idx1] = scratch5 - scratch6
outRe[idx1] = scratch5Re - scratch6Re;
outIm[idx1] = scratch5Im - scratch6Im;
// out[idx4] = scratch5 + scratch6
outRe[idx4] = scratch5Re + scratch6Re;
outIm[idx4] = scratch5Im + scratch6Im;
scratch11Re = scratch0Re + scratch7Re * ybRe + scratch8Re * yaRe;
scratch11Im = scratch0Im + scratch7Im * ybRe + scratch8Im * yaRe;
scratch12Re = -scratch10Im * ybIm + scratch9Im * yaIm;
scratch12Im = scratch10Re * ybIm - scratch9Re * yaIm;
// out[idx2] = scratch11 + scratch12
outRe[idx2] = scratch11Re + scratch12Re;
outIm[idx2] = scratch11Im + scratch12Im;
// out[idx3] = scratch11 - scratch12
outRe[idx3] = scratch11Re - scratch12Re;
outIm[idx3] = scratch11Im - scratch12Im;
tw1 += stride; tw2 += stride2; tw3 += stride3; tw4 += stride4;
++idx0; ++idx1; ++idx2; ++idx3; ++idx4;
}
};
var butterflyN = function (outRe, outIm, outIdx, stride, twRe, twIm, m, p, size) {
var u, q1, q, idx0;
var out0Re, out0Im, aRe, aIm, tRe, tIm;
// FIXME: Allocate statically
var scratchRe = new Float32Array(p);
var scratchIm = new Float32Array(p);
var scale = Math.sqrt(1 / p);
for (u = 0; u < m; ++u) {
idx0 = outIdx + u;
for (q1 = 0; q1 < p; ++q1) {
// scratch[q1] = out[idx0] / sqrt(p)
scratchRe[q1] = outRe[idx0] * scale;
scratchIm[q1] = outIm[idx0] * scale;
idx0 += m;
}
idx0 = outIdx + u;
var tw1Incr = stride * u;
for (q1 = 0; q1 < p; ++q1) {
// out0 = scratch[0]
out0Re = scratchRe[0];
out0Im = scratchIm[0];
var tw1 = 0;
for (q = 1; q < p; ++q) {
tw1 += tw1Incr;
if (tw1 >= size)
tw1 -= size;
// out0 += scratch[q] * tw[tw1]
aRe = scratchRe[q], aIm = scratchIm[q];
tRe = twRe[tw1], tIm = twIm[tw1];
out0Re += aRe * tRe - aIm * tIm;
out0Im += aRe * tIm + aIm * tRe;
}
// out[idx0] = out0
outRe[idx0] = out0Re;
outIm[idx0] = out0Im;
idx0 += m;
tw1Incr += stride;
}
}
};
var work = function (outRe, outIm, outIdx, fRe, fIm, fIdx, stride, inStride, factors, factorsIdx, twRe, twIm, size, inverse) {
var p = factors[factorsIdx++]; // Radix
var m = factors[factorsIdx++]; // Stage's FFT length / p
var outIdxBeg = outIdx;
var outIdxEnd = outIdx + p * m;
var fIdxIncr = stride * inStride;
if (m == 1) {
do {
outRe[outIdx] = fRe[fIdx];
outIm[outIdx] = fIm[fIdx];
fIdx += fIdxIncr;
++outIdx;
}
while (outIdx != outIdxEnd);
}
else {
do {
// DFT of size m*p performed by doing p instances of smaller DFTs of
// size m, each one takes a decimated version of the input.
work(outRe, outIm, outIdx, fRe, fIm, fIdx, stride * p, inStride, factors, factorsIdx, twRe, twIm, size, inverse);
fIdx += fIdxIncr;
outIdx += m;
}
while (outIdx != outIdxEnd);
}
outIdx = outIdxBeg;
// Recombine the p smaller DFTs
switch (p) {
case 2: butterfly2(outRe, outIm, outIdx, stride, twRe, twIm, m); break;
case 3: butterfly3(outRe, outIm, outIdx, stride, twRe, twIm, m); break;
case 4: butterfly4(outRe, outIm, outIdx, stride, twRe, twIm, m, inverse); break;
case 5: butterfly5(outRe, outIm, outIdx, stride, twRe, twIm, m); break;
default: butterflyN(outRe, outIm, outIdx, stride, twRe, twIm, m, p, size); break;
}
};
/* facBuf is populated by p1,m1,p2,m2, ...
where
p[i] * m[i] = m[i-1]
m0 = n */
var factor = function (n, facBuf) {
// Factor out powers of 4, powers of 2, then any remaining primes
var p = 4;
var floorSqrt = Math.floor(Math.sqrt(n));
var idx = 0;
do {
while (n % p) {
switch (p) {
case 4: p = 2; break;
case 2: p = 3; break;
default: p += 2; break;
}
if (p > floorSqrt)
p = n;
}
n = Math.floor(n / p);
facBuf[idx++] = p;
facBuf[idx++] = n;
}
while (n > 1);
};
var FFT = function (size) {
if (!size)
size = 256;
Object.defineProperty(this, "size", {
configurable: false,
writable: false,
value: size
});
// Allocate arrays for twiddle factors
this._twiddlesFwdRe = new Float32Array(size);
this._twiddlesFwdIm = new Float32Array(size);
this._twiddlesInvRe = this._twiddlesFwdRe;
this._twiddlesInvIm = new Float32Array(size);
// Init twiddle factors (both forward & reverse)
for (var i = 0; i < size; ++i) {
var phase = -2 * Math.PI * i / size;
var cosPhase = Math.cos(phase), sinPhase = Math.sin(phase);
this._twiddlesFwdRe[i] = cosPhase;
this._twiddlesFwdIm[i] = sinPhase;
this._twiddlesInvIm[i] = -sinPhase;
}
// Allocate arrays for radix plan
this._factors = new Int32Array(2 * 32); // MAXFACTORS = 32
// Init radix factors (mixed radix breakdown)
// FIXME: Something seems to go wrong when using an FFT size that can be
// factorized into more than one butterflyN (e.g. try an FFT size of 11*13).
factor(size, this._factors);
};
FFT.prototype.forwardCplx = function (dstReal, dstImag, xReal, xImag) {
var twRe = this._twiddlesFwdRe;
var twIm = this._twiddlesFwdIm;
work(dstReal, dstImag, 0, xReal, xImag, 0, 1, 1, this._factors, 0, twRe, twIm, this.size, false);
};
FFT.prototype.forward = function (dstReal, dstImag, x) {
// FIXME: Optimize this case (real input signal)
this.forwardCplx(dstReal, dstImag, x, new Float32Array(this.size));
};
FFT.prototype.inverseCplx = function (dstReal, dstImag, xReal, xImag) {
var twRe = this._twiddlesInvRe;
var twIm = this._twiddlesInvIm;
work(dstReal, dstImag, 0, xReal, xImag, 0, 1, 1, this._factors, 0, twRe, twIm, this.size, true);
};
FFT.prototype.inverse = function (dst, xReal, xImag) {
// FIXME: Optimize this case (real output signal)
this.inverseCplx(dst, new Float32Array(this.size), xReal, xImag);
};
context.FFT = FFT;
})();