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

   This file is part of the Water library.
   Copyright (c) 2015 ROLI Ltd.
   Copyright (C) 2017-2020 Filipe Coelho <falktx@falktx.com>

   Permission is granted to use this software under the terms of the GNU
   General Public License as published by the Free Software Foundation;
   either version 2 of the License, or any later version.

   This program is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

   For a full copy of the GNU General Public License see the doc/GPL.txt file.

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

#include "AudioProcessorGraph.h"
#include "../containers/SortedSet.h"

namespace water {

//==============================================================================
namespace GraphRenderingOps
{

struct AudioGraphRenderingOpBase
{
    AudioGraphRenderingOpBase() noexcept {}
    virtual ~AudioGraphRenderingOpBase() {}

    virtual void perform (AudioSampleBuffer& sharedAudioBufferChans,
                          AudioSampleBuffer& sharedCVBufferChans,
                          const OwnedArray<MidiBuffer>& sharedMidiBuffers,
                          const int numSamples) = 0;
};

// use CRTP
template <class Child>
struct AudioGraphRenderingOp  : public AudioGraphRenderingOpBase
{
    void perform (AudioSampleBuffer& sharedAudioBufferChans,
                  AudioSampleBuffer& sharedCVBufferChans,
                  const OwnedArray<MidiBuffer>& sharedMidiBuffers,
                  const int numSamples) override
    {
        static_cast<Child*> (this)->perform (sharedAudioBufferChans,
                                             sharedCVBufferChans,
                                             sharedMidiBuffers,
                                             numSamples);
    }
};

//==============================================================================
struct ClearChannelOp  : public AudioGraphRenderingOp<ClearChannelOp>
{
    ClearChannelOp (const int channel, const bool cv) noexcept
        : channelNum (channel), isCV (cv) {}

    void perform (AudioSampleBuffer& sharedAudioBufferChans,
                  AudioSampleBuffer& sharedCVBufferChans,
                  const OwnedArray<MidiBuffer>&,
                  const int numSamples)
    {
        if (isCV)
            sharedCVBufferChans.clear (channelNum, 0, numSamples);
        else
            sharedAudioBufferChans.clear (channelNum, 0, numSamples);
    }

    const int channelNum;
    const bool isCV;

    CARLA_DECLARE_NON_COPY_CLASS (ClearChannelOp)
};

//==============================================================================
struct CopyChannelOp  : public AudioGraphRenderingOp<CopyChannelOp>
{
    CopyChannelOp (const int srcChan, const int dstChan, const bool cv) noexcept
        : srcChannelNum (srcChan), dstChannelNum (dstChan), isCV (cv) {}

    void perform (AudioSampleBuffer& sharedAudioBufferChans,
                  AudioSampleBuffer& sharedCVBufferChans,
                  const OwnedArray<MidiBuffer>&,
                  const int numSamples)
    {
        if (isCV)
            sharedCVBufferChans.copyFrom (dstChannelNum, 0, sharedCVBufferChans, srcChannelNum, 0, numSamples);
        else
            sharedAudioBufferChans.copyFrom (dstChannelNum, 0, sharedAudioBufferChans, srcChannelNum, 0, numSamples);
    }

    const int srcChannelNum, dstChannelNum;
    const bool isCV;

    CARLA_DECLARE_NON_COPY_CLASS (CopyChannelOp)
};

//==============================================================================
struct AddChannelOp  : public AudioGraphRenderingOp<AddChannelOp>
{
    AddChannelOp (const int srcChan, const int dstChan, const bool cv) noexcept
        : srcChannelNum (srcChan), dstChannelNum (dstChan), isCV (cv) {}

    void perform (AudioSampleBuffer& sharedAudioBufferChans,
                  AudioSampleBuffer& sharedCVBufferChans,
                  const OwnedArray<MidiBuffer>&,
                  const int numSamples)
    {
        if (isCV)
            sharedCVBufferChans.addFrom (dstChannelNum, 0, sharedCVBufferChans, srcChannelNum, 0, numSamples);
        else
            sharedAudioBufferChans.addFrom (dstChannelNum, 0, sharedAudioBufferChans, srcChannelNum, 0, numSamples);
    }

    const int srcChannelNum, dstChannelNum;
    const bool isCV;

    CARLA_DECLARE_NON_COPY_CLASS (AddChannelOp)
};

//==============================================================================
struct ClearMidiBufferOp  : public AudioGraphRenderingOp<ClearMidiBufferOp>
{
    ClearMidiBufferOp (const int buffer) noexcept  : bufferNum (buffer)  {}

    void perform (AudioSampleBuffer&, AudioSampleBuffer&,
                  const OwnedArray<MidiBuffer>& sharedMidiBuffers,
                  const int)
    {
        sharedMidiBuffers.getUnchecked (bufferNum)->clear();
    }

    const int bufferNum;

    CARLA_DECLARE_NON_COPY_CLASS (ClearMidiBufferOp)
};

//==============================================================================
struct CopyMidiBufferOp  : public AudioGraphRenderingOp<CopyMidiBufferOp>
{
    CopyMidiBufferOp (const int srcBuffer, const int dstBuffer) noexcept
        : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer)
    {}

    void perform (AudioSampleBuffer&, AudioSampleBuffer&,
                  const OwnedArray<MidiBuffer>& sharedMidiBuffers,
                  const int)
    {
        *sharedMidiBuffers.getUnchecked (dstBufferNum) = *sharedMidiBuffers.getUnchecked (srcBufferNum);
    }

    const int srcBufferNum, dstBufferNum;

    CARLA_DECLARE_NON_COPY_CLASS (CopyMidiBufferOp)
};

//==============================================================================
struct AddMidiBufferOp  : public AudioGraphRenderingOp<AddMidiBufferOp>
{
    AddMidiBufferOp (const int srcBuffer, const int dstBuffer)
        : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer)
    {}

    void perform (AudioSampleBuffer&, AudioSampleBuffer&,
                  const OwnedArray<MidiBuffer>& sharedMidiBuffers,
                  const int numSamples)
    {
        sharedMidiBuffers.getUnchecked (dstBufferNum)
            ->addEvents (*sharedMidiBuffers.getUnchecked (srcBufferNum), 0, numSamples, 0);
    }

    const int srcBufferNum, dstBufferNum;

    CARLA_DECLARE_NON_COPY_CLASS (AddMidiBufferOp)
};

//==============================================================================
struct DelayChannelOp  : public AudioGraphRenderingOp<DelayChannelOp>
{
    DelayChannelOp (const int chan, const int delaySize, const bool cv)
        : channel (chan),
          bufferSize (delaySize + 1),
          readIndex (0), writeIndex (delaySize),
          isCV (cv)
    {
        buffer.calloc ((size_t) bufferSize);
    }

    void perform (AudioSampleBuffer& sharedAudioBufferChans,
                  AudioSampleBuffer& sharedCVBufferChans,
                  const OwnedArray<MidiBuffer>&,
                  const int numSamples)
    {
        float* data = isCV
                    ? sharedCVBufferChans.getWritePointer (channel, 0)
                    : sharedAudioBufferChans.getWritePointer (channel, 0);
        HeapBlock<float>& block = buffer;

        for (int i = numSamples; --i >= 0;)
        {
            block [writeIndex] = *data;
            *data++ = block [readIndex];

            if (++readIndex  >= bufferSize) readIndex = 0;
            if (++writeIndex >= bufferSize) writeIndex = 0;
        }
    }

private:
    HeapBlock<float> buffer;
    const int channel, bufferSize;
    int readIndex, writeIndex;
    const bool isCV;

    CARLA_DECLARE_NON_COPY_CLASS (DelayChannelOp)
};

