Rust Audio

How to do real-time pitch metering using FFT?

Hi. Newbie here, sorry if questions are stupid.

I’m trying to build a real-time pitch meter. My naive approach is to do FFT and then pick the frequency with the maximum magnitude. First I collect a number of samples into the buffer (4096 currently with 41k sample rate), and when it fills up I calculate spectrum using RustFFT (Radix4 algorithm). It works, but the frequency resolution is very low (sample rate / buffer size = 41k/4096 = ~10Hz if I calculate it correctly). The obvious way to increase resolution is to increase the buffer size, but it would proportionally increase the time needed to fill it up (and even more increase DFT calculation time). And I would really want to keep it below 100 ms.

I wonder if it’s possible to limit frequency range for FFT (say from 0 to 1kHz) so that its output buffer would represent frequencies only in that range but with higher resolution.

Also, I wonder if it’s possible to calculate FFT in a logarithmic frequency scale so that lower frequencies would have higher resolution.

Are those things possible for FFT algorithms fundamentally? If yes is there any rust FFT crate that provides such configurability? Or maybe it’s possible to do with RustFFT and I just didn’t figure it out?

And if no, how do guitar tuner apps work then, providing resolution down to 0.1 Hz with almost immediate feedback?

1 Like

easier pitch detection algorithm, average the interval between zero crossings, reciprocal of that is your pitch. Cheap tuners use that approach, but first lowpass the signal.

For better time resolution (at the cost of CPU) you can overlap your FFTs by buffering the incoming data using delay lines, staggered by some interval. If you only care about analyzing the data you can make that overlap ratio as low as you want.

3 Likes

FFT is a low-level operation and doesn’t directly give you pitch. You need a pitch detection algorithm, which may or may not use the FFT.

I haven’t seen anyone implement a pitch detection algorithm in Rust. You can try and have a go, yourself, but it can get pretty sophisticated!

1 Like

FFT is a low-level operation and doesn’t directly give you pitch. You need a pitch detection algorithm, which may or may not use the FFT.

Once you’ve done an FFT getting the pitch is reasonably trivial given your signal is now in the frequency domain. The tricky part is identifying a stable pitch - which is mostly trial and error.

I’m kind of curious as to whether a hybrid approach (using the time domain 0-crossing domain) would give you good results?

FFT has indeed a linear scale, which gives you a high frequency resolution for high pitches, but an insufficiently low resolution for lower pitches. If you really need something log-scale, you can have a look at the constant Q transforms. Wavelets also have a log-distibution of the resolution in the frequency spectrum, but it’s typically very coarse (one value per octave), so not suitable for your purposes.

For pitch estimation, a typical approach is to use (some variation of) auto-correlation, then apply some peak picking algorithm and then quadratic interpolation around the peak to find the wavelength that corresponds to the pitch. As I see it, the most tricky part is to pick the correct peak. I have an implementation of the paper A smarter way to find pitch (pdf) in Rust, but I’m not yet ready to publish it.

Main concerns about my implementations are that I don’t know if it’s suitable for a real-time context, and that I still need to tune some of the parameters, and that it doesn’t always pick the right peak. Let me know if you’re interested.

In the meanwhile, you can have a look at this repository that has some pitch detection algorithms in Rust (thanks @doomy for sharing this link).

The technique that I describe above may not be the best approach for a real-time applications. The technique that @m-hilgendorf describes is definitely better in that regards. It all depends on what you want to use it for.
Which leads me to the following question: what application do you have in mind?

Some more advise that you didn’t ask for:
In my experience, it’s the best to start with the easiest algorithm (implementation wise).
In your case, I would start implementing the algorithm that was sketched by m-hilgendorf above.
Secondly: prototype.
This would probably imply that your first implementation is not in Rust, but rather in a language that allows you to easily create graphs etc. and that can be used interactively. I often do prototyping in GNU Octave, but you can also use Python or something else you are more familiar with.

Good luck!

If you use FFTs to calculate the autocorrelation it certainly is fast enough for realtime, I’ve implemented MPM before in C++ with that method so in Rust it should be fine - usually I’d go with an FFT Size of 2048.