Audiobus

I've got this working in my synth app, and made some modifications I though you all might find useful:

1. Currently, any time you change a buffer size, sample rate, or restart the audio device, JUCE deletes the AudioIODevice and re-creates it. This was causing connectivity issues with Audiobus/IAA including loss of audio, duplicate Audiobus interfaces on the side of the screen, crashes, etc. So I made the AudioUnit and audiobus Wrapper static, and instead of deleting and recreating them, it simply stops and starts them.

2. Added background mode management, so if your app is in the background, and it's not connected to Audiobus or IAA, and there aren't any available MIDI Sources, the AudioUnit and AVAudioSession shut down. Good for the user not to have unnecessarily running apps, and I've heard Apple looks for this during the App Store review.

3. Added methods for retrieving IAA host info like getting the host icon, play time, playhead position, triggering record, play, etc.

juce_ios_Audio.mm

​
/*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2013 - Raw Material Software Ltd.
   Permission is granted to use this software under the terms of either:
   a) the GPL v2 (or any later version)
   b) the Affero GPL v3
   Details of these licenses can be found at: www.gnu.org/licenses
   JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
   ------------------------------------------------------------------------------
   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.juce.com for more information.
  ==============================================================================
*/

#include "juce_ios_Audio.h"

#import <AVFoundation/AVAudioSession.h>
#import <AudioToolbox/AudioToolbox.h>

} // juce namespace

// AB
#import "Audiobus.h"

@interface Wrapper : NSObject
{
    juce::iOSAudioIODevice* owner;
    
// AB
    ABSenderPort *audiobusOutput;
}

// AB
@property (readonly) ABSenderPort* audiobusOutput;
@property (strong, nonatomic) ABAudiobusController* audiobusController;
@property bool isRegistered;
@property bool isActivated;

- (void) stop;
- (void) start;
- (void)registerForRouteChangeNotification;
-(void)applicationDidEnterBackground:(NSNotification *)notification;
-(void)applicationWillEnterForeground:(NSNotification *)notification;
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context;
- (void)routeChange:(NSNotification*)notification;
- (void)activateAudiobus:(AudioUnit)outputUnit;

@end

static void * kMemberOfActiveAudiobusSessionChanged = &kMemberOfActiveAudiobusSessionChanged;
static void * kAudiobusConnectedChanged = &kAudiobusConnectedChanged;
static Wrapper *wrapper = nil;

@implementation Wrapper

// AB
@synthesize audiobusOutput;
@synthesize audiobusController;
@synthesize isRegistered;
@synthesize isActivated;

+ (id)sharedInstance
{
    if (wrapper == nil)
        wrapper = [[Wrapper alloc] init];
    
    return wrapper;
}

- (void)assignOwner: (juce::iOSAudioIODevice*) owner_
{
    owner = owner_;
}

- (void)dealloc {
    
// AB
    [audiobusController removeSenderPort:audiobusOutput];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [audiobusController removeObserver:self forKeyPath:@"connected"];
    [audiobusController removeObserver:self forKeyPath:@"memberOfActiveAudiobusSession"];
    [super dealloc];
}

- (void) stop
{
    owner->stopAudioUnit();
}

- (void) start
{
    owner->startAudioUnit();
}

- (void) registerForRouteChangeNotification {
    
    if (!isRegistered)
    {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(routeChange:)
                                                     name:AVAudioSessionRouteChangeNotification
                                                   object:nil];
        
        isRegistered = true;
    }
}

- (void)routeChange:(NSNotification*)notification {
    
    if (owner != 0)
        owner->routingChanged (notification);
}

-(void)applicationDidEnterBackground:(NSNotification *)notification
{
    [self updateAudioEngineState];
}

-(void)applicationWillEnterForeground:(NSNotification *)notification
{
    bool running = false;
    UInt32 size = sizeof(running);
    AudioUnitGetProperty(owner->getAudioUnit(), kAudioOutputUnitProperty_IsRunning, kAudioUnitScope_Global, 0, &running, &size);
    
    if (!running || [ABAudioUnitFader transitionsRunning] )
    {
        // fade in is causing problems when launchign app from IAA host. So we'll just abruptly start it
        //[ABAudioUnitFader fadeInAudioUnit:owner->getAudioUnit() beginBlock:^{ [self start]; } completionBlock:nil];
        [self start];
    }
}

