JUCE FFT - Filter howto?


#1

[copy+pasted from Reddit]

Okay, so I have JUCE, which has it’s own FFT class. I (think) I understand the basics of FFT algorithms. Here’s what I ‘know’, and my questions regarding these ‘knowings’:

1. FFT’s turn amplitude/time into frequency/magnitude, but FFT algorithms take ‘Complex’ numbers (with real/imaginary parts) as parameters.

From what I understand, what the FFT spits out is basically an (x,y) coordinate. I know that it’s not actually called x y, and is actually called real imaginary, but that’s how I think of it. Calculating the distance (a2 + b2 =c2) gives you the magnitude (volume) of the bin, and calculating the angle (atan(y/x)) gives you the phase of the bin… (right?).

  • How to I go from my input samples to these complex number inputs?
  • Do I arrange my data in the array so that every even index has a sample, and every odd is blank?
  • Furthermore, do I have to convert from ‘samples’ to ‘complexes’ somehow before I FFT? I can’t imagine what that even means or how to do it
    Is the output arranged like a pattern like:

[r, i, r, i, r,…]
or is it
[r, r, r, r, … i,i,i,i]

2. Once you’ve FFT’d, you can modify this spectral data, then iFFT back to amplitude/time.
Can someone explain to me why if I zero the first 257 samples of a 2048 size FFT, instead of a steep hi-pass filter, I’m left with a -6dB shelf?
I’m going to pseudo type my FFT code here - let me know if there are any glaringly obvious mistakes:

header
class MyFFT
{
public:
void FFT_Test(float * const, int);
juce::FFT myfft[2];
std::vectorFFT::Complex inputFFT;
std::vectorFFT::Complex scratchFFT;
std::vectorFFT::Complex outputFFT;
int i_fft;
};

source

// 2048-sized FFT/iFFT ...
myfft[0] = new juce::FFT(11, forward); //11th power, forward
myfft[1] = new juce::FFT(11, inverse); //11th power, inverse

// 2048-sized complex vector (as requested by juce::FFT)
inputFFT.resize(2048)
scratchFFT.resize(2048);
outputFFT.resize(2048)

void FFT_Test (float * const samples, int sampleCount)
{
    for(int i = 0; i < sampleCount; ++i)
    {

           float in = samples[i];

           samples[i] = output[i_fft];      // output FFT'd samples
           inputFFT[i_fft].r = in;              //  input new samples.... set real = sample .... ? ....

           i_fft = (i_fft + 1) % fft_size;  // == 2048

           if(i_fft == 0)
           {
                 // forward FFT ...
                 myfft[0]->perform(&inputFFT[0], &scratchFFT[0]); 

                 const int hipassindex = 256;

                 // hipass filter wannabe - only drops 6dB
                 // (hipassindex +1) to also zero the 'imaginary' ... ? ....
                 memset(&scratchFFT[0], 0, sizeof(float) * (hipassindex +1));


                 // inverseFFT ...
                 myfft[1]->perform(&scratchFFT[0], &outputFFT[0]);
           }
    }
}

#2

That’s a really bad way to do filtering and not efficient either. Why do you want to do it this way?
Zeroing bins introduces a lot of ringing in the output signal.
https://www.dsprelated.com/showthread/comp.dsp/111418-1.php

As for the first set of questions, it depends on the FFT library/algorithm how the data is arranged or what’s expected. For JUCE’s fft, the documentation tells you how to do it.

void FFT::performRealOnlyForwardTransform ( float * inputOutputData ) const

Performs an in-place forward transform on a block of real data. The size of the array passed in must be 2 * getSize(), and the first half should contain your raw input sample data. On return, the array will contain complex frequency + phase data, and can be passed to performRealOnlyInverseTransform() in order to convert it back to reals.


#3

That’s a really bad way to do filtering and not efficient either

I’m trying to design an efficient Linear Phase filter that has an adjustable dB/oct control. First I want to get something that works, and then figure out how to make it efficient.

I’m not actually trying to create an inifinite dB/oct filter - I’m simply testing out if it works at all to try.

On return, the array will contain complex frequency + phase data

Does this mean that it’s a pattern output (real, imaginary, …) or it’s first half = real, second half = imaginary ?


#4

Hello !

In Juce::FFT, you have some functions allowing you to use a float to Complex FFT and its inverse instead of Complex to Complex, you might want to use that, so you don’t have to care anymore about the indexing. Then, in the frequency domain, you get either samples organized as r i r i r i etc. or Complex data. In Juce::FFT, if I remember it well, if the FFT size is N, then you get N complex numbers, which must be antisymmetric to get back something real after the inverse FFT.

Otherwise, the magnitude is sqrt(realreal + imagimag), but the phase is atan2(imag, real), otherwise your phase will always be between 0 and pi / 2

Last thing, your method for designing a FIR linear phase filter is wrong for two reasons. First, you do a convolution the wrong way. What you do is like having two signals, with a size N, being convoluted together in the frequency domain, one of them being only samples 0 and samples 1. However, to do that correctly, you need a FFT size being at least the power of 2 superior or equal to the sum of the input signals length - 1, N+N-1. And you need to handle the overlap between the calls of the convolution. Otherwise, you get wrong results, artefacts etc. For more information about that stuff, see the documents from dspguide.com.

Second, your FIR filter design method is wrong. Let’s say you do the convolution properly, you can do it either in the frequency or the time domain. Convolution in frequency domain is more efficient when the length is higher than 64 samples in general, but whatever. How do we do that ? One very very simple and not that accurate way of doing so is starting by a signal in the FFT domain, like you did, with only zeroes and ones. Why is that not accurate ? First because doing so is like taking an infinite length signal with the same specifications than you do, and deciding to truncate the samples in the time domain at one moment to get a useable filter. Just truncating these samples is bad, because its equivalent to applying a rectangular windowing in the time domain to your impulse response. The rectangular windowing will give you a bad and slow attenuation (see dspguide.com again for that), an it would be more efficient to use another window instead in the time domain, such as Hanning/Hamming/Triangular etc. Moreover, nobody really puts the specs of the filter directly in the frequency domain, because otherwise it would be difficult to design a filter with a very specific cutoff frequency. Imagine you want a high pass filter at exactly 8000 Hz, and another one at 8100 Hz. How do you do that if your FFT bins give the information only at 8000 and 8200 Hz ? That’s why usually for the lowpass filter for example, we take into account that a rectangle in the frequency domain is a cardinal sine in the time domain, and we start from there directly in the time domain, allowing us to state exactly the cutoff frequency and apply any window very simply…

Unfortunately, without digging a little more into the theory of convolution and FIR filter design, you won’t be able to get a right result. But everything is well explained on the link I have given to you :wink: