/*
    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 <boost/utility/enable_if.hpp>
#include <boost/align/is_aligned.hpp>

#ifdef NOVA_SIMD
#    include "simd_unary_arithmetic.hpp"
#    include "simd_binary_arithmetic.hpp"
#    include "simd_ternary_arithmetic.hpp"
#    include "simd_math.hpp"
#    include "simd_memory.hpp"
#    include "softclip.hpp"
#    include "simd_unit_conversion.hpp"

#    include "function_attributes.h"

using nova::wrap_argument;

#    define NOVA_WRAPPER(NAME, NOVANAME)                                                                               \
        FLATTEN void NAME##_nova(UnaryOpUGen* unit, int inNumSamples) {                                                \
            nova::NOVANAME##_vec_simd(OUT(0), IN(0), inNumSamples);                                                    \
        }

#    define NOVA_WRAPPER_CT_UNROLL(NAME, NOVANAME)                                                                     \
        FLATTEN void NAME##_nova(UnaryOpUGen* unit, int inNumSamples) {                                                \
            nova::NOVANAME##_vec_simd(OUT(0), IN(0), inNumSamples);                                                    \
        }                                                                                                              \
                                                                                                                       \
        FLATTEN void NAME##_nova_64(UnaryOpUGen* unit, int inNumSamples) {                                             \
            nova::NOVANAME##_vec_simd<64>(OUT(0), IN(0));                                                              \
        }

struct sc_distort_functor {
    template <typename FloatType> inline FloatType operator()(FloatType arg) const { return sc_distort(arg); }

    template <typename FloatType> inline nova::vec<FloatType> operator()(nova::vec<FloatType> arg) const {
        nova::vec<FloatType> one(1.f);
        return arg * reciprocal(one + abs(arg));
    }
};

struct sc_scurve_functor {
    template <typename FloatType> inline FloatType operator()(FloatType arg) const { return sc_scurve(arg); }

    template <typename FloatType> inline nova::vec<FloatType> operator()(nova::vec<FloatType> arg) const {
        return perform(arg);
    }

    template <typename VecType>
    inline typename boost::disable_if_c<VecType::has_compare_bitmask, VecType>::type perform(VecType arg) const {
        typedef VecType vec;

        vec result;
        for (int i = 0; i != result.size; ++i)
            result.set(i, sc_scurve(arg.get(i)));
        return result;
    }

    template <typename VecType>
    inline typename boost::enable_if_c<VecType::has_compare_bitmask, VecType>::type perform(VecType arg) const {
        typedef VecType vec;
        vec one(1.f);
        vec zero(0.f);
        vec two(2.f);
        vec three(3.f);

        vec result = (arg * arg) * (three - (two * arg));

        vec boundLow = mask_lt(arg, zero);
        vec boundHigh = mask_gt(arg, one);

        result = select(result, zero, boundLow);
        result = select(result, one, boundHigh);

        return result;
    }
};


namespace nova {
NOVA_SIMD_DEFINE_UNARY_WRAPPER(distort, sc_distort_functor)
NOVA_SIMD_DEFINE_UNARY_WRAPPER(scurve, sc_scurve_functor)
}
#endif

using namespace std; // for math functions

static InterfaceTable* ft;

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


/* special unary math operators */
enum {
    opNeg,
    opNot,
    opIsNil,
    opNotNil,
    opBitNot,
    opAbs,
    opAsFloat,
    opAsInteger,
    opCeil,
    opFloor,
    opFrac,
    opSign,
    opSquared,
    opCubed,
    opSqrt,
    opExp,
    opRecip,
    opMIDICPS,
    opCPSMIDI,

    opMIDIRatio,
    opRatioMIDI,
    opDbAmp,
    opAmpDb,
    opOctCPS,
    opCPSOct,
    opLog,
    opLog2,
    opLog10,
    opSin,
    opCos,
    opTan,
    opArcSin,
    opArcCos,
    opArcTan,
    opSinH,
    opCosH,
    opTanH,

    opRand,
    opRand2,
    opLinRand,
    opBiLinRand,
    opSum3Rand,

    opDistort,
    opSoftClip,
    opCoin,

    opDigitValue,
    opSilence,
    opThru,
    opRectWindow,
    opHanWindow,
    opWelchWindow,
    opTriWindow,

    opRamp,
    opSCurve,

    opNumUnarySelectors
};


struct UnaryOpUGen : public Unit {};

typedef void (*UnaryOpFunc)(UnaryOpUGen* unit, int inNumSamples);

extern "C" {

void UnaryOpUGen_Ctor(UnaryOpUGen* unit);
}

bool ChooseOperatorFunc(UnaryOpUGen* unit);

void UnaryOpUGen_Ctor(UnaryOpUGen* unit) {
    bool initialized = ChooseOperatorFunc(unit);
    if (!initialized)
        (unit->mCalcFunc)(unit, 1);
}

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

#define DEFINE_UNARY_OP_FUNCS(name, function)                                                                          \
    extern "C" void name##_a(UnaryOpUGen* unit, int inNumSamples) {                                                    \
        float* out = ZOUT(0);                                                                                          \
        float* a = ZIN(0);                                                                                             \
                                                                                                                       \
        LOOP1(inNumSamples, ZXP(out) = function(ZXP(a)););                                                             \
    }                                                                                                                  \
                                                                                                                       \
    extern "C" void name##_1(UnaryOpUGen* unit, int inNumSamples) { ZOUT0(0) = function(ZIN0(0)); }                    \
                                                                                                                       \
    extern "C" void name##_d(UnaryOpUGen* unit, int inNumSamples) {                                                    \
        if (inNumSamples) {                                                                                            \
            float x = DEMANDINPUT_A(0, inNumSamples);                                                                  \
            OUT0(0) = sc_isnan(x) ? NAN : function(x);                                                                 \
        } else {                                                                                                       \
            RESETINPUT(0);                                                                                             \
        }                                                                                                              \
    }

template <typename F> inline F sc_invert(F x) { return -x; }

DEFINE_UNARY_OP_FUNCS(invert, sc_invert)

#ifdef NOVA_SIMD
FLATTEN void invert_nova(UnaryOpUGen* unit, int inNumSamples) {
    nova::minus_vec_simd(OUT(0), 0.f, IN(0), inNumSamples);
}

FLATTEN void invert_nova_64(UnaryOpUGen* unit, int inNumSamples) { nova::minus_vec_simd<64>(OUT(0), 0.f, IN(0)); }

#endif


template <typename F> inline F sc_not(F x) { return x > 0.f ? 0.f : 1.f; }

DEFINE_UNARY_OP_FUNCS(not, sc_not)
DEFINE_UNARY_OP_FUNCS(bitNot, sc_bitNot)


void zero_a(UnaryOpUGen* unit, int inNumSamples) {
    float* out = ZOUT(0);

    ZClear(inNumSamples, out);
}

void thru_a(UnaryOpUGen* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* a = ZIN(0);

    ZCopy(inNumSamples, out, a);
}

DEFINE_UNARY_OP_FUNCS(abs, std::abs)

#ifdef NOVA_SIMD
FLATTEN void zero_nova(UnaryOpUGen* unit, int inNumSamples) { nova::zerovec_simd(OUT(0), inNumSamples); }

FLATTEN void zero_nova_64(UnaryOpUGen* unit, int inNumSamples) { nova::zerovec_simd<64>(OUT(0)); }

FLATTEN void thru_nova(UnaryOpUGen* unit, int inNumSamples) { nova::copyvec_simd(OUT(0), IN(0), inNumSamples); }

FLATTEN void thru_nova_64(UnaryOpUGen* unit, int inNumSamples) { nova::copyvec_simd<64>(OUT(0), IN(0)); }

FLATTEN void abs_nova(UnaryOpUGen* unit, int inNumSamples) { nova::abs_vec_simd(OUT(0), IN(0), inNumSamples); }

FLATTEN void abs_nova_64(UnaryOpUGen* unit, int inNumSamples) { nova::abs_vec_simd<64>(OUT(0), IN(0)); }
#endif

DEFINE_UNARY_OP_FUNCS(recip, sc_reciprocal)

#ifdef NOVA_SIMD
FLATTEN void recip_nova(UnaryOpUGen* unit, int inNumSamples) { nova::reciprocal_vec_simd(OUT(0), IN(0), inNumSamples); }

FLATTEN void recip_nova_64(UnaryOpUGen* unit, int inNumSamples) { nova::reciprocal_vec_simd<64>(OUT(0), IN(0)); }
#endif


DEFINE_UNARY_OP_FUNCS(floor, floor)
DEFINE_UNARY_OP_FUNCS(ceil, ceil)

#ifdef NOVA_SIMD
NOVA_WRAPPER_CT_UNROLL(floor, floor)
NOVA_WRAPPER_CT_UNROLL(ceil, ceil)
#endif

