poly-peach
Version:
A targeted pitch-detection library for node in the browser
199 lines (198 loc) • 8.5 kB
JavaScript
"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;