From 4b2d79ca27dcbb85465829595ad81cec5fc63983 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Isma=C3=ABl=20Bouya?= Date: Sat, 25 Jun 2016 23:37:49 +0200 Subject: [PATCH] Migrate to kivy --- helpers/__init__.py | 149 --------------------- helpers/font.py | 10 -- helpers/key.py | 202 ++++++++++------------------- helpers/mapping.py | 281 +++++++++++++++++----------------------- helpers/rounded_rect.py | 62 --------- keyboard.py | 222 ------------------------------- music_sampler.py | 123 ++++++++---------- musicsampler.kv | 7 + 8 files changed, 243 insertions(+), 813 deletions(-) delete mode 100644 helpers/font.py delete mode 100644 helpers/rounded_rect.py delete mode 100644 keyboard.py diff --git a/helpers/__init__.py b/helpers/__init__.py index eb948f2..40a96af 100644 --- a/helpers/__init__.py +++ b/helpers/__init__.py @@ -1,150 +1 @@ # -*- coding: utf-8 -*- -from .music_file import * -from .mapping import * -from .lock import * -from .font import * -import yaml - -def parse_config2(): - stream = open("config.yml", "r") - config = yaml.load(stream) - stream.close() - - aliases = config['aliases'] - seen_files = {} - - file_lock = Lock("file") - - channel_id = 0 - - key_properties = {} - - for key in config['key_properties']: - if key not in key_properties: - key_properties[key] = { - "actions": [], - "properties": config['key_properties'][key], - "files": [] - } - - for mapped_key in config['keys']: - if mapped_key not in key_properties: - key_properties[mapped_key] = { - "actions": [], - "properties": {}, - "files": [] - } - for action in config['keys'][mapped_key]: - action_name = list(action)[0] - action_args = {} - 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 == 'file': - filename = action[action_name]['file'] - if filename not in seen_files: - if filename in config['music_properties']: - seen_files[filename] = MusicFile( - filename, - file_lock, - channel_id, - **config['music_properties'][filename]) - else: - seen_files[filename] = MusicFile( - filename, - file_lock, - channel_id) - channel_id = channel_id + 1 - - if filename not in key_properties[mapped_key]['files']: - key_properties[mapped_key]['files'].append(seen_files[filename]) - - action_args['music'] = seen_files[filename] - - else: - action_args[argument] = action[action_name][argument] - - key_properties[mapped_key]['actions'].append([action_name, action_args]) - - return (key_properties, channel_id + 1, seen_files) - -def parse_config(mapping): - stream = open("config.yml", "r") - config = yaml.load(stream) - stream.close() - - aliases = config['aliases'] - seen_files = {} - - file_lock = Lock("file") - - channel_id = 0 - - for mapped_key in config['keys']: - key = mapping.find_by_unicode(mapped_key) - if key is None: - continue - - for action in config['keys'][mapped_key]: - action_name = list(action)[0] - action_args = {} - 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 == 'file': - filename = action[action_name]['file'] - if filename not in seen_files: - if filename in config['music_properties']: - seen_files[filename] = MusicFile( - filename, - file_lock, - channel_id, - **config['music_properties'][filename]) - else: - seen_files[filename] = MusicFile( - filename, - file_lock, - channel_id) - channel_id = channel_id + 1 - - action_args['music'] = seen_files[filename] - - else: - action_args[argument] = action[action_name][argument] - - key.add_action(action_name, **action_args) - - for key_property in config['key_properties']: - key = mapping.find_by_unicode(key_property) - if key is None: - continue - - if 'description' in config['key_properties'][key_property]: - key.set_description(config['key_properties'][key_property]['description']) - if 'color' in config['key_properties'][key_property]: - key.set_color(config['key_properties'][key_property]['color']) - - # Return the number of channels reserved - return (channel_id + 1, seen_files) diff --git a/helpers/font.py b/helpers/font.py deleted file mode 100644 index 83999ef..0000000 --- a/helpers/font.py +++ /dev/null @@ -1,10 +0,0 @@ -import os -import pygame -import sys - -def font(size, font = "Ubuntu-Regular"): - if getattr(sys, 'frozen', False): - return pygame.font.Font(sys._MEIPASS + "/fonts/" + font + ".ttf", size) - else: - path = os.path.dirname(os.path.realpath(__file__)) - return pygame.font.Font(path + "/../fonts/" + font + ".ttf", size) diff --git a/helpers/key.py b/helpers/key.py index d923b1d..658c17f 100644 --- a/helpers/key.py +++ b/helpers/key.py @@ -1,169 +1,101 @@ -from .rounded_rect import * +from kivy.uix.widget import Widget +from kivy.properties import AliasProperty, BooleanProperty, ListProperty, StringProperty +from kivy.clock import Clock +from kivy.uix.behaviors import ButtonBehavior + from .action import * -from .font import font import time -import sys -import pygame - -class Key: - 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 = ( 0, 255, 0, 100) - - 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 - - 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) - self.disabled = disabled - - if disabled: - self.outer_color = self.lighter_outer_color - self.linewidth = 1 + +class Key(ButtonBehavior, Widget): + key_sym = StringProperty(None) + custom_color = ListProperty([0, 1, 0, 1]) + custom_unready_color = ListProperty([0, 1, 0, 100/255]) + description_title = StringProperty("") + description = ListProperty([]) + is_key_ready = BooleanProperty(True) + + def get_color(self): + if not self.has_actions: + return [1, 1, 1, 1] + elif self.all_actions_ready: + return self.custom_color else: - self.outer_color = self.default_outer_color - self.linewidth = 3 + return self.custom_unready_color + def set_color(self): + pass + + color = AliasProperty(get_color, set_color, bind=['is_key_ready']) - self.inner_color = self.default_inner_color + def __init__(self, **kwargs): + super(Key, self).__init__(**kwargs) self.actions = [] - self.description = [] - self.custom_color = self.mapped_inner_color - self.custom_unready_color = self.mapped_unready_inner_color - - def square(self, all_actions_ready): - if self.has_actions(): - if all_actions_ready: - self.inner_color = self.custom_color - else: - self.inner_color = self.custom_unready_color - return RoundedRect((0, 0, self.width, self.height), - self.outer_color, self.inner_color, self.linewidth) + def on_key_sym(self, key, key_sym): + if key_sym in self.parent.key_config: + self.is_key_ready = False + + self.config = self.parent.key_config[key_sym] - def collidepoint(self, position): - return self.surface.get_rect().collidepoint( - position[0] - self.position[0], - position[1] - self.position[1] - ) + self.actions = [] + for key_action in self.config['actions']: + self.add_action(key_action[0], **key_action[1]) + + if 'description' in self.config['properties']: + key.set_description(self.config['properties']['description']) + if 'color' in self.config['properties']: + key.set_color(self.config['properties']['color']) + + Clock.schedule_interval(self.check_all_active, 1) + + def check_all_active(self, dt): + if self.all_actions_ready: + self.is_key_ready = True + return False def set_description(self, description): - for desc in description: + if description[0] is not None: + self.description_title = str(description[0]) + for desc in description[1:]: if desc is None: self.description.append("") else: - self.description.append(str(desc)) + self.description.append(str(desc).replace(" ", " ")) def set_color(self, color): - self.custom_color = tuple(color) - color.append(100) + color = [x / 255 for x in color] + color.append(1) + self.custom_color = color + color[3] = 100 / 255 self.custom_unready_color = tuple(color) - def draw(self, background_surface): - self.draw_lock.acquire() - all_actions_ready = self.all_actions_ready() - - self.surface = self.square(all_actions_ready).surface() - - police = font(14) - text_police = font(10) - - police.set_bold(True) - text = police.render(self.key_sym, True, (0,0,0)) - self.surface.blit(text, (5,5)) - - is_first_line = True - offset = 11 + text_police.get_linesize() - 4 - first_line_offset = 18 - for description in self.description: - text = text_police.render(description, True, (0,0,0)) - if is_first_line: - self.surface.blit(text, (first_line_offset, 9)) - is_first_line = False - else: - self.surface.blit(text, (3, offset)) - offset += text_police.get_linesize() - 4 - - 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 - + @property def has_actions(self): return len(self.actions) > 0 + @property 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, screen): + def do_actions(self): print("running actions for {}".format(self.key_sym)) start_time = time.time() - self.mapping.start_running(self, start_time) + self.parent.start_running(self, start_time) action_number = 0 for action in self.actions: - if self.mapping.keep_running(self, start_time): - self.list_actions(screen, action_number = action_number + 0.5) + if self.parent.keep_running(self, start_time): + self.list_actions(action_number = action_number + 0.5) action.run() action_number += 1 - self.list_actions(screen, action_number = action_number) - - self.mapping.finished_running(self, start_time) - - def list_actions(self, screen, action_number = 0): - action_descriptions = [action.description() for action in self.actions] - #print("actions linked to key {}:".format(self.key_sym)) - #print("\t" + "\n\t".join(action_descriptions)) - self.draw_lock.acquire() - surface = pygame.Surface((690, 250)).convert() - surface.fill((250, 250, 250)) - police = font(14) - - offset = 0 - police.set_bold(True) - text = police.render("actions linked to key {}:".format(self.key_sym), True, (0,0,0)) - surface.blit(text, (0, offset)) - offset += police.get_linesize() - - police.set_bold(False) - icon_police = font(14, font = "Symbola") - for index, description in enumerate(action_descriptions): - if index < int(action_number): - icon = icon_police.render("✓", True, (0,0,0)) - elif index + 0.5 == action_number: - icon = icon_police.render("✅", True, (0,0,0)) - else: - icon = icon_police.render(" ", True, (0,0,0)) - - text = police.render(description, True, (0,0,0)) - surface.blit(icon, (0, offset)) - surface.blit(text, (10, offset)) - offset += police.get_linesize() + self.list_actions(action_number = action_number) - screen.blit(surface, (5, 308)) - pygame.display.flip() - self.draw_lock.release() + self.parent.finished_running(self, start_time) + def list_actions(self, action_number = 0): + self.parent.parent.ids['ActionList'].update_list(self, action_number) + def on_press(self): + self.list_actions() + pass diff --git a/helpers/mapping.py b/helpers/mapping.py index 58aaf3d..4a1cd98 100644 --- a/helpers/mapping.py +++ b/helpers/mapping.py @@ -1,175 +1,51 @@ +from kivy.uix.relativelayout import RelativeLayout +from kivy.properties import NumericProperty +from kivy.core.window import Window + import threading import pygame -from .key import * - -class Mapping: - WIDTH = 903 - HEIGHT = 298 - SIZE = WIDTH, HEIGHT - - ROW_POSITIONS = { - 'first': 0, - 'second': 50, - 'third': 100, - 'fourth': 150, - 'fifth': 200, - 'sixth': 250, - } - - 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 = {} +import yaml + +from .lock import * +from .music_file import * + +class Mapping(RelativeLayout): + expected_keys = NumericProperty(0) + + def __init__(self, **kwargs): + self.key_config, self.channel_number, self.open_files = self.parse_config() + super(Mapping, self).__init__(**kwargs) + self._keyboard = Window.request_keyboard(self._keyboard_closed, self) + self._keyboard.bind(on_key_down=self._on_keyboard_down) self.running = [] - for key in self.KEYS: - if key[2] in self.ROW_POSITIONS: - position = self.ROW_POSITIONS[key[2]] - else: - position = key[2] - self.keys[key[0]] = Key(self, - self.draw_lock, - key[0], key[1], position, key[3], - **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(name = "MSPollRedraw", 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] + + pygame.mixer.init(frequency = 44100) + pygame.mixer.set_num_channels(self.channel_number) + + def _keyboard_closed(self): + self._keyboard.unbind(on_key_down=self._on_keyboard_down) + self._keyboard = None + + def _on_keyboard_down(self, keyboard, keycode, text, modifiers): + key = self.find_by_key_code(keycode) + if key is not None: + threading.Thread(name = "MSKeyAction", target=key.do_actions).start() + return True + + def find_by_key_code(self, key_code): + if "Key_" + str(key_code[0]) in self.ids: + return self.ids["Key_" + str(key_code[0])] 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] + for key in self.children: + if not type(key).__name__ == "Key": + continue + print(key.key_sym, key_sym) + if key.key_sym == key_sym: + print("found") + return key return None def stop_all_running(self): @@ -185,3 +61,78 @@ class Mapping: if (key, start_time) in self.running: self.running.remove((key, start_time)) + def parse_config(self): + stream = open("config.yml", "r") + config = yaml.load(stream) + stream.close() + + aliases = config['aliases'] + seen_files = {} + + file_lock = Lock("file") + + channel_id = 0 + + key_properties = {} + + for key in config['key_properties']: + if key not in key_properties: + key_properties[key] = { + "actions": [], + "properties": config['key_properties'][key], + "files": [] + } + + for mapped_key in config['keys']: + if mapped_key not in key_properties: + key_properties[mapped_key] = { + "actions": [], + "properties": {}, + "files": [] + } + for action in config['keys'][mapped_key]: + action_name = list(action)[0] + action_args = {} + 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 == 'file': + filename = action[action_name]['file'] + if filename not in seen_files: + if filename in config['music_properties']: + seen_files[filename] = MusicFile( + filename, + file_lock, + channel_id, + **config['music_properties'][filename]) + else: + seen_files[filename] = MusicFile( + filename, + file_lock, + channel_id) + channel_id = channel_id + 1 + + if filename not in key_properties[mapped_key]['files']: + key_properties[mapped_key]['files'].append(seen_files[filename]) + + action_args['music'] = seen_files[filename] + + else: + action_args[argument] = action[action_name][argument] + + key_properties[mapped_key]['actions'].append([action_name, action_args]) + + return (key_properties, channel_id + 1, seen_files) + + diff --git a/helpers/rounded_rect.py b/helpers/rounded_rect.py deleted file mode 100644 index f168e0e..0000000 --- a/helpers/rounded_rect.py +++ /dev/null @@ -1,62 +0,0 @@ -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/keyboard.py b/keyboard.py deleted file mode 100644 index 0c1115f..0000000 --- a/keyboard.py +++ /dev/null @@ -1,222 +0,0 @@ -from kivy.app import App -from kivy.uix.widget import Widget -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.relativelayout import RelativeLayout -from kivy.properties import AliasProperty, ReferenceListProperty, BooleanProperty, NumericProperty, ListProperty, StringProperty, ObjectProperty -from kivy.vector import Vector -from kivy.clock import Clock -from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.label import Label -from kivy.core.window import Window - -from helpers.action import * -import time -import sys -import math -import pygame -import helpers -import threading - -class KeyDescription(Label): - pass - -class Key(ButtonBehavior, Widget): - key_sym = StringProperty(None) - custom_color = ListProperty([0, 1, 0, 1]) - custom_unready_color = ListProperty([0, 1, 0, 100/255]) - description_title = StringProperty("") - description = ListProperty([]) - is_key_ready = BooleanProperty(True) - - def get_color(self): - if not self.has_actions: - return [1, 1, 1, 1] - elif self.all_actions_ready: - return self.custom_color - else: - return self.custom_unready_color - def set_color(self): - pass - - color = AliasProperty(get_color, set_color, bind=['is_key_ready']) - - def __init__(self, **kwargs): - super(Key, self).__init__(**kwargs) - self.actions = [] - - def on_key_sym(self, key, key_sym): - if key_sym in self.parent.key_config: - self.is_key_ready = False - - self.config = self.parent.key_config[key_sym] - - self.actions = [] - for key_action in self.config['actions']: - self.add_action(key_action[0], **key_action[1]) - - if 'description' in self.config['properties']: - key.set_description(self.config['properties']['description']) - if 'color' in self.config['properties']: - key.set_color(self.config['properties']['color']) - - Clock.schedule_interval(self.check_all_active, 1) - - def check_all_active(self, dt): - if self.all_actions_ready: - self.is_key_ready = True - return False - - def set_description(self, description): - if description[0] is not None: - self.description_title = str(description[0]) - for desc in description[1:]: - if desc is None: - self.description.append("") - else: - self.description.append(str(desc).replace(" ", " ")) - - def set_color(self, color): - color = [x / 255 for x in color] - color.append(1) - self.custom_color = color - color[3] = 100 / 255 - self.custom_unready_color = tuple(color) - - @property - def has_actions(self): - return len(self.actions) > 0 - - @property - 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.parent.start_running(self, start_time) - action_number = 0 - for action in self.actions: - if self.parent.keep_running(self, start_time): - self.list_actions(action_number = action_number + 0.5) - action.run() - action_number += 1 - self.list_actions(action_number = action_number) - - self.parent.finished_running(self, start_time) - - def list_actions(self, action_number = 0): - self.parent.parent.ids['ActionList'].update_list(self, action_number) - - def on_press(self): - self.list_actions() - pass - -class PlayList(RelativeLayout): - playlist = ListProperty([]) - - def __init__(self, **kwargs): - super(PlayList, self).__init__(**kwargs) - Clock.schedule_interval(self.update_playlist, 0.5) - - def update_playlist(self, dt): - if self.parent is None or 'Mapping' not in self.parent.ids: - return True - - open_files = self.parent.ids['Mapping'].open_files - self.playlist = [] - for music_file in open_files.values(): - if not music_file.is_playing(): - continue - if music_file.is_paused(): - self.playlist.append(["⏸", music_file.name, False]) - else: - self.playlist.append(["⏵", music_file.name, True]) - - -class ActionList(RelativeLayout): - action_title = StringProperty("") - action_list = ListProperty([]) - - def update_list(self, key, action_number = 0): - self.action_title = "actions linked to key {}:".format(key.key_sym) - self.action_list = [] - - action_descriptions = [action.description() for action in key.actions] - - for index, description in enumerate(action_descriptions): - if index < int(action_number): - icon = "✓" - elif index + 0.5 == action_number: - icon = "✅" - else: - icon = " " - - self.action_list.append([icon, description]) - -class Mapping(RelativeLayout): - expected_keys = NumericProperty(0) - - def __init__(self, **kwargs): - self.key_config, self.channel_number, self.open_files = helpers.parse_config2() - super(Mapping, self).__init__(**kwargs) - self._keyboard = Window.request_keyboard(self._keyboard_closed, self) - self._keyboard.bind(on_key_down=self._on_keyboard_down) - self.running = [] - - - pygame.mixer.init(frequency = 44100) - pygame.mixer.set_num_channels(self.channel_number) - - def _keyboard_closed(self): - self._keyboard.unbind(on_key_down=self._on_keyboard_down) - self._keyboard = None - - def _on_keyboard_down(self, keyboard, keycode, text, modifiers): - key = self.find_by_key_code(keycode) - if key is not None: - threading.Thread(name = "MSKeyAction", target=key.do_actions).start() - return True - - def find_by_key_code(self, key_code): - if "Key_" + str(key_code[0]) in self.ids: - return self.ids["Key_" + str(key_code[0])] - return None - - def find_by_unicode(self, key_sym): - for key in self.children: - if not type(key).__name__ == "Key": - continue - print(key.key_sym, key_sym) - if key.key_sym == key_sym: - print("found") - return 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 Screen(FloatLayout): - pass - -class MusicSamplerApp(App): - def build(self): - Window.size = (913, 563) - - return Screen() - -if __name__ == '__main__': - MusicSamplerApp().run() diff --git a/music_sampler.py b/music_sampler.py index 42c01e3..0d9a7a9 100644 --- a/music_sampler.py +++ b/music_sampler.py @@ -1,80 +1,63 @@ -import sys -import pygame -import helpers -import threading - -pygame.mixer.pre_init(frequency = 44100) -pygame.init() - -size = width, height = 913, 563 -screen = pygame.display.set_mode(size) -screen.fill((229, 228, 226)) - -draw_lock = helpers.Lock("draw") - -mapping = helpers.Mapping(screen, draw_lock) -channel_number, open_files = helpers.parse_config(mapping) -pygame.mixer.set_num_channels(channel_number) - -mapping.draw() +from kivy.app import App +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.relativelayout import RelativeLayout +from kivy.properties import ListProperty, StringProperty +from kivy.clock import Clock +from kivy.core.window import Window + +from helpers.key import Key +from helpers.mapping import Mapping + +class PlayList(RelativeLayout): + playlist = ListProperty([]) + + def __init__(self, **kwargs): + super(PlayList, self).__init__(**kwargs) + Clock.schedule_interval(self.update_playlist, 0.5) + + def update_playlist(self, dt): + if self.parent is None or 'Mapping' not in self.parent.ids: + return True + + open_files = self.parent.ids['Mapping'].open_files + self.playlist = [] + for music_file in open_files.values(): + if not music_file.is_playing(): + continue + if music_file.is_paused(): + self.playlist.append(["⏸", music_file.name, False]) + else: + self.playlist.append(["⏵", music_file.name, True]) -draw_lock.acquire() -pygame.display.flip() -draw_lock.release() -contexts = [ - 'normal' -] +class ActionList(RelativeLayout): + action_title = StringProperty("") + action_list = ListProperty([]) -context = 'normal' + def update_list(self, key, action_number = 0): + self.action_title = "actions linked to key {}:".format(key.key_sym) + self.action_list = [] -#### Normal workflow #### -while 1: - event = pygame.event.wait() + action_descriptions = [action.description() for action in key.actions] - if event.type == pygame.QUIT or ( - event.type == pygame.KEYDOWN and - event.mod == 4160 and - event.key == pygame.K_c): - for thread in threading.enumerate(): - if thread.getName()[0:2] != "MS": - continue - thread.join() + for index, description in enumerate(action_descriptions): + if index < int(action_number): + icon = "✓" + elif index + 0.5 == action_number: + icon = "✅" + else: + icon = " " - pygame.quit() - sys.exit() + self.action_list.append([icon, description]) - if context == 'normal': - if event.type == pygame.KEYDOWN: - key = mapping.find_by_key_num(event.key) - if key is not None and not key.disabled: - threading.Thread(name = "MSKeyAction", target=key.do_actions, args = [screen]).start() - threading.Thread(name = "MSClic", target=key.list_actions, args = [screen]).start() - elif event.type == pygame.MOUSEBUTTONUP: - key = mapping.find_by_collidepoint(pygame.mouse.get_pos()) - if key is not None: - threading.Thread(name = "MSClic", target=key.list_actions, args = [screen]).start() +class Screen(FloatLayout): + pass - draw_lock.acquire() - police = helpers.font(14) - icon_police = helpers.font(14, font = "Symbola") +class MusicSamplerApp(App): + def build(self): + Window.size = (913, 563) - surface = pygame.Surface((208, 250)).convert() - surface.fill((250, 250, 250)) - offset = 0 - for music_file in open_files.values(): - police.set_bold(False) - if music_file.is_playing(): - if music_file.is_paused(): - icon = icon_police.render("⏸", True, (0,0,0)) - else: - icon = icon_police.render("⏵", True, (0,0,0)) - police.set_bold(True) - text = police.render(music_file.name, True, (0,0,0)) - surface.blit(icon, (0, offset)) - surface.blit(text, (20, offset)) - offset += police.get_linesize() - screen.blit(surface, (700, 308)) + return Screen() - pygame.display.flip() - draw_lock.release() +if __name__ == '__main__': + MusicSamplerApp().run() diff --git a/musicsampler.kv b/musicsampler.kv index 3c1964b..17be1f5 100644 --- a/musicsampler.kv +++ b/musicsampler.kv @@ -67,6 +67,13 @@ size: 2 * self.parent.width - 2 * self.parent.line_width, self.parent.height - key_label.font_size : + canvas: + Color: + rgba: 229/255, 228/255, 226/255, 1 + Rectangle: + pos: 0, 0 + size: self.width, self.height + key_size: int( (3 * self.width - 16) / 56) key_sep: int( self.key_size / 24) key_pad_sep: int( self.key_size / 7) + 1 -- 2.41.0