/*
    SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
    http://www.audiosynth.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) 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.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/


#include "SC_PlugIn.h"

#include <algorithm> /* for std::min and std::max */

#include <boost/align/is_aligned.hpp>

#include "simd_peakmeter.hpp"

#ifdef NOVA_SIMD
#    include "simd_memory.hpp"

#    include "function_attributes.h"

#endif

static InterfaceTable* ft;

//////////////////////////////////////////////////////////////////////////////////////////////////

struct Trig1 : public Unit {
    float m_prevtrig;
    long mCounter;
};

struct Trig : public Unit {
    float mLevel;
    float m_prevtrig;
    long mCounter;
};

struct SendTrig : public Unit {
    float m_prevtrig;
};

struct SendReply : public Unit {
    float m_prevtrig;
    int m_valueSize;
    int m_valueOffset;
    float* m_values;
    int m_cmdNameSize;
    char* m_cmdName;
};


struct Poll : public Unit {
    int m_samplesRemain, m_intervalSamples;
    float m_trig;
    float m_lastPoll;
    char* m_id_string;
    bool m_mayprint;
};


struct ToggleFF : public Unit {
    float mLevel;
    float m_prevtrig;
};

struct SetResetFF : public Unit {
    float mLevel;
    float m_prevtrig, m_prevreset;
};

struct Latch : public Unit {
    float mLevel;
    float m_prevtrig;
};

struct Gate : public Unit {
    float mLevel;
};

struct Schmidt : public Unit {
    float mLevel;
};

struct PulseDivider : public Unit {
    float mLevel;
    float m_prevtrig;
    long mCounter;
};

struct PulseCount : public Unit {
    float mLevel;
    float m_prevtrig, m_prevreset;
};

struct Stepper : public Unit {
    float mLevel;
    float m_prevtrig, m_prevreset;
};

struct TDelay : public Unit {
    long mCounter;
    float m_prevtrig;
};

struct ZeroCrossing : public Unit {
    float mLevel, m_prevfrac, m_previn;
    long mCounter;
};

struct Timer : public Unit {
    float mLevel, m_prevfrac, m_previn;
    long mCounter;
};

struct Sweep : public Unit {
    double mLevel;
    float m_previn;
};

struct Phasor : public Unit {
    double mLevel;
    float m_previn;
};

struct Peak : public Unit {
    float mLevel;
    float m_prevtrig;
};

struct RunningMin : public Unit {
    float mLevel;
    float m_prevtrig;
};

struct RunningMax : public Unit {
    float mLevel;
    float m_prevtrig;
};

struct PeakFollower : public Unit {
    float mLevel;
    float mDecay;
};

struct MostChange : public Unit {
    float mPrevA, mPrevB;
    int mRecent;
};

struct LeastChange : public Unit {
    float mPrevA, mPrevB;
    int mRecent;
};

struct LastValue : public Unit {
    float mPrev;
    float mCurr;
};


struct Done : public Unit {
    Unit* m_src;
};

struct FreeSelf : public Unit {
    float m_prevtrig;
};

struct PauseSelf : public Unit {
    float m_prevtrig;
};

struct Pause : public Unit {
    int m_state;
};

struct Free : public Unit {
    float m_prevtrig;
};

struct FreeSelfWhenDone : public Unit {
    Unit* m_src;
};

struct PauseSelfWhenDone : public Unit {
    Unit* m_src;
};

extern "C" {
void Trig1_Ctor(Trig1* unit);
void Trig1_next(Trig1* unit, int inNumSamples);
void Trig1_next_k(Trig1* unit, int inNumSamples);

void Trig_Ctor(Trig* unit);
void Trig_next(Trig* unit, int inNumSamples);
void Trig_next_k(Trig* unit, int inNumSamples);

void SendTrig_Ctor(SendTrig* unit);
void SendTrig_next(SendTrig* unit, int inNumSamples);
void SendTrig_next_aka(SendTrig* unit, int inNumSamples);

void SendReply_Ctor(SendReply* unit);
void SendReply_next(SendReply* unit, int inNumSamples);
void SendReply_next_aka(SendReply* unit, int inNumSamples);

void Poll_Ctor(Poll* unit);
void Poll_next_aa(Poll* unit, int inNumSamples);
void Poll_next_ak(Poll* unit, int inNumSamples);
void Poll_next_kk(Poll* unit, int inNumSamples);

void SetResetFF_Ctor(SetResetFF* unit);
void SetResetFF_next_a(SetResetFF* unit, int inNumSamples);
void SetResetFF_next_k(SetResetFF* unit, int inNumSamples);

void ToggleFF_Ctor(ToggleFF* unit);
void ToggleFF_next(ToggleFF* unit, int inNumSamples);
void ToggleFF_next_ak(ToggleFF* unit, int inNumSamples);

void Latch_Ctor(Latch* unit);
void Latch_next_ak(Latch* unit, int inNumSamples);
void Latch_next_aa(Latch* unit, int inNumSamples);

void Gate_Ctor(Gate* unit);
void Gate_next_ak(Gate* unit, int inNumSamples);
void Gate_next_aa(Gate* unit, int inNumSamples);

void Schmidt_Ctor(Schmidt* unit);
void Schmidt_next(Schmidt* unit, int inNumSamples);

void PulseDivider_Ctor(PulseDivider* unit);
void PulseDivider_next(PulseDivider* unit, int inNumSamples);

void PulseCount_Ctor(PulseCount* unit);
void PulseCount_next_a(PulseCount* unit, int inNumSamples);
void PulseCount_next_k(PulseCount* unit, int inNumSamples);
void PulseCount_next_0(PulseCount* unit, int inNumSamples);

void Stepper_Ctor(Stepper* unit);
void Stepper_next_aa(Stepper* unit, int inNumSamples);
void Stepper_next_ak(Stepper* unit, int inNumSamples);
void Stepper_next_a0(Stepper* unit, int inNumSamples);

void TDelay_Ctor(TDelay* unit);
void TDelay_next(TDelay* unit, int inNumSamples);

void ZeroCrossing_Ctor(ZeroCrossing* unit);
void ZeroCrossing_next_a(ZeroCrossing* unit, int inNumSamples);

void Timer_Ctor(Timer* unit);
void Timer_next_a(Timer* unit, int inNumSamples);

void Sweep_Ctor(Sweep* unit);
void Sweep_next_0k(Sweep* unit, int inNumSamples);
void Sweep_next_0a(Sweep* unit, int inNumSamples);
void Sweep_next_kk(Sweep* unit, int inNumSamples);
void Sweep_next_ka(Sweep* unit, int inNumSamples);
void Sweep_next_ak(Sweep* unit, int inNumSamples);
void Sweep_next_aa(Sweep* unit, int inNumSamples);

void Phasor_Ctor(Phasor* unit);
void Phasor_next_kk(Phasor* unit, int inNumSamples);
void Phasor_next_ak(Phasor* unit, int inNumSamples);
void Phasor_next_aa(Phasor* unit, int inNumSamples);

void Peak_Ctor(Peak* unit);
void Peak_next_ak(Peak* unit, int inNumSamples);
void Peak_next_ai(Peak* unit, int inNumSamples);
void Peak_next_aa(Peak* unit, int inNumSamples);
void Peak_next_ak_k(Peak* unit, int inNumSamples);
void Peak_next_ai_k(Peak* unit, int inNumSamples);
void Peak_next_aa_k(Peak* unit, int inNumSamples);

void RunningMin_Ctor(RunningMin* unit);
void RunningMin_next_ak(RunningMin* unit, int inNumSamples);
void RunningMin_next_ai(RunningMin* unit, int inNumSamples);
void RunningMin_next_aa(RunningMin* unit, int inNumSamples);

void RunningMax_Ctor(RunningMax* unit);
void RunningMax_next_ak(RunningMax* unit, int inNumSamples);
void RunningMax_next_ai(RunningMax* unit, int inNumSamples);
void RunningMax_next_aa(RunningMax* unit, int inNumSamples);


void PeakFollower_Ctor(PeakFollower* unit);
void PeakFollower_next(PeakFollower* unit, int inNumSamples);
void PeakFollower_next_ai(PeakFollower* unit, int inNumSamples);

void MostChange_Ctor(MostChange* unit);
void MostChange_next_ak(MostChange* unit, int inNumSamples);
void MostChange_next_ka(MostChange* unit, int inNumSamples);
void MostChange_next_aa(MostChange* unit, int inNumSamples);

void LeastChange_Ctor(LeastChange* unit);
void LeastChange_next_ak(LeastChange* unit, int inNumSamples);
void LeastChange_next_ka(LeastChange* unit, int inNumSamples);
void LeastChange_next_aa(LeastChange* unit, int inNumSamples);

void LastValue_Ctor(LastValue* unit);
void LastValue_next_ak(LastValue* unit, int inNumSamples);
void LastValue_next_kk(LastValue* unit, int inNumSamples);

void Done_Ctor(Done* unit);
void Done_next(Done* unit, int inNumSamples);

void FreeSelf_Ctor(FreeSelf* unit);
void FreeSelf_next(FreeSelf* unit, int inNumSamples);

void FreeSelfWhenDone_Ctor(FreeSelfWhenDone* unit);
void FreeSelfWhenDone_next(FreeSelfWhenDone* unit, int inNumSamples);

void PauseSelf_Ctor(PauseSelf* unit);
void PauseSelf_next(PauseSelf* unit, int inNumSamples);

void Pause_Ctor(Pause* unit);
void Pause_next(Pause* unit, int inNumSamples);

void Free_Ctor(Free* unit);
void Free_next(Free* unit, int inNumSamples);

void PauseSelfWhenDone_Ctor(PauseSelfWhenDone* unit);
void PauseSelfWhenDone_next(PauseSelfWhenDone* unit, int inNumSamples);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef NOVA_SIMD
FLATTEN void Trig1_next_nova(Trig1* unit, int inNumSamples);
FLATTEN void Trig1_next_k_nova(Trig1* unit, int inNumSamples);
#endif

void Trig1_Ctor(Trig1* unit) {
    if (unit->mCalcRate == calc_FullRate && INRATE(0) != calc_FullRate) {
#ifdef NOVA_SIMD
        if (boost::alignment::is_aligned(BUFLENGTH, 16))
            SETCALC(Trig1_next_k_nova);
        else
#endif
            SETCALC(Trig1_next_k);
    } else {
#ifdef NOVA_SIMD
        if (boost::alignment::is_aligned(BUFLENGTH, 16))
            SETCALC(Trig1_next_nova);
        else
#endif
            SETCALC(Trig1_next);
    }
    unit->mCounter = 0;
    unit->m_prevtrig = 0.f;

    Trig1_next(unit, 1);

    unit->mCounter = 0;
    unit->m_prevtrig = 0.f;
}

void Trig1_next(Trig1* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float dur = ZIN0(1);
    float sr = unit->mRate->mSampleRate;
    float prevtrig = unit->m_prevtrig;
    unsigned long counter = unit->mCounter;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); float zout; if (counter > 0) { zout = --counter ? 1.f : 0.f; } else {
            if (curtrig > 0.f && prevtrig <= 0.f) {
                counter = (long)(dur * sr + .5f);
                if (counter < 1)
                    counter = 1;
                zout = 1.f;
            } else {
                zout = 0.f;
            }
        } prevtrig = curtrig;
        ZXP(out) = zout;);
    unit->m_prevtrig = prevtrig;
    unit->mCounter = counter;
}

