diff options
-rw-r--r-- | helpers/__init__.py | 492 | ||||
-rw-r--r-- | helpers/action.py | 119 | ||||
-rw-r--r-- | helpers/key.py | 120 | ||||
-rw-r--r-- | helpers/mapping.py | 171 | ||||
-rw-r--r-- | helpers/music_file.py | 35 | ||||
-rw-r--r-- | helpers/rounded_rect.py | 62 | ||||
-rw-r--r-- | music_sampler.py | 7 |
7 files changed, 515 insertions, 491 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 -*- |
2 | from pygame import * | ||
3 | import pydub | ||
4 | import sys | ||
5 | import time | ||
6 | import threading | 2 | import threading |
3 | from .music_file import * | ||
4 | from .mapping import * | ||
7 | 5 | ||
8 | draw_lock = threading.RLock() | 6 | draw_lock = threading.RLock() |
9 | 7 | ||
10 | class 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 | |||
125 | class 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 | |||
238 | class 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 | |||
405 | class 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 | |||
436 | class 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 | |||
496 | def parse_config(mapping): | 8 | def 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 @@ | |||
1 | import pygame | ||
2 | import time | ||
3 | |||
4 | class 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 @@ | |||
1 | from .rounded_rect import * | ||
2 | from .action import * | ||
3 | import time | ||
4 | import sys | ||
5 | import pygame | ||
6 | |||
7 | class 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 @@ | |||
1 | import threading | ||
2 | import pygame | ||
3 | from .key import * | ||
4 | |||
5 | class 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 @@ | |||
1 | import threading | ||
2 | import pydub | ||
3 | import pygame | ||
4 | |||
5 | class 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 @@ | |||
1 | import pygame | ||
2 | |||
3 | class 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 | |||
diff --git a/music_sampler.py b/music_sampler.py index 6cc49df..9f14d56 100644 --- a/music_sampler.py +++ b/music_sampler.py | |||
@@ -10,7 +10,7 @@ size = width, height = 1024, 600 | |||
10 | 10 | ||
11 | helpers.draw_lock.acquire() | 11 | helpers.draw_lock.acquire() |
12 | screen = pygame.display.set_mode(size) | 12 | screen = pygame.display.set_mode(size) |
13 | mapping = helpers.Mapping(screen) | 13 | mapping = helpers.Mapping(screen, helpers.draw_lock) |
14 | 14 | ||
15 | action_surface = pygame.Surface((600, 250)).convert() | 15 | action_surface = pygame.Surface((600, 250)).convert() |
16 | action_surface.fill((0,0,0)) | 16 | action_surface.fill((0,0,0)) |
@@ -38,6 +38,11 @@ while 1: | |||
38 | event.type == pygame.KEYDOWN and | 38 | event.type == pygame.KEYDOWN and |
39 | event.mod == 4160 and | 39 | event.mod == 4160 and |
40 | event.key == pygame.K_c): | 40 | event.key == pygame.K_c): |
41 | for thread in threading.enumerate(): | ||
42 | if thread is threading.current_thread(): | ||
43 | continue | ||
44 | thread.join() | ||
45 | |||
41 | pygame.quit() | 46 | pygame.quit() |
42 | sys.exit() | 47 | sys.exit() |
43 | 48 | ||