DEFINE_UNARY_OP_FUNCS(sin, sin)
DEFINE_UNARY_OP_FUNCS(cos, cos)
DEFINE_UNARY_OP_FUNCS(tan, tan)

DEFINE_UNARY_OP_FUNCS(asin, asin)
DEFINE_UNARY_OP_FUNCS(acos, acos)
DEFINE_UNARY_OP_FUNCS(atan, atan)

DEFINE_UNARY_OP_FUNCS(sinh, sinh)
DEFINE_UNARY_OP_FUNCS(cosh, cosh)
DEFINE_UNARY_OP_FUNCS(tanh, tanh)


#ifdef NOVA_SIMD
NOVA_WRAPPER(sin, sin)
NOVA_WRAPPER(cos, cos)
NOVA_WRAPPER(tan, tan)
NOVA_WRAPPER(asin, asin)
NOVA_WRAPPER(acos, acos)
NOVA_WRAPPER(atan, atan)
NOVA_WRAPPER(tanh, tanh)
#endif


DEFINE_UNARY_OP_FUNCS(log, std::log)
DEFINE_UNARY_OP_FUNCS(log2, sc_log2)
DEFINE_UNARY_OP_FUNCS(log10, sc_log10)
DEFINE_UNARY_OP_FUNCS(exp, exp)

#ifdef NOVA_SIMD
NOVA_WRAPPER(log, log)
NOVA_WRAPPER(log2, log2)
NOVA_WRAPPER(log10, log10)
NOVA_WRAPPER(exp, exp)
#endif

DEFINE_UNARY_OP_FUNCS(sqrt, sc_sqrt)

#ifdef NOVA_SIMD
NOVA_WRAPPER_CT_UNROLL(sqrt, signed_sqrt)
#endif

DEFINE_UNARY_OP_FUNCS(ampdb, sc_ampdb)
DEFINE_UNARY_OP_FUNCS(dbamp, sc_dbamp)
DEFINE_UNARY_OP_FUNCS(midicps, sc_midicps)
DEFINE_UNARY_OP_FUNCS(cpsmidi, sc_cpsmidi)
DEFINE_UNARY_OP_FUNCS(midiratio, sc_midiratio)
DEFINE_UNARY_OP_FUNCS(ratiomidi, sc_ratiomidi)
DEFINE_UNARY_OP_FUNCS(cpsoct, sc_cpsoct)
DEFINE_UNARY_OP_FUNCS(octcps, sc_octcps)

#ifdef NOVA_SIMD
NOVA_WRAPPER(ampdb, amp2db)
NOVA_WRAPPER(dbamp, db2amp)
NOVA_WRAPPER(midicps, midi2freq)
NOVA_WRAPPER(cpsmidi, freq2midi)
NOVA_WRAPPER(midiratio, midi2ratio)
NOVA_WRAPPER(ratiomidi, ratio2midi)
NOVA_WRAPPER(cpsoct, freq2oct)
NOVA_WRAPPER(octcps, oct2freq)
#endif


DEFINE_UNARY_OP_FUNCS(frac, sc_frac)
#ifdef NOVA_SIMD
NOVA_WRAPPER_CT_UNROLL(frac, frac)
#endif


DEFINE_UNARY_OP_FUNCS(squared, sc_squared)
#ifdef NOVA_SIMD
NOVA_WRAPPER_CT_UNROLL(squared, square)
#endif


DEFINE_UNARY_OP_FUNCS(cubed, sc_cubed)
#ifdef NOVA_SIMD
NOVA_WRAPPER_CT_UNROLL(cubed, cube)
#endif

DEFINE_UNARY_OP_FUNCS(sign, sc_sign)

#ifdef NOVA_SIMD
NOVA_WRAPPER_CT_UNROLL(sign, sgn)
#endif

DEFINE_UNARY_OP_FUNCS(distort, sc_distort)

#ifdef NOVA_SIMD
NOVA_WRAPPER_CT_UNROLL(distort, distort)
#endif

DEFINE_UNARY_OP_FUNCS(distortneg, sc_distortneg)
DEFINE_UNARY_OP_FUNCS(softclip, sc_softclip)

#ifdef NOVA_SIMD
NOVA_WRAPPER_CT_UNROLL(softclip, softclip)
#endif

