meyda
Version:
Real-time feature extraction for the web audio api
417 lines (382 loc) • 13.5 kB
JavaScript
'use strict';
function rms (_a) {
var signal = _a.signal;
// Keeping this bad runtime typecheck for consistency
if (typeof signal !== "object") {
throw new TypeError();
}
var rms = 0;
for (var i = 0; i < signal.length; i++) {
rms += Math.pow(signal[i], 2);
}
rms = rms / signal.length;
rms = Math.sqrt(rms);
return rms;
}
function energy (_a) {
var signal = _a.signal;
if (typeof signal !== "object") {
throw new TypeError();
}
var energy = 0;
for (var i = 0; i < signal.length; i++) {
energy += Math.pow(Math.abs(signal[i]), 2);
}
return energy;
}
function spectralSlope (_a) {
var ampSpectrum = _a.ampSpectrum, sampleRate = _a.sampleRate, bufferSize = _a.bufferSize;
if (typeof ampSpectrum !== "object") {
throw new TypeError();
}
//linear regression
var ampSum = 0;
var freqSum = 0;
var freqs = new Float32Array(ampSpectrum.length);
var powFreqSum = 0;
var ampFreqSum = 0;
for (var i = 0; i < ampSpectrum.length; i++) {
ampSum += ampSpectrum[i];
var curFreq = (i * sampleRate) / bufferSize;
freqs[i] = curFreq;
powFreqSum += curFreq * curFreq;
freqSum += curFreq;
ampFreqSum += curFreq * ampSpectrum[i];
}
return ((ampSpectrum.length * ampFreqSum - freqSum * ampSum) /
(ampSum * (powFreqSum - Math.pow(freqSum, 2))));
}
function mu(i, amplitudeSpect) {
var numerator = 0;
var denominator = 0;
for (var k = 0; k < amplitudeSpect.length; k++) {
numerator += Math.pow(k, i) * Math.abs(amplitudeSpect[k]);
denominator += amplitudeSpect[k];
}
return numerator / denominator;
}
function spectralCentroid (_a) {
var ampSpectrum = _a.ampSpectrum;
if (typeof ampSpectrum !== "object") {
throw new TypeError();
}
return mu(1, ampSpectrum);
}
function spectralRolloff (_a) {
var ampSpectrum = _a.ampSpectrum, sampleRate = _a.sampleRate;
if (typeof ampSpectrum !== "object") {
throw new TypeError();
}
var ampspec = ampSpectrum;
//calculate nyquist bin
var nyqBin = sampleRate / (2 * (ampspec.length - 1));
var ec = 0;
for (var i = 0; i < ampspec.length; i++) {
ec += ampspec[i];
}
var threshold = 0.99 * ec;
var n = ampspec.length - 1;
while (ec > threshold && n >= 0) {
ec -= ampspec[n];
--n;
}
return (n + 1) * nyqBin;
}
function spectralFlatness (_a) {
var ampSpectrum = _a.ampSpectrum;
if (typeof ampSpectrum !== "object") {
throw new TypeError();
}
var numerator = 0;
var denominator = 0;
for (var i = 0; i < ampSpectrum.length; i++) {
numerator += Math.log(ampSpectrum[i]);
denominator += ampSpectrum[i];
}
return ((Math.exp(numerator / ampSpectrum.length) * ampSpectrum.length) /
denominator);
}
function spectralSpread (_a) {
var ampSpectrum = _a.ampSpectrum;
if (typeof ampSpectrum !== "object") {
throw new TypeError();
}
return Math.sqrt(mu(2, ampSpectrum) - Math.pow(mu(1, ampSpectrum), 2));
}
function spectralSkewness (_a) {
var ampSpectrum = _a.ampSpectrum;
if (typeof ampSpectrum !== "object") {
throw new TypeError();
}
var mu1 = mu(1, ampSpectrum);
var mu2 = mu(2, ampSpectrum);
var mu3 = mu(3, ampSpectrum);
var numerator = 2 * Math.pow(mu1, 3) - 3 * mu1 * mu2 + mu3;
var denominator = Math.pow(Math.sqrt(mu2 - Math.pow(mu1, 2)), 3);
return numerator / denominator;
}
function spectralKurtosis (_a) {
var ampSpectrum = _a.ampSpectrum;
if (typeof ampSpectrum !== "object") {
throw new TypeError();
}
var ampspec = ampSpectrum;
var mu1 = mu(1, ampspec);
var mu2 = mu(2, ampspec);
var mu3 = mu(3, ampspec);
var mu4 = mu(4, ampspec);
var numerator = -3 * Math.pow(mu1, 4) + 6 * mu1 * mu2 - 4 * mu1 * mu3 + mu4;
var denominator = Math.pow(Math.sqrt(mu2 - Math.pow(mu1, 2)), 4);
return numerator / denominator;
}
function zcr (_a) {
var signal = _a.signal;
if (typeof signal !== "object") {
throw new TypeError();
}
var zcr = 0;
for (var i = 1; i < signal.length; i++) {
if ((signal[i - 1] >= 0 && signal[i] < 0) ||
(signal[i - 1] < 0 && signal[i] >= 0)) {
zcr++;
}
}
return zcr;
}
function loudness (_a) {
var ampSpectrum = _a.ampSpectrum, barkScale = _a.barkScale, _b = _a.numberOfBarkBands, numberOfBarkBands = _b === void 0 ? 24 : _b;
if (typeof ampSpectrum !== "object" || typeof barkScale !== "object") {
throw new TypeError();
}
var NUM_BARK_BANDS = numberOfBarkBands;
var specific = new Float32Array(NUM_BARK_BANDS);
var total = 0;
var normalisedSpectrum = ampSpectrum;
var bbLimits = new Int32Array(NUM_BARK_BANDS + 1);
bbLimits[0] = 0;
var currentBandEnd = barkScale[normalisedSpectrum.length - 1] / NUM_BARK_BANDS;
var currentBand = 1;
for (var i = 0; i < normalisedSpectrum.length; i++) {
while (barkScale[i] > currentBandEnd) {
bbLimits[currentBand++] = i;
currentBandEnd =
(currentBand * barkScale[normalisedSpectrum.length - 1]) /
NUM_BARK_BANDS;
}
}
bbLimits[NUM_BARK_BANDS] = normalisedSpectrum.length - 1;
//process
for (var i = 0; i < NUM_BARK_BANDS; i++) {
var sum = 0;
for (var j = bbLimits[i]; j < bbLimits[i + 1]; j++) {
sum += normalisedSpectrum[j];
}
specific[i] = Math.pow(sum, 0.23);
}
//get total loudness
for (var i = 0; i < specific.length; i++) {
total += specific[i];
}
return {
specific: specific,
total: total,
};
}
function perceptualSpread (_a) {
var ampSpectrum = _a.ampSpectrum, barkScale = _a.barkScale;
var loudnessValue = loudness({ ampSpectrum: ampSpectrum, barkScale: barkScale });
var max = 0;
for (var i = 0; i < loudnessValue.specific.length; i++) {
if (loudnessValue.specific[i] > max) {
max = loudnessValue.specific[i];
}
}
var spread = Math.pow((loudnessValue.total - max) / loudnessValue.total, 2);
return spread;
}
function perceptualSharpness (_a) {
var ampSpectrum = _a.ampSpectrum, barkScale = _a.barkScale;
var loudnessValue = loudness({ ampSpectrum: ampSpectrum, barkScale: barkScale });
var spec = loudnessValue.specific;
var output = 0;
for (var i = 0; i < spec.length; i++) {
if (i < 15) {
output += (i + 1) * spec[i + 1];
}
else {
output += 0.066 * Math.exp(0.171 * (i + 1));
}
}
output *= 0.11 / loudnessValue.total;
return output;
}
function extractPowerSpectrum (_a) {
var ampSpectrum = _a.ampSpectrum;
if (typeof ampSpectrum !== "object") {
throw new TypeError();
}
var powerSpectrum = new Float32Array(ampSpectrum.length);
for (var i = 0; i < powerSpectrum.length; i++) {
powerSpectrum[i] = Math.pow(ampSpectrum[i], 2);
}
return powerSpectrum;
}
function extractMelBands (_a) {
var ampSpectrum = _a.ampSpectrum, melFilterBank = _a.melFilterBank, bufferSize = _a.bufferSize;
if (typeof ampSpectrum !== "object") {
throw new TypeError("Valid ampSpectrum is required to generate melBands");
}
if (typeof melFilterBank !== "object") {
throw new TypeError("Valid melFilterBank is required to generate melBands");
}
var powSpec = extractPowerSpectrum({ ampSpectrum: ampSpectrum });
var numFilters = melFilterBank.length;
var filtered = Array(numFilters);
var loggedMelBands = new Float32Array(numFilters);
for (var i = 0; i < loggedMelBands.length; i++) {
filtered[i] = new Float32Array(bufferSize / 2);
loggedMelBands[i] = 0;
for (var j = 0; j < bufferSize / 2; j++) {
//point-wise multiplication between power spectrum and filterbanks.
filtered[i][j] = melFilterBank[i][j] * powSpec[j];
//summing up all of the coefficients into one array
loggedMelBands[i] += filtered[i][j];
}
//log each coefficient.
loggedMelBands[i] = Math.log(loggedMelBands[i] + 1);
}
return Array.prototype.slice.call(loggedMelBands);
}
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
/*===========================================================================*\
* Discrete Cosine Transform
*
* (c) Vail Systems. Joshua Jung and Ben Bryan. 2015
*
* This code is not designed to be highly optimized but as an educational
* tool to understand the Mel-scale and its related coefficients used in
* human speech analysis.
\*===========================================================================*/
var cosMap = null;
// Builds a cosine map for the given input size. This allows multiple input sizes to be memoized automagically
// if you want to run the DCT over and over.
var memoizeCosines = function(N) {
cosMap = cosMap || {};
cosMap[N] = new Array(N*N);
var PI_N = Math.PI / N;
for (var k = 0; k < N; k++) {
for (var n = 0; n < N; n++) {
cosMap[N][n + (k * N)] = Math.cos(PI_N * (n + 0.5) * k);
}
}
};
function dct$2(signal, scale) {
var L = signal.length;
scale = scale || 2;
if (!cosMap || !cosMap[L]) memoizeCosines(L);
var coefficients = signal.map(function () {return 0;});
return coefficients.map(function (__, ix) {
return scale * signal.reduce(function (prev, cur, ix_, arr) {
return prev + (cur * cosMap[L][ix_ + (ix * L)]);
}, 0);
});
}
var dct_1 = dct$2;
var dct = dct_1;
var dct$1 = /*@__PURE__*/getDefaultExportFromCjs(dct);
function mfcc (_a) {
// Tutorial from:
// http://practicalcryptography.com/miscellaneous/machine-learning
// /guide-mel-frequency-cepstral-coefficients-mfccs/
// @ts-ignore
var ampSpectrum = _a.ampSpectrum, melFilterBank = _a.melFilterBank, numberOfMFCCCoefficients = _a.numberOfMFCCCoefficients, bufferSize = _a.bufferSize;
var _numberOfMFCCCoefficients = Math.min(40, Math.max(1, numberOfMFCCCoefficients || 13));
var numFilters = melFilterBank.length;
if (numFilters < _numberOfMFCCCoefficients) {
throw new Error("Insufficient filter bank for requested number of coefficients");
}
var loggedMelBandsArray = extractMelBands({
ampSpectrum: ampSpectrum,
melFilterBank: melFilterBank,
bufferSize: bufferSize,
});
var mfccs = dct$1(loggedMelBandsArray).slice(0, _numberOfMFCCCoefficients);
return mfccs;
}
function chroma (_a) {
var ampSpectrum = _a.ampSpectrum, chromaFilterBank = _a.chromaFilterBank;
if (typeof ampSpectrum !== "object") {
throw new TypeError("Valid ampSpectrum is required to generate chroma");
}
if (typeof chromaFilterBank !== "object") {
throw new TypeError("Valid chromaFilterBank is required to generate chroma");
}
var chromagram = chromaFilterBank.map(function (row, i) {
return ampSpectrum.reduce(function (acc, v, j) { return acc + v * row[j]; }, 0);
});
var maxVal = Math.max.apply(Math, chromagram);
return maxVal ? chromagram.map(function (v) { return v / maxVal; }) : chromagram;
}
// This file isn't being typechecked at all because there are major issues with it.
// See #852 for details. Once that's merged, this file should be typechecked.
// @ts-nocheck
function spectralFlux (_a) {
var signal = _a.signal, previousSignal = _a.previousSignal, bufferSize = _a.bufferSize;
if (typeof signal !== "object" || typeof previousSignal != "object") {
throw new TypeError();
}
var sf = 0;
for (var i = -(bufferSize / 2); i < signal.length / 2 - 1; i++) {
x = Math.abs(signal[i]) - Math.abs(previousSignal[i]);
sf += (x + Math.abs(x)) / 2;
}
return sf;
}
function spectralCrest (_a) {
var ampSpectrum = _a.ampSpectrum;
if (typeof ampSpectrum !== "object") {
throw new TypeError();
}
var rms = 0;
var peak = -Infinity;
ampSpectrum.forEach(function (x) {
rms += Math.pow(x, 2);
peak = x > peak ? x : peak;
});
rms = rms / ampSpectrum.length;
rms = Math.sqrt(rms);
return peak / rms;
}
var buffer = function (args) {
return args.signal;
};
var complexSpectrum = function (args) {
return args.complexSpectrum;
};
var amplitudeSpectrum = function (args) {
return args.ampSpectrum;
};
exports.amplitudeSpectrum = amplitudeSpectrum;
exports.buffer = buffer;
exports.chroma = chroma;
exports.complexSpectrum = complexSpectrum;
exports.energy = energy;
exports.loudness = loudness;
exports.melBands = extractMelBands;
exports.mfcc = mfcc;
exports.perceptualSharpness = perceptualSharpness;
exports.perceptualSpread = perceptualSpread;
exports.powerSpectrum = extractPowerSpectrum;
exports.rms = rms;
exports.spectralCentroid = spectralCentroid;
exports.spectralCrest = spectralCrest;
exports.spectralFlatness = spectralFlatness;
exports.spectralFlux = spectralFlux;
exports.spectralKurtosis = spectralKurtosis;
exports.spectralRolloff = spectralRolloff;
exports.spectralSkewness = spectralSkewness;
exports.spectralSlope = spectralSlope;
exports.spectralSpread = spectralSpread;
exports.zcr = zcr;