9aeef24004bfec784c9636c2891f77a998856dd9
[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
147 class Column(pygame.sprite.DirtySprite, EventHandlerMixin) :
148
149 def __init__(self, group, hue, rect, tone) :
150 pygame.sprite.DirtySprite.__init__(self, group)
151 self.tone = tone
152 toneName = FONT.render(tone.nom, True, (0,0,0))
153 sur = pygame.surface.Surface(rect.size)
154 rgba = hls_to_rgba_8bits(hue, OFF_LUMINANCE, OFF_SATURATION)
155 sur.fill(rgba)
156 w, h = rect.w, rect.h
157 tw, th, = toneName.get_size()
158 toneRect = pygame.Rect(((w - tw) / 2, h - th), (tw, th))
159 sur.blit(toneName, toneRect)
160 self.stateOff = sur
161 self.rectOff = rect
162
163 topRgba = hls_to_rgba_8bits(hue, ON_TOP_LUMINANCE, ON_SATURATION, ON_COLUMN_ALPHA)
164 bottomRgba = hls_to_rgba_8bits(hue, ON_BOTTOM_LUMINANCE, ON_SATURATION, ON_COLUMN_ALPHA)
165 onWidth = rect.width * ON_COLUMN_OVERSIZING
166 onLeft = rect.centerx - onWidth / 2
167 rectOn = pygame.Rect((onLeft, 0),
168 (onWidth, rect.height))
169 self.stateOn = gradients.vertical(rectOn.size, topRgba, bottomRgba)
170 w, h = rectOn.w, rectOn.h
171 toneRect = pygame.Rect(((w - tw) / 2, h - th), (tw, th))
172 self.stateOn.blit(toneName, toneRect)
173 self.rectOn = rectOn
174
175 self.image = self.stateOff
176 self.rect = rect
177
178 def update(self, state) :
179 group = self.groups()[0]
180 if state :
181 group.change_layer(self, 1)
182 self.image = self.stateOn
183 self.rect = self.rectOn
184 else :
185 group.change_layer(self, 0)
186 self.image = self.stateOff
187 self.rect = self.rectOff
188
189 @event_handler(pygame.MOUSEBUTTONDOWN)
190 def onMouseDown(self, event) :
191 if self.rect.collidepoint(*event.pos) :
192 self.update(True)
193 self.raiseNoteOn()
194
195 @event_handler(pygame.MOUSEBUTTONUP)
196 def onMouseUp(self, event) :
197 self.update(False)
198 self.raiseNoteOff()
199
200 def raiseNoteOn(self) :
201 evt = pygame.event.Event(events.NOTEON, tone=self.tone)
202 pygame.event.post(evt)
203
204 def raiseNoteOff(self) :
205 evt = pygame.event.Event(events.NOTEOFF, tone=self.tone)
206 pygame.event.post(evt)
207
208
209
210 def hls_to_rgba_8bits(h, l, s, a=1) :
211 #convert to rgb ranging from 0 to 255
212 rgba = [floor(255 * i) for i in hls_to_rgb(h, l, s) + (a,)]
213 return tuple(rgba)