Digital audio synthesis part 3: Envelope generation

This is the third part of our blog series about digital audio synthesis.

The earlier parts is easy to find on the other side of thees links:

  1. The oscillator
  2. MIDI

What is envelope generation?

“Automatic envelope generation” is commonly used to adjust the sound amplitude when triggering a note.

It’s job is to create something which can be used to adjust the size of another signal through time. That can be the pitch of a note, a variable in a filter or as here: the sound amplitude.

ADSR-envelopes

A common envelope-generation-method is the four-stage envelope generator called ADSR:

Attack is the time taken for initial run-up of level from nil to peak, beginning when the key is pressed.

Decay
is the time taken for the subsequent run down from the attack level to the designated sustain level.

Sustain
is the level during the main sequence of the sound’s duration, until the key is released.

Release
is the time taken for the level to decay from the sustain level to zero after the key is released.

(source: Wikipedia)

Example

Excerpts from our code running on the Daisy looks like this:

We (re)trigger the envelope generator when a note arrives:

// Switch case for MIDI Message Type
void HandleMidiMessage(MidiEvent m)
{
    switch ( m.type )
    {
        case NoteOn:
        {
            NoteOnEvent p = m.AsNoteOn();

            // This is to avoid Max/MSP Note outs for now..
            if ( m.data[1] != 0 )
            {
                p = m.AsNoteOn();
                osc.SetFreq(mtof(p.note));
                env.Retrigger(false);       // retrigger the ENV generator
                GATE = true;
            }
            else
            {
                GATE = false;
            }
        }
        break;

        case NoteOff:
        {
            GATE = false;
        }
        break;

        default:
        break;
    }
}

And we use the calculated envelope to adjust the signal amplitude:

void AudioCallback(AudioHandle::InterleavingInputBuffer  in,
                   AudioHandle::InterleavingOutputBuffer out,
                   size_t                                size)
{

    for ( size_t i = 0; i < size; i += 2 )
    {
        osc.SetAmp(env.Process(GATE));
        out[i] = out[i + 1] = osc.Process();
    }
}

So how does this sound? Let us use the demo-song from part 1 and part 2.

It sound like this in part 1 (8 bits, 15.6 kHz, Arduino UNO):

 

And in part 2 (24 bits, 48 kHz, Daisy Seed):

 

With added ADSR (“random” selected A-, D-, S- and R-values, 24 bits, 48 kHz, Daisy Seed):

Final thoughts

It sounds much better! But what about adding some digital filtering to the notes? That is something we will look more into in the next blog post regarding this! So stay tuned =)

Related Posts