Síntesis del sonido

Envolventes

La envolvente (o envelope) de un sonido es la función que describe la evolución de su potencia a lo largo del tiempo. Los sonidos simples, y en general los sonidos generados por función, tienen una envolvente constante. Los sonidos reales tienen envolventes variables en el tiempo, que a menudo son tan características como su espectro de potencia.
Habitualmente, utilizamos la envolvente para reconocer un sonido familiar, porque la envolvente viene determinada por la física de la fuente sonora.

La envolvente del piano

Para la síntesis musical, un referente analógico muy estudiado fue el piano. El mecanismo del piano es el resultado de una evolución tecnológica y estética que buscaba un instrumento de gran dinámica y capacidad expresiva.  El piano es el instrumento preferente de la música occidental y su papel en la música moderna es importantísimo. 

La articulación de una nota del piano se basa en la física del instrumento. El sonido de cada cuerda es producido por un martillo que la golpea y se amortigua mediante un apagador. El mecanismo se activa por la acción del intérprete sobre una tecla y pasa por las fases siguientes:

Reposo: el martillo está preparado y el apagador descansa sobre la cuerda. La cuerda no tiene energía.

Ataque (Attack): al pulsar la tecla, el martillo golpea la cuerda y el apagador se alza. La cuerda absorbe la energía del martillo y la potencia del sonido crece rápidamente..

Decaimiento (Decay): el martillo rebota. La cuerda devuelve parte de su energía al martillo. La potencia del sonido decrece.

Sostenimiento (Sustain): el martillo recupera su posición de reposo. La cuerda oscila libremente, perdiendo energía poco a poco.

Relajación (Release): al soltar la tecla, el apagador cae sobre la cuerda. La cuerda pierde energía rápidamente.

La envolvente de los sintetizadores electrónicos

Basada, de forma aproximada, en la envolvente característica del piano, muchos sintetizadores electrónicos aplican a cada nota un envolvente de cuatro etapas denominada ADSR. Las tres primeras etapas van asociadas al evento NOTE ON que marca el principio de la nota; la última va referida al evento NOTE OFF que señala el final de la nota. Gráficamente, la evolución de la potencia a lo largo del tiempo, es esta:

ADSR

En el diseño de los sintetizadores analógicos clásicos, había un circuito, el generador de envolventes, que generaba una tensión positiva cuya evolución a lo largo del tiempo correspondía a la figura anterior. Esta envolvente regulaba otro circuito, el VCA o Voltage Controlled Amplifier, que aplicaba una ganancia proporcional a su entrada de control. La figura siguiente muestra el proceso: aplicada la envolvente (en rojo) a la onda generada por un oscilador (en azul), se produce el resultado (en verde).

aplica ADSR

Los parámetros aplicables son cuatro:

(A)ttack:
Tiempo en el que la potencia llega a su valor máximo desde el evento NOTE ON.
(D)ecay:
Tiempo en el que la potencia baja desde el máximo hasta un valor estacionario.
(S)ustain:
Nivel de potencia estacionario en relación con el máximo conseguido durante el ataque.
(R)elease:
Tiempo que transcurre desde el evento NOTE OFF hasta que la potencia del sonido se anula

Los sintetizadores tenían los mandos para graduar los tres tiempos y el nivel de sustain:

Sobre esta figura se crearon los primeros generadores de envolvente. Luego, cuando la tecnología lo hizo posible, la envolvente llegó a complicarse. A lo largo de su evolución, los sintetizadores han manejado envolventes de complejidad creciente, pero siempre han permitido al usuario seleccionar una envolvente como la de la nota del piano para regular la potencia instantánea (o loudness) de los sonidos producidos por los osciladores.

En la música electrónica, la temporización de la envolvente es mucho más libre que en el piano. Por ejemplo, si la física del mecanismo y de la cuerda del instrumento determinan tiempos de ataque y relajación de centésimas de segundo, en la síntesis no existen límites, y estos tiempos se pueden medir, si se desea, en segundos.

Además, se ha generalizado su uso. Aunque la envolvente del piano define la potencia instantánea del sonido, en la música electrónica una envolvente se puede aplicar a cualquier parámetro de la síntesis, como a la frecuencia de corte de los filtros pasabajos o al índice de modulación de un oscilador de baja frecuencia.

Generación de envolvente

El generador de envolvente se puede modelar como un autómata de estados finitos con cinco estados.

enum t_Envelope {attack, decay, sustain, release, silent};

La estructura de datos necesaria sería:

typedef struct
  float value; // valor actual de l'embolicant
  t_Envelope stage; // fase actual
  long attackF; // frames restantes de la etapa de attack
  long decayF; // frames restantes de la etapa de decay  
  float sustainL; // nivel de sustain 
  long releaseF; // frames restantes de la etapa de release  
} envStatus;  
envStatus eS;

que se inicializaría así, en función de los cuatro parámetros de la envolvente y una frecuencia de muestreo SampleRate determinada.

void resetEnvelope (float t_attack, float t_decay, float n_sustain, float t_release){
  eS.value = 0; eS.stage = silent; 
eS.attackF = t_attack * SampleRate;
eS.decayF = t_decay * SampleRate;
eS.sustainL = n_sustain;
eS.releaseF = t_release * SampleRate;
}

Si asumimos que hay dos transiciones de estado provocadas por eventos externos: NoteOn marca el inicio del sonido y equivale a la acción de pulsar la tecla del piano

void NoteOn(){ eS.stage = attack; }

NoteOff marca el final del sonido, y equivale a la acción de soltar la tecla del piano

void NoteOff(){ eS.stage = release; }

El código que rige el autómata de estados finitos para calcular el valor de la envolvente en cada muestra es el siguiente:

float envelopeStep(){
    switch (eS.stage){
    case attack:  eS.value = eS.value + (1.0 - eS.value)/eS.attackF;
                  eS.attackF--;
                  if (eS.attack == 0) eS.stage = decay;
                  break; 
    case decay:   eS.value = eS.value - (eS.value - eS.sustainL)/eS.decayF; 
                  eS.decayF--; 
                  if (eS.decay == 0) eS.stage = sustain;
                  break; 
    case sustain: eS.value = eS.sustainL; 
                  break;
    case release: eS.value = eS.value – eS.value / eS.releaseF;
                  eS.releaseF --;
                  if(eS.releaseF == 0) eS.stage = silent;
                  break;
    case silent:  eS.value = 0;
    }
   return eS.value;
}

Para aplicar la envolvente como amplitud a la onda producida por un oscilador, el código es este :

float phase = 0;
resetEnvelope();
for( i=0; true; i++ ) {
  // Calcula envolvente
  float loudness = envelopeStep();
  // Calcula forma de onda
  float wf = forma(phase);
  phase = phase + f;
  // Aplica envolvente a la onda
  vector[i] = loudness * wf;
}