Clignotement du curseur basé sur un thread indépendant de pygame.
[minwii.git] / src / app / logfilereader.py
index 502b332..64711c1 100755 (executable)
@@ -13,11 +13,17 @@ from synth import Synth
 from musicxml import musicXml2Song
 import pygame
 
-SUPPORTED_FILE_HEADER = 'ENV winwii log format version : 1.0-alpha'
+SUPPORTED_FILE_HEADER = 'ENV winwii log format version : 1.0'
 
 class LogFileReader(object) :
+    """
+    classe utilitaire pour l'accès aux données d'un fichier de log MinWii.
+    """
     
     def __init__(self, logfile) :
+        """ logfile : chemin d'accès au fichier de log MinWii.
+            le format supporté est actuellement la version 1.0 uniquement.
+        """
         if isinstance(logfile, str) :
             self.logfile = open(logfile, 'r')
         else :
@@ -28,6 +34,7 @@ class LogFileReader(object) :
         
     
     def getSongFile(self) :
+        "retourne le chemin d'accès au fichier musicxml de la chanson"
         f = self.logfile
         pos = f.tell()
 
@@ -40,6 +47,7 @@ class LogFileReader(object) :
         return songfile
     
     def getSoundFontFile(self) :
+        "retourne le chemin d'accès au fichier de la soundfont (*.sf2)"
         f = self.logfile
         pos = f.tell()
         f.seek(0)
@@ -47,9 +55,35 @@ class LogFileReader(object) :
             if l.startswith('ENV soundfont :') :
                 break
         soundFontFile = l.split(':', 1)[1].strip()
+        f.seek(pos)
         return soundFontFile
 
+    def getBank(self) :
+        "retourne le paramètre bank du synthétiseur (entier)"
+        f = self.logfile
+        pos = f.tell()
+        f.seek(0)
+        for l in self :
+            if l.startswith('APP bank :') :
+                break
+        f.seek(pos)
+        bank = l.split(':', 1)[1].strip()
+        return int(bank)
+    
+    def getPreset(self) :
+        "retourne le paramètre preset du synthétiseur (entier)"
+        f = self.logfile
+        pos = f.tell()
+        f.seek(0)
+        for l in self :
+            if l.startswith('APP preset :') :
+                break
+        f.seek(pos)
+        preset = l.split(':', 1)[1].strip()
+        return int(preset)
+
     def getScreenResolution(self) :
+        "retourne la résolution écran (tuple de deux entiers)"
         f = self.logfile
         pos = f.tell()
         f.seek(0)
@@ -57,9 +91,11 @@ class LogFileReader(object) :
             if l.startswith('ENV résolution écran :') :
                 break
         screenResolution = eval(l.split(':', 1)[1].strip())
+        f.seek(pos)
         return screenResolution
     
     def getFirstEventTicks(self) :
+        "retourne le timecode du premier événement (entier)"
         f = self.logfile
         pos = f.tell()
         f.seek(0)
@@ -67,6 +103,7 @@ class LogFileReader(object) :
             if l.startswith('EVT ') :
                 break
         firstTicks = int(l.split(None, 2)[1])
+        f.seek(pos)
         return firstTicks
     
     def __del__(self) :
@@ -80,6 +117,12 @@ class LogFileReader(object) :
         return line
     
     def getEventsIterator(self) :
+        """ Retourne un itérateur sur les événements.
+            Chaque itération retourne un tuple de 3 éléments :
+            (timecode, nom_événement, données) avec le typage :
+            (entier,   chaîne,        chaîne)
+        """
+        self.logfile.seek(0)
         while True :
             try :
                 l = self.next()
@@ -90,9 +133,11 @@ class LogFileReader(object) :
                 continue
             try :
                 ticks, eventName, message = l.split(None, 3)[1:]
+                ticks = int(ticks)
                 yield ticks, eventName, message
             except ValueError :
                 ticks, eventName = l.split(None, 3)[1:]
+                ticks = int(ticks)
                 yield ticks, eventName, ''
                 
 
@@ -102,17 +147,20 @@ class LogFilePlayer(PlayingScreenBase) :
     """
 
     def __init__(self, logfile) :
-       lfr = self.lfr = LogFileReader(logfile)
-       songFile = lfr.getSongFile()
-       soundFontFile = lfr.getSoundFontFile()
-       sfPath = lfr.getSoundFontFile()
-       synth = Synth(sfPath=sfPath)
-       self.song = musicXml2Song(songFile)
-       screenResolution = lfr.getScreenResolution()
-       
-       pygame.display.set_mode(screenResolution)
-       
-       super(LogFilePlayer, self).__init__(synth, self.song.distinctNotes)
+        lfr = self.lfr = LogFileReader(logfile)
+        songFile = lfr.getSongFile()
+        soundFontFile = lfr.getSoundFontFile()
+        sfPath = lfr.getSoundFontFile()
+        bank = lfr.getBank()
+        preset = lfr.getPreset()
+        synth = Synth(sfPath=sfPath)
+        synth.program_select(0, bank, preset)
+        self.song = musicXml2Song(songFile)
+        screenResolution = lfr.getScreenResolution()
+        
+        pygame.display.set_mode(screenResolution)
+        
+        super(LogFilePlayer, self).__init__(synth, self.song.distinctNotes)
     
     def run(self):
         self._running = True
@@ -124,7 +172,7 @@ class LogFilePlayer(PlayingScreenBase) :
         eIter = self.lfr.getEventsIterator()
 
         for ticks, eventName, message in eIter :
-            ticks = int(ticks)
+            t0 = pygame.time.get_ticks()
             if eventName == 'COLSTATECHANGE' :
                 parts = message.split(None, 4)
                 if len(parts) == 4 :
@@ -135,25 +183,32 @@ class LogFilePlayer(PlayingScreenBase) :
                 state = state == 'True'
                 col = self.columns[midi]
                 col.update(state, syllabus=syllabus.decode('utf-8'))
+
+            elif eventName == 'NOTEON':
+                chan, key, vel = [int(v) for v in message.split(None, 2)]
+                self.synth.noteon(chan, key, vel)
+
+            elif eventName == 'NOTEOFF':
+                chan, key = [int(v) for v in message.split(None, 1)]
+                self.synth.noteoff(chan, key)
+            
+            elif eventName.startswith('COL') :
+                pos = [int(n) for n in message.split(None, 4)[-1].strip('()').split(',')]
+                self.cursor.setPosition(pos)
+            
                 
-            pygame.event.clear() # à virer
-            #EventDispatcher.dispatchEvents()
+            pygame.event.clear()
 
             dirty = self.draw(pygame.display.get_surface())
             pygame.display.update(dirty)
-            execTime = clock.tick()
+            execTime = pygame.time.get_ticks() - t0
             
             delay = ticks - previousTicks - execTime
             if delay > 0 :
                 pygame.time.wait(delay)
             
             previousTicks = ticks
-            #print ticks, eventName, message
-            
-        #while self._running :
-        #    EventDispatcher.dispatchEvents()
-        #    dirty = self.draw(pygame.display.get_surface())
-        #    pygame.display.update(dirty)
-        #    clock.tick()
+        
+        self.stop()
         
     
\ No newline at end of file