Skip to content

File Utils.h

File List > Amplitude > Math > Utils.h

Go to the documentation of this file

// Copyright (c) 2021-present Sparky Studios. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#ifndef _AM_MATH_UTILS_H
#define _AM_MATH_UTILS_H

#include <SparkyStudios/Audio/Amplitude/Core/AudioBuffer.h>
#include <SparkyStudios/Audio/Amplitude/Math/LinearAlgebra.h>

#define AM_LCG_M 2147483647
#define AM_LCG_A 48271
#define AM_LCG_C 0
#define AM_PI 3.14159265358979323846
#define AM_PI32 3.14159265359f
#define AM_DEG180 180.0
#define AM_DEG18032 180.0f
#define AM_TURNHALF 0.5
#define AM_TURNHALF32 0.5f
#define AM_RadToDeg ((float)(AM_DEG180 / AM_PI))
#define AM_RadToTurn ((float)(AM_TURNHALF / AM_PI))
#define AM_DegToRad ((float)(AM_PI / AM_DEG180))
#define AM_DegToTurn ((float)(AM_TURNHALF / AM_DEG180))
#define AM_TurnToRad ((float)(AM_PI / AM_TURNHALF))
#define AM_TurnToDeg ((float)(AM_DEG180 / AM_TURNHALF))

namespace SparkyStudios::Audio::Amplitude
{
    struct BezierCurveControlPoints
    {
        AmReal32 x1;

        AmReal32 y1;

        AmReal32 x2;

        AmReal32 y2;
    };

    AM_API_PRIVATE struct
    {
        AmInt32 state;
    } gLCG = { 4321 };

    AM_API_PRIVATE AM_INLINE AmReal32 AmDitherReal32(const AmReal32 ditherMin, const AmReal32 ditherMax)
    {
        gLCG.state = (AM_LCG_A * gLCG.state + AM_LCG_C) % AM_LCG_M;
        const AmReal32 x = gLCG.state / static_cast<double>(0x7FFFFFFF);
        return ditherMin + x * (ditherMax - ditherMin);
    }

    AM_API_PRIVATE AM_INLINE AmInt32 AmFloatToFixedPoint(const AmReal32 x)
    {
        return static_cast<AmInt32>(x * kAmFixedPointUnit);
    }

    AM_API_PRIVATE AM_INLINE AmReal32 AmInt16ToReal32(const AmInt16 x)
    {
        auto y = static_cast<AmReal32>(x);

#if defined(AM_ACCURATE_CONVERSION)
        // The accurate way.
        y = y + 32768.0f; // -32768..32767 to 0..65535
        y = y * 0.00003051804379339284f; // 0..65535 to 0..2
        y = y - 1; // 0..2 to -1..1
#else
        // The fast way.
        y = y * 0.000030517578125f; // -32768..32767 to -1..0.999969482421875
#endif

        return y;
    }

    AM_API_PRIVATE AM_INLINE AmReal32 AmInt32ToReal32(const AmInt32 x)
    {
        auto y = static_cast<AmReal32>(x);

#if defined(AM_ACCURATE_CONVERSION)
        // The accurate way.
        y = y + 32768.0f; // -32768..32767 to 0..65535
        y = y * 0.00003051804379339284f; // 0..65535 to 0..2
        y = y - 1; // 0..2 to -1..1
#else
        // The fast way.
        y = y * 0.000030517578125f; // -32768..32767 to -1..0.999969482421875
#endif

        return y;
    }

    AM_API_PRIVATE AM_INLINE AmInt16 AmReal32ToInt16(const AmReal32 x, bool dithering = false)
    {
        AmReal32 y = x;

        if (dithering)
        {
            // Performs a rectangular dithering
            y += AmDitherReal32(1.0f / INT16_MIN, 1.0f / INT16_MAX);
        }

        y = AM_CLAMP(y, -1.0f, 1.0f);

#if defined(AM_ACCURATE_CONVERSION)
        // The accurate way.
        y = y + 1; // -1..1 to 0..2
        y = y * 32767.5f; // 0..2 to 0..65535
        y = y - 32768.0f; // 0...65535 to -32768..32767
#else
        // The fast way.
        y = y * 32767.0f; // -1..1 to -32767..32767
#endif

        return static_cast<AmInt16>(y);
    }

    AM_API_PRIVATE AM_INLINE AmReal32
    CatmullRom(const AmReal32 t, const AmReal32 p0, const AmReal32 p1, const AmReal32 p2, const AmReal32 p3)
    {
        // clang-format off
        return 0.5f * (
            (2 * p1) +
            (-p0 + p2) * t +
            (2 * p0 - 5 * p1 + 4 * p2 - p3) * t * t +
            (-p0 + 3 * p1 - 3 * p2 + p3) * t * t * t
        );
        // clang-format on
    }

    template<typename T>
    AM_API_PRIVATE AM_INLINE T NextPowerOf2(const T& val)
    {
        T nextPowerOf2 = 1;
        while (nextPowerOf2 < val)
            nextPowerOf2 *= 2;

        return nextPowerOf2;
    }

