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

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

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

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

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

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

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

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

#if ! DOXYGEN

namespace juce
{

//==============================================================================
#define JUCE_DECLARE_VST3_COM_REF_METHODS \
    Steinberg::uint32 PLUGIN_API addRef() override   { return (Steinberg::uint32) ++refCount; } \
    Steinberg::uint32 PLUGIN_API release() override  { const int r = --refCount; if (r == 0) delete this; return (Steinberg::uint32) r; }

#define JUCE_DECLARE_VST3_COM_QUERY_METHODS \
    Steinberg::tresult PLUGIN_API queryInterface (const Steinberg::TUID, void** obj) override \
    { \
        jassertfalse; \
        *obj = nullptr; \
        return Steinberg::kNotImplemented; \
    }

static bool doUIDsMatch (const Steinberg::TUID a, const Steinberg::TUID b) noexcept
{
    return std::memcmp (a, b, sizeof (Steinberg::TUID)) == 0;
}

#define TEST_FOR_AND_RETURN_IF_VALID(iidToTest, ClassType) \
    if (doUIDsMatch (iidToTest, ClassType::iid)) \
    { \
        addRef(); \
        *obj = dynamic_cast<ClassType*> (this); \
        return Steinberg::kResultOk; \
    }

#define TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID(iidToTest, CommonClassType, SourceClassType) \
    if (doUIDsMatch (iidToTest, CommonClassType::iid)) \
    { \
        addRef(); \
        *obj = (CommonClassType*) static_cast<SourceClassType*> (this); \
        return Steinberg::kResultOk; \
    }

//==============================================================================
#if VST_VERSION < 0x030608
 #define kAmbi1stOrderACN kBFormat
#endif

//==============================================================================
inline juce::String toString (const Steinberg::char8* string) noexcept      { return juce::String (string); }
inline juce::String toString (const Steinberg::char16* string) noexcept     { return juce::String (juce::CharPointer_UTF16 ((juce::CharPointer_UTF16::CharType*) string)); }

// NB: The casts are handled by a Steinberg::UString operator
inline juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); }
inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); }

inline void toString128 (Steinberg::Vst::String128 result, const char* source)
{
    Steinberg::UString (result, 128).fromAscii (source);
}

inline void toString128 (Steinberg::Vst::String128 result, const juce::String& source)
{
    Steinberg::UString (result, 128).fromAscii (source.toUTF8());
}

inline Steinberg::Vst::TChar* toString (const juce::String& source) noexcept
{
    return reinterpret_cast<Steinberg::Vst::TChar*> (source.toUTF16().getAddress());
}

#if JUCE_WINDOWS
 static const Steinberg::FIDString defaultVST3WindowType = Steinberg::kPlatformTypeHWND;
#else
 static const Steinberg::FIDString defaultVST3WindowType = Steinberg::kPlatformTypeNSView;
#endif


//==============================================================================
static inline Steinberg::Vst::SpeakerArrangement getArrangementForBus (Steinberg::Vst::IAudioProcessor* processor,
                                                                       bool isInput, int busIndex)
{
    Steinberg::Vst::SpeakerArrangement arrangement = Steinberg::Vst::SpeakerArr::kEmpty;

    if (processor != nullptr)
        processor->getBusArrangement (isInput ? Steinberg::Vst::kInput : Steinberg::Vst::kOutput,
                                      (Steinberg::int32) busIndex, arrangement);

    return arrangement;
}

/** For the sake of simplicity, there can only be 1 arrangement type per channel count.
    i.e.: 4 channels == k31Cine OR k40Cine
*/
static inline Steinberg::Vst::SpeakerArrangement getArrangementForNumChannels (int numChannels) noexcept
{
    using namespace Steinberg::Vst::SpeakerArr;

    switch (numChannels)
    {
        case 0:     return kEmpty;
        case 1:     return kMono;
        case 2:     return kStereo;
        case 3:     return k30Cine;
        case 4:     return k31Cine;
        case 5:     return k50;
        case 6:     return k51;
        case 7:     return k61Cine;
        case 8:     return k71CineFullFront;
        case 9:     return k90;
        case 10:    return k91;
        case 11:    return k101;
        case 12:    return k111;
        case 13:    return k130;
        case 14:    return k131;
       #if VST_VERSION >= 0x030608
        case 16:    return kAmbi3rdOrderACN;
       #endif
        case 24:    return (Steinberg::Vst::SpeakerArrangement) 1929904127; // k222
        default:    break;
    }

    jassert (numChannels >= 0);

    juce::BigInteger bi;
    bi.setRange (0, jmin (numChannels, (int) (sizeof (Steinberg::Vst::SpeakerArrangement) * 8)), true);
    return (Steinberg::Vst::SpeakerArrangement) bi.toInt64();
}

