/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

#include "../../juce_core/system/juce_TargetPlatform.h"
#include "../utility/juce_CheckSettingMacros.h"

#if JucePlugin_Build_AUv3

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

#if JUCE_MAC
 #if (! defined MAC_OS_X_VERSION_MIN_REQUIRED) || (! defined MAC_OS_X_VERSION_10_11) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11)
  #error AUv3 needs Deployment Target OS X 10.11 or higher to compile
 #endif
#endif

#if JUCE_IOS
 #if (! defined __IPHONE_OS_VERSION_MIN_REQUIRED) || (! defined __IPHONE_9_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
  #error AUv3 needs Deployment Target iOS 9.0 or higher to compile
 #endif
#endif

#ifndef __OBJC2__
 #error AUv3 needs Objective-C 2 support (compile with 64-bit)
#endif

#include "../utility/juce_IncludeSystemHeaders.h"
#include "../utility/juce_IncludeModuleHeaders.h"
#include "../../juce_core/native/juce_osx_ObjCHelpers.h"
#include "../../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h"

#include "../../juce_audio_processors/format_types/juce_AU_Shared.h"

#define JUCE_VIEWCONTROLLER_OBJC_NAME(x) JUCE_JOIN_MACRO (x, FactoryAUv3)

#if ! JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES
 #error AUv3 wrapper requires variadic template support
#endif

#if JUCE_IOS
 #define JUCE_IOS_MAC_VIEW  UIView
#else
 #define JUCE_IOS_MAC_VIEW  NSView
#endif

#define JUCE_AUDIOUNIT_OBJC_NAME(x) JUCE_JOIN_MACRO (x, AUv3)

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullability-completeness"

