FFT Spectroscope - Low-End Resolution

Dear all,

I’m currently trying to build a spectroscope based on the “Tutorial: Visualise the frequencies of a signal in real time”.

Technicaly speaking everything is working fine, but as we all know, FFT always has the tradeoff of time / resolution.

With the fftSize of 2048 (used in the tutorial) and my desired SR of 48khz I get ~23.5hz per bin, which just isn’t enough for my purpose in the lower end.

Now I’m trying to come up with a good solution to get a better resolution in the lower end without introducing too much delay / huge fft-sizes (if that’s even mathematical possible?).

My first thought was to make a second FFT just for the lower end where I do lowpass filter -> downsampling so I get a higher resolution per bin. But From my understanding that would introduce quite a big delay, since I have to “wait” to collect enough samples at the lower sample rate to perform the FFT.
(for example downsample to 6khz with fft-size 2048 I get ~3hz per bin which would be fine, but 2048 samples at 6khz are a third of a second before my fft-buffer is filled).

Another thought was to avoid FFT in the lower end and maybe use bandpass-filters and get the magnitude for each one, but then I would need some real steep filters to really separate the bands,wouldn’t I?

So long story short, has anyone any experience / ideas how to get a good resolution in the lower end of the spectrum without introducing too much delay / complicating things for the rest of the spectrum?

Maybe there are other techniques (overlapping etc.) around which I’m not aware might help me in this case?

Many thanks in advance :slight_smile:

I’ve found good results by using a circular buffer of samples instead of a FIFO. This means I can use FFTs with a size of 65k and still display at 60fps with 44.1kHz samplerate. The only downside with this method is that the result isn’t as ‘instant’ when using large FFT sizes since the part of the waveform causing a peak in the spectrum (a kick drum hit for example) is also mixed in with over a second’s worth of ‘old’ samples. However I find that a better compromise than having ~0.5fps for a 65k FFT using a FIFO.

Dear Jimmi,

Thanks for the fast reply!
I read about that approach aswell in an electronics forum somewhere and it sounds like a good solution.
My concern there was (as you mentioned) this “smearing” of old samples.
Since my Spectroscope will be used in a “live” context, so for example during a soundcheck seeing where are problems in a specific venue, I thought this might be a “no-go” but if you say it is a better compromise, I guess I just have to try it out and see for myself if it fits my purposes / demands.

Thank you very much! :slight_smile:

CQT could help for a better resolution in the lower frequencies.
For example: https://git.iem.at/audioplugins/cqt-analyzer

Implementation is far more complex as a FFT, but maybe it helps :slight_smile:

The synth solo in the demo video is quite nice to see the resolution in the lower frequencies.

3 Likes

I used this approach in GT Analyser which is free so you could have a look to see if it’s feasible for your needs: https://gramotech.co.uk/plug-ins/gt_analyser/

1 Like

Letting the idea sink for a bit I came across two more questions concerning the ring-buffer approach:

How do you do windowing in this case ? Don’t you get some weird behaviour at the point where newest / oldest samples “collide”?

Ring buffer’s maybe not the right term, a rolling buffer maybe? Either way the end of the buffer is always the most recent sample that’s arrived so the windowing function is always 0 on the most recent and oldest samples.

Ah I see, so you push at the end and pop at the start, makes sense.
Are there any multithreading pitfalls that can happen there, I should be concerned about? When the audio thread pushes while the Gui / analyse thread does the FFT?
Thanks again for the clarification!

You have basically two options:

  • Do the ring buffering and the FFT on the audio thread and send the spectral data into a lock free queue that is read from the GUI thread. You can set a flag that is set when an editor gets created and is reset if it gets destroyed and query that in the processing callback to skip calculation if no editor is present
  • Send raw sample blocks into a lock free queue and manage the ring buffering and fft on the message thread or on a separate worker thread

Both approaches have pros and cons, while the second approach doesn’t put too much load on the processing thread and therefore will be more efficient, it will probably be more straightforward to implement the first one

1 Like