How to get microphone frequency in Hertz in Java Script?

Asked

Viewed 101 times

-1

I am working on a component of an application, where I have to pick up the frequency of a sound sent by an audio input (microphone).

Although I’ve never done any of this, I did a little research, read all the Audiocontext documentation and couldn’t identify a way to do it except for ready-made Apis. Still researching I found an example that almost certainly.

var display = document.getElementById("display");

navigator.mediaDevices.getUserMedia({audio: true})
.then(function(stream) {
    var ctx = new AudioContext();
    /* Cria um fonte de stream */
    var source = ctx.createMediaStreamSource(stream);
    var analyser = ctx.createAnalyser();
    var processor = ctx.createScriptProcessor(256, 1, 1);
    source.connect(processor);
    source.connect(analyser);
    processor.connect(ctx.destination);

    processor.onaudioprocess = (data) => {
        var dataArray = new Float32Array(analyser.frequencyBinCount);

        setInterval(() => {
            analyser.getFloatFrequencyData(dataArray);
            display.innerHTML = dataArray;
        }, 1000);
    }


})
.catch(err => {
  console.log(err.message);
});

If the variable dataArray return the frequency in hertz would be enough, but apparently it reacts to the decibels and not to the tone(note) because its values are not faithful to the chord tones of the instruments in hertz.

My project is to make a guitar/guitar/bass tuner. So when the note is played the code needs to give frequency in hertz to compare and tell if the strings should be tightened or loosened.

Does anyone know a way to get the frequency of a sound in HERTZ ?

1 answer

3

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:

inserir a descrição da imagem aqui

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:

inserir a descrição da imagem aqui

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 !

Browser other questions tagged

You are not signed in. Login or sign up in order to post.