ADSR - Linear vs. Exponential

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

mistertoast wrote:Speaking of volume. I want to know what you guys do about polyphony that's changing on the fly. Suppose somebody bangs out a big 8-note chord on two hands, then leaves one held down and releases the other 7.

Do you have some sort of built-in compression to handle this?
In Meridian, the last thing before the effects and master volume is a waveshaper which soft clips at +/- 1.0. There's a gain knob before that, so the user can get variable compression at the cost of some distortion.
Image
Don't do it my way.

Post

I'm a profoundly untalented mathematician, but here is a minor tweak that may be useful in some cases.

Leslie's original formula to find the envelope multiplier:

x = e^(-1/d)

There's nothing wrong with that, as far as I know, but the following two equivalent constructions will approximate the answer except for very short time constants, and will probably run a miniscule amount quicker:

x = 1 - 1 / d

OR

x = (d - 1) / d

The time savings may be so minor as to be silly, unless you might be doing something unusual which wants to frequently modify the time constant during playback.

I ran some quick comparisons on the palmpilot math program "Power One Graph", to demo the limits of the approximation:

Code: Select all

d = SampleRate * TimeConstantInSeconds 
//using 44.1K in this simple test

TargetTC     x = e^(-1 / d)     x = (d - 1) / d    The "real" approximation TC, back-solved by the first formula

500 ms       0.999954649554     0.999954648526     499.99 ms
100 ms       0.999773268338     0.99977324263      99.99  ms
20  ms       0.998866855645     0.998866213152     19.99  ms
10  ms       0.997734995307     0.997732426304     9.99   ms
5   ms       0.99547512086      0.995464852608     4.99   ms
1   ms       0.977579425250     0.977324263039     0.99   ms
0.25 ms      0.913288965063     0.909297052154     0.24   ms
The 'back solved' time constants given by plugging the approximate 'x' into the original exponential equation-- I rounded the answers to 2 decimal places.

The PowerOneGraph solver function uses successive approximation rather than smart math gymnastics if asked to solve something 'not completely obvious' like calculating D out of the exponential equation, given X.

There is slight math rounding in the back-solve. If I plug-in the x's from the first row and back solve, I also get numbers like 499.99....., except after a few decimal places the first row x's are closer to the target than the second-row approximation x's.

If you use the approximation for very small TC's, only a few samples, the error difference gets really big, though. But all-in-all, that 0.25 ms example is only a d of 11, and it's not all that out-of-whack. Close-enough-fer-rock-n-roll in many cases.

=====

I'm such a feeb on math. I look at that term (d - 1) / d, and it naggingly seems that there ought to be some way to reconfigure it to use a multiply rather than a divide, but I can't think of a way. Probably isn't a way. Maybe the only alternatives would be some elaborate bit-twiddling that would burn more cpu cycles than a straightforward divide. Anyway, a divide should run faster than the exponential.

Post

(d - 1) / d

is the same as

1-1/d

What is d, exactly? Is it a constant? If it is, you can just keep 1/d around rather than d.

Post

Hi Mistertoast

Yes, both of those are the same. I just mentioned both because there may be algorithmic advantages to one or the other, in asm or whatever. Also, if one were doing fixed-point math with not many bits, perhaps the (d - 1) / d would be more accurate, but I haven't thought about it very much.

What is D?
I was just using Leslie's original message's expression of the formula, to be familiarly talking about the same thing rather than inventing new terminology.

To get an envelope time constant multiplier, using more descriptive names, perhaps simple code like this:

Code: Select all

TCSampSpan = SampRate * TimeConstInMillisecs * 0.001;
TCMult = pow(2.718281828459, -1.0 / TCSampSpan);
And then the approximation equivalent, which probably runs marginally faster, would be:

Code: Select all

TCSampSpan = SampRate * TimeConstInMillisecs * 0.001;
TCMult = (TCSampSpan - 1.0) / TCSampSpan;

--OR--

TCMult = 1.0 - (1.0 / TCSampSpan);
Is it a constant?

Unfortunately no. It is only constant until you decide you need to change the envelope time constants (GRIN). Back in the cheezy 1960's, math nerds had dumb jokes about 'variable constants'... I wasn't a math nerd, but unfortunately got to hear the jokes anyway...

Post

AUTO-ADMIN: Non-MP3, WAV, OGG, SoundCloud, YouTube, Vimeo, Twitter and Facebook links in this post have been protected automatically. Once the member reaches 5 posts the links will function as normal.
Hi there, so I saw this thread yesterday and am blown away by the valuable info here. One thing that really bothered me though is that nobody went and resolved the final issue with using a recurrence relation to generate the envelope:

Code: Select all (#)

 y(n) = y(n - 1) + k * ((1 / 0.67) - y(n - 1)) 
How do we set k when we know that we want to be at 1.0 in T samples? In other words, how can we map the attack-time to a beta value for the logarithmic growth?

Well, I did a bunch of re-reading of my diff-eq's text books, and it turns out that we have to take the definite integral from y(0) to y(T) and solve for k. Just to spare you the work, the formula is:

Code: Select all (#)

 k(T) = -ln( ((1.0/0.67) - 1.0) / (1.0/0.67) ) / T 
where T is the number of samples you want to hit the value 1.0 at.
Please note that this will shoot you slightly past 1.0 in some cases. Perhaps you had better use T - 1, as the last sample.

For example, if you want your attack to be 1 second (44,000 samples), then k = approx. 0.000025980315. You should probably be doing the math in double-precision, because a very small rounding factor appears to make a big difference.

anyhow, thanks for the help! I hope my extra bit of work helps somebody!
_Mark

Post

probably way out of my depth here by far a maths head but from what I gather the reason linear decay sounds odd is because we don't hear changes in volume in a linear fashion we hear it in an exponential curve. So it will sound uneven if it is linear.

Post

sound_buoy wrote:probably way out of my depth here by far a maths head but from what I gather the reason linear decay sounds odd is because we don't hear changes in volume in a linear fashion we hear it in an exponential curve. So it will sound uneven if it is linear.
Yeah, you're right. Humans hear power logarithmically, not linearly. If you consider that your envelope is acting on DB, then looking at amplitude you'll find that the envelope has logarithmic segments. Using the recursive method, where you add a value based on a coefficient times the distance between where you are now and where you want to be is just a cheap way of getting the exponential curve cpu-wise.
_Mark

Post

AUTO-ADMIN: Non-MP3, WAV, OGG, SoundCloud, YouTube, Vimeo, Twitter and Facebook links in this post have been protected automatically. Once the member reaches 5 posts the links will function as normal.
Pauley Unsaturated wrote: Well, I did a bunch of re-reading of my diff-eq's text books, and it turns out that we have to take the definite integral from y(0) to y(T) and solve for k. Just to spare you the work, the formula is:

Code: Select all (#)

 k(T) = -ln( ((1.0/0.67) - 1.0) / (1.0/0.67) ) / T 
Hi,

It would be nice if you could elaborate a bit more about your way of solving the difference equation, as I would be very interested in the theoretical background.

On the other hand I noticed, that your solution is just an elaborate construction for:

Code: Select all (#)

 k(T) = 1 / T 
As the factor 0.63 (as it was mentioned in other posts) is nothing more than 1.0 - 1 / e, the logarithm reduces to:

Code: Select all (#)

 k(T) = - ln (1 / e) / T = 1 / T. 
As much as I love the simplicity of this answer, I would really like to understand the theoretical background...

Post

From my ignorant point of view, is there a reason to don't do it with the simple exponential formula instead?

Code: Select all

y = C * (B ^ (K*x))
the cost is only one multiplication and one substract for the offset.

Post

mystran wrote: I might also add that I like to have my envelopes go from 1 to 2, and then substract 1 to get the real value. Nicely avoids denormalization problems near zero. YMMV :)
Watch out here, this will only prevent your output from being a de-normal number, which is useful. The envelope will still run slow since (input - value) will de-normal, then your multiply will be slow.
The Glue, The Drop - www.cytomic.com

Post

andy-cytomic wrote:
mystran wrote: I might also add that I like to have my envelopes go from 1 to 2, and then substract 1 to get the real value. Nicely avoids denormalization problems near zero. YMMV :)
Watch out here, this will only prevent your output from being a de-normal number, which is useful. The envelope will still run slow since (input - value) will de-normal, then your multiply will be slow.
I don't think I do this anywhere anymore anyway. These days I just let hardware flush to zero... plus I tend to do this stuff in double precision so the time constants would have to be pretty long for precision to really become an issue.

Post

andy-cytomic wrote:
mystran wrote: I might also add that I like to have my envelopes go from 1 to 2, and then substract 1 to get the real value. Nicely avoids denormalization problems near zero. YMMV :)
Watch out here, this will only prevent your output from being a de-normal number, which is useful. The envelope will still run slow since (input - value) will de-normal, then your multiply will be slow.
I don't think so. To my best knowledge there is no way to get a denormal from subtracting two numbers both of which are >= 1. An associated fact is that there is quite some precision loss when subtracting two close numbers. For that reason working closer to zero than [1,2] could be a better option (as the numbers would be relatively farther apart for the same absolute difference). However, as mystran pointed out, nowadays that's usually academic.

Post

Just a bonus comment on this interesting topic:

* Consider which instrument the ADSR is supposed to be similar to.
* Then make a clean recording of this instrument, e.g. a piano tone, hihat, kick drum, conga, violin, whatever. (Make sure not to use compression, tube saturation or anything. It's gotta be clean. Don't trust sample CDs because they're often very processed.)
* Take a look at the way the attack and decay looks.

Percussion typically has very very exponential fade-out curves. Most synthesizers I use are too close to linear, which sounds unnatural to me. (Oberheim SEM and Arp Odyssey are quite nice though.)

Post

Z1202 wrote:I don't think so. To my best knowledge there is no way to get a denormal from subtracting two numbers both of which are >= 1. An associated fact is that there is quite some precision loss when subtracting two close numbers. For that reason working closer to zero than [1,2] could be a better option (as the numbers would be relatively farther apart for the same absolute difference). However, as mystran pointed out, nowadays that's usually academic.
Yep, just tested this out of a variety of cutoffs it doesn't de-normal, it just gets stuck quite a few bits away from reaching the end, so as this is taken into account when testing for thresholds then all good.
The Glue, The Drop - www.cytomic.com

Post

I made an ADSR enveloppe which I use for all my effects, it goes from linear to exponetial, and it's invertible. Used the lowpass filter method (low computational cost, as mentioned earlier in this thread).
ADSR.png
Since the exponential never reaches it's destination (converges towards it), you can use an aiming point higher than the actual destination. You can calculate exactly which point.

N=total number of steps
y=total distance to cover
z=aiming point
a=fraction of remaining distance to cover in each step

Then there's the equation for "z":
z-z(1-a)^N=y
=> z=y/(1-(1-a)^N)

Now it's just the matter of choosing "a". You can choose it fixed or use user input to control the steepness of the curve. It's a bit long to explain but if someone is interested I can type it out.
You do not have the required permissions to view the files attached to this post.

Post Reply

Return to “DSP and Plugin Development”