From 045a1b6343dc1a1ec4b27f5ae11856b8a422c9db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Sat, 13 Aug 2016 23:09:12 +0200 Subject: Fix configuring not resetting the key --- music_sampler/key.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/music_sampler/key.py b/music_sampler/key.py index ce2f45b..68e6f04 100644 --- a/music_sampler/key.py +++ b/music_sampler/key.py @@ -119,21 +119,20 @@ class KeyMachine(Widget): return self.is_loaded_no_config() or self.is_loaded_no_actions() def on_enter_configuring(self): + self.destroy_actions() + self.key.unset_description() + self.key.unset_color() + if self.key.key_sym in self.key.parent.key_config: self.key.config = self.key.parent.key_config[self.key.key_sym] - self.key.actions = [] for key_action in self.key.config['actions']: self.key.add_action(key_action[0], **key_action[1]) if 'description' in self.key.config['properties']: self.key.set_description(self.key.config['properties']['description']) - else: - self.key.unset_description() if 'color' in self.key.config['properties']: self.key.set_color(self.key.config['properties']['color']) - else: - self.key.unset_color() self.success() else: self.no_config() @@ -145,6 +144,11 @@ class KeyMachine(Widget): else: self.no_actions() + def destroy_actions(self): + for action in self.key.actions: + action.destroy() + self.key.actions = [] + def run_actions(self, modifiers): self.key.parent.parent.ids['KeyList'].append(self.key.key_sym) debug_print("running actions for {}".format(self.key.key_sym)) -- cgit v1.2.3 From d88794ab3f5e9ce896fa58224dd8c6b1ec33b8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Mon, 19 Sep 2016 13:57:38 +0200 Subject: bump config.yml --- config.yml | 80 +++++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/config.yml b/config.yml index 36c804b..2e89a0f 100644 --- a/config.yml +++ b/config.yml @@ -92,6 +92,42 @@ key_properties: - - Noise include: light_blue + 'q': + description: + - + - Load + - Music 1 + include: light_blue + 's': + description: + - + - Load + - Music 2 + include: light_blue + 'd': + description: + - + - Load + - Noise + include: light_blue + 'w': + description: + - + - Unload + - Music 1 + include: light_blue + 'x': + description: + - + - Unload + - Music 2 + include: light_blue + 'c': + description: + - + - Unload + - Noise + include: light_blue 't': description: - @@ -105,12 +141,6 @@ key_properties: - Stop wait - music 2 include: green - 'u': - description: - - - - Noise - - + Music 2 - include: green 'g': description: @@ -266,6 +296,28 @@ keys: - play: include: noise +# Load the songs + 'q': + - load_music: + include: music1 + 's': + - load_music: + include: music2 + 'd': + - load_music: + include: noise + +# Unload the songs + 'w': + - unload_music: + include: music1 + 'x': + - unload_music: + include: music2 + 'c': + - unload_music: + include: noise + # Crossfade from any music to music 2 't': - stop: @@ -276,26 +328,10 @@ keys: # Fade out, then wait and start music 2 'y': - - stop: - fade_out: 3 - wait: true - wait: - duration: 3 - - play: include: music2 # Play the noise, stop music 1, wait a bit then start music 2 - 'u': - - play: - include: noise - - wait: - duration: 0.2 - - stop: - include: music1 - - wait: - duration: 3 - - play: - include: music2 # Play music 1, starting at 30 seconds then seeks at 60 after 5 seconds 'g': -- cgit v1.2.3 From a9324e30da6292f53f008f1b827779c7f8e2fcdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Mon, 19 Sep 2016 14:02:12 +0200 Subject: Use @mainthread decorator where necessary --- music_sampler/app_blocks/actionlist.py | 3 +++ music_sampler/app_blocks/playlist.py | 3 ++- music_sampler/key.py | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/music_sampler/app_blocks/actionlist.py b/music_sampler/app_blocks/actionlist.py index f48072f..59315de 100644 --- a/music_sampler/app_blocks/actionlist.py +++ b/music_sampler/app_blocks/actionlist.py @@ -4,6 +4,8 @@ from kivy.uix.relativelayout import RelativeLayout from kivy.properties import ListProperty, StringProperty from ..lock import Lock +from kivy.clock import mainthread + __all__ = ["ActionList", "ActionListIcons", "ActionListIcon", "ActionListDescriptions", "ActionListDescription"] @@ -14,6 +16,7 @@ class ActionList(RelativeLayout): action_title = StringProperty("") action_list = ListProperty([]) + @mainthread def update_list(self, key, action_descriptions): if key.repeat_delay > 0: self.action_title = _( diff --git a/music_sampler/app_blocks/playlist.py b/music_sampler/app_blocks/playlist.py index 5894995..706e4fc 100644 --- a/music_sampler/app_blocks/playlist.py +++ b/music_sampler/app_blocks/playlist.py @@ -2,7 +2,7 @@ from kivy.uix.label import Label from kivy.uix.stacklayout import StackLayout from kivy.uix.relativelayout import RelativeLayout from kivy.properties import ListProperty -from kivy.clock import Clock +from kivy.clock import Clock, mainthread from ..helpers import duration_to_min_sec from ..lock import Lock @@ -20,6 +20,7 @@ class PlayList(RelativeLayout): super(PlayList, self).__init__(**kwargs) Clock.schedule_interval(self.update_playlist, 0.5) + @mainthread def update_playlist(self, dt): if self.parent is None or 'Mapping' not in self.parent.ids: return True diff --git a/music_sampler/key.py b/music_sampler/key.py index 68e6f04..e524c35 100644 --- a/music_sampler/key.py +++ b/music_sampler/key.py @@ -9,6 +9,10 @@ import time import threading from transitions.extensions import HierarchicalMachine as Machine +# All drawing operations should happen in the main thread +# https://github.com/kivy/kivy/wiki/Working-with-Python-threads-inside-a-Kivy-application +from kivy.clock import mainthread + class KeyMachine(Widget): STATES = [ 'initial', @@ -118,6 +122,7 @@ class KeyMachine(Widget): def is_loaded_inactive(self): return self.is_loaded_no_config() or self.is_loaded_no_actions() + @mainthread def on_enter_configuring(self): self.destroy_actions() self.key.unset_description() @@ -172,6 +177,7 @@ class KeyMachine(Widget): self.key.repeat_protection_finished() # Callbacks + @mainthread def key_loaded_callback(self): self.key.parent.key_loaded_callback() @@ -250,6 +256,10 @@ class Key(ButtonBehavior, Widget): super(Key, self).__init__(**kwargs) # Kivy events + @mainthread + def update_state(self, value): + self.machine_state = value + def on_key_sym(self, key, key_sym): if key_sym != "": self.configure() -- cgit v1.2.3 From 6dc040edf2f31497d4492c159397c4634037be66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Mon, 19 Sep 2016 15:57:26 +0200 Subject: Add load_all_musics flag and corresponding actions --- music_sampler/actions/__init__.py | 2 ++ music_sampler/actions/load_music.py | 12 ++++++++++++ music_sampler/actions/unload_music.py | 10 ++++++++++ music_sampler/helpers.py | 8 ++++++++ 4 files changed, 32 insertions(+) create mode 100644 music_sampler/actions/load_music.py create mode 100644 music_sampler/actions/unload_music.py diff --git a/music_sampler/actions/__init__.py b/music_sampler/actions/__init__.py index 7c812cb..e0671fe 100644 --- a/music_sampler/actions/__init__.py +++ b/music_sampler/actions/__init__.py @@ -1,4 +1,5 @@ from . import interrupt_wait +from . import load_music from . import pause from . import pause_wait from . import play @@ -7,6 +8,7 @@ from . import run_command from . import seek from . import stop from . import stop_all_actions +from . import unload_music from . import unpause from . import unpause_wait from . import volume diff --git a/music_sampler/actions/load_music.py b/music_sampler/actions/load_music.py new file mode 100644 index 0000000..f3e02ba --- /dev/null +++ b/music_sampler/actions/load_music.py @@ -0,0 +1,12 @@ +import threading + +def run(action, music=None, **kwargs): + for music in action.music_list(music): + if not music.is_loaded(allow_substates=True): + threading.Thread(name="MSMusicLoad", target=music.load).start() + +def description(action, music=None, **kwargs): + if music is not None: + return "load music « {} » to memory".format(music.name) + else: + return "load all music to memory" diff --git a/music_sampler/actions/unload_music.py b/music_sampler/actions/unload_music.py new file mode 100644 index 0000000..b3de316 --- /dev/null +++ b/music_sampler/actions/unload_music.py @@ -0,0 +1,10 @@ +def run(action, music=None, **kwargs): + for music in action.music_list(music): + if music.is_unloadable(): + music.unload() + +def description(action, music=None, **kwargs): + if music is not None: + return "unload music « {} » from memory".format(music.name) + else: + return "unload all music from memory" diff --git a/music_sampler/helpers.py b/music_sampler/helpers.py index 9403875..fbd338b 100644 --- a/music_sampler/helpers.py +++ b/music_sampler/helpers.py @@ -124,6 +124,13 @@ Configs = { 'help_no': _("Don't show warning when focus is lost"), 'type': 'boolean' }, + 'load_all_musics': { + 'default': True, + 'help_yes': _("Load all the musics at launch time (default)"), + 'help_no': _("Don't load all the musics at launch time (use it if you \ + have memory problems)"), + 'type': 'boolean' + }, 'list_devices': { 'help': _("List available sound devices"), 'type': 'action' @@ -142,6 +149,7 @@ Configs_order = [ 'language', 'list_devices', 'device', + 'load_all_musics', ] def parse_args(): argv = sys.argv[1 :] -- cgit v1.2.3 From 93a3e51e749afc0c3ba8488b900124fda6bb8774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Mon, 19 Sep 2016 15:57:42 +0200 Subject: Cleanup key and action workflows --- music_sampler/action.py | 70 +++++++++++++++++--------- music_sampler/key.py | 35 +++++++------ music_sampler/mapping.py | 34 ++++++------- music_sampler/music_file.py | 119 ++++++++++++++++++++++---------------------- 4 files changed, 142 insertions(+), 116 deletions(-) diff --git a/music_sampler/action.py b/music_sampler/action.py index 22a2bdc..bc62f33 100644 --- a/music_sampler/action.py +++ b/music_sampler/action.py @@ -9,8 +9,9 @@ class Action: 'failed', { 'name': 'loaded', - 'children': ['running'] - } + 'children': ['stopped', 'running'] + }, + 'destroyed' ] TRANSITIONS = [ @@ -21,36 +22,42 @@ class Action: }, { 'trigger': 'fail', - 'source': 'loading', + 'source': ['loading', 'loaded'], 'dest': 'failed', - 'after': 'poll_loaded' }, { 'trigger': 'success', 'source': 'loading', - 'dest': 'loaded', - 'after': 'poll_loaded' + 'dest': 'loaded_stopped', }, { - 'trigger': 'run', + 'trigger': 'reload', 'source': 'loaded', + 'dest': 'loading', + }, + { + 'trigger': 'run', + 'source': 'loaded_stopped', 'dest': 'loaded_running', 'after': 'finish_action', - # if a child has no transitions, then it is bubbled to the parent, - # and we don't want that. Not useful in that machine precisely. - 'conditions': ['is_loaded'] }, { 'trigger': 'finish_action', 'source': 'loaded_running', - 'dest': 'loaded' + 'dest': 'loaded_stopped' + }, + { + 'trigger': 'destroy', + 'source': '*', + 'dest': 'destroyed' } ] def __init__(self, action, key, **kwargs): Machine(model=self, states=self.STATES, transitions=self.TRANSITIONS, initial='initial', - ignore_invalid_triggers=True, queued=True) + ignore_invalid_triggers=True, queued=True, + after_state_change=self.notify_state_change) self.action = action self.key = key @@ -62,18 +69,31 @@ class Action: def is_loaded_or_failed(self): return self.is_loaded(allow_substates=True) or self.is_failed() - def callback_music_loaded(self, success): - if success: - self.success() - else: + def callback_music_state(self, new_state): + # If a music gets unloaded while the action is loaded_running and + # depending on the music, it won't be able to do the finish_action. + # Can that happen? + # a: play 'mp3'; + # z: wait 'mp3'; + # e: pause 'mp3'; + # r: stop 'mp3'; unload_music 'mp3' + if new_state == 'failed': self.fail() + elif self.is_loaded(allow_substates=True) and\ + new_state in ['initial', 'loading']: + self.reload(reloading=True) + elif self.is_loading() and new_state.startswith('loaded_'): + self.success() # Machine states / events - def on_enter_loading(self): + def on_enter_loading(self, reloading=False): + if reloading: + return if hasattr(actions, self.action): - if 'music' in self.arguments: - self.arguments['music'].subscribe_loaded( - self.callback_music_loaded) + if 'music' in self.arguments and\ + self.action not in ['unload_music', 'load_music']: + self.arguments['music'].subscribe_state_change( + self.callback_music_state) else: self.success() else: @@ -86,9 +106,13 @@ class Action: getattr(actions, self.action).run(self, key_start_time=key_start_time, **self.arguments) - def poll_loaded(self): - self.key.callback_action_ready(self, - self.is_loaded(allow_substates=True)) + def on_enter_destroyed(self): + if 'music' in self.arguments: + self.arguments['music'].unsubscribe_state_change( + self.callback_music_state) + + def notify_state_change(self, *args, **kwargs): + self.key.callback_action_state_changed() # This one cannot be in the Machine state since it would be queued to run # *after* the wait is ended... diff --git a/music_sampler/key.py b/music_sampler/key.py index e524c35..e05bb16 100644 --- a/music_sampler/key.py +++ b/music_sampler/key.py @@ -13,7 +13,7 @@ from transitions.extensions import HierarchicalMachine as Machine # https://github.com/kivy/kivy/wiki/Working-with-Python-threads-inside-a-Kivy-application from kivy.clock import mainthread -class KeyMachine(Widget): +class KeyMachine(): STATES = [ 'initial', 'configuring', @@ -101,11 +101,15 @@ class KeyMachine(Widget): { 'trigger': 'repeat_protection_finished', 'source': 'loaded_protecting_repeat', - 'dest': 'loaded' + 'dest': 'loaded', + 'after': 'callback_action_state_changed' }, ] - state = StringProperty("") + def __setattr__(self, name, value): + if hasattr(self, 'initialized') and name == 'state': + self.key.update_state(value) + super().__setattr__(name, value) def __init__(self, key, **kwargs): self.key = key @@ -113,7 +117,8 @@ class KeyMachine(Widget): Machine(model=self, states=self.STATES, transitions=self.TRANSITIONS, initial='initial', ignore_invalid_triggers=True, queued=True) - super(KeyMachine, self).__init__(**kwargs) + + self.initialized = True # Machine states / events def is_loaded_or_failed(self): @@ -181,6 +186,17 @@ class KeyMachine(Widget): def key_loaded_callback(self): self.key.parent.key_loaded_callback() + def callback_action_state_changed(self): + if self.state not in ['failed', 'loading', 'loaded']: + return + + if any(action.is_failed() for action in self.key.actions): + self.to_failed() + elif any(action.is_loading() for action in self.key.actions): + self.to_loading() + else: + self.to_loaded() + self.key_loaded_callback() class Key(ButtonBehavior, Widget): @@ -244,14 +260,10 @@ class Key(ButtonBehavior, Widget): else: raise AttributeError - def machine_state_changed(self, instance, machine_state): - self.machine_state = self.machine.state - def __init__(self, **kwargs): self.actions = [] self.current_action = None self.machine = KeyMachine(self) - self.machine.bind(state=self.machine_state_changed) super(Key, self).__init__(**kwargs) @@ -272,13 +284,6 @@ class Key(ButtonBehavior, Widget): def interrupt(self): self.current_action.interrupt() - # Callbacks - def callback_action_ready(self, action, success): - if not success: - self.fail() - elif all(action.is_loaded_or_failed() for action in self.actions): - self.success() - # Setters def set_description(self, description): if description[0] is not None: diff --git a/music_sampler/mapping.py b/music_sampler/mapping.py index 9e40d40..5c61f8a 100644 --- a/music_sampler/mapping.py +++ b/music_sampler/mapping.py @@ -22,8 +22,7 @@ class Mapping(RelativeLayout): 'configuring', 'configured', 'loading', - 'loaded', - 'failed' + 'loaded' ] TRANSITIONS = [ @@ -32,11 +31,6 @@ class Mapping(RelativeLayout): 'source': 'initial', 'dest': 'configuring' }, - { - 'trigger': 'fail', - 'source': 'configuring', - 'dest': 'failed' - }, { 'trigger': 'success', 'source': 'configuring', @@ -48,11 +42,6 @@ class Mapping(RelativeLayout): 'source': 'configured', 'dest': 'loading' }, - { - 'trigger': 'fail', - 'source': 'loading', - 'dest': 'failed' - }, { 'trigger': 'success', 'source': 'loading', @@ -74,10 +63,11 @@ 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) @@ -127,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() 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(): @@ -167,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): diff --git a/music_sampler/music_file.py b/music_sampler/music_file.py index 4ba65e3..ec50951 100644 --- a/music_sampler/music_file.py +++ b/music_sampler/music_file.py @@ -22,6 +22,7 @@ class MusicFile: { 'name': 'loaded', 'children': [ + 'stopped', 'playing', 'paused', 'stopping' @@ -31,27 +32,28 @@ class MusicFile: TRANSITIONS = [ { 'trigger': 'load', - 'source': 'initial', - 'dest': 'loading', - 'after': 'poll_loaded' + 'source': ['initial', 'failed'], + 'dest': 'loading' }, { 'trigger': 'fail', 'source': 'loading', 'dest': 'failed' }, + { + 'trigger': 'unload', + 'source': ['failed', 'loaded_stopped'], + 'dest': 'initial', + }, { 'trigger': 'success', 'source': 'loading', - 'dest': 'loaded' + 'dest': 'loaded_stopped' }, { 'trigger': 'start_playing', - 'source': 'loaded', - 'dest': 'loaded_playing', - # if a child has no transitions, then it is bubbled to the parent, - # and we don't want that. Not useful in that machine precisely. - 'conditions': ['is_loaded'] + 'source': 'loaded_stopped', + 'dest': 'loaded_playing' }, { 'trigger': 'pause', @@ -70,19 +72,20 @@ class MusicFile: }, { 'trigger': 'stopped', - 'source': '*', - 'dest': 'loaded', + 'source': 'loaded', + 'dest': 'loaded_stopped', 'before': 'trigger_stopped_events', - 'conditions': ['is_in_use'] + 'unless': 'is_loaded_stopped', } ] def __init__(self, filename, mapping, name=None, gain=1): - Machine(model=self, states=self.STATES, + machine = Machine(model=self, states=self.STATES, transitions=self.TRANSITIONS, initial='initial', - ignore_invalid_triggers=True) + auto_transitions=False, + after_state_change=self.notify_state_change) - self.loaded_callbacks = [] + self.state_change_callbacks = [] self.mapping = mapping self.filename = filename self.name = name or filename @@ -90,48 +93,41 @@ class MusicFile: self.initial_volume_factor = gain self.music_lock = Lock("music__" + filename) - threading.Thread(name="MSMusicLoad", target=self.load).start() + if Config.load_all_musics: + threading.Thread(name="MSMusicLoad", target=self.load).start() def reload_properties(self, name=None, gain=1): self.name = name or self.filename if gain != self.initial_volume_factor: self.initial_volume_factor = gain - self.reload_music_file() + self.stopped() + self.unload() + self.load(reloading=True) - def reload_music_file(self): - with file_lock: - try: - if self.filename.startswith("/"): - filename = self.filename - else: - filename = Config.music_path + self.filename + # Machine related events + def on_enter_initial(self): + self.audio_segment = None - debug_print("Reloading « {} »".format(self.name)) - initial_db_gain = gain(self.initial_volume_factor * 100) - self.audio_segment = pydub.AudioSegment \ - .from_file(filename) \ - .set_frame_rate(Config.frame_rate) \ - .set_channels(Config.channels) \ - .set_sample_width(Config.sample_width) \ - .apply_gain(initial_db_gain) - except Exception as e: - error_print("failed to reload « {} »: {}"\ - .format(self.name, e)) - self.loading_error = e - self.to_failed() - else: - debug_print("Reloaded « {} »".format(self.name)) + def on_enter_loading(self, reloading=False): + if reloading: + prefix = 'Rel' + prefix_s = 'rel' + else: + prefix = 'L' + prefix_s = 'l' - # Machine related events - def on_enter_loading(self): with file_lock: + if self.mapping.is_leaving_application: + self.fail() + return + try: if self.filename.startswith("/"): filename = self.filename else: filename = Config.music_path + self.filename - debug_print("Loading « {} »".format(self.name)) + debug_print("{}oading « {} »".format(prefix, self.name)) self.mixer = self.mapping.mixer or Mixer() initial_db_gain = gain(self.initial_volume_factor * 100) self.audio_segment = pydub.AudioSegment \ @@ -142,12 +138,13 @@ class MusicFile: .apply_gain(initial_db_gain) self.sound_duration = self.audio_segment.duration_seconds except Exception as e: - error_print("failed to load « {} »: {}".format(self.name, e)) + error_print("failed to {}oad « {} »: {}".format( + prefix_s, self.name, e)) self.loading_error = e self.fail() else: self.success() - debug_print("Loaded « {} »".format(self.name)) + debug_print("{}oaded « {} »".format(prefix, self.name)) def on_enter_loaded(self): self.cleanup() @@ -165,11 +162,15 @@ class MusicFile: # Machine related states def is_in_use(self): - return self.is_loaded(allow_substates=True) and not self.is_loaded() + return self.is_loaded(allow_substates=True) and\ + not self.is_loaded_stopped() def is_in_use_not_stopping(self): return self.is_loaded_playing() or self.is_loaded_paused() + def is_unloadable(self): + return self.is_loaded_stopped() or self.is_failed() + # Machine related triggers def trigger_stopped_events(self): self.mixer.remove_file(self) @@ -243,7 +244,7 @@ class MusicFile: if wait: self.mapping.add_wait(self.wait_event, wait_id=set_wait_id) self.wait_end() - else: + elif self.is_loaded(allow_substates=True): self.stopped() def abandon_all_effects(self): @@ -274,21 +275,19 @@ class MusicFile: self.wait_event.clear() self.wait_event.wait() - # Let other subscribe for an event when they are ready - def subscribe_loaded(self, callback): - # FIXME: should lock to be sure we have no race, but it makes the - # initialization screen not showing until everything is loaded - if self.is_loaded(allow_substates=True): - callback(True) - elif self.is_failed(): - callback(False) - else: - self.loaded_callbacks.append(callback) + # Let other subscribe for state change + def notify_state_change(self, **kwargs): + for callback in self.state_change_callbacks: + callback(self.state) + + def subscribe_state_change(self, callback): + if callback not in self.state_change_callbacks: + self.state_change_callbacks.append(callback) + callback(self.state) - def poll_loaded(self): - for callback in self.loaded_callbacks: - callback(self.is_loaded()) - self.loaded_callbacks = [] + def unsubscribe_state_change(self, callback): + if callback in self.state_change_callbacks: + self.state_change_callbacks.remove(callback) # Callbacks def finished_callback(self): -- cgit v1.2.3 From 269c6bdaba62b79bb832d3e1cc516fff013a7dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Mon, 19 Sep 2016 19:09:29 +0200 Subject: Disable dead keys and enable capslock --- music_sampler/music_sampler.kv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/music_sampler/music_sampler.kv b/music_sampler/music_sampler.kv index 0432e14..839d2ce 100644 --- a/music_sampler/music_sampler.kv +++ b/music_sampler/music_sampler.kv @@ -611,6 +611,8 @@ key_sym: "^" row: 3 col: 12.5 + line_width: 1 + enabled: False Key: id: Key_36 key_code: 36 @@ -632,8 +634,6 @@ row: 4 col: 1 key_width: 1.75 - line_width: 1 - enabled: False Key: id: Key_113 -- cgit v1.2.3 From f9aeecf1a00e0e632546db00cb0cfa31b078dbe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Mon, 19 Sep 2016 19:58:54 +0200 Subject: Don't lock the application when failing while reloading configuration --- music_sampler/mapping.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/music_sampler/mapping.py b/music_sampler/mapping.py index 5c61f8a..a526ad2 100644 --- a/music_sampler/mapping.py +++ b/music_sampler/mapping.py @@ -72,9 +72,9 @@ class Mapping(RelativeLayout): 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: @@ -84,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: @@ -118,7 +118,7 @@ class Mapping(RelativeLayout): self.leave_application() sys.exit() elif 'ctrl' in modifiers and keycode[0] == 114 and self.is_loaded(): - self.reload() + self.reload(initial=False) return True def leave_application(self): @@ -348,13 +348,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'] -- cgit v1.2.3