UNPKG

poly-peach

Version:

A targeted pitch-detection library for node in the browser

199 lines (198 loc) 8.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Chromagram = void 0; var FFT = require('fft.js'); var TOTAL_NOTES = 12; var DEFAULT_REFERENCE_FREQUENCY = 130.81278265; // C3 var DEFAULT_HARMONICS = 2; var DEFAULT_OCTAVES = 4; var DEFAULT_BINS = 2; var Chromagram = /** @class */ (function () { function Chromagram(frameSize, fs) { this.bufferSize = 8192; this.kiss_ready = false; this.setSamplingFrequency(fs); this.setInputAudioFrameSize(frameSize); this.setParameters(DEFAULT_REFERENCE_FREQUENCY, DEFAULT_HARMONICS, DEFAULT_OCTAVES, DEFAULT_BINS); } Chromagram.prototype.setParameters = function (_referenceFrequency, _numHarmonics, _numOctaves, _numBinsToSearch) { this.setReferenceFrequency(_referenceFrequency); this.setNumHarmonics(_numHarmonics); this.setNumOctaves(_numOctaves); this.setNumBinsToSearch(_numBinsToSearch); this.noteFrequencies = new Array(TOTAL_NOTES * this.numOctaves); for (var i = 0; i < this.noteFrequencies.length; i++) { this.noteFrequencies[i] = this.referenceFrequency * Math.pow(2, ((i) / TOTAL_NOTES)); } this.setupFFT(); this.buffer = new Array(this.bufferSize); this.buffer.fill(0); this.realInput = new Array(this.bufferSize); this.realInput.fill(0); this.chromagram = new Array(TOTAL_NOTES); this.chromagram.fill(0); this.pitches = new Array(TOTAL_NOTES * this.numOctaves); this.pitches.fill(0); for (var i = 0; i < this.chromagram.length; i++) { this.chromagram[i] = 0.0; } for (var i = 0; i < this.pitches.length; i++) { this.pitches[i] = 0.0; } this.magnitudeSpectrum = new Array((this.bufferSize / 2) + 1); this.magnitudeSpectrum.fill(0); this.makeHammingWindow(); this.numSamplesSinceLastCalculation = 0; this.chromaCalculationInterval = 4096; this.chromaReady = false; }; Chromagram.prototype.processAudioFrame = function (inputAudioFrame) { this.chromaReady = false; this.downSampleFrame(inputAudioFrame); for (var i = 0; i < this.bufferSize - this.downSampledAudioFrameSize; i++) { this.buffer[i] = this.buffer[i + this.downSampledAudioFrameSize]; } var n = 0; for (var i = (this.bufferSize - this.downSampledAudioFrameSize); i < this.bufferSize; i++) { this.buffer[i] = this.downsampledInputAudioFrame[n]; n++; } this.numSamplesSinceLastCalculation += this.inputAudioFrameSize; if (this.numSamplesSinceLastCalculation >= this.chromaCalculationInterval) { this.calculateChromagram(); this.numSamplesSinceLastCalculation = 0; } }; Chromagram.prototype.setInputAudioFrameSize = function (frameSize) { this.inputAudioFrameSize = frameSize; this.downsampledInputAudioFrame = new Array(this.inputAudioFrameSize / 4); this.downsampledInputAudioFrame.fill(0); this.downSampledAudioFrameSize = this.downsampledInputAudioFrame.length; }; Chromagram.prototype.setReferenceFrequency = function (freq) { this.referenceFrequency = freq; }; Chromagram.prototype.getReferenceFrequency = function () { return this.referenceFrequency; }; Chromagram.prototype.setNumHarmonics = function (n) { this.numHarmonics = n; }; Chromagram.prototype.setNumOctaves = function (n) { this.numOctaves = n; }; Chromagram.prototype.getNumOctaves = function () { return this.numOctaves; }; Chromagram.prototype.setNumBinsToSearch = function (n) { this.numBinsToSearch = n; }; Chromagram.prototype.setSamplingFrequency = function (fs) { this.samplingFrequency = fs; }; Chromagram.prototype.setChromaCalculationInterval = function (numSamples) { this.chromaCalculationInterval = numSamples; }; Chromagram.prototype.getChromagram = function () { return this.chromagram; }; Chromagram.prototype.getPitches = function () { return this.pitches; }; Chromagram.prototype.isReady = function () { return this.chromaReady; }; Chromagram.prototype.setupFFT = function () { this.fft = new FFT(this.bufferSize); this.complexOutput = this.fft.createComplexArray(); this.kiss_ready = true; }; Chromagram.prototype.calculateChromagram = function () { this.calculateMagnitudeSpectrum(); var divisorRatio = ((this.samplingFrequency) / 4.0) / (this.bufferSize); for (var n = 0; n < TOTAL_NOTES; n++) { var chromaSum = 0.0; for (var octave = 1; octave <= this.numOctaves; octave++) { var noteSum = 0.0; var centerBin = this.round((this.noteFrequencies[n + (octave - 1) * TOTAL_NOTES]) / divisorRatio); var minBin = centerBin - (this.numBinsToSearch); var maxBin = centerBin + (this.numBinsToSearch); var maxVal = 0.0; for (var k = minBin; k < maxBin; k++) { if (this.magnitudeSpectrum[k] > maxVal) { maxVal = this.magnitudeSpectrum[k]; } } noteSum += maxVal; this.pitches[n + (octave - 1) * TOTAL_NOTES] = noteSum; //TODO:deprecated? noteSum = 0.0; for (var harmonic = 1; harmonic <= this.numHarmonics; harmonic++) { var centerBin_1 = this.round((this.noteFrequencies[n + (octave - 1) * TOTAL_NOTES] * octave * harmonic) / divisorRatio); var minBin_1 = centerBin_1 - (this.numBinsToSearch * harmonic); var maxBin_1 = centerBin_1 + (this.numBinsToSearch * harmonic); var maxVal_1 = 0.0; for (var k = minBin_1; k < maxBin_1; k++) { if (this.magnitudeSpectrum[k] > maxVal_1) { maxVal_1 = this.magnitudeSpectrum[k]; } } noteSum += (maxVal_1 / harmonic); } chromaSum += noteSum; } this.chromagram[n] = chromaSum; } this.chromaReady = true; }; Chromagram.prototype.calculateMagnitudeSpectrum = function () { var i = 0; for (var i_1 = 0; i_1 < this.bufferSize; i_1++) { this.realInput[i_1] = this.buffer[i_1] * this.window[i_1]; } // This is where all the magic happens. this.fft.realTransform(this.complexOutput, this.realInput); this.fft.completeSpectrum(this.complexOutput); //TODO:is this unnecessary? for (var i_2 = 0; i_2 < (this.bufferSize / 2) + 1; i_2++) { this.magnitudeSpectrum[i_2] = Math.sqrt(Math.pow(this.complexOutput[i_2 * 2], 2) + Math.pow(this.complexOutput[i_2 * 2 + 1], 2)); this.magnitudeSpectrum[i_2] = Math.sqrt(this.magnitudeSpectrum[i_2]); } }; Chromagram.prototype.downSampleFrame = function (inputAudioFrame) { var filteredFrame = new Array(this.inputAudioFrameSize); filteredFrame.fill(0); var b0, b1, b2, a1, a2; var x_1, x_2, y_1, y_2; b0 = 0.2929; b1 = 0.5858; b2 = 0.2929; a1 = -0.0000; a2 = 0.1716; x_1 = 0; x_2 = 0; y_1 = 0; y_2 = 0; for (var i = 0; i < this.inputAudioFrameSize; i++) { filteredFrame[i] = inputAudioFrame[i] * b0 + x_1 * b1 + x_2 * b2 - y_1 * a1 - y_2 * a2; x_2 = x_1; x_1 = inputAudioFrame[i]; y_2 = y_1; y_1 = filteredFrame[i]; } for (var i = 0; i < this.inputAudioFrameSize / 4; i++) { this.downsampledInputAudioFrame[i] = filteredFrame[i * 4]; } }; Chromagram.prototype.makeHammingWindow = function () { this.window = new Array(this.bufferSize); this.window.fill(0); for (var n = 0; n < this.bufferSize; n++) { this.window[n] = 0.54 - 0.46 * Math.cos(2 * Math.PI * (n / (this.bufferSize))); } }; Chromagram.prototype.round = function (val) { return Math.floor(val + 0.5); }; return Chromagram; }()); exports.Chromagram = Chromagram;