#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: *

* @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: *

* @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: *

* @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: *

* @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