Sample Code

Signal Extraction from Noise

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

Download

Overview

Accelerate’s vDSP module provides functions to perform discrete and fast Fourier transforms (FFTs) on 1D vectors and 2D matrices containing complex numbers. If you want to perform a similar transform on a vector of real numbers, vDSP includes discrete cosine transforms (DCTs).

FFTs and DCTs decompose a signal into its frequency components (known as the frequency domain representation of the signal), and the inverse transform rebuilds a signal from the frequency components.

By zeroing low-amplitude data, such as noise, from the frequency domain data, you can reconstruct a signal, leaving only its dominant frequencies. Meaningful signals that you’re trying to isolate tend to have their energy packed at a few frequencies. Noise, however, has its energy more uniformly spread across the frequency spectrum (that’s what makes it noise). If you zero out low-amplitude frequency components, you can eliminate much of the noise from the spectrum.

This sample walks you through the steps for extracting a signal from noise:

  1. Creating a signal from composite cosine waves and adding random noise to it

  2. Performing a forward DCT on the signal

  3. Zeroing the frequency domain data where the value is less than a specified threshold

  4. Rebuilding the signal from the frequency domain data using an inverse DCT

Generate the Test Signal

Build the test signal from a series of cosine waves, stored as 1024 samples in an array of single-precision values:

lazy var noisySignal = ViewController.signal(noiseAmount: noiseAmount,
                                             numSamples: numSamples)

The signal function generates a sample at each data point:

static func signal(noiseAmount: Float,
                   numSamples: Int) -> [Float] {
    
    let tau = Float.pi * 2
    
    return (0 ..< numSamples).map { i in
        let phase = Float(i) / Float(numSamples) * tau
        
        var signal = cos(phase * 1) * 1.0
        signal += cos(phase * 2) * 0.8
        signal += cos(phase * 4) * 0.4
        signal += cos(phase * 8) * 0.8
        signal += cos(phase * 16) * 1.0
        signal += cos(phase * 32) * 0.8
        
        return signal + .random(in: -1...1) * noiseAmount
    }
}

The values generated by this code return a signal like the one in the image below:

Graphic showing smooth signal waveform.

Adding noise to the signal makes it unrecognizable, and it’s impossible to hear the original tones:

Graphic showing jagged noisy signal waveform.

Prepare the DCT Setups

Create setup objects that contain all the information required to perform the forward and inverse DCT operations. Creating these setup objects can be expensive, so do it only once—for example, when your app is starting—and reuse them.

The forward transform is a type II DCT:

let forwardDCTSetup = vDSP.DCT(count: numSamples,
                               transformType: vDSP.DCTTransformType.II)

The inverse transform is a type III DCT:

let inverseDCTSetup = vDSP.DCT(count: numSamples,
                               transformType: vDSP.DCTTransformType.III)

Perform the DCT

Use the transform(_:) function to perform the DCT. This function requires the array containing the source signal and returns the frequency domain data:


var forwardDCT = forwardDCTSetup!.transform(noisySignal)

The following visualization of the frequency domain data shows the component cosine parts. The cos(phase * 1) * 1.0 component is on the left, and cos(phase * 16) * 1.0 is on the right:

Graphic showing frequency domain representation of signal consisting of five peaks.

The frequency domain visualization of the noisy signal shows the dominant frequencies with the noise spread evenly throughout the frequency range. The sample zeroes the low-amplitude data to generate the noise-free signal.

Graphic showing frequency domain representation of noisy signal consisting of many small peaks.

Apply a Threshold to the Amplitude Data

Remove the noise from the signal by zeroing all values in the frequency domain data that are below a specified threshold.

The threshold(_:to:with:result:) function sets all values in the forwardDCT array that fall below the threshold to 0:

vDSP.threshold(forwardDCT,
               to: threshold,
               with: .zeroFill,
               result: &forwardDCT)

Recreate the Signal

Use an inverse DCT to generate a new signal using the cleaned-up frequency domain data:

lazy var inverseDCT = [Float](repeating: 0,
                              count: numSamples)
var inverseDCT = inverseDCTSetup!.transform(forwardDCT)

Now scale the inverse DCT. The scaling factor for the forward transform is 2, and the scaling factor for the inverse transform is the number of samples (in this case, 1024). Use divide(_:_:) to divide the inverse DCT result by count / 2 to return a signal with the correct amplitude.

let divisor = Float(numSamples / 2)

vDSP.divide(inverseDCT,
            divisor,
            result: &inverseDCT)

See Also

First Steps

Equalizing Audio with vDSP

Shape audio output using discrete cosine transforms and biquadratic filters.

Beta Software

This documentation contains preliminary information about an API or technology in development. This information is subject to change, and software implemented according to this documentation should be tested with final operating system software.

Learn more about using Apple's beta software