1a014e74e5c9be884b02959f94ed5d5c0aee812d
[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
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=True)
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 while self._running :
97 EventDispatcher.dispatchEvents()
98 dirty = self.draw(pygame.display.get_surface())
99 pygame.display.update(dirty)
100 clock.tick(FRAMERATE)
101
102 @event_handler(pygame.KEYDOWN)
103 def handleKeyDown(self, event) :
104 if event.key == pygame.K_q:
105 self._running = False
106
107
108 @event_handler(pygame.MOUSEMOTION)
109 def handleMouseMotion(self, event) :
110 pass
111
112
113 class PlayingScreen(_PlayingScreenBase) :
114 "fenêtre de jeu pour improvisation"
115 scale = [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
116
117 def __init__(self) :
118 distinctNotes = []
119 for midi in self.scale :
120 tone = Tone(midi)
121 distinctNotes.append(tone)
122
123 super(PlayingScreen, self).__init__(distinctNotes)
124
125 #cracra code
126 soundFont = '/Users/pinbe/dev/minwii/fluid-soundfont-3.1/FluidR3_GM.sf2'
127 bank = preset = 0
128
129 self.fs = fs = fluidsynth.Synth()
130 fs.start()
131 self.fsid = fsid = fs.sfload(soundFont)
132 fs.program_select(0, fsid, bank, preset)
133
134 def __del__(self) :
135 self.fs.delete()
136
137 @event_handler(events.NOTEON)
138 def noteon(self, evt) :
139 tone = evt.tone
140 self.fs.noteon(0, tone.midi, 64)
141
142 @event_handler(events.NOTEOFF)
143 def noteoff(self, evt) :
144 tone = evt.tone
145 self.fs.noteoff(0, tone.midi)
146
147
148
149 class SongPlayingScreen(_PlayingScreenBase) :
150
151 def __init__(self, song) :
152 super(SongPlayingScreen, self).__init__(song.distinctNotes)
153 self.song = song
154
155 class SongPlayingScreenTest(_PlayingScreenBase) :
156 def __init__(self) :
157 class C:pass
158 o = C()
159 o.midi=1
160 super(SongPlayingScreenTest, self).__init__([o])
161
162
163 class Column(pygame.sprite.DirtySprite, EventHandlerMixin) :
164
165 def __init__(self, group, hue, rect, tone) :
166 pygame.sprite.DirtySprite.__init__(self, group)
167 self.tone = tone
168 toneName = FONT.render(tone.nom, True, (0,0,0))
169 sur = pygame.surface.Surface(rect.size)
170 rgba = hls_to_rgba_8bits(hue, OFF_LUMINANCE, OFF_SATURATION)
171 sur.fill(rgba)
172 w, h = rect.w, rect.h
173 tw, th, = toneName.get_size()
174 toneRect = pygame.Rect(((w - tw) / 2, h - th), (tw, th))
175 sur.blit(toneName, toneRect)
176 self.stateOff = sur
177 self.rectOff = rect
178
179 topRgba = hls_to_rgba_8bits(hue, ON_TOP_LUMINANCE, ON_SATURATION, ON_COLUMN_ALPHA)
180 bottomRgba = hls_to_rgba_8bits(hue, ON_BOTTOM_LUMINANCE, ON_SATURATION, ON_COLUMN_ALPHA)
181 onWidth = rect.width * ON_COLUMN_OVERSIZING
182 onLeft = rect.centerx - onWidth / 2
183 rectOn = pygame.Rect((onLeft, 0),
184 (onWidth, rect.height))
185 self.stateOn = gradients.vertical(rectOn.size, topRgba, bottomRgba)
186 w, h = rectOn.w, rectOn.h
187 toneRect = pygame.Rect(((w - tw) / 2, h - th), (tw, th))
188 self.stateOn.blit(toneName, toneRect)
189 self.rectOn = rectOn
190
191 self.image = self.stateOff
192 self.rect = rect
193
194 def update(self, state) :
195 group = self.groups()[0]
196 if state :
197 group.change_layer(self, 1)
198 self.image = self.stateOn
199 self.rect = self.rectOn
200 else :
201 group.change_layer(self, 0)
202 self.image = self.stateOff
203 self.rect = self.rectOff
204
205 @event_handler(pygame.MOUSEBUTTONDOWN)
206 def onMouseDown(self, event) :
207 if self.rect.collidepoint(*event.pos) :
208 self.update(True)
209 self.raiseNoteOn()
210
211 @event_handler(pygame.MOUSEBUTTONUP)
212 def onMouseUp(self, event) :
213 self.update(False)
214 self.raiseNoteOff()
215
216 def raiseNoteOn(self) :
217 evt = pygame.event.Event(events.NOTEON, tone=self.tone)
218 pygame.event.post(evt)
219
220 def raiseNoteOff(self) :
221 evt = pygame.event.Event(events.NOTEOFF, tone=self.tone)
222 pygame.event.post(evt)
223
224
225
226 def hls_to_rgba_8bits(h, l, s, a=1) :
227 #convert to rgb ranging from 0 to 255
228 rgba = [floor(255 * i) for i in hls_to_rgb(h, l, s) + (a,)]
229 return tuple(rgba)