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 😉
Hi Nigel, I am fascinated by the prospect of software generated ADSR.
I have a home designed synthesiser which has individual control of the Fundamental and 9 harmonics i.e. 1F to 10F.
This requires 10 ADSR generators which, with individual control of A, D, S, R, necessarily needs at least 40 pots.
I am not understanding much about how your ADSR programming is implemented in hardware/ processor/ DtoA converter, etc, and how the parameters are manually varied to achieve different ADSR waveforms.
Could you please explain how your project is actually built.
But I am particularly excited by your ADSR widget on page https://www.earlevel.com/main/2013/06/23/envelope-generators-adsr-widget/ and how your on screen sliders make real time change to the displayed ADSR.
Would it be possible for such a widget to run on a Windows PC and directly control the ADSR parameters just by using the sliders on the screen.
My ideal solution would be to have 10 such widgets on the screen thus removing the need for a panel of 40 pots.
I would be grateful for any help or advice.
Fundamentally, there is a “give me the next output” (process) function. This must get called at a regular rate. It could be the audio sample rate, it could be at a lower control rate to save cycle, but it has to be regularly spaced. The output depends on the state (release, etc.), and the the coefficient values. Both of those things can be set asynchronously—when a key is release via MIDI, when the user turns a knob.
As for displaying the envelope shape, for a user interface (like the widget), you’d just repeatedly call the process function. But you’d use a relatively low sample rate as your divisor, according to the horizontal resolution you want to display. You only need to iterate through it once, whenever the controls change. You’d instantiate another ADSR to actually do the envelope.
thank you very much for your reply.
I know very little about software and programing and so I am finding it difficult to picture a complete working ADSR system as your posts and your correspondents
do not mention any physical hardware on which this system runs or an overview of a working system.
Perhaps it it beyond my ability to understand but I found it hard to see how to create a real working system.
My aim would be to create a set of 10 independant ADSRs with A, D, S, and R set by sliders on a PC screen so no physical potentiometers are required.
I think I would need such a lot of guidance to create such a system that it may be impractical for me to create.
I will probably have to continue my efforts to build discrete component ADSRs or use a preprogramed ADSR chip like Electric Druid’s ENVGEN 8C with 40 or more pots to control.
It might be helpful to try a plug-in framework (JUCE, or IPlug2), to get the idea of how things are organized. Basically, you have a thread that services the audio—it gives you a buffer of audio to process, and expects you to return the buffer fully processed, at a rate that keeps up with playback. It has to be timely, to not get dropouts, so you don’t do any user-interface work there, just use settings that you have stored in instance variables. For instance, for each sample in the buffer, you might call the ADSR::process method to get the next envelope output. Maybe you multiply the current sample by it for a loudness envelope, and put the result in the output buffer (often it’s in-place, you just modify the buffer it gave you).
There is another thread that services the user interface—it’s a lower priority, no great harm if it runs a little sluggish at times. When a user turns a knob, for instance, the framework dispatches that info to your service routines. At that point, you can recognize that the Attack knob changed, and can use it’s new value to calculate a new attack time, and call the setAttackRate procedure of the ADSR instance to set it. That calculates the coefficient needed by the ADSR and saves it in the appropriate ADSR internal variable. So, the next time the audio thread is called, the ADSR has a new setting to act on.