Looking for a function for envelope line segments

Hi,
I want to create an envelope editor. I need a function that lets me easily change a line segment between linear and exponential using one parameter. And the function should be simple so that finding points is fast.
Any idea what function is often used for this?

Check out Geraint’s excellent blog:

There is also a header only C++ example which worked for me perfectly.

2 Likes

Very interesting and usable for other interface components. But for the envelope editor I do not need smooth curves. Just a function that lets me connect each two dots with a line ranging from linear to exponential (two forms) by changing one parameter.

For envelope type stuff you want exponential and logarithmic curving.

Here’s a Desmos graph showing the formula:

Alter the ‘s’ slider to get different curve amounts in either direction.

Code wise:

template <typename T>
int sgn(T val)
{
    return (T(0) < val) - (val < T(0));
}

// Get exp curve Y value given the X [-1..1] and curvature [-1..1]
// MaxTension allows more or less corner pinching in the sigmoid
float getExpCurveY(float curvature, float x, float maxTension = 16.f)
{
    if (std::abs(curvature) < 1e-5)
        return x;

    float s = curvature * maxTension;

    return ((1.f - std::exp(std::abs(x) * s)) / (1.f - std::exp(s))) * sgn(x);
}

Because of the divide, there needs to be a special handling when curvature gets near zero.

This gives you the full sigmoid but you generally just use x [0, 1] - this will be the relative position in your envelope segment. ‘curvature’ is which way it curves and ‘maxTension’ allows you to adjust it to get more pinching in the corners if you like.

You may also want to use faster versions of std::exp. I’ve used the std ones here for brevity.

Neat. I’ve modelled your skew in my Desmos chart: Exp curve | Desmos

It’s the ‘b’ slider.

It doesn’t fit the log/exp curve (templated in the blue background images) which is generally what you’d classically want for envelopes. It’s going to be faster to calculate though.

But this may or may not matter…YMMV. :slight_smile:

Thanks. I will go for the exp() function. Maybe there are some tricks to make a fast exp() function that is accurate enough. For envelopes I think it does not need to be very precise.

Voila! Save you some time…

// Fast exp()
inline float fast_exp(float p)
{
    union
    {
        float f;
        int32_t i;
    } reinterpreter;

    reinterpreter.i = (int32_t)(12102203.0f * p) + 127 * (1 << 23);
    int32_t m = (reinterpreter.i >> 7) & 0xFFFF; // Copy mantissa
    // Empirical values for small maximum relative error (1.21e-5):
    reinterpreter.i += (((((((((((3537 * m) >> 16) + 13668) * m) >> 18) + 15817) * m) >> 14) - 80470) * m) >> 11);
    return reinterpreter.f;
}

Magical bit tweaking, but it’s reasonably accurate and extremely fast. It’s a great fit for envelope curves etc. Replace the std::exp calls in the snippet I gave and you’re good to go.

2 Likes

Thanks!
It looks very very tricky. Does it work for all platforms? It does something strange with the floating point format. Is there some explanation available? I normally do not use stuff that I do not understand. Especially such tricky things :wink:

It works for IEEE 754 32 bit floating point numbers. On pretty much all platforms you’d write a plugin for you should be fine.

Without going into too much detail here about how it works, the gist of it is that it applies a calculation to the exponent of the float in the first line which gets it most of the way towards an answer, then refines it using a polynomial approximation (minmax).

Another option would be to use a pre-calculated JUCE look up table. A few thousand entries and linear interpolation will get you in the ball park, but you’ll exchange cpu cycles for memory pressure (caching etc).

1 Like