Does this connect to the Web Audio API?
No…just a widget to see the ADSR in action…
This is really great, but it would be better if you could see the values that the sliders correspond to. That way you can get a feel for the ranges of useful targetRatios.
Hi Tom—You’d be surprised how little help it would be. If you want it linear, then pretty much any large value will do (something like 15 will look pretty straight here—go ahead and pick 200). If you want an exponential segment, you’ll have to switch to log view to have a clue as to how it will sound. For the Decay and Release segments, the smaller the value, the more exponential—0.0001 decays to -80 dB, so you’re probably not going to hear the difference with anything smaller. See the ADSR Part 2 article for more details (“More math”).
So, that leaves the Attack Curve adjustment. It’s really unlikely you’d want anything close to exponential shape for the Attack segment—the range I’m giving it here are for illustrative purposes, mainly to show how wrong it would be to use that shape. Most likely, you would either want it to be linear, or simulate classic analog ADSRs by targeting something around 1.3 as the asymptote of the curve. We don’t care about dB here, just the ratio of the overshoot to the envelope peak. The peak is always 1.0, so the ratio is always just the amount over 1.0, in this case 0.3. For instance, in a 10v peak ADSR running on a 15v supply, that corresponds to a 13v target (asymptote), a typical design choice. So, in practical applications, the ratio will be either 0.3 or something pretty close to that, or a large number (200).
I concede that the values would be a little useful for decay when switched to log display. You can see that on the log display, the decay appears pretty linear, then drops off abruptly at a certain dB down. But the controls are pretty rough, and it’s easy enough to calculate. If you want it to drop off abruptly at -40 dB, then 10 raised to the power of -40/20, which equals 0.01 (0.001 for -60 dB, 0.0001 for -80 dB…).
An aside: Just like the attack target, the decay target is the asymptote of the curve on the other side of the target. So in a real world analog ADSR, it’s not practical to shoot for the precision we have with the -80 dB setting (or at least that would be the far reaches of practical analog). The ADSR might be biased slightly negative to avoid the VCA leaking (the VCA might be biased too for the same reason). But even if everything is designed with precision, -80 dB might be a little much to ask, since it would require no more than 0.001v (a millivolt) offset for a 10v peak envelope, better precision than the analog components have without precision trimming.
As far as going for even more precision (because we can, in software), it makes little sense. For instance, you could shoot for -120 dB on the curve for a more perfect exponential, but it would just sound like the envelope time is shortened (because we set the rate by number of samples—setting 4 seconds worth of decay to -80 dB will sound a lot longer than 4 seconds to -120 dB, since we’re not going to hear much more when it gets below -80 dB). So, I figure something in the -60 to -80 dB range is going to simulate an analog ADSR pretty well. And again, a big number if you want linear, and probably little use for anything in between.
So, that’s the long story of why the slider numbers aren’t very useful. In the case of either attack or decay/release, there a relatively small range of useful values that make sense for a curved segment, plus a single large number suitable for simulating linear.
Thanks for your extensive reply! That actually cleared up quite a few points for me. I’m building a synthesiser, and I’ve found envelopes, which I thought would be the easiest component to nail, more tricky than I thought to get sounding ‘right’. You have helped immensely.
Your site is by far and away the best that I’ve found on the internet that explains the murky secrets of audio DSP. Many thanks.
thanks for the great tutorials. Which language do you use to write the widgets? Is it possible for you to share the ADSR Widget source code?
Thank you for all the good information on your site. I just discovered it 2 weeks ago, I’m trying to catch all and there’s a lot.
Selecting the Plot=”log” options seems to prevent any action of the sliders.
Thanks! Fixed now—I made changes when updating the plot engine from flotr to flotr2 recently, and didn’t update the log case.
First of all, thanks for writing this blog, I am learning so much from it! And with all the exelent examples it is a pleasure to read as well.
return Math.exp(-Math.log((1.0 + targetRatio) / targetRatio) / rate);
You mentioned in the ADSR articles leading to this widget that “… the constant 1 is because we are moving a distance of 1 (0.0 to 1.0, or 1.0 to 0.0) …”. Am I correct that the widget does not take into account that for decay we move from 1.0 to sustainlevel and for release we move from sustainlevel to 0?
Right. The easiest way to see how it behaves is with the ADSR widget—move sustain up and down, and you’ll see that it takes the same amount of time to decay to sustain, and to release to zero. That way, it maintains the useful property of being able to set the decay and release time, unaffected by the sustain level (a big advantage for things like auto-retriggered envelopes). It also means that the ADSR doesn’t need to recalculate decay and release times as the sustain level is moved.
Sorry for reviving this thread this late. I’ve adapted your ADSR class in my FM synthesizer engine and those have a curvature slider for A, D and R. So far so good, but when I go past 1 second in terms of rate (for ex. SetRate(2.5*1.0*sampleRate)) and I bump the linearity of the attack phase (let’s say 100.0 instead of 0.3) I run into trouble, in that the attack phase is skipped immediately (I believe the coefficient is 1 in this case). Now I’m a bit of a tool when it comes to audio DSP math, but that said, I could of course debug further and see exactly what’s going on. I was however wondering if you could explain this behaviour; that is, if it’s not a dumb bug on my end.
Thanks for this blog, it’s of massive use to me.
It’s been a few years, but as soon as I looked at the ADSR code and saw “float”, I suspected the issue…
A combination of the large attack ratio and long attack time makes calcCoef round to 1. Specifically, exp of a tiny number is close to 1, and exp apparently rounds up to 1 when there isn’t enough precision. Subsequently, attackBase is calculated as 0. For me, that means it stays in the attack phase instead of skipping it, but either way it’s a precision problem. You could use a smaller target ratio (something much smaller than 100 would still be effectively linear), or fix it the right way which would be to change all “float” to “double” in ADSR.cpp/.h. While everything doesn’t need to be double, you might as well. I’ve written everything in double for years, since the penalty is pretty slight with modern processors, and it avoids most problems.
I’ll update the code when I get a chance, thanks for finding it.
PS—I did update the code, and discovered the exact reason for the issue: For x very close to zero, exp(x) does not evaluate to a value very close to 1, but 1.0 exactly, when “very close to zero” is an unnormalized (“denormal”) number.
Great! Thanks for the quick reply; I’ll convert to doubles (I’ve tinkered a bit here and there with the implementation to suit my needs).
And that also pushes the “convert entire pipeline to double precision” idea I had for my code higher up the priority list 😉
Your email address will not be published. Required fields are marked *
Save my name, email, and website in this browser for the next time I comment.