-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
    
    if ( context == kAudiobusConnectedChanged || context == kMemberOfActiveAudiobusSessionChanged )
    {
        /*
        bool memberChanged = context == kAudiobusConnectedChanged;
        bool connectChanged = context == kMemberOfActiveAudiobusSessionChanged;
        bool appInBG = [UIApplication sharedApplication].applicationState == UIApplicationStateBackground;
        bool kConnectChanged;
        
        if (kAudiobusConnectedChanged)
            kConnectChanged = true;
        else
            kConnectChanged = false;
         */
        
        if ( [UIApplication sharedApplication].applicationState == UIApplicationStateBackground
            && !audiobusController.connected
            && !audiobusController.memberOfActiveAudiobusSession )
        {
            // Audiobus session is finished. Time to sleep.
            [self stop];
        }
        
        if (kAudiobusConnectedChanged && audiobusController.connected )
        {
            // Make sure we're running, if we're connected
            [self start];
        }
    }
    else
    {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

- (void)activateAudiobus:(AudioUnit)outputUnit;
{
// AB
    if (!isActivated)
    {
        audiobusController = [[ABAudiobusController alloc] initWithApiKey:@"YOUR_API_KEY"]; //use your API key here
        
        audiobusController.connectionPanelPosition = ABConnectionPanelPositionRight; //choose where you want the Audiobus navigation widget to show up
        
        
        
        // port information needs to match the information entered into the .plist (see audiobus integration guide)
        audiobusOutput = [[ABSenderPort alloc] initWithName:@"Company: App"
                                                            title:NSLocalizedString(@"Company: App", @"")
                                        audioComponentDescription:(AudioComponentDescription)
                                {
                                    .componentType = kAudioUnitType_RemoteInstrument,
                                    .componentSubType = 'SUBTYPE', // Note single quotes
                                    .componentManufacturer = 'MTFR'
                                } //
                                                        audioUnit:outputUnit];
        
        [audiobusController addSenderPort:audiobusOutput];
        
        //would create filter or input ports here if I needed them
        
        // Watch the audiobusAppRunning and connected properties
        [audiobusController addObserver:self
                             forKeyPath:@"connected"
                                options:0
                                context:kAudiobusConnectedChanged];
        
        [audiobusController addObserver:self
                             forKeyPath:@"memberOfActiveAudiobusSession"
                                options:0
                                context:kMemberOfActiveAudiobusSessionChanged];
        
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
        
        isActivated = true;
    }
}

-(bool) areMIDISourcesOpen
{
    if (MIDIGetNumberOfSources() > 0)
        return true;
    else
        return false;
}

-(void) updateAudioEngineState
{
    if (![self areMIDISourcesOpen]
        && !audiobusController.connected
        && !audiobusController.memberOfActiveAudiobusSession
        && [UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
    {
        [ABAudioUnitFader fadeOutAudioUnit:owner->getAudioUnit() completionBlock:^{ [self stop]; }];
    }
    else
    {
        [self start];
    }
}

-(bool) isHostConnectedViaAudiobus
{
    return audiobusController.audiobusConnected;
}

@end


namespace juce 
{
    AudioUnit iOSAudioIODevice::audioUnit = 0;
        
    iOSAudioIODevice::iOSAudioIODevice (const String& deviceName)
    : AudioIODevice (deviceName, "Audio"),
    actualBufferSize (0),
    isRunning (false),
    callback (nullptr),
    floatData (1, 2)
    {
        // NEW
        if (audioUnit != 0)
            AudioOutputUnitStop(audioUnit);

        [[Wrapper sharedInstance] assignOwner:this];
        
        getSessionHolder().activeDevices.add (this);
        numInputChannels = 2;
        numOutputChannels = 2;
        preferredBufferSize = 0;
        updateDeviceInfo();
    }

    iOSAudioIODevice::~iOSAudioIODevice()
    {
        setAudioUnitCallback(false);
        getSessionHolder().activeDevices.removeFirstMatchingValue (this);
        close();
    }

    StringArray iOSAudioIODevice::getOutputChannelNames()
    {
        StringArray s;
        s.add ("Left");
        s.add ("Right");
        return s;
    }

    StringArray iOSAudioIODevice::getInputChannelNames()
    {
        StringArray s;
        if (audioInputIsAvailable)
        {
            s.add ("Left");
            s.add ("Right");
        }
        return s;
    }

    Array<double> iOSAudioIODevice::getAvailableSampleRates()
    {
        // can't find a good way to actually ask the device for which of these it supports..
        static const double rates[] = { 8000.0, 16000.0, 22050.0, 32000.0, 44100.0, 48000.0 };
        return Array<double> (rates, numElementsInArray (rates));
    }

    Array<int> iOSAudioIODevice::getAvailableBufferSizes()
    {
        Array<int> r;
        for (int i = 6; i < 12; ++i)
            r.add (1 << i);
        return r;
    }

    int iOSAudioIODevice::getDefaultBufferSize()
    {
        return 1024;
    }

    String iOSAudioIODevice::open (const BigInteger& inputChannelsWanted,
                 const BigInteger& outputChannelsWanted,
                 double targetSampleRate, int bufferSize)
    {
        close();
        
        lastError.clear();
        preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize;
        //  xxx set up channel mapping
        activeOutputChans = outputChannelsWanted;
        activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false);
        numOutputChannels = activeOutputChans.countNumberOfSetBits();
        monoOutputChannelNumber = activeOutputChans.findNextSetBit (0);
        
        activeInputChans = inputChannelsWanted;
        activeInputChans.setRange (2, activeInputChans.getHighestBit(), false);
        numInputChannels = activeInputChans.countNumberOfSetBits();
        monoInputChannelNumber = activeInputChans.findNextSetBit (0);

        // OLD
        // AudioSessionSetActive (true);
        
        if (numInputChannels > 0 && audioInputIsAvailable)
        {
            // NEW
            [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayAndRecord
                            withOptions: AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionDefaultToSpeaker |AVAudioSessionCategoryOptionAllowBluetooth
                                  error:  &err];
            // OLD
            //setSessionUInt32Property (kAudioSessionProperty_AudioCategory, kAudioSessionCategory_PlayAndRecord);
            //setSessionUInt32Property (kAudioSessionProperty_OverrideCategoryEnableBluetoothInput, 1);
        }
        else
        {
            // NEW
            [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback
                            withOptions: AVAudioSessionCategoryOptionMixWithOthers
                                  error:  &err];
            // OLD
            //setSessionUInt32Property (kAudioSessionProperty_AudioCategory, kAudioSessionCategory_MediaPlayback);
        }

        // NEW
        [[AVAudioSession sharedInstance] setActive: YES error:  &err];
        
        if (audioUnit != 0)
            AudioOutputUnitStart(audioUnit);
        
        [[Wrapper sharedInstance] registerForRouteChangeNotification];
        [[AVAudioSession sharedInstance] setPreferredSampleRate:targetSampleRate error:&err];
        
        // OLD
        //AudioSessionAddPropertyListener (kAudioSessionProperty_AudioRouteChange, routingChangedStatic, this);
        //fixAudioRouteIfSetToReceiver();
        //setSessionFloat64Property (kAudioSessionProperty_PreferredHardwareSampleRate, targetSampleRate);
        
        updateDeviceInfo();
        // NEW
        [[AVAudioSession sharedInstance] setPreferredIOBufferDuration: preferredBufferSize/sampleRate error: &err];
        
        // OLD
        //setSessionFloat32Property (kAudioSessionProperty_PreferredHardwareIOBufferDuration, preferredBufferSize / sampleRate);
        updateCurrentBufferSize();
        prepareFloatBuffers (actualBufferSize);
        isRunning = true;
        routingChanged (nullptr);  // creates and starts the AU
        lastError = audioUnit != 0 ? "" : "Couldn't open the device";
        return lastError;
    }

    void iOSAudioIODevice::close()
    {
        if (isRunning)
        {
            isRunning = false;
            // NEW
            [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback
                            withOptions: AVAudioSessionCategoryOptionMixWithOthers
                                  error:  &err];
            // OLD
            /*
            setSessionUInt32Property (kAudioSessionProperty_AudioCategory, kAudioSessionCategory_MediaPlayback);
            AudioSessionRemovePropertyListenerWithUserData (kAudioSessionProperty_AudioRouteChange, routingChangedStatic, this);
            AudioSessionSetActive (false);
            if (audioUnit != 0)
            {
                AudioComponentInstanceDispose (audioUnit);
                audioUnit = 0;
            }
             */
        }
        
        // NEW
        if (audioUnit != 0)
            AudioOutputUnitStop(audioUnit);
        
        [[AVAudioSession sharedInstance] setActive: NO error:  &err];
    }

    bool iOSAudioIODevice::isOpen()                       { return isRunning; }
    int iOSAudioIODevice::getCurrentBufferSizeSamples()   { return actualBufferSize; }
    double iOSAudioIODevice::getCurrentSampleRate()       { return sampleRate; }
    int iOSAudioIODevice::getCurrentBitDepth()            { return 16; }
    BigInteger iOSAudioIODevice::getActiveOutputChannels() const    { return activeOutputChans; }
    BigInteger iOSAudioIODevice::getActiveInputChannels() const     { return activeInputChans; }
    
    int iOSAudioIODevice::getOutputLatencyInSamples()
    {
        // NEW
        double latency = [AVAudioSession sharedInstance].outputLatency;
        return roundToInt (latency * getCurrentSampleRate());

        // OLD
        //return getLatency (kAudioSessionProperty_CurrentHardwareOutputLatency);
    }
        
    int iOSAudioIODevice::getInputLatencyInSamples()
    {
        // NEW
        double latency = [AVAudioSession sharedInstance].inputLatency;
        return roundToInt (latency * getCurrentSampleRate());

        // OLD
        //return getLatency (kAudioSessionProperty_CurrentHardwareInputLatency);
    }
        
    // OLD
    /*
    int iOSAudioIODevice::getLatency (AudioSessionPropertyID propID)
    {
        Float32 latency = 0;
        getSessionProperty (propID, latency);
        return roundToInt (latency * getCurrentSampleRate());
    }
    */
        
    void iOSAudioIODevice::start (AudioIODeviceCallback* newCallback)
    {
        if (isRunning && callback != newCallback)
        {
            if (newCallback != nullptr)
                newCallback->audioDeviceAboutToStart (this);

            const ScopedLock sl (callbackLock);
            callback = newCallback;
        }
    }

    void iOSAudioIODevice::stop()
    {
        if (isRunning)
        {
            AudioIODeviceCallback* lastCallback;
            {
                const ScopedLock sl (callbackLock);
                lastCallback = callback;
                callback = nullptr;
            }

            if (lastCallback != nullptr)
                lastCallback->audioDeviceStopped();
        }
    }

    bool iOSAudioIODevice::isPlaying()            { return isRunning && callback != nullptr; }
    String iOSAudioIODevice::getLastError()       { return lastError; }

    bool iOSAudioIODevice::setAudioPreprocessingEnabled (bool enable)
    {
        // NEW
        return [[AVAudioSession sharedInstance] setMode: enable ? AVAudioSessionModeDefault : AVAudioSessionModeMeasurement
                                 error:  &err];
        
        // OLD
        //return setSessionUInt32Property (kAudioSessionProperty_Mode, enable ? kAudioSessionMode_Default : kAudioSessionMode_Measurement);
    }
        
    // NEW
    void iOSAudioIODevice::routingChanged (const NSNotification* notification)
    {
        if (! isRunning)
            return;
        
        if (notification != nullptr)
        {
            //        CFDictionaryRef routeChangeDictionary = (CFDictionaryRef) propertyValue;
            //        CFNumberRef routeChangeReasonRef = (CFNumberRef) CFDictionaryGetValue (routeChangeDictionary,
            //                                                                                CFSTR (kAudioSession_AudioRouteChangeKey_Reason));
            //
            //        SInt32 routeChangeReason;
            //        CFNumberGetValue (routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason);
            //
            //        if (routeChangeReason == kAudioSessionRouteChangeReason_OldDeviceUnavailable)
            //        {
            //            const ScopedLock sl (callbackLock);
            //
            //            if (callback != nullptr)
            //                callback->audioDeviceError ("Old device unavailable");
            //        }
            
            //again, not doing anything here, but if you wanted to:
            
            NSDictionary *routeChangeDict = notification.userInfo;
            
            NSInteger routeChangeReason = [[routeChangeDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
            
            switch (routeChangeReason) {
                case AVAudioSessionRouteChangeReasonUnknown:
                    NSLog(@"routeChangeReason : AVAudioSessionRouteChangeReasonUnknown");
                    break;
                    
                case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
                    // a headset was added or removed
                    NSLog(@"routeChangeReason : AVAudioSessionRouteChangeReasonNewDeviceAvailable");
                    break;
                    
                case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
                    // a headset was added or removed
                    NSLog(@"routeChangeReason : AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
                    break;
                    
                case AVAudioSessionRouteChangeReasonCategoryChange:
                    // called at start - also when other audio wants to play
                    NSLog(@"routeChangeReason : AVAudioSessionRouteChangeReasonCategoryChange");//AVAudioSessionRouteChangeReasonCategoryChange
                    break;
                    
                case AVAudioSessionRouteChangeReasonOverride:
                    NSLog(@"routeChangeReason : AVAudioSessionRouteChangeReasonOverride");
                    break;
                    
                case AVAudioSessionRouteChangeReasonWakeFromSleep:
                    NSLog(@"routeChangeReason : AVAudioSessionRouteChangeReasonWakeFromSleep");
                    break;
                    
                case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
                    NSLog(@"routeChangeReason : AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory");
                    break;
                    
                default:
                    break;
            }
            
            if (routeChangeReason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable)
            {
                const ScopedLock sl (callbackLock);
                
                if (callback != nullptr)
                    callback->audioDeviceError ("Old device unavailable");
            }
        }
        
        updateDeviceInfo();
        
        if (audioUnit == 0)
            createAudioUnit();
        else
            setAudioUnitCallback(true);
        
        // OLD
        //AudioSessionSetActive (true);
        
        // NEW
        [[AVAudioSession sharedInstance] setActive: YES error:&err];
        
        if (audioUnit != 0)
        {
            AudioOutputUnitStart(audioUnit);
            
            UInt32 formatSize = sizeof (format);
            AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, &formatSize);
            
            updateCurrentBufferSize();
            AudioOutputUnitStart (audioUnit);
        }
    }
        
    void iOSAudioIODevice::closeAudioUnit()
    {
        stopAudioUnit();
     
        if (audioUnit != 0)
        {
            AudioComponentInstanceDispose (audioUnit);
            audioUnit = 0;
        }
        
        [[Wrapper sharedInstance] release];
    }

    void iOSAudioIODevice::stopAudioUnit()
    {
        if (audioUnit != 0)
            AudioOutputUnitStop(audioUnit);
        
        [[AVAudioSession sharedInstance] setActive: NO error:  &err];
    }
        
    void iOSAudioIODevice::startAudioUnit()
    {
        if (audioUnit != 0)
            AudioOutputUnitStart(audioUnit);
        
        [[AVAudioSession sharedInstance] setActive: YES error:  &err];
    }
        
    void iOSAudioIODevice::toggleHostPlayback()
    {
        if (audioUnit != 0)
        {
            UInt32 controlEvent = kAudioUnitRemoteControlEvent_TogglePlayPause;
            UInt32 dataSize = sizeof(controlEvent);
        
            AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_RemoteControlToHost, kAudioUnitScope_Global, 0, &controlEvent, dataSize);
        }
    }
        
    void iOSAudioIODevice::toggleHostRecord()
    {
        if (audioUnit != 0)
        {
            UInt32 controlEvent = kAudioUnitRemoteControlEvent_ToggleRecord;
            UInt32 dataSize = sizeof(controlEvent);
            
            AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_RemoteControlToHost, kAudioUnitScope_Global, 0, &controlEvent, dataSize);
        }
    }
        
    void iOSAudioIODevice::toggleHostRewind()
    {
        if (audioUnit != 0)
        {
            UInt32 controlEvent = kAudioUnitRemoteControlEvent_Rewind;
            UInt32 dataSize = sizeof(controlEvent);
            
            AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_RemoteControlToHost, kAudioUnitScope_Global, 0, &controlEvent, dataSize);
        }
    }

    void* iOSAudioIODevice::getHostIcon()
    {
        if (audioUnit != 0)
            return AudioOutputUnitGetHostIcon(audioUnit, 114);
        
        return nullptr;
    }
        
    void iOSAudioIODevice::goToHost()
    {
        if (audioUnit != 0)
        {
            CFURLRef instrumentUrl;
            UInt32 dataSize = sizeof(instrumentUrl);
            OSStatus result = AudioUnitGetProperty(audioUnit, kAudioUnitProperty_PeerURL, kAudioUnitScope_Global, 0, &instrumentUrl, &dataSize);
            
            if (result == noErr)
                [[UIApplication sharedApplication] openURL:(NSURL*)instrumentUrl];
        }
    }
        
    String iOSAudioIODevice::getHostPlayTime()
    {
        float hostPlayTime = 0.0;
        
        if (isHostConnectedViaIAA() && [UIApplication sharedApplication].applicationState !=  UIApplicationStateBackground)
        {
            HostCallbackInfo hostCallbackInfo;
            UInt32 dataSize = sizeof(HostCallbackInfo);
            OSStatus result = AudioUnitGetProperty(audioUnit, kAudioUnitProperty_HostCallbacks, kAudioUnitScope_Global, 0, &hostCallbackInfo, &dataSize);
            
            if (result == noErr)
            {
                Boolean isPlaying  = false;
                Boolean isRecording = false;
                Float64 outCurrentSampleInTimeLine = 0;
                void* hostUserData = hostCallbackInfo.hostUserData;
                
                OSStatus result =  hostCallbackInfo.transportStateProc2(hostUserData,
                                                                        &isPlaying,
                                                                        &isRecording, NULL,
                                                                        &outCurrentSampleInTimeLine,
                                                                        NULL, NULL, NULL);
                
                
                
                if (result == noErr)
                    hostPlayTime = outCurrentSampleInTimeLine;
                else
                    NSLog(@"Error occured fetching callBackInfo->transportStateProc2 : %d", (int)result);
            }
            
        }
        
        if (hostPlayTime < 0.0)
            hostPlayTime = 0.0;
        
        int totalMilliseconds = hostPlayTime / [[AVAudioSession sharedInstance] sampleRate] * 1000.0;
        int minutes = totalMilliseconds / 60000;
        int secondsLeft = totalMilliseconds % 60000;
        int seconds = secondsLeft / 1000;
        int milliseconds = secondsLeft % 1000;
        
        String minutesString = String(minutes);
        String secondsString = String(seconds);
        String millisecondsString = String(milliseconds);;
        
        if (minutes < 10)
            minutesString = "0" + minutesString;
        
        if (seconds < 10)
            secondsString = "0" + secondsString;
        
        if (milliseconds < 10)
            millisecondsString = "00" + millisecondsString;
        else if (milliseconds < 100)
            millisecondsString = "0" + millisecondsString;
        
        return minutesString + ":" + secondsString + ":" + millisecondsString;
    }
    
    float iOSAudioIODevice::getHostTempo()
    {
        float tempo = 120.0;
        
        if (isHostConnectedViaIAA())
        {
            HostCallbackInfo hostCallbackInfo;
            UInt32 dataSize = sizeof(HostCallbackInfo);
            OSStatus result = AudioUnitGetProperty(audioUnit, kAudioUnitProperty_HostCallbacks, kAudioUnitScope_Global, 0, &hostCallbackInfo, &dataSize);
            
            if (result == noErr)
            {
                Float64 outCurrentBeat = 0;
                Float64 outCurrentTempo = 0;
                void* hostUserData = hostCallbackInfo.hostUserData;
                
                OSStatus result = hostCallbackInfo.beatAndTempoProc(hostUserData, &outCurrentBeat, &outCurrentTempo);
                
                if (result == noErr)
                    tempo = outCurrentTempo;
                else
                    NSLog(@"Error occured fetching callBackInfo->beatAndTempoProc : %d", (int)result);
            }
        }
        
        return tempo;
    }
    
    void iOSAudioIODevice::getHostPlayHeadPositionInfo(double* ppqPosition, double* ppqPositionOfLastBarStart)
    {
        if (isHostConnectedViaIAA())
        {
            HostCallbackInfo hostCallbackInfo;
            UInt32 dataSize = sizeof(HostCallbackInfo);
            OSStatus result = AudioUnitGetProperty(audioUnit, kAudioUnitProperty_HostCallbacks, kAudioUnitScope_Global, 0, &hostCallbackInfo, &dataSize);
            
            if (result == noErr)
            {
                UInt32 outDeltaSampleOffsetToNextBeat = 0;
                Float32 outTimeSig_Numerator = 4.0;
                UInt32 outTimeSig_Denominator = 4;
                Float64 outCurrentMeasureDownBeat = 0.0;
                void* hostUserData = hostCallbackInfo.hostUserData;
                
                OSStatus result =  hostCallbackInfo.musicalTimeLocationProc(hostUserData,
                                                                            &outDeltaSampleOffsetToNextBeat,
                                                                            &outTimeSig_Numerator,
                                                                            &outTimeSig_Denominator,
                                                                            &outCurrentMeasureDownBeat);
                
                if (result == noErr)
                {
                    *ppqPositionOfLastBarStart = outCurrentMeasureDownBeat;
                    
                    Float64 outCurrentBeat = 0;
                    Float64 outCurrentTempo = 0;
                    void* hostUserData = hostCallbackInfo.hostUserData;
                    
                    OSStatus result = hostCallbackInfo.beatAndTempoProc(hostUserData, &outCurrentBeat, &outCurrentTempo);
                    
                    if (result == noErr)
                    {
                        *ppqPosition = outCurrentBeat;
                    }
                    else
                        NSLog(@"Error occured fetching callBackInfo->beatAndTempoProc : %d", (int)result);
                }
                else
                    NSLog(@"Error occured fetching callBackInfo->musicalTimeLocationProc : %d", (int)result);
            }
            
        }
    }
    
    bool iOSAudioIODevice::isHostConnectedViaIAA()
    {
        if (audioUnit != 0)
        {
            if ([[Wrapper sharedInstance] isHostConnectedViaAudiobus])
                return false;
            
            UInt32 connect;
            UInt32 dataSize = sizeof(UInt32);
            AudioUnitGetProperty(audioUnit, kAudioUnitProperty_IsInterAppConnected, kAudioUnitScope_Global, 0, &connect, &dataSize);
            
            return connect;
        }
        
        return false;
    }
    
    void iOSAudioIODevice::updateAudioEngineState()
    {
        [[Wrapper sharedInstance] updateAudioEngineState];
    }
        
    void iOSAudioIODevice::prepareFloatBuffers (int bufferSize)
    {
        if (numInputChannels + numOutputChannels > 0)
        {
            floatData.setSize (numInputChannels + numOutputChannels, bufferSize);
            zeromem (inputChannels, sizeof (inputChannels));
            zeromem (outputChannels, sizeof (outputChannels));
            for (int i = 0; i < numInputChannels; ++i)
                inputChannels[i] = floatData.getWritePointer (i);
            for (int i = 0; i < numOutputChannels; ++i)
                outputChannels[i] = floatData.getWritePointer (i + numInputChannels);
        }
    }

    //==================================================================================================
    OSStatus iOSAudioIODevice::process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
                      const UInt32 numFrames, AudioBufferList* data)
    {
        OSStatus err = noErr;

        if (audioInputIsAvailable && numInputChannels > 0)
            err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data);

        const ScopedLock sl (callbackLock);

        if (callback != nullptr)
        {
            if ((int) numFrames > floatData.getNumSamples())
                prepareFloatBuffers ((int) numFrames);

            if (audioInputIsAvailable && numInputChannels > 0)
            {
                short* shortData = (short*) data->mBuffers[0].mData;

                if (numInputChannels >= 2)
                {
                    for (UInt32 i = 0; i < numFrames; ++i)
                    {
                        inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f);
                        inputChannels[1][i] = *shortData++ * (1.0f / 32768.0f);
                    }
                }
                else
                {
                    if (monoInputChannelNumber > 0)
                        ++shortData;
                    for (UInt32 i = 0; i < numFrames; ++i)
                    {
                        inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f);
                        ++shortData;
                    }
                }
            }
            else
            {
                for (int i = numInputChannels; --i >= 0;)
                    zeromem (inputChannels[i], sizeof (float) * numFrames);
            }

            callback->audioDeviceIOCallback ((const float**) inputChannels, numInputChannels,
                                             outputChannels, numOutputChannels, (int) numFrames);

            short* shortData = (short*) data->mBuffers[0].mData;
            int n = 0;

            if (numOutputChannels >= 2)
            {
                for (UInt32 i = 0; i < numFrames; ++i)
                {
                    shortData [n++] = (short) (outputChannels[0][i] * 32767.0f);
                    shortData [n++] = (short) (outputChannels[1][i] * 32767.0f);
                }
            }
            else if (numOutputChannels == 1)
            {
                for (UInt32 i = 0; i < numFrames; ++i)
                {
                    const short s = (short) (outputChannels[monoOutputChannelNumber][i] * 32767.0f);
                    shortData [n++] = s;
                    shortData [n++] = s;
                }
            }
            else
            {
                zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames);
            }
        }
        else
        {
            zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames);
        }
            return err;
    }

    void iOSAudioIODevice::updateDeviceInfo()
    {
        // NEW
        sampleRate = [AVAudioSession sharedInstance].sampleRate;
        audioInputIsAvailable = [AVAudioSession sharedInstance].inputAvailable;

        // OLD
        //getSessionProperty (kAudioSessionProperty_CurrentHardwareSampleRate, sampleRate);
        //getSessionProperty (kAudioSessionProperty_AudioInputAvailable, audioInputIsAvailable);
    }
    void iOSAudioIODevice::updateCurrentBufferSize()
    {
        Float32 bufferDuration = sampleRate > 0 ? (Float32) (preferredBufferSize / sampleRate) : 0.0f;
        
        // NEW
        bufferDuration = [AVAudioSession sharedInstance].IOBufferDuration;

        // OLD
        //getSessionProperty (kAudioSessionProperty_CurrentHardwareIOBufferDuration, bufferDuration);
        actualBufferSize = (int) (sampleRate * bufferDuration + 0.5);
    }

    // OLD
    /*
    void iOSAudioIODevice::routingChanged (const void* propertyValue)
    {
        if (! isRunning)
            return;

        if (propertyValue != nullptr)
        {
            CFDictionaryRef routeChangeDictionary = (CFDictionaryRef) propertyValue;
            CFNumberRef routeChangeReasonRef = (CFNumberRef) CFDictionaryGetValue (routeChangeDictionary,
                                                                                   CFSTR (kAudioSession_AudioRouteChangeKey_Reason));
            SInt32 routeChangeReason;
            CFNumberGetValue (routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason);

            if (routeChangeReason == kAudioSessionRouteChangeReason_OldDeviceUnavailable)
            {
                const ScopedLock sl (callbackLock);
                if (callback != nullptr)
                    callback->audioDeviceError ("Old device unavailable");
            }
        }

        updateDeviceInfo();
        createAudioUnit();
        AudioSessionSetActive (true);

        if (audioUnit != 0)
        {
            UInt32 formatSize = sizeof (format);
            AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, &formatSize);
            updateCurrentBufferSize();
            AudioOutputUnitStart (audioUnit);
        }
    }
    */
        
    void iOSAudioIODevice::setAudioUnitCallback(bool isEnabled)
    {
        AURenderCallbackStruct inputProc;
        
        if (isEnabled)
        {
            inputProc.inputProc = processStatic;
            inputProc.inputProcRefCon = this;
        }
        else
        {
            inputProc.inputProc = nullptr;
            inputProc.inputProcRefCon = nullptr;
        }
        
        AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc));
    }

    void iOSAudioIODevice::interruptionListener (const UInt32 interruptionType)
    {
        if (interruptionType == kAudioSessionBeginInterruption)
        {
            isRunning = false;
            AudioOutputUnitStop (audioUnit);
            
            // NEW
            [[AVAudioSession sharedInstance] setActive: NO error:&err];
            
            // OLD
            //AudioSessionSetActive (false);
            
            const ScopedLock sl (callbackLock);

            if (callback != nullptr)
                callback->audioDeviceError ("iOS audio session interruption");
        }

        if (interruptionType == kAudioSessionEndInterruption)
        {
            isRunning = true;
            
            // NEW
            [[AVAudioSession sharedInstance] setActive: YES error:&err];

            // OLD
            //AudioSessionSetActive (true);
            
            AudioOutputUnitStart (audioUnit);
            const ScopedLock sl (callbackLock);

            if (callback != nullptr)
                callback->audioDeviceError ("iOS audio session resumed");
        }
    }

    //==================================================================================================
    void iOSAudioIODevice::resetFormat (const int numChannels) noexcept
    {
        zerostruct (format);
        format.mFormatID = kAudioFormatLinearPCM;
        format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
        format.mBitsPerChannel = 8 * sizeof (short);
        format.mChannelsPerFrame = (UInt32) numChannels;
        format.mFramesPerPacket = 1;
        format.mBytesPerFrame = format.mBytesPerPacket = (UInt32) numChannels * sizeof (short);
    }

    bool iOSAudioIODevice::createAudioUnit()
    {
        //OLD
        /*
        if (audioUnit != 0)
        {
            AudioComponentInstanceDispose (audioUnit);
            audioUnit = 0;
        }
         */
        resetFormat (2);
        
        AudioComponentDescription desc;
        desc.componentType = kAudioUnitType_Output;
        desc.componentSubType = kAudioUnitSubType_RemoteIO;
        desc.componentManufacturer = kAudioUnitManufacturer_Apple;
        desc.componentFlags = 0;
        desc.componentFlagsMask = 0;
        AudioComponent comp = AudioComponentFindNext (0, &desc);
        AudioComponentInstanceNew (comp, &audioUnit);
        
        if (audioUnit == 0)
            return false;

        if (numInputChannels > 0)
        {
            const UInt32 one = 1;
            AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one));
        }

        {
            AudioChannelLayout layout;
            layout.mChannelBitmap = 0;
            layout.mNumberChannelDescriptions = 0;
            layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
            AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input,  0, &layout, sizeof (layout));
            AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, 0, &layout, sizeof (layout));
        }

        {
            // NEW
            setAudioUnitCallback(true);
            
            // OLD
            /*
            AURenderCallbackStruct inputProc;
            inputProc.inputProc = processStatic;
            inputProc.inputProcRefCon = this;
            AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc));
             */
        }

        AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,  0, &format, sizeof (format));

        AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format));

        AudioUnitInitialize (audioUnit);
        
        // AB
        [[Wrapper sharedInstance] activateAudiobus: audioUnit];
        
        return true;
    }
    // OLD
    /*
    // If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it
    // to make it loud. Needed because by default when using an input + output, the output is kept quiet.
    void iOSAudioIODevice::fixAudioRouteIfSetToReceiver()
    {
        CFStringRef audioRoute = 0;
        if (getSessionProperty (kAudioSessionProperty_AudioRoute, audioRoute) == noErr)
        {
            NSString* route = (NSString*) audioRoute;
            //DBG ("audio route: " + nsStringToJuce (route));
            if ([route hasPrefix: @"Receiver"])
                setSessionUInt32Property (kAudioSessionProperty_OverrideAudioRoute, kAudioSessionOverrideAudioRoute_Speaker);
            CFRelease (audioRoute);
        }
    }
    */

    //==============================================================================
    class iOSAudioIODeviceType  : public AudioIODeviceType
    {
    public:
        iOSAudioIODeviceType()  : AudioIODeviceType ("iOS Audio") {}
        
        void scanForDevices() {}
        StringArray getDeviceNames (bool /*wantInputNames*/) const       { return StringArray ("iOS Audio"); }
        int getDefaultDeviceIndex (bool /*forInput*/) const              { return 0; }
        int getIndexOfDevice (AudioIODevice* d, bool /*asInput*/) const  { return d != nullptr ? 0 : -1; }
        bool hasSeparateInputsAndOutputs() const                         { return false; }
        
        AudioIODevice* createDevice (const String& outputDeviceName, const String& inputDeviceName)
        {
            if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty())
                return new iOSAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
                                             : inputDeviceName);
            
            return nullptr;
        }
        
    private:
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType)
    };

    //==============================================================================
    AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio()
    {
        return new iOSAudioIODeviceType();
    }

