Size difference between buffer size and FFT vector causes clipping

Hi Juce Forum!

Im making a new project in which i want to boost some specific frequencies. To get a result i used FFT. I think my code is okay because when i display amplitude of some frequencies it seems to be correct. The problem starts when i want to test the plugin in AudioPluginHost. Audio is clliping.

I think it might be because of difference between buffer size and FFT vector size.
buffer_size = 480
FFT_vector_size = 1024
I have tried to change the FFT order but it didn’t make any difference. I know that the size of FFT vector should be twice bigger then buffer size.

How can i manipulate those two sizes to make it work?

Thanks in advance!

The size of the FFT vector does not have to be twice the buffer size. If your buffer is 480, using a 512-length FFT is sufficient.

A common mistake is to not account for the negative frequencies. If you’re using the real-only transform (and you probably should) then you may only modify the first N / 2 + 1 elements from the FFT vector, where N is the FFT length.

Hi, thanks for quick response. So i set my arrays like that:
static constexpr auto fftOrder = 9 ;
buffer_size = 1 << fftOrder;
int fftHalfSize = buffer_size / 2+1;

std::array<float, buffer_size> fifo;
std::array<float, buffer_size> fftData;

it compiles correctly, but when i want to debug it, exception pops up ( which is connected with vector /array size). Do you know why?

Thanks in advance!!

You haven’t really shared enough code to say what’s going on. Please share more of the code, and on which line the exception happens.

okey so this is my PluginProcessor.h:
juce::dsp::FFT FFT;

static constexpr auto fftOrder = 9 , buffer_size = 1 << fftOrder;
int fftHalfSize = buffer_size / 2+1;

std::array<float, buffer_size> fifo;
std::array<float, buffer_size> fftData;

int fifoIndex = 0;

float resolution = 1.f;
float m_sampleRate = 0;
float fundamentalFreq = 0;
int idx; /// bin's index
float max = 0.f; /// max amplitude of freq
float magnitude;

PluginProcessor.cpp:

for (int channel = 0; channel < totalNumInputChannels; ++channel)
{
auto* channelData = buffer.getWritePointer(channel);

        for (int s = 0; s < buffer.getNumSamples(); s++)
        {
            float currentSample = channelData[s];
         
                if (fifoIndex == buffer_size)
                {
                    std::fill(std::next(fftData.begin(), buffer_size), fftData.end(), 0.0f);
                    std::copy(fifo.begin(), fifo.end(), fftData.begin());
                    FFT.performRealOnlyForwardTransform(fftData.data());
                    fifoIndex = 0;
                }

            fifo[(size_t)fifoIndex++] = currentSample;
        }
        

       #searching for fundamental freq 

        max = 0;
        idx = 0;
        for (int i = 0; i < buffer_size; ++i)
        {
            magnitude = abs(fftData[i]);

            if (max<magnitude)
            {
                max = magnitude;
                idx = i;
               
            }
        }    
    
        fundamentalFreq = resolution * idx;
    
         #boosting 


        float checkSample = 0;
        float currentFreq = 0;
        for (int j = 0; j < fftHalfSize; ++j)
        {
            checkSample = fftData[j];
            currentFreq = resolution * j; 

            if (currentFreq == fundamentalFreq)
            {                 
                fftData[j] *= 2;     //// booostibg by 3 dB
            }

            else if (currentFreq == 2 * fundamentalFreq)
            {
                fftData[j] *= 1.5;
            }

            else if (currentFreq == 4 * fundamentalFreq)
            {
                fftData[j] *= 1.2;
            }
        }
         # backward FFT
        FFT.performRealOnlyInverseTransform(fftData.data());
       # output data
        int bufferSizeCheck = buffer.getNumSamples();

        for (int s = 0; s < buffer.getNumSamples(); s++)
        {
            channelData[s] = fftData[s];             
        }
     fundamentalFreq = 0;
    }

So this is my demo project, this is my first time when I work with JUCE, so sory for all dumb mistakes…

Exception in new_scalar.cpp:

Now that your buffer_size equals the FFT length, there is some stuff in your code that doesn’t need to happen (the std::fill and even the copy from fifo to fftData). But that’s not what’s causing this.

The buffer you pass into FFT.performRealOnlyForwardTransform must be twice as long, since it will need to store both the real and imaginary parts of the numbers! Maybe that’s what you meant in your original message with “I know that the size of FFT vector should be twice bigger then buffer size.” I guess my answer was a bit misleading there (the FFT length should equal the length of the input audio, but the actual memory buffer needs to be twice the FFT length).

