UNPKG

wavesurfer.js

Version:

Interactive navigable audio visualization using Web Audio and Canvas

223 lines (199 loc) 7.27 kB
/* 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; }; }