X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=helpers%2Fmapping.py;h=dbc1a811e405de4273f53259c711f09e1d83269d;hb=6c42e32d98aa2e04d446f31b8e667a280acf4b54;hp=6e3b29153e4d817205d015cf8a441f56da654189;hpb=e4917bcc6c5355a82f05880a389d0a1fd868561d;p=perso%2FImmae%2FProjets%2FPython%2FMusicSampler.git diff --git a/helpers/mapping.py b/helpers/mapping.py index 6e3b291..dbc1a81 100644 --- a/helpers/mapping.py +++ b/helpers/mapping.py @@ -1,5 +1,5 @@ from kivy.uix.relativelayout import RelativeLayout -from kivy.properties import NumericProperty, ListProperty +from kivy.properties import NumericProperty, ListProperty, StringProperty from kivy.core.window import Window from kivy.clock import Clock @@ -8,17 +8,82 @@ import yaml import sys 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 .action import Action class Mapping(RelativeLayout): - expected_keys = NumericProperty(0) + STATES = [ + 'initial', + 'configuring', + 'configured', + 'loading', + 'loaded', + 'failed' + ] + + TRANSITIONS = [ + { + 'trigger': 'configure', + 'source': 'initial', + 'dest': 'configuring' + }, + { + 'trigger': 'fail', + 'source': 'configuring', + 'dest': 'failed' + }, + { + 'trigger': 'success', + 'source': 'configuring', + 'dest': 'configured', + 'after': 'load' + }, + { + 'trigger': 'load', + 'source': 'configured', + 'dest': 'loading' + }, + { + 'trigger': 'fail', + 'source': 'loading', + 'dest': 'failed' + }, + { + 'trigger': 'success', + 'source': 'loading', + 'dest': 'loaded' + }, + { + 'trigger': 'reload', + 'source': 'loaded', + 'dest': 'configuring' + } + ] + master_volume = NumericProperty(100) ready_color = ListProperty([1, 165/255, 0, 1]) + state = StringProperty("") def __init__(self, **kwargs): + self.keys = [] + self.running = [] + self.wait_ids = {} + self.open_files = {} + + Machine(model=self, states=self.STATES, + transitions=self.TRANSITIONS, initial='initial', + ignore_invalid_triggers=True, 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() + + def on_enter_configuring(self): if Config.builtin_mixing: self.mixer = Mixer() else: @@ -30,46 +95,35 @@ class Mapping(RelativeLayout): error_print("Error while loading configuration: {}".format(e), with_trace=True) sys.exit() + else: + self.success() - 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 = [] - 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 on_enter_loading(self): + for key in self.keys: + key.reload() + self.success() - def add_wait_id(self, wait_id, action_or_wait): - self.wait_ids[wait_id] = action_or_wait + # Kivy events + def add_widget(self, widget, index=0): + if type(widget).__name__ == "Key" and widget not in self.keys: + self.keys.append(widget) + return super(Mapping, self).add_widget(widget, index) - 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 remove_widget(self, widget, index=0): + if type(widget).__name__ == "Key" and widget in self.keys: + self.keys.remove(widget) + return super(Mapping, self).remove_widget(widget, index) - def _keyboard_closed(self): - self._keyboard.unbind(on_key_down=self._on_keyboard_down) - self._keyboard = None + def on_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): + 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.run).start() + if self.allowed_modifiers(modifiers) and key is not None: + modifiers.sort() + 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(): @@ -78,28 +132,76 @@ class Mapping(RelativeLayout): thread.join() sys.exit() + elif 'ctrl' in modifiers and keycode[0] == 114: + threading.Thread(name="MSReload", target=self.reload).start() return True + # Helpers + def allowed_modifiers(self, modifiers): + allowed = [] + return len([a for a in modifiers if a not in allowed]) == 0 + 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 not_all_keys_ready(self, dt): - for key in self.children: - if not type(key).__name__ == "Key": - continue + def all_keys_ready(self): + partial = False + for key in self.keys: if not key.is_loaded_or_failed(): - return True - self.ready_color = [0, 1, 0, 1] - return False + return "not_ready" + partial = partial or key.is_failed() + if partial: + return "partial" + else: + return "success" + + # Callbacks + def key_loaded_callback(self): + result = self.all_keys_ready() + if result == "success": + self.ready_color = [0, 1, 0, 1] + elif result == "partial": + self.ready_color = [1, 0, 0, 1] + else: + self.ready_color = [1, 165/255, 0, 1] + + ## Some global actions def stop_all_running(self): running = self.running self.running = [] for (key, start_time) in running: key.interrupt() + # Master volume methods + @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) + + # 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() + + # Methods to control running keys def start_running(self, key, start_time): self.running.append((key, start_time)) @@ -110,6 +212,7 @@ class Mapping(RelativeLayout): if (key, start_time) in self.running: self.running.remove((key, start_time)) + # YML config parser def parse_config(self): def update_alias(prop_hash, aliases, key): if isinstance(aliases[key], dict): @@ -268,8 +371,15 @@ class Mapping(RelativeLayout): music_properties[filename], filename) - seen_files[filename] = MusicFile( - filename, self, **music_property) + 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'] \