Discrete Fourier Transform and Fast Fourier Transform

### 21.4.4 Discrete Fourier Transform and Fast Fourier Transform

For any integer N, the Discrete Fourier Transform (DFT) is a transformation FN defined on the set of periodic sequences of period N; it depends on a choice of a primitive nth root of unity ωN. For sequences with complex coefficients, we take ωN=e2iπ/N. If x is a periodic sequence of period N, defined by the vector x=[x0,x1,… xN−1] then FN(x)=y is a periodic sequence of period N, defined by:

(FN(x))k=yk= |
N−1
∑
j=0
| xjωN−k j, k=0,…,N−1.

FN is bijective with inverse

FN−1= |
1
N
FN,ωN
| i.e. (FN,ωN−1(x))k= |
1
N
N−1
∑
j=0
| xjωNk j.

The Fast Fourier Transform (FFT) is an efficient way to compute the discrete Fourier transform; faster than computing each term individually. Xcas implements the FFT algorithm to compute the DFT when the period of the sequence is a power of 2.

The fft command computes the discrete Fourier transform.

- fft takes x, a list or sequence regarded as one period of a periodic sequence.
- fft(x) returns FN(x), the discrete Fourier transform of x.

  If x has length which is a power of 2, then FN(x) is computed with the Fast Fourier Transform.

The ifft command computes the inverse discrete Fourier transform.

- ifft takes x, a list or sequence regarded as one period of a periodic sequence.
- ifft(x) returns FN−1(x), the inverse discrete Fourier transform of x.

  If x has length which is a power of 2, then FN−1(x) is computed with the Fast Fourier Transform.

#### Examples

fft(0,1,1,0)

| ⎡ ⎣ | 2.0,−1.0−i,0.0,−1.0+i | ⎤ ⎦

ifft([2,-1-i,0,-1+i])

| ⎡ ⎣ | 0.0,1.0,1.0,0.0 | ⎤ ⎦

#### Applications

##### Value of a polynomial.

Define a polynomial P(x)=∑j=0N−1cjxj by the vector of its coefficients c:=[c0,c1,…,cN−1], where zeroes may be added so that N is a power of 2 (so the Fast Fourier Transform can be used).

Let us compute the values of P(x) at x=ak=ωN−k=e−2ikπ/N, k=0,…,N−1. This is just the DFT of c since

P(ak)= |
N−1
∑
j=0
| cj(ωN−k)j=FN(c)k.

For example, let P(x+x2) and x=1,i,−1,−i. Here the coefficients of P are [0,1,1,0], N=4 and ω=e2iπ/4=i.

fft([0,1,1,0])

| ⎡ ⎣ | 2.0,−1.0−i,0.0,−1.0+i | ⎤ ⎦

Hence P(1)=2, P(−i)=P(ω−1)=−1−i, P(−1)=P(ω−2)=0, P(i)=P(ω−3)=−1+i.

Let us now compute the values of P(x) at x=bk=ωNk=e2ikπ/N for k=0,…,N−1. This is the inverse DFT of c since

P(ak)= |
N−1
∑
j=0
| cj(ωNk)j=NFN−1(c)k.

Use this method to find the values of P(x+x2) at x=1,i,−1,−i. Again, the coefficients of P are [0,1,1,0], N=4 and ω=e2iπ/4=i.

4*ifft([0,1,1,0])

| ⎡ ⎣ | 2.0,−1.0+i,0.0,−1.0−i | ⎤ ⎦

Hence P(1)=2, P(i)=P(ω1)=−1+i, P(−1)=P(ω2)=0, P(−i)=P(ω3)=−1−i. You find of course the same values as above.

##### Trigonometric interpolation.

Let f be periodic function of period 2π and let fk=f(2kπ/N) for k=0,…,N−1. Find a trigonometric polynomial p that interpolates f at xk=2kπ/N, that is find pj for j=0,…,N−1 such that

p(x)= |
N−1
∑
j=0
| pj xj, p(xk)=fk.

Replacing xk by its value in p(x) we get:

N−1
∑
j=0
| pj e |
i |
j2kπ
N
| =fk.

In other words, (fk) is the inverse DFT of (pk), hence

(pk)= |
1
N
| FN | ⎛ ⎝ | (fk) | ⎞ ⎠ | .

If the function f is real then p−k=pk, hence

