/*
 * DISTRHO Plugin Framework (DPF)
 * Copyright (C) 2021 Jean Pierre Cimalando <jp-dev@inbox.ru>
 * Copyright (C) 2021-2023 Filipe Coelho <falktx@falktx.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any purpose with
 * or without fee is hereby granted, provided that the above copyright notice and this
 * permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
 * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#ifndef DISTRHO_VALUE_SMOOTHER_HPP_INCLUDED
#define DISTRHO_VALUE_SMOOTHER_HPP_INCLUDED

#include "../DistrhoUtils.hpp"

START_NAMESPACE_DISTRHO

// --------------------------------------------------------------------------------------------------------------------

/**
 * @brief An exponential smoother for control values
 *
 * This continually smooths a value towards a defined target,
 * using a low-pass filter of the 1st order, which creates an exponential curve.
 *
 * The length of the curve is defined by a T60 constant,
 * which is the time it takes for a 1-to-0 smoothing to fall to -60dB.
 *
 * Note that this smoother has asymptotical behavior,
 * and it must not be assumed that the final target is ever reached.
 */
class ExponentialValueSmoother {
    float coef;
    float target;
    float mem;
    float tau;
    float sampleRate;

public:
    ExponentialValueSmoother()
        : coef(0.f),
          target(0.f),
          mem(0.f),
          tau(0.f),
          sampleRate(0.f) {}

    void setSampleRate(const float newSampleRate) noexcept
    {
        if (d_isNotEqual(sampleRate, newSampleRate))
        {
            sampleRate = newSampleRate;
            updateCoef();
        }
    }

    void setTimeConstant(const float newT60) noexcept
    {
        const float newTau = newT60 * (float)(1.0 / 6.91);

        if (d_isNotEqual(tau, newTau))
        {
            tau = newTau;
            updateCoef();
        }
    }

    float getCurrentValue() const noexcept
    {
        return mem;
    }

    float getTargetValue() const noexcept
    {
        return target;
    }

    void setTargetValue(const float newTarget) noexcept
    {
        target = newTarget;
    }

    void clearToTargetValue() noexcept
    {
        mem = target;
    }

    inline float peek() const noexcept
    {
        return mem * coef + target * (1.f - coef);
    }

    inline float next() noexcept
    {
        return (mem = mem * coef + target * (1.f - coef));
    }

private:
    void updateCoef() noexcept
    {
        coef = std::exp(-1.f / (tau * sampleRate));
    }
};

// --------------------------------------------------------------------------------------------------------------------

/**
 * @brief A linear smoother for control values
 *
 * This continually smooths a value towards a defined target, using linear segments.
 *
 * The duration of the smoothing segment is defined by the given time constant.
 * Every time the target changes, a new segment restarts for the whole duration of the time constant.
 *
 * Note that this smoother, unlike an exponential smoother, eventually should converge to its target value.
 */
class LinearValueSmoother {
    float step;
    float target;
    float mem;
    float tau;
    float sampleRate;

public:
    LinearValueSmoother()
        : step(0.f),
          target(0.f),
          mem(0.f),
          tau(0.f),
          sampleRate(0.f) {}

    void setSampleRate(const float newSampleRate) noexcept
    {
        if (d_isNotEqual(sampleRate, newSampleRate))
        {
            sampleRate = newSampleRate;
            updateStep();
        }
    }

    void setTimeConstant(const float newTau) noexcept
    {
        if (d_isNotEqual(tau, newTau))
        {
            tau = newTau;
            updateStep();
        }
    }

    float getCurrentValue() const noexcept
    {
        return mem;
    }

    float getTargetValue() const noexcept
    {
        return target;
    }

    void setTargetValue(const float newTarget) noexcept
    {
        if (d_isNotEqual(target, newTarget))
        {
            target = newTarget;
            updateStep();
        }
    }

    void clearToTargetValue() noexcept
    {
        mem = target;
    }

    inline float peek() const noexcept
    {
        const float dy = target - mem;
        return mem + std::copysign(std::fmin(std::abs(dy), step), dy);
    }

    inline float next() noexcept
    {
        const float y0 = mem;
        const float dy = target - y0;
        return (mem = y0 + std::copysign(std::fmin(std::abs(dy), step), dy));
    }

private:
    void updateStep() noexcept
    {
        step = std::abs(target - mem) / (tau * sampleRate);
    }
};

// --------------------------------------------------------------------------------------------------------------------

END_NAMESPACE_DISTRHO

#endif // DISTRHO_VALUE_SMOOTHER_HPP_INCLUDED
