1 # -*- coding: utf-8 -*-
3 Module de lecture des fichiers de log minwii
9 from types
import StringTypes
10 from widgets
.playingscreen
import PlayingScreenBase
11 from eventutils
import EventDispatcher
12 from events
import eventCodes
13 from synth
import Synth
14 from musicxml
import musicXml2Song
16 from backwardsfilereader
import BackwardsReader
18 SUPPORTED_FILE_HEADER
= 'ENV winwii log format version : 1.0'
21 def readinplace(self
, *args
, **kw
) :
22 pos
= self
.logfile
.tell()
24 ret
= m(self
, *args
, **kw
)
25 self
.logfile
.seek(pos
)
29 class LogFileReader(object) :
31 classe utilitaire pour l'accès aux données d'un fichier de log MinWii.
34 def __init__(self
, logfile
) :
35 """ logfile : chemin d'accès au fichier de log MinWii.
36 le format supporté est actuellement la version 1.0 uniquement.
38 if isinstance(logfile
, StringTypes
) :
39 self
.logfile
= open(logfile
, 'r')
41 self
.logfile
= logfile
43 firstline
= self
.next()
44 assert firstline
== SUPPORTED_FILE_HEADER
48 def getSongFile(self
) :
49 "retourne le chemin d'accès au fichier musicxml de la chanson"
51 if l
.startswith('APP chanson :') :
53 songfile
= l
.split(':', 1)[1].strip()
57 def getSoundFontFile(self
) :
58 "retourne le chemin d'accès au fichier de la soundfont (*.sf2)"
60 if l
.startswith('ENV soundfont :') :
62 soundFontFile
= l
.split(':', 1)[1].strip()
67 "retourne le paramètre bank du synthétiseur (entier)"
69 if l
.startswith('APP bank :') :
71 bank
= l
.split(':', 1)[1].strip()
76 "retourne le paramètre preset du synthétiseur (entier)"
78 if l
.startswith('APP preset :') :
80 preset
= l
.split(':', 1)[1].strip()
84 def getScreenResolution(self
) :
85 "retourne la résolution écran (tuple de deux entiers)"
87 if l
.startswith('ENV résolution écran :') :
89 screenResolution
= eval(l
.split(':', 1)[1].strip())
90 return screenResolution
94 "retourne le niveau de difficulté"
96 if l
.startswith('APP mode :') :
99 mode
= l
.split(':', 1)[1].strip()
103 def getFirstEventTicks(self
) :
104 "retourne le timecode du premier événement (entier)"
106 if l
.startswith('EVT ') :
108 firstTicks
= int(l
.split(None, 2)[1])
112 def getLastEventTicks(self
) :
113 "retourne le timecode du dernier événement (entier)"
114 for l
in self
.getBackwardLineIterator() :
115 if l
.startswith('EVT ') :
118 lastTicks
= int(l
.split(None, 2)[1])
128 line
= self
.logfile
.next().strip()
131 def getEventsIterator(self
) :
132 """ Retourne un itérateur sur les événements.
133 Chaque itération retourne un tuple de 3 éléments :
134 (timecode, nom_événement, données) avec le typage :
135 (entier, chaîne, chaîne)
141 except StopIteration :
144 if not l
.startswith('EVT ') :
147 ticks
, eventName
, message
= l
.split(None, 3)[1:]
149 yield ticks
, eventName
, message
151 ticks
, eventName
= l
.split(None, 3)[1:]
153 yield ticks
, eventName
, ''
155 def getBackwardLineIterator(self
) :
156 br
= BackwardsReader(self
.logfile
, BLKSIZE
=128)
164 class LogFilePlayer(PlayingScreenBase
) :
166 ré-exécution d'une chanson sur la base de son fichier de log.
169 def __init__(self
, logfile
) :
170 lfr
= self
.lfr
= LogFileReader(logfile
)
171 songFile
= lfr
.getSongFile()
172 soundFontFile
= lfr
.getSoundFontFile()
173 sfPath
= lfr
.getSoundFontFile()
175 preset
= lfr
.getPreset()
176 synth
= Synth(sfPath
=sfPath
)
177 synth
.program_select(0, bank
, preset
)
178 self
.song
= musicXml2Song(songFile
)
179 screenResolution
= lfr
.getScreenResolution()
181 pygame
.display
.set_mode(screenResolution
)
183 super(LogFilePlayer
, self
).__init
__(synth
, self
.song
.distinctNotes
)
187 clock
= pygame
.time
.Clock()
188 pygame
.display
.flip()
189 pygame
.mouse
.set_visible(False)
191 previousTicks
= self
.lfr
.getFirstEventTicks()
192 eIter
= self
.lfr
.getEventsIterator()
194 for ticks
, eventName
, message
in eIter
:
195 t0
= pygame
.time
.get_ticks()
196 if eventName
== 'COLSTATECHANGE' :
197 parts
= message
.split(None, 4)
200 index
, state
, midi
, name
, syllabus
= parts
203 state
= state
== 'True'
204 col
= self
.columns
[midi
]
205 col
.update(state
, syllabus
=syllabus
.decode('utf-8'))
207 elif eventName
== 'NOTEON':
208 chan
, key
, vel
= [int(v
) for v
in message
.split(None, 2)]
209 self
.synth
.noteon(chan
, key
, vel
)
211 elif eventName
== 'NOTEOFF':
212 chan
, key
= [int(v
) for v
in message
.split(None, 1)]
213 self
.synth
.noteoff(chan
, key
)
215 elif eventName
.startswith('COL') :
216 pos
= [int(n
) for n
in message
.split(None, 4)[-1].strip('()').split(',')]
217 self
.cursor
.setPosition(pos
)
222 dirty
= self
.draw(pygame
.display
.get_surface())
223 pygame
.display
.update(dirty
)
224 execTime
= pygame
.time
.get_ticks() - t0
226 delay
= ticks
- previousTicks
- execTime
228 pygame
.time
.wait(delay
)
230 previousTicks
= ticks