UNPKG

meyda

Version:

Real-time feature extraction for the web audio api

205 lines (204 loc) 7.6 kB
import { __spreadArray } from "tslib"; import * as windowing from "./windowing"; var windows = {}; export function isPowerOfTwo(num) { while (num % 2 === 0 && num > 1) { num /= 2; } return num === 1; } export function error(message) { throw new Error("Meyda: " + message); } export function pointwiseBufferMult(a, b) { var c = []; for (var i = 0; i < Math.min(a.length, b.length); i++) { c[i] = a[i] * b[i]; } return c; } export function applyWindow(signal, windowname) { if (windowname !== "rect") { if (windowname === "" || !windowname) windowname = "hanning"; if (!windows[windowname]) windows[windowname] = {}; if (!windows[windowname][signal.length]) { try { windows[windowname][signal.length] = windowing[windowname](signal.length); } catch (e) { throw new Error("Invalid windowing function"); } } signal = pointwiseBufferMult(signal, windows[windowname][signal.length]); } return signal; } export function createBarkScale(length, sampleRate, bufferSize) { var barkScale = new Float32Array(length); for (var i = 0; i < barkScale.length; i++) { barkScale[i] = (i * sampleRate) / bufferSize; barkScale[i] = 13 * Math.atan(barkScale[i] / 1315.8) + 3.5 * Math.atan(Math.pow(barkScale[i] / 7518, 2)); } return barkScale; } export function typedToArray(t) { // utility to convert typed arrays to normal arrays return Array.prototype.slice.call(t); } export function arrayToTyped(t) { // utility to convert arrays to typed F32 arrays return Float32Array.from(t); } export function _normalize(num, range) { return num / range; } export function normalize(a, range) { return a.map(function (n) { return _normalize(n, range); }); } export function normalizeToOne(a) { var max = Math.max.apply(null, a); return a.map(function (n) { return n / max; }); } export function mean(a) { return (a.reduce(function (prev, cur) { return prev + cur; }) / a.length); } function _melToFreq(melValue) { var freqValue = 700 * (Math.exp(melValue / 1125) - 1); return freqValue; } function _freqToMel(freqValue) { var melValue = 1125 * Math.log(1 + freqValue / 700); return melValue; } export function melToFreq(mV) { return _melToFreq(mV); } export function freqToMel(fV) { return _freqToMel(fV); } export function createMelFilterBank(numFilters, sampleRate, bufferSize) { //the +2 is the upper and lower limits var melValues = new Float32Array(numFilters + 2); var melValuesInFreq = new Float32Array(numFilters + 2); //Generate limits in Hz - from 0 to the nyquist. var lowerLimitFreq = 0; var upperLimitFreq = sampleRate / 2; //Convert the limits to Mel var lowerLimitMel = _freqToMel(lowerLimitFreq); var upperLimitMel = _freqToMel(upperLimitFreq); //Find the range var range = upperLimitMel - lowerLimitMel; //Find the range as part of the linear interpolation var valueToAdd = range / (numFilters + 1); var fftBinsOfFreq = new Array(numFilters + 2); for (var i = 0; i < melValues.length; i++) { // Initialising the mel frequencies // They're a linear interpolation between the lower and upper limits. melValues[i] = i * valueToAdd; // Convert back to Hz melValuesInFreq[i] = _melToFreq(melValues[i]); // Find the corresponding bins fftBinsOfFreq[i] = Math.floor(((bufferSize + 1) * melValuesInFreq[i]) / sampleRate); } var filterBank = new Array(numFilters); for (var j = 0; j < filterBank.length; j++) { // Create a two dimensional array of size numFilters * (buffersize/2)+1 // pre-populating the arrays with 0s. filterBank[j] = new Array(bufferSize / 2 + 1).fill(0); //creating the lower and upper slopes for each bin for (var i = fftBinsOfFreq[j]; i < fftBinsOfFreq[j + 1]; i++) { filterBank[j][i] = (i - fftBinsOfFreq[j]) / (fftBinsOfFreq[j + 1] - fftBinsOfFreq[j]); } for (var i = fftBinsOfFreq[j + 1]; i < fftBinsOfFreq[j + 2]; i++) { filterBank[j][i] = (fftBinsOfFreq[j + 2] - i) / (fftBinsOfFreq[j + 2] - fftBinsOfFreq[j + 1]); } } return filterBank; } export function hzToOctaves(freq, A440) { return Math.log2((16 * freq) / A440); } export function normalizeByColumn(a) { var emptyRow = a[0].map(function () { return 0; }); var colDenominators = a .reduce(function (acc, row) { row.forEach(function (cell, j) { acc[j] += Math.pow(cell, 2); }); return acc; }, emptyRow) .map(Math.sqrt); return a.map(function (row, i) { return row.map(function (v, j) { return v / (colDenominators[j] || 1); }); }); } export function createChromaFilterBank(numFilters, sampleRate, bufferSize, centerOctave, octaveWidth, baseC, A440) { if (centerOctave === void 0) { centerOctave = 5; } if (octaveWidth === void 0) { octaveWidth = 2; } if (baseC === void 0) { baseC = true; } if (A440 === void 0) { A440 = 440; } var numOutputBins = Math.floor(bufferSize / 2) + 1; var frequencyBins = new Array(bufferSize) .fill(0) .map(function (_, i) { return numFilters * hzToOctaves((sampleRate * i) / bufferSize, A440); }); // Set a value for the 0 Hz bin that is 1.5 octaves below bin 1 // (so chroma is 50% rotated from bin 1, and bin width is broad) frequencyBins[0] = frequencyBins[1] - 1.5 * numFilters; var binWidthBins = frequencyBins .slice(1) .map(function (v, i) { return Math.max(v - frequencyBins[i]); }, 1) .concat([1]); var halfNumFilters = Math.round(numFilters / 2); var filterPeaks = new Array(numFilters) .fill(0) .map(function (_, i) { return frequencyBins.map(function (frq) { return ((10 * numFilters + halfNumFilters + frq - i) % numFilters) - halfNumFilters; }); }); var weights = filterPeaks.map(function (row, i) { return row.map(function (_, j) { return Math.exp(-0.5 * Math.pow((2 * filterPeaks[i][j]) / binWidthBins[j], 2)); }); }); weights = normalizeByColumn(weights); if (octaveWidth) { var octaveWeights = frequencyBins.map(function (v) { return Math.exp(-0.5 * Math.pow((v / numFilters - centerOctave) / octaveWidth, 2)); }); weights = weights.map(function (row) { return row.map(function (cell, j) { return cell * octaveWeights[j]; }); }); } if (baseC) { weights = __spreadArray(__spreadArray([], weights.slice(3), true), weights.slice(0, 3), true); } return weights.map(function (row) { return row.slice(0, numOutputBins); }); } export function frame(buffer, frameLength, hopLength) { if (buffer.length < frameLength) { throw new Error("Buffer is too short for frame length"); } if (hopLength < 1) { throw new Error("Hop length cannot be less that 1"); } if (frameLength < 1) { throw new Error("Frame length cannot be less that 1"); } var numFrames = 1 + Math.floor((buffer.length - frameLength) / hopLength); return new Array(numFrames) .fill(0) .map(function (_, i) { return buffer.slice(i * hopLength, i * hopLength + frameLength); }); }