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
)
142 duration
= song
.duration
143 return self
._toTimeDelta
(duration
)
146 def noteEndNoteOnLatency(self
) :
148 eIter
= self
.getEventsIterator()
152 for ticks
, eventName
, message
in eIter
:
153 if eventName
== 'NOTEEND':
155 if eventName
== 'NOTEON' and lastnoteEndT
:
156 latencies
.append(ticks
- lastnoteEndT
)
160 def noteOnCount(self
) :
161 "retourne le nombre d'événements NOTEON"
163 eIter
= self
.getEventsIterator()
166 for ticks
, eventName
, message
in eIter
:
167 if eventName
== 'NOTEON' :
172 def realisationRate(self
) :
173 'Taux de réalisation'
174 #taux de réalisation en nombre de note
175 #peut être supérieur à 100 % car la chanson
178 songFile
= self
.getSongFile()
179 song
= musicXml2Song(songFile
)
181 for note
, verseIndex
in song
.iterNotes() :
182 songNoteCpt
= songNoteCpt
+ 1
184 return round(self
.noteOnCount() / float(songNoteCpt
) * 100, 1)
186 def missCount(self
) :
188 eIter
= self
.getEventsIterator()
190 if self
.mode
in ('EASY', 'NORMAL') :
192 for ticks
, eventName
, message
in eIter
:
193 if eventName
== 'COLDOWN' :
194 colState
= message
.split(None, 2)[1]
195 colState
= colState
== 'True'
201 elif eventName
== 'NOTEON' :
203 elif eventName
== 'COLUP' and catchColUp
:
206 for ticks
, eventName
, message
in eIter
:
207 if eventName
== 'COLDOWN' :
208 colState
= message
.split(None, 2)[1]
209 colState
= colState
== 'True'
216 def getMissPerTimeFrame(self
, timeFrame
=10000) :
217 "Nombre d'erreurs en fonction du temps"
218 eIter
= self
.getEventsIterator()
219 firstTicks
= self
.getFirstEventTicks()
222 if self
.mode
in ('EASY', 'NORMAL') :
224 for ticks
, eventName
, message
in eIter
:
225 if ticks
- firstTicks
> timeFrame
:
229 if eventName
== 'COLDOWN' :
230 colState
= message
.split(None, 2)[1]
231 colState
= colState
== 'True'
237 elif eventName
== 'NOTEON' :
239 elif eventName
== 'COLUP' and catchColUp
:
240 frames
[-1] = frames
[-1] + 1
242 for ticks
, eventName
, message
in eIter
:
243 if ticks
- firstTicks
> timeFrame
:
247 if eventName
== 'COLDOWN' :
248 colState
= message
.split(None, 2)[1]
249 colState
= colState
== 'True'
251 frames
[-1] = frames
[-1] + 1
260 from optparse
import OptionParser
261 usage
= "%prog logfile"
262 op
= OptionParser(usage
)
263 options
, args
= op
.parse_args()
265 op
.error("incorrect number of arguments")
268 lfa
= LogFileAnalyser(args
[0])
269 pprint(lfa
.analyse())
271 if __name__
== "__main__" :
272 from os
.path
import realpath
, sep
274 minwiipath
= realpath(__file__
).split(sep
)
275 minwiipath
= minwiipath
[:-2]
276 minwiipath
= sep
.join(minwiipath
)
277 sys
.path
.insert(1, minwiipath
)