DEFINE_UNARY_OP_FUNCS(rectwindow, sc_rectwindow)
DEFINE_UNARY_OP_FUNCS(hanwindow, sc_hanwindow)
DEFINE_UNARY_OP_FUNCS(welwindow, sc_welwindow)
DEFINE_UNARY_OP_FUNCS(triwindow, sc_triwindow)

DEFINE_UNARY_OP_FUNCS(scurve, sc_scurve)
#ifdef NOVA_SIMD
NOVA_WRAPPER_CT_UNROLL(scurve, scurve)
#endif

DEFINE_UNARY_OP_FUNCS(ramp, sc_ramp)


#ifdef NOVA_SIMD
FLATTEN void ramp_nova(UnaryOpUGen* unit, int inNumSamples) {
    nova::clip_vec_simd(OUT(0), wrap_argument(IN(0)), wrap_argument(0.f), wrap_argument(1.f), inNumSamples);
}

FLATTEN void ramp_nova_64(UnaryOpUGen* unit, int inNumSamples) {
    nova::clip_vec_simd<64>(OUT(0), wrap_argument(IN(0)), wrap_argument(0.f), wrap_argument(1.f));
}
#endif

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

void zero_d(UnaryOpUGen* unit, int inNumSamples) {
    if (inNumSamples) {
        float x = DEMANDINPUT_A(0, inNumSamples);
        OUT0(0) = sc_isnan(x) ? NAN : 0.f;
    } else {
        RESETINPUT(0);
    }
}

void thru_d(UnaryOpUGen* unit, int inNumSamples) {
    if (inNumSamples) {
        float x = DEMANDINPUT_A(0, inNumSamples);
        OUT0(0) = sc_isnan(x) ? NAN : (x);
    } else {
        RESETINPUT(0);
    }
}


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

#define DEFINE_UNARY_OP_RANDOM_FUNCS(name, function)                                                                   \
    extern "C" void name##_a(UnaryOpUGen* unit, int inNumSamples) {                                                    \
        float* out = ZOUT(0);                                                                                          \
        float* a = ZIN(0);                                                                                             \
        RGen& rgen = *unit->mParent->mRGen;                                                                            \
        LOOP1(inNumSamples, ZXP(out) = rgen.function() * ZXP(a););                                                     \
    }                                                                                                                  \
                                                                                                                       \
    extern "C" void name##_1(UnaryOpUGen* unit, int inNumSamples) {                                                    \
        RGen& rgen = *unit->mParent->mRGen;                                                                            \
        ZOUT0(0) = rgen.function() * ZIN0(0);                                                                          \
    }                                                                                                                  \
                                                                                                                       \
    extern "C" void name##_d(UnaryOpUGen* unit, int inNumSamples) {                                                    \
        if (inNumSamples) {                                                                                            \
            float x = DEMANDINPUT_A(0, inNumSamples);                                                                  \
            RGen& rgen = *unit->mParent->mRGen;                                                                        \
            OUT0(0) = sc_isnan(x) ? NAN : (rgen.function() * x);                                                       \
        } else {                                                                                                       \
            RESETINPUT(0);                                                                                             \
        }                                                                                                              \
    }

DEFINE_UNARY_OP_RANDOM_FUNCS(rand, frand);
DEFINE_UNARY_OP_RANDOM_FUNCS(rand2, frand2);
DEFINE_UNARY_OP_RANDOM_FUNCS(linrand, flinrand);
DEFINE_UNARY_OP_RANDOM_FUNCS(bilinrand, fbilinrand);
DEFINE_UNARY_OP_RANDOM_FUNCS(sum3rand, fsum3rand);


void coin_a(UnaryOpUGen* unit, int inNumSamples) {
    float* out = ZOUT(0);
    float* a = ZIN(0);
    RGen& rgen = *unit->mParent->mRGen;
    LOOP1(inNumSamples, ZXP(out) = (rgen.frand() < ZXP(a)) ? 1.f : 0.f;);
}

void coin_1(UnaryOpUGen* unit, int inNumSamples) {
    RGen& rgen = *unit->mParent->mRGen;
    float x = ZIN0(0);
    ZOUT0(0) = (rgen.frand() < x) ? 1.f : 0.f;
}

void coin_d(UnaryOpUGen* unit, int inNumSamples) {
    if (inNumSamples) {
        float x = DEMANDINPUT_A(0, inNumSamples);
        RGen& rgen = *unit->mParent->mRGen;
        float val = (rgen.frand() < x) ? 1.f : 0.f;
        OUT0(0) = sc_isnan(x) ? NAN : val;
    } else {
        RESETINPUT(0);
    }
}


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