void Trig1_next_k(Trig1* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float curtrig = ZIN0(0);
    float dur = ZIN0(1);
    float sr = unit->mRate->mSampleRate;
    float prevtrig = unit->m_prevtrig;
    unsigned long counter = unit->mCounter;

    LOOP1(
        inNumSamples, float zout; if (counter > 0) { zout = --counter ? 1.f : 0.f; } else {
            if (curtrig > 0.f && prevtrig <= 0.f) {
                counter = (long)(dur * sr + .5f);
                if (counter < 1)
                    counter = 1;
                zout = 1.f;
            } else {
                zout = 0.f;
            }
        } prevtrig = curtrig;
        ZXP(out) = zout;);
    unit->m_prevtrig = prevtrig;
    unit->mCounter = counter;
}

#ifdef NOVA_SIMD
void Trig1_next_nova(Trig1* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float dur = ZIN0(1);
    float sr = unit->mRate->mSampleRate;
    float prevtrig = unit->m_prevtrig;
    unsigned long counter = unit->mCounter;

    if (counter > inNumSamples) {
        nova::setvec_simd(OUT(0), 1.f, inNumSamples);
        counter -= inNumSamples;
        assert(counter > 0);
        prevtrig = IN(0)[inNumSamples - 1];
    } else {
        LOOP1(
            inNumSamples, float curtrig = ZXP(trig); float zout;
            if (counter > 0) { zout = --counter ? 1.f : 0.f; } else {
                if (curtrig > 0.f && prevtrig <= 0.f) {
                    counter = (long)(dur * sr + .5f);
                    if (counter < 1)
                        counter = 1;
                    zout = 1.f;
                } else {
                    zout = 0.f;
                }
            } prevtrig = curtrig;
            ZXP(out) = zout;);
    }
    unit->m_prevtrig = prevtrig;
    unit->mCounter = counter;
}


void Trig1_next_k_nova(Trig1* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float curtrig = ZIN0(0);
    float dur = ZIN0(1);
    float sr = unit->mRate->mSampleRate;
    float prevtrig = unit->m_prevtrig;
    unsigned long counter = unit->mCounter;

    if (counter > inNumSamples) {
        nova::setvec_simd(OUT(0), 1.f, inNumSamples);
        counter -= inNumSamples;
        assert(counter > 0);
        prevtrig = curtrig;
    } else if (counter == 0 && (curtrig <= 0.f || prevtrig > 0.f)) {
        nova::zerovec_simd(OUT(0), inNumSamples);
        prevtrig = curtrig;
    } else {
        LOOP1(
            inNumSamples, float zout; if (counter > 0) { zout = --counter ? 1.f : 0.f; } else {
                if (curtrig > 0.f && prevtrig <= 0.f) {
                    counter = (long)(dur * sr + .5f);
                    if (counter < 1)
                        counter = 1;
                    zout = 1.f;
                } else {
                    zout = 0.f;
                }
            } prevtrig = curtrig;
            ZXP(out) = zout;);
    }

    unit->m_prevtrig = prevtrig;
    unit->mCounter = counter;
}
#endif


////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef NOVA_SIMD
void Trig_next_nova(Trig* unit, int inNumSamples);
void Trig_next_k_nova(Trig* unit, int inNumSamples);
#endif

void Trig_Ctor(Trig* unit) {
    if (unit->mCalcRate == calc_FullRate && INRATE(0) != calc_FullRate) {
#ifdef NOVA_SIMD
        if (boost::alignment::is_aligned(BUFLENGTH, 16))
            SETCALC(Trig_next_k_nova);
        else
#endif
            SETCALC(Trig_next_k);
    } else {
#ifdef NOVA_SIMD
        if (boost::alignment::is_aligned(BUFLENGTH, 16))
            SETCALC(Trig_next_nova);
        else
#endif
            SETCALC(Trig_next);
    }

    unit->mCounter = 0;
    unit->m_prevtrig = 0.f;
    unit->mLevel = 0.f;

    Trig_next(unit, 1);

    unit->mCounter = 0;
    unit->m_prevtrig = 0.f;
    unit->mLevel = 0.f;
}

void Trig_next(Trig* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float dur = ZIN0(1);
    float sr = unit->mRate->mSampleRate;
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;
    unsigned long counter = unit->mCounter;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); float zout; if (counter > 0) { zout = --counter ? level : 0.f; } else {
            if (curtrig > 0.f && prevtrig <= 0.f) {
                counter = (long)(dur * sr + .5f);
                if (counter < 1)
                    counter = 1;
                level = curtrig;
                zout = level;
            } else {
                zout = 0.f;
            }
        } prevtrig = curtrig;
        ZXP(out) = zout;);
    unit->m_prevtrig = prevtrig;
    unit->mCounter = counter;
    unit->mLevel = level;
}

void Trig_next_k(Trig* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float curtrig = ZIN0(0);
    float dur = ZIN0(1);
    float sr = unit->mRate->mSampleRate;
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;
    unsigned long counter = unit->mCounter;

    LOOP1(
        inNumSamples, float zout; if (counter > 0) { zout = --counter ? level : 0.f; } else {
            if (curtrig > 0.f && prevtrig <= 0.f) {
                counter = (long)(dur * sr + .5f);
                if (counter < 1)
                    counter = 1;
                level = curtrig;
                zout = level;
            } else {
                zout = 0.f;
            }
        } prevtrig = curtrig;
        ZXP(out) = zout;);
    unit->m_prevtrig = prevtrig;
    unit->mCounter = counter;
    unit->mLevel = level;
}

#ifdef NOVA_SIMD
void Trig_next_nova(Trig* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float dur = ZIN0(1);
    float sr = unit->mRate->mSampleRate;
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;
    unsigned long counter = unit->mCounter;

    if (counter > inNumSamples) {
        nova::setvec_simd(OUT(0), level, inNumSamples);
        counter -= inNumSamples;
        assert(counter > 0);
        prevtrig = IN(0)[inNumSamples - 1];
    } else {
        LOOP1(
            inNumSamples, float curtrig = ZXP(trig); float zout;
            if (counter > 0) { zout = --counter ? level : 0.f; } else {
                if (curtrig > 0.f && prevtrig <= 0.f) {
                    counter = (long)(dur * sr + .5f);
                    if (counter < 1)
                        counter = 1;
                    level = curtrig;
                    zout = level;
                } else {
                    zout = 0.f;
                }
            } prevtrig = curtrig;
            ZXP(out) = zout;);
    }
    unit->m_prevtrig = prevtrig;
    unit->mCounter = counter;
    unit->mLevel = level;
}

void Trig_next_k_nova(Trig* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float curtrig = ZIN0(0);
    float dur = ZIN0(1);
    float sr = unit->mRate->mSampleRate;
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;
    unsigned long counter = unit->mCounter;

    if (counter > inNumSamples) {
        nova::setvec_simd(OUT(0), level, inNumSamples);
        counter -= inNumSamples;
        assert(counter > 0);
        prevtrig = curtrig;
    } else if (counter == 0 && (curtrig <= 0.f || prevtrig > 0.f)) {
        nova::zerovec_simd(OUT(0), inNumSamples);
        prevtrig = curtrig;
    } else {
        LOOP1(
            inNumSamples, float zout; if (counter > 0) { zout = --counter ? level : 0.f; } else {
                if (curtrig > 0.f && prevtrig <= 0.f) {
                    counter = (long)(dur * sr + .5f);
                    if (counter < 1)
                        counter = 1;
                    level = curtrig;
                    zout = level;
                } else {
                    zout = 0.f;
                }
            } prevtrig = curtrig;
            ZXP(out) = zout;);
    }
    unit->m_prevtrig = prevtrig;
    unit->mCounter = counter;
    unit->mLevel = level;
}
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////////

void SendTrig_Ctor(SendTrig* unit) {
    if (INRATE(2) == calc_FullRate) {
        SETCALC(SendTrig_next_aka);
    } else {
        SETCALC(SendTrig_next);
    }
    unit->m_prevtrig = 0.f;
}

void SendTrig_next(SendTrig* unit, int inNumSamples) {
    float* trig = ZIN(0);
    float prevtrig = unit->m_prevtrig;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); if (curtrig > 0.f && prevtrig <= 0.f) {
            SendTrigger(&unit->mParent->mNode, (int)ZIN0(1), ZIN0(2));
        } prevtrig = curtrig;);
    unit->m_prevtrig = prevtrig;
}

void SendTrig_next_aka(SendTrig* unit, int inNumSamples) {
    float* trig = ZIN(0);
    int id = (int)ZIN0(1);
    float* value = ZIN(2);
    float prevtrig = unit->m_prevtrig;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); float curval = ZXP(value);
        if (curtrig > 0.f && prevtrig <= 0.f) { SendTrigger(&unit->mParent->mNode, id, curval); } prevtrig = curtrig;);
    unit->m_prevtrig = prevtrig;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////

static void Unit_next_nop(SendReply* unit, int inNumSamples) {}

