73581cb2648abca9a92d121f705ff06f3041ebf7
[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['playingMode'] = mode
70 for name in self.POSSIBLE_ANALYSES[mode] :
71 meth = getattr(self, name)
72 results[name] = meth()
73 except :
74 pass
75
76 return results
77
78 def playingDuration(self) :
79 """ retourne la durée écoulée entre le premier et de dernier message
80 de type événement : correspond à la durée d'interprétation.
81 """
82 last = self.getLastEventTicks()
83 first = self.getFirstEventTicks()
84 return last - first
85
86 def songDuration(self) :
87 """ retourne la durée de référence de la chanson
88 en prenant en compte le tempo présent dans la transcription
89 et en effectuant toutes les répétitions des couplets / refrains.
90 """
91 songFile = self.getSongFile()
92 song = musicXml2Song(songFile)
93 duration = 0
94 for note, verseIndex in song.iterNotes() :
95 duration = duration + note.duration
96 return duration * song.quarterNoteDuration
97
98 @statsresults
99 def noteEndNoteOnLatency(self) :
100 eIter = self.getEventsIterator()
101 latencies = []
102 lastnoteEndT = 0
103
104 for ticks, eventName, message in eIter :
105 if eventName == 'NOTEEND':
106 lastnoteEndT = ticks
107 if eventName == 'NOTEON' and lastnoteEndT :
108 latencies.append(ticks - lastnoteEndT)
109
110 return latencies
111
112 def noteOnCount(self) :
113 "retourne le nombre d'événements NOTEON"
114
115 eIter = self.getEventsIterator()
116 cpt = 0
117
118 for ticks, eventName, message in eIter :
119 if eventName == 'NOTEON' :
120 cpt = cpt + 1
121
122 return cpt
123
124 def realisationRate(self) :
125 """ taux de réalisation en nombre de note
126 peut être supérieur à 100 % car la chanson
127 boucle à l'infini.
128 """
129 songFile = self.getSongFile()
130 song = musicXml2Song(songFile)
131 songNoteCpt = 0
132 for note, verseIndex in song.iterNotes() :
133 songNoteCpt = songNoteCpt + 1
134
135 return int(round(self.noteOnCount() / float(songNoteCpt) * 100, 0))
136
137 def missCount(self) :
138 eIter = self.getEventsIterator()
139 miss = 0
140 if self.mode in ('EASY', 'NORMAL') :
141 catchColUp = False
142 for ticks, eventName, message in eIter :
143 if eventName == 'COLDOWN' :
144 colState = message.split(None, 2)[1]
145 colState = colState == 'True'
146 if colState :
147 catchColUp = False
148 continue
149 else :
150 catchColUp = True
151 elif eventName == 'NOTEON' :
152 catchColUp = False
153 elif eventName == 'COLUP' and catchColUp :
154 miss = miss + 1
155 else :
156 for ticks, eventName, message in eIter :
157 if eventName == 'COLDOWN' :
158 colState = message.split(None, 2)[1]
159 colState = colState == 'True'
160 if not colState :
161 miss = miss + 1
162
163 return miss
164
165
166
167
168
169 def main() :
170 from optparse import OptionParser
171 usage = "%prog logfile"
172 op = OptionParser(usage)
173 options, args = op.parse_args()
174 if len(args) != 1 :
175 op.error("incorrect number of arguments")
176
177
178 lfa = LogFileAnalyser(args[0])
179 pprint(lfa.analyse())
180
181 if __name__ == "__main__" :
182 from os.path import realpath, sep
183 import sys
184 minwiipath = realpath(__file__).split(sep)
185 minwiipath = minwiipath[:-2]
186 minwiipath = sep.join(minwiipath)
187 sys.path.insert(1, minwiipath)
188 main()