static UnaryOpFunc ChooseNormalFunc(UnaryOpUGen* unit) {
    void (*func)(UnaryOpUGen * unit, int inNumSamples);

    switch (unit->mSpecialIndex) {
    case opSilence:
        func = &zero_a;
        break;
    case opThru:
        func = &thru_a;
        break;
    case opNeg:
        func = &invert_a;
        break;
    case opNot:
        func = &not_a;
        break;
    case opBitNot:
        func = &bitNot_a;
        break;
    case opAbs:
        func = &abs_a;
        break;
    case opCeil:
        func = &ceil_a;
        break;
    case opFloor:
        func = &floor_a;
        break;
    case opFrac:
        func = &frac_a;
        break;
    case opSign:
        func = &sign_a;
        break;
    case opSquared:
        func = &squared_a;
        break;
    case opCubed:
        func = &cubed_a;
        break;
    case opSqrt:
        func = &sqrt_a;
        break;
    case opExp:
        func = &exp_a;
        break;
    case opRecip:
        func = &recip_a;
        break;
    case opMIDICPS:
        func = &midicps_a;
        break;
    case opCPSMIDI:
        func = &cpsmidi_a;
        break;

    case opMIDIRatio:
        func = &midiratio_a;
        break;
    case opRatioMIDI:
        func = &ratiomidi_a;
        break;
    case opDbAmp:
        func = &dbamp_a;
        break;
    case opAmpDb:
        func = &ampdb_a;
        break;
    case opOctCPS:
        func = &octcps_a;
        break;
    case opCPSOct:
        func = &cpsoct_a;
        break;
    case opLog:
        func = &log_a;
        break;
    case opLog2:
        func = &log2_a;
        break;
    case opLog10:
        func = &log10_a;
        break;
    case opSin:
        func = &sin_a;
        break;
    case opCos:
        func = &cos_a;
        break;
    case opTan:
        func = &tan_a;
        break;
    case opArcSin:
        func = &asin_a;
        break;
    case opArcCos:
        func = &acos_a;
        break;
    case opArcTan:
        func = &atan_a;
        break;
    case opSinH:
        func = &sinh_a;
        break;
    case opCosH:
        func = &cosh_a;
        break;
    case opTanH:
        func = &tanh_a;
        break;

    case opRand:
        func = &rand_a;
        break;
    case opRand2:
        func = &rand2_a;
        break;
    case opLinRand:
        func = &linrand_a;
        break;
    case opBiLinRand:
        func = &bilinrand_a;
        break;
    case opSum3Rand:
        func = &sum3rand_a;
        break;
    case opCoin:
        func = &coin_a;
        break;

    case opDistort:
        func = &distort_a;
        break;
    case opSoftClip:
        func = &softclip_a;
        break;

    case opRectWindow:
        func = &rectwindow_a;
        break;
    case opHanWindow:
        func = &hanwindow_a;
        break;
    case opWelchWindow:
        func = &welwindow_a;
        break;
    case opTriWindow:
        func = &triwindow_a;
        break;

    case opSCurve:
        func = &scurve_a;
        break;
    case opRamp:
        func = &ramp_a;
        break;

    default:
        func = &thru_a;
        break;
    }
    return func;
}

