Outils pour utilisateurs

Outils du site


numeriser_jean_de_la_fontaine

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
numeriser_jean_de_la_fontaine [2022/03/07 09:47] – Simon Deplatnumeriser_jean_de_la_fontaine [2024/12/10 16:05] (Version actuelle) – Benjamin Labomedia
Ligne 121: Ligne 121:
 =====Vocalisation===== =====Vocalisation=====
  
-Maintenant que le  plus important est fait, à savoir, d'une certaine manière, avoir mis en place le contexte cérébral de ce petit Jean, il faut commencer à l'incarner.+Maintenant que le  plus important est fait, à savoir, d'une certaine manière, avoir mis en place le contexte cérébral de ce petit Jean, il faut commencer à l'**incarner**.
  
-La première étape est de pouvoir le faire dicter ses nouvelles fables, en utilisant un logiciel de synthèse sonore, en anglais Text To Speech, souvent abrévié TTS.+La première étape est de pouvoir **le faire dicter ses nouvelles fables**, en utilisant **un logiciel de synthèse sonore**, en anglais //Text To Speech//, souvent abrévié //TTS//.
  
-La ressource principale que j'ai trouvée, sur ce sujet et concernant les logiciels libres, est [[https://doc.ubuntu-fr.org/synthese_vocale|la documentation Ubuntu]]. Cependant, Manu a également trouvé un logiciel pertinent développé par une fondation bien connue : [[https://github.com/mozilla/TTS|Mozilla TTS]].+**La ressource principale** que j'ai trouvée, sur ce sujet et concernant les logiciels libres, est [[https://doc.ubuntu-fr.org/synthese_vocale|la documentation Ubuntu]]. Cependant, Manu a également trouvé un logiciel pertinent développé par une fondation bien connue : //[[https://github.com/mozilla/TTS|Mozilla TTS]]//.
  
-De manière succincte, voici les résultats auxquels j'ai abouti pour ces logiciels. Je n'ai pas essayé festival.+De manière succincte, voici les résultats auxquels j'ai abouti pour ces logiciels. Je n'ai pas essayé //festival//.
  
-Premièrement, espeak. C'est presque un artefact des anciens temps, tant la voix est robotisée. On n'y comprends pas grand chose et c'est très moche.+Premièrement, //espeak//. C'est presque un artefact des anciens temps, tant **la voix est robotisée**. On n'y comprends pas grand chose et c'est **très moche** :
  
 <code> <code>
 espeak -v fr -f text.txt espeak -v fr -f text.txt
-<\code>+</code>
  
-Concernant svoxpico, la voix est meilleure qu'espeak, ce qui n'est pas bien difficile au final. Elle hoquette quelque peu mais cela lui confère un certain charme. Par contre elle est uniquement féminine en langue française :+Concernant //svoxpico//, la voix est meilleure qu'//espeak//, ce qui n'est pas bien difficile au final. **Elle hoquette quelque peu mais cela lui confère un certain charme. Par contre elle est uniquement féminine** en langue française :
  
 <code> <code>
 pico2wave -l fr-FR -w text.wav < text.txt && play text.wav pico2wave -l fr-FR -w text.wav < text.txt && play text.wav
-<\code>+</code>
  
-**[EN CONSTRUCTION]**+Avec //Mozilla TTS//, **les résultats sont plus probants**, même s'il y a quelques cafouillis par-ci par-là. Mais là encore, **seules des voix féminines sont disponibles** : 
 + 
 +<code> 
 +tts --text "$(cat text.txt)" --model_name tts_models/fr/mai/tacotron2-DDC --vocoder_name vocoder_models/universal/libri-tts/fullband-melgan && play tts_output.wav 
 +</code> 
 + 
 +**De fait, la solution que j'ai finalement retenue se sert d'//espeak// ! Mais en conjonction avec un autre logiciel, qui s'appelle //mbrola//.** Je l'ai installé, ainsi que l'ensemble des voix françaises, via cette commande : 
 + 
 +<code> 
 +sudo apt-get install mbrola mbrola-fr* 
 +</code> 
 + 
 +Cette fois-ci, **nous utilisons //espeak// pour générer un fichier de phonèmes, puis nous utilisons les vocoders de //mbrola// pour les lire** : 
 + 
 + 
 +<code> 
 +espeak -q -s 100 -v mb/mb-fr1 -f text.txt --pho --phonout=text.pho && mbrola /usr/share/mbrola/fr6/fr6 text.pho text.wav && play text.wav 
 +</code> 
 + 
 +Étrangement, cela ne marche pas avec l'argument //-v fr// d'//espeak//, il faut utiliser //-v mb/mb-fr1// à la place. 
 + 
 +**Pour changer de vocoder**, il faut **spécifier le chemin de celui-ci** dans la commande //mbrola//. Dans le cas précédent, c'est le chemin ///usr/share/mbrola/fr6/fr6// qui le spécifie. 
 + 
 +Cette fois-ci, nous avons **une voix masculine d'assez bonne qualité**, ce qui me satisfait. 
 + 
 +=====Quoi ma gueule ?===== 
 + 
 +Il faut maintenant associer la voix à un visage. Pour ce faire, **un petit tour sur le net et un copier-coller** d'un tableau représentatif de l'écrivain. 
 + 
 +Mais ce n'est pas tout, j'aimerai bien pouvoir **l'animer** un tantinet. Mon idée était de singer l'intelligence artificielle et la culture française **en transformant Jidéhéléf en pantin**. À l'aide de //[[https://www.gimp.org/|GIMP]]//, j'ai donc séparé le tableau original en plusieurs parties faciles à animer, **à la manière d'un système de calques** : 
 + 
 +{{ ::jdlfgimp.png?1000 |}} 
 + 
 +=====LaFontaine.exe===== 
 + 
 +Il est maintenant temps de regrouper ensemble les pensées, la voix et la frimousse de Jean ! 
 + 
 +J'avais en premier pensé utiliser le moteur de jeu vidéo libre //[[https://godotengine.org/|Godot]]// pour ce faire, car il intègre des outils d'animation simple à utiliser. Cependant, **j'ai finalement opté pour //[[https://tutos.labomedia.org/books/musique-assistee-par-ordinateur/page/quest-ce-que-supercollider|SuperCollider]]//** pour faire cela, notamment car c'est le logiciel le plus simple que je connaisse pour implémenter le déplacement de la mâchoire de Jean en fonction des conneries qu'il raconte. 
 + 
 +Voici le code du projet : 
 + 
 +<code> 
 +( 
 + 
 +var win = Window.new( 
 + "La Fontaine", 
 + Rect( 1000, 500, 200, 200 ) ); 
 + 
 +var filePath = thisProcess.nowExecutingPath.dirname ++ "/text.wav"; 
 + 
 +var lock = false; // Empêche de regénérer une fable tant que la précédente n'est pas finie 
 +var lockDelay = 1; // Délai supplémentaire de restriction 
 + 
 +var stringCmd; // Variable qui va stocker l'appel Bash à la génération du texte et du fichier son 
 + 
 +var view = UserView(); 
 + 
 +var imgJean = Image.new( 
 + thisProcess.nowExecutingPath.dirname ++ "/JEAN.png" ); 
 +var imgEmpty = Image.new( 
 + thisProcess.nowExecutingPath.dirname ++ "/emptyeyes.png" ); 
 +var imgChin = Image.new( 
 + thisProcess.nowExecutingPath.dirname ++ "/lafontaine_chin.png" ); 
 +var imgPupille = Image.new( 
 + thisProcess.nowExecutingPath.dirname ++ "/pupille.png" ); 
 + 
 +var textSpeed = 120; // Vitesse d'élocution 
 +var voiceTag = "fr3"; // Vocoder utilisé 
 + 
 +var jawOffset = 0; // Décalage de la mâchoire 
 + 
 +var eg = Point( 390, 512 ); // Position de l'oeil gauche 
 +var ed = Point( 385, 415 ); // Position de l'oeil droit 
 + 
 +var egtarget = Point( 390, 512 ); // Position de référence l'oeil gauche 
 +var edtarget = Point( 385, 415 ); // Position de référence l'oeil droit 
 + 
 +if( ~masterIn == nil, { // Bus master 
 + ~masterIn = Bus.audio( s, 1 ) } ); 
 + 
 +if( ~ampBus == nil, { // Bus de valeur d'amplitude 
 + ~ampBus = Bus.control( s, 1 ) } ); 
 + 
 + 
 +SynthDef( \master, { 
 + 
 + var sound = In.ar( ~masterIn, 1 ); 
 + var amp = Amplitude.kr( sound ); 
 + var panSound = Pan2.ar( sound ); 
 + 
 + Out.kr( ~ampBus, amp ); 
 + Out.ar( 0, panSound ); 
 + 
 +} ).play; 
 + 
 + 
 +// Construction de la commande Bash 
 +voiceTag = voiceTag ++ "/" ++ voiceTag; 
 + 
 +stringCmd = "cd ~/CNFS/bestiaireia/La_Fontaine/;"; 
 +stringCmd = stringCmd ++ "./mon_env/bin/python3 textgen.py > text.txt;"; 
 +stringCmd = stringCmd ++ "espeak -q -s " ++ textSpeed.asString ++ " -v mb/mb-fr1 -f text.txt --pho --phonout=text.pho;"; 
 +stringCmd = stringCmd ++ "mbrola /usr/share/mbrola/" ++ voiceTag ++ " text.pho text.wav"; 
 + 
 +// Fonction déclenchée à l'input 
 +win.view.keyDownAction = { arg view, char, modifiers, unicode,keycode; 
 + 
 + if( lock == false, { 
 + if( unicode == 32, { 
 + 
 + stringCmd.systemCmd; 
 + lock = true; 
 + 
 + b = Buffer.read( 
 + s, 
 + filePath, 
 + action: { { lock = false; }.defer( b.numFrames * ( 1.0 / b.sampleRate ) ) } 
 + ); 
 + 
 + SynthDef( "playbuf",{ 
 + Out.ar( ~masterIn, 
 + PlayBuf.ar( 
 + numChannels: 1, 
 + bufnum: b.bufnum, 
 + rate: BufRateScale.kr( b.bufnum ) * 1, 
 + trigger: 1, 
 + startPos: 0, 
 + loop: 0, 
 + doneAction: Done.freeSelf ), 
 + 0.0 
 + ) 
 + } ).play; 
 + } ); 
 + } ); 
 +}; 
 + 
 +win.view.layout_( HLayout() ); 
 + 
 +win.view.layout.add( view ); 
 + 
 +// Fonction d'affichage graphique : 
 +view.drawFunc = { 
 + Pen.drawImage( Point( 1920 - 1284 / 2 + 350, 1080 - 1000 / 2 + ( 1000 - 437 - 175 ) ), imgEmpty, operation: 'sourceOver', opacity:1); 
 + 
 + 
 + 
 + Pen.drawImage( Point( 1920 - 1284 / 2 + eg.x, 1080 - 1000 / 2 + eg.y ), imgPupille, operation: 'sourceOver', opacity:1); // Oeil droit 
 + 
 + Pen.drawImage( Point( 1920 - 1284 / 2 + ed.x, 1080 - 1000 / 2 + ed.y ), imgPupille, operation: 'sourceOver', opacity:1); // Oeil gauche 
 + 
 + Pen.drawImage( Point( 1920 - 1284 / 2, 1080 - 1000 / 2 ), imgJean, operation: 'sourceOver', opacity:1); 
 + Pen.drawImage( Point( 1920 / 2 - 132 + jawOffset, 1080 / 2 - 83 ), imgChin, operation: 'sourceOver', opacity:1); 
 +}; 
 + 
 + 
 +// Routine qui associe l'amplitude sonore à la position de la mâchoire : 
 +Routine( { 
 + 
 + var rand; 
 + var randN = 10; 
 + 
 + loop { 
 + 
 + ~ampBus.get( { | val | jawOffset = val.linlin( 0.0, 1.0, 0, 100 ); } ); 
 + jawOffset = jawOffset.asInteger; 
 + 
 + if( lock == true, { 
 + rand = randN.rand; 
 + if( rand == 0, { eg.x = eg.x + 1 } ); 
 + if( rand == 1, { eg.x = eg.x - 1 } ); 
 + if( eg.x < 385, { eg.x = 385 } ); 
 + if( eg.x > 410, { eg.x = 410 } ); 
 + 
 + rand = randN.rand; 
 + if( rand == 0, { eg.y = eg.y + 1 } ); 
 + if( rand == 1, { eg.y = eg.y - 1 } ); 
 + if( eg.y < 488, { eg.y = 488 } ); 
 + if( eg.y > 524, { eg.y = 524 } ); 
 + 
 + rand = randN.rand; 
 + if( rand == 0, { ed.x = ed.x + 1 } ); 
 + if( rand == 1, { ed.x = ed.x - 1 } ); 
 + if( ed.x < 380, { ed.x = 380 } ); 
 + if( ed.x > 395, { ed.x = 395 } ); 
 + 
 + rand = randN.rand; 
 + if( rand == 0, { ed.y = ed.y + 1 } ); 
 + if( rand == 1, { ed.y = ed.y - 1 } ); 
 + if( ed.y < 400, { ed.y = 400 } ); 
 + if( ed.y > 430, { ed.y = 430 } ); 
 + } ); 
 + 
 + if( lock == false, { 
 + if( eg.x < egtarget.x, { eg.x = eg.x + 1 } ); 
 + if( eg.x > egtarget.x, { eg.x = eg.x - 1 } ); 
 + if( eg.y < egtarget.y, { eg.y = eg.y + 1 } ); 
 + if( eg.y > egtarget.y, { eg.y = eg.y - 1 } ); 
 + 
 + if( ed.x < edtarget.x, { ed.x = ed.x + 1 } ); 
 + if( ed.x > edtarget.x, { ed.x = ed.x - 1 } ); 
 + if( ed.y < edtarget.y, { ed.y = ed.y + 1 } ); 
 + if( ed.y > edtarget.y, { ed.y = ed.y - 1 } ); 
 + } ); 
 + 
 + { view.refresh }.defer; 
 + 
 + (1/30).wait 
 + }; 
 +} ).play; 
 + 
 +win.background = Color.black; // Set main widow background as black 
 +win.fullScreen; 
 + 
 +win.front; 
 + 
 +// À la fermeture, libération de la mémoire : 
 +CmdPeriod.doOnce( { 
 + Window.closeAll; 
 + s.freeAll; 
 + imgChin.free; 
 + imgJean.free; 
 + imgEmpty.free; 
 + imgPupille.free; 
 +} ); 
 + 
 +) 
 +</code> 
 + 
 +Sans rentrer trop dans le détail, quelques points sont intéressants à aborder sur l'algorithme. 
 + 
 +===Corrélation de l'audio et de l'affichage graphique=== 
 + 
 +L'idée principale était d'**utiliser SuperCollider afin de relier le volume sonore ( l'amplitude ) du fichier son au déplacement de la mâchoire**. Pour mettre cela en place, j'ai d'abord décidé d'utiliser //espeak// et //mbrola// de telle manière qu'ils produisent non pas un son direct, mais **un enregistrement audio**. 
 + 
 +Au lancement du programme, **un synthé maître est créé**, qui utilise un //Ugen IN// afin de pouvoir bénéficier d'un son d'entrée créé //a posteriori//. Lors de la génération d'une fable, **le fichier son résultant est chargé dans SC**, puis **envoyé dans l'entrée du synthé maître**. **Important** : le synthé créé lors de la génération est supprimé lors de la fin de la lecture grâce au paramètre //doneAction: Done.freeSelf// du //PlayBuf//. Le cas échéant, les //UGens// créés pour jouer le sample resteraient présent en mémoire et risqueraient de rapidement saturer l'ordinateur. 
 + 
 +Dans le synthé maître, avant de **le passer en stéréo et de l'envoyer vers la carte son**, **le son est en premier lieu analysé grâce à l'//UGen Amplitude//, qui redirige son résultat d'analyse dans un //Bus//** créé au préalable. 
 + 
 +C'est **une //Routine// qui**, à intervalle régulier, **récupère la valeur d'amplitude au sein du //Bus//**, modifie la valeur de référence de la position de la mâchoire puis lance un appel de rafraîchissement de l'interface graphique. On utilise //{}.defer// car **la mise-à-jour graphique ne peut s'effectuer dans le contexte audio**. 
 + 
 +La méthode d'accès à une valeur présente dans un //UGen//, c'est-à-dire une information qui passe du serveur à //sclang//, [[https://scsynth.org/t/getting-values-out-of-ugens/489|est discutée dans ce post sur le forum de SC]], et [[http://modularbrains.net/dx490a/DX490A_su2010_02.1_%5BServer-language_communication%5D.html|approfondie dans ce tutoriel]]. 
 + 
 +===Commandes BASH depuis SC et synchronicité=== 
 + 
 +**Sous //Linux//, il est très simple d'envoyer des commandes //BASH// dans le terminal depuis //SC//.** Pour ce faire, **on utilise une méthode sur une chaîne de caractères qui contient la commande à exécuter.** Pour exécuter plusieurs commandes, on les sépare par des points virgules : 
 + 
 +<code> 
 +"ls".unixCmd 
 +</code> 
 + 
 + 
 +<code> 
 +"cd Dossier/;ls".systemCmd 
 +</code> 
 + 
 +Dans //SuperCollider//, on distingue **deux types de fonctions : les commandes //synchrones//, et les commandes //asynchrones//**. En règle générale, **une commande synchrone bloque l'éxecution du code dans //sclang// le temps que le serveur se mette à jour.** Cela permet par exemple d'éviter, après avoir ajouté une //SynthDef//, de l'appeler avant qu'il soit effectivement initialisé. 
 + 
 +Dans notre cadre, la génération de la fable, et dans une moindre mesure la génération du fichier audio, prend un peu de temps à l'ordinateur. **Nous utilisons donc .//systemCmd//, qui est la fonction synchrone d'accès au terminal.** 
 + 
 +Le cas échéant, en utilisant .//unixCmd//, le fichier audio est chargé par SuperCollider avant que la nouvelle fable soit générée, c'est donc la fable précédente qui est récitée... 
 + 
 +Ici, un problème posé est que la suspension d'activité dans //sclang// via **l'appel à la fonction synchrone .//systemCmd// arrête également la //Routine//** responsable de la mise-à-jour graphique, **ce qui bloque l'animation**. J'ai résolu ce problème en n'animant pas la partie d'attente au cours de laquelle aucune fable n'est récitée. Le blocage de la //Routine// est donc invisible à l'utilisateur, même s'il existe. 
 + 
 +Dans l'absolu, il aurait fallu plutôt utiliser .//unixCmd//, et mettre en place un retour depuis le terminal afin de lancer l'enregistrement audio après la génération du nouveau fichier. Tant qu'on y est, il est à noter que lorsque l'image est immobile, la //Routine// contiue de tourner, ce qui n'est pas optimal mais pas catastrophique par ailleurs. 
 + 
 +=====La Suite ?===== 
 + 
 +À ce stade, **le prototype est fonctionnel mais quelques améliorations sont possibles** : 
 + 
 +  * Refaire l'algorithme de Routine pour qu'il ne reste activé que le temps de l'animation. 
 +  * Mettre l'image de Jean de La Fontaine en plein écran, mais cela dépend de la résolution du moniteur que nous utiliserons pour l'installation. 
 +  * Ajouter un retour visuel de la fable générée, parce que la voix n'est pas toujours claire, et pour les publics malentendants. 
 +  * Réaliser le dispositif physique de l'installation, avec un joli cadre en bois, et tout le toutim. 
 +  * Trouver un moyen de générer des fables plus longues. 
 + 
 +{{tag> bestiaire_ia tal ia}}
numeriser_jean_de_la_fontaine.1646646456.txt.gz · Dernière modification : de Simon Deplat