X-Git-Url: https://scm.cri.minesparis.psl.eu/git/minwii.git/blobdiff_plain/e7854ed82aa375273bfbf772493739f875817061..46f3ffd7fdde386f41020171b5733e55a8e64a85:/src/minwii/logfilereader.py?ds=sidebyside diff --git a/src/minwii/logfilereader.py b/src/minwii/logfilereader.py new file mode 100755 index 0000000..64711c1 --- /dev/null +++ b/src/minwii/logfilereader.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +""" +Module de lecture des fichiers de log minwii + +$Id$ +$URL$ +""" + +from widgets.playingscreen import PlayingScreenBase +from eventutils import EventDispatcher +from events import eventCodes +from synth import Synth +from musicxml import musicXml2Song +import pygame + +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 : + self.logfile = logfile + + firstline = self.next() + assert firstline == SUPPORTED_FILE_HEADER + + + def getSongFile(self) : + "retourne le chemin d'accès au fichier musicxml de la chanson" + f = self.logfile + pos = f.tell() + + f.seek(0) + for l in self : + if l.startswith('APP chanson :') : + break + songfile = l.split(':', 1)[1].strip() + f.seek(pos) + 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) + for l in self : + 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) + for l in self : + 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) + for l in self : + if l.startswith('EVT ') : + break + firstTicks = int(l.split(None, 2)[1]) + f.seek(pos) + return firstTicks + + def __del__(self) : + self.logfile.close() + + def __iter__(self) : + return self + + def next(self) : + line = self.logfile.next().strip() + 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() + except StopIteration : + break + + if not l.startswith('EVT ') : + 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, '' + + +class LogFilePlayer(PlayingScreenBase) : + """ + ré-exécution d'une chanson sur la base de son fichier de log. + """ + + def __init__(self, logfile) : + 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 + clock = pygame.time.Clock() + pygame.display.flip() + pygame.mouse.set_visible(False) + + previousTicks = self.lfr.getFirstEventTicks() + eIter = self.lfr.getEventsIterator() + + for ticks, eventName, message in eIter : + t0 = pygame.time.get_ticks() + if eventName == 'COLSTATECHANGE' : + parts = message.split(None, 4) + if len(parts) == 4 : + parts.append('') + index, state, midi, name, syllabus = parts + index = int(index) + midi = int(midi) + 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() + + dirty = self.draw(pygame.display.get_surface()) + pygame.display.update(dirty) + execTime = pygame.time.get_ticks() - t0 + + delay = ticks - previousTicks - execTime + if delay > 0 : + pygame.time.wait(delay) + + previousTicks = ticks + + self.stop() + + \ No newline at end of file