static UnaryOpFunc ChooseOneFunc(UnaryOpUGen* unit) {
    void (*func)(UnaryOpUGen * unit, int inNumSamples);

    switch (unit->mSpecialIndex) {
    case opSilence:
        func = &zero_a;
        break;
    case opThru:
        func = &thru_a;
        break;
    case opNeg:
        func = &invert_1;
        break;
    case opNot:
        func = &not_1;
        break;
    case opBitNot:
        func = &bitNot_1;
        break;
    case opAbs:
        func = &abs_1;
        break;
    case opCeil:
        func = &ceil_1;
        break;
    case opFloor:
        func = &floor_1;
        break;
    case opFrac:
        func = &frac_1;
        break;
    case opSign:
        func = &sign_1;
        break;
    case opSquared:
        func = &squared_1;
        break;
    case opCubed:
        func = &cubed_1;
        break;
    case opSqrt:
        func = &sqrt_1;
        break;
    case opExp:
        func = &exp_1;
        break;
    case opRecip:
        func = &recip_1;
        break;
    case opMIDICPS:
        func = &midicps_1;
        break;
    case opCPSMIDI:
        func = &cpsmidi_1;
        break;

    case opMIDIRatio:
        func = &midiratio_1;
        break;
    case opRatioMIDI:
        func = &ratiomidi_1;
        break;
    case opDbAmp:
        func = &dbamp_1;
        break;
    case opAmpDb:
        func = &ampdb_1;
        break;
    case opOctCPS:
        func = &octcps_1;
        break;
    case opCPSOct:
        func = &cpsoct_1;
        break;
    case opLog:
        func = &log_1;
        break;
    case opLog2:
        func = &log2_1;
        break;
    case opLog10:
        func = &log10_1;
        break;
    case opSin:
        func = &sin_1;
        break;
    case opCos:
        func = &cos_1;
        break;
    case opTan:
        func = &tan_1;
        break;
    case opArcSin:
        func = &asin_1;
        break;
    case opArcCos:
        func = &acos_1;
        break;
    case opArcTan:
        func = &atan_1;
        break;
    case opSinH:
        func = &sinh_1;
        break;
    case opCosH:
        func = &cosh_1;
        break;
    case opTanH:
        func = &tanh_1;
        break;

    case opRand:
        func = &rand_1;
        break;
    case opRand2:
        func = &rand2_1;
        break;
    case opLinRand:
        func = &linrand_1;
        break;
    case opBiLinRand:
        func = &bilinrand_1;
        break;
    case opSum3Rand:
        func = &sum3rand_1;
        break;
    case opCoin:
        func = &coin_1;
        break;

    case opDistort:
        func = &distort_1;
        break;
    case opSoftClip:
        func = &softclip_1;
        break;

    case opRectWindow:
        func = &rectwindow_1;
        break;
    case opHanWindow:
        func = &hanwindow_1;
        break;
    case opWelchWindow:
        func = &welwindow_1;
        break;
    case opTriWindow:
        func = &triwindow_1;
        break;

    case opSCurve:
        func = &scurve_1;
        break;
    case opRamp:
        func = &ramp_1;
        break;

    default:
        func = &thru_a;
        break;
    }
    return func;
}


static UnaryOpFunc ChooseDemandFunc(UnaryOpUGen* unit) {
    void (*func)(UnaryOpUGen * unit, int inNumSamples);

    switch (unit->mSpecialIndex) {
    case opSilence:
        func = &zero_d;
        break;
    case opThru:
        func = &thru_d;
        break;
    case opNeg:
        func = &invert_d;
        break;
    case opNot:
        func = &not_d;
        break;
    case opBitNot:
        func = &bitNot_d;
        break;
    case opAbs:
        func = &abs_d;
        break;
    case opCeil:
        func = &ceil_d;
        break;
    case opFloor:
        func = &floor_d;
        break;
    case opFrac:
        func = &frac_d;
        break;
    case opSign:
        func = &sign_d;
        break;
    case opSquared:
        func = &squared_d;
        break;
    case opCubed:
        func = &cubed_d;
        break;
    case opSqrt:
        func = &sqrt_d;
        break;
    case opExp:
        func = &exp_d;
        break;
    case opRecip:
        func = &recip_d;
        break;
    case opMIDICPS:
        func = &midicps_d;
        break;
    case opCPSMIDI:
        func = &cpsmidi_d;
        break;

    case opMIDIRatio:
        func = &midiratio_d;
        break;
    case opRatioMIDI:
        func = &ratiomidi_d;
        break;
    case opDbAmp:
        func = &dbamp_d;
        break;
    case opAmpDb:
        func = &ampdb_d;
        break;
    case opOctCPS:
        func = &octcps_d;
        break;
    case opCPSOct:
        func = &cpsoct_d;
        break;
    case opLog:
        func = &log_d;
        break;
    case opLog2:
        func = &log2_d;
        break;
    case opLog10:
        func = &log10_d;
        break;
    case opSin:
        func = &sin_d;
        break;
    case opCos:
        func = &cos_d;
        break;
    case opTan:
        func = &tan_d;
        break;
    case opArcSin:
        func = &asin_d;
        break;
    case opArcCos:
        func = &acos_d;
        break;
    case opArcTan:
        func = &atan_d;
        break;
    case opSinH:
        func = &sinh_d;
        break;
    case opCosH:
        func = &cosh_d;
        break;
    case opTanH:
        func = &tanh_d;
        break;

    case opRand:
        func = &rand_d;
        break;
    case opRand2:
        func = &rand2_d;
        break;
    case opLinRand:
        func = &linrand_d;
        break;
    case opBiLinRand:
        func = &bilinrand_d;
        break;
    case opSum3Rand:
        func = &sum3rand_d;
        break;
    case opCoin:
        func = &coin_d;
        break;

    case opDistort:
        func = &distort_d;
        break;
    case opSoftClip:
        func = &softclip_d;
        break;

    case opRectWindow:
        func = &rectwindow_d;
        break;
    case opHanWindow:
        func = &hanwindow_d;
        break;
    case opWelchWindow:
        func = &welwindow_d;
        break;
    case opTriWindow:
        func = &triwindow_d;
        break;

    case opSCurve:
        func = &scurve_d;
        break;
    case opRamp:
        func = &ramp_d;
        break;


    default:
        func = &thru_d;
        break;
    }
    return func;
}

