]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/commitdiff
Migrate to kivy
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Sat, 25 Jun 2016 21:37:49 +0000 (23:37 +0200)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Sat, 25 Jun 2016 21:40:51 +0000 (23:40 +0200)
helpers/__init__.py
helpers/font.py [deleted file]
helpers/key.py
helpers/mapping.py
helpers/rounded_rect.py [deleted file]
keyboard.py [deleted file]
music_sampler.py
musicsampler.kv

index eb948f25ae5aa86b01f8b666624bcf39b4787d1c..40a96afc6ff09d58a702b76e3f7dd412fe975e26 100644 (file)
@@ -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 (file)
index 83999ef..0000000
+++ /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)
index d923b1dcde7a7d0bbd51ce0b344a24b6d2da1882..658c17f485e197d80d089737124f8ec25731a4bb 100644 (file)
-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
index 58aaf3d698461fed22e2913a5ffdb757e33dc597..4a1cd980e67546395267044206e0ca415090afe1 100644 (file)
+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 (file)
index f168e0e..0000000
+++ /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 (file)
index 0c1115f..0000000
+++ /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()
index 42c01e3adfa629c789cfd6f2ff570cb6106e8613..0d9a7a98263e358c99ab578a028da41be0028475 100644 (file)
@@ -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()
index 3c1964bfe3eec5911fca08e3735a8ee5a25bffb4..17be1f538f96963b8c2edff1cbb187babbec8ac7 100644 (file)
     size: 2 * self.parent.width - 2 * self.parent.line_width, self.parent.height - key_label.font_size
 
 <Screen>:
+  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