Potential mistake in the FFT Tutorial

Hi,

well i’m not sure if this is really a mistake because i’m still a beginner, but i found something perculiar. So i was finishing the fft tutorial and transfered the code into a plugin preset to see how it performs in my daw. looked pretty fine to me first, at least visually. but then i tried to get the fundamental frequency from a monophonic audio source by doing this:

const float& getFundamentalFrequency() {
        float index = 0.f;
        float max = 0.f;
        float absSample;
        for (auto i = 0; i < m_sizeHalf; ++i) {
            absSample = abs(m_fftData[i]);
            if (max < absSample) {
                max = absSample;
                index = i;
            }
        }
        return m_sampleRateSizeMaxDiv * index;
    }

(btw m_sizeHalf is half of the size of fftData and m_sampleRateSizeMaxDiv = 1 / sampleRate * (1 / (size - 1)))

however this worked pretty fine and i could hear that it more or less grabbed the right frequency when asigned that to an oscillator. but it also had a lot of other noises in the highend especially and no matter what kinda smoothing i used on the frequency it just wasn’t going to get a lot better. so what i did then was moving the line:

m_forwardFFT.performFrequencyOnlyForwardTransform(m_fftData.data());

from the beginning of the method drawNextLineOfSpectrogram() into the method pushNextSampleIntoFifo() instantly after the lines with memset and memcpy and voila, it creates a perfect representation of the fundamental frequency on my oscillator.

i’m guessing that when this line is in the drawNextLine… method it will try to calculate the forward-fft on an array that already has been transformed. the visuals also became a bit better then btw.

so just for reference this is the code that i then ended up with:

class HDLFFTVisual {
public:
    enum WindowType {
        NoWindowing,
        LinearType,
        SineType
    };

    HDLFFTVisual(int order = 10, int imageWidth = 512, int imageHeight = 512, int windowType = SineType) :
        m_order(order),
        m_size(1 << m_order),
        m_size2(2 * m_size),
        m_sizeHalf(m_size / 2),
        m_sizeHalfF(float(m_sizeHalf)),
        m_sizeDiv(1.f / float(m_size)),
        m_sizeMaxDiv(1.f / float(m_size - 1)),
        m_sizeMaxDivPi(m_sizeMaxDiv * MathConstants<float>::pi),
        m_imageWidth(imageWidth),
        m_imageWidthMax(imageWidth - 1),
        m_imageHeight(imageHeight),
        m_imageHeightF(float(imageHeight)),
        m_imageHeightDiv(1.f / m_imageHeightF),
        m_imageHeightMaxDiv(1.f / (m_imageHeightF - 1.f)),
        m_image(Image::ARGB, imageWidth, imageHeight, true),
        m_fifo(m_size),
        m_fftData(m_size2),
        m_fifoIdx(0),
        m_blockReady(false),
        m_forwardFFT(order),
        m_windowType(windowType),
        m_sampleRate(1.f),
        m_sampleRateSizeMaxDiv(1.f)
    {
        
	}

    // in prepareToPlay
    void setSampleRate(const double& sampleRate) {
        m_sampleRate = float(sampleRate);
        m_sampleRateSizeMaxDiv = m_sampleRate * m_sizeMaxDiv;
    }

    // in resized (optional)
    void setImageBounds(Rectangle<int> bounds) { setImageBounds(bounds.getWidth(), bounds.getHeight()); }
    void setImageBounds(int width, int height) {
        m_imageWidth = width;
        m_imageWidthMax = width - 1;
        m_imageHeight = height;
        m_imageHeightF = float(height);
        m_imageHeightDiv = 1.f / m_imageHeightF;
        m_imageHeightMaxDiv = 1.f / (m_imageHeightF - 1.f);
        m_image = Image(Image::ARGB, width, height, true);
    }

    // in paint
    void draw(Graphics& g, Rectangle<float> bounds){ g.drawImage(m_image, bounds); }

    // get sample from processBlock
    void pushSampleIntoFifo(const float& sample) {
        if (m_fifoIdx == m_size) {
            if (!m_blockReady) {
                std::fill(std::next(m_fftData.begin(), m_size), m_fftData.end(), 0.f); // this clears shit
                std::copy(m_fifo.begin(), m_fifo.end(), m_fftData.begin()); // this copies shit (apparently^^)
                m_forwardFFT.performFrequencyOnlyForwardTransform(m_fftData.data());
                m_blockReady = true;
            }
            m_fifoIdx = 0;
        }

        float windowAmp;
        switch (m_windowType) {
            case LinearType:
                windowAmp = m_fifoIdx < m_sizeHalf ?
                    float(2 * m_fifoIdx) * m_sizeMaxDiv :
                    2.f - float(2 * m_fifoIdx) * m_sizeMaxDiv;
                break;
            case SineType:
                windowAmp = std::sin(float(m_fifoIdx) * m_sizeMaxDivPi);
                break;
            default:
                // No Windowing
                windowAmp = 1.f;
                break;
        }
        m_fifo[m_fifoIdx] = sample * windowAmp;
        ++m_fifoIdx;
    }

    // returns frequency where magnitude is max
    const float& getFundamentalFrequency() {
        float index = 0.f;
        float max = 0.f;
        float absSample;
        for (auto i = 0; i < m_sizeHalf; ++i) {
            absSample = abs(m_fftData[i]);
            if (max < absSample) {
                max = absSample;
                index = i;
            }
        }
        return m_sampleRateSizeMaxDiv * index;
    }

    // returns true if repaint() should be called from timerCallback() in editor
    bool shouldRepaint() {
        if (m_blockReady) {
            m_blockReady = false;
            drawSpectogram();
            return true;
        }
        return false;
    }

private:
    void drawSpectogram() {
        m_image.moveImageSection(0, 0, 1, 0, m_imageWidthMax, m_imageHeight);
        auto maxLevel = FloatVectorOperations::findMinAndMax(m_fftData.data(), m_sizeHalf);
        for (auto y = 1; y < m_imageHeight; ++y) {
            auto yF = float(y);
            auto skewedProportionY = 1.f - std::exp(std::log(yF * m_imageHeightDiv) * .2f);
            auto fftDataIndex = int(jlimit(0.f, m_sizeHalfF, skewedProportionY * m_sizeHalfF));
            auto level = jmap(m_fftData[fftDataIndex], 0.f, jmax(maxLevel.getEnd(), 1e-5f), 0.f, 1.f);
            m_image.setPixelAt(m_imageWidthMax, y, Colour::fromHSV(level, 1.f, level, 1.f));
        }
    }

    int m_order, m_size, m_size2, m_sizeHalf;
    float m_sizeHalfF, m_sizeDiv, m_sizeMaxDiv, m_sizeMaxDivPi;
    int m_imageWidth, m_imageWidthMax, m_imageHeight;
    float m_imageHeightF, m_imageHeightDiv, m_imageHeightMaxDiv;
    Image m_image;
    std::vector<float> m_fifo, m_fftData;
    int m_fifoIdx;
    bool m_blockReady;
    dsp::FFT m_forwardFFT;
    int m_windowType;
    float m_sampleRate, m_sampleRateSizeMaxDiv;
};

Can you tell, what actually needs to be changed to improve the demo?
I am a bit too lazy to do a side by side comparison of your code :wink:

yeah you basically just have to cut the line:

forwardFFT.performFrequencyOnlyForwardTransform (fftData);

out of the method called void drawNextLineOfSpectrogram()
and put it into the method called void pushNextSampleIntoFifo (float sample)

right after this line:

memcpy (fftData, fifo, sizeof (fifo));

to reproduce the fix.

and then in order to be able to test it ofc draw the visuals or try to get the fundamental frequency of some material