1

I have tidied my code up and condensed it to make it readable, as per another users comments. I have a complexFloatArray class, to store arrays of Complex vectors

class complexFloatArray {
    var reals: [Float]
    var imaginaries: [Float]

    init(reals: [Float], imaginaries: [Float]){
    self.reals = reals
    self.imaginaries = imaginaries
    }
}

I then have some functions defined in extensions to this class. One being:

func useAsDSPSplitComplex<R>(_ closure: (inout DSPSplitComplex) -> R) -> R {
    return reals.withUnsafeMutableBufferPointer { realBufferPointer in
        return imaginaries.withUnsafeMutableBufferPointer { imaginaryBufferPointer in
            var dspSplitComplex = DSPSplitComplex(realp: realBufferPointer.baseAddress!, imagp: imaginaryBufferPointer.baseAddress!)
            return closure(&dspSplitComplex)
        }
    }
}

The idea is when called on an instance of complexFloatArray, it creates a DSPSplitComplex pointer for use with the Accelerate framework.

Finally I have an accelerate function I would like to use (vDSP_zrvmul) to multiply my complex vector by a real vector.

func floatMultiply(with other: [Float]) -> complexFloatArray {
    assert(self.count == other.count, "Multiplied Vectors Must have the same size!")

    var result = complexFloatArray.zeros(count: other.count)

    self.useAsDSPSplitComplex { selfPointer in
        result.useAsDSPSplitComplex { resultPointer in
            vDSP_zrvmul(
                &selfPointer, complexFloatArray.stride,
                other, complexFloatArray.stride,
                &resultPointer, complexFloatArray.stride,
                vDSP_Length(result.count))
        }

    }

    return result
}

I call the function using:

var kernel = sine.floatMultiply(with: gauss)

where sine is a complexFloatArray, and gauss is a FloatArray, both of equal length to create a morlet wavelet. However the result is a complexFloatArray filled with zeros.

When debugging I can put a breakpoint at any point in the floatMultiply function and confirm that both self and other (sine and gauss) are filled with vales. So it is somewhere in the vDSP call that is not returning the correct results.

For completeness complexFloatArray.stride = 1 (this value is declared within the complexFloatArray class).

