Suggestion: Replace skew with lambdas

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.

2 Likes

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 Likes

We also can’t wait to do that!

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
     }
 };
1 Like

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

4 Likes

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.

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

2 Likes

Any updates on this?

2 Likes

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

3 Likes

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

5 Likes

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

Thanks !

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

Fixed.

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.

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

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.

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

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.

1 Like

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?

1 Like

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

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: