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