Affichage, déjà plus « user-friendly ».
[minwii.git] / src / minwii / loganalyse.py
1 # -*- coding: utf-8 -*-
2 """
3 Module d'analyse des fichiers de log minwii.
4
5 $Id$
6 $URL$
7 """
8
9 from minwii.logfilereader import LogFileReader
10 from pprint import pprint
11 from minwii.musicxml import musicXml2Song
12 from statlib import stats
13
14 DEFAULT_STATS = ('geometricmean',
15 'harmonicmean',
16 'mean',
17 'median',
18 'medianscore',
19 #'mode',
20 'moment',
21 'variation',
22 'skew',
23 'kurtosis',
24 #'itemfreq',
25 #'histogram',
26 #'cumfreq',
27 #'relfreq',
28 )
29
30 def statsresults(m) :
31 def computeList(self):
32 l = m(self)
33 ret = {}
34 for name in DEFAULT_STATS :
35 ret[name] = getattr(stats, name)(l)
36 return ret
37 return computeList
38
39 class LogFileAnalyser(LogFileReader) :
40
41 POSSIBLE_ANALYSES = {'BEGINNER' : ('songDuration',
42 'playingDuration',
43 'noteEndNoteOnLatency',
44 'realisationRate')
45 ,'EASY' : ('songDuration',
46 'playingDuration',
47 'noteEndNoteOnLatency',
48 'realisationRate',
49 'missCount')
50 ,'NORMAL' : ('songDuration',
51 'playingDuration',
52 'realisationRate',
53 'missCount')
54 ,'ADVANCED' : ('songDuration',
55 'playingDuration',
56 'realisationRate',
57 'missCount')
58 ,'EXPERT' : ('songDuration',
59 'playingDuration',
60 'realisationRate',
61 'missCount')
62 }
63
64 def analyse(self) :
65 results = []
66
67 try :
68 self.mode = mode = self.getMode()
69 results.append(('Mode de jeu', mode))
70 for name in self.POSSIBLE_ANALYSES[mode] :
71 meth = getattr(self, name)
72 results.append((meth.__doc__, meth()))
73 except :
74 pass
75
76 return results
77
78 def playingDuration(self) :
79 'Temps de jeu'
80 #retourne la durée écoulée entre le premier et de dernier message
81 #de type événement : correspond à la durée d'interprétation.
82
83 last = self.getLastEventTicks()
84 first = self.getFirstEventTicks()
85 return last - first
86
87 def songDuration(self) :
88 'Durée de la chanson'
89 #retourne la durée de référence de la chanson
90 #en prenant en compte le tempo présent dans la transcription
91 #et en effectuant toutes les répétitions des couplets / refrains.
92
93 songFile = self.getSongFile()
94 song = musicXml2Song(songFile)
95 duration = 0
96 for note, verseIndex in song.iterNotes() :
97 duration = duration + note.duration
98 return duration * song.quarterNoteDuration
99
100 @statsresults
101 def noteEndNoteOnLatency(self) :
102 'Réactivité'
103 eIter = self.getEventsIterator()
104 latencies = []
105 lastnoteEndT = 0
106
107 for ticks, eventName, message in eIter :
108 if eventName == 'NOTEEND':
109 lastnoteEndT = ticks
110 if eventName == 'NOTEON' and lastnoteEndT :
111 latencies.append(ticks - lastnoteEndT)
112
113 return latencies
114
115 def noteOnCount(self) :
116 "retourne le nombre d'événements NOTEON"
117
118 eIter = self.getEventsIterator()
119 cpt = 0
120
121 for ticks, eventName, message in eIter :
122 if eventName == 'NOTEON' :
123 cpt = cpt + 1
124
125 return cpt
126
127 def realisationRate(self) :
128 'Taux de réalisation'
129 #taux de réalisation en nombre de note
130 #peut être supérieur à 100 % car la chanson
131 #boucle à l'infini.
132
133 songFile = self.getSongFile()
134 song = musicXml2Song(songFile)
135 songNoteCpt = 0
136 for note, verseIndex in song.iterNotes() :
137 songNoteCpt = songNoteCpt + 1
138
139 return int(round(self.noteOnCount() / float(songNoteCpt) * 100, 0))
140
141 def missCount(self) :
142 "Nombre d'erreurs"
143 eIter = self.getEventsIterator()
144 miss = 0
145 if self.mode in ('EASY', 'NORMAL') :
146 catchColUp = False
147 for ticks, eventName, message in eIter :
148 if eventName == 'COLDOWN' :
149 colState = message.split(None, 2)[1]
150 colState = colState == 'True'
151 if colState :
152 catchColUp = False
153 continue
154 else :
155 catchColUp = True
156 elif eventName == 'NOTEON' :
157 catchColUp = False
158 elif eventName == 'COLUP' and catchColUp :
159 miss = miss + 1
160 else :
161 for ticks, eventName, message in eIter :
162 if eventName == 'COLDOWN' :
163 colState = message.split(None, 2)[1]
164 colState = colState == 'True'
165 if not colState :
166 miss = miss + 1
167
168 return miss
169
170
171
172
173
174 def main() :
175 from optparse import OptionParser
176 usage = "%prog logfile"
177 op = OptionParser(usage)
178 options, args = op.parse_args()
179 if len(args) != 1 :
180 op.error("incorrect number of arguments")
181
182
183 lfa = LogFileAnalyser(args[0])
184 pprint(lfa.analyse())
185
186 if __name__ == "__main__" :
187 from os.path import realpath, sep
188 import sys
189 minwiipath = realpath(__file__).split(sep)
190 minwiipath = minwiipath[:-2]
191 minwiipath = sep.join(minwiipath)
192 sys.path.insert(1, minwiipath)
193 main()