1 /* 2 ============================================================================== 3 4 This file is part of the JUCE library. 5 Copyright (c) 2017 - ROLI Ltd. 6 7 JUCE is an open source library subject to commercial or open-source 8 licensing. 9 10 The code included in this file is provided under the terms of the ISC license 11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission 12 To use, copy, modify, and/or distribute this software for any purpose with or 13 without fee is hereby granted provided that the above copyright notice and 14 this permission notice appear in all copies. 15 16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER 17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE 18 DISCLAIMED. 19 20 ============================================================================== 21 */ 22 23 namespace juce 24 { 25 26 //============================================================================== 27 /** 28 Represents a mapping between an arbitrary range of values and a 29 normalised 0->1 range. 30 31 The properties of the mapping also include an optional snapping interval 32 and skew-factor. 33 34 @see Range 35 36 @tags{Core} 37 */ 38 template <typename ValueType> 39 class NormalisableRange 40 { 41 public: 42 /** Creates a continuous range that performs a dummy mapping. */ 43 NormalisableRange() = default; 44 45 NormalisableRange (const NormalisableRange&) = default; 46 NormalisableRange& operator= (const NormalisableRange&) = default; 47 NormalisableRange (NormalisableRange&&) = default; 48 NormalisableRange& operator= (NormalisableRange&&) = default; 49 50 /** Creates a NormalisableRange with a given range, interval and skew factor. */ 51 NormalisableRange (ValueType rangeStart, 52 ValueType rangeEnd, 53 ValueType intervalValue, 54 ValueType skewFactor, 55 bool useSymmetricSkew = false) noexcept start(rangeStart)56 : start (rangeStart), end (rangeEnd), interval (intervalValue), 57 skew (skewFactor), symmetricSkew (useSymmetricSkew) 58 { 59 checkInvariants(); 60 } 61 62 /** Creates a NormalisableRange with a given range, continuous interval, but a dummy skew-factor. */ NormalisableRange(ValueType rangeStart,ValueType rangeEnd)63 NormalisableRange (ValueType rangeStart, 64 ValueType rangeEnd) noexcept 65 : start (rangeStart), end (rangeEnd) 66 { 67 checkInvariants(); 68 } 69 70 /** Creates a NormalisableRange with a given range and interval, but a dummy skew-factor. */ NormalisableRange(ValueType rangeStart,ValueType rangeEnd,ValueType intervalValue)71 NormalisableRange (ValueType rangeStart, 72 ValueType rangeEnd, 73 ValueType intervalValue) noexcept 74 : start (rangeStart), end (rangeEnd), interval (intervalValue) 75 { 76 checkInvariants(); 77 } 78 79 /** Creates a NormalisableRange with a given range, continuous interval, but a dummy skew-factor. */ NormalisableRange(Range<ValueType> range)80 NormalisableRange (Range<ValueType> range) noexcept 81 : NormalisableRange (range.getStart(), range.getEnd()) 82 { 83 } 84 85 /** Creates a NormalisableRange with a given range and interval, but a dummy skew-factor. */ NormalisableRange(Range<ValueType> range,ValueType intervalValue)86 NormalisableRange (Range<ValueType> range, ValueType intervalValue) noexcept 87 : NormalisableRange (range.getStart(), range.getEnd(), intervalValue) 88 { 89 } 90 91 /** A function object which can remap a value in some way based on the start and end of a range. */ 92 using ValueRemapFunction = std::function<ValueType(ValueType rangeStart, 93 ValueType rangeEnd, 94 ValueType valueToRemap)>; 95 96 /** Creates a NormalisableRange with a given range and an injective mapping function. 97 98 @param rangeStart The minimum value in the range. 99 @param rangeEnd The maximum value in the range. 100 @param convertFrom0To1Func A function which uses the current start and end of this NormalisableRange 101 and produces a mapped value from a normalised value. 102 @param convertTo0To1Func A function which uses the current start and end of this NormalisableRange 103 and produces a normalised value from a mapped value. 104 @param snapToLegalValueFunc A function which uses the current start and end of this NormalisableRange 105 to take a mapped value and snap it to the nearest legal value. 106 */ 107 NormalisableRange (ValueType rangeStart, 108 ValueType rangeEnd, 109 ValueRemapFunction convertFrom0To1Func, 110 ValueRemapFunction convertTo0To1Func, 111 ValueRemapFunction snapToLegalValueFunc = {}) noexcept start(rangeStart)112 : start (rangeStart), 113 end (rangeEnd), 114 convertFrom0To1Function (std::move (convertFrom0To1Func)), 115 convertTo0To1Function (std::move (convertTo0To1Func)), 116 snapToLegalValueFunction (std::move (snapToLegalValueFunc)) 117 { 118 checkInvariants(); 119 } 120 121 /** Uses the properties of this mapping to convert a non-normalised value to 122 its 0->1 representation. 123 */ convertTo0to1(ValueType v)124 ValueType convertTo0to1 (ValueType v) const noexcept 125 { 126 if (convertTo0To1Function != nullptr) 127 return clampTo0To1 (convertTo0To1Function (start, end, v)); 128 129 auto proportion = clampTo0To1 ((v - start) / (end - start)); 130 131 if (skew == static_cast<ValueType> (1)) 132 return proportion; 133 134 if (! symmetricSkew) 135 return std::pow (proportion, skew); 136 137 auto distanceFromMiddle = static_cast<ValueType> (2) * proportion - static_cast<ValueType> (1); 138 139 return (static_cast<ValueType> (1) + std::pow (std::abs (distanceFromMiddle), skew) 140 * (distanceFromMiddle < ValueType() ? static_cast<ValueType> (-1) 141 : static_cast<ValueType> (1))) 142 / static_cast<ValueType> (2); 143 } 144 145 /** Uses the properties of this mapping to convert a normalised 0->1 value to 146 its full-range representation. 147 */ convertFrom0to1(ValueType proportion)148 ValueType convertFrom0to1 (ValueType proportion) const noexcept 149 { 150 proportion = clampTo0To1 (proportion); 151 152 if (convertFrom0To1Function != nullptr) 153 return convertFrom0To1Function (start, end, proportion); 154 155 if (! symmetricSkew) 156 { 157 if (skew != static_cast<ValueType> (1) && proportion > ValueType()) 158 proportion = std::exp (std::log (proportion) / skew); 159 160 return start + (end - start) * proportion; 161 } 162 163 auto distanceFromMiddle = static_cast<ValueType> (2) * proportion - static_cast<ValueType> (1); 164 165 if (skew != static_cast<ValueType> (1) && distanceFromMiddle != static_cast<ValueType> (0)) 166 distanceFromMiddle = std::exp (std::log (std::abs (distanceFromMiddle)) / skew) 167 * (distanceFromMiddle < ValueType() ? static_cast<ValueType> (-1) 168 : static_cast<ValueType> (1)); 169 170 return start + (end - start) / static_cast<ValueType> (2) * (static_cast<ValueType> (1) + distanceFromMiddle); 171 } 172 173 /** Takes a non-normalised value and snaps it based on either the interval property of 174 this NormalisableRange or the lambda function supplied to the constructor. 175 */ snapToLegalValue(ValueType v)176 ValueType snapToLegalValue (ValueType v) const noexcept 177 { 178 if (snapToLegalValueFunction != nullptr) 179 return snapToLegalValueFunction (start, end, v); 180 181 if (interval > ValueType()) 182 v = start + interval * std::floor ((v - start) / interval + static_cast<ValueType> (0.5)); 183 184 return (v <= start || end <= start) ? start : (v >= end ? end : v); 185 } 186 187 /** Returns the extent of the normalisable range. */ getRange()188 Range<ValueType> getRange() const noexcept { return { start, end }; } 189 190 /** Given a value which is between the start and end points, this sets the skew 191 such that convertFrom0to1 (0.5) will return this value. 192 193 If you have used lambda functions for convertFrom0to1Func and convertFrom0to1Func in the 194 constructor of this class then the skew value is ignored. 195 196 @param centrePointValue this must be greater than the start of the range and less than the end. 197 */ setSkewForCentre(ValueType centrePointValue)198 void setSkewForCentre (ValueType centrePointValue) noexcept 199 { 200 jassert (centrePointValue > start); 201 jassert (centrePointValue < end); 202 203 symmetricSkew = false; 204 skew = std::log (static_cast<ValueType> (0.5)) / std::log ((centrePointValue - start) / (end - start)); 205 checkInvariants(); 206 } 207 208 /** The minimum value of the non-normalised range. */ 209 ValueType start = 0; 210 211 /** The maximum value of the non-normalised range. */ 212 ValueType end = 1; 213 214 /** The snapping interval that should be used (for a non-normalised value). Use 0 for a 215 continuous range. 216 217 If you have used a lambda function for snapToLegalValueFunction in the constructor of 218 this class then the interval is ignored. 219 */ 220 ValueType interval = 0; 221 222 /** An optional skew factor that alters the way values are distribute across the range. 223 224 The skew factor lets you skew the mapping logarithmically so that larger or smaller 225 values are given a larger proportion of the available space. 226 227 A factor of 1.0 has no skewing effect at all. If the factor is < 1.0, the lower end 228 of the range will fill more of the slider's length; if the factor is > 1.0, the upper 229 end of the range will be expanded. 230 231 If you have used lambda functions for convertFrom0to1Func and convertFrom0to1Func in the 232 constructor of this class then the skew value is ignored. 233 */ 234 ValueType skew = 1; 235 236 /** If true, the skew factor applies from the middle of the slider to each of its ends. */ 237 bool symmetricSkew = false; 238 239 private: checkInvariants()240 void checkInvariants() const 241 { 242 jassert (end > start); 243 jassert (interval >= ValueType()); 244 jassert (skew > ValueType()); 245 } 246 clampTo0To1(ValueType value)247 static ValueType clampTo0To1 (ValueType value) 248 { 249 auto clampedValue = jlimit (static_cast<ValueType> (0), static_cast<ValueType> (1), value); 250 251 // If you hit this assertion then either your normalisation function is not working 252 // correctly or your input is out of the expected bounds. 253 jassert (clampedValue == value); 254 255 return clampedValue; 256 } 257 258 ValueRemapFunction convertFrom0To1Function, convertTo0To1Function, snapToLegalValueFunction; 259 }; 260 261 } // namespace juce 262