X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=music_sampler%2Fmapping.py;h=fc9768b7c665b1bbd709e500ebcf5f04ce6b5b97;hb=de71c01cd5c6dca3b33201a9effafc6c422fa239;hp=bb20e679b1e4a9eb8312e0404a913411b178e93c;hpb=63ba5a8dc2aa4ec3e6f203b0ba4db249ecf0b00e;p=perso%2FImmae%2FProjets%2FPython%2FMusicSampler.git diff --git a/music_sampler/mapping.py b/music_sampler/mapping.py index bb20e67..fc9768b 100644 --- a/music_sampler/mapping.py +++ b/music_sampler/mapping.py @@ -6,13 +6,14 @@ from kivy.clock import Clock import threading import yaml import sys +import copy from collections import defaultdict from transitions.extensions import HierarchicalMachine as Machine from .music_file import MusicFile from .mixer import Mixer -from . import Config, gain, error_print, warn_print +from .helpers import Config, gain, error_print, warn_print from .action import Action class Mapping(RelativeLayout): @@ -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,10 +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) - sys.exit() - else: - self.success() + with_trace=False, exit=initial) + + self.success() def on_enter_loading(self): for key in self.keys: @@ -125,17 +115,25 @@ class Mapping(RelativeLayout): threading.Thread(name="MSKeyAction", target=key.run, args=['-'.join(modifiers)]).start() elif 'ctrl' in modifiers and (keycode[0] == 113 or keycode[0] == '99'): - self.stop_all_running() - for thread in threading.enumerate(): - if thread.getName()[0:2] != "MS": - continue - thread.join() - + 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(): + if thread.getName()[0:2] == "MS": + thread.join() + elif thread.__class__ == threading.Timer: + thread.cancel() + thread.join() + # Helpers def allowed_modifiers(self, modifiers): allowed = [] @@ -160,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): @@ -191,17 +196,53 @@ class Mapping(RelativeLayout): music.set_gain_with_effect(db_gain, fade=fade) # Wait handler methods - 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: - action_or_wait.set() + def add_wait(self, action_or_wait, wait_id=None): + if wait_id is not None: + self.wait_ids[wait_id] = [action_or_wait] + else: + if None not in self.wait_ids: + self.wait_ids[None] = [] + self.wait_ids[None].append(action_or_wait) + + def matching_wait_ids(self, wait_id=None): + if wait_id is None: + matching_ids = list(self.wait_ids.keys()) + elif wait_id in self.wait_ids: + matching_ids = [wait_id] + else: + matching_ids = [] + return matching_ids + + 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: + if isinstance(action_or_wait, Action): + action_or_wait.interrupt() + 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): @@ -218,7 +259,8 @@ class Mapping(RelativeLayout): def parse_config(self): def update_alias(prop_hash, aliases, key): if isinstance(aliases[key], dict): - prop_hash.update(aliases[key], **prop_hash) + for alias in aliases[key]: + prop_hash.setdefault(alias, aliases[key][alias]) else: warn_print("Alias {} is not a hash, ignored".format(key)) @@ -278,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): @@ -302,12 +391,11 @@ class Mapping(RelativeLayout): "a float, ignored".format(filename)) return music_property - stream = open(Config.yml_file, "r") + stream = open(Config.yml_file, "r", encoding='utf8') try: config = yaml.safe_load(stream) except Exception as e: - error_print("Error while loading config file: {}".format(e)) - sys.exit() + raise Exception("Error while loading config file: {}".format(e)) from e stream.close() if not isinstance(config, dict): @@ -329,13 +417,25 @@ class Mapping(RelativeLayout): seen_files = {} + common_key_properties = {} + if 'common' in config['key_properties'] and\ + 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": [] }) for key in check_key_properties(config): + if key == 'common': + continue + key_prop = config['key_properties'][key] if not isinstance(key_prop, dict): @@ -346,53 +446,12 @@ class Mapping(RelativeLayout): include_aliases(key_prop, aliases) 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)