#pragma once
#include "range.h"
namespace DHE {
namespace Sigmoid {
static constexpr auto sigmoid_range = Range{-1.f, 1.f};
static constexpr auto proportion_range = Range{0.f, 1.f};
/**
* Applies an inverse sigmoid function to the input.
*
* The curvature determines the shape and intensity of the transfer function.
* A positive curvature applies an inverted S-shaped transfer function.
* A curvature of 0 applies a linear transfer function.
* A negative curvature applies an S-shaped transfer function.
*
* Before the function is applied:
*
* - The input is clamped to the range [-1.0, 1.0].
* - The curvature is clamped to the range [0.0001, 0.9999].
*
* @param input the input to the inverse sigmoid function
* @param curvature the intensity and direction of the curvature
* @return the sigmoid function result
*/
inline auto inverse(float input, float curvature) -> float {
static constexpr auto precision = 1e-4f;
static constexpr auto max_curvature = 1.0f - precision;
static constexpr auto curvature_range = Range{-max_curvature, max_curvature};
curvature = curvature_range.clamp(curvature);
input = sigmoid_range.clamp(input);
return (input - input * curvature) /
(curvature - std::abs(input) * 2.0f * curvature + 1.0f);
}
/**
* Applies a sigmoid function to the input.
*
* The curvature determines the shape and intensity of the transfer function.
* A positive curvature applies an S-shaped transfer function.
* A curvature of 0 applies a linear transfer function.
* A negative curvature applies an inverted S-shaped transfer function.
*
* Before the function is applied:
*
* - The input is clamped to the range [-1.0, 1.0].
* - The curvature is clamped to the range [0.0001, 0.9999].
*
* @param input the input to the sigmoid function
* @param curvature the intensity and direction of the curvature
* @return the sigmoid function result
*/
inline auto curve(float input, float curvature) -> float {
return inverse(input, -curvature);
}
/**
* Applies a gentle S-shaped transfer function to map an input in the range
* [0.0, 1.0] to an output in the range [-1.0, 1.0]. The transfer function makes
* the output more sensitive to changes in inputs near 0.5 and less sensitive to
* changes near 0.0 and 1.0.
*
* This function is intended to translate DHE-Modules CURVE knob rotation to a
* curvature value suitable to pass to the curve(), inverse(), j_taper(), and
* s_taper() functions.
*
* @param input the value to map to a curvature
* @return the curvature
*/
inline auto curvature(float input) -> float {
// This curvature creates a gentle S curve, increasing sensitivity in the
// middle of the input range and decreasing sensitivity toward the extremes.
static constexpr auto gentle_s = 0.65f;
auto scaled = sigmoid_range.scale(input);
return curve(scaled, gentle_s);
}
/**
* Applies a J-shaped transfer function to the input.
*
* The curvature determines the shape and intensity of the taper.
* A positive curvature applies a J-taper.
* A curvature of 0 applies a linear taper.
* A negative curvature applies an inverted J-taper.
*
* Before the function is applied:
*
* - The input is clamped to the range [0.0, 1.0].
* - The curvature is clamped to the range [0.0001, 0.9999].
*
* @param input the input to the taper function
* @param curvature the intensity and direction of the taper
* @return the taper function result
*/
inline auto j_taper(float input, float curvature) -> float {
return inverse(proportion_range.clamp(input), curvature);
}
/**
* Applies an S-shaped transfer function to the input.
*
* The curvature determines the shape and intensity of the taper.
* A positive curvature applies an S-taper.
* A curvature of 0 applies a linear taper.
* A negative curvature applies an inverted S-taper.
*
* Before the function is applied:
*
* - The input is clamped to the range [0.0, 1.0].
* - The curvature is clamped to the range [0.0001, 0.9999].
*
* @param input the input to the taper function
* @param curvature the intensity and direction of the taper
* @return the taper function result
*/
inline auto s_taper(float input, float curvature) -> float {
const auto scaled = sigmoid_range.scale(input);
const auto tapered = curve(scaled, curvature);
return sigmoid_range.normalize(tapered);
}
inline auto taper(float input, float curvature, bool is_s) -> float {
return is_s ? s_taper(input, curvature) : j_taper(input, curvature);
}
} // namespace Sigmoid
} // namespace DHE