juce_ios_Audio.h

​
//
//  juce_ios_Audio.h
//  Syntorial
//
//  Created by Joe Hanley on 2/4/15.
//
//
#ifndef Syntorial_juce_ios_Audio_h
#define Syntorial_juce_ios_Audio_h

class iOSAudioIODevice  : public AudioIODevice
{
public:
    iOSAudioIODevice (const String& deviceName);
    ~iOSAudioIODevice();
    
    StringArray getOutputChannelNames() override;
    StringArray getInputChannelNames() override;
    
    Array<double> getAvailableSampleRates() override;
    
    Array<int> getAvailableBufferSizes() override;
    
    int getDefaultBufferSize() override;
    
    String open (const BigInteger& inputChannelsWanted,
                 const BigInteger& outputChannelsWanted,
                 double targetSampleRate, int bufferSize) override;
    
    void close() override;
    
    bool isOpen() override;
    
    int getCurrentBufferSizeSamples() override;
    double getCurrentSampleRate() override;
    int getCurrentBitDepth() override;
    
    BigInteger getActiveOutputChannels() const override;
    BigInteger getActiveInputChannels() const override;
    
    int getOutputLatencyInSamples() override;
    int getInputLatencyInSamples() override;
    
    // OLD
    //int getLatency (AudioSessionPropertyID propID);
    
