J’ai développé la première version de cette bibliothèque, baptisée à l’époque Taupistool pendant mon doctorat. Elle répondait à une problématique très spécifique, à savoir mon obligation de développer plusieurs fois la même application, mais pour des logiciels différents. En effet, comme je l’ai évoqué en 1.2.3, durant ma thèse, j’ai développé un module pour le logiciel Maya dans le cadre de la création d’un effet visuel, puis j’ai dû adapter ce même module à un autre moteur ou logiciel (Figure 48).
Mon idée était alors de passer par des passerelles logicielles qui feraient la correspondance entre le logiciel hôte et ma bibliothèque. Ainsi, pour chaque logiciel hôte, il y avait une version de « traduction » nommée TT_2_nom_du_logiciel_hote dans laquelle j’implémentais une structure informatique appelée Adaptateur (Gamma et al. 1994, pages 139-150), ce qui me permettait de faire correspondre la manière d’utiliser un objet informatique à un autre objet informatique. De ce fait, imaginons un objet de ClasseA qui dispose d’une fonction je_mange() et un objet de ClasseB qui dispose d’une fonction je_me_nourris() : alors l’adaptateur ClasseB_2_ClasseA, adapte l’interface de ClasseB afin de me permettre d’utiliser ClasseB de la même manière que ClasseA. En effet ClasseB_2_ClasseA aura une fonction je_mange() qui appellera la fonction je_me_nourris() de la ClasseB (Figure 49).
En outre, j’avais créé une structure de fichier appelé TTML. Ce fichier était basé sur le langage de balisage XML et me permettait de sauvegarder les valeurs de mes différentes entités informatiques. Ainsi, je pouvais par exemple sauvegarder le comportement d’un banc de poissons ou d’un papillon que j’avais jugé intéressant précédemment.
Les Taupistool m’ont permis d’accélérer ma vitesse de développement, mais je me rendais compte que le développement au fur et à mesure de mes besoins allait devenir contreproductif, car je passais plus de temps à écrire des adaptateurs que faire réellement de la création.
Unity et le passage au c#
Lors du développement du projet OutilNum, nous avons décidé d’utiliser le moteur de jeu Unity 3D. Ce moteur repose sur le langage C#, mais il est toujours possible de programmer en C++ des plug-ins de plus bas niveau. Ceci fut l’occasion du démarrage du projet AKeNe, en commençant par le changement de nom. En effet Taupistool était un nom trop personnel pour une bibliothèque ouverte que je voulais utilisable par toute personne souhaitant collaborer au projet. C’est ainsi qu’en hommage à l’installation Les Pissenlits de Marie Hélène Tramus, Michel Bret et Edmond Couchot, j’ai décidé de l’appeler AKeNe, comme le nom des graines qui s’envolent lorsque nous soufflons sur cette fleur. Ceci souligne aussi ma volonté de dissémination de cette bibliothèque.
La partie la plus conséquente de la programmation du code fut réalisée en C#. Le code reposait ainsi fortement sur les mécanismes du moteur de jeu Unity 3D. Ce choix a été assumé pour me permettre de le rendre plus accessible aux étudiants et ainsi de m’en servir comme support pédagogique, en particulier pour mon cours d’intelligence artificielle appliquée au jeu vidéo. C’est ainsi que 4 sujets de Projet de Fin d’Études d’étudiants de l’école d’ingénieur tunisienne ISAMM furent réalisés et intégrés à la bibliothèque[1].
Cette version d’AKeNe a été le support bien sûr de l’application OutilNum, mais aussi de l’installation Lucky première version, Between the lines et des deux premiers ateliers IDEFI-CréaTIC. De plus, certains modules ont été utilisés pour des projets de Master 2 du département ATI, comme le module de logique floue que Michèle Quéré utilisa pour contrôler les interactions d’un tigre 3D interactif[2].
Cette imbrication forte avec le moteur Unity 3D a néanmoins eu deux inconvénients de taille : le premier était de lier trop fortement AKeNe à ce moteur, ce qui faisait perdre la vocation première d’interopérabilité de la bibliothèque AKeNe, en contrepartie cela permettait de pouvoir nous appuyer plus fortement sur le travail des nombreux développeurs qui programmaient sur Unity ; le second inconvénient, et de mon point de vue le problème majeur, était de développer en langage C# d’un plus haut niveau que le C++. Si ceci a un avantage de taille en termes de pédagogie et de vitesse de programmation, il introduit une certaine distance avec l’architecture de l’ordinateur et en particulier la gestion de la mémoire, ainsi que la vitesse d’exécution du programme.
Le redémarrage C++/Unreal
En 2015, à l’arrêt du projet OutilNum et avec l’annonce de la nouvelle politique tarifaire de Unreal et de Unity[1], j’ai décidé de changer de moteur pour passer sur Unreal. Ceci a rendu caduc tout le développement mené durant les années précédentes. J’ai tiré trois conclusions de cette excursion dans le moteur Unity :
- Les personnes avec qui je collaborais n’ont pas forcément le même souci de qualité de code et d’architecture que moi ;
- Plus on est lié à un logiciel, plus il nous sera difficile de changer de logiciel par la suite ;
- Un développeur seul n’est pas en mesure de maintenir au niveau une bibliothèque en comparaison d’équipes complètes de programmeurs dédiés.
Pour résoudre le premier problème, j’ai isolé ce qui est critique et donc accessible à des programmeurs expérimentés (le noyau) et ce qui est du domaine de la « petite programmation » (ou scripting), qui est du domaine d’un projet spécifique, ou ce à vocation à être réutilisé pour du développement futur.
Unreal dispose d’un avantage de taille pour permettre de séparer ce qui est du domaine du noyau et ce qui est du scripting. En effet, ce moteur est basé sur la même approche, une approche « programmation lourde » reposant sur le C++ et une approche « scripting » reposant sur un système de programmation graphique.
Le deuxième point m’a poussé à revenir aux fondamentaux des Taupistools décrits en 7.1.1, à savoir : m’affranchir, autant que possible, du noyau du logiciel hôte, afin de ne pas perdre tout mon travail de développement, comme ce fut le cas dans ma transition de Unity vers Unreal. A contrario, je souhaitais éviter de devoir redévelopper pour chaque logiciel mes propres passerelles, tâche complexe à mener à bien pour un développeur seul, d’autant plus que je ne souhaitais pas me limiter au développement d’une librairie de programmation.
Pour le troisième point, j’ai décidé de m’appuyer le plus possible sur des bibliothèques de programmation, ou des logiciels tiers afin de ne pas avoir à tout développer. Pour ce faire, j’ai décidé de m’appuyer au maximum sur des passerelles de communications entre les logiciels, en créant mon protocole de communication autour de protocoles déjà existants (à savoir le protocole OSC).
Malheureusement, pour l’instant, je n’ai pas du tout eu le temps de réintégrer tous les développements passés, par exemple les réseaux bayésiens, la logique floue, ou les Hidden Markov Model. En outre, j’ai perdu beaucoup de temps sur des problèmes annexes d’architectures afin d’être sûr de ne pas entendre les premières strophes du poème de Kipling résonner de nouveau dans ma tête comme ce fut le cas lors de ce redémarrage de changement de moteur :
Si tu peux voir détruit l’ouvrage de ta vie
Et sans dire un seul mot te mettre à rebâtir,
Ou perdre en un seul coup le gain de cent parties
Sans un geste et sans un soupir
…
Tu seras un homme, mon fils.
1.1.1. Réfléchir en Node
Il y a donc eu un certain nombre d’étapes entre ma première version des Taupistool et la version actuelle d’AKeNe. De ces différentes évolutions, j’ai retenu l’idée d’être le moins possible dépendant d’un unique logiciel. Pour cela, je sépare ce qui est du domaine du développement du cœur de la bibliothèque et ce qui est du domaine de la mise en application dans le projet artistique. Je réserve l’accès au cœur de la bibliothèque aux personnes qui disposent des compétences nécessaires.
Pour réaliser ce projet, il me fallait avant tout réfléchir en termes d’architecture et voir comment AKeNe pouvait s’intégrer facilement dans l’architecture des logiciels hôtes. Au-delà de l’intégration logicielle, l’idée est d’engranger le développement d’autres développeurs contribuant à AKeNe, pour ne plus redévelopper sans arrêt les mêmes outils[1]…
Il convient tout d’abord d’observer les architectures des différents logiciels utilisés dans notre domaine. Elles reposent de plus en plus fréquemment sur une logique nodale, c’est-à-dire une approche cybernétique qui consiste à interconnecter des processus entre eux, assimilée à des boites noires. Ainsi Unreal, Motion Builder, Maya, Houdini, Nuke, TACTIC ou même la future version de Blender[2] reposent sur ce genre de mécanisme.
J’étais pour ma part déjà convaincu par cette architecture, bien avant mon intégration à ATI, après la lecture du livre Strategies for Real-Time System Specification (Hatley et Pirbhai 1988). J’avais lu une première fois ce livre, par hasard au lycée et, même si je n’étais pas en mesure de comprendre toutes les implications qu’entraînait cette architecture, il a influencé fondamentalement ma manière de programmer. Ainsi, même si son propos est plus large que notre domaine, cet ouvrage m’avait fait entrevoir la puissance d’une logique reposant sur des « nœuds interconnectés ».
Ma réflexion sur ce modèle m’amène à la conclusion suivante : je me dois de penser mon architecture comme pouvant s’interconnecter aux nodes des différents logiciels. Ceci me permettra d’isoler ce que je considère comme faisant partie du noyau et ce que les utilisateurs pourront plus simplement utiliser. Pour cela, il me restait à définir comment interconnecter AKeNe aux nodes des différents logiciels. Je me suis inspiré de l’architecture de Maya, dont les nodes reposent sur un concept particulier de data block (Gould 2003).
(a) (b)
(c) (d)
Figure 50 : (a) Un graphe de Unreal ; (b) un graphe de Motion-builder ; (c) un graphe de Maya ; (d) un graphe de Houdini.
Pour expliquer les data block, je vais reprendre une image que je donne en cours. Imaginons que je veuille envoyer quelque chose à quelqu’un, par exemple un livre. Je mets ce livre dans un colis que je poste par la suite. À partir de ce moment, le colis est géré par des postiers qui l’amènent à destination. Or, les facteurs n’ont pas à connaitre le contenu du colis, ni à savoir lire le livre : ils n’ont qu’une seule mission : acheminer le paquet. Une fois le paquet arrivé à destination, mon destinataire n’aura plus qu’à ouvrir le colis pour prendre le livre. En outre, si je désire envoyer une lettre ou un cadeau divers, j’appliquerai la même méthode. En conséquence, le facteur effectuera l’opération de la même manière, quel que soit le contenu du colis.
J’ai donc décidé d’implémenter ce mécanisme appelé boxing/unboxing (Leroy 1997) dans ma bibliothèque. Ainsi, nous avons un objet AKN_Node, qui dispose de sorte de boites aux lettres (les AKN_Plug), dans lequel nous pouvons mettre ou récupérer des colis (les AKN_Box). Ensuite, lorsque le logiciel hôte veut transférer des données à un objet d’AKeNe, il met ces données dans un AKN_Box[3] ainsi que le type de la donnée qu’il poste ensuite dans un AKN_Plug. Enfin, si pour fonctionner le AKN_Node est obligé d’ouvrir l’AKN_Box, alors il l’ouvre et regarde la donnée ; s’il n’en a pas besoin, il se contente de faire circuler la boite en accord avec son programme sans l’ouvrir.
1.1.2. Une logique de greffon
Il nous est très facile de créer un node pour un logiciel particulier qui englobera notre AKN_Node. Ainsi, le node du logiciel prend les données de ses entrées, les met dans une ou plusieurs AKN_Box, puis les donne à un AKN_Node, qui effectue le traitement en utilisant les outils d’AKeNe. Notre AKN_Node retourne ensuite les données dans des AKN_Box ; le node du logiciel les ouvre et prend les données qu’elle envoie au logiciel hôte par ses sorties (Figure 51).
Je ne présente ici que le principe général. En effet, il existe quelques mécanismes particuliers que j’ai implémentés, comme le fait que les AKN_Box disposent d’un mécanisme afin d’éviter que deux entités accèdent en même temps au contenu de la boite, ou le fait qu’il est possible de réserver un AKN_Plug pour déposer une AKN_Box dans le futur, ou encore la possibilité de créer pour un AKN_Plug une liste d’attente d’AKN_Box à traiter.
Un informaticien pourrait mettre en avant qu’ouvrir une AKN_Box, y mettre une valeur, la fermer et la poster dans le AKN_Plug peut ralentir le processus. Je ne le contredirai pas, mais pour le moment cela n’a pas été handicapant (voire notable). Si je m’aperçois dans le futur que le dispositif nécessite d’être optimisé, j’ai quelques idées. Par exemple, je pourrais mettre en action l’utilisation des algorithmes de sérialisation contenue dans la bibliothèque de programmation Boost (Ramey s. d.).
Figure 51 : Intégration d’un AKN_Node dans le logiciel hôte.
Ainsi, l’intégration entre le logiciel hôte et AKeNe est relativement discrète. S’il faut encore développer le node passerelle du logiciel hôte, je pense qu’il y aurait moyen d’automatiser ce processus dans le futur, mais pour le moment ce développement n’est pas ma priorité.
1.1.3. Une logique de transmission de données
AKeNe partage la même philosophie que son ancêtre Taupistool, à savoir agir comme ce que les informaticiens appellent le « code glue », c’est-à-dire un code qui sert à faire le lien entre plusieurs programmes ou librairies de programmation. L’idée est de capitaliser le code, de sorte que nous ayons déjà programmé le node logiciel qui enrobe notre AKN_Node. De plus, si nous avons créé dans AKeNe les objets qui enveloppent les fonctionnalités qui nous intéressent dans une librairie spécifique, il nous sera très rapide d’effectuer un portage vers un autre logiciel (Figure 52).
Figure 52 : AKeNe comme médium de communication entre logiciel hôte et librairie.
Pour comprendre le fonctionnement d’AKeNe, nous allons étudier comment le module de reconnaissance vocale — utilisé pour Lucky, OutilNum et Cou2Garnack, Rêverie et Kelly Conférencière — a été implémenté dans AKeNe et comment il a été rendu disponible pour le logiciel Unreal et Unity.
Dans sa forme la plus simple, nous devons envoyer une phrase au module. Nous avons donc besoin d’une entrée : à savoir une chaine de caractère. En sortie le module nous retourne un code de statu.
Nous allons donc créer un AKN_Node avec une entrée et une sortie. Si une entrée est modifiée, elle appelle une série de fonctions contenue dans une librairie extérieure à savoir actuellement SAPI 5.0 (mais je pense que j’abandonnerai cette bibliothèque au profit d’une autre, à savoir Sphynx) et retourne le résultat de l’exécution de la librairie externe dans sa sortie. Ce travail n’est en soit pas spécialement long ni spécialement compliqué (ni, au passage, spécialement intéressant), mais une fois ceci fait, il est extrêmement rapide de faire le code glue qui permet de créer les nodes des logiciels hôtes qui enveloppent ou sont enveloppés par nos AKN_Nodes.
Comme évoqué plus haut, une fonctionnalité intéressante que permet AKeNe est la possibilité de servir de passerelle de communication entre deux logiciels. Ainsi, l’idée est de faire transiter par l’intermédiaire d’un AKN_Node les données issues d’un logiciel hôte, afin de les envoyer par l’intermédiaire du réseau vers un autre AKN_Node en utilisant deux objets AKN_Client et AKN_Serveur[4], des versions spécialisées de AKN_Node conçues pour envoyer des AKN_Box au travers du réseau. Cette partie de code est un exercice que je donne à mes étudiants dans mon cours de programmation avancée.
Figure 53 : AKeNe comme passerelle de communication entre deux logiciels hôtes.
Par cette méthode, il devient très facile de connecter par exemple Unreal à Unity ou à Motion-Builder, c’est d’ailleurs le cas pour le projet Agamemnon Redux (Lavander et al. 2017)ou le retargeting est effectué sur le logiciel Motion Builder et l’affichage sur le moteur Unreal (cf. Figure 54).
Cette approche modulaire nous a permis d’adapter assez facilement notre outil de prévisualisation OutilNum (Plessiet, Chaabane, et Khemiri 2015) pour une application au théâtre (Gagneré, Plessiet, et Sohier 2018a) en réarrangeant les blocs de programmes entre eux.
Figure 54 : Les différents modules interconnectés de la pièce Agamemnon Redux.
Figure 55 : Réorganisation des modules d’OutilNum pour AvatarStaging.
[1] Pour l’anecdote, j’ai compté avoir développé 4 fois la même bibliothèque de calcul matriciel et de rotation, soit dans différents langages (c++, c#, python), soit dans des repères d’espaces différents (Blender, Maya et Unreal…). Un tel gaspillage d’énergie m’éloigne de mes problèmes artistiques à cause de développements purement mathématiques.
[2] La version 2.8 de Blender serait fortement orientée node, selon la Blender Fondation.
[3] Si c’est un type de base, le AKN_Box est construit automatiquement.
[4] Il existe néanmoins une exception lorsque j’utilise le logiciel hôte Unreal. En effet, dans ce cas-là je préfère utiliser directement les fonctions de réseau directement implémentées dans le moteur, car je les pense beaucoup plus rapides et efficaces que mes propres fonctions.
[1] Unity a changé sa politique vis-à-vis des écoles et des chercheurs, leur interdisant l’utilisation de la licence gratuite. En parallèle, Unreal a décidé de facturer son moteur par royalties, ce qui le rend gratuit si aucune commercialisation du travail n’est envisagée. Unity est redevenu par la suite gratuit pour l’éducation.
[1] Hidden Markov Model par Khoubaieb Klai, Algorithme Génétique par Ghaya Khemeri, Perceptron par Salma Chaabane, Goal Oriented Behavior par Hela Rida.
[2] Michele Quéré, 2012. « La force de la communication de la réalité virtuelle », Mémoire de Master 2 Arts et Technologie de l’Image, Université Paris 8.