Our EG responds to two events: noteOn and noteOff, which occur for example when a keyboard key is depressed and subsequently released. As soon as a noteOn event occurs, the EG's output starts rising from 0.0 to 1.0 at the rate controlled by the attack time setting. As soon as the attack time has expired, the decay time starts and the EG's output falls from 1.0 to the sustain level in the time specified by the decay time setting. The output stays at the sustain level (set by the sustain level setting) until a noteOff event occurs. At that time the EG's output falls from the sustain level to 0.0 in the time set by the release time setting. All of the EG times can range from 1 millisecond to 5 seconds (5000 milliseconds) in duration.
If you were to couple an oscillator, an envelope generator controlling a variable gain element and a SamplePlayer together what you would hear when a noteOn event occurs is the volume of the oscillator going from silence to full volume in the attack time period, the volume then dropping to a specified sustain level within the decay time period and staying there until a noteOff event causes the volume to drop from the sustain level to silence in the release interval. As an aside, if you were to set the attack time to longer than the decay time and the release time really short, the notes would sound as if they were being played backwards.
Accurate timing is an important aspect of the EG's operation. To provide this timing, the EG is implemented as a state machine that runs at the sample rate. This results in accurate timing because, as mentioned earlier, samples must be delivered in a continuous fashion otherwise audible artifacts will be generated. The EG's state machine is shown in Listing Two. The getValue method is called every sample time and returns a value between 0.0 and 1.0. Both events and counter timeouts cause transitions between states in the state machine.
Listing Two: The EG’s State Machine
public double getValue() {
double value = 0.0;
switch (state) {
// Process the idle state
case STATE_IDLE:
noteOff = false;
if (noteOn) {
noteOn = false;
count = 0;
state = SM_STATE.STATE_ATTACK;
}
break;
// Process the attack state
case STATE_ATTACK:
// Did another noteOn event occur?
if (noteOn) {
state = SM_STATE.STATE_IDLE;
break;
}
// Calculate the value to return
value = count * attackSlope;
// Has attack time elapsed ?
if (count >= attackCount) {
count = 0;
state = SM_STATE.STATE_DECAY;
} else {
count++;
}
break;
// Process the decay state
case STATE_DECAY:
// Did another noteOn event occur?
if (noteOn) {
state = SM_STATE.STATE_IDLE;
break;
}
// Calculate the value to return
value = 1.0 - (count * decaySlope);
// Has decay time elapsed ?
if (count >= decayCount) {
state = SM_STATE.STATE_SUSTAIN;
} else {
count++;
}
break;
// Process the sustain state
case STATE_SUSTAIN:
// Did another noteOn event occur?
if (noteOn) {
state = SM_STATE.STATE_IDLE;
break;
}
// Get value to return
value = sustainLevel;
// Did a noteOff event occur ?
if (noteOff) {
noteOff = false;
count = 0;
state = SM_STATE.STATE_RELEASE;
}
break;
// Process the release state
case STATE_RELEASE:
// Did another noteOn event occur?
if (noteOn) {
state = SM_STATE.STATE_IDLE;
break;
}
// Calculate the value to return
value = sustainLevel - (count * releaseSlope);
if (value < 0) {
value = 0;
}
// Has release time elapsed ?
if (count >= releaseCount) {
state = SM_STATE.STATE_IDLE;
} else {
count++;
}
break;
}
return value;
}


