vendredi 19 avril 2019

Synthèse sonore avec Mozzi et Arduino (3)

Pour cette troisième exploration des possibilités de la bibliothèque Mozzi, nous allons faire jouer par l'Arduino un échantillon ("sample") sonore que nous aurons nous-mêmes produit. Cet échantillon peut être n'importe quel son de courte durée: une note jouée par un instrument de musique, un cri d'animal, un mot que vous aurez vous-même prononcé... Ce son, toutefois, devra être très court (1 seconde ou 2), à cause du peu de mémoire disponible dans l'Arduino Uno.

Trouver un son

Vous pouvez enregistrer le son vous-même, ou encore trouver un son dans une des nombreuses banques d'effets sonores libres de droits disponibles sur internet comme La Sonothèque, par exemple. Puisque je suis enrhumé aujourd'hui, j'ai choisi un son d'éternuement.

Préparation d'un fichier .raw avec Audacity

Le fichier audio de type .wav doit d'abord être épuré au moyen de l'indispensable logiciel (gratuit) Audacity.  Après avoir ouvert votre fichier .wav dans Audacity et, au besoin, effacé toute partie que vous ne désirez pas conserver, réglez la valeur du "Taux du projet (Hz)", dans le coin inférieur gauche de la fenêtre, à 16384.


On choisit ensuite le menu Exporter / Exporter l'audio...


Il faut ensuite modifier les options d'enregistrement: "Autres formats non-compressés", entête "RAW (header-less)" et encodage "Signed 8-bit PCM".



Transformation du fichier .raw en fichier d'entête pour Mozzi avec char2mozzi.py

Le fichier de type ".raw" que nous venons tout juste de produire au moyen d'Audacity doit ensuite être analysé par le script "char2mozzi.py", qui est fourni avec Mozzi dans le répertoire libraries/Mozzi/extra/python.

Vous placez le fichier sonore ".raw" dans le même répertoire que le script "char2mozzi.py". Avec le terminal, vous naviguez jusqu'à ce répertoire, et vous écrivez la commande suivante:

python char2mozzi.py "atchoum.raw" "atchoum.h" "echantillon" "16384"

Dans mon cas, "atchoum.raw" était le nom de mon fichier source, "atchoum.h" est le nom que je désirais donner à mon fichier texte utilisable par mozzi, "echantillon" est le nom que je désirais donner à la variable contenant les données (à l'intérieur du fichier atchoum.h), et 16384 est la fréquence d'échantillonnage.


Tel que prévu, le fichier "atchoum.h" est créé dans le même répertoire que "char2mozzi.py".


Voici le contenu du fichier "atchoum.h":


Sketch qui fait jouer notre échantillon

Finalement, voici un sketch qui fera jouer notre échantillon (il s'agit, à peu de choses près, de l'exemple "Sample" fourni avec Mozzi). Pour que ça fonctionne, le fichier "atchoum.h" doit se situer dans le même répertoire que le sketch.

Yves Pelletier   (TwitterFacebook)

dimanche 14 avril 2019

Synthèse sonore avec Mozzi et Arduino (2)

Dans le billet précédent, nous avons vu comment produire un son avec Mozzi. Cette fois, nous allons  écrire un sketch qui joue une mélodie. Ensuite, nous améliorerons progressivement ce sketch en y ajoutant de la polyphonie, puis une enveloppe ADSR.

Jouer une mélodie: la classe EventDelay

Nous voulons donc, dans un premier temps, écrire un sketch qui jouera automatiquement une mélodie. L'algorithme sera donc : jouer la première note, attendre un peu, jouer la deuxième note, attendre un peu, etc.

Mais attention: la bonne vieille fonction "delay()" est désactivée dans Mozzi. Il faut plutôt utiliser la classe EventDelay, ce qui implique de démarrer le chronométrage ("start()"), puis de vérifier périodiquement si le délai est écoulé ou non ("ready()").

Vous en avez une illustration dans le sketch ci-dessous, qui joue de façon répétitive une suite de 12 notes au rythme de 4 notes par seconde.

Pour utiliser des délais dans le sketch, il faut d'abord inclure le fichier "EventDelay.h". C'est fait à la ligne 8 du sketch.

On doit ensuite définir un objet de type "EventDelay": c'est ce que j'ai fait à la ligne 14, je l'ai baptisé "attente".

À la ligne 26, je démarre un temps d'attente de 250 millisecondes grâce à la commande "attente.start(duree);".

Ensuite, à l'intérieur d'updateControl(), je vérifie si le délai est écoulé; ça commence à la ligne 31 du sketch ("if (attente.ready())"). Cette condition deviendra vraie 250 millisecondes après le démarrage du délai.

Lorsque les 250 millisecondes sont écoulées, les lignes 32 à 38 sont exécutées: elles consistent à modifier la valeur de la fréquence de l'oscillateur (ligne 32), à incrémenter la variable "compteur" qui indique le rang de la note à jouer (ligne 33), et à redémarrer le chronomètre pour un nouveau délai de 250 millisecondes (ligne 38).


Jouer des accords (polyphonie)

Rien ne nous oblige à nous limiter à jouer une note à la fois. Le sketch ci-dessous est très similaire au précédent, sauf que nous utilisons 3 oscillateurs afin de jouer trois notes simultanément.




Enveloppe ADSR

Nos deux programmes précédents donnent un résultat qui manque un peu d'expression, puisque chaque note (ou accord) est joué avec un volume sonore égal du début à la fin.  Pour améliorer les choses, nous allons maintenant définir une enveloppe ADSR qui nous permettra de modifier le volume pendant l'exécution de la note.

"ADSR" est l'acronyme pour Attack, Decay, Sustain et Release, quatre phases qui se succèdent pendant l'exécution d'une note.

  • L'attaque (attack) est la première phase; il s'agit du temps pendant lequel le volume de la note augmente progressivement d'une valeur nulle jusqu'à la valeur maximale. Pour un son percussif, on utilise une attaque courte (le son atteint instantanément son volume maximal), alors qu'une attaque longue donnera un résultat beaucoup plus doux (le volume augmente lentement au début de la note).
  • La chute (decay) est le temps pendant lequel le volume de la note diminue afin de passer de la valeur maximale (atteinte à la fin de l'attaque) jusqu'à une valeur un peu plus faible.
  • L'entretien (sustain) est le temps pendant lequel le volume de la note demeure constant.
  • L'extinction (release) est l'étape finale, pendant laquelle le volume de la note diminue progressivement jusqu'à devenir nul.

Le sketch ci-dessous joue 5 fois la même note en utilisant chaque fois une enveloppe dont les paramètres sont différents.

Cette fois, il est important d'inclure le fichier ADSR.h; c'est fait à la ligne numéro 10.

À la ligne 31, j'ai créé un objet de type ADSR que j'ai baptisé "enveloppe".

Les caractéristiques de l'enveloppe sont réglées aux lignes 44 et 47.

"setADLevels(niveau_attaque, niveau_chute)" (ligne 44) permet de régler le volume sonore atteint à la fin de l'attaque et le volume qui sera maintenu constant pendant la phase d'entretien. Les deux paramètres peuvent prendre n'importe quelle valeur entre 0 et 255.

À la ligne 47, "setTimes(durée_attaque, durée_chute, durée_entretien, durée_extinction)" permet de définir, en millisecondes, la durée de chacune des 4 phases de l'enveloppe. Il semble nécessaire d'éviter les durées inférieures à 20 ms, qui génèrent parfois des résultats indésirables.

À la ligne 60, "update()" met l'enveloppe à jour.

Finalement, la ligne 66 retourne la multiplication de notre enveloppe et de la note jouée par l'oscillateur principal. Il faut diviser par 256 (">> 8") pour que le résultat demeure à l'intérieur des limites requises.

Vous devriez entendre 5 notes qui ne diffèrent que par les paramètres de leur enveloppe.



Résultat final

Pour terminer, voici un autre sketch qui joue une suite d'accords mais, cette fois, je leur applique une enveloppe ADSL (définie dans setUp()). Sans l'enveloppe, ça sonnait un peu comme un orgue. Maintenant, c'est plus proche d'un accordéon...



Yves Pelletier   (TwitterFacebook)

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)
Related Posts Plugin for WordPress, Blogger...