You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

131 lines
4.6KB

  1. #pragma once
  2. #include "range.h"
  3. namespace DHE {
  4. namespace Sigmoid {
  5. static constexpr auto sigmoid_range = Range{-1.f, 1.f};
  6. static constexpr auto proportion_range = Range{0.f, 1.f};
  7. /**
  8. * Applies an inverse sigmoid function to the input.
  9. * <p>
  10. * The curvature determines the shape and intensity of the transfer function.
  11. * A positive curvature applies an inverted S-shaped transfer function.
  12. * A curvature of 0 applies a linear transfer function.
  13. * A negative curvature applies an S-shaped transfer function.
  14. * <p>
  15. * Before the function is applied:
  16. * <ul>
  17. * <li>The input is clamped to the range [-1.0, 1.0].</li>
  18. * <li>The curvature is clamped to the range [0.0001, 0.9999].</li>
  19. * </ul>
  20. * @param input the input to the inverse sigmoid function
  21. * @param curvature the intensity and direction of the curvature
  22. * @return the sigmoid function result
  23. */
  24. inline auto inverse(float input, float curvature) -> float {
  25. static constexpr auto precision = 1e-4f;
  26. static constexpr auto max_curvature = 1.0f - precision;
  27. static constexpr auto curvature_range = Range{-max_curvature, max_curvature};
  28. curvature = curvature_range.clamp(curvature);
  29. input = sigmoid_range.clamp(input);
  30. return (input - input * curvature) /
  31. (curvature - std::abs(input) * 2.0f * curvature + 1.0f);
  32. }
  33. /**
  34. * Applies a sigmoid function to the input.
  35. * <p>
  36. * The curvature determines the shape and intensity of the transfer function.
  37. * A positive curvature applies an S-shaped transfer function.
  38. * A curvature of 0 applies a linear transfer function.
  39. * A negative curvature applies an inverted S-shaped transfer function.
  40. * <p>
  41. * Before the function is applied:
  42. * <ul>
  43. * <li>The input is clamped to the range [-1.0, 1.0].</li>
  44. * <li>The curvature is clamped to the range [0.0001, 0.9999].</li>
  45. * </ul>
  46. * @param input the input to the sigmoid function
  47. * @param curvature the intensity and direction of the curvature
  48. * @return the sigmoid function result
  49. */
  50. inline auto curve(float input, float curvature) -> float {
  51. return inverse(input, -curvature);
  52. }
  53. /**
  54. * Applies a gentle S-shaped transfer function to map an input in the range
  55. * [0.0, 1.0] to an output in the range [-1.0, 1.0]. The transfer function makes
  56. * the output more sensitive to changes in inputs near 0.5 and less sensitive to
  57. * changes near 0.0 and 1.0.
  58. * <p>
  59. * This function is intended to translate DHE-Modules CURVE knob rotation to a
  60. * curvature value suitable to pass to the curve(), inverse(), j_taper(), and
  61. * s_taper() functions.
  62. *
  63. * @param input the value to map to a curvature
  64. * @return the curvature
  65. */
  66. inline auto curvature(float input) -> float {
  67. // This curvature creates a gentle S curve, increasing sensitivity in the
  68. // middle of the input range and decreasing sensitivity toward the extremes.
  69. static constexpr auto gentle_s = 0.65f;
  70. auto scaled = sigmoid_range.scale(input);
  71. return curve(scaled, gentle_s);
  72. }
  73. /**
  74. * Applies a J-shaped transfer function to the input.
  75. * <p>
  76. * The curvature determines the shape and intensity of the taper.
  77. * A positive curvature applies a J-taper.
  78. * A curvature of 0 applies a linear taper.
  79. * A negative curvature applies an inverted J-taper.
  80. * <p>
  81. * Before the function is applied:
  82. * <ul>
  83. * <li>The input is clamped to the range [0.0, 1.0].</li>
  84. * <li>The curvature is clamped to the range [0.0001, 0.9999].</li>
  85. * </ul>
  86. * @param input the input to the taper function
  87. * @param curvature the intensity and direction of the taper
  88. * @return the taper function result
  89. */
  90. inline auto j_taper(float input, float curvature) -> float {
  91. return inverse(proportion_range.clamp(input), curvature);
  92. }
  93. /**
  94. * Applies an S-shaped transfer function to the input.
  95. * <p>
  96. * The curvature determines the shape and intensity of the taper.
  97. * A positive curvature applies an S-taper.
  98. * A curvature of 0 applies a linear taper.
  99. * A negative curvature applies an inverted S-taper.
  100. * <p>
  101. * Before the function is applied:
  102. * <ul>
  103. * <li>The input is clamped to the range [0.0, 1.0].</li>
  104. * <li>The curvature is clamped to the range [0.0001, 0.9999].</li>
  105. * </ul>
  106. * @param input the input to the taper function
  107. * @param curvature the intensity and direction of the taper
  108. * @return the taper function result
  109. */
  110. inline auto s_taper(float input, float curvature) -> float {
  111. const auto scaled = sigmoid_range.scale(input);
  112. const auto tapered = curve(scaled, curvature);
  113. return sigmoid_range.normalize(tapered);
  114. }
  115. inline auto taper(float input, float curvature, bool is_s) -> float {
  116. return is_s ? s_taper(input, curvature) : j_taper(input, curvature);
  117. }
  118. } // namespace Sigmoid
  119. } // namespace DHE