// TODO: ask Timur: use SFINAE to automatically generate this for all NSObjects
template <> struct ContainerDeletePolicy<AUAudioUnitBusArray>                   { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<AUParameterTree>                       { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<NSMutableArray<AUParameterNode *> >    { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<AUParameter>                           { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<NSMutableArray<AUAudioUnitBus*> >      { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<AUAudioUnitBus>                        { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<AVAudioFormat>                         { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<AVAudioPCMBuffer>                      { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<NSMutableArray<NSNumber*> >            { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<NSNumber>                              { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<NSMutableArray<AUAudioUnitPreset*> >   { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<AUAudioUnitPreset>                     { static void destroy (NSObject* o) { [o release]; } };

//==============================================================================
struct AudioProcessorHolder  : public ReferenceCountedObject
{
    AudioProcessorHolder() {}
    AudioProcessorHolder (AudioProcessor* p) : processor (p) {}
    AudioProcessor& operator*() noexcept        { return *processor; }
    AudioProcessor* operator->() noexcept       { return processor; }
    AudioProcessor* get() noexcept              { return processor; }

    typedef ReferenceCountedObjectPtr<AudioProcessorHolder> Ptr;

private:
    ScopedPointer<AudioProcessor> processor;

    AudioProcessorHolder& operator= (AudioProcessor*) JUCE_DELETED_FUNCTION;
    AudioProcessorHolder (AudioProcessorHolder&) JUCE_DELETED_FUNCTION;
    AudioProcessorHolder& operator= (AudioProcessorHolder&) JUCE_DELETED_FUNCTION;
};

//==============================================================================
class JuceAudioUnitv3Base
{
public:
    JuceAudioUnitv3Base (const AudioComponentDescription& descr,
                         AudioComponentInstantiationOptions options,
                         NSError** error)
       #pragma clang diagnostic push
       #pragma clang diagnostic ignored "-Wobjc-method-access"
        : au ([audioUnitObjCClass.createInstance() initWithComponentDescription: descr
                                                                        options: options
                                                                          error: error
                                                                      juceClass: this])
       #pragma clang diagnostic pop
    {
    }

    JuceAudioUnitv3Base (AUAudioUnit* audioUnit) : au (audioUnit)
    {
        jassert (MessageManager::getInstance()->isThisTheMessageThread());
        initialiseJuce_GUI();
    }

    virtual ~JuceAudioUnitv3Base() {}

    //==============================================================================
    AUAudioUnit* getAudioUnit() noexcept                                   { return au; }
    virtual int getVirtualMIDICableCount()                                 { return 0; }
    virtual void reset()                                                   {}
    virtual bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* bus)
    {
        objc_super s = { getAudioUnit(), [AUAudioUnit class] };
        return (ObjCMsgSendSuper<BOOL, AVAudioFormat*,AUAudioUnitBus* > (&s, @selector (shouldChangeToFormat:forBus:), format, bus) == YES);
    }

    virtual AUAudioUnitPreset* getCurrentPreset()                          { return nullptr; }
    virtual void setCurrentPreset(AUAudioUnitPreset*)                      {}
    virtual NSTimeInterval getLatency()                                    { return 0.0; }
    virtual NSTimeInterval getTailTime()                                   { return 0.0; }
    virtual bool getCanProcessInPlace()                                    { return false; }
    virtual bool getRenderingOffline()                                     { return false; }

    //==============================================================================
    virtual AUAudioUnitBusArray* getInputBusses()          = 0;
    virtual AUAudioUnitBusArray* getOutputBusses()         = 0;
    virtual AUParameterTree* getParameterTree()            = 0;
    virtual AUInternalRenderBlock getInternalRenderBlock() = 0;
    virtual void setRenderingOffline (bool offline)        = 0;
    virtual NSArray<NSNumber*> *getChannelCapabilities()   = 0;

    //==============================================================================
    virtual NSArray<NSNumber*>* parametersForOverviewWithCount (int)
    {
        return [NSArray<NSNumber*> array];
    }

    virtual NSArray<AUAudioUnitPreset*>* getFactoryPresets()
    {
        return [NSArray<AUAudioUnitPreset*> array];
    }

    virtual NSDictionary<NSString*, id>* getFullState()
    {
        objc_super s = { getAudioUnit(), [AUAudioUnit class] };
        return ObjCMsgSendSuper<NSDictionary<NSString*, id>*> (&s, @selector (fullState));
    }

    virtual void setFullState (NSDictionary<NSString*, id>* state)
    {
        objc_super s = { getAudioUnit(), [AUAudioUnit class] };
        ObjCMsgSendSuper<void, NSDictionary<NSString*, id>*> (&s, @selector (setFullState:), state);
    }

    virtual bool allocateRenderResourcesAndReturnError (NSError **outError)
    {
        objc_super s = { getAudioUnit(), [AUAudioUnit class] };
        return (ObjCMsgSendSuper<BOOL, NSError**> (&s, @selector (allocateRenderResourcesAndReturnError:), outError) == YES);
    }

    virtual void deallocateRenderResources()
    {
        objc_super s = { getAudioUnit(), [AUAudioUnit class] };
        ObjCMsgSendSuper<void> (&s, @selector (deallocateRenderResources));
    }

private:
    struct Class  : public ObjCClass<AUAudioUnit>
    {
        Class() : ObjCClass<AUAudioUnit> ("AUAudioUnit_")
        {
            addIvar<JuceAudioUnitv3Base*> ("cppObject");

            addMethod (@selector (initWithComponentDescription:options:error:juceClass:),
                       initWithComponentDescriptionAndJuceClass, "@@:",
                       @encode (AudioComponentDescription),
                       @encode (AudioComponentInstantiationOptions), "^@@");

            addMethod (@selector (initWithComponentDescription:options:error:),
                       initWithComponentDescription, "@@:",
                       @encode (AudioComponentDescription),
                       @encode (AudioComponentInstantiationOptions), "^@");

            addMethod (@selector (dealloc),                                 dealloc,                               "v@:");
            addMethod (@selector (inputBusses),                             getInputBusses,                        "@@:");
            addMethod (@selector (outputBusses),                            getOutputBusses,                       "@@:");
            addMethod (@selector (parameterTree),                           getParameterTree,                      "@@:");
            addMethod (@selector (deallocateRenderResources),               deallocateRenderResources,             "v@:");
            addMethod (@selector (reset),                                   reset,                                 "v@:");
            addMethod (@selector (shouldChangeToFormat:forBus:),            shouldChangeToFormat,                  "B@:@@");
            addMethod (@selector (factoryPresets),                          getFactoryPresets,                     "@@:");
            addMethod (@selector (currentPreset),                           getCurrentPreset,                      "@@:");
            addMethod (@selector (setCurrentPreset:),                       setCurrentPreset,                      "v@:@");
            addMethod (@selector (fullState),                               getFullState,                          "@@:");
            addMethod (@selector (setFullState:),                           setFullState,                          "v@:@");
            addMethod (@selector (channelCapabilities),                     getChannelCapabilities,                "@@:");
            addMethod (@selector (allocateRenderResourcesAndReturnError:),  allocateRenderResourcesAndReturnError, "B@:^@");

            addMethod (@selector (parametersForOverviewWithCount:), parametersForOverviewWithCount, "@@:", @encode (NSInteger));
            addMethod (@selector (setRenderingOffline:),            setRenderingOffline,            "v@:", @encode (BOOL));


            addMethod (@selector (internalRenderBlock),   getInternalRenderBlock,   @encode (AUInternalRenderBlock), "@:");
            addMethod (@selector (virtualMIDICableCount), getVirtualMIDICableCount, @encode (NSInteger),             "@:");
            addMethod (@selector (latency),               getLatency,               @encode (NSTimeInterval),        "@:");
            addMethod (@selector (tailTime),              getTailTime,              @encode (NSTimeInterval),        "@:");
            addMethod (@selector (canProcessInPlace),     getCanProcessInPlace,     @encode (BOOL),                  "@:");
            addMethod (@selector (isRenderingOffline),    getRenderingOffline,      @encode (BOOL),                  "@:");

            registerClass();
        }

        //==============================================================================
        static JuceAudioUnitv3Base* _this (id self)                     { return getIvar<JuceAudioUnitv3Base*> (self, "cppObject"); }
        static void setThis (id self, JuceAudioUnitv3Base* cpp)         { object_setInstanceVariable           (self, "cppObject", cpp); }

        //==============================================================================
        static id initWithComponentDescription (id _self, SEL, AudioComponentDescription descr, AudioComponentInstantiationOptions options, NSError** error)
        {
            AUAudioUnit* self = _self;

            objc_super s = { self, [AUAudioUnit class] };
            self = ObjCMsgSendSuper<AUAudioUnit*, AudioComponentDescription,
                                    AudioComponentInstantiationOptions, NSError**> (&s, @selector(initWithComponentDescription:options:error:), descr, options, error);

            JuceAudioUnitv3Base* juceAU = JuceAudioUnitv3Base::create (self, descr, options, error);

            setThis (self, juceAU);
            return self;
        }

        static id initWithComponentDescriptionAndJuceClass (id _self, SEL, AudioComponentDescription descr, AudioComponentInstantiationOptions options, NSError** error, JuceAudioUnitv3Base* juceAU)
        {
            AUAudioUnit* self = _self;

            objc_super s = { self, [AUAudioUnit class] };
            self = ObjCMsgSendSuper<AUAudioUnit*, AudioComponentDescription,
                                    AudioComponentInstantiationOptions, NSError**> (&s, @selector(initWithComponentDescription:options:error:), descr, options, error);


            setThis (self, juceAU);
            return self;
        }

        static void dealloc (id self, SEL)                                                          { delete _this (self); }
        static AUAudioUnitBusArray* getInputBusses   (id self, SEL)                                 { return _this (self)->getInputBusses(); }
        static AUAudioUnitBusArray* getOutputBusses  (id self, SEL)                                 { return _this (self)->getOutputBusses(); }
        static AUParameterTree*     getParameterTree (id self, SEL)                                 { return _this (self)->getParameterTree(); }
        static AUInternalRenderBlock getInternalRenderBlock (id self, SEL)                          { return _this (self)->getInternalRenderBlock();  }
        static BOOL allocateRenderResourcesAndReturnError (id self, SEL, NSError** error)           { return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; }
        static void deallocateRenderResources (id self, SEL)                                        { _this (self)->deallocateRenderResources(); }
        static void reset (id self, SEL)                                                            { _this (self)->reset(); }
        static NSInteger getVirtualMIDICableCount (id self, SEL)                                    { return _this (self)->getVirtualMIDICableCount(); }
        static BOOL shouldChangeToFormat (id self, SEL, AVAudioFormat* format, AUAudioUnitBus* bus) { return _this (self)->shouldChangeToFormat (format, bus) ? YES : NO; }
        static NSArray<NSNumber*>* parametersForOverviewWithCount (id self, SEL, NSInteger count)   { return _this (self)->parametersForOverviewWithCount (static_cast<int> (count)); }
        static NSArray<AUAudioUnitPreset*>* getFactoryPresets (id self, SEL)                        { return _this (self)->getFactoryPresets(); }
        static AUAudioUnitPreset* getCurrentPreset (id self, SEL)                                   { return _this (self)->getCurrentPreset(); }
        static void setCurrentPreset (id self, SEL, AUAudioUnitPreset* preset)                      { return _this (self)->setCurrentPreset (preset); }
        static NSDictionary<NSString*, id>* getFullState (id self, SEL)                             { return _this (self)->getFullState(); }
        static void setFullState (id self, SEL, NSDictionary<NSString *, id>* state)                { return _this (self)->setFullState (state); }
        static NSTimeInterval getLatency (id self, SEL)                                             { return _this (self)->getLatency(); }
        static NSTimeInterval getTailTime (id self, SEL)                                            { return _this (self)->getTailTime(); }
        static BOOL getCanProcessInPlace (id self, SEL)                                             { return _this (self)->getCanProcessInPlace() ? YES : NO; }
        static BOOL getRenderingOffline (id self, SEL)                                              { return _this (self)->getRenderingOffline() ? YES : NO; }
        static void setRenderingOffline (id self, SEL, BOOL renderingOffline)                       { _this (self)->setRenderingOffline (renderingOffline); }
        static NSArray<NSNumber*>* getChannelCapabilities (id self, SEL)                            { return _this (self)->getChannelCapabilities(); }
    };

    static JuceAudioUnitv3Base* create (AUAudioUnit*, AudioComponentDescription, AudioComponentInstantiationOptions, NSError**);

    //==============================================================================
    static Class audioUnitObjCClass;

protected:
    AUAudioUnit* au;
};

//==============================================================================
JuceAudioUnitv3Base::Class JuceAudioUnitv3Base::audioUnitObjCClass;

//==============================================================================
//=========================== The actual AudioUnit =============================
//==============================================================================
class JuceAudioUnitv3  : public JuceAudioUnitv3Base,
                         public AudioProcessorListener,
                         public AudioPlayHead
{
public:
    JuceAudioUnitv3 (const AudioProcessorHolder::Ptr& processor,
                     const AudioComponentDescription& descr,
                     AudioComponentInstantiationOptions options,
                     NSError** error)
        : JuceAudioUnitv3Base (descr, options, error),
          processorHolder (processor),
          mapper (*processorHolder->get())
    {
        init();
    }

    JuceAudioUnitv3 (AUAudioUnit* audioUnit, AudioComponentDescription, AudioComponentInstantiationOptions, NSError**)
        : JuceAudioUnitv3Base (audioUnit),
          processorHolder (new AudioProcessorHolder (createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3))),
          mapper (*processorHolder->get())
    {
        init();
    }

    ~JuceAudioUnitv3()
    {
        auto& processor = getAudioProcessor();
        processor.removeListener (this);

        removeEditor (processor);

        if (editorObserverToken != nullptr)
        {
            [paramTree removeParameterObserver: editorObserverToken];
            editorObserverToken = nullptr;
        }
    }

    //==============================================================================
    void init()
    {
        AudioProcessor& processor = getAudioProcessor();
        const AUAudioFrameCount maxFrames = [getAudioUnit() maximumFramesToRender];

       #ifdef JucePlugin_PreferredChannelConfigurations
        short configs[][2] = {JucePlugin_PreferredChannelConfigurations};
        const int numConfigs = sizeof (configs) / sizeof (short[2]);

        jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0));
        processor.setPlayConfigDetails (configs[0][0], configs[0][1], kDefaultSampleRate, static_cast<int> (maxFrames));

        Array<AUChannelInfo> channelInfos;

        for (int i = 0; i < numConfigs; ++i)
        {
            AUChannelInfo channelInfo;

            channelInfo.inChannels  = configs[i][0];
            channelInfo.outChannels = configs[i][1];

            channelInfos.add (channelInfo);
        }
       #else
        Array<AUChannelInfo> channelInfos = AudioUnitHelpers::getAUChannelInfo (processor);
       #endif

        processor.setPlayHead (this);

        totalInChannels  = processor.getTotalNumInputChannels();
        totalOutChannels = processor.getTotalNumOutputChannels();

        {
            channelCapabilities = [[NSMutableArray<NSNumber*> alloc] init];


            for (int i = 0; i < channelInfos.size(); ++i)
            {
                AUChannelInfo& info = channelInfos.getReference (i);

                [channelCapabilities addObject: [NSNumber numberWithInteger: info.inChannels]];
                [channelCapabilities addObject: [NSNumber numberWithInteger: info.outChannels]];
            }
        }

        editorObserverToken = nullptr;
        internalRenderBlock = CreateObjCBlock (this, &JuceAudioUnitv3::renderCallback);

        processor.setRateAndBufferSizeDetails (kDefaultSampleRate, static_cast<int> (maxFrames));
        processor.prepareToPlay (kDefaultSampleRate, static_cast<int> (maxFrames));
        processor.addListener (this);

        addParameters();
        addPresets();

        addAudioUnitBusses (true);
        addAudioUnitBusses (false);
    }

    //==============================================================================
    AudioProcessor& getAudioProcessor() const noexcept        { return **processorHolder; }
    AUAudioUnitBusArray* getInputBusses() override            { return inputBusses;  }
    AUAudioUnitBusArray* getOutputBusses() override           { return outputBusses; }
    AUParameterTree*     getParameterTree() override          { return paramTree; }
    AUInternalRenderBlock getInternalRenderBlock() override   { return internalRenderBlock;  }
    NSArray<AUAudioUnitPreset*>* getFactoryPresets() override { return factoryPresets; }
    bool getRenderingOffline() override                       { return getAudioProcessor().isNonRealtime(); }
    void setRenderingOffline (bool offline) override          { getAudioProcessor().setNonRealtime (offline); }
    NSArray<NSNumber*>* getChannelCapabilities() override     { return channelCapabilities; }

    //==============================================================================
    AUAudioUnitPreset* getCurrentPreset() override
    {
        const int n = static_cast<int> ([factoryPresets count]);
        const int idx = static_cast<int> (getAudioProcessor().getCurrentProgram());

        if (idx < n)
            return [factoryPresets objectAtIndex:static_cast<unsigned int> (idx)];

        return nullptr;
    }

    void setCurrentPreset(AUAudioUnitPreset* preset) override
    {
        const int n = static_cast<int> ([factoryPresets count]);
        const int idx = static_cast<int> ([preset number]);

        if (isPositiveAndBelow (idx, n))
            getAudioProcessor().setCurrentProgram (idx);
    }

    //==============================================================================
    NSDictionary<NSString*, id>* getFullState() override
    {
        NSMutableDictionary<NSString*, id>* retval = [[NSMutableDictionary<NSString*, id> alloc] init];

        {
            NSDictionary<NSString*, id>* superRetval = JuceAudioUnitv3Base::getFullState();

            if (superRetval != nullptr)
                [retval addEntriesFromDictionary:superRetval];
        }

        juce::MemoryBlock state;
        getAudioProcessor().getCurrentProgramStateInformation (state);

        if (state.getSize() > 0)
        {
            NSData* ourState = [[NSData alloc] initWithBytes: state.getData()
                                                      length: state.getSize()];

            NSString* nsKey = [[NSString alloc] initWithUTF8String: JUCE_STATE_DICTIONARY_KEY];

            [retval setObject: ourState
                       forKey: nsKey];

            [nsKey    release];
            [ourState release];
        }

        return [retval autorelease];
    }

    void setFullState (NSDictionary<NSString*, id>* state) override
    {
        if (state == nullptr)
            return;

        NSMutableDictionary<NSString*, id>* modifiedState = [[NSMutableDictionary<NSString*, id> alloc] init];
        [modifiedState addEntriesFromDictionary: state];

        NSString* nsPresetKey = [[NSString alloc] initWithUTF8String: kAUPresetDataKey];
        [modifiedState removeObjectForKey: nsPresetKey];
        [nsPresetKey release];

        JuceAudioUnitv3Base::setFullState (modifiedState);

        NSString* nsKey = [[NSString alloc] initWithUTF8String: JUCE_STATE_DICTIONARY_KEY];
        NSObject* obj = [modifiedState objectForKey: nsKey];
        [nsKey release];

        if (obj != nullptr)
        {
            if ([obj isKindOfClass:[NSData class]])
            {
                NSData* data = reinterpret_cast<NSData*> (obj);
                const int numBytes = static_cast<int> ([data length]);
                const juce::uint8* const rawBytes = reinterpret_cast< const juce::uint8* const> ([data bytes]);

                if (numBytes > 0)
                    getAudioProcessor().setCurrentProgramStateInformation (rawBytes, numBytes);
            }
        }

        [modifiedState release];
    }

    //==============================================================================
    NSArray<NSNumber*>* parametersForOverviewWithCount (int count) override
    {
        const int n = static_cast<int> ([overviewParams count]);

        if (count >= n)
            return overviewParams;

        NSMutableArray<NSNumber*>* retval = [[NSMutableArray<NSNumber*>alloc] initWithArray: overviewParams];
        [retval removeObjectsInRange: NSMakeRange (static_cast<unsigned int> (count), static_cast<unsigned int> (n - count))];

        return [retval autorelease];
    }

    int getVirtualMIDICableCount() override
    {
       #if JucePlugin_WantsMidiInput
        return 1;
       #else
        return 0;
       #endif
    }

    //==============================================================================
    bool allocateRenderResourcesAndReturnError (NSError **outError) override
    {
        AudioProcessor& processor = getAudioProcessor();
        const AUAudioFrameCount maxFrames = [getAudioUnit() maximumFramesToRender];

        if (! JuceAudioUnitv3Base::allocateRenderResourcesAndReturnError (outError))
            return false;

        if (outError != nullptr)
            *outError = nullptr;

        AudioProcessor::BusesLayout layouts;
        for (int dir = 0; dir < 2; ++dir)
        {
            const bool isInput = (dir == 0);
            const int n = AudioUnitHelpers::getBusCount (&processor, isInput);
            Array<AudioChannelSet>& channelSets = (isInput ? layouts.inputBuses : layouts.outputBuses);

            AUAudioUnitBusArray* auBuses = (isInput ? [getAudioUnit() inputBusses] : [getAudioUnit() outputBusses]);
            jassert ([auBuses count] == static_cast<NSUInteger> (n));

            for (int busIdx = 0; busIdx < n; ++busIdx)
            {
                AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx);
                AVAudioFormat* format = [[auBuses objectAtIndexedSubscript:static_cast<NSUInteger> (busIdx)] format];

                AudioChannelSet newLayout;
                const AVAudioChannelLayout* layout    = [format channelLayout];
                const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0);

                if (layoutTag != 0)
                    newLayout = AudioUnitHelpers::CALayoutTagToChannelSet (layoutTag);
                else
                    newLayout = bus->supportedLayoutWithChannels (static_cast<int> ([format channelCount]));

                if (newLayout.isDisabled())
                    return false;

                channelSets.add (newLayout);
            }
        }

       #ifdef JucePlugin_PreferredChannelConfigurations
        short configs[][2] = {JucePlugin_PreferredChannelConfigurations};

        if (! AudioProcessor::containsLayout (layouts, configs))
        {
            if (outError != nullptr)
                *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FormatNotSupported userInfo:nullptr];

            return false;
        }
       #endif

        if (! AudioUnitHelpers::setBusesLayout (&getAudioProcessor(), layouts))
        {
            if (outError != nullptr)
                *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FormatNotSupported userInfo:nullptr];

            return false;
        }

        totalInChannels  = processor.getTotalNumInputChannels();
        totalOutChannels = processor.getTotalNumOutputChannels();

        allocateBusBuffer (true);
        allocateBusBuffer (false);

        mapper.alloc();

        audioBuffer.prepare (totalInChannels, totalOutChannels, static_cast<int> (maxFrames));

        double sampleRate = (jmax (AudioUnitHelpers::getBusCount (&processor, true), AudioUnitHelpers::getBusCount (&processor, false)) > 0 ?
                             [[[([inputBusses count] > 0 ? inputBusses : outputBusses) objectAtIndexedSubscript: 0] format] sampleRate] : 44100.0);

        processor.setRateAndBufferSizeDetails (sampleRate, static_cast<int> (maxFrames));
        processor.prepareToPlay (sampleRate, static_cast<int> (maxFrames));

        zeromem (&lastAudioHead, sizeof (lastAudioHead));
        hostMusicalContextCallback = [getAudioUnit() musicalContextBlock];
        hostTransportStateCallback = [getAudioUnit() transportStateBlock];

        reset();

        return true;
    }

    void deallocateRenderResources() override
    {
        hostMusicalContextCallback = nullptr;
        hostTransportStateCallback = nullptr;

        getAudioProcessor().releaseResources();
        audioBuffer.release();

        inBusBuffers. clear();
        outBusBuffers.clear();

        mapper.release();

        JuceAudioUnitv3Base::deallocateRenderResources();
    }

    void reset() override
    {
        midiMessages.clear();
        lastTimeStamp.mSampleTime = std::numeric_limits<Float64>::max();
    }

    //==============================================================================
    bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* auBus) override
    {
        const bool isInput = ([auBus busType] == AUAudioUnitBusTypeInput);
        const int busIdx = static_cast<int> ([auBus index]);
        const int newNumChannels = static_cast<int> ([format channelCount]);

        AudioProcessor& processor = getAudioProcessor();

        if (AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx))
        {
          #ifdef JucePlugin_PreferredChannelConfigurations
            ignoreUnused (bus);

            short configs[][2] = {JucePlugin_PreferredChannelConfigurations};

            if (! AudioUnitHelpers::isLayoutSupported (processor, isInput, busIdx, newNumChannels, configs))
                return false;
          #else
            const AVAudioChannelLayout* layout    = [format channelLayout];
            const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0);

            if (layoutTag != 0)
            {
                AudioChannelSet newLayout = AudioUnitHelpers::CALayoutTagToChannelSet (layoutTag);

                if (newLayout.size() != newNumChannels)
                    return false;

                if (! bus->isLayoutSupported (newLayout))
                    return false;
            }
            else
            {
                if (! bus->isNumberOfChannelsSupported (newNumChannels))
                    return false;
            }
           #endif

            return true;
        }

        return false;
    }

    //==============================================================================
    void audioProcessorChanged (AudioProcessor* processor) override
    {
        ignoreUnused (processor);

        [au willChangeValueForKey: @"allParameterValues"];
        [au didChangeValueForKey: @"allParameterValues"];
    }

    void audioProcessorParameterChanged (AudioProcessor*, int idx, float newValue) override
    {
        if (isPositiveAndBelow (idx, getAudioProcessor().getNumParameters()))
        {
            if (AUParameter* param = [paramTree parameterWithAddress: getAUParameterAddressForIndex (idx)])
            {
                if (editorObserverToken != nullptr)
                    [param setValue: newValue  originator: editorObserverToken];
                else
                    [param setValue: newValue];
            }
        }
    }

    //==============================================================================
    NSTimeInterval getLatency() override
    {
        auto& p = getAudioProcessor();
        return p.getLatencySamples() / p.getSampleRate();
    }

    NSTimeInterval getTailTime() override       { return getAudioProcessor().getTailLengthSeconds(); }

    //==============================================================================
    bool getCurrentPosition (CurrentPositionInfo& info) override
    {
        bool musicContextCallSucceeded = false;
        bool transportStateCallSucceeded = false;

        info = lastAudioHead;
        info.timeInSamples = (int64) (lastTimeStamp.mSampleTime + 0.5);
        info.timeInSeconds = info.timeInSamples / getAudioProcessor().getSampleRate();

        switch (lastTimeStamp.mSMPTETime.mType)
        {
            case kSMPTETimeType24:          info.frameRate = AudioPlayHead::fps24; break;
            case kSMPTETimeType25:          info.frameRate = AudioPlayHead::fps25; break;
            case kSMPTETimeType2997:        info.frameRate = AudioPlayHead::fps2997; break;
            case kSMPTETimeType2997Drop:    info.frameRate = AudioPlayHead::fps2997drop; break;
            case kSMPTETimeType30Drop:      info.frameRate = AudioPlayHead::fps30drop; break;
            case kSMPTETimeType30:          info.frameRate = AudioPlayHead::fps30; break;
            case kSMPTETimeType60Drop:      info.frameRate = AudioPlayHead::fps60drop; break;
            case kSMPTETimeType60:          info.frameRate = AudioPlayHead::fps60; break;
            default:                        info.frameRate = AudioPlayHead::fpsUnknown; break;
        }

        double num;
        NSInteger den;
        NSInteger outDeltaSampleOffsetToNextBeat;
        double outCurrentMeasureDownBeat, bpm;
        double ppqPosition;

        if (hostMusicalContextCallback != nullptr)
        {
            AUHostMusicalContextBlock musicalContextCallback = hostMusicalContextCallback;

            if (musicalContextCallback (&bpm, &num, &den, &ppqPosition, &outDeltaSampleOffsetToNextBeat, &outCurrentMeasureDownBeat))
            {
                musicContextCallSucceeded = true;

                info.timeSigNumerator   = (int) num;
                info.timeSigDenominator = (int) den;
                info.ppqPositionOfLastBarStart = outCurrentMeasureDownBeat;
                info.bpm = bpm;
                info.ppqPosition = ppqPosition;
                info.ppqPositionOfLastBarStart = outCurrentMeasureDownBeat;
            }
        }

        double outCurrentSampleInTimeLine, outCycleStartBeat = 0, outCycleEndBeat = 0;
        AUHostTransportStateFlags flags;

        if (hostTransportStateCallback != nullptr)
        {
            AUHostTransportStateBlock transportStateCallback = hostTransportStateCallback;

            if (transportStateCallback (&flags, &outCurrentSampleInTimeLine, &outCycleStartBeat, &outCycleEndBeat))
            {
                transportStateCallSucceeded = true;

                info.timeInSamples  = (int64) (outCurrentSampleInTimeLine + 0.5);
                info.timeInSeconds  = info.timeInSamples / getAudioProcessor().getSampleRate();
                info.isPlaying      = ((flags & AUHostTransportStateMoving) != 0);
                info.isLooping      = ((flags & AUHostTransportStateCycling) != 0);
                info.isRecording    = ((flags & AUHostTransportStateRecording) != 0);
                info.ppqLoopStart   = outCycleStartBeat;
                info.ppqLoopEnd     = outCycleEndBeat;
            }
        }

        if (musicContextCallSucceeded && transportStateCallSucceeded)
            lastAudioHead = info;

        return true;
    }

    static void removeEditor (AudioProcessor& processor)
    {
        ScopedLock editorLock (processor.getCallbackLock());

        if (AudioProcessorEditor* editor = processor.getActiveEditor())
        {
            processor.editorBeingDeleted (editor);
            delete editor;
        }
    }

private:
    //==============================================================================
    struct BusBuffer
    {
        BusBuffer (AUAudioUnitBus* bus, int maxFramesPerBuffer)
            : auBus (bus), bufferList (nullptr),
              maxFrames (maxFramesPerBuffer),
              numberOfChannels (static_cast<int> ([[auBus format] channelCount])),
              isInterleaved ([[auBus format] isInterleaved])
        {
            alloc();
        }

        //==============================================================================
        void alloc()
        {
            const int numBuffers = isInterleaved ? 1 : numberOfChannels;
            int bytes = static_cast<int> (sizeof (AudioBufferList))
                          + ((numBuffers - 1) * static_cast<int> (sizeof (::AudioBuffer)));
            jassert (bytes > 0);

            bufferListStorage.calloc (static_cast<size_t> (bytes));
            bufferList = reinterpret_cast<AudioBufferList*> (bufferListStorage.getData());

            const int bufferChannels = isInterleaved ? numberOfChannels : 1;
            scratchBuffer.setSize (numBuffers, bufferChannels * maxFrames);
        }

        void dealloc()
        {
            bufferList = nullptr;
            bufferListStorage.free();
            scratchBuffer.setSize (0, 0);
        }

        //==============================================================================
        int numChannels() const noexcept                { return numberOfChannels; }
        bool interleaved() const noexcept               { return isInterleaved; }
        AudioBufferList* get() const noexcept           { return bufferList; }

        //==============================================================================
        void prepare (UInt32 nFrames, const AudioBufferList* other = nullptr) noexcept
        {
            const int numBuffers = isInterleaved ? 1 : numberOfChannels;
            const bool isCompatible = isCompatibleWith (other);

            bufferList->mNumberBuffers = static_cast<UInt32> (numBuffers);

            for (int i = 0; i < numBuffers; ++i)
            {
                const UInt32 bufferChannels = static_cast<UInt32> (isInterleaved ? numberOfChannels : 1);
                bufferList->mBuffers[i].mNumberChannels = bufferChannels;
                bufferList->mBuffers[i].mData = (isCompatible ? other->mBuffers[i].mData
                                                              : scratchBuffer.getWritePointer (i));
                bufferList->mBuffers[i].mDataByteSize = nFrames * bufferChannels * sizeof (float);
            }
        }

        //==============================================================================
        bool isCompatibleWith (const AudioBufferList* other) const noexcept
        {
            if (other == nullptr)
                return false;

            if (other->mNumberBuffers > 0)
            {
                const bool otherInterleaved = AudioUnitHelpers::isAudioBufferInterleaved (*other);
                const int otherChannels = static_cast<int> (otherInterleaved ? other->mBuffers[0].mNumberChannels
                                                                             : other->mNumberBuffers);

                return otherInterleaved == isInterleaved
                    && numberOfChannels == otherChannels;
            }

            return numberOfChannels == 0;
        }

    private:
        AUAudioUnitBus* auBus;
        HeapBlock<char> bufferListStorage;
        AudioBufferList* bufferList;
        int maxFrames, numberOfChannels;
        bool isInterleaved;
        AudioSampleBuffer scratchBuffer;
    };

    //==============================================================================
    void addAudioUnitBusses (bool isInput)
    {
        ScopedPointer<NSMutableArray<AUAudioUnitBus*> > array = [[NSMutableArray<AUAudioUnitBus*> alloc] init];
        AudioProcessor& processor = getAudioProcessor();
        const int n = AudioUnitHelpers::getBusCount (&processor, isInput);

        for (int i = 0; i < n; ++i)
        {
            ScopedPointer<AUAudioUnitBus> audioUnitBus;

            {
                ScopedPointer<AVAudioFormat> defaultFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate: kDefaultSampleRate
                                                                                                            channels: static_cast<AVAudioChannelCount> (processor.getChannelCountOfBus (isInput, i))];

                audioUnitBus = [[AUAudioUnitBus alloc] initWithFormat: defaultFormat
                                                                error: nullptr];
            }

            [array addObject: audioUnitBus];
        }

        (isInput ? inputBusses : outputBusses) = [[AUAudioUnitBusArray alloc] initWithAudioUnit: au
                                                                                        busType: (isInput ? AUAudioUnitBusTypeInput : AUAudioUnitBusTypeOutput)
                                                                                         busses: array];
    }

    void addParameters()
    {
        ScopedPointer<NSMutableArray<AUParameterNode*> > params = [[NSMutableArray<AUParameterNode*> alloc] init];

        paramObserver = CreateObjCBlock (this, &JuceAudioUnitv3::valueChangedFromHost);
        paramProvider = CreateObjCBlock (this, &JuceAudioUnitv3::getValue);

        overviewParams = [[NSMutableArray<NSNumber*> alloc] init];

        auto& processor = getAudioProcessor();
        const int n = processor.getNumParameters();

       #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS
        // check if all parameters are managed?
        usingManagedParameter = (processor.getParameters().size() == processor.getNumParameters());
       #endif

        for (int idx = 0; idx < n; ++idx)
        {
            const String identifier (idx);
            const String name = processor.getParameterName (idx);

            AudioUnitParameterUnit unit = kAudioUnitParameterUnit_Generic;
            AudioUnitParameterOptions flags = (UInt32) (kAudioUnitParameterFlag_IsWritable
                                                      | kAudioUnitParameterFlag_IsReadable
                                                      | kAudioUnitParameterFlag_HasCFNameString
                                                      | kAudioUnitParameterFlag_ValuesHaveStrings);

           #if JucePlugin_AUHighResolutionParameters
            flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution;
           #endif

            // set whether the param is automatable (unnamed parameters aren't allowed to be automated)
            if (name.isEmpty() || ! processor.isParameterAutomatable (idx))
                flags |= kAudioUnitParameterFlag_NonRealTime;

            if (processor.isMetaParameter (idx))
                flags |= kAudioUnitParameterFlag_IsGlobalMeta;

            // is this a meter?
            if (((processor.getParameterCategory (idx) & 0xffff0000) >> 16) == 2)
            {
                flags &= ~kAudioUnitParameterFlag_IsWritable;
                flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic;
                unit = kAudioUnitParameterUnit_LinearGain;
            }

           #if JUCE_FORCE_USE_LEGACY_PARAM_IDS
            AUParameterAddress address = static_cast<AUParameterAddress> (idx);
           #else
            AUParameterAddress address = generateAUParameterAddressForIndex (idx);

            // Consider yourself very unlucky if you hit this assertion. The hash code of your
            // parameter ids are not unique.
            jassert (! paramMap.contains (static_cast<int64> (address)));

            paramAddresses.add (address);
            paramMap.set (static_cast<int64> (address), idx);
          #endif

            // create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h

            ScopedPointer<AUParameter> param = [[AUParameterTree createParameterWithIdentifier: juceStringToNS (identifier)
                                                                                          name: juceStringToNS (name)
                                                                                       address: address
                                                                                           min: 0.0f
                                                                                           max: 1.0f
                                                                                          unit: unit
                                                                                      unitName: nullptr
                                                                                         flags: flags
                                                                                  valueStrings: nullptr
                                                                           dependentParameters: nullptr] retain];

            [params addObject: param];
            [overviewParams addObject: [NSNumber numberWithUnsignedLongLong:address]];
        }

        // create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h
        paramTree = [[AUParameterTree createTreeWithChildren: params] retain];

        [paramTree setImplementorValueObserver: paramObserver];
        [paramTree setImplementorValueProvider: paramProvider];

        if (processor.hasEditor())
        {
            editorParamObserver = CreateObjCBlock (this, &JuceAudioUnitv3::valueChangedForObserver);
            editorObserverToken = [paramTree tokenByAddingParameterObserver: editorParamObserver];
        }
    }

    void addPresets()
    {
        factoryPresets = [[NSMutableArray<AUAudioUnitPreset*> alloc] init];

        const int n = getAudioProcessor().getNumPrograms();

        for (int idx = 0; idx < n; ++idx)
        {
            String name = getAudioProcessor().getProgramName (idx);

            ScopedPointer<AUAudioUnitPreset> preset = [[AUAudioUnitPreset alloc] init];
            [preset setName: juceStringToNS (name)];
            [preset setNumber: static_cast<NSInteger> (idx)];

            [factoryPresets addObject: preset];
        }
    }

    //==============================================================================
    void allocateBusBuffer (bool isInput)
    {
        OwnedArray<BusBuffer>& busBuffers = isInput ? inBusBuffers : outBusBuffers;
        busBuffers.clear();

        const int n = AudioUnitHelpers::getBusCount (&getAudioProcessor(), isInput);
        const AUAudioFrameCount maxFrames = [getAudioUnit() maximumFramesToRender];

        for (int busIdx = 0; busIdx < n; ++busIdx)
            busBuffers.add (new BusBuffer ([(isInput ? inputBusses : outputBusses) objectAtIndexedSubscript: static_cast<unsigned int> (busIdx)],
                                           static_cast<int> (maxFrames)));
    }

    void processEvents (const AURenderEvent *__nullable realtimeEventListHead, int numParams, AUEventSampleTime startTime)
    {
        for (const AURenderEvent* event = realtimeEventListHead; event != nullptr; event = event->head.next)
        {
            switch (event->head.eventType)
            {
                case AURenderEventMIDI:
                {
                    const AUMIDIEvent& midiEvent = event->MIDI;
                    midiMessages.addEvent (midiEvent.data, midiEvent.length, static_cast<int> (midiEvent.eventSampleTime - startTime));
                }
                break;

                case AURenderEventParameter:
                case AURenderEventParameterRamp:
                {
                    const AUParameterEvent& paramEvent = event->parameter;
                    const int idx = getJuceParameterIndexForAUAddress (paramEvent.parameterAddress);

                    if (isPositiveAndBelow (idx, numParams))
                        getAudioProcessor().setParameter (idx, paramEvent.value);
                }
                break;

                default:
                    break;
            }
        }
    }

    AUAudioUnitStatus renderCallback (AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timestamp, AUAudioFrameCount frameCount,
                                      NSInteger outputBusNumber, AudioBufferList* outputData, const AURenderEvent *__nullable realtimeEventListHead,
                                      AURenderPullInputBlock __nullable pullInputBlock)
    {
        auto& processor = getAudioProcessor();
        jassert (static_cast<int> (frameCount) <= getAudioProcessor().getBlockSize());

        // process params
        const int numParams = processor.getNumParameters();
        processEvents (realtimeEventListHead, numParams, static_cast<AUEventSampleTime> (timestamp->mSampleTime));

        if (lastTimeStamp.mSampleTime != timestamp->mSampleTime)
        {
            lastTimeStamp = *timestamp;

            const int numInputBuses  = inBusBuffers. size();
            const int numOutputBuses = outBusBuffers.size();

            // prepare buffers
            {
                for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx)
                {
                     BusBuffer& busBuffer = *outBusBuffers[busIdx];
                     const bool canUseDirectOutput =
                         (busIdx == outputBusNumber && outputData != nullptr && outputData->mNumberBuffers > 0);

                    busBuffer.prepare (frameCount, canUseDirectOutput ? outputData : nullptr);
                }

                for (int busIdx = 0; busIdx < numInputBuses; ++busIdx)
                {
                    BusBuffer& busBuffer = *inBusBuffers[busIdx];
                    busBuffer.prepare (frameCount, busIdx < numOutputBuses ? outBusBuffers[busIdx]->get() : nullptr);
                }

                audioBuffer.reset();
            }

            // pull inputs
            {
                for (int busIdx = 0; busIdx < numInputBuses; ++busIdx)
                {
                    BusBuffer& busBuffer = *inBusBuffers[busIdx];
                    AudioBufferList* buffer = busBuffer.get();

                    if (pullInputBlock == nullptr || pullInputBlock (actionFlags, timestamp, frameCount, busIdx, buffer) != noErr)
                        AudioUnitHelpers::clearAudioBuffer (*buffer);

                    if (actionFlags != nullptr && (*actionFlags & kAudioUnitRenderAction_OutputIsSilence) != 0)
                        AudioUnitHelpers::clearAudioBuffer (*buffer);
                }
            }

            // set buffer pointer to minimize copying
            {
                int chIdx = 0;

                for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx)
                {
                    BusBuffer& busBuffer = *outBusBuffers[busIdx];
                    AudioBufferList* buffer = busBuffer.get();

                    const bool interleaved = busBuffer.interleaved();
                    const int numChannels = busBuffer.numChannels();

                    const int* outLayoutMap = mapper.get (false, busIdx);

                    for (int ch = 0; ch < numChannels; ++ch)
                        audioBuffer.setBuffer (chIdx++, interleaved ? nullptr : static_cast<float*> (buffer->mBuffers[outLayoutMap[ch]].mData));
                }

                // use input pointers on remaining channels

                for (int busIdx = 0; chIdx < totalInChannels;)
                {
                    const int channelOffset = processor.getOffsetInBusBufferForAbsoluteChannelIndex (true, chIdx, busIdx);

                    BusBuffer& busBuffer = *inBusBuffers[busIdx];
                    AudioBufferList* buffer = busBuffer.get();

                    const int* inLayoutMap = mapper.get (true, busIdx);
                    audioBuffer.setBuffer (chIdx++,  busBuffer.interleaved() ? nullptr : static_cast<float*> (buffer->mBuffers[inLayoutMap[channelOffset]].mData));
                }
            }

            // copy input
            {
                for (int busIdx = 0; busIdx < numInputBuses; ++busIdx)
                    audioBuffer.push (*inBusBuffers[busIdx]->get(), mapper.get (true, busIdx));

                // clear remaining channels
                for (int i = totalInChannels; i < totalOutChannels; ++i)
                    zeromem (audioBuffer.push(), sizeof (float) * frameCount);
            }

            // process audio
            processBlock (audioBuffer.getBuffer (frameCount), midiMessages);
            midiMessages.clear();
        }

        // copy back
        audioBuffer.pop (*outBusBuffers[(int) outputBusNumber]->get(),
                         mapper.get (false, (int) outputBusNumber));

        return noErr;
    }

    void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiBuffer) noexcept
    {
        auto& processor = getAudioProcessor();
        const ScopedLock sl (processor.getCallbackLock());

        if (processor.isSuspended())
            buffer.clear();
        else if ([au shouldBypassEffect])
            processor.processBlockBypassed (buffer, midiBuffer);
        else
            processor.processBlock (buffer, midiBuffer);
    }

    //==============================================================================
    void valueChangedFromHost (AUParameter* param, AUValue value)
    {
        if (param != nullptr)
        {
            const int idx = getJuceParameterIndexForAUAddress ([param address]);
            auto& processor = getAudioProcessor();

            if (isPositiveAndBelow (idx, processor.getNumParameters()))
                processor.setParameter (idx, value);
        }
    }

    AUValue getValue (AUParameter* param)
    {
        if (param != nullptr)
        {
            const int idx = getJuceParameterIndexForAUAddress ([param address]);
            auto& processor = getAudioProcessor();

            if (isPositiveAndBelow (idx, processor.getNumParameters()))
                return processor.getParameter (idx);
        }

        return 0;
    }

    void valueChangedForObserver(AUParameterAddress, AUValue)
    {
        // this will have already been handled bny valueChangedFromHost
    }

    //==============================================================================
   #if JUCE_FORCE_USE_LEGACY_PARAM_IDS
    inline AUParameterAddress getAUParameterAddressForIndex (int paramIndex) const noexcept     { return static_cast<AUParameterAddress> (paramIndex); }
    inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept    { return static_cast<int> (address); }
   #else
    AUParameterAddress generateAUParameterAddressForIndex (int paramIndex) const
    {
        auto& processor = getAudioProcessor();
        const int n = processor.getNumParameters();

        if (isPositiveAndBelow (paramIndex, n))
        {
            const String& juceParamID = processor.getParameterID (paramIndex);
            return usingManagedParameter ? static_cast<AUParameterAddress> (juceParamID.hashCode64())
                                         : static_cast<AUParameterAddress> (juceParamID.getIntValue());
        }

        return static_cast<AUParameterAddress> (-1);
    }

    inline AUParameterAddress getAUParameterAddressForIndex (int paramIndex) const noexcept
    {
        return usingManagedParameter ? paramAddresses.getReference (paramIndex)
                                     : static_cast<AUParameterAddress> (paramIndex);
    }

    inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept
    {
        return usingManagedParameter ? paramMap[static_cast<int64> (address)]
                                     : static_cast<int> (address);
    }
   #endif

    //==============================================================================
    static const double kDefaultSampleRate;

    AudioProcessorHolder::Ptr processorHolder;

    int totalInChannels, totalOutChannels;

    ScopedPointer<AUAudioUnitBusArray> inputBusses;
    ScopedPointer<AUAudioUnitBusArray> outputBusses;

    ObjCBlock<AUImplementorValueObserver> paramObserver;
    ObjCBlock<AUImplementorValueProvider> paramProvider;

   #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS
    bool usingManagedParameter;
    Array<AUParameterAddress> paramAddresses;
    HashMap<int64, int> paramMap;
   #endif

    // to avoid recursion on parameter changes, we need to add an
    // editor observer to do the parameter changes
    ObjCBlock<AUParameterObserver> editorParamObserver;
    AUParameterObserverToken editorObserverToken;

    ScopedPointer<AUParameterTree> paramTree;
    ScopedPointer<NSMutableArray<NSNumber*> > overviewParams;
    ScopedPointer<NSMutableArray<NSNumber*> > channelCapabilities;

    ScopedPointer<NSMutableArray<AUAudioUnitPreset*> > factoryPresets;

    ObjCBlock<AUInternalRenderBlock> internalRenderBlock;

    AudioUnitHelpers::CoreAudioBufferList audioBuffer;
    AudioUnitHelpers::ChannelRemapper mapper;

    OwnedArray<BusBuffer> inBusBuffers, outBusBuffers;
    MidiBuffer midiMessages;

    ObjCBlock<AUHostMusicalContextBlock> hostMusicalContextCallback;
    ObjCBlock<AUHostTransportStateBlock> hostTransportStateCallback;

    AudioTimeStamp lastTimeStamp;
    CurrentPositionInfo lastAudioHead;
};