//==============================================================================
struct ProcessBufferOp   : public AudioGraphRenderingOp<ProcessBufferOp>
{
    ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& n,
                     const Array<uint>& audioChannelsUsed,
                     const uint totalNumChans,
                     const Array<uint>& cvInChannelsUsed,
                     const Array<uint>& cvOutChannelsUsed,
                     const int midiBuffer)
        : node (n),
          processor (n->getProcessor()),
          audioChannelsToUse (audioChannelsUsed),
          cvInChannelsToUse (cvInChannelsUsed),
          cvOutChannelsToUse (cvOutChannelsUsed),
          totalAudioChans (jmax (1U, totalNumChans)),
          totalCVIns (cvInChannelsUsed.size()),
          totalCVOuts (cvOutChannelsUsed.size()),
          midiBufferToUse (midiBuffer)
    {
        audioChannels.calloc (totalAudioChans);
        cvInChannels.calloc (totalCVIns);
        cvOutChannels.calloc (totalCVOuts);

        while (audioChannelsToUse.size() < static_cast<int>(totalAudioChans))
            audioChannelsToUse.add (0);
    }

    void perform (AudioSampleBuffer& sharedAudioBufferChans,
                  AudioSampleBuffer& sharedCVBufferChans,
                  const OwnedArray<MidiBuffer>& sharedMidiBuffers,
                  const int numSamples)
    {
        HeapBlock<float*>& audioChannelsCopy = audioChannels;
        HeapBlock<float*>& cvInChannelsCopy  = cvInChannels;
        HeapBlock<float*>& cvOutChannelsCopy = cvOutChannels;

        for (uint i = 0; i < totalAudioChans; ++i)
            audioChannelsCopy[i] = sharedAudioBufferChans.getWritePointer (audioChannelsToUse.getUnchecked (i), 0);

        for (uint i = 0; i < totalCVIns; ++i)
            cvInChannels[i] = sharedCVBufferChans.getWritePointer (cvInChannelsToUse.getUnchecked (i), 0);

        for (uint i = 0; i < totalCVOuts; ++i)
            cvOutChannels[i] = sharedCVBufferChans.getWritePointer (cvOutChannelsToUse.getUnchecked (i), 0);

        AudioSampleBuffer audioBuffer (audioChannelsCopy, totalAudioChans, numSamples);
        AudioSampleBuffer cvInBuffer  (cvInChannelsCopy, totalCVIns, numSamples);
        AudioSampleBuffer cvOutBuffer (cvOutChannelsCopy, totalCVOuts, numSamples);

        if (processor->isSuspended())
        {
            audioBuffer.clear();
            cvOutBuffer.clear();
        }
        else
        {
            const CarlaRecursiveMutexLocker cml (processor->getCallbackLock());

            callProcess (audioBuffer, cvInBuffer, cvOutBuffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse));
        }
    }

    void callProcess (AudioSampleBuffer& audioBuffer,
                      AudioSampleBuffer& cvInBuffer,
                      AudioSampleBuffer& cvOutBuffer,
                      MidiBuffer& midiMessages)
    {
        processor->processBlockWithCV (audioBuffer, cvInBuffer, cvOutBuffer, midiMessages);
    }

    const AudioProcessorGraph::Node::Ptr node;
    AudioProcessor* const processor;

private:
    Array<uint> audioChannelsToUse;
    Array<uint> cvInChannelsToUse;
    Array<uint> cvOutChannelsToUse;
    HeapBlock<float*> audioChannels;
    HeapBlock<float*> cvInChannels;
    HeapBlock<float*> cvOutChannels;
    AudioSampleBuffer tempBuffer;
    const uint totalAudioChans;
    const uint totalCVIns;
    const uint totalCVOuts;
    const int midiBufferToUse;

    CARLA_DECLARE_NON_COPY_CLASS (ProcessBufferOp)
};

//==============================================================================
/** Used to calculate the correct sequence of rendering ops needed, based on
    the best re-use of shared buffers at each stage.
*/
struct RenderingOpSequenceCalculator
{
    RenderingOpSequenceCalculator (AudioProcessorGraph& g,
                                   const Array<AudioProcessorGraph::Node*>& nodes,
                                   Array<void*>& renderingOps)
        : graph (g),
          orderedNodes (nodes),
          totalLatency (0)
    {
        audioNodeIds.add ((uint32) zeroNodeID); // first buffer is read-only zeros
        audioChannels.add (0);

        cvNodeIds.add ((uint32) zeroNodeID);
        cvChannels.add (0);

        midiNodeIds.add ((uint32) zeroNodeID);

        for (int i = 0; i < orderedNodes.size(); ++i)
        {
            createRenderingOpsForNode (*orderedNodes.getUnchecked(i), renderingOps, i);
            markAnyUnusedBuffersAsFree (i);
        }

        graph.setLatencySamples (totalLatency);
    }

    int getNumAudioBuffersNeeded() const noexcept    { return audioNodeIds.size(); }
    int getNumCVBuffersNeeded() const noexcept       { return cvNodeIds.size(); }
    int getNumMidiBuffersNeeded() const noexcept     { return midiNodeIds.size(); }

private:
    //==============================================================================
    AudioProcessorGraph& graph;
    const Array<AudioProcessorGraph::Node*>& orderedNodes;
    Array<uint> audioChannels, cvChannels;
    Array<uint32> audioNodeIds, cvNodeIds, midiNodeIds;

    enum { freeNodeID = 0xffffffff, zeroNodeID = 0xfffffffe, anonymousNodeID = 0xfffffffd };

    static bool isNodeBusy (uint32 nodeID) noexcept     { return nodeID != freeNodeID; }

    Array<uint32> nodeDelayIDs;
    Array<int> nodeDelays;
    int totalLatency;

    int getNodeDelay (const uint32 nodeID) const        { return nodeDelays [nodeDelayIDs.indexOf (nodeID)]; }

    void setNodeDelay (const uint32 nodeID, const int latency)
    {
        const int index = nodeDelayIDs.indexOf (nodeID);

        if (index >= 0)
        {
            nodeDelays.set (index, latency);
        }
        else
        {
            nodeDelayIDs.add (nodeID);
            nodeDelays.add (latency);
        }
    }

    int getInputLatencyForNode (const uint32 nodeID) const
    {
        int maxLatency = 0;

        for (int i = graph.getNumConnections(); --i >= 0;)
        {
            const AudioProcessorGraph::Connection* const c = graph.getConnection (i);

            if (c->destNodeId == nodeID)
                maxLatency = jmax (maxLatency, getNodeDelay (c->sourceNodeId));
        }

        return maxLatency;
    }

