aboutsummaryrefslogtreecommitdiff
path: root/helpers
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2016-06-18 22:13:19 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2016-06-18 22:13:19 +0200
commitbe27763f8be0f647cbe17ecee8c782901ce2cede (patch)
tree30e02392fa946f92755c83454f85c4572f2d4e77 /helpers
parent98fee7ff51eac3660f05aefcb18bbd9c2d32c35c (diff)
downloadMusicSampler-be27763f8be0f647cbe17ecee8c782901ce2cede.tar.gz
MusicSampler-be27763f8be0f647cbe17ecee8c782901ce2cede.tar.zst
MusicSampler-be27763f8be0f647cbe17ecee8c782901ce2cede.zip
Move classes to separate file
Diffstat (limited to 'helpers')
-rw-r--r--helpers/__init__.py492
-rw-r--r--helpers/action.py119
-rw-r--r--helpers/key.py120
-rw-r--r--helpers/mapping.py171
-rw-r--r--helpers/music_file.py35
-rw-r--r--helpers/rounded_rect.py62
6 files changed, 509 insertions, 490 deletions
diff --git a/helpers/__init__.py b/helpers/__init__.py
index 8570008..6935342 100644
--- a/helpers/__init__.py
+++ b/helpers/__init__.py
@@ -1,498 +1,10 @@
1# -*- coding: utf-8 -*- 1# -*- coding: utf-8 -*-
2from pygame import *
3import pydub
4import sys
5import time
6import threading 2import threading
3from .music_file import *
4from .mapping import *
7 5
8draw_lock = threading.RLock() 6draw_lock = threading.RLock()
9 7
10class Action:
11 action_types = [
12 'command',
13 'pause',
14 'play',
15 'stop',
16 'stop_all_actions',
17 'volume',
18 'wait',
19 ]
20
21 def __init__(self, action, key, **kwargs):
22 if action in self.action_types:
23 self.action = action
24 else:
25 raise Exception("Unknown action {}".format(action))
26
27 self.key = key
28 self.arguments = kwargs
29
30 def ready(self):
31 if 'music' in self.arguments:
32 return self.arguments['music'].loaded
33 else:
34 return True
35
36 def run(self):
37 print(getattr(self, self.action + "_print")(**self.arguments))
38 return getattr(self, self.action)(**self.arguments)
39
40 def command(self, command = "", **kwargs):
41 pass
42
43 def pause(self, music = None, **kwargs):
44 if music is not None:
45 music.pause()
46 else:
47 mixer.pause()
48
49 def play(self, music = None, fade_in = 0, start_at = 0,
50 restart_if_running = False, volume = 100, **kwargs):
51 if music is not None:
52 music.play()
53 else:
54 mixer.unpause()
55
56 def stop(self, music = None, fade_out = 0, **kwargs):
57 if music is not None:
58 music.stop()
59 else:
60 mixer.stop()
61
62 def stop_all_actions(self, **kwargs):
63 self.key.mapping.stop_all_running()
64
65 def volume(self, music = None, value = 100, **kwargs):
66 pass
67
68 def wait(self, duration = 0, **kwargs):
69 time.sleep(duration)
70
71 def command_print(self, command = "", **kwargs):
72 return "running command {}".format(command)
73
74 def pause_print(self, music = None, **kwargs):
75 if music is not None:
76 return "pausing {}".format(music.filename)
77 else:
78 return "pausing all musics"
79
80 def play_print(self, music = None, fade_in = 0, start_at = 0,
81 restart_if_running = False, volume = 100, **kwargs):
82 message = "starting "
83 if music is not None:
84 message += music.filename
85 else:
86 message += "music"
87
88 if start_at != 0:
89 message += " at {}s".format(start_at)
90
91 if fade_in != 0:
92 message += " with {}s fade_in".format(fade_in)
93
94 message += " at volume {}%".format(volume)
95
96 if restart_if_running:
97 message += " (restarting if already running)"
98
99 return message
100
101 def stop_print(self, music = None, fade_out = 0, **kwargs):
102 if music is not None:
103 if fade_out == 0:
104 return "stopping music {}".format(music.filename)
105 else:
106 return "stopping music {} with {}s fadeout".format(music.filename, fade_out)
107 else:
108 if fade_out == 0:
109 return "stopping all musics"
110 else:
111 return "stopping all musics with {}s fadeout".format(fade_out)
112
113 def stop_all_actions_print(self):
114 return "stopping all actions"
115
116 def volume_print(self, music = None, value = 100, *kwargs):
117 if music is not None:
118 return "setting volume of {} to {}%".format(music.filename, value)
119 else:
120 return "setting volume to {}%".format(value)
121
122 def wait_print(self, duration, **kwargs):
123 return "waiting {}s".format(duration)
124
125class Key:
126 row_positions = {
127 'first': 0,
128 'second': 50,
129 'third': 100,
130 'fourth': 150,
131 'fifth': 200,
132 'sixth': 250,
133 }
134
135 default_outer_color = (120, 120, 120)
136 lighter_outer_color = (200, 200, 200)
137 default_inner_color = (255, 255, 255)
138 mapped_inner_color = ( 0, 255, 0)
139 mapped_unready_inner_color = (255, 165, 0)
140
141 def __init__(self, mapping, key_name, key_sym, top, left, width = 48, height = 48, disabled = False):
142 self.mapping = mapping
143 self.key_name = key_name
144 self.key_sym = key_sym
145
146 if isinstance(top, str):
147 self.top = self.row_positions[top]
148 else:
149 self.top = top
150
151 self.left = left
152 self.width = width
153 self.height = height
154
155 self.bottom = self.top + self.height
156 self.right = self.left + self.width
157
158 self.rect = (self.left, self.top, self.right, self.bottom)
159 self.position = (self.left, self.top)
160
161 if disabled:
162 self.outer_color = self.lighter_outer_color
163 self.linewidth = 1
164 else:
165 self.outer_color = self.default_outer_color
166 self.linewidth = 3
167
168 self.inner_color = self.default_inner_color
169 self.actions = []
170
171 def square(self, all_actions_ready):
172 if self.has_actions():
173 if all_actions_ready:
174 self.inner_color = self.mapped_inner_color
175 else:
176 self.inner_color = self.mapped_unready_inner_color
177
178 return RoundedRect((0, 0, self.width, self.height),
179 self.outer_color, self.inner_color, self.linewidth)
180
181 def collidepoint(self, position):
182 return self.surface.get_rect().collidepoint(
183 position[0] - self.position[0],
184 position[1] - self.position[1]
185 )
186
187 def draw(self, background_surface):
188 draw_lock.acquire()
189 all_actions_ready = self.all_actions_ready()
190
191 self.surface = self.square(all_actions_ready).surface()
192
193 if getattr(sys, 'frozen', False):
194 police = font.Font(sys._MEIPASS + "/Ubuntu-Regular.ttf", 14)
195 else:
196 police = font.Font("Ubuntu-Regular.ttf", 14)
197
198 text = police.render(self.key_sym, True, (0,0,0))
199 self.surface.blit(text, (5,5))
200 background_surface.blit(self.surface, self.position)
201 draw_lock.release()
202
203 return not all_actions_ready
204
205 def poll_redraw(self, background):
206 while True:
207 time.sleep(1)
208 if self.all_actions_ready():
209 self.draw(background)
210 self.mapping.blit()
211 break
212
213 def has_actions(self):
214 return len(self.actions) > 0
215
216 def all_actions_ready(self):
217 return all(action.ready() for action in self.actions)
218
219 def add_action(self, action_name, **arguments):
220 self.actions.append(Action(action_name, self, **arguments))
221
222 def do_actions(self):
223 print("running actions for {}".format(self.key_sym))
224 start_time = time.time()
225 self.mapping.start_running(self, start_time)
226 for action in self.actions:
227 if self.mapping.keep_running(self, start_time):
228 action.run()
229
230 self.mapping.finished_running(self, start_time)
231
232 def list_actions(self, surface):
233 # FIXME: Todo
234 print("bouh", self.key_sym)
235 surface.fill((255, 0, 0))
236
237
238class Mapping:
239 WIDTH = 903
240 HEIGHT = 298
241 SIZE = WIDTH, HEIGHT
242
243 KEYS = [
244 (K_ESCAPE, 'ESC', 'first', 0, {}),
245
246 (K_F1, 'F1', 'first', 100, {}),
247 (K_F2, 'F2', 'first', 150, {}),
248 (K_F3, 'F3', 'first', 200, {}),
249 (K_F4, 'F4', 'first', 250, {}),
250
251 (K_F5, 'F5', 'first', 325, {}),
252 (K_F6, 'F6', 'first', 375, {}),
253 (K_F7, 'F7', 'first', 425, {}),
254 (K_F8, 'F8', 'first', 475, {}),
255
256 (K_F9, 'F9', 'first', 550, {}),
257 (K_F10, 'F10', 'first', 600, {}),
258 (K_F11, 'F11', 'first', 650, {}),
259 (K_F12, 'F12', 'first', 700, {}),
260
261
262 (178, '²', 'second', 0, {}),
263 (K_AMPERSAND, '&', 'second', 50, {}),
264 (233, 'é', 'second', 100, {}),
265 (K_QUOTEDBL, '"', 'second', 150, {}),
266 (K_QUOTE, "'", 'second', 200, {}),
267 (K_LEFTPAREN, '(', 'second', 250, {}),
268 (K_MINUS, '-', 'second', 300, {}),
269 (232, 'è', 'second', 350, {}),
270 (K_UNDERSCORE, '_', 'second', 400, {}),
271 (231, 'ç', 'second', 450, {}),
272 (224, 'à', 'second', 500, {}),
273 (K_RIGHTPAREN, ')', 'second', 550, {}),
274 (K_EQUALS, '=', 'second', 600, {}),
275
276 (K_BACKSPACE, '<-', 'second', 650, { 'width': 98 }),
277
278
279 (K_TAB, 'tab', 'third', 0, { 'width' : 73 }),
280 (K_a, 'a', 'third', 75, {}),
281 (K_z, 'z', 'third', 125, {}),
282 (K_e, 'e', 'third', 175, {}),
283 (K_r, 'r', 'third', 225, {}),
284 (K_t, 't', 'third', 275, {}),
285 (K_y, 'y', 'third', 325, {}),
286 (K_u, 'u', 'third', 375, {}),
287 (K_i, 'i', 'third', 425, {}),
288 (K_o, 'o', 'third', 475, {}),
289 (K_p, 'p', 'third', 525, {}),
290 (K_CARET, '^', 'third', 575, {}),
291 (K_DOLLAR, '$', 'third', 625, {}),
292
293 (K_RETURN, 'Enter', 'third', 692, { 'width': 56, 'height': 98 }),
294
295 (K_CAPSLOCK, 'CAPS', 'fourth', 0, { 'width': 88, 'disabled': True }),
296
297 (K_q, 'q', 'fourth', 90, {}),
298 (K_s, 's', 'fourth', 140, {}),
299 (K_d, 'd', 'fourth', 190, {}),
300 (K_f, 'f', 'fourth', 240, {}),
301 (K_g, 'g', 'fourth', 290, {}),
302 (K_h, 'h', 'fourth', 340, {}),
303 (K_j, 'j', 'fourth', 390, {}),
304 (K_k, 'k', 'fourth', 440, {}),
305 (K_l, 'l', 'fourth', 490, {}),
306 (K_m, 'm', 'fourth', 540, {}),
307 (249, 'ù', 'fourth', 590, {}),
308 (K_ASTERISK, '*', 'fourth', 640, {}),
309
310
311 (K_LSHIFT, 'LShift', 'fifth', 0, { 'width': 63, 'disabled': True }),
312
313 (K_LESS, '<', 'fifth', 65, {}),
314 (K_w, 'w', 'fifth', 115, {}),
315 (K_x, 'x', 'fifth', 165, {}),
316 (K_c, 'c', 'fifth', 215, {}),
317 (K_v, 'v', 'fifth', 265, {}),
318 (K_b, 'b', 'fifth', 315, {}),
319 (K_n, 'n', 'fifth', 365, {}),
320 (K_COMMA, ',', 'fifth', 415, {}),
321 (K_SEMICOLON, ';', 'fifth', 465, {}),
322 (K_COLON, ':', 'fifth', 515, {}),
323 (K_EXCLAIM, '!', 'fifth', 565, {}),
324
325 (K_RSHIFT, 'RShift', 'fifth', 615, { 'width': 133, 'disabled': True }),
326
327 (K_LCTRL, 'LCtrl', 'sixth', 0, { 'width': 63, 'disabled': True }),
328 (K_LSUPER, 'LSuper', 'sixth', 115, { 'disabled': True }),
329 (K_LALT, 'LAlt', 'sixth', 165, { 'disabled': True }),
330 (K_SPACE, 'Espace', 'sixth', 215, { 'width': 248 }),
331 (K_MODE, 'AltGr', 'sixth', 465, { 'disabled': True }),
332 (314, 'Compose', 'sixth', 515, { 'disabled': True }),
333 (K_RCTRL, 'RCtrl', 'sixth', 565, { 'width': 63, 'disabled': True }),
334
335
336 (K_INSERT, 'ins', 'second', 755, {}),
337 (K_HOME, 'home', 'second', 805, {}),
338 (K_PAGEUP, 'pg_u', 'second', 855, {}),
339 (K_DELETE, 'del', 'third', 755, {}),
340 (K_END, 'end', 'third', 805, {}),
341 (K_PAGEDOWN, 'pg_d', 'third', 855, {}),
342
343
344 (K_UP, 'up', 'fifth', 805, {}),
345 (K_DOWN, 'down', 'sixth', 805, {}),
346 (K_LEFT, 'left', 'sixth', 755, {}),
347 (K_RIGHT, 'right', 'sixth', 855, {}),
348 ]
349
350 def __init__(self, screen):
351 self.screen = screen
352 self.background = Surface(self.SIZE).convert()
353 self.background.fill((250, 250, 250))
354 self.keys = {}
355 self.running = []
356 for key in self.KEYS:
357 self.keys[key[0]] = Key(self, *key[0:4], **key[4])
358
359 def draw(self):
360 for key_name in self.keys:
361 key = self.keys[key_name]
362 should_redraw_key = key.draw(self.background)
363
364 if should_redraw_key:
365 threading.Thread(target = key.poll_redraw, args = [self.background]).start()
366 self.blit()
367
368 def blit(self):
369 draw_lock.acquire()
370 self.screen.blit(self.background, (5, 5))
371 display.flip()
372 draw_lock.release()
373
374 def find_by_key_num(self, key_num):
375 if key_num in self.keys:
376 return self.keys[key_num]
377 return None
378
379 def find_by_collidepoint(self, position):
380 for key in self.keys:
381 if self.keys[key].collidepoint(position):
382 return self.keys[key]
383 return None
384
385 def find_by_unicode(self, key_sym):
386 for key in self.keys:
387 if self.keys[key].key_sym == key_sym:
388 return self.keys[key]
389 return None
390
391 def stop_all_running(self):
392 self.running = []
393
394 def start_running(self, key, start_time):
395 self.running.append((key, start_time))
396
397 def keep_running(self, key, start_time):
398 return (key, start_time) in self.running
399
400 def finished_running(self, key, start_time):
401 if (key, start_time) in self.running:
402 self.running.remove((key, start_time))
403
404
405class MusicFile:
406 def __init__(self, filename, lock):
407 self.filename = filename
408 self.channel = None
409 self.raw_data = None
410 self.sound = None
411
412 self.loaded = False
413 threading.Thread(target = self.load_sound, args = [lock]).start()
414
415 def load_sound(self, lock):
416 lock.acquire()
417 print("Loading {}".format(self.filename))
418 self.raw_data = pydub.AudioSegment.from_file(self.filename).raw_data
419 self.sound = mixer.Sound(self.raw_data)
420 print("Loaded {}".format(self.filename))
421 self.loaded = True
422 lock.release()
423
424 def play(self):
425 self.channel = self.sound.play()
426
427 def pause(self):
428 if self.channel is not None:
429 self.channel.pause()
430
431 def stop(self):
432 self.channel = None
433 self.sound.stop()
434
435
436class RoundedRect:
437 def __init__(self, rect, outer_color, inner_color, linewidth = 2, radius = 0.4):
438 self.rect = Rect(rect)
439 self.outer_color = Color(*outer_color)
440 self.inner_color = Color(*inner_color)
441 self.linewidth = linewidth
442 self.radius = radius
443
444 def surface(self):
445 rectangle = self.filledRoundedRect(self.rect, self.outer_color, self.radius)
446
447 inner_rect = Rect((
448 self.rect.left + 2 * self.linewidth,
449 self.rect.top + 2 * self.linewidth,
450 self.rect.right - 2 * self.linewidth,
451 self.rect.bottom - 2 * self.linewidth
452 ))
453
454 inner_rectangle = self.filledRoundedRect(inner_rect, self.inner_color, self.radius)
455
456 rectangle.blit(inner_rectangle, (self.linewidth, self.linewidth))
457
458 return rectangle
459
460 def filledRoundedRect(self, rect, color, radius=0.4):
461 """
462 filledRoundedRect(rect,color,radius=0.4)
463
464 rect : rectangle
465 color : rgb or rgba
466 radius : 0 <= radius <= 1
467 """
468
469 alpha = color.a
470 color.a = 0
471 pos = rect.topleft
472 rect.topleft = 0,0
473 rectangle = Surface(rect.size,SRCALPHA)
474
475 circle = Surface([min(rect.size)*3]*2,SRCALPHA)
476 draw.ellipse(circle,(0,0,0),circle.get_rect(),0)
477 circle = transform.smoothscale(circle,[int(min(rect.size)*radius)]*2)
478
479 radius = rectangle.blit(circle,(0,0))
480 radius.bottomright = rect.bottomright
481 rectangle.blit(circle,radius)
482 radius.topright = rect.topright
483 rectangle.blit(circle,radius)
484 radius.bottomleft = rect.bottomleft
485 rectangle.blit(circle,radius)
486
487 rectangle.fill((0,0,0),rect.inflate(-radius.w,0))
488 rectangle.fill((0,0,0),rect.inflate(0,-radius.h))
489
490 rectangle.fill(color,special_flags=BLEND_RGBA_MAX)
491 rectangle.fill((255,255,255,alpha),special_flags=BLEND_RGBA_MIN)
492
493 return rectangle
494
495
496def parse_config(mapping): 8def parse_config(mapping):
497 import yaml 9 import yaml
498 stream = open("config.yml", "r") 10 stream = open("config.yml", "r")
diff --git a/helpers/action.py b/helpers/action.py
new file mode 100644
index 0000000..a68de90
--- /dev/null
+++ b/helpers/action.py
@@ -0,0 +1,119 @@
1import pygame
2import time
3
4class Action:
5 action_types = [
6 'command',
7 'pause',
8 'play',
9 'stop',
10 'stop_all_actions',
11 'volume',
12 'wait',
13 ]
14
15 def __init__(self, action, key, **kwargs):
16 if action in self.action_types:
17 self.action = action
18 else:
19 raise Exception("Unknown action {}".format(action))
20
21 self.key = key
22 self.arguments = kwargs
23
24 def ready(self):
25 if 'music' in self.arguments:
26 return self.arguments['music'].loaded
27 else:
28 return True
29
30 def run(self):
31 print(getattr(self, self.action + "_print")(**self.arguments))
32 return getattr(self, self.action)(**self.arguments)
33
34 def command(self, command = "", **kwargs):
35 pass
36
37 def pause(self, music = None, **kwargs):
38 if music is not None:
39 music.pause()
40 else:
41 pygame.mixer.pause()
42
43 def play(self, music = None, fade_in = 0, start_at = 0,
44 restart_if_running = False, volume = 100, **kwargs):
45 if music is not None:
46 music.play()
47 else:
48 pygame.mixer.unpause()
49
50 def stop(self, music = None, fade_out = 0, **kwargs):
51 if music is not None:
52 music.stop()
53 else:
54 pygame.mixer.stop()
55
56 def stop_all_actions(self, **kwargs):
57 self.key.mapping.stop_all_running()
58
59 def volume(self, music = None, value = 100, **kwargs):
60 pass
61
62 def wait(self, duration = 0, **kwargs):
63 time.sleep(duration)
64
65 def command_print(self, command = "", **kwargs):
66 return "running command {}".format(command)
67
68 def pause_print(self, music = None, **kwargs):
69 if music is not None:
70 return "pausing {}".format(music.filename)
71 else:
72 return "pausing all musics"
73
74 def play_print(self, music = None, fade_in = 0, start_at = 0,
75 restart_if_running = False, volume = 100, **kwargs):
76 message = "starting "
77 if music is not None:
78 message += music.filename
79 else:
80 message += "music"
81
82 if start_at != 0:
83 message += " at {}s".format(start_at)
84
85 if fade_in != 0:
86 message += " with {}s fade_in".format(fade_in)
87
88 message += " at volume {}%".format(volume)
89
90 if restart_if_running:
91 message += " (restarting if already running)"
92
93 return message
94
95 def stop_print(self, music = None, fade_out = 0, **kwargs):
96 if music is not None:
97 if fade_out == 0:
98 return "stopping music {}".format(music.filename)
99 else:
100 return "stopping music {} with {}s fadeout".format(music.filename, fade_out)
101 else:
102 if fade_out == 0:
103 return "stopping all musics"
104 else:
105 return "stopping all musics with {}s fadeout".format(fade_out)
106
107 def stop_all_actions_print(self):
108 return "stopping all actions"
109
110 def volume_print(self, music = None, value = 100, *kwargs):
111 if music is not None:
112 return "setting volume of {} to {}%".format(music.filename, value)
113 else:
114 return "setting volume to {}%".format(value)
115
116 def wait_print(self, duration, **kwargs):
117 return "waiting {}s".format(duration)
118
119
diff --git a/helpers/key.py b/helpers/key.py
new file mode 100644
index 0000000..2e4a313
--- /dev/null
+++ b/helpers/key.py
@@ -0,0 +1,120 @@
1from .rounded_rect import *
2from .action import *
3import time
4import sys
5import pygame
6
7class Key:
8 row_positions = {
9 'first': 0,
10 'second': 50,
11 'third': 100,
12 'fourth': 150,
13 'fifth': 200,
14 'sixth': 250,
15 }
16
17 default_outer_color = (120, 120, 120)
18 lighter_outer_color = (200, 200, 200)
19 default_inner_color = (255, 255, 255)
20 mapped_inner_color = ( 0, 255, 0)
21 mapped_unready_inner_color = (255, 165, 0)
22
23 def __init__(self, mapping, draw_lock, key_name, key_sym, top, left, width = 48, height = 48, disabled = False):
24 self.draw_lock = draw_lock
25 self.mapping = mapping
26 self.key_name = key_name
27 self.key_sym = key_sym
28
29 if isinstance(top, str):
30 self.top = self.row_positions[top]
31 else:
32 self.top = top
33
34 self.left = left
35 self.width = width
36 self.height = height
37
38 self.bottom = self.top + self.height
39 self.right = self.left + self.width
40
41 self.rect = (self.left, self.top, self.right, self.bottom)
42 self.position = (self.left, self.top)
43
44 if disabled:
45 self.outer_color = self.lighter_outer_color
46 self.linewidth = 1
47 else:
48 self.outer_color = self.default_outer_color
49 self.linewidth = 3
50
51 self.inner_color = self.default_inner_color
52 self.actions = []
53
54 def square(self, all_actions_ready):
55 if self.has_actions():
56 if all_actions_ready:
57 self.inner_color = self.mapped_inner_color
58 else:
59 self.inner_color = self.mapped_unready_inner_color
60
61 return RoundedRect((0, 0, self.width, self.height),
62 self.outer_color, self.inner_color, self.linewidth)
63
64 def collidepoint(self, position):
65 return self.surface.get_rect().collidepoint(
66 position[0] - self.position[0],
67 position[1] - self.position[1]
68 )
69
70 def draw(self, background_surface):
71 self.draw_lock.acquire()
72 all_actions_ready = self.all_actions_ready()
73
74 self.surface = self.square(all_actions_ready).surface()
75
76 if getattr(sys, 'frozen', False):
77 police = pygame.font.Font(sys._MEIPASS + "/Ubuntu-Regular.ttf", 14)
78 else:
79 police = pygame.font.Font("Ubuntu-Regular.ttf", 14)
80
81 text = police.render(self.key_sym, True, (0,0,0))
82 self.surface.blit(text, (5,5))
83 background_surface.blit(self.surface, self.position)
84 self.draw_lock.release()
85
86 return not all_actions_ready
87
88 def poll_redraw(self, background):
89 while True:
90 time.sleep(1)
91 if self.all_actions_ready():
92 self.draw(background)
93 self.mapping.blit()
94 break
95
96 def has_actions(self):
97 return len(self.actions) > 0
98
99 def all_actions_ready(self):
100 return all(action.ready() for action in self.actions)
101
102 def add_action(self, action_name, **arguments):
103 self.actions.append(Action(action_name, self, **arguments))
104
105 def do_actions(self):
106 print("running actions for {}".format(self.key_sym))
107 start_time = time.time()
108 self.mapping.start_running(self, start_time)
109 for action in self.actions:
110 if self.mapping.keep_running(self, start_time):
111 action.run()
112
113 self.mapping.finished_running(self, start_time)
114
115 def list_actions(self, surface):
116 # FIXME: Todo
117 print("bouh", self.key_sym)
118 surface.fill((255, 0, 0))
119
120
diff --git a/helpers/mapping.py b/helpers/mapping.py
new file mode 100644
index 0000000..ee5623a
--- /dev/null
+++ b/helpers/mapping.py
@@ -0,0 +1,171 @@
1import threading
2import pygame
3from .key import *
4
5class Mapping:
6 WIDTH = 903
7 HEIGHT = 298
8 SIZE = WIDTH, HEIGHT
9
10 KEYS = [
11 (pygame.K_ESCAPE, 'ESC', 'first', 0, {}),
12
13 (pygame.K_F1, 'F1', 'first', 100, {}),
14 (pygame.K_F2, 'F2', 'first', 150, {}),
15 (pygame.K_F3, 'F3', 'first', 200, {}),
16 (pygame.K_F4, 'F4', 'first', 250, {}),
17
18 (pygame.K_F5, 'F5', 'first', 325, {}),
19 (pygame.K_F6, 'F6', 'first', 375, {}),
20 (pygame.K_F7, 'F7', 'first', 425, {}),
21 (pygame.K_F8, 'F8', 'first', 475, {}),
22
23 (pygame.K_F9, 'F9', 'first', 550, {}),
24 (pygame.K_F10, 'F10', 'first', 600, {}),
25 (pygame.K_F11, 'F11', 'first', 650, {}),
26 (pygame.K_F12, 'F12', 'first', 700, {}),
27
28
29 (178, '²', 'second', 0, {}),
30 (pygame.K_AMPERSAND, '&', 'second', 50, {}),
31 (233, 'é', 'second', 100, {}),
32 (pygame.K_QUOTEDBL, '"', 'second', 150, {}),
33 (pygame.K_QUOTE, "'", 'second', 200, {}),
34 (pygame.K_LEFTPAREN, '(', 'second', 250, {}),
35 (pygame.K_MINUS, '-', 'second', 300, {}),
36 (232, 'è', 'second', 350, {}),
37 (pygame.K_UNDERSCORE, '_', 'second', 400, {}),
38 (231, 'ç', 'second', 450, {}),
39 (224, 'à', 'second', 500, {}),
40 (pygame.K_RIGHTPAREN, ')', 'second', 550, {}),
41 (pygame.K_EQUALS, '=', 'second', 600, {}),
42
43 (pygame.K_BACKSPACE, '<-', 'second', 650, { 'width': 98 }),
44
45
46 (pygame.K_TAB, 'tab', 'third', 0, { 'width' : 73 }),
47 (pygame.K_a, 'a', 'third', 75, {}),
48 (pygame.K_z, 'z', 'third', 125, {}),
49 (pygame.K_e, 'e', 'third', 175, {}),
50 (pygame.K_r, 'r', 'third', 225, {}),
51 (pygame.K_t, 't', 'third', 275, {}),
52 (pygame.K_y, 'y', 'third', 325, {}),
53 (pygame.K_u, 'u', 'third', 375, {}),
54 (pygame.K_i, 'i', 'third', 425, {}),
55 (pygame.K_o, 'o', 'third', 475, {}),
56 (pygame.K_p, 'p', 'third', 525, {}),
57 (pygame.K_CARET, '^', 'third', 575, {}),
58 (pygame.K_DOLLAR, '$', 'third', 625, {}),
59
60 (pygame.K_RETURN, 'Enter', 'third', 692, { 'width': 56, 'height': 98 }),
61
62 (pygame.K_CAPSLOCK, 'CAPS', 'fourth', 0, { 'width': 88, 'disabled': True }),
63
64 (pygame.K_q, 'q', 'fourth', 90, {}),
65 (pygame.K_s, 's', 'fourth', 140, {}),
66 (pygame.K_d, 'd', 'fourth', 190, {}),
67 (pygame.K_f, 'f', 'fourth', 240, {}),
68 (pygame.K_g, 'g', 'fourth', 290, {}),
69 (pygame.K_h, 'h', 'fourth', 340, {}),
70 (pygame.K_j, 'j', 'fourth', 390, {}),
71 (pygame.K_k, 'k', 'fourth', 440, {}),
72 (pygame.K_l, 'l', 'fourth', 490, {}),
73 (pygame.K_m, 'm', 'fourth', 540, {}),
74 (249, 'ù', 'fourth', 590, {}),
75 (pygame.K_ASTERISK, '*', 'fourth', 640, {}),
76
77
78 (pygame.K_LSHIFT, 'LShift', 'fifth', 0, { 'width': 63, 'disabled': True }),
79
80 (pygame.K_LESS, '<', 'fifth', 65, {}),
81 (pygame.K_w, 'w', 'fifth', 115, {}),
82 (pygame.K_x, 'x', 'fifth', 165, {}),
83 (pygame.K_c, 'c', 'fifth', 215, {}),
84 (pygame.K_v, 'v', 'fifth', 265, {}),
85 (pygame.K_b, 'b', 'fifth', 315, {}),
86 (pygame.K_n, 'n', 'fifth', 365, {}),
87 (pygame.K_COMMA, ',', 'fifth', 415, {}),
88 (pygame.K_SEMICOLON, ';', 'fifth', 465, {}),
89 (pygame.K_COLON, ':', 'fifth', 515, {}),
90 (pygame.K_EXCLAIM, '!', 'fifth', 565, {}),
91
92 (pygame.K_RSHIFT, 'RShift', 'fifth', 615, { 'width': 133, 'disabled': True }),
93
94 (pygame.K_LCTRL, 'LCtrl', 'sixth', 0, { 'width': 63, 'disabled': True }),
95 (pygame.K_LSUPER, 'LSuper', 'sixth', 115, { 'disabled': True }),
96 (pygame.K_LALT, 'LAlt', 'sixth', 165, { 'disabled': True }),
97 (pygame.K_SPACE, 'Espace', 'sixth', 215, { 'width': 248 }),
98 (pygame.K_MODE, 'AltGr', 'sixth', 465, { 'disabled': True }),
99 (314, 'Compose', 'sixth', 515, { 'disabled': True }),
100 (pygame.K_RCTRL, 'RCtrl', 'sixth', 565, { 'width': 63, 'disabled': True }),
101
102
103 (pygame.K_INSERT, 'ins', 'second', 755, {}),
104 (pygame.K_HOME, 'home', 'second', 805, {}),
105 (pygame.K_PAGEUP, 'pg_u', 'second', 855, {}),
106 (pygame.K_DELETE, 'del', 'third', 755, {}),
107 (pygame.K_END, 'end', 'third', 805, {}),
108 (pygame.K_PAGEDOWN, 'pg_d', 'third', 855, {}),
109
110
111 (pygame.K_UP, 'up', 'fifth', 805, {}),
112 (pygame.K_DOWN, 'down', 'sixth', 805, {}),
113 (pygame.K_LEFT, 'left', 'sixth', 755, {}),
114 (pygame.K_RIGHT, 'right', 'sixth', 855, {}),
115 ]
116
117 def __init__(self, screen, draw_lock):
118 self.draw_lock = draw_lock
119 self.screen = screen
120 self.background = pygame.Surface(self.SIZE).convert()
121 self.background.fill((250, 250, 250))
122 self.keys = {}
123 self.running = []
124 for key in self.KEYS:
125 self.keys[key[0]] = Key(self, self.draw_lock, *key[0:4], **key[4])
126
127 def draw(self):
128 for key_name in self.keys:
129 key = self.keys[key_name]
130 should_redraw_key = key.draw(self.background)
131
132 if should_redraw_key:
133 threading.Thread(target = key.poll_redraw, args = [self.background]).start()
134 self.blit()
135
136 def blit(self):
137 self.draw_lock.acquire()
138 self.screen.blit(self.background, (5, 5))
139 pygame.display.flip()
140 self.draw_lock.release()
141
142 def find_by_key_num(self, key_num):
143 if key_num in self.keys:
144 return self.keys[key_num]
145 return None
146
147 def find_by_collidepoint(self, position):
148 for key in self.keys:
149 if self.keys[key].collidepoint(position):
150 return self.keys[key]
151 return None
152
153 def find_by_unicode(self, key_sym):
154 for key in self.keys:
155 if self.keys[key].key_sym == key_sym:
156 return self.keys[key]
157 return None
158
159 def stop_all_running(self):
160 self.running = []
161
162 def start_running(self, key, start_time):
163 self.running.append((key, start_time))
164
165 def keep_running(self, key, start_time):
166 return (key, start_time) in self.running
167
168 def finished_running(self, key, start_time):
169 if (key, start_time) in self.running:
170 self.running.remove((key, start_time))
171
diff --git a/helpers/music_file.py b/helpers/music_file.py
new file mode 100644
index 0000000..39b0566
--- /dev/null
+++ b/helpers/music_file.py
@@ -0,0 +1,35 @@
1import threading
2import pydub
3import pygame
4
5class MusicFile:
6 def __init__(self, filename, lock):
7 self.filename = filename
8 self.channel = None
9 self.raw_data = None
10 self.sound = None
11
12 self.loaded = False
13 threading.Thread(target = self.load_sound, args = [lock]).start()
14
15 def load_sound(self, lock):
16 lock.acquire()
17 print("Loading {}".format(self.filename))
18 self.raw_data = pydub.AudioSegment.from_file(self.filename).raw_data
19 self.sound = pygame.mixer.Sound(self.raw_data)
20 print("Loaded {}".format(self.filename))
21 self.loaded = True
22 lock.release()
23
24 def play(self):
25 self.channel = self.sound.play()
26
27 def pause(self):
28 if self.channel is not None:
29 self.channel.pause()
30
31 def stop(self):
32 self.channel = None
33 self.sound.stop()
34
35
diff --git a/helpers/rounded_rect.py b/helpers/rounded_rect.py
new file mode 100644
index 0000000..f168e0e
--- /dev/null
+++ b/helpers/rounded_rect.py
@@ -0,0 +1,62 @@
1import pygame
2
3class RoundedRect:
4 def __init__(self, rect, outer_color, inner_color, linewidth = 2, radius = 0.4):
5 self.rect = pygame.Rect(rect)
6 self.outer_color = pygame.Color(*outer_color)
7 self.inner_color = pygame.Color(*inner_color)
8 self.linewidth = linewidth
9 self.radius = radius
10
11 def surface(self):
12 rectangle = self.filledRoundedRect(self.rect, self.outer_color, self.radius)
13
14 inner_rect = pygame.Rect((
15 self.rect.left + 2 * self.linewidth,
16 self.rect.top + 2 * self.linewidth,
17 self.rect.right - 2 * self.linewidth,
18 self.rect.bottom - 2 * self.linewidth
19 ))
20
21 inner_rectangle = self.filledRoundedRect(inner_rect, self.inner_color, self.radius)
22
23 rectangle.blit(inner_rectangle, (self.linewidth, self.linewidth))
24
25 return rectangle
26
27 def filledRoundedRect(self, rect, color, radius=0.4):
28 """
29 filledRoundedRect(rect,color,radius=0.4)
30
31 rect : rectangle
32 color : rgb or rgba
33 radius : 0 <= radius <= 1
34 """
35
36 alpha = color.a
37 color.a = 0
38 pos = rect.topleft
39 rect.topleft = 0,0
40 rectangle = pygame.Surface(rect.size,pygame.SRCALPHA)
41
42 circle = pygame.Surface([min(rect.size)*3]*2,pygame.SRCALPHA)
43 pygame.draw.ellipse(circle,(0,0,0),circle.get_rect(),0)
44 circle = pygame.transform.smoothscale(circle,[int(min(rect.size)*radius)]*2)
45
46 radius = rectangle.blit(circle,(0,0))
47 radius.bottomright = rect.bottomright
48 rectangle.blit(circle,radius)
49 radius.topright = rect.topright
50 rectangle.blit(circle,radius)
51 radius.bottomleft = rect.bottomleft
52 rectangle.blit(circle,radius)
53
54 rectangle.fill((0,0,0),rect.inflate(-radius.w,0))
55 rectangle.fill((0,0,0),rect.inflate(0,-radius.h))
56
57 rectangle.fill(color,special_flags=pygame.BLEND_RGBA_MAX)
58 rectangle.fill((255,255,255,alpha),special_flags=pygame.BLEND_RGBA_MIN)
59
60 return rectangle
61
62