Tuesday 9 September 2014

LiveCoding with Swift Audio continued...

After getting stuck trying out Swift Playgrounds using the new AVFoundation classes because I was on Mavericks the temptation was too much at the weekend and I created a new partition, downloaded the latest Yosemite build and got started. Jamie Bullock's Live coding audio with Swift playgrounds worked like a dream and I was up and running with some playground code making sounds. Great.

Googling a bit further I came across Gene de Lisa's excellent post trailblazing Swift and Core Audio and his note on using Swift with AVFoundation to play audio and generate midi notes from a sound bank. Good, this all worked nicely.

So, as this was going so smoothly I decided to jump straight in at the deep end and see how far the new AVFoundation classes and Swift bindings could be pushed... undeterred by Thomas Royal's notes on the current limitations of Audio Unit implementations with Swift as his blocker had seemed to be resolved in XCode6 Build7 I thought I'd see how far I could get following Matt Gallagher's wonderfully simple introduction to AudioUnits building an iOS tone generator. I thought I'd see how far this could go in a playground and with the basis of generating a tone the whole of audio synthesis would be open to me.

It's always fun to have something real and a little challenging as a goal to learning something new like Swift so I stumbled through reading the unfamiliar syntax and how to make the various cross-language bindings. All reasonably straightforward and answers were pretty forthcoming with some googling. Then, I got stuck. It seems the final step of being able to give a Swift function as a callback from C/C++ code is currently blocked by Apple. I followed this up on a number of different APIs and it looks like everyone is running into the same problem and the Apple Dev forums are saying this is currently not available and the alternatives are to make a C++/Objective-C/Closure trampoline [maybe I'll tackle that later] or another bit of kludgy glue. Hmmm, not nice! It's a little bit frustrating as Apple have done a good job of getting the typealias of the AURenderCallback nicely imported into AVFoundation, so let's just hope this is a matter of time and it will be resolved soon.

I'll detail where I got to and the code so far so I can share this in case it gets unblocked soon (it's in a playground, so not written for beauty, but to kick the tyres of what is possible):

import Cocoa
import AVFoundation


func RenderTone(inRefCon:UnsafeMutablePointer<Void>,
        ioActionFlags:UnsafeMutablePointer<AudioUnitRenderActionFlags>,
        inTimeStamp:UnsafePointer<AudioTimeStamp>,
        inBusNumber:UInt32,
        inNumberFrames:UInt32,
        ioData:UnsafeMutablePointer<AudioBufferList>) -> (OSStatus)
{
    // no-code as could not get this far!
    return 0;
}


var acd:AudioComponentDescription = AudioComponentDescription(
    componentType: OSType(kAudioUnitType_Output),
    componentSubType: OSType(kAudioUnitSubType_DefaultOutput),
    componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
    componentFlags: 0,
    componentFlagsMask: 0)

var ao:AudioComponent = AudioComponentFindNext(nil, &acd)

var err:OSStatus

var aci:AudioComponentInstance = nil

err = AudioComponentInstanceNew(ao, &aci)

var aci2:UnsafeMutablePointer<AudioComponentInstance> = UnsafeMutablePointer<AudioComponentInstance>(aci)


var callback:AURenderCallbackStruct = AURenderCallbackStruct(RenderTone,nil)

It's the last line where things get stuck. What is happening is that the Swift function (RenderTone) cannot be matched to the typealias of AURenderCallback which is defined as a CFunctionPointer:

typealias AURenderCallback = CFunctionPointer<(
            (UnsafeMutablePointer<Void>,
             UnsafeMutablePointer<AudioUnitRenderActionFlags>,
             UnsafePointer<AudioTimeStamp>,
             UInt32,
             UInt32,

             UnsafeMutablePointer<AudioBufferList>)->OSStatus)

Having got all this way, I then found this post on the Apple Developer forums..... which kind of summarises that I'm not the only one to have got here only to bash my head against a wall :-(

However, as another post indicates, this might not be forever as it does seem that Apple may not be completely against putting this in eventually and the devs could just have a long list to get Swift, Yosemite and iOS8 out of the door... I know how that feels!

(Note, the reason for the slight differences in the type alias definition in the message and what I have stated above is that a number of these bindings changed in Xcode6 Build4 - ConstUnsafePointer to UnsafePointer and UnsafePointer to UnsafeMutablePointer and UnsafePointer<()> to UnsafeMutablePointer<Void>)


2 comments:

  1. Hello, hondrou!
    Am I right that situation is the same for now? There is still now way to add AURenderCallback in swift?

    ReplyDelete
  2. Hi Sharrp,
    Thanks for posting. When I last took a look at this there was no simple way round this as Swift does not allow callbacks from Cfunctions. However, since posting I've played around a bit with bridging headers which allow you to do the same kind of thing in applications, I've also had some success making an Objective-C framework (to bundle things up) which handles the Cfunction callback and uses the Objective-C block callback concept to allow this to work with Swift. More importantly I have this working in a playground which makes some LiveCoding audio possible. When I get a chance I'll write up a fuller posting on what I've got working to share with everyone. I've been a bit distracted by some OSX Cocoa since then.

    Cheers,

    Hondrou

    ReplyDelete