static inline Steinberg::Vst::Speaker getSpeakerType (const AudioChannelSet& set, AudioChannelSet::ChannelType type) noexcept
{
    switch (type)
    {
        case AudioChannelSet::left:              return Steinberg::Vst::kSpeakerL;
        case AudioChannelSet::right:             return Steinberg::Vst::kSpeakerR;
        case AudioChannelSet::centre:            return (set == AudioChannelSet::mono() ? Steinberg::Vst::kSpeakerM : Steinberg::Vst::kSpeakerC);

        case AudioChannelSet::LFE:               return Steinberg::Vst::kSpeakerLfe;
        case AudioChannelSet::leftSurround:      return Steinberg::Vst::kSpeakerLs;
        case AudioChannelSet::rightSurround:     return Steinberg::Vst::kSpeakerRs;
        case AudioChannelSet::leftCentre:        return Steinberg::Vst::kSpeakerLc;
        case AudioChannelSet::rightCentre:       return Steinberg::Vst::kSpeakerRc;
        case AudioChannelSet::centreSurround:    return Steinberg::Vst::kSpeakerCs;
        case AudioChannelSet::leftSurroundSide:  return (1ull << 26); /* kSpeakerLcs */
        case AudioChannelSet::rightSurroundSide: return (1ull << 27); /* kSpeakerRcs */
        case AudioChannelSet::topMiddle:         return (1ull << 11); /* kSpeakerTm */
        case AudioChannelSet::topFrontLeft:      return Steinberg::Vst::kSpeakerTfl;
        case AudioChannelSet::topFrontCentre:    return Steinberg::Vst::kSpeakerTfc;
        case AudioChannelSet::topFrontRight:     return Steinberg::Vst::kSpeakerTfr;
        case AudioChannelSet::topRearLeft:       return Steinberg::Vst::kSpeakerTrl;
        case AudioChannelSet::topRearCentre:     return Steinberg::Vst::kSpeakerTrc;
        case AudioChannelSet::topRearRight:      return Steinberg::Vst::kSpeakerTrr;
        case AudioChannelSet::LFE2:              return Steinberg::Vst::kSpeakerLfe2;
        case AudioChannelSet::leftSurroundRear:  return Steinberg::Vst::kSpeakerSl;
        case AudioChannelSet::rightSurroundRear: return Steinberg::Vst::kSpeakerSr;
        case AudioChannelSet::wideLeft:          return Steinberg::Vst::kSpeakerPl;
        case AudioChannelSet::wideRight:         return Steinberg::Vst::kSpeakerPr;
        case AudioChannelSet::ambisonicACN0:     return (1ull << 20); /* kSpeakerACN0 */
        case AudioChannelSet::ambisonicACN1:     return (1ull << 21); /* kSpeakerACN1 */
        case AudioChannelSet::ambisonicACN2:     return (1ull << 22); /* kSpeakerACN2 */
        case AudioChannelSet::ambisonicACN3:     return (1ull << 23); /* kSpeakerACN3 */
        case AudioChannelSet::ambisonicACN4:     return (1ull << 38); /* kSpeakerACN4 */
        case AudioChannelSet::ambisonicACN5:     return (1ull << 39); /* kSpeakerACN5 */
        case AudioChannelSet::ambisonicACN6:     return (1ull << 40); /* kSpeakerACN6 */
        case AudioChannelSet::ambisonicACN7:     return (1ull << 41); /* kSpeakerACN7 */
        case AudioChannelSet::ambisonicACN8:     return (1ull << 42); /* kSpeakerACN8 */
        case AudioChannelSet::ambisonicACN9:     return (1ull << 43); /* kSpeakerACN9 */
        case AudioChannelSet::ambisonicACN10:    return (1ull << 44); /* kSpeakerACN10 */
        case AudioChannelSet::ambisonicACN11:    return (1ull << 45); /* kSpeakerACN11 */
        case AudioChannelSet::ambisonicACN12:    return (1ull << 46); /* kSpeakerACN12 */
        case AudioChannelSet::ambisonicACN13:    return (1ull << 47); /* kSpeakerACN13 */
        case AudioChannelSet::ambisonicACN14:    return (1ull << 48); /* kSpeakerACN14 */
        case AudioChannelSet::ambisonicACN15:    return (1ull << 49); /* kSpeakerACN15 */
        case AudioChannelSet::topSideLeft:       return (1ull << 24); /* kSpeakerTsl */
        case AudioChannelSet::topSideRight:      return (1ull << 25); /* kSpeakerTsr */

        case AudioChannelSet::discreteChannel0:  return Steinberg::Vst::kSpeakerM;
        default:
            break;
    }


    switch (static_cast<int> (type))
    {
        case (int) AudioChannelSet::discreteChannel0 + 3: return (1ull << 28); /* kSpeakerBfl */
        case (int) AudioChannelSet::discreteChannel0 + 4: return (1ull << 29); /* kSpeakerBfc */
        case (int) AudioChannelSet::discreteChannel0 + 5: return (1ull << 30); /* kSpeakerBfr */
        default:
            break;
    }

    auto channelIndex = static_cast<Steinberg::Vst::Speaker> (type) - (static_cast<Steinberg::Vst::Speaker> (AudioChannelSet::discreteChannel0) + 6ull);
    return (1ull << (channelIndex + 33ull /* last speaker in vst layout + 1 */));
}

