I’ve mentioned it a few times here, it may seem silly to want to pick up frequencies, but it turns out it’s not, everything will depend on N
factors, how accurate do you need the capture to be, the frequencies captured will be of monophonic sounds or not ? what would be the maximum frequency that your tuner needs to capture, are many variables, there is no single way to do this, capture the Pitch/F0/Frequency/Period/Hz can be something physical, that is to count how many times a pattern is repeated or something psychoacoustic in which involves the tonal perception of sound...
There are different ways to do this, have how to do this in the time domain (in JS there is the getFloatTimeDomainData
to return the initial data in the time domain) applying techniques of Autocorrelation in the signal, there are techniques derived from autocorrelation such as AMDF, ASDF, YIN, etc., in the Frequency domain there is also the equivalent of autocorrelation to find the frequency in the signal, there are more elaborate techniques that involves finding CEPSTRUM, other techniques involve summing the subharmonics of the spectral components returned by the FFT(Fourier), by its above code I realized that it is going by frequency domain and trying to apply the Transforming of Fourier, there is a very simplistic way to find frequencies using a FFT, it is not recommended to do this in tuners but it is an initial output for your tests and studies, When returning the components of the FFT you can find the frequency of the signal if you find which is the component of greater amplitude, remembering that this method is very rudimentary and can cause several false positives .... in short to do this you will need the following data:
Frequencia = Fs * i / N
Onde:
Fs = taxa de amostragem (Hz)
i = index da maior amplitude/magnitude
N = número de pontos da sua FFT
The standard sampling rate of Web Audio API is of 44100Hz
then if you use 4096
points in your FFT
vc will have a resolution order frequency of 10,7666015625hz
that is, each component may be missing approximately 10Hz
so it’s not so accurate, you can then smooth/improve accuracy using parabolic interpolation with the help of neighbouring adjacent components...
I wrote a basic conceptual code to demonstrate how to use the web audio API to pick up spectral components, capture maximum signal amplitude, and convert the index to hertz:
hertz.html
<!DOCTYPE html>
<html>
<head>
<style>
div {
--volume: 0%;
position: relative;
width: 200px;
height: 20px;
margin: 50px;
background-color: #DDD;
}
div::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: var(--volume);
background-color: green;
transition: width 100ms linear;
}
button {
margin-left: 50px;
}
h3 {
margin: 20px;
font-family: sans-serif;
}
</style>
</head>
<body>
<h3>ederwander hertz teste</h3>
<div id="volumeBar"></div>
<button id="start">Start</button>
<button id="stop">Stop</button>
<p id="Hertz"></p>
<script type="text/javascript">
(async () => {
let volumeCallback = null;
let volumeInterval = null;
const volumeShow = document.getElementById('volumeBar');
const startButton = document.getElementById('start');
const stopButton = document.getElementById('stop');
var index = 0;
try {
const audioStream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true
}
});
const audioContext = new AudioContext();
const audioSource = audioContext.createMediaStreamSource(audioStream);
const analyser = audioContext.createAnalyser();
const sampleRate = audioContext.sampleRate;
analyser.fftSize = 4096;
analyser.minDecibels = -127;
analyser.maxDecibels = 0;
analyser.smoothingTimeConstant = 0.4;
audioSource.connect(analyser);
var FFT = new Uint8Array(analyser.frequencyBinCount);
volumeCallback = () => {
analyser.getByteFrequencyData(FFT);
let dBsoma = 0;
for(const dB of FFT)
dBsoma += dB;
var mediadB = dBsoma / FFT.length;
volumeShow.style.setProperty('--volume', (mediadB * 100 / 127) + '%');
index = FFT.indexOf(Math.max(...FFT));
document.getElementById("Hertz").innerHTML = index*sampleRate/analyser.fftSize;
};
} catch(e) {
console.error('Falha para inicializar', e);
}
startButton.addEventListener('click', () => {
// update a cada 100ms
if(volumeCallback !== null && volumeInterval === null)
volumeInterval = setInterval(volumeCallback, 100);
});
stopButton.addEventListener('click', () => {
if(volumeInterval !== null) {
clearInterval(volumeInterval);
volumeInterval = null;
}
});
})();
</script>
</body>
</html>
basic test with pure senoids:
440hz test with Mic turned on capturing a senoid of 440Hz
(Youtube link of the senoid):
https://www.youtube.com/watch?v=xGXYFJmvIvk
print of the above code in operation:
It was very close, captured 441Hz
One more test with senoid, trying to capture with Mic a senoid in 528Hz
https://www.youtube.com/watch?v=qgSb8QdFU7k
print of the above code in operation:
Also passed very close ...
Well this is the basics of how to capture freq in the frequency domain (FFT), the code is very simplistic, if you want something more accurate try to introduce parabolic interpolation or use the combination of other methods described above ....
PS: To capture monophonic audios that approach a pure sine this method may work, maybe it is a good start to capture waves of frequencies of loose strings (string by string), this method could in short capture frequencies that are above the limit of human hearing (22050hz)
a little more than the limit of a human super-ear ... follows the table of frequencies per string of a guitar... I use more elaborate and more precise code than this example to tune my own instruments at home, a guitar’s free string frequency table:
Rope |
Note |
Frequency |
Formal notation |
1 (more acute) |
Mi |
330 Hz |
E4 |
2 |
Si |
247 Hz |
B3 |
3 |
Sol |
196 Hz |
G3 |
4 |
D |
146 Hz |
D3 |
5 |
There |
110 Hz |
A2 |
6 |
Mi |
82 Hz |
E2 |
Then for each played loose string you will have a parameter to know if it is near or far from the correct tuning !