Search code examples
pythonswiftsignal-processingfftaccelerate-framework

Why is FFT different in Swift than in Python?


I'm trying to port some python numpy code to Swift using the Accelerate framework.

In python I write

import numpy as np
frames = np.array([1.0, 2.0, 3.0, 4.0])
fftArray = np.fft.fft(frames, len(frames))
print(fftArray)

And the output is:

[10.+0.j -2.+2.j -2.+0.j -2.-2.j]

So in Swift I'm trying to calculate the FFT like this:

import Foundation
import Accelerate

    func fftAnalyzer(frameOfSamples: [Float]) {
        // As above, frameOfSamples = [1.0, 2.0, 3.0, 4.0]            

        let analysisBuffer = frameOfSamples
        let frameCount = frameOfSamples.count

        var reals = [Float]()
        var imags = [Float]()
        for (idx, element) in analysisBuffer.enumerated() {
            if idx % 2 == 0 {
                reals.append(element)
            } else {
                imags.append(element)
            }
        }
        var complexBuffer = DSPSplitComplex(realp: UnsafeMutablePointer(mutating: reals), imagp: UnsafeMutablePointer(mutating: imags))

        let log2Size = Int(log2f(Float(frameCount)))

        guard let fftSetup = vDSP_create_fftsetup(vDSP_Length(log2Size), Int32(kFFTRadix2)) else {
            return []
        }

        // Perform a forward FFT
        vDSP_fft_zrip(fftSetup, &(complexBuffer), 1, UInt(log2Size), Int32(FFT_FORWARD))

        let realFloats = Array(UnsafeBufferPointer(start: complexBuffer.realp, count: Int(frameCount)))
        let imaginaryFloats = Array(UnsafeBufferPointer(start: complexBuffer.imagp, count: Int(frameCount)))

        print(realFloats)
        print(imaginaryFloats)

        // Release the setup
        vDSP_destroy_fftsetup(fftSetup)

        return realFloats
    }

The realFloats and imaginaryFloats are printed like so:

[20.0, -4.0, 0.0, 0.0]
[-4.0, 4.0, 0.0, 0.0]

Any ideas on what I should be doing differently?


Solution

  • I'm not good at numpy, but according to the doc, fft takes complex-input. Then its equivalent would be vDSP_fft_zip, not vDSP_fft_zrip.

    And your code causes buffer overflow or might cause dangling pointer, with all such things fixed I get this:

    func fftAnalyzer(frameOfSamples: [Float]) -> [Float] {
        // As above, frameOfSamples = [1.0, 2.0, 3.0, 4.0]
    
        let frameCount = frameOfSamples.count
    
        let reals = UnsafeMutableBufferPointer<Float>.allocate(capacity: frameCount)
        defer {reals.deallocate()}
        let imags =  UnsafeMutableBufferPointer<Float>.allocate(capacity: frameCount)
        defer {imags.deallocate()}
        _ = reals.initialize(from: frameOfSamples)
        imags.initialize(repeating: 0.0)
        var complexBuffer = DSPSplitComplex(realp: reals.baseAddress!, imagp: imags.baseAddress!)
    
        let log2Size = Int(log2(Float(frameCount)))
        print(log2Size)
    
        guard let fftSetup = vDSP_create_fftsetup(vDSP_Length(log2Size), FFTRadix(kFFTRadix2)) else {
            return []
        }
        defer {vDSP_destroy_fftsetup(fftSetup)}
    
        // Perform a forward FFT
        vDSP_fft_zip(fftSetup, &complexBuffer, 1, vDSP_Length(log2Size), FFTDirection(FFT_FORWARD))
    
        let realFloats = Array(reals)
        let imaginaryFloats = Array(imags)
    
        print(realFloats)
        print(imaginaryFloats)
    
        return realFloats
    }
    

    Printed

    [10.0, -2.0, -2.0, -2.0]
    [0.0, 2.0, 0.0, -2.0]