static inline AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::SpeakerArrangement arr, Steinberg::Vst::Speaker type) noexcept
{
    switch (type)
    {
        case Steinberg::Vst::kSpeakerL:     return AudioChannelSet::left;
        case Steinberg::Vst::kSpeakerR:     return AudioChannelSet::right;
        case Steinberg::Vst::kSpeakerC:     return AudioChannelSet::centre;
        case Steinberg::Vst::kSpeakerLfe:   return AudioChannelSet::LFE;
        case Steinberg::Vst::kSpeakerLs:    return AudioChannelSet::leftSurround;
        case Steinberg::Vst::kSpeakerRs:    return AudioChannelSet::rightSurround;
        case Steinberg::Vst::kSpeakerLc:    return AudioChannelSet::leftCentre;
        case Steinberg::Vst::kSpeakerRc:    return AudioChannelSet::rightCentre;
        case Steinberg::Vst::kSpeakerCs:    return AudioChannelSet::centreSurround;
        case Steinberg::Vst::kSpeakerSl:    return AudioChannelSet::leftSurroundRear;
        case Steinberg::Vst::kSpeakerSr:    return AudioChannelSet::rightSurroundRear;
        case (1ull << 11):  return AudioChannelSet::topMiddle;  /* kSpeakerTm */
        case Steinberg::Vst::kSpeakerTfl:   return AudioChannelSet::topFrontLeft;
        case Steinberg::Vst::kSpeakerTfc:   return AudioChannelSet::topFrontCentre;
        case Steinberg::Vst::kSpeakerTfr:   return AudioChannelSet::topFrontRight;
        case Steinberg::Vst::kSpeakerTrl:   return AudioChannelSet::topRearLeft;
        case Steinberg::Vst::kSpeakerTrc:   return AudioChannelSet::topRearCentre;
        case Steinberg::Vst::kSpeakerTrr:   return AudioChannelSet::topRearRight;
        case Steinberg::Vst::kSpeakerLfe2:  return AudioChannelSet::LFE2;
        case (1ull << 19):                  return ((arr & Steinberg::Vst::kSpeakerC) != 0 ? AudioChannelSet::discreteChannel0 : AudioChannelSet::centre);
        case (1ull << 20):                  return AudioChannelSet::ambisonicACN0;  /* kSpeakerACN0 */
        case (1ull << 21):                  return AudioChannelSet::ambisonicACN1;  /* kSpeakerACN1 */
        case (1ull << 22):                  return AudioChannelSet::ambisonicACN2;  /* kSpeakerACN2 */
        case (1ull << 23):                  return AudioChannelSet::ambisonicACN3;  /* kSpeakerACN3 */
        case (1ull << 38):                  return AudioChannelSet::ambisonicACN4;  /* kSpeakerACN4 */
        case (1ull << 39):                  return AudioChannelSet::ambisonicACN5;  /* kSpeakerACN5 */
        case (1ull << 40):                  return AudioChannelSet::ambisonicACN6;  /* kSpeakerACN6 */
        case (1ull << 41):                  return AudioChannelSet::ambisonicACN7;  /* kSpeakerACN7 */
        case (1ull << 42):                  return AudioChannelSet::ambisonicACN8;  /* kSpeakerACN8 */
        case (1ull << 43):                  return AudioChannelSet::ambisonicACN9;  /* kSpeakerACN9 */
        case (1ull << 44):                  return AudioChannelSet::ambisonicACN10; /* kSpeakerACN10 */
        case (1ull << 45):                  return AudioChannelSet::ambisonicACN11; /* kSpeakerACN11 */
        case (1ull << 46):                  return AudioChannelSet::ambisonicACN12; /* kSpeakerACN12 */
        case (1ull << 47):                  return AudioChannelSet::ambisonicACN13; /* kSpeakerACN13 */
        case (1ull << 48):                  return AudioChannelSet::ambisonicACN14; /* kSpeakerACN14 */
        case (1ull << 49):                  return AudioChannelSet::ambisonicACN15; /* kSpeakerACN15 */
        case (1ull << 24):                  return AudioChannelSet::topSideLeft;  /* kSpeakerTsl */
        case (1ull << 25):                  return AudioChannelSet::topSideRight; /* kSpeakerTsr */
        case (1ull << 26):                  return AudioChannelSet::leftSurroundSide;  /* kSpeakerLcs */
        case (1ull << 27):                  return AudioChannelSet::rightSurroundSide; /* kSpeakerRcs */
        case (1ull << 28):                  return static_cast<AudioChannelSet::ChannelType> ((int)AudioChannelSet::discreteChannel0 + 3); /* kSpeakerBfl */
        case (1ull << 29):                  return static_cast<AudioChannelSet::ChannelType> ((int)AudioChannelSet::discreteChannel0 + 4); /* kSpeakerBfc */
        case (1ull << 30):                  return static_cast<AudioChannelSet::ChannelType> ((int)AudioChannelSet::discreteChannel0 + 5); /* kSpeakerBfr */
        case Steinberg::Vst::kSpeakerPl:    return AudioChannelSet::wideLeft;
        case Steinberg::Vst::kSpeakerPr:    return AudioChannelSet::wideRight;
        default: break;
    }

    auto channelType = BigInteger (static_cast<int64> (type)).findNextSetBit (0);

    // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum
    jassert (channelType >= 33);

    return static_cast<AudioChannelSet::ChannelType> (static_cast<int> (AudioChannelSet::discreteChannel0) + 6 + (channelType - 33));
}

