From e7f8dab4980d8a477f305e3565ca3c80abd7d790 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Isma=C3=ABl=20Bouya?= Date: Sat, 18 Jun 2016 00:29:15 +0200 Subject: [PATCH] Modifications to mapping/keys --- config.yml | 14 +- helpers/__init__.py | 495 +++++++++++++++++++++++++++++--------------- music_sampler.py | 24 +-- 3 files changed, 346 insertions(+), 187 deletions(-) diff --git a/config.yml b/config.yml index 1384c92..11251b2 100644 --- a/config.yml +++ b/config.yml @@ -1,16 +1,11 @@ aliases: jongle: - file: "/home/sekhmet/bla/jongle.mp3" + file: "/home/immae/temp/Pirates/PIRATE_01-1-intro_jonglacro.mp3" volume: 110 # pouvoir dire que "par défaut" la musique a un volume à 110% acros: - file: "/home/sekhmet/chemin/ultra/chiant/acros.mp3" + file: "../../temp/Pirates/PIRATE_01-2-jonglacro_suite.mp3" keys: - 'bla': - - unknown: - include: "bla" - - play: - include: "coucou" 'a': - play: include: jongle @@ -20,16 +15,17 @@ keys: - stop: include: jongle - wait: - time: 3 + duration: 3 - play: include: acros - wait: - time: 10 + duration: 10 - stop: include: acros fade_out: 10 'ESC': - stop: ~ + - stop_all_actions: ~ '-': - volume: value: 90 diff --git a/helpers/__init__.py b/helpers/__init__.py index 7dbffdd..ec7da22 100644 --- a/helpers/__init__.py +++ b/helpers/__init__.py @@ -1,6 +1,11 @@ # -*- coding: utf-8 -*- from pygame import * +import pydub import sys +import time +import threading + +draw_lock = threading.RLock() class Action: action_types = [ @@ -8,6 +13,7 @@ class Action: 'pause', 'play', 'stop', + 'stop_all_actions', 'volume', 'wait', ] @@ -20,49 +26,120 @@ class Action: self.arguments = kwargs - def run(self, callback): - getattr(self, self.action)(callback, **self.arguments) + def ready(self): + if 'music' in self.arguments: + return self.arguments['music'].loaded + else: + return True - def command(self, callback, command = "", **kwargs): - pass + def run(self): + print(getattr(self, self.action + "_print")(**self.arguments)) + return getattr(self, self.action)(**self.arguments) - def pause(self, callback, music = None, **kwargs): + def command(self, command = "", **kwargs): pass - def play(self, callback, - music = None, - fade_in = 0, - restart_if_running = False, - volume = 100, - **kwargs): - pass + def pause(self, music = None, **kwargs): + if music is not None: + music.pause() + else: + mixer.pause() - def stop(self, callback, music = None, fade_out = 0, **kwargs): - print('stopping') - return callback() + 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 volume(self, callback, music = None, value = 100, **kwargs): - pass + 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): + Key.running = [] - def wait(self, callback, time = 0, **kwargs): + 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': 5, - 'second': 55, - 'third': 105, - 'fourth': 155, - 'fifth': 205, - 'sixth': 255, + '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) + running = [] - def __init__(self, key_name, key_sym, top, left, width = 48, height = 48, disabled = False): + 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 @@ -91,9 +168,12 @@ class Key: self.inner_color = self.default_inner_color self.actions = [] - def square(self): - if self.has_action(): - self.inner_color = self.mapped_inner_color + 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) @@ -105,7 +185,10 @@ class Key: ) def draw(self, background_surface): - self.surface = self.square().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) @@ -115,154 +198,229 @@ class Key: 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 has_action(self): + 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, **arguments)) - def next_action(self): - print("running next action") - def do_actions(self): - self.current_action = 0 print("running actions for {}".format(self.key_sym)) - if len(self.actions) > 0: - self.actions[0].run(self.next_action) + Key.running.append(self) + for action in self.actions: + #FIXME: si on stop_all_actions et qu'on relance, "self" est de + #nouveau dans Key.running + if self in Key.running: + action.run() + + if self in Key.running: + Key.running.remove(self) def list_actions(self, surface): + # FIXME: Todo print("bouh", self.key_sym) surface.fill((255, 0, 0)) - def find_by_key_num(key_num): - if key_num in Mapping.KEYS: - return Mapping.KEYS[key_num] + +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 = {} + 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(position): - for key in Mapping.KEYS: - if Mapping.KEYS[key].collidepoint(position): - return Mapping.KEYS[key] + 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(key_sym): - for key in Mapping.KEYS: - if Mapping.KEYS[key].key_sym == key_sym: - return Mapping.KEYS[key] + 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 -class Mapping: - KEYS = { - K_ESCAPE: Key(K_ESCAPE, 'ESC', 'first', 0), - - K_F1: Key(K_F1, 'F1', 'first', 100), - K_F2: Key(K_F2, 'F2', 'first', 150), - K_F3: Key(K_F3, 'F3', 'first', 200), - K_F4: Key(K_F4, 'F4', 'first', 250), - - K_F5: Key(K_F5, 'F5', 'first', 325), - K_F6: Key(K_F6, 'F6', 'first', 375), - K_F7: Key(K_F7, 'F7', 'first', 425), - K_F8: Key(K_F8, 'F8', 'first', 475), - - K_F9: Key(K_F9, 'F9', 'first', 550), - K_F10: Key(K_F10, 'F10', 'first', 600), - K_F11: Key(K_F11, 'F11', 'first', 650), - K_F12: Key(K_F12, 'F12', 'first', 700), - - - 178: Key(178, '²', 'second', 0), - K_AMPERSAND: Key(K_AMPERSAND, '&', 'second', 50), - 233: Key(233, 'é', 'second', 100), - K_QUOTEDBL: Key(K_QUOTEDBL, '"', 'second', 150), - K_QUOTE: Key(K_QUOTE, "'", 'second', 200), - K_LEFTPAREN: Key(K_LEFTPAREN, '(', 'second', 250), - K_MINUS: Key(K_MINUS, '-', 'second', 300), - 232: Key(232, 'è', 'second', 350), - K_UNDERSCORE: Key(K_UNDERSCORE, '_', 'second', 400), - 231: Key(231, 'ç', 'second', 450), - 224: Key(224, 'à', 'second', 500), - K_RIGHTPAREN: Key(K_RIGHTPAREN, ')', 'second', 550), - K_EQUALS: Key(K_EQUALS, '=', 'second', 600), - - K_BACKSPACE: Key(K_BACKSPACE, '<-', 'second', 650, width = 98), - - - K_TAB: Key(K_TAB, 'tab', 'third', 0, width = 73), - K_a: Key(K_a, 'a', 'third', 75), - K_z: Key(K_z, 'z', 'third', 125), - K_e: Key(K_e, 'e', 'third', 175), - K_r: Key(K_r, 'r', 'third', 225), - K_t: Key(K_t, 't', 'third', 275), - K_y: Key(K_y, 'y', 'third', 325), - K_u: Key(K_u, 'u', 'third', 375), - K_i: Key(K_i, 'i', 'third', 425), - K_o: Key(K_o, 'o', 'third', 475), - K_p: Key(K_p, 'p', 'third', 525), - K_CARET: Key(K_CARET, '^', 'third', 575), - K_DOLLAR: Key(K_DOLLAR, '$', 'third', 625), - - K_RETURN: Key(K_RETURN, 'Enter', 'third', 692, width = 56, height = 98), - - K_CAPSLOCK: Key(K_CAPSLOCK, 'CAPS', 'fourth', 0, width = 88, disabled = True), - - K_q: Key(K_q, 'q', 'fourth', 90), - K_s: Key(K_s, 's', 'fourth', 140), - K_d: Key(K_d, 'd', 'fourth', 190), - K_f: Key(K_f, 'f', 'fourth', 240), - K_g: Key(K_g, 'g', 'fourth', 290), - K_h: Key(K_h, 'h', 'fourth', 340), - K_j: Key(K_j, 'j', 'fourth', 390), - K_k: Key(K_k, 'k', 'fourth', 440), - K_l: Key(K_l, 'l', 'fourth', 490), - K_m: Key(K_m, 'm', 'fourth', 540), - 249: Key(249, 'ù', 'fourth', 590), - K_ASTERISK: Key(K_ASTERISK, '*', 'fourth', 640), - - - K_LSHIFT: Key(K_LSHIFT, 'LShift', 'fifth', 0, width = 63, disabled = True), - - K_LESS: Key(K_LESS, '<', 'fifth', 65), - K_w: Key(K_w, 'w', 'fifth', 115), - K_x: Key(K_x, 'x', 'fifth', 165), - K_c: Key(K_c, 'c', 'fifth', 215), - K_v: Key(K_v, 'v', 'fifth', 265), - K_b: Key(K_b, 'b', 'fifth', 315), - K_n: Key(K_n, 'n', 'fifth', 365), - K_COMMA: Key(K_COMMA, ',', 'fifth', 415), - K_SEMICOLON: Key(K_SEMICOLON, ';', 'fifth', 465), - K_COLON: Key(K_COLON, ':', 'fifth', 515), - K_EXCLAIM: Key(K_EXCLAIM, '!', 'fifth', 565), - - K_RSHIFT: Key(K_RSHIFT, 'RShift', 'fifth', 615, width = 133, disabled = True), - - K_LCTRL: Key(K_LCTRL, 'LCtrl', 'sixth', 0, width = 63, disabled = True), - K_LSUPER: Key(K_LSUPER, 'LSuper', 'sixth', 115, disabled = True), - K_LALT: Key(K_LALT, 'LAlt', 'sixth', 165, disabled = True), - K_SPACE: Key(K_SPACE, 'Espace', 'sixth', 215, width = 248), - K_MODE: Key(K_MODE, 'AltGr', 'sixth', 465, disabled = True), - 314: Key(314, 'Compose', 'sixth', 515, disabled = True), - K_RCTRL: Key(K_RCTRL, 'RCtrl', 'sixth', 565, width = 63, disabled = True), - - - K_INSERT: Key(K_INSERT, 'ins', 'second', 755), - K_HOME: Key(K_HOME, 'home', 'second', 805), - K_PAGEUP: Key(K_PAGEUP, 'pg_u', 'second', 855), - K_DELETE: Key(K_DELETE, 'del', 'third', 755), - K_END: Key(K_END, 'end', 'third', 805), - K_PAGEDOWN: Key(K_PAGEDOWN, 'pg_d', 'third', 855), - - - K_UP: Key(K_UP, 'up', 'fifth', 805), - K_DOWN: Key(K_DOWN, 'down', 'sixth', 805), - K_LEFT: Key(K_LEFT, 'left', 'sixth', 755), - K_RIGHT: Key(K_RIGHT, 'right', 'sixth', 855), - } + class MusicFile: - def __init__(self, filename): + 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): @@ -324,7 +482,7 @@ class RoundedRect: return rectangle -def parse_config(): +def parse_config(mapping): import yaml stream = open("config.yml", "r") config = yaml.load(stream) @@ -333,8 +491,10 @@ def parse_config(): aliases = config['aliases'] seen_files = {} + file_lock = threading.RLock() + for mapped_key in config['keys']: - key = Key.find_by_unicode(mapped_key) + key = mapping.find_by_unicode(mapped_key) if key is None: continue @@ -344,18 +504,21 @@ def parse_config(): if action[action_name] is None: action[action_name] = [] + if 'include' in action[action_name]: + included = action[action_name]['include'] + del(action[action_name]['include']) + + if isinstance(included, str): + action[action_name].update(aliases[included], **action[action_name]) + else: + for included_ in included: + action[action_name].update(aliases[included_], **action[action_name]) + for argument in action[action_name]: - if argument == 'include': - included = action[action_name]['include'] - if isinstance(included, str): - action_args.update(aliases[included]) - else: - for included_ in included: - action_args.update(aliases[included_]) - elif argument == 'file': + if argument == 'file': filename = action[action_name]['file'] if filename not in seen_files: - seen_files[filename] = MusicFile.new(filename) + seen_files[filename] = MusicFile(filename, file_lock) action_args['music'] = seen_files[filename] diff --git a/music_sampler.py b/music_sampler.py index 7933d50..6cc49df 100644 --- a/music_sampler.py +++ b/music_sampler.py @@ -1,30 +1,29 @@ import sys import pygame -import pydub import helpers +import threading pygame.mixer.pre_init(frequency = 44100) pygame.init() size = width, height = 1024, 600 +helpers.draw_lock.acquire() screen = pygame.display.set_mode(size) -background = pygame.Surface(screen.get_size()) -background = background.convert() -background.fill((250, 250, 250)) +mapping = helpers.Mapping(screen) action_surface = pygame.Surface((600, 250)).convert() action_surface.fill((0,0,0)) -helpers.parse_config() +helpers.parse_config(mapping) +helpers.draw_lock.release() -for key_name in helpers.Mapping.KEYS: - key = helpers.Mapping.KEYS[key_name] - key.draw(background) +mapping.draw() -screen.blit(background, (0, 0)) +helpers.draw_lock.acquire() screen.blit(action_surface, (10, 330)) pygame.display.flip() +helpers.draw_lock.release() contexts = [ 'normal' @@ -34,6 +33,7 @@ context = 'normal' while 1: event = pygame.event.wait() + if event.type == pygame.QUIT or ( event.type == pygame.KEYDOWN and event.mod == 4160 and @@ -43,11 +43,11 @@ while 1: if context == 'normal': if event.type == pygame.KEYDOWN: - key = helpers.Key.find_by_key_num(event.key) + key = mapping.find_by_key_num(event.key) if key is not None: - key.do_actions() + threading.Thread(target=key.do_actions).start() elif event.type == pygame.MOUSEBUTTONUP: - key = helpers.Key.find_by_collidepoint(pygame.mouse.get_pos()) + key = mapping.find_by_collidepoint(pygame.mouse.get_pos()) if key is not None: key.list_actions(action_surface) -- 2.41.0