    template<typename T>
    AM_API_PRIVATE AM_INLINE T IntegerPow(T base, AmInt32 exp)
    {
        AMPLITUDE_ASSERT(exp >= 0);
        T result = static_cast<T>(1);

        while (true)
        {
            if (exp & 1)
                result *= base;

            exp >>= 1;

            if (!exp)
                break;

            base *= base;
        }

        return result;
    }

    AM_API_PRIVATE AM_INLINE AmInt64 FindGCD(AmInt64 a, AmInt64 b)
    {
        a = std::abs(a);
        b = std::abs(b);

        AmInt64 c = 0;

        while (b != 0)
        {
            c = b;
            b = a % b;
            a = c;
        }

        return a;
    }

    struct AmRational
    {
        AmUInt64 numerator;   
        AmUInt64 denominator; 
        bool exact;           
    };

    AM_API_PRIVATE AM_INLINE AmRational ApproximateRational(AmUInt64 numerator, AmUInt64 denominator, AmUInt64 maxTerm)
    {
        AMPLITUDE_ASSERT(numerator > 0 && denominator > 0 && maxTerm > 0);

        const auto gcd = static_cast<AmUInt64>(FindGCD(static_cast<AmInt64>(numerator), static_cast<AmInt64>(denominator)));

        const AmUInt64 a = numerator / gcd;
        const AmUInt64 b = denominator / gcd;

        if (a <= maxTerm && b <= maxTerm)
            return { a, b, true };

        // Continued fraction expansion of a / b. h is the convergent numerator, k its denominator.
        AmUInt64 hm2 = 0, hm1 = 1;
        AmUInt64 km2 = 1, km1 = 0;
        AmUInt64 x = a, y = b;

        while (y != 0)
        {
            const AmUInt64 t = x / y;
            const AmUInt64 h = t * hm1 + hm2;
            const AmUInt64 k = t * km1 + km2;

            if (h > maxTerm || k > maxTerm)
            {
                // Largest semiconvergent that still fits the bound.
                AmUInt64 low = 0, high = t;
                while (low < high)
                {
                    const AmUInt64 mid = (low + high + 1) / 2;
                    if ((mid * hm1 + hm2) <= maxTerm && (mid * km1 + km2) <= maxTerm)
                        low = mid;
                    else
                        high = mid - 1;
                }

                AmUInt64 bestNumerator = hm1;
                AmUInt64 bestDenominator = km1;

                if (low > 0)
                {
                    const AmUInt64 candidateNumerator = low * hm1 + hm2;
                    const AmUInt64 candidateDenominator = low * km1 + km2;

                    if (bestDenominator == 0)
                    {
                        bestNumerator = candidateNumerator;
                        bestDenominator = candidateDenominator;
                    }
                    else
                    {
                        // Compare |a/b - p/q| for both candidates without leaving integer arithmetic:
                        // |a*q - b*p| / q, cross-multiplied by the other denominator.
                        const AmUInt64 bestLeft = a * bestDenominator;
                        const AmUInt64 bestRight = b * bestNumerator;
                        const AmUInt64 bestError = bestLeft > bestRight ? bestLeft - bestRight : bestRight - bestLeft;

                        const AmUInt64 candidateLeft = a * candidateDenominator;
                        const AmUInt64 candidateRight = b * candidateNumerator;
                        const AmUInt64 candidateError =
                            candidateLeft > candidateRight ? candidateLeft - candidateRight : candidateRight - candidateLeft;

                        if (candidateError * bestDenominator <= bestError * candidateDenominator)
                        {
                            bestNumerator = candidateNumerator;
                            bestDenominator = candidateDenominator;
                        }
                    }
                }

                return { bestNumerator, bestDenominator, false };
            }

            hm2 = hm1;
            hm1 = h;
            km2 = km1;
            km1 = k;

            const AmUInt64 remainder = x - t * y;
            x = y;
            y = remainder;
        }

        // Unreachable: the early return above covers every pair that fits the bound.
        return { hm1, km1, hm1 <= maxTerm && km1 <= maxTerm };
    }

    AM_API_PRIVATE AM_INLINE AmReal32 InverseSquareRoot(AmReal32 x)
    {
        // Use the fast inverse square root method (Quake III)
        AmReal32 h = 0.5f * x;
        AmInt32 i = *(AmInt32*)&x;
        i = 0x5f3759df - (i >> 1);
        x = *(AmReal32*)&i;
        x = x * (1.5f - h * x * x); // 1st iteration
#if defined(AM_ACCURATE_CONVERSION)
        x = x * (1.5f - h * x * x); // 2nd iteration for more accuracy
#endif
        return x;
    }

    AM_API_PRIVATE AM_INLINE AmReal32 Lerp(const AmReal32 t, const AmReal32 p0, const AmReal32 p1)
    {
        return (1.0f - t) * p0 + t * p1;
    }
} // namespace SparkyStudios::Audio::Amplitude

#endif // _AM_MATH_UTILS_H