voiceapp/script.js

127 lines
3 KiB
JavaScript
Raw Permalink Normal View History

2024-10-30 16:12:48 +00:00
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, ' '));
}