Suggestion: Replace skew with lambdas


#1

In the NormalisedRange class the constructor takes a template argument for a skew which is used to essentially create curves, however I think a possibly more flexible alternative would be to take two lambdas. These lambdas could take values between 0 and 1 and return values between 0 and 1, one would map linear values to non-linear values and the the other would linearise the non-linear values. This would allow for curves such as a sigmoid for example.


NormalisableRange suggestions
Parameter ranges
Symmetric Slider Skew
Frequency parameter / slider log scaling
#2

As a frequent user of the skew argument I think this is a cool idea. There’s a TON of JUCE which could be improved through lambdas and other C++11 features, but sadly for whatever reason C++98 compatibility is required. Here’s hoping that JUCE 5 will drop C++98 so we can get neat stuff like this…


#3

We also can’t wait to do that!


Why no NormalisableRange::setSkewFromCenter()?
#4

Although the subject title says ‘replace skew’ I think it be would possible to have the lambdas available as an alternative to the skew, leaving the current skew in there for backwards compatibility and using the JUCE_COMPILER_SUPPORTS_LAMBDAS to prevent any of the code causing compilation issues on C++98.

I haven’t done very thorough testing but the following seems to be working for me…

diff --git a/modules/juce_core/maths/juce_NormalisableRange.h b/modules/juce_core/maths/juce_NormalisableRange.h
index 81fbb8c..063ddab 100644
--- a/modules/juce_core/maths/juce_NormalisableRange.h
+++ b/modules/juce_core/maths/juce_NormalisableRange.h
@@ -45,12 +45,28 @@ class NormalisableRange
 {
 public:
     /** Creates a continuous range that performs a dummy mapping. */
-    NormalisableRange() noexcept  : start(), end (1), interval(), skew (static_cast<ValueType> (1)) {}
+    NormalisableRange() noexcept
+        : start()
+        , end (1)
+        , interval()
+        , skew (static_cast<ValueType> (1))
+#if JUCE_COMPILER_SUPPORTS_LAMBDAS
+        , linearise (nullptr)
+        , nonLinearise (nullptr)
+#endif
+    {
+    }
 
     /** Creates a copy of another range. */
     NormalisableRange (const NormalisableRange& other) noexcept
-        : start (other.start), end (other.end),
-          interval (other.interval), skew (other.skew)
+        : start (other.start)
+        , end (other.end)
+        , interval (other.interval)
+        , skew (other.skew)
+#if JUCE_COMPILER_SUPPORTS_LAMBDAS
+        , linearise (nullptr)
+        , nonLinearise (nullptr)
+#endif    
     {
         checkInvariants();
     }
@@ -62,6 +78,10 @@ public:
         end = other.end;
         interval = other.interval;
         skew = other.skew;
+#if JUCE_COMPILER_SUPPORTS_LAMBDAS
+        linearise = other.linearise;
+        nonLinearise = other.nonLinearise;
+#endif
         checkInvariants();
         return *this;
     }
@@ -71,8 +91,14 @@ public:
                        ValueType rangeEnd,
                        ValueType intervalValue,
                        ValueType skewFactor) noexcept
-        : start (rangeStart), end (rangeEnd),
-          interval (intervalValue), skew (skewFactor)
+        : start (rangeStart)
+        , end (rangeEnd)
+        , interval (intervalValue)
+        , skew (skewFactor)
+#if JUCE_COMPILER_SUPPORTS_LAMBDAS
+        , linearise (nullptr)
+        , nonLinearise (nullptr)
+#endif
     {
         checkInvariants();
     }
@@ -81,8 +107,14 @@ public:
     NormalisableRange (ValueType rangeStart,
                        ValueType rangeEnd,
                        ValueType intervalValue) noexcept
-        : start (rangeStart), end (rangeEnd),
-          interval (intervalValue), skew (static_cast<ValueType> (1))
+        : start (rangeStart)
+        , end (rangeEnd)
+        , interval (intervalValue)
+        , skew (static_cast<ValueType> (1))
+#if JUCE_COMPILER_SUPPORTS_LAMBDAS
+        , linearise (nullptr)
+        , nonLinearise (nullptr)
+#endif
     {
         checkInvariants();
     }
@@ -90,12 +122,52 @@ public:
     /** Creates a NormalisableRange with a given range, continuous interval, but a dummy skew-factor. */
     NormalisableRange (ValueType rangeStart,
                        ValueType rangeEnd) noexcept
-        : start (rangeStart), end (rangeEnd),
-          interval(), skew (static_cast<ValueType> (1))
+        : start (rangeStart)
+        , end (rangeEnd)
+        , interval()
+        , skew (static_cast<ValueType> (1))
+#if JUCE_COMPILER_SUPPORTS_LAMBDAS
+        , linearise (nullptr)
+        , nonLinearise (nullptr)
+#endif
     {
         checkInvariants();
     }
