Java Synthesizer, Part 5 – ADSR

Sound doesn’t simply “happen”. Usually, there’s some kind of rise time from 0 to full amplitude, and some measurable fall time back to zero. If we look at a flute, the amplitude grows relatively slowly and drops off a bit faster. A snare drum, on the other hand is close to instant on and instant off.  This change in volume really doesn’t have much to do with the type of waveform used for the audio signal (sine or squarewave). Instead, it’s the “shape” of that signal’s amplitude over time, or it’s “envelope”.  That is, different instruments have different “envelopes”. This is a concept that took me years to really figure out.

While real-world sounds have a wide variety of envelope types, early analog synthesizers employed straight-line formats that were generally composed of at least 3 common parts, while more complex envelopes are built on the basic 3.  They are:

A – Attack
D – Decay
S – Sustain

Attack is the rise time, or the speed at which the audio signal goes from 0 to full volume.
Decay is the first fall time, or the speed at which the audio signal goes from full volume to the sustain level.
Sustain is the level, or the audio signal amplitude that plays while the keyboard key remains pressed.

In this “ADS” system, when the user presses a keyboard key, the sound goes from 0 to full volume at the rate given by attack. If the key is still pressed, the sound immediately goes from full volume to the sustain level at the rate given by decay. While the key remains pressed, the sound stays at the sustain level. When the user stops pressing the key, the sound goes straight to 0. As an example, if A = 1s, D = 0.6s, S = 0.4, we get the following envelope (this is how the Gakken SX-150 Mark II works).

From what I’ve read, old Moog synthesizers had a “hold” parameter of 20 ms between attack and decay to add “punch” to the envelope output. And, ADSR systems add a release parameter to specify how quickly to go to zero when the user lets go of the key. In a Moog-style ADSR, with R = 0.2s, we get the following envelope.

An even greater elaboration would be to have an attack2/peak level/release combination instead of just release for something of a “wah-oh” effect at the end, but I haven’t taken the time to address that complication yet.  Thinking about it, it wouldn’t be that hard to write, I guess.

Most analog synths have A, D and R controls that go from 0 to 4 seconds or so, either in linear increments or a gradient scale. For my purposes, since the Roland keyboard dials go from 0 to 127, I’m looking at roughly 30 ms steps, which is more than small enough (for most purposes). For S and full volume, I’m sticking with 1.0 and employing a VCA (voltage-controlled amplifier) approach for volume control. So, full volume will be 1.0, and S will go from 0 to 1.0 in 0.01 steps (0 to 100).

Triggering for Attack and Release occurs with the gate signal. It’s an edge trigger, so I’m storing the last gate value and checking if it’s changed. If so, I store the new value and advance the envelope phase pointer to either the A or R modes. Everything else is just a matter of incrementing the cnt counter and advancing phases from A to Punch to D to S automatically. Now, there’s a serious problem with introducing clicks in the sound if any given time parameter is set to 0 (i.e. – Decay = 0 ms), or when going from Punch to Decay when cnt == the Punch value. This is because normally saying “if cnt == punch, set mode = decay” means that there’s one time slice where there’s no processing of the envelope and the method returns 0. That 0 val is what causes the click. So, my if-statements get a bit kludgy in checking for all combinations of cnt and ADSR timing settings. The good part is that now that the code’s written, it’s easy enough to extend by adding attack2/peak level at a later date.

(Actual ADSR output. 200 hz tone; 10ms Attack and Decay; 0.4 sustain; 10 ms Release.)

One elaboration I added is gate event triggers. I can use these to trigger additional oscillators to run only during the Punch phase, or during Release. A future mod will be to add an Invert mode, which is just “return(1.0 – ret * signalIn)”, for flipping the envelope upside-down.

I’ll use a for-loop this time to demonstrate how the 20 ms buffers are built up for the sound engine, using a single oscillator and the adsr.

osc      osc1     = new osc(200,   0, 0.5); // Frequency, Waveform type, Ratio
ADSR     adsr1    = new ADSR(8000, 8000, 0.4, 8000); // ADSR (for AD and R, at 16000 sampling rate, 8000 = 1 second)

addBuffer() {
adsr1.gateIn(keyBoard.gateOut());  // I’ll discuss keyBoard() later.

for(int pCnt = 0; pCnt < 320; pCnt++){
shortBuffer.put( (short)( 16000 * ( adsr1.nextSlice(osc1.nextSlice() ) ) ) );
An amazingly fascinating possibility opens up if you look closely at that above circuit diagram. The zigzag lines with the arrows indicate a fixed value being assigned to each of ADS and R; e.g. – from the dials of the Roland keyboard. But what if you route the output of an LFO (low frequency oscillator) to, say, Attack? The rate value for Attack would change over time and Attack would no longer be linear. Good choices for waveforms would be the sawtooth and reverse sawtooth, which could be triggered by the Attack Gate event. You’d need to multiply the LFO output by a scalar, since the LFO only goes from 0 to 1.0, but that’s what the upcoming VCA class is for. Just make sure that one cycle of the reverse sawtooth aligns with the Attack time you want.

(The actual shapes will depend on the algorithm used to feed the Attack input, and could result in the two output envelopes being exchanged.)

Raw formatted textfile here.


Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: