Les enveloppes
Une enveloppe est une fonction qui dépend du temps. En synthèse de son, les enveloppes sont très utilisés pour mimer le comportement temporel des vrais instruments acoustiques. A titre d'exemple, lorsque un pianiste déclenche une note de piano, le volume de la note va tout d'abord augmenter lors de l'attaque, avant de décroître, pour ensuite se stabiliser. En reproduisant ce type de comportant, les sonorités produites par un synthétiseurs peuvent paraître plus naturelle et agréable à l'écoute.
L'enveloppe ADSR
Une des enveloppes les plus utilisés est l'enveloppe ADSR. Cette enveloppe dépend de 4 paramètres:
Dans une enveloppe ADSR, les états peuvent être définis comme suit :
- Attack (Attaque) : C'est le premier état de l'enveloppe, où l'amplitude augmente rapidement depuis zéro jusqu'à son niveau maximal. Pendant cette phase, l'enveloppe passe progressivement de son niveau initial à son niveau maximal en fonction du temps défini pour l'attaque.
- Decay (Décroissance) : Après la phase d'attaque, l'enveloppe entre dans l'état de décroissance, où l'amplitude diminue jusqu'à atteindre le niveau de maintien (sustain level). Cette phase est contrôlée par le temps de décroissance.
- Sustain (Soutien) : Une fois que la phase de décroissance est terminée, l'enveloppe atteint le niveau de soutien, où elle reste constante tant que la note est maintenue enfoncée.
- Release (Relâchement) : Lorsque la note est relâchée, l'enveloppe entre dans la phase de relâchement, où l'amplitude diminue de manière contrôlée jusqu'à zéro. Cette phase est déterminée par le temps de relâchement.
TIP
Pour illustrer le fonctionnement de l'enveloppe, il est nécessaire que votre programme simule, au minimum, un oscillateur pour lequel le pitch et la velocité sont contrôlés en MIDI via des messages Note On et Note Off.
Définition des états de l'enveloppe
Avant d'implémenter l'enveloppe, nous allons tout d'abord définir une énumération décrivant les différents états pour faciliter la lecture du code.
typedef enum
{
Idle,
Attack,
Decay,
Sustain,
Release
}EnvelopeState;
Définition d'une structure
Comme dans les autres parties, nous allons utiliser une structure pour stocker les paramètres de notre enveloppe. Dans notre structure, nous allons définir 4 membres pour stocker les paramètres de l'enveloppe ADSR (attackTimeSeconds
, decayTimeSeconds
, sustainLevel
, releaseTimeSeconds
). Afin d'optimiser les calculs, nous allons également utiliser des membres pour stocker les pentes des différentes parties de l'enveloppe attackRate
, decayRate
, releaseRate
.
typedef struct {
float attackTimeSeconds;
float decayTimeSeconds;
float sustainLevel;
float releaseTimeSeconds;
float attackRate;
float decayRate;
float releaseRate;
float currentValue;
uint8_t currentN; //counter
uint8_t downsamplingRate;
EnvelopeState state;
} LinearADSREnv;
Implémentation des fonctions associées
Pour faciliter la gestion de l'enveloppe, nous allons implémenter plusieurs fonctions dont les prototypes sont spécifiés ci dessous.
void linearADSREnvInit(LinearADSREnv* env, uint8_t downsamplingRate);
void linearADSREnvNoteOn(LinearADSREnv* env, bool resetCurrentValue);
void linearADSREnvNoteOff(LinearADSREnv* env);
float linearADSREnvNextSample(LinearADSREnv* env);
Initialisation des paramètres
void linearADSREnvInit(LinearADSREnv* env, uint8_t downsamplingRate)
{
env->state = Idle;
env->attackTimeSeconds = 0.2;
env->decayTimeSeconds = 0.3;
env->sustainLevel = 0.1;
env->releaseTimeSeconds=0.4;
env->attackRate = 0;
env->decayRate = 0;
env->releaseRate = 0;
env->currentValue = 0.;
env->currentN = 0; //counter
env->downsamplingRate = downsamplingRate;
}
Réception d'un message Note On
A la réception d'un message Note On, les pentes doivent être précalculées et l'état de l'enveloppe initialisée à l'état Attack
. La fonction contient également un paramètre resetCurrentValue
permettant de préciser si la valeur de l'enveloppe doit être réinitialisée à 0.
void linearADSREnvNoteOn(LinearADSREnv* env, bool resetCurrentValue)
{
env->attackRate = env->downsamplingRate/(env->attackTimeSeconds*SAMPLE_RATE);
env->decayRate = -(1-env->sustainLevel)*env->downsamplingRate/(env->decayTimeSeconds*SAMPLE_RATE);
env->releaseRate = -(env->sustainLevel)*env->downsamplingRate/(env->releaseTimeSeconds*SAMPLE_RATE);
env->state = Attack;
env->currentN = 0;
if (resetCurrentValue == true)
{
env->currentValue = 0;
}
}
Réception d'un message Note Off
A la réception d'un message Note Off, l'enveloppe doit être initialisée à l'état Release
.
void linearADSREnvNoteOff(LinearADSREnv* env)
{
env->state = Release;
}
Calcul de la valeur de l'enveloppe
La valeur de enveloppe, nommée current_value
, est recalculée tous les downsamplingRate
échantillons. Le calcul de la valeur dépend de l'état de l'enveloppe et s'obtient au moyen d'un switch
.
float linearADSREnvNextSample(LinearADSREnv* env)
{
//update the value of the current N
env->currentN += 1;
if ((env->currentN % env->downsamplingRate) == 0)
{
// update the envelope value only 1 sample over downsamplingRate.
env->currentN = 0;
switch (env->state)
{
case Idle:
{
env->currentValue = 0;
break;
}
case Attack:
{
env->currentValue += env->attackRate;
if (env->currentValue >= 1.)
{
env->state = Decay;
}
break;
}
case Decay:
{
env->currentValue += env->decayRate;
if (env->currentValue <= env->sustainLevel)
{
env->state = Sustain;
}
break;
}
case Sustain:
{
env->currentValue = env->sustainLevel;
break;
}
case Release:
{
env->currentValue += env->releaseRate;
if (env->currentValue <= 0)
{
env->state = Idle;
}
break;
}
}
}
return env->currentValue;
}
Manipulation 1
Ajoutez les codes relatifs à la gestion de l'enveloppe dans votre programme.
Modulation du gain
Dans cette partie, nous souhaitons moduler le volume de signal en sortie du synthétiseur par une enveloppe ADSR.
Interaction avec le contrôleur MIDI
L'enveloppe ADSR est contrôlée par la réception des messages MIDI Note On et Note Off.
- Réception d'un message Note On
- A la réception d'un message Note On, la fonction
monoSynthNoteOn
doit lancer la fonctionlinearADSREnvNoteOn
. Cette fonction précalcule les pentes et initialise l'état de l'enveloppe à l'étatAttack
.
- A la réception d'un message Note On, la fonction
- Réception d'un message Note Off:
- A la réception d'un message Note Off, la fonction
monoSynthNoteOff
doit lancer la fonctionlinearADSREnvNoteOff
.
- A la réception d'un message Note Off, la fonction
Interaction avec le moteur audio
Pour moduler le volume, le signal audio est multiplié par la valeur de l'enveloppe ADSR.
LinearADSREnv env;
...
void render_audio(int16_t *buf, int16_t length) {
...
for (uint16_t i = 0; i < length; i = i+2) {
//compute sample here
float x = ...
//update enveloppe
float gain = linearADSREnvNextSample(&env);
//compute output
float y = gain*x;
int16_t value = ((int16_t) ((32767.0f) * y)); // conversion float -> int
buf[i] = value; // left channel sample
buf[i+1] = value; // right channel sample
}
}
Manipulation 2
Modifiez votre code pour permettre à l'enveloppe ADSR de moduler le volume du signal de sortie.
Modification de l'enveloppe
Dans la plupart des synthétiseurs, les paramètres de l'enveloppe ADSR peuvent être modifié par le musicien. Nous souhaitons implémenté cette fonctionnalité via la reception de message MIDI CC.
Manipulation 3
Modifiez votre code pour permettre au musicien de modifier les paramètres de l'enveloppe attackTimeSeconds
, decayTimeSeconds
, sustainLevel
, releaseTimeSeconds
via MIDI.