    void start (AudioIODeviceCallback* newCallback) override;
    
    void stop() override;
    
    bool isPlaying() override;
    String getLastError() override;
    
    bool setAudioPreprocessingEnabled (bool enable) override;
    
    // NEW
    void routingChanged (const NSNotification* notification);
    void closeAudioUnit();
    void stopAudioUnit();
    void startAudioUnit();
    AudioUnit getAudioUnit() {return audioUnit;}
    void toggleHostPlayback();
    void toggleHostRecord();
    void toggleHostRewind();
    void* getHostIcon();
    void goToHost();
    String getHostPlayTime();
    float getHostTempo();
    void getHostPlayHeadPositionInfo(double* ppqPosition, double* ppqPositionOfLastBarStart);
    bool isHostConnectedViaIAA();
    static void updateAudioEngineState();
    
    void setAudioUnitCallback(bool isEnabled);
    
private:
    //==================================================================================================
    
    // NEW
    NSError* err;
    
    CriticalSection callbackLock;
    Float64 sampleRate;
    int numInputChannels, numOutputChannels;
    int preferredBufferSize, actualBufferSize;
    bool isRunning;
    String lastError;
    
    AudioStreamBasicDescription format;
    static AudioUnit audioUnit;
    UInt32 audioInputIsAvailable;
    AudioIODeviceCallback* callback;
    BigInteger activeOutputChans, activeInputChans;
    
