wavesurfer.js
Version:
Interactive navigable audio visualization using Web Audio and Canvas
223 lines (199 loc) • 7.27 kB
JavaScript
/* eslint-disable complexity, no-redeclare, no-var, one-var */
/**
* Calculate FFT - Based on https://github.com/corbanbrook/dsp.js
*
* @param {Number} bufferSize Buffer size
* @param {Number} sampleRate Sample rate
* @param {Function} windowFunc Window function
* @param {Number} alpha Alpha channel
*/
export default function FFT(bufferSize, sampleRate, windowFunc, alpha) {
this.bufferSize = bufferSize;
this.sampleRate = sampleRate;
this.bandwidth = (2 / bufferSize) * (sampleRate / 2);
this.sinTable = new Float32Array(bufferSize);
this.cosTable = new Float32Array(bufferSize);
this.windowValues = new Float32Array(bufferSize);
this.reverseTable = new Uint32Array(bufferSize);
this.peakBand = 0;
this.peak = 0;
var i;
switch (windowFunc) {
case 'bartlett':
for (i = 0; i < bufferSize; i++) {
this.windowValues[i] =
(2 / (bufferSize - 1)) *
((bufferSize - 1) / 2 - Math.abs(i - (bufferSize - 1) / 2));
}
break;
case 'bartlettHann':
for (i = 0; i < bufferSize; i++) {
this.windowValues[i] =
0.62 -
0.48 * Math.abs(i / (bufferSize - 1) - 0.5) -
0.38 * Math.cos((Math.PI * 2 * i) / (bufferSize - 1));
}
break;
case 'blackman':
alpha = alpha || 0.16;
for (i = 0; i < bufferSize; i++) {
this.windowValues[i] =
(1 - alpha) / 2 -
0.5 * Math.cos((Math.PI * 2 * i) / (bufferSize - 1)) +
(alpha / 2) *
Math.cos((4 * Math.PI * i) / (bufferSize - 1));
}
break;
case 'cosine':
for (i = 0; i < bufferSize; i++) {
this.windowValues[i] = Math.cos(
(Math.PI * i) / (bufferSize - 1) - Math.PI / 2
);
}
break;
case 'gauss':
alpha = alpha || 0.25;
for (i = 0; i < bufferSize; i++) {
this.windowValues[i] = Math.pow(
Math.E,
-0.5 *
Math.pow(
(i - (bufferSize - 1) / 2) /
((alpha * (bufferSize - 1)) / 2),
2
)
);
}
break;
case 'hamming':
for (i = 0; i < bufferSize; i++) {
this.windowValues[i] =
0.54 -
0.46 * Math.cos((Math.PI * 2 * i) / (bufferSize - 1));
}
break;
case 'hann':
case undefined:
for (i = 0; i < bufferSize; i++) {
this.windowValues[i] =
0.5 * (1 - Math.cos((Math.PI * 2 * i) / (bufferSize - 1)));
}
break;
case 'lanczoz':
for (i = 0; i < bufferSize; i++) {
this.windowValues[i] =
Math.sin(Math.PI * ((2 * i) / (bufferSize - 1) - 1)) /
(Math.PI * ((2 * i) / (bufferSize - 1) - 1));
}
break;
case 'rectangular':
for (i = 0; i < bufferSize; i++) {
this.windowValues[i] = 1;
}
break;
case 'triangular':
for (i = 0; i < bufferSize; i++) {
this.windowValues[i] =
(2 / bufferSize) *
(bufferSize / 2 - Math.abs(i - (bufferSize - 1) / 2));
}
break;
default:
throw Error("No such window function '" + windowFunc + "'");
}
var limit = 1;
var bit = bufferSize >> 1;
var i;
while (limit < bufferSize) {
for (i = 0; i < limit; i++) {
this.reverseTable[i + limit] = this.reverseTable[i] + bit;
}
limit = limit << 1;
bit = bit >> 1;
}
for (i = 0; i < bufferSize; i++) {
this.sinTable[i] = Math.sin(-Math.PI / i);
this.cosTable[i] = Math.cos(-Math.PI / i);
}
this.calculateSpectrum = function(buffer) {
// Locally scope variables for speed up
var bufferSize = this.bufferSize,
cosTable = this.cosTable,
sinTable = this.sinTable,
reverseTable = this.reverseTable,
real = new Float32Array(bufferSize),
imag = new Float32Array(bufferSize),
bSi = 2 / this.bufferSize,
sqrt = Math.sqrt,
rval,
ival,
mag,
spectrum = new Float32Array(bufferSize / 2);
var k = Math.floor(Math.log(bufferSize) / Math.LN2);
if (Math.pow(2, k) !== bufferSize) {
throw 'Invalid buffer size, must be a power of 2.';
}
if (bufferSize !== buffer.length) {
throw 'Supplied buffer is not the same size as defined FFT. FFT Size: ' +
bufferSize +
' Buffer Size: ' +
buffer.length;
}
var halfSize = 1,
phaseShiftStepReal,
phaseShiftStepImag,
currentPhaseShiftReal,
currentPhaseShiftImag,
off,
tr,
ti,
tmpReal;
for (var i = 0; i < bufferSize; i++) {
real[i] =
buffer[reverseTable[i]] * this.windowValues[reverseTable[i]];
imag[i] = 0;
}
while (halfSize < bufferSize) {
phaseShiftStepReal = cosTable[halfSize];
phaseShiftStepImag = sinTable[halfSize];
currentPhaseShiftReal = 1;
currentPhaseShiftImag = 0;
for (var fftStep = 0; fftStep < halfSize; fftStep++) {
var i = fftStep;
while (i < bufferSize) {
off = i + halfSize;
tr =
currentPhaseShiftReal * real[off] -
currentPhaseShiftImag * imag[off];
ti =
currentPhaseShiftReal * imag[off] +
currentPhaseShiftImag * real[off];
real[off] = real[i] - tr;
imag[off] = imag[i] - ti;
real[i] += tr;
imag[i] += ti;
i += halfSize << 1;
}
tmpReal = currentPhaseShiftReal;
currentPhaseShiftReal =
tmpReal * phaseShiftStepReal -
currentPhaseShiftImag * phaseShiftStepImag;
currentPhaseShiftImag =
tmpReal * phaseShiftStepImag +
currentPhaseShiftImag * phaseShiftStepReal;
}
halfSize = halfSize << 1;
}
for (var i = 0, N = bufferSize / 2; i < N; i++) {
rval = real[i];
ival = imag[i];
mag = bSi * sqrt(rval * rval + ival * ival);
if (mag > this.peak) {
this.peakBand = i;
this.peak = mag;
}
spectrum[i] = mag;
}
return spectrum;
};
}