1 # -*- coding: utf-8 -*-
4 bandes arc-en-ciel représentant un clavier.
10 from cursors
import WarpingCursor
11 from column
import Column
13 from eventutils
import event_handler
, EventDispatcher
, EventHandlerMixin
15 from musicxml
import Tone
17 from config
import FRAMERATE
18 from config
import FIRST_HUE
19 from config
import MIDI_VELOCITY_RANGE
20 from config
import MIDI_PAN_RANGE
21 from config
import MIDI_VELOCITY_WRONG_NOTE_ATTN
23 from globals import BACKGROUND_LAYER
24 from globals import CURSOR_LAYER
25 from globals import PLAYING_MODES_DICT
27 class PlayingScreenBase(pygame
.sprite
.LayeredDirty
, EventHandlerMixin
) :
29 def __init__(self
, synth
, distinctNotes
=[]) :
31 distinctNotes : notes disctinctes présentes dans la chanson
32 triées du plus grave au plus aigu.
34 super(PlayingScreenBase
, self
).__init
__()
36 self
.distinctNotes
= distinctNotes
37 self
.keyboardLength
= 0
38 self
.keyboardRects
= []
44 self
.draw(pygame
.display
.get_surface())
47 def _initRects(self
) :
48 """ création des espaces réservés pour
49 afficher les colonnes.
51 self
.keyboardLength
= len(self
.distinctNotes
)
53 screen
= pygame
.display
.get_surface()
55 self
.dispWidth
= dispWidth
= screen
.get_width()
56 self
.dispHeight
= dispHeight
= screen
.get_height()
58 columnWidth
= int(round(float(dispWidth
) / self
.keyboardLength
))
61 for i
in range(self
.keyboardLength
) :
62 upperLeftCorner
= (i
*columnWidth
, 0)
63 rect
= pygame
.Rect(upperLeftCorner
, (columnWidth
, dispHeight
))
66 self
.keyboardRects
= rects
68 def _initColumns(self
) :
70 hueStep
= FIRST_HUE
/ (self
.keyboardLength
- 1)
71 for i
, rect
in enumerate(self
.keyboardRects
) :
72 hue
= FIRST_HUE
- hueStep
* i
73 tone
= self
.distinctNotes
[i
]
74 c
= Column(self
, i
, hue
, rect
, tone
)
75 self
.add(c
, layer
=BACKGROUND_LAYER
)
76 self
.columns
[tone
.midi
] = c
79 def _initCursor(self
) :
80 self
.cursor
= WarpingCursor(blinkMode
=True)
81 self
.add(self
.cursor
, layer
=CURSOR_LAYER
)
85 clock
= pygame
.time
.Clock()
87 pygame
.mouse
.set_visible(False)
89 EventDispatcher
.dispatchEvents()
90 dirty
= self
.draw(pygame
.display
.get_surface())
91 pygame
.display
.update(dirty
)
96 self
.synth
.system_reset()
97 pygame
.mouse
.set_visible(True)
98 self
.cursor
._stopBlink
()
100 @event_handler(pygame
.KEYDOWN
)
101 def handleKeyDown(self
, event
) :
102 if event
.key
== pygame
.K_q
or event
.unicode == u
'q':
105 @event_handler(pygame
.MOUSEBUTTONDOWN
)
106 def onMouseDown(self
, event
) :
107 # TODO à cleaner : on vire le dernier élément
108 # parce qu'il s'agit du curseur
109 for col
in reversed(self
.sprites()[:-1]) :
110 if col
.rect
.collidepoint(*event
.pos
):
111 self
.raiseColDown(col
, event
)
114 @event_handler(pygame
.MOUSEBUTTONUP
)
115 def onMouseUp(self
, event
) :
116 for col
in reversed(self
.sprites()[:-1]) :
117 if col
.rect
.collidepoint(*event
.pos
) :
118 self
.raiseColUp(col
, event
)
121 @event_handler(pygame
.MOUSEMOTION
)
122 def onMouseMove(self
, event
) :
123 for col
in reversed(self
.sprites()[:-1]) :
124 if col
.rect
.collidepoint(*event
.pos
) :
125 self
.raiseColOver(col
, event
)
128 def raiseColDown(self
, col
, mouseEvent
) :
129 evt
= pygame
.event
.Event(events
.COLDOWN
, column
=col
, pos
=mouseEvent
.pos
)
130 pygame
.event
.post(evt
)
132 def raiseColUp(self
, col
, mouseEvent
) :
133 evt
= pygame
.event
.Event(events
.COLUP
, column
=col
, pos
=mouseEvent
.pos
)
134 pygame
.event
.post(evt
)
136 def raiseColOver(self
, col
, mouseEvent
) :
137 evt
= pygame
.event
.Event(events
.COLOVER
, column
=col
, pos
=mouseEvent
.pos
, mouseEvent
=mouseEvent
)
138 pygame
.event
.post(evt
)
140 def getVelocity(self
, pos
) :
141 vel
= (float(self
.dispWidth
) - pos
[1]) / self
.dispWidth
142 vel
= int(vel
* (MIDI_VELOCITY_RANGE
[1] - MIDI_VELOCITY_RANGE
[0])) + MIDI_VELOCITY_RANGE
[0]
145 def getPan(self
, index
) :
146 pan
= float(index
) / (self
.keyboardLength
-1)
147 pan
= int(pan
* (MIDI_PAN_RANGE
[1] - MIDI_PAN_RANGE
[0])) + MIDI_PAN_RANGE
[0]
150 def playnote(self
, col
, pos
, vel
=None) :
151 pan
= self
.getPan(col
.index
)
152 self
.synth
.cc(0, 10, pan
)
153 vel
= vel
or self
.getVelocity(pos
)
154 self
.synth
.noteon(0, col
.tone
.midi
, vel
)
156 class PlayingScreen(PlayingScreenBase
) :
157 "fenêtre de jeu pour improvisation"
159 scale
= [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
161 def __init__(self
, synth
) :
163 for midi
in self
.scale
:
165 distinctNotes
.append(tone
)
167 super(PlayingScreen
, self
).__init
__(synth
, distinctNotes
)
169 @event_handler(events
.NOTEON
)
170 def noteon(self
, evt
) :
172 self
.synth
.noteon(0, tone
.midi
, 96)
174 @event_handler(events
.NOTEOFF
)
175 def noteoff(self
, evt
) :
177 self
.synth
.noteoff(0, tone
.midi
)
180 class SongPlayingScreen(PlayingScreenBase
) :
182 def __init__(self
, synth
, song
, mode
=PLAYING_MODES_DICT
['NORMAL']) :
183 super(SongPlayingScreen
, self
).__init
__(synth
, song
.distinctNotes
)
185 self
.quarterNoteDuration
= song
.quarterNoteDuration
186 self
.currentColumn
= None
187 self
.noteIterator
= self
.song
.iterNotes()
189 self
._plugListeners
(mode
)
191 def _plugListeners(self
, mode
) :
192 "initialisation des gestionnaires d'événements en fonction du mode"
194 if mode
== PLAYING_MODES_DICT
['EASY'] :
195 EventDispatcher
.addEventListener(events
.COLOVER
, self
.handleEasyColumnOver
)
197 elif mode
== PLAYING_MODES_DICT
['NORMAL'] :
198 EventDispatcher
.addEventListener(events
.COLOVER
, self
.handleNormalColumnOver
)
199 EventDispatcher
.addEventListener(events
.COLDOWN
, self
.handleColumnDown
)
200 EventDispatcher
.addEventListener(events
.COLUP
, self
.handleColumnUp
)
202 elif mode
== PLAYING_MODES_DICT
['ADVANCED'] :
203 EventDispatcher
.addEventListener(events
.COLDOWN
, self
.handleColumnDown
)
204 EventDispatcher
.addEventListener(events
.COLUP
, self
.handleColumnUp
)
206 elif mode
== PLAYING_MODES_DICT
['EXPERT'] :
207 EventDispatcher
.addEventListener(events
.COLDOWN
, self
.handleExpertColumnDown
)
208 EventDispatcher
.addEventListener(events
.COLUP
, self
.handleExpertColumnUp
)
211 # --- HID listeners ---
212 def handleEasyColumnOver(self
, event
) :
214 if col
.state
and not self
.currentNotePlayed
:
215 self
.playnote(col
, event
.pos
)
216 SongPlayingScreen
.setNoteTimeout(
217 int(self
.currentNote
.duration
* \
218 self
.quarterNoteDuration
)
220 self
.currentNotePlayed
= True
222 def handleNormalColumnOver(self
, event
) :
225 any(event
.mouseEvent
.buttons
) and \
226 not self
.currentNotePlayed
:
227 self
.playnote(col
, event
.pos
)
228 self
.currentNotePlayed
= True
230 def handleColumnDown(self
, event
) :
233 self
.playnote(col
, event
.pos
)
234 self
.currentNotePlayed
= True
236 def handleExpertColumnDown(self
, event
) :
239 self
.playnote(col
, event
.pos
)
240 self
.currentNotePlayed
= True
242 vel
= self
.getVelocity(event
.pos
) * MIDI_VELOCITY_WRONG_NOTE_ATTN
244 self
.playnote(col
, event
.pos
, vel
=vel
)
245 self
.alternateColumn
= col
246 self
.currentNotePlayed
= False
248 def handleColumnUp(self
, event
) :
249 if self
.currentNotePlayed
:
250 self
.synth
.noteoff(0, self
.currentColumn
.tone
.midi
)
253 def handleExpertColumnUp(self
, event
) :
254 if self
.currentNotePlayed
:
255 self
.synth
.noteoff(0, self
.currentColumn
.tone
.midi
)
258 self
.synth
.noteoff(0, self
.alternateColumn
.tone
.midi
)
260 # --- End HID listeners ---
263 def displayNext(self
, event
=None) :
264 if self
.currentColumn
:
265 self
.currentColumn
.update(False)
266 note
, verseIndex
= self
.noteIterator
.next()
267 syllabus
= note
.lyrics
[verseIndex
].syllabus()
268 column
= self
.columns
[note
.midi
]
269 column
.update(True, syllabus
)
270 self
.currentColumn
= column
271 self
.currentNote
= note
272 self
.currentNotePlayed
= False
274 @event_handler(events
.NOTEEND
)
275 def clearTimeOutAndDisplayNext(self
, evt
) :
276 pygame
.time
.set_timer(evt
.type, 0)
277 self
.synth
.noteoff(0, self
.currentNote
.midi
)
281 def setNoteTimeout(delay
) :
282 pygame
.time
.set_timer(events
.NOTEEND
, delay
)
285 pygame
.time
.set_timer(events
.NOTEEND
, 0)
286 super(SongPlayingScreen
, self
).stop()