void SendReply_Ctor(SendReply* unit) {
    const int kVarOffset = 3;

    unit->m_prevtrig = 0.f;
    unit->m_cmdNameSize = IN0(2);
    unit->m_valueSize = unit->mNumInputs - unit->m_cmdNameSize - kVarOffset;
    unit->m_valueOffset = kVarOffset + unit->m_cmdNameSize;

    // allocations
    const int cmdNameAllocSize = (unit->m_cmdNameSize + 1) * sizeof(char);
    const int valuesAllocSize = unit->m_valueSize * sizeof(float);
    char* chunk = (char*)RTAlloc(unit->mWorld, cmdNameAllocSize + valuesAllocSize);
    ClearUnitIfMemFailed(chunk);

    unit->m_cmdName = chunk;
    unit->m_values = (float*)(chunk + cmdNameAllocSize);

    for (int i = 0; i < (int)unit->m_cmdNameSize; i++)
        unit->m_cmdName[i] = (char)IN0(kVarOffset + i);

    // terminate string
    unit->m_cmdName[unit->m_cmdNameSize] = 0;

    if (INRATE(0) == calc_FullRate)
        SETCALC(SendReply_next_aka);
    else
        SETCALC(SendReply_next);
}

void SendReply_Dtor(SendReply* unit) { RTFree(unit->mWorld, unit->m_cmdName); }


void SendReply_next(SendReply* unit, int inNumSamples) {
    float* trig = IN(0);
    float prevtrig = unit->m_prevtrig;
    float* values = unit->m_values;
    int valueSize = unit->m_valueSize;
    int valueOffset = unit->m_valueOffset;
    for (int j = 0; j < inNumSamples; j++) {
        float curtrig = trig[j];
        if (curtrig > 0.f && prevtrig <= 0.f) {
            for (int i = 0; i < valueSize; i++)
                values[i] = IN(i + valueOffset)[0];

            SendNodeReply(&unit->mParent->mNode, (int)ZIN0(1), unit->m_cmdName, unit->m_valueSize, values);
        }
        prevtrig = curtrig;
    }
    unit->m_prevtrig = prevtrig;
}

void SendReply_next_aka(SendReply* unit, int inNumSamples) {
    float* trig = IN(0);
    float prevtrig = unit->m_prevtrig;
    float* values = unit->m_values;
    int valueSize = unit->m_valueSize;
    int valueOffset = unit->m_valueOffset;
    for (int j = 0; j < inNumSamples; j++) {
        float curtrig = trig[j];
        if (curtrig > 0.f && prevtrig <= 0.f) {
            for (int i = 0; i < valueSize; i++) {
                int offset = INRATE(i + valueOffset) != calc_FullRate ? 0 : j;
                values[i] = IN(i + valueOffset)[offset];
            }
            SendNodeReply(&unit->mParent->mNode, (int)ZIN0(1), unit->m_cmdName, unit->m_valueSize, values);
        }
        prevtrig = curtrig;
    }
    unit->m_prevtrig = prevtrig;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////

void Poll_Ctor(Poll* unit) {
    if (INRATE(0) == calc_FullRate) {
        if (INRATE(1) == calc_FullRate) {
            SETCALC(Poll_next_aa);
        } else {
            SETCALC(Poll_next_ak);
        }
    } else {
        SETCALC(Poll_next_kk);
    }

    const int idSize = (int)IN0(3); // number of chars in the id string
    unit->m_id_string = (char*)RTAlloc(unit->mWorld, (idSize + 1) * sizeof(char));
    ClearUnitIfMemFailed(unit->m_id_string);

    for (int i = 0; i < idSize; i++)
        unit->m_id_string[i] = (char)IN0(4 + i);

    unit->m_id_string[idSize] = '\0';
    unit->m_mayprint = unit->mWorld->mVerbosity >= -1;
    unit->m_trig = 0.f; // ready for trigger in first sample
}

void Poll_Dtor(Poll* unit) { RTFree(unit->mWorld, unit->m_id_string); }

void Poll_next_aa(Poll* unit, int inNumSamples) {
    float* in = IN(1);
    float* trig = IN(0);
    float lasttrig = unit->m_trig;
    for (int i = 0; i < inNumSamples; i++) {
        if ((lasttrig <= 0.0) && (trig[i] > 0.0)) {
            if (unit->m_mayprint)
                Print("%s: %g\n", unit->m_id_string, in[i]);

            if (IN0(2) >= 0.0)
                SendTrigger(&unit->mParent->mNode, (int)IN0(2), in[i]);
        }
        lasttrig = trig[i];
    }
    unit->m_trig = lasttrig;
}

void Poll_next_kk(Poll* unit, int inNumSamples) {
    float in = IN0(1);
    float trig = IN0(0);
    if ((unit->m_trig <= 0.0) && (trig > 0.0)) {
        if (unit->m_mayprint)
            Print("%s: %g\n", unit->m_id_string, in);

        if (IN0(2) >= 0.0)
            SendTrigger(&unit->mParent->mNode, (int)IN0(2), in);
    }
    unit->m_trig = trig;
}


void Poll_next_ak(Poll* unit, int inNumSamples) {
    float in = IN0(1);
    float* trig = IN(0);
    float lasttrig = unit->m_trig;
    for (int i = 0; i < inNumSamples; i++) {
        if ((lasttrig <= 0.0) && (trig[i] > 0.0)) {
            if (unit->m_mayprint) {
                Print("%s: %g\n", unit->m_id_string, in);
            }
            if (IN0(2) >= 0.0)
                SendTrigger(&unit->mParent->mNode, (int)IN0(2), in);
        }
        lasttrig = trig[i];
    }
    unit->m_trig = lasttrig;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////

void SetResetFF_Ctor(SetResetFF* unit) {
    if (INRATE(1) == calc_FullRate) {
        SETCALC(SetResetFF_next_a);
    } else {
        SETCALC(SetResetFF_next_k);
    }

    unit->m_prevtrig = 0.f;
    unit->m_prevreset = 0.f;
    unit->mLevel = 0.f;

    SetResetFF_next_k(unit, 1);

    unit->m_prevtrig = 0.f;
    unit->m_prevreset = 0.f;
    unit->mLevel = 0.f;
}


void SetResetFF_next_a(SetResetFF* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float* reset = ZIN(1);
    float prevtrig = unit->m_prevtrig;
    float prevreset = unit->m_prevreset;
    float level = unit->mLevel;

    LOOP1(inNumSamples, float curtrig = ZXP(trig); float curreset = ZXP(reset);
          if (prevreset <= 0.f && curreset > 0.f) level = 0.f; else if (prevtrig <= 0.f && curtrig > 0.f) level = 1.f;
          ZXP(out) = level; prevtrig = curtrig; prevreset = curreset;);
    unit->m_prevtrig = prevtrig;
    unit->m_prevreset = prevreset;
    unit->mLevel = level;
}

void SetResetFF_next_k(SetResetFF* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float curreset = ZIN0(1);
    float prevtrig = unit->m_prevtrig;
    float prevreset = unit->m_prevreset;
    float level = unit->mLevel;

    float curtrig = ZXP(trig);
    if (prevreset <= 0.f && curreset > 0.f)
        level = 0.f;
    else if (prevtrig <= 0.f && curtrig > 0.f)
        level = 1.f;
    ZXP(out) = level;
    prevtrig = curtrig;

    LOOP(inNumSamples - 1, curtrig = ZXP(trig); if (prevtrig <= 0.f && curtrig > 0.f) level = 1.f; ZXP(out) = level;
         prevtrig = curtrig;);
    unit->m_prevtrig = prevtrig;
    unit->m_prevreset = curreset;
    unit->mLevel = level;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////


void ToggleFF_Ctor(ToggleFF* unit) {
    if (unit->mCalcRate == calc_FullRate && INRATE(0) != calc_FullRate) {
        SETCALC(ToggleFF_next_ak);
    } else {
        SETCALC(ToggleFF_next);
    }

    unit->m_prevtrig = 0.f;
    unit->mLevel = 0.f;
    ToggleFF_next(unit, 1);
    unit->m_prevtrig = 0.f;
    unit->mLevel = 0.f;
}

void ToggleFF_next(ToggleFF* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;

    LOOP1(inNumSamples, float curtrig = ZXP(trig); if (prevtrig <= 0.f && curtrig > 0.f) level = 1.f - level;
          ZXP(out) = level; prevtrig = curtrig;);

    unit->m_prevtrig = prevtrig;
    unit->mLevel = level;
}

void ToggleFF_next_ak(ToggleFF* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float curtrig = ZIN0(0);
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;

    if (prevtrig <= 0.f && curtrig > 0.f)
        level = 1.f - level;
    prevtrig = curtrig;
    LOOP1(inNumSamples, ZXP(out) = level;);

    unit->m_prevtrig = prevtrig;
    unit->mLevel = level;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////


#ifdef NOVA_SIMD
void Latch_next_ak_nova(Latch* unit, int inNumSamples) {
    float level = unit->mLevel;

    float curtrig = ZIN0(1);
    if (unit->m_prevtrig <= 0.f && curtrig > 0.f)
        level = ZIN0(0);

    nova::setvec_simd(OUT(0), level, inNumSamples);

    unit->m_prevtrig = curtrig;
    unit->mLevel = level;
}

void Latch_next_ak_nova_64(Latch* unit, int inNumSamples) {
    float level = unit->mLevel;

    float curtrig = ZIN0(1);
    if (unit->m_prevtrig <= 0.f && curtrig > 0.f)
        level = ZIN0(0);

    nova::setvec_simd<64>(OUT(0), level);

    unit->m_prevtrig = curtrig;
    unit->mLevel = level;
}
#endif

void Latch_Ctor(Latch* unit) {
    if (INRATE(1) == calc_FullRate) {
        SETCALC(Latch_next_aa);
    } else {
#ifdef NOVA_SIMD
        if (BUFLENGTH == 64)
            SETCALC(Latch_next_ak_nova_64);
        if (boost::alignment::is_aligned(BUFLENGTH, 16))
            SETCALC(Latch_next_ak_nova);
        else
#endif
            SETCALC(Latch_next_ak);
    }

    unit->m_prevtrig = 0.f;
    unit->mLevel = 0.f;

    Latch_next_ak(unit, 1);

    unit->m_prevtrig = 0.f;
    unit->mLevel = 0.f;
}


void Latch_next_ak(Latch* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float level = unit->mLevel;

    float curtrig = ZIN0(1);
    if (unit->m_prevtrig <= 0.f && curtrig > 0.f)
        level = ZIN0(0);

    LOOP1(inNumSamples, ZXP(out) = level;);

    unit->m_prevtrig = curtrig;
    unit->mLevel = level;
}


void Latch_next_aa(Latch* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float* trig = ZIN(1);
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); if (prevtrig <= 0.f && curtrig > 0.f) level = ZXP(in);
        else { PZ(in); } ZXP(out) = level; prevtrig = curtrig;);
    unit->m_prevtrig = prevtrig;
    unit->mLevel = level;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef NOVA_SIMD
FLATTEN void Gate_next_ak_nova(Gate* unit, int inNumSamples) {
    float* trig = ZIN(1);
    float level = unit->mLevel;

    float curtrig = ZXP(trig);
    if (curtrig > 0.f) {
        nova::copyvec_simd(OUT(0), IN(0), inNumSamples);
        unit->mLevel = IN(0)[inNumSamples - 1];
    } else
        nova::setvec_simd(OUT(0), level, inNumSamples);
}

FLATTEN void Gate_next_ak_nova_64(Gate* unit, int inNumSamples) {
    float* trig = ZIN(1);
    float level = unit->mLevel;

    float curtrig = ZXP(trig);
    if (curtrig > 0.f) {
        nova::copyvec_simd<64>(OUT(0), IN(0));
        unit->mLevel = IN(0)[inNumSamples - 1];
    } else
        nova::setvec_simd<64>(OUT(0), level);
}
#endif

void Gate_Ctor(Gate* unit) {
    if (INRATE(1) == calc_FullRate) {
        SETCALC(Gate_next_aa);
    } else {
#ifdef NOVA_SIMD
        if (BUFLENGTH == 64)
            SETCALC(Gate_next_ak_nova_64);
        if (boost::alignment::is_aligned(BUFLENGTH, 16))
            SETCALC(Gate_next_ak_nova);
        else
#endif
            SETCALC(Gate_next_ak);
    }

    unit->mLevel = 0.f;

    Gate_next_ak(unit, 1);

    unit->mLevel = 0.f;
}


void Gate_next_ak(Gate* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float* trig = ZIN(1);
    float level = unit->mLevel;

    float curtrig = ZXP(trig);
    if (curtrig > 0.f) {
        LOOP1(inNumSamples, level = ZXP(in); ZXP(out) = level;);
        unit->mLevel = level;
    } else {
        LOOP1(inNumSamples, ZXP(out) = level;);
    }
}

void Gate_next_aa(Gate* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float* trig = ZIN(1);
    float level = unit->mLevel;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); if (curtrig > 0.f) level = ZXP(in);
        else { PZ(in); } ZXP(out) = level;);
    unit->mLevel = level;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////


void Schmidt_Ctor(Schmidt* unit) {
    SETCALC(Schmidt_next);

    unit->mLevel = 0.f;

    Schmidt_next(unit, 1);

    unit->mLevel = 0.f;
}

void Schmidt_next(Schmidt* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float lo = ZIN0(1);
    float hi = ZIN0(2);
    float level = unit->mLevel;
    LOOP1(
        inNumSamples, float zin = ZXP(in); if (level == 1.) {
            if (zin < lo)
                level = 0.f;
        } else {
            if (zin > hi)
                level = 1.f;
        } ZXP(out) = level;);
    unit->mLevel = level;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////

void PulseDivider_Ctor(PulseDivider* unit) {
    SETCALC(PulseDivider_next);

    unit->m_prevtrig = 0.f;
    unit->mLevel = 0.f;
    long initCounter = unit->mCounter = static_cast<long>(floor(ZIN0(2) + 0.5));

    PulseDivider_next(unit, 1);

    unit->m_prevtrig = 0.f;
    unit->mLevel = 0.f;
    unit->mCounter = initCounter;
}


void PulseDivider_next(PulseDivider* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    long div = (long)ZIN0(1);
    float prevtrig = unit->m_prevtrig;
    long counter = unit->mCounter;

    LOOP1(
        inNumSamples, float z; float curtrig = ZXP(trig); if (prevtrig <= 0.f && curtrig > 0.f) {
            counter++;
            if (counter >= div) {
                counter = 0;
                z = 1.f;
            } else {
                z = 0.f;
            }
        } else { z = 0.f; } ZXP(out) = z;
        prevtrig = curtrig;);
    unit->mCounter = counter;
    unit->m_prevtrig = prevtrig;
}

//////////////////////////////////////////////////////////////////////////////////////////


void PulseCount_Ctor(PulseCount* unit) {
    if (INRATE(1) == calc_FullRate) {
        SETCALC(PulseCount_next_a);
    } else if (INRATE(1) == calc_BufRate) {
        SETCALC(PulseCount_next_k);
    } else {
        SETCALC(PulseCount_next_0);
    }
    unit->m_prevtrig = 0.f;
    unit->m_prevreset = 0.f;
    unit->mLevel = 0.f;

    PulseCount_next_k(unit, 1);

    unit->m_prevtrig = 0.f;
    unit->m_prevreset = 0.f;
    unit->mLevel = 0.f;
}


void PulseCount_next_a(PulseCount* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float* reset = ZIN(1);
    float prevtrig = unit->m_prevtrig;
    float prevreset = unit->m_prevreset;
    float level = unit->mLevel;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); float curreset = ZXP(reset);
        if (prevreset <= 0.f && curreset > 0.f) level = 0.f;
        else if (prevtrig <= 0.f && curtrig > 0.f) { level += 1.f; } ZXP(out) = level; prevtrig = curtrig;
        prevreset = curreset;);
    unit->mLevel = level;
    unit->m_prevtrig = prevtrig;
    unit->m_prevreset = prevreset;
}

void PulseCount_next_k(PulseCount* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float curreset = ZIN0(1);
    float prevtrig = unit->m_prevtrig;
    float prevreset = unit->m_prevreset;
    float level = unit->mLevel;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); if (prevreset <= 0.f && curreset > 0.f) level = 0.f;
        else if (prevtrig <= 0.f && curtrig > 0.f) { level += 1.f; } ZXP(out) = level; prevtrig = curtrig;
        prevreset = curreset;);
    unit->mLevel = level;
    unit->m_prevtrig = prevtrig;
    unit->m_prevreset = prevreset;
}

void PulseCount_next_0(PulseCount* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig);
        if (prevtrig <= 0.f && curtrig > 0.f) { level += 1.f; } ZXP(out) = level; prevtrig = curtrig;);
    unit->mLevel = level;
    unit->m_prevtrig = prevtrig;
}

//////////////////////////////////////////////////////////////////////////////////////////


void Stepper_Ctor(Stepper* unit) {
    if (unit->mCalcRate == calc_FullRate && INRATE(0) == calc_FullRate && INRATE(1) == calc_ScalarRate) {
        SETCALC(Stepper_next_a0);
    } else if (unit->mCalcRate == calc_FullRate && INRATE(0) == calc_FullRate && INRATE(1) != calc_FullRate) {
        SETCALC(Stepper_next_ak);
    } else {
        SETCALC(Stepper_next_aa);
    }

    int32 resetval = (int32)ZIN0(5);

    unit->m_prevtrig = 0.f;
    unit->m_prevreset = 0.f;
    unit->mLevel = (float)resetval;

    Stepper_next_ak(unit, 1);

    unit->m_prevtrig = 0.f;
    unit->m_prevreset = 0.f;
    unit->mLevel = (float)resetval;
}


void Stepper_next_aa(Stepper* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float* reset = ZIN(1);
    int32 zmin = (int32)ZIN0(2);
    int32 zmax = (int32)ZIN0(3);
    int32 step = (int32)ZIN0(4);
    int32 resetval = (int32)ZIN0(5);
    float prevtrig = unit->m_prevtrig;
    float prevreset = unit->m_prevreset;
    float level = unit->mLevel;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); float curreset = ZXP(reset); if (prevreset <= 0.f && curreset > 0.f) {
            level = (float)sc_wrap(resetval, zmin, zmax);
        } else if (prevtrig <= 0.f && curtrig > 0.f) {
            level = (float)sc_wrap((int32)level + step, zmin, zmax);
        } ZXP(out) = level;
        prevtrig = curtrig; prevreset = curreset;);
    unit->mLevel = level;
    unit->m_prevtrig = prevtrig;
    unit->m_prevreset = prevreset;
}

void Stepper_next_ak(Stepper* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float curreset = ZIN0(1);
    int32 zmin = (int32)ZIN0(2);
    int32 zmax = (int32)ZIN0(3);
    int32 step = (int32)ZIN0(4);
    int32 resetval = (int32)ZIN0(5);
    float prevtrig = unit->m_prevtrig;
    float prevreset = unit->m_prevreset;
    float level = unit->mLevel;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); if (prevreset <= 0.f && curreset > 0.f) {
            level = (float)sc_wrap(resetval, zmin, zmax);
        } else if (prevtrig <= 0.f && curtrig > 0.f) {
            level = (float)sc_wrap((int32)level + step, zmin, zmax);
        } ZXP(out) = level;
        prevtrig = curtrig; prevreset = curreset;);
    unit->mLevel = level;
    unit->m_prevtrig = prevtrig;
    unit->m_prevreset = prevreset;
}

void Stepper_next_a0(Stepper* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    int32 zmin = (int32)ZIN0(2);
    int32 zmax = (int32)ZIN0(3);
    int32 step = (int32)ZIN0(4);
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); if (prevtrig <= 0.f && curtrig > 0.f) {
            level = (float)sc_wrap((int32)level + step, zmin, zmax);
        } ZXP(out) = level;
        prevtrig = curtrig;);
    unit->mLevel = level;
    unit->m_prevtrig = prevtrig;
}

//////////////////////////////////////////////////////////////////////////////////////////

void TDelay_Ctor(TDelay* unit) {
    SETCALC(TDelay_next);

    unit->m_prevtrig = 0.f;
    unit->mCounter = 0;

    TDelay_next(unit, 1);

    unit->m_prevtrig = 0.f;
    unit->mCounter = 0;
}


void TDelay_next(TDelay* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* trig = ZIN(0);
    float dur = ZIN0(1);
    float prevtrig = unit->m_prevtrig;
    long counter = unit->mCounter;

    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); float zout; if (counter > 1) {
            counter--;
            zout = 0.f;
        } else if (counter <= 0) {
            if (prevtrig <= 0.f && curtrig > 0.f) {
                counter = (long)(dur * unit->mRate->mSampleRate + .5f);
                if (counter < 1)
                    counter = 1;
            }
            zout = 0.f;
        } else {
            counter = 0;
            zout = 1.f;
        } ZXP(out) = zout;
        prevtrig = curtrig;);
    unit->m_prevtrig = prevtrig;
    unit->mCounter = counter;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////


