f699a56dff789e28f8da505d49889aaad3715533
[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 class _PlayingScreenBase(pygame.sprite.LayeredDirty, EventHandlerMixin) :
34
35 def __init__(self, synth, distinctNotes=[]) :
36 """
37 distinctNotes : notes disctinctes présentes dans la chanson
38 triées du plus grave au plus aigu.
39 """
40 super(_PlayingScreenBase, self).__init__()
41 self.synth = synth
42 self.distinctNotes = distinctNotes
43 self.keyboardLength = 0
44 self.keyboardRects = []
45 self.cursor = None
46 self._initRects()
47 self._initColumns()
48 self._running = False
49 self.draw(pygame.display.get_surface())
50 self._initCursor()
51
52
53 def _initRects(self) :
54 """ création des espaces réservés pour
55 afficher les colonnes.
56 """
57 ambitus = self.distinctNotes[-1].midi - self.distinctNotes[0].midi
58 if ambitus <= 12 :
59 self.keyboardLength = 8
60 else :
61 self.keyboardLength = 11
62
63 screen = pygame.display.get_surface()
64
65 # taille de la zone d'affichage utile (bordure autour)
66 dispWidth = screen.get_width() - 2 * BORDER
67 dispHeight = screen.get_height() - 2 * BORDER
68
69 columnWidth = int(round(float(dispWidth) / self.keyboardLength))
70
71 rects = []
72 for i in range(self.keyboardLength) :
73 upperLeftCorner = (i*columnWidth + BORDER, BORDER)
74 rect = pygame.Rect(upperLeftCorner, (columnWidth, dispHeight))
75 rects.append(rect)
76
77 self.keyboardRects = rects
78
79 def _initColumns(self) :
80
81 hueStep = FIRST_HUE / (self.keyboardLength - 1)
82 for i, rect in enumerate(self.keyboardRects) :
83 hue = FIRST_HUE - hueStep * i
84 tone = self.distinctNotes[i]
85 c = Column(self, hue, rect, tone)
86 self.add(c, layer=0)
87
88 def _initCursor(self) :
89 self.cursor = WarpingCursor(blinkMode=False)
90 self.add(self.cursor, layer=2)
91
92 def run(self):
93 self._running = True
94 clock = pygame.time.Clock()
95 pygame.display.flip()
96 pygame.mouse.set_visible(False)
97 while self._running :
98 EventDispatcher.dispatchEvents()
99 dirty = self.draw(pygame.display.get_surface())
100 pygame.display.update(dirty)
101 clock.tick(FRAMERATE)
102
103 pygame.mouse.set_visible(True)
104 self.cursor._stopBlink()
105
106 @event_handler(pygame.KEYDOWN)
107 def handleKeyDown(self, event) :
108 if event.key == pygame.K_q:
109 self._running = False
110
111
112 @event_handler(pygame.MOUSEMOTION)
113 def handleMouseMotion(self, event) :
114 pass
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, song) :
143 super(SongPlayingScreen, self).__init__(song.distinctNotes)
144 self.song = song
145
146 class SongPlayingScreenTest(_PlayingScreenBase) :
147 def __init__(self) :
148 class C:pass
149 o = C()
150 o.midi=1
151 super(SongPlayingScreenTest, self).__init__([o])
152
153
154 class Column(pygame.sprite.DirtySprite, EventHandlerMixin) :
155
156 def __init__(self, group, hue, rect, tone) :
157 pygame.sprite.DirtySprite.__init__(self, group)
158 self.tone = tone
159 toneName = FONT.render(tone.nom, True, (0,0,0))
160 sur = pygame.surface.Surface(rect.size)
161 rgba = hls_to_rgba_8bits(hue, OFF_LUMINANCE, OFF_SATURATION)
162 sur.fill(rgba)
163 w, h = rect.w, rect.h
164 tw, th, = toneName.get_size()
165 toneRect = pygame.Rect(((w - tw) / 2, h - th), (tw, th))
166 sur.blit(toneName, toneRect)
167 self.stateOff = sur
168 self.rectOff = rect
169
170 topRgba = hls_to_rgba_8bits(hue, ON_TOP_LUMINANCE, ON_SATURATION, ON_COLUMN_ALPHA)
171 bottomRgba = hls_to_rgba_8bits(hue, ON_BOTTOM_LUMINANCE, ON_SATURATION, ON_COLUMN_ALPHA)
172 onWidth = rect.width * ON_COLUMN_OVERSIZING
173 onLeft = rect.centerx - onWidth / 2
174 rectOn = pygame.Rect((onLeft, 0),
175 (onWidth, rect.height))
176 self.stateOn = gradients.vertical(rectOn.size, topRgba, bottomRgba)
177 w, h = rectOn.w, rectOn.h
178 toneRect = pygame.Rect(((w - tw) / 2, h - th), (tw, th))
179 self.stateOn.blit(toneName, toneRect)
180 self.rectOn = rectOn
181
182 self.image = self.stateOff
183 self.rect = rect
184
185 def update(self, state) :
186 group = self.groups()[0]
187 if state :
188 group.change_layer(self, 1)
189 self.image = self.stateOn
190 self.rect = self.rectOn
191 else :
192 group.change_layer(self, 0)
193 self.image = self.stateOff
194 self.rect = self.rectOff
195
196 @event_handler(pygame.MOUSEBUTTONDOWN)
197 def onMouseDown(self, event) :
198 if self.rect.collidepoint(*event.pos) :
199 self.update(True)
200 self.raiseNoteOn()
201
202 @event_handler(pygame.MOUSEBUTTONUP)
203 def onMouseUp(self, event) :
204 self.update(False)
205 self.raiseNoteOff()
206
207 def raiseNoteOn(self) :
208 evt = pygame.event.Event(events.NOTEON, tone=self.tone)
209 pygame.event.post(evt)
210
211 def raiseNoteOff(self) :
212 evt = pygame.event.Event(events.NOTEOFF, tone=self.tone)
213 pygame.event.post(evt)
214
215
216
217 def hls_to_rgba_8bits(h, l, s, a=1) :
218 #convert to rgb ranging from 0 to 255
219 rgba = [floor(255 * i) for i in hls_to_rgb(h, l, s) + (a,)]
220 return tuple(rgba)