]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/blobdiff - music_sampler/mapping.py
Change "keys" hash to "key_properties" in config.yml
[perso/Immae/Projets/Python/MusicSampler.git] / music_sampler / mapping.py
index a04c2f6ec550021dacc65ec891a4d4e7a5f6f731..193f5e5d034a7a797076d3a68ca21c80627092cf 100644 (file)
@@ -6,6 +6,7 @@ from kivy.clock import Clock
 import threading
 import yaml
 import sys
+import copy
 from collections import defaultdict
 
 from transitions.extensions import HierarchicalMachine as Machine
@@ -21,8 +22,7 @@ class Mapping(RelativeLayout):
         'configuring',
         'configured',
         'loading',
-        'loaded',
-        'failed'
+        'loaded'
     ]
 
     TRANSITIONS = [
@@ -31,11 +31,6 @@ class Mapping(RelativeLayout):
             'source': 'initial',
             'dest': 'configuring'
         },
-        {
-            'trigger': 'fail',
-            'source': 'configuring',
-            'dest': 'failed'
-        },
         {
             'trigger': 'success',
             'source': 'configuring',
@@ -47,11 +42,6 @@ class Mapping(RelativeLayout):
             'source': 'configured',
             'dest': 'loading'
         },
-        {
-            'trigger': 'fail',
-            'source': 'loading',
-            'dest': 'failed'
-        },
         {
             'trigger': 'success',
             'source': 'loading',
@@ -73,17 +63,18 @@ class Mapping(RelativeLayout):
         self.running = []
         self.wait_ids = {}
         self.open_files = {}
+        self.is_leaving_application = False
 
         Machine(model=self, states=self.STATES,
                 transitions=self.TRANSITIONS, initial='initial',
-                ignore_invalid_triggers=True, queued=True)
+                auto_transitions=False, queued=True)
         super(Mapping, self).__init__(**kwargs)
         self.keyboard = Window.request_keyboard(self.on_keyboard_closed, self)
         self.keyboard.bind(on_key_down=self.on_keyboard_down)
 
-        self.configure()
+        self.configure(initial=True)
 
-    def on_enter_configuring(self):
+    def on_enter_configuring(self, initial=True):
         if Config.builtin_mixing:
             self.mixer = Mixer()
         else:
@@ -93,9 +84,9 @@ class Mapping(RelativeLayout):
             self.key_config, self.open_files = self.parse_config()
         except Exception as e:
             error_print("Error while loading configuration: {}".format(e),
-                    with_trace=True, exit=True)
-        else:
-            self.success()
+                    with_trace=False, exit=initial)
+
+        self.success()
 
     def on_enter_loading(self):
         for key in self.keys:
@@ -126,13 +117,14 @@ class Mapping(RelativeLayout):
         elif 'ctrl' in modifiers and (keycode[0] == 113 or keycode[0] == '99'):
             self.leave_application()
             sys.exit()
-        elif 'ctrl' in modifiers and keycode[0] == 114:
-            threading.Thread(name="MSReload", target=self.reload).start()
+        elif 'ctrl' in modifiers and keycode[0] == 114 and self.is_loaded():
+            self.reload(initial=False)
         return True
 
     def leave_application(self):
         self.keyboard.unbind(on_key_down=self.on_keyboard_down)
         self.stop_all_running()
+        self.is_leaving_application = True
         for music in self.open_files.values():
             music.stop()
         for thread in threading.enumerate():
@@ -166,13 +158,20 @@ class Mapping(RelativeLayout):
 
     # Callbacks
     def key_loaded_callback(self):
+        if hasattr(self, 'finished_loading'):
+            return
+
+        opacity = int(Config.load_all_musics)
+
         result = self.all_keys_ready()
         if result == "success":
-            self.ready_color = [0, 1, 0, 1]
+            self.ready_color = [0, 1, 0, opacity]
+            self.finished_loading = True
         elif result == "partial":
-            self.ready_color = [1, 0, 0, 1]
+            self.ready_color = [1, 0, 0, opacity]
+            self.finished_loading = True
         else:
-            self.ready_color = [1, 165/255, 0, 1]
+            self.ready_color = [1, 165/255, 0, opacity]
 
     ## Some global actions
     def stop_all_running(self, except_key=None, key_start_time=0):
@@ -205,15 +204,17 @@ class Mapping(RelativeLayout):
                 self.wait_ids[None] = []
             self.wait_ids[None].append(action_or_wait)
 
-    def interrupt_wait(self, wait_id=None):
+    def matching_wait_ids(self, wait_id=None):
         if wait_id is None:
-            ids_to_interrupt = list(self.wait_ids.keys())
+            matching_ids = list(self.wait_ids.keys())
         elif wait_id in self.wait_ids:
-            ids_to_interrupt = [wait_id]
+            matching_ids = [wait_id]
         else:
-            ids_to_interrupt = []
+            matching_ids = []
+        return matching_ids
 
-        for _wait_id in ids_to_interrupt:
+    def interrupt_wait(self, wait_id=None):
+        for _wait_id in self.matching_wait_ids(wait_id=wait_id):
             action_or_waits = self.wait_ids[_wait_id]
             del(self.wait_ids[_wait_id])
             for action_or_wait in action_or_waits:
@@ -222,6 +223,27 @@ class Mapping(RelativeLayout):
                 else:
                     action_or_wait.set()
 
