xxxxxxxxxx
191
/**
* Pitch Detection using Auto Correlation.
*
* Auto correlation multiplies each sample in a buffer by all
* of the other samples. This emphasizes the fundamental
* frequency.
*
* Running the signal through a low pass filter prior to
* autocorrelation helps bring out the fundamental frequency.
*
* The visualization is a correlogram, which plots
* the autocorrelations.
*
* We calculate the pitch by counting the number of samples
* between peaks.
*
* Example by Jason Sigal and Golan Levin.
*/
var source, fft, lowPass;
// center clip nullifies samples below a clip amount
var doCenterClip = true;
var centerClipThreshold = 20.0;
// normalize pre / post autocorrelation
var preNormalize = true;
var postNormalize = true;
function setup() {
createCanvas(windowWidth, windowHeight);
noFill();
textSize(24);
textAlign(CENTER, CENTER);
textFont('monospace');
source = new p5.AudioIn();
source.start();
lowPass = new p5.LowPass();
lowPass.disconnect();
source.connect(lowPass);
fft = new p5.FFT();
fft.setInput(lowPass);
}
function draw() {
background(255);
noFill();
// array of values from -1 to 1
var timeDomain = fft.waveform(1024, 'float32');
var corrBuff = autoCorrelate(timeDomain);
strokeWeight(3);
stroke(66, 244, 155);
beginShape();
for (var i = 0; i < corrBuff.length; i++) {
var w = map(i, 0, corrBuff.length, width/2, 0);
var h = map(corrBuff[i], -1, 1, height, 0);
curveVertex(w, h);
}
endShape();
beginShape();
for (var i = 0; i < corrBuff.length; i++) {
var w = map(i, 0, corrBuff.length, width/2, width);
var h = map(corrBuff[i], -1, 1, height, 0);
curveVertex(w, h);
}
endShape();
strokeWeight(3);
stroke(20);
line(0, height/2, width, height/2);
var freq = findFrequency(corrBuff);
fill(0);
noStroke();
text ('Fundamental Frequency: ' + (freq.toString() + '000').slice(0, 5) + 'Hz', width/2, height - 50);
}
// accepts a timeDomainBuffer and multiplies every value
function autoCorrelate(timeDomainBuffer) {
var nSamples = timeDomainBuffer.length;
// pre-normalize the input buffer
if (preNormalize){
timeDomainBuffer = normalize(timeDomainBuffer);
}
// zero out any values below the centerClipThreshold
if (doCenterClip) {
timeDomainBuffer = centerClip(timeDomainBuffer);
}
var autoCorrBuffer = [];
for (var lag = 0; lag < nSamples; lag++){
var sum = 0;
for (var index = 0; index < nSamples-lag; index++){
var indexLagged = index+lag;
var sound1 = timeDomainBuffer[index];
var sound2 = timeDomainBuffer[indexLagged];
var product = sound1 * sound2;
sum += product;
}
// average to a value between -1 and 1
autoCorrBuffer[lag] = sum/nSamples;
}
// normalize the output buffer
if (postNormalize){
autoCorrBuffer = normalize(autoCorrBuffer);
}
return autoCorrBuffer;
}
// Find the biggest value in a buffer, set that value to 1.0,
// and scale every other value by the same amount.
function normalize(buffer) {
var biggestVal = 0;
var nSamples = buffer.length;
for (var index = 0; index < nSamples; index++){
if (abs(buffer[index]) > biggestVal){
biggestVal = abs(buffer[index]);
}
}
for (var index = 0; index < nSamples; index++){
// divide each sample of the buffer by the biggest val
buffer[index] /= biggestVal;
}
return buffer;
}
// Accepts a buffer of samples, and sets any samples whose
// amplitude is below the centerClipThreshold to zero.
// This factors them out of the autocorrelation.
function centerClip(buffer) {
var nSamples = buffer.length;
// center clip removes any samples whose abs is less than centerClipThreshold
centerClipThreshold = map(mouseY, 0, height, 0,1);
if (centerClipThreshold > 0.0) {
for (var i = 0; i < nSamples; i++) {
var val = buffer[i];
buffer[i] = (Math.abs(val) > centerClipThreshold) ? val : 0;
}
}
return buffer;
}
// Calculate the fundamental frequency of a buffer
// by finding the peaks, and counting the distance
// between peaks in samples, and converting that
// number of samples to a frequency value.
function findFrequency(autocorr) {
var nSamples = autocorr.length;
var valOfLargestPeakSoFar = 0;
var indexOfLargestPeakSoFar = -1;
for (var index = 1; index < nSamples; index++){
var valL = autocorr[index-1];
var valC = autocorr[index];
var valR = autocorr[index+1];
var bIsPeak = ((valL < valC) && (valR < valC));
if (bIsPeak){
if (valC > valOfLargestPeakSoFar){
valOfLargestPeakSoFar = valC;
indexOfLargestPeakSoFar = index;
}
}
}
var distanceToNextLargestPeak = indexOfLargestPeakSoFar - 0;
// convert sample count to frequency
var fundamentalFrequency = sampleRate() / distanceToNextLargestPeak;
return fundamentalFrequency;
}