Synthèse par Wavetable
Pour générer des oscillateurs, une méthode efficace est la synthèse par wavetable. Cette technique consiste à précalculer les échantillons et à les stocker dans une table. L'oscillateur peut ensuite être créé en parcourant simplement cette table à une vitesse variable, ce qui permet de contrôler la fréquence de l'oscillateur. La synthèse par wavetable offre ainsi une flexibilité remarquable pour la création de timbres variés et complexes, tout en optimisant l'utilisation des ressources processeur. Cette technique est à la base de synthétiseurs comme le Massive de Native Instruments ou Serum de Xfer.
Création de la wavetable
La synthèse par wavetable repose sur la construction d'une table de
A titre d'exemple, le modèle suivant décrit une table d'échantillons décrivant une période d'un signal sinusoidal :
: nombre d'échantillons dans la table d'onde : entier variant de 0 à
La figure suivante présente le contenu de la table pour
Manipulation 1
Dans le fichier src/audio_processor.c
, précalculez une table d'échantillons nommée sine_wavetable
de
Génération du signal
Pour générer un oscillateur, une approche possible consiste à lire les éléments de la table avec un pas de
: pointeur dans la table, : opérateur modulo, : incrément de lecture.
Lorsque la table contient exactement une période et lorsque l'incrément de lecture est fixé à
Manipulation 2
Générez une sinusoïde avec un incrément de lecture
Modification de la fréquence fondamentale
Un synthétiseur doit pouvoir générer des oscillateurs de fréquence variable. Pour générer un oscillateur de fréquence
Dans le cas général, l'incrément de lecture
Pour approximer la valeur de
Interpolation d'ordre zéro
Une technique simple consiste à arrondir la valeur de
Manipulation 3
A partir d'une table d'onde de
Interpolation d'ordre 1
Pour réduire l'erreur d'approximation, une seconde technique consiste à appliquer une interpolation linéaire (ordre 1) entre les échantillons
où :
correspond au coefficient de pondération entre les échantillons et . Notons que lorsque et lorsque .
Manipulation 4
A partir d'une table d'onde de
INFO
Le calcul de l_1
peut se faire efficacement en utilisant l'opérateur ternaire du C.
Création de wavetable standards
Pour obtenir un oscillateur au contenu sonore intéressant, une technique possible consiste à utiliser des signaux périodiques spectralement riches. La plupart des synthétiseurs analogiques et numériques permettent de générer des signaux de type carré, dent de scie ou triangulaire.
Manipulation 5
Créez 3 nouvelles tables d'onde de
- un signal carré,
- un signal en dent de scie,
- un signal triangulaire.
Observez ensuite ces signaux à l'oscilloscope.
Pour gagner en souplesse dans l'utilisation des wavetable (changement à chaud de la table), il est recommandé de stocker les 4 wavetable (sinusoïde, carré, dent de scie et triangle) dans un tableau à 2 dimensions.
Structure WavetableOsc
Définition de la structure
Pour centraliser les paramètres relatifs à la gestion des wavetable, nous allons définir une structure stockant la valeur de l'amplitude du signal, de l'incrément de lecture Delta
, de l'index de position l[n]
, et de l'index de la wavetable (0
: sinusoide, 1
: carré, ...), et un booléen permettant de spécifier le type d'interpolation utilisé.
typedef struct {
float amp; // Amplitude du signal
float delta; //increment de lecture
float positionIndex; //index de lecture dans la table
uint8_t wavetableIndex; //choix de la forme d'onde
bool isInterpolated; //gestion de l'interpolation (false: ordre 0, true: ordre 1)
} WavetableOsc;
Manipulation 6
Ajoutez la définition de la structure WavetableOsc
dans votre programme.
Fonctions associées
Pour améliorer l'organisation du code, une approche possible consiste à développer des fonctions permettant de manipuler la structure WavetableOsc
.
void wavetableOscInit(WavetableOsc* osc, float amp, uint8_t wavetableIndex, bool isInterpolated); //Initialisation du contenu de la structure (positionIndex=0, delta=0)
void wavetableOscSetDelta(WavetableOsc* osc, float frequency); // Changement de l'incrément de lecture à partir de la fréquence
void wavetableOscSetAmp(WavetableOsc* osc, float amp); // Changement de l'amplitude
float wavetableOscNextSample(WavetableOsc* osc); // Calcul du prochain index de position, puis calcul et renvoie du prochain échantillon.
Manipulation 7
Ajouter les définitions puis implémentez les fonctions wavetableOscInit
, wavetableOscSetDelta
, wavetableOscSetAmp
et wavetableOscNextSample
.
References
- JUCE C++ implementation: https://docs.juce.com/master/tutorial_wavetable_synth.html