layers sous forme de constantes globales.
[minwii.git] / src / app / widgets / playingscreen.py
1 # -*- coding: utf-8 -*-
2 """
3 Écran de jeu MinWii :
4 bandes arc-en-ciel représentant un clavier.
5
6 $Id$
7 $URL$
8 """
9 import pygame
10 from colorsys import hls_to_rgb
11 from gradients import gradients
12 from cursors import WarpingCursor
13 import events
14 from eventutils import event_handler, EventDispatcher, EventHandlerMixin
15 from math import floor
16 import types
17 from musicxml import Tone
18 import fluidsynth
19
20 from config import FRAMERATE
21 from config import BORDER
22 from config import FIRST_HUE
23 from config import OFF_LUMINANCE
24 from config import OFF_SATURATION
25 from config import ON_TOP_LUMINANCE
26 from config import ON_BOTTOM_LUMINANCE
27 from config import ON_SATURATION
28 from config import ON_COLUMN_OVERSIZING
29 from config import ON_COLUMN_ALPHA
30 from config import FONT
31 from config import FONT_COLOR
32
33 BACKGROUND_LAYER = 0
34 FOREGROUND_LAYER = 1
35 CURSOR_LAYER = 2
36
37 class _PlayingScreenBase(pygame.sprite.LayeredDirty, EventHandlerMixin) :
38
39 def __init__(self, synth, distinctNotes=[]) :
40 """
41 distinctNotes : notes disctinctes présentes dans la chanson
42 triées du plus grave au plus aigu.
43 """
44 super(_PlayingScreenBase, self).__init__()
45 self.synth = synth
46 self.distinctNotes = distinctNotes
47 self.keyboardLength = 0
48 self.keyboardRects = []
49 self.cursor = None
50 self._initRects()
51 self._initColumns()
52 self._running = False
53 self.draw(pygame.display.get_surface())
54 self._initCursor()
55
56
57 def _initRects(self) :
58 """ création des espaces réservés pour
59 afficher les colonnes.
60 """
61 #ambitus = self.distinctNotes[-1].midi - self.distinctNotes[0].midi
62 #if ambitus <= 12 :
63 # self.keyboardLength = 8
64 #else :
65 # self.keyboardLength = 11
66 self.keyboardLength = len(self.distinctNotes)
67
68 screen = pygame.display.get_surface()
69
70 # taille de la zone d'affichage utile (bordure autour)
71 dispWidth = screen.get_width() - 2 * BORDER
72 dispHeight = screen.get_height() - 2 * BORDER
73
74 columnWidth = int(round(float(dispWidth) / self.keyboardLength))
75
76 rects = []
77 for i in range(self.keyboardLength) :
78 upperLeftCorner = (i*columnWidth + BORDER, BORDER)
79 rect = pygame.Rect(upperLeftCorner, (columnWidth, dispHeight))
80 rects.append(rect)
81
82 self.keyboardRects = rects
83
84 def _initColumns(self) :
85
86 hueStep = FIRST_HUE / (self.keyboardLength - 1)
87 for i, rect in enumerate(self.keyboardRects) :
88 hue = FIRST_HUE - hueStep * i
89 tone = self.distinctNotes[i]
90 c = Column(self, hue, rect, tone)
91 self.add(c, layer=BACKGROUND_LAYER)
92
93 def _initCursor(self) :
94 self.cursor = WarpingCursor(blinkMode=True)
95 self.add(self.cursor, layer=CURSOR_LAYER)
96
97 def run(self):
98 self._running = True
99 clock = pygame.time.Clock()
100 pygame.display.flip()
101 pygame.mouse.set_visible(False)
102 while self._running :
103 EventDispatcher.dispatchEvents()
104 dirty = self.draw(pygame.display.get_surface())
105 pygame.display.update(dirty)
106 clock.tick(FRAMERATE)
107
108 pygame.mouse.set_visible(True)
109 self.cursor._stopBlink()
110
111 @event_handler(pygame.KEYDOWN)
112 def handleKeyDown(self, event) :
113 if event.key == pygame.K_q:
114 self._running = False
115
116
117 class PlayingScreen(_PlayingScreenBase) :
118 "fenêtre de jeu pour improvisation"
119 scale = [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
120
121 def __init__(self, synth) :
122 distinctNotes = []
123 for midi in self.scale :
124 tone = Tone(midi)
125 distinctNotes.append(tone)
126
127 super(PlayingScreen, self).__init__(synth, distinctNotes)
128
129 @event_handler(events.NOTEON)
130 def noteon(self, evt) :
131 tone = evt.tone
132 self.synth.noteon(0, tone.midi, 64)
133
134 @event_handler(events.NOTEOFF)
135 def noteoff(self, evt) :
136 tone = evt.tone
137 self.synth.noteoff(0, tone.midi)
138
139
140 class SongPlayingScreen(_PlayingScreenBase) :
141
142 def __init__(self, synth, song) :
143 super(SongPlayingScreen, self).__init__(synth, song.distinctNotes)
144 self.song = song
145
146 @event_handler(events.NOTEON)
147 def noteon(self, evt) :
148 tone = evt.tone
149 self.synth.noteon(0, tone.midi, 64)
150
151 @event_handler(events.NOTEOFF)
152 def noteoff(self, evt) :
153 tone = evt.tone
154 self.synth.noteoff(0, tone.midi)
155
156
157
158 class Column(pygame.sprite.DirtySprite, EventHandlerMixin) :
159
160 def __init__(self, group, hue, rect, tone) :
161 pygame.sprite.DirtySprite.__init__(self, group)
162 self.tone = tone
163 toneName = FONT.render(tone.nom, True, (0,0,0))
164 sur = pygame.surface.Surface(rect.size)
165 rgba = hls_to_rgba_8bits(hue, OFF_LUMINANCE, OFF_SATURATION)
166 sur.fill(rgba)
167 w, h = rect.w, rect.h
168 tw, th, = toneName.get_size()
169 toneRect = pygame.Rect(((w - tw) / 2, h - th), (tw, th))
170 sur.blit(toneName, toneRect)
171 self.stateOff = sur
172 self.rectOff = rect
173
174 topRgba = hls_to_rgba_8bits(hue, ON_TOP_LUMINANCE, ON_SATURATION, ON_COLUMN_ALPHA)
175 bottomRgba = hls_to_rgba_8bits(hue, ON_BOTTOM_LUMINANCE, ON_SATURATION, ON_COLUMN_ALPHA)
176 onWidth = rect.width * ON_COLUMN_OVERSIZING
177 onLeft = rect.centerx - onWidth / 2
178 rectOn = pygame.Rect((onLeft, 0),
179 (onWidth, rect.height))
180 self.stateOn = gradients.vertical(rectOn.size, topRgba, bottomRgba)
181 w, h = rectOn.w, rectOn.h
182 toneRect = pygame.Rect(((w - tw) / 2, h - th), (tw, th))
183 self.stateOn.blit(toneName, toneRect)
184 self.rectOn = rectOn
185
186 self.image = self.stateOff
187 self.rect = rect
188
189 def update(self, state) :
190 group = self.groups()[0]
191 if state :
192 group.change_layer(self, FOREGROUND_LAYER)
193 self.image = self.stateOn
194 self.rect = self.rectOn
195 else :
196 group.change_layer(self, BACKGROUND_LAYER)
197 self.image = self.stateOff
198 self.rect = self.rectOff
199
200 @event_handler(pygame.MOUSEBUTTONDOWN)
201 def onMouseDown(self, event) :
202 if self.rect.collidepoint(*event.pos) :
203 self.update(True)
204 self.raiseNoteOn()
205
206 @event_handler(pygame.MOUSEBUTTONUP)
207 def onMouseUp(self, event) :
208 self.update(False)
209 self.raiseNoteOff()
210
211 def raiseNoteOn(self) :
212 evt = pygame.event.Event(events.NOTEON, tone=self.tone)
213 pygame.event.post(evt)
214
215 def raiseNoteOff(self) :
216 evt = pygame.event.Event(events.NOTEOFF, tone=self.tone)
217 pygame.event.post(evt)
218
219
220
221 def hls_to_rgba_8bits(h, l, s, a=1) :
222 #convert to rgb ranging from 0 to 255
223 rgba = [floor(255 * i) for i in hls_to_rgb(h, l, s) + (a,)]
224 return tuple(rgba)