    AudioSampleBuffer floatData;
    float* inputChannels[3];
    float* outputChannels[3];
    bool monoInputChannelNumber, monoOutputChannelNumber;
    
    void prepareFloatBuffers (int bufferSize);
    
    //==================================================================================================
    OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
                      const UInt32 numFrames, AudioBufferList* data);
    
    void updateDeviceInfo();
    
    void updateCurrentBufferSize();
    
    // OLD
    //void routingChanged (const void* propertyValue);
    
    //==================================================================================================
    struct AudioSessionHolder
    {
        AudioSessionHolder()
        {
            // OLD
            //AudioSessionInitialize (0, 0, interruptionListenerCallback, this);
        }
        
        static void interruptionListenerCallback (void* client, UInt32 interruptionType)
        {
            const Array <iOSAudioIODevice*>& activeDevices = static_cast <AudioSessionHolder*> (client)->activeDevices;
            
            for (int i = activeDevices.size(); --i >= 0;)
                activeDevices.getUnchecked(i)->interruptionListener (interruptionType);
        }
        
        Array <iOSAudioIODevice*> activeDevices;
    };
    
    static AudioSessionHolder& getSessionHolder()
    {
        static AudioSessionHolder audioSessionHolder;
        return audioSessionHolder;
    }
    
    void interruptionListener (const UInt32 interruptionType);
    
    //==================================================================================================
    static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
                                   UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data)
    {
        return static_cast<iOSAudioIODevice*> (client)->process (flags, time, numFrames, data);
    }
    
    // OLD
    //static void routingChangedStatic (void* client, AudioSessionPropertyID, UInt32 /*inDataSize*/, const void* propertyValue)
    //{
    //   static_cast<iOSAudioIODevice*> (client)->routingChanged (propertyValue);
    //}
    
    //==================================================================================================
    void resetFormat (const int numChannels) noexcept;
    
    bool createAudioUnit();
    
    // OLD
    /*
    // If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it
    // to make it loud. Needed because by default when using an input + output, the output is kept quiet.
    static void fixAudioRouteIfSetToReceiver();
    
    template <typename Type>
    static OSStatus getSessionProperty (AudioSessionPropertyID propID, Type& result) noexcept
    {
        UInt32 valueSize = sizeof (result);
        return AudioSessionGetProperty (propID, &valueSize, &result);
    }
    
    static bool setSessionUInt32Property  (AudioSessionPropertyID propID, UInt32  v) noexcept  { return AudioSessionSetProperty (propID, sizeof (v), &v) == kAudioSessionNoError; }
    static bool setSessionFloat32Property (AudioSessionPropertyID propID, Float32 v) noexcept  { return AudioSessionSetProperty (propID, sizeof (v), &v) == kAudioSessionNoError; }
    static bool setSessionFloat64Property (AudioSessionPropertyID propID, Float64 v) noexcept  { return AudioSessionSetProperty (propID, sizeof (v), &v) == kAudioSessionNoError; }
    */
    
    JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice)
};