void ZeroCrossing_Ctor(ZeroCrossing* unit) {
    SETCALC(ZeroCrossing_next_a);

    unit->m_prevfrac = 0.f;
    unit->m_previn = ZIN0(0);
    ZOUT0(0) = unit->mLevel = 0.f;
    unit->mCounter = 0;
}

void ZeroCrossing_next_a(ZeroCrossing* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float previn = unit->m_previn;
    float prevfrac = unit->m_prevfrac;
    float level = unit->mLevel;
    long counter = unit->mCounter;

    LOOP1(
        inNumSamples, counter++; float curin = ZXP(in); if (counter > 4 && previn <= 0.f && curin > 0.f) {
            float frac = -previn / (curin - previn);
            level = unit->mRate->mSampleRate / (frac + counter - prevfrac);
            prevfrac = frac;
            counter = 0;
        } ZXP(out) = level;
        previn = curin;);

    unit->m_previn = previn;
    unit->m_prevfrac = prevfrac;
    unit->mLevel = level;
    unit->mCounter = counter;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////

void Timer_Ctor(Timer* unit) {
    SETCALC(Timer_next_a);

    unit->m_prevfrac = 0.f;
    unit->m_previn = 0;
    unit->mCounter = -1;
    unit->mLevel = 0;
    Timer_next_a(unit, 1);
    unit->m_prevfrac = 0.f;
    unit->m_previn = 0;
    unit->mCounter = -1;
    unit->mLevel = 0;
}

void Timer_next_a(Timer* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float previn = unit->m_previn;
    float prevfrac = unit->m_prevfrac;
    float level = unit->mLevel;
    long counter = unit->mCounter;

    LOOP1(
        inNumSamples, counter++; float curin = ZXP(in); if (previn <= 0.f && curin > 0.f) {
            float frac = -previn / (curin - previn);
            level = unit->mRate->mSampleDur * (frac + counter - prevfrac);
            prevfrac = frac;
            counter = 0;
        } ZXP(out) = level;
        previn = curin;);

    unit->m_previn = previn;
    unit->m_prevfrac = prevfrac;
    unit->mLevel = level;
    unit->mCounter = counter;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////

void Sweep_Ctor(Sweep* unit) {
    if (INRATE(0) == calc_ScalarRate) {
        if (INRATE(1) == calc_FullRate) {
            SETCALC(Sweep_next_0a);
        } else {
            SETCALC(Sweep_next_0k);
        }
    } else if (INRATE(0) == calc_BufRate) {
        if (INRATE(1) == calc_FullRate) {
            SETCALC(Sweep_next_ka);
        } else {
            SETCALC(Sweep_next_kk);
        }
    } else {
        if (INRATE(1) == calc_FullRate) {
            SETCALC(Sweep_next_aa);
        } else {
            SETCALC(Sweep_next_ak);
        }
    }

    unit->m_previn = 0.f;
    unit->mLevel = 0.f;
    Sweep_next_aa(unit, 1);
    unit->m_previn = 0.f;
    unit->mLevel = 0.f;
}

void Sweep_next_0k(Sweep* unit, int inNumSamples) {
    float* out = ZOUT(0);
    double rate = ZIN0(1) * SAMPLEDUR;
    double level = unit->mLevel;

    LOOP1(inNumSamples, ZXP(out) = level; level += rate);

    unit->mLevel = level;
}

void Sweep_next_0a(Sweep* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* rate = ZIN(1);
    double level = unit->mLevel;
    float sampledur = SAMPLEDUR;

    LOOP1(inNumSamples, float step = ZXP(rate) * sampledur; ZXP(out) = level; level += step);

    unit->mLevel = level;
}

void Sweep_next_kk(Sweep* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float curin = ZIN0(0);
    double step = ZIN0(1) * SAMPLEDUR;
    float previn = unit->m_previn;
    double level = unit->mLevel;

    if (previn <= 0.f && curin > 0.f) {
        // note: frac calculation is wrong
        float frac = -previn / (curin - previn);
        // also, we are applying current rate to previous increment, which is not formally correct
        level = frac * step;
    }

    LOOP1(inNumSamples, ZXP(out) = level; level += step);

    unit->m_previn = curin;
    unit->mLevel = level;
}

void Sweep_next_ka(Sweep* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float curin = ZIN0(0);
    float* rate = ZIN(1);
    float previn = unit->m_previn;
    double level = unit->mLevel;
    float sampledur = SAMPLEDUR;

    if (previn <= 0.f && curin > 0.f) {
        // note: frac calculation is wrong
        float frac = -previn / (curin - previn);
        // also, we are applying current rate to previous increment, which is not formally correct
        level = frac * rate[ZOFF] * sampledur;
    }

    LOOP1(inNumSamples, float step = ZXP(rate) * sampledur; ZXP(out) = level; level += step;);

    unit->m_previn = curin;
    unit->mLevel = level;
}

void Sweep_next_ak(Sweep* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    double step = ZIN0(1) * SAMPLEDUR;
    float previn = unit->m_previn;
    double level = unit->mLevel;

    LOOP1(
        inNumSamples, float curin = ZXP(in); if (previn <= 0.f && curin > 0.f) {
            // note: frac calculation is wrong
            float frac = -previn / (curin - previn);
            // also, we are applying current rate to previous increment, which is not formally correct
            level = frac * step;
        } ZXP(out) = level;
        level += step; previn = curin;);

    unit->m_previn = previn;
    unit->mLevel = level;
}

void Sweep_next_aa(Sweep* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float* rate = ZIN(1);
    float previn = unit->m_previn;
    double level = unit->mLevel;
    float sampledur = SAMPLEDUR;

    LOOP1(
        inNumSamples, float curin = ZXP(in); float step = ZXP(rate) * sampledur; if (previn <= 0.f && curin > 0.f) {
            // note: frac calculation is wrong
            float frac = -previn / (curin - previn);
            // also, we are applying current rate to previous increment, which is not formally correct
            level = frac * step;
        } ZXP(out) = level;
        level += step; previn = curin;);

    unit->m_previn = previn;
    unit->mLevel = level;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////

void Phasor_Ctor(Phasor* unit) {
    if (unit->mCalcRate == calc_FullRate) {
        if (INRATE(0) == calc_FullRate) {
            if (INRATE(1) == calc_FullRate) {
                SETCALC(Phasor_next_aa);
            } else {
                SETCALC(Phasor_next_ak);
            }
        } else {
            SETCALC(Phasor_next_kk);
        }
    } else {
        SETCALC(Phasor_next_ak);
    }

    unit->m_previn = 0;
    float initLevel = unit->mLevel = IN0(2);

    unit->mCalcFunc(unit, 1);

    unit->m_previn = 0;
    unit->mLevel = initLevel;
}

void Phasor_next_kk(Phasor* unit, int inNumSamples) {
    float* out = ZOUT(0);

    float in = ZIN0(0);
    double rate = ZIN0(1);
    double start = ZIN0(2);
    double end = ZIN0(3);
    float resetPos = ZIN0(4);

    float previn = unit->m_previn;
    double level = unit->mLevel;

    if (previn <= 0.f && in > 0.f) {
        level = resetPos;
    }
    LOOP1(inNumSamples, level = sc_wrap(level, start, end); ZXP(out) = level; level += rate;);

    unit->m_previn = in;
    unit->mLevel = level;
}

void Phasor_next_ak(Phasor* unit, int inNumSamples) {
    float* out = ZOUT(0);

    float* in = ZIN(0);
    double rate = ZIN0(1);
    double start = ZIN0(2);
    double end = ZIN0(3);
    float resetPos = ZIN0(4);

    float previn = unit->m_previn;
    double level = unit->mLevel;

    LOOP1(
        inNumSamples, float curin = ZXP(in); if (previn <= 0.f && curin > 0.f) {
            float frac = 1.f - previn / (curin - previn);
            level = resetPos + frac * rate;
        } ZXP(out) = level;
        level += rate; level = sc_wrap(level, start, end);

        previn = curin;);

    unit->m_previn = previn;
    unit->mLevel = level;
}

void Phasor_next_aa(Phasor* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float* rate = ZIN(1);
    double start = ZIN0(2);
    double end = ZIN0(3);
    float resetPos = ZIN0(4);

    float previn = unit->m_previn;
    double level = unit->mLevel;

    LOOP1(
        inNumSamples, float curin = ZXP(in); double zrate = ZXP(rate); if (previn <= 0.f && curin > 0.f) {
            float frac = 1.f - previn / (curin - previn);
            level = resetPos + frac * zrate;
        } ZXP(out) = level;
        level += zrate; level = sc_wrap(level, start, end); previn = curin;);

    unit->m_previn = previn;
    unit->mLevel = level;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////


void Peak_next_ak_unroll(Peak* unit, int inNumSamples);
void Peak_next_ai_unroll(Peak* unit, int inNumSamples);

#ifdef NOVA_SIMD
FLATTEN void Peak_next_ak_k_nova(Peak* unit, int inNumSamples);
FLATTEN void Peak_next_ai_k_nova(Peak* unit, int inNumSamples);
#endif

void Peak_Ctor(Peak* unit) {
    if (BUFLENGTH == 1 && INRATE(0) == calc_FullRate) {
        /* audio-rate input with control-rate output */
        if (INRATE(1) == calc_FullRate) {
            SETCALC(Peak_next_aa_k);
        } else if (INRATE(1) == calc_ScalarRate) {
#ifdef NOVA_SIMD
            if (INBUFLENGTH(0) & 7)
                SETCALC(Peak_next_ai_k);
            else
                SETCALC(Peak_next_ai_k_nova);
#else
            SETCALC(Peak_next_ai_k);
#endif
        } else {
#ifdef NOVA_SIMD
            if (INBUFLENGTH(0) & 7)
                SETCALC(Peak_next_ak_k);
            else
                SETCALC(Peak_next_ak_k_nova);
#else
            SETCALC(Peak_next_ak_k);
#endif
        }
    } else {
        if (INRATE(1) == calc_FullRate) {
            SETCALC(Peak_next_aa);
        } else if (INRATE(1) == calc_ScalarRate) {
            if (BUFLENGTH & 15)
                SETCALC(Peak_next_ai);
            else
                SETCALC(Peak_next_ai_unroll);
        } else {
            if (BUFLENGTH & 15)
                SETCALC(Peak_next_ak);
            else
                SETCALC(Peak_next_ak_unroll);
        }
    }
    unit->m_prevtrig = 0.f;
    float initLevel = unit->mLevel = std::abs(ZIN0(0));
    Peak_next_ai(unit, 1);
    unit->m_prevtrig = 0.f;
    unit->mLevel = initLevel;
}

void Peak_next_ak(Peak* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float curtrig = ZIN0(1);
    float level = unit->mLevel;
    float inlevel;
    LOOP1(inNumSamples, inlevel = std::abs(ZXP(in)); level = std::max(inlevel, level); ZXP(out) = level;);
    if (unit->m_prevtrig <= 0.f && curtrig > 0.f)
        level = inlevel;
    unit->m_prevtrig = curtrig;
    unit->mLevel = level;
}

void Peak_next_ai(Peak* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float level = unit->mLevel;
    float inlevel;
    LOOP1(inNumSamples, inlevel = std::abs(ZXP(in)); level = std::max(inlevel, level); ZXP(out) = level;);
    unit->mLevel = level;
}

void Peak_next_aa(Peak* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float* trig = ZIN(1);
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;

    LOOP1(inNumSamples, float curtrig = ZXP(trig); float inlevel = std::abs(ZXP(in)); level = std::max(inlevel, level);
          ZXP(out) = level; if (prevtrig <= 0.f && curtrig > 0.f) level = inlevel; prevtrig = curtrig;);
    unit->m_prevtrig = prevtrig;
    unit->mLevel = level;
}

static inline float Peak_unroll_body(Peak* unit, int inNumSamples, float& level) {
    float* out = OUT(0);
    float* in = IN(0);
    float inlevel;

    using namespace std;
    for (int i = 0; i != inNumSamples; i += 8, out += 8, in += 8) {
        float level0 = max(abs(in[0]), level);
        float level1 = max(abs(in[1]), level0);
        float level2 = max(abs(in[2]), level1);
        float level3 = max(abs(in[3]), level2);
        float level4 = max(abs(in[4]), level3);
        float level5 = max(abs(in[5]), level4);
        float level6 = max(abs(in[6]), level5);
        inlevel = abs(in[7]);
        float level7 = max(inlevel, level6);
        out[0] = level0;
        out[1] = level1;
        out[2] = level2;
        out[3] = level3;
        out[4] = level4;
        out[5] = level5;
        out[6] = level6;
        out[7] = level7;
        level = level7;
    }
    return inlevel; /* input level of the last sample */
}

void Peak_next_ak_unroll(Peak* unit, int inNumSamples) {
    float curtrig = ZIN0(1);
    float level = unit->mLevel;
    float inlevel = Peak_unroll_body(unit, inNumSamples, level);

    if (unit->m_prevtrig <= 0.f && curtrig > 0.f)
        level = inlevel;
    unit->m_prevtrig = curtrig;
    unit->mLevel = level;
}

void Peak_next_ai_unroll(Peak* unit, int inNumSamples) {
    float level = unit->mLevel;
    Peak_unroll_body(unit, inNumSamples, level);
    unit->mLevel = level;
}

void Peak_next_ak_k(Peak* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float curtrig = ZIN0(1);
    float level;
    inNumSamples = INBUFLENGTH(0);
    if (unit->m_prevtrig <= 0.f && curtrig > 0.f) {
        level = std::abs(ZXP(in));
        inNumSamples -= 1;
    } else
        level = unit->mLevel;
    LOOP1(inNumSamples, level = std::max(std::abs(ZXP(in)), level););
    ZXP(out) = level;
    unit->m_prevtrig = curtrig;
    unit->mLevel = level;
}

void Peak_next_ai_k(Peak* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float level = unit->mLevel;
    inNumSamples = INBUFLENGTH(0);
    LOOP1(inNumSamples, level = std::max(std::abs(ZXP(in)), level););
    ZXP(out) = level;
    unit->mLevel = level;
}

void Peak_next_aa_k(Peak* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float* trig = ZIN(1);
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;
    float out_level;
    bool triggered = false;

    inNumSamples = INBUFLENGTH(0);
    LOOP1(
        inNumSamples, float curtrig = ZXP(trig); float inlevel = std::abs(ZXP(in)); level = std::max(inlevel, level);
        if (prevtrig <= 0.f && curtrig > 0.f) {
            triggered = true;
            out_level = level;
            level = inlevel;
        } prevtrig = curtrig;);
    if (triggered)
        ZXP(out) = out_level;
    else
        ZXP(out) = level;
    unit->m_prevtrig = prevtrig;
    unit->mLevel = level;
}

#ifdef NOVA_SIMD
void Peak_next_ak_k_nova(Peak* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = IN(0);
    float curtrig = ZIN0(1);
    float level = unit->mLevel;
    float inlevel;
    inNumSamples = INBUFLENGTH(0);
    inlevel = nova::peak_vec_simd(in, &level, inNumSamples);
    ZXP(out) = level;
    if (unit->m_prevtrig <= 0.f && curtrig > 0.f)
        level = inlevel;
    unit->m_prevtrig = curtrig;
    unit->mLevel = level;
}

void Peak_next_ai_k_nova(Peak* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float level = unit->mLevel;
    float inlevel;
    inNumSamples = INBUFLENGTH(0);
    inlevel = nova::peak_vec_simd(in, &level, inNumSamples);
    ZXP(out) = level;
    unit->mLevel = level;
}
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////////

void RunningMin_Ctor(RunningMin* unit) {
    if (INRATE(1) == calc_FullRate) {
        SETCALC(RunningMin_next_aa);
    } else if (INRATE(1) == calc_ScalarRate) {
        SETCALC(RunningMin_next_ai);
    } else {
        SETCALC(RunningMin_next_ak);
    }
    unit->m_prevtrig = 0.f;
    ZOUT0(0) = unit->mLevel = ZIN0(0);
}

void RunningMin_next_ak(RunningMin* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float curtrig = ZIN0(1);
    float level = unit->mLevel;
    float inlevel;
    LOOP1(inNumSamples, inlevel = ZXP(in); if (inlevel < level) level = inlevel; ZXP(out) = level;);
    if (unit->m_prevtrig <= 0.f && curtrig > 0.f)
        level = inlevel;
    unit->m_prevtrig = curtrig;
    unit->mLevel = level;
}

void RunningMin_next_ai(RunningMin* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float level = unit->mLevel;
    float inlevel;
    LOOP1(inNumSamples, inlevel = ZXP(in); if (inlevel < level) level = inlevel; ZXP(out) = level;);
    unit->mLevel = level;
}

void RunningMin_next_aa(RunningMin* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float* trig = ZIN(1);
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;

    LOOP1(inNumSamples, float curtrig = ZXP(trig); float inlevel = ZXP(in); if (inlevel < level) level = inlevel;
          ZXP(out) = level; if (prevtrig <= 0.f && curtrig > 0.f) level = inlevel; prevtrig = curtrig;);
    unit->m_prevtrig = prevtrig;
    unit->mLevel = level;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////
void RunningMax_Ctor(RunningMax* unit) {
    if (INRATE(1) == calc_FullRate) {
        SETCALC(RunningMax_next_aa);
    } else if (INRATE(1) == calc_ScalarRate) {
        SETCALC(RunningMax_next_ai);
    } else {
        SETCALC(RunningMax_next_ak);
    }
    unit->m_prevtrig = 0.f;
    ZOUT0(0) = unit->mLevel = ZIN0(0);
}

void RunningMax_next_ak(RunningMax* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float curtrig = ZIN0(1);
    float level = unit->mLevel;
    float inlevel;
    LOOP1(inNumSamples, inlevel = ZXP(in); if (inlevel > level) level = inlevel; ZXP(out) = level;);
    if (unit->m_prevtrig <= 0.f && curtrig > 0.f)
        level = inlevel;
    unit->m_prevtrig = curtrig;
    unit->mLevel = level;
}

void RunningMax_next_ai(RunningMax* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float level = unit->mLevel;
    float inlevel;
    LOOP1(inNumSamples, inlevel = ZXP(in); if (inlevel > level) level = inlevel; ZXP(out) = level;);
    unit->mLevel = level;
}

void RunningMax_next_aa(RunningMax* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float* trig = ZIN(1);
    float prevtrig = unit->m_prevtrig;
    float level = unit->mLevel;

    LOOP1(inNumSamples, float curtrig = ZXP(trig); float inlevel = ZXP(in); if (inlevel > level) level = inlevel;
          ZXP(out) = level; if (prevtrig <= 0.f && curtrig > 0.f) level = inlevel; prevtrig = curtrig;);
    unit->m_prevtrig = prevtrig;
    unit->mLevel = level;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////


void PeakFollower_Ctor(PeakFollower* unit) {
    if (INRATE(1) == calc_ScalarRate) {
        SETCALC(PeakFollower_next_ai);
    } else {
        SETCALC(PeakFollower_next);
    }

    unit->mDecay = ZIN0(1);
    float initLevel = unit->mLevel = std::abs(ZIN0(0));
    PeakFollower_next(unit, 1);
    unit->mLevel = initLevel;
}

void PeakFollower_next(PeakFollower* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float decay = ZIN0(1);
    float level = unit->mLevel;


    if (decay == unit->mDecay) {
        LOOP1(
            inNumSamples, float inlevel = std::abs(ZXP(in)); if (inlevel >= level) { level = inlevel; } else {
                level = inlevel + decay * (level - inlevel);
            } ZXP(out) = level;);

    } else {
        float decay_slope = CALCSLOPE(decay, unit->mDecay);
        if (decay >= 0.f && unit->mDecay >= 0.f) {
            LOOP1(
                inNumSamples, float inlevel = std::abs(ZXP(in)); if (inlevel >= level) { level = inlevel; } else {
                    level = inlevel + decay * (level - inlevel);
                    decay += decay_slope;
                };
                ZXP(out) = level;);
        } else if (decay <= 0.f && unit->mDecay <= 0.f) {
            LOOP1(
                inNumSamples, float inlevel = std::abs(ZXP(in)); if (inlevel >= level) { level = inlevel; } else {
                    level = inlevel + decay * (level + inlevel);
                    decay += decay_slope;
                };
                ZXP(out) = level;);
        } else {
            LOOP1(
                inNumSamples, float inlevel = std::abs(ZXP(in)); if (inlevel >= level) { level = inlevel; } else {
                    level = (1.f - std::abs(decay)) * inlevel + decay * level;
                    decay += decay_slope;
                };
                ZXP(out) = level;);
        };
    }

    unit->mLevel = level;
    unit->mDecay = decay;
}


void PeakFollower_next_ai(PeakFollower* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float decay = ZIN0(1);
    float level = unit->mLevel;

    LOOP1(
        inNumSamples, float inlevel = std::abs(ZXP(in)); if (inlevel >= level) { level = inlevel; } else {
            level = inlevel + decay * (level - inlevel);
        } ZXP(out) = level;);

    unit->mLevel = level;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////


void MostChange_Ctor(MostChange* unit) {
    if (INRATE(0) == calc_FullRate) {
        if (INRATE(1) == calc_FullRate) {
            SETCALC(MostChange_next_aa);
        } else {
            SETCALC(MostChange_next_ak);
        }
    } else {
        if (INRATE(1) == calc_FullRate) {
            SETCALC(MostChange_next_ka);
        } else {
            SETCALC(MostChange_next_aa);
        }
    }
    unit->mPrevA = 0.f;
    unit->mPrevB = 0.f;
    unit->mRecent = 1;
    MostChange_next_aa(unit, 1);
    unit->mPrevA = 0.f;
    unit->mPrevB = 0.f;
    unit->mRecent = 1;
}

void MostChange_next_ak(MostChange* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* a = ZIN(0);
    float xb = ZIN0(1);
    float prevA = unit->mPrevA;
    float prevB = unit->mPrevB;
    int recent = unit->mRecent;
    LOOP1(
        inNumSamples, float xa = ZXP(a); float diff = std::abs(xa - prevA) - std::abs(xb - prevB); if (diff > 0.f) {
            recent = 0;
            ZXP(out) = xa;
        } else if (diff < 0.f) {
            recent = 1;
            ZXP(out) = xb;
        } else { ZXP(out) = recent ? xb : xa; } prevA = xa;
        prevB = xb;);
    unit->mPrevA = prevA;
    unit->mPrevB = prevB;
    unit->mRecent = recent;
}

void MostChange_next_aa(MostChange* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* a = ZIN(0);
    float* b = ZIN(1);
    float prevA = unit->mPrevA;
    float prevB = unit->mPrevB;
    int recent = unit->mRecent;
    LOOP1(
        inNumSamples, float xa = ZXP(a); float xb = ZXP(b); float diff = std::abs(xa - prevA) - std::abs(xb - prevB);
        if (diff > 0.f) {
            recent = 0;
            ZXP(out) = xa;
        } else if (diff < 0.f) {
            recent = 1;
            ZXP(out) = xb;
        } else { ZXP(out) = recent ? xb : xa; } prevA = xa;
        prevB = xb;);
    unit->mPrevA = prevA;
    unit->mPrevB = prevB;
    unit->mRecent = recent;
}

void MostChange_next_ka(MostChange* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float xa = ZIN0(0);
    float* b = ZIN(1);
    float prevA = unit->mPrevA;
    float prevB = unit->mPrevB;
    int recent = unit->mRecent;
    LOOP1(
        inNumSamples, float xb = ZXP(b); float diff = std::abs(xa - prevA) - std::abs(xb - prevB); if (diff > 0.f) {
            recent = 0;
            ZXP(out) = xa;
        } else if (diff < 0.f) {
            recent = 1;
            ZXP(out) = xb;
        } else { ZXP(out) = recent ? xb : xa; } prevA = xa;
        prevB = xb;);
    unit->mPrevA = prevA;
    unit->mPrevB = prevB;
    unit->mRecent = recent;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////

void LeastChange_Ctor(LeastChange* unit) {
    if (INRATE(0) == calc_FullRate) {
        if (INRATE(1) == calc_FullRate) {
            SETCALC(LeastChange_next_aa);
        } else {
            SETCALC(LeastChange_next_ak);
        }
    } else {
        if (INRATE(1) == calc_FullRate) {
            SETCALC(LeastChange_next_ka);
        } else {
            SETCALC(LeastChange_next_aa);
        }
    }
    unit->mPrevA = 0.f;
    unit->mPrevB = 0.f;
    unit->mRecent = 0;
    LeastChange_next_aa(unit, 1);
    unit->mPrevA = 0.f;
    unit->mPrevB = 0.f;
    unit->mRecent = 0;
}

void LeastChange_next_ak(LeastChange* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* a = ZIN(0);
    float xb = ZIN0(1);
    float prevA = unit->mPrevA;
    float prevB = unit->mPrevB;
    int recent = unit->mRecent;
    LOOP1(
        inNumSamples, float xa = ZXP(a); float diff = std::abs(xa - prevA) - std::abs(xb - prevB); if (diff < 0.f) {
            recent = 0;
            ZXP(out) = xa;
        } else if (diff > 0.f) {
            recent = 1;
            ZXP(out) = xb;
        } else { ZXP(out) = recent ? xb : xa; } prevA = xa;
        prevB = xb;);
    unit->mPrevA = prevA;
    unit->mPrevB = prevB;
    unit->mRecent = recent;
}

void LeastChange_next_aa(LeastChange* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* a = ZIN(0);
    float* b = ZIN(1);
    float prevA = unit->mPrevA;
    float prevB = unit->mPrevB;
    int recent = unit->mRecent;
    LOOP1(
        inNumSamples, float xa = ZXP(a); float xb = ZXP(b); float diff = std::abs(xa - prevA) - std::abs(xb - prevB);
        if (diff < 0.f) {
            recent = 0;
            ZXP(out) = xa;
        } else if (diff > 0.f) {
            recent = 1;
            ZXP(out) = xb;
        } else { ZXP(out) = recent ? xb : xa; } prevA = xa;
        prevB = xb;);
    unit->mPrevA = prevA;
    unit->mPrevB = prevB;
    unit->mRecent = recent;
}

void LeastChange_next_ka(LeastChange* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float xa = ZIN0(0);
    float* b = ZIN(1);
    float prevA = unit->mPrevA;
    float prevB = unit->mPrevB;
    int recent = unit->mRecent;
    LOOP1(
        inNumSamples, float xb = ZXP(b); float diff = std::abs(xa - prevA) - std::abs(xb - prevB); if (diff < 0.f) {
            recent = 0;
            ZXP(out) = xa;
        } else if (diff > 0.f) {
            recent = 1;
            ZXP(out) = xb;
        } else { ZXP(out) = recent ? xb : xa; } prevA = xa;
        prevB = xb;);
    unit->mPrevA = prevA;
    unit->mPrevB = prevB;
    unit->mRecent = recent;
}


////////////////////

void LastValue_Ctor(LastValue* unit) {
    if (INRATE(0) == calc_FullRate) {
        SETCALC(LastValue_next_ak);
    } else {
        SETCALC(LastValue_next_kk);
    }

    float initPrev = unit->mPrev = ZIN0(0);
    float initCurr = unit->mCurr = ZIN0(0);
    LastValue_next_kk(unit, 1);

    unit->mPrev = initPrev;
    unit->mCurr = initCurr;
}

void LastValue_next_kk(LastValue* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float inval = ZIN0(0);
    float delta = ZIN0(1);
    float diff = std::abs(inval - unit->mCurr);
    if (diff >= delta) {
        unit->mPrev = unit->mCurr;
        unit->mCurr = inval;
    }
    float level = unit->mPrev;
    LOOP1(inNumSamples, ZXP(out) = level;);
}


void LastValue_next_ak(LastValue* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* in = ZIN(0);
    float delta = ZIN0(1);
    float prev = unit->mPrev;
    float curr = unit->mCurr;

    LOOP1(
        inNumSamples, float inval = ZXP(in); float diff = std::abs(inval - curr); if (diff >= delta) {
            prev = curr;
            curr = inval;
        } ZXP(out) = prev);
    unit->mPrev = prev;
    unit->mCurr = curr;
}


//////////////////////////////////////////////////////////////////////////////////////////

void Done_Ctor(Done* unit) {
    SETCALC(Done_next);

    unit->m_src = unit->mInput[0]->mFromUnit;

    Done_next(unit, 1);
}


void Done_next(Done* unit, int inNumSamples) {
    float* out = OUT(0);
    Unit* src = unit->m_src;
    if (src)
        *out = src->mDone ? 1.f : 0.f;
    else
        *out = 0.f;
}

//////////////////////////////////////////////////////////////////////////////////////////

void FreeSelf_Ctor(FreeSelf* unit) {
    SETCALC(FreeSelf_next);
    unit->m_prevtrig = 0.f;
    FreeSelf_next(unit, 1);
    unit->m_prevtrig = 0.f;
}


void FreeSelf_next(FreeSelf* unit, int inNumSamples) {
    float in = ZIN0(0);
    if (in > 0.f && unit->m_prevtrig <= 0.f) {
        NodeEnd(&unit->mParent->mNode);
    }
    unit->m_prevtrig = in;
}

//////////////////////////////////////////////////////////////////////////////////////////

void PauseSelf_Ctor(PauseSelf* unit) {
    SETCALC(PauseSelf_next);
    unit->m_prevtrig = 0.f;
    PauseSelf_next(unit, 1);
    unit->m_prevtrig = 0.f;
}


void PauseSelf_next(PauseSelf* unit, int inNumSamples) {
    float in = ZIN0(0);
    if (in > 0.f && unit->m_prevtrig <= 0.f) {
        NodeRun(&unit->mParent->mNode, 0);
    }
    unit->m_prevtrig = in;
}

//////////////////////////////////////////////////////////////////////////////////////////

void Pause_Ctor(Pause* unit) {
    SETCALC(Pause_next);

    unit->m_state = 1;

    ZOUT0(0) = ZIN0(0);
}


void Pause_next(Pause* unit, int inNumSamples) {
    float in = ZIN0(0);
    int state = in == 0.f ? 0 : 1;
    if (state != unit->m_state) {
        unit->m_state = state;
        int id = (int)ZIN0(1);
        Node* node = SC_GetNode(unit->mWorld, id);
        if (node) {
            NodeRun(node, state);
        }
    }
    ZOUT0(0) = in;
}

//////////////////////////////////////////////////////////////////////////////////////////

void Free_Ctor(Free* unit) {
    SETCALC(Free_next);

    unit->m_prevtrig = 0.f;

    ZOUT0(0) = ZIN0(0);
}


void Free_next(Free* unit, int inNumSamples) {
    float trig = ZIN0(0);
    if (trig > 0.f && unit->m_prevtrig <= 0) {
        int id = (int)ZIN0(1);
        Node* node = SC_GetNode(unit->mWorld, id);
        if (node) {
            NodeEnd(node);
        }
    }
    unit->m_prevtrig = trig;
    ZOUT0(0) = trig;
}

//////////////////////////////////////////////////////////////////////////////////////////

void FreeSelfWhenDone_Ctor(FreeSelfWhenDone* unit) {
    unit->m_src = unit->mInput[0]->mFromUnit;

    if (unit->m_src) {
        SETCALC(FreeSelfWhenDone_next);
        FreeSelfWhenDone_next(unit, 1);
    } else {
        SETCALC(ClearUnitOutputs);
        ClearUnitOutputs(unit, 1);
    }
}


void FreeSelfWhenDone_next(FreeSelfWhenDone* unit, int inNumSamples) {
    float* out = OUT(0);
    float* in = IN(0);
    Unit* src = unit->m_src;
    if (src->mDone) {
        NodeEnd(&unit->mParent->mNode);
        SETCALC(ClearUnitOutputs);
    }
    *out = *in;
}

//////////////////////////////////////////////////////////////////////////////////////////

void PauseSelfWhenDone_Ctor(PauseSelfWhenDone* unit) {
    unit->m_src = unit->mInput[0]->mFromUnit;

    if (unit->m_src) {
        SETCALC(PauseSelfWhenDone_next);
        PauseSelfWhenDone_next(unit, 1);
    } else {
        SETCALC(ClearUnitOutputs);
        ClearUnitOutputs(unit, 1);
    }
}


void PauseSelfWhenDone_next(PauseSelfWhenDone* unit, int inNumSamples) {
    float* out = OUT(0);
    float* in = IN(0);
    Unit* src = unit->m_src;
    if (src->mDone) {
        NodeRun(&unit->mParent->mNode, 0);
        SETCALC(ClearUnitOutputs);
    }
    *out = *in;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////

struct SendPeakRMS : public Unit {
    // rate, level lag, replyid, channel count, [channels, ], cmd name size, [cmdname, ]
    static const int rateIndex = 0;
    static const int levelLagIndex = 1;
    static const int replyIdIndex = 2;
    static const int channelCountIndex = 3;
    static const int signalStartIndex = 4;

    SendPeakRMS(void) {
        SendPeakRMS* unit = this;

        mChannelCount = (unsigned int)IN0(channelCountIndex);
        size_t channelDataAllocSize = mChannelCount * 3 * sizeof(float);

        int cmdSizeIndex = signalStartIndex + mChannelCount;
        size_t cmdNameSize = IN0(cmdSizeIndex);
        size_t cmdNameAllocSize = (cmdNameSize + 1) * sizeof(char);

        void* allocData = RTAlloc(unit->mWorld, channelDataAllocSize + cmdNameAllocSize);
        ClearUnitIfMemFailed(allocData);

        memset(allocData, 0, channelDataAllocSize);
        mChannelData = (float*)allocData;

        char* cmdName = (char*)(allocData) + channelDataAllocSize;

        size_t cmdNameIndex = cmdSizeIndex + 1;
        for (int i = 0; i < cmdNameSize; i++)
            cmdName[i] = (char)IN0(cmdNameIndex + i);
        cmdName[cmdNameSize] = 0;

        if ((FULLBUFLENGTH & 15) == 0) {
            if (mCalcRate == calc_FullRate)
                SETCALC(SendPeakRMS::perform_a<true>);
            else
                SETCALC(SendPeakRMS::perform_k<true>);
        } else {
            if (mCalcRate == calc_FullRate)
                SETCALC(SendPeakRMS::perform_a<false>);
            else
                SETCALC(SendPeakRMS::perform_k<false>);
        }

        float replyRate = IN0(rateIndex);

        mAudioSamplesPerTick = FULLRATE / replyRate;
        mControlSamplesPerTick = BUFRATE / replyRate;

        mPhaseRemain = (mCalcRate == calc_FullRate) ? mAudioSamplesPerTick : mControlSamplesPerTick;

        float32 lag = ZIN0(levelLagIndex);
        mB1 = (lag != 0.f) ? exp(log001 / (lag * replyRate)) : 0.f;
    }

    ~SendPeakRMS(void) {
        SendPeakRMS* unit = this;
        RTFree(unit->mWorld, mChannelData);
    }

    unsigned int mChannelCount;
    float* mChannelData;

    float mB1;
    int mAudioSamplesPerTick;
    int mControlSamplesPerTick;
    int mPhaseRemain;

    void performLevelLag(float& out, float y0, float& y1) {
        if (y0 >= y1)
            out = y1 = y0;
        else
            out = y1 = y0 + mB1 * (y1 - y0);
    }

    char* getCmdName(void) {
        void* buffer = mChannelData;
        return (char*)(buffer) + mChannelCount * 3 * sizeof(float);
    }

    void sendReply(void) {
        SendPeakRMS* unit = this;
        float* reply = (float*)alloca(mChannelCount * 2 * sizeof(float));


        for (int i = 0; i != mChannelCount; ++i) {
            float& maxLevel = reply[2 * i];
            float& rms = reply[2 * i + 1];

            performLevelLag(maxLevel, mChannelData[2 * i], mChannelData[2 * mChannelCount + i]);

            if (INRATE(signalStartIndex + i) == calc_FullRate)
                rms = std::sqrt(mChannelData[2 * i + 1] / (float)mAudioSamplesPerTick);
            else
                rms = std::sqrt(mChannelData[2 * i + 1] / (float)mControlSamplesPerTick);
        }

        SendNodeReply(&unit->mParent->mNode, (int)ZIN0(replyIdIndex), getCmdName(), mChannelCount * 2, reply);
        memset(mChannelData, 0, mChannelCount * 2 * sizeof(float));
    }

    template <bool simd> void analyzeFullBlock(void) {
        SendPeakRMS* unit = this;
        for (int i = 0; i != mChannelCount; ++i) {
            float* in = IN(signalStartIndex + i);
            int numSamples = INBUFLENGTH(signalStartIndex + i);

            float& level = mChannelData[2 * i];
            float& sqrsum = mChannelData[2 * i + 1];
            if (numSamples == 1)
                nova::peak_rms_vec(in, &level, &sqrsum, 1);
            else {
                if (simd)
                    nova::peak_rms_vec_simd(in, &level, &sqrsum, numSamples);
                else
                    nova::peak_rms_vec(in, &level, &sqrsum, numSamples);
            }
        }
    }

    void analyzePartialBlock(int firstSample, int samplesToAnalyze) {
        SendPeakRMS* unit = this;
        for (int i = 0; i != mChannelCount; ++i) {
            float* in = IN(signalStartIndex + i) + firstSample;
            int numSamples = INBUFLENGTH(signalStartIndex + i);

            float& level = mChannelData[2 * i];
            float& sqrsum = mChannelData[2 * i + 1];
            if (numSamples == 1) {
                if (firstSample == 0)
                    nova::peak_rms_vec(in, &level, &sqrsum, 1);
            } else {
                if (!(samplesToAnalyze & 15) && !(firstSample & 3)) // check for unrolling and alignment
                    nova::peak_rms_vec_simd(in, &level, &sqrsum, samplesToAnalyze);
                else
                    nova::peak_rms_vec(in, &level, &sqrsum, samplesToAnalyze);
            }
        }
    }

    template <bool simd> inline void next_k(int inNumSamples) {
        mPhaseRemain -= 1;

        if (mPhaseRemain <= 0) {
            mPhaseRemain += mControlSamplesPerTick;
            sendReply();
        }

        analyzeFullBlock<simd>();
    }

    template <bool simd> inline void next_a(int inNumSamples) {
        if (mPhaseRemain >= inNumSamples) {
            mPhaseRemain -= inNumSamples;
            analyzeFullBlock<simd>();
        } else {
            if (mPhaseRemain == 0) {
                sendReply();
                mPhaseRemain = mAudioSamplesPerTick;
            }

            int startSample = 0;
            int samplesToAnalyze = std::min(mPhaseRemain, inNumSamples);
            int remain = inNumSamples;

            do {
                analyzePartialBlock(startSample, samplesToAnalyze);

                startSample += samplesToAnalyze;
                mPhaseRemain -= samplesToAnalyze;
                if (mPhaseRemain == 0) {
                    sendReply();
                    mPhaseRemain = mAudioSamplesPerTick;
                }

                remain -= samplesToAnalyze;
                samplesToAnalyze = std::min(remain, mPhaseRemain);
            } while (remain);
        }
    }

    template <bool simd> static void perform_k(Unit* unit, int inNumSamples) {
        static_cast<SendPeakRMS*>(unit)->next_k<simd>(inNumSamples);
    }

    template <bool simd> static void perform_a(Unit* unit, int inNumSamples) {
        static_cast<SendPeakRMS*>(unit)->next_a<simd>(inNumSamples);
    }
};

static void SendPeakRMS_Ctor(SendPeakRMS* unit) { new (unit) SendPeakRMS(); }
static void SendPeakRMS_Dtor(SendPeakRMS* unit) { unit->~SendPeakRMS(); }

////////////////////////////////////////////////////////////////////////////////////////////////////////

PluginLoad(Trigger) {
    ft = inTable;

    DefineSimpleUnit(Trig1);
    DefineSimpleUnit(Trig);
    DefineSimpleUnit(SendTrig);
    DefineDtorUnit(SendReply);
    DefineDtorUnit(Poll);
    DefineSimpleUnit(ToggleFF);
    DefineSimpleUnit(SetResetFF);
    DefineSimpleUnit(Latch);
    DefineSimpleUnit(Gate);
    DefineSimpleUnit(Schmidt);
    DefineSimpleUnit(PulseDivider);
    DefineSimpleUnit(PulseCount);
    DefineSimpleUnit(Stepper);
    DefineSimpleUnit(TDelay);
    DefineSimpleUnit(ZeroCrossing);
    DefineSimpleUnit(Timer);
    DefineSimpleUnit(Sweep);
    DefineSimpleUnit(Phasor);
    DefineSimpleUnit(Peak);
    DefineSimpleUnit(RunningMin);
    DefineSimpleUnit(RunningMax);
    DefineSimpleUnit(PeakFollower);
    DefineSimpleUnit(MostChange);
    DefineSimpleUnit(LeastChange);
    DefineSimpleUnit(LastValue);
    DefineSimpleUnit(Done);
    DefineSimpleUnit(Pause);
    DefineSimpleUnit(FreeSelf);
    DefineSimpleUnit(PauseSelf);
    DefineSimpleUnit(Free);
    DefineSimpleUnit(FreeSelfWhenDone);
    DefineSimpleUnit(PauseSelfWhenDone);

    DefineDtorUnit(SendPeakRMS);
}
