/*
 * DISTRHO Plugin Framework (DPF)
 * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any purpose with
 * or without fee is hereby granted, provided that the above copyright notice and this
 * permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
 * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// TODO
// - g_nextBundlePath vs d_nextBundlePath cleanup
// - scale points to kAudioUnitParameterFlag_ValuesHaveStrings
// - report latency changes

#include "DistrhoPluginInternal.hpp"
#include "../DistrhoPluginUtils.hpp"

#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
# include "../extra/RingBuffer.hpp"
#endif

#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioUnitUtilities.h>

#include <map>
#include <vector>

#ifndef DISTRHO_PLUGIN_BRAND_ID
# error DISTRHO_PLUGIN_BRAND_ID undefined!
#endif

#ifndef DISTRHO_PLUGIN_UNIQUE_ID
# error DISTRHO_PLUGIN_UNIQUE_ID undefined!
#endif

START_NAMESPACE_DISTRHO

// --------------------------------------------------------------------------------------------------------------------

#ifndef __MAC_12_3
enum {
    kAudioUnitProperty_MIDIOutputBufferSizeHint = 66,
};
#endif

// --------------------------------------------------------------------------------------------------------------------

static const char* AudioUnitPropertyID2Str(const AudioUnitPropertyID prop) noexcept
{
    switch (prop)
    {
    #define PROP(s) case s: return #s;
    PROP(kAudioUnitProperty_ClassInfo)
    PROP(kAudioUnitProperty_MakeConnection)
    PROP(kAudioUnitProperty_SampleRate)
    PROP(kAudioUnitProperty_ParameterList)
    PROP(kAudioUnitProperty_ParameterInfo)
   #if !TARGET_OS_IPHONE
    PROP(kAudioUnitProperty_FastDispatch)
   #endif
    PROP(kAudioUnitProperty_CPULoad)
    PROP(kAudioUnitProperty_StreamFormat)
    PROP(kAudioUnitProperty_ElementCount)
    PROP(kAudioUnitProperty_Latency)
    PROP(kAudioUnitProperty_SupportedNumChannels)
    PROP(kAudioUnitProperty_MaximumFramesPerSlice)
    PROP(kAudioUnitProperty_ParameterValueStrings)
    PROP(kAudioUnitProperty_AudioChannelLayout)
    PROP(kAudioUnitProperty_TailTime)
    PROP(kAudioUnitProperty_BypassEffect)
    PROP(kAudioUnitProperty_LastRenderError)
    PROP(kAudioUnitProperty_SetRenderCallback)
    PROP(kAudioUnitProperty_FactoryPresets)
    PROP(kAudioUnitProperty_RenderQuality)
    PROP(kAudioUnitProperty_HostCallbacks)
    PROP(kAudioUnitProperty_InPlaceProcessing)
    PROP(kAudioUnitProperty_ElementName)
    PROP(kAudioUnitProperty_SupportedChannelLayoutTags)
    PROP(kAudioUnitProperty_PresentPreset)
    PROP(kAudioUnitProperty_DependentParameters)
    PROP(kAudioUnitProperty_InputSamplesInOutput)
    PROP(kAudioUnitProperty_ShouldAllocateBuffer)
    PROP(kAudioUnitProperty_FrequencyResponse)
    PROP(kAudioUnitProperty_ParameterHistoryInfo)
    PROP(kAudioUnitProperty_NickName)
    PROP(kAudioUnitProperty_OfflineRender)
    PROP(kAudioUnitProperty_ParameterIDName)
    PROP(kAudioUnitProperty_ParameterStringFromValue)
    PROP(kAudioUnitProperty_ParameterClumpName)
    PROP(kAudioUnitProperty_ParameterValueFromString)
    PROP(kAudioUnitProperty_PresentationLatency)
    PROP(kAudioUnitProperty_ClassInfoFromDocument)
    PROP(kAudioUnitProperty_RequestViewController)
    PROP(kAudioUnitProperty_ParametersForOverview)
    PROP(kAudioUnitProperty_SupportsMPE)
    PROP(kAudioUnitProperty_RenderContextObserver)
    PROP(kAudioUnitProperty_LastRenderSampleTime)
    PROP(kAudioUnitProperty_LoadedOutOfProcess)
   #if !TARGET_OS_IPHONE
    PROP(kAudioUnitProperty_SetExternalBuffer)
    PROP(kAudioUnitProperty_GetUIComponentList)
    PROP(kAudioUnitProperty_CocoaUI)
    PROP(kAudioUnitProperty_IconLocation)
    PROP(kAudioUnitProperty_AUHostIdentifier)
   #endif
    PROP(kAudioUnitProperty_MIDIOutputCallbackInfo)
    PROP(kAudioUnitProperty_MIDIOutputCallback)
    PROP(kAudioUnitProperty_MIDIOutputEventListCallback)
    PROP(kAudioUnitProperty_AudioUnitMIDIProtocol)
    PROP(kAudioUnitProperty_HostMIDIProtocol)
    PROP(kAudioUnitProperty_MIDIOutputBufferSizeHint)
    PROP(kMusicDeviceProperty_DualSchedulingMode)
    #undef PROP
    // DPF specific properties
    #define PROPX(s) (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0)
    #define PROP(s) case PROPX(#s): return #s;
    PROP(DPFi)
    PROP(DPFe)
    PROP(DPFp)
    PROP(DPFn)
    PROP(DPFo)
    PROP(DPFl)
    PROP(DPFs)
    PROP(DPFa)
    #undef PROP
    #undef PROPX
    }
    return "[unknown]";
}

static const char* AudioUnitScope2Str(const AudioUnitScope scope) noexcept
{
    switch (scope)
    {
    #define SCOPE(s) case s: return #s;
    SCOPE(kAudioUnitScope_Global)
    SCOPE(kAudioUnitScope_Input)
    SCOPE(kAudioUnitScope_Output)
    SCOPE(kAudioUnitScope_Group)
    SCOPE(kAudioUnitScope_Part)
    SCOPE(kAudioUnitScope_Note)
    SCOPE(kAudioUnitScope_Layer)
    SCOPE(kAudioUnitScope_LayerItem)
    #undef SCOPE
    }
    return "[unknown]";
}

static const char* AudioUnitSelector2Str(const SInt16 selector) noexcept
{
    switch (selector)
    {
    #define SEL(s) case s: return #s;
    SEL(kAudioUnitInitializeSelect)
    SEL(kAudioUnitUninitializeSelect)
    SEL(kAudioUnitGetPropertyInfoSelect)
    SEL(kAudioUnitGetPropertySelect)
    SEL(kAudioUnitSetPropertySelect)
    SEL(kAudioUnitAddPropertyListenerSelect)
    SEL(kAudioUnitRemovePropertyListenerSelect)
    SEL(kAudioUnitRemovePropertyListenerWithUserDataSelect)
    SEL(kAudioUnitAddRenderNotifySelect)
    SEL(kAudioUnitRemoveRenderNotifySelect)
    SEL(kAudioUnitGetParameterSelect)
    SEL(kAudioUnitSetParameterSelect)
    SEL(kAudioUnitScheduleParametersSelect)
    SEL(kAudioUnitRenderSelect)
    SEL(kAudioUnitResetSelect)
    SEL(kAudioUnitComplexRenderSelect)
    SEL(kAudioUnitProcessSelect)
    SEL(kAudioUnitProcessMultipleSelect)
    SEL(kMusicDeviceMIDIEventSelect)
    SEL(kMusicDeviceSysExSelect)
    SEL(kMusicDevicePrepareInstrumentSelect)
    SEL(kMusicDeviceReleaseInstrumentSelect)
    SEL(kMusicDeviceStartNoteSelect)
    SEL(kMusicDeviceStopNoteSelect)
    SEL(kMusicDeviceMIDIEventListSelect)
    SEL(kAudioOutputUnitStartSelect)
    SEL(kAudioOutputUnitStopSelect)
    #undef SEL
    }
    return "[unknown]";
}

#if 0
static OSStatus FastDispatchGetParameter(void*, AudioUnitParameterID, AudioUnitScope, AudioUnitElement, Float32*);
static OSStatus FastDispatchSetParameter(void*, AudioUnitParameterID, AudioUnitScope, AudioUnitElement, Float32, UInt32);
static OSStatus FastDispatchRender(void*, AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32, UInt32, AudioBufferList*);
#endif

// --------------------------------------------------------------------------------------------------------------------

static constexpr const uint32_t kType = d_cconst(STRINGIFY(DISTRHO_PLUGIN_AU_TYPE));
static constexpr const uint32_t kSubType = d_cconst(STRINGIFY(DISTRHO_PLUGIN_UNIQUE_ID));
static constexpr const uint32_t kManufacturer = d_cconst(STRINGIFY(DISTRHO_PLUGIN_BRAND_ID));

static constexpr const uint32_t kWantedAudioFormat = 0
                                                   | kAudioFormatFlagsNativeFloatPacked
                                                   | kAudioFormatFlagIsNonInterleaved;


// --------------------------------------------------------------------------------------------------------------------
// clang `std::max` is not constexpr compatible, we need to define our own

template<typename T>
static inline constexpr T d_max(const T a, const T b) { return a > b ? a : b; }

// --------------------------------------------------------------------------------------------------------------------

static constexpr const AUChannelInfo kChannelInfo[] = {
    { DISTRHO_PLUGIN_NUM_INPUTS, DISTRHO_PLUGIN_NUM_OUTPUTS },
   #ifdef DISTRHO_PLUGIN_EXTRA_IO
    DISTRHO_PLUGIN_EXTRA_IO
   #endif
};

#ifdef DISTRHO_PLUGIN_EXTRA_IO
#if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS == 0
#error DISTRHO_PLUGIN_EXTRA_IO defined but no IO available
#endif

static inline
bool isInputNumChannelsValid(const uint16_t numChannels)
{
    for (uint16_t i = 0; i < ARRAY_SIZE(kChannelInfo); ++i)
    {
        if (kChannelInfo[i].inChannels == numChannels)
            return true;
    }
    return false;
}

static inline
bool isOutputNumChannelsValid(const uint16_t numChannels)
{
    for (uint16_t i = 0; i < ARRAY_SIZE(kChannelInfo); ++i)
    {
        if (kChannelInfo[i].outChannels == numChannels)
            return true;
    }
    return false;
}

static inline
bool isNumChannelsComboValid(const uint16_t numInputs, const uint16_t numOutputs)
{
    for (uint16_t i = 0; i < ARRAY_SIZE(kChannelInfo); ++i)
    {
        if (kChannelInfo[i].inChannels == numInputs && kChannelInfo[i].outChannels == numOutputs)
            return true;
    }
    return false;
}
#endif

// --------------------------------------------------------------------------------------------------------------------

struct PropertyListener {
    AudioUnitPropertyID prop;
    AudioUnitPropertyListenerProc proc;
    void* userData;
};

struct RenderListener {
    AURenderCallback proc;
    void* userData;
};

typedef std::vector<PropertyListener> PropertyListeners;
typedef std::vector<RenderListener> RenderListeners;

#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
// useful definitions
static constexpr const uint32_t kMIDIPacketNonDataSize = sizeof(MIDIPacket)
                                                      #if __cplusplus >= 201103L
                                                       - sizeof(MIDIPacket::data);
                                                      #else
                                                       - sizeof(static_cast<MIDIPacket*>(0)->data);
                                                      #endif
static constexpr const uint32_t kMIDIPacketListNonDataSize = sizeof(MIDIPacketList)
                                                          #if __cplusplus >= 201103L
                                                           - sizeof(MIDIPacketList::packet);
                                                          #else
                                                           - sizeof(static_cast<MIDIPacketList*>(0)->packet);
                                                          #endif

// size of data used for midi events
static constexpr const uint32_t kMIDIPacketListMaxDataSize = kMIDIPacketNonDataSize * kMaxMidiEvents
                                                           + sizeof(Byte) * MidiEvent::kDataSize * kMaxMidiEvents;

// size of midi list + data
static constexpr const uint32_t kMIDIPacketListSize = kMIDIPacketListNonDataSize + kMIDIPacketListMaxDataSize;
#endif

// --------------------------------------------------------------------------------------------------------------------

#if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
static constexpr const writeMidiFunc writeMidiCallback = nullptr;
#endif
#if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST
static constexpr const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr;
#endif
#if ! DISTRHO_PLUGIN_WANT_STATE
static constexpr const updateStateValueFunc updateStateValueCallback = nullptr;
#endif
#if DISTRHO_PLUGIN_WANT_TIMEPOS
static constexpr const double kDefaultTicksPerBeat = 1920.0;
#endif

typedef std::map<const String, String> StringMap;

// --------------------------------------------------------------------------------------------------------------------

class PluginAU
{
public:
    PluginAU(const AudioUnit component)
        : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, updateStateValueCallback),
          fComponent(component),
          fLastRenderError(noErr),
          fPropertyListeners(),
          fRenderListeners(),
        #if DISTRHO_PLUGIN_NUM_INPUTS != 0
          fInputConnectionBus(0),
          fInputConnectionUnit(nullptr),
          fSampleRateForInput(d_nextSampleRate),
         #ifdef DISTRHO_PLUGIN_EXTRA_IO
          fNumInputs(DISTRHO_PLUGIN_NUM_INPUTS),
         #endif
        #endif
        #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
          fSampleRateForOutput(d_nextSampleRate),
         #ifdef DISTRHO_PLUGIN_EXTRA_IO
          fNumOutputs(DISTRHO_PLUGIN_NUM_OUTPUTS),
         #endif
        #endif
         #if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0
          fAudioBufferList(nullptr),
         #endif
          fUsingRenderListeners(false),
          fParameterCount(fPlugin.getParameterCount()),
          fLastParameterValues(nullptr),
          fBypassParameterIndex(UINT32_MAX),
          fResetParameterIndex(UINT32_MAX)
       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
        , fMidiEventCount(0)
       #endif
       #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
        , fMidiOutputDataOffset(0)
       #endif
       #if DISTRHO_PLUGIN_WANT_PROGRAMS
        , fCurrentProgram(-1)
        , fLastFactoryProgram(0)
        , fProgramCount(fPlugin.getProgramCount())
        , fFactoryPresetsData(nullptr)
       #endif
       #if DISTRHO_PLUGIN_WANT_STATE
        , fStateCount(fPlugin.getStateCount())
       #endif
    {
        if (fParameterCount != 0)
        {
            fLastParameterValues = new float[fParameterCount];
            std::memset(fLastParameterValues, 0, sizeof(float) * fParameterCount);

            for (uint32_t i=0; i<fParameterCount; ++i)
            {
                fLastParameterValues[i] = fPlugin.getParameterValue(i);

                switch (fPlugin.getParameterDesignation(i))
                {
                case kParameterDesignationNull:
                    break;
                case kParameterDesignationBypass:
                    fBypassParameterIndex = i;
                    break;
                case kParameterDesignationReset:
                    fResetParameterIndex = i;
                    break;
                }
            }
        }

       #if DISTRHO_PLUGIN_NUM_INPUTS != 0
        std::memset(&fInputRenderCallback, 0, sizeof(fInputRenderCallback));
        fInputRenderCallback.inputProc = nullptr;
        fInputRenderCallback.inputProcRefCon = nullptr;
       #endif

       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
        std::memset(&fMidiEvents, 0, sizeof(fMidiEvents));
       #endif

       #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
        if ((fMidiOutputPackets = static_cast<MIDIPacketList*>(std::malloc(kMIDIPacketListSize))) != nullptr)
            std::memset(fMidiOutputPackets, 0, kMIDIPacketListSize);

        std::memset(&fMidiOutput, 0, sizeof(fMidiOutput));
       #endif

       #if DISTRHO_PLUGIN_WANT_PROGRAMS
        if (fProgramCount != 0)
        {
            fFactoryPresetsData = new AUPreset[fProgramCount];
            std::memset(fFactoryPresetsData, 0, sizeof(AUPreset) * fProgramCount);

            for (uint32_t i=0; i<fProgramCount; ++i)
            {
                fFactoryPresetsData[i].presetNumber = i;

                if (const CFStringRef nameRef = CFStringCreateWithCString(nullptr,
                                                                          fPlugin.getProgramName(i),
                                                                          kCFStringEncodingUTF8))
                    fFactoryPresetsData[i].presetName = nameRef;
                else
                    fFactoryPresetsData[i].presetName = CFSTR("");
            }
        }
        else
        {
            fFactoryPresetsData = new AUPreset;
            std::memset(fFactoryPresetsData, 0, sizeof(AUPreset));

            fFactoryPresetsData->presetNumber = 0;
            fFactoryPresetsData->presetName = CFSTR("Default");
        }
       #endif

        fUserPresetData.presetNumber = -1;
        fUserPresetData.presetName = CFSTR("");

       #if DISTRHO_PLUGIN_WANT_STATE
        for (uint32_t i=0; i<fStateCount; ++i)
        {
            const String& dkey(fPlugin.getStateKey(i));
            fStateMap[dkey] = fPlugin.getStateDefaultValue(i);
        }
       #endif

       #if DISTRHO_PLUGIN_WANT_TIMEPOS
        std::memset(&fHostCallbackInfo, 0, sizeof(fHostCallbackInfo));
        fTimePosition.bbt.ticksPerBeat = kDefaultTicksPerBeat;
       #endif
    }

    ~PluginAU()
    {
        delete[] fLastParameterValues;
        CFRelease(fUserPresetData.presetName);

       #if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0
        reallocAudioBufferList(false);
       #endif

       #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
        std::free(fMidiOutputPackets);
       #endif

       #if DISTRHO_PLUGIN_WANT_PROGRAMS
        for (uint32_t i=0; i<fProgramCount; ++i)
            CFRelease(fFactoryPresetsData[i].presetName);
        delete[] fFactoryPresetsData;
       #endif
    }

    OSStatus auInitialize()
    {
       #if defined(DISTRHO_PLUGIN_EXTRA_IO) && DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
        if (! isNumChannelsComboValid(fNumInputs, fNumOutputs))
            return kAudioUnitErr_FormatNotSupported;
       #endif

       #if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0
        if (! reallocAudioBufferList(true))
            return kAudio_ParamError;
       #endif

       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
        fMidiEventCount = 0;
       #endif
       #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
        fMidiOutputDataOffset = 0;
        fMidiOutputPackets->numPackets = 0;
       #endif
       #if DISTRHO_PLUGIN_WANT_TIMEPOS
        fTimePosition.clear();
        fTimePosition.bbt.ticksPerBeat = kDefaultTicksPerBeat;
       #endif

        fPlugin.activate();
        return noErr;
    }

    OSStatus auUninitialize()
    {
        fPlugin.deactivateIfNeeded();

       #if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0
        reallocAudioBufferList(false);
       #endif
        return noErr;
    }

    OSStatus auGetPropertyInfo(const AudioUnitPropertyID inProp,
                               const AudioUnitScope inScope,
                               const AudioUnitElement inElement,
                               UInt32& outDataSize,
                               Boolean& outWritable)
    {
        switch (inProp)
        {
        case kAudioUnitProperty_ClassInfo:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(CFPropertyListRef);
            outWritable = true;
            return noErr;

        case kAudioUnitProperty_MakeConnection:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Input, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_NUM_INPUTS != 0
            outDataSize = sizeof(AudioUnitConnection);
            outWritable = true;
            return noErr;
           #else
            return kAudioUnitErr_InvalidProperty;
           #endif

        case kAudioUnitProperty_SampleRate:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_NUM_INPUTS != 0
            if (inScope == kAudioUnitScope_Input)
            {
                outDataSize = sizeof(Float64);
                outWritable = true;
                return noErr;
            }
           #endif
           #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
            if (inScope == kAudioUnitScope_Output)
            {
                outDataSize = sizeof(Float64);
                outWritable = true;
                return noErr;
            }
           #endif
            return kAudioUnitErr_InvalidScope;

        case kAudioUnitProperty_ParameterList:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = inScope == kAudioUnitScope_Global ? sizeof(AudioUnitParameterID) * fParameterCount : 0;
            outWritable = false;
            return noErr;

        case kAudioUnitProperty_ParameterInfo:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement < fParameterCount, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(AudioUnitParameterInfo);
            outWritable = false;
            return noErr;

       #if 0
        case kAudioUnitProperty_FastDispatch:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            outDataSize = sizeof(void*);
            outWritable = false;
            return noErr;
       #endif

        case kAudioUnitProperty_StreamFormat:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_NUM_INPUTS != 0
            if (inScope == kAudioUnitScope_Input)
            {
                outDataSize = sizeof(AudioStreamBasicDescription);
                outWritable = true;
                return noErr;
            }
           #endif
           #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
            if (inScope == kAudioUnitScope_Output)
            {
                outDataSize = sizeof(AudioStreamBasicDescription);
                outWritable = true;
                return noErr;
            }
           #endif
            return kAudioUnitErr_InvalidScope;

        case kAudioUnitProperty_ElementCount:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(UInt32);
            outWritable = false;
            return noErr;

        case kAudioUnitProperty_Latency:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_WANT_LATENCY
            if (inScope == kAudioUnitScope_Global)
            {
                outDataSize = sizeof(Float64);
                outWritable = false;
                return noErr;
            }
           #endif
            return kAudioUnitErr_InvalidProperty;

        case kAudioUnitProperty_SupportedNumChannels:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(kChannelInfo);
            outWritable = false;
            return noErr;

        case kAudioUnitProperty_MaximumFramesPerSlice:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(UInt32);
            outWritable = true;
            return noErr;

        case kAudioUnitProperty_BypassEffect:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            if (inScope == kAudioUnitScope_Global && fBypassParameterIndex != UINT32_MAX)
            {
                outDataSize = sizeof(UInt32);
                outWritable = true;
                return noErr;
            }
            return kAudioUnitErr_InvalidProperty;

        case kAudioUnitProperty_LastRenderError:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(OSStatus);
            outWritable = false;
            return noErr;

        case kAudioUnitProperty_SetRenderCallback:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Input, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_NUM_INPUTS != 0
            outDataSize = sizeof(AURenderCallbackStruct);
            outWritable = true;
            return noErr;
           #else
            return kAudioUnitErr_InvalidProperty;
           #endif

        case kAudioUnitProperty_FactoryPresets:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_WANT_PROGRAMS
            outDataSize = sizeof(CFArrayRef);
            outWritable = false;
            return noErr;
           #else
            return kAudioUnitErr_InvalidProperty;
           #endif

        case kAudioUnitProperty_HostCallbacks:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_WANT_TIMEPOS
            outDataSize = sizeof(HostCallbackInfo);
            outWritable = true;
            return noErr;
           #else
            return kAudioUnitErr_InvalidProperty;
           #endif

        case kAudioUnitProperty_InPlaceProcessing:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
            outDataSize = sizeof(UInt32);
            outWritable = false;
            return noErr;
           #else
            return kAudioUnitErr_InvalidProperty;
           #endif

        case kAudioUnitProperty_PresentPreset:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(AUPreset);
            outWritable = true;
            return noErr;

        case kAudioUnitProperty_SupportsMPE:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_WANT_MIDI_AS_MPE
            outDataSize = sizeof(UInt32);
            outWritable = false;
            return noErr;
           #else
            return kAudioUnitErr_InvalidProperty;
           #endif

        case kAudioUnitProperty_CocoaUI:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_HAS_UI
            outDataSize = sizeof(AudioUnitCocoaViewInfo);
            outWritable = false;
            return noErr;
           #else
            return kAudioUnitErr_InvalidProperty;
           #endif

        case kAudioUnitProperty_MIDIOutputCallbackInfo:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
            outDataSize = sizeof(CFArrayRef);
            outWritable = false;
            return noErr;
           #else
            return kAudioUnitErr_InvalidProperty;
           #endif

        case kAudioUnitProperty_MIDIOutputCallback:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
            outDataSize = sizeof(AUMIDIOutputCallbackStruct);
            outWritable = true;
            return noErr;
           #else
            return kAudioUnitErr_InvalidProperty;
           #endif

        case kAudioUnitProperty_AudioUnitMIDIProtocol:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            // FIXME implement the event list stuff
           #if 0 && (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT)
            outDataSize = sizeof(SInt32);
            outWritable = false;
            return noErr;
           #else
            return kAudioUnitErr_InvalidProperty;
           #endif

        case 'DPFi':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(uint16_t);
            outWritable = true;
            return noErr;

        case 'DPFe':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement < fParameterCount, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(uint8_t);
            outWritable = true;
            return noErr;

        case 'DPFp':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement < fParameterCount, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(float);
            outWritable = true;
            return noErr;

       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
        case 'DPFn':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(uint8_t) * 3;
            outWritable = true;
            return noErr;
       #endif

       #if DISTRHO_PLUGIN_WANT_PROGRAMS
        case 'DPFo':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(uint32_t);
            outWritable = true;
            return noErr;
       #endif

       #if DISTRHO_PLUGIN_WANT_STATE
        case 'DPFl':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(CFArrayRef);
            outWritable = true;
            return noErr;

        case 'DPFs':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement < fStateCount, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(CFStringRef);
            outWritable = true;
            return noErr;
       #endif

       #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
        case 'DPFa':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            outDataSize = sizeof(void*);
            outWritable = true;
            return noErr;
       #endif

        // unwanted properties
        case kAudioUnitProperty_CPULoad:
        case kAudioUnitProperty_RenderContextObserver:
        case kAudioUnitProperty_AudioChannelLayout:
        case kAudioUnitProperty_TailTime:
        case kAudioUnitProperty_SupportedChannelLayoutTags:
        case kMusicDeviceProperty_DualSchedulingMode:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            return kAudioUnitErr_InvalidProperty;
        }

        d_stdout("TODO GetPropertyInfo(%d:%x:%s, %d:%s, %d, ...)",
                 inProp, inProp, AudioUnitPropertyID2Str(inProp), inScope, AudioUnitScope2Str(inScope), inElement);
        return kAudioUnitErr_InvalidProperty;
    }

    OSStatus auGetProperty(const AudioUnitPropertyID inProp,
                           const AudioUnitScope inScope,
                           const AudioUnitElement inElement,
                           void* const outData)
    {
        switch (inProp)
        {
        case kAudioUnitProperty_ClassInfo:
            *static_cast<CFPropertyListRef*>(outData) = retrieveClassInfo();
            return noErr;

        case kAudioUnitProperty_SampleRate:
           #if DISTRHO_PLUGIN_NUM_INPUTS != 0
            if (inScope == kAudioUnitScope_Input)
            {
                *static_cast<Float64*>(outData) = fSampleRateForInput;
            }
            else
           #endif
           #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
            if (inScope == kAudioUnitScope_Output)
            {
                *static_cast<Float64*>(outData) = fSampleRateForOutput;
            }
            else
           #endif
            {
                return kAudioUnitErr_InvalidScope;
            }
            return noErr;

        case kAudioUnitProperty_ParameterList:
            {
                AudioUnitParameterID* const paramList = static_cast<AudioUnitParameterID*>(outData);

                for (uint32_t i=0; i<fParameterCount; ++i)
                    paramList[i] = i;
            }
            return noErr;

        case kAudioUnitProperty_ParameterInfo:
            {
                AudioUnitParameterInfo* const info = static_cast<AudioUnitParameterInfo*>(outData);
                std::memset(info, 0, sizeof(*info));

                const ParameterRanges& ranges(fPlugin.getParameterRanges(inElement));

                info->flags = kAudioUnitParameterFlag_IsHighResolution
                            | kAudioUnitParameterFlag_IsReadable
                            | kAudioUnitParameterFlag_HasCFNameString;

                if (fPlugin.getParameterDesignation(inElement) == kParameterDesignationBypass)
                {
                    info->flags |= kAudioUnitParameterFlag_IsWritable;
                    info->unit = kAudioUnitParameterUnit_Boolean;

                    d_strncpy(info->name, "Bypass", sizeof(info->name));
                    info->cfNameString = CFSTR("Bypass");
                }
                else
                {
                    const uint32_t hints = fPlugin.getParameterHints(inElement);

                    info->flags |= kAudioUnitParameterFlag_CFNameRelease;

                    if (hints & kParameterIsOutput)
                    {
                        info->flags |= kAudioUnitParameterFlag_MeterReadOnly;
                    }
                    else
                    {
                        info->flags |= kAudioUnitParameterFlag_IsWritable;

                        if ((hints & kParameterIsAutomatable) == 0x0)
                            info->flags |= kAudioUnitParameterFlag_NonRealTime;
                    }

                    if (hints & kParameterIsBoolean)
                        info->unit = kAudioUnitParameterUnit_Boolean;
                    else if (hints & kParameterIsInteger)
                        info->unit = kAudioUnitParameterUnit_Indexed;
                    else
                        info->unit = kAudioUnitParameterUnit_Generic;

                    // | kAudioUnitParameterFlag_ValuesHaveStrings;

                    const String& name(fPlugin.getParameterName(inElement));
                    d_strncpy(info->name, name, sizeof(info->name));
                    info->cfNameString = CFStringCreateWithCString(nullptr, name, kCFStringEncodingUTF8);
                }

                info->minValue = ranges.min;
                info->maxValue = ranges.max;
                info->defaultValue = ranges.def;
            }
            return noErr;

       #if 0
        case kAudioUnitProperty_FastDispatch:
            switch (inElement)
            {
            case kAudioUnitGetParameterSelect:
                *static_cast<AudioUnitGetParameterProc*>(outData) = FastDispatchGetParameter;
                return noErr;
            case kAudioUnitSetParameterSelect:
                *static_cast<AudioUnitSetParameterProc*>(outData) = FastDispatchSetParameter;
                return noErr;
            case kAudioUnitRenderSelect:
                *static_cast<AudioUnitRenderProc*>(outData) = FastDispatchRender;
                return noErr;
            }
            d_stdout("WIP FastDispatch(%d:%x:%s)", inElement, inElement, AudioUnitPropertyID2Str(inElement));
            return kAudioUnitErr_InvalidElement;
       #endif

        case kAudioUnitProperty_StreamFormat:
            {
                AudioStreamBasicDescription* const desc = static_cast<AudioStreamBasicDescription*>(outData);
                std::memset(desc, 0, sizeof(*desc));

                if (inElement != 0)
                    return kAudioUnitErr_InvalidElement;

               #if DISTRHO_PLUGIN_NUM_INPUTS != 0
                if (inScope == kAudioUnitScope_Input)
                {
                   #ifdef DISTRHO_PLUGIN_EXTRA_IO
                    desc->mChannelsPerFrame = fNumInputs;
                   #else
                    desc->mChannelsPerFrame = DISTRHO_PLUGIN_NUM_INPUTS;
                   #endif
                }
                else
               #endif
               #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
                if (inScope == kAudioUnitScope_Output)
                {
                   #ifdef DISTRHO_PLUGIN_EXTRA_IO
                    desc->mChannelsPerFrame = fNumOutputs;
                   #else
                    desc->mChannelsPerFrame = DISTRHO_PLUGIN_NUM_OUTPUTS;
                   #endif
                }
                else
               #endif
                {
                    return kAudioUnitErr_InvalidScope;
                }

                desc->mFormatID         = kAudioFormatLinearPCM;
                desc->mFormatFlags      = kWantedAudioFormat;
                desc->mSampleRate       = fPlugin.getSampleRate();
                desc->mBitsPerChannel   = 32;
                desc->mBytesPerFrame    = sizeof(float);
                desc->mBytesPerPacket   = sizeof(float);
                desc->mFramesPerPacket  = 1;
            }
            return noErr;

        case kAudioUnitProperty_ElementCount:
            switch (inScope)
            {
            case kAudioUnitScope_Global:
                *static_cast<UInt32*>(outData) = 1;
                break;
            case kAudioUnitScope_Input:
                *static_cast<UInt32*>(outData) = DISTRHO_PLUGIN_NUM_INPUTS != 0 ? 1 : 0;
                break;
            case kAudioUnitScope_Output:
                *static_cast<UInt32*>(outData) = DISTRHO_PLUGIN_NUM_OUTPUTS != 0 ? 1 : 0;
                break;
            default:
                *static_cast<UInt32*>(outData) = 0;
                break;
            }
            return noErr;

       #if DISTRHO_PLUGIN_WANT_LATENCY
        case kAudioUnitProperty_Latency:
            *static_cast<Float64*>(outData) = static_cast<double>(fPlugin.getLatency()) / fPlugin.getSampleRate();
            return noErr;
       #endif

        case kAudioUnitProperty_SupportedNumChannels:
            std::memcpy(outData, kChannelInfo, sizeof(kChannelInfo));
            return noErr;

        case kAudioUnitProperty_MaximumFramesPerSlice:
            *static_cast<UInt32*>(outData) = fPlugin.getBufferSize();
            return noErr;

        case kAudioUnitProperty_LastRenderError:
            *static_cast<OSStatus*>(outData) = fLastRenderError;
            fLastRenderError = noErr;
            return noErr;

        case kAudioUnitProperty_BypassEffect:
            *static_cast<OSStatus*>(outData) = fPlugin.getParameterValue(fBypassParameterIndex) > 0.5f ? 1 : 0;
            return noErr;

       #if DISTRHO_PLUGIN_NUM_INPUTS != 0
        case kAudioUnitProperty_SetRenderCallback:
            std::memcpy(outData, &fInputRenderCallback, sizeof(AURenderCallbackStruct));
            return noErr;
       #endif

       #if DISTRHO_PLUGIN_WANT_PROGRAMS
        case kAudioUnitProperty_FactoryPresets:
            if (const CFMutableArrayRef presetsRef = CFArrayCreateMutable(nullptr, fProgramCount, nullptr))
            {
                for (uint32_t i=0; i<fProgramCount; ++i)
                    CFArrayAppendValue(presetsRef, &fFactoryPresetsData[i]);

                *static_cast<CFArrayRef*>(outData) = presetsRef;
                return noErr;
            }
            return kAudio_ParamError;
       #endif

       #if DISTRHO_PLUGIN_WANT_TIMEPOS
        case kAudioUnitProperty_HostCallbacks:
            std::memcpy(outData, &fHostCallbackInfo, sizeof(HostCallbackInfo));
            return noErr;
       #endif

       #if DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
        case kAudioUnitProperty_InPlaceProcessing:
            *static_cast<UInt32*>(outData) = 1;
            return noErr;
       #endif

        case kAudioUnitProperty_PresentPreset:
           #if DISTRHO_PLUGIN_WANT_PROGRAMS
            if (fCurrentProgram >= 0)
            {
                std::memcpy(outData, &fFactoryPresetsData[fCurrentProgram], sizeof(AUPreset));
            }
            else
           #endif
            {
                std::memcpy(outData, &fUserPresetData, sizeof(AUPreset));
            }
            return noErr;

       #if DISTRHO_PLUGIN_WANT_MIDI_AS_MPE
        case kAudioUnitProperty_SupportsMPE:
            *static_cast<UInt32*>(outData) = 1;
            return noErr;
       #endif

       #if DISTRHO_PLUGIN_HAS_UI
        case kAudioUnitProperty_CocoaUI:
            {
                AudioUnitCocoaViewInfo* const info = static_cast<AudioUnitCocoaViewInfo*>(outData);
                std::memset(info, 0, sizeof(*info));

                NSString* const bundlePathString = [[NSString alloc]
                    initWithBytes:d_nextBundlePath
                           length:strlen(d_nextBundlePath)
                         encoding:NSUTF8StringEncoding];

                info->mCocoaAUViewBundleLocation = static_cast<CFURLRef>([[NSURL fileURLWithPath: bundlePathString] retain]);

                #define MACRO_STR3(a, b, c) a "_" b "_" c
                #define MACRO_STR2(a, b, c) MACRO_STR3(#a, #b, #c)
                #define MACRO_STR(a, b, c) MACRO_STR2(a, b, c)

                info->mCocoaAUViewClass[0] = CFSTR("CocoaAUView_" MACRO_STR(DISTRHO_PLUGIN_AU_TYPE,
                                                                            DISTRHO_PLUGIN_UNIQUE_ID,
                                                                            DISTRHO_PLUGIN_BRAND_ID));

                #undef MACRO_STR
                #undef MACRO_STR2
                #undef MACRO_STR3

                [bundlePathString release];
            }
            return noErr;
       #endif

       #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
        case kAudioUnitProperty_MIDIOutputCallbackInfo:
            {
                CFStringRef refs[1] = { CFSTR("MIDI Output") };
                *static_cast<CFArrayRef*>(outData) = CFArrayCreate(nullptr,
                                                                   reinterpret_cast<const void**>(refs),
                                                                   1,
                                                                   &kCFTypeArrayCallBacks);
            }
            return noErr;
       #endif

       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
        /* FIXME implement the event list stuff
        case kAudioUnitProperty_AudioUnitMIDIProtocol:
            *static_cast<SInt32*>(outData) = kMIDIProtocol_1_0;
            return noErr;
        */
       #endif

        case 'DPFp':
            *static_cast<float*>(outData) = fPlugin.getParameterValue(inElement);
            return noErr;

       #if DISTRHO_PLUGIN_WANT_PROGRAMS
        case 'DPFo':
            *static_cast<uint32_t*>(outData) = fLastFactoryProgram;
            return noErr;
       #endif

       #if DISTRHO_PLUGIN_WANT_STATE
        case 'DPFl':
            if (const CFMutableArrayRef keysRef = CFArrayCreateMutable(nullptr,
                                                                       fStateCount,
                                                                       &kCFTypeArrayCallBacks))
            {
                for (uint32_t i=0; i<fStateCount; ++i)
                {
                    const CFStringRef keyRef = CFStringCreateWithCString(nullptr,
                                                                         fPlugin.getStateKey(i),
                                                                         kCFStringEncodingASCII);
                    DISTRHO_SAFE_ASSERT_CONTINUE(keyRef != nullptr);

                    CFArrayAppendValue(keysRef, keyRef);
                    CFRelease(keyRef);
                }

                *static_cast<CFArrayRef*>(outData) = keysRef;
                return noErr;
            }
            return kAudio_ParamError;

        case 'DPFs':
            {
                const String& key(fPlugin.getStateKey(inElement));
               #if DISTRHO_PLUGIN_WANT_FULL_STATE
                fStateMap[key] = fPlugin.getStateValue(key);
               #endif

                *static_cast<CFStringRef*>(outData) = CFStringCreateWithCString(nullptr,
                                                                                fStateMap[key],
                                                                                kCFStringEncodingUTF8);
            }
            return noErr;
       #endif

       #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
        case 'DPFa':
            *static_cast<void**>(outData) = fPlugin.getInstancePointer();
            return noErr;
       #endif
        }

        d_stdout("TODO GetProperty(%d:%x:%s, %d:%s, %d, ...)",
                 inProp, inProp, AudioUnitPropertyID2Str(inProp), inScope, AudioUnitScope2Str(inScope), inElement);
        return kAudioUnitErr_InvalidProperty;
    }

    OSStatus auSetProperty(const AudioUnitPropertyID inProp,
                           const AudioUnitScope inScope,
                           const AudioUnitElement inElement,
                           const void* const inData,
                           const UInt32 inDataSize)
    {
        switch (inProp)
        {
        case kAudioUnitProperty_ClassInfo:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(CFPropertyListRef), inDataSize, kAudioUnitErr_InvalidPropertyValue);
            {
                const CFPropertyListRef propList = *static_cast<const CFPropertyListRef*>(inData);
                DISTRHO_SAFE_ASSERT_RETURN(CFGetTypeID(propList) == CFDictionaryGetTypeID(), kAudioUnitErr_InvalidPropertyValue);

                restoreClassInfo(static_cast<CFDictionaryRef>(propList));
            }
            return noErr;

        case kAudioUnitProperty_MakeConnection:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Input, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(AudioUnitConnection), inDataSize, kAudioUnitErr_InvalidPropertyValue);
           #if DISTRHO_PLUGIN_NUM_INPUTS != 0
            {
                const AudioUnitConnection conn = *static_cast<const AudioUnitConnection*>(inData);

                if (conn.sourceAudioUnit == nullptr)
                {
                    fInputConnectionBus = 0;
                    fInputConnectionUnit = nullptr;
                    return noErr;
                }

                AudioStreamBasicDescription desc;
                std::memset(&desc, 0, sizeof(desc));
                UInt32 dataSize = sizeof(AudioStreamBasicDescription);
                if (AudioUnitGetProperty(conn.sourceAudioUnit,
                                         kAudioUnitProperty_StreamFormat,
                                         kAudioUnitScope_Output,
                                         conn.sourceOutputNumber, &desc, &dataSize) != noErr)
                    return kAudioUnitErr_InvalidPropertyValue;

                DISTRHO_SAFE_ASSERT_INT_RETURN(desc.mFormatID == kAudioFormatLinearPCM,
                                               desc.mFormatID, kAudioUnitErr_FormatNotSupported);
                DISTRHO_SAFE_ASSERT_INT_RETURN(desc.mBitsPerChannel == 32,
                                               desc.mBitsPerChannel, kAudioUnitErr_FormatNotSupported);
                DISTRHO_SAFE_ASSERT_INT_RETURN(desc.mBytesPerFrame == sizeof(float),
                                               desc.mBytesPerFrame, kAudioUnitErr_FormatNotSupported);
                DISTRHO_SAFE_ASSERT_INT_RETURN(desc.mBytesPerPacket == sizeof(float),
                                               desc.mBytesPerPacket, kAudioUnitErr_FormatNotSupported);
                DISTRHO_SAFE_ASSERT_INT_RETURN(desc.mFramesPerPacket == 1,
                                               desc.mFramesPerPacket, kAudioUnitErr_FormatNotSupported);
                DISTRHO_SAFE_ASSERT_INT_RETURN(desc.mFormatFlags == kWantedAudioFormat,
                                               desc.mFormatFlags, kAudioUnitErr_FormatNotSupported);
               #ifdef DISTRHO_PLUGIN_EXTRA_IO
                DISTRHO_SAFE_ASSERT_UINT_RETURN(desc.mChannelsPerFrame == fNumInputs,
                                                desc.mChannelsPerFrame, kAudioUnitErr_FormatNotSupported);
               #else
                DISTRHO_SAFE_ASSERT_UINT_RETURN(desc.mChannelsPerFrame == DISTRHO_PLUGIN_NUM_INPUTS,
                                                desc.mChannelsPerFrame, kAudioUnitErr_FormatNotSupported);
               #endif

                fInputConnectionBus = conn.sourceOutputNumber;
                fInputConnectionUnit = conn.sourceAudioUnit;
            }
            return noErr;
           #else
            return kAudioUnitErr_PropertyNotInUse;
           #endif

        case kAudioUnitProperty_SampleRate:
           #if DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Input || inScope == kAudioUnitScope_Output, inScope, kAudioUnitErr_InvalidScope);
           #elif DISTRHO_PLUGIN_NUM_OUTPUTS != 0
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Output, inScope, kAudioUnitErr_InvalidScope);
           #else
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Input, inScope, kAudioUnitErr_InvalidScope);
           #endif
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(Float64), inDataSize, kAudioUnitErr_InvalidPropertyValue);
            {
               #if DISTRHO_PLUGIN_NUM_INPUTS != 0 || DISTRHO_PLUGIN_NUM_OUTPUTS != 0
                const Float64 sampleRate = *static_cast<const Float64*>(inData);
               #endif

               #if DISTRHO_PLUGIN_NUM_INPUTS != 0
                if (inScope == kAudioUnitScope_Input)
                {
                    if (d_isNotEqual(fSampleRateForInput, sampleRate))
                    {
                        fSampleRateForInput = sampleRate;
                        d_nextSampleRate = sampleRate;

                       #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
                        if (d_isEqual(fSampleRateForOutput, sampleRate))
                       #endif
                        {
                            fPlugin.setSampleRate(sampleRate, true);
                        }

                        notifyPropertyListeners(inProp, inScope, inElement);
                    }
                    return noErr;
                }
               #endif

               #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
                if (inScope == kAudioUnitScope_Output)
                {
                    if (d_isNotEqual(fSampleRateForOutput, sampleRate))
                    {
                        fSampleRateForOutput = sampleRate;
                        d_nextSampleRate = sampleRate;

                       #if DISTRHO_PLUGIN_NUM_INPUTS != 0
                        if (d_isEqual(fSampleRateForInput, sampleRate))
                       #endif
                        {
                            fPlugin.setSampleRate(sampleRate, true);
                        }

                        notifyPropertyListeners(inProp, inScope, inElement);
                    }
                    return noErr;
                }
               #endif
            }
            return kAudioUnitErr_PropertyNotInUse;

        case kAudioUnitProperty_StreamFormat:
           #if DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Input || inScope == kAudioUnitScope_Output, inScope, kAudioUnitErr_InvalidScope);
           #elif DISTRHO_PLUGIN_NUM_OUTPUTS != 0
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Output, inScope, kAudioUnitErr_InvalidScope);
           #else
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Input, inScope, kAudioUnitErr_InvalidScope);
           #endif
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(AudioStreamBasicDescription), inDataSize, kAudioUnitErr_InvalidPropertyValue);
            {
                const AudioStreamBasicDescription* const desc = static_cast<const AudioStreamBasicDescription*>(inData);

                if (desc->mFormatID != kAudioFormatLinearPCM)
                    return kAudioUnitErr_FormatNotSupported;
                if (desc->mBitsPerChannel != 32)
                    return kAudioUnitErr_FormatNotSupported;
                if (desc->mBytesPerFrame != sizeof(float))
                    return kAudioUnitErr_FormatNotSupported;
                if (desc->mBytesPerPacket != sizeof(float))
                    return kAudioUnitErr_FormatNotSupported;
                if (desc->mFramesPerPacket != 1)
                    return kAudioUnitErr_FormatNotSupported;
                if (desc->mFormatFlags != kWantedAudioFormat)
                    return kAudioUnitErr_FormatNotSupported;

               #ifndef DISTRHO_PLUGIN_EXTRA_IO
                if (desc->mChannelsPerFrame != (inScope == kAudioUnitScope_Input ? DISTRHO_PLUGIN_NUM_INPUTS
                                                                                 : DISTRHO_PLUGIN_NUM_OUTPUTS))
                    return kAudioUnitErr_FormatNotSupported;
               #endif

               #if DISTRHO_PLUGIN_NUM_INPUTS != 0
                if (inScope == kAudioUnitScope_Input)
                {
                    bool changed = false;

                   #ifdef DISTRHO_PLUGIN_EXTRA_IO
                    if (! isInputNumChannelsValid(desc->mChannelsPerFrame))
                        return kAudioUnitErr_FormatNotSupported;

                    if (fNumInputs != desc->mChannelsPerFrame)
                    {
                        changed = true;
                        fNumInputs = desc->mChannelsPerFrame;

                       #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
                        if (isNumChannelsComboValid(fNumInputs, fNumOutputs))
                       #endif
                        {
                            fPlugin.setAudioPortIO(fNumInputs, fNumOutputs);
                        }
                    }
                   #endif

                    if (d_isNotEqual(fSampleRateForInput, desc->mSampleRate))
                    {
                        changed = true;
                        fSampleRateForInput = desc->mSampleRate;

                       #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
                        if (d_isEqual(fSampleRateForOutput, desc->mSampleRate))
                       #endif
                        {
                            fPlugin.setSampleRate(desc->mSampleRate, true);
                        }

                        notifyPropertyListeners(kAudioUnitProperty_SampleRate, inScope, inElement);
                    }

                    if (changed)
                        notifyPropertyListeners(inProp, inScope, inElement);

                    return noErr;
                }
               #endif

               #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
                if (inScope == kAudioUnitScope_Output)
                {
                    bool changed = false;

                   #ifdef DISTRHO_PLUGIN_EXTRA_IO
                    if (! isOutputNumChannelsValid(desc->mChannelsPerFrame))
                        return kAudioUnitErr_FormatNotSupported;

                    if (fNumOutputs != desc->mChannelsPerFrame)
                    {
                        changed = true;
                        fNumOutputs = desc->mChannelsPerFrame;

                       #if DISTRHO_PLUGIN_NUM_INPUTS != 0
                        if (isNumChannelsComboValid(fNumInputs, fNumOutputs))
                       #endif
                        {
                            fPlugin.setAudioPortIO(fNumInputs, fNumOutputs);
                        }
                    }
                   #endif

                    if (d_isNotEqual(fSampleRateForOutput, desc->mSampleRate))
                    {
                        changed = true;
                        fSampleRateForOutput = desc->mSampleRate;

                       #if DISTRHO_PLUGIN_NUM_INPUTS != 0
                        if (d_isEqual(fSampleRateForInput, desc->mSampleRate))
                       #endif
                        {
                            fPlugin.setSampleRate(desc->mSampleRate, true);
                        }

                        notifyPropertyListeners(kAudioUnitProperty_SampleRate, inScope, inElement);
                    }

                    if (changed)
                        notifyPropertyListeners(inProp, inScope, inElement);

                    return noErr;
                }
               #endif
            }
            return kAudioUnitErr_PropertyNotInUse;

        case kAudioUnitProperty_MaximumFramesPerSlice:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(UInt32), inDataSize, kAudioUnitErr_InvalidPropertyValue);
            {
                const UInt32 bufferSize = *static_cast<const UInt32*>(inData);

                if (fPlugin.setBufferSize(bufferSize, true))
                    notifyPropertyListeners(inProp, inScope, inElement);
            }
            return noErr;

        case kAudioUnitProperty_BypassEffect:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(UInt32), inDataSize, kAudioUnitErr_InvalidPropertyValue);
            DISTRHO_SAFE_ASSERT_RETURN(fBypassParameterIndex != UINT32_MAX, kAudioUnitErr_PropertyNotInUse);
            {
                const bool bypass = *static_cast<const UInt32*>(inData) != 0;

                if ((fLastParameterValues[fBypassParameterIndex] > 0.5f) != bypass)
                {
                    const float value = bypass ? 1.f : 0.f;
                    fLastParameterValues[fBypassParameterIndex] = value;
                    fPlugin.setParameterValue(fBypassParameterIndex, value);
                    notifyPropertyListeners(inProp, inScope, inElement);
                }
            }
            return noErr;

        case kAudioUnitProperty_SetRenderCallback:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Input, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(AURenderCallbackStruct), inDataSize, kAudioUnitErr_InvalidPropertyValue);
           #if DISTRHO_PLUGIN_NUM_INPUTS != 0
            std::memcpy(&fInputRenderCallback, inData, sizeof(AURenderCallbackStruct));
            return noErr;
           #else
            return kAudioUnitErr_PropertyNotInUse;
           #endif

        case kAudioUnitProperty_HostCallbacks:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_WANT_TIMEPOS
            {
                const UInt32 usableDataSize = std::min(inDataSize, static_cast<UInt32>(sizeof(HostCallbackInfo)));
                const bool changed = std::memcmp(&fHostCallbackInfo, inData, usableDataSize) != 0;

                std::memcpy(&fHostCallbackInfo, inData, usableDataSize);

                if (sizeof(HostCallbackInfo) > usableDataSize)
                    std::memset(&fHostCallbackInfo + usableDataSize, 0, sizeof(HostCallbackInfo) - usableDataSize);

                if (changed)
                    notifyPropertyListeners(inProp, inScope, inElement);
            }
            return noErr;
           #else
            return kAudioUnitErr_PropertyNotInUse;
           #endif

        case kAudioUnitProperty_InPlaceProcessing:
            // nothing to do
            return noErr;

        case kAudioUnitProperty_PresentPreset:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(AUPreset), inDataSize, kAudioUnitErr_InvalidPropertyValue);
            {
                const int32_t presetNumber = static_cast<const AUPreset*>(inData)->presetNumber;

               #if DISTRHO_PLUGIN_WANT_PROGRAMS
                if (presetNumber >= 0)
                {
                    if (fCurrentProgram != presetNumber)
                    {
                        fCurrentProgram = presetNumber;
                        fLastFactoryProgram = presetNumber;
                        fPlugin.loadProgram(fLastFactoryProgram);
                        notifyPropertyListeners('DPFo', kAudioUnitScope_Global, 0);
                    }
                }
                else
                {
                    fCurrentProgram = presetNumber;
                    CFRelease(fUserPresetData.presetName);
                    std::memcpy(&fUserPresetData, inData, sizeof(AUPreset));
                }
               #else
                DISTRHO_SAFE_ASSERT_INT_RETURN(presetNumber < 0, presetNumber, kAudioUnitErr_InvalidPropertyValue);
                CFRelease(fUserPresetData.presetName);
                std::memcpy(&fUserPresetData, inData, sizeof(AUPreset));
               #endif
            }
            return noErr;

        case kAudioUnitProperty_MIDIOutputCallback:
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(AUMIDIOutputCallbackStruct), inDataSize, kAudioUnitErr_InvalidPropertyValue);
           #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
            std::memcpy(&fMidiOutput, inData, sizeof(AUMIDIOutputCallbackStruct));
            return noErr;
           #else
            return kAudioUnitErr_PropertyNotInUse;
           #endif

        case 'DPFi':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(uint16_t), inDataSize, kAudioUnitErr_InvalidPropertyValue);
            {
                const uint16_t magic = *static_cast<const uint16_t*>(inData);

                if (magic != 1337)
                    return noErr;

               #if DISTRHO_PLUGIN_WANT_PROGRAMS
                notifyPropertyListeners('DPFo', kAudioUnitScope_Global, 0);
               #endif

               #if DISTRHO_PLUGIN_WANT_STATE
                for (uint32_t i=0; i<fStateCount; ++i)
                    notifyPropertyListeners('DPFs', kAudioUnitScope_Global, i);
               #endif

                for (uint32_t i=0; i<fParameterCount; ++i)
                    notifyPropertyListeners('DPFp', kAudioUnitScope_Global, i);
            }
            return noErr;

        case 'DPFe':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement < fParameterCount, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(uint8_t), inDataSize, kAudioUnitErr_InvalidPropertyValue);
            {
                const uint8_t flag = *static_cast<const uint8_t*>(inData);

                if (flag != 1 && flag != 2)
                    return noErr;

                AudioUnitEvent event;
                std::memset(&event, 0, sizeof(event));

                event.mEventType                        = flag == 1 ? kAudioUnitEvent_BeginParameterChangeGesture
                                                                    : kAudioUnitEvent_EndParameterChangeGesture;
                event.mArgument.mParameter.mAudioUnit   = fComponent;
                event.mArgument.mParameter.mParameterID = inElement;
                event.mArgument.mParameter.mScope       = kAudioUnitScope_Global;
                AUEventListenerNotify(NULL, NULL, &event);
            }
            return noErr;

        case 'DPFp':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement < fParameterCount, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(float), inDataSize, kAudioUnitErr_InvalidPropertyValue);
            {
                const float value = *static_cast<const float*>(inData);
                DISTRHO_SAFE_ASSERT_RETURN(std::isfinite(value), kAudioUnitErr_InvalidParameterValue);

                if (d_isEqual(fLastParameterValues[inElement], value))
                    return noErr;

                fLastParameterValues[inElement] = value;
                fPlugin.setParameterValue(inElement, value);

                AudioUnitEvent event;
                std::memset(&event, 0, sizeof(event));

                event.mEventType                        = kAudioUnitEvent_ParameterValueChange;
                event.mArgument.mParameter.mAudioUnit   = fComponent;
                event.mArgument.mParameter.mParameterID = inElement;
                event.mArgument.mParameter.mScope       = kAudioUnitScope_Global;
                AUEventListenerNotify(NULL, NULL, &event);

                if (fBypassParameterIndex == inElement)
                    notifyPropertyListeners(kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0);
            }
            return noErr;

        case 'DPFn':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(uint8_t) * 3, inDataSize, kAudioUnitErr_InvalidPropertyValue);
           #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
            {
                const uint8_t* const midiData = static_cast<const uint8_t*>(inData);

                if (midiData[0] == 0)
                    return noErr;

                fNotesRingBuffer.writeCustomData(midiData, 3);
                fNotesRingBuffer.commitWrite();
            }
            return noErr;
           #else
            return kAudioUnitErr_PropertyNotInUse;
           #endif

        case 'DPFs':
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(CFDictionaryRef), inDataSize, kAudioUnitErr_InvalidPropertyValue);
           #if DISTRHO_PLUGIN_WANT_STATE
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
            {
                const CFDictionaryRef dictRef = *static_cast<const CFDictionaryRef*>(inData);
                DISTRHO_SAFE_ASSERT_RETURN(CFGetTypeID(dictRef) == CFDictionaryGetTypeID(),
                                           kAudioUnitErr_InvalidPropertyValue);
                DISTRHO_SAFE_ASSERT_RETURN(CFDictionaryGetCount(dictRef) == 1, kAudioUnitErr_InvalidPropertyValue);

                CFStringRef keyRef = nullptr;
                CFStringRef valueRef = nullptr;
                CFDictionaryGetKeysAndValues(dictRef,
                                             reinterpret_cast<const void**>(&keyRef),
                                             reinterpret_cast<const void**>(&valueRef));
                DISTRHO_SAFE_ASSERT_RETURN(keyRef != nullptr && CFGetTypeID(keyRef) == CFStringGetTypeID(),
                                           kAudioUnitErr_InvalidPropertyValue);
                DISTRHO_SAFE_ASSERT_RETURN(valueRef != nullptr && CFGetTypeID(valueRef) == CFStringGetTypeID(),
                                           kAudioUnitErr_InvalidPropertyValue);

                const CFIndex keyRefLen = CFStringGetLength(keyRef);
                char* key = static_cast<char*>(std::malloc(keyRefLen + 1));
                DISTRHO_SAFE_ASSERT_RETURN(CFStringGetCString(keyRef, key, keyRefLen + 1, kCFStringEncodingASCII),
                                           kAudioUnitErr_InvalidPropertyValue);

                const CFIndex valueRefLen = CFStringGetLength(valueRef);
                char* value = static_cast<char*>(std::malloc(valueRefLen + 1));
                DISTRHO_SAFE_ASSERT_RETURN(CFStringGetCString(valueRef, value, valueRefLen + 1, kCFStringEncodingUTF8),
                                           kAudioUnitErr_InvalidPropertyValue);

                const String dkey(key);

                // save this key as needed
                if (fPlugin.wantStateKey(dkey))
                    fStateMap[dkey] = value;

                fPlugin.setState(dkey, value);

                std::free(key);
                std::free(value);
            }
            return noErr;
           #else
            return kAudioUnitErr_PropertyNotInUse;
           #endif

        // unwanted properties
        case kMusicDeviceProperty_DualSchedulingMode:
            return kAudioUnitErr_PropertyNotInUse;
        }

        d_stdout("TODO SetProperty(%d:%x:%s, %d:%s, %d, %p, %u)",
                 inProp, inProp, AudioUnitPropertyID2Str(inProp), inScope, AudioUnitScope2Str(inScope), inElement, inData, inDataSize);
        return kAudioUnitErr_InvalidProperty;
    }

    OSStatus auAddPropertyListener(const AudioUnitPropertyID prop,
                                   const AudioUnitPropertyListenerProc proc,
                                   void* const userData)
    {
        const PropertyListener pl = {
            prop, proc, userData
        };

        fPropertyListeners.push_back(pl);
        return noErr;
    }

    OSStatus auRemovePropertyListener(const AudioUnitPropertyID prop, const AudioUnitPropertyListenerProc proc)
    {
        for (PropertyListeners::iterator it = fPropertyListeners.begin(); it != fPropertyListeners.end(); ++it)
        {
            const PropertyListener& pl(*it);

            if (pl.prop == prop && pl.proc == proc)
            {
                fPropertyListeners.erase(it);
                return auRemovePropertyListener(prop, proc);
            }
        }

        return noErr;
    }

    OSStatus auRemovePropertyListenerWithUserData(const AudioUnitPropertyID prop,
                                                  const AudioUnitPropertyListenerProc proc,
                                                  void* const userData)
    {
        for (PropertyListeners::iterator it = fPropertyListeners.begin(); it != fPropertyListeners.end(); ++it)
        {
            const PropertyListener& pl(*it);

            if (pl.prop == prop && pl.proc == proc && pl.userData == userData)
            {
                fPropertyListeners.erase(it);
                return auRemovePropertyListenerWithUserData(prop, proc, userData);
            }
        }

        return noErr;
    }

    OSStatus auAddRenderNotify(const AURenderCallback proc, void* const userData)
    {
        fUsingRenderListeners = true;

        const RenderListener rl = {
            proc, userData
        };

        fRenderListeners.push_back(rl);
        return noErr;
    }

    OSStatus auRemoveRenderNotify(const AURenderCallback proc, void* const userData)
    {
        for (RenderListeners::iterator it = fRenderListeners.begin(); it != fRenderListeners.end(); ++it)
        {
            const RenderListener& rl(*it);

            if (rl.proc == proc && rl.userData == userData)
            {
                fRenderListeners.erase(it);
                return auRemoveRenderNotify(proc, userData);
            }
        }

        if (fRenderListeners.empty())
            fUsingRenderListeners = false;

        return noErr;
    }

    OSStatus auGetParameter(const AudioUnitParameterID param,
                            const AudioUnitScope scope,
                            const AudioUnitElement elem,
                            AudioUnitParameterValue* const value)
    {
        DISTRHO_SAFE_ASSERT_UINT_RETURN(scope == kAudioUnitScope_Global, scope, kAudioUnitErr_InvalidScope);
        DISTRHO_SAFE_ASSERT_UINT_RETURN(param < fParameterCount, param, kAudioUnitErr_InvalidElement);
        DISTRHO_SAFE_ASSERT_UINT_RETURN(elem == 0, elem, kAudioUnitErr_InvalidElement);
        DISTRHO_SAFE_ASSERT_RETURN(value != nullptr, kAudio_ParamError);

        *value = fPlugin.getParameterValue(param);
        return noErr;
    }

    OSStatus auSetParameter(const AudioUnitParameterID param,
                            const AudioUnitScope scope,
                            const AudioUnitElement elem,
                            const AudioUnitParameterValue value,
                            UInt32 /* bufferOffset */)
    {
        DISTRHO_SAFE_ASSERT_UINT_RETURN(scope == kAudioUnitScope_Global, scope, kAudioUnitErr_InvalidScope);
        DISTRHO_SAFE_ASSERT_UINT_RETURN(param < fParameterCount, param, kAudioUnitErr_InvalidElement);
        DISTRHO_SAFE_ASSERT_UINT_RETURN(elem == 0, elem, kAudioUnitErr_InvalidElement);
        DISTRHO_SAFE_ASSERT_RETURN(std::isfinite(value), kAudioUnitErr_InvalidParameterValue);

        if (d_isNotEqual(fLastParameterValues[param], value))
        {
            fLastParameterValues[param] = value;
            fPlugin.setParameterValue(param, value);

            // TODO flag param only, notify listeners later on bg thread (sem_post etc)
            notifyPropertyListeners('DPFp', kAudioUnitScope_Global, param);

            if (fBypassParameterIndex == elem)
                notifyPropertyListeners(kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0);
        }

        return noErr;
    }

    OSStatus auScheduleParameters(const AudioUnitParameterEvent* const events, const UInt32 numEvents)
    {
        for (UInt32 i=0; i<numEvents; ++i)
        {
            const AudioUnitParameterEvent& event(events[i]);

            if (event.eventType == kParameterEvent_Immediate)
            {
                auSetParameter(event.parameter,
                               event.scope,
                               event.element,
                               event.eventValues.immediate.value,
                               event.eventValues.immediate.bufferOffset);
            }
            else
            {
                // TODO?
                d_stdout("WIP ScheduleParameters(%p, %u)", events, numEvents);
            }
        }
        return noErr;
    }

    OSStatus auReset(const AudioUnitScope scope, const AudioUnitElement elem)
    {
        DISTRHO_SAFE_ASSERT_UINT_RETURN(scope == kAudioUnitScope_Global || scope == kAudioUnitScope_Input || scope == kAudioUnitScope_Output, scope, kAudioUnitErr_InvalidScope);
        DISTRHO_SAFE_ASSERT_UINT_RETURN(elem == 0, elem, kAudioUnitErr_InvalidElement);

        if (fResetParameterIndex != UINT32_MAX)
        {
            fPlugin.setParameterValue(fResetParameterIndex, 1.f);
            fPlugin.setParameterValue(fResetParameterIndex, 0.f);
        }
        else if (fPlugin.isActive())
        {
            fPlugin.deactivate();
            fPlugin.activate();
        }

       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
        fMidiEventCount = 0;
       #endif
       #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
        fMidiOutputDataOffset = 0;
        fMidiOutputPackets->numPackets = 0;
       #endif
       #if DISTRHO_PLUGIN_WANT_TIMEPOS
        fTimePosition.clear();
        fTimePosition.bbt.ticksPerBeat = kDefaultTicksPerBeat;
       #endif
        return noErr;
    }

    OSStatus auRender(const AudioUnitRenderActionFlags actionFlags,
                      const AudioTimeStamp* const inTimeStamp,
                      const UInt32 inBusNumber,
                      const UInt32 inFramesToProcess,
                      AudioBufferList* const ioData)
    {
        if ((actionFlags & kAudioUnitRenderAction_DoNotCheckRenderArgs) == 0x0)
        {
            DISTRHO_SAFE_ASSERT_RETURN(fPlugin.isActive(), kAudio_ParamError);
            DISTRHO_SAFE_ASSERT_UINT_RETURN(inBusNumber == 0, inBusNumber, kAudioUnitErr_InvalidElement);
           #if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0
            DISTRHO_SAFE_ASSERT_UINT_RETURN(ioData->mNumberBuffers == fAudioBufferList->mNumberBuffers,
                                            ioData->mNumberBuffers, kAudio_ParamError);
           #else
            DISTRHO_SAFE_ASSERT_UINT_RETURN(ioData->mNumberBuffers == 0, ioData->mNumberBuffers, kAudio_ParamError);
           #endif

            if (inFramesToProcess > fPlugin.getBufferSize())
            {
                setLastRenderError(kAudioUnitErr_TooManyFramesToProcess);
                return kAudioUnitErr_TooManyFramesToProcess;
            }

           #if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0
            for (uint16_t i = 0; i < ioData->mNumberBuffers; ++i)
            {
                if (ioData->mBuffers[i].mDataByteSize != sizeof(float) * inFramesToProcess)
                {
                    setLastRenderError(kAudio_ParamError);
                    return kAudio_ParamError;
                }
            }
           #endif
        }

      #if DISTRHO_PLUGIN_NUM_INPUTS != 0
       #ifdef DISTRHO_PLUGIN_EXTRA_IO
        const uint32_t numInputs = fNumInputs;
       #else
        constexpr const uint32_t numInputs = DISTRHO_PLUGIN_NUM_INPUTS;
       #endif
        const float* inputs[numInputs];
      #else
        constexpr const float** inputs = nullptr;
      #endif

      #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
       #ifdef DISTRHO_PLUGIN_EXTRA_IO
        const uint32_t numOutputs = fNumOutputs;
       #else
        constexpr const uint32_t numOutputs = DISTRHO_PLUGIN_NUM_OUTPUTS;
       #endif
        float* outputs[numOutputs];
      #else
        constexpr float** outputs = nullptr;
      #endif

       #if DISTRHO_PLUGIN_NUM_INPUTS != 0
        if (fInputConnectionUnit != nullptr)
        {
            AudioUnitRenderActionFlags ioActionFlags = 0;
            const OSStatus err = AudioUnitRender(fInputConnectionUnit,
                                                 &ioActionFlags,
                                                 inTimeStamp,
                                                 fInputConnectionBus,
                                                 inFramesToProcess,
                                                 fAudioBufferList);

            if (err != noErr)
            {
                setLastRenderError(err);
                return err;
            }

            for (uint16_t i = 0; i < numInputs; ++i)
                inputs[i] = static_cast<const float*>(fAudioBufferList->mBuffers[i].mData);

           #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
            for (uint16_t i = 0; i < numOutputs; ++i)
            {
                if (ioData->mBuffers[i].mData == nullptr)
                    ioData->mBuffers[i].mData = fAudioBufferList->mBuffers[i].mData;

                outputs[i] = static_cast<float*>(ioData->mBuffers[i].mData);
            }
           #endif
        }
        else if (fInputRenderCallback.inputProc != nullptr)
        {
            bool adjustDataByteSize, usingHostBuffer = true;
            UInt32 prevDataByteSize;

            for (uint16_t i = 0; i < ioData->mNumberBuffers; ++i)
            {
                if (ioData->mBuffers[i].mData == nullptr)
                {
                    usingHostBuffer = false;
                    ioData->mBuffers[i].mData = fAudioBufferList->mBuffers[i].mData;
                }
            }

            if (! usingHostBuffer)
            {
                prevDataByteSize = fAudioBufferList->mBuffers[0].mDataByteSize;
                adjustDataByteSize = prevDataByteSize != sizeof(float) * inFramesToProcess;

                if (adjustDataByteSize)
                {
                    for (uint16_t i = 0; i < ioData->mNumberBuffers; ++i)
                        fAudioBufferList->mBuffers[i].mDataByteSize = sizeof(float) * inFramesToProcess;
                }
            }
            else
            {
                adjustDataByteSize = false;
            }

            AudioUnitRenderActionFlags rActionFlags = 0;
            AudioBufferList* const rData = usingHostBuffer ? ioData : fAudioBufferList;
            const OSStatus err = fInputRenderCallback.inputProc(fInputRenderCallback.inputProcRefCon,
                                                                &rActionFlags,
                                                                inTimeStamp,
                                                                inBusNumber,
                                                                inFramesToProcess,
                                                                rData);

            if (err != noErr)
            {
                if (adjustDataByteSize)
                {
                    for (uint16_t i = 0; i < ioData->mNumberBuffers; ++i)
                        fAudioBufferList->mBuffers[i].mDataByteSize = prevDataByteSize;
                }

                setLastRenderError(err);
                return err;
            }

            if (usingHostBuffer)
            {
                for (uint16_t i = 0; i < numInputs; ++i)
                    inputs[i] = static_cast<const float*>(ioData->mBuffers[i].mData);

               #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
                for (uint16_t i = 0; i < numOutputs; ++i)
                    outputs[i] = static_cast<float*>(ioData->mBuffers[i].mData);
               #endif

            }
            else
            {
                for (uint16_t i = 0; i < numInputs; ++i)
                    inputs[i] = static_cast<const float*>(fAudioBufferList->mBuffers[i].mData);

               #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
                for (uint16_t i = 0; i < numOutputs; ++i)
                    outputs[i] = static_cast<float*>(ioData->mBuffers[i].mData);
               #endif
            }
        }
        else
       #endif // DISTRHO_PLUGIN_NUM_INPUTS != 0
        {
           #if DISTRHO_PLUGIN_NUM_INPUTS != 0
            for (uint16_t i = 0; i < numInputs; ++i)
            {
                if (ioData->mBuffers[i].mData == nullptr)
                {
                    ioData->mBuffers[i].mData = fAudioBufferList->mBuffers[i].mData;
                    std::memset(ioData->mBuffers[i].mData, 0, sizeof(float) * inFramesToProcess);
                }

                inputs[i] = static_cast<const float*>(ioData->mBuffers[i].mData);
            }
           #endif

           #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
            for (uint16_t i = 0; i < numOutputs; ++i)
            {
                if (ioData->mBuffers[i].mData == nullptr)
                    ioData->mBuffers[i].mData = fAudioBufferList->mBuffers[i].mData;

                outputs[i] = static_cast<float*>(ioData->mBuffers[i].mData);
            }
           #endif
        }

        if (fUsingRenderListeners)
        {
            AudioUnitRenderActionFlags ioActionFlags = actionFlags | kAudioUnitRenderAction_PreRender;
            notifyRenderListeners(&ioActionFlags, inTimeStamp, inBusNumber, inFramesToProcess, ioData);
        }

        run(inputs, outputs, inFramesToProcess, inTimeStamp);

        if (fUsingRenderListeners)
        {
            AudioUnitRenderActionFlags ioActionFlags = actionFlags | kAudioUnitRenderAction_PostRender;
            notifyRenderListeners(&ioActionFlags, inTimeStamp, inBusNumber, inFramesToProcess, ioData);
        }

        return noErr;
    }

   #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    OSStatus auMIDIEvent(const UInt32 inStatus,
                         const UInt32 inData1,
                         const UInt32 inData2,
                         const UInt32 inOffsetSampleFrame)
    {
        if (fMidiEventCount >= kMaxMidiEvents)
            return noErr;

        MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]);
        midiEvent.frame   = inOffsetSampleFrame;
        midiEvent.data[0] = inStatus;
        midiEvent.data[1] = inData1;
        midiEvent.data[2] = inData2;

        switch (inStatus & 0xF0)
        {
        case 0x80:
        case 0x90:
        case 0xA0:
        case 0xB0:
        case 0xE0:
            midiEvent.size = 3;
            break;
        case 0xC0:
        case 0xD0:
            midiEvent.size = 2;
            break;
        case 0xF0:
            switch (inStatus & 0x0F)
            {
            case 0x0:
            case 0x4:
            case 0x5:
            case 0x7:
            case 0x9:
            case 0xD:
                // unsupported
                return kAudioUnitErr_InvalidPropertyValue;
            case 0x1:
            case 0x2:
            case 0x3:
            case 0xE:
                midiEvent.size = 3;
                break;
            case 0x6:
            case 0x8:
            case 0xA:
            case 0xB:
            case 0xC:
            case 0xF:
                midiEvent.size = 1;
                break;
            }
            break;
        default:
            // invalid
            d_debug("auMIDIEvent received invalid event %u %u %u %u @ %u",
                    inStatus, inData1, inData2, inOffsetSampleFrame, fMidiEventCount);
            return kAudioUnitErr_InvalidPropertyValue;
        }

       #if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS == 0
        // handle case of plugin having no working audio, simulate audio-side processing
        run(nullptr, nullptr, std::max(1u, inOffsetSampleFrame), nullptr);
       #endif

        return noErr;
    }

    OSStatus auSysEx(const UInt8* const inData, const UInt32 inLength)
    {
        if (fMidiEventCount >= kMaxMidiEvents)
            return noErr;

        MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]);
        midiEvent.frame = fMidiEventCount != 1 ? fMidiEvents[fMidiEventCount - 1].frame : 0;
        midiEvent.size  = inLength;

        // FIXME who owns inData ??
        if (inLength > MidiEvent::kDataSize)
        {
            std::memset(midiEvent.data, 0, MidiEvent::kDataSize);
            midiEvent.dataExt = inData;
        }
        else
        {
            std::memcpy(midiEvent.data, inData, inLength);
        }

       #if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS == 0
        // handle case of plugin having no working audio, simulate audio-side processing
        run(nullptr, nullptr, 1, nullptr);
       #endif

        return noErr;
    }
   #endif

    // ----------------------------------------------------------------------------------------------------------------

