PrivateData() noexcept
        : isProcessing(false),
          parameterCount(0),
          parameters(nullptr),
#if DISTRHO_PLUGIN_WANT_PROGRAMS
          programCount(0),
          programNames(nullptr),
#endif
#if DISTRHO_PLUGIN_WANT_STATE
          stateCount(0),
          stateKeys(nullptr),
          stateDefValues(nullptr),
#endif
#if DISTRHO_PLUGIN_WANT_LATENCY
          latency(0),
#endif
          bufferSize(d_lastBufferSize),
          sampleRate(d_lastSampleRate)
    {
        DISTRHO_SAFE_ASSERT(bufferSize != 0);
        DISTRHO_SAFE_ASSERT(d_isNotZero(sampleRate));
    }
    void lv2_run(const uint32_t sampleCount)
    {
        // cache midi input and time position first
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
        uint32_t midiEventCount = 0;
#endif

#if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS
        LV2_ATOM_SEQUENCE_FOREACH(fPortEventsIn, event)
        {
            if (event == nullptr)
                break;

# if DISTRHO_PLUGIN_WANT_MIDI_INPUT
            if (event->body.type == fURIDs.midiEvent)
            {
                if (midiEventCount >= kMaxMidiEvents)
                    continue;

                const uint8_t* const data((const uint8_t*)(event + 1));

                MidiEvent& midiEvent(fMidiEvents[midiEventCount++]);

                midiEvent.frame = event->time.frames;
                midiEvent.size  = event->body.size;

                if (midiEvent.size > MidiEvent::kDataSize)
                {
                    midiEvent.dataExt = data;
                    std::memset(midiEvent.data, 0, MidiEvent::kDataSize);
                }
                else
                {
                    midiEvent.dataExt = nullptr;
                    std::memcpy(midiEvent.data, data, midiEvent.size);
                }

                continue;
            }
# endif
# if DISTRHO_PLUGIN_WANT_TIMEPOS
            if (event->body.type == fURIDs.atomBlank || event->body.type == fURIDs.atomObject)
            {
                const LV2_Atom_Object* const obj((const LV2_Atom_Object*)&event->body);

                if (obj->body.otype != fURIDs.timePosition)
                    continue;

                LV2_Atom* bar     = nullptr;
                LV2_Atom* barBeat = nullptr;
                LV2_Atom* beatUnit = nullptr;
                LV2_Atom* beatsPerBar = nullptr;
                LV2_Atom* beatsPerMinute = nullptr;
                LV2_Atom* frame = nullptr;
                LV2_Atom* speed = nullptr;
                LV2_Atom* ticksPerBeat = nullptr;

                lv2_atom_object_get(obj,
                                    fURIDs.timeBar, &bar,
                                    fURIDs.timeBarBeat, &barBeat,
                                    fURIDs.timeBeatUnit, &beatUnit,
                                    fURIDs.timeBeatsPerBar, &beatsPerBar,
                                    fURIDs.timeBeatsPerMinute, &beatsPerMinute,
                                    fURIDs.timeFrame, &frame,
                                    fURIDs.timeSpeed, &speed,
                                    fURIDs.timeTicksPerBeat, &ticksPerBeat,
                                    nullptr);

                // need to handle this first as other values depend on it
                if (ticksPerBeat != nullptr)
                {
                    /**/ if (ticksPerBeat->type == fURIDs.atomDouble)
                        fLastPositionData.ticksPerBeat = ((LV2_Atom_Double*)ticksPerBeat)->body;
                    else if (ticksPerBeat->type == fURIDs.atomFloat)
                        fLastPositionData.ticksPerBeat = ((LV2_Atom_Float*)ticksPerBeat)->body;
                    else if (ticksPerBeat->type == fURIDs.atomInt)
                        fLastPositionData.ticksPerBeat = ((LV2_Atom_Int*)ticksPerBeat)->body;
                    else if (ticksPerBeat->type == fURIDs.atomLong)
                        fLastPositionData.ticksPerBeat = ((LV2_Atom_Long*)ticksPerBeat)->body;
                    else
                        d_stderr("Unknown lv2 ticksPerBeat value type");

                    if (fLastPositionData.ticksPerBeat > 0)
                        fTimePosition.bbt.ticksPerBeat = fLastPositionData.ticksPerBeat;
                }

                // same
                if (speed != nullptr)
                {
                    /**/ if (speed->type == fURIDs.atomDouble)
                        fLastPositionData.speed = ((LV2_Atom_Double*)speed)->body;
                    else if (speed->type == fURIDs.atomFloat)
                        fLastPositionData.speed = ((LV2_Atom_Float*)speed)->body;
                    else if (speed->type == fURIDs.atomInt)
                        fLastPositionData.speed = ((LV2_Atom_Int*)speed)->body;
                    else if (speed->type == fURIDs.atomLong)
                        fLastPositionData.speed = ((LV2_Atom_Long*)speed)->body;
                    else
                        d_stderr("Unknown lv2 speed value type");

                    fTimePosition.playing = d_isNotZero(fLastPositionData.speed);
                }

                if (bar != nullptr)
                {
                    /**/ if (bar->type == fURIDs.atomDouble)
                        fLastPositionData.bar = ((LV2_Atom_Double*)bar)->body;
                    else if (bar->type == fURIDs.atomFloat)
                        fLastPositionData.bar = ((LV2_Atom_Float*)bar)->body;
                    else if (bar->type == fURIDs.atomInt)
                        fLastPositionData.bar = ((LV2_Atom_Int*)bar)->body;
                    else if (bar->type == fURIDs.atomLong)
                        fLastPositionData.bar = ((LV2_Atom_Long*)bar)->body;
                    else
                        d_stderr("Unknown lv2 bar value type");

                    if (fLastPositionData.bar >= 0)
                        fTimePosition.bbt.bar = fLastPositionData.bar + 1;
                }

                if (barBeat != nullptr)
                {
                    /**/ if (barBeat->type == fURIDs.atomDouble)
                        fLastPositionData.barBeat = ((LV2_Atom_Double*)barBeat)->body;
                    else if (barBeat->type == fURIDs.atomFloat)
                        fLastPositionData.barBeat = ((LV2_Atom_Float*)barBeat)->body;
                    else if (barBeat->type == fURIDs.atomInt)
                        fLastPositionData.barBeat = ((LV2_Atom_Int*)barBeat)->body;
                    else if (barBeat->type == fURIDs.atomLong)
                        fLastPositionData.barBeat = ((LV2_Atom_Long*)barBeat)->body;
                    else
                        d_stderr("Unknown lv2 barBeat value type");

                    if (fLastPositionData.barBeat >= 0.0f)
                    {
                        const double rest = std::fmod(fLastPositionData.barBeat, 1.0);
                        fTimePosition.bbt.beat = fLastPositionData.barBeat-rest+1.0;
                        fTimePosition.bbt.tick = rest*fTimePosition.bbt.ticksPerBeat+0.5;
                    }
                }

                if (beatUnit != nullptr)
                {
                    /**/ if (beatUnit->type == fURIDs.atomDouble)
                        fLastPositionData.beatUnit = ((LV2_Atom_Double*)beatUnit)->body;
                    else if (beatUnit->type == fURIDs.atomFloat)
                        fLastPositionData.beatUnit = ((LV2_Atom_Float*)beatUnit)->body;
                    else if (beatUnit->type == fURIDs.atomInt)
                        fLastPositionData.beatUnit = ((LV2_Atom_Int*)beatUnit)->body;
                    else if (beatUnit->type == fURIDs.atomLong)
                        fLastPositionData.beatUnit = ((LV2_Atom_Long*)beatUnit)->body;
                    else
                        d_stderr("Unknown lv2 beatUnit value type");

                    if (fLastPositionData.beatUnit > 0)
                        fTimePosition.bbt.beatType = fLastPositionData.beatUnit;
                }

                if (beatsPerBar != nullptr)
                {
                    /**/ if (beatsPerBar->type == fURIDs.atomDouble)
                        fLastPositionData.beatsPerBar = ((LV2_Atom_Double*)beatsPerBar)->body;
                    else if (beatsPerBar->type == fURIDs.atomFloat)
                        fLastPositionData.beatsPerBar = ((LV2_Atom_Float*)beatsPerBar)->body;
                    else if (beatsPerBar->type == fURIDs.atomInt)
                        fLastPositionData.beatsPerBar = ((LV2_Atom_Int*)beatsPerBar)->body;
                    else if (beatsPerBar->type == fURIDs.atomLong)
                        fLastPositionData.beatsPerBar = ((LV2_Atom_Long*)beatsPerBar)->body;
                    else
                        d_stderr("Unknown lv2 beatsPerBar value type");

                    if (fLastPositionData.beatsPerBar > 0.0f)
                        fTimePosition.bbt.beatsPerBar = fLastPositionData.beatsPerBar;
                }

                if (beatsPerMinute != nullptr)
                {
                    /**/ if (beatsPerMinute->type == fURIDs.atomDouble)
                        fLastPositionData.beatsPerMinute = ((LV2_Atom_Double*)beatsPerMinute)->body;
                    else if (beatsPerMinute->type == fURIDs.atomFloat)
                        fLastPositionData.beatsPerMinute = ((LV2_Atom_Float*)beatsPerMinute)->body;
                    else if (beatsPerMinute->type == fURIDs.atomInt)
                        fLastPositionData.beatsPerMinute = ((LV2_Atom_Int*)beatsPerMinute)->body;
                    else if (beatsPerMinute->type == fURIDs.atomLong)
                        fLastPositionData.beatsPerMinute = ((LV2_Atom_Long*)beatsPerMinute)->body;
                    else
                        d_stderr("Unknown lv2 beatsPerMinute value type");

                    if (fLastPositionData.beatsPerMinute > 0.0f)
                    {
                        fTimePosition.bbt.beatsPerMinute = fLastPositionData.beatsPerMinute;

                        if (d_isNotZero(fLastPositionData.speed))
                            fTimePosition.bbt.beatsPerMinute *= std::abs(fLastPositionData.speed);
                    }
                }

                if (frame != nullptr)
                {
                    /**/ if (frame->type == fURIDs.atomDouble)
                        fLastPositionData.frame = ((LV2_Atom_Double*)frame)->body;
                    else if (frame->type == fURIDs.atomFloat)
                        fLastPositionData.frame = ((LV2_Atom_Float*)frame)->body;
                    else if (frame->type == fURIDs.atomInt)
                        fLastPositionData.frame = ((LV2_Atom_Int*)frame)->body;
                    else if (frame->type == fURIDs.atomLong)
                        fLastPositionData.frame = ((LV2_Atom_Long*)frame)->body;
                    else
                        d_stderr("Unknown lv2 frame value type");

                    if (fLastPositionData.frame >= 0)
                        fTimePosition.frame = fLastPositionData.frame;
                }

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

                fTimePosition.bbt.valid = (fLastPositionData.beatsPerMinute > 0.0 &&
                                           fLastPositionData.beatUnit > 0 &&
                                           fLastPositionData.beatsPerBar > 0.0f);

                fPlugin.setTimePosition(fTimePosition);

                continue;
            }
# endif
        }
#endif

        // check for messages from UI
#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI
        LV2_ATOM_SEQUENCE_FOREACH(fPortEventsIn, event)
        {
            if (event == nullptr)
                break;

            if (event->body.type == fURIDs.distrhoState && fWorker != nullptr)
            {
                const void* const data((const void*)(event + 1));

                // check if this is our special message
                if (std::strcmp((const char*)data, "__dpf_ui_data__") == 0)
                {
                    for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i)
                        fNeededUiSends[i] = true;
                }
                else
                // no, send to DSP as usual
                {
                    fWorker->schedule_work(fWorker->handle, event->body.size, data);
                }
            }
        }
#endif

        // Check for updated parameters
        float curValue;

        for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i)
        {
            if (fPortControls[i] == nullptr)
                continue;

            curValue = *fPortControls[i];

            if (fLastControlValues[i] != curValue && ! fPlugin.isParameterOutput(i))
            {
                fLastControlValues[i] = curValue;
                fPlugin.setParameterValue(i, curValue);
            }
        }

        // Run plugin
        if (sampleCount != 0)
        {
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
            fPlugin.run(fPortAudioIns, fPortAudioOuts, sampleCount, fMidiEvents, midiEventCount);
#else
            fPlugin.run(fPortAudioIns, fPortAudioOuts, sampleCount);
#endif

#if DISTRHO_PLUGIN_WANT_TIMEPOS
            // update timePos for next callback
            if (d_isNotZero(fLastPositionData.speed))
            {
                if (fLastPositionData.speed > 0.0)
                {
                    // playing forwards
                    fLastPositionData.frame += sampleCount;
                }
                else
                {
                    // playing backwards
                    fLastPositionData.frame -= sampleCount;

                    if (fLastPositionData.frame < 0)
                        fLastPositionData.frame = 0;
                }

                fTimePosition.frame = fLastPositionData.frame;

                if (fTimePosition.bbt.valid)
                {
                    const double beatsPerMinute = fLastPositionData.beatsPerMinute * fLastPositionData.speed;
                    const double framesPerBeat  = 60.0 * fSampleRate / beatsPerMinute;
                    const double addedBarBeats  = double(sampleCount) / framesPerBeat;

                    if (fLastPositionData.barBeat >= 0.0f)
                    {
                        fLastPositionData.barBeat = std::fmod(fLastPositionData.barBeat+addedBarBeats,
                                                              fLastPositionData.beatsPerBar);

                        const double rest = std::fmod(fLastPositionData.barBeat, 1.0);
                        fTimePosition.bbt.beat = fLastPositionData.barBeat-rest+1.0;
                        fTimePosition.bbt.tick = rest*fTimePosition.bbt.ticksPerBeat+0.5;

                        if (fLastPositionData.bar >= 0)
                        {
                            fLastPositionData.bar += std::floor((fLastPositionData.barBeat+addedBarBeats)/
                                                             fLastPositionData.beatsPerBar);

                            if (fLastPositionData.bar < 0)
                                fLastPositionData.bar = 0;

                            fTimePosition.bbt.bar = fLastPositionData.bar + 1;

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

                    fTimePosition.bbt.beatsPerMinute = std::abs(beatsPerMinute);
                }
            }
#endif
        }

        updateParameterOutputs();

#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI
        const uint32_t capacity = fPortEventsOut->atom.size;

        bool needsInit = true;
        uint32_t size, offset = 0;
        LV2_Atom_Event* aev;

        // TODO - MIDI Output

        for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i)
        {
            if (! fNeededUiSends[i])
                continue;

            const String& key = fPlugin.getStateKey(i);

            for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
            {
                const String& curKey = cit->first;

                if (curKey != key)
                    continue;

                const String& value = cit->second;

                // set msg size (key + value + separator + 2x null terminator)
                const size_t msgSize(key.length()+value.length()+3);

                if (sizeof(LV2_Atom_Event) + msgSize > capacity - offset)
                    break;

                if (needsInit)
                {
                    fPortEventsOut->atom.size = 0;
                    fPortEventsOut->atom.type = fURIDs.atomSequence;
                    fPortEventsOut->body.unit = 0;
                    fPortEventsOut->body.pad  = 0;
                    needsInit = false;
                }

                // reserve msg space
                char msgBuf[msgSize];
                std::memset(msgBuf, 0, msgSize);

                // write key and value in atom bufer
                std::memcpy(msgBuf, key.buffer(), key.length());
                std::memcpy(msgBuf+(key.length()+1), value.buffer(), value.length());

                // put data
                aev = (LV2_Atom_Event*)(LV2_ATOM_CONTENTS(LV2_Atom_Sequence, fPortEventsOut) + offset);
                aev->time.frames = 0;
                aev->body.type   = fURIDs.distrhoState;
                aev->body.size   = msgSize;
                std::memcpy(LV2_ATOM_BODY(&aev->body), msgBuf, msgSize-1);

                size    = lv2_atom_pad_size(sizeof(LV2_Atom_Event) + msgSize);
                offset += size;
                fPortEventsOut->atom.size += size;

                fNeededUiSends[i] = false;
                break;
            }
        }
#endif
    }