0b1b272b960cb776f13bc298d892b548da1236ff
[minwii.git] / src / gui / PlayingScreen.py
1 '''
2 Created on 23 juil. 2009
3
4 @author: Samuel Benveniste
5 '''
6 from math import floor, ceil
7 import pygame
8 import sys
9 import colorsys
10 import constants
11 from gradients import gradients
12 from logging.PickleableEvent import PickleableEvent
13
14
15 class PlayingScreen:
16 '''
17 The screen on which the game is played
18
19 wiimotes:
20 The wiimotes used in this session
21 window:
22 The main display window
23 screen:
24 The main display surface
25 clock:
26 The clock used to animate the screen
27 savedScreen:
28 The background that is painted every time
29 playerScreen:
30 The buffer for painting everything before bliting
31 width:
32 The width of the window in pixels
33 height:
34 The height of the window in pixels
35 extendScale :
36 True if the scale is G to C instead of C to C
37 cascade:
38 True if crossing from note to note with a button pressed triggers a new note
39 scaleSize:
40 The size of the scale used
41 cursorPositions:
42 The positions of the cursors on the screen, in pixels
43 '''
44
45
46
47 def __init__(self, instrumentChoice=None, song = None, cascade=False, extendedScale=False, defaultInstrumentChannel = 16, defaultNote = 60):
48 '''
49 Constructor
50 '''
51 self.blinkLength = 200
52 self.minimalVelocity = 64
53 self.shortScaleSize = 8
54 self.longScaleSize = 11
55 if not extendedScale:
56 self.offset = self.longScaleSize - self.shortScaleSize
57 else:
58 self.offset = 0
59 self.borderSize = 5
60 self.savedHighlightedNote = 0
61
62 self.wiimotes = instrumentChoice.wiimotes
63 self.activeWiimotes = instrumentChoice.activeWiimotes
64 self.window = instrumentChoice.window
65 self.screen = instrumentChoice.screen
66 self.blitOrigin = instrumentChoice.blitOrigin
67 self.clock = instrumentChoice.clock
68 self.width = instrumentChoice.width
69 self.height = instrumentChoice.height
70 self.cursorPositions = instrumentChoice.cursorPositions
71 self.savedScreen = instrumentChoice.savedScreen
72 self.playerScreen = instrumentChoice.playerScreen
73 self.extendedScale = extendedScale
74 self.cascade = cascade
75 self.joys = instrumentChoice.joys
76 self.portOffset = instrumentChoice.portOffset
77 self.eventLog = instrumentChoice.eventLog
78 self.cursorPositions = instrumentChoice.cursorPositions
79 self.song = song
80 self.songIterator = self.moveToNextNote()
81 self.replay = instrumentChoice.replay
82
83 self.defaultInstrumentChannel = defaultInstrumentChannel
84 self.defaultNote = defaultNote
85
86 self.done = False
87 self.backToInstrumentChoice = False
88 self.easyMode = False
89
90 self.highlightedNote = self.songIterator.next()
91
92 self.blinkOn = False
93 self.savedBlinkOn = False
94 ##Will prevent the song to move on if two consecutive notes are identical and the buttons have not been released in between the two
95 ##i.e. it guarantees that there will be an attack between two identical consecutive notes
96 self.highlightIsFree = True
97
98 self.noteRects = []
99 self.boundingRect = None
100 self.notes = []
101 self.buttonDown = []
102 self.velocityLock = []
103
104 self._blinkOffset = 0
105
106 self.font = pygame.font.Font(None,50)
107 self.firstWiimote = 0
108 while not self.activeWiimotes[self.firstWiimote] :
109 self.firstWiimote += 1
110 self.renderedNoteNames = [self.font.render(constants.noteNumberToName(note),False,(0,0,0)) for note in self.wiimotes[self.firstWiimote].instrument.notes[self.offset:]]
111
112 self.drawBackground()
113 self.initializeWiimotes()
114
115 #The main loop
116 while not self.done :
117
118 #Clear the cursors from the screen
119 if self.hasChanged():
120 self.drawBackground()
121 self.playerScreen.blit(self.savedScreen, (0, 0))
122
123 # Limit frame speed to 50 FPS
124 #
125 timePassed = self.clock.tick(50)
126
127 self._blinkOffset += timePassed
128 if self._blinkOffset > self.blinkLength:
129 self._blinkOffset -= self.blinkLength
130 self.blinkOn = not self.blinkOn
131
132 if self.replay:
133 self.eventLog.update(timePassed)
134 pickledEventsToPost = self.eventLog.getPickledEvents()
135 for pickledEvent in pickledEventsToPost:
136 pygame.event.post(pickledEvent.event)
137
138 events = pygame.event.get()
139
140 if not self.replay:
141 pickledEvents = [PickleableEvent(event.type,event.dict) for event in events]
142 if pickledEvents != [] :
143 self.eventLog.appendEventGroup(pickledEvents)
144
145 for event in events:
146 self.input(event)
147
148 for i in range(len(self.wiimotes)):
149 if self.activeWiimotes[i]:
150 self.wiimotes[i].cursor.update(timePassed, self.cursorPositions[i])
151 if self.buttonDown[i] :
152 self.wiimotes[i].cursor.flash()
153 self.wiimotes[i].cursor.blit(self.playerScreen)
154
155 self.screen.blit(self.playerScreen, (0,0))
156
157 pygame.display.flip()
158
159 for i in range(len(self.wiimotes)):
160 if self.activeWiimotes[i]:
161 self.wiimotes[i].stopNote(self.notes[i])
162
163 def drawBackground(self):
164 self.savedScreen.fill((255,255,255))
165
166 if self.extendedScale :
167 self.scaleSize = self.longScaleSize
168 else:
169 self.scaleSize = self.shortScaleSize
170
171 self.noteRects = [pygame.Rect(i * self.width / self.scaleSize+self.blitOrigin[0], self.blitOrigin[1], self.width / self.scaleSize + 1, self.height+1) for i in range(self.scaleSize)]
172 #inflate last noteRect to cover the far right pixels
173 self.noteRects[-1].width = self.noteRects[-1].width + 1
174
175 #create bounding rect
176 self.boundingRect = self.noteRects[0].unionall(self.noteRects)
177
178 #fill the rectangles with a color gradient
179 #We start with blue
180 startingHue = 0.66666666666666663
181
182 for rectNumber in range(self.scaleSize):
183 colorRatio = float(rectNumber) / (self.scaleSize - 1)
184 #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up
185 hue = startingHue * (1 - colorRatio)
186 if self.song != None:
187 if rectNumber + self.offset == self.highlightedNote:
188 #The color of the bottom of the rectangle in hls coordinates
189 bottomColorHls = (hue, 0.6, 1)
190 #The color of the top of the rectangle in hls coordinates
191 topColorHls = (hue, 0.9, 1)
192 else:
193 #The color of the bottom of the rectangle in hls coordinates
194 bottomColorHls = (hue, 0.2, 1)
195 #The color of the top of the rectangle in hls coordinates
196 topColorHls = (hue, 0.4, 1)
197 else:
198 #The color of the bottom of the rectangle in hls coordinates
199 bottomColorHls = (hue, 0.6, 1)
200 #The color of the top of the rectangle in hls coordinates
201 topColorHls = (hue, 0.9, 1)
202
203 #convert to rgb ranging from 0 to 255
204 bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)]
205 topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)]
206 #add transparency
207 bottomColorRgb.append(255)
208 topColorRgb.append(255)
209 #convert to tuple
210 bottomColorRgb = tuple(bottomColorRgb)
211 topColorRgb = tuple(topColorRgb)
212
213 self.savedScreen.blit(gradients.vertical(self.noteRects[rectNumber].size, topColorRgb, bottomColorRgb), self.noteRects[rectNumber])
214
215 textBlitPoint = (self.noteRects[rectNumber].left+(self.noteRects[rectNumber].width-self.renderedNoteNames[rectNumber].get_width())/2,
216 self.noteRects[rectNumber].bottom-self.renderedNoteNames[rectNumber].get_height())
217
218 self.savedScreen.blit(self.renderedNoteNames[rectNumber], textBlitPoint)
219
220 pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[rectNumber], 2)
221
222
223 if self.song != None and self.blinkOn:
224 borderSize = self.borderSize
225 pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 0), self.noteRects[self.highlightedNote-self.offset].inflate(borderSize/2,borderSize/2), borderSize)
226
227 def initializeWiimotes(self):
228 for loop in self.wiimotes:
229 if loop.port == None :
230 loop.port = pygame.midi.Output(loop.portNumber)
231 self.notes.append(0)
232 self.buttonDown.append(False)
233 self.velocityLock.append(False)
234
235 def updateCursorPositionFromJoy(self, joyEvent):
236 joyName = pygame.joystick.Joystick(joyEvent.joy).get_name()
237 correctedJoyId = constants.joyNames.index(joyName)
238 if correctedJoyId < len(self.cursorPositions):
239 if joyEvent.axis == 0 :
240 self.cursorPositions[correctedJoyId] = (int((joyEvent.value + 1) / 2 * self.screen.get_width()), self.cursorPositions[correctedJoyId][1])
241 if joyEvent.axis == 1 :
242 self.cursorPositions[correctedJoyId] = (self.cursorPositions[correctedJoyId][0], int((joyEvent.value + 1) / 2 * self.screen.get_height()))
243
244 def heightToVelocity(self, pos, controllerNumber):
245 if self.song != None:
246 if self.boundingRect.collidepoint(pos) and (self.highlightedNote == self.notes[controllerNumber] or self.velocityLock[controllerNumber]):
247 velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity)
248 else :
249 if self.easyMode:
250 velocity = None
251 else:
252 velocity = self.minimalVelocity/3
253 else:
254 if self.boundingRect.collidepoint(pos):
255 velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity)
256 else :
257 velocity = self.minimalVelocity
258 return(velocity)
259
260 def widthToNote(self, pos):
261 nn = 0
262 try :
263 while self.noteRects[nn].collidepoint(pos) == False:
264 nn = nn + 1
265 return(nn + self.offset)
266 except(IndexError):
267 return(None)
268
269 def input(self, event):
270
271 if event.type == pygame.QUIT:
272 for loop in self.wiimotes:
273 del loop.port
274 pygame.midi.quit()
275 sys.exit(0)
276
277 if event.type == pygame.KEYDOWN:
278 if event.key == pygame.K_q:
279 self.done = True
280
281 if event.key == pygame.K_i:
282 self.backToInstrumentChoice = True
283 self.done = True
284
285 if event.type == pygame.JOYAXISMOTION:
286 joyName = pygame.joystick.Joystick(event.joy).get_name()
287 correctedJoyId = constants.joyNames.index(joyName)
288 if self.activeWiimotes[correctedJoyId]:
289 self.updateCursorPositionFromJoy(event)
290 wiimote = self.wiimotes[correctedJoyId]
291 pos = self.cursorPositions[correctedJoyId]
292
293 if self.buttonDown[correctedJoyId]:
294 if self.notes[correctedJoyId] != None:
295 velocity = self.heightToVelocity(pos, correctedJoyId)
296 CCHexCode = wiimote.getCCHexCode()
297 wiimote.port.write_short(CCHexCode, 07, velocity)
298 if self.cascade:
299 n = self.widthToNote(pos)
300 if n != self.notes[correctedJoyId]:
301 wiimote.stopNote(self.notes[correctedJoyId])
302 self.notes[correctedJoyId] = n
303
304 if self.song != None :
305 if self.highlightedNote == self.notes[correctedJoyId]:
306 self.highlightedNote = self.songIterator.next()
307 self.velocityLock[correctedJoyId] = True
308 else:
309 self.velocityLock[correctedJoyId] = False
310
311 velocity = self.heightToVelocity(pos, correctedJoyId)
312
313 wiimote.playNote(self.notes[correctedJoyId],velocity)
314
315 if event.type == pygame.JOYBUTTONDOWN :
316
317 joyName = pygame.joystick.Joystick(event.joy).get_name()
318 correctedJoyId = constants.joyNames.index(joyName)
319 if self.activeWiimotes[correctedJoyId]:
320 wiimote = self.wiimotes[correctedJoyId]
321 pos = self.cursorPositions[correctedJoyId]
322
323 if not self.buttonDown[correctedJoyId]:
324 savedNote = self.notes[correctedJoyId]
325 self.notes[correctedJoyId] = self.widthToNote(pos)
326
327 if self.song != None :
328 if self.highlightedNote == self.notes[correctedJoyId]:
329 self.highlightedNote = self.songIterator.next()
330 self.velocityLock[correctedJoyId] = True
331
332 velocity = self.heightToVelocity(pos, correctedJoyId)
333
334 if velocity != None :
335 if self.easyMode :
336 wiimote.stopNote(savedNote)
337 wiimote.playNote(self.notes[correctedJoyId],velocity)
338 self.buttonDown[correctedJoyId] = True
339
340 if event.type == pygame.JOYBUTTONUP:
341 joyName = pygame.joystick.Joystick(event.joy).get_name()
342 correctedJoyId = constants.joyNames.index(joyName)
343 if self.activeWiimotes[correctedJoyId]:
344 self.buttonDown[correctedJoyId] = False
345 wiimote = self.wiimotes[correctedJoyId]
346 if not self.easyMode:
347 wiimote.stopNote(self.notes[correctedJoyId])
348 self.velocityLock[correctedJoyId] = False
349
350 if event.type == pygame.MOUSEMOTION:
351
352 self.updateCursorPositionFromMouse(event)
353
354 correctedJoyId = 0
355 while not self.activeWiimotes[correctedJoyId] :
356 correctedJoyId += 1
357 wiimote = self.wiimotes[correctedJoyId]
358 pos = self.cursorPositions[correctedJoyId]
359
360 if self.buttonDown[correctedJoyId]:
361 if self.notes[correctedJoyId] != None:
362 velocity = self.heightToVelocity(pos, correctedJoyId)
363 CCHexCode = wiimote.getCCHexCode()
364 wiimote.port.write_short(CCHexCode, 07, velocity)
365 if self.cascade:
366 n = self.widthToNote(pos)
367 if n != self.notes[correctedJoyId]:
368 wiimote.stopNote(self.notes[correctedJoyId])
369 self.notes[correctedJoyId] = n
370
371 if self.song != None :
372 if self.highlightedNote == self.notes[correctedJoyId]:
373 self.highlightedNote = self.songIterator.next()
374 self.velocityLock[correctedJoyId] = True
375 else:
376 self.velocityLock[correctedJoyId] = False
377
378 velocity = self.heightToVelocity(pos, correctedJoyId)
379
380 wiimote.playNote(self.notes[correctedJoyId],velocity)
381
382 if event.type == pygame.MOUSEBUTTONDOWN:
383
384 if event.button == 1:
385 correctedJoyId = 0
386 while not self.activeWiimotes[correctedJoyId] :
387 correctedJoyId += 1
388 wiimote = self.wiimotes[correctedJoyId]
389 pos = self.cursorPositions[correctedJoyId]
390
391 if not self.buttonDown[correctedJoyId]:
392 self.notes[correctedJoyId] = self.widthToNote(pos)
393
394 if self.song != None :
395 if self.highlightedNote == self.notes[correctedJoyId]:
396 self.highlightedNote = self.songIterator.next()
397 self.velocityLock[correctedJoyId] = True
398
399 velocity = self.heightToVelocity(pos, correctedJoyId)
400
401 wiimote.playNote(self.notes[correctedJoyId],velocity)
402 self.buttonDown[correctedJoyId] = True
403
404 if event.button == 2:
405
406 self.done = True
407
408 if event.type == pygame.MOUSEBUTTONUP:
409
410 correctedJoyId = 0
411 while not self.activeWiimotes[correctedJoyId] :
412 correctedJoyId += 1
413 wiimote = self.wiimotes[correctedJoyId]
414 wiimote.stopNote(self.notes[correctedJoyId])
415 self.buttonDown[correctedJoyId] = False
416 self.velocityLock[correctedJoyId] = False
417
418 def hasChanged(self):
419 changed = False
420 if self.song != None:
421 if self.blinkOn != self.savedBlinkOn or self.highlightedNote != self.savedHighlightedNote:
422 self.savedBlinkOn = self.blinkOn
423 self.savedHighlightedNote = self.highlightedNote
424 changed = True
425 return(changed)
426
427 def updateCursorPositionFromMouse(self, mouseEvent):
428 correctedJoyId = 0
429 while not self.activeWiimotes[correctedJoyId] :
430 correctedJoyId += 1
431 self.cursorPositions[correctedJoyId] = mouseEvent.pos
432
433 def moveToNextNote(self):
434 while True:
435 if self.song == None:
436 yield(None)
437 else:
438 for note in self.song:
439 yield note