1 # -*- coding: utf-8 -*-
4 bandes arc-en-ciel représentant un clavier.
10 from colorsys
import hls_to_rgb
11 from gradients
import gradients
12 from cursors
import WarpingCursor
14 from eventutils
import event_handler
, EventDispatcher
, EventHandlerMixin
15 from math
import floor
17 from musicxml
import Tone
19 from config
import FRAMERATE
20 from config
import BORDER
21 from config
import FIRST_HUE
22 from config
import OFF_LUMINANCE
23 from config
import OFF_SATURATION
24 from config
import ON_TOP_LUMINANCE
25 from config
import ON_BOTTOM_LUMINANCE
26 from config
import ON_SATURATION
27 from config
import ON_COLUMN_OVERSIZING
28 from config
import ON_COLUMN_ALPHA
29 from config
import FONT
30 from config
import FONT_COLOR
31 from config
import DEFAULT_MIDI_VELOCITY
36 PLAYING_MODES
= {'EASY':0
41 class _PlayingScreenBase(pygame
.sprite
.LayeredDirty
, EventHandlerMixin
) :
43 def __init__(self
, synth
, distinctNotes
=[]) :
45 distinctNotes : notes disctinctes présentes dans la chanson
46 triées du plus grave au plus aigu.
48 super(_PlayingScreenBase
, self
).__init
__()
50 self
.distinctNotes
= distinctNotes
51 self
.keyboardLength
= 0
52 self
.keyboardRects
= []
58 self
.draw(pygame
.display
.get_surface())
62 def _initRects(self
) :
63 """ création des espaces réservés pour
64 afficher les colonnes.
66 #ambitus = self.distinctNotes[-1].midi - self.distinctNotes[0].midi
68 # self.keyboardLength = 8
70 # self.keyboardLength = 11
71 self
.keyboardLength
= len(self
.distinctNotes
)
73 screen
= pygame
.display
.get_surface()
75 # taille de la zone d'affichage utile (bordure autour)
76 dispWidth
= screen
.get_width() - 2 * BORDER
77 dispHeight
= screen
.get_height() - 2 * BORDER
79 columnWidth
= int(round(float(dispWidth
) / self
.keyboardLength
))
82 for i
in range(self
.keyboardLength
) :
83 upperLeftCorner
= (i
*columnWidth
+ BORDER
, BORDER
)
84 rect
= pygame
.Rect(upperLeftCorner
, (columnWidth
, dispHeight
))
87 self
.keyboardRects
= rects
89 def _initColumns(self
) :
91 hueStep
= FIRST_HUE
/ (self
.keyboardLength
- 1)
92 for i
, rect
in enumerate(self
.keyboardRects
) :
93 hue
= FIRST_HUE
- hueStep
* i
94 tone
= self
.distinctNotes
[i
]
95 c
= Column(self
, hue
, rect
, tone
)
96 self
.add(c
, layer
=BACKGROUND_LAYER
)
97 self
.columns
[tone
.midi
] = c
100 def _initCursor(self
) :
101 self
.cursor
= WarpingCursor(blinkMode
=True)
102 self
.add(self
.cursor
, layer
=CURSOR_LAYER
)
106 clock
= pygame
.time
.Clock()
107 pygame
.display
.flip()
108 pygame
.mouse
.set_visible(False)
109 while self
._running
:
110 EventDispatcher
.dispatchEvents()
111 dirty
= self
.draw(pygame
.display
.get_surface())
112 pygame
.display
.update(dirty
)
113 clock
.tick(FRAMERATE
)
115 pygame
.mouse
.set_visible(True)
116 self
.cursor
._stopBlink
()
118 @event_handler(pygame
.KEYDOWN
)
119 def handleKeyDown(self
, event
) :
120 if event
.key
== pygame
.K_q
:
121 self
._running
= False
124 class PlayingScreen(_PlayingScreenBase
) :
125 "fenêtre de jeu pour improvisation"
127 scale
= [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
129 def __init__(self
, synth
) :
131 for midi
in self
.scale
:
133 distinctNotes
.append(tone
)
135 super(PlayingScreen
, self
).__init
__(synth
, distinctNotes
)
137 @event_handler(events
.NOTEON
)
138 def noteon(self
, evt
) :
140 self
.synth
.noteon(0, tone
.midi
, DEFAULT_MIDI_VELOCITY
)
142 @event_handler(events
.NOTEOFF
)
143 def noteoff(self
, evt
) :
145 self
.synth
.noteoff(0, tone
.midi
)
148 class SongPlayingScreen(_PlayingScreenBase
) :
150 def __init__(self
, synth
, song
, mode
=PLAYING_MODES
['NORMAL']) :
151 super(SongPlayingScreen
, self
).__init
__(synth
, song
.distinctNotes
)
153 self
.noteIterator
= self
.song
.iterNotes()
157 note
, verseIndex
= self
.noteIterator
.next()
158 syllabus
= note
.lyrics
[verseIndex
].syllabus()
159 column
= self
.columns
[note
.midi
]
160 column
.update(True, syllabus
)
163 @event_handler(events
.NOTEON
)
164 def noteon(self
, evt
) :
166 self
.synth
.noteon(0, tone
.midi
, DEFAULT_MIDI_VELOCITY
)
168 @event_handler(events
.NOTEOFF
)
169 def noteoff(self
, evt
) :
171 self
.synth
.noteoff(0, tone
.midi
)
175 class Column(pygame
.sprite
.DirtySprite
, EventHandlerMixin
) :
177 def __init__(self
, group
, hue
, rect
, tone
) :
178 pygame
.sprite
.DirtySprite
.__init
__(self
, group
)
181 # nom de l'intonation
183 toneName
= FONT
.render(tone
.nom
, True, (0,0,0))
185 # état off : surface unie et nom de l'intonation
186 sur
= pygame
.surface
.Surface(rect
.size
)
187 rgba
= hls_to_rgba_8bits(hue
, OFF_LUMINANCE
, OFF_SATURATION
)
189 w
, h
= rect
.w
, rect
.h
190 tw
, th
, = toneName
.get_size()
191 toneRect
= pygame
.Rect(((w
- tw
) / 2, h
- th
), (tw
, th
))
192 sur
.blit(toneName
, toneRect
)
197 # état on : surface dégradée avec nom de la note avec largeur agrandie
198 topRgba
= hls_to_rgba_8bits(hue
, ON_TOP_LUMINANCE
, ON_SATURATION
, ON_COLUMN_ALPHA
)
199 bottomRgba
= hls_to_rgba_8bits(hue
, ON_BOTTOM_LUMINANCE
, ON_SATURATION
, ON_COLUMN_ALPHA
)
200 onWidth
= rect
.width
* ON_COLUMN_OVERSIZING
201 onLeft
= rect
.centerx
- onWidth
/ 2
202 rectOn
= pygame
.Rect((onLeft
, 0),
203 (onWidth
, rect
.height
))
204 self
.surOn
= gradients
.vertical(rectOn
.size
, topRgba
, bottomRgba
)
205 w
, h
= rectOn
.w
, rectOn
.h
206 toneRect
= pygame
.Rect(((w
- tw
) / 2, h
- th
), (tw
, th
))
207 self
.surOn
.blit(toneName
, toneRect
)
210 self
.image
= self
.surOff
212 #EventDispatcher.addEventListener(pygame.MOUSEBUTTONDOWN, self.onMouseDown)
213 #EventDispatcher.addEventListener(pygame.MOUSEBUTTONUP, self.onMouseUp)
215 def update(self
, state
, syllabus
='') :
216 group
= self
.groups()[0]
217 if state
== self
.state
:
221 group
.change_layer(self
, FOREGROUND_LAYER
)
225 renderedSyl
= FONT
.render(syllabus
, True, (0,0,0))
226 sw
, sh
, = renderedSyl
.get_size()
227 w
, h
= self
.rectOn
.w
, self
.rectOn
.h
228 sylRect
= pygame
.Rect(((w
- sw
) / 2, (h
- sh
) / 2), (sw
, sh
))
229 sur
.blit(renderedSyl
, sylRect
)
232 self
.rect
= self
.rectOn
234 group
.change_layer(self
, BACKGROUND_LAYER
)
235 self
.image
= self
.surOff
236 self
.rect
= self
.rectOff
240 @event_handler(pygame
.MOUSEBUTTONDOWN
)
241 def onMouseDown(self
, event
) :
242 if self
.rect
.collidepoint(*event
.pos
) :
246 @event_handler(pygame
.MOUSEBUTTONUP
)
247 def onMouseUp(self
, event
) :
251 def raiseNoteOn(self
) :
252 evt
= pygame
.event
.Event(events
.NOTEON
, tone
=self
.tone
)
253 pygame
.event
.post(evt
)
255 def raiseNoteOff(self
) :
256 evt
= pygame
.event
.Event(events
.NOTEOFF
, tone
=self
.tone
)
257 pygame
.event
.post(evt
)
261 def hls_to_rgba_8bits(h
, l
, s
, a
=1) :
262 #convert to rgb ranging from 0 to 255
263 rgba
= [floor(255 * i
) for i
in hls_to_rgb(h
, l
, s
) + (a
,)]