passage en V1 du format de log.
[minwii.git] / src / app / logfilereader.py
1 # -*- coding: utf-8 -*-
2 """
3 Module de lecture des fichiers de log minwii
4
5 $Id$
6 $URL$
7 """
8
9 from widgets.playingscreen import PlayingScreenBase
10 from eventutils import EventDispatcher
11 from events import eventCodes
12 from synth import Synth
13 from musicxml import musicXml2Song
14 import pygame
15
16 SUPPORTED_FILE_HEADER = 'ENV winwii log format version : 1.0'
17
18 class LogFileReader(object) :
19
20 def __init__(self, logfile) :
21 if isinstance(logfile, str) :
22 self.logfile = open(logfile, 'r')
23 else :
24 self.logfile = logfile
25
26 firstline = self.next()
27 assert firstline == SUPPORTED_FILE_HEADER
28
29
30 def getSongFile(self) :
31 f = self.logfile
32 pos = f.tell()
33
34 f.seek(0)
35 for l in self :
36 if l.startswith('APP chanson :') :
37 break
38 songfile = l.split(':', 1)[1].strip()
39 f.seek(pos)
40 return songfile
41
42 def getSoundFontFile(self) :
43 f = self.logfile
44 pos = f.tell()
45 f.seek(0)
46 for l in self :
47 if l.startswith('ENV soundfont :') :
48 break
49 soundFontFile = l.split(':', 1)[1].strip()
50 f.seek(pos)
51 return soundFontFile
52
53 def getBank(self) :
54 f = self.logfile
55 pos = f.tell()
56 f.seek(0)
57 for l in self :
58 if l.startswith('APP bank :') :
59 break
60 f.seek(pos)
61 bank = l.split(':', 1)[1].strip()
62 return int(bank)
63
64 def getPreset(self) :
65 f = self.logfile
66 pos = f.tell()
67 f.seek(0)
68 for l in self :
69 if l.startswith('APP preset :') :
70 break
71 f.seek(pos)
72 preset = l.split(':', 1)[1].strip()
73 return int(preset)
74
75 def getScreenResolution(self) :
76 f = self.logfile
77 pos = f.tell()
78 f.seek(0)
79 for l in self :
80 if l.startswith('ENV résolution écran :') :
81 break
82 screenResolution = eval(l.split(':', 1)[1].strip())
83 f.seek(pos)
84 return screenResolution
85
86 def getFirstEventTicks(self) :
87 f = self.logfile
88 pos = f.tell()
89 f.seek(0)
90 for l in self :
91 if l.startswith('EVT ') :
92 break
93 firstTicks = int(l.split(None, 2)[1])
94 f.seek(pos)
95 return firstTicks
96
97 def __del__(self) :
98 self.logfile.close()
99
100 def __iter__(self) :
101 return self
102
103 def next(self) :
104 line = self.logfile.next().strip()
105 return line
106
107 def getEventsIterator(self) :
108 self.logfile.seek(0)
109 while True :
110 try :
111 l = self.next()
112 except StopIteration :
113 break
114
115 if not l.startswith('EVT ') :
116 continue
117 try :
118 ticks, eventName, message = l.split(None, 3)[1:]
119 yield ticks, eventName, message
120 except ValueError :
121 ticks, eventName = l.split(None, 3)[1:]
122 yield ticks, eventName, ''
123
124
125 class LogFilePlayer(PlayingScreenBase) :
126 """
127 ré-exécution d'une chanson sur la base de son fichier de log.
128 """
129
130 def __init__(self, logfile) :
131 lfr = self.lfr = LogFileReader(logfile)
132 songFile = lfr.getSongFile()
133 soundFontFile = lfr.getSoundFontFile()
134 sfPath = lfr.getSoundFontFile()
135 bank = lfr.getBank()
136 preset = lfr.getPreset()
137 synth = Synth(sfPath=sfPath)
138 synth.program_select(0, bank, preset)
139 self.song = musicXml2Song(songFile)
140 screenResolution = lfr.getScreenResolution()
141
142 pygame.display.set_mode(screenResolution)
143
144 super(LogFilePlayer, self).__init__(synth, self.song.distinctNotes)
145
146 def run(self):
147 self._running = True
148 clock = pygame.time.Clock()
149 pygame.display.flip()
150 pygame.mouse.set_visible(False)
151
152 previousTicks = self.lfr.getFirstEventTicks()
153 eIter = self.lfr.getEventsIterator()
154
155 for ticks, eventName, message in eIter :
156 t0 = pygame.time.get_ticks()
157 ticks = int(ticks)
158 if eventName == 'COLSTATECHANGE' :
159 parts = message.split(None, 4)
160 if len(parts) == 4 :
161 parts.append('')
162 index, state, midi, name, syllabus = parts
163 index = int(index)
164 midi = int(midi)
165 state = state == 'True'
166 col = self.columns[midi]
167 col.update(state, syllabus=syllabus.decode('utf-8'))
168
169 elif eventName == 'NOTEON':
170 chan, key, vel = [int(v) for v in message.split(None, 2)]
171 self.synth.noteon(chan, key, vel)
172
173 elif eventName == 'NOTEOFF':
174 chan, key = [int(v) for v in message.split(None, 1)]
175 self.synth.noteoff(chan, key)
176
177 elif eventName.startswith('COL') :
178 pos = [int(n) for n in message.split(None, 4)[-1].strip('()').split(',')]
179 self.cursor.setPosition(pos)
180
181
182 pygame.event.clear() # à virer
183 #EventDispatcher.dispatchEvents()
184
185 dirty = self.draw(pygame.display.get_surface())
186 pygame.display.update(dirty)
187 execTime = pygame.time.get_ticks() - t0
188
189 delay = ticks - previousTicks - execTime
190 if delay > 0 :
191 pygame.time.wait(delay)
192
193 previousTicks = ticks
194 #print ticks, eventName, message
195
196 #while self._running :
197 # EventDispatcher.dispatchEvents()
198 # dirty = self.draw(pygame.display.get_surface())
199 # pygame.display.update(dirty)
200 # clock.tick()
201
202