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()
103 def getFirstEventTicks(self
) :
104 "retourne le timecode du premier événement (entier)"
106 if l
.startswith('EVT ') :
108 firstTicks
= int(l
.split(None, 2)[1])
112 def getLastEventTicks(self
) :
113 "retourne le timecode du dernier événement (entier)"
114 for l
in self
.getBackwardLineIterator() :
115 if l
.startswith('EVT ') :
120 lastTicks
= int(l
.split(None, 2)[1])
130 line
= self
.logfile
.next().strip()
133 def getEventsIterator(self
) :
134 """ Retourne un itérateur sur les événements.
135 Chaque itération retourne un tuple de 3 éléments :
136 (timecode, nom_événement, données) avec le typage :
137 (entier, chaîne, chaîne)
143 except StopIteration :
146 if not l
.startswith('EVT ') :
149 ticks
, eventName
, message
= l
.split(None, 3)[1:]
151 yield ticks
, eventName
, message
153 ticks
, eventName
= l
.split(None, 3)[1:]
155 yield ticks
, eventName
, ''
157 def getBackwardLineIterator(self
) :
158 br
= BackwardsReader(self
.logfile
, BLKSIZE
=128)
165 def getMetadata(self
) :
167 self
.next() # skip identification line.
169 while line
.startswith('METADATA ') :
170 line
= line
.split(None, 1)[1]
171 name
, value
= [v
.strip() for v
in line
.split(':', 1)]
172 metadata
[name
] = value
176 def setMetadata(self
, metadata
) :
179 before
= f
.readline()
181 while line
.startswith('METADATA ') :
183 after
= line
+ f
.read()
186 for name
, value
in metadata
:
187 lines
.append('METADATA %s : %s' % (name
, value
.encode('utf-8')))
188 metadata
= '\n'.join(lines
)
199 class LogFilePlayer(PlayingScreenBase
) :
201 ré-exécution d'une chanson sur la base de son fichier de log.
204 def __init__(self
, logfile
) :
205 lfr
= self
.lfr
= LogFileReader(logfile
)
206 songFile
= lfr
.getSongFile()
207 soundFontFile
= lfr
.getSoundFontFile()
208 sfPath
= lfr
.getSoundFontFile()
210 preset
= lfr
.getPreset()
211 synth
= Synth(sfPath
=sfPath
)
212 synth
.program_select(0, bank
, preset
)
213 self
.song
= musicXml2Song(songFile
)
214 screenResolution
= lfr
.getScreenResolution()
216 pygame
.display
.set_mode(screenResolution
)
218 super(LogFilePlayer
, self
).__init
__(synth
, self
.song
.distinctNotes
)
222 clock
= pygame
.time
.Clock()
223 pygame
.display
.flip()
224 pygame
.mouse
.set_visible(False)
226 previousTicks
= self
.lfr
.getFirstEventTicks()
227 eIter
= self
.lfr
.getEventsIterator()
229 for ticks
, eventName
, message
in eIter
:
230 t0
= pygame
.time
.get_ticks()
231 if eventName
== 'COLSTATECHANGE' :
232 parts
= message
.split(None, 4)
235 index
, state
, midi
, name
, syllabus
= parts
238 state
= state
== 'True'
239 col
= self
.columns
[midi
]
240 col
.update(state
, syllabus
=syllabus
.decode('utf-8'))
242 elif eventName
== 'NOTEON':
243 chan
, key
, vel
= [int(v
) for v
in message
.split(None, 2)]
244 self
.synth
.noteon(chan
, key
, vel
)
246 elif eventName
== 'NOTEOFF':
247 chan
, key
= [int(v
) for v
in message
.split(None, 1)]
248 self
.synth
.noteoff(chan
, key
)
250 elif eventName
.startswith('COL') :
251 pos
= [int(n
) for n
in message
.split(None, 4)[-1].strip('()').split(',')]
252 self
.cursor
.setPosition(pos
)
257 dirty
= self
.draw(pygame
.display
.get_surface())
258 pygame
.display
.update(dirty
)
259 execTime
= pygame
.time
.get_ticks() - t0
261 delay
= ticks
- previousTicks
- execTime
263 pygame
.time
.wait(delay
)
265 previousTicks
= ticks