const double JuceAudioUnitv3::kDefaultSampleRate = 44100.0;

JuceAudioUnitv3Base* JuceAudioUnitv3Base::create (AUAudioUnit* audioUnit, AudioComponentDescription descr, AudioComponentInstantiationOptions options, NSError** error)
{
    PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_AudioUnitv3;
    return new JuceAudioUnitv3 (audioUnit, descr, options, error);
}

//==============================================================================
class JuceAUViewController
{
public:

    JuceAUViewController (AUViewController<AUAudioUnitFactory>* p)
        : myself (p), processorHolder (nullptr), preferredSize (1.0f, 1.0f)
    {
        jassert (MessageManager::getInstance()->isThisTheMessageThread());

        PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_AudioUnitv3;
        initialiseJuce_GUI();
    }

    ~JuceAUViewController()
    {
        jassert (MessageManager::getInstance()->isThisTheMessageThread());

        if (processorHolder != nullptr)
            JuceAudioUnitv3::removeEditor (getAudioProcessor());
    }

    //==============================================================================
    void loadView()
    {
        jassert (MessageManager::getInstance()->isThisTheMessageThread());

        if (AudioProcessor* p = createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3))
        {
            processorHolder = new AudioProcessorHolder (p);
            auto& processor = getAudioProcessor();

            if (processor.hasEditor())
            {
                if (AudioProcessorEditor* editor = processor.createEditorIfNeeded())
                {
                    preferredSize = editor->getBounds();

                    JUCE_IOS_MAC_VIEW* view = [[[JUCE_IOS_MAC_VIEW alloc] initWithFrame: convertToCGRect (editor->getBounds())] autorelease];
                    [myself setView: view];

                    editor->setVisible (true);
                    editor->addToDesktop (0, view);
                }
            }
        }
    }

    void viewDidLayoutSubviews()
    {
        if (processorHolder != nullptr && [myself view] != nullptr)
        {
            if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor())
            {
                editor->setBounds (convertToRectInt ([[myself view] bounds]));

                if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0])
                {
                   #if JUCE_IOS
                    [peerView setNeedsDisplay];
                   #else
                    [peerView setNeedsDisplay: YES];
                   #endif
                }
            }
        }
    }

    CGSize getPreferredContentSize() const
    {
        return CGSizeMake (static_cast<float> (preferredSize.getWidth()),
                           static_cast<float> (preferredSize.getHeight()));
    }

    //==============================================================================
    AUAudioUnit* createAudioUnit (const AudioComponentDescription& descr, NSError** error)
    {
        AUAudioUnit* retval = nil;

        if (! MessageManager::getInstance()->isThisTheMessageThread())
        {
            WaitableEvent creationEvent;

            // AUv3 headers say that we may block this thread and that the message thread is guaranteed
            // to be unblocked
            struct AUCreator  : public CallbackMessage
            {
                JuceAUViewController& owner;
                AudioComponentDescription pDescr;
                NSError** pError;
                AUAudioUnit*& outAU;
                WaitableEvent& e;

                AUCreator (JuceAUViewController& parent, const AudioComponentDescription& paramDescr, NSError** paramError,
                           AUAudioUnit*& outputAU, WaitableEvent& event)
                    : owner (parent), pDescr (paramDescr), pError (paramError), outAU (outputAU), e (event)
                {}

                void messageCallback() override
                {
                    outAU = owner.createAudioUnitOnMessageThread (pDescr, pError);
                    e.signal();
                }
            };

            (new AUCreator (*this, descr, error, retval, creationEvent))->post();
            creationEvent.wait (-1);
        }
        else
            retval = createAudioUnitOnMessageThread (descr, error);

        return [retval autorelease];
    }

private:
    //==============================================================================
    AUViewController<AUAudioUnitFactory>* myself;
    AudioProcessorHolder::Ptr processorHolder;
    Rectangle<int> preferredSize;

    //==============================================================================
    AUAudioUnit* createAudioUnitOnMessageThread (const AudioComponentDescription& descr, NSError** error)
    {
        jassert (MessageManager::getInstance()->isThisTheMessageThread());

        [myself view];  // this will call [view load] and ensure that the AudioProcessor has been instantiated

        if (processorHolder == nullptr)
            return nullptr;

        return (new JuceAudioUnitv3 (processorHolder, descr, 0, error))->getAudioUnit();
    }

    AudioProcessor& getAudioProcessor() const noexcept       { return **processorHolder; }
};

//==============================================================================
// necessary glue code
@interface JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix) : AUViewController<AUAudioUnitFactory>
@end

@implementation JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix)
{
    ScopedPointer<JuceAUViewController> cpp;
}

- (instancetype) initWithNibName: (nullable NSString*) nib bundle: (nullable NSBundle*) bndl { self = [super initWithNibName: nib bundle: bndl]; cpp = new JuceAUViewController (self); return self;}
- (void) loadView               { cpp->loadView(); }
- (AUAudioUnit *)createAudioUnitWithComponentDescription:(AudioComponentDescription)desc error:(NSError **)error { return cpp->createAudioUnit (desc, error); }
- (CGSize) preferredContentSize { return cpp->getPreferredContentSize(); }
- (void)viewDidLayoutSubviews   { return cpp->viewDidLayoutSubviews(); }
@end

//==============================================================================
#if JUCE_IOS
bool JUCE_CALLTYPE juce_isInterAppAudioConnected() { return false; }
void JUCE_CALLTYPE juce_switchToHostApplication()  {}
#if JUCE_MODULE_AVAILABLE_juce_gui_basics
Image JUCE_CALLTYPE juce_getIAAHostIcon (int)      { return Image(); }
#endif
#endif

#pragma clang diagnostic pop
#endif
