ménage.
[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, 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.distinctNotes = distinctNotes
42 self.keyboardLength = 0
43 self.keyboardRects = []
44 self.cursor = None
45 self._initRects()
46 self._initColumns()
47 self._running = False
48 self.draw(pygame.display.get_surface())
49 self._initCursor()
50
51
52 def _initRects(self) :
53 """ création des espaces réservés pour
54 afficher les colonnes.
55 """
56 ambitus = self.distinctNotes[-1].midi - self.distinctNotes[0].midi
57 if ambitus <= 12 :
58 self.keyboardLength = 8
59 else :
60 self.keyboardLength = 11
61
62 screen = pygame.display.get_surface()
63
64 # taille de la zone d'affichage utile (bordure autour)
65 dispWidth = screen.get_width() - 2 * BORDER
66 dispHeight = screen.get_height() - 2 * BORDER
67
68 columnWidth = int(round(float(dispWidth) / self.keyboardLength))
69
70 rects = []
71 for i in range(self.keyboardLength) :
72 upperLeftCorner = (i*columnWidth + BORDER, BORDER)
73 rect = pygame.Rect(upperLeftCorner, (columnWidth, dispHeight))
74 rects.append(rect)
75
76 self.keyboardRects = rects
77
78 def _initColumns(self) :
79
80 hueStep = FIRST_HUE / (self.keyboardLength - 1)
81 for i, rect in enumerate(self.keyboardRects) :
82 hue = FIRST_HUE - hueStep * i
83 tone = self.distinctNotes[i]
84 c = Column(self, hue, rect, tone)
85 self.add(c, layer=0)
86
87 def _initCursor(self) :
88 self.cursor = WarpingCursor(blinkMode=True)
89 self.add(self.cursor, layer=2)
90
91 def run(self):
92 self._running = True
93 clock = pygame.time.Clock()
94 pygame.display.flip()
95 while self._running :
96 EventDispatcher.dispatchEvents()
97 dirty = self.draw(pygame.display.get_surface())
98 pygame.display.update(dirty)
99 clock.tick(FRAMERATE)
100
101 self.cursor._stopBlink()
102
103 @event_handler(pygame.KEYDOWN)
104 def handleKeyDown(self, event) :
105 if event.key == pygame.K_q:
106 self._running = False
107
108
109 @event_handler(pygame.MOUSEMOTION)
110 def handleMouseMotion(self, event) :
111 pass
112
113
114 class PlayingScreen(_PlayingScreenBase) :
115 "fenêtre de jeu pour improvisation"
116 scale = [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
117
118 def __init__(self) :
119 distinctNotes = []
120 for midi in self.scale :
121 tone = Tone(midi)
122 distinctNotes.append(tone)
123
124 super(PlayingScreen, self).__init__(distinctNotes)
125
126 #cracra code
127 soundFont = '/Users/pinbe/dev/minwii/fluid-soundfont-3.1/FluidR3_GM.sf2'
128 bank = preset = 0
129
130 self.fs = fs = fluidsynth.Synth()
131 fs.start()
132 self.fsid = fsid = fs.sfload(soundFont)
133 fs.program_select(0, fsid, bank, preset)
134
135 def __del__(self) :
136 print 'PlayingScreen.__del__'
137 self.fs.delete()
138
139 @event_handler(events.NOTEON)
140 def noteon(self, evt) :
141 tone = evt.tone
142 self.fs.noteon(0, tone.midi, 64)
143
144 @event_handler(events.NOTEOFF)
145 def noteoff(self, evt) :
146 tone = evt.tone
147 self.fs.noteoff(0, tone.midi)
148
149
150
151 class SongPlayingScreen(_PlayingScreenBase) :
152
153 def __init__(self, song) :
154 super(SongPlayingScreen, self).__init__(song.distinctNotes)
155 self.song = song
156
157 class SongPlayingScreenTest(_PlayingScreenBase) :
158 def __init__(self) :
159 class C:pass
160 o = C()
161 o.midi=1
162 super(SongPlayingScreenTest, self).__init__([o])
163
164
165 class Column(pygame.sprite.DirtySprite, EventHandlerMixin) :
166
167 def __init__(self, group, hue, rect, tone) :
168 pygame.sprite.DirtySprite.__init__(self, group)
169 self.tone = tone
170 toneName = FONT.render(tone.nom, True, (0,0,0))
171 sur = pygame.surface.Surface(rect.size)
172 rgba = hls_to_rgba_8bits(hue, OFF_LUMINANCE, OFF_SATURATION)
173 sur.fill(rgba)
174 w, h = rect.w, rect.h
175 tw, th, = toneName.get_size()
176 toneRect = pygame.Rect(((w - tw) / 2, h - th), (tw, th))
177 sur.blit(toneName, toneRect)
178 self.stateOff = sur
179 self.rectOff = rect
180
181 topRgba = hls_to_rgba_8bits(hue, ON_TOP_LUMINANCE, ON_SATURATION, ON_COLUMN_ALPHA)
182 bottomRgba = hls_to_rgba_8bits(hue, ON_BOTTOM_LUMINANCE, ON_SATURATION, ON_COLUMN_ALPHA)
183 onWidth = rect.width * ON_COLUMN_OVERSIZING
184 onLeft = rect.centerx - onWidth / 2
185 rectOn = pygame.Rect((onLeft, 0),
186 (onWidth, rect.height))
187 self.stateOn = gradients.vertical(rectOn.size, topRgba, bottomRgba)
188 w, h = rectOn.w, rectOn.h
189 toneRect = pygame.Rect(((w - tw) / 2, h - th), (tw, th))
190 self.stateOn.blit(toneName, toneRect)
191 self.rectOn = rectOn
192
193 self.image = self.stateOff
194 self.rect = rect
195
196 def update(self, state) :
197 group = self.groups()[0]
198 if state :
199 group.change_layer(self, 1)
200 self.image = self.stateOn
201 self.rect = self.rectOn
202 else :
203 group.change_layer(self, 0)
204 self.image = self.stateOff
205 self.rect = self.rectOff
206
207 @event_handler(pygame.MOUSEBUTTONDOWN)
208 def onMouseDown(self, event) :
209 if self.rect.collidepoint(*event.pos) :
210 self.update(True)
211 self.raiseNoteOn()
212
213 @event_handler(pygame.MOUSEBUTTONUP)
214 def onMouseUp(self, event) :
215 self.update(False)
216 self.raiseNoteOff()
217
218 def raiseNoteOn(self) :
219 evt = pygame.event.Event(events.NOTEON, tone=self.tone)
220 pygame.event.post(evt)
221
222 def raiseNoteOff(self) :
223 evt = pygame.event.Event(events.NOTEOFF, tone=self.tone)
224 pygame.event.post(evt)
225
226
227
228 def hls_to_rgba_8bits(h, l, s, a=1) :
229 #convert to rgb ranging from 0 to 255
230 rgba = [floor(255 * i) for i in hls_to_rgb(h, l, s) + (a,)]
231 return tuple(rgba)