ADSR envelopes in Pd

logo for article: ADSR envelopes in Pd

ADSR envelopes are the building blocks of many instruments in Pure Data. Here I show you some alternative ways to build ADSR envelopes.

The canonical ADSR envelope in Pd is the one written by Miller Puckette as part of the included documentation in 3.audio.examples/adsr.pd. According to an included note that abstraction was last updated for Pd version 0.37. Today we are at Pd 0.42 and we have the object [vline~] available which not only makes envelopes more accurate in regard to timing, it also allows to write much simpler code.

The patch adsr-variations.pd shows two ways to build an ADSR: one is using [vline~] directly and builds messages to schedule the attack/decay and the release portion of the ADSR.

The second variation uses a [pipe] delay to just schedule smaller breakpoint envelopes with two elements: a target to go to and the time to reach that target. This version can then be used to drive an explicit [vline~], or drive a [line] with it to make a message ADSR instead of an audio signal ADSR. That sometimes is handy to drive for example a filter envelope if the filter you want to use doesn't have a signal inlet.

Now lets dive into the patch.

First we have to collect the settings. I stay compatible to the MSP-adsr.pd in that the env accepts the following data:

  • level: the peak level to ramp to.
  • attack: the time in msec to reach the peak
  • decay: the time in msec to reach the sustain level
  • sustain: the percentage of the level to use in the sustain phase
  • release: release time in msec

The adsr is triggered using numbers. A zero schedules the release: Go to 0 over the release time. Any positive number schedules the attack and decay, taking off where the envelope currently is. Negative triggers just like in Miller's adsr.pd cause the output to jump to zero and then attack (instead of attacking from the current location).

Screenshot: massaging ADSR control data

Massaging ADSR control data to correct the sustain levels.

The raw ADSR control data is packed into a 5-element list: "lvl a d s r", and sent to [s $0-env-ctl]. But we need to massage it a bit: Because the sustain level is relative to the peak level, we now "clean" the raw envelope list to take that into account. That's made by unpacking the list, dividing the sustain by 100 (it was a percentage before) and multiplying the current level by that sustain factor. This is packed again into a "cleaned" envelope list with five elements.

Now lets take a look at the two envelopes.

pd adsr-vline

Using [vline~] to make an ADSR envelope.

Using [vline~] to make an ADSR envelope.

Here [vline~] is the central object to drive the envelope. As a reminder here's how vline~ works: It takes messages consisting of up to three elements. The first number is the target to ramp to, the second is the duration of the ramp. The third number is special: It specifies a delay to wait before starting the ramp. Now think of the adsr: The first portion of that is easy: ramp to the target over "a" milliseconds, do it immediatly without wait.

But the decay portion is trickier: It should go to the (cleaned) sustain level over "d" msecs, but start doing so when the attack portion has finished playing. We know how long the attack phase takes: Its duration is "a" milliseconds. So the complete message to schedule the decay is:

<sustain level> <decay duration>  <attack duration>
  go here         over this dur.    but first wait until attack is finished

That's exactly what this $-polluted message box in the subpatch does: "$1 $2, $4 $3 $2" using $1=lvl, $2=attack time, $3=decay time, $4=sustain level.

$5 is the release time in the cleaned envelope data. This is only used when we receive a 0 as a trigger at [select 0]. The [moses 0] at the trigger chooses if we should start the attack/decay from zero or not.

pd adsr:_delayed_messages_only

Using [pipe] to make an ADSR envelope.

Using [pipe] to make an ADSR envelope.

The second subpatch is very similar to the vline~ example. But now, [vline~] is replaced with [pipe 0 0 0]! What does this mean? [pipe] is a delay for messages. Normally it just delays a single number, but just like [pack] you can change that to make [pipe] delay lists as well. You need to know the length of the list and the types of its elements, though. We want to delay lists with two elements: a target and a duration for a ramp. [pipe] also needs to know the delay for each message which always goes into the last inlet.

Now take note of this interesting trivia: Our [pipe 0 0 0] now accepts the same three numbers that [vline~] accepts: a target, a duration and a predelay. That's handy, isn't it?

It is, because we can basically reuse the full subpatch from before to drive the pipe instead of vline~. I added two zeroes as delays into the message boxes because [pipe] otherwise would reuse the previous delays. Those can be left out in the [vline~] part, but they wouldn't hurt there either. I also threw in a "clear" message just in case to clear the pipe everytime we get new messages.

Now as I said before this version can be used as a general purpose breakpoint generator. You can drive [vline~], [line~] or a message [line] with it or modify it for different kinds of envelopes, like an ADSADSADDDADADDSSDAADSDSDADADSR or so. Eat that, Absynth! ;-)

Some comments

On May 23, 2009, Z said:

That's cool, next part should be how to drive a multipoint envelope with a graph.

Post a comment

footils.org