Afinador Visual

Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 4

import React, { useState, useEffect, useRef } from 'react';

import './App.css';

const standardTuning = {
E2: 82.41,
A2: 110.00,
D3: 146.83,
G3: 196.00,
B3: 246.94,
E4: 329.63,
};

const Tuner = () => {


const [note, setNote] = useState('--');
const [frequency, setFrequency] = useState(0);
const needleRef = useRef(null);

useEffect(() => {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const analyser = audioContext.createAnalyser();
analyser.fftSize = 2048;
const bufferLength = analyser.fftSize;
const dataArray = new Float32Array(bufferLength);

navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {


const microphone = audioContext.createMediaStreamSource(stream);
microphone.connect(analyser);
detectPitch(analyser, dataArray, audioContext.sampleRate);
});

const detectPitch = (analyser, dataArray, sampleRate) => {


analyser.getFloatTimeDomainData(dataArray);
const freq = autoCorrelate(dataArray, sampleRate);
if (freq !== -1) {
setFrequency(freq);
const closestNote = getClosestNote(freq);
setNote(closestNote);
updateNeedle(freq, closestNote);
}
requestAnimationFrame(() => detectPitch(analyser, dataArray, sampleRate));
};

const updateNeedle = (frequency, note) => {


const deviation = (frequency - standardTuning[note]) / standardTuning[note] *
100;
needleRef.current.style.transform = `rotate(${deviation * 1.5}deg)`;
};

const getClosestNote = (frequency) => {


let closestNote = null;
let closestDistance = Infinity;
for (const note in standardTuning) {
const distance = Math.abs(frequency - standardTuning[note]);
if (distance < closestDistance) {
closestDistance = distance;
closestNote = note;
}
}
return closestNote;
};

const autoCorrelate = (buffer, sampleRate) => {


const SIZE = buffer.length;
let rms = 0;

for (let i = 0; i < SIZE; i++) {


const val = buffer[i];
rms += val * val;
}

rms = Math.sqrt(rms / SIZE);


if (rms < 0.01) return -1;

let r1 = 0, r2 = SIZE - 1, threshold = 0.2;


for (let i = 0; i < SIZE / 2; i++) {
if (Math.abs(buffer[i]) < threshold) {
r1 = i;
break;
}
}

for (let i = 1; i < SIZE / 2; i++) {


if (Math.abs(buffer[SIZE - i]) < threshold) {
r2 = SIZE - i;
break;
}
}

buffer = buffer.slice(r1, r2);


const newSize = buffer.length;

let c = new Array(newSize).fill(0);


for (let i = 0; i < newSize; i++) {
for (let j = 0; j < newSize - i; j++) {
c[i] = c[i] + buffer[j] * buffer[j + i];
}
}

let d = 0;
while (c[d] > c[d + 1]) d++;
let maxval = -1, maxpos = -1;

for (let i = d; i < newSize; i++) {


if (c[i] > maxval) {
maxval = c[i];
maxpos = i;
}
}

let T0 = maxpos;

const x1 = c[T0 - 1], x2 = c[T0], x3 = c[T0 + 1];


const a = (x1 + x3 - 2 * x2) / 2;
const b = (x3 - x1) / 2;

if (a) T0 = T0 - b / (2 * a);

return sampleRate / T0;


};

}, []);

return (
<div className="tuner">
<h1>Visor de Afinação de Violão</h1>
<div className="dial">
<div className="needle" ref={needleRef}></div>
</div>
<p>Nota: {note} ({frequency.toFixed(2)} Hz)</p>
</div>
);
};

export default Tuner;

/* styles.css */
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f5f5;
margin: 0;
}

.tuner {
text-align: center;
}

.dial {
width: 200px;
height: 200px;
border: 2px solid #000;
border-radius: 50%;
position: relative;
margin: 0 auto 20px;
}

.needle {
width: 2px;
height: 100px;
background-color: red;
position: absolute;
top: 50%;
left: 50%;
transform-origin: bottom;
transform: rotate(0deg);
}

You might also like