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 10:57] Simon Deplatnumeriser_jean_de_la_fontaine [2022/03/07 16:10] (Version actuelle) Simon Deplat
Ligne 170: Ligne 170:
 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. 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 GIMP, j'ai donc séparé le tableau original en plusieurs parties faciles à animer :+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 |}} {{ ::jdlfgimp.png?1000 |}}
  
-**[EN CONSTRUCTION]**+=====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 }}
numeriser_jean_de_la_fontaine.1646650656.txt.gz · Dernière modification : 2022/03/07 10:57 de Simon Deplat