1 # -*- coding: utf-8 -*-
3 Module de lecture des fichiers de log minwii
9 from widgets
.playingscreen
import PlayingScreenBase
10 from eventutils
import EventDispatcher
11 from events
import eventCodes
12 from synth
import Synth
13 from musicxml
import musicXml2Song
15 from backwardsfilereader
import BackwardsReader
17 SUPPORTED_FILE_HEADER
= 'ENV winwii log format version : 1.0'
20 def readinplace(self
, *args
, **kw
) :
21 pos
= self
.logfile
.tell()
23 ret
= m(self
, *args
, **kw
)
24 self
.logfile
.seek(pos
)
28 class LogFileReader(object) :
30 classe utilitaire pour l'accès aux données d'un fichier de log MinWii.
33 def __init__(self
, logfile
) :
34 """ logfile : chemin d'accès au fichier de log MinWii.
35 le format supporté est actuellement la version 1.0 uniquement.
37 if isinstance(logfile
, str) :
38 self
.logfile
= open(logfile
, 'r')
40 self
.logfile
= logfile
42 firstline
= self
.next()
43 assert firstline
== SUPPORTED_FILE_HEADER
47 def getSongFile(self
) :
48 "retourne le chemin d'accès au fichier musicxml de la chanson"
50 if l
.startswith('APP chanson :') :
52 songfile
= l
.split(':', 1)[1].strip()
56 def getSoundFontFile(self
) :
57 "retourne le chemin d'accès au fichier de la soundfont (*.sf2)"
59 if l
.startswith('ENV soundfont :') :
61 soundFontFile
= l
.split(':', 1)[1].strip()
66 "retourne le paramètre bank du synthétiseur (entier)"
68 if l
.startswith('APP bank :') :
70 bank
= l
.split(':', 1)[1].strip()
75 "retourne le paramètre preset du synthétiseur (entier)"
77 if l
.startswith('APP preset :') :
79 preset
= l
.split(':', 1)[1].strip()
83 def getScreenResolution(self
) :
84 "retourne la résolution écran (tuple de deux entiers)"
86 if l
.startswith('ENV résolution écran :') :
88 screenResolution
= eval(l
.split(':', 1)[1].strip())
89 return screenResolution
93 "retourne le niveau de difficulté"
95 if l
.startswith('APP mode :') :
98 mode
= l
.split(':', 1)[1].strip()
102 def getFirstEventTicks(self
) :
103 "retourne le timecode du premier événement (entier)"
105 if l
.startswith('EVT ') :
107 firstTicks
= int(l
.split(None, 2)[1])
111 def getLastEventTicks(self
) :
112 "retourne le timecode du dernier événement (entier)"
113 for l
in self
.getBackwardLineIterator() :
114 if l
.startswith('EVT ') :
117 lastTicks
= int(l
.split(None, 2)[1])
127 line
= self
.logfile
.next().strip()
130 def getEventsIterator(self
) :
131 """ Retourne un itérateur sur les événements.
132 Chaque itération retourne un tuple de 3 éléments :
133 (timecode, nom_événement, données) avec le typage :
134 (entier, chaîne, chaîne)
140 except StopIteration :
143 if not l
.startswith('EVT ') :
146 ticks
, eventName
, message
= l
.split(None, 3)[1:]
148 yield ticks
, eventName
, message
150 ticks
, eventName
= l
.split(None, 3)[1:]
152 yield ticks
, eventName
, ''
154 def getBackwardLineIterator(self
) :
155 br
= BackwardsReader(self
.logfile
, BLKSIZE
=128)
163 class LogFilePlayer(PlayingScreenBase
) :
165 ré-exécution d'une chanson sur la base de son fichier de log.
168 def __init__(self
, logfile
) :
169 lfr
= self
.lfr
= LogFileReader(logfile
)
170 songFile
= lfr
.getSongFile()
171 soundFontFile
= lfr
.getSoundFontFile()
172 sfPath
= lfr
.getSoundFontFile()
174 preset
= lfr
.getPreset()
175 synth
= Synth(sfPath
=sfPath
)
176 synth
.program_select(0, bank
, preset
)
177 self
.song
= musicXml2Song(songFile
)
178 screenResolution
= lfr
.getScreenResolution()
180 pygame
.display
.set_mode(screenResolution
)
182 super(LogFilePlayer
, self
).__init
__(synth
, self
.song
.distinctNotes
)
186 clock
= pygame
.time
.Clock()
187 pygame
.display
.flip()
188 pygame
.mouse
.set_visible(False)
190 previousTicks
= self
.lfr
.getFirstEventTicks()
191 eIter
= self
.lfr
.getEventsIterator()
193 for ticks
, eventName
, message
in eIter
:
194 t0
= pygame
.time
.get_ticks()
195 if eventName
== 'COLSTATECHANGE' :
196 parts
= message
.split(None, 4)
199 index
, state
, midi
, name
, syllabus
= parts
202 state
= state
== 'True'
203 col
= self
.columns
[midi
]
204 col
.update(state
, syllabus
=syllabus
.decode('utf-8'))
206 elif eventName
== 'NOTEON':
207 chan
, key
, vel
= [int(v
) for v
in message
.split(None, 2)]
208 self
.synth
.noteon(chan
, key
, vel
)
210 elif eventName
== 'NOTEOFF':
211 chan
, key
= [int(v
) for v
in message
.split(None, 1)]
212 self
.synth
.noteoff(chan
, key
)
214 elif eventName
.startswith('COL') :
215 pos
= [int(n
) for n
in message
.split(None, 4)[-1].strip('()').split(',')]
216 self
.cursor
.setPosition(pos
)
221 dirty
= self
.draw(pygame
.display
.get_surface())
222 pygame
.display
.update(dirty
)
223 execTime
= pygame
.time
.get_ticks() - t0
225 delay
= ticks
- previousTicks
- execTime
227 pygame
.time
.wait(delay
)
229 previousTicks
= ticks