And 'zeros' is a function within complexFloatArray to populate an Array with zeros of a certain length. (Array(repeating:0, count:N)

Any suggestions why I am getting zero in my result to this call?

I have now included the full code, rather than snippets which give an incomplete picture.
The full code for ComplexFloatArray Class is below:

class ComplexFloatArray {
    var reals: [Float]
    var imaginaries: [Float]

    init(reals: [Float], imaginaries: [Float]){
    self.reals = reals
    self.imaginaries = imaginaries
    }
}

extension ComplexFloatArray {
    var count: Int {
    assert(reals.count == imaginaries.count)
    return reals.count
    }

    static let stride = 1

    func append(real: Float, imaginary: Float) {
        self.reals.append(real)
        self.imaginaries.append(imaginary)
    }

    func removeAtIndex(index: Int) {
        self.reals.remove(at: index)
        self.imaginaries.remove(at: index)
    }

    func useAsDSPSplitComplex<R>(_ closure: (inout DSPSplitComplex) -> R) -> R {
    return reals.withUnsafeMutableBufferPointer { realBufferPointer in
        return imaginaries.withUnsafeMutableBufferPointer { imaginaryBufferPointer in
            var dspSplitComplex = DSPSplitComplex(realp: realBufferPointer.baseAddress!, imagp: imaginaryBufferPointer.baseAddress!)
            return closure(&dspSplitComplex)
            }
        }
    }
}

extension ComplexFloatArray {
    convenience init() {
        self.init(reals:[], imaginaries:[])
    }

static func zeros(count: Int) -> ComplexFloatArray {
    return ComplexFloatArray(reals:Array(repeating: 0, count: count), imaginaries: Array(repeating:0, count:count))
    }
}

extension ComplexFloatArray {
    enum ComplexMultiplicationType: Int32 { case normal = 1, conjugate = -1}

    func ComplexMultiply(
        with other: ComplexFloatArray,
        multiplicationType: ComplexMultiplicationType = .normal
    ) -> ComplexFloatArray {
        assert(self.count == other.count, "Multiplied Vectors Must have the same size!")

        var result = ComplexFloatArray.zeros(count: self.count)

        self.useAsDSPSplitComplex { selfPointer in
            other.useAsDSPSplitComplex { otherPointer in
                result.useAsDSPSplitComplex { resultPointer in
                    vDSP_zvmul(
                    &selfPointer, ComplexFloatArray.stride,
                    &otherPointer, ComplexFloatArray.stride,
                    &resultPointer, ComplexFloatArray.stride,
                    vDSP_Length(result.count),
                    multiplicationType.rawValue)
                }
            }
        }

        return result
    }
}


extension ComplexFloatArray {

    func floatMultiply(
        with other: [Float]
        ) -> ComplexFloatArray {
        assert(self.count == other.count, "Multiplied Vectors Must have the same size!")

        var result = ComplexFloatArray.zeros(count: other.count)

        self.useAsDSPSplitComplex { selfPointer in
            result.useAsDSPSplitComplex { resultPointer in
                vDSP_zrvmul(
                    &selfPointer, ComplexFloatArray.stride,
                    other, ComplexFloatArray.stride,
                    &resultPointer, ComplexFloatArray.stride,
                    vDSP_Length(result.count))
            }
         }

        return result
    }
 }

extension ComplexFloatArray {
enum FourierTransformDirection: Int32  { case forward = 1, inverse = -1 }

    func outOfPlaceComplexFourierTransform(
        setup: FFTSetup,
        resultSize:Int,
        logSize: UInt,
        direction: FourierTransformDirection) -> ComplexFloatArray {

        var result = ComplexFloatArray.zeros(count:resultSize)

        self.useAsDSPSplitComplex { selfPointer in
            result.useAsDSPSplitComplex { resultPointer in
                vDSP_fft_zop(
                setup,
                &selfPointer,
                ComplexFloatArray.stride,
                &resultPointer,
                ComplexFloatArray.stride,
                logSize,
                direction.rawValue)
            }
        }
        return result
    }
}

The full code for my CWT class is below:

var nVoices = 96            //number of voices per octave
var kernelLength = 2048     //Length of N
var fs = globalSampleRate


class CWT{

    var timeArray:[Float] = []
    var sines: [ComplexFloatArray] = []
    var gaussian:[[Float]] = []
    var fftFilterBank:[ComplexFloatArray] = []
    var filterBank:[ComplexFloatArray] = []
    var convProduct:[ComplexFloatArray] = []
    var centreFreqs:[Float]=[]
    var phase:[Float] = []
    var magnitude:[Float] = []


    func synthesizeKernels(){

        timeArray = makeArray(from: ((1.0/Float(fs))*((-0.5)*Float(kernelLength))), to: ((1.0/Float(fs))*((0.5)*Float(kernelLength))), increment: 1/fs)

         centreFreqs = getCentreFreqs(N:timeArray.count)

         for i in 0..<centreFreqs.count {
            makeSine(freq: centreFreqs[i], N:timeArray.count, iteration: i)
            makeGaus(freq: centreFreqs[i], N:timeArray.count, iteration: i)
            makeMorlet(sine: sines[i], gauss: gaussian[i], count:timeArray.count, iteration: i)
            fftKernel(kernel: filterBank[i], N:timeArray.count, iteration:i)
        }
    }



    func convolveSignal(realSamples:[Float], imagSamples:[Float]) {

        let logN = 11
        let fft1Setup = vDSP_create_fftsetup(UInt(logN), FFTRadix(FFT_RADIX2))!
        var product = ComplexFloatArray.zeros(count: filterBank.count)
        var input = ComplexFloatArray(reals: realSamples, imaginaries: imagSamples)
        var fftOfSamples = ComplexFloatArray.zeros(count: input.count)
        fftOfSamples = input.outOfPlaceComplexFourierTransform(setup: fft1Setup, resultSize: input.count, logSize: UInt(logN), direction: ComplexFloatArray.FourierTransformDirection(rawValue: 1)!)

        fftOfSamples.removeAtIndex(index: 0)


        for i in 0..<self.filterBank.count {
            var kernel = fftFilterBank[i]
            var multiplyResult = kernel.ComplexMultiply(with: fftOfSamples)
            convProduct.append(multiplyResult)
        }
   }



    //HELPER FUNCTION FOR TIME ARRAY
    func makeArray(from:Float, to:Float, increment:Float) ->[Float]{
        var Array:[Float]=[]
        for i in stride(from: from, to: to, by: increment) {
            Array.append(i)
        }
        return Array
    }


    //MAKE COMPLEX SINE WAVE
    func makeSine(freq:Float, N:Int, iteration:Int) {
        var compSine = ComplexFloatArray.init()
        for i in 0..<timeArray.count{
            let x = 2 * Float.pi * freq * timeArray[i]
            compSine.append(real: cos(x), imaginary: sin(x))
        }
        sines.append(compSine)
    }



    //MAKE GAUSSIAN WINDOW
    func makeGaus(freq:Float, N:Int, iteration:Int) {
        var gaus:[Float] = Array(repeating:0, count:N)
        let s:Float = 7 / (2.0 * Float.pi * freq)
        let interimCalc: Float = Float(2)*Float(pow(s,2))
        for i in 0..<N{
            var u = pow(timeArray[i],2)
            u = (-u)
            let v = u / interimCalc
            gaus[i] = exp(v)
        }
        gaussian.append(gaus)

    }


    //CREATE CENTRE FREQUENCIES
    func getCentreFreqs(N:Int) ->[Float]{
        var CF:[Float] = []
        var filteredCF:[Float] = []
        var G:Float = pow(10,(3/10))
        var x = makeArray(from: -1000, to: 1350, increment: 1)

        for i in 0..<x.count {
            var fraction:Float = (Float(2*Float(x[i]))-Float(59.0)) / Float(2*nVoices)
            var fr:Float = Float(1000.0) * Float(powf(Float(G), Float(fraction)))
            CF.append(fr)
        }

        for i in 0..<CF.count {
            if (Float(20) < CF[i] && CF[i] < Float(20000))  {
                filteredCF.append(CF[i])
            }
        }
        return filteredCF
    }


    //MAKE COMPLEX MORLET WAVELET
    func makeMorlet(sine:ComplexFloatArray, gauss:[Float], count:Int, iteration:Int) {
        var kernel = sine.floatMultiply(with: gauss)
        filterBank.append(kernel)
    }


    //PERFORM FFT ON KERNEL
    func fftKernel(kernel: ComplexFloatArray, N:Int, iteration:Int) {
        var size = kernel.count
        var logSize = 11
        var FFTSetup = vDSP_create_fftsetup(vDSP_Length(logSize), FFTRadix(FFT_RADIX2))
        var output = kernel.outOfPlaceComplexFourierTransform(setup: FFTSetup!, resultSize: size, logSize: UInt(logSize), direction: ComplexFloatArray.FourierTransformDirection(rawValue: 1)!)
        output.removeAtIndex(index:0)
        fftFilterBank.append(output)

    }


    //Test Signal to Convolve - 1kHz Sine Wave
    func testSine(){
        var testTimeArray = makeArray(from: ((1.0/Float(fs))*((-0.5)*Float(kernelLength))), to: ((1.0/Float(fs))*((0.5)*Float(kernelLength))), increment: 1/fs)
        var testSine = ComplexFloatArray.zeros(count: testTimeArray.count)

        for i in 0..<testTimeArray.count{
            var x = 2 * Float.pi * 1000 * testTimeArray[i]
            testSine.reals[i] = cos(x)
            testSine.imaginaries[i] = sin(x)
        }
        convolveSignal(realSamples: testSine.reals, imagSamples:testSine.imaginaries)

    }
}

finally in my ViewController class I have the following:

class ViewController: UIViewController {

    var wavelet = CWT()

    func viewDidLoad(){
        wavelet.synthesizeKernels()
        wavelet.testSine()
    }
}

If I debug this and pause on the makeMorlet function, the results of FloatMultiply are all zeros, despite having the same length values in both the left and right side of the equation.

samp17
  • 547
  • 1
  • 4
  • 16
  • I cannot reproduce the same issue with your code shown. Your implementation of `zeros(count:)` may be wrong or you have simplified your code too much and the code shown has lost some important thing from your actual code. Anyway, please try to create a project which reproduces the same issue and show whole code without hiding any lines. – OOPer Apr 12 '19 at 16:36
  • By the way, it's conventional to use UpperCamelCase for type names, as I did when I originally implemented `ComplexFloatArray`. This makes it easy to distinguish them from variables which hold instances of that type. E.g. `let complexFloatArray = ComplexFloatArray()` – Alexander Apr 12 '19 at 17:00
  • @OOPer Do you know if the usage of `other: [Float]` as an argument to a parameter of type `UnsafePointer` is valid? I don't really understand Swift's bridging between Arrays and pointer types. – Alexander Apr 12 '19 at 17:03
  • @Alexander: Yes it is, it passes a pointer to the (contiguous) element storage. The only thing to keep in mind is that that pointer is only guaranteed to be valid during the function call. – Also discussed and clarified here: https://forums.swift.org/t/passing-possibly-non-contiguous-array-to-a-c-api/21810. – Martin R Apr 12 '19 at 17:05
  • @MartinR Bleh, I don't know how I feel about these implied conversions. On the other hand, doing "withUnsafeBytes" calls get tedious, and causes a lot of nesting. Now that I think about it, I wish there was a varadiac generic function like that could let your write something like: `withUnsafeBytesTo(arrayA, arrayB, arrayC) { pointerA, pointerB, pointerC in`... – Alexander Apr 12 '19 at 17:13
  • @MartinR Do you have any idea what might be going wrong in this question's code? – Alexander Apr 12 '19 at 17:14
  • @Alexander, Please read [this article](https://developer.apple.com/documentation/swift/swift_standard_library/manual_memory_management/calling_functions_with_pointer_parameters). One thing sure is that with all the code shown in the question, filling up some missing parts with proper implementations, `floatMultiply(with:)` returns the right result. – OOPer Apr 12 '19 at 17:20
  • 1
    As usual, a [mcve] is needed :) – Martin R Apr 12 '19 at 17:23
  • I have now included my full code. I have tried this in a new Xcode project to make sure there were no weird leftovers or settings affecting my results. I have just the 4 files in this test app. The ComplexFloatArray Class, the CWT class, ViewController, and AppDelegate. AppDelegate is untouched, otherwise all code is shown above. I can confirm that I am still getting zeros on the vDSP_zrvmul function within floatMultiply. – samp17 Apr 13 '19 at 10:15
  • If I try and create a DSPSplitComplex for the result using var r:[Float] = Array(repeating:0, count: other.count) var i:[Float] = Array(repeating: 0, count: other.count) var pointer = DSPSplitComplex(realp: UnsafeMutablePointer(mutating:r), imagp: UnsafeMutablePointer(mutating: i)) and then breakpoint on the return result line, inspecting arrays r and i, they are still populated with zeros. Hovering over the 'other' array within the vDSP_zrvmul I can see the correct values in the function, so it may be with the self.useAsDSPSplitComplex call – samp17 Apr 13 '19 at 11:32

1 Answers1

1

Unfortunately, your code does not run on Xcode 10.2 with default settings.

Thread 1: Simultaneous accesses to 0x600001170550, but modification requires exclusive access

I'm not sure you are setting Exclusive Access to Memory to off (Compile time Enforcement Only), or using some older version of Xcode, but Swift compiler optimizes and generates code assuming Exclusivity Enforcement is fully valid. (So, you should never set Exclusive Access to Memory off.)

Please read this article carefully:

Swift 5 Exclusivity Enforcement

Your implementation of count in ComplexFloatArray is violating this enforcement. While executing the closure passed to reals.withUnsafeMutableBufferPointer, you cannot access reals as the array is exclusively occupied by the method.

And with violating this rule, Swift runtime may show any sort of unexpected behavior.

Try changing the implementation of count like following, and see what happens:

class ComplexFloatArray {
    var reals: [Float]
    var imaginaries: [Float]

    init(reals: [Float], imaginaries: [Float]){
        self.reals = reals
        self.imaginaries = imaginaries

        assert(reals.count == imaginaries.count)
        self.count = reals.count
    }

    //Make `count` a stored property.
    var count: Int
}

extension ComplexFloatArray {
    //Remove this computed property.
//    var count: Int {
//        assert(reals.count == imaginaries.count)
//        return reals.count
//    }

    static let stride = 1

    func append(real: Float, imaginary: Float) {
        self.reals.append(real)
        self.imaginaries.append(imaginary)

        count += 1
    }

    func removeAtIndex(index: Int) {
        self.reals.remove(at: index)
        self.imaginaries.remove(at: index)

        count -= 1
    }

    //...    
}

One more, your code generates many warnings with recommended settings, you should better not ignore them.

OOPer
  • 47,149
  • 6
  • 107
  • 142
  • I have just read this and checked and you are correct, the Exclusive Access to Memory was set to off, although I do not remember doing this, even when I just created a new project. I have corrected and implemented count as you have suggested and it works correctly. – samp17 Apr 13 '19 at 12:50
  • @samp17, Exclusivity Enforcement rule is new and so strong that the default setting may be affected by some other things. I will consider such possibility when I find some issues related to Exclusivity Enforcement. Anyway, happy to hear you have made it work. – OOPer Apr 13 '19 at 12:56