dimanche 7 avril 2019

Synthèse sonore avec Mozzi et Arduino (1)


Aujourd'hui, je vous propose de jouer un peu avec Mozzi, une bibliothèque dédiée à la synthèse sonore sur Arduino.

Bien sûr, nous avons eu de multiples occasions de faire chanter notre Arduino au moyen de la fonction Tone(), mais cette méthode simple nous contraint à utiliser un signal carré d'amplitude constante, n'ayant le contrôle que sur la hauteur et la durée des notes (pour un résultat qui rappelle inévitablement la musique des jeux vidéos du début des années 1980).

Avec Mozzi, vous transformez votre Arduino en véritable synthétiseur, en contrôlant non seulement la fréquence du son généré, mais aussi son timbre et son enveloppe, pour des résultats beaucoup plus variés.

Matériel

Pour cette première exploration, j'ai utilisé un modeste Arduino Uno. Mozzi fonctionne également sur des cartes plus rapides comme la Blue Pill (STM32Duino) ou l'ESP8266, ce qui pourrait se révéler particulièrement approprié pour des projets plus élaborés.

Par défaut, le signal sonore est généré sur la broche 9 de l'Arduino. J'y a branché une paire d'enceintes multimédias (le signal généré est beaucoup plus faible que celui produit avec la fonction Tone(), et il est nécessaire de l'amplifier pour bien l'entendre). La pointe de la prise jack est relié à la broche 9 de l'Arduino, alors que le manchon est relié à la masse (GND).



Installation de la bibliothèque

Vous trouverez sur cette page la version la plus récente de Mozzi (cliquez sur le bouton vert "Get the most recent release"). 

Après l'installation de la bibliothèque dans l'IDE Arduino, je vous encourage à faire l'essai de quelques-uns des exemples qui l'accompagnent, question de vous assurer que tout est bien fonctionnel, en plus de constater la diversité des sons possibles.

Sketch minimal

Commençons par analyser l'exemple "Sinewave" (chemin d'accès: Exemples / Mozzi / 01.Basics / Sinewave ) qui constitue le strict minimum: la production d'un son sinusoïdal de 440 Hz (pas particulièrement agréable à l'oreille, je dois l'admettre).




Les premières lignes du sketch font référence à quelques fichiers indispensables: "MozziGuts.h" est la bibliothèque elle-même, et vous devez donc toujours l'inclure dans votre sketch. "Oscil.h" est une description d'oscillateur, alors que "sin2048_int8.h" est un tableau comportant 2048 entiers décrivant une oscillation complète d'un sinus (puisque la rapidité est primordiale lorsqu'on synthétise du son, l'Arduino utilisera les valeurs pré-calculées de ce tableau plutôt que de les calculer lui-même au gré de ses besoins).

C'est intéressant d'aller jeter un oeil sur le dossier "/libraries/Mozzi/tables": on y trouve une soixantaine de tableaux décrivant des ondes sinusoïdales, carrées, triangulaires, en dent de scie, du bruit blanc, etc.  


À la ligne 22, nous définissons un oscillateur nommé "aSin" qui, grâce aux données incluses dans le fichier "sin2048_int8.h", oscillera de façon sinusoïdale. "SIN2048_NUM_CELLS" est défini dans le fichier "sin2048_int8.h": il s'agit du nombre de valeurs comprises dans le tableau (on aurait pu écrire tout simplement 2048; il s'agit toujours d'une puissance de 2). AUDIO_RATE est le rythme auquel l'onde sonore sera générée, "aSin" est le nom qui a été donné à cet oscillateur dans ce sketch, et "SIN2048_DATA" est le nom du tableau de données dans le fichier "sin2048_int8.h".

La ligne 25 définit le "CONTROL_RATE", c'est à dire à quel rythme l'Arduino vérifiera si un changement doit être effectué dans les paramètres du son (si la position d'un potentiomètre a changé, par exemple). Il est de 64 Hz par défaut, et doit être une puissance de 2.


L'instruction "startMozzi(CONTROL_RATE)" doit obligatoirement se trouver à l'intérieur du setup(). Ici, on règle aussi la fréquence de l'oscillateur à 440 Hz (ligne 30).


Deux parties qu'on ne retrouve pas dans un sketch Arduino classique sont nécessaires dans un sketch pour Mozzi: updateControl() comporte les instructions permettant de modifier les paramètres du son (par exemple: suite à l'enfoncement d'un bouton ou la rotation d'un potentiomètre). Il est exécuté 64 fois par seconde.  updateAudio() permet la génération du son lui-même, et est exécuté plus de 16000 fois par seconde. Dans ce sketch, on se contente d'émettre la prochaine valeur de notre oscillateur sinusoïdal, mais on pourrait aussi lui faire subir des transformations, l'additionner au signal d'un autre oscillateur, etc.

On retrouve finalement le loop(), qui doit obligatoirement contenir l'instruction audioHook(). Dans un souci d'optimiser la rapidité d'exécution, on doit éviter d'ajouter d'autres instructions dans cette partie.

Combinaison de deux oscillateurs

Amusons-nous maintenant à modifier cet exemple, afin d'explorer progressivement les possibilités de Mozzi.

Le sketch ci-dessous est une version légèrement modifiée de l'exemple Sinewave: il produit un effet de trémolo en multipliant le signal sonore sinusoïdal de 440 Hz par un deuxième signal sinusoïdal dont la fréquence est beaucoup plus lente (16 Hz). Remarquez le décalage à droite (>>8) à l'intérieur d'updateAudio(): le signal de chacun des deux oscillateurs peut prendre une valeur maximale de 255, ce qui implique que la multiplication des deux signaux peut atteindre une valeur de 2552, qu'il faut diviser par 255 afin de ne pas dépasser le maximum imposé. Le décalage de 8 positions vers la droite constitue une division par 255, mais elle s'effectue plus rapidement.  (Au besoin, vous pouvez consulter cet article sur les opérations bit à bit)




Modification de la fréquence du son généré, avec un potentiomètre

Pour rendre les choses un tout petit peu plus interactives, je branche un potentiomètre à l'entrée A0 de l'Arduino.


Le sketch ci-dessous génère une onde en dent de scie dont la fréquence est contrôlée par le potentiomètre.  Puisque la fréquence doit être modifiée en cours d'exécution, elle est donc réglée à l'intérieur de updateControl() plutôt que dans setup(). Remarquez l'utilisation de l'instruction "mozziAnalogRead()" qui doit être préférée à notre classique "analogRead()" (car plus rapide). J'ai ajouté 50 à la valeur retournée par "mozziAnalogRead()" car une fréquence de 0 Hz ne serait pas d'un grand intérêt.




Modification du volume sonore, avec un potentiomètre

Le volume sonore est un autre paramètre susceptible d'être contrôlé au moyen d'un potentiomètre, particulièrement lorsque vous désirez moduler le signal principal au moyen d'un deuxième oscillateur.  Le sketch ci-dessous génère une onde triangulaire dont l'amplitude est contrôlée par un potentiomètre.

C'est encore à l'intérieur de updateControl() qu'on vérifie l'état du potentiomètre. Puisque le volume peut prendre une valeur maximale de 255 alors que mozziAnalogRead() retourne une valeur située entre 0 et 1023, on effectue une division par 4. Pour plus de rapidité, toutefois, cette division par 4 est effectuée au moyen d'un décalage à droite (>> 2).

À l'intérieur de updateAudio(), la valeur de l'oscillateur triangulaire est multipliée par la valeur qui dépend du potentiomètre, puis divisée par 256 au moyen d'un autre décalage à droite (>> 8). Par exemple, si la variable "volume" prend la valeur 100, l'opération "(leSon.next() * volume) >> 8" revient à multiplier par 100/256 les valeurs provenant du tableau de données.



Il reste encore beaucoup de choses à raconter concernant Mozzi...un deuxième billet est en préparation.

Yves Pelletier   (TwitterFacebook)

2 commentaires:

  1. Merci pour cette très intelligible introduction à la synthèse musicale sur Arduino.

    RépondreSupprimer
  2. Bonjour, je souhaite reproduire les bits touches de téléphone (DTMF). Est-ce possible avec cette bibliothèque?

    RépondreSupprimer