-
+    
+#if JUCE_COMPILER_SUPPORTS_LAMBDAS
+    
+    /** Creates a NormalisableRange with a given range, continuous interval, but a dummy skew-factor. */
+    NormalisableRange (ValueType rangeStart,
+                       ValueType rangeEnd,
+                       ValueType interval,
+                       std::function<ValueType (ValueType)> lineariseFunction,
+                       std::function<ValueType (ValueType)> nonLineariseFunction) noexcept
+        : start (rangeStart)
+        , end (rangeEnd)
+        , interval()
+        , skew (static_cast<ValueType> (1))
+        , linearise (lineariseFunction)
+        , nonLinearise (nonLineariseFunction)
+    {
+        checkInvariants();
+    }
+    
+    NormalisableRange (ValueType rangeStart,
+                       ValueType rangeEnd,
+                       std::function<ValueType (ValueType)> lineariseFunction,
+                       std::function<ValueType (ValueType)> nonLineariseFunction) noexcept
+        : start (rangeStart)
+        , end (rangeEnd)
+        , interval()
+        , skew (static_cast<ValueType> (1))
+        , linearise (lineariseFunction)
+        , nonLinearise (nonLineariseFunction)
+    {
+        checkInvariants();
+    }
+    
+#endif
+    
     /** Uses the properties of this mapping to convert a non-normalised value to
         its 0->1 representation.
     */
@@ -105,6 +177,11 @@ public:
 
         if (skew != static_cast<ValueType> (1))
             proportion = std::pow (proportion, skew);
+            
+#if JUCE_COMPILER_SUPPORTS_LAMBDAS
+        else if (linearise)
+            proportion = linearise (proportion);
+#endif
 
         return proportion;
     }
@@ -116,6 +193,11 @@ public:
     {
         if (skew != static_cast<ValueType> (1) && proportion > ValueType())
             proportion = std::exp (std::log (proportion) / skew);
+            
+#if JUCE_COMPILER_SUPPORTS_LAMBDAS
+        else if (nonLinearise)
+            proportion = nonLinearise (proportion);
+#endif
 
         return start + (end - start) * proportion;
     }
@@ -159,11 +241,18 @@ public:
     ValueType skew;
 
 private:
+    std::function<ValueType (ValueType)> linearise;
+    
+    std::function<ValueType (ValueType)> nonLinearise;
+    
     void checkInvariants() const
     {
         jassert (end > start);
         jassert (interval >= ValueType());
         jassert (skew > ValueType());
+#if JUCE_COMPILER_SUPPORTS_LAMBDAS
+        jassert ((linearise == nullptr) == (nonLinearise == nullptr));
+#endif
     }
 };

#5

Lambdas are cool, backwards compatibility is required! We can have both!
Just make it optional!


#6

Yes sorry my poorly named subject would seem to suggest that I’m for replacing the skew but actually if you look at my suggestion above I’ve implemented the lambdas along-side the skew and kept backwards compatibility using the JUCE_COMPILER_SUPPORTS_LAMBDAS conditional.


#7

Good suggestions. We are looking into this currently. Just needs some testing and cleanup.


#8

Any updates on this?


#9

This would be a great addition.
A log frequency parameter cannot be done currently using juce_NormalisableRange


#10

This is now available on the develop branch! https://github.com/julianstorer/JUCE/commit/8a680bc4f663cafeb431185bdeb115c9cb3232f8


#11

@t0m There is bug, you will need to fix the copy ctor and assignement

Thanks !


#12

Ah, that’s rather embarrassing… Thank you for spotting!

Fixed.


#13

Now it doesn’t compile if JUCE_COMPILER_SUPPORTS_LAMBDAS isn’t set because the members that were missing before do not exist in that case. It needs two more #if JUCE_COMPILER_SUPPORTS_LAMBDAS sections around the code you added in the latest fix.


#14

You’re right, of course. The lesson there is not to rush fixes out for silly mistakes :anguished:


#15

On a related note - we’re currently working on a replacement for std::function that will be compatible with older versions of OS X, so we should be able to remove those precompiler instructions soon.


#16

As long as it still work with lambdas, whatever fits you guys :slight_smile:


#17

We’re in the unusual situation where lambda functions are supported on all platforms, but not std::function. Lambda functions are a compiler feature, and you can use a modern compiler to target an older platform, whereas std::function lives in the C++ library, which is incompatible with OS X 10.6 and earlier. VS2013 supports both.


#18

Thanks for the fix. Not sure it’s worth your time to replace std::function though. Even I will soon stop supporting 10.6.8. I guess when Juce 5 will be here it’ll be time to get rid of this outdated stuff. Or do you need it for linux support as well?


#19

We only support >= 10.7 at UVI but there are still some users on 10.6.8, the best version ever as they say.


#20

Maybe because apple made a cut for certain hardware, that just won’t get OSX updates. I have a perfectly fine MacBook from 2006 running (sure, as a spare, but it works except WiFi), but latest OSX is 10.6.8…
So yes, 10.6.8 is the perfect version for this machine :wink: