/*
* Copyright (c) 2003-2010, Mark Borgerding. All rights reserved.
* This file is part of KISS FFT - https://github.com/mborgerding/kissfft
*
* SPDX-License-Identifier: BSD-3-Clause
* See COPYING file for more information.
*/
#ifndef DLIB_KISS_FFT_H
#define DLIB_KISS_FFT_H
#include <complex>
#include <vector>
#include <cmath>
#include <stdexcept>
#include <algorithm>
#include <unordered_map>
#include <mutex>
#include <numeric>
#include "fft_size.h"
#include "../hash.h"
#include "../assert.h"
#define C_FIXDIV(x,y) /*noop*/
namespace dlib
{
namespace kiss_details
{
struct plan_key
{
fft_size dims;
bool is_inverse;
plan_key(const fft_size& dims_, bool is_inverse_)
: dims(dims_), is_inverse(is_inverse_)
{}
bool operator==(const plan_key& other) const
{
return std::tie(dims, is_inverse) == std::tie(other.dims, other.is_inverse);
}
uint32 hash() const
{
using dlib::hash;
uint32 ret = 0;
ret = hash(dims, ret);
ret = hash((uint32)is_inverse, ret);
return ret;
}
};
template<typename T>
struct kiss_fft_state
{
long nfft;
bool inverse;
std::vector<int> factors;
std::vector<std::complex<T>> twiddles;
kiss_fft_state() = default;
kiss_fft_state(const plan_key& key);
};
template<typename T>
struct kiss_fftnd_state
{
fft_size dims;
std::vector<kiss_fft_state<T>> plans;
kiss_fftnd_state() = default;
kiss_fftnd_state(const plan_key& key);
};
template<typename T>
struct kiss_fftr_state
{
kiss_fft_state<T> substate;
std::vector<std::complex<T>> super_twiddles;
kiss_fftr_state() = default;
kiss_fftr_state(const plan_key& key);
};
template<typename T>
struct kiss_fftndr_state
{
kiss_fftr_state<T> cfg_r;
kiss_fftnd_state<T> cfg_nd;
kiss_fftndr_state() = default;
kiss_fftndr_state(const plan_key& key);
};
template<typename T>
inline void kf_bfly2(
std::complex<T> * Fout,
const size_t fstride,
const kiss_fft_state<T>& cfg,
const int m
)
{
const std::complex<T> * tw1 = &cfg.twiddles[0];
std::complex<T> t;
std::complex<T> * Fout2 = Fout + m;
for (int i = 0 ; i < m ; i++)
{
t = Fout2[i] * tw1[i*fstride];
Fout2[i] = Fout[i] - t;
Fout[i] += t;
}
}
template<typename T>
inline std::complex<T> rot_PI_2(std::complex<T> z)
{
return std::complex<T>(z.imag(), -z.real());
}
template<typename T>
inline void kf_bfly3 (
std::complex<T> * Fout,
const size_t fstride,
const kiss_fft_state<T>& cfg,
const size_t m
)
{
const size_t m2 = 2*m;
const std::complex<T> *tw1,*tw2;
std::complex<T> scratch[5];
const std::complex<T> epi3 = cfg.twiddles[fstride*m];
tw1=tw2=&cfg.twiddles[0];
constexpr T half = 0.5;
for (size_t k = 0 ; k < m ; k++)
{
C_FIXDIV(Fout[k],3); C_FIXDIV(Fout[k+m],3); C_FIXDIV(Fout[m2+k],3); //noop for float and double
scratch[1] = Fout[k+m] * tw1[k*fstride];
scratch[2] = Fout[k+m2] * tw2[k*fstride*2];
scratch[3] = scratch[1] + scratch[2];
scratch[0] = scratch[1] - scratch[2];
Fout[m+k] = Fout[k] - half * scratch[3];
scratch[0] *= epi3.imag();
Fout[k] += scratch[3];
Fout[k+m2] = Fout[k+m] + rot_PI_2(scratch[0]);
Fout[k+m] -= rot_PI_2(scratch[0]);
}
}
template<typename T>
inline void kf_bfly4(
std::complex<T> * Fout,
const size_t fstride,
const kiss_fft_state<T>& cfg,
const size_t m
)
{
const std::complex<T> *tw1,*tw2,*tw3;
std::complex<T> scratch[6];
const size_t m2=2*m;
const size_t m3=3*m;
tw3 = tw2 = tw1 = &cfg.twiddles[0];
for (size_t k = 0 ; k < m ; k++)
{
C_FIXDIV(Fout[k],4); C_FIXDIV(Fout[m],4); C_FIXDIV(Fout[m2+k],4); C_FIXDIV(Fout[m3+k],4);
scratch[0] = Fout[m+k] * tw1[k*fstride];
scratch[1] = Fout[m2+k] * tw2[k*fstride*2];
scratch[2] = Fout[m3+k] * tw3[k*fstride*3];
scratch[5] = Fout[k] - scratch[1];
Fout[k] += scratch[1];
scratch[3] = scratch[0] + scratch[2];
scratch[4] = scratch[0] - scratch[2];
Fout[m2+k] = Fout[k] - scratch[3];
Fout[k] += scratch[3];
if(cfg.inverse) {
Fout[m+k] = scratch[5] - rot_PI_2(scratch[4]);
Fout[m3+k] = scratch[5] + rot_PI_2(scratch[4]);
}else {
Fout[m+k] = scratch[5] + rot_PI_2(scratch[4]);
Fout[m3+k] = scratch[5] - rot_PI_2(scratch[4]);
}
}
}
template<typename T>
inline void kf_bfly5(
std::complex<T> * Fout,
const size_t fstride,
const kiss_fft_state<T>& cfg,
const int m
)
{
std::complex<T> scratch[13];
const std::complex<T> * twiddles = &cfg.twiddles[0];
const std::complex<T> ya = twiddles[fstride*m];
const std::complex<T> yb = twiddles[fstride*2*m];
std::complex<T> *Fout0=Fout;
std::complex<T> *Fout1=Fout0+m;
std::complex<T> *Fout2=Fout0+2*m;
std::complex<T> *Fout3=Fout0+3*m;
std::complex<T> *Fout4=Fout0+4*m;
const std::complex<T> *tw = &cfg.twiddles[0];
for (int u=0; u<m; ++u )
{
scratch[0] = Fout0[u];
scratch[1] = Fout1[u] * tw[u*fstride]; //C_MUL(scratch[1] ,*Fout1, tw[u*fstride]);
scratch[2] = Fout2[u] * tw[2*u*fstride]; //C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]);
scratch[3] = Fout3[u] * tw[3*u*fstride]; //C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]);
scratch[4] = Fout4[u] * tw[4*u*fstride]; //C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]);
scratch[7] = scratch[1] + scratch[4]; //C_ADD( scratch[7],scratch[1],scratch[4]);
scratch[10] = scratch[1] - scratch[4]; //C_SUB( scratch[10],scratch[1],scratch[4]);
scratch[8] = scratch[2] + scratch[3]; //C_ADD( scratch[8],scratch[2],scratch[3]);
scratch[9] = scratch[2] - scratch[3]; //C_SUB( scratch[9],scratch[2],scratch[3]);
Fout0[u] += scratch[7] + scratch[8];
scratch[5].real(scratch[0].real() + scratch[7].real() * ya.real() + scratch[8].real() * yb.real());
scratch[5].imag(scratch[0].imag() + scratch[7].imag() * ya.real() + scratch[8].imag() * yb.real());
scratch[6].real(scratch[10].imag() * ya.imag() + scratch[9].imag() * yb.imag());
scratch[6].imag(-scratch[10].real() * ya.imag() - scratch[9].real() * yb.imag());
Fout1[u] = scratch[5] - scratch[6]; //C_SUB(*Fout1,scratch[5],scratch[6]);
Fout4[u] = scratch[5] + scratch[6]; //C_ADD(*Fout4,scratch[5],scratch[6]);
scratch[11].real(scratch[0].real() + scratch[7].real()*yb.real() + scratch[8].real()*ya.real());
scratch[11].imag(scratch[0].imag() + scratch[7].imag()*yb.real() + scratch[8].imag()*ya.real());
scratch[12].real(- scratch[10].imag()*yb.imag() + scratch[9].imag()*ya.imag());
scratch[12].imag(scratch[10].real()*yb.imag() - scratch[9].real()*ya.imag());
Fout2[u] = scratch[11] + scratch[12];
Fout3[u] = scratch[11] - scratch[12];
}
}
/* perform the butterfly for one stage of a mixed radix FFT */
template<typename T>
inline void kf_bfly_generic(
std::complex<T> * Fout,
const size_t fstride,
const kiss_fft_state<T>& cfg,
const int m,
const int p
)
{
int u,k,q1,q;
const std::complex<T> * twiddles = &cfg.twiddles[0];
std::complex<T> t;
const int Norig = cfg.nfft;
std::vector<std::complex<T>> scratch(p);
for ( u=0; u<m; ++u ) {
k=u;
for ( q1=0 ; q1<p ; ++q1 ) {
scratch[q1] = Fout[ k ];
C_FIXDIV(scratch[q1],p);
k += m;
}
k=u;
for ( q1=0 ; q1<p ; ++q1 ) {
int twidx=0;
Fout[ k ] = scratch[0];
for (q=1;q<p;++q ) {
twidx += fstride * k;
if (twidx>=Norig) twidx-=Norig;
t = scratch[q] * twiddles[twidx];
Fout[ k ] += t;
}
k += m;
}
}
}
template<typename T>
inline void kf_work(
const kiss_fft_state<T>& cfg,
const int* factors,
std::complex<T>* Fout,
const std::complex<T>* f,
const size_t fstride,
const int in_stride
)
{
std::complex<T> * Fout_beg = Fout;
const int p=*factors++; /* the radix */
const int m=*factors++; /* stage's fft length/p */
const std::complex<T> * Fout_end = Fout + p*m;
if (m==1) {
do{
*Fout = *f;
f += fstride*in_stride;
}while(++Fout != Fout_end );
}else{
do{
// recursive call:
// DFT of size m*p performed by doing
// p instances of smaller DFTs of size m,
// each one takes a decimated version of the input
kf_work(cfg, factors, Fout , f, fstride*p, in_stride);
f += fstride*in_stride;
}while( (Fout += m) != Fout_end );
}
Fout=Fout_beg;
// recombine the p smaller DFTs
switch (p) {
case 2: kf_bfly2(Fout,fstride,cfg,m); break;
case 3: kf_bfly3(Fout,fstride,cfg,m); break;
case 4: kf_bfly4(Fout,fstride,cfg,m); break;
case 5: kf_bfly5(Fout,fstride,cfg,m); break;
default: kf_bfly_generic(Fout,fstride,cfg,m,p); break;
}
}
/* facbuf is populated by p1,m1,p2,m2, ...
where
p[i] * m[i] = m[i-1]
m0 = n */
inline void kf_factor(int n, std::vector<int>& facbuf)
{
int p=4;
const double floor_sqrt = std::floor( std::sqrt((double)n) );
/*factor out powers of 4, powers of 2, then any remaining primes */
do {
while (n % p) {
switch (p) {
case 4: p = 2; break;
case 2: p = 3; break;
default: p += 2; break;
}
if (p > floor_sqrt)
p = n; /* no more factors, skip to end */
}
n /= p;
facbuf.push_back(p);
facbuf.push_back(n);
} while (n > 1);
}
template<typename T>
inline kiss_fft_state<T>::kiss_fft_state(const plan_key& key)
{
constexpr double twopi = 6.283185307179586476925286766559005768394338798;
nfft = key.dims[0];
inverse = key.is_inverse;
twiddles.resize(nfft);
for (int i = 0 ; i < nfft ; ++i)
{
double phase = -twopi*i / nfft;
if (inverse)
phase *= -1;
twiddles[i] = std::polar(1.0, phase);
}
kf_factor(nfft,factors);
}
template<typename T>
void kiss_fft_stride(const kiss_fft_state<T>& cfg, const std::complex<T>* in, std::complex<T>* out,int fin_stride)
{
if (in == out)
{
DLIB_ASSERT(out != nullptr, "out buffer is NULL!");
std::vector<std::complex<T>> tmpbuf(cfg.nfft);
kiss_fft_stride(cfg, in, &tmpbuf[0], fin_stride);
std::copy(tmpbuf.begin(), tmpbuf.end(), out);
}
else
{
kf_work(cfg, &cfg.factors[0], out, in, 1, fin_stride);
}
}
template<typename T>
inline kiss_fftnd_state<T>::kiss_fftnd_state(const plan_key& key)
{
dims = key.dims;
for (size_t i = 0 ; i < dims.num_dims() ; i++)
plans.push_back(std::move(kiss_fft_state<T>(plan_key({dims[i]}, key.is_inverse))));
}
template<typename T>
void kiss_fftnd(const kiss_fftnd_state<T>& cfg, const std::complex<T>* in, std::complex<T>* out)
{
const std::complex<T>* bufin=in;
std::complex<T>* bufout;
std::vector<std::complex<T>> tmpbuf(cfg.dims.num_elements());
/*arrange it so the last bufout == out*/
if ( cfg.dims.num_dims() & 1 )
{
bufout = out;
if (in==out) {
std::copy(in, in + cfg.dims.num_elements(), tmpbuf.begin());
bufin = &tmpbuf[0];
}
}
else
bufout = &tmpbuf[0];
for (size_t k=0; k < cfg.dims.num_dims(); ++k)
{
int curdim = cfg.dims[k];
int stride = cfg.dims.num_elements() / curdim;
for (int i=0 ; i<stride ; ++i )
kiss_fft_stride(cfg.plans[k], bufin+i , bufout+i*curdim, stride );
/*toggle back and forth between the two buffers*/
if (bufout == &tmpbuf[0])
{
bufout = out;
bufin = &tmpbuf[0];
}
else
{
bufout = &tmpbuf[0];
bufin = out;
}
}
}
template<typename T>
inline kiss_fftr_state<T>::kiss_fftr_state(const plan_key& key)
{
DLIB_ASSERT((key.dims[0] & 1) == 0, "real FFT must have even dimension");
const int nfft = key.dims[0] / 2;
substate = kiss_fft_state<T>(plan_key({nfft}, key.is_inverse));
super_twiddles.resize(nfft/2);
for (size_t i = 0 ; i < super_twiddles.size() ; ++i)
{
double phase = -3.141592653589793238462643383279502884197169399 * ((double) (i+1) / nfft + .5);
if (key.is_inverse)
phase *= -1;
super_twiddles[i] = std::polar(1.0, phase);
}
}
template<typename T>
void kiss_fftr(const kiss_fftr_state<T>& plan, const T* timedata, std::complex<T>* freqdata)
{
DLIB_ASSERT(!plan.substate.inverse, "bad fftr plan : need a forward plan. This is an inverse plan");
const int nfft_h = plan.substate.nfft; //recall that the FFT size is actually half the original requested FFT size, i.e. the size of timedata
/*perform the parallel fft of two real signals packed in real,imag*/
std::vector<std::complex<T>> tmpbuf(nfft_h);
kiss_fft_stride(plan.substate, reinterpret_cast<const std::complex<T>*>(timedata), &tmpbuf[0], 1);
/* The real part of the DC element of the frequency spectrum in st->tmpbuf
* contains the sum of the even-numbered elements of the input time sequence
* The imag part is the sum of the odd-numbered elements
*
* The sum of tdc.r and tdc.i is the sum of the input time sequence.
* yielding DC of input time sequence
* The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1...
* yielding Nyquist bin of input time sequence
*/
freqdata[0] = std::complex<T>(tmpbuf[0].real() + tmpbuf[0].imag(), 0);
freqdata[nfft_h] = std::complex<T>(tmpbuf[0].real() - tmpbuf[0].imag(), 0);
constexpr T half = 0.5;
for (int k = 1 ; k <= nfft_h / 2 ; ++k)
{
const auto fpk = tmpbuf[k];
const auto fpnk = std::conj(tmpbuf[nfft_h-k]);
const auto f1k = fpk + fpnk;
const auto f2k = fpk - fpnk;
const auto tw = f2k * plan.super_twiddles[k-1];
freqdata[k] = half * (f1k + tw);
freqdata[nfft_h-k] = half * std::conj(f1k - tw);
}
}
template<typename T>
void kiss_ifftr(const kiss_fftr_state<T>& plan, const std::complex<T>* freqdata, T* timedata)
{
DLIB_ASSERT(plan.substate.inverse, "bad Ifftr plan : need an inverse plan. This is a forward plan")
const int nfft_h = plan.substate.nfft; //recall that the FFT size is actually half the original requested FFT size, i.e. the size of timedata
std::vector<std::complex<T>> tmpbuf(nfft_h);
tmpbuf[0] = std::complex<T>(freqdata[0].real() + freqdata[nfft_h].real(),
freqdata[0].real() - freqdata[nfft_h].real());
for (int k = 1; k <= nfft_h / 2; ++k)
{
std::complex<T> fk = freqdata[k];
std::complex<T> fnkc = std::conj(freqdata[nfft_h - k]);
auto fek = fk + fnkc;
auto tmp = fk - fnkc;
auto fok = tmp * plan.super_twiddles[k-1];
tmpbuf[k] = fek + fok;
tmpbuf[nfft_h - k] = std::conj(fek - fok);
}
kiss_fft_stride (plan.substate, &tmpbuf[0], (std::complex<T>*)timedata, 1);
}
template<typename T>
inline kiss_fftndr_state<T>::kiss_fftndr_state(const plan_key& key)
{
const long realdim = key.dims.back();
const fft_size otherdims = pop_back(key.dims);
cfg_r = kiss_fftr_state<T>(plan_key({realdim}, key.is_inverse));
cfg_nd = kiss_fftnd_state<T>(plan_key(otherdims, key.is_inverse));
}
template<typename T>
void kiss_fftndr(const kiss_fftndr_state<T>& plan, const T* timedata, std::complex<T>* freqdata)
{
const int dimReal = plan.cfg_r.substate.nfft*2; //recall the real fft size is half the length of the input
const int dimOther = plan.cfg_nd.dims.num_elements();
const int nrbins = dimReal/2+1;
std::vector<std::complex<T>> tmp1(std::max<int>(nrbins, dimOther));
std::vector<std::complex<T>> tmp2(plan.cfg_nd.dims.num_elements()*dimReal);
// take a real chunk of data, fft it and place the output at correct intervals
for (int k1 = 0; k1 < dimOther; ++k1)
{
kiss_fftr(plan.cfg_r, timedata + k1*dimReal , &tmp1[0]); // tmp1 now holds nrbins complex points
for (int k2 = 0; k2 < nrbins; ++k2)
tmp2[k2*dimOther+k1] = tmp1[k2];
}
for (int k2 = 0; k2 < nrbins; ++k2)
{
kiss_fftnd(plan.cfg_nd, &tmp2[k2*dimOther], &tmp1[0]); // tmp1 now holds dimOther complex points
for (int k1 = 0; k1 < dimOther; ++k1)
freqdata[ k1*(nrbins) + k2] = tmp1[k1];
}
}
template<typename T>
void kiss_ifftndr(const kiss_fftndr_state<T>& plan, const std::complex<T>* freqdata, T* timedata)
{
const int dimReal = plan.cfg_r.substate.nfft*2; //recall the real fft size is half the length of the input
const int dimOther = plan.cfg_nd.dims.num_elements();
const int nrbins = dimReal/2+1;
std::vector<std::complex<T>> tmp1(std::max<int>(nrbins, dimOther));
std::vector<std::complex<T>> tmp2(plan.cfg_nd.dims.num_elements()*dimReal);
for (int k2 = 0; k2 < nrbins; ++k2)
{
for (int k1 = 0; k1 < dimOther; ++k1)
tmp1[k1] = freqdata[ k1*(nrbins) + k2 ];
kiss_fftnd(plan.cfg_nd, &tmp1[0], &tmp2[k2*dimOther]);
}
for (int k1 = 0; k1 < dimOther; ++k1)
{
for (int k2 = 0; k2 < nrbins; ++k2)
tmp1[k2] = tmp2[ k2*dimOther+k1 ];
kiss_ifftr(plan.cfg_r, &tmp1[0], timedata + k1*dimReal);
}
}
struct hasher
{
size_t operator()(const plan_key& key) const {return key.hash();}
};
template<typename plan_type>
const plan_type& get_plan(const plan_key& key)
{
static std::mutex m;
static std::unordered_map<plan_key, plan_type, hasher> plans;
std::lock_guard<std::mutex> l(m);
auto it = plans.find(key);
if (it != plans.end())
{
return it->second;
}
else
{
plans[key] = plan_type(key);
return plans[key];
}
}
}
template<typename T>
void kiss_fft(const fft_size& dims, const std::complex<T>* in, std::complex<T>* out, bool is_inverse)
/*!
requires
- T must be either float or double
- dims represents the dimensions of both `in` and `out`
- dims.num_dims() > 0
ensures
- performs an FFT on `in` and stores the result in `out`.
- if `is_inverse` is true, a backward FFT is performed,
otherwise a forward FFT is performed.
!*/
{
using namespace kiss_details;
static_assert(std::is_floating_point<T>::value, "template parameter needs to be a floating point type");
DLIB_ASSERT(dims.num_dims() > 0, "dims can't be empty");
const fft_size squeezed_dims = squeeze_ones(dims);
if (squeezed_dims.num_elements() == 1)
{
if (in != out)
{
out[0] = in[0];
}
}
else if (squeezed_dims.num_dims() == 1)
{
const auto& plan = get_plan<kiss_fft_state<T>>({squeezed_dims, is_inverse});
kiss_fft_stride(plan, in, out, 1);
}
else
{
const auto& plan = get_plan<kiss_fftnd_state<T>>({squeezed_dims,is_inverse});
kiss_fftnd(plan, in, out);
}
}
/*
* in has dims[0] * dims[1] * ... * dims[-2] * dims[-1] points
* out has dims[0] * dims[1] * ... * dims[-2] * (dims[-1]/2+1) points
*/
template<typename T>
void kiss_fftr(const fft_size& dims, const T* in, std::complex<T>* out)
/*!
requires
- T must be either float or double
- dims represent the dimensions of `in`
- `out` has dimensions {dims[0], dims[1], ..., dims[-2], dims[-1]/2+1}
- dims.num_dims() > 0
- dims.back() must be even
ensures
- performs a real FFT on `in` and stores the result in `out`.
!*/
{
using namespace kiss_details;
static_assert(std::is_floating_point<T>::value, "template parameter needs to be a floating point type");
DLIB_ASSERT(dims.num_dims() > 0, "dims can't be empty");
DLIB_ASSERT(dims.back() % 2 == 0, "last dimension needs to be even");
const fft_size squeezed_dims = squeeze_ones(dims);
if (squeezed_dims.num_dims() == 1)
{
const auto& plan = get_plan<kiss_fftr_state<T>>({squeezed_dims,false});
kiss_fftr(plan, in, out);
}
else
{
const auto& plan = get_plan<kiss_fftndr_state<T>>({squeezed_dims,false});
kiss_fftndr(plan, in, out);
}
}
/*
* in has dims[0] * dims[1] * ... * dims[-2] * (dims[-1]/2+1) points
* out has dims[0] * dims[1] * ... * dims[-2] * dims[-1] points
*/
template<typename T>
void kiss_ifftr(const fft_size& dims, const std::complex<T>* in, T* out)
/*!
requires
- T must be either float or double
- dims represent the dimensions of `out`
- `in` has dimensions {dims[0], dims[1], ..., dims[-2], dims[-1]/2+1}
- dims.num_dims() > 0
- dims.back() must be even
ensures
- performs an inverse real FFT on `in` and stores the result in `out`.
!*/
{
using namespace kiss_details;
static_assert(std::is_floating_point<T>::value, "template parameter needs to be a floating point type");
DLIB_ASSERT(dims.num_dims() > 0, "dims can't be empty");
DLIB_ASSERT(dims.back() % 2 == 0, "last dimension needs to be even");
const fft_size squeezed_dims = squeeze_ones(dims);
if (squeezed_dims.num_dims() == 1)
{
const auto& plan = get_plan<kiss_fftr_state<T>>({squeezed_dims,true});
kiss_ifftr(plan, in, out);
}
else
{
const auto& plan = get_plan<kiss_fftndr_state<T>>({squeezed_dims,true});
kiss_ifftndr(plan, in, out);
}
}
inline int kiss_fft_next_fast_size(int n)
{
while(1) {
int m=n;
while ( (m%2) == 0 ) m/=2;
while ( (m%3) == 0 ) m/=3;
while ( (m%5) == 0 ) m/=5;
if (m<=1)
break; /* n is completely factorable by twos, threes, and fives */
n++;
}
return n;
}
inline int kiss_fftr_next_fast_size_real(int n)
{
return kiss_fft_next_fast_size((n+1)>>1) << 1;
}
}
#endif // DLIB_KISS_FFT_H