1 # -*- coding: utf-8 -*-
4 bandes arc-en-ciel représentant un clavier.
12 import minwii
.events
as events
13 from minwii
.log
import eventLogger
14 from minwii
.eventutils
import event_handler
, EventDispatcher
, EventHandlerMixin
15 from minwii
.musicxml
import Tone
16 from minwii
.config
import FRAMERATE
17 from minwii
.config
import FIRST_HUE
18 from minwii
.config
import MIDI_VELOCITY_RANGE
19 from minwii
.config
import MIDI_PAN_RANGE
20 from minwii
.config
import MIDI_VELOCITY_WRONG_NOTE_ATTN
21 from minwii
.globals import BACKGROUND_LAYER
22 from minwii
.globals import CURSOR_LAYER
23 from minwii
.globals import PLAYING_MODES_DICT
25 from cursors
import WarpingCursor
26 from column
import Column
28 class PlayingScreenBase(pygame
.sprite
.LayeredDirty
, EventHandlerMixin
) :
30 def __init__(self
, synth
, distinctNotes
=[]) :
32 distinctNotes : notes disctinctes présentes dans la chanson
33 triées du plus grave au plus aigu.
35 super(PlayingScreenBase
, self
).__init
__()
37 self
.distinctNotes
= distinctNotes
38 self
.keyboardLength
= 0
39 self
.keyboardRects
= []
45 self
.draw(pygame
.display
.get_surface())
48 def _initRects(self
) :
49 """ création des espaces réservés pour
50 afficher les colonnes.
52 self
.keyboardLength
= len(self
.distinctNotes
)
54 screen
= pygame
.display
.get_surface()
56 self
.dispWidth
= dispWidth
= screen
.get_width()
57 self
.dispHeight
= dispHeight
= screen
.get_height()
59 columnWidth
= int(round(float(dispWidth
) / self
.keyboardLength
))
62 for i
in range(self
.keyboardLength
) :
63 upperLeftCorner
= (i
*columnWidth
, 0)
64 rect
= pygame
.Rect(upperLeftCorner
, (columnWidth
, dispHeight
))
67 self
.keyboardRects
= rects
69 def _initColumns(self
) :
71 hueStep
= FIRST_HUE
/ (self
.keyboardLength
- 1)
72 for i
, rect
in enumerate(self
.keyboardRects
) :
73 hue
= FIRST_HUE
- hueStep
* i
74 tone
= self
.distinctNotes
[i
]
75 c
= Column(self
, i
, hue
, rect
, tone
)
76 self
.add(c
, layer
=BACKGROUND_LAYER
)
77 self
.columns
[tone
.midi
] = c
80 def _initCursor(self
) :
81 self
.cursor
= WarpingCursor(blinkMode
=True)
82 self
.add(self
.cursor
, layer
=CURSOR_LAYER
)
86 clock
= pygame
.time
.Clock()
88 pygame
.mouse
.set_visible(False)
90 EventDispatcher
.dispatchEvents()
91 dirty
= self
.draw(pygame
.display
.get_surface())
92 pygame
.display
.update(dirty
)
97 self
.synth
.system_reset()
98 pygame
.mouse
.set_visible(True)
99 self
.cursor
._stopBlink
()
101 @event_handler(pygame
.KEYDOWN
)
102 def handleKeyDown(self
, event
) :
103 if event
.key
== pygame
.K_q
or \
104 event
.unicode == u
'q' or \
108 @event_handler(pygame
.MOUSEBUTTONDOWN
)
109 def onMouseDown(self
, event
) :
110 # TODO à cleaner : on vire le dernier élément
111 # parce qu'il s'agit du curseur
112 for col
in reversed(self
.sprites()[:-1]) :
113 if col
.rect
.collidepoint(*event
.pos
):
114 self
.raiseColDown(col
, event
)
117 @event_handler(pygame
.MOUSEBUTTONUP
)
118 def onMouseUp(self
, event
) :
119 for col
in reversed(self
.sprites()[:-1]) :
120 if col
.rect
.collidepoint(*event
.pos
) :
121 self
.raiseColUp(col
, event
)
124 @event_handler(pygame
.MOUSEMOTION
)
125 def onMouseMove(self
, event
) :
126 for col
in reversed(self
.sprites()[:-1]) :
127 if col
.rect
.collidepoint(*event
.pos
) :
128 self
.raiseColOver(col
, event
)
131 def raiseColDown(self
, col
, mouseEvent
) :
132 evt
= pygame
.event
.Event(events
.COLDOWN
, column
=col
, pos
=mouseEvent
.pos
)
133 pygame
.event
.post(evt
)
135 def raiseColUp(self
, col
, mouseEvent
) :
136 evt
= pygame
.event
.Event(events
.COLUP
, column
=col
, pos
=mouseEvent
.pos
)
137 pygame
.event
.post(evt
)
139 def raiseColOver(self
, col
, mouseEvent
) :
140 evt
= pygame
.event
.Event(events
.COLOVER
, column
=col
, pos
=mouseEvent
.pos
, mouseEvent
=mouseEvent
)
141 pygame
.event
.post(evt
)
143 def getVelocity(self
, pos
) :
144 vel
= (float(self
.dispWidth
) - pos
[1]) / self
.dispWidth
145 vel
= int(vel
* (MIDI_VELOCITY_RANGE
[1] - MIDI_VELOCITY_RANGE
[0])) + MIDI_VELOCITY_RANGE
[0]
148 def getPan(self
, index
) :
149 pan
= float(index
) / (self
.keyboardLength
-1)
150 pan
= int(pan
* (MIDI_PAN_RANGE
[1] - MIDI_PAN_RANGE
[0])) + MIDI_PAN_RANGE
[0]
153 def playnote(self
, col
, pos
, vel
=None) :
154 pan
= self
.getPan(col
.index
)
155 self
.synth
.cc(0, 10, pan
)
156 vel
= vel
or self
.getVelocity(pos
)
157 self
.synth
.noteon(0, col
.tone
.midi
, vel
)
159 class PlayingScreen(PlayingScreenBase
) :
160 "fenêtre de jeu pour improvisation"
162 scale
= [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
164 def __init__(self
, synth
) :
166 self
.currentColumn
= None
167 for midi
in self
.scale
:
169 distinctNotes
.append(tone
)
171 super(PlayingScreen
, self
).__init
__(synth
, distinctNotes
)
173 @event_handler(events
.COLDOWN
)
174 def noteon(self
, event
) :
177 self
.currentColumn
= col
178 self
.playnote(col
, event
.pos
)
180 @event_handler(events
.COLUP
)
181 def noteoff(self
, event
) :
182 if self
.currentColumn
:
183 self
.currentColumn
.update(False)
184 self
.synth
.noteoff(0, self
.currentColumn
.tone
.midi
)
187 class SongPlayingScreen(PlayingScreenBase
) :
189 def __init__(self
, synth
, song
, mode
=PLAYING_MODES_DICT
['NORMAL']) :
190 super(SongPlayingScreen
, self
).__init
__(synth
, song
.distinctNotes
)
192 self
.quarterNoteDuration
= song
.quarterNoteDuration
193 self
.currentColumn
= None
194 self
.noteIterator
= self
.song
.iterNotes()
196 self
._plugListeners
(mode
)
198 def _plugListeners(self
, mode
) :
199 "initialisation des gestionnaires d'événements en fonction du mode"
201 if mode
== PLAYING_MODES_DICT
['BEGINNER'] :
202 EventDispatcher
.addEventListener(events
.COLOVER
, self
.handleBeginnerColumnOver
)
204 elif mode
== PLAYING_MODES_DICT
['EASY'] :
205 EventDispatcher
.addEventListener(events
.COLDOWN
, self
.handleEasyColumnDown
)
206 EventDispatcher
.addEventListener(events
.COLOVER
, self
.handleEasyColumnOver
)
208 elif mode
== PLAYING_MODES_DICT
['NORMAL'] :
209 EventDispatcher
.addEventListener(events
.COLOVER
, self
.handleNormalColumnOver
)
210 EventDispatcher
.addEventListener(events
.COLDOWN
, self
.handleColumnDown
)
211 EventDispatcher
.addEventListener(events
.COLUP
, self
.handleColumnUp
)
213 elif mode
== PLAYING_MODES_DICT
['ADVANCED'] :
214 EventDispatcher
.addEventListener(events
.COLDOWN
, self
.handleColumnDown
)
215 EventDispatcher
.addEventListener(events
.COLUP
, self
.handleColumnUp
)
217 elif mode
== PLAYING_MODES_DICT
['EXPERT'] :
218 EventDispatcher
.addEventListener(events
.COLDOWN
, self
.handleExpertColumnDown
)
219 EventDispatcher
.addEventListener(events
.COLUP
, self
.handleExpertColumnUp
)
222 # --- HID listeners ---
223 def handleBeginnerColumnOver(self
, event
) :
225 if col
.state
and not self
.currentNotePlayed
:
226 self
.playnote(col
, event
.pos
)
227 SongPlayingScreen
.setNoteTimeout(
228 int(self
.currentNote
.duration
* \
229 self
.quarterNoteDuration
)
231 self
.currentNotePlayed
= True
233 def handleEasyColumnOver(self
, event
) :
236 self
.cursor
.pressed
and \
237 not self
.currentNotePlayed
:
238 self
.playnote(col
, event
.pos
)
239 SongPlayingScreen
.setNoteTimeout(
240 int(self
.currentNote
.duration
* \
241 self
.quarterNoteDuration
)
243 self
.currentNotePlayed
= True
246 def handleNormalColumnOver(self
, event
) :
249 self
.cursor
.pressed
and \
250 not self
.currentNotePlayed
:
251 self
.playnote(col
, event
.pos
)
252 self
.currentNotePlayed
= True
254 def handleColumnDown(self
, event
) :
257 self
.playnote(col
, event
.pos
)
258 self
.currentNotePlayed
= True
260 def handleEasyColumnDown(self
, event
) :
263 not self
.currentNotePlayed
:
264 self
.playnote(col
, event
.pos
)
265 SongPlayingScreen
.setNoteTimeout(
266 int(self
.currentNote
.duration
* \
267 self
.quarterNoteDuration
)
269 self
.currentNotePlayed
= True
272 def handleExpertColumnDown(self
, event
) :
275 self
.playnote(col
, event
.pos
)
276 self
.currentNotePlayed
= True
278 vel
= self
.getVelocity(event
.pos
) * MIDI_VELOCITY_WRONG_NOTE_ATTN
280 self
.playnote(col
, event
.pos
, vel
=vel
)
281 self
.alternateColumn
= col
282 self
.currentNotePlayed
= False
284 def handleColumnUp(self
, event
) :
285 if self
.currentNotePlayed
:
286 self
.synth
.noteoff(0, self
.currentColumn
.tone
.midi
)
289 def handleExpertColumnUp(self
, event
) :
290 if self
.currentNotePlayed
:
291 self
.synth
.noteoff(0, self
.currentColumn
.tone
.midi
)
294 self
.synth
.noteoff(0, self
.alternateColumn
.tone
.midi
)
296 # --- End HID listeners ---
299 def displayNext(self
, event
=None) :
300 if self
.currentColumn
:
301 self
.currentColumn
.update(False)
303 note
, verseIndex
= self
.noteIterator
.next()
304 except StopIteration :
305 self
.noteIterator
= self
.song
.iterNotes()
306 note
, verseIndex
= self
.noteIterator
.next()
307 eventLogger
.info(pygame
.event
.Event(events
.SONGEND
))
309 syllabus
= note
.lyrics
[verseIndex
].syllabus()
313 column
= self
.columns
[note
.midi
]
314 column
.update(True, syllabus
)
315 self
.currentColumn
= column
316 self
.currentNote
= note
317 self
.currentNotePlayed
= False
319 @event_handler(events
.NOTEEND
)
320 def clearTimeOutAndDisplayNext(self
, evt
) :
321 pygame
.time
.set_timer(evt
.type, 0)
322 self
.synth
.noteoff(0, self
.currentNote
.midi
)
326 def setNoteTimeout(delay
) :
327 pygame
.time
.set_timer(events
.NOTEEND
, delay
)
330 pygame
.time
.set_timer(events
.NOTEEND
, 0)
331 super(SongPlayingScreen
, self
).stop()