Projet IMA3 P7, 2016/2017, TD1

De Wiki de Projets IMA

Projet IMA3-SC 2016/2017 : Simon musical

Cahier des charges

Une zone de vide sur deux dimensions est proposée à l'utilisateur. Lorsque l'utilisateur met sa main dans cette zone, un son est produit. La hauteur (donc la note) du son dépend de la position verticale de la main. Sa position horizontale définit l'instrument. Sous la zone se trouvent plusieurs indicateurs lumineux répartis en dessous de chaque colonne correspondant à un instrument.

L'interface web permet de configurer le mode d'utilisation du système, ainsi que les paramètres spécifiques au mode.

  • Mode libre : L'utilisateur peut associer un instrument à une colonne et jouer librement. Il peut choisir depuis l'interface web de lancer un enregistrement, enregistrements qui pourront être sauvegardés et ré-écoutés.
  • Mode Simon : L'utilisateur peut choisir une difficulté depuis l'interface web. Une suite de sons aléatoires est jouée à des hauteurs et des instruments différents. Pour repérer la position des instruments, l'indicateur lumineux associé à l'instrument s'allume lorsque celui-ci est joué. L'utilisateur doit ensuite rejouer la suite de sons dans l'ordre, avec le bon instrument et la bonne hauteur (avec un certain pourcentage de tolérance défini par la difficulté). Cette opération est répétée plusieurs fois, commençant avec une suite de taille 1, et un son est ajouté à la liste pour chaque opération. Le mode se termine lorsque l'utilisateur se trompe, auquel cas un score lui est attribué et est enregistré.
  • Mode répétition : Une suite de sons d'instruments et de hauteur différente est générée aléatoirement et jouée. L'utilisateur doit alors recréer la suite sans limite d'essai et avec possibilité de réécouter la suite. Lorsque l'utilisateur recrée la suite correctement, un score lui est attribué en fonction du temps qu'il a mis pour arriver à son but. Le nombre de sons dans la suite est déterminé par la difficulté, qui peut être changée depuis l'interface web.
  • Mode boucle : L'utilisateur définit un tempo (en battements par minute) et un nombre de battements par mesure (unité de temps en musique). Un métronome est alors lancé selon ces contraintes. L'utilisateur peut associer un instrument à une colonne et jouer librement. Il peut choisir depuis l'interface web d'enregistrer la prochaine mesure qu'il jouera dans une "boucle". Il peut enregistrer plusieurs boucles à la suite. L'utilisateur peut ensuite activer ou désactiver les boucles qui seront jouées automatiquement.

Description du système

Pour capter la position de la main sur la zone, on utilisera des capteurs à ultrason pour évaluer la distance entre le capteur et la main. À chaque capteur est associé un instrument et une LED. Les sons seront modulés en fréquence avec un codage. Cet ensemble de capteurs envoie les données au FPGA qui convertit les valeurs analogiques en valeurs numériques transmissibles au Raspberry Pi par port série. Le Raspberry Pi va alors lire via une enceinte reliée au port jack les sons liés aux différents capteurs à ultrasons et il va envoyer les données au client web. Le site web va quand a lui laisser l'utilisateur choisir les modes de jeu et les paramètres via une interface faite en HTML, CSS et Javascript

Le matériel

  • 1 Raspberry Pi
  • 1 Circuit Numérique Programmable
  • 4 Capteurs à ultrasons
  • 4 LED rouges
  • Câbles ethernet
  • Câble série

Séance supplémentaire (30/01/2017)

Brainstorming pour définir le projet et rédaction du cahier des charges, description du système et du matériel.


Séance supplémentaire (16/02/2017)

- Création de la base de l'architecture web

- Appels entre les pages