static inline Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept
{
    using namespace Steinberg::Vst::SpeakerArr;

    if      (channels == AudioChannelSet::disabled())            return kEmpty;
    else if (channels == AudioChannelSet::mono())                return kMono;
    else if (channels == AudioChannelSet::stereo())              return kStereo;
    else if (channels == AudioChannelSet::createLCR())           return k30Cine;
    else if (channels == AudioChannelSet::createLRS())           return k30Music;
    else if (channels == AudioChannelSet::createLCRS())          return k40Cine;
    else if (channels == AudioChannelSet::create5point0())       return k50;
    else if (channels == AudioChannelSet::create5point1())       return k51;
    else if (channels == AudioChannelSet::create6point0())       return k60Cine;
    else if (channels == AudioChannelSet::create6point1())       return k61Cine;
    else if (channels == AudioChannelSet::create6point0Music())  return k60Music;
    else if (channels == AudioChannelSet::create6point1Music())  return k61Music;
    else if (channels == AudioChannelSet::create7point0())       return k70Music;
    else if (channels == AudioChannelSet::create7point0SDDS())   return k70Cine;
    else if (channels == AudioChannelSet::create7point1())       return k71CineSideFill;
    else if (channels == AudioChannelSet::create7point1SDDS())   return k71Cine;
    else if (channels == AudioChannelSet::ambisonic())           return kAmbi1stOrderACN;
    else if (channels == AudioChannelSet::quadraphonic())        return k40Music;
    else if (channels == AudioChannelSet::create7point0point2()) return k71_2 & ~(Steinberg::Vst::kSpeakerLfe);
    else if (channels == AudioChannelSet::create7point1point2()) return k71_2;
    else if (channels == AudioChannelSet::ambisonic (0))         return (1ull << 20);
    else if (channels == AudioChannelSet::ambisonic (1))         return (1ull << 20) | (1ull << 21) | (1ull << 22) | (1ull << 23);
   #if VST_VERSION >= 0x030608
    else if (channels == AudioChannelSet::ambisonic (2))         return kAmbi2cdOrderACN;
    else if (channels == AudioChannelSet::ambisonic (3))         return kAmbi3rdOrderACN;
   #endif

    Steinberg::Vst::SpeakerArrangement result = 0;

    Array<AudioChannelSet::ChannelType> types (channels.getChannelTypes());

    for (int i = 0; i < types.size(); ++i)
        result |= getSpeakerType (channels, types.getReference(i));

    return result;
}

static inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept
{
    using namespace Steinberg::Vst::SpeakerArr;

    if      (arr == kEmpty)                                          return AudioChannelSet::disabled();
    else if (arr == kMono)                                           return AudioChannelSet::mono();
    else if (arr == kStereo)                                         return AudioChannelSet::stereo();
    else if (arr == k30Cine)                                         return AudioChannelSet::createLCR();
    else if (arr == k30Music)                                        return AudioChannelSet::createLRS();
    else if (arr == k40Cine)                                         return AudioChannelSet::createLCRS();
    else if (arr == k50)                                             return AudioChannelSet::create5point0();
    else if (arr == k51)                                             return AudioChannelSet::create5point1();
    else if (arr == k60Cine)                                         return AudioChannelSet::create6point0();
    else if (arr == k61Cine)                                         return AudioChannelSet::create6point1();
    else if (arr == k60Music)                                        return AudioChannelSet::create6point0Music();
    else if (arr == k61Music)                                        return AudioChannelSet::create6point1Music();
    else if (arr == k70Music)                                        return AudioChannelSet::create7point0();
    else if (arr == k70Cine)                                         return AudioChannelSet::create7point0SDDS();
    else if (arr == k71CineSideFill)                                 return AudioChannelSet::create7point1();
    else if (arr == k71Cine)                                         return AudioChannelSet::create7point1SDDS();
    else if (arr == kAmbi1stOrderACN)                                return AudioChannelSet::ambisonic();
    else if (arr == k40Music)                                        return AudioChannelSet::quadraphonic();
    else if (arr == k71_2)                                           return AudioChannelSet::create7point1point2();
    else if (arr == (k71_2 & ~(Steinberg::Vst::kSpeakerLfe)))        return AudioChannelSet::create7point0point2();
    else if (arr == (1 << 20))                                       return AudioChannelSet::ambisonic (0);
    else if (arr == ((1 << 20) | (1 << 21) | (1 << 22) | (1 << 23))) return AudioChannelSet::ambisonic (1);
   #if VST_VERSION >= 0x030608
    else if (arr == kAmbi2cdOrderACN)                                return AudioChannelSet::ambisonic (2);
    else if (arr == kAmbi3rdOrderACN)                                return AudioChannelSet::ambisonic (3);
   #endif

    AudioChannelSet result;

    BigInteger vstChannels (static_cast<int64> (arr));
    for (auto bit = vstChannels.findNextSetBit (0); bit != -1; bit = vstChannels.findNextSetBit (bit + 1))
    {
        AudioChannelSet::ChannelType channelType = getChannelType (arr, 1ull << static_cast<uint64> (bit));
        if (channelType != AudioChannelSet::unknown)
            result.addChannel (channelType);
    }

    // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum
    jassert (result.size() == vstChannels.countNumberOfSetBits());

    return result;
}

//==============================================================================
template <class ObjectType>
class ComSmartPtr
{
public:
    ComSmartPtr() noexcept : source (nullptr) {}
    ComSmartPtr (ObjectType* object, bool autoAddRef = true) noexcept  : source (object)    { if (source != nullptr && autoAddRef) source->addRef(); }
    ComSmartPtr (const ComSmartPtr& other) noexcept : source (other.source)                 { if (source != nullptr) source->addRef(); }
    ~ComSmartPtr()                                                                          { if (source != nullptr) source->release(); }

    operator ObjectType*() const noexcept    { return source; }
    ObjectType* get() const noexcept         { return source; }
    ObjectType& operator*() const noexcept   { return *source; }
    ObjectType* operator->() const noexcept  { return source; }

    ComSmartPtr& operator= (const ComSmartPtr& other)       { return operator= (other.source); }

    ComSmartPtr& operator= (ObjectType* const newObjectToTakePossessionOf)
    {
        ComSmartPtr p (newObjectToTakePossessionOf);
        std::swap (p.source, source);
        return *this;
    }

    bool operator== (ObjectType* const other) noexcept { return source == other; }
    bool operator!= (ObjectType* const other) noexcept { return source != other; }

    bool loadFrom (Steinberg::FUnknown* o)
    {
        *this = nullptr;
        return o != nullptr && o->queryInterface (ObjectType::iid, (void**) &source) == Steinberg::kResultOk;
    }

    bool loadFrom (Steinberg::IPluginFactory* factory, const Steinberg::TUID& uuid)
    {
        jassert (factory != nullptr);
        *this = nullptr;
        return factory->createInstance (uuid, ObjectType::iid, (void**) &source) == Steinberg::kResultOk;
    }

private:
    ObjectType* source;
};

//==============================================================================
class MidiEventList  : public Steinberg::Vst::IEventList
{
public:
    MidiEventList() {}
    virtual ~MidiEventList() {}

    JUCE_DECLARE_VST3_COM_REF_METHODS
    JUCE_DECLARE_VST3_COM_QUERY_METHODS

    //==============================================================================
    void clear()
    {
        events.clearQuick();
    }

    Steinberg::int32 PLUGIN_API getEventCount() override
    {
        return (Steinberg::int32) events.size();
    }

    // NB: This has to cope with out-of-range indexes from some plugins.
    Steinberg::tresult PLUGIN_API getEvent (Steinberg::int32 index, Steinberg::Vst::Event& e) override
    {
        if (isPositiveAndBelow ((int) index, events.size()))
        {
            e = events.getReference ((int) index);
            return Steinberg::kResultTrue;
        }

        return Steinberg::kResultFalse;
    }

    Steinberg::tresult PLUGIN_API addEvent (Steinberg::Vst::Event& e) override
    {
        events.add (e);
        return Steinberg::kResultTrue;
    }

    //==============================================================================
    static void toMidiBuffer (MidiBuffer& result, Steinberg::Vst::IEventList& eventList)
    {
        const int32 numEvents = eventList.getEventCount();

        for (Steinberg::int32 i = 0; i < numEvents; ++i)
        {
            Steinberg::Vst::Event e;

            if (eventList.getEvent (i, e) == Steinberg::kResultOk)
            {
                switch (e.type)
                {
                    case Steinberg::Vst::Event::kNoteOnEvent:
                        result.addEvent (MidiMessage::noteOn (createSafeChannel (e.noteOn.channel),
                                                              createSafeNote (e.noteOn.pitch),
                                                              (Steinberg::uint8) denormaliseToMidiValue (e.noteOn.velocity)),
                                         e.sampleOffset);
                        break;

                    case Steinberg::Vst::Event::kNoteOffEvent:
                        result.addEvent (MidiMessage::noteOff (createSafeChannel (e.noteOff.channel),
                                                               createSafeNote (e.noteOff.pitch),
                                                               (Steinberg::uint8) denormaliseToMidiValue (e.noteOff.velocity)),
                                         e.sampleOffset);
                        break;

                    case Steinberg::Vst::Event::kPolyPressureEvent:
                        result.addEvent (MidiMessage::aftertouchChange (createSafeChannel (e.polyPressure.channel),
                                                                        createSafeNote (e.polyPressure.pitch),
                                                                        denormaliseToMidiValue (e.polyPressure.pressure)),
                                         e.sampleOffset);
                        break;

                    case Steinberg::Vst::Event::kDataEvent:
                        result.addEvent (MidiMessage::createSysExMessage (e.data.bytes, (int) e.data.size),
                                         e.sampleOffset);
                        break;

                    default:
                        break;
                }
            }
        }
    }

