I’m trying to make a sketch that find the pitch of a human voice (speaking or singing). I thought I was headed in the right direction, but I’m getting stuck.

Here is my strategy:

- Loop through all the frequencies in the range of the typical human voice (about 50hz to 400hz).
- Use getEnergy() to find the “energy” of each of these frequencies.
- From here, find the frequency with the highest energy. This, I assume, is the primary frequency, or pitch, of the voice at that moment in time.

This almost works, but the frequencies seem to be grouped in a weird way. I’m not sure how to describe it precisely. I think this picture can explain better:

I am playing a tone of 200hz on the right. My code generated the chart on the left. Notice how the frequencies group themselves in bands of about 50. This makes it hard to get a precise pitch!

Here is my code:

```
var mic;
var spectrum;
var fft;
var maxes = []
const SENSITIVITY = 50; // How fast the avg moves
const LOWER_BOUND = 150; // Hz
const UPPER_BOUND = 300; // Hz
var gap = 2 // Granularity - gap between frequencies that are tested.
function setup()
{
createCanvas(640, 600);
mic = new p5.AudioIn()
mic.start();
fft = new p5.FFT();
fft.setInput(mic)
}
function draw()
{
background(0);
noStroke();
micLevel = mic.getLevel();
fft.analyze();
var max = {freq: 0, energy: 0}
for (let freq = LOWER_BOUND; freq < UPPER_BOUND; freq += gap) {
// Labels
if (freq % 10 === 0) {
const y = getY(freq)
text(freq, 550, y)
}
// Get energy of this freq, and draw it
const energy = fft.getEnergy(freq, freq + gap)
drawFreqLine(freq, 5, energy)
// Find primary frequency
max = (energy > max.energy) ? {freq, energy} : max
}
// Rolling average of the primary frequency. This makes the graph less jumpy.
if (micLevel > 0.001) {
maxes.push(max)
}
if (maxes.length > SENSITIVITY) {
maxes.splice(0, 1)
}
const sumOfMaxFrequencies = maxes.reduce((s, m) => s + m.freq, 0)
const rollingAvgFreq = sumOfMaxFrequencies / maxes.length
drawFreqLine(rollingAvgFreq, 10, 10);
}
function drawFreqLine(freq, thickness=10, energy=255)
{
const line = {
x: 0,
y: getY(freq),
w: width * energy/255,
h: thickness,
};
fill(0, 50, 250);
rect(line.x, line.y, line.w, line.h);
}
function getY(freq)
{
return map(freq, LOWER_BOUND, UPPER_BOUND, height, 0);
}
```