1 # -*- coding: utf-8 -*-
4 bandes arc-en-ciel représentant un clavier.
12 import kinect
.pygamedisplay
as kinect
14 import minwii
.events
as events
15 from minwii
.log
import eventLogger
16 from minwii
.eventutils
import event_handler
, EventDispatcher
, EventHandlerMixin
17 from minwii
.musicxml
import Tone
18 from minwii
.config
import FRAMERATE
19 from minwii
.config
import FIRST_HUE
20 from minwii
.config
import MIDI_VELOCITY_RANGE
21 from minwii
.config
import MIDI_PAN_RANGE
22 from minwii
.config
import MIDI_VELOCITY_WRONG_NOTE_ATTN
23 from minwii
.globals import BACKGROUND_LAYER
24 from minwii
.globals import CURSOR_LAYER
25 from minwii
.globals import PLAYING_MODES_DICT
27 from cursors
import WarpingCursor
28 from column
import Column
30 class PlayingScreenBase(pygame
.sprite
.LayeredDirty
, EventHandlerMixin
) :
32 def __init__(self
, synth
, distinctNotes
=[], displayNotes
=True) :
34 distinctNotes : notes disctinctes présentes dans la chanson
35 triées du plus grave au plus aigu.
37 super(PlayingScreenBase
, self
).__init
__()
39 self
.distinctNotes
= distinctNotes
40 self
.displayNotes
= displayNotes
41 self
.keyboardLength
= 0
42 self
.keyboardRects
= []
48 self
.draw(pygame
.display
.get_surface())
51 self
.kinectRgb
= kinect
.RGB()
52 self
.kinectRgbSur
= pygame
.Surface((640, 480))
54 def _initRects(self
) :
55 """ création des espaces réservés pour
56 afficher les colonnes.
58 self
.keyboardLength
= len(self
.distinctNotes
)
60 screen
= pygame
.display
.get_surface()
62 self
.dispWidth
= dispWidth
= screen
.get_width()
63 self
.dispHeight
= dispHeight
= screen
.get_height()
65 columnWidth
= int(round(float(dispWidth
) / self
.keyboardLength
))
68 for i
in range(self
.keyboardLength
- 1) :
69 upperLeftCorner
= (i
*columnWidth
, 0)
70 rect
= pygame
.Rect(upperLeftCorner
, (columnWidth
, dispHeight
))
73 # la dernière colonne à la largeur du reste
74 upperLeftCorner
= ((i
+1) * columnWidth
, 0)
75 rect
= pygame
.Rect(upperLeftCorner
, (dispWidth
- (self
.keyboardLength
- 1) * columnWidth
, dispHeight
))
78 self
.keyboardRects
= rects
80 def _initColumns(self
) :
82 hueStep
= FIRST_HUE
/ (self
.keyboardLength
- 1)
83 for i
, rect
in enumerate(self
.keyboardRects
) :
84 hue
= FIRST_HUE
- hueStep
* i
85 tone
= self
.distinctNotes
[i
]
86 c
= Column(self
, i
, hue
, rect
, tone
, displayNote
=self
.displayNotes
)
87 self
.add(c
, layer
=BACKGROUND_LAYER
)
88 self
.columns
[tone
.midi
] = c
91 def _initCursor(self
) :
92 self
.cursor
= WarpingCursor(blinkMode
=True)
93 self
.add(self
.cursor
, layer
=CURSOR_LAYER
)
97 clock
= pygame
.time
.Clock()
99 pygame
.mouse
.set_visible(False)
100 while self
._running
:
101 EventDispatcher
.dispatchEvents()
102 dirty
= self
.draw(pygame
.display
.get_surface())
103 pygame
.display
.update(dirty
)
105 self
.kinectRgb
.update()
106 rgbImg
= self
.kinectRgb
.capture()
107 self
.kinectRgbSur
.blit(rgbImg
, (0, 0))
108 screen
= pygame
.display
.get_surface()
109 screen
.blit(pygame
.transform
.flip(self
.kinectRgbSur
, True, False), (0, 0))
110 pygame
.display
.flip()
112 clock
.tick(FRAMERATE
)
115 self
._running
= False
116 self
.synth
.system_reset()
117 pygame
.mouse
.set_visible(True)
118 self
.cursor
._stopBlink
()
120 @event_handler(pygame
.KEYDOWN
)
121 def handleKeyDown(self
, event
) :
122 if event
.key
in (pygame
.K_q
, pygame
.K_ESCAPE
) or \
123 event
.unicode == u
'q' :
126 @event_handler(pygame
.MOUSEBUTTONDOWN
)
127 def onMouseDown(self
, event
) :
128 # TODO à cleaner : on vire le dernier élément
129 # parce qu'il s'agit du curseur
130 for col
in reversed(self
.sprites()[:-1]) :
131 if col
.rect
.collidepoint(*event
.pos
):
132 self
.raiseColDown(col
, event
)
135 @event_handler(pygame
.MOUSEBUTTONUP
)
136 def onMouseUp(self
, event
) :
137 for col
in reversed(self
.sprites()[:-1]) :
138 if col
.rect
.collidepoint(*event
.pos
) :
139 self
.raiseColUp(col
, event
)
142 @event_handler(pygame
.MOUSEMOTION
)
143 def onMouseMove(self
, event
) :
144 for col
in reversed(self
.sprites()[:-1]) :
145 if col
.rect
.collidepoint(*event
.pos
) :
146 self
.raiseColOver(col
, event
)
149 def raiseColDown(self
, col
, mouseEvent
) :
150 evt
= pygame
.event
.Event(events
.COLDOWN
, column
=col
, pos
=mouseEvent
.pos
)
151 pygame
.event
.post(evt
)
153 def raiseColUp(self
, col
, mouseEvent
) :
154 evt
= pygame
.event
.Event(events
.COLUP
, column
=col
, pos
=mouseEvent
.pos
)
155 pygame
.event
.post(evt
)
157 def raiseColOver(self
, col
, mouseEvent
) :
158 evt
= pygame
.event
.Event(events
.COLOVER
, column
=col
, pos
=mouseEvent
.pos
, mouseEvent
=mouseEvent
)
159 pygame
.event
.post(evt
)
161 def getVelocity(self
, pos
) :
162 vel
= (float(self
.dispWidth
) - pos
[1]) / self
.dispWidth
163 vel
= int(vel
* (MIDI_VELOCITY_RANGE
[1] - MIDI_VELOCITY_RANGE
[0])) + MIDI_VELOCITY_RANGE
[0]
166 def getPan(self
, index
) :
167 pan
= float(index
) / (self
.keyboardLength
-1)
168 pan
= int(pan
* (MIDI_PAN_RANGE
[1] - MIDI_PAN_RANGE
[0])) + MIDI_PAN_RANGE
[0]
171 def playnote(self
, col
, pos
, vel
=None) :
172 pan
= self
.getPan(col
.index
)
173 self
.synth
.cc(0, 10, pan
)
174 vel
= vel
or self
.getVelocity(pos
)
175 self
.synth
.noteon(0, col
.tone
.midi
, vel
)
177 class PlayingScreen(PlayingScreenBase
) :
178 "fenêtre de jeu pour improvisation"
180 scale
= [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
182 def __init__(self
, synth
, displayNotes
=True) :
184 self
.currentColumn
= None
185 for midi
in self
.scale
:
187 distinctNotes
.append(tone
)
189 super(PlayingScreen
, self
).__init
__(synth
, distinctNotes
, displayNotes
=displayNotes
)
191 @event_handler(events
.COLDOWN
)
192 def noteon(self
, event
) :
195 self
.currentColumn
= col
196 self
.playnote(col
, event
.pos
)
198 @event_handler(events
.COLUP
)
199 def noteoff(self
, event
) :
200 if self
.currentColumn
:
201 self
.currentColumn
.update(False)
202 self
.synth
.noteoff(0, self
.currentColumn
.tone
.midi
)
205 class SongPlayingScreen(PlayingScreenBase
) :
207 def __init__(self
, synth
, song
, mode
=PLAYING_MODES_DICT
['NORMAL'], displayNotes
=True, tempoTrim
=0) :
208 super(SongPlayingScreen
, self
).__init
__(synth
, song
.distinctNotes
, displayNotes
=displayNotes
)
210 self
.quarterNoteDuration
= song
.quarterNoteDuration
211 self
.tempoTrim
= tempoTrim
212 self
.currentColumn
= None
213 self
.noteIterator
= self
.song
.iterNotes()
215 self
._plugListeners
(mode
)
217 def _plugListeners(self
, mode
) :
218 "initialisation des gestionnaires d'événements en fonction du mode"
220 if mode
== PLAYING_MODES_DICT
['BEGINNER'] :
221 EventDispatcher
.addEventListener(events
.COLOVER
, self
.handleBeginnerColumnOver
)
223 elif mode
== PLAYING_MODES_DICT
['EASY'] :
224 EventDispatcher
.addEventListener(events
.COLDOWN
, self
.handleEasyColumnDown
)
225 EventDispatcher
.addEventListener(events
.COLOVER
, self
.handleEasyColumnOver
)
227 elif mode
== PLAYING_MODES_DICT
['NORMAL'] :
228 EventDispatcher
.addEventListener(events
.COLOVER
, self
.handleNormalColumnOver
)
229 EventDispatcher
.addEventListener(events
.COLDOWN
, self
.handleColumnDown
)
230 EventDispatcher
.addEventListener(events
.COLUP
, self
.handleColumnUp
)
232 elif mode
== PLAYING_MODES_DICT
['ADVANCED'] :
233 EventDispatcher
.addEventListener(events
.COLDOWN
, self
.handleColumnDown
)
234 EventDispatcher
.addEventListener(events
.COLUP
, self
.handleColumnUp
)
236 elif mode
== PLAYING_MODES_DICT
['EXPERT'] :
237 EventDispatcher
.addEventListener(events
.COLDOWN
, self
.handleExpertColumnDown
)
238 EventDispatcher
.addEventListener(events
.COLUP
, self
.handleExpertColumnUp
)
241 # --- HID listeners ---
242 def handleBeginnerColumnOver(self
, event
) :
244 if col
.state
and not self
.currentNotePlayed
:
245 self
.playnote(col
, event
.pos
)
246 self
.setNoteTimeout()
247 self
.currentNotePlayed
= True
249 def handleEasyColumnOver(self
, event
) :
252 self
.cursor
.pressed
and \
253 not self
.currentNotePlayed
:
254 self
.playnote(col
, event
.pos
)
255 self
.setNoteTimeout()
256 self
.currentNotePlayed
= True
259 def handleNormalColumnOver(self
, event
) :
262 self
.cursor
.pressed
and \
263 not self
.currentNotePlayed
:
264 self
.playnote(col
, event
.pos
)
265 self
.currentNotePlayed
= True
267 def handleColumnDown(self
, event
) :
270 self
.playnote(col
, event
.pos
)
271 self
.currentNotePlayed
= True
273 def handleEasyColumnDown(self
, event
) :
276 not self
.currentNotePlayed
:
277 self
.playnote(col
, event
.pos
)
278 self
.setNoteTimeout()
279 self
.currentNotePlayed
= True
282 def handleExpertColumnDown(self
, event
) :
285 self
.playnote(col
, event
.pos
)
286 self
.currentNotePlayed
= True
288 vel
= self
.getVelocity(event
.pos
) * MIDI_VELOCITY_WRONG_NOTE_ATTN
290 self
.playnote(col
, event
.pos
, vel
=vel
)
291 self
.alternateColumn
= col
292 self
.currentNotePlayed
= False
294 def handleColumnUp(self
, event
) :
295 if self
.currentNotePlayed
:
296 self
.synth
.noteoff(0, self
.currentColumn
.tone
.midi
)
299 def handleExpertColumnUp(self
, event
) :
300 if self
.currentNotePlayed
:
301 self
.synth
.noteoff(0, self
.currentColumn
.tone
.midi
)
304 self
.synth
.noteoff(0, self
.alternateColumn
.tone
.midi
)
306 # --- End HID listeners ---
309 def displayNext(self
, event
=None) :
310 if self
.currentColumn
:
311 self
.currentColumn
.update(False)
313 note
, verseIndex
= self
.noteIterator
.next()
314 except StopIteration :
315 self
.noteIterator
= self
.song
.iterNotes()
316 note
, verseIndex
= self
.noteIterator
.next()
317 eventLogger
.info(pygame
.event
.Event(events
.SONGEND
))
319 syllabus
= note
.lyrics
[verseIndex
].syllabus()
323 column
= self
.columns
[note
.midi
]
324 column
.update(True, syllabus
)
325 self
.currentColumn
= column
326 self
.currentNote
= note
327 self
.currentNotePlayed
= False
329 @event_handler(events
.NOTEEND
)
330 def clearTimeOutAndDisplayNext(self
, evt
) :
331 pygame
.time
.set_timer(evt
.type, 0)
332 self
.synth
.noteoff(0, self
.currentNote
.midi
)
335 def setNoteTimeout(self
) :
336 delay
= self
.currentNote
.duration
* self
.quarterNoteDuration
337 delay
= delay
+ delay
* self
.tempoTrim
340 delay
= 1 # durée minimale, car 0 désactiverait le timer.
341 pygame
.time
.set_timer(events
.NOTEEND
, delay
)
343 def tempoTrimUp(self
, step
=0.1) :
344 self
.tempoTrim
= round(self
.tempoTrim
- step
, 1)
346 def tempoTrimDown(self
, step
=0.1) :
347 self
.tempoTrim
= round(self
.tempoTrim
+ step
, 1)
350 pygame
.time
.set_timer(events
.NOTEEND
, 0)
351 super(SongPlayingScreen
, self
).stop()