p(x)= |
⎧ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎩ |
p0+ 2 Re | ⎛ ⎜ ⎜ ⎝ |
N
2
| −1
∑
k=0
| pk eikx | ⎞ ⎟ ⎟ ⎠ | +Re | ⎛ ⎜ ⎝ | p |
N
2
| e |
i |
Nx
2
| ⎞ ⎟ ⎠ | ,
| N even,
p(x)=p0+ 2 Re | ⎛ ⎜ ⎜ ⎝ |
N−1
2
∑
k=0
| pk eikx | ⎞ ⎟ ⎟ ⎠ | ,
| N odd.

For an example, see Section 17.1.4.

##### Fourier series.

Let f be a periodic function of period 2π and let yk=f(xk) where xk=2kπ/N for k=0,…,N−1. Suppose that the Fourier series of f converges to f (this will be the case if for example f is continuous). If N is large, a good approximation of f will be given by:

∑
− |
N
2
| ≤ n< |
N
2
| cn einx.

Hence we want a numeric approximation of

cn= |
1
2π
|  | ∫ |
2π
0
| f(t) e−int dt.

The numeric value of the integral ∫02πf(t) e−int dt can be computed by the trapezoidal rule (note that the Romberg algorithm would not work here because the Euler-Maclaurin development has its coefficients equal to zero, since the integrated function is periodic, hence all its derivatives have the same value at 0 and at 2π). If cn is the numeric value of cn obtained by the trapezoidal rule, then

cn= |
1
2π
2π
N
N−1
∑
k=0
| yk e |
−2i |
nkπ
N
| , − |
N
2
| ≤ n< |
N
2
| .

Indeed, since xk=2kπ/N and f(xk)=yk:

f(xk) e−inxk |
=yk e |
−2i |
nkπ
N
| ,
f(0) e0=f(2π) e |
−2i |
nNπ
N
| =y0=yN. |  |  |  |  |  |  |  |  |

Hence [c0,…,cN/2−1,cN/2+1,…,cN−1]= 1/NFN([y0,y1,…,y(N−1)]), since

- if n≥0, then cn=yn.
- if n<0, then cn=yn+N.
- ωN=e2iπ/N, hence ωNn=ωNn+N.

Several properties are listed below.

- The coefficients of the trigonometric polynomial that interpolates f at x=2kπ/N are

  pn=cn, − |
  N
  2
  | ≤ n< |
  N
  2
  | .

- If f is a trigonometric polynomial of degree m≤ N/2, then

  f(t)= |
  m−1
  ∑
  k=−m
  | ck e2ikπ t.

  The trigonometric polynomial that interpolates f is f itself, the numeric approximation of the coefficients are in fact exact (cn=cn).
- More generally, you can compute cn−cn.

  Suppose that f is equal to its Fourier series, i.e. that:

  f(t)= |
  +∞
  ∑
  m=−∞
  | cm e2iπ mt, |
  +∞
  ∑
  m=−∞
  | |cm|<∞.

  Then:

  f(xk)=f | ⎛ ⎜ ⎜ ⎝ |
  2kπ
  N
  | ⎞ ⎟ ⎟ ⎠ | =yk= |
  +∞
  ∑
  m=−∞
  | cmωNkm, cn= |
  1
  N
  N−1
  ∑
  k=0
  | ykωN−kn.

  Replace yk by its value in cn:

  cn= |
  1
  N
  N−1
  ∑
  k=0
  +∞
  ∑
  m=−∞
  | cmωNkmωN−kn.

  If m≠ n (mod N ), ωNm−n is an nth root of unity different from 1, hence:

  ωN(m−n)N=1, |
  N−1
  ∑
  k=0
  | ωN(m−n)k=0.

  Therefore, if m−n is a multiple of N (m=n+l· N) then ∑k=0N−1ωNk(m−n)=N, otherwise ∑k=0N−1ωNk(m−n)=0. By reversing the two sums, you get

  cn |
  = |
  1
  N
  +∞
  ∑
  m=−∞
  | cm |
  N−1
  ∑
  k=0
  | ωNk(m−n)
  = |
  +∞
  ∑
  l=−∞
  | c(n+l· N)
  | =⋯ + cn−2 N+cn−N+cn+cn+N+cn+2N+⋯ |  |  |  |  |  |  |  |  |

  Conclusion: if |n|<N/2, then cn−cn is a sum of cj with large indices (at least N/2 in absolute value), hence is small (depending on the rate of convergence of the Fourier series).

For example, input:

f(t):=cos(t)+cos(2*t):; x:=f(2*k*pi/8)$(k=0..7)

