Article

Using Windowing with Discrete Fourier Transforms

Multiply signal data by window sequence values to reduce spectral leakage.

Overview

Discrete Fourier and cosine transforms, which decompose a signal into its component frequencies and recreate a signal from a component frequency representation, work over vectors of specific lengths. For example, if you're analyzing audio data, you may be supplied with pages of 1024 samples. Discrete Fourier and cosine transforms can accurately approximate the component frequencies that have an integer number of periods, that is, signals where the start and end points join to form a continuous waveform. However, with noninteger period signals, where the endpoints don't meet, the discontinuities appear as false frequency components in a forward transform. This smearing of data is called spectral leakage.

Here you'll learn an approach to reduce spectral leakage when performing transforms over data that includes noninteger period signals. Windowing multiplies a signal by a vector that represents a smooth curve with boundary values of zero or near zero. This technique ensures the endpoints of a signal meet and reduces the discontinuities.

The signal data used here is synthesized from a series of sine waves. In a real-world app, you will most likely acquire signal data from a sensor such as as a microphone.

Synthesize a Test Signal

Use the following function to generate a composite sine wave from a supplied array of component frequencies and amplitudes:

static func synthesizeSignal(frequencyAmplitudePairs: [(f: Float, a: Float)],
                             count: Int) -> [Float] {
    
    let tau: Float = .pi * 2
    let signal: [Float] = (0 ..< count).map { index in
        frequencyAmplitudePairs(0) { accumulator, frequenciesAmplitudePair in
            let normalizedIndex = Float(index) / Float(count)
            return accumulator + sin(normalizedIndex * frequenciesAmplitudePair.f * tau) * frequenciesAmplitudePair.a
        }
    }
    
    return signal
}

Create a Signal with an Integer Number of Periods

The following code shows a Fourier series approximation of a square wave built from a series of sine waves. Each component sine wave has an integer number of periods over the length of the data:

let n = 2048

let baseFrequency: Float = 5

let frequencyAmplitudePairs = stride(from: 1, to: 50, by: 2).map { i in
    return(f: baseFrequency * Float(i), a: (1 / Float(i)))
}

var signal = synthesizeSignal(frequencyAmplitudePairs: frequencyAmplitudePairs,
                              count: n)

Use the vDSP fast Fourier transform (FFT) to compute the component frequencies of signal:

let count = n / 2
var realParts = [Float](repeating: 0,
                        count: count)
var imagParts = [Float](repeating: 0,
                        count: count)

realParts.withUnsafeMutableBufferPointer { realPtr in
    imagParts.withUnsafeMutableBufferPointer { imagPtr in
        
        var complexSignal = DSPSplitComplex(realp: realPtr.baseAddress!,
                                            imagp: imagPtr.baseAddress!)
               
        signal.withUnsafeBytes {
            vDSP.convert(interleavedComplexVector: [DSPComplex]($0.bindMemory(to: DSPComplex.self)),
                         toSplitComplexVector: &complexSignal)
        }
        
        let log2n = vDSP_Length(log2(Float(n)))
        let fft = vDSP.FFT(log2n: log2n,
                           radix: .radix2,
                           ofType: DSPSplitComplex.self)
        
        fft?.forward(input: complexSignal,
                     output: &complexSignal)
    }
}

To learn more about computing the frequency components of a signal, see Finding the Component Frequencies in a Composite Sine Wave.

The FFT treats the data set as a single period of a continuous signal. The following visualization wraps the signal around a virtual cylinder to illustrate how the FFT interprets the data, and this figure also shows that the endpoints meet:

Diagram showing a square wave wrapped around a cylinder. The signal's endpoints meet.

The following illustration shows a representation of the signal, in blue, and uses yellow to highlight the imaginary parts, that is, the imagParts array, of the result of the forward FFT:

Diagram showing a square wave and its frequency domain representation. The frequency domain consists of 25 discrete peaks that represent the 25 component sine waves.

The FFT result clearly shows that the signal is composed of 25 sine waves, represented as spikes in the graph.

Create a Signal with Noninteger Number of Periods

The following code is subtly different from the previous listing. The code defines noninteger periods, of 5.75, of the component sine waves:

let n = 2048

let baseFrequency: Float = 5.75