#ifdef NOVA_SIMD

static UnaryOpFunc ChooseNovaSimdFunc(UnaryOpUGen* unit) {
    void (*func)(UnaryOpUGen * unit, int inNumSamples);

    if (BUFLENGTH == 64) {
        switch (unit->mSpecialIndex) {
        case opSilence:
            return &zero_nova_64;
        case opThru:
            func = &thru_nova;
            break;
        case opNeg:
            return &invert_nova_64;
        case opNot:
            func = &not_a;
            break;
        case opBitNot:
            func = &bitNot_a;
            break;
        case opAbs:
            return &abs_nova_64;
        case opCeil:
            func = &ceil_nova_64;
            break;
        case opFloor:
            func = &floor_nova_64;
            break;
        case opFrac:
            func = &frac_nova_64;
            break;
        case opSign:
            return &sign_nova_64;
        case opSquared:
            return &squared_nova_64;
        case opCubed:
            return &cubed_nova_64;
        case opSqrt:
#    ifdef __ARM_NEON // remove this once nova-simd implementation is fixed
            func = &sqrt_a;
#    else
            func = &sqrt_nova_64;
#    endif
            break;
        case opExp:
            func = &exp_nova;
            break;
        case opRecip:
#    ifdef __ARM_NEON // remove this once nova-simd implementation is fixed
            return &recip_a;
#    else
            return &recip_nova_64;
#    endif
        case opMIDICPS:
            func = &midicps_nova;
            break;
        case opCPSMIDI:
            func = &cpsmidi_nova;
            break;
        case opMIDIRatio:
            func = &midiratio_nova;
            break;
        case opRatioMIDI:
            func = &ratiomidi_nova;
            break;
        case opDbAmp:
            func = &dbamp_nova;
            break;
        case opAmpDb:
            func = &ampdb_nova;
            break;
        case opOctCPS:
            func = &octcps_nova;
            break;
        case opCPSOct:
            func = &cpsoct_nova;
            break;
        case opLog:
            func = &log_nova;
            break;
        case opLog2:
            func = &log2_nova;
            break;
        case opLog10:
            func = &log10_nova;
            break;
        case opSin:
            func = &sin_nova;
            break;
        case opCos:
            func = &cos_nova;
            break;
        case opTan:
            func = &tan_nova;
            break;
        case opArcSin:
            func = &asin_nova;
            break;
        case opArcCos:
            func = &acos_nova;
            break;
        case opArcTan:
            func = &atan_nova;
            break;
        case opSinH:
            func = &sinh_a;
            break;
        case opCosH:
            func = &cosh_a;
            break;
        case opTanH:
            func = &tanh_nova;
            break;

        case opRand:
            func = &rand_a;
            break;
        case opRand2:
            func = &rand2_a;
            break;
        case opLinRand:
            func = &linrand_a;
            break;
        case opBiLinRand:
            func = &bilinrand_a;
            break;
        case opSum3Rand:
            func = &sum3rand_a;
            break;
        case opCoin:
            func = &coin_a;
            break;

        case opDistort:
            func = &distort_nova_64;
            break;
        case opSoftClip:
            func = &softclip_nova_64;
            break;

        case opRectWindow:
            func = &rectwindow_a;
            break;
        case opHanWindow:
            func = &hanwindow_a;
            break;
        case opWelchWindow:
            func = &welwindow_a;
            break;
        case opTriWindow:
            func = &triwindow_a;
            break;

        case opSCurve:
            func = &scurve_nova_64;
            break;
        case opRamp:
            return &ramp_nova_64;

        default:
            return &thru_nova_64;
        }
        return func;
    }
    switch (unit->mSpecialIndex) {
    case opSilence:
        func = &zero_nova;
        break;
    case opThru:
        func = &thru_nova;
        break;
    case opNeg:
        func = &invert_nova;
        break;
    case opNot:
        func = &not_a;
        break;
    case opBitNot:
        func = &bitNot_a;
        break;
    case opAbs:
        func = &abs_nova;
        break;
    case opCeil:
        func = &ceil_nova;
        break;
    case opFloor:
        func = &floor_nova;
        break;
    case opFrac:
        func = &frac_nova;
        break;
    case opSign:
        func = &sign_nova;
        break;
    case opSquared:
        func = &squared_nova;
        break;
    case opCubed:
        func = &cubed_nova;
        break;
    case opSqrt:
#    ifdef __ARM_NEON // remove this once nova-simd implementation is fixed
        func = &sqrt_a;
#    else
        func = &sqrt_nova;
#    endif
        break;
    case opExp:
        func = &exp_nova;
        break;
    case opRecip:
#    ifdef __ARM_NEON // remove this once nova-simd implementation is fixed
        func = &recip_a;
#    else
        func = &recip_nova;
#    endif
        break;
    case opMIDICPS:
        func = &midicps_nova;
        break;
    case opCPSMIDI:
        func = &cpsmidi_nova;
        break;
    case opMIDIRatio:
        func = &midiratio_nova;
        break;
    case opRatioMIDI:
        func = &ratiomidi_nova;
        break;
    case opDbAmp:
        func = &dbamp_nova;
        break;
    case opAmpDb:
        func = &ampdb_nova;
        break;
    case opOctCPS:
        func = &octcps_nova;
        break;
    case opCPSOct:
        func = &cpsoct_nova;
        break;
    case opLog:
        func = &log_nova;
        break;
    case opLog2:
        func = &log2_nova;
        break;
    case opLog10:
        func = &log10_nova;
        break;
    case opSin:
        func = &sin_nova;
        break;
    case opCos:
        func = &cos_nova;
        break;
    case opTan:
        func = &tan_nova;
        break;
    case opArcSin:
        func = &asin_nova;
        break;
    case opArcCos:
        func = &acos_nova;
        break;
    case opArcTan:
        func = &atan_nova;
        break;
    case opSinH:
        func = &sinh_a;
        break;
    case opCosH:
        func = &cosh_a;
        break;
    case opTanH:
        func = &tanh_nova;
        break;

    case opRand:
        func = &rand_a;
        break;
    case opRand2:
        func = &rand2_a;
        break;
    case opLinRand:
        func = &linrand_a;
        break;
    case opBiLinRand:
        func = &bilinrand_a;
        break;
    case opSum3Rand:
        func = &sum3rand_a;
        break;
    case opCoin:
        func = &coin_a;
        break;

    case opDistort:
        func = &distort_nova;
        break;
    case opSoftClip:
        func = &softclip_nova;
        break;

    case opRectWindow:
        func = &rectwindow_a;
        break;
    case opHanWindow:
        func = &hanwindow_a;
        break;
    case opWelchWindow:
        func = &welwindow_a;
        break;
    case opTriWindow:
        func = &triwindow_a;
        break;

    case opSCurve:
        func = &scurve_nova;
        break;
    case opRamp:
        func = &ramp_nova;
        break;

    default:
        func = &thru_nova;
        break;
    }
    return func;
}
#endif

bool ChooseOperatorFunc(UnaryOpUGen* unit) {
    // Print("->ChooseOperatorFunc %d\n", unit->mSpecialIndex);
    UnaryOpFunc func;
    bool ret = false;
    if (unit->mCalcRate == calc_DemandRate) {
        func = ChooseDemandFunc(unit);
    } else if (BUFLENGTH == 1) {
        func = ChooseOneFunc(unit);
#if defined(NOVA_SIMD)
    } else if (boost::alignment::is_aligned(BUFLENGTH, 16)) {
        /* select normal function for initialization */
        func = ChooseNormalFunc(unit);
        func(unit, 1);

        /* select simd function */
        func = ChooseNovaSimdFunc(unit);
        ret = true;
#endif
    } else {
        func = ChooseNormalFunc(unit);
    }
    unit->mCalcFunc = (UnitCalcFunc)func;
    // Print("<-ChooseOperatorFunc %p\n", func);
    // Print("calc %d\n", unit->mCalcRate);
    return ret;
}


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

PluginLoad(UnaryOp) {
    ft = inTable;

    DefineSimpleUnit(UnaryOpUGen);
}
