[Concours] Théorie et pratique Musicale

(Théorie et pratique Musicale est un des projets retenus par le jury d’OLPC France pour son concours d’idée. Dans ce post, François Sénéquier, son auteur nous présente son projet et son portage pour le XO)

Présentation du logiciel

Il s’agit d’un outil d’apprentissage de la théorie musicale (gammes, accords, relation entre gammes/accords) et de la pratique d’un instrument de musique (guitare, piano, harmonica…).

Capture Théorie Musicale

Pour commencer l’apprentissage d’un instrument, il est nécessaire de connaître les gammes (blues, majeure, mineures, …) et leurs modes (modes de la gamme majeure pour le Jazz…), mais aussi les accords (7M, sus, dim,…) et leurs renversements.

Il faut aussi comprendre la relation entre les gammes et les accords : que
lle gamme sonne bien avec quels accords et vice versa ?

L’outil permet de :

  • effectuer des comparaisons entre les gammes et les accords comme obtenir l’harmonisation d’une gamme particulière (tous les accords existants construits sur les notes de la gamme en question);
  • de retrouver le nom d’un accord ou d’une gamme par rapport à un ensemble de notes;
  • de savoir comment jouer les notes d’une gamme ou d’un accord (en arpèges) sur un instrument de musique (corde / case pour une guitare, alvéoles + souffler / aspirer + tirette pour un harmonica, …) grâce â une représentation graphique simple des notes.

Le projet est disponible sur le site Sourceforge :

Portage de l’application

