Advertisement
jonv99

Juce-compatible improved normalisable ranges

Jan 26th, 2021
456
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 17.55 KB | None | 0 0
  1. #pragma once
  2.  
  3. #include <algorithm>
  4. #include <array>
  5. #include <cassert>
  6. #include <cmath>
  7.  
  8. namespace improved_norm_range {
  9.  
  10. template <typename NumType>
  11. inline NumType constrain(const NumType min, const NumType max, const NumType x) {
  12.     return std::min(max, std::max(min, x));
  13. }
  14.  
  15. template <typename NumType>
  16. inline NumType reflect(const NumType mirror, const NumType x) {
  17.     return mirror + mirror - x;
  18. }
  19.  
  20. // Private class used for implementation, use RangeLin instead.
  21. template <typename FloatType>
  22. struct RangeLin_Inc_ {
  23.     FloatType lowerb_, upperb_;
  24.     RangeLin_Inc_() : lowerb_(0.0), upperb_(1.0) {}
  25.     RangeLin_Inc_(const FloatType start, const FloatType end) : lowerb_(start), upperb_(end) {
  26.         if (end < start) std::swap(lowerb_, upperb_);
  27.         assert(lowerb_ < upperb_);
  28.     }
  29.  
  30.     FloatType lowerb() const { return lowerb_; }
  31.     FloatType upperb() const { return upperb_; }
  32.     FloatType start() const { return lowerb_; }
  33.     FloatType end() const { return upperb_; }
  34.  
  35.     FloatType normalise(const FloatType unnorm_val) const {
  36.         assert(lowerb_ <= unnorm_val && unnorm_val <= upperb_);
  37.         FloatType norm = (unnorm_val-lowerb_) / (upperb_-lowerb_);
  38.         // in case of rounding error
  39.         return constrain<FloatType>(0.0, 1.0, norm);
  40.     }
  41.  
  42.     FloatType unnormalise(const FloatType norm_val) const {
  43.         assert(0.0 <= norm_val && norm_val <= 1.0);
  44.         FloatType unnorm = lowerb_ + norm_val*(upperb_-lowerb_);
  45.         return constrain(lowerb_, upperb_, unnorm);
  46.     }
  47. };
  48.  
  49. // Remembers whether or not to flip a value in the normal domain, for
  50. // ranges where start > end
  51. template <typename FloatType>
  52. struct FlipNorm {
  53.     bool flip_;
  54.     FlipNorm() : flip_(false) {}
  55.     FlipNorm(const FloatType start, const FloatType end) : flip_(start > end) {}
  56.     FlipNorm(const bool flip) : flip_(flip) {}
  57.  
  58.     bool flipped() const { return flip_; }
  59.  
  60.     FloatType flipn(const FloatType norm_val) const {
  61.         assert(0 <= norm_val && norm_val <= 1.0);
  62.         return flip_ ? 1.0 - norm_val : norm_val;
  63.     }
  64. };
  65.  
  66. // Linear range, increasing or decreasing
  67. // RangeLin_Inc_ does much of the assertions & constraining
  68. template <typename FloatType>
  69. struct RangeLin {
  70.     RangeLin_Inc_<FloatType> range_;
  71.     FlipNorm<FloatType> fn_;
  72.     RangeLin() {}
  73.     RangeLin(const FloatType start, const FloatType end) : range_(start, end), fn_(start, end) {}
  74.  
  75.     FloatType normalise(const FloatType unnorm_val) const {
  76.         return fn_.flipn(range_.normalise(unnorm_val));
  77.     }
  78.  
  79.     FloatType unnormalise(const FloatType norm_val) const {
  80.         return range_.unnormalise(fn_.flipn(norm_val));
  81.     }
  82.  
  83.     FloatType lowerb() const { return range_.lowerb(); }
  84.     FloatType upperb() const { return range_.upperb(); }
  85.     FloatType start() const { return fn_.flipped() ? range_.upperb() : range_.lowerb(); }
  86.     FloatType end() const { return fn_.flipped() ? range_.lowerb() : range_.upperb(); }
  87. };
  88.  
  89. // Skewed range, increasing or decreasing
  90. // Aims to be compatible with juce skew, but with mirroring OFF
  91. // If you need juce mirroring, use RangSymmSkew with a mirror point in the center
  92. template <typename FloatType>
  93. struct RangeSkewed {
  94.     RangeLin_Inc_<FloatType> range_;
  95.     FloatType skew_exp_;
  96.     FlipNorm<FloatType> fn_;
  97.  
  98.     RangeSkewed() : skew_exp_(1.0) {}
  99.     RangeSkewed(const FloatType start, const FloatType end) : range_(start, end), skew_exp_(1.0), fn_(start, end) {}
  100.     RangeSkewed(const FloatType start, const FloatType end, const FloatType skew) : range_(start, end), skew_exp_(skew), fn_(start, end) {}
  101.  
  102.     // Tested
  103.     RangeSkewed(const FloatType start, const FloatType end,
  104.         const FloatType map_unnorm_val, const FloatType to_norm_val)
  105.         : range_(start, end), fn_(start, end)
  106.     {
  107.         assert(0.0 <= to_norm_val && to_norm_val <= 1.0);
  108.         assert(range_.lowerb() <= map_unnorm_val && map_unnorm_val <= range_.upperb());
  109.         skew_exp_ = std::log(to_norm_val) / std::log(range_.normalise(map_unnorm_val));
  110.     }
  111.  
  112.     // Skew a value in the range [0, 1] into the range [0, 1]
  113.     FloatType skew_(const FloatType unsk_val) const {
  114.         assert(0.0 <= unsk_val && unsk_val <= 1.0);
  115.         // We assume that a std lib implementation of pow() is always the identity for
  116.         // args of 0.0 or 1.0
  117.         return std::pow(unsk_val, skew_exp_);
  118.     }
  119.  
  120.     // Unskew a value in the range [0, 1] into the range [0, 1]
  121.     FloatType unskew_(const FloatType skewed_val) const {
  122.         assert(0.0 <= skewed_val && skewed_val <= 1.0);
  123.         // less code method that gives almost-same result as juce with some rounding error
  124.         //return std::pow(skewed_val, 1.0 / skew_exp_);
  125.         // juce method that should give identical result
  126.         return skewed_val > 0.0 ? std::exp(std::log(skewed_val) / skew_exp_) : skewed_val;
  127.     }
  128.  
  129.     FloatType normalise(const FloatType unnorm_val) const {
  130.         return fn_.flipn(skew_(range_.normalise(unnorm_val)));
  131.     }
  132.  
  133.     FloatType unnormalise(const FloatType norm_val) const {
  134.         return range_.unnormalise(unskew_(fn_.flipn(norm_val)));
  135.     }
  136.  
  137.     FloatType lowerb() const { return range_.lowerb(); }
  138.     FloatType upperb() const { return range_.upperb(); }
  139.     FloatType start() const { return fn_.flipped() ? range_.upperb() : range_.lowerb(); }
  140.     FloatType end() const { return fn_.flipped() ? range_.lowerb() : range_.upperb(); }
  141. };
  142.  
  143. // Note that to_partial_norm_val is not normalised over the whole range,
  144. // as this would require numerical methods to solve. Instead, it represents
  145. // the proportion between mirror_point and the upper bound. Eg:
  146. // RangeSymmSkew(-12.0, 12.0, 6.0, 0.5, 0.0) which maps 6.0 to 0.5 about 0.0,
  147. // gives a straight line
  148. template <typename FloatType>
  149. struct RangeSymmSkew {
  150.     RangeSkewed<FloatType> lower_range_;
  151.     RangeLin_Inc_<FloatType> whole_range_;
  152.     FloatType lowerb_, upperb_, mirror_point_;
  153.     FlipNorm<FloatType> fn_;
  154.     // The following params are only needed to make to() versions with different FloatType
  155.     //FloatType map_unnorm_val_, to_partial_norm_val_;
  156.  
  157.     //RangeSymmSkew() : lower_range_(-1.0, 0.0), whole_range_(-1.0, 1.0), lowerb_(-1.0), upperb_(1.0), mirror_point_(0.0) {}
  158.     RangeSymmSkew(const FloatType start, const FloatType end,
  159.         const FloatType map_unnorm_val, const FloatType to_partial_norm_val,
  160.         const FloatType mirror_point) : fn_(start, end)
  161.     {
  162.         lowerb_ = start, upperb_ = end;
  163.         if (fn_.flipped()) std::swap(lowerb_, upperb_);
  164.         assert(lowerb_ <= mirror_point && mirror_point <= upperb_);
  165.         assert(mirror_point != map_unnorm_val);
  166.         mirror_point_ = mirror_point;
  167.         FloatType map_un = map_unnorm_val;
  168.         FloatType to_pn = to_partial_norm_val;
  169.         if (map_unnorm_val > mirror_point_) {
  170.             map_un = reflect(mirror_point_, map_un);
  171.             to_pn = 1.0 - to_pn;
  172.         }
  173.         FloatType extended_lower_range = std::max(mirror_point_ - lowerb_, upperb_ - mirror_point_);
  174.         lower_range_ = RangeSkewed<FloatType>(mirror_point_-extended_lower_range, mirror_point_, map_un, to_pn);
  175.         whole_range_ = RangeLin_Inc_<FloatType>(partial_normalise_(lowerb_), partial_normalise_(upperb_));
  176.     }
  177.  
  178.     FloatType partial_normalise_(const FloatType unnorm_val) const {
  179.         assert(lowerb_ <= unnorm_val && unnorm_val <= upperb_);
  180.         bool upper = unnorm_val > mirror_point_;
  181.         FloatType un = unnorm_val;
  182.         if (upper) un = reflect<FloatType>(mirror_point_, un);
  183.         FloatType n = lower_range_.normalise(un);
  184.         if (upper) n = reflect<FloatType>(1.0, n);
  185.         return n;
  186.         // Don't need to check for small fp out-of-bounds error here as this
  187.         // function is used to define whole_range_ bounds in the first place
  188.     }
  189.  
  190.     FloatType normalise(const FloatType unnorm_val) const {
  191.         FloatType pn = partial_normalise_(unnorm_val);
  192.         return fn_.flipn(whole_range_.normalise(pn));
  193.     }
  194.  
  195.     FloatType unnormalise(const FloatType norm_val) const {
  196.         FloatType pu = whole_range_.unnormalise(fn_.flipn(norm_val));
  197.         bool upper = pu > 1.0;
  198.         if (upper) pu = reflect<FloatType>(1.0, pu);
  199.         FloatType un = lower_range_.unnormalise(pu);
  200.         if (upper) un = reflect<FloatType>(mirror_point_, un);
  201.         return constrain(lowerb_, upperb_, un); // small fp error mid-way through a cut-off skewed curve
  202.     }
  203.  
  204.     FloatType lowerb() const { return lowerb_; }
  205.     FloatType upperb() const { return upperb_; }
  206.     FloatType start() const { return fn_.flipped() ? upperb_ : lowerb_; }
  207.     FloatType end() const { return fn_.flipped() ? lowerb_ : upperb_; }
  208. };
  209.  
  210. // Eg, base 10, start 30, end 20000 will give nice Hz plot
  211. // For any base B, all powers of B will be equidistant from each other
  212. // in the normalised domain
  213. // Start and end must both be > 0, but end < start is allowed
  214. template <typename FloatType>
  215. struct RangeLog {
  216.     FloatType base_, loge_base_;
  217.     // range_ is to remeber our upper and lower bounds, as recalc
  218.     // from log_range_ gives fp errors that fail assertions
  219.     RangeLin<FloatType> log_range_, range_;
  220.  
  221.     RangeLog(const FloatType base, const FloatType start, const FloatType end) {
  222.         assert(base > 0.0);
  223.         assert(start > 0.0 && end > 0.0);
  224.         base_ = base;
  225.         loge_base_ = std::log(base);
  226.         log_range_ = RangeLin<FloatType>(std::log(start) / loge_base_, std::log(end) / loge_base_);
  227.         range_ = RangeLin<FloatType>(start, end);
  228.     }
  229.     RangeLog(const FloatType start, const FloatType end) : RangeLog(10.0, start, end) {}
  230.  
  231.     FloatType normalise(const FloatType unnorm_val) const {
  232.         // This doesn't assert that unnorm_val is within range, but
  233.         // prevents log of an invalid value. The range_.normalise() will then
  234.         // have a valid range check assertion in the log domain
  235.         assert(0.0 < unnorm_val);
  236.         FloatType logb = std::log(unnorm_val) / loge_base_;
  237.         return log_range_.normalise(logb);
  238.     }
  239.  
  240.     FloatType unnormalise(const FloatType norm_val) const {
  241.         // lin_range_.unnormalise() does the norm_val in range check before the pow()
  242.         const FloatType result = std::pow(base_, log_range_.unnormalise(norm_val));
  243.         // Constrain needed due to FP error
  244.         return constrain(lowerb(), upperb(), result);
  245.     }
  246.  
  247.     FloatType lowerb() const { return range_.lowerb(); }
  248.     FloatType upperb() const { return range_.upperb(); }
  249.     FloatType start() const { return range_.start(); }
  250.     FloatType end() const { return range_.end(); }
  251. };
  252.  
  253. //
  254. //
  255. // SNAPPERS
  256. // Snapping notes:
  257. // - Snappers are expected by juce to constrain their output to legal values!
  258. // - snap_unnorm(unnorm_val) should NOT assert that unnorm_val is in range,
  259. //   because the juce gui code often puts 0.0 through snappers.
  260. // - Snappers interpret all ranges as increasing (but can be used with decreasing ranges,
  261. //   just set the snap as if it is increasing)
  262.  
  263. // Doesn't snap, but still constrains as juce expects it to
  264. template <typename FloatType>
  265. struct SnapperNone {
  266.     FloatType lowerb_, upperb_;
  267.  
  268.     SnapperNone(const FloatType lower_bound, const FloatType upper_bound)
  269.         : lowerb_(lower_bound), upperb_(upper_bound)
  270.     {
  271.         assert(lower_bound < upper_bound);
  272.     }
  273.  
  274.     FloatType snap_unnorm(const FloatType unnorm_val) const {
  275.         return constrain(lowerb_, upperb_, unnorm_val);
  276.     }
  277.  
  278.     FloatType lowerb() const { return lowerb_; } FloatType upperb() const { return upperb_; }
  279. };
  280.  
  281. // eg 3 sig places = 23.2 or 232000
  282. // WARNING: If the lower bound is 0, this will behave strangely!
  283. template <typename FloatType>
  284. struct SnapperSigFigures {
  285.     FloatType lowerb_, upperb_; int sig_places_;
  286.  
  287.     SnapperSigFigures(const FloatType lower_bound, const FloatType upper_bound, const int sig_places)
  288.         : lowerb_(lower_bound), upperb_(upper_bound), sig_places_(sig_places)
  289.     {
  290.         assert(lowerb_ < upperb_);
  291.     }
  292.  
  293.     FloatType snap_unnorm(const FloatType unnorm_val) const {
  294.         FloatType un = unnorm_val;
  295.         FloatType snapped;
  296.         if (un != 0.0) {
  297.             FloatType sign = 1;
  298.             if (un < 0.0) {
  299.                 un = -un;
  300.                 sign = -1.0;
  301.             }
  302.             FloatType lg10 = floor(std::log10(un));
  303.             FloatType scale = std::pow(10, lg10+1.0-static_cast<FloatType>(sig_places_));
  304.             un /= scale;
  305.             un = std::round(un);
  306.             snapped = sign * un * scale;
  307.         } else {
  308.             snapped = un;
  309.         }
  310.         return constrain(lowerb_, upperb_, snapped);
  311.     }
  312.     FloatType lowerb() const { return lowerb_; } FloatType upperb() const { return upperb_; }
  313. };
  314.  
  315. // Same as juce
  316. template <typename FloatType>
  317. struct SnapperInterval {
  318.     FloatType lowerb_, upperb_, snap_;
  319.     // A default is ctor is needed to have an array of these, it invariants intentionally to save processing
  320.     SnapperInterval() : lowerb_(0.0), upperb_(0.0), snap_(0.0) {}
  321.     SnapperInterval(const FloatType lowerb, const FloatType upperb, const FloatType snap)
  322.         : lowerb_(lowerb), upperb_(upperb), snap_(snap)
  323.     {
  324.         assert(lowerb_ < upperb_);
  325.         // we don't care if a snap value is larger than the interval: that just means it always
  326.         // snaps to the lower bound of that interval
  327.         assert(snap >= 0.0);
  328.     }
  329.  
  330.     FloatType snap_unnorm(const FloatType unnorm_val) const {
  331.         FloatType snapped = unnorm_val;
  332.         FloatType unnv_offset = snapped - lowerb_;
  333.         snapped = lowerb_ + std::round(unnv_offset / snap_) * snap_;
  334.         return constrain(lowerb_, upperb_, snapped); // small chance to snap outside interval
  335.     }
  336.     FloatType lowerb() const { return lowerb_; } FloatType upperb() const { return upperb_; }
  337. };
  338.  
  339. // Multiple intervals across different parts of the range.
  340. // It is acceptable to have areas of the range not covered by any interval.
  341. // Intervals may not overlap, but the upperbound of one may equal the lower bound of another.
  342. // They must be passed to the ctor in ascending order for assertions to work.
  343. //
  344. // To construct:
  345. // SnapperIntervals(range.lowerb(), range.upperb(), {{{lb,ub,snap}, {lb,ub,snap}, {...}}})
  346. template <typename FloatType, int NumIntervals>
  347. struct SnapperIntervals {
  348.     FloatType lowerb_, upperb_;
  349.     std::array<SnapperInterval<FloatType>, NumIntervals> snaps_;
  350.  
  351.     SnapperIntervals() { assert(NumIntervals >= 0); }
  352.     SnapperIntervals(const FloatType lower_bound, const FloatType upper_bound, const std::array<SnapperInterval<FloatType>, NumIntervals>& snaps)
  353.         : lowerb_(lower_bound), upperb_(upper_bound), snaps_(snaps)
  354.     {
  355.         assert(NumIntervals >= 0);
  356.         // intervals are not allowed to overlap, and they must increase (to help find errors, we don't care about efficiency here)
  357.         // but they DON'T have to cover the whole range (flexible)
  358.         FloatType lastupperb = lowerb_;
  359.         for (int i = 0; i < NumIntervals; ++i) {
  360.             assert(snaps[i].lowerb() >= lastupperb);
  361.             lastupperb = snaps[i].upperb();
  362.             assert(lastupperb <= upperb_);
  363.         }
  364.     }
  365.  
  366.     FloatType snap_unnorm(const FloatType unnorm_val) const {
  367.         FloatType snapped = unnorm_val;
  368.         for (int i = 0; i < NumIntervals; ++i) {
  369.             // Use of <= and < is intentional to avoid snapping unnorm val in next interval
  370.             if (snaps_[i].lowerb() <= unnorm_val && unnorm_val < snaps_[i].upperb()) {
  371.                 snapped = snaps_[i].snap_unnorm(unnorm_val);
  372.                 break; // for efficiency. Program should behave identically without this line, so acceptable use
  373.             }
  374.         }
  375.         // We still have to constrain here, because there is a chance the unnorm_val was not in
  376.         // any snapping interval (intervals don't have to cover the whole range).
  377.         return constrain(lowerb_, upperb_, snapped);
  378.     }
  379.  
  380.     FloatType lowerb() const { return lowerb_; } FloatType upperb() const { return upperb_; }
  381. };
  382.  
  383. // OUTPUT TO JUCE. The resulting range can be used in juce::AudioParameterFloat (pass float type via constructor)
  384. // or with GUI sliders (pass double type via ::setNormalisableRange(), then use ::setNumDecimalPlacesToDisplay() to set
  385. // sane display digits)
  386. template <typename FloatType, typename RangeType, typename SnapperType>
  387. inline juce::NormalisableRange<FloatType> get_juce_norm_range(const RangeType& range, const SnapperType& snapper) {
  388.     assert(range.lowerb() == snapper.lowerb() && range.upperb() == snapper.upperb());
  389.     return juce::NormalisableRange<FloatType>(range.lowerb(), range.upperb(),
  390.         [range](FloatType start, FloatType end, FloatType norm_val) -> FloatType {
  391.             return static_cast<FloatType>(range.unnormalise(static_cast<double>(norm_val))); },
  392.         [range](FloatType start, FloatType end, FloatType unnorm_val) -> FloatType {
  393.                 return static_cast<FloatType>(range.normalise(static_cast<double>(unnorm_val))); },
  394.                 [snapper](FloatType start, FloatType end, FloatType unnorm_val) -> FloatType {
  395.                     return static_cast<FloatType>(snapper.snap_unnorm(static_cast<double>(unnorm_val))); });
  396. }
  397.  
  398. // Convenience function
  399. template<typename RangeType, typename SnapperType>
  400. inline void get_juce_norm_ranges(const RangeType& range, const SnapperType& snapper,
  401.     juce::NormalisableRange<double>& doubleDest, juce::NormalisableRange<float>& floatDest)
  402. {
  403.     doubleDest = get_juce_norm_range<double>(range, snapper);
  404.     floatDest = get_juce_norm_range<float>(range, snapper);
  405. }
  406.  
  407. } // namespace improved_norm_range
  408.  
  409. // Uncomment for convenience
  410. // using namespace improved_norm_range;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement