class PitchIndicator {
constructor(sampleRate) {
this.sampleRate = sampleRate;
this.pitch = -1;
this.accuracy = 0;
this.bufferSize = sampleRate * 0.1;
this.inputBuffer = new Float32Array(this.bufferSize);
this.corrBuffer = new Float32Array(this.bufferSize);
this.cepsBuffer = new Float32Array(this.bufferSize);
this.cepsLifter = new Float32Array(this.bufferSize);
this.cepsPeak = 0;
this.cepsMaxVal = -Infinity;
this.cepsReady = false;
}
process(sample) {
this.inputBuffer.set(this.inputBuffer.subarray(1));
this.inputBuffer[this.inputBuffer.length - 1] = sample;
this.corrBuffer.fill(0);
for (let i = 0; i < this.bufferSize; i++) {
for (let j = 0; j < this.bufferSize - i; j++) {
this.corrBuffer[i] += this.inputBuffer[j] * this.inputBuffer[j + i];
}
}
this.cepsBuffer.fill(0);
for (let i = 0; i < this.bufferSize; i++) {
for (let j = 0; j < i; j++) {
this.cepsBuffer[i] += this.corrBuffer[j] * this.cepsBuffer[i - j];
}
this.cepsBuffer[i] = Math.log(Math.abs(this.cepsBuffer[i] + 1e-10));
}
this.cepsLifter.fill(0);
let lifterSize = Math.round(this.sampleRate / 2000);
for (let i = 0; i < lifterSize; i++) {
this.cepsLifter[i] = 1;
}
for (let i = this.bufferSize - lifterSize; i < this.bufferSize; i++) {
this.cepsLifter[i] = 1;
}
for (let i = 0; i < this.bufferSize; i++) {
this.cepsBuffer[i] *= this.cepsLifter[i];
}
if (this.cepsReady) {
for (let i = 0; i < this.bufferSize / 2; i++) {
if (this.cepsBuffer[i] > this.cepsMaxVal) {
this.cepsPeak = i;
this.cepsMaxVal = this.cepsBuffer[i];
}
}
this.pitch = this.sampleRate / this.cepsPeak;
this.accuracy = this.cepsMaxVal;
}
this.cepsReady = true;
return {
pitch: this.pitch,
accuracy: this.accuracy,
};
}
}