From be27763f8be0f647cbe17ecee8c782901ce2cede Mon Sep 17 00:00:00 2001 From: =?utf8?q?Isma=C3=ABl=20Bouya?= Date: Sat, 18 Jun 2016 22:13:19 +0200 Subject: [PATCH] Move classes to separate file --- helpers/__init__.py | 492 +--------------------------------------- helpers/action.py | 119 ++++++++++ helpers/key.py | 120 ++++++++++ helpers/mapping.py | 171 ++++++++++++++ helpers/music_file.py | 35 +++ helpers/rounded_rect.py | 62 +++++ music_sampler.py | 7 +- 7 files changed, 515 insertions(+), 491 deletions(-) create mode 100644 helpers/action.py create mode 100644 helpers/key.py create mode 100644 helpers/mapping.py create mode 100644 helpers/music_file.py create mode 100644 helpers/rounded_rect.py 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 @@ # -*- coding: utf-8 -*- -from pygame import * -import pydub -import sys -import time import threading +from .music_file import * +from .mapping import * draw_lock = threading.RLock() -class Action: - action_types = [ - 'command', - 'pause', - 'play', - 'stop', - 'stop_all_actions', - 'volume', - 'wait', - ] - - def __init__(self, action, key, **kwargs): - if action in self.action_types: - self.action = action - else: - raise Exception("Unknown action {}".format(action)) - - self.key = key - self.arguments = kwargs - - def ready(self): - if 'music' in self.arguments: - return self.arguments['music'].loaded - else: - return True - - def run(self): - print(getattr(self, self.action + "_print")(**self.arguments)) - return getattr(self, self.action)(**self.arguments) - - def command(self, command = "", **kwargs): - pass - - def pause(self, music = None, **kwargs): - if music is not None: - music.pause() - else: - mixer.pause() - - def play(self, music = None, fade_in = 0, start_at = 0, - restart_if_running = False, volume = 100, **kwargs): - if music is not None: - music.play() - else: - mixer.unpause() - - def stop(self, music = None, fade_out = 0, **kwargs): - if music is not None: - music.stop() - else: - mixer.stop() - - def stop_all_actions(self, **kwargs): - self.key.mapping.stop_all_running() - - def volume(self, music = None, value = 100, **kwargs): - pass - - def wait(self, duration = 0, **kwargs): - time.sleep(duration) - - def command_print(self, command = "", **kwargs): - return "running command {}".format(command) - - def pause_print(self, music = None, **kwargs): - if music is not None: - return "pausing {}".format(music.filename) - else: - return "pausing all musics" - - def play_print(self, music = None, fade_in = 0, start_at = 0, - restart_if_running = False, volume = 100, **kwargs): - message = "starting " - if music is not None: - message += music.filename - else: - message += "music" - - if start_at != 0: - message += " at {}s".format(start_at) - - if fade_in != 0: - message += " with {}s fade_in".format(fade_in) - - message += " at volume {}%".format(volume) - - if restart_if_running: - message += " (restarting if already running)" - - return message - - def stop_print(self, music = None, fade_out = 0, **kwargs): - if music is not None: - if fade_out == 0: - return "stopping music {}".format(music.filename) - else: - return "stopping music {} with {}s fadeout".format(music.filename, fade_out) - else: - if fade_out == 0: - return "stopping all musics" - else: - return "stopping all musics with {}s fadeout".format(fade_out) - - def stop_all_actions_print(self): - return "stopping all actions" - - def volume_print(self, music = None, value = 100, *kwargs): - if music is not None: - return "setting volume of {} to {}%".format(music.filename, value) - else: - return "setting volume to {}%".format(value) - - def wait_print(self, duration, **kwargs): - return "waiting {}s".format(duration) - -class Key: - row_positions = { - 'first': 0, - 'second': 50, - 'third': 100, - 'fourth': 150, - 'fifth': 200, - 'sixth': 250, - } - - default_outer_color = (120, 120, 120) - lighter_outer_color = (200, 200, 200) - default_inner_color = (255, 255, 255) - mapped_inner_color = ( 0, 255, 0) - mapped_unready_inner_color = (255, 165, 0) - - def __init__(self, mapping, key_name, key_sym, top, left, width = 48, height = 48, disabled = False): - self.mapping = mapping - self.key_name = key_name - self.key_sym = key_sym - - if isinstance(top, str): - self.top = self.row_positions[top] - else: - self.top = top - - self.left = left - self.width = width - self.height = height - - self.bottom = self.top + self.height - self.right = self.left + self.width - - self.rect = (self.left, self.top, self.right, self.bottom) - self.position = (self.left, self.top) - - if disabled: - self.outer_color = self.lighter_outer_color - self.linewidth = 1 - else: - self.outer_color = self.default_outer_color - self.linewidth = 3 - - self.inner_color = self.default_inner_color - self.actions = [] - - def square(self, all_actions_ready): - if self.has_actions(): - if all_actions_ready: - self.inner_color = self.mapped_inner_color - else: - self.inner_color = self.mapped_unready_inner_color - - return RoundedRect((0, 0, self.width, self.height), - self.outer_color, self.inner_color, self.linewidth) - - def collidepoint(self, position): - return self.surface.get_rect().collidepoint( - position[0] - self.position[0], - position[1] - self.position[1] - ) - - def draw(self, background_surface): - draw_lock.acquire() - all_actions_ready = self.all_actions_ready() - - self.surface = self.square(all_actions_ready).surface() - - if getattr(sys, 'frozen', False): - police = font.Font(sys._MEIPASS + "/Ubuntu-Regular.ttf", 14) - else: - police = font.Font("Ubuntu-Regular.ttf", 14) - - text = police.render(self.key_sym, True, (0,0,0)) - self.surface.blit(text, (5,5)) - background_surface.blit(self.surface, self.position) - draw_lock.release() - - return not all_actions_ready - - def poll_redraw(self, background): - while True: - time.sleep(1) - if self.all_actions_ready(): - self.draw(background) - self.mapping.blit() - break - - def has_actions(self): - return len(self.actions) > 0 - - def all_actions_ready(self): - return all(action.ready() for action in self.actions) - - def add_action(self, action_name, **arguments): - self.actions.append(Action(action_name, self, **arguments)) - - def do_actions(self): - print("running actions for {}".format(self.key_sym)) - start_time = time.time() - self.mapping.start_running(self, start_time) - for action in self.actions: - if self.mapping.keep_running(self, start_time): - action.run() - - self.mapping.finished_running(self, start_time) - - def list_actions(self, surface): - # FIXME: Todo - print("bouh", self.key_sym) - surface.fill((255, 0, 0)) - - -class Mapping: - WIDTH = 903 - HEIGHT = 298 - SIZE = WIDTH, HEIGHT - - KEYS = [ - (K_ESCAPE, 'ESC', 'first', 0, {}), - - (K_F1, 'F1', 'first', 100, {}), - (K_F2, 'F2', 'first', 150, {}), - (K_F3, 'F3', 'first', 200, {}), - (K_F4, 'F4', 'first', 250, {}), - - (K_F5, 'F5', 'first', 325, {}), - (K_F6, 'F6', 'first', 375, {}), - (K_F7, 'F7', 'first', 425, {}), - (K_F8, 'F8', 'first', 475, {}), - - (K_F9, 'F9', 'first', 550, {}), - (K_F10, 'F10', 'first', 600, {}), - (K_F11, 'F11', 'first', 650, {}), - (K_F12, 'F12', 'first', 700, {}), - - - (178, '²', 'second', 0, {}), - (K_AMPERSAND, '&', 'second', 50, {}), - (233, 'é', 'second', 100, {}), - (K_QUOTEDBL, '"', 'second', 150, {}), - (K_QUOTE, "'", 'second', 200, {}), - (K_LEFTPAREN, '(', 'second', 250, {}), - (K_MINUS, '-', 'second', 300, {}), - (232, 'è', 'second', 350, {}), - (K_UNDERSCORE, '_', 'second', 400, {}), - (231, 'ç', 'second', 450, {}), - (224, 'à', 'second', 500, {}), - (K_RIGHTPAREN, ')', 'second', 550, {}), - (K_EQUALS, '=', 'second', 600, {}), - - (K_BACKSPACE, '<-', 'second', 650, { 'width': 98 }), - - - (K_TAB, 'tab', 'third', 0, { 'width' : 73 }), - (K_a, 'a', 'third', 75, {}), - (K_z, 'z', 'third', 125, {}), - (K_e, 'e', 'third', 175, {}), - (K_r, 'r', 'third', 225, {}), - (K_t, 't', 'third', 275, {}), - (K_y, 'y', 'third', 325, {}), - (K_u, 'u', 'third', 375, {}), - (K_i, 'i', 'third', 425, {}), - (K_o, 'o', 'third', 475, {}), - (K_p, 'p', 'third', 525, {}), - (K_CARET, '^', 'third', 575, {}), - (K_DOLLAR, '$', 'third', 625, {}), - - (K_RETURN, 'Enter', 'third', 692, { 'width': 56, 'height': 98 }), - - (K_CAPSLOCK, 'CAPS', 'fourth', 0, { 'width': 88, 'disabled': True }), - - (K_q, 'q', 'fourth', 90, {}), - (K_s, 's', 'fourth', 140, {}), - (K_d, 'd', 'fourth', 190, {}), - (K_f, 'f', 'fourth', 240, {}), - (K_g, 'g', 'fourth', 290, {}), - (K_h, 'h', 'fourth', 340, {}), - (K_j, 'j', 'fourth', 390, {}), - (K_k, 'k', 'fourth', 440, {}), - (K_l, 'l', 'fourth', 490, {}), - (K_m, 'm', 'fourth', 540, {}), - (249, 'ù', 'fourth', 590, {}), - (K_ASTERISK, '*', 'fourth', 640, {}), - - - (K_LSHIFT, 'LShift', 'fifth', 0, { 'width': 63, 'disabled': True }), - - (K_LESS, '<', 'fifth', 65, {}), - (K_w, 'w', 'fifth', 115, {}), - (K_x, 'x', 'fifth', 165, {}), - (K_c, 'c', 'fifth', 215, {}), - (K_v, 'v', 'fifth', 265, {}), - (K_b, 'b', 'fifth', 315, {}), - (K_n, 'n', 'fifth', 365, {}), - (K_COMMA, ',', 'fifth', 415, {}), - (K_SEMICOLON, ';', 'fifth', 465, {}), - (K_COLON, ':', 'fifth', 515, {}), - (K_EXCLAIM, '!', 'fifth', 565, {}), - - (K_RSHIFT, 'RShift', 'fifth', 615, { 'width': 133, 'disabled': True }), - - (K_LCTRL, 'LCtrl', 'sixth', 0, { 'width': 63, 'disabled': True }), - (K_LSUPER, 'LSuper', 'sixth', 115, { 'disabled': True }), - (K_LALT, 'LAlt', 'sixth', 165, { 'disabled': True }), - (K_SPACE, 'Espace', 'sixth', 215, { 'width': 248 }), - (K_MODE, 'AltGr', 'sixth', 465, { 'disabled': True }), - (314, 'Compose', 'sixth', 515, { 'disabled': True }), - (K_RCTRL, 'RCtrl', 'sixth', 565, { 'width': 63, 'disabled': True }), - - - (K_INSERT, 'ins', 'second', 755, {}), - (K_HOME, 'home', 'second', 805, {}), - (K_PAGEUP, 'pg_u', 'second', 855, {}), - (K_DELETE, 'del', 'third', 755, {}), - (K_END, 'end', 'third', 805, {}), - (K_PAGEDOWN, 'pg_d', 'third', 855, {}), - - - (K_UP, 'up', 'fifth', 805, {}), - (K_DOWN, 'down', 'sixth', 805, {}), - (K_LEFT, 'left', 'sixth', 755, {}), - (K_RIGHT, 'right', 'sixth', 855, {}), - ] - - def __init__(self, screen): - self.screen = screen - self.background = Surface(self.SIZE).convert() - self.background.fill((250, 250, 250)) - self.keys = {} - self.running = [] - for key in self.KEYS: - self.keys[key[0]] = Key(self, *key[0:4], **key[4]) - - def draw(self): - for key_name in self.keys: - key = self.keys[key_name] - should_redraw_key = key.draw(self.background) - - if should_redraw_key: - threading.Thread(target = key.poll_redraw, args = [self.background]).start() - self.blit() - - def blit(self): - draw_lock.acquire() - self.screen.blit(self.background, (5, 5)) - display.flip() - draw_lock.release() - - def find_by_key_num(self, key_num): - if key_num in self.keys: - return self.keys[key_num] - return None - - def find_by_collidepoint(self, position): - for key in self.keys: - if self.keys[key].collidepoint(position): - return self.keys[key] - return None - - def find_by_unicode(self, key_sym): - for key in self.keys: - if self.keys[key].key_sym == key_sym: - return self.keys[key] - return None - - def stop_all_running(self): - self.running = [] - - def start_running(self, key, start_time): - self.running.append((key, start_time)) - - def keep_running(self, key, start_time): - return (key, start_time) in self.running - - def finished_running(self, key, start_time): - if (key, start_time) in self.running: - self.running.remove((key, start_time)) - - -class MusicFile: - def __init__(self, filename, lock): - self.filename = filename - self.channel = None - self.raw_data = None - self.sound = None - - self.loaded = False - threading.Thread(target = self.load_sound, args = [lock]).start() - - def load_sound(self, lock): - lock.acquire() - print("Loading {}".format(self.filename)) - self.raw_data = pydub.AudioSegment.from_file(self.filename).raw_data - self.sound = mixer.Sound(self.raw_data) - print("Loaded {}".format(self.filename)) - self.loaded = True - lock.release() - - def play(self): - self.channel = self.sound.play() - - def pause(self): - if self.channel is not None: - self.channel.pause() - - def stop(self): - self.channel = None - self.sound.stop() - - -class RoundedRect: - def __init__(self, rect, outer_color, inner_color, linewidth = 2, radius = 0.4): - self.rect = Rect(rect) - self.outer_color = Color(*outer_color) - self.inner_color = Color(*inner_color) - self.linewidth = linewidth - self.radius = radius - - def surface(self): - rectangle = self.filledRoundedRect(self.rect, self.outer_color, self.radius) - - inner_rect = Rect(( - self.rect.left + 2 * self.linewidth, - self.rect.top + 2 * self.linewidth, - self.rect.right - 2 * self.linewidth, - self.rect.bottom - 2 * self.linewidth - )) - - inner_rectangle = self.filledRoundedRect(inner_rect, self.inner_color, self.radius) - - rectangle.blit(inner_rectangle, (self.linewidth, self.linewidth)) - - return rectangle - - def filledRoundedRect(self, rect, color, radius=0.4): - """ - filledRoundedRect(rect,color,radius=0.4) - - rect : rectangle - color : rgb or rgba - radius : 0 <= radius <= 1 - """ - - alpha = color.a - color.a = 0 - pos = rect.topleft - rect.topleft = 0,0 - rectangle = Surface(rect.size,SRCALPHA) - - circle = Surface([min(rect.size)*3]*2,SRCALPHA) - draw.ellipse(circle,(0,0,0),circle.get_rect(),0) - circle = transform.smoothscale(circle,[int(min(rect.size)*radius)]*2) - - radius = rectangle.blit(circle,(0,0)) - radius.bottomright = rect.bottomright - rectangle.blit(circle,radius) - radius.topright = rect.topright - rectangle.blit(circle,radius) - radius.bottomleft = rect.bottomleft - rectangle.blit(circle,radius) - - rectangle.fill((0,0,0),rect.inflate(-radius.w,0)) - rectangle.fill((0,0,0),rect.inflate(0,-radius.h)) - - rectangle.fill(color,special_flags=BLEND_RGBA_MAX) - rectangle.fill((255,255,255,alpha),special_flags=BLEND_RGBA_MIN) - - return rectangle - - def parse_config(mapping): import yaml 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 @@ +import pygame +import time + +class Action: + action_types = [ + 'command', + 'pause', + 'play', + 'stop', + 'stop_all_actions', + 'volume', + 'wait', + ] + + def __init__(self, action, key, **kwargs): + if action in self.action_types: + self.action = action + else: + raise Exception("Unknown action {}".format(action)) + + self.key = key + self.arguments = kwargs + + def ready(self): + if 'music' in self.arguments: + return self.arguments['music'].loaded + else: + return True + + def run(self): + print(getattr(self, self.action + "_print")(**self.arguments)) + return getattr(self, self.action)(**self.arguments) + + def command(self, command = "", **kwargs): + pass + + def pause(self, music = None, **kwargs): + if music is not None: + music.pause() + else: + pygame.mixer.pause() + + def play(self, music = None, fade_in = 0, start_at = 0, + restart_if_running = False, volume = 100, **kwargs): + if music is not None: + music.play() + else: + pygame.mixer.unpause() + + def stop(self, music = None, fade_out = 0, **kwargs): + if music is not None: + music.stop() + else: + pygame.mixer.stop() + + def stop_all_actions(self, **kwargs): + self.key.mapping.stop_all_running() + + def volume(self, music = None, value = 100, **kwargs): + pass + + def wait(self, duration = 0, **kwargs): + time.sleep(duration) + + def command_print(self, command = "", **kwargs): + return "running command {}".format(command) + + def pause_print(self, music = None, **kwargs): + if music is not None: + return "pausing {}".format(music.filename) + else: + return "pausing all musics" + + def play_print(self, music = None, fade_in = 0, start_at = 0, + restart_if_running = False, volume = 100, **kwargs): + message = "starting " + if music is not None: + message += music.filename + else: + message += "music" + + if start_at != 0: + message += " at {}s".format(start_at) + + if fade_in != 0: + message += " with {}s fade_in".format(fade_in) + + message += " at volume {}%".format(volume) + + if restart_if_running: + message += " (restarting if already running)" + + return message + + def stop_print(self, music = None, fade_out = 0, **kwargs): + if music is not None: + if fade_out == 0: + return "stopping music {}".format(music.filename) + else: + return "stopping music {} with {}s fadeout".format(music.filename, fade_out) + else: + if fade_out == 0: + return "stopping all musics" + else: + return "stopping all musics with {}s fadeout".format(fade_out) + + def stop_all_actions_print(self): + return "stopping all actions" + + def volume_print(self, music = None, value = 100, *kwargs): + if music is not None: + return "setting volume of {} to {}%".format(music.filename, value) + else: + return "setting volume to {}%".format(value) + + def wait_print(self, duration, **kwargs): + return "waiting {}s".format(duration) + + 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 @@ +from .rounded_rect import * +from .action import * +import time +import sys +import pygame + +class Key: + row_positions = { + 'first': 0, + 'second': 50, + 'third': 100, + 'fourth': 150, + 'fifth': 200, + 'sixth': 250, + } + + default_outer_color = (120, 120, 120) + lighter_outer_color = (200, 200, 200) + default_inner_color = (255, 255, 255) + mapped_inner_color = ( 0, 255, 0) + mapped_unready_inner_color = (255, 165, 0) + + def __init__(self, mapping, draw_lock, key_name, key_sym, top, left, width = 48, height = 48, disabled = False): + self.draw_lock = draw_lock + self.mapping = mapping + self.key_name = key_name + self.key_sym = key_sym + + if isinstance(top, str): + self.top = self.row_positions[top] + else: + self.top = top + + self.left = left + self.width = width + self.height = height + + self.bottom = self.top + self.height + self.right = self.left + self.width + + self.rect = (self.left, self.top, self.right, self.bottom) + self.position = (self.left, self.top) + + if disabled: + self.outer_color = self.lighter_outer_color + self.linewidth = 1 + else: + self.outer_color = self.default_outer_color + self.linewidth = 3 + + self.inner_color = self.default_inner_color + self.actions = [] + + def square(self, all_actions_ready): + if self.has_actions(): + if all_actions_ready: + self.inner_color = self.mapped_inner_color + else: + self.inner_color = self.mapped_unready_inner_color + + return RoundedRect((0, 0, self.width, self.height), + self.outer_color, self.inner_color, self.linewidth) + + def collidepoint(self, position): + return self.surface.get_rect().collidepoint( + position[0] - self.position[0], + position[1] - self.position[1] + ) + + def draw(self, background_surface): + self.draw_lock.acquire() + all_actions_ready = self.all_actions_ready() + + self.surface = self.square(all_actions_ready).surface() + + if getattr(sys, 'frozen', False): + police = pygame.font.Font(sys._MEIPASS + "/Ubuntu-Regular.ttf", 14) + else: + police = pygame.font.Font("Ubuntu-Regular.ttf", 14) + + text = police.render(self.key_sym, True, (0,0,0)) + self.surface.blit(text, (5,5)) + background_surface.blit(self.surface, self.position) + self.draw_lock.release() + + return not all_actions_ready + + def poll_redraw(self, background): + while True: + time.sleep(1) + if self.all_actions_ready(): + self.draw(background) + self.mapping.blit() + break + + def has_actions(self): + return len(self.actions) > 0 + + def all_actions_ready(self): + return all(action.ready() for action in self.actions) + + def add_action(self, action_name, **arguments): + self.actions.append(Action(action_name, self, **arguments)) + + def do_actions(self): + print("running actions for {}".format(self.key_sym)) + start_time = time.time() + self.mapping.start_running(self, start_time) + for action in self.actions: + if self.mapping.keep_running(self, start_time): + action.run() + + self.mapping.finished_running(self, start_time) + + def list_actions(self, surface): + # FIXME: Todo + print("bouh", self.key_sym) + surface.fill((255, 0, 0)) + + 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 @@ +import threading +import pygame +from .key import * + +class Mapping: + WIDTH = 903 + HEIGHT = 298 + SIZE = WIDTH, HEIGHT + + KEYS = [ + (pygame.K_ESCAPE, 'ESC', 'first', 0, {}), + + (pygame.K_F1, 'F1', 'first', 100, {}), + (pygame.K_F2, 'F2', 'first', 150, {}), + (pygame.K_F3, 'F3', 'first', 200, {}), + (pygame.K_F4, 'F4', 'first', 250, {}), + + (pygame.K_F5, 'F5', 'first', 325, {}), + (pygame.K_F6, 'F6', 'first', 375, {}), + (pygame.K_F7, 'F7', 'first', 425, {}), + (pygame.K_F8, 'F8', 'first', 475, {}), + + (pygame.K_F9, 'F9', 'first', 550, {}), + (pygame.K_F10, 'F10', 'first', 600, {}), + (pygame.K_F11, 'F11', 'first', 650, {}), + (pygame.K_F12, 'F12', 'first', 700, {}), + + + (178, '²', 'second', 0, {}), + (pygame.K_AMPERSAND, '&', 'second', 50, {}), + (233, 'é', 'second', 100, {}), + (pygame.K_QUOTEDBL, '"', 'second', 150, {}), + (pygame.K_QUOTE, "'", 'second', 200, {}), + (pygame.K_LEFTPAREN, '(', 'second', 250, {}), + (pygame.K_MINUS, '-', 'second', 300, {}), + (232, 'è', 'second', 350, {}), + (pygame.K_UNDERSCORE, '_', 'second', 400, {}), + (231, 'ç', 'second', 450, {}), + (224, 'à', 'second', 500, {}), + (pygame.K_RIGHTPAREN, ')', 'second', 550, {}), + (pygame.K_EQUALS, '=', 'second', 600, {}), + + (pygame.K_BACKSPACE, '<-', 'second', 650, { 'width': 98 }), + + + (pygame.K_TAB, 'tab', 'third', 0, { 'width' : 73 }), + (pygame.K_a, 'a', 'third', 75, {}), + (pygame.K_z, 'z', 'third', 125, {}), + (pygame.K_e, 'e', 'third', 175, {}), + (pygame.K_r, 'r', 'third', 225, {}), + (pygame.K_t, 't', 'third', 275, {}), + (pygame.K_y, 'y', 'third', 325, {}), + (pygame.K_u, 'u', 'third', 375, {}), + (pygame.K_i, 'i', 'third', 425, {}), + (pygame.K_o, 'o', 'third', 475, {}), + (pygame.K_p, 'p', 'third', 525, {}), + (pygame.K_CARET, '^', 'third', 575, {}), + (pygame.K_DOLLAR, '$', 'third', 625, {}), + + (pygame.K_RETURN, 'Enter', 'third', 692, { 'width': 56, 'height': 98 }), + + (pygame.K_CAPSLOCK, 'CAPS', 'fourth', 0, { 'width': 88, 'disabled': True }), + + (pygame.K_q, 'q', 'fourth', 90, {}), + (pygame.K_s, 's', 'fourth', 140, {}), + (pygame.K_d, 'd', 'fourth', 190, {}), + (pygame.K_f, 'f', 'fourth', 240, {}), + (pygame.K_g, 'g', 'fourth', 290, {}), + (pygame.K_h, 'h', 'fourth', 340, {}), + (pygame.K_j, 'j', 'fourth', 390, {}), + (pygame.K_k, 'k', 'fourth', 440, {}), + (pygame.K_l, 'l', 'fourth', 490, {}), + (pygame.K_m, 'm', 'fourth', 540, {}), + (249, 'ù', 'fourth', 590, {}), + (pygame.K_ASTERISK, '*', 'fourth', 640, {}), + + + (pygame.K_LSHIFT, 'LShift', 'fifth', 0, { 'width': 63, 'disabled': True }), + + (pygame.K_LESS, '<', 'fifth', 65, {}), + (pygame.K_w, 'w', 'fifth', 115, {}), + (pygame.K_x, 'x', 'fifth', 165, {}), + (pygame.K_c, 'c', 'fifth', 215, {}), + (pygame.K_v, 'v', 'fifth', 265, {}), + (pygame.K_b, 'b', 'fifth', 315, {}), + (pygame.K_n, 'n', 'fifth', 365, {}), + (pygame.K_COMMA, ',', 'fifth', 415, {}), + (pygame.K_SEMICOLON, ';', 'fifth', 465, {}), + (pygame.K_COLON, ':', 'fifth', 515, {}), + (pygame.K_EXCLAIM, '!', 'fifth', 565, {}), + + (pygame.K_RSHIFT, 'RShift', 'fifth', 615, { 'width': 133, 'disabled': True }), + + (pygame.K_LCTRL, 'LCtrl', 'sixth', 0, { 'width': 63, 'disabled': True }), + (pygame.K_LSUPER, 'LSuper', 'sixth', 115, { 'disabled': True }), + (pygame.K_LALT, 'LAlt', 'sixth', 165, { 'disabled': True }), + (pygame.K_SPACE, 'Espace', 'sixth', 215, { 'width': 248 }), + (pygame.K_MODE, 'AltGr', 'sixth', 465, { 'disabled': True }), + (314, 'Compose', 'sixth', 515, { 'disabled': True }), + (pygame.K_RCTRL, 'RCtrl', 'sixth', 565, { 'width': 63, 'disabled': True }), + + + (pygame.K_INSERT, 'ins', 'second', 755, {}), + (pygame.K_HOME, 'home', 'second', 805, {}), + (pygame.K_PAGEUP, 'pg_u', 'second', 855, {}), + (pygame.K_DELETE, 'del', 'third', 755, {}), + (pygame.K_END, 'end', 'third', 805, {}), + (pygame.K_PAGEDOWN, 'pg_d', 'third', 855, {}), + + + (pygame.K_UP, 'up', 'fifth', 805, {}), + (pygame.K_DOWN, 'down', 'sixth', 805, {}), + (pygame.K_LEFT, 'left', 'sixth', 755, {}), + (pygame.K_RIGHT, 'right', 'sixth', 855, {}), + ] + + def __init__(self, screen, draw_lock): + self.draw_lock = draw_lock + self.screen = screen + self.background = pygame.Surface(self.SIZE).convert() + self.background.fill((250, 250, 250)) + self.keys = {} + self.running = [] + for key in self.KEYS: + self.keys[key[0]] = Key(self, self.draw_lock, *key[0:4], **key[4]) + + def draw(self): + for key_name in self.keys: + key = self.keys[key_name] + should_redraw_key = key.draw(self.background) + + if should_redraw_key: + threading.Thread(target = key.poll_redraw, args = [self.background]).start() + self.blit() + + def blit(self): + self.draw_lock.acquire() + self.screen.blit(self.background, (5, 5)) + pygame.display.flip() + self.draw_lock.release() + + def find_by_key_num(self, key_num): + if key_num in self.keys: + return self.keys[key_num] + return None + + def find_by_collidepoint(self, position): + for key in self.keys: + if self.keys[key].collidepoint(position): + return self.keys[key] + return None + + def find_by_unicode(self, key_sym): + for key in self.keys: + if self.keys[key].key_sym == key_sym: + return self.keys[key] + return None + + def stop_all_running(self): + self.running = [] + + def start_running(self, key, start_time): + self.running.append((key, start_time)) + + def keep_running(self, key, start_time): + return (key, start_time) in self.running + + def finished_running(self, key, start_time): + if (key, start_time) in self.running: + self.running.remove((key, start_time)) + 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 @@ +import threading +import pydub +import pygame + +class MusicFile: + def __init__(self, filename, lock): + self.filename = filename + self.channel = None + self.raw_data = None + self.sound = None + + self.loaded = False + threading.Thread(target = self.load_sound, args = [lock]).start() + + def load_sound(self, lock): + lock.acquire() + print("Loading {}".format(self.filename)) + self.raw_data = pydub.AudioSegment.from_file(self.filename).raw_data + self.sound = pygame.mixer.Sound(self.raw_data) + print("Loaded {}".format(self.filename)) + self.loaded = True + lock.release() + + def play(self): + self.channel = self.sound.play() + + def pause(self): + if self.channel is not None: + self.channel.pause() + + def stop(self): + self.channel = None + self.sound.stop() + + 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 @@ +import pygame + +class RoundedRect: + def __init__(self, rect, outer_color, inner_color, linewidth = 2, radius = 0.4): + self.rect = pygame.Rect(rect) + self.outer_color = pygame.Color(*outer_color) + self.inner_color = pygame.Color(*inner_color) + self.linewidth = linewidth + self.radius = radius + + def surface(self): + rectangle = self.filledRoundedRect(self.rect, self.outer_color, self.radius) + + inner_rect = pygame.Rect(( + self.rect.left + 2 * self.linewidth, + self.rect.top + 2 * self.linewidth, + self.rect.right - 2 * self.linewidth, + self.rect.bottom - 2 * self.linewidth + )) + + inner_rectangle = self.filledRoundedRect(inner_rect, self.inner_color, self.radius) + + rectangle.blit(inner_rectangle, (self.linewidth, self.linewidth)) + + return rectangle + + def filledRoundedRect(self, rect, color, radius=0.4): + """ + filledRoundedRect(rect,color,radius=0.4) + + rect : rectangle + color : rgb or rgba + radius : 0 <= radius <= 1 + """ + + alpha = color.a + color.a = 0 + pos = rect.topleft + rect.topleft = 0,0 + rectangle = pygame.Surface(rect.size,pygame.SRCALPHA) + + circle = pygame.Surface([min(rect.size)*3]*2,pygame.SRCALPHA) + pygame.draw.ellipse(circle,(0,0,0),circle.get_rect(),0) + circle = pygame.transform.smoothscale(circle,[int(min(rect.size)*radius)]*2) + + radius = rectangle.blit(circle,(0,0)) + radius.bottomright = rect.bottomright + rectangle.blit(circle,radius) + radius.topright = rect.topright + rectangle.blit(circle,radius) + radius.bottomleft = rect.bottomleft + rectangle.blit(circle,radius) + + rectangle.fill((0,0,0),rect.inflate(-radius.w,0)) + rectangle.fill((0,0,0),rect.inflate(0,-radius.h)) + + rectangle.fill(color,special_flags=pygame.BLEND_RGBA_MAX) + rectangle.fill((255,255,255,alpha),special_flags=pygame.BLEND_RGBA_MIN) + + return rectangle + + 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 helpers.draw_lock.acquire() screen = pygame.display.set_mode(size) -mapping = helpers.Mapping(screen) +mapping = helpers.Mapping(screen, helpers.draw_lock) action_surface = pygame.Surface((600, 250)).convert() action_surface.fill((0,0,0)) @@ -38,6 +38,11 @@ while 1: event.type == pygame.KEYDOWN and event.mod == 4160 and event.key == pygame.K_c): + for thread in threading.enumerate(): + if thread is threading.current_thread(): + continue + thread.join() + pygame.quit() sys.exit() -- 2.41.0