Le portage de l’application de musique dévelopée sur EEE PC s’est déroulé en deux étapes :

  • exécution directe à partir de la console (terminal) sans utiliser les packages particuliers des activités de Sugar : Le XO étant un vrai système Linux rien n’empêche d’exécuter directement l’application afin de vérifier la portabilité du code mais aussi que les librairies nécessaires sont déjà pré-installées sur le XO.Sur EEE PC, les librairies utilisées sont :
    • python-cairo (pour les sorties graphiques);
    • python-gtk2 (pour la gestion de l’interface GTK);
    • librsvg2-2 (pour pouvoir utiliser les images vectorielles SVG dans Cairo);
    • python-psyco (pour accélérer l’éxécution des programmes Python).

    La seule librairie manquante dans la configuration d’origine du XO est Psyco.
    Cette librairie permet de faire tourner les programmes Python plus rapidement mais au prix d’une plus grande consommation mémoire. Psyco peut être installé sur le XO à l’aide du gestionnaire de package YUM et cela fonctionne très bien.

    Pour activer Psyco dans un programme Python, le code suivant peut être utilisé :


    try:
    import psyco
    psyco.full()
    except ImportError:
    print "'Psyco' introuvable !"

    Pour éviter tout problème de mémoire, l’utilisation du garbage collector peut être forcée et le maximum de mémoire peut être récupérée grâce aux commandes suivantes :


    try:
    import gc
    gc.enable()
    gc.collect()
    except:
    print "Garbage collector error !"

    Compte-tenu des ressources du XO (processeur, interface en Python, Psyco non installé), certaines parties du code ont du être optimisées. Mais ici, ce sont bien des optimisations d’algorithmes ou de pure logique et non des optimisations de bas niveau comme il est possible de le faire avec le langage C.

    Le programme, à la suite de ces optimisations, est stable (plus de problème de mémoire, rapidité acceptable) sur le XO.

  • Création de l’activité :
    • Créer le répertoire de l’activité
      : theorie.activity;
    • Créer dans le répertoire theorie.activity le sous-répertoire activity;
    • Créer dans le répertoire activity, l’icone pour le projet au format SVG : theorie-activity.svg
      Remarque : l’icone de l’activité (clé de sol) provient de la librairie OpenClipart et a été modifié pour intégrer le mécanisme de changement de couleur de Sugar. Déclaration de deux variables contenant les couleurs de tracé (stroke) et de remplissage (fill) :

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
      <!ENTITY stroke_color "#666666">
      <!ENTITY fill_color "#FFFFFF">
      ]>
      Puis, dans les objets du SVG (path, rec, …), ces couleurs peuvent être
      directement associées en référençant ces variables :


      <path fill="&fill_color;" stroke="&stroke_color;" stroke-width="4" ...

      Sugar peut ensuite modifier la couleur de remplissage dynamiquement lors du survol de l’icone.

      Remarque : pour générer ses propres icones, le logiciel libre Inkscape est très adapté.

    • Créer le fichier activity.info dans le répertoire theorie.activity/activity qui contient :

      [Activity]
      name = Theorie
      service_name = Theorie
      bundle_id = Theorie
      class = theorie.Theorie
      icon = activity-theorie
      activity_version = 1
      host_version = 1
      show_launcher = yes
      Avec :

      • class le nom de la classe Python héritant de la classe Activity de Sugar
      • icon le nom de l’icone de l’activité sans l’extension SVG >
    • Créer ou copier le fichier setup.py en mettant le nom de l’activité en paramètre de la méthode start :
      #!/usr/bin/env python
      from sugar.activity import bundlebuilder
      if __name__ == "__main__":
      bundlebuilder.start("TheorieActivity")

      Ce fichier Python facilite certaines opérations comme :

      • l’installation de l’activité en environnement de développement (création dans le répertoire des activités d’un lien sur le répertoire de l’activité développée) :
        python setup.py dev
        Ainsi, il n’est pas utile, à chaque modification du code Python, de recréer le fichier XO pour le déployer.
      • la création ou la mise à jour du fichier MANIFEST (qui contient les noms de tous les fichiers du projet) pour la création du fichier XO :
        python setup.py fix_manifest
        Voici le contenu du fichier MANIFEST situé à la racine du répertoire de l’activité :

        setup.py
        theorie.py
        commun.py
        modele.py
        modele_data.py
        observable.py
        canvas_instrument.py
        canvas_clarinette.py
        canvas_flute.py
        canvas_guitare.py
        canvas_harmonica.py
        canvas_piano.py
        composant_notes.py
        composant_degres.py
        composant_comparaison.py
        composant_selection.py
        composant_affichage.py
        composant_clarinette.py
        composant_flute.py
        composant_guitare.py
        composant_harmonica.py
        composant_piano.py
        clarinette.svg
        gpl.txt

        activity/activity.info
        activity/theorie.activity.svg

      • la création du fichier XO pour la distribution de l’application :
        python setup.py dist_xo
    • Transformation de l’application PyGtk en activité Sugar :
      Le programme original utilise une seule fenêtre GTK. Le point d’entrée du programme PyGtk est une classe héritant de gtk.Window :

      class ComposantTheorie(gtk.Window):

      def __init__(self):
      gtk.Window.__init__(self)
      self.set_title(« Théorie musicale – Instruments de musique »)
      # creation des composants
      SET_AFF(MOD_NOT)
      SET_CMP(MOD_OCT)
      compGAM = ComposantGammes()
      compACC = ComposantAccords()
      compNOT = ComposantNotes()
      compSEL = ComposantSelection(«  », [compGAM, compACC, compNOT], gtk.POS_LEFT)
      notes = compGAM.getNotes()
      compCMP = ComposantComparaison(notes)
      compHAR = ComposantHarmonica(notes)
      compFLU = ComposantFlute(notes)
      compGUI = ComposantGuitare(notes)
      compCLA = ComposantClarinette(notes)
      # recuperation des dimensions de l’ecran
      lar = screen_width()
      hau = screen_height()
      # composition de l’ecran suivant sa hauteur (en pixels)
      if hau <= 480:
      compAFF = ComposantAffichage(«  », notes, [compGUI , compHAR, compFLU, compCLA, compCMP], gtk.POS_TOP)
      else:
      compINS = ComposantAffichage(TXT_ONG_INS, notes, [compGUI, compHAR, compFLU, compCLA], gtk.POS_BOTTOM)
      compAFF = ComposantAffichage(«  », notes, [compINS, compCMP], gtk.POS_TOP)
      paned = definirVPANED(compSEL.getContainer(), compAFF.getContainer())
      self.add(paned)
      # affichage en plein ecran ou non
      if lar <= 1024 or hau <= 600:
      self.fullscreen()
      else:
      self.set_size_request(1024,600)
      # gestion des evenements compSEL.addObserver(compAFF)
      self.connect(« destroy », gtk.main_quit)
      self.show_all()

      Le programme est ensuite lancé par le code suivant :

      if __name__ == "__main__":
      try:
      import psyco
      psyco.full()
      except ImportError:
      print "'Psyco' introuvable !"
      try:
      import gc
      gc.enable()
      gc.collect()
      except:
      print "Garbage collector error !"
      obj = ComposantTheorie()
      gtk.main()

      Le programme doit être modifié :

      • la classe principale
        doit hériter de activity.Activity (et non de gtk.Window);
      • il faut ajouter la barre d’outil standard des activités;
      • il faut associer le contenu de la fenêtre du programme PyGtk à l’activité par l’intermédiaire de la méthode set_canvas de la classe Activity;
      • compte-tenu du fait que Sugar se charge de démarrer l’activité :
        • le code de l’activité ne doit pas contenir de gtk.Main() (lancement du programme et gestion des événements);
        • le code relatif à Psyco et au garbage collector est déplacé dans le constructeur de l’activité.

      Ce qui donne :

      class Theorie(activity.Activity):
      ...
      def __init__(self, handle):
      # initialisations (initialisation de l'activite + creation de la toolbox)
      activity.Activity.__init__(self, handle)
      toolbox = activity.ActivityToolbox(self)
      self.set_toolbox(toolbox)
      toolbox.show()
      # recuperation du maximum de memoire
      try:
      import gc
      gc.enable()
      gc.collect()
      except:
      pass
      # activation de Psyco
      try:
      import psyco
      psyco.full()
      except:
      pass
      # creation des composants specifiques a l'application
      self.set_title("Théorie musicale - Pratique des instruments de musique")
      SET_AFF(MOD_NOT)
      SET_CMP(MOD_OCT)
      # creation des onglets gammes, accords, notes
      compGAM = ComposantGammes()
      compACC = ComposantAccords()
      compNOT = ComposantNotes()
      compSEL = ComposantSelection("", [compGAM, compACC, compNOT], gtk.POS_TOP)
      # recuperation des notes initiales
      notes = compGAM.getNotes()
      # creation des onglets instruments et theorie
      compHAR = ComposantHarmonica(notes)
      compFLU = ComposantFlute(notes)
      compGUI = ComposantGuitare(notes)
      compCLA = ComposantClarinette(notes)
      compPIA = ComposantPiano(notes)
      compINS = Composant Affichage(TXT_ONG_INS, notes, [compGUI, compHAR, compFLU, compCLA, compPIA], gtk.POS_BOTTOM)
      compCMP = ComposantComparaison(notes)
      compAFF = ComposantAffichage("", notes, [compINS, compCMP], gtk.POS_TOP)
      panedUI = definirVPANED(compSEL.getContainer(), compAFF.getContainer())
      # liaison 'evenement' entre les deux zones
      compSEL.addObserver(compAFF)
      # ajout de l'ensemble des composants dans la zone ecran de l'activite
      self.set_canvas(panedUI)
      # gestion des evenements 'clavier' self.connect('key-press-event', self.__keyPress)

    • Gestion des touches de l’écran pour une utilisation en mode replié :
      Curieusement, les touches des deux PADs (disposées à gauche et à droite de l’écran) ne permettent pas dans leur configuration par défaut de piloter le logiciel entièrement : naviguer d’un contrôle à l’autre, activer une case à cocher… Le comportement des touches a donc ét
      é modifié pour que le logiciel puisse être piloté sans le clavier :

      • naviguer entre les différents contrôles;
      • pouvoir sélectionner ou désélectioner une case à cocher ou un bouton à état.

      Le principe est exactement le même que pour réagir aux autres touches du clavier :
      il suffit d’associer à l’événement key-press-event du contrôle considéré une fonction ayant comme paramètre le widget et l’événement.

      Pour exemple, voici le code pour que les cases à cocher puissent être cochées ou décochées en appuyant sur les touches de gauche et de droite du PAD disposé à droite de l’écran.
      self.hKEY = self.checkREL.connect("key-press-event", self.__keyPress)
      Avec self.checkREL, la case à
      cocher et self.__keyPress la méthode à appeler.


      def __keyPress(self, widget, event):
      key = gtk.gdk.keyval_name(event.keyval)
      if key in ['KP_Home', 'KP_End']:
      active = widget.get_active()
      widget.set_active(not active)
      return True
      return False

      PyGtk fournit les méthodes get_active() et set_active() pour changer l’état de la case à cocher.

      Autre exemple de code pour faire en sorte que les boutons du haut et du bas du PAD de gauche permettent de passer d’un contrôle à l’autre :


      class Theorie(activity.Activity):
      ...
      def __keyPress(self, widget, event):
      key = gtk.gdk.keyval_name(event.keyval)
      if key == 'KP_Page_Up':
      widget.get_toplevel().child_focus(gtk.DIR_TAB_BACKWARD)
      return True
      elif key == 'KP_Page_Down':
      widget.get_toplevel().child_focus(gtk.DIR_TAB_FORWARD)
      return True
      return False

    • Pour lancer le programme, deux possibi
      lités :

      • en mode développement :
        python setup.py dev pour installer l’application dans le menu sous la forme d’un lien (les fichiers *.py ne sont pas copiés dans le répertoire principal des activités)
      • lorsque le développement est terminé :
        • python setup.py dist_xo pour créer le package XO (fichier compressé ZIP);
        • sugar-install-bundle pour installer le package XO dans le menu (copie physique des fichiers nécessaires dans le répertoire des activités).

Documents ou liens utiles