+    def pause_wait(self, wait_id=None):
+        for _wait_id in self.matching_wait_ids(wait_id=wait_id):
+            action_or_waits = self.wait_ids[_wait_id]
+            for action_or_wait in action_or_waits:
+                if isinstance(action_or_wait, Action):
+                    action_or_wait.pause()
+
+    def unpause_wait(self, wait_id=None):
+        for _wait_id in self.matching_wait_ids(wait_id=wait_id):
+            action_or_waits = self.wait_ids[_wait_id]
+            for action_or_wait in action_or_waits:
+                if isinstance(action_or_wait, Action):
+                    action_or_wait.unpause()
+
+    def reset_wait(self, wait_id=None):
+        for _wait_id in self.matching_wait_ids(wait_id=wait_id):
+            action_or_waits = self.wait_ids[_wait_id]
+            for action_or_wait in action_or_waits:
+                if isinstance(action_or_wait, Action):
+                    action_or_wait.reset()
+
     # Methods to control running keys
     def start_running(self, key, start_time):
         self.running.append((key, start_time))
@@ -298,13 +320,60 @@ class Mapping(RelativeLayout):
             else:
                 return {}
 
-        def check_mapped_key(mapped_keys, key):
-            if not isinstance(mapped_keys[key], list):
+        def check_mapped_key(actions, key):
+            if not isinstance(actions, list):
                 warn_print("key config '{}' is not an array, ignored"
                         .format(key))
                 return []
             else:
-                return mapped_keys[key]
+                return actions
+
+        def append_actions_to_key(mapped_key, actions, aliases, seen_files, music_properties, key_properties):
+            for index, action in enumerate(check_mapped_key(actions, mapped_key)):
+                if not isinstance(action, dict) or\
+                        not len(action) == 1 or\
+                        not isinstance(list(action.values())[0] or {}, dict):
+                    warn_print("action number {} of key '{}' is invalid, "
+                            "ignored".format(index + 1, mapped_key))
+                    continue
+                append_action_to_key(action, mapped_key, aliases, seen_files, music_properties, key_properties)
+
+        def append_action_to_key(action, mapped_key, aliases, seen_files, music_properties, key_properties):
+            action_name = list(action)[0]
+            action_args = {}
+            if action[action_name] is None:
+                action[action_name] = {}
+
+            include_aliases(action[action_name], aliases)
+
+            for argument in action[action_name]:
+                if argument == 'file':
+                    filename = str(action[action_name]['file'])
+                    if filename not in seen_files:
+                        music_property = check_music_property(
+                                music_properties[filename],
+                                filename)
+
+                        if filename in self.open_files:
+                            self.open_files[filename]\
+                                    .reload_properties(**music_property)
+
+                            seen_files[filename] =\
+                                    self.open_files[filename]
+                        else:
+                            seen_files[filename] = MusicFile(
+                                    filename, self, **music_property)
+
+                    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])
 
         def check_music_property(music_property, filename):
             if not isinstance(music_property, dict):
@@ -326,13 +395,11 @@ class Mapping(RelativeLayout):
         try:
             config = yaml.safe_load(stream)
         except Exception as e:
-            error_print("Error while loading config file: {}".format(e),
-                    exit=True)
+            raise Exception("Error while loading config file: {}".format(e)) from e
         stream.close()
 
         if not isinstance(config, dict):
-            error_print("Top level config is supposed to be a hash",
-                    exit=True)
+            raise Exception("Top level config is supposed to be a hash")
 
         if 'aliases' in config and isinstance(config['aliases'], dict):
             aliases = config['aliases']
@@ -355,12 +422,13 @@ class Mapping(RelativeLayout):
                 isinstance(config['key_properties'], dict):
             common_key_properties = config['key_properties']['common']
             include_aliases(common_key_properties, aliases)
+            check_key_property(common_key_properties, 'common')
         elif 'common' in config['key_properties']:
             warn_print("'common' key in key_properties is not a hash, ignored")
 
         key_properties = defaultdict(lambda: {
                 "actions":    [],
-                "properties": {},
+                "properties": copy.deepcopy(common_key_properties),
                 "files":      []
             })
 
@@ -376,58 +444,14 @@ class Mapping(RelativeLayout):
                 continue
 
             include_aliases(key_prop, aliases)
-            for _key in common_key_properties:
-                key_prop.setdefault(_key, common_key_properties[_key])
-
             check_key_property(key_prop, key)
 
-            key_properties[key]["properties"] = key_prop
+            key_properties[key]["properties"].update(key_prop)
+            if 'actions' in key_prop:
+                append_actions_to_key(key, key_prop['actions'], aliases, seen_files, music_properties, key_properties)
 
         for mapped_key in check_mapped_keys(config):
-            for index, action in enumerate(check_mapped_key(
-                    config['keys'], mapped_key)):
-                if not isinstance(action, dict) or\
-                        not len(action) == 1 or\
-                        not isinstance(list(action.values())[0] or {}, dict):
-                    warn_print("action number {} of key '{}' is invalid, "
-                            "ignored".format(index + 1, mapped_key))
-                    continue
-
-                action_name = list(action)[0]
-                action_args = {}
-                if action[action_name] is None:
-                    action[action_name] = {}
-
-                include_aliases(action[action_name], aliases)
-
-                for argument in action[action_name]:
-                    if argument == 'file':
-                        filename = str(action[action_name]['file'])
-                        if filename not in seen_files:
-                            music_property = check_music_property(
-                                    music_properties[filename],
-                                    filename)
-
-                            if filename in self.open_files:
-                                self.open_files[filename]\
-                                        .reload_properties(**music_property)
-
-                                seen_files[filename] =\
-                                        self.open_files[filename]
-                            else:
-                                seen_files[filename] = MusicFile(
-                                        filename, self, **music_property)
-
-                        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])
+            append_actions_to_key(mapped_key, config['keys'][mapped_key], aliases, seen_files, music_properties, key_properties)
 
         return (key_properties, seen_files)