let mic, fft; let pitchHistory = []; const minPitch = 50; const maxPitch = parseFloat(getUrlParameter('cutoff')) || 300; function setup() { createCanvas(800, 400); mic = new p5.AudioIn(); mic.start(); fft = new p5.FFT(0, 2048); fft.setInput(mic); textSize(16); } function draw() { background(240); drawPitchRanges(); let timeDomain = fft.waveform(2048, 'float32'); let frequency = autoCorrelate(timeDomain); if (frequency > minPitch && frequency < maxPitch) { pitchHistory.push(frequency); if (pitchHistory.length > width) { pitchHistory.splice(0, 1); } } drawPitchPlot(); } function drawPitchRanges() { noStroke(); fill(135, 206, 250, 100); rect(0, frequencyToY(180), width, frequencyToY(85) - frequencyToY(180)); // "Male" range fill(255, 182, 193, 100); rect(0, frequencyToY(255), width, frequencyToY(165) - frequencyToY(255)); // "Female" range stroke(0); fill(0); textAlign(LEFT); text('Typical "Male" Range (85-180 Hz)', 10, frequencyToY(85) - 5); text('Typical "Female" Range (165-255 Hz)', 10, frequencyToY(165) - 5); } function frequencyToY(freq) { return map(freq, minPitch, maxPitch, height, 0); } function drawPitchPlot() { noFill(); stroke(50); beginShape(); for (let i = 0; i < pitchHistory.length; i++) { vertex(i, frequencyToY(pitchHistory[i])); } endShape(); } function autoCorrelate(buf) { let size = buf.length; let rms = 0; for (let i = 0; i < size; i++) { let val = buf[i]; rms += val * val; } rms = Math.sqrt(rms / size); if (rms < 0.01) // Likely silence/noise return -1; let r1 = 0, r2 = size - 1; for (let i = 0; i < size / 2; i++) { if (Math.abs(buf[i]) < 0.2) { r1 = i; break; } } for (let i = 1; i < size / 2; i++) { if (Math.abs(buf[size - i]) < 0.2) { r2 = size - i; break; } } buf = buf.slice(r1, r2); size = buf.length; let c = new Array(size).fill(0); for (let i = 0; i < size; i++) { for (let j = 0; j < size - i; j++) { c[i] = c[i] + buf[j] * buf[j + i]; } } let d = 0; while (c[d] > c[d + 1]) d++; let maxval = -1, maxpos = -1; for (let i = d; i < size; i++) { if (c[i] > maxval) { maxval = c[i]; maxpos = i; } } let T0 = maxpos; let x1 = c[T0 - 1], x2 = c[T0], x3 = c[T0 + 1]; let a = (x1 + x3 - 2 * x2) / 2; let b = (x3 - x1) / 2; if (a) T0 = T0 - b / (2 * a); let sampleRate = getAudioContext().sampleRate; return sampleRate / T0; } function getUrlParameter(name) { name = name.replace(/[\[\]]/g, '\\$&'); let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); let results = regex.exec(window.location.href); if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, ' ')); }