    static void toEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer,
                             Steinberg::Vst::IParameterChanges* parameterChanges = nullptr,
                             Steinberg::Vst::IMidiMapping* midiMapping = nullptr)
    {
        MidiBuffer::Iterator iterator (midiBuffer);
        const uint8* midiEventData = nullptr;
        int midiEventSize = 0;
        int midiEventPosition = 0;

        enum { maxNumEvents = 2048 }; // Steinberg's Host Checker states that no more than 2048 events are allowed at once
        int numEvents = 0;

        while (iterator.getNextEvent (midiEventData, midiEventSize, midiEventPosition))
        {
            if (++numEvents > maxNumEvents)
                break;

            MidiMessage msg (midiEventData, midiEventSize);

            if (midiMapping != nullptr && parameterChanges != nullptr)
            {
                Vst3MidiControlEvent controlEvent;

                if (toVst3ControlEvent (msg, controlEvent))
                {
                    Steinberg::Vst::ParamID controlParamID;

                    if (midiMapping->getMidiControllerAssignment (0, createSafeChannel (msg.getChannel()),
                                                                  controlEvent.controllerNumber,
                                                                  controlParamID) == Steinberg::kResultOk)
                    {
                        Steinberg::int32 ignore;

                        if (auto* queue = parameterChanges->addParameterData (controlParamID, ignore))
                            queue->addPoint (midiEventPosition, controlEvent.paramValue, ignore);
                    }

                    continue;
                }
            }

            Steinberg::Vst::Event e = { 0 };

            if (msg.isNoteOn())
            {
                e.type              = Steinberg::Vst::Event::kNoteOnEvent;
                e.noteOn.channel    = createSafeChannel (msg.getChannel());
                e.noteOn.pitch      = createSafeNote (msg.getNoteNumber());
                e.noteOn.velocity   = normaliseMidiValue (msg.getVelocity());
                e.noteOn.length     = 0;
                e.noteOn.tuning     = 0.0f;
                e.noteOn.noteId     = -1;
            }
            else if (msg.isNoteOff())
            {
                e.type              = Steinberg::Vst::Event::kNoteOffEvent;
                e.noteOff.channel   = createSafeChannel (msg.getChannel());
                e.noteOff.pitch     = createSafeNote (msg.getNoteNumber());
                e.noteOff.velocity  = normaliseMidiValue (msg.getVelocity());
                e.noteOff.tuning    = 0.0f;
                e.noteOff.noteId    = -1;
            }
            else if (msg.isSysEx())
            {
                e.type          = Steinberg::Vst::Event::kDataEvent;
                e.data.bytes    = midiEventData + 1;
                e.data.size     = (uint32) msg.getSysExDataSize();
                e.data.type     = Steinberg::Vst::DataEvent::kMidiSysEx;
            }
            else if (msg.isChannelPressure())
            {
                e.type                   = Steinberg::Vst::Event::kPolyPressureEvent;
                e.polyPressure.channel   = createSafeChannel (msg.getChannel());
                e.polyPressure.pitch     = createSafeNote (msg.getNoteNumber());
                e.polyPressure.pressure  = normaliseMidiValue (msg.getChannelPressureValue());
            }
            else
            {
                continue;
            }

            e.busIndex = 0;
            e.sampleOffset = midiEventPosition;

            result.addEvent (e);
        }
    }

private:
    Array<Steinberg::Vst::Event, CriticalSection> events;
    Atomic<int> refCount;

    static Steinberg::int16 createSafeChannel (int channel) noexcept  { return (Steinberg::int16) jlimit (0, 15, channel - 1); }
    static int createSafeChannel (Steinberg::int16 channel) noexcept  { return (int) jlimit (1, 16, channel + 1); }

    static Steinberg::int16 createSafeNote (int note) noexcept        { return (Steinberg::int16) jlimit (0, 127, note); }
    static int createSafeNote (Steinberg::int16 note) noexcept        { return jlimit (0, 127, (int) note); }

    static float normaliseMidiValue (int value) noexcept              { return jlimit (0.0f, 1.0f, (float) value / 127.0f); }
    static int denormaliseToMidiValue (float value) noexcept          { return roundToInt (jlimit (0.0f, 127.0f, value * 127.0f)); }

    //==============================================================================
    struct Vst3MidiControlEvent
    {
        Steinberg::Vst::CtrlNumber controllerNumber;
        Steinberg::Vst::ParamValue paramValue;
    };

    static bool toVst3ControlEvent (const MidiMessage& msg, Vst3MidiControlEvent& result)
    {
        result.controllerNumber = -1;

        if      (msg.isController())        result = { (Steinberg::Vst::CtrlNumber) msg.getControllerNumber(), msg.getControllerValue() / 127.0};
        else if (msg.isPitchWheel())        result = { Steinberg::Vst::kPitchBend, msg.getPitchWheelValue() / 16383.0};
        else if (msg.isAftertouch())        result = { Steinberg::Vst::kAfterTouch, msg.getAfterTouchValue() / 127.0};

        return (result.controllerNumber != -1);
    }

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiEventList)
};

