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