meyda
Version:
Real-time feature extraction for the web audio api
205 lines (204 loc) • 7.76 kB
JavaScript
/**
* This file contains the default export for Meyda, you probably want to check
* out {@link default}
*
* @module Meyda
*/
import * as utilities from "./utilities";
import * as extractors from "./featureExtractors";
import { fft } from "fftjs";
import { MeydaAnalyzer } from "./meyda-wa";
var Meyda = {
audioContext: null,
spn: null,
bufferSize: 512,
sampleRate: 44100,
melBands: 26,
chromaBands: 12,
callback: null,
windowingFunction: "hanning",
featureExtractors: extractors,
EXTRACTION_STARTED: false,
numberOfMFCCCoefficients: 13,
numberOfBarkBands: 24,
_featuresToExtract: [],
windowing: utilities.applyWindow,
/** @hidden */
_errors: {
notPow2: new Error("Meyda: Buffer size must be a power of 2, e.g. 64 or 512"),
featureUndef: new Error("Meyda: No features defined."),
invalidFeatureFmt: new Error("Meyda: Invalid feature format"),
invalidInput: new Error("Meyda: Invalid input."),
noAC: new Error("Meyda: No AudioContext specified."),
noSource: new Error("Meyda: No source node specified."),
},
/**
* @summary
* Create a MeydaAnalyzer
*
* A factory function for creating a MeydaAnalyzer, the interface for using
* Meyda in the context of Web Audio.
*
* ```javascript
* const analyzer = Meyda.createMeydaAnalyzer({
* "audioContext": audioContext,
* "source": source,
* "bufferSize": 512,
* "featureExtractors": ["rms"],
* "inputs": 2,
* "callback": features => {
* levelRangeElement.value = features.rms;
* }
* });
* ```
*/
createMeydaAnalyzer: createMeydaAnalyzer,
/**
* List available audio feature extractors. Return format provides the key to
* be used in selecting the extractor in the extract methods
*/
listAvailableFeatureExtractors: listAvailableFeatureExtractors,
/**
* Extract an audio feature from a buffer
*
* Unless `meyda.windowingFunction` is set otherwise, `extract` will
* internally apply a hanning window to the buffer prior to conversion into
* the frequency domain.
*
* ```javascript
* meyda.bufferSize = 2048;
* const features = meyda.extract(['zcr', 'spectralCentroid'], signal);
* ```
*/
extract: function (feature, signal, previousSignal) {
var _this = this;
if (!signal)
throw this._errors.invalidInput;
else if (typeof signal != "object")
throw this._errors.invalidInput;
else if (!feature)
throw this._errors.featureUndef;
else if (!utilities.isPowerOfTwo(signal.length))
throw this._errors.notPow2;
if (typeof this.barkScale == "undefined" ||
this.barkScale.length != this.bufferSize) {
this.barkScale = utilities.createBarkScale(this.bufferSize, this.sampleRate, this.bufferSize);
}
// Recalculate mel bank if buffer length changed
if (typeof this.melFilterBank == "undefined" ||
this.barkScale.length != this.bufferSize ||
this.melFilterBank.length != this.melBands) {
this.melFilterBank = utilities.createMelFilterBank(Math.max(this.melBands, this.numberOfMFCCCoefficients), this.sampleRate, this.bufferSize);
}
// Recalculate chroma bank if buffer length changed
if (typeof this.chromaFilterBank == "undefined" ||
this.chromaFilterBank.length != this.chromaBands) {
this.chromaFilterBank = utilities.createChromaFilterBank(this.chromaBands, this.sampleRate, this.bufferSize);
}
if ("buffer" in signal && typeof signal.buffer == "undefined") {
//signal is a normal array, convert to F32A
this.signal = utilities.arrayToTyped(signal);
}
else {
this.signal = signal;
}
var preparedSignal = prepareSignalWithSpectrum(signal, this.windowingFunction, this.bufferSize);
this.signal = preparedSignal.windowedSignal;
this.complexSpectrum = preparedSignal.complexSpectrum;
this.ampSpectrum = preparedSignal.ampSpectrum;
if (previousSignal) {
var preparedSignal_1 = prepareSignalWithSpectrum(previousSignal, this.windowingFunction, this.bufferSize);
this.previousSignal = preparedSignal_1.windowedSignal;
this.previousComplexSpectrum = preparedSignal_1.complexSpectrum;
this.previousAmpSpectrum = preparedSignal_1.ampSpectrum;
}
var extract = function (feature) {
return _this.featureExtractors[feature]({
ampSpectrum: _this.ampSpectrum,
chromaFilterBank: _this.chromaFilterBank,
complexSpectrum: _this.complexSpectrum,
signal: _this.signal,
bufferSize: _this.bufferSize,
sampleRate: _this.sampleRate,
barkScale: _this.barkScale,
melFilterBank: _this.melFilterBank,
previousSignal: _this.previousSignal,
previousAmpSpectrum: _this.previousAmpSpectrum,
previousComplexSpectrum: _this.previousComplexSpectrum,
numberOfMFCCCoefficients: _this.numberOfMFCCCoefficients,
numberOfBarkBands: _this.numberOfBarkBands,
});
};
if (typeof feature === "object") {
return feature.reduce(function (acc, el) {
var _a;
return Object.assign({}, acc, (_a = {},
_a[el] = extract(el),
_a));
}, {});
}
else if (typeof feature === "string") {
return extract(feature);
}
else {
throw this._errors.invalidFeatureFmt;
}
},
};
var prepareSignalWithSpectrum = function (signal, windowingFunction, bufferSize) {
var preparedSignal = {};
if (typeof signal.buffer == "undefined") {
//signal is a normal array, convert to F32A
preparedSignal.signal = utilities.arrayToTyped(signal);
}
else {
preparedSignal.signal = signal;
}
preparedSignal.windowedSignal = utilities.applyWindow(preparedSignal.signal, windowingFunction);
preparedSignal.complexSpectrum = fft(preparedSignal.windowedSignal);
preparedSignal.ampSpectrum = new Float32Array(bufferSize / 2);
for (var i = 0; i < bufferSize / 2; i++) {
preparedSignal.ampSpectrum[i] = Math.sqrt(Math.pow(preparedSignal.complexSpectrum.real[i], 2) +
Math.pow(preparedSignal.complexSpectrum.imag[i], 2));
}
return preparedSignal;
};
export default Meyda;
/**
* List available audio feature extractors. Return format provides the key to
* be used in selecting the extractor in the extract methods
*/
function listAvailableFeatureExtractors() {
return Object.keys(this.featureExtractors);
}
/**
* Create a MeydaAnalyzer
*
* A factory function for creating a MeydaAnalyzer, the interface for using
* Meyda in the context of Web Audio.
*
* ```javascript
* const analyzer = Meyda.createMeydaAnalyzer({
* "audioContext": audioContext,
* "source": source,
* "bufferSize": 512,
* "featureExtractors": ["rms"],
* "inputs": 2,
* "callback": features => {
* levelRangeElement.value = features.rms;
* }
* });
* ```
*/
function createMeydaAnalyzer(options) {
return new MeydaAnalyzer(options, Object.assign({}, Meyda));
}
/**
* Apply a windowing function to a signal
*/
function windowing(signal, windowname) {
return utilities.applyWindow(signal, windowname);
}
// @ts-ignore
if (typeof window !== "undefined")
window.Meyda = Meyda;