private:
    PluginExporter fPlugin;

    // AU component
    const AudioUnit fComponent;

    // AUv2 related fields
    OSStatus fLastRenderError;
    PropertyListeners fPropertyListeners;
    RenderListeners fRenderListeners;
  #if DISTRHO_PLUGIN_NUM_INPUTS != 0
    UInt32 fInputConnectionBus;
    AudioUnit fInputConnectionUnit;
    AURenderCallbackStruct fInputRenderCallback;
    Float64 fSampleRateForInput;
   #ifdef DISTRHO_PLUGIN_EXTRA_IO
    uint32_t fNumInputs;
   #endif
  #endif
  #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
    Float64 fSampleRateForOutput;
   #ifdef DISTRHO_PLUGIN_EXTRA_IO
    uint32_t fNumOutputs;
   #endif
  #endif
   #if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0
    AudioBufferList* fAudioBufferList;
   #endif
    bool fUsingRenderListeners;

    // Caching
    const uint32_t fParameterCount;
    float* fLastParameterValues;
    uint32_t fBypassParameterIndex;
    uint32_t fResetParameterIndex;

   #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    uint32_t fMidiEventCount;
    MidiEvent fMidiEvents[kMaxMidiEvents];
    SmallStackRingBuffer fNotesRingBuffer;
   #endif

   #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
    uint32_t fMidiOutputDataOffset;
    MIDIPacketList* fMidiOutputPackets;
    AUMIDIOutputCallbackStruct fMidiOutput;
   #endif

   #if DISTRHO_PLUGIN_WANT_PROGRAMS
    int32_t fCurrentProgram;
    uint32_t fLastFactoryProgram;
    uint32_t fProgramCount;
    AUPreset* fFactoryPresetsData;
   #endif
    AUPreset fUserPresetData;

   #if DISTRHO_PLUGIN_WANT_STATE
    const uint32_t fStateCount;
    StringMap fStateMap;
   #endif

   #if DISTRHO_PLUGIN_WANT_TIMEPOS
    HostCallbackInfo fHostCallbackInfo;
    TimePosition fTimePosition;
   #endif

    // ----------------------------------------------------------------------------------------------------------------

    void notifyPropertyListeners(const AudioUnitPropertyID prop, const AudioUnitScope scope, const AudioUnitElement elem)
    {
        for (PropertyListeners::iterator it = fPropertyListeners.begin(); it != fPropertyListeners.end(); ++it)
        {
            const PropertyListener& pl(*it);

            if (pl.prop == prop)
                pl.proc(pl.userData, fComponent, prop, scope, elem);
        }
    }

    void notifyRenderListeners(AudioUnitRenderActionFlags* const ioActionFlags,
                               const AudioTimeStamp* const inTimeStamp,
                               const UInt32 inBusNumber,
                               const UInt32 inNumberFrames,
                               AudioBufferList* const ioData)
    {
        for (RenderListeners::iterator it = fRenderListeners.begin(); it != fRenderListeners.end(); ++it)
        {
            const RenderListener& rl(*it);

            rl.proc(rl.userData, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
        }
    }

    // ----------------------------------------------------------------------------------------------------------------

    void run(const float** inputs, float** outputs, const uint32_t frames, const AudioTimeStamp* const inTimeStamp)
    {
       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
        if (fMidiEventCount != kMaxMidiEvents && fNotesRingBuffer.isDataAvailableForReading())
        {
            uint8_t midiData[3];
            const uint32_t frame = fMidiEventCount != 0 ? fMidiEvents[fMidiEventCount - 1].frame : 0;

            while (fNotesRingBuffer.isDataAvailableForReading())
            {
                if (! fNotesRingBuffer.readCustomData(midiData, 3))
                    break;

                MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]);
                midiEvent.frame = frame;
                midiEvent.size  = 3;
                std::memcpy(midiEvent.data, midiData, 3);

                if (fMidiEventCount == kMaxMidiEvents)
                    break;
            }
        }
       #endif

       #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
        fMidiOutputDataOffset = 0;
        fMidiOutputPackets->numPackets = 0;
       #endif

       #if DISTRHO_PLUGIN_WANT_TIMEPOS
        if (fHostCallbackInfo.beatAndTempoProc != nullptr ||
            fHostCallbackInfo.musicalTimeLocationProc != nullptr ||
            fHostCallbackInfo.transportStateProc != nullptr)
        {
            // reused values so we can check null and return value together
            Boolean b1 = false;
            Float32 f1 = 4.f; // initial value for beats per bar
            Float64 g1 = 0.0;
            Float64 g2 = 0.0;
            UInt32 u1 = 0;

            if (fHostCallbackInfo.musicalTimeLocationProc != nullptr
                && fHostCallbackInfo.musicalTimeLocationProc(fHostCallbackInfo.hostUserData,
                                                             nullptr, &f1, &u1, nullptr) == noErr)
            {
                fTimePosition.bbt.beatsPerBar = f1;
                fTimePosition.bbt.beatType    = u1;
            }
            else
            {
                fTimePosition.bbt.beatsPerBar = 4.f;
                fTimePosition.bbt.beatType    = 4.f;
            }

            if (fHostCallbackInfo.beatAndTempoProc != nullptr
                && fHostCallbackInfo.beatAndTempoProc(fHostCallbackInfo.hostUserData, &g1, &g2) == noErr)
            {
                const double beat = static_cast<int32_t>(g1);

                fTimePosition.bbt.valid = true;
                fTimePosition.bbt.bar   = static_cast<int32_t>(beat / f1) + 1;
                fTimePosition.bbt.beat  = static_cast<int32_t>(std::fmod(beat, f1)) + 1;
                fTimePosition.bbt.tick  = std::fmod(g1, 1.0) * 1920.0;
                fTimePosition.bbt.beatsPerMinute = g2;
            }
            else
            {
                fTimePosition.bbt.valid = false;
                fTimePosition.bbt.bar   = 1;
                fTimePosition.bbt.beat  = 1;
                fTimePosition.bbt.tick  = 0.0;
                fTimePosition.bbt.beatsPerMinute = 120.0;
            }

            if (fHostCallbackInfo.transportStateProc != nullptr
                && fHostCallbackInfo.transportStateProc(fHostCallbackInfo.hostUserData,
                                                        &b1, nullptr, &g1, nullptr, nullptr, nullptr) == noErr)
            {
                fTimePosition.playing = b1;
                fTimePosition.frame = static_cast<int64_t>(g1);
            }
            else
            {
                fTimePosition.playing = false;
                fTimePosition.frame = 0;
            }

            fTimePosition.bbt.barStartTick = kDefaultTicksPerBeat *
                                             fTimePosition.bbt.beatsPerBar *
                                             (fTimePosition.bbt.bar - 1);

            fPlugin.setTimePosition(fTimePosition);
        }
       #endif

       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
        fPlugin.run(inputs, outputs, frames, fMidiEvents, fMidiEventCount);
        fMidiEventCount = 0;
       #else
        fPlugin.run(inputs, outputs, frames);
       #endif

       #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
        if (fMidiOutputPackets != nullptr &&
            fMidiOutputPackets->numPackets != 0 &&
            fMidiOutput.midiOutputCallback != nullptr)
        {
            fMidiOutput.midiOutputCallback(fMidiOutput.userData, inTimeStamp, 0, fMidiOutputPackets);
        }
       #else
        // unused
        (void)inTimeStamp;
       #endif

        float value;
        AudioUnitEvent event;
        std::memset(&event, 0, sizeof(event));
        event.mEventType                      = kAudioUnitEvent_ParameterValueChange;
        event.mArgument.mParameter.mAudioUnit = fComponent;
        event.mArgument.mParameter.mScope     = kAudioUnitScope_Global;

        for (uint32_t i=0; i<fParameterCount; ++i)
        {
            if (fPlugin.isParameterOutputOrTrigger(i))
            {
                value = fPlugin.getParameterValue(i);

                if (d_isEqual(fLastParameterValues[i], value))
                    continue;

                fLastParameterValues[i] = value;

                // TODO flag param only, notify listeners later on bg thread (sem_post etc)
                event.mArgument.mParameter.mParameterID = i;
                AUEventListenerNotify(NULL, NULL, &event);
                notifyPropertyListeners('DPFp', kAudioUnitScope_Global, i);
            }
        }
    }

    void setLastRenderError(const OSStatus err)
    {
        if (fLastRenderError != noErr)
            return;

        fLastRenderError = err;
        notifyPropertyListeners(kAudioUnitProperty_LastRenderError, kAudioUnitScope_Global, 0);
    }

    // ----------------------------------------------------------------------------------------------------------------

   #if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0
    bool reallocAudioBufferList(const bool alloc)
    {
        if (fAudioBufferList != nullptr)
        {
            for (uint16_t i = 0; i < fAudioBufferList->mNumberBuffers; ++i)
                delete[] static_cast<float*>(fAudioBufferList->mBuffers[i].mData);
        }

      #ifdef DISTRHO_PLUGIN_EXTRA_IO
       #if DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
        const uint16_t numBuffers = std::max(fNumInputs, fNumOutputs);
       #elif DISTRHO_PLUGIN_NUM_INPUTS != 0
        const uint16_t numBuffers = fNumInputs;
       #else
        const uint16_t numBuffers = fNumOutputs;
       #endif
      #else
        constexpr const uint16_t numBuffers = d_max(DISTRHO_PLUGIN_NUM_INPUTS, DISTRHO_PLUGIN_NUM_OUTPUTS);
      #endif
        const uint32_t bufferSize = fPlugin.getBufferSize();

        if (! alloc)
        {
            std::free(fAudioBufferList);
            fAudioBufferList = nullptr;
            return true;
        }

        if (AudioBufferList* const abl = static_cast<AudioBufferList*>(
            std::realloc(fAudioBufferList, sizeof(uint32_t) + sizeof(AudioBuffer) * numBuffers)))
        {
            abl->mNumberBuffers = numBuffers;

            for (uint16_t i = 0; i < numBuffers; ++i)
            {
                abl->mBuffers[i].mNumberChannels = 1;
                abl->mBuffers[i].mData = new float[bufferSize];
                abl->mBuffers[i].mDataByteSize = sizeof(float) * bufferSize;
            }

            fAudioBufferList = abl;
            return true;
        }

        std::free(fAudioBufferList);
        fAudioBufferList = nullptr;
        return false;
    }
   #endif

    // ----------------------------------------------------------------------------------------------------------------

    CFMutableDictionaryRef retrieveClassInfo()
    {
        CFMutableDictionaryRef clsInfo = CFDictionaryCreateMutable(nullptr,
                                                                   0,
                                                                   &kCFTypeDictionaryKeyCallBacks,
                                                                   &kCFTypeDictionaryValueCallBacks);
        SInt32 value;

        value = 0;
        if (const CFNumberRef num = CFNumberCreate(nullptr, kCFNumberSInt32Type, &value))
        {
            CFDictionarySetValue(clsInfo, CFSTR(kAUPresetVersionKey), num);
            CFRelease(num);
        }

        value = kType;
        if (const CFNumberRef num = CFNumberCreate(nullptr, kCFNumberSInt32Type, &value))
        {
            CFDictionarySetValue(clsInfo, CFSTR(kAUPresetTypeKey), num);
            CFRelease(num);
        }

        value = kSubType;
        if (const CFNumberRef num = CFNumberCreate(nullptr, kCFNumberSInt32Type, &value))
        {
            CFDictionarySetValue(clsInfo, CFSTR(kAUPresetSubtypeKey), num);
            CFRelease(num);
        }

        value = kManufacturer;
        if (const CFNumberRef num = CFNumberCreate(nullptr, kCFNumberSInt32Type, &value))
        {
            CFDictionarySetValue(clsInfo, CFSTR(kAUPresetManufacturerKey), num);
            CFRelease(num);
        }

       #if DISTRHO_PLUGIN_WANT_PROGRAMS
        if (fCurrentProgram >= 0)
        {
            CFDictionarySetValue(clsInfo, CFSTR(kAUPresetNameKey), fFactoryPresetsData[fCurrentProgram].presetName);
        }
        else
       #endif
        {
            CFDictionarySetValue(clsInfo, CFSTR(kAUPresetNameKey), fUserPresetData.presetName);
        }

        if (const CFMutableDictionaryRef data = CFDictionaryCreateMutable(nullptr,
                                                                          0,
                                                                          &kCFTypeDictionaryKeyCallBacks,
                                                                          &kCFTypeDictionaryValueCallBacks))
        {
           #if DISTRHO_PLUGIN_WANT_PROGRAMS
            const SInt32 program = fCurrentProgram;
            if (const CFNumberRef programRef = CFNumberCreate(nullptr, kCFNumberSInt32Type, &program))
            {
               CFDictionarySetValue(data, CFSTR("program"), programRef);
               CFRelease(programRef);
            }
           #endif

           #if DISTRHO_PLUGIN_WANT_FULL_STATE
            // Update current state
            for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
            {
                const String& key(cit->first);
                fStateMap[key] = fPlugin.getStateValue(key);
            }
           #endif

           #if DISTRHO_PLUGIN_WANT_STATE
            if (const CFMutableArrayRef statesRef = CFArrayCreateMutable(nullptr,
                                                                         fStateCount,
                                                                         &kCFTypeArrayCallBacks))
            {
                for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
                {
                    const String& key(cit->first);
                    const String& value(cit->second);

                   #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS && ! DISTRHO_PLUGIN_HAS_UI
                    bool wantStateKey = true;

                    for (uint32_t i=0; i<fStateCount; ++i)
                    {
                        if (fPlugin.getStateKey(i) == key)
                        {
                            if (fPlugin.getStateHints(i) & kStateIsOnlyForUI)
                                wantStateKey = false;

                            break;
                        }
                    }

                    if (! wantStateKey)
                        continue;
                   #endif

                    CFStringRef keyRef = CFStringCreateWithCString(nullptr, key, kCFStringEncodingASCII);
                    CFStringRef valueRef = CFStringCreateWithCString(nullptr, value, kCFStringEncodingUTF8);

                    if (const CFDictionaryRef dictRef = CFDictionaryCreate(nullptr,
                                                                           reinterpret_cast<const void**>(&keyRef),
                                                                           reinterpret_cast<const void**>(&valueRef),
                                                                           1,
                                                                           &kCFTypeDictionaryKeyCallBacks,
                                                                           &kCFTypeDictionaryValueCallBacks))
                    {
                        CFArrayAppendValue(statesRef, dictRef);
                        CFRelease(dictRef);
                    }

                    CFRelease(keyRef);
                    CFRelease(valueRef);
                }

                CFDictionarySetValue(data, CFSTR("states"), statesRef);
                CFRelease(statesRef);
            }
           #endif

            if (const CFMutableArrayRef paramsRef = CFArrayCreateMutable(nullptr,
                                                                         fParameterCount,
                                                                         &kCFTypeArrayCallBacks))
            {
                for (uint32_t i=0; i<fParameterCount; ++i)
                {
                    if (fPlugin.isParameterOutputOrTrigger(i))
                        continue;

                    const float value = fPlugin.getParameterValue(i);

                    CFStringRef keyRef = CFStringCreateWithCString(nullptr,
                                                                   fPlugin.getParameterSymbol(i),
                                                                   kCFStringEncodingASCII);
                    CFNumberRef valueRef = CFNumberCreate(nullptr, kCFNumberFloat32Type, &value);

                    if (CFDictionaryRef dictRef = CFDictionaryCreate(nullptr,
                                                                     reinterpret_cast<const void**>(&keyRef),
                                                                     reinterpret_cast<const void**>(&valueRef),
                                                                     1,
                                                                     &kCFTypeDictionaryKeyCallBacks,
                                                                     &kCFTypeDictionaryValueCallBacks))
                    {
                        CFArrayAppendValue(paramsRef, dictRef);
                        CFRelease(dictRef);
                    }

                    CFRelease(keyRef);
                    CFRelease(valueRef);
                }

                CFDictionarySetValue(data, CFSTR("params"), paramsRef);
                CFRelease(paramsRef);
            }

            CFDictionarySetValue(clsInfo, CFSTR(kAUPresetDataKey), data);
            CFRelease(data);
        }

        return clsInfo;
    }

    void restoreClassInfo(const CFDictionaryRef clsInfo)
    {
        CFDictionaryRef data = nullptr;
        DISTRHO_SAFE_ASSERT_RETURN(CFDictionaryGetValueIfPresent(clsInfo,
                                                                 CFSTR(kAUPresetDataKey),
                                                                 reinterpret_cast<const void**>(&data)),);
        DISTRHO_SAFE_ASSERT_RETURN(CFGetTypeID(data) == CFDictionaryGetTypeID(),);

       #if DISTRHO_PLUGIN_WANT_PROGRAMS
        CFNumberRef programRef = nullptr;
        if (CFDictionaryGetValueIfPresent(data, CFSTR("program"), reinterpret_cast<const void**>(&programRef))
            && CFGetTypeID(programRef) == CFNumberGetTypeID())
        {
            SInt32 program = -1;
            if (CFNumberGetValue(programRef, kCFNumberSInt32Type, &program))
            {
                fCurrentProgram = program;

                if (program >= 0)
                {
                    fLastFactoryProgram = program;
                    fPlugin.loadProgram(fLastFactoryProgram);
                    notifyPropertyListeners('DPFo', kAudioUnitScope_Global, 0);
                }

                notifyPropertyListeners(kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0);
            }
        }
       #endif

       #if DISTRHO_PLUGIN_WANT_STATE
        CFArrayRef statesRef = nullptr;
        if (CFDictionaryGetValueIfPresent(data, CFSTR("states"), reinterpret_cast<const void**>(&statesRef))
            && CFGetTypeID(statesRef) == CFArrayGetTypeID())
        {
            const CFIndex numStates = CFArrayGetCount(statesRef);
            char* key = nullptr;
            char* value = nullptr;
            CFIndex keyLen = -1;
            CFIndex valueLen = -1;

            for (CFIndex i=0; i<numStates; ++i)
            {
                const CFDictionaryRef state = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(statesRef, i));
                DISTRHO_SAFE_ASSERT_BREAK(CFGetTypeID(state) == CFDictionaryGetTypeID());
                DISTRHO_SAFE_ASSERT_BREAK(CFDictionaryGetCount(state) == 1);

                CFStringRef keyRef = nullptr;
                CFStringRef valueRef = nullptr;
                CFDictionaryGetKeysAndValues(state,
                                             reinterpret_cast<const void**>(&keyRef),
                                             reinterpret_cast<const void**>(&valueRef));
                DISTRHO_SAFE_ASSERT_BREAK(keyRef != nullptr && CFGetTypeID(keyRef) == CFStringGetTypeID());
                DISTRHO_SAFE_ASSERT_BREAK(valueRef != nullptr && CFGetTypeID(valueRef) == CFStringGetTypeID());

                const CFIndex keyRefLen = CFStringGetLength(keyRef);
                if (keyLen < keyRefLen)
                {
                    keyLen = keyRefLen;
                    key = static_cast<char*>(std::realloc(key, keyLen + 1));
                }
                DISTRHO_SAFE_ASSERT_BREAK(CFStringGetCString(keyRef, key, keyLen + 1, kCFStringEncodingASCII));

                if (! fPlugin.wantStateKey(key))
                    continue;

                const CFIndex valueRefLen = CFStringGetLength(valueRef);
                if (valueLen < valueRefLen)
                {
                    valueLen = valueRefLen;
                    value = static_cast<char*>(std::realloc(value, valueLen + 1));
                }
                DISTRHO_SAFE_ASSERT_BREAK(CFStringGetCString(valueRef, value, valueLen + 1, kCFStringEncodingUTF8));

                const String dkey(key);
                fStateMap[dkey] = value;
                fPlugin.setState(key, value);

                for (uint32_t j=0; j<fStateCount; ++j)
                {
                    if (fPlugin.getStateKey(j) == key)
                    {
                        if ((fPlugin.getStateHints(i) & kStateIsOnlyForDSP) == 0x0)
                            notifyPropertyListeners('DPFs', kAudioUnitScope_Global, j);

                        break;
                    }
                }
            }

            std::free(key);
            std::free(value);
        }
       #endif

        CFArrayRef paramsRef = nullptr;
        if (CFDictionaryGetValueIfPresent(data, CFSTR("params"), reinterpret_cast<const void**>(&paramsRef))
            && CFGetTypeID(paramsRef) == CFArrayGetTypeID())
        {
            const CFIndex numParams = CFArrayGetCount(paramsRef);
            char* symbol = nullptr;
            CFIndex symbolLen = -1;

            for (CFIndex i=0; i<numParams; ++i)
            {
                const CFDictionaryRef param = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(paramsRef, i));
                DISTRHO_SAFE_ASSERT_BREAK(CFGetTypeID(param) == CFDictionaryGetTypeID());
                DISTRHO_SAFE_ASSERT_BREAK(CFDictionaryGetCount(param) == 1);

                CFStringRef keyRef = nullptr;
                CFNumberRef valueRef = nullptr;
                CFDictionaryGetKeysAndValues(param,
                                             reinterpret_cast<const void**>(&keyRef),
                                             reinterpret_cast<const void**>(&valueRef));
                DISTRHO_SAFE_ASSERT_BREAK(keyRef != nullptr && CFGetTypeID(keyRef) == CFStringGetTypeID());
                DISTRHO_SAFE_ASSERT_BREAK(valueRef != nullptr && CFGetTypeID(valueRef) == CFNumberGetTypeID());

                float value = 0.f;
                DISTRHO_SAFE_ASSERT_BREAK(CFNumberGetValue(valueRef, kCFNumberFloat32Type, &value));

                const CFIndex keyRefLen = CFStringGetLength(keyRef);
                if (symbolLen < keyRefLen)
                {
                    symbolLen = keyRefLen;
                    symbol = static_cast<char*>(std::realloc(symbol, symbolLen + 1));
                }
                DISTRHO_SAFE_ASSERT_BREAK(CFStringGetCString(keyRef, symbol, symbolLen + 1, kCFStringEncodingASCII));

                for (uint32_t j=0; j<fParameterCount; ++j)
                {
                    if (fPlugin.isParameterOutputOrTrigger(j))
                        continue;
                    if (fPlugin.getParameterSymbol(j) != symbol)
                        continue;

                    fLastParameterValues[j] = value;
                    fPlugin.setParameterValue(j, value);
                    notifyPropertyListeners('DPFp', kAudioUnitScope_Global, j);

                    if (fBypassParameterIndex == j)
                        notifyPropertyListeners(kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0);

                    break;
                }
            }

            std::free(symbol);
        }
    }

    // ----------------------------------------------------------------------------------------------------------------
    // DPF callbacks

   #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
    bool writeMidi(const MidiEvent& midiEvent)
    {
        DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN("MIDI output unsupported", fMidiOutput.midiOutputCallback != nullptr, false);
        DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN("Out of memory", fMidiOutputPackets != nullptr, false);

        if (fMidiOutputDataOffset + kMIDIPacketNonDataSize + midiEvent.size >= kMIDIPacketListMaxDataSize)
            return false;

        const uint8_t* const midiData = midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data;
        MIDIPacket* const packet = reinterpret_cast<MIDIPacket*>(
             reinterpret_cast<uint8_t*>(fMidiOutputPackets->packet) + fMidiOutputDataOffset);

        packet->timeStamp = midiEvent.frame;
        packet->length = midiEvent.size;
        std::memcpy(packet->data, midiData, midiEvent.size);

        ++fMidiOutputPackets->numPackets;
        fMidiOutputDataOffset += kMIDIPacketNonDataSize + midiEvent.size;
        return true;
    }

    static bool writeMidiCallback(void* const ptr, const MidiEvent& midiEvent)
    {
        return static_cast<PluginAU*>(ptr)->writeMidi(midiEvent);
    }
   #endif

   #if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST
    bool requestParameterValueChange(const uint32_t index, const float value)
    {
        AudioUnitEvent event;
        std::memset(&event, 0, sizeof(event));
        event.mEventType                        = kAudioUnitEvent_ParameterValueChange;
        event.mArgument.mParameter.mAudioUnit   = fComponent;
        event.mArgument.mParameter.mParameterID = index;
        event.mArgument.mParameter.mScope       = kAudioUnitScope_Global;

        fLastParameterValues[index] = value;
        AUEventListenerNotify(NULL, NULL, &event);
        notifyPropertyListeners('DPFp', kAudioUnitScope_Global, index);
        return true;
    }

    static bool requestParameterValueChangeCallback(void* const ptr, const uint32_t index, const float value)
    {
        return static_cast<PluginAU*>(ptr)->requestParameterValueChange(index, value);
    }
   #endif

   #if DISTRHO_PLUGIN_WANT_STATE
    bool updateState(const char* const key, const char* const newValue)
    {
        fPlugin.setState(key, newValue);

        for (uint32_t i=0; i<fStateCount; ++i)
        {
            if (fPlugin.getStateKey(i) == key)
            {
                const String dkey(key);
                fStateMap[dkey] = newValue;

                if ((fPlugin.getStateHints(i) & kStateIsOnlyForDSP) == 0x0)
                    notifyPropertyListeners('DPFs', kAudioUnitScope_Global, i);

                return true;
            }
        }

        d_stderr("Failed to find plugin state with key \"%s\"", key);
        return false;
    }

    static bool updateStateValueCallback(void* const ptr, const char* const key, const char* const newValue)
    {
        return static_cast<PluginAU*>(ptr)->updateState(key, newValue);
    }
   #endif

    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginAU)
};

