Search code examples
cswiftpointersgrand-central-dispatchcore-audio

Update a progress bar from a C function


I am working on an audio app for iOS 8 in Swift. A MIDI file is loaded and, when the user pushes the "Save" button, it is rendered to a file in a C function that manually pulls the samples.
Now, I would like to update a progress bar in the interface for this save action.
I added a property observer to a var in ViewController, and I passed a pointer to this var to the C function, hoping that it would update it during the loop… well, no. Even if the C function updates the value of the var inside the loop, this update is "fired" only once, at the end. Is there a way to update the progress bar?
Here is some code.
Thanks!

In ViewController:

var outProgressBarValue:Float = 0.0 {
    willSet {
        dispatch_async(dispatch_get_main_queue(), {
            println("New value received! -> \(newValue)")
            self.progressBarOutlet.setProgress(newValue, animated: true)
        })
    }
}

@IBAction func saveButton(sender: AnyObject) {
    [...]
    let backgroundQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
    dispatch_async(backgroundQueue, {
        WriteOutputFile(url, self.musicSequence!, Sound.sharedInstance.musicPlayer, self.processingGraph,  &self.outProgressBarValue)

    dispatch_async(dispatch_get_main_queue(), {
            restoreOnlineRendering(processingGraph: self.processingGraph, mainMixerNode: self.mainMixerNode, ioNode: &(self.ioNode))
            self.saveButtonOutlet.setTitle("Done", forState: UIControlState.Normal)
    [...]
}

In a linked C file:

void WriteOutputFile(CFURLRef url, MusicSequence musicSequence, MusicPlayer musicPlayer, AUGraph processingGraph, float *outProgressBarValue) {  
[…] 
    CheckError(MusicPlayerStart(musicPlayer), "MusicPlayerStart");
    do {
        CheckError(AudioUnitRender (outputUnit, &actionFlags, &tStamp, 0, numFrames, bufferList), "AudioUnitRender");
        tStamp.mSampleTime += (Float64)numFrames;
        CheckError(ExtAudioFileWrite(outfile, numFrames, bufferList), "ExtAudioFileWrite");
        CheckError(MusicPlayerGetTime (musicPlayer, &currentTime), "MusicPlayerGetTime");
        *outProgressBarValue = currentTime / sequenceLength;
        printf("outProgressBarValue:\t %f\n", *outProgressBarValue);
    } while (currentTime < sequenceLength);

    CheckError(MusicPlayerStop(musicPlayer), "MusicPlayerStop"); 
[…] 
}  

Solution

  • If observing does not work, why not send the update explicitly to the controller? For example, via NSNotification.

    Here is what I did as a proof of concept:

    1. Add an Objective-C class file with .h and .m files. No need to define a class in this file, just put your c function in .m, and the declaration in .h.

    2. Configure the bridging header, where you import the .h file. The bridging header is typically called "-Bridging-Header.h".

    3. You can now just call your function in any swift class.

    4. In the .m file with the c function, import "<UIKit/UIkit.h>" (or Foundation).

    5. When needed, post a notification from your c function similar to this:

      NSNotification *notif = [[NSNotification alloc] initWithName:@"Update"
         object:nil userInfo:@{@"progress" : @0.42}];
      [[NSNotificationCenter defaultCenter] postNotification:notif];
      
    6. In the swift file, follow the usual procedure to add self as observer for the notification and call the update function with the values passed in the userInfo dictionary.