1 # -*- coding: utf-8 -*-
3 Module de lecture des fichiers de log minwii
9 from types
import StringTypes
10 from widgets
.playingscreen
import PlayingScreenBase
11 from eventutils
import EventDispatcher
12 from events
import eventCodes
13 from synth
import Synth
14 from musicxml
import musicXml2Song
16 from backwardsfilereader
import BackwardsReader
18 SUPPORTED_FILE_HEADER
= 'ENV winwii log format version : 1.0'
21 def readinplace(self
, *args
, **kw
) :
22 pos
= self
.logfile
.tell()
24 ret
= m(self
, *args
, **kw
)
25 self
.logfile
.seek(pos
)
29 class LogFileReader(object) :
31 classe utilitaire pour l'accès aux données d'un fichier de log MinWii.
34 def __init__(self
, logfile
, mode
='r') :
35 """ logfile : chemin d'accès au fichier de log MinWii.
36 le format supporté est actuellement la version 1.0 uniquement.
38 if isinstance(logfile
, StringTypes
) :
39 self
.logfile
= open(logfile
, mode
)
41 self
.logfile
= logfile
43 firstline
= self
.next()
44 assert firstline
== SUPPORTED_FILE_HEADER
48 def getSongFile(self
) :
49 "retourne le chemin d'accès au fichier musicxml de la chanson"
51 if l
.startswith('APP chanson :') :
53 songfile
= l
.split(':', 1)[1].strip()
57 def getSoundFontFile(self
) :
58 "retourne le chemin d'accès au fichier de la soundfont (*.sf2)"
60 if l
.startswith('ENV soundfont :') :
62 soundFontFile
= l
.split(':', 1)[1].strip()
67 "retourne le paramètre bank du synthétiseur (entier)"
69 if l
.startswith('APP bank :') :
71 bank
= l
.split(':', 1)[1].strip()
76 "retourne le paramètre preset du synthétiseur (entier)"
78 if l
.startswith('APP preset :') :
80 preset
= l
.split(':', 1)[1].strip()
84 def getScreenResolution(self
) :
85 "retourne la résolution écran (tuple de deux entiers)"
87 if l
.startswith('ENV résolution écran :') :
89 screenResolution
= eval(l
.split(':', 1)[1].strip())
90 return screenResolution
94 "retourne le niveau de difficulté"
96 if l
.startswith('APP mode :') :
99 mode
= l
.split(':', 1)[1].strip()
104 "retourne l'interface homme-machine utilisée"
106 if l
.startswith('APP HID :') :
109 mode
= l
.split(':', 1)[1].strip()
113 def getFirstEventTicks(self
) :
114 "retourne le timecode du premier événement (entier)"
116 if l
.startswith('EVT ') :
118 firstTicks
= int(l
.split(None, 2)[1])
122 def getLastEventTicks(self
) :
123 "retourne le timecode du dernier événement (entier)"
124 for l
in self
.getBackwardLineIterator() :
125 if l
.startswith('EVT ') :
130 lastTicks
= int(l
.split(None, 2)[1])
140 line
= self
.logfile
.next().strip()
143 def getEventsIterator(self
) :
144 """ Retourne un itérateur sur les événements.
145 Chaque itération retourne un tuple de 3 éléments :
146 (timecode, nom_événement, données) avec le typage :
147 (entier, chaîne, chaîne)
153 except StopIteration :
156 if not l
.startswith('EVT ') :
159 ticks
, eventName
, message
= l
.split(None, 3)[1:]
161 yield ticks
, eventName
, message
163 ticks
, eventName
= l
.split(None, 3)[1:]
165 yield ticks
, eventName
, ''
167 def getBackwardLineIterator(self
) :
168 br
= BackwardsReader(self
.logfile
, BLKSIZE
=128)
175 def getMetadata(self
) :
177 self
.next() # skip identification line.
179 while line
.startswith('METADATA ') :
180 line
= line
.split(None, 1)[1]
181 name
, value
= [v
.strip() for v
in line
.split(':', 1)]
182 metadata
[name
] = value
186 def setMetadata(self
, metadata
) :
189 before
= f
.readline()
191 while line
.startswith('METADATA ') :
193 after
= line
+ f
.read()
196 for name
, value
in metadata
:
197 lines
.append('METADATA %s : %s' % (name
, value
.encode('utf-8')))
198 metadata
= '\n'.join(lines
)
209 class LogFilePlayer(PlayingScreenBase
) :
211 ré-exécution d'une chanson sur la base de son fichier de log.
214 def __init__(self
, logfile
) :
215 lfr
= self
.lfr
= LogFileReader(logfile
)
216 songFile
= lfr
.getSongFile()
217 soundFontFile
= lfr
.getSoundFontFile()
218 sfPath
= lfr
.getSoundFontFile()
220 preset
= lfr
.getPreset()
221 synth
= Synth(sfPath
=sfPath
)
222 synth
.program_select(0, bank
, preset
)
223 self
.song
= musicXml2Song(songFile
)
224 screenResolution
= lfr
.getScreenResolution()
226 pygame
.display
.set_mode(screenResolution
)
228 super(LogFilePlayer
, self
).__init
__(synth
, self
.song
.distinctNotes
)
232 clock
= pygame
.time
.Clock()
233 pygame
.display
.flip()
234 pygame
.mouse
.set_visible(False)
236 previousTicks
= self
.lfr
.getFirstEventTicks()
237 eIter
= self
.lfr
.getEventsIterator()
239 for ticks
, eventName
, message
in eIter
:
240 t0
= pygame
.time
.get_ticks()
241 if eventName
== 'COLSTATECHANGE' :
242 parts
= message
.split(None, 4)
245 index
, state
, midi
, name
, syllabus
= parts
248 state
= state
== 'True'
249 col
= self
.columns
[midi
]
250 col
.update(state
, syllabus
=syllabus
.decode('utf-8'))
252 elif eventName
== 'NOTEON':
253 chan
, key
, vel
= [int(v
) for v
in message
.split(None, 2)]
254 self
.synth
.noteon(chan
, key
, vel
)
256 elif eventName
== 'NOTEOFF':
257 chan
, key
= [int(v
) for v
in message
.split(None, 1)]
258 self
.synth
.noteoff(chan
, key
)
260 elif eventName
.startswith('COL') :
261 pos
= [int(n
) for n
in message
.split(None, 4)[-1].strip('()').split(',')]
262 self
.cursor
.setPosition(pos
)
267 dirty
= self
.draw(pygame
.display
.get_surface())
268 pygame
.display
.update(dirty
)
269 execTime
= pygame
.time
.get_ticks() - t0
271 delay
= ticks
- previousTicks
- execTime
273 pygame
.time
.wait(delay
)
275 previousTicks
= ticks