let frequencyAmplitudePairs = stride(from: 1, to: 50, by: 2).map { i in
    return(f: baseFrequency * Float(i), a: (1 / Float(i)))
}

var signal = synthesizeSignal(frequencyAmplitudePairs: frequenciesAmplitudePairs,
                              count: n)

The following visualization wraps the noninteger period signal around a virtual cylinder and shows the endpoint discontinuities:

Diagram showing a square wave wrapped around a cylinder. The signal's endpoints do not meet.

The image below shows the results of a transform of this data: the results shows additional, intermediate values that are a result of spectral leakage:

Diagram showing a square wave and its frequency domain representation. The frequency domain peaks are smeared across the component frequencies.

Create a Windowed Signal with Noninteger Number of Periods

The following code shows the same noninteger period signal, but in this example, you multiply the signal by the result of window(ofType:usingSequence:count:isHalfWindow:).

let n = 2048

let baseFrequency: Float = 5.75

let frequencyAmplitudePairs = stride(from: 1, to: 50, by: 2).map { i in
    return(f: baseFrequency * Float(i), a: (1 / Float(i)))
}

var signal = synthesizeSignal(frequencyAmplitudePairs: frequencyAmplitudePairs,
                              count: n)
 
let window = vDSP.window(ofType: Float.self,
                         usingSequence: .hanningDenormalized,
                         count: n, 
                         isHalfWindow: false)

signal = vDSP.multiply(signal, window)

The illustration below shows the windowed signal in blue, with its boundaries tapered toward zero, and the transformed version with reduced spectral leakage in yellow:

Diagram showing a square wave and its frequency domain representation. The square wave is tapered toward the boundaries and the smearing of frequencies is reduced.

Select Window Sequence

vDSP provides functions for generating three different windows:

Diagram showing the curves generated by Hamming, Hann, and Blackman window functions.

Create a Sine Wave with Noninteger Period

To understand the different effects of the different windows provided by vDSP, create a single composed of a signal sine wave with a noninteger period:

let frequencyAmplitudePairs = [(f: Float(32.25), a: Float(1))]

The illustration below shows the sine wave and the forward FFT result.

Diagram showing a sine wave and its frequency domain representation. The frequency domain representation shows a main central peak that's surrounded by peaks that decrease toward the edges.

Spectral leakage is apparent throughout the rendered FFT result.

Reduce Spectral Leakage Using a Hann Window

All the above examples have used the Hann window sequence. This is a great-general purpose window that reduces spectral leakage in most cases as illustrated below.

Diagram showing a sine wave and its frequency domain representation. The signal tapers toward zero at the edges. The frequency domain representation shows a main central peak that's surrounded by peaks that rapidly decrease toward the edges.

Reduce Spectral Leakage Using a Hamming Window

The Hamming window, created by passing vDSP.WindowSequence.hamming to the window(ofType:usingSequence:count:isHalfWindow:) function, unlike the Hann window, doesn't reach zero at its boundaries.

The figure below shows the result of multiplying a signal by a Hamming window: high values around the base frequency in the forward FFT are tighter than the Hann-windowed result, but there is low-level spectral leakage across the entire forward FFT.

Diagram showing a sine wave and its frequency domain representation. The signal tapers toward zero at the edges. The frequency domain representation shows a main central peak that's surrounded by peaks that rapidly decrease toward the edges.

The narrower main peak of the Hamming-windowed result provides better discrimination of component sine waves with close frequencies.

Reduce Spectral Leakage Using a Blackman Window

The Blackman window, created with vDSP.WindowSequence.blackman, does a better job of reducing spectral leakage away from the main frequency compared to Hann and Hamming, but has a wider main peak than Hann.

Diagram showing a sine wave and its frequency domain representation. The signal tapers toward zero at the edges. The frequency domain representation shows a main central peak that's surrounded by peaks that rapidly decrease toward the edges.

See Also

Fourier and Cosine Transforms

Finding the Component Frequencies in a Composite Sine Wave

Use 1D fast Fourier transform to compute the frequency components of a signal.

Signal Extraction from Noise

Use Accelerate’s discrete cosine transform to remove noise from a signal.

Halftone Descreening with 2D Fast Fourier Transform

Reduce or remove periodic artifacts from images.