3

I have written a sample project in Swift to try out the relatively new Core Audio V3 API stuff. Everything seems to work around creating a custom Audio Unit and loading it in process. But the actual audio rendering isn't going so well. I've often read that the rendering code needs to be in C or C++ but I've also heard Swift is fast and thought I could write some minimal audio rendering code in it.

the rendering code

override var internalRenderBlock: AUInternalRenderBlock {
    get {
        return {
            (_ actionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
            _ timeStamp: UnsafePointer<AudioTimeStamp>,
            _ frameCount: AUAudioFrameCount,
            _ outputBusNumber: Int,
            _ bufferList: UnsafeMutablePointer<AudioBufferList>,
            _ renderEvent: UnsafePointer<AURenderEvent>?,
            _ pull: AudioToolbox.AURenderPullInputBlock?) -> AUAudioUnitStatus in

            let bufferList = bufferList.pointee
            let theBuffers = bufferList.mBuffers // only one (AudioBuffer) ??

            guard let theBufferData = theBuffers.mData?.assumingMemoryBound(to: Float.self) else {
                return 1 // come up with better error?
            }

            let amountFrames = Int(frameCount)

            for frame in 0...amountFrames / 2 {
                let frame = theBufferData.advanced(by: frame)
                frame.pointee = sin(self.phase)
                self.phase += 0.0001
            }

            return noErr
        }
    }
}

Sounds Bad

The resulting sound is not what I'd expect. My initial thoughts are that Swift is the wrong choice. Yet Interestingly, AudioToolbox does provide a typealias for this AUAudioUnit's rendering property which looks like:

public typealias AUInternalRenderBlock = (UnsafeMutablePointer<AudioUnitRenderActionFlags>, UnsafePointer<AudioTimeStamp>, AUAudioFrameCount, Int, UnsafeMutablePointer<AudioBufferList>, UnsafePointer<AURenderEvent>?, AudioToolbox.AURenderPullInputBlock?) -> AUAudioUnitStatus

This would lead me to believe that it is perhaps possible to write rendering code in Swift.

observed problems

But still, there are a few things going wrong here. (aside from my obvious lack of competency with Swift memory management stuff).

A) despite theBuffers saying that its mNumberOfBuffers is 2, theBuffers winds up not being an array but rather of type (AudioBuffer). I don't understand the need for parenthesis. I can't find a second AudioBuffer.

B) more importantly, when I write a basic sin wave to the one AudioBuffer I can access, the resulting sound is distorted and inconsistent. Could this be Swift's fault? Is it just impossible to write any audio unit rendering code in Swift? Or have a made some assumptions here that is breaking my rendering somehow?

Finally

If it is simply the case that writing this part in Swift is infeasible, then I would like to have some resources on interoperating Swift and C for Audio Unit rendering blocks. So, could the property returning the closure be written in Swift, but the closure's implementation calls down into C? or does the property have to simply return a C function whose prototype matches the closure's type?

Thanks in advance.

The rest of this project can be seen here for context.

Alex Bollbach
  • 4,370
  • 9
  • 32
  • 80
  • Possible duplicate of [Using AudioBufferList with Swift](https://stackoverflow.com/questions/24838106/using-audiobufferlist-with-swift) – Joshua Nozzi Jul 22 '17 at 16:24
  • In a recent WWDC session on Core Audio, Apple explicitly said to do certain audio context code in C, not Swift or Obj C. (But my github gist on V3 rendering in Swift seems to work anyway... currently... could break). – hotpaw2 Jul 22 '17 at 19:23
  • that has been made clear to me at this point. I was thrown off by the Swift typealias in public "headers". why would that be there? – Alex Bollbach Jul 22 '17 at 19:25

1 Answers1

3

The main reason that you were listening a distorted sound was that the phase increment of 0.0001 is too small, which would take 62832 samples to fill up one period of the sine wave -- merely 0.70 hertz! (Assuming your sample rate is 44100)

In addition to the ultra-low-frequency sine wave, you were listening to a sound of about 44100 / 512 = 86.1 Hz, because you were filling only the half of the audio buffer (amountFrames / 2). So the sound was a near-rectangular wave of the period of your audio rendering period, with slowly varying amplitude in about 0.70 Hz.

I could write a working sine wave generator unit based on your code:

override var internalRenderBlock: AUInternalRenderBlock {
    return { ( _, _, frameCount, _, bufferList, _, _) in
        let srate = Float(self.bus.format.sampleRate)
        var phase = self.phase

        for buffer in UnsafeMutableAudioBufferListPointer(bufferList) {
            phase = self.phase
            assert(buffer.mNumberChannels == 1, "interleaved channel not supported")

            let frames = buffer.mData!.assumingMemoryBound(to: Float.self)

            for i in 0 ..< Int(frameCount) {
                frames[i] = sin(phase)
                phase += 2 * .pi * 440 / srate // 440 Hz
                if phase > 2 * .pi {
                    phase -= 2 * .pi // to avoid floating point inaccuracy
                }
            }
        }

        self.phase = phase
        return noErr
    }
}

Regarding the observed problem A, the AudioBufferList is a wrapper for variable length C struct, where the first field mNumberBuffers indicates the number of buffers (i.e. number of non-interleaved channels), and the second field is a variable length array:

typedef struct AudioBufferList {
    UInt32 mNumberBuffers;
    AudioBuffer mBuffers[1];
} AudioBufferList;

The user of this struct, in Objective-C or C++, is expected to allocate mNumberBuffers * sizeof(AudioBuffer) bytes, which is enough for storing multiple mBuffers. Since C does not perform boundary checks on arrays, the users could just write mBuffers[1] or mBuffers[2] to access the second or third buffer.

Because Swift doesn't have this variable length array feature, Apple provides UnsafeMutableAudioBufferListPointer, which can be used like a Swift collection of AudioBuffers; I used this in the outer for loop above.

Finally, I tried not to access self in the innermost loop in the code, because accessing a Swift or Objective-C object might involve unexpected lags, which was the reason why Apple recommends writing rendering loop in C/C++. But for simple cases like this, I would say writing in Swift is a lot easier and the latency is still manageable.

lyomi
  • 4,230
  • 6
  • 30
  • 39