//==============================================================================
template <typename FloatType>
struct VST3BufferExchange
{
    using Bus = Array<FloatType*>;
    using BusMap = Array<Bus>;

    static inline void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, float** raw)  { vstBuffers.channelBuffers32 = raw; }
    static inline void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, double** raw) { vstBuffers.channelBuffers64 = raw; }

    /** Assigns a series of AudioBuffer's channels to an AudioBusBuffers'
        @warning For speed, does not check the channel count and offsets according to the AudioBuffer
    */
    static void associateBufferTo (Steinberg::Vst::AudioBusBuffers& vstBuffers,
                                   Bus& bus,
                                   AudioBuffer<FloatType>& buffer,
                                   int numChannels, int channelStartOffset,
                                   int sampleOffset = 0)
    {
        const int channelEnd = numChannels + channelStartOffset;
        jassert (channelEnd >= 0 && channelEnd <= buffer.getNumChannels());

        bus.clearQuick();

        for (int i = channelStartOffset; i < channelEnd; ++i)
            bus.add (buffer.getWritePointer (i, sampleOffset));

        assignRawPointer (vstBuffers, (numChannels > 0 ? bus.getRawDataPointer() : nullptr));
        vstBuffers.numChannels      = numChannels;
        vstBuffers.silenceFlags     = 0;
    }

    static void mapArrangementToBuses (int& channelIndexOffset, int index,
                                        Array<Steinberg::Vst::AudioBusBuffers>& result,
                                        BusMap& busMapToUse, const AudioChannelSet& arrangement,
                                        AudioBuffer<FloatType>& source)
    {
        const int numChansForBus = arrangement.size();

        if (index >= result.size())
            result.add (Steinberg::Vst::AudioBusBuffers());

        if (index >= busMapToUse.size())
            busMapToUse.add (Bus());

        associateBufferTo (result.getReference (index),
                           busMapToUse.getReference (index),
                           source, numChansForBus, channelIndexOffset);

        channelIndexOffset += numChansForBus;
    }

    static inline void mapBufferToBuses (Array<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse,
                                          const Array<AudioChannelSet>& arrangements,
                                          AudioBuffer<FloatType>& source)
    {
        int channelIndexOffset = 0;

        for (int i = 0; i < arrangements.size(); ++i)
            mapArrangementToBuses (channelIndexOffset, i, result, busMapToUse,
                                    arrangements.getUnchecked (i), source);
    }

    static inline void mapBufferToBuses (Array<Steinberg::Vst::AudioBusBuffers>& result,
                                          Steinberg::Vst::IAudioProcessor& processor,
                                          BusMap& busMapToUse, bool isInput, int numBuses,
                                          AudioBuffer<FloatType>& source)
    {
        int channelIndexOffset = 0;

        for (int i = 0; i < numBuses; ++i)
            mapArrangementToBuses (channelIndexOffset, i,
                                    result, busMapToUse,
                                    getArrangementForBus (&processor, isInput, i),
                                    source);
    }
};

template <typename FloatType>
struct VST3FloatAndDoubleBusMapCompositeHelper {};

struct VST3FloatAndDoubleBusMapComposite
{
    VST3BufferExchange<float>::BusMap  floatVersion;
    VST3BufferExchange<double>::BusMap doubleVersion;

    template <typename FloatType>
    inline typename VST3BufferExchange<FloatType>::BusMap& get()   { return VST3FloatAndDoubleBusMapCompositeHelper<FloatType>::get (*this); }
};


template <> struct VST3FloatAndDoubleBusMapCompositeHelper<float>
{
    static inline VST3BufferExchange<float>::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl)  { return impl.floatVersion; }
};

template <> struct VST3FloatAndDoubleBusMapCompositeHelper<double>
{
    static inline VST3BufferExchange<double>::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.doubleVersion; }
};

} // namespace juce

#endif // ! DOXYGEN
