| Author | Topic: ADSR - Linear vs. Exponential | ||
|---|---|---|---|
|
|||
Thanks to everyone who responded to my linear interpolation thread. My oscillator code is coming along well. Next, the ADSR...
I've written an ADSR class that uses a linear approach instead of an exponential one, i.e. the values rise and fall linearly. One thing that I've noticed when I use the ADSR to modulate the amplitude of an oscillator is that when I set the sustain to zero and the decay to some value, e.g. 1.5 seconds, the sound fades out as expected. But at the tail end, the sound seems to drop off too quickly. I've not had a chance to try an exponential ADSR as I'm still exploring ways of implementing one, but could this drop off be related to the ADSR being linear? I've searched through past posts here for implementing an exponential ADSR. I've come across some very clever solutions. One that I like is using a first order lowpass IIR filter. This solution has been mentioned a few times, at least once by JCJR and a few times by braindoc. It's attractive because of the low computational costs and, from what I understand, it's the way analog synths implement envelopes (with "RC" circuits, I think?). In examining this approach I noticed that "attack" portion is logarithmic rather than exponential. To see what I mean, check out an excerpt from Steven W. Smith's excellent book, The Scientist and Engineer's Guide to Digital Signal Processing. This shows the attack as logarithmic. However, the decay curve looks exponential. This graphic shows what I'm talking about. Apparently, this is the way an analog synth's ADSR behaves. For an example, look here. Now, to calculate how long a segment should last, you use the following equation: x = e^(-1/d) Where x is the amount of decay between samples and d is the number of samples for the filter to decay to 36.8%. From what I understand, the amplitude of the envelope will be in the neighborhood of 0.63 at the end of the attack. If you set up your algorithm thinking that at the end of the attack you'll have an amplitude close to 1, you'll be disappointed. Same goes for targetting the sustain level. I found in testing out some code that if I divide "d" by some value larger than one, it causes the filter to run faster thus getting it closer to the target amplitude. This feels a little kludgey, but seems to be a simple enough solution. |
|||
| ^ | Joined: 03 Dec 2006 Member: #131095 | ||
|
|||
Leslie Sanford wrote: But at the tail end, the sound seems to drop off too quickly. I've not had a chance to try an exponential ADSR as I'm still exploring ways of implementing one, but could this drop off be related to the ADSR being linear?
Yes. I find exponential falloff in release (or decay-to-zero as in your case) to sound much more natural. Quote: In examining this approach I noticed that "attack" portion is logarithmic rather than exponential.
Actually, it's exponential but upside down, which is, I think, different from log. Quote: x = e^(-1/d) Where x is the amount of decay between samples and d is the number of samples for the filter to decay to 36.8%. From what I understand, the amplitude of the envelope will be in the neighborhood of 0.63 at the end of the attack. I found in testing out some code that if I divide "d" by some value larger than one, it causes the filter to run faster thus getting it closer to the target amplitude. This feels a little kludgey, but seems to be a simple enough solution. You can adjust the d scale, which will get you closer to 1 but never quite get there, or simply multiply x by 1/0.63 to get it to hit 1 in one "d" time. Chamberlin (which I think you've referenced here before?) has a nice formula for using an exponential decay to "aim" at a point slightly beyond the target you're trying to hit, and stopping early, with his usual lucid-and-not-too-math-abstracted explanation. |
|||
| ^ | Joined: 03 Oct 2002 Member: #3996 Location: SF CA USA NA Earth | ||
|
|||
My 0.02 euro on envelopes:
Where you wanna stop your attack is a matter of taste (just like linear/exponential really). The exponential envelope is easiest generated by onepole lowpass filter. Now typical onepole lowpass will look something like: out = out + lag * (in - out) If you put 1 in place of "in", initialize "out" as 0, and decide attack is over when "out" goes over 0.63, you have the desired(?) attack. "lag" is whatever "x" you had for attack. What happens now is that attack "decays" exponentially upwards. Alternatively if you wanna hit 1 instead, either multiply the result with 1/0.63, or put 1/0.63 in place of in. You can shape the attack as you like by aiming more (more like linear) or less ("punchier?") over the endpoint. As for decay, you can start with whatever value out had after attack was over, just switch "in" to sustain level, and "lag" to decay setting. I don't see a point cutting decay for reasons other than possibly saving some computation, and I kinda like it that you can move sustain and all sustained notes will start "decaying" (possibly upwards) to the new setting. I am under the impression that quite a few analog synths actually have this property. For release, there's some options depending on what you want. One option is to overlay (multiply with) a release envelope on top of the ADS envelope (that setup lets sustain changes have effect even while some notes are already releasing). Alternatively just switch the envelope to another mode which releases to zero from wherever it was. Same onepole setup still. Aim slightly (or more, depends on the shape you want really) below zero (just like with attack, just other direction now) and cut it out when it crosses zero. As for timing the parts (for GUI representation and/or samplerate independence), I'd solve attack and release exactly and use the same formulas for decay even if it was technically left infinite (as this is the right thing to do for samplerate independence anyway). Oh and pseudo C code for the loop goes basicly like: if(gate) { if(rising) { value += a_coeff * ((1/0.63) - value); if(value > 1) { value = 1; // clips really snap attacks rising = false; } } else { value += d_coeff * (sustain - value); } } else { value += r_coeff * (1-(1/0.63) - value); if (value < 0) { killthisvoice(); } } For modulation envelopes just clip release to 0 instead of killing the voice, ofcourse. 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 |
|||
| ^ | Joined: 11 Feb 2006 Member: #97939 Location: Helsinki, Finland | ||
|
|||
>>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
Good trick. |
|||
| ^ | Joined: 15 Sep 2002 Member: #3851 | ||
|
|||
Leslie Sanford wrote: In examining this approach I noticed that "attack" portion is logarithmic rather than exponential. To see what I mean, check out an excerpt from Steven W. Smith's excellent book, The Scientist and Engineer's Guide to Digital Signal Processing. This shows the attack as logarithmic. However, the decay curve looks exponential. This graphic shows what I'm talking about. it is an exponential saturation curve - just like the RC-loading curve. and i think, it is exactly, what a user expects an attack to look like - at least, i would expect that. and it sounds good. Leslie Sanford wrote: From what I understand, the amplitude of the envelope will be in the neighborhood of 0.63 at the end of the attack. If you set up your algorithm thinking that at the end of the attack you'll have an amplitude close to 1, you'll be disappointed. Same goes for targetting the sustain level. I found in testing out some code that if I divide "d" by some value larger than one, it causes the filter to run faster thus getting it closer to the target amplitude. This feels a little kludgey, but seems to be a simple enough solution. in my aggressor-synth, i decided to make this factor accessible to the user. i simply provide a parameter which scales the RC-time-constant tau by a number between 0.1...1.0. this scaling only affects tau and does not affect the time at which i switch to the next time-constant and target-level. |
|||
| ^ | Joined: 08 Mar 2004 Member: #15959 Location: Berlin, Germany | ||
|
|||
Borogove wrote: You can adjust the d scale, which will get you closer to 1 but never quite get there, or simply multiply x by 1/0.63 to get it to hit 1 in one "d" time. Chamberlin (which I think you've referenced here before?) has a nice formula for using an exponential decay to "aim" at a point slightly beyond the target you're trying to hit, and stopping early, with his usual lucid-and-not-too-math-abstracted explanation.
I found it, thanks! Can't believe I missed it my other times through the book. It's in chapter 18 of Musical Applications of Microprocessors, if anyone's interested. |
|||
| ^ | Joined: 03 Dec 2006 Member: #131095 | ||
|
|||
mystran wrote: My 0.02 euro on envelopes:
<snip> Just gotta say that this was a tour de force post for me. I've read through it several times. It's very helpful. I updated my code to use this approach, and it sounds Nice. |
|||
| ^ | Joined: 03 Dec 2006 Member: #131095 | ||
|
|||
braindoc wrote: Leslie Sanford wrote: In examining this approach I noticed that "attack" portion is logarithmic rather than exponential. To see what I mean, check out an excerpt from Steven W. Smith's excellent book, The Scientist and Engineer's Guide to Digital Signal Processing. This shows the attack as logarithmic. However, the decay curve looks exponential. This graphic shows what I'm talking about. it is an exponential saturation curve - just like the RC-loading curve. Yeah, I was mistaken to say it was logaritmic. Quote: and i think, it is exactly, what a user expects an attack to look like - at least, i would expect that. For me, I wasn't sure until today if the attack should be a negative exponential curve or positive (hope I'm using those terms correctly). Quote: and it sounds good. I've just found this out. Quote: in my aggressor-synth, i decided to make this factor accessible to the user. i simply provide a parameter which scales the RC-time-constant tau by a number between 0.1...1.0. this scaling only affects tau and does not affect the time at which i switch to the next time-constant and target-level. Sounds like a good approach, thanks. |
|||
| ^ | Joined: 03 Dec 2006 Member: #131095 | ||
|
|||
I have a related issue I'd be very interested to hear peoples opinions on. Having a system such as that described above for ADSR envelopes works well enough - however in the case of graphical volume envelopes, do you think the y axis should have a linear mapping to volume or some other mapping? Similarly, what if you assign an LFO to the volume parameter?
The approach I have taken is that all my modulators just work with values in the range 0-1 and my ADSRs just generate values linearly, however when I apply these volume values to the signal, I cube the values before applying. Cubing the values seemed to result in a mapping which is similar to the way most knobs on synths work. Eg. When you have the volume knob at the half way (0.5) position, you are actually using 0.5^3 = 0.125 as the scaling value. The other approach I am considering taking is to go back to applying the amplitudes linearly, and instead altering my ADSR envelope to do something similar to that which is described in this thread so far, and scale just the knob values with the x^3 mapping. This would mean that the y axis of any envelope assigned to amplitude would just be linear - ie the half way position would result in multiplying the amplitude value by 0.5. Similarly, if you assigned an LFO to an amplitude destination it would just be applying its values in the linear scaling rather than the x^3 scaling. This seems to work reasonably well, but I'm not sure if this is really the best approach, and I would be really interested to hear other peoples opinions on this issue. I would be particularly interested to hear from anyone who has made synths with very flexible modulation systems. Thanks very much in advance for any advice and thoughts Ben |
|||
| ^ | Joined: 17 Sep 2001 Member: #1122 Location: Edinburgh, Scotland | ||
|
|||
Best thing is to generate in linear, then convert to the range and curve of the destination model. This way you could have the same LFO/ADSR connected to the volume/pitch/filter cutoff/ without generating twice the source buffer.
If you're using midi controller as modulation input it will apply the same way, so it's very flexible because you want that CC are applied in a scaled fashion when connected to volume. HTH |
|||
| ^ | Joined: 28 Mar 2005 Member: #63153 | ||
|
|||
I too simply cube the envelope output. It wont work for -1 to 1 stuff though.
(Edit: Though I suppose you could always transpose it.) Opps, posted a sine, meant to post this: |
|||
| ^ | Joined: 03 Sep 2003 Member: #8788 | ||
|
|||
[quote="Leslie Sanford]
Quote: and it sounds good. I've just found this out. [/quote] yeah. and it resembles the behaviour of analog envelope-genrators. i really tend to think that the envelope-shape has at least the same importance as the oscillator and filter for a good analog emulation. |
|||
| ^ | Joined: 08 Mar 2004 Member: #15959 Location: Berlin, Germany | ||
|
|||
FWIW, you usually use exp envelope for volume (directly of after conversion)
because it gives a linear curve in dB. Just as you do for filter cutoff in log, because you want linear mods in final results (aka what you hear) |
|||
| ^ | Joined: 28 Mar 2005 Member: #63153 | ||
|
|||
otristan wrote: FWIW, you usually use exp envelope for volume (directly of after conversion)
because it gives a linear curve in dB. Just as you do for filter cutoff in log, because you want linear mods in final results (aka what you hear) this was, what i did before i found the solution with the RC-filter: generate a line-segment envelope in the dB-domain and then converting it to "raw" amplitude values. this was really terribly awkward - in particular for the attack-phase where i additionally had to do some stuff to invert the slope. |
|||
| ^ | Joined: 08 Mar 2004 Member: #15959 Location: Berlin, Germany | ||
|
|||
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? |
|||
| ^ | Joined: 15 Sep 2002 Member: #3851 |
![]() |
All times are GMT - 8 Hours | |
|
Printable version |
||
![]() Previous Topic Next Topic |
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum |
Disclaimer: All communications made available as part of this forum and any opinions, advice, statements, views or other information expressed in this forum are solely provided by, and the responsibility of, the person posting such communication and not of kvraudio.com (unless kvraudio.com is specifically identified as the author of the communication).
Powered by phpBB © phpBB Group
