    //==============================================================================
    void createRenderingOpsForNode (AudioProcessorGraph::Node& node,
                                    Array<void*>& renderingOps,
                                    const int ourRenderingIndex)
    {
        AudioProcessor& processor = *node.getProcessor();
        const uint numAudioIns = processor.getTotalNumInputChannels(AudioProcessor::ChannelTypeAudio);
        const uint numAudioOuts = processor.getTotalNumOutputChannels(AudioProcessor::ChannelTypeAudio);
        const uint numCVIns = processor.getTotalNumInputChannels(AudioProcessor::ChannelTypeCV);
        const uint numCVOuts = processor.getTotalNumOutputChannels(AudioProcessor::ChannelTypeCV);
        const uint totalAudioChans = jmax (numAudioIns, numAudioOuts);

        Array<uint> audioChannelsToUse, cvInChannelsToUse, cvOutChannelsToUse;
        int midiBufferToUse = -1;

        int maxLatency = getInputLatencyForNode (node.nodeId);

        for (uint inputChan = 0; inputChan < numAudioIns; ++inputChan)
        {
            // get a list of all the inputs to this node
            Array<uint32> sourceNodes;
            Array<uint> sourceOutputChans;

            for (int i = graph.getNumConnections(); --i >= 0;)
            {
                const AudioProcessorGraph::Connection* const c = graph.getConnection (i);

                if (c->destNodeId == node.nodeId
                    && c->destChannelIndex == inputChan
                    && c->channelType == AudioProcessor::ChannelTypeAudio)
                {
                    sourceNodes.add (c->sourceNodeId);
                    sourceOutputChans.add (c->sourceChannelIndex);
                }
            }

            int bufIndex = -1;

            if (sourceNodes.size() == 0)
            {
                // unconnected input channel
                bufIndex = getFreeBuffer (AudioProcessor::ChannelTypeAudio);
                renderingOps.add (new ClearChannelOp (bufIndex, false));
            }
            else if (sourceNodes.size() == 1)
            {
                // channel with a straightforward single input..
                const uint32 srcNode = sourceNodes.getUnchecked(0);
                const uint srcChan = sourceOutputChans.getUnchecked(0);

                bufIndex = getBufferContaining (AudioProcessor::ChannelTypeAudio, srcNode, srcChan);

                if (bufIndex < 0)
                {
                    // if not found, this is probably a feedback loop
                    bufIndex = getReadOnlyEmptyBuffer();
                    wassert (bufIndex >= 0);
                }

                if (inputChan < numAudioOuts
                     && isBufferNeededLater (AudioProcessor::ChannelTypeAudio,
                                             ourRenderingIndex,
                                             inputChan,
                                             srcNode, srcChan))
                {
                    // can't mess up this channel because it's needed later by another node, so we
                    // need to use a copy of it..
                    const int newFreeBuffer = getFreeBuffer (AudioProcessor::ChannelTypeAudio);

                    renderingOps.add (new CopyChannelOp (bufIndex, newFreeBuffer, false));

                    bufIndex = newFreeBuffer;
                }

                const int nodeDelay = getNodeDelay (srcNode);

                if (nodeDelay < maxLatency)
                    renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay, false));
            }
            else
            {
                // channel with a mix of several inputs..

                // try to find a re-usable channel from our inputs..
                int reusableInputIndex = -1;

                for (int i = 0; i < sourceNodes.size(); ++i)
                {
                    const int sourceBufIndex = getBufferContaining (AudioProcessor::ChannelTypeAudio,
                                                                    sourceNodes.getUnchecked(i),
                                                                    sourceOutputChans.getUnchecked(i));

                    if (sourceBufIndex >= 0
                        && ! isBufferNeededLater (AudioProcessor::ChannelTypeAudio,
                                                  ourRenderingIndex,
                                                  inputChan,
                                                  sourceNodes.getUnchecked(i),
                                                  sourceOutputChans.getUnchecked(i)))
                    {
                        // we've found one of our input chans that can be re-used..
                        reusableInputIndex = i;
                        bufIndex = sourceBufIndex;

                        const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (i));
                        if (nodeDelay < maxLatency)
                            renderingOps.add (new DelayChannelOp (sourceBufIndex, maxLatency - nodeDelay, false));

                        break;
                    }
                }

                if (reusableInputIndex < 0)
                {
                    // can't re-use any of our input chans, so get a new one and copy everything into it..
                    bufIndex = getFreeBuffer (AudioProcessor::ChannelTypeAudio);
                    wassert (bufIndex != 0);

                    markBufferAsContaining (AudioProcessor::ChannelTypeAudio,
                                            bufIndex, static_cast<uint32> (anonymousNodeID), 0);

                    const int srcIndex = getBufferContaining (AudioProcessor::ChannelTypeAudio,
                                                              sourceNodes.getUnchecked (0),
                                                              sourceOutputChans.getUnchecked (0));
                    if (srcIndex < 0)
                    {
                        // if not found, this is probably a feedback loop
                        renderingOps.add (new ClearChannelOp (bufIndex, false));
                    }
                    else
                    {
                        renderingOps.add (new CopyChannelOp (srcIndex, bufIndex, false));
                    }

                    reusableInputIndex = 0;
                    const int nodeDelay = getNodeDelay (sourceNodes.getFirst());

                    if (nodeDelay < maxLatency)
                        renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay, false));
                }

                for (int j = 0; j < sourceNodes.size(); ++j)
                {
                    if (j != reusableInputIndex)
                    {
                        int srcIndex = getBufferContaining (AudioProcessor::ChannelTypeAudio,
                                                            sourceNodes.getUnchecked(j),
                                                            sourceOutputChans.getUnchecked(j));
                        if (srcIndex >= 0)
                        {
                            const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (j));

                            if (nodeDelay < maxLatency)
                            {
                                if (! isBufferNeededLater (AudioProcessor::ChannelTypeAudio,
                                                           ourRenderingIndex, inputChan,
                                                           sourceNodes.getUnchecked(j),
                                                           sourceOutputChans.getUnchecked(j)))
                                {
                                    renderingOps.add (new DelayChannelOp (srcIndex, maxLatency - nodeDelay, false));
                                }
                                else // buffer is reused elsewhere, can't be delayed
                                {
                                    const int bufferToDelay = getFreeBuffer (AudioProcessor::ChannelTypeAudio);
                                    renderingOps.add (new CopyChannelOp (srcIndex, bufferToDelay, false));
                                    renderingOps.add (new DelayChannelOp (bufferToDelay, maxLatency - nodeDelay, false));
                                    srcIndex = bufferToDelay;
                                }
                            }

                            renderingOps.add (new AddChannelOp (srcIndex, bufIndex, false));
                        }
                    }
                }
            }

            CARLA_SAFE_ASSERT_CONTINUE (bufIndex >= 0);
            audioChannelsToUse.add (bufIndex);

            if (inputChan < numAudioOuts)
                markBufferAsContaining (AudioProcessor::ChannelTypeAudio, bufIndex, node.nodeId, inputChan);
        }

        for (uint outputChan = numAudioIns; outputChan < numAudioOuts; ++outputChan)
        {
            const int bufIndex = getFreeBuffer (AudioProcessor::ChannelTypeAudio);
            CARLA_SAFE_ASSERT_CONTINUE (bufIndex > 0);
            audioChannelsToUse.add (bufIndex);
            markBufferAsContaining (AudioProcessor::ChannelTypeAudio, bufIndex, node.nodeId, outputChan);
        }

        for (uint inputChan = 0; inputChan < numCVIns; ++inputChan)
        {
            // get a list of all the inputs to this node
            Array<uint32> sourceNodes;
            Array<uint> sourceOutputChans;

            for (int i = graph.getNumConnections(); --i >= 0;)
            {
                const AudioProcessorGraph::Connection* const c = graph.getConnection (i);

                if (c->destNodeId == node.nodeId
                    && c->destChannelIndex == inputChan
                    && c->channelType == AudioProcessor::ChannelTypeCV)
                {
                    sourceNodes.add (c->sourceNodeId);
                    sourceOutputChans.add (c->sourceChannelIndex);
                }
            }

            int bufIndex = -1;

            if (sourceNodes.size() == 0)
            {
                // unconnected input channel
                bufIndex = getFreeBuffer (AudioProcessor::ChannelTypeCV);
                renderingOps.add (new ClearChannelOp (bufIndex, true));
            }
            else if (sourceNodes.size() == 1)
            {
                // channel with a straightforward single input..
                const uint32 srcNode = sourceNodes.getUnchecked(0);
                const uint srcChan = sourceOutputChans.getUnchecked(0);

                bufIndex = getBufferContaining (AudioProcessor::ChannelTypeCV, srcNode, srcChan);

                if (bufIndex < 0)
                {
                    // if not found, this is probably a feedback loop
                    bufIndex = getReadOnlyEmptyBuffer();
                    wassert (bufIndex >= 0);
                }

                const int newFreeBuffer = getFreeBuffer (AudioProcessor::ChannelTypeCV);

                renderingOps.add (new CopyChannelOp (bufIndex, newFreeBuffer, true));

                bufIndex = newFreeBuffer;

                const int nodeDelay = getNodeDelay (srcNode);

                if (nodeDelay < maxLatency)
                    renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay, true));
            }
            else
            {
                // channel with a mix of several inputs..

                {
                    bufIndex = getFreeBuffer (AudioProcessor::ChannelTypeCV);
                    wassert (bufIndex != 0);

                    const int srcIndex = getBufferContaining (AudioProcessor::ChannelTypeCV,
                                                              sourceNodes.getUnchecked (0),
                                                              sourceOutputChans.getUnchecked (0));
                    if (srcIndex < 0)
                    {
                        // if not found, this is probably a feedback loop
                        renderingOps.add (new ClearChannelOp (bufIndex, true));
                    }
                    else
                    {
                        renderingOps.add (new CopyChannelOp (srcIndex, bufIndex, true));
                    }

                    const int nodeDelay = getNodeDelay (sourceNodes.getFirst());

                    if (nodeDelay < maxLatency)
                        renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay, true));
                }

                for (int j = 1; j < sourceNodes.size(); ++j)
                {
                    int srcIndex = getBufferContaining (AudioProcessor::ChannelTypeCV,
                                                        sourceNodes.getUnchecked(j),
                                                        sourceOutputChans.getUnchecked(j));
                    if (srcIndex >= 0)
                    {
                        const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (j));

                        if (nodeDelay < maxLatency)
                        {
                            const int bufferToDelay = getFreeBuffer (AudioProcessor::ChannelTypeCV);
                            renderingOps.add (new CopyChannelOp (srcIndex, bufferToDelay, true));
                            renderingOps.add (new DelayChannelOp (bufferToDelay, maxLatency - nodeDelay, true));
                            srcIndex = bufferToDelay;
                        }

                        renderingOps.add (new AddChannelOp (srcIndex, bufIndex, true));
                    }
                }
            }

            CARLA_SAFE_ASSERT_CONTINUE (bufIndex >= 0);
            cvInChannelsToUse.add (bufIndex);
            markBufferAsContaining (AudioProcessor::ChannelTypeCV, bufIndex, node.nodeId, inputChan);
        }

        for (uint outputChan = 0; outputChan < numCVOuts; ++outputChan)
        {
            const int bufIndex = getFreeBuffer (AudioProcessor::ChannelTypeCV);
            CARLA_SAFE_ASSERT_CONTINUE (bufIndex > 0);
            cvOutChannelsToUse.add (bufIndex);
            markBufferAsContaining (AudioProcessor::ChannelTypeCV, bufIndex, node.nodeId, outputChan);
        }

        // Now the same thing for midi..
        Array<uint32> midiSourceNodes;

        for (int i = graph.getNumConnections(); --i >= 0;)
        {
            const AudioProcessorGraph::Connection* const c = graph.getConnection (i);

            if (c->destNodeId == node.nodeId && c->channelType == AudioProcessor::ChannelTypeMIDI)
                midiSourceNodes.add (c->sourceNodeId);
        }

        if (midiSourceNodes.size() == 0)
        {
            // No midi inputs..
            midiBufferToUse = getFreeBuffer (AudioProcessor::ChannelTypeMIDI); // need to pick a buffer even if the processor doesn't use midi

            if (processor.acceptsMidi() || processor.producesMidi())
                renderingOps.add (new ClearMidiBufferOp (midiBufferToUse));
        }
        else if (midiSourceNodes.size() == 1)
        {
            // One midi input..
            midiBufferToUse = getBufferContaining (AudioProcessor::ChannelTypeMIDI,
                                                   midiSourceNodes.getUnchecked(0),
                                                   0);
            if (midiBufferToUse >= 0)
            {
                if (isBufferNeededLater (AudioProcessor::ChannelTypeMIDI,
                                         ourRenderingIndex, 0,
                                         midiSourceNodes.getUnchecked(0), 0))
                {
                    // can't mess up this channel because it's needed later by another node, so we
                    // need to use a copy of it..
                    const int newFreeBuffer = getFreeBuffer (AudioProcessor::ChannelTypeMIDI);
                    renderingOps.add (new CopyMidiBufferOp (midiBufferToUse, newFreeBuffer));
                    midiBufferToUse = newFreeBuffer;
                }
            }
            else
            {
                // probably a feedback loop, so just use an empty one..
                midiBufferToUse = getFreeBuffer (AudioProcessor::ChannelTypeMIDI); // need to pick a buffer even if the processor doesn't use midi
            }
        }
        else
        {
            // More than one midi input being mixed..
            int reusableInputIndex = -1;

            for (int i = 0; i < midiSourceNodes.size(); ++i)
            {
                const int sourceBufIndex = getBufferContaining (AudioProcessor::ChannelTypeMIDI,
                                                                midiSourceNodes.getUnchecked(i),
                                                                0);

                if (sourceBufIndex >= 0
                     && ! isBufferNeededLater (AudioProcessor::ChannelTypeMIDI,
                                               ourRenderingIndex, 0,
                                               midiSourceNodes.getUnchecked(i), 0))
                {
                    // we've found one of our input buffers that can be re-used..
                    reusableInputIndex = i;
                    midiBufferToUse = sourceBufIndex;
                    break;
                }
            }

            if (reusableInputIndex < 0)
            {
                // can't re-use any of our input buffers, so get a new one and copy everything into it..
                midiBufferToUse = getFreeBuffer (AudioProcessor::ChannelTypeMIDI);
                wassert (midiBufferToUse >= 0);

                const int srcIndex = getBufferContaining (AudioProcessor::ChannelTypeMIDI,
                                                          midiSourceNodes.getUnchecked(0),
                                                          0);
                if (srcIndex >= 0)
                    renderingOps.add (new CopyMidiBufferOp (srcIndex, midiBufferToUse));
                else
                    renderingOps.add (new ClearMidiBufferOp (midiBufferToUse));

                reusableInputIndex = 0;
            }

            for (int j = 0; j < midiSourceNodes.size(); ++j)
            {
                if (j != reusableInputIndex)
                {
                    const int srcIndex = getBufferContaining (AudioProcessor::ChannelTypeMIDI,
                                                              midiSourceNodes.getUnchecked(j),
                                                              0);
                    if (srcIndex >= 0)
                        renderingOps.add (new AddMidiBufferOp (srcIndex, midiBufferToUse));
                }
            }
        }

        if (processor.producesMidi())
            markBufferAsContaining (AudioProcessor::ChannelTypeMIDI,
                                    midiBufferToUse, node.nodeId,
                                    0);

        setNodeDelay (node.nodeId, maxLatency + processor.getLatencySamples());

        if (numAudioOuts == 0)
            totalLatency = maxLatency;

        renderingOps.add (new ProcessBufferOp (&node,
                                               audioChannelsToUse,
                                               totalAudioChans,
                                               cvInChannelsToUse,
                                               cvOutChannelsToUse,
                                               midiBufferToUse));
    }

    //==============================================================================
    int getFreeBuffer (const AudioProcessor::ChannelType channelType)
    {
        switch (channelType)
        {
        case AudioProcessor::ChannelTypeAudio:
            for (int i = 1; i < audioNodeIds.size(); ++i)
                if (audioNodeIds.getUnchecked(i) == freeNodeID)
                    return i;

            audioNodeIds.add ((uint32) freeNodeID);
            audioChannels.add (0);
            return audioNodeIds.size() - 1;

        case AudioProcessor::ChannelTypeCV:
            for (int i = 1; i < cvNodeIds.size(); ++i)
                if (cvNodeIds.getUnchecked(i) == freeNodeID)
                    return i;

            cvNodeIds.add ((uint32) freeNodeID);
            cvChannels.add (0);
            return cvNodeIds.size() - 1;

        case AudioProcessor::ChannelTypeMIDI:
            for (int i = 1; i < midiNodeIds.size(); ++i)
                if (midiNodeIds.getUnchecked(i) == freeNodeID)
                    return i;

            midiNodeIds.add ((uint32) freeNodeID);
            return midiNodeIds.size() - 1;
        }

        return -1;
    }

    int getReadOnlyEmptyBuffer() const noexcept
    {
        return 0;
    }

    int getBufferContaining (const AudioProcessor::ChannelType channelType,
                             const uint32 nodeId,
                             const uint outputChannel) const noexcept
    {
        switch (channelType)
        {
        case AudioProcessor::ChannelTypeAudio:
            for (int i = audioNodeIds.size(); --i >= 0;)
                if (audioNodeIds.getUnchecked(i) == nodeId && audioChannels.getUnchecked(i) == outputChannel)
                    return i;
            break;

        case AudioProcessor::ChannelTypeCV:
            for (int i = cvNodeIds.size(); --i >= 0;)
                if (cvNodeIds.getUnchecked(i) == nodeId && cvChannels.getUnchecked(i) == outputChannel)
                    return i;
            break;

        case AudioProcessor::ChannelTypeMIDI:
            for (int i = midiNodeIds.size(); --i >= 0;)
            {
                if (midiNodeIds.getUnchecked(i) == nodeId)
                    return i;
            }
            break;
        }

        return -1;
    }

    void markAnyUnusedBuffersAsFree (const int stepIndex)
    {
        for (int i = 0; i < audioNodeIds.size(); ++i)
        {
            if (isNodeBusy (audioNodeIds.getUnchecked(i))
                 && ! isBufferNeededLater (AudioProcessor::ChannelTypeAudio,
                                           stepIndex, -1,
                                           audioNodeIds.getUnchecked(i),
                                           audioChannels.getUnchecked(i)))
            {
                audioNodeIds.set (i, (uint32) freeNodeID);
            }
        }

        // NOTE: CV skipped on purpose

        for (int i = 0; i < midiNodeIds.size(); ++i)
        {
            if (isNodeBusy (midiNodeIds.getUnchecked(i))
                 && ! isBufferNeededLater (AudioProcessor::ChannelTypeMIDI,
                                           stepIndex, -1,
                                           midiNodeIds.getUnchecked(i), 0))
            {
                midiNodeIds.set (i, (uint32) freeNodeID);
            }
        }
    }

    bool isBufferNeededLater (const AudioProcessor::ChannelType channelType,
                              int stepIndexToSearchFrom,
                              uint inputChannelOfIndexToIgnore,
                              const uint32 nodeId,
                              const uint outputChanIndex) const
    {
        while (stepIndexToSearchFrom < orderedNodes.size())
        {
            const AudioProcessorGraph::Node* const node = (const AudioProcessorGraph::Node*) orderedNodes.getUnchecked (stepIndexToSearchFrom);

            for (uint i = 0; i < node->getProcessor()->getTotalNumInputChannels(channelType); ++i)
                if (i != inputChannelOfIndexToIgnore
                        && graph.getConnectionBetween (channelType,
                                                       nodeId, outputChanIndex,
                                                       node->nodeId, i) != nullptr)
                    return true;

            inputChannelOfIndexToIgnore = (uint)-1;
            ++stepIndexToSearchFrom;
        }

        return false;
    }

    void markBufferAsContaining (const AudioProcessor::ChannelType channelType,
                                 int bufferNum, uint32 nodeId, int outputIndex)
    {
        switch (channelType)
        {
        case AudioProcessor::ChannelTypeAudio:
            CARLA_SAFE_ASSERT_BREAK (bufferNum >= 0 && bufferNum < audioNodeIds.size());
            audioNodeIds.set (bufferNum, nodeId);
            audioChannels.set (bufferNum, outputIndex);
            break;

        case AudioProcessor::ChannelTypeCV:
            CARLA_SAFE_ASSERT_BREAK (bufferNum >= 0 && bufferNum < cvNodeIds.size());
            cvNodeIds.set (bufferNum, nodeId);
            cvChannels.set (bufferNum, outputIndex);
            break;

        case AudioProcessor::ChannelTypeMIDI:
            CARLA_SAFE_ASSERT_BREAK (bufferNum > 0 && bufferNum < midiNodeIds.size());
            midiNodeIds.set (bufferNum, nodeId);
            break;
        }
    }

    CARLA_DECLARE_NON_COPY_CLASS (RenderingOpSequenceCalculator)
};

//==============================================================================
// Holds a fast lookup table for checking which nodes are inputs to others.
class ConnectionLookupTable
{
public:
    explicit ConnectionLookupTable (const OwnedArray<AudioProcessorGraph::Connection>& connections)
    {
        for (int i = 0; i < static_cast<int>(connections.size()); ++i)
        {
            const AudioProcessorGraph::Connection* const c = connections.getUnchecked(i);

            int index;
            Entry* entry = findEntry (c->destNodeId, index);

            if (entry == nullptr)
            {
                entry = new Entry (c->destNodeId);
                entries.insert (index, entry);
            }

            entry->srcNodes.add (c->sourceNodeId);
        }
    }

    bool isAnInputTo (const uint32 possibleInputId,
                      const uint32 possibleDestinationId) const noexcept
    {
        return isAnInputToRecursive (possibleInputId, possibleDestinationId, entries.size());
    }

private:
    //==============================================================================
    struct Entry
    {
        explicit Entry (const uint32 destNodeId_) noexcept : destNodeId (destNodeId_) {}

        const uint32 destNodeId;
        SortedSet<uint32> srcNodes;

        CARLA_DECLARE_NON_COPY_CLASS (Entry)
    };

    OwnedArray<Entry> entries;

    bool isAnInputToRecursive (const uint32 possibleInputId,
                               const uint32 possibleDestinationId,
                               int recursionCheck) const noexcept
    {
        int index;

        if (const Entry* const entry = findEntry (possibleDestinationId, index))
        {
            const SortedSet<uint32>& srcNodes = entry->srcNodes;

            if (srcNodes.contains (possibleInputId))
                return true;

            if (--recursionCheck >= 0)
            {
                for (int i = 0; i < srcNodes.size(); ++i)
                    if (isAnInputToRecursive (possibleInputId, srcNodes.getUnchecked(i), recursionCheck))
                        return true;
            }
        }

        return false;
    }

    Entry* findEntry (const uint32 destNodeId, int& insertIndex) const noexcept
    {
        Entry* result = nullptr;

        int start = 0;
        int end = entries.size();

        for (;;)
        {
            if (start >= end)
            {
                break;
            }
            else if (destNodeId == entries.getUnchecked (start)->destNodeId)
            {
                result = entries.getUnchecked (start);
                break;
            }
            else
            {
                const int halfway = (start + end) / 2;

                if (halfway == start)
                {
                    if (destNodeId >= entries.getUnchecked (halfway)->destNodeId)
                        ++start;

                    break;
                }
                else if (destNodeId >= entries.getUnchecked (halfway)->destNodeId)
                    start = halfway;
                else
                    end = halfway;
            }
        }

        insertIndex = start;
        return result;
    }

    CARLA_DECLARE_NON_COPY_CLASS (ConnectionLookupTable)
};

//==============================================================================
struct ConnectionSorter
{
    static int compareElements (const AudioProcessorGraph::Connection* const first,
                                const AudioProcessorGraph::Connection* const second) noexcept
    {
        if (first->sourceNodeId < second->sourceNodeId)                return -1;
        if (first->sourceNodeId > second->sourceNodeId)                return 1;
        if (first->destNodeId < second->destNodeId)                    return -1;
        if (first->destNodeId > second->destNodeId)                    return 1;
        if (first->sourceChannelIndex < second->sourceChannelIndex)    return -1;
        if (first->sourceChannelIndex > second->sourceChannelIndex)    return 1;
        if (first->destChannelIndex < second->destChannelIndex)        return -1;
        if (first->destChannelIndex > second->destChannelIndex)        return 1;

        return 0;
    }
};

}

//==============================================================================
AudioProcessorGraph::Connection::Connection (ChannelType ct,
                                             const uint32 sourceID, const uint sourceChannel,
                                             const uint32 destID, const uint destChannel) noexcept
    : channelType (ct),
      sourceNodeId (sourceID), sourceChannelIndex (sourceChannel),
      destNodeId (destID), destChannelIndex (destChannel)
{
}

//==============================================================================
AudioProcessorGraph::Node::Node (const uint32 nodeID, AudioProcessor* const p) noexcept
    : nodeId (nodeID), processor (p), isPrepared (false)
{
    wassert (processor != nullptr);
}

void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int newBlockSize,
                                         AudioProcessorGraph* const graph)
{
    if (! isPrepared)
    {
        setParentGraph (graph);

        processor->setRateAndBufferSizeDetails (newSampleRate, newBlockSize);
        processor->prepareToPlay (newSampleRate, newBlockSize);
        isPrepared = true;
    }
}

void AudioProcessorGraph::Node::unprepare()
{
    if (isPrepared)
    {
        isPrepared = false;
        processor->releaseResources();
    }
}

void AudioProcessorGraph::Node::setParentGraph (AudioProcessorGraph* const graph) const
{
    if (AudioProcessorGraph::AudioGraphIOProcessor* const ioProc
            = dynamic_cast<AudioProcessorGraph::AudioGraphIOProcessor*> (processor.get()))
        ioProc->setParentGraph (graph);
}

//==============================================================================
struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers
{
    AudioProcessorGraphBufferHelpers() noexcept
        : currentAudioInputBuffer (nullptr),
          currentCVInputBuffer (nullptr) {}

    void setRenderingBufferSize (int newNumAudioChannels, int newNumCVChannels, int newNumSamples) noexcept
    {
        renderingAudioBuffers.setSize (newNumAudioChannels, newNumSamples);
        renderingAudioBuffers.clear();

        renderingCVBuffers.setSize (newNumCVChannels, newNumSamples);
        renderingCVBuffers.clear();
    }

    void release() noexcept
    {
        renderingAudioBuffers.setSize (1, 1);
        currentAudioInputBuffer = nullptr;
        currentCVInputBuffer = nullptr;
        currentAudioOutputBuffer.setSize (1, 1);
        currentCVOutputBuffer.setSize (1, 1);

        renderingCVBuffers.setSize (1, 1);
    }

    void prepareInOutBuffers (int newNumAudioChannels, int newNumCVChannels, int newNumSamples) noexcept
    {
        currentAudioInputBuffer = nullptr;
        currentCVInputBuffer = nullptr;
        currentAudioOutputBuffer.setSize (newNumAudioChannels, newNumSamples);
        currentCVOutputBuffer.setSize (newNumCVChannels, newNumSamples);
    }

    AudioSampleBuffer        renderingAudioBuffers;
    AudioSampleBuffer        renderingCVBuffers;
    AudioSampleBuffer*       currentAudioInputBuffer;
    const AudioSampleBuffer* currentCVInputBuffer;
    AudioSampleBuffer        currentAudioOutputBuffer;
    AudioSampleBuffer        currentCVOutputBuffer;
};

//==============================================================================
AudioProcessorGraph::AudioProcessorGraph()
    : lastNodeId (0), audioAndCVBuffers (new AudioProcessorGraphBufferHelpers),
      currentMidiInputBuffer (nullptr), isPrepared (false), needsReorder (false)
{
}

AudioProcessorGraph::~AudioProcessorGraph()
{
    clearRenderingSequence();
    clear();
}

const String AudioProcessorGraph::getName() const
{
    return "Audio Graph";
}

//==============================================================================
void AudioProcessorGraph::clear()
{
    nodes.clear();
    connections.clear();
    needsReorder = true;
}

AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (const uint32 nodeId) const
{
    for (int i = nodes.size(); --i >= 0;)
        if (nodes.getUnchecked(i)->nodeId == nodeId)
            return nodes.getUnchecked(i);

    return nullptr;
}

AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor, uint32 nodeId)
{
    CARLA_SAFE_ASSERT_RETURN (newProcessor != nullptr && newProcessor != this, nullptr);

    for (int i = nodes.size(); --i >= 0;)
    {
        CARLA_SAFE_ASSERT_RETURN (nodes.getUnchecked(i)->getProcessor() != newProcessor, nullptr);
    }

    if (nodeId == 0)
    {
        nodeId = ++lastNodeId;
    }
    else
    {
        // you can't add a node with an id that already exists in the graph..
        CARLA_SAFE_ASSERT_RETURN (getNodeForId (nodeId) == nullptr, nullptr);
        removeNode (nodeId);

        if (nodeId > lastNodeId)
            lastNodeId = nodeId;
    }

    Node* const n = new Node (nodeId, newProcessor);
    nodes.add (n);

    if (isPrepared)
        needsReorder = true;

    n->setParentGraph (this);
    return n;
}

bool AudioProcessorGraph::removeNode (const uint32 nodeId)
{
    disconnectNode (nodeId);

    for (int i = nodes.size(); --i >= 0;)
    {
        if (nodes.getUnchecked(i)->nodeId == nodeId)
        {
            nodes.remove (i);

            if (isPrepared)
                needsReorder = true;

            return true;
        }
    }

    return false;
}

bool AudioProcessorGraph::removeNode (Node* node)
{
    CARLA_SAFE_ASSERT_RETURN(node != nullptr, false);

    return removeNode (node->nodeId);
}

//==============================================================================
const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const ChannelType ct,
                                                                                  const uint32 sourceNodeId,
                                                                                  const uint sourceChannelIndex,
                                                                                  const uint32 destNodeId,
                                                                                  const uint destChannelIndex) const
{
    const Connection c (ct, sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex);
    GraphRenderingOps::ConnectionSorter sorter;
    return connections [connections.indexOfSorted (sorter, &c)];
}

bool AudioProcessorGraph::isConnected (const uint32 possibleSourceNodeId,
                                       const uint32 possibleDestNodeId) const
{
    for (int i = connections.size(); --i >= 0;)
    {
        const Connection* const c = connections.getUnchecked(i);

        if (c->sourceNodeId == possibleSourceNodeId
             && c->destNodeId == possibleDestNodeId)
        {
            return true;
        }
    }

    return false;
}

bool AudioProcessorGraph::canConnect (ChannelType ct,
                                      const uint32 sourceNodeId,
                                      const uint sourceChannelIndex,
                                      const uint32 destNodeId,
                                      const uint destChannelIndex) const
{
    if (sourceNodeId == destNodeId)
        return false;

    const Node* const source = getNodeForId (sourceNodeId);

    if (source == nullptr
         || (ct != ChannelTypeMIDI && sourceChannelIndex >= source->processor->getTotalNumOutputChannels(ct))
         || (ct == ChannelTypeMIDI && ! source->processor->producesMidi()))
        return false;

    const Node* const dest = getNodeForId (destNodeId);

    if (dest == nullptr
         || (ct != ChannelTypeMIDI && destChannelIndex >= dest->processor->getTotalNumInputChannels(ct))
         || (ct == ChannelTypeMIDI && ! dest->processor->acceptsMidi()))
        return false;

    return getConnectionBetween (ct,
                                 sourceNodeId, sourceChannelIndex,
                                 destNodeId, destChannelIndex) == nullptr;
}

bool AudioProcessorGraph::addConnection (const ChannelType ct,
                                         const uint32 sourceNodeId,
                                         const uint sourceChannelIndex,
                                         const uint32 destNodeId,
                                         const uint destChannelIndex)
{
    if (! canConnect (ct, sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex))
        return false;

    GraphRenderingOps::ConnectionSorter sorter;
    connections.addSorted (sorter, new Connection (ct,
                                                   sourceNodeId, sourceChannelIndex,
                                                   destNodeId, destChannelIndex));

    if (isPrepared)
        needsReorder = true;

    return true;
}

void AudioProcessorGraph::removeConnection (const int index)
{
    connections.remove (index);

    if (isPrepared)
        needsReorder = true;
}

bool AudioProcessorGraph::removeConnection (const ChannelType ct,
                                            const uint32 sourceNodeId, const uint sourceChannelIndex,
                                            const uint32 destNodeId, const uint destChannelIndex)
{
    bool doneAnything = false;

    for (int i = connections.size(); --i >= 0;)
    {
        const Connection* const c = connections.getUnchecked(i);

        if (c->channelType == ct
             && c->sourceNodeId == sourceNodeId
             && c->destNodeId == destNodeId
             && c->sourceChannelIndex == sourceChannelIndex
             && c->destChannelIndex == destChannelIndex)
        {
            removeConnection (i);
            doneAnything = true;
        }
    }

    return doneAnything;
}

bool AudioProcessorGraph::disconnectNode (const uint32 nodeId)
{
    bool doneAnything = false;

    for (int i = connections.size(); --i >= 0;)
    {
        const Connection* const c = connections.getUnchecked(i);

        if (c->sourceNodeId == nodeId || c->destNodeId == nodeId)
        {
            removeConnection (i);
            doneAnything = true;
        }
    }

    return doneAnything;
}

bool AudioProcessorGraph::isConnectionLegal (const Connection* const c) const
{
    CARLA_SAFE_ASSERT_RETURN (c != nullptr, false);

    const Node* const source = getNodeForId (c->sourceNodeId);
    const Node* const dest   = getNodeForId (c->destNodeId);

    return source != nullptr
        && dest != nullptr
        && (c->channelType != ChannelTypeMIDI ? (c->sourceChannelIndex < source->processor->getTotalNumOutputChannels(c->channelType))
                                              : source->processor->producesMidi())
        && (c->channelType != ChannelTypeMIDI ? (c->destChannelIndex < dest->processor->getTotalNumInputChannels(c->channelType))
                                              : dest->processor->acceptsMidi());
}

bool AudioProcessorGraph::removeIllegalConnections()
{
    bool doneAnything = false;

    for (int i = connections.size(); --i >= 0;)
    {
        if (! isConnectionLegal (connections.getUnchecked(i)))
        {
            removeConnection (i);
            doneAnything = true;
        }
    }

    return doneAnything;
}

//==============================================================================
static void deleteRenderOpArray (Array<void*>& ops)
{
    for (int i = ops.size(); --i >= 0;)
        delete static_cast<GraphRenderingOps::AudioGraphRenderingOpBase*> (ops.getUnchecked(i));
}

void AudioProcessorGraph::clearRenderingSequence()
{
    Array<void*> oldOps;

    {
        const CarlaRecursiveMutexLocker cml (getCallbackLock());
        renderingOps.swapWith (oldOps);
    }

    deleteRenderOpArray (oldOps);
}

bool AudioProcessorGraph::isAnInputTo (const uint32 possibleInputId,
                                       const uint32 possibleDestinationId,
                                       const int recursionCheck) const
{
    if (recursionCheck > 0)
    {
        for (int i = connections.size(); --i >= 0;)
        {
            const AudioProcessorGraph::Connection* const c = connections.getUnchecked (i);

            if (c->destNodeId == possibleDestinationId
                 && (c->sourceNodeId == possibleInputId
                      || isAnInputTo (possibleInputId, c->sourceNodeId, recursionCheck - 1)))
                return true;
        }
    }

    return false;
}

void AudioProcessorGraph::buildRenderingSequence()
{
    Array<void*> newRenderingOps;
    int numAudioRenderingBuffersNeeded = 2;
    int numCVRenderingBuffersNeeded = 0;
    int numMidiBuffersNeeded = 1;

    {
        const CarlaRecursiveMutexLocker cml (reorderMutex);

        Array<Node*> orderedNodes;

        {
            const GraphRenderingOps::ConnectionLookupTable table (connections);

            for (int i = 0; i < nodes.size(); ++i)
            {
                Node* const node = nodes.getUnchecked(i);

                node->prepare (getSampleRate(), getBlockSize(), this);

                int j = 0;
                for (; j < orderedNodes.size(); ++j)
                    if (table.isAnInputTo (node->nodeId, ((Node*) orderedNodes.getUnchecked(j))->nodeId))
                      break;

                orderedNodes.insert (j, node);
            }
        }

        GraphRenderingOps::RenderingOpSequenceCalculator calculator (*this, orderedNodes, newRenderingOps);

        numAudioRenderingBuffersNeeded = calculator.getNumAudioBuffersNeeded();
        numCVRenderingBuffersNeeded = calculator.getNumCVBuffersNeeded();
        numMidiBuffersNeeded = calculator.getNumMidiBuffersNeeded();
    }

    {
        // swap over to the new rendering sequence..
        const CarlaRecursiveMutexLocker cml (getCallbackLock());

        audioAndCVBuffers->setRenderingBufferSize (numAudioRenderingBuffersNeeded,
                                                   numCVRenderingBuffersNeeded,
                                                   getBlockSize());

        for (int i = static_cast<int>(midiBuffers.size()); --i >= 0;)
            midiBuffers.getUnchecked(i)->clear();

        while (static_cast<int>(midiBuffers.size()) < numMidiBuffersNeeded)
            midiBuffers.add (new MidiBuffer());

        renderingOps.swapWith (newRenderingOps);
    }

    // delete the old ones..
    deleteRenderOpArray (newRenderingOps);
}

//==============================================================================
void AudioProcessorGraph::prepareToPlay (double sampleRate, int estimatedSamplesPerBlock)
{
    setRateAndBufferSizeDetails(sampleRate, estimatedSamplesPerBlock);

    audioAndCVBuffers->prepareInOutBuffers(jmax(1U, getTotalNumOutputChannels(AudioProcessor::ChannelTypeAudio)),
                                           jmax(1U, getTotalNumOutputChannels(AudioProcessor::ChannelTypeCV)),
                                           estimatedSamplesPerBlock);

    currentMidiInputBuffer = nullptr;
    currentMidiOutputBuffer.clear();

    clearRenderingSequence();
    buildRenderingSequence();

    isPrepared = true;
}

void AudioProcessorGraph::releaseResources()
{
    isPrepared = false;

    for (int i = 0; i < nodes.size(); ++i)
        nodes.getUnchecked(i)->unprepare();

    audioAndCVBuffers->release();
    midiBuffers.clear();

    currentMidiInputBuffer = nullptr;
    currentMidiOutputBuffer.clear();
}

void AudioProcessorGraph::reset()
{
    const CarlaRecursiveMutexLocker cml (getCallbackLock());

    for (int i = 0; i < nodes.size(); ++i)
        nodes.getUnchecked(i)->getProcessor()->reset();
}

void AudioProcessorGraph::setNonRealtime (bool isProcessingNonRealtime) noexcept
{
    const CarlaRecursiveMutexLocker cml (getCallbackLock());

    AudioProcessor::setNonRealtime (isProcessingNonRealtime);

    for (int i = 0; i < nodes.size(); ++i)
        nodes.getUnchecked(i)->getProcessor()->setNonRealtime (isProcessingNonRealtime);
}

/*
void AudioProcessorGraph::processAudio (AudioSampleBuffer& audioBuffer, MidiBuffer& midiMessages)
{
    AudioSampleBuffer*& currentAudioInputBuffer  = audioAndCVBuffers->currentAudioInputBuffer;
    AudioSampleBuffer&  currentAudioOutputBuffer = audioAndCVBuffers->currentAudioOutputBuffer;
    AudioSampleBuffer&  renderingAudioBuffers    = audioAndCVBuffers->renderingAudioBuffers;
    AudioSampleBuffer&  renderingCVBuffers       = audioAndCVBuffers->renderingCVBuffers;

    const int numSamples = audioBuffer.getNumSamples();

    if (! audioAndCVBuffers->currentAudioOutputBuffer.setSizeRT(numSamples))
        return;
    if (! audioAndCVBuffers->renderingAudioBuffers.setSizeRT(numSamples))
        return;
    if (! audioAndCVBuffers->renderingCVBuffers.setSizeRT(numSamples))
        return;

    currentAudioInputBuffer = &audioBuffer;
    currentAudioOutputBuffer.clear();
    currentMidiInputBuffer = &midiMessages;
    currentMidiOutputBuffer.clear();

    for (int i = 0; i < renderingOps.size(); ++i)
    {
        GraphRenderingOps::AudioGraphRenderingOpBase* const op
            = (GraphRenderingOps::AudioGraphRenderingOpBase*) renderingOps.getUnchecked(i);

        op->perform (renderingAudioBuffers, renderingCVBuffers, midiBuffers, numSamples);
    }

    for (uint32_t i = 0; i < audioBuffer.getNumChannels(); ++i)
        audioBuffer.copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples);

    midiMessages.clear();
    midiMessages.addEvents (currentMidiOutputBuffer, 0, audioBuffer.getNumSamples(), 0);
}
*/

void AudioProcessorGraph::processAudioAndCV (AudioSampleBuffer& audioBuffer,
                                             const AudioSampleBuffer& cvInBuffer,
                                             AudioSampleBuffer& cvOutBuffer,
                                             MidiBuffer& midiMessages)
{
    AudioSampleBuffer*&       currentAudioInputBuffer  = audioAndCVBuffers->currentAudioInputBuffer;
    const AudioSampleBuffer*& currentCVInputBuffer     = audioAndCVBuffers->currentCVInputBuffer;
    AudioSampleBuffer&        currentAudioOutputBuffer = audioAndCVBuffers->currentAudioOutputBuffer;
    AudioSampleBuffer&        currentCVOutputBuffer    = audioAndCVBuffers->currentCVOutputBuffer;
    AudioSampleBuffer&        renderingAudioBuffers    = audioAndCVBuffers->renderingAudioBuffers;
    AudioSampleBuffer&        renderingCVBuffers       = audioAndCVBuffers->renderingCVBuffers;

    const int numSamples = audioBuffer.getNumSamples();

    if (! audioAndCVBuffers->currentAudioOutputBuffer.setSizeRT(numSamples))
        return;
    if (! audioAndCVBuffers->currentCVOutputBuffer.setSizeRT(numSamples))
        return;
    if (! audioAndCVBuffers->renderingAudioBuffers.setSizeRT(numSamples))
        return;
    if (! audioAndCVBuffers->renderingCVBuffers.setSizeRT(numSamples))
        return;

    currentAudioInputBuffer = &audioBuffer;
    currentCVInputBuffer = &cvInBuffer;
    currentMidiInputBuffer = &midiMessages;
    currentAudioOutputBuffer.clear();
    currentCVOutputBuffer.clear();
    currentMidiOutputBuffer.clear();

    for (int i = 0; i < renderingOps.size(); ++i)
    {
        GraphRenderingOps::AudioGraphRenderingOpBase* const op
            = (GraphRenderingOps::AudioGraphRenderingOpBase*) renderingOps.getUnchecked(i);

        op->perform (renderingAudioBuffers, renderingCVBuffers, midiBuffers, numSamples);
    }

    for (uint32_t i = 0; i < audioBuffer.getNumChannels(); ++i)
        audioBuffer.copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples);

    for (uint32_t i = 0; i < cvOutBuffer.getNumChannels(); ++i)
        cvOutBuffer.copyFrom (i, 0, currentCVOutputBuffer, i, 0, numSamples);

    midiMessages.clear();
    midiMessages.addEvents (currentMidiOutputBuffer, 0, audioBuffer.getNumSamples(), 0);
}

bool AudioProcessorGraph::acceptsMidi() const                       { return true; }
bool AudioProcessorGraph::producesMidi() const                      { return true; }

/*
void AudioProcessorGraph::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
    processAudio (buffer, midiMessages);
}
*/

void AudioProcessorGraph::processBlockWithCV (AudioSampleBuffer& audioBuffer,
                                              const AudioSampleBuffer& cvInBuffer,
                                              AudioSampleBuffer& cvOutBuffer,
                                              MidiBuffer& midiMessages)
{
    processAudioAndCV (audioBuffer, cvInBuffer, cvOutBuffer, midiMessages);
}

void AudioProcessorGraph::reorderNowIfNeeded()
{
    if (needsReorder)
    {
        needsReorder = false;
        buildRenderingSequence();
    }
}

const CarlaRecursiveMutex& AudioProcessorGraph::getReorderMutex() const
{
    return reorderMutex;
}

//==============================================================================
AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType deviceType)
    : type (deviceType), graph (nullptr)
{
}

AudioProcessorGraph::AudioGraphIOProcessor::~AudioGraphIOProcessor()
{
}

const String AudioProcessorGraph::AudioGraphIOProcessor::getName() const
{
    switch (type)
    {
        case audioOutputNode:   return "Audio Output";
        case audioInputNode:    return "Audio Input";
        case cvOutputNode:      return "CV Output";
        case cvInputNode:       return "CV Input";
        case midiOutputNode:    return "Midi Output";
        case midiInputNode:     return "Midi Input";
        default:                break;
    }

    return String();
}

void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int)
{
    CARLA_SAFE_ASSERT (graph != nullptr);
}

void AudioProcessorGraph::AudioGraphIOProcessor::releaseResources()
{
}

void AudioProcessorGraph::AudioGraphIOProcessor::processAudioAndCV (AudioSampleBuffer& audioBuffer,
                                                                    const AudioSampleBuffer& cvInBuffer,
                                                                    AudioSampleBuffer& cvOutBuffer,
                                                                    MidiBuffer& midiMessages)
{
    CARLA_SAFE_ASSERT_RETURN(graph != nullptr,);

    switch (type)
    {
        case audioOutputNode:
        {
            AudioSampleBuffer&  currentAudioOutputBuffer =
                graph->audioAndCVBuffers->currentAudioOutputBuffer;

            for (int i = jmin (currentAudioOutputBuffer.getNumChannels(),
                               audioBuffer.getNumChannels()); --i >= 0;)
            {
                currentAudioOutputBuffer.addFrom (i, 0, audioBuffer, i, 0, audioBuffer.getNumSamples());
            }

            break;
        }

        case audioInputNode:
        {
            AudioSampleBuffer*& currentAudioInputBuffer =
                graph->audioAndCVBuffers->currentAudioInputBuffer;

            for (int i = jmin (currentAudioInputBuffer->getNumChannels(),
                               audioBuffer.getNumChannels()); --i >= 0;)
            {
                audioBuffer.copyFrom (i, 0, *currentAudioInputBuffer, i, 0, audioBuffer.getNumSamples());
            }

            break;
        }

        case cvOutputNode:
        {
            AudioSampleBuffer&  currentCVOutputBuffer =
                graph->audioAndCVBuffers->currentCVOutputBuffer;

            for (int i = jmin (currentCVOutputBuffer.getNumChannels(),
                               cvInBuffer.getNumChannels()); --i >= 0;)
            {
                currentCVOutputBuffer.addFrom (i, 0, cvInBuffer, i, 0, cvInBuffer.getNumSamples());
            }

            break;
        }

        case cvInputNode:
        {
            const AudioSampleBuffer*& currentCVInputBuffer =
                graph->audioAndCVBuffers->currentCVInputBuffer;

            for (int i = jmin (currentCVInputBuffer->getNumChannels(),
                               cvOutBuffer.getNumChannels()); --i >= 0;)
            {
                cvOutBuffer.copyFrom (i, 0, *currentCVInputBuffer, i, 0, cvOutBuffer.getNumSamples());
            }

            break;
        }

        case midiOutputNode:
            graph->currentMidiOutputBuffer.addEvents (midiMessages, 0, audioBuffer.getNumSamples(), 0);
            break;

        case midiInputNode:
            midiMessages.addEvents (*graph->currentMidiInputBuffer, 0, audioBuffer.getNumSamples(), 0);
            break;

        default:
            break;
    }
}

void AudioProcessorGraph::AudioGraphIOProcessor::processBlockWithCV (AudioSampleBuffer& audioBuffer,
                                                                     const AudioSampleBuffer& cvInBuffer,
                                                                     AudioSampleBuffer& cvOutBuffer,
                                                                     MidiBuffer& midiMessages)
{
    processAudioAndCV (audioBuffer, cvInBuffer, cvOutBuffer, midiMessages);
}

bool AudioProcessorGraph::AudioGraphIOProcessor::acceptsMidi() const
{
    return type == midiOutputNode;
}

bool AudioProcessorGraph::AudioGraphIOProcessor::producesMidi() const
{
    return type == midiInputNode;
}

bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const noexcept
{
    return type == audioInputNode || type == cvInputNode || type == midiInputNode;
}

bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const noexcept
{
    return type == audioOutputNode || type == cvOutputNode || type == midiOutputNode;
}

void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorGraph* const newGraph)
{
    graph = newGraph;

    if (graph != nullptr)
    {
        setPlayConfigDetails (type == audioOutputNode
                                   ? graph->getTotalNumOutputChannels(AudioProcessor::ChannelTypeAudio)
                                   : 0,
                              type == audioInputNode
                                   ? graph->getTotalNumInputChannels(AudioProcessor::ChannelTypeAudio)
                                   : 0,
                              type == cvOutputNode
                                   ? graph->getTotalNumOutputChannels(AudioProcessor::ChannelTypeCV)
                                   : 0,
                              type == cvInputNode
                                   ? graph->getTotalNumInputChannels(AudioProcessor::ChannelTypeCV)
                                   : 0,
                              type == midiOutputNode
                                   ? graph->getTotalNumOutputChannels(AudioProcessor::ChannelTypeMIDI)
                                   : 0,
                              type == midiInputNode
                                   ? graph->getTotalNumInputChannels(AudioProcessor::ChannelTypeMIDI)
                                   : 0,
                              getSampleRate(),
                              getBlockSize());
    }
}

}
