The decay of modern society (or at least my volume)

Now that I've gotten the oscillators to deliver the approximate signal that I want, it's time to move on to modulation. I'm not going to do full-featured modulation, at least for now, but I'll do one feature to cause the volume to decline. Modulation is basically a way of controlling and changing values as a note plays. The most commonly modulated value relates to volume, but you can modulate other things too, including pitch, panning, and pulse width.

A common form of modulation is the ADSR envelope. ADSR stands for attack, decay, sustain, and release. I'll describe these as relates to volume (or amplitude). Attack is the time it takes to get from zero to the peak volume - a value of 0 means it will start at peak volume. Decay is the time it takes to get from peak volume to the designated sustain level. Sustain is the volume level a note should be sustained at until the note is released. Release is the time it takes to zero after the note is released. You can see how this works in the below graph.


You will notice that three of these values represent time (attack, decay, and release) but sustain represents a level.

I decided I'm just going to implement decay, with no values for attack, sustain, or release. We will assume attack value of 0, meaning immediate ramp up to max volume, and no sustain or release to worry about since we're not concerned with when a note is released at the moment.

All these does is add a new calculation: instead of a maximum amplitude, what should the amplitude when factoring in the decay?

The math ended up being simple.

amplitude / decay * (decay - position)

Simply put, we take the specified amplitude and divide it by the decay value multiplied by the decay value minus our current x-value position. Since we are dealing with sample, the position and decay have to be represented in the number of samples and not the time value. To make it more intuitive, I implemented decay in playNote in milliseconds, which means I need to convert from milliseconds to samples before I play anything. To make things consistent, I also changed the note duration to milliseconds as well (and added a conversion to samples).

There was one problem to fix - when decay exceeds the x position, we start getting negative values for amplitude, which is no good. We need to make sure the amplitude is never negative, so the equation ends up looking more like this:

amplitude / decay * max(0, (decay - position))

And my final code looks like this:

I don't know if I'm going to implement the rest of the envelope or allow for modulation of other parameters. Not yet, at least. Maybe later if I'm feeling ambitious.

Next up: There needs to be a better way of playing a tune. I'm going to implement classes for making instructions to play a simple song, and have the audio player accept that instead. This might take a little while longer, so expect this to take me a few days.

Comments