So that’s probably the cause for the crash. It’s a little bit scary that this appears to use operator new but I’d have to see more of the stacktrace to say whether this is an issue or not.

P.S. The following should use fftHalfSize instead of buffer_size.

        for (int i = 0; i < buffer_size; ++i)

Thank you for your advice, so I have tried to make some changes as you suggested. But it doesnt seems to change anything, probably it’s my fault.

So right now I m using different audio interface so my buffer size is 441, but I think it s not a reason why it doesnt work. Sonow i m passing to FFT.performRealOnlyForwardTransform(f); a vector which has size 1024 and is half-fished as you said. I also added windowing, but it didnt help. I thought the rough sound is becouse of the changed amplitude of some frequencies, so I have tried without it and it didint help.

Maybe you see some mistake in my code that makes my sound clipping?
Or is there chance that clipping is caused by low code efficiency?

There is my code:

header file:

 int fftHalfSize = buffer_size / 2+1;
std::array<float, buffer_size> fifo;               
std::array<float, buffer_size*2> fftData;            
float sizeMaxInv = (1.f / float(buffer_size - 1));
float  sizeMaxInvPi = sizeMaxInv * juce::MathConstants<float>::pi; 
int fifoIndex = 0;
float resolution = 1.f;
float m_sampleRate = 0;
float fundamentalFreq = 0;
int idx; /// bin's index
float max = 0.f; /// max amplitude of freq
float magnitude;

cpp. file:

prepareToPlay method:

m_sampleRate = float(sampleRate);
resolution = m_sampleRate * (1.f/buffer_size);

processBlock:

juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();

for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) //11 min jak dzia³a process block

    buffer.clear(i, 0, buffer.getNumSamples());
     
    for (int channel = 0; channel < totalNumInputChannels; ++channel)
    {
        auto* channelData = buffer.getWritePointer(channel);
      
        for (int s = 0; s < buffer.getNumSamples(); s++)
        {
            float currentSample = channelData[s];
         
                if (fifoIndex == buffer_size)
                {
                    std::fill(std::next(fftData.begin(), buffer_size), fftData.end(), 0.0f);
                    std::copy(fifo.begin(), fifo.end(), fftData.begin());
                    FFT.performRealOnlyForwardTransform(fftData.data());
                    fifoIndex = 0;
                }

            float windowAmp;
            windowAmp = std::sin(float(fifoIndex) * sizeMaxInvPi);

            fifo[fifoIndex] = currentSample * windowAmp;
            ++fifoIndex;            
        }

     # searching for fundamental freq 

        max = 0;
        idx = 0;
        for (int i = 0; i < fftHalfSize; ++i)
        {
            magnitude = abs(fftData[i]);

            if (max<magnitude)
            {
                max = magnitude;
                idx = i;
               
            }
        }

        fundamentalFreq = resolution * idx;

        # boosting 
        float checkSample = 0;
        float currentFreq = 0;
        for (int j = 0; j < fftHalfSize; ++j)
        {
            checkSample = fftData[j];
            currentFreq = resolution * j; 

            if (currentFreq == fundamentalFreq)
            {                  
                fftData[j] *= 1;     
            }

            else if (currentFreq == 2 * fundamentalFreq)
            {
                fftData[j] *= 1;
            }

            else if (currentFreq == 4 * fundamentalFreq)
            {
                fftData[j] *= 1;
            }
        }
         # inverse FFT
        FFT.performRealOnlyInverseTransform(fftData.data());
       # output data
        for (int s = 0; s < buffer.getNumSamples(); s++)
        {
            channelData[s] = fftData[s];             
        }
        fundamentalFreq = 0;
    }

So that’s my code. Maybe you can see some mistake in here.
Thanks in advance!

Well, after the FFT the contents of fftData are real, imag, real, imag, real, imag, ... in other words, interleaved complex numbers. You’re treating everything as real numbers, not as complex numbers.

1 Like

Ohhh okey, so if i want to boost specific freq. I should multiple just a real part?

You need to combine the real + imag parts to get the magnitude and phase, then change the magnitude, and finally convert the magnitude and phase back to real + imag parts. I also suggest reading up a bit on how the FFT works, that will clear up a lot of these questions.

1 Like

Okey. I will check it out.
Thanks for your time!