]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/blobdiff - helpers/mapping.py
Reorder MusicFile methods
[perso/Immae/Projets/Python/MusicSampler.git] / helpers / mapping.py
index 58aaf3d698461fed22e2913a5ffdb757e33dc597..c2a94e67baa52f3bfe4b06eccb78d88741805789 100644 (file)
+from kivy.uix.relativelayout import RelativeLayout
+from kivy.properties import NumericProperty, ListProperty
+from kivy.core.window import Window
+from kivy.clock import Clock
+
 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
+import sys
+
+from .music_file import *
+from .mixer import Mixer
+from . import Config, gain, error_print
+from .action import Action
+
+class Mapping(RelativeLayout):
+    expected_keys = NumericProperty(0)
+    master_volume = NumericProperty(100)
+    ready_color = ListProperty([1, 165/255, 0, 1])
+
+    def __init__(self, **kwargs):
+        if Config.builtin_mixing:
+            self.mixer = Mixer()
+        else:
+            self.mixer = None
+
+        try:
+            self.key_config, self.open_files = self.parse_config()
+        except Exception as e:
+            error_print("Error while loading configuration: {}".format(e))
+            sys.exit()
+
+        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]]
+        self.wait_ids = {}
+        Clock.schedule_interval(self.not_all_keys_ready, 1)
+
+    @property
+    def master_gain(self):
+        return gain(self.master_volume)
+
+    def set_master_volume(self, value, delta=False, fade=0):
+        [db_gain, self.master_volume] = gain(
+                value + int(delta) * self.master_volume,
+                self.master_volume)
+
+        for music in self.open_files.values():
+            music.set_gain_with_effect(db_gain, fade=fade)
+
+    def add_wait_id(self, wait_id, action_or_wait):
+        self.wait_ids[wait_id] = action_or_wait
+
+    def interrupt_wait(self, wait_id):
+        if wait_id in self.wait_ids:
+            action_or_wait = self.wait_ids[wait_id]
+            del(self.wait_ids[wait_id])
+            if isinstance(action_or_wait, Action):
+                action_or_wait.interrupt()
             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]
+                action_or_wait.set()
+
+    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 len(modifiers) == 0 and key is not None:
+            threading.Thread(name="MSKeyAction", target=key.do_actions).start()
+        elif 'ctrl' in modifiers and (keycode[0] == 113 or keycode[0] == '99'):
+            for thread in threading.enumerate():
+                if thread.getName()[0:2] != "MS":
+                    continue
+                thread.join()
+
+            sys.exit()
+        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]
-        return None
+    def not_all_keys_ready(self, dt):
+        for key in self.children:
+            if not type(key).__name__ == "Key":
+                continue
+            if not key.is_key_ready:
+                return True
+        self.ready_color = [0, 1, 0, 1]
+        return False
 
     def stop_all_running(self):
+        running = self.running
         self.running = []
+        for (key, start_time) in running:
+            key.interrupt_action()
 
     def start_running(self, key, start_time):
         self.running.append((key, start_time))
@@ -185,3 +107,92 @@ class Mapping:
         if (key, start_time) in self.running:
             self.running.remove((key, start_time))
 
+    def parse_config(self):
+        stream = open(Config.yml_file, "r")
+        try:
+            config = yaml.load(stream)
+        except Exception as e:
+            error_print("Error while loading config file: {}".format(e))
+            sys.exit()
+        stream.close()
+
+        aliases = config['aliases']
+        seen_files = {}
+
+        key_properties = {}
+
+        for key in config['key_properties']:
+            if key not in key_properties:
+                key_prop = config['key_properties'][key]
+                if 'include' in key_prop:
+                    included = key_prop['include']
+                    del(key_prop['include'])
+
+                    if isinstance(included, str):
+                        key_prop.update(aliases[included], **key_prop)
+                    else:
+                        for included_ in included:
+                            key_prop.update(aliases[included_], **key_prop)
+
+                key_properties[key] = {
+                    "actions":    [],
+                    "properties": key_prop,
+                    "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,
+                                        self,
+                                        **config['music_properties'][filename])
+                            else:
+                                seen_files[filename] = MusicFile(
+                                        self,
+                                        filename)
+
+                        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, seen_files)
+
+