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

   This file is part of the JUCE framework.
   Copyright (c) Raw Material Software Limited

   JUCE is an open source framework subject to commercial or open source
   licensing.

   By downloading, installing, or using the JUCE framework, or combining the
   JUCE framework with any other source code, object code, content or any other
   copyrightable work, you agree to the terms of the JUCE End User Licence
   Agreement, and all incorporated terms including the JUCE Privacy Policy and
   the JUCE Website Terms of Service, as applicable, which will bind you. If you
   do not agree to the terms of these agreements, we will not license the JUCE
   framework to you, and you must discontinue the installation or download
   process and cease use of the JUCE framework.

   JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
   JUCE Privacy Policy: https://juce.com/juce-privacy-policy
   JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/

   Or:

   You may also use this code under the terms of the AGPLv3:
   https://www.gnu.org/licenses/agpl-3.0.en.html

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

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

namespace juce
{

namespace FloatVectorHelpers
{
    #define JUCE_INCREMENT_SRC_DEST         dest += (16 / sizeof (*dest)); src += (16 / sizeof (*dest));
    #define JUCE_INCREMENT_SRC1_SRC2_DEST   dest += (16 / sizeof (*dest)); src1 += (16 / sizeof (*dest)); src2 += (16 / sizeof (*dest));
    #define JUCE_INCREMENT_DEST             dest += (16 / sizeof (*dest));

   #if JUCE_USE_SSE_INTRINSICS
    static bool isAligned (const void* p) noexcept
    {
        return (((pointer_sized_int) p) & 15) == 0;
    }

    struct BasicOps32
    {
        using Type = float;
        using ParallelType = __m128;
        using IntegerType  = __m128;
        enum { numParallel = 4 };

        // Integer and parallel types are the same for SSE. On neon they have different types
        static forcedinline IntegerType toint (ParallelType v) noexcept                 { return v; }
        static forcedinline ParallelType toflt (IntegerType v) noexcept                 { return v; }

        static forcedinline ParallelType load1 (Type v) noexcept                        { return _mm_load1_ps (&v); }
        static forcedinline ParallelType loadA (const Type* v) noexcept                 { return _mm_load_ps (v); }
        static forcedinline ParallelType loadU (const Type* v) noexcept                 { return _mm_loadu_ps (v); }
        static forcedinline void storeA (Type* dest, ParallelType a) noexcept           { _mm_store_ps (dest, a); }
        static forcedinline void storeU (Type* dest, ParallelType a) noexcept           { _mm_storeu_ps (dest, a); }

        static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept  { return _mm_add_ps (a, b); }
        static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept  { return _mm_sub_ps (a, b); }
        static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept  { return _mm_mul_ps (a, b); }
        static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept  { return _mm_max_ps (a, b); }
        static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept  { return _mm_min_ps (a, b); }

        static forcedinline ParallelType bit_and (ParallelType a, ParallelType b) noexcept  { return _mm_and_ps (a, b); }
        static forcedinline ParallelType bit_not (ParallelType a, ParallelType b) noexcept  { return _mm_andnot_ps (a, b); }
        static forcedinline ParallelType bit_or  (ParallelType a, ParallelType b) noexcept  { return _mm_or_ps (a, b); }
        static forcedinline ParallelType bit_xor (ParallelType a, ParallelType b) noexcept  { return _mm_xor_ps (a, b); }

        static forcedinline Type max (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmax (v[0], v[1], v[2], v[3]); }
        static forcedinline Type min (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmin (v[0], v[1], v[2], v[3]); }
    };

    struct BasicOps64
    {
        using Type = double;
        using ParallelType = __m128d;
        using IntegerType  = __m128d;
        enum { numParallel = 2 };

        // Integer and parallel types are the same for SSE. On neon they have different types
        static forcedinline IntegerType toint (ParallelType v) noexcept                 { return v; }
        static forcedinline ParallelType toflt (IntegerType v) noexcept                 { return v; }

        static forcedinline ParallelType load1 (Type v) noexcept                        { return _mm_load1_pd (&v); }
        static forcedinline ParallelType loadA (const Type* v) noexcept                 { return _mm_load_pd (v); }
        static forcedinline ParallelType loadU (const Type* v) noexcept                 { return _mm_loadu_pd (v); }
        static forcedinline void storeA (Type* dest, ParallelType a) noexcept           { _mm_store_pd (dest, a); }
        static forcedinline void storeU (Type* dest, ParallelType a) noexcept           { _mm_storeu_pd (dest, a); }

        static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept  { return _mm_add_pd (a, b); }
        static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept  { return _mm_sub_pd (a, b); }
        static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept  { return _mm_mul_pd (a, b); }
        static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept  { return _mm_max_pd (a, b); }
        static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept  { return _mm_min_pd (a, b); }

        static forcedinline ParallelType bit_and (ParallelType a, ParallelType b) noexcept  { return _mm_and_pd (a, b); }
        static forcedinline ParallelType bit_not (ParallelType a, ParallelType b) noexcept  { return _mm_andnot_pd (a, b); }
        static forcedinline ParallelType bit_or  (ParallelType a, ParallelType b) noexcept  { return _mm_or_pd (a, b); }
        static forcedinline ParallelType bit_xor (ParallelType a, ParallelType b) noexcept  { return _mm_xor_pd (a, b); }

        static forcedinline Type max (ParallelType a) noexcept  { Type v[numParallel]; storeU (v, a); return jmax (v[0], v[1]); }
        static forcedinline Type min (ParallelType a) noexcept  { Type v[numParallel]; storeU (v, a); return jmin (v[0], v[1]); }
    };



    #define JUCE_BEGIN_VEC_OP \
        using Mode = FloatVectorHelpers::ModeType<sizeof(*dest)>::Mode; \
        { \
            const auto numLongOps = num / Mode::numParallel;

    #define JUCE_FINISH_VEC_OP(normalOp) \
            num &= (Mode::numParallel - 1); \
            if (num == 0) return; \
        } \
        for (auto i = (decltype (num)) 0; i < num; ++i) normalOp;

    #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \
        JUCE_BEGIN_VEC_OP \
        setupOp \
        if (FloatVectorHelpers::isAligned (dest))   JUCE_VEC_LOOP (vecOp, dummy, Mode::loadA, Mode::storeA, locals, JUCE_INCREMENT_DEST) \
        else                                        JUCE_VEC_LOOP (vecOp, dummy, Mode::loadU, Mode::storeU, locals, JUCE_INCREMENT_DEST) \
        JUCE_FINISH_VEC_OP (normalOp)

    #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \
        JUCE_BEGIN_VEC_OP \
        setupOp \
        if (FloatVectorHelpers::isAligned (dest)) \
        { \
            if (FloatVectorHelpers::isAligned (src)) JUCE_VEC_LOOP (vecOp, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \
            else                                     JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \
        }\
        else \
        { \
            if (FloatVectorHelpers::isAligned (src)) JUCE_VEC_LOOP (vecOp, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \
            else                                     JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \
        } \
        JUCE_FINISH_VEC_OP (normalOp)

    #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \
        JUCE_BEGIN_VEC_OP \
        setupOp \
        if (FloatVectorHelpers::isAligned (dest)) \
        { \
            if (FloatVectorHelpers::isAligned (src1)) \
            { \
                if (FloatVectorHelpers::isAligned (src2))   JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \
                else                                        JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadA, Mode::loadU, Mode::storeA, locals, increment) \
            } \
            else \
            { \
                if (FloatVectorHelpers::isAligned (src2))   JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \
                else                                        JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadU, Mode::storeA, locals, increment) \
            } \
        } \
        else \
        { \
            if (FloatVectorHelpers::isAligned (src1)) \
            { \
                if (FloatVectorHelpers::isAligned (src2))   JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadA, Mode::loadA, Mode::storeU, locals, increment) \
                else                                        JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \
            } \
            else \
            { \
                if (FloatVectorHelpers::isAligned (src2))   JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadA, Mode::storeU, locals, increment) \
                else                                        JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \
            } \
        } \
        JUCE_FINISH_VEC_OP (normalOp)

    #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST(normalOp, vecOp, locals, increment, setupOp) \
        JUCE_BEGIN_VEC_OP \
        setupOp \
        if (FloatVectorHelpers::isAligned (dest)) \
        { \
            if (FloatVectorHelpers::isAligned (src1)) \
            { \
                if (FloatVectorHelpers::isAligned (src2))   JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadA, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \
                else                                        JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadA, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \
            } \
            else \
            { \
                if (FloatVectorHelpers::isAligned (src2))   JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \
                else                                        JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \
            } \
        } \
        else \
        { \
            if (FloatVectorHelpers::isAligned (src1)) \
            { \
                if (FloatVectorHelpers::isAligned (src2))   JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadA, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \
                else                                        JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadA, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \
            } \
            else \
            { \
                if (FloatVectorHelpers::isAligned (src2))   JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \
                else                                        JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \
            } \
        } \
        JUCE_FINISH_VEC_OP (normalOp)


    //==============================================================================
   #elif JUCE_USE_ARM_NEON

    struct BasicOps32
    {
        using Type = float;
        using ParallelType = float32x4_t;
        using IntegerType = uint32x4_t;
        union signMaskUnion { ParallelType f; IntegerType i; };
        enum { numParallel = 4 };

        static forcedinline IntegerType toint (ParallelType v) noexcept                 { signMaskUnion u; u.f = v; return u.i; }
        static forcedinline ParallelType toflt (IntegerType v) noexcept                 { signMaskUnion u; u.i = v; return u.f; }

        static forcedinline ParallelType load1 (Type v) noexcept                        { return vld1q_dup_f32 (&v); }
        static forcedinline ParallelType loadA (const Type* v) noexcept                 { return vld1q_f32 (v); }
        static forcedinline ParallelType loadU (const Type* v) noexcept                 { return vld1q_f32 (v); }
        static forcedinline void storeA (Type* dest, ParallelType a) noexcept           { vst1q_f32 (dest, a); }
        static forcedinline void storeU (Type* dest, ParallelType a) noexcept           { vst1q_f32 (dest, a); }

        static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept  { return vaddq_f32 (a, b); }
        static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept  { return vsubq_f32 (a, b); }
        static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept  { return vmulq_f32 (a, b); }
        static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept  { return vmaxq_f32 (a, b); }
        static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept  { return vminq_f32 (a, b); }

        static forcedinline ParallelType bit_and (ParallelType a, ParallelType b) noexcept  {  return toflt (vandq_u32 (toint (a), toint (b))); }
        static forcedinline ParallelType bit_not (ParallelType a, ParallelType b) noexcept  {  return toflt (vbicq_u32 (toint (a), toint (b))); }
        static forcedinline ParallelType bit_or  (ParallelType a, ParallelType b) noexcept  {  return toflt (vorrq_u32 (toint (a), toint (b))); }
        static forcedinline ParallelType bit_xor (ParallelType a, ParallelType b) noexcept  {  return toflt (veorq_u32 (toint (a), toint (b))); }

        static forcedinline Type max (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmax (v[0], v[1], v[2], v[3]); }
        static forcedinline Type min (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmin (v[0], v[1], v[2], v[3]); }
    };

    struct BasicOps64
    {
        using Type = double;
        using ParallelType = double;
        using IntegerType = uint64;
        union signMaskUnion { ParallelType f; IntegerType i; };
        enum { numParallel = 1 };

        static forcedinline IntegerType toint (ParallelType v) noexcept                 { signMaskUnion u; u.f = v; return u.i; }
        static forcedinline ParallelType toflt (IntegerType v) noexcept                 { signMaskUnion u; u.i = v; return u.f; }

        static forcedinline ParallelType load1 (Type v) noexcept                        { return v; }
        static forcedinline ParallelType loadA (const Type* v) noexcept                 { return *v; }
        static forcedinline ParallelType loadU (const Type* v) noexcept                 { return *v; }
        static forcedinline void storeA (Type* dest, ParallelType a) noexcept           { *dest = a; }
        static forcedinline void storeU (Type* dest, ParallelType a) noexcept           { *dest = a; }

        static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept  { return a + b; }
        static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept  { return a - b; }
        static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept  { return a * b; }
        static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept  { return jmax (a, b); }
        static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept  { return jmin (a, b); }

        static forcedinline ParallelType bit_and (ParallelType a, ParallelType b) noexcept  {  return toflt (toint (a) & toint (b)); }
        static forcedinline ParallelType bit_not (ParallelType a, ParallelType b) noexcept  {  return toflt ((~toint (a)) & toint (b)); }
        static forcedinline ParallelType bit_or  (ParallelType a, ParallelType b) noexcept  {  return toflt (toint (a) | toint (b)); }
        static forcedinline ParallelType bit_xor (ParallelType a, ParallelType b) noexcept  {  return toflt (toint (a) ^ toint (b)); }

        static forcedinline Type max (ParallelType a) noexcept  { return a; }
        static forcedinline Type min (ParallelType a) noexcept  { return a; }
    };

    #define JUCE_BEGIN_VEC_OP \
        using Mode = FloatVectorHelpers::ModeType<sizeof(*dest)>::Mode; \
        if (Mode::numParallel > 1) \
        { \
            const auto numLongOps = num / Mode::numParallel;

    #define JUCE_FINISH_VEC_OP(normalOp) \
            num &= (Mode::numParallel - 1); \
            if (num == 0) return; \
        } \
        for (auto i = (decltype (num)) 0; i < num; ++i) normalOp;

    #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \
        JUCE_BEGIN_VEC_OP \
        setupOp \
        JUCE_VEC_LOOP (vecOp, dummy, Mode::loadU, Mode::storeU, locals, JUCE_INCREMENT_DEST) \
        JUCE_FINISH_VEC_OP (normalOp)

    #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \
        JUCE_BEGIN_VEC_OP \
        setupOp \
        JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \
        JUCE_FINISH_VEC_OP (normalOp)

    #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \
        JUCE_BEGIN_VEC_OP \
        setupOp \
        JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \
        JUCE_FINISH_VEC_OP (normalOp)

    #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST(normalOp, vecOp, locals, increment, setupOp) \
        JUCE_BEGIN_VEC_OP \
        setupOp \
        JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \
        JUCE_FINISH_VEC_OP (normalOp)


    //==============================================================================
   #else
    #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \
        for (auto i = (decltype (num)) 0; i < num; ++i) normalOp;

    #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \
        for (auto i = (decltype (num)) 0; i < num; ++i) normalOp;

    #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \
        for (auto i = (decltype (num)) 0; i < num; ++i) normalOp;

    #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST(normalOp, vecOp, locals, increment, setupOp) \
        for (auto i = (decltype (num)) 0; i < num; ++i) normalOp;

   #endif

    //==============================================================================
    #define JUCE_VEC_LOOP(vecOp, srcLoad, dstLoad, dstStore, locals, increment) \
        for (auto i = (decltype (numLongOps)) 0; i < numLongOps; ++i) \
        { \
            locals (srcLoad, dstLoad); \
            dstStore (dest, vecOp); \
            increment; \
        }

    #define JUCE_VEC_LOOP_TWO_SOURCES(vecOp, src1Load, src2Load, dstStore, locals, increment) \
        for (auto i = (decltype (numLongOps)) 0; i < numLongOps; ++i) \
        { \
            locals (src1Load, src2Load); \
            dstStore (dest, vecOp); \
            increment; \
        }

    #define JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD(vecOp, src1Load, src2Load, dstLoad, dstStore, locals, increment) \
        for (auto i = (decltype (numLongOps)) 0; i < numLongOps; ++i) \
        { \
            locals (src1Load, src2Load, dstLoad); \
            dstStore (dest, vecOp); \
            increment; \
        }

    #define JUCE_LOAD_NONE(srcLoad, dstLoad)
    #define JUCE_LOAD_DEST(srcLoad, dstLoad)                        const Mode::ParallelType d = dstLoad (dest);
    #define JUCE_LOAD_SRC(srcLoad, dstLoad)                         const Mode::ParallelType s = srcLoad (src);
    #define JUCE_LOAD_SRC1_SRC2(src1Load, src2Load)                 const Mode::ParallelType s1 = src1Load (src1), s2 = src2Load (src2);
    #define JUCE_LOAD_SRC1_SRC2_DEST(src1Load, src2Load, dstLoad)   const Mode::ParallelType d = dstLoad (dest), s1 = src1Load (src1), s2 = src2Load (src2);
    #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad)                    const Mode::ParallelType d = dstLoad (dest), s = srcLoad (src);

    union signMask32 { float  f; uint32 i; };
    union signMask64 { double d; uint64 i; };

   #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON
    template <int typeSize> struct ModeType    { using Mode = BasicOps32; };
    template <>             struct ModeType<8> { using Mode = BasicOps64; };

    template <typename Mode>
    struct MinMax
    {
        using Type = typename Mode::Type;
        using ParallelType = typename Mode::ParallelType;

        template <typename Size>
        static Type findMinOrMax (const Type* src, Size num, const bool isMinimum) noexcept
        {
            auto numLongOps = num / Mode::numParallel;

            if (numLongOps > 1)
            {
                ParallelType val;

               #if ! JUCE_USE_ARM_NEON
                if (isAligned (src))
                {
                    val = Mode::loadA (src);

                    if (isMinimum)
                    {
                        while (--numLongOps > 0)
                        {
                            src += Mode::numParallel;
                            val = Mode::min (val, Mode::loadA (src));
                        }
                    }
                    else
                    {
                        while (--numLongOps > 0)
                        {
                            src += Mode::numParallel;
                            val = Mode::max (val, Mode::loadA (src));
                        }
                    }
                }
                else
               #endif
                {
                    val = Mode::loadU (src);

                    if (isMinimum)
                    {
                        while (--numLongOps > 0)
                        {
                            src += Mode::numParallel;
                            val = Mode::min (val, Mode::loadU (src));
                        }
                    }
                    else
                    {
                        while (--numLongOps > 0)
                        {
                            src += Mode::numParallel;
                            val = Mode::max (val, Mode::loadU (src));
                        }
                    }
                }

                Type result = isMinimum ? Mode::min (val)
                                        : Mode::max (val);

                num &= (Mode::numParallel - 1);
                src += Mode::numParallel;

                for (auto i = (decltype (num)) 0; i < num; ++i)
                    result = isMinimum ? jmin (result, src[i])
                                       : jmax (result, src[i]);

                return result;
            }

            if (num <= 0)
                return 0;

            return isMinimum ? *std::min_element (src, src + num)
                             : *std::max_element (src, src + num);
        }

        template <typename Size>
        static Range<Type> findMinAndMax (const Type* src, Size num) noexcept
        {
            auto numLongOps = num / Mode::numParallel;

            if (numLongOps > 1)
            {
                ParallelType mn, mx;

               #if ! JUCE_USE_ARM_NEON
                if (isAligned (src))
                {
                    mn = Mode::loadA (src);
                    mx = mn;

                    while (--numLongOps > 0)
                    {
                        src += Mode::numParallel;
                        const ParallelType v = Mode::loadA (src);
                        mn = Mode::min (mn, v);
                        mx = Mode::max (mx, v);
                    }
                }
                else
               #endif
                {
                    mn = Mode::loadU (src);
                    mx = mn;

                    while (--numLongOps > 0)
                    {
                        src += Mode::numParallel;
                        const ParallelType v = Mode::loadU (src);
                        mn = Mode::min (mn, v);
                        mx = Mode::max (mx, v);
                    }
                }

                Range<Type> result (Mode::min (mn),
                                    Mode::max (mx));

                num &= (Mode::numParallel - 1);
                src += Mode::numParallel;

                for (auto i = (decltype (num)) 0; i < num; ++i)
                    result = result.getUnionWith (src[i]);

                return result;
            }

            return Range<Type>::findMinAndMax (src, num);
        }
    };
   #endif

//==============================================================================
namespace
{
    template <typename Size>
    void clear (float* dest, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vclr (dest, 1, (vDSP_Length) num);
       #else
        zeromem (dest, (size_t) num * sizeof (float));
       #endif
    }

    template <typename Size>
    void clear (double* dest, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vclrD (dest, 1, (vDSP_Length) num);
       #else
        zeromem (dest, (size_t) num * sizeof (double));
       #endif
    }

    template <typename Size>
    void fill (float* dest, float valueToFill, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vfill (&valueToFill, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_DEST (dest[i] = valueToFill,
                                  val,
                                  JUCE_LOAD_NONE,
                                  const Mode::ParallelType val = Mode::load1 (valueToFill);)
       #endif
    }

    template <typename Size>
    void fill (double* dest, double valueToFill, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vfillD (&valueToFill, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_DEST (dest[i] = valueToFill,
                                  val,
                                  JUCE_LOAD_NONE,
                                  const Mode::ParallelType val = Mode::load1 (valueToFill);)
       #endif
    }

    template <typename Size>
    void copyWithMultiply (float* dest, const float* src, float multiplier, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsmul (src, 1, &multiplier, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier,
                                      Mode::mul (mult, s),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType mult = Mode::load1 (multiplier);)
       #endif
    }

    template <typename Size>
    void copyWithMultiply (double* dest, const double* src, double multiplier, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsmulD (src, 1, &multiplier, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier,
                                      Mode::mul (mult, s),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType mult = Mode::load1 (multiplier);)
       #endif
    }

    template <typename Size>
    void add (float* dest, float amount, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsadd (dest, 1, &amount, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_DEST (dest[i] += amount,
                                  Mode::add (d, amountToAdd),
                                  JUCE_LOAD_DEST,
                                  const Mode::ParallelType amountToAdd = Mode::load1 (amount);)
       #endif
    }

    template <typename Size>
    void add (double* dest, double amount, Size num) noexcept
    {
        JUCE_PERFORM_VEC_OP_DEST (dest[i] += amount,
                                  Mode::add (d, amountToAdd),
                                  JUCE_LOAD_DEST,
                                  const Mode::ParallelType amountToAdd = Mode::load1 (amount);)
    }

    template <typename Size>
    void add (float* dest, const float* src, float amount, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsadd (src, 1, &amount, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount,
                                      Mode::add (am, s),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType am = Mode::load1 (amount);)
       #endif
    }

    template <typename Size>
    void add (double* dest, const double* src, double amount, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsaddD (src, 1, &amount, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount,
                                      Mode::add (am, s),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType am = Mode::load1 (amount);)
       #endif
    }

    template <typename Size>
    void add (float* dest, const float* src, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vadd (src, 1, dest, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i],
                                      Mode::add (d, s),
                                      JUCE_LOAD_SRC_DEST,
                                      JUCE_INCREMENT_SRC_DEST, )
       #endif
    }

    template <typename Size>
    void add (double* dest, const double* src, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vaddD (src, 1, dest, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i],
                                      Mode::add (d, s),
                                      JUCE_LOAD_SRC_DEST,
                                      JUCE_INCREMENT_SRC_DEST, )
       #endif
    }

    template <typename Size>
    void add (float* dest, const float* src1, const float* src2, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vadd (src1, 1, src2, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] + src2[i],
                                            Mode::add (s1, s2),
                                            JUCE_LOAD_SRC1_SRC2,
                                            JUCE_INCREMENT_SRC1_SRC2_DEST, )
       #endif
    }

    template <typename Size>
    void add (double* dest, const double* src1, const double* src2, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vaddD (src1, 1, src2, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] + src2[i],
                                            Mode::add (s1, s2),
                                            JUCE_LOAD_SRC1_SRC2,
                                            JUCE_INCREMENT_SRC1_SRC2_DEST, )
       #endif
    }

    template <typename Size>
    void subtract (float* dest, const float* src, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsub (src, 1, dest, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i],
                                      Mode::sub (d, s),
                                      JUCE_LOAD_SRC_DEST,
                                      JUCE_INCREMENT_SRC_DEST, )
       #endif
    }

    template <typename Size>
    void subtract (double* dest, const double* src, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsubD (src, 1, dest, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i],
                                      Mode::sub (d, s),
                                      JUCE_LOAD_SRC_DEST,
                                      JUCE_INCREMENT_SRC_DEST, )
       #endif
    }

    template <typename Size>
    void subtract (float* dest, const float* src1, const float* src2, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsub (src2, 1, src1, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] - src2[i],
                                            Mode::sub (s1, s2),
                                            JUCE_LOAD_SRC1_SRC2,
                                            JUCE_INCREMENT_SRC1_SRC2_DEST, )
       #endif
    }

    template <typename Size>
    void subtract (double* dest, const double* src1, const double* src2, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsubD (src2, 1, src1, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] - src2[i],
                                            Mode::sub (s1, s2),
                                            JUCE_LOAD_SRC1_SRC2,
                                            JUCE_INCREMENT_SRC1_SRC2_DEST, )
       #endif
    }

    template <typename Size>
    void addWithMultiply (float* dest, const float* src, float multiplier, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsma (src, 1, &multiplier, dest, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i] * multiplier,
                                      Mode::add (d, Mode::mul (mult, s)),
                                      JUCE_LOAD_SRC_DEST,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType mult = Mode::load1 (multiplier);)
       #endif
    }

    template <typename Size>
    void addWithMultiply (double* dest, const double* src, double multiplier, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsmaD (src, 1, &multiplier, dest, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i] * multiplier,
                                      Mode::add (d, Mode::mul (mult, s)),
                                      JUCE_LOAD_SRC_DEST,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType mult = Mode::load1 (multiplier);)
       #endif
    }

    template <typename Size>
    void addWithMultiply (float* dest, const float* src1, const float* src2, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vma ((float*) src1, 1, (float*) src2, 1, dest, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST (dest[i] += src1[i] * src2[i],
                                                 Mode::add (d, Mode::mul (s1, s2)),
                                                 JUCE_LOAD_SRC1_SRC2_DEST,
                                                 JUCE_INCREMENT_SRC1_SRC2_DEST, )
       #endif
    }

    template <typename Size>
    void addWithMultiply (double* dest, const double* src1, const double* src2, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vmaD ((double*) src1, 1, (double*) src2, 1, dest, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST (dest[i] += src1[i] * src2[i],
                                                 Mode::add (d, Mode::mul (s1, s2)),
                                                 JUCE_LOAD_SRC1_SRC2_DEST,
                                                 JUCE_INCREMENT_SRC1_SRC2_DEST, )
       #endif
    }

    template <typename Size>
    void subtractWithMultiply (float* dest, const float* src, float multiplier, Size num) noexcept
    {
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i] * multiplier,
                                      Mode::sub (d, Mode::mul (mult, s)),
                                      JUCE_LOAD_SRC_DEST,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType mult = Mode::load1 (multiplier);)
    }

    template <typename Size>
    void subtractWithMultiply (double* dest, const double* src, double multiplier, Size num) noexcept
    {
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i] * multiplier,
                                      Mode::sub (d, Mode::mul (mult, s)),
                                      JUCE_LOAD_SRC_DEST,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType mult = Mode::load1 (multiplier);)
    }

    template <typename Size>
    void subtractWithMultiply (float* dest, const float* src1, const float* src2, Size num) noexcept
    {
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST (dest[i] -= src1[i] * src2[i],
                                                 Mode::sub (d, Mode::mul (s1, s2)),
                                                 JUCE_LOAD_SRC1_SRC2_DEST,
                                                 JUCE_INCREMENT_SRC1_SRC2_DEST, )
    }

    template <typename Size>
    void subtractWithMultiply (double* dest, const double* src1, const double* src2, Size num) noexcept
    {
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST (dest[i] -= src1[i] * src2[i],
                                                 Mode::sub (d, Mode::mul (s1, s2)),
                                                 JUCE_LOAD_SRC1_SRC2_DEST,
                                                 JUCE_INCREMENT_SRC1_SRC2_DEST, )
    }

    template <typename Size>
    void multiply (float* dest, const float* src, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vmul (src, 1, dest, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] *= src[i],
                                      Mode::mul (d, s),
                                      JUCE_LOAD_SRC_DEST,
                                      JUCE_INCREMENT_SRC_DEST, )
       #endif
    }

    template <typename Size>
    void multiply (double* dest, const double* src, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vmulD (src, 1, dest, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] *= src[i],
                                      Mode::mul (d, s),
                                      JUCE_LOAD_SRC_DEST,
                                      JUCE_INCREMENT_SRC_DEST, )
       #endif
    }

    template <typename Size>
    void multiply (float* dest, const float* src1, const float* src2, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vmul (src1, 1, src2, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] * src2[i],
                                            Mode::mul (s1, s2),
                                            JUCE_LOAD_SRC1_SRC2,
                                            JUCE_INCREMENT_SRC1_SRC2_DEST, )
       #endif
    }

    template <typename Size>
    void multiply (double* dest, const double* src1, const double* src2, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vmulD (src1, 1, src2, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] * src2[i],
                                            Mode::mul (s1, s2),
                                            JUCE_LOAD_SRC1_SRC2,
                                            JUCE_INCREMENT_SRC1_SRC2_DEST, )
       #endif
    }

    template <typename Size>
    void multiply (float* dest, float multiplier, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsmul (dest, 1, &multiplier, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_DEST (dest[i] *= multiplier,
                                  Mode::mul (d, mult),
                                  JUCE_LOAD_DEST,
                                  const Mode::ParallelType mult = Mode::load1 (multiplier);)
       #endif
    }

    template <typename Size>
    void multiply (double* dest, double multiplier, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vsmulD (dest, 1, &multiplier, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_DEST (dest[i] *= multiplier,
                                  Mode::mul (d, mult),
                                  JUCE_LOAD_DEST,
                                  const Mode::ParallelType mult = Mode::load1 (multiplier);)
       #endif
    }

    template <typename Size>
    void multiply (float* dest, const float* src, float multiplier, Size num) noexcept
    {
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier,
                                      Mode::mul (mult, s),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType mult = Mode::load1 (multiplier);)
    }

    template <typename Size>
    void multiply (double* dest, const double* src, double multiplier, Size num) noexcept
    {
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier,
                                      Mode::mul (mult, s),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType mult = Mode::load1 (multiplier);)
    }

    template <typename Size>
    void negate (float* dest, const float* src, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vneg ((float*) src, 1, dest, 1, (vDSP_Length) num);
       #else
        copyWithMultiply (dest, src, -1.0f, num);
       #endif
    }

    template <typename Size>
    void negate (double* dest, const double* src, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vnegD ((double*) src, 1, dest, 1, (vDSP_Length) num);
       #else
        copyWithMultiply (dest, src, -1.0f, num);
       #endif
    }

    template <typename Size>
    void abs (float* dest, const float* src, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vabs ((float*) src, 1, dest, 1, (vDSP_Length) num);
       #else
        [[maybe_unused]] FloatVectorHelpers::signMask32 signMask;
        signMask.i = 0x7fffffffUL;
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = std::abs (src[i]),
                                      Mode::bit_and (s, mask),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType mask = Mode::load1 (signMask.f);)
       #endif
    }

    template <typename Size>
    void abs (double* dest, const double* src, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vabsD ((double*) src, 1, dest, 1, (vDSP_Length) num);
       #else
        [[maybe_unused]] FloatVectorHelpers::signMask64 signMask;
        signMask.i = 0x7fffffffffffffffULL;

        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = std::abs (src[i]),
                                      Mode::bit_and (s, mask),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType mask = Mode::load1 (signMask.d);)
       #endif
    }

    template <typename Size>
    void min (float* dest, const float* src, float comp, Size num) noexcept
    {
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmin (src[i], comp),
                                      Mode::min (s, cmp),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType cmp = Mode::load1 (comp);)
    }

    template <typename Size>
    void min (double* dest, const double* src, double comp, Size num) noexcept
    {
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmin (src[i], comp),
                                      Mode::min (s, cmp),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType cmp = Mode::load1 (comp);)
    }

    template <typename Size>
    void min (float* dest, const float* src1, const float* src2, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vmin ((float*) src1, 1, (float*) src2, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = jmin (src1[i], src2[i]),
                                            Mode::min (s1, s2),
                                            JUCE_LOAD_SRC1_SRC2,
                                            JUCE_INCREMENT_SRC1_SRC2_DEST, )
       #endif
    }

    template <typename Size>
    void min (double* dest, const double* src1, const double* src2, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vminD ((double*) src1, 1, (double*) src2, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = jmin (src1[i], src2[i]),
                                            Mode::min (s1, s2),
                                            JUCE_LOAD_SRC1_SRC2,
                                            JUCE_INCREMENT_SRC1_SRC2_DEST, )
       #endif
    }

    template <typename Size>
    void max (float* dest, const float* src, float comp, Size num) noexcept
    {
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmax (src[i], comp),
                                      Mode::max (s, cmp),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType cmp = Mode::load1 (comp);)
    }

    template <typename Size>
    void max (double* dest, const double* src, double comp, Size num) noexcept
    {
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmax (src[i], comp),
                                      Mode::max (s, cmp),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType cmp = Mode::load1 (comp);)
    }

    template <typename Size>
    void max (float* dest, const float* src1, const float* src2, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vmax ((float*) src1, 1, (float*) src2, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = jmax (src1[i], src2[i]),
                                            Mode::max (s1, s2),
                                            JUCE_LOAD_SRC1_SRC2,
                                            JUCE_INCREMENT_SRC1_SRC2_DEST, )
       #endif
    }

    template <typename Size>
    void max (double* dest, const double* src1, const double* src2, Size num) noexcept
    {
       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vmaxD ((double*) src1, 1, (double*) src2, 1, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = jmax (src1[i], src2[i]),
                                            Mode::max (s1, s2),
                                            JUCE_LOAD_SRC1_SRC2,
                                            JUCE_INCREMENT_SRC1_SRC2_DEST, )
       #endif
    }

    template <typename Size>
    void clip (float* dest, const float* src, float low, float high, Size num) noexcept
    {
        jassert (high >= low);

       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vclip ((float*) src, 1, &low, &high, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmax (jmin (src[i], high), low),
                                      Mode::max (Mode::min (s, hi), lo),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType lo = Mode::load1 (low);
                                      const Mode::ParallelType hi = Mode::load1 (high);)
       #endif
    }

    template <typename Size>
    void clip (double* dest, const double* src, double low, double high, Size num) noexcept
    {
        jassert (high >= low);

       #if JUCE_USE_VDSP_FRAMEWORK
        vDSP_vclipD ((double*) src, 1, &low, &high, dest, 1, (vDSP_Length) num);
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmax (jmin (src[i], high), low),
                                      Mode::max (Mode::min (s, hi), lo),
                                      JUCE_LOAD_SRC,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType lo = Mode::load1 (low);
                                      const Mode::ParallelType hi = Mode::load1 (high);)
       #endif
    }

    template <typename Size>
    Range<float> findMinAndMax (const float* src, Size num) noexcept
    {
       #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON
        return FloatVectorHelpers::MinMax<FloatVectorHelpers::BasicOps32>::findMinAndMax (src, num);
       #else
        return Range<float>::findMinAndMax (src, num);
       #endif
    }

    template <typename Size>
    Range<double> findMinAndMax (const double* src, Size num) noexcept
    {
       #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON
        return FloatVectorHelpers::MinMax<FloatVectorHelpers::BasicOps64>::findMinAndMax (src, num);
       #else
        return Range<double>::findMinAndMax (src, num);
       #endif
    }

    template <typename Size>
    float findMinimum (const float* src, Size num) noexcept
    {
       #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON
        return FloatVectorHelpers::MinMax<FloatVectorHelpers::BasicOps32>::findMinOrMax (src, num, true);
       #else
        return juce::findMinimum (src, num);
       #endif
    }

    template <typename Size>
    double findMinimum (const double* src, Size num) noexcept
    {
       #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON
        return FloatVectorHelpers::MinMax<FloatVectorHelpers::BasicOps64>::findMinOrMax (src, num, true);
       #else
        return juce::findMinimum (src, num);
       #endif
    }

    template <typename Size>
    float findMaximum (const float* src, Size num) noexcept
    {
       #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON
        return FloatVectorHelpers::MinMax<FloatVectorHelpers::BasicOps32>::findMinOrMax (src, num, false);
       #else
        return juce::findMaximum (src, num);
       #endif
    }

    template <typename Size>
    double findMaximum (const double* src, Size num) noexcept
    {
       #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON
        return FloatVectorHelpers::MinMax<FloatVectorHelpers::BasicOps64>::findMinOrMax (src, num, false);
       #else
        return juce::findMaximum (src, num);
       #endif
    }

    template <typename Size>
    void convertFixedToFloat (float* dest, const int* src, float multiplier, Size num) noexcept
    {
       #if JUCE_USE_ARM_NEON
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = (float) src[i] * multiplier,
                                  vmulq_n_f32 (vcvtq_f32_s32 (vld1q_s32 (src)), multiplier),
                                  JUCE_LOAD_NONE,
                                  JUCE_INCREMENT_SRC_DEST, )
       #else
        JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = (float) src[i] * multiplier,
                                      Mode::mul (mult, _mm_cvtepi32_ps (_mm_loadu_si128 (reinterpret_cast<const __m128i*> (src)))),
                                      JUCE_LOAD_NONE,
                                      JUCE_INCREMENT_SRC_DEST,
                                      const Mode::ParallelType mult = Mode::load1 (multiplier);)
       #endif
    }

} // namespace
} // namespace FloatVectorHelpers

//==============================================================================
template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::clear (FloatType* dest,
                                                                           CountType numValues) noexcept
{
    FloatVectorHelpers::clear (dest, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::fill (FloatType* dest,
                                                                          FloatType valueToFill,
                                                                          CountType numValues) noexcept
{
    FloatVectorHelpers::fill (dest, valueToFill, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::copy (FloatType* dest,
                                                                          const FloatType* src,
                                                                          CountType numValues) noexcept
{
    memcpy (dest, src, (size_t) numValues * sizeof (FloatType));
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::copyWithMultiply (FloatType* dest,
                                                                                      const FloatType* src,
                                                                                      FloatType multiplier,
                                                                                      CountType numValues) noexcept
{
    FloatVectorHelpers::copyWithMultiply (dest, src, multiplier, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::add (FloatType* dest,
                                                                         FloatType amountToAdd,
                                                                         CountType numValues) noexcept
{
    FloatVectorHelpers::add (dest, amountToAdd, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::add (FloatType* dest,
                                                                         const FloatType* src,
                                                                         FloatType amount,
                                                                         CountType numValues) noexcept
{
    FloatVectorHelpers::add (dest, src, amount, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::add (FloatType* dest,
                                                                         const FloatType* src,
                                                                         CountType numValues) noexcept
{
    FloatVectorHelpers::add (dest, src, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::add (FloatType* dest,
                                                                         const FloatType* src1,
                                                                         const FloatType* src2,
                                                                         CountType num) noexcept
{
    FloatVectorHelpers::add (dest, src1, src2, num);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::subtract (FloatType* dest,
                                                                              const FloatType* src,
                                                                              CountType numValues) noexcept
{
    FloatVectorHelpers::subtract (dest, src, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::subtract (FloatType* dest,
                                                                              const FloatType* src1,
                                                                              const FloatType* src2,
                                                                              CountType num) noexcept
{
    FloatVectorHelpers::subtract (dest, src1, src2, num);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::addWithMultiply (FloatType* dest,
                                                                                     const FloatType* src,
                                                                                     FloatType multiplier,
                                                                                     CountType numValues) noexcept
{
    FloatVectorHelpers::addWithMultiply (dest, src, multiplier, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::addWithMultiply (FloatType* dest,
                                                                                     const FloatType* src1,
                                                                                     const FloatType* src2,
                                                                                     CountType num) noexcept
{
    FloatVectorHelpers::addWithMultiply (dest, src1, src2, num);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::subtractWithMultiply (FloatType* dest,
                                                                                          const FloatType* src,
                                                                                          FloatType multiplier,
                                                                                          CountType numValues) noexcept
{
    FloatVectorHelpers::subtractWithMultiply (dest, src, multiplier, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::subtractWithMultiply (FloatType* dest,
                                                                                          const FloatType* src1,
                                                                                          const FloatType* src2,
                                                                                          CountType num) noexcept
{
    FloatVectorHelpers::subtractWithMultiply (dest, src1, src2, num);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::multiply (FloatType* dest,
                                                                              const FloatType* src,
                                                                              CountType numValues) noexcept
{
    FloatVectorHelpers::multiply (dest, src, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::multiply (FloatType* dest,
                                                                              const FloatType* src1,
                                                                              const FloatType* src2,
                                                                              CountType numValues) noexcept
{
    FloatVectorHelpers::multiply (dest, src1, src2, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::multiply (FloatType* dest,
                                                                              FloatType multiplier,
                                                                              CountType numValues) noexcept
{
    FloatVectorHelpers::multiply (dest, multiplier, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::multiply (FloatType* dest,
                                                                              const FloatType* src,
                                                                              FloatType multiplier,
                                                                              CountType num) noexcept
{
    FloatVectorHelpers::multiply (dest, src, multiplier, num);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::negate (FloatType* dest,
                                                                            const FloatType* src,
                                                                            CountType numValues) noexcept
{
    FloatVectorHelpers::negate (dest, src, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::abs (FloatType* dest,
                                                                         const FloatType* src,
                                                                         CountType numValues) noexcept
{
    FloatVectorHelpers::abs (dest, src, numValues);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::min (FloatType* dest,
                                                                         const FloatType* src,
                                                                         FloatType comp,
                                                                         CountType num) noexcept
{
    FloatVectorHelpers::min (dest, src, comp, num);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::min (FloatType* dest,
                                                                         const FloatType* src1,
                                                                         const FloatType* src2,
                                                                         CountType num) noexcept
{
    FloatVectorHelpers::min (dest, src1, src2, num);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::max (FloatType* dest,
                                                                         const FloatType* src,
                                                                         FloatType comp,
                                                                         CountType num) noexcept
{
    FloatVectorHelpers::max (dest, src, comp, num);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::max (FloatType* dest,
                                                                         const FloatType* src1,
                                                                         const FloatType* src2,
                                                                         CountType num) noexcept
{
    FloatVectorHelpers::max (dest, src1, src2, num);
}

template <typename FloatType, typename CountType>
void JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::clip (FloatType* dest,
                                                                          const FloatType* src,
                                                                          FloatType low,
                                                                          FloatType high,
                                                                          CountType num) noexcept
{
    FloatVectorHelpers::clip (dest, src, low, high, num);
}

template <typename FloatType, typename CountType>
Range<FloatType> JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::findMinAndMax (const FloatType* src,
                                                                                               CountType numValues) noexcept
{
    return FloatVectorHelpers::findMinAndMax (src, numValues);
}

template <typename FloatType, typename CountType>
FloatType JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::findMinimum (const FloatType* src,
                                                                                      CountType numValues) noexcept
{
    return FloatVectorHelpers::findMinimum (src, numValues);
}

template <typename FloatType, typename CountType>
FloatType JUCE_CALLTYPE FloatVectorOperationsBase<FloatType, CountType>::findMaximum (const FloatType* src,
                                                                                      CountType numValues) noexcept
{
    return FloatVectorHelpers::findMaximum (src, numValues);
}

template struct FloatVectorOperationsBase<float, int>;
template struct FloatVectorOperationsBase<float, size_t>;
template struct FloatVectorOperationsBase<double, int>;
template struct FloatVectorOperationsBase<double, size_t>;

void JUCE_CALLTYPE FloatVectorOperations::convertFixedToFloat (float* dest, const int* src, float multiplier, size_t num) noexcept
{
   FloatVectorHelpers::convertFixedToFloat (dest, src, multiplier, num);
}

void JUCE_CALLTYPE FloatVectorOperations::convertFixedToFloat (float* dest, const int* src, float multiplier, int num) noexcept
{
    FloatVectorHelpers::convertFixedToFloat (dest, src, multiplier, num);
}

intptr_t JUCE_CALLTYPE FloatVectorOperations::getFpStatusRegister() noexcept
{
    intptr_t fpsr = 0;
 #if JUCE_INTEL && JUCE_USE_SSE_INTRINSICS
    fpsr = static_cast<intptr_t> (_mm_getcsr());
 #elif (JUCE_64BIT && JUCE_ARM) || JUCE_USE_ARM_NEON
  #if _MSC_VER
    // _control87 returns static values for x86 bits that don't exist on arm
    // to emulate x86 behaviour. We are only ever interested in de-normal bits
    // so mask out only those.
    fpsr = (intptr_t) (_control87 (0, 0) & _MCW_DN);
  #else
   #if JUCE_64BIT
    asm volatile("mrs %0, fpcr"
                 : "=r"(fpsr));
   #elif JUCE_USE_ARM_NEON
    asm volatile("vmrs %0, fpscr"
                 : "=r"(fpsr));
   #endif
  #endif
 #else
  #if ! (defined (JUCE_INTEL) || defined (JUCE_ARM))
    jassertfalse; // No support for getting the floating point status register for your platform
  #endif
 #endif

    return fpsr;
}

void JUCE_CALLTYPE FloatVectorOperations::setFpStatusRegister ([[maybe_unused]] intptr_t fpsr) noexcept
{
 #if JUCE_INTEL && JUCE_USE_SSE_INTRINSICS
    // the volatile keyword here is needed to workaround a bug in AppleClang 13.0
    // which aggressively optimises away the variable otherwise
    volatile auto fpsr_w = static_cast<uint32_t> (fpsr);
    _mm_setcsr (fpsr_w);
 #elif (JUCE_64BIT && JUCE_ARM) || JUCE_USE_ARM_NEON
  #if _MSC_VER
    _control87 ((unsigned int) fpsr, _MCW_DN);
  #else
   #if JUCE_64BIT
    asm volatile("msr fpcr, %0"
                 :
                 : "ri"(fpsr));
   #elif JUCE_USE_ARM_NEON
    asm volatile("vmsr fpscr, %0"
                 :
                 : "ri"(fpsr));
   #endif
  #endif
 #else
  #if ! (defined (JUCE_INTEL) || defined (JUCE_ARM))
    jassertfalse; // No support for getting the floating point status register for your platform
  #endif
 #endif
}

void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode ([[maybe_unused]] bool shouldEnable) noexcept
{
  #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || (JUCE_64BIT && JUCE_ARM))
   #if JUCE_USE_SSE_INTRINSICS
    intptr_t mask = _MM_FLUSH_ZERO_MASK;
   #else /*JUCE_USE_ARM_NEON*/
    intptr_t mask = (1 << 24 /* FZ */);
   #endif
    setFpStatusRegister ((getFpStatusRegister() & (~mask)) | (shouldEnable ? mask : 0));
  #else
   #if ! (defined (JUCE_INTEL) || defined (JUCE_ARM))
    jassertfalse; // No support for flush to zero mode on your platform
   #endif
  #endif
}

void JUCE_CALLTYPE FloatVectorOperations::disableDenormalisedNumberSupport ([[maybe_unused]] bool shouldDisable) noexcept
{
  #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || (JUCE_64BIT && JUCE_ARM))
   #if JUCE_USE_SSE_INTRINSICS
    intptr_t mask = 0x8040;
   #else /*JUCE_USE_ARM_NEON*/
    intptr_t mask = (1 << 24 /* FZ */);
   #endif

    setFpStatusRegister ((getFpStatusRegister() & (~mask)) | (shouldDisable ? mask : 0));
  #else

   #if ! (defined (JUCE_INTEL) || defined (JUCE_ARM))
    jassertfalse; // No support for disable denormals mode on your platform
   #endif
  #endif
}

bool JUCE_CALLTYPE FloatVectorOperations::areDenormalsDisabled() noexcept
{
  #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || (JUCE_64BIT && JUCE_ARM))
   #if JUCE_USE_SSE_INTRINSICS
    intptr_t mask = 0x8040;
   #else /*JUCE_USE_ARM_NEON*/
    intptr_t mask = (1 << 24 /* FZ */);
   #endif

    return ((getFpStatusRegister() & mask) == mask);
  #else
    return false;
  #endif
}

ScopedNoDenormals::ScopedNoDenormals() noexcept
{
  #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || (JUCE_64BIT && JUCE_ARM))
   #if JUCE_USE_SSE_INTRINSICS
    intptr_t mask = 0x8040;
   #else /*JUCE_USE_ARM_NEON*/
    intptr_t mask = (1 << 24 /* FZ */);
   #endif

    fpsr = FloatVectorOperations::getFpStatusRegister();
    FloatVectorOperations::setFpStatusRegister (fpsr | mask);
  #endif
}

ScopedNoDenormals::~ScopedNoDenormals() noexcept
{
   #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || (JUCE_64BIT && JUCE_ARM))
    FloatVectorOperations::setFpStatusRegister (fpsr);
   #endif
}


//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS

class FloatVectorOperationsTests final : public UnitTest
{
public:
    FloatVectorOperationsTests()
        : UnitTest ("FloatVectorOperations", UnitTestCategories::audio)
    {}

    template <typename ValueType>
    struct TestRunner
    {
        static void runTest (UnitTest& u, Random random)
        {
            const int range = random.nextBool() ? 500 : 10;
            const int num = random.nextInt (range) + 1;

            HeapBlock<ValueType> buffer1 (num + 16), buffer2 (num + 16);
            HeapBlock<int> buffer3 (num + 16, true);

           #if JUCE_ARM
            ValueType* const data1 = buffer1;
            ValueType* const data2 = buffer2;
            int* const int1 = buffer3;
           #else
            // These tests deliberately operate on misaligned memory and will be flagged up by
            // checks for undefined behavior!
            ValueType* const data1 = addBytesToPointer (buffer1.get(), random.nextInt (16));
            ValueType* const data2 = addBytesToPointer (buffer2.get(), random.nextInt (16));
            int* const int1 = addBytesToPointer (buffer3.get(), random.nextInt (16));
           #endif

            fillRandomly (random, data1, num);
            fillRandomly (random, data2, num);

            Range<ValueType> minMax1 (FloatVectorOperations::findMinAndMax (data1, num));
            Range<ValueType> minMax2 (Range<ValueType>::findMinAndMax (data1, num));
            u.expect (minMax1 == minMax2);

            u.expect (valuesMatch (FloatVectorOperations::findMinimum (data1, num), juce::findMinimum (data1, num)));
            u.expect (valuesMatch (FloatVectorOperations::findMaximum (data1, num), juce::findMaximum (data1, num)));

            u.expect (valuesMatch (FloatVectorOperations::findMinimum (data2, num), juce::findMinimum (data2, num)));
            u.expect (valuesMatch (FloatVectorOperations::findMaximum (data2, num), juce::findMaximum (data2, num)));

            FloatVectorOperations::clear (data1, num);
            u.expect (areAllValuesEqual (data1, num, 0));

            FloatVectorOperations::fill (data1, (ValueType) 2, num);
            u.expect (areAllValuesEqual (data1, num, (ValueType) 2));

            FloatVectorOperations::add (data1, (ValueType) 2, num);
            u.expect (areAllValuesEqual (data1, num, (ValueType) 4));

            FloatVectorOperations::copy (data2, data1, num);
            u.expect (areAllValuesEqual (data2, num, (ValueType) 4));

            FloatVectorOperations::add (data2, data1, num);
            u.expect (areAllValuesEqual (data2, num, (ValueType) 8));

            FloatVectorOperations::copyWithMultiply (data2, data1, (ValueType) 4, num);
            u.expect (areAllValuesEqual (data2, num, (ValueType) 16));

            FloatVectorOperations::addWithMultiply (data2, data1, (ValueType) 4, num);
            u.expect (areAllValuesEqual (data2, num, (ValueType) 32));

            FloatVectorOperations::multiply (data1, (ValueType) 2, num);
            u.expect (areAllValuesEqual (data1, num, (ValueType) 8));

            FloatVectorOperations::multiply (data1, data2, num);
            u.expect (areAllValuesEqual (data1, num, (ValueType) 256));

            FloatVectorOperations::negate (data2, data1, num);
            u.expect (areAllValuesEqual (data2, num, (ValueType) -256));

            FloatVectorOperations::subtract (data1, data2, num);
            u.expect (areAllValuesEqual (data1, num, (ValueType) 512));

            FloatVectorOperations::abs (data1, data2, num);
            u.expect (areAllValuesEqual (data1, num, (ValueType) 256));

            FloatVectorOperations::abs (data2, data1, num);
            u.expect (areAllValuesEqual (data2, num, (ValueType) 256));

            fillRandomly (random, int1, num);
            doConversionTest (u, data1, data2, int1, num);

            FloatVectorOperations::fill (data1, (ValueType) 2, num);
            FloatVectorOperations::fill (data2, (ValueType) 3, num);
            FloatVectorOperations::addWithMultiply (data1, data1, data2, num);
            u.expect (areAllValuesEqual (data1, num, (ValueType) 8));
        }

        static void doConversionTest (UnitTest& u, float* data1, float* data2, int* const int1, int num)
        {
            FloatVectorOperations::convertFixedToFloat (data1, int1, 2.0f, num);
            convertFixed (data2, int1, 2.0f, num);
            u.expect (buffersMatch (data1, data2, num));
        }

        static void doConversionTest (UnitTest&, double*, double*, int*, int) {}

        static void fillRandomly (Random& random, ValueType* d, int num)
        {
            while (--num >= 0)
                *d++ = (ValueType) (random.nextDouble() * 1000.0);
        }

        static void fillRandomly (Random& random, int* d, int num)
        {
            while (--num >= 0)
                *d++ = random.nextInt();
        }

        static void convertFixed (float* d, const int* s, ValueType multiplier, int num)
        {
            while (--num >= 0)
                *d++ = (float) *s++ * multiplier;
        }

        static bool areAllValuesEqual (const ValueType* d, int num, ValueType target)
        {
            while (--num >= 0)
                if (! exactlyEqual (*d++, target))
                    return false;

            return true;
        }

        static bool buffersMatch (const ValueType* d1, const ValueType* d2, int num)
        {
            while (--num >= 0)
                if (! valuesMatch (*d1++, *d2++))
                    return false;

            return true;
        }

        static bool valuesMatch (ValueType v1, ValueType v2)
        {
            return std::abs (v1 - v2) < std::numeric_limits<ValueType>::epsilon();
        }
    };

    void runTest() override
    {
        beginTest ("FloatVectorOperations");

        for (int i = 1000; --i >= 0;)
        {
            TestRunner<float>::runTest (*this, getRandom());
            TestRunner<double>::runTest (*this, getRandom());
        }
    }
};

static FloatVectorOperationsTests vectorOpTests;

#endif

} // namespace juce
