Skip to content

How to apply Fast Fourier Transform

This article shows how to use Fast Fourier Transform and how to apply forward and inverse FFT on complex and real data using the KFR framework.

KFR DFT supports all sizes, KFR automatically chooses the best algorithm to perform DFT for the given size.

For power of 2 sizes, it uses Fast Fourier Transform.

Quick example

Complex input/output

Apply the FFT to the complex input data: See how to pass data to KFR

// prepare data (0, 1, 2, ..., 255)
univector<complex<double>, 256> data = cexp(
        linspace(0, c_pi<double, 2>, 256) * make_complex(0, 1));
univector<complex<double>, 256> freq;

// do the forward FFT
freq = dft(data);
To perform the inverse FFT:
data = idft(freq);

Scaling is not performed by KFR. To get output in the same scale as input, divide it by size.

Real input, complex output

Frequency data are stored in CCS or Perm format.

The size of the output data is equal to size/2+1 for CCS and size/2 for Perm format.

For the inverse FFT, you have to prepare frequency data in the CCS or Perm format as well.

For CCS format, you must ensure that freq[0] and freq[N/2] are real numbers to get correct real result.

data = irealdft(freq);
// or to get the properly scaled result:
data = irealdft(freq) / data.size();

Note

Real to complex and complex to real transforms are only available for even sizes. This is caused by the way real DFT is calculated. Pair of real values are interpreted as complex for high performance, so there is limitation for real DFT size, it must be even. Use complex transform and data conversion instead.

const dft_plan<double> dft(N); // N is odd
univector<complex<double>> output(N);
univector<double> input;
univector<u8> temp(dft.temp_size);
dft.execute(output, univector<complex<double>>(input), temp);

Creating FFT plan

Implementation of FFT requires twiddle coefficients to be prepared before actual processing occurs. If FFT will be performed more than once, then it makes sense to store the coefficients and reuse it every time.

FFT Plan caching

If you are using dft, idft, realdft or irealdft functions, all plans will be kept in memory, so the next call to these functions will reuse the saved data.

You can manually get the plan from the cache (or create a new if it doesn’t exist in the cache):

dft_plan_ptr<T> dft = dft_cache::instance().get(ctype<T>, size);

Note

All functions related to the FFT caching are protected with mutex and are thread safe

ctype is a special template variable intended just to pass a type to the function.

dft_plan_ptr is an alias for std::shared_ptr<const dft_plan<T>>

To clear all saved plans and free memory, call the clear function:

dft_cache::instance().clear();

Note

As of version 1.3.0, functions convolve and correlate use these cache internally.

Examples using plan

Complex-to-complex transform

You can create a plan manually without using the cache to get more control over it.

dft_plan class is intended to store all data needed for FFT.

template <typename T> // data type, float or double
struct dft_plan
{
    dft_plan(size_t size);
    void execute(complex<T>* out, 
                 const complex<T>* in, 
                 u8* temp, 
                 bool inverse = false) const;
    void execute(univector<complex<T>, N>& out, 
                 const univector<const complex<T>, N>& in, 
                 univector<u8, N2>& temp, 
                 bool inverse = false) const;
    ...
};

dft_plan has constructor that takes FFT size as an argument.

Input and output arguments for calls to the execute function must have types complex<T>* or univector<complex<T>, N>.

You can create a plan and use it as many times as needed. After creating a plan, all access to it is thread-safe, because executing the FFT doesn’t modify its internal data, so you can share this plan across threads in your application without protecting the plan with locks and mutexes.

dft_plan<double> plan(1024);
univector<complex<double>, 1024> in;
univector<complex<double>, 1024> out;
// here fill `in` array with our data (samples)
univector<u8> temp(plan.temp_size);
plan.execute(out, in, temp, false); // direct FFT
// `out` now contains frequencies which have to be processed
plan.execute(in, out, temp, true);  // inverse FFT
// `in` now contains processed data (samples)
...
// process new data
plan.execute(out, in, temp, false); // direct FFT

Real to complex and complex to real transform

dft_plan_real<double> plan(1024); // dft_plan_real for real transform
univector<double, 1024> in;
univector<complex<double>, 1024> out;
// here fill `in` array with our data (samples)
univector<u8> temp(plan.temp_size);
plan.execute(out, in, temp); // direct FFT
// `out` now contains frequencies which have to be processed
plan.execute(in, out, temp); // inverse FFT
// `in` now contains processed data (samples)