Parameter Ranges With X-Fade

Hello JUCE users,

So I just started using JUCE like a week ago and I finally noticed that all plugin parameters have to eventually be within the range of 0-1. Then I started wondering how I would accomplish this, then remembered that I did this exact thing in my work in Reaktor. What i would do in Reaktor is I would set the actual knob to have a range from 0-1 (or 0-Max then muliply it by a fraction to get it to the range 0-1). Then I would use a crossfader to set the range that the knob would eventually output. So the actual position of the knob would change the position of the crossfader, and the two inputs (in1, in2) would be the min and max values that the crossfader would output.

So I took the same crossfader algorithm from Reaktor and implemented it in C++. I thought I would share it with y'all in case there's any noobs like me. :) Hey, maybe this can help the more experience users that just haven't thought of crossfading. This is for doubles, so you can just change it as needed. I also use a function that clips the X-Fade's position value, so you can't go out of the range 0-1. I'll give you the code for that too. Hope this helps someone.

Some of you may not like the argument names I gave to the crossfader (in0, in1), but you could change them to something that makes more sense to you. I use them because in Reaktor we do something similar, and it more closely describes how the xFadeCtrl is affecting the output. You know, if xFadeCtrl = 0 then it returns in0. Just makes more sense to me that way.


Crossfader:

/**
    Crossfades linearly between two values (in0, in1). The value returned is  
    determined by the value of the xFadeCtrl parameter. 
    xFadeCtrl Range: 0->1 
    - xFadeCtrl = 0    (only in0 comes through) 
    - xFadeCtrl = 0.5  (equal mix of in0 and in1) 
    - xfadeCtrl = 1    (only in1 comes through) 
*/ 
double xFadeLin(double xFadeCtrl, double in0, double in1) 
{ 
    // Clip the xFadeCtrl parameter to only have range of 0->1 
    xFadeCtrl = clipMinMax(xFadeCtrl, 0.0, 1.0); 
    // Perform crossfading and return the value 
    return (in0 * (1.0 - xFadeCtrl) + in1 * xFadeCtrl); 
}

Min/Max Clipper:

/**
    Takes a value as input and clips it according to the minimum and maximum values 
    Returns the input if (minValue <= in <= maxValue).  
    If (in < minValue), then return minValue. 
    If (in > maxValue), then return maxValue. 
*/ 
double clipMinMax(double in, double minValue, double maxValue) 
{ 
    if (in < minValue) 
        return minValue; 
    else if (in > maxValue) 
        return maxValue; 
    else 
        return in; 
}

Note:

I think you could use this same function for crossfading audio, but I would recommend a parabolic (or sine) crossfader instead of the linear crossfader shown here. If anyone's interested, I'd be happy to make them a parabolic or sine crossfader.

 

Since I'm discussing this, maybe y'all can help me to figure out where I shoud actually be doing this range conversion to make everything work right. I'm still not totally familiar with how JUCE and the host treats parameters.

Here's a handy Parabolic control shaper to go with it. You can use it to "bend" the controller curve torward the X or Y axis. It can be really useful if you need extra precision in like the lower or higher values. It pretty much changes from exponential-like (bend = -1), to linear (bend = 0), to logarithmic-like (bend = 1). These should be some useful tools for converting/shaping parameters. You could use them for audio too though. For instance, you could run a triangle oscillator through the control shaper and morph between a triangle (bend = 0) and a sine wave (bend = 1).

Parabolic Control Shaper:


/** 
  Parabolic Controller Shaper: 
  "Bends" the controller curve torwards the X or Y axis. 
  input range (-1..0..1) maps to output range: (-1..0..1). 
  bend range: (-1..0..1) 
  bend = -1 (max bend towards X axis) 
  bend = 0 (don't bend) 
  bend = 1 (max bend towards Y axis) 
*/ 
double parCtrlShaper(double input, double bend) 
{ 
    // clip input and bend because the shaper only works in that range.
    input = clipMinMax(input, -1.0, 1.0); 
    bend = clipMinMax(bend, -1.0, 1.0); 
    return input * ((bend + 1) - abs(input) * bend); 
}

Note:

You could take out the clipMinMax function from this, but I used it here so it doesn't use/make any invalid values. It only works with ranges of -1 to 1. You could use the crossfader with it to use it with any valus you want though (first going through the control shaper, then using that to control crossfader's position).

I think that we should make a forum post for posting useful DSP/audio utilities like these. What do you guys think? I have a couple more I'm willing to share, and I'll share any that I'll make in the future.

Instead of clipMinMax you could use jlimit in juce_MathFunctions.h ( https://github.com/julianstorer/JUCE/blob/master/modules/juce_core/maths/juce_MathsFunctions.h#L220 ).

Sweet! Thanks for that. I'm glad to know that there are things like that in JUCE. I still have so much to explore. I'll have to look at the other "utilities" JUCE has. 

In the fade formula, you can save one multiplication:


in0 * (1.0 - xFadeCtrl) + in1 * xFadeCtrl   =>   in0 + xFadeCtrl*(in1 - in0)
 

 

Nice! Did you come up with that?

It is just basic algebra ;-)

The same rule often applies to recursive filters:

y = b*x + (1-b)*y    =>   y = b*x + y -b*y    =>   y = y + b*(x-y) 

Ahh. Do you know what rules or whatever this uses? I'm can't seem to figure it out in my head, but I'm very interested. I'm really good at math, but I have to do it frequently. 

I might as well throw this one out there too. Notice all of these are extremely simple, yet very useful. I'm starting to make a collection of these useful "utilities" and I'll try to share them with everyone in case anyone can find some use out of them. Hopefully some other people could contribute too. I'll probably make a post when I have a few more to share. I thought this one would be a good one to add to this post though since it's closely related to the initial post.

This normalizeRange() function is used to take a range of values and convert them to a range of 0->1.

Normalize Range:

Edit: before I was trying to check if min was 0, but as pointed out below I definitely shouldn't be doing that. All I did was take out the conditionals. Also, as Jules said, check out the NormalisableRange class in JUCE. Also note that I changed the names to be more sensable (min to start, max to end). I did this because (start > end) is valid and (min > max) wouldn't make much sense.

 


/**
    Normalizes a range of values to the range 0->1.
    (start/end should probably be the range of a parameter) 
    - input: the value to be normalized 
    - start: the start of the input's range 
    - end: the end of the input's range 
    Note: (start < end) and (start > end) are both valid. 
*/ 
double normalizeRange(double input, double start, double end) 
{ 
    return (input - start) / (end - start); 
}

 

 

You might want to look at the juce NormalisableRange class.

Also, testing for zero is probably going to make your code slower. The subtractions would be pipelined very efficiently on most modern CPUs, and adding a branch operation will most likely just make things worse.

Learn the 2 rules of optimisation! http://c2.com/cgi/wiki?RulesOfOptimization

And testing floating point equality should be avoided most of the time ( https://isocpp.org/wiki/faq/newbie#floating-point-arith ).

Thank y'all very much for pointing that out. I still have a lot to learn about C++ and JUCE. I really appreciate any advice/help anyone can give me. I was thinking that I probably shouldn't test against 0, but I wasn't really sure. I'll have to do a google search before I start trying to optimize like that. I've been trying to teach myself C++ for the past few months and I've learned how to get things working and how to get ideas out, but I still need some work. Luckily, I start my C++ classes next semester. :) it is awesome being able to apply what I do know with JUCE in a fun way though. I gotta say, you did a great job at making it user friendly. 

Hey, thanks for pointing out NormalisableRange! I'll definitely be checking that out.