- Création des formulaires dans les différents modes disponibles (sélection des instruments et mesures de la difficulté

Séance supplémentaire (16/03/2017)

Mise en commun du travail de chacun et redéfinition des nouveaux objectifs court terme pour avancer dans le projet.

COMPTE RENDU DE LA MISE EN COMMUN :

Partie informatique

2017 ima3 sc td1 p7 sc.png

Interface Web : Explication à tous le monde du fonctionnement de l'interface web.

Raspberry : -connecté aux haut parleurs -division des tâches en sous programmes (afin de permettre aux différentes parties d'avancer indépendemment et de mieux se répartir le travail).

Chacun de ses programmes va tourner dans une boucle infini à une fréquence différente.

  • création des sons (tend vers 44kHz) à l'aide de sinusoïde.
  • séquenceur
    • garde en mémoire les sons qu'il va devoir jouer plus tard, cela permet d'éviter les risques de sons saccadés.)
    • récupère le signal des ultrasons
    • envoi le signal aux LEDS

Récepteur Websocket : envoi à l'interface les notes jouées et reçois les notes à jouer (en mode Simon)

Haut parleur : pour s'y connecter cela va dépendre de notre système d'exploitation qui va décider des bibliothèques que l'on va utiliser pour transformer le signal en analogique. On va utiliser des pipes pour ne pas avoir à intégrer dans notre code la bibliothèque, on déléguera donc cette tâche à la fonction play qui vient du programme sox. On a deux int16_r (entier sur 16 bits ) "gauche" et "droite" qui vont envoyer dans le haut parleur droit ou gauche le signal analogique correspondant à 44kHz.

Toutefois un pipe a besoin de recevoir des morceaux du son plutôt que de le couper par échantillons.

On va utiliser un header afin de pouvoir créer des alias et ainsi permettre de faire des ajustements du programme plus simplement (ex: si le type double est trop lourd on change dans le header l'alias plutôt que de devoir parcourir toutes les fonctions de notre programmes).

Partie électronique

FPGA : Aller en E303 pour voir avec le logiciel de programmation de la Nanoboard.

Séance supplémentaire (18/03/2017)

Partie électronique

Partie informatique

Raspberry Pi : Création du programme "synthétiseur" qui permet de créer des sons, et création du lien avec la commande play du programme SoX permettant de les envoyer sur les haut-parleurs. Création d'un programme "clavier" qui permet d'envoyer des sons entrés via un clavier d'ordinateur au synthéthiseur pour tester ce dernier. Pas de gestion de plusieurs sons en même temps pour le moment, mais fonctionne en temps réel à au moins 15 ms de latence (pourra être optimisé si besoin).

Séance supplémentaire (20/03/2017)

Partie électronique

Partie informatique

Arduino : recherche et réalisation d'un programme arduino permettant de lire les 4 ultrasons.

Séance 1

Partie esthétique

Nous avons récupéré une plaque en bois usinable au fablab qui nous servira de support pour les capteurs. Après réflexion nous avons eu l'idée de disposer les 4 capteurs à ultrasons sur les 4 faces d'une pyramide. Ce faisant nous évitons tous risque de parasitage des ultrasons entre eux et améliorons l'ergonomie du produit final.


Partie électronique

Arduino : câblage des 4 ultrasons sur un shield de prototypage. Après de nombreux débug nous avons découvert qu'un des 5 capteurs était mort et que la breadboard que nous utilisions comportait des faux contacts. Ces deux éléments ont donc été changés.

Partie informatique

Arduino : Le programme a été adapté au câblage et optimisé pour permettre une meilleure lecture des résultats ainsi que le choix de l'activation ou non des différents capteurs. A terme cela permettra au raspberry de sélectionner le capteur dont il veut recevoir le signal et ainsi faciliter la communication de données par le port série.


Définition et test de communication entre un programme C (sur Raspberry Pi) et l'Arduino via port USB (dans un premier temps).

Le protocole de communication s'effectura comme suit :

  • Le FPGA / Arduino attend des données
  • Le Raspberry envoie un octet contenant l'information des LED à allumer / éteindre. Chaque bit représente une LED. Par exemple, pour allumer la LED n°1 et la LED n°3, on enverra 0b00000101.
  • Pour chaque capteur ultrason qu'il possède, l'Arduino / FPGA envoie sur un entier codé sur un octet la valeur de ce capteur
  • Retour au début

Séance 2

Partie électronique

FPGA : Nous avons travaillé sur la création d'une clock adapté à nos besoins pour les capteurs ultrasons. Arduino : après de nombreux problèmes électroniques nous avons compris qu'il fallait nettoyer les capteurs à ultrasons afin de réduire les problèmes de courts-circuits. Une fois cette dernière étape remplie l'intégralité du systèmes arduino fonctionne et permet de jouer un son en passant la main devant un des 4 capteurs.

Partie informatique

Arduino : optimisation du code et mise en commun entre les différents développeurs du code. Le programme ne peut pour le moment pas jouer deux instruments à la fois, cette fonctionnalité sera normalement implémenté au cours de la semaine.

Séance 3

Partie électronique

FPGA : réflexion sur comment le circuit devra fonctionner et création d'une partie du circuit. À la fin de la séance on a apparamment réussi à afficher sur les LED la valeur d'un capteur à ultrason. Lors de la prochaine séance on réarrangera un peu mieux le circuit afin de pouvoir le faire fonctionner avec plusieurs capteurs à ultrason et le port série.

Sur l'image ci-dessous on peut voir sur la courbe supérieur de l'oscilloscope le signal que nous avons construit pour envoyer au trig de l'ultrason, Nous avons paramétrer qu'à chaque appui sur un bouton de la Nanoboard une acquisiont de l'echo serait faite, on peut en voir une sur la courbe inférieure. On notera une intensité beaucoup plus faible de ce signal mais qui correspond cependant à une valeur cohérente de l'ultrason (nous avons réalisé plusieurs tests en modifiant la distance capteur-objet).

Oscillo.jpg

Réflexion sur comment envoyer les données des ultrasons à la partie Raspberry Pi. Jusqu'à lors, l'Arduino envoyait au RPi un entier sur 8 bits représantant la distance entre le capteur et la limite (virtuelle) haute de l'instrument. Pour cela, l'Arduino effectuait une division décimale, traitement qu'on juge trop compliqué à implémenter sur le FPGA, on pensait donc envoyer les données brutes renvoyées par les capteurs à ultrason sur un entier 16bits.

Partie informatique

Websockets : première approche du serveur websocket. Nous avons établi le serveur et su nous y connecté à distance à l'aide du code fourni dans les archives du projet. Le client arrive à envoyer au serveur et recevoir un message de celui ci.

Nous allons maintenant fusionner le code de notre page web avec celui du serveur afin de créer une intéraction agréable pour l'utilisateur.


Partie physique

Nous avons été au Fablab afin de discuter avec les responsables sur les logiciels à utiliser et le fonctionnement des machines. Il a été discuté d'une possibilité de disposer les capteurs à ultrasons sur les faces d'un cube, d'une pyramide ou à la surface d'une barrette. Des tests seront réalisés sur des prototypes en cartons pendant les vacances. Le prototype sera normalement conçu dans la première quinzaine après la rentrée.

Séance supplémentaire (3/04/2017)

`synthetiseur` peut désormais jouer plusieurs notes à la fois.

Chaque structure de note envoyée par le pipe étant connecté à `synthetiseur` possède un id. Pour signaler que la note est relâchée, on envoie simplement la même structure avec le même id mais une note non correcte (par exemple : l'octave -1). Cela permet d'éviter de traiter des paquets de taille différentes.

Quant à la génération du son, on pensait au début additionner les amplitudes de chaque instrument, puis diviser par le nombre d'instruments, mais le volume global de la sortie audio reste constant alors que celui des instruments individuels ne le reste pas. Il est plus naturel que lorsque l'on appuie sur deux touches en même temps, on entende une intensité doublée. On divise donc la somme des amplitudes des instruments par 5 constamment (arbitrairement). Cela permet de garantir que 5 instruments peuvent jouer simultanément sans qu'il n'y ait de saturation.

Séance supplémentaire (5/04/2017)

On a vite constaté que la création d'un son purement sinusoïdal, triangulaire ou carré était très loin d'être agréable à l'oreille. On a donc cherché à ce que le système émette des sons proches d'instruments réels. On a pensé à différentes solutions :

- Garder en mémoire chaque enregistrement des couples instrument / notes et les rejouer quand nécessaire. Bien qu'étant la méthode la plus fidèle, elle nécessite une mémoire à la fois rapide en lecture et grande en stockage. À raison de 300 Ko par enregistrement (non compressé) avec 88 notes par instrument (un piano possède 88 touches), il faudrait 25 Mo de mémoire par instrument.

- Utiliser un seul enregistrement et changer sa fréquence à l’exécution. Bien que plus économe en mémoire, Cette méthode ne permet pas de reproduire suffisamment fidèlement l'instrument. En effet, bien que les harmoniques d'un enregistrement d'une note puisse être retrouvés à partir de celui d'une autre note (par exemple, augmenter d'une octave consiste à multiplier par deux la fréquence), certaines fréquences restes constantes ou presque qu'importe la note. C'est une des propriétés du timbre d'un instrument. Voici par exemple un Fa4 à 394 Hz d'un piano, que nous avons essayé de transformer en Fa 5 à 698 Hz en transposant, puis le vrai Fa 5 d'un piano pour comparer.

- Garder en mémoire les fréquences dominantes des couples instruments / notes et recréer les sons à l’exécution. Cette méthode, bien que plus coûteuse en calculs à l’exécution, est aussi beaucoup moins gourmande en mémoire, et plus intéressante d'un point de vue académique.

On choisira donc cette dernière méthode. Pour exporter les fréquences dominantes d'un enregistrement, on va d'abord découper un court extrait de l'enregistrement et l'importer de manière à pouvoir l'exploiter. On a choisi pour cela d'utiliser Python et le module Scipy (voir fichier `rpi/instruments/analyze.ppy`).

2017 ima3 sc td1 p7 la4piano.png

Notre échantillon a été échantillonné à 44,1 kHz sur une durée de 0,92 secondes, on obtient donc 40572 échantillons étant des entiers codés sur 16 bits, leur amplitude va donc de -32768 à 32767. L'enregistrement étant fait en stéréo, chaque couleur correspond à un canal. On ne retiendra qu'un canal pour la suite.

On applique ensuite une transformée de Fourrier, afin d'obtenir un diagramme fréquentiel.

2017 ima3 sc td1 p7 fourrierSimple.png

La propriété de symétrie du signal audio se reflète sur sa transformée. On ne conservera que les fréquences inférieures à 20 kHz, inaudibles pour l'oreille humaine. On remarque que la fréquence d’échantillonnage choisie par notre enregistreur numérique n'est pas anodine, en effet elle respecte le théorème de Shannon qui postule que la fréquence d’échantillonnage doit être au moins deux fois supérieure à la plus grande fréquence à capter.

On lance alors une détection de "pics" sur ce diagramme, et on conservera ceux au dessus d'un certain seuil. Il ne doit pas être trop haut pour assurer la qualité de la représentation, ni trop bas pour éviter le surplus de calcul lors de l’exécution. Pour simplifier cette détection, on a réajusté notre transformée de fourrier en utilisant une fenêtre de Hanning plutôt que rectangulaire, les pics devenant alors plus nets.

2017 ima3 sc td1 p7 fourrierLA4.png

On exporte ces données dans un fichier `.h` généré automatiquement.

Pour reconstruire le son à l’exécution, il suffira alors de faire la moyenne de signaux sinusoïdaux purs dont les fréquence sont celles des pics et la pondération correspond à l'amplitude du pic. Voici le résultat, d'abord comparé à l'enregistrement réel.

On constate que l'audio généré sonne un peu « en coin », ou comme un « nez bouché ». On suppose que l'utilisation d'intensité comme facteur de pondération de moyenne en est la cause, une analyse dimensionnelle plus poussée de ce qu'on a appelé à tort « l'amplitude » pourrait probablement corriger le problème. Ou alors, le fait que les fréquences de faible intensité soient filtrées joue un rôle. On s'en contentera pour le moment.

Séance supplémentaire (16/04/2017)

Les signaux sonores que nous avons générés jusque là étaient très "bruts", il passe d'un volume nul à maximal en très peu de temps, ce qui provoque un craquement. L'effet est le même sur la fin du signal. On pourrait considérer d'ajouter un fondu (c'est à dire que le volume évolue progressivement) au début et à la fin d'un son. Cependant, cela ne permet pas de rendre compte de "l'attaque" que possède certains instruments. On utilisera alors une enveloppe (l'évolution d'une propriété d'un son) de volume de type ADSR.

Représentation d'une enveloppe ADSR

Attack et Decay représentent l'attaque que subit l'instrument quand il est pincé / frotté / tappé, Sustain correspond au niveau par rapport au maximum de volume auquel reste l'instrument lorsqu'il y a encore contact, et Release correspond au temps de dissipation des vibrations dans l'instrument après rupture du contact. Bien entendu c'est assez simplificateur, et fonctionne mieux pour les instruments frottés (tels que le violon). Pour la guitare, instrument pincé, il n'y a pas de temps de sustension, et pour le piano, instrument frappé, il y a relâche même si la touche reste appuyée.

Les propriétés A, D, S et R se mesurent simplement sur chaque enregistrement et on les considérera comme communes pour toutes les notes d'un instrument.

On obtient un résultat plus agréable à entendre. Voici la comparaison d'un La4 d'un piano et sa recréation par l’exécutable `synthetiseur`.

Ce genre d'enveloppe peut s'appliquer pour d'autre propriété du son, notamment sur des filtres, le plus souvent passe-bas ou passe-haut, car les fréquences d'un instrument ne restent les mêmes durant la durée de la note. Cependant la détermination des paramètres des filtres prend beaucoup de temps, et on sort du cadre de ce projet. La re-création d'instruments de synthèses est un processus compliqué.

Séance supplémentaire (26/04/2017)

Enregistrement de notes provenant de piano, guitare et alto à l'aide d'un enregistreur numérique.

Profitons-en pour tester la robustesse de notre process de re-création de sons. Voici les enregistrements qui ont été faits, puis ce qui a été regénéré.

Cela nous paraît suffisant. Il y a quelques abbérations au début et à la fin de la gamme, on pense que c'est lié à la précision de l'enregistrement (le process est très sensible aux bruits parasites), et au fait que les notes très aigues n'ont pas beaucoup de "pics" à exploiter.

Séance supplémentaire (8/05/2017)

Travail sur le FPGA en faisant fonctionner le capteur à ultrasons. On a créé des sous modules pour chaque capteur à ultrasons, dont le rôle est de lire la valeur de ce dernier dès qu'un signal go est arrivé, puis de sortir sa valeur sur un bus data de 16 bits puis d'envoyer un signal done lorsque le signal a été lu et on a attendu suffisament longtemps (60 ms, soit à peu près 10 m de parcours par une onde sonore) pour éviter les rebonds sur les autres capteurs. La bascule "de fonctionnement" JK permet de savoir si le module est en cours de lecture ou pas. Le premier compteur 16 bits "de fonctionnement" compte le temps (en µs) depuis la reception du signal go. Il permettra l'émission du signal done après 60 ms. Le deuxième compteur 16 bits permet de compter le nombre de µs pendant lesquelles on a lu une entrée sur la patte "echo" du capteur à ultrasons, qui est proportionnelle à la distance lue. On a aussi deux comparateurs, le premier compare le premier compteur (de fonctionnement) avec la valeur de 10 µs correspondant au temps pendant lequel il faut envoyer un signal sur la patte "trigger" du capteur à ultrason pour déclencher une lecture. Le deuxième compare le même compreur avec la valeur correspondant à 60 000 µs, et dès que le timer dépasse le signal done est envoyé, la bascule est remise dans son état "pas de lecture en cours" et les compteurs sont remis à 0 pour la prochaine lecture.

2017 ima3 sc td1 p7 fpga mod.png

Pulse est un bloc qui convertit un front montant en impulsion en utilisant le fait qu'une bascule synchrone met un certain temps avant de se mettre à jour.

Dans le niveau superieur, on fonctionnera de la manière suivante : dès qu'un octet est disponible, on sépare chaque bit que l'on envoie sur les leds, puis on déclenche la lecture du premier capteur, qui déclenchera la lecture du deuxième et ainsi de suite. Dès qu'un capteur a fini sa lecture, on envoie les 8 bits de point fort de ses données, puis dès que le signal est transmis les 8 bits de points faibles. On suppose que l'envoi se fait avant que le prochain capteur ait finit sa lecture, ce qui est une approximation très correcte étant donné un décalage entre les lectures de 60 ms et une vitesse de 9600 Baud/s.

2017 ima3 sc td1 p7 fpga mod.png

Reste à faire la liaison série pour pouvoir tester tout ça.

Séance supplémentaire (19/05/2017)

Définition de la forme de la maquette pour le support des ultrasons: Fer a cheval. Découpage à la découpeuse laser du Fablab pour permettre la mise en place de notre prototype Arduino essentiellement.


Séance supplémentaire (29/05/2017)

Découpe à la découpeuse laser du Fablab le support de la carte Arduino et breadboard. Fixation des deux supports (capteurs et Carte), Mise en place des capteurs et des Leds sur le support et optimisation du câblage.

Maquette et Arduino

Séance supplémentaire (31/05/2017)

Finalisation de la maquette, modifications esthétiques et câblage.

Séance supplémentaire (18/06/2017)

Vidéo de démonstration. Le Raspberry Pi est ici connecté à un partage de connexion via USB vers un téléphone en 4G. Le PC est connecté au réseau domestique via Wi-Fi. La communication entre les deux est assurée par un forwarding de port via SSH. Tout cela est pour démontrer que le système ne nécessite pas une connexion à grand débit ni à faible latence pour fonctionner correctement.


Conclusion

Etat de la partie informatique

Le code source de toute la partie informatique du projet est disponible sur le GitLab de l'école.


Dans son ensemble le code de la partie Raspberry Pi est de qualité médiocre. En effet, en ayant voulu utiliser des pipes nommés et un script bash pour pouvoir faire un multi-threading simple s'est avéré rendre tout le système plus complexe. En effet, il a fallu implanter une logique de scrutation et de conversion des données passées. Le but recherché étant de réduire la latence, c'est probablement l'effet inverse qui a été achevé, car même si on ne la voit pas les pipes impliquent une recopie en mémoire des données transférées. Au moins on comprendra à quels problèmes les forks et des threads répondent l'année prochaine. De plus, certaines parties du code auraient pu être repensées, comme l'attribution d'ID aux sons et l'utilisation de liste chaînées pour éviter de parcourir un grand tableau à chaque itération et imposant par la même occasion une limite de sons simultanés. Les variable globales est aussi scandaleusement sur-utilisées. Pour que le programme fonctionne sur Raspberry Pi, on a du diviser la fréquence d’échantillonnage par deux. Il en va de même pour le code Javascript côté navigateur. Dans le même ordre d'idée il manque de commentaires et d'explications, de rigueur dans les conventions... Mais les deux choses les plus importantes sont que le programme fonctionne, et que l'on a su reconnaître nos erreurs pour ne pas les refaire dans le futur.