1 # -*- coding: utf-8 -*-
3 Module d'analyse des fichiers de log minwii.
9 from minwii
.logfilereader
import LogFileReader
10 from pprint
import pprint
11 from minwii
.musicxml
import musicXml2Song
12 from minwii
.globals import PLAYING_MODES
13 from statlib
import stats
14 from datetime
import timedelta
15 from xml
.etree
import ElementTree
18 PLAYING_MODES
= dict(PLAYING_MODES
)
20 DEFAULT_STATS
= (#'geometricmean',
21 ('harmonicmean', 'Moyenne harmonique'),
23 ('median', 'Médiane'),
27 ('variation', 'Variation'),
29 ('kurtosis', 'Kurtosis'),
37 def computeList(self
):
40 for name
, label
in DEFAULT_STATS
:
41 results
.append('%s : %s' % (label
, getattr(stats
, name
)(l
)))
42 return '\n'.join(results
)
43 computeList
.__name
__ = m
.__name
__
44 computeList
.__doc
__ = m
.__doc
__
51 class LogFileAnalyser(LogFileReader
) :
53 POSSIBLE_ANALYSES
= {'BEGINNER' : ('songDuration',
55 'noteEndNoteOnLatency',
58 ,'EASY' : ('songDuration',
60 'noteEndNoteOnLatency',
63 'getMissPerTimeFrame')
65 ,'NORMAL' : ('songDuration',
69 'getMissPerTimeFrame')
71 ,'ADVANCED' : ('songDuration',
75 'getMissPerTimeFrame')
77 ,'EXPERT' : ('songDuration',
81 'getMissPerTimeFrame')
88 self
.mode
= mode
= self
.getMode()
89 results
.append(('Mode de jeu', PLAYING_MODES
.get(mode
, mode
), False))
91 self
.songTitle
= LogFileAnalyser
.getSongTitle(self
.getSongFile())
92 results
.append(('Chanson', self
.songTitle
, False))
94 for name
in self
.POSSIBLE_ANALYSES
[mode
] :
95 meth
= getattr(self
, name
)
96 results
.append( (meth
.__doc
__, meth(), getattr(meth
, 'timebased', False)) )
103 def getSongTitle(file) :
104 if os
.path
.exists(file) :
105 it
= ElementTree
.iterparse(file, ['start', 'end'])
109 if el
.tag
== 'credit' :
111 if el
.tag
== 'credit-words' and creditFound
:
113 if el
.tag
== 'part-list' :
114 # plus de chance de trouver un titre
115 return os
.path
.basename(file)
117 return os
.path
.basename(file)
119 def _toTimeDelta(self
, milliseconds
) :
120 duration
= milliseconds
/ 1000.
121 duration
= int(round(duration
, 0))
122 return str(timedelta(seconds
=duration
))
124 def playingDuration(self
) :
126 #retourne la durée écoulée entre le premier et de dernier message
127 #de type événement : correspond à la durée d'interprétation.
129 last
= self
.getLastEventTicks()
130 first
= self
.getFirstEventTicks()
131 return self
._toTimeDelta
(last
- first
)
134 def songDuration(self
) :
135 'Durée de référence de la chanson'
136 #retourne la durée de référence de la chanson
137 #en prenant en compte le tempo présent dans la transcription
138 #et en effectuant toutes les répétitions des couplets / refrains.
140 songFile
= self
.getSongFile()
141 song
= musicXml2Song(songFile
)
143 for note
, verseIndex
in song
.iterNotes() :
144 duration
= duration
+ note
.duration
145 duration
= duration
* song
.quarterNoteDuration
# en milisecondes
146 return self
._toTimeDelta
(duration
)
149 def noteEndNoteOnLatency(self
) :
151 eIter
= self
.getEventsIterator()
155 for ticks
, eventName
, message
in eIter
:
156 if eventName
== 'NOTEEND':
158 if eventName
== 'NOTEON' and lastnoteEndT
:
159 latencies
.append(ticks
- lastnoteEndT
)
163 def noteOnCount(self
) :
164 "retourne le nombre d'événements NOTEON"
166 eIter
= self
.getEventsIterator()
169 for ticks
, eventName
, message
in eIter
:
170 if eventName
== 'NOTEON' :
175 def realisationRate(self
) :
176 'Taux de réalisation'
177 #taux de réalisation en nombre de note
178 #peut être supérieur à 100 % car la chanson
181 songFile
= self
.getSongFile()
182 song
= musicXml2Song(songFile
)
184 for note
, verseIndex
in song
.iterNotes() :
185 songNoteCpt
= songNoteCpt
+ 1
187 return round(self
.noteOnCount() / float(songNoteCpt
) * 100, 1)
189 def missCount(self
) :
191 eIter
= self
.getEventsIterator()
193 if self
.mode
in ('EASY', 'NORMAL') :
195 for ticks
, eventName
, message
in eIter
:
196 if eventName
== 'COLDOWN' :
197 colState
= message
.split(None, 2)[1]
198 colState
= colState
== 'True'
204 elif eventName
== 'NOTEON' :
206 elif eventName
== 'COLUP' and catchColUp
:
209 for ticks
, eventName
, message
in eIter
:
210 if eventName
== 'COLDOWN' :
211 colState
= message
.split(None, 2)[1]
212 colState
= colState
== 'True'
219 def getMissPerTimeFrame(self
, timeFrame
=10000) :
220 "Nombre d'erreurs en fonction du temps"
221 eIter
= self
.getEventsIterator()
222 firstTicks
= self
.getFirstEventTicks()
225 if self
.mode
in ('EASY', 'NORMAL') :
227 for ticks
, eventName
, message
in eIter
:
228 if ticks
- firstTicks
> timeFrame
:
232 if eventName
== 'COLDOWN' :
233 colState
= message
.split(None, 2)[1]
234 colState
= colState
== 'True'
240 elif eventName
== 'NOTEON' :
242 elif eventName
== 'COLUP' and catchColUp
:
243 frames
[-1] = frames
[-1] + 1
245 for ticks
, eventName
, message
in eIter
:
246 if ticks
- firstTicks
> timeFrame
:
250 if eventName
== 'COLDOWN' :
251 colState
= message
.split(None, 2)[1]
252 colState
= colState
== 'True'
254 frames
[-1] = frames
[-1] + 1
263 from optparse
import OptionParser
264 usage
= "%prog logfile"
265 op
= OptionParser(usage
)
266 options
, args
= op
.parse_args()
268 op
.error("incorrect number of arguments")
271 lfa
= LogFileAnalyser(args
[0])
272 pprint(lfa
.analyse())
274 if __name__
== "__main__" :
275 from os
.path
import realpath
, sep
277 minwiipath
= realpath(__file__
).split(sep
)
278 minwiipath
= minwiipath
[:-2]
279 minwiipath
= sep
.join(minwiipath
)
280 sys
.path
.insert(1, minwiipath
)