Adjustable Linear/Exponential envelope curve

Hi,

I am in need of an envelope curve with 3 parameters: start, end, and curve amount. So for a linear curve it looks like this:
(s = start, t = time)


But I would like a parameter to vary the curvature as you go from start to end.

The best example I can think of this is the “power” setting in vital. Simply clicking and dragging allows you to alter the response of the curve.
image
image
Does this type of curve have a name? Is it easily calculable?
Any help would be greatly appreciated.

Vital is open source and some digging around in the source code reveals it uses this code to calculate the curve: https://github.com/mtytel/vital/blob/636ca0ef517a4db087a6a08a6a8a5e704e21f836/src/synthesis/framework/futils.h#L360-L369

Essentially (exp(power * x) - 1) / (exp(power) - 1). The denominator makes sure the curve is always between 0 and 1. To make it go between 5 and 10 like you want, you’d simply add a scaling factor.

1 Like

Amazing! I forgot vital was open source :slight_smile: . Need to do more digging around in these open source projects. Thanks.

I use an algorithm that is used for image processing and that I found more interesting to use in envelopes and many other things. This does not use exponential functions. I have created the function with the same format as juce::map

template <typename Type>
static Type curveMap(Type sourceValue, Type sourceRangeMin, Type sourceRangeMax, Type targetRangeMin, Type targetRangeMax, Type bend)
{
    jassert (sourceRangeMax != sourceRangeMin); // mapping from a range of zero will produce NaN!
    bend = juce::jlimit((Type)0.000001, (Type)0.999999, bend);
    sourceValue = juce::jlimit(sourceRangeMin, sourceRangeMax, sourceValue);
    Type value0to1 = (sourceValue - sourceRangeMin) / (sourceRangeMax - sourceRangeMin);
    return targetRangeMin + ((bend - (Type)1.0) * value0to1) / (((Type)2.0 * bend - (Type)1.0) * value0to1 - bend) * (targetRangeMax - targetRangeMin);
}

This accepts bend values from 0 to 1, with 0.5 as a straight line.

The difference with respect to exponential functions is that it allows pronounced attacks or decays while maintaining some volume in the rest of the curve. Here you can see the comparison, in blue curveMap, in red pow function. Even if the curve is steeper, its usefulness is maintained throughout its duration, while the exponential curve quickly reaches a value close to 0, leaving 2/3 unused.

(Maybe it is possible to obtain the same result with exponential or logarithmic functions, but I don’t know them)

1 Like

(Maybe it is possible to obtain the same result with exponential or logarithmic functions, but I don’t know them)

Here’s a Desmos link showing what I settled on, at least for MSEG/envelope curves:

The ‘s’ variable alters the curve and the ‘t’ variable applies a tension to the curve so that you can get the tighter corner fits.

Code, sans my optimised math functions:

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

    float s = curvature * maxTension;

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

Note you have to handle the case where curvature is 0 (‘s’ in the Desmos graph).

I think there is a formula to do this kind of stuff in the great Nigel Redmon’s tutorials:

https://www.earlevel.com/main/2013/06/23/envelope-generators-adsr-widget/
https://www.earlevel.com/main/2013/06/03/envelope-generators-adsr-code/