// --------------------------------------------------------------------------------------------------------------------

struct AudioComponentPlugInInstance {
    AudioComponentPlugInInterface acpi;
    PluginAU* plugin;

    AudioComponentPlugInInstance() noexcept
        : acpi(),
          plugin(nullptr)
    {
        std::memset(&acpi, 0, sizeof(acpi));
        acpi.Open = Open;
        acpi.Close = Close;
        acpi.Lookup = Lookup;
        acpi.reserved = nullptr;
    }

    ~AudioComponentPlugInInstance()
    {
        delete plugin;
    }

    static OSStatus Open(void* const self, const AudioUnit component)
    {
        d_debug("AudioComponentPlugInInstance::Open(%p)", self);

        static_cast<AudioComponentPlugInInstance*>(self)->plugin = new PluginAU(component);
        return noErr;
    }

    static OSStatus Close(void* const self)
    {
        d_debug("AudioComponentPlugInInstance::Close(%p)", self);

        delete static_cast<AudioComponentPlugInInstance*>(self);
        return noErr;
    }

    static AudioComponentMethod Lookup(const SInt16 selector)
    {
        d_debug("AudioComponentPlugInInstance::Lookup(%3d:%s)", selector, AudioUnitSelector2Str(selector));

        switch (selector)
        {
        case kAudioUnitInitializeSelect:
            return reinterpret_cast<AudioComponentMethod>(Initialize);
        case kAudioUnitUninitializeSelect:
            return reinterpret_cast<AudioComponentMethod>(Uninitialize);
        case kAudioUnitGetPropertyInfoSelect:
            return reinterpret_cast<AudioComponentMethod>(GetPropertyInfo);
        case kAudioUnitGetPropertySelect:
            return reinterpret_cast<AudioComponentMethod>(GetProperty);
        case kAudioUnitSetPropertySelect:
            return reinterpret_cast<AudioComponentMethod>(SetProperty);
        case kAudioUnitAddPropertyListenerSelect:
            return reinterpret_cast<AudioComponentMethod>(AddPropertyListener);
        case kAudioUnitRemovePropertyListenerSelect:
            return reinterpret_cast<AudioComponentMethod>(RemovePropertyListener);
        case kAudioUnitRemovePropertyListenerWithUserDataSelect:
            return reinterpret_cast<AudioComponentMethod>(RemovePropertyListenerWithUserData);
        case kAudioUnitAddRenderNotifySelect:
            return reinterpret_cast<AudioComponentMethod>(AddRenderNotify);
        case kAudioUnitRemoveRenderNotifySelect:
            return reinterpret_cast<AudioComponentMethod>(RemoveRenderNotify);
        case kAudioUnitGetParameterSelect:
            return reinterpret_cast<AudioComponentMethod>(GetParameter);
        case kAudioUnitSetParameterSelect:
            return reinterpret_cast<AudioComponentMethod>(SetParameter);
        case kAudioUnitScheduleParametersSelect:
            return reinterpret_cast<AudioComponentMethod>(ScheduleParameters);
        case kAudioUnitRenderSelect:
            return reinterpret_cast<AudioComponentMethod>(Render);
        /*
        case kAudioUnitComplexRenderSelect:
            return reinterpret_cast<AudioComponentMethod>(ComplexRender);
        */
        case kAudioUnitResetSelect:
            return reinterpret_cast<AudioComponentMethod>(Reset);
        /*
        case kAudioUnitProcessSelect:
            return reinterpret_cast<AudioComponentMethod>(Process);
        case kAudioUnitProcessMultipleSelect:
            return reinterpret_cast<AudioComponentMethod>(ProcessMultiple);
        */
       #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
        case kMusicDeviceMIDIEventSelect:
            return reinterpret_cast<AudioComponentMethod>(MIDIEvent);
        case kMusicDeviceSysExSelect:
            return reinterpret_cast<AudioComponentMethod>(SysEx);
       #else
        case kMusicDeviceMIDIEventSelect:
        case kMusicDeviceSysExSelect:
            return nullptr;
       #endif
        }

        d_stdout("TODO Lookup(%3d:%s)", selector, AudioUnitSelector2Str(selector));
        return nullptr;
    }

    static OSStatus Initialize(AudioComponentPlugInInstance* const self)
    {
        d_debug("AudioComponentPlugInInstance::Initialize(%p)", self);
        return self->plugin->auInitialize();
    }

    static OSStatus Uninitialize(AudioComponentPlugInInstance* const self)
    {
        d_debug("AudioComponentPlugInInstance::Uninitialize(%p)", self);
        return self->plugin->auUninitialize();
    }

    static OSStatus GetPropertyInfo(AudioComponentPlugInInstance* const self,
                                    const AudioUnitPropertyID inProp,
                                    const AudioUnitScope inScope,
                                    const AudioUnitElement inElement,
                                    UInt32* const outDataSize,
                                    Boolean* const outWritable)
    {
        d_debug("AudioComponentPlugInInstance::GetPropertyInfo(%p, %d:%x:%s, %d:%s, %d, ...)",
                self, inProp, inProp, AudioUnitPropertyID2Str(inProp), inScope, AudioUnitScope2Str(inScope), inElement);

        UInt32 dataSize = 0;
        Boolean writable = false;
        const OSStatus res = self->plugin->auGetPropertyInfo(inProp, inScope, inElement, dataSize, writable);

        if (outDataSize != nullptr)
            *outDataSize = dataSize;

        if (outWritable != nullptr)
            *outWritable = writable;

        return res;
    }

    static OSStatus GetProperty(AudioComponentPlugInInstance* const self,
                                const AudioUnitPropertyID inProp,
                                const AudioUnitScope inScope,
                                const AudioUnitElement inElement,
                                void* const outData,
                                UInt32* const ioDataSize)
    {
       #ifdef DEBUG
        switch (inProp) {
        case kAudioUnitProperty_PresentPreset:
            break;
        default:
            d_debug("AudioComponentPlugInInstance::GetProperty(%p, %d:%x:%s, %d:%s, %d, ...)",
                    self, inProp, inProp, AudioUnitPropertyID2Str(inProp), inScope, AudioUnitScope2Str(inScope), inElement);
            break;
        }
       #endif
        DISTRHO_SAFE_ASSERT_RETURN(ioDataSize != nullptr, kAudio_ParamError);

        Boolean writable;
        UInt32 outDataSize = 0;
        OSStatus res;

        if (outData == nullptr)
        {
            res = self->plugin->auGetPropertyInfo(inProp, inScope, inElement, outDataSize, writable);
            *ioDataSize = outDataSize;
            return res;
        }

        const UInt32 inDataSize = *ioDataSize;
        if (inDataSize == 0)
            return kAudio_ParamError;

        res = self->plugin->auGetPropertyInfo(inProp, inScope, inElement, outDataSize, writable);

        if (res != noErr)
            return res;

        void* outBuffer;
        uint8_t* tmpBuffer;
        if (inDataSize < outDataSize)
        {
            tmpBuffer = new uint8_t[outDataSize];
            outBuffer = tmpBuffer;
        }
        else
        {
            tmpBuffer = nullptr;
            outBuffer = outData;
        }

        res = self->plugin->auGetProperty(inProp, inScope, inElement, outBuffer);

        if (res != noErr)
        {
            *ioDataSize = 0;
            return res;
        }

        if (tmpBuffer != nullptr)
        {
            memcpy(outData, tmpBuffer, inDataSize);
            delete[] tmpBuffer;
        }
        else
        {
            *ioDataSize = outDataSize;
        }

        return noErr;
    }

    static OSStatus SetProperty(AudioComponentPlugInInstance* const self,
                                const AudioUnitPropertyID inProp,
                                const AudioUnitScope inScope,
                                const AudioUnitElement inElement,
                                const void* const inData,
                                const UInt32 inDataSize)
    {
        d_debug("AudioComponentPlugInInstance::SetProperty(%p, %d:%x:%s, %d:%s, %d, %p, %u)",
                self, inProp, inProp, AudioUnitPropertyID2Str(inProp), inScope, AudioUnitScope2Str(inScope), inElement, inData, inDataSize);
        return self->plugin->auSetProperty(inProp, inScope, inElement, inData, inDataSize);
    }

    static OSStatus AddPropertyListener(AudioComponentPlugInInstance* const self,
                                        const AudioUnitPropertyID prop,
                                        const AudioUnitPropertyListenerProc proc,
                                        void* const userData)
    {
        d_debug("AudioComponentPlugInInstance::AddPropertyListener(%p, %d:%x:%s, %p, %p)",
                self, prop, prop, AudioUnitPropertyID2Str(prop), proc, userData);
        return self->plugin->auAddPropertyListener(prop, proc, userData);
    }

    static OSStatus RemovePropertyListener(AudioComponentPlugInInstance* const self,
                                           const AudioUnitPropertyID prop,
                                           const AudioUnitPropertyListenerProc proc)
    {
        d_debug("AudioComponentPlugInInstance::RemovePropertyListener(%p, %d:%x:%s, %p)",
                self, prop, prop, AudioUnitPropertyID2Str(prop), proc);
        return self->plugin->auRemovePropertyListener(prop, proc);
    }

    static OSStatus RemovePropertyListenerWithUserData(AudioComponentPlugInInstance* const self,
                                                       const AudioUnitPropertyID prop,
                                                       const AudioUnitPropertyListenerProc proc,
                                                       void* const userData)
    {
        d_debug("AudioComponentPlugInInstance::RemovePropertyListenerWithUserData(%p, %d:%x:%s, %p, %p)",
                self, prop, prop, AudioUnitPropertyID2Str(prop), proc, userData);
        return self->plugin->auRemovePropertyListenerWithUserData(prop, proc, userData);
    }

    static OSStatus AddRenderNotify(AudioComponentPlugInInstance* const self,
                                    const AURenderCallback proc,
                                    void* const userData)
    {
        d_debug("AudioComponentPlugInInstance::AddRenderNotify(%p, %p, %p)", self, proc, userData);
        return self->plugin->auAddRenderNotify(proc, userData);
    }

    static OSStatus RemoveRenderNotify(AudioComponentPlugInInstance* const self,
                                       const AURenderCallback proc,
                                       void* const userData)
    {
        d_debug("AudioComponentPlugInInstance::RemoveRenderNotify(%p, %p, %p)", self, proc, userData);
        return self->plugin->auRemoveRenderNotify(proc, userData);
    }

    static OSStatus GetParameter(AudioComponentPlugInInstance* const self,
                                 const AudioUnitParameterID param,
                                 const AudioUnitScope scope,
                                 const AudioUnitElement elem,
                                 AudioUnitParameterValue* const value)
    {
        d_debug("AudioComponentPlugInInstance::GetParameter(%p, %d, %d:%s, %d, %p)",
                self, param, scope, AudioUnitScope2Str(scope), elem, value);
        return self->plugin->auGetParameter(param, scope, elem, value);
    }

    static OSStatus SetParameter(AudioComponentPlugInInstance* const self,
                                 const AudioUnitParameterID param,
                                 const AudioUnitScope scope,
                                 const AudioUnitElement elem,
                                 const AudioUnitParameterValue value,
                                 const UInt32 bufferOffset)
    {
        d_debug("AudioComponentPlugInInstance::SetParameter(%p, %d %d:%s, %d, %f, %u)",
                self, param, scope, AudioUnitScope2Str(scope), elem, value, bufferOffset);
        return self->plugin->auSetParameter(param, scope, elem, value, bufferOffset);
    }

    static OSStatus ScheduleParameters(AudioComponentPlugInInstance* const self,
                                       const AudioUnitParameterEvent* const events,
                                       const UInt32 numEvents)
    {
        d_debug("AudioComponentPlugInInstance::ScheduleParameters(%p, %p, %u)", self, events, numEvents);
        return self->plugin->auScheduleParameters(events, numEvents);
    }

    static OSStatus Reset(AudioComponentPlugInInstance* const self,
                          const AudioUnitScope scope,
                          const AudioUnitElement elem)
    {
        d_debug("AudioComponentPlugInInstance::Reset(%p, %d:%s, %d)", self, scope, AudioUnitScope2Str(scope), elem);
        return self->plugin->auReset(scope, elem);
    }

    static OSStatus Render(AudioComponentPlugInInstance* const self,
                           AudioUnitRenderActionFlags* ioActionFlags,
                           const AudioTimeStamp* const inTimeStamp,
                           const UInt32 inOutputBusNumber,
                           const UInt32 inNumberFrames,
                           AudioBufferList* const ioData)
    {
        const AudioUnitRenderActionFlags actionFlags = ioActionFlags != nullptr ? *ioActionFlags : 0;

        if ((actionFlags & kAudioUnitRenderAction_DoNotCheckRenderArgs) == 0x0)
        {
            DISTRHO_SAFE_ASSERT_RETURN(inTimeStamp != nullptr, kAudio_ParamError);
            DISTRHO_SAFE_ASSERT_RETURN(ioData != nullptr, kAudio_ParamError);
        }

        return self->plugin->auRender(actionFlags, inTimeStamp, inOutputBusNumber, inNumberFrames, ioData);
    }

   #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    static OSStatus MIDIEvent(AudioComponentPlugInInstance* const self,
                              const UInt32 inStatus,
                              const UInt32 inData1,
                              const UInt32 inData2,
                              const UInt32 inOffsetSampleFrame)
    {
        return self->plugin->auMIDIEvent(inStatus, inData1, inData2, inOffsetSampleFrame);
    }

    static OSStatus SysEx(AudioComponentPlugInInstance* const self, const UInt8* const inData, const UInt32 inLength)
    {
        return self->plugin->auSysEx(inData, inLength);
    }
   #endif

    DISTRHO_DECLARE_NON_COPYABLE(AudioComponentPlugInInstance)
};

#if 0
static OSStatus FastDispatchGetParameter(void* const self,
                                         const AudioUnitParameterID param,
                                         const AudioUnitScope scope,
                                         const AudioUnitElement elem,
                                         Float32* const value)
{
    d_debug("FastDispatchGetParameter(%p, %d, %d:%s, %d, %p)",
            self, param, scope, AudioUnitScope2Str(scope), elem, value);
    return static_cast<AudioComponentPlugInInstance*>(self)->plugin->auGetParameter(param, scope, elem, value);
}

static OSStatus FastDispatchSetParameter(void* const self,
                                         const AudioUnitParameterID param,
                                         const AudioUnitScope scope,
                                         const AudioUnitElement elem,
                                         const Float32 value,
                                         const UInt32 bufferOffset)
{
    d_debug("FastDispatchSetParameter(%p, %d %d:%s, %d, %f, %u)",
            self, param, scope, AudioUnitScope2Str(scope), elem, value, bufferOffset);
    return static_cast<AudioComponentPlugInInstance*>(self)->plugin->auSetParameter(param, scope, elem, value, bufferOffset);
}

static OSStatus FastDispatchRender(void* const self,
                                   AudioUnitRenderActionFlags* const ioActionFlags,
                                   const AudioTimeStamp* const inTimeStamp,
                                   const UInt32 inBusNumber,
                                   const UInt32 inNumberFrames,
                                   AudioBufferList* const ioData)
{
    DISTRHO_SAFE_ASSERT_RETURN(inTimeStamp != nullptr, kAudio_ParamError);
    DISTRHO_SAFE_ASSERT_RETURN(ioData != nullptr, kAudio_ParamError);

    return static_cast<AudioComponentPlugInInstance*>(self)->plugin->auRender(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, *ioData);
}
#endif

// --------------------------------------------------------------------------------------------------------------------

END_NAMESPACE_DISTRHO

DISTRHO_PLUGIN_EXPORT
void* PluginAUFactory(const AudioComponentDescription* const desc)
{
    USE_NAMESPACE_DISTRHO

    DISTRHO_SAFE_ASSERT_UINT2_RETURN(desc->componentType == kType, desc->componentType, kType, nullptr);
    DISTRHO_SAFE_ASSERT_UINT2_RETURN(desc->componentSubType == kSubType, desc->componentSubType, kSubType, nullptr);
    DISTRHO_SAFE_ASSERT_UINT2_RETURN(desc->componentManufacturer == kManufacturer, desc->componentManufacturer, kManufacturer, nullptr);

    if (d_nextBufferSize == 0)
        d_nextBufferSize = 1156;

    if (d_isZero(d_nextSampleRate))
        d_nextSampleRate = 44100.0;

    if (d_nextBundlePath == nullptr)
    {
        static String bundlePath;

        String tmpPath(getBinaryFilename());
        tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP));
        tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP));

        if (tmpPath.endsWith(DISTRHO_OS_SEP_STR "Contents"))
        {
            tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP));
            bundlePath = tmpPath;
        }
        else
        {
            bundlePath = "error";
        }

        d_nextBundlePath = bundlePath.buffer();
    }

    d_nextCanRequestParameterValueChanges = true;

    return new AudioComponentPlugInInstance();
}

// --------------------------------------------------------------------------------------------------------------------