2, |
√ |
2
2
| ,−1,− |
√ |
2
2
| ,0,− |
√ |
2
2
| ,−1, |
√ |
2
2

fft(x)

| ⎡ ⎣ | 0.0,4.0,4.0,0.0,0.0,0.0,4.0,4.0 | ⎤ ⎦

Dividing by N=8, you get

| c0=0,c1=0.5,c2=0.5,c3=0.0, |
| c−4=0.0,c−3=0.0,c−2=0.5,c−1=0.5. |

Hence bk=0 and ak=c−k+ck equals 1 for k=1,2 and 0 otherwise.

##### Convolution Product.

If P(x)=∑j=0n−1ajxj and Q(x)=∑j=0m−1bjxj are given by the vectors of their coefficients a=[a0,a1,…,an−1] and b=[b0,b1,…,bm−1], you can compute the product of these two polynomials using the DFT. The product of polynomials is the convolution product of the periodic sequence of their coefficients if the period is greater or equal to (n+m). Therefore we complete a (resp. b) with m+p (resp. n+p) zeros, where p is chosen such that N=n+m+p is a power of 2. If a=[a0,a1,…,an−1,0,…,0] and b=[b0,b1,…,bm−1,0,…,0], then:

P(x)Q(x)= |
n+m−1
∑
j=0
| (a∗ b)jxj.

If you know FN(a) and FN(b), then a∗ b=FN−1(FN(a)⊙ FN(b)), where ⊙ denotes the Hadamard (elementwise) product.

##### Noise removal with spectral subtraction.

We use Xcas to implement a simple algorithm for static noise removal based on the spectral subtraction method1.

The efficiency of the spectral subtraction method largely depends on a good noise spectrum estimate. Below is the code for a function noiseprof that takes data and wlen as its arguments. These are, respectively, a signal chunk containing only noise and the window length for signal segmentation (the best values are powers of two, such as 256, 512 or 1024). The function returns an estimate of the noise power spectrum obtained by averaging the power spectra of a (not too large) number of distinct chunks of data of length wlen. The Hamming window is applied prior to FFT.

noiseprof(data,wlen):={ local N,h,dx,x,v,cnt; N:=length(data); h:=wlen/2; dx:=min(h,max(1,(N-wlen)/100)); v:=[0$wlen]; for (x:=h,cnt:=0;x<N-h;x+=dx,cnt++) v+=abs(fft(hamming_window(data,floor(x)-h,wlen))).^2; return 1.0/cnt*v; }:;

The main function is noisered, which takes three arguments: the input signal data, the noise power spectrum np and the “spectral floor” parameter beta (β, the minimum power level). The function performs subtraction of the noise spectrum in chunks of length wlen (the length of list np) using the overlap-and-add approach with the Hamming window function.

noisered(data,np,beta):={ local wlen,h,N,L,padded,out,j,k,s,ds,r,alpha; wlen:=length(np); N:=length(data); h:=wlen/2; L:=0; repeat L+=wlen; until L>=N; padded:=concat(data,[0$(L-N)]); out:=[0$L]; for (k:=0;k<L-wlen;k+=h) { s:=fft(hamming_window(padded,k,wlen)); alpha:=max(1,4-3*sum(abs(s).^2)/(20*sum(np))); r:=ifft(zip(max,abs(s).^2-alpha*np,beta*np).^(1/2).*exp(i*arg(s))); for (j:=0;j<wlen;j++) out[k+j]+=re(r[j]); }; return mid(out,0,N); }:;

You can test the algorithm on a small speech sample with an audible amount of static noise (you can download the audio from here).

clip:=normalize(readwav("/home/luka/Downloads/sf1_n1L.wav"),-1)

a sound clip with 41677 samples at 16000 Hz (16 bit, mono) |  |  |  |  |  |  |  |  |  |

plotwav(clip)

Speech starts after approximately 0.2 seconds of pure noise. You can use that part of the clip for obtaining an estimate of the noise power spectrum with wlen set to 256.

noise:=channel_data(clip,range=0.0..0.15):; np:=noiseprof(noise,256):;

Now call the noisered function with β=0.02:

c:=noisered(channel_data(clip),np,0.02):; cleaned:=createwav(c):; plotwav(cleaned)

Observe that the noise level is significantly lower than in the original clip. You could use the playsnd command to compare the input with the output by hearing, which would reveal that the noise is still present but in a lesser degree. The parameter β controls how much noise is left in.