#endif

Add these virtual methods to juce_AudioIODevice.h

​
    virtual void closeAudioUnit() {}
    virtual void toggleHostPlayback() {}
    virtual void toggleHostRecord() {}
    virtual void toggleHostRewind() {}
    virtual void* getHostIcon() {return nullptr;}
    virtual void goToHost() {}
    virtual String getHostPlayTime() {return "00:00:000";}
    virtual float getHostTempo() {return 120.0;}
    virtual void getHostPlayHeadPositionInfo(double* ppqPosition, double* ppqPositionOfLastBarStart) {}
    virtual bool isHostConnectedViaIAA() {return false;}

Delete the AudioUnit and Wrapper from AudioDeviceManager.cpp's deconstructor

​
AudioDeviceManager::~AudioDeviceManager()
{
    currentAudioDevice->closeAudioUnit();
    
    currentAudioDevice = nullptr;
    defaultMidiOutput = nullptr;
}

In juce_mac_CoreMIDI.cpp change the globalSystemChangeCallback method, so that it notifies the iOSAudioIODevice when a MIDI source is added or removed:

​
static void globalSystemChangeCallback (const MIDINotification* notification, void*)
{
#if JUCE_IOS
        
        if (notification->messageID == kMIDIMsgObjectAdded || notification->messageID == kMIDIMsgObjectRemoved)
            iOSAudioIODevice::updateAudioEngineState();
#endif
        
        // TODO.. Should pass-on this notification..
}

And lastly, a UIImageConverter class to take the UIImage of the host icon and convert it into a Juce image:

UIImageConvert.h

​
#ifndef __UIImageConverter__
#define __UIImageConverter__

class UIImageConverter
{
public:
    UIImageConverter() {}
    ~UIImageConverter() {}
    
    static Image convertToJuceImage(void* uiImage_);
    
    
private:
};
#endif /* defined(__UIImageConverter__) */

UIImageConverter.cpp

#import <UIKit/UIKit.h>
#include "UIImageConverter.h"

class CoreGraphicsImageSimple : public ImagePixelData
{
public:
    CoreGraphicsImageSimple (const Image::PixelFormat format, const int w, const int h, const bool clearImage)
    : ImagePixelData (format, w, h), cachedImageRef (0)
    {
        pixelStride = format == Image::RGB ? 3 : ((format == Image::ARGB) ? 4 : 1);
        lineStride = (pixelStride * jmax (1, width) + 3) & ~3;
        
        imageData.allocate ((size_t) (lineStride * jmax (1, height)), clearImage);
        
        CGColorSpaceRef colourSpace = (format == Image::SingleChannel) ? CGColorSpaceCreateDeviceGray()
        : CGColorSpaceCreateDeviceRGB();
        
        context = CGBitmapContextCreate (imageData, (size_t) width, (size_t) height, 8, (size_t) lineStride,
                                         colourSpace, getCGImageFlags (format));
        
        CGColorSpaceRelease (colourSpace);
    }
    
    ~CoreGraphicsImageSimple()
    {
        freeCachedImageRef();
        CGContextRelease (context);
    }
    
    
    
    //==============================================================================
    CGContextRef context;
    CGImageRef cachedImageRef;
    HeapBlock<uint8> imageData;
    int pixelStride, lineStride;
    
private:
    void freeCachedImageRef()
    {
        if (cachedImageRef != 0)
        {
            CGImageRelease (cachedImageRef);
            cachedImageRef = 0;
        }
    }
    
    static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
    {
#if JUCE_BIG_ENDIAN
        return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big) : kCGBitmapByteOrderDefault;
#else
        return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) : kCGBitmapByteOrderDefault;
#endif
    }
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsImageSimple)
};

Image UIImageConverter::convertToJuceImage(void *uiImage_)
{
    UIImage* uiImage = (UIImage*) uiImage_;
    CGImageRef loadedImage = uiImage.CGImage;
    
    if (loadedImage != 0)
    {
        CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo (loadedImage);
        const bool hasAlphaChan = (alphaInfo != kCGImageAlphaNone
                                   && alphaInfo != kCGImageAlphaNoneSkipLast
                                   && alphaInfo != kCGImageAlphaNoneSkipFirst);
        
        Image image (NativeImageType().create (Image::ARGB, // (CoreImage doesn't work with 24-bit images)
                                               (int) CGImageGetWidth (loadedImage),
                                               (int) CGImageGetHeight (loadedImage),
                                               hasAlphaChan));
        
        CoreGraphicsImageSimple* const cgImage = static_cast<CoreGraphicsImageSimple*> (image.getPixelData());
       
        jassert (cgImage != nullptr); // if USE_COREGRAPHICS_RENDERING is set, the CoreGraphicsImageSimple class should have been used.
        
        CGContextDrawImage (cgImage->context, CGRectMake(0, 0, image.getWidth(), image.getHeight()), loadedImage);

        CGContextFlush (cgImage->context);
        
#if ! JUCE_IOS
        CFRelease (loadedImage);
#endif
        
        // Because it's impossible to create a truly 24-bit CG image, this flag allows a user
        // to find out whether the file they just loaded the image from had an alpha channel or not.
        image.getProperties()->set ("originalImageHadAlpha", hasAlphaChan);
        return image;
    }
    
    return Image::null;
}