A fast algorithm for computing the DFT for N=2^k
To use in your own programs you just need SUB fft or SUB rfft if dealing with real only signals.
i.e.:
'create signal
x_r
(i
) = 100*sin(2*pi
*62.27*i
/n
) + 25*cos(2*pi
*132.27*i
/n
) x_i(i) = 0
fft xx_r(), xx_i(), x_r(), x_i(), n
SUB fft
x_r, x_i is the input signal real and complex values, must be arrays of size n
xx_r, xx_i must also be arrays of size n and will contain output
sub fft
(xx_r
(), xx_i
(), x_r
(), x_i
(), n
)
'bit rev copy
rev = 0
if i
and (2^j
) then rev
= rev
+ (2^(log2n
- 1 - j
))
xx_r(i) = x_r(rev)
xx_i(i) = x_i(rev)
m = 2^i
w_r = 1
w_i = 0
p = j + k
q = p + (m \ 2)
u_r = w_r*xx_r(q) - w_i*xx_i(q)
u_i = w_r*xx_i(q) + w_i*xx_r(q)
v_r = xx_r(p)
v_i = xx_i(p)
xx_r(p) = v_r + u_r
xx_i(p) = v_i + u_i
xx_r(q) = v_r - u_r
xx_i(q) = v_i - u_i
u_r = w_r
u_i = w_i
w_r = u_r*wm_r - u_i*wm_i
w_i = u_r*wm_i + u_i*wm_r
SUB rfft
This is about 2x faster than above fft if dealing with only real values
x_r is the input signal, must be an array of size n
xx_r, xx_i must also be arrays of size n and will contain output
Since in the real FFT the second half of the output are complex conjugates of the first half, you may wish to not compute the second half and remove the last 4 lines in this SUB. I left it in just in case.
sub rfft
(xx_r
(), xx_i
(), x_r
(), n
)
rev = 0
if i
and (2^j
) then rev
= rev
+ (2^(log2n
- 1 - j
))
xx_r(i) = x_r(2*rev)
xx_i(i) = x_r(2*rev + 1)
m = 2^i
w_r = 1
w_i = 0
p = j + k
q = p + (m \ 2)
u_r = w_r*xx_r(q) - w_i*xx_i(q)
u_i = w_r*xx_i(q) + w_i*xx_r(q)
v_r = xx_r(p)
v_i = xx_i(p)
xx_r(p) = v_r + u_r
xx_i(p) = v_i + u_i
xx_r(q) = v_r - u_r
xx_i(q) = v_i - u_i
u_r = w_r
u_i = w_i
w_r = u_r*wm_r - u_i*wm_i
w_i = u_r*wm_i + u_i*wm_r
xx_r(n/2) = xx_r(0)
xx_i(n/2) = xx_i(0)
xx_r(n/2 + i) = xx_r(n/2 - i)
xx_i(n/2 + i) = xx_i(n/2 - i)
xpr = (xx_r(i) + xx_r(n/2 + i)) / 2
xpi = (xx_i(i) + xx_i(n/2 + i)) / 2
xmr = (xx_r(i) - xx_r(n/2 + i)) / 2
xmi = (xx_i(i) - xx_i(n/2 + i)) / 2
xx_r
(i
) = xpr
+ xpi
*cos(2*pi
*i
/n
) - xmr
*sin(2*pi
*i
/n
) xx_i
(i
) = xmi
- xpi
*sin(2*pi
*i
/n
) - xmr
*cos(2*pi
*i
/n
)
'symmetry, complex conj
xx_r(n/2 + i) = xx_r(n/2 - 1 - i)
xx_i(n/2 + i) =-xx_i(n/2 - 1 - i)
As an example FYI, here is a direct unoptimized DFT for comparison. It is much slower but can be used with any n
sub dft
(xx_r
(), xx_i
(), x_r
(), x_i
(), n
) xx_r(i) = 0
xx_i(i) = 0
xx_r
(i
) = xx_r
(i
) + x_r
(j
)*cos(2*pi
*i
*j
/n
) + x_i
(j
)*sin(2*pi
*i
*j
/n
) xx_i
(i
) = xx_i
(i
) - x_r
(j
)*sin(2*pi
*i
*j
/n
) + x_i
(j
)*cos(2*pi
*i
*j
/n
)
if you wish to take the inverse FFT then do the following to get back x_r and x_i from xx_r and xx_i. I chose not to include a inverse flag in the sub for my own reasons, it should be easy enough to modify above SUBs to add an inverse option if you so wish.
'inverse fft
xx_i(i) = -xx_i(i)
fft x_r(), x_i(), xx_r(), xx_i(), n
x_r(i) = x_r(i)/n
x_i(i) = x_i(i)/n
Here is a basic example using and plotting all the SUBs:
'defdbl a-z
'pi = 2*asin(1)
dim xx_r
(sw
-1), xx_i
(sw
-1)
x_r
(i
) = 100*sin(2*pi
*62.27*i
/sw
) + 25*cos(2*pi
*132.27*i
/sw
) x_i(i) = 0
'screenres sw, sh, 32
line -(i
, sh
/4 - x_r
(i
)), _rgb(100,100,100)
dft xx_r(), xx_i(), x_r(), x_i(), sw
pset (0, 3*sh
/4 - 0.1*sqr(xx_r
(0)*xx_r
(0) + xx_i
(0)*xx_i
(0)) ), _rgb(0,255,0) line -(i
, 3*sh
/4 - 0.1*sqr(xx_r
(i
)*xx_r
(i
) + xx_i
(i
)*xx_i
(i
)) ), _rgb(0,255,0)
fft xx_r(), xx_i(), x_r(), x_i(), sw
pset (0, 50+3*sh
/4 - 0.1*sqr(xx_r
(0)*xx_r
(0) + xx_i
(0)*xx_i
(0)) ), _rgb(255,0,0) line -(i
, 50+3*sh
/4 - 0.1*sqr(xx_r
(i
)*xx_r
(i
) + xx_i
(i
)*xx_i
(i
)) ), _rgb(255,0,0)
xx_r(i) = 0
xx_i(i) = 0
rfft xx_r(), xx_i(), x_r(), sw
pset (0, 100+3*sh
/4 - 0.1*sqr(xx_r
(0)*xx_r
(0) + xx_i
(0)*xx_i
(0)) ), _rgb(255,255,0) line -(i
, 100+3*sh
/4 - 0.1*sqr(xx_r
(i
)*xx_r
(i
) + xx_i
(i
)*xx_i
(i
)) ), _rgb(255,255,0)
sub rfft
(xx_r
(), xx_i
(), x_r
(), n
)
rev = 0
if i
and (2^j
) then rev
= rev
+ (2^(log2n
- 1 - j
))
xx_r(i) = x_r(2*rev)
xx_i(i) = x_r(2*rev + 1)
m = 2^i
w_r = 1
w_i = 0
p = j + k
q = p + (m \ 2)
u_r = w_r*xx_r(q) - w_i*xx_i(q)
u_i = w_r*xx_i(q) + w_i*xx_r(q)
v_r = xx_r(p)
v_i = xx_i(p)
xx_r(p) = v_r + u_r
xx_i(p) = v_i + u_i
xx_r(q) = v_r - u_r
xx_i(q) = v_i - u_i
u_r = w_r
u_i = w_i
w_r = u_r*wm_r - u_i*wm_i
w_i = u_r*wm_i + u_i*wm_r
xx_r(n/2) = xx_r(0)
xx_i(n/2) = xx_i(0)
xx_r(n/2 + i) = xx_r(n/2 - i)
xx_i(n/2 + i) = xx_i(n/2 - i)
xpr = (xx_r(i) + xx_r(n/2 + i)) / 2
xpi = (xx_i(i) + xx_i(n/2 + i)) / 2
xmr = (xx_r(i) - xx_r(n/2 + i)) / 2
xmi = (xx_i(i) - xx_i(n/2 + i)) / 2
xx_r
(i
) = xpr
+ xpi
*cos(2*pi
*i
/n
) - xmr
*sin(2*pi
*i
/n
) xx_i
(i
) = xmi
- xpi
*sin(2*pi
*i
/n
) - xmr
*cos(2*pi
*i
/n
)
'symmetry, complex conj
xx_r(n/2 + i) = xx_r(n/2 - 1 - i)
xx_i(n/2 + i) =-xx_i(n/2 - 1 - i)
sub fft
(xx_r
(), xx_i
(), x_r
(), x_i
(), n
)
'bit rev copy
rev = 0
if i
and (2^j
) then rev
= rev
+ (2^(log2n
- 1 - j
))
xx_r(i) = x_r(rev)
xx_i(i) = x_i(rev)
m = 2^i
w_r = 1
w_i = 0
p = j + k
q = p + (m \ 2)
u_r = w_r*xx_r(q) - w_i*xx_i(q)
u_i = w_r*xx_i(q) + w_i*xx_r(q)
v_r = xx_r(p)
v_i = xx_i(p)
xx_r(p) = v_r + u_r
xx_i(p) = v_i + u_i
xx_r(q) = v_r - u_r
xx_i(q) = v_i - u_i
u_r = w_r
u_i = w_i
w_r = u_r*wm_r - u_i*wm_i
w_i = u_r*wm_i + u_i*wm_r
sub dft
(xx_r
(), xx_i
(), x_r
(), x_i
(), n
) xx_r(i) = 0
xx_i(i) = 0
xx_r
(i
) = xx_r
(i
) + x_r
(j
)*cos(2*pi
*i
*j
/n
) + x_i
(j
)*sin(2*pi
*i
*j
/n
) xx_i
(i
) = xx_i
(i
) - x_r
(j
)*sin(2*pi
*i
*j
/n
) + x_i
(j
)*cos(2*pi
*i
*j
/n
)
A common use for FFT is filtering unwanted frequencies. The following example demonstrates this, with screenshots attached
'defdbl a-z
'pi = 2*asin(1)
dim xx_r
(sw
-1), xx_i
(sw
-1)
'x_r(i) = 100*sin(2*pi*62.27*i/sw) + 25*cos(2*pi*132.27*i/sw)
x_r
(i
) = 100*sin(0.08*i
) + 25*cos(i
) x_i(i) = 0
'screenres sw, sh, 32
'plot input signal
fft xx_r(), xx_i(), x_r(), x_i(), sw
'plot its fft
pset (0, 50+3*sh
/4 - 0.01*sqr(xx_r
(0)*xx_r
(0) + xx_i
(0)*xx_i
(0)) ), _rgb(255,255,0) line -(i
*2, 50+3*sh
/4 - 0.01*sqr(xx_r
(i
)*xx_r
(i
) + xx_i
(i
)*xx_i
(i
)) ), _rgb(255,255,0)
'set unwanted frequencies to zero
xx_r(i) = 0
xx_i(i) = 0
xx_r(sw - i) = 0
xx_i(sw - i) = 0
'plot fft of filtered signal
pset (sw
, 50+3*sh
/4 - 0.01*sqr(xx_r
(0)*xx_r
(0) + xx_i
(0)*xx_i
(0)) ), _rgb(255,255,0) line -(sw
+ i
*2, 50+3*sh
/4 - 0.01*sqr(xx_r
(i
)*xx_r
(i
) + xx_i
(i
)*xx_i
(i
)) ), _rgb(0,155,255)
'take inverse fft
xx_i(i) = -xx_i(i)
fft x_r(), x_i(), xx_r(), xx_i(), sw
x_r(i) = x_r(i)/sw
x_i(i) = x_i(i)/sw
'plot filtered signal
line -(sw
+ i
, sh
/4 - x_r
(i
)), _rgb(0,255,0)
sub fft
(xx_r
(), xx_i
(), x_r
(), x_i
(), n
)
'bit rev copy
rev = 0
if i
and (2^j
) then rev
= rev
+ (2^(log2n
- 1 - j
))
xx_r(i) = x_r(rev)
xx_i(i) = x_i(rev)
m = 2^i
w_r = 1
w_i = 0
p = j + k
q = p + (m \ 2)
u_r = w_r*xx_r(q) - w_i*xx_i(q)
u_i = w_r*xx_i(q) + w_i*xx_r(q)
v_r = xx_r(p)
v_i = xx_i(p)
xx_r(p) = v_r + u_r
xx_i(p) = v_i + u_i
xx_r(q) = v_r - u_r
xx_i(q) = v_i - u_i
u_r = w_r
u_i = w_i
w_r = u_r*wm_r - u_i*wm_i
w_i = u_r*wm_i + u_i*wm_r
Another common thing seems to be wanting to find an exact frequency of a signal. The following demonstrates calculating the frequency of a sine corrupted in noise and applying bin to frequency correction for extra accuracy, screenshot attached.
'defdbl a-z
'pi = 2*asin(1)
dim xx_r
(sw
-1), xx_i
(sw
-1)
x_r
(i
) = 100*sin(2*pi
*(sw
*2000/44000)*i
/sw
) + (100*rnd - 50)
'screenres sw, sh, 32
'plot signal
_printstring (0, 0), "2000 kHz signal with RND noise sampled at 44 kHz in 1024 samples"
rfft xx_r(), xx_i(), x_r(), sw
'plot its fft
pset (0, 50+3*sh
/4 - 0.005*sqr(xx_r
(0)*xx_r
(0) + xx_i
(0)*xx_i
(0)) ), _rgb(255,255,0) line -(i
*2, 50+3*sh
/4 - 0.005*sqr(xx_r
(i
)*xx_r
(i
) + xx_i
(i
)*xx_i
(i
)) ), _rgb(255,255,0)
'find peak
max = 0
m = 0
d
= 0.01*sqr(xx_r
(i
)*xx_r
(i
) + xx_i
(i
)*xx_i
(i
)) max = d
m = i
_printstring (0, sh
/2 + 16), "f_peak = m_peak * 44 kHz / 1024 samples = "+str$(m
*44000/1024)+" Hz"
'apply frequency correction, only works for some signals
u_r = xx_r(m - 1) - xx_r(m + 1)
u_i = xx_i(m - 1) - xx_i(m + 1)
v_r = 2*xx_r(m) - xx_r(m - 1) - xx_r(m + 1)
v_i = 2*xx_i(m) - xx_i(m - 1) - xx_i(m + 1)
c = (u_r*v_r + u_i*v_i)/(v_r*v_r + v_i*v_i)
sub rfft
(xx_r
(), xx_i
(), x_r
(), n
)
rev = 0
if i
and (2^j
) then rev
= rev
+ (2^(log2n
- 1 - j
))
xx_r(i) = x_r(2*rev)
xx_i(i) = x_r(2*rev + 1)
m = 2^i
w_r = 1
w_i = 0
p = j + k
q = p + (m \ 2)
u_r = w_r*xx_r(q) - w_i*xx_i(q)
u_i = w_r*xx_i(q) + w_i*xx_r(q)
v_r = xx_r(p)
v_i = xx_i(p)
xx_r(p) = v_r + u_r
xx_i(p) = v_i + u_i
xx_r(q) = v_r - u_r
xx_i(q) = v_i - u_i
u_r = w_r
u_i = w_i
w_r = u_r*wm_r - u_i*wm_i
w_i = u_r*wm_i + u_i*wm_r
xx_r(n/2) = xx_r(0)
xx_i(n/2) = xx_i(0)
xx_r(n/2 + i) = xx_r(n/2 - i)
xx_i(n/2 + i) = xx_i(n/2 - i)
xpr = (xx_r(i) + xx_r(n/2 + i)) / 2
xpi = (xx_i(i) + xx_i(n/2 + i)) / 2
xmr = (xx_r(i) - xx_r(n/2 + i)) / 2
xmi = (xx_i(i) - xx_i(n/2 + i)) / 2
xx_r
(i
) = xpr
+ xpi
*cos(2*pi
*i
/n
) - xmr
*sin(2*pi
*i
/n
) xx_i
(i
) = xmi
- xpi
*sin(2*pi
*i
/n
) - xmr
*cos(2*pi
*i
/n
)
'symmetry, complex conj
xx_r(n/2 + i) = xx_r(n/2 - 1 - i)
xx_i(n/2 + i) =-xx_i(n/2 - 1 - i)