Linear Smooth Value on Spectrum Analyzer

Hello, i’m beginner at audio plugin development, i follow matkat music to made EQ plugin, and successfully build my first plugin. can everyone here give me a tutorial about how to smooth the analyzer

here is the shot

#pragma once
#include <JuceHeader.h>

enum FFTOrder
  order2048 = 11,
  order4096 = 12,
  order8192 = 13

template <typename BlockType>
struct FFTDataGenerator
   produces the FFT data from an audio buffer.

  void produceFFTDataForRendering(const juce::AudioBuffer<float> &audioData, const float negativeInfinity)

    const auto fftSize = getFFTSize();

    fftData.assign(fftData.size(), 0);
    auto *readIndex = audioData.getReadPointer(0);
    std::copy(readIndex, readIndex + fftSize, fftData.begin());

    // first apply a windowing function to our data
    window->multiplyWithWindowingTable(, fftSize); // [1]

    // then render our FFT data..
    forwardFFT->performFrequencyOnlyForwardTransform(; // [2]

    int numBins = (int)fftSize / 2;

    // normalize the fft values.
    for (int i = 0; i < numBins; ++i)

      auto v = fftData[i];
      if (!std::isinf(v) && !std::isnan(v))
        v /= float(numBins);
        v = 0.f;
      fftData[i] = Decibels::gainToDecibels(v, negativeInfinity);

    // convert them to decibels
    for (int i = 0; i < numBins; ++i)
      auto v = juce::Decibels::gainToDecibels(fftData[i], negativeInfinity);
      if (v < smoothedValue[i].currentValue)
        v = smoothedValue[i].currentValue;
      fftData[i] = smoothedValue[i].getCurrentValue();


  void changeOrder(FFTOrder newOrder)
    // when you change order, recreate the window, forwardFFT, fifo, fftData
    // also reset the fifoIndex
    // things that need recreating should be created on the heap via std::make_unique<>

    order = newOrder;
    auto fftSize = getFFTSize();

    forwardFFT = std::make_unique<juce::dsp::FFT>(order);
    window = std::make_unique<juce::dsp::WindowingFunction<float>>(fftSize, juce::dsp::WindowingFunction<float>::blackmanHarris);

    fftData.resize(fftSize * 2, 0);

  int getFFTSize() const { return 1 << order; }
  int getNumAvailableFFTDataBlocks() const { return fftDataFifo.getNumAvailableForReading(); }
  bool getFFTData(BlockType &fftData) { return fftDataFifo.pull(fftData); }

  void setSampleRate(double newSampleRate)

    smoothedValue.resize(getFFTSize() / 2);
    for (auto &v : smoothedValue)
      v.reset(sampleRate, 0.1);

  FFTOrder order;
  BlockType fftData;
  std::unique_ptr<juce::dsp::FFT> forwardFFT;
  std::unique_ptr<juce::dsp::WindowingFunction<float>> window;

  double sampleRate;
  Fifo<BlockType> fftDataFifo;
  Array<LinearSmoothedValue<float>> smoothedValue;

Code above is how to get FFT data and pass them into FIFO and then consume by path generator to generate path.

what i mean about Smoothing is using linear smoothing, let say freq 50Hz, if current level value is more than prev value, it will jump to targeted Y, if less, will decrease 1px time by time

to make this look smoother you should rather improve the drawing code. log scaled FFT will always have a worse resolution in the lowend than in the highend, unless you go to unreasonable FFT sizes, which you probably won’t. the solution is to interpolate between the bins with something better than linear interpolation. cubic hermite spline is what i usually choose because it performs well, doesn’t have lots of overshoot and it is just a nice curve that always feels mostly continuous. mostly as in, there are sorta multiple levels of continuity. you can mostly ignore it, but if you’re interested:


Thanks for your aswers. it will be my referense to draw path with smooth curve, but
what i mean about Smoothing is using LinearSmoothedValue class , let say freq 50Hz, if current level value is more than prev value, it will jump to targeted Y, if less, will decrease 1px time by time

i see. i’d personally solve it like this. whenever your fft object has updated all the bins, you loop over all bins. for each bin (magnitudes only) you throw the value into a lowpass filter, because they smoothen out a signal and look better than linear interpolation. depending on wether or not the new bin value is higher than the last one, the lowpass filter would switch between some value that is above and one that is below 60hz because the higher lowpass will let the bin jump up quickly while the lower one lets it fall gently. the lowpassed magnitudes then get their own buffer which is the one the editor reads from then

Ohh, look’s interesting, let me try you suggestion, big thanks for you… :ok_hand: