diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2016-09-19 15:57:42 +0200 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2016-09-19 18:36:08 +0200 |
commit | 93a3e51e749afc0c3ba8488b900124fda6bb8774 (patch) | |
tree | 70cb772450af2b989c51a937e36c74275daedc02 | |
parent | 6dc040edf2f31497d4492c159397c4634037be66 (diff) | |
download | MusicSampler-93a3e51e749afc0c3ba8488b900124fda6bb8774.tar.gz MusicSampler-93a3e51e749afc0c3ba8488b900124fda6bb8774.tar.zst MusicSampler-93a3e51e749afc0c3ba8488b900124fda6bb8774.zip |
Cleanup key and action workflows
-rw-r--r-- | music_sampler/action.py | 70 | ||||
-rw-r--r-- | music_sampler/key.py | 35 | ||||
-rw-r--r-- | music_sampler/mapping.py | 34 | ||||
-rw-r--r-- | 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: | |||
9 | 'failed', | 9 | 'failed', |
10 | { | 10 | { |
11 | 'name': 'loaded', | 11 | 'name': 'loaded', |
12 | 'children': ['running'] | 12 | 'children': ['stopped', 'running'] |
13 | } | 13 | }, |
14 | 'destroyed' | ||
14 | ] | 15 | ] |
15 | 16 | ||
16 | TRANSITIONS = [ | 17 | TRANSITIONS = [ |
@@ -21,36 +22,42 @@ class Action: | |||
21 | }, | 22 | }, |
22 | { | 23 | { |
23 | 'trigger': 'fail', | 24 | 'trigger': 'fail', |
24 | 'source': 'loading', | 25 | 'source': ['loading', 'loaded'], |
25 | 'dest': 'failed', | 26 | 'dest': 'failed', |
26 | 'after': 'poll_loaded' | ||
27 | }, | 27 | }, |
28 | { | 28 | { |
29 | 'trigger': 'success', | 29 | 'trigger': 'success', |
30 | 'source': 'loading', | 30 | 'source': 'loading', |
31 | 'dest': 'loaded', | 31 | 'dest': 'loaded_stopped', |
32 | 'after': 'poll_loaded' | ||
33 | }, | 32 | }, |
34 | { | 33 | { |
35 | 'trigger': 'run', | 34 | 'trigger': 'reload', |
36 | 'source': 'loaded', | 35 | 'source': 'loaded', |
36 | 'dest': 'loading', | ||
37 | }, | ||
38 | { | ||
39 | 'trigger': 'run', | ||
40 | 'source': 'loaded_stopped', | ||
37 | 'dest': 'loaded_running', | 41 | 'dest': 'loaded_running', |
38 | 'after': 'finish_action', | 42 | 'after': 'finish_action', |
39 | # if a child has no transitions, then it is bubbled to the parent, | ||
40 | # and we don't want that. Not useful in that machine precisely. | ||
41 | 'conditions': ['is_loaded'] | ||
42 | }, | 43 | }, |
43 | { | 44 | { |
44 | 'trigger': 'finish_action', | 45 | 'trigger': 'finish_action', |
45 | 'source': 'loaded_running', | 46 | 'source': 'loaded_running', |
46 | 'dest': 'loaded' | 47 | 'dest': 'loaded_stopped' |
48 | }, | ||
49 | { | ||
50 | 'trigger': 'destroy', | ||
51 | 'source': '*', | ||
52 | 'dest': 'destroyed' | ||
47 | } | 53 | } |
48 | ] | 54 | ] |
49 | 55 | ||
50 | def __init__(self, action, key, **kwargs): | 56 | def __init__(self, action, key, **kwargs): |
51 | Machine(model=self, states=self.STATES, | 57 | Machine(model=self, states=self.STATES, |
52 | transitions=self.TRANSITIONS, initial='initial', | 58 | transitions=self.TRANSITIONS, initial='initial', |
53 | ignore_invalid_triggers=True, queued=True) | 59 | ignore_invalid_triggers=True, queued=True, |
60 | after_state_change=self.notify_state_change) | ||
54 | 61 | ||
55 | self.action = action | 62 | self.action = action |
56 | self.key = key | 63 | self.key = key |
@@ -62,18 +69,31 @@ class Action: | |||
62 | def is_loaded_or_failed(self): | 69 | def is_loaded_or_failed(self): |
63 | return self.is_loaded(allow_substates=True) or self.is_failed() | 70 | return self.is_loaded(allow_substates=True) or self.is_failed() |
64 | 71 | ||
65 | def callback_music_loaded(self, success): | 72 | def callback_music_state(self, new_state): |
66 | if success: | 73 | # If a music gets unloaded while the action is loaded_running and |
67 | self.success() | 74 | # depending on the music, it won't be able to do the finish_action. |
68 | else: | 75 | # Can that happen? |
76 | # a: play 'mp3'; | ||
77 | # z: wait 'mp3'; | ||
78 | # e: pause 'mp3'; | ||
79 | # r: stop 'mp3'; unload_music 'mp3' | ||
80 | if new_state == 'failed': | ||
69 | self.fail() | 81 | self.fail() |
82 | elif self.is_loaded(allow_substates=True) and\ | ||
83 | new_state in ['initial', 'loading']: | ||
84 | self.reload(reloading=True) | ||
85 | elif self.is_loading() and new_state.startswith('loaded_'): | ||
86 | self.success() | ||
70 | 87 | ||
71 | # Machine states / events | 88 | # Machine states / events |
72 | def on_enter_loading(self): | 89 | def on_enter_loading(self, reloading=False): |
90 | if reloading: | ||
91 | return | ||
73 | if hasattr(actions, self.action): | 92 | if hasattr(actions, self.action): |
74 | if 'music' in self.arguments: | 93 | if 'music' in self.arguments and\ |
75 | self.arguments['music'].subscribe_loaded( | 94 | self.action not in ['unload_music', 'load_music']: |
76 | self.callback_music_loaded) | 95 | self.arguments['music'].subscribe_state_change( |
96 | self.callback_music_state) | ||
77 | else: | 97 | else: |
78 | self.success() | 98 | self.success() |
79 | else: | 99 | else: |
@@ -86,9 +106,13 @@ class Action: | |||
86 | getattr(actions, self.action).run(self, | 106 | getattr(actions, self.action).run(self, |
87 | key_start_time=key_start_time, **self.arguments) | 107 | key_start_time=key_start_time, **self.arguments) |
88 | 108 | ||
89 | def poll_loaded(self): | 109 | def on_enter_destroyed(self): |
90 | self.key.callback_action_ready(self, | 110 | if 'music' in self.arguments: |
91 | self.is_loaded(allow_substates=True)) | 111 | self.arguments['music'].unsubscribe_state_change( |
112 | self.callback_music_state) | ||
113 | |||
114 | def notify_state_change(self, *args, **kwargs): | ||
115 | self.key.callback_action_state_changed() | ||
92 | 116 | ||
93 | # This one cannot be in the Machine state since it would be queued to run | 117 | # This one cannot be in the Machine state since it would be queued to run |
94 | # *after* the wait is ended... | 118 | # *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 | |||
13 | # https://github.com/kivy/kivy/wiki/Working-with-Python-threads-inside-a-Kivy-application | 13 | # https://github.com/kivy/kivy/wiki/Working-with-Python-threads-inside-a-Kivy-application |
14 | from kivy.clock import mainthread | 14 | from kivy.clock import mainthread |
15 | 15 | ||
16 | class KeyMachine(Widget): | 16 | class KeyMachine(): |
17 | STATES = [ | 17 | STATES = [ |
18 | 'initial', | 18 | 'initial', |
19 | 'configuring', | 19 | 'configuring', |
@@ -101,11 +101,15 @@ class KeyMachine(Widget): | |||
101 | { | 101 | { |
102 | 'trigger': 'repeat_protection_finished', | 102 | 'trigger': 'repeat_protection_finished', |
103 | 'source': 'loaded_protecting_repeat', | 103 | 'source': 'loaded_protecting_repeat', |
104 | 'dest': 'loaded' | 104 | 'dest': 'loaded', |
105 | 'after': 'callback_action_state_changed' | ||
105 | }, | 106 | }, |
106 | ] | 107 | ] |
107 | 108 | ||
108 | state = StringProperty("") | 109 | def __setattr__(self, name, value): |
110 | if hasattr(self, 'initialized') and name == 'state': | ||
111 | self.key.update_state(value) | ||
112 | super().__setattr__(name, value) | ||
109 | 113 | ||
110 | def __init__(self, key, **kwargs): | 114 | def __init__(self, key, **kwargs): |
111 | self.key = key | 115 | self.key = key |
@@ -113,7 +117,8 @@ class KeyMachine(Widget): | |||
113 | Machine(model=self, states=self.STATES, | 117 | Machine(model=self, states=self.STATES, |
114 | transitions=self.TRANSITIONS, initial='initial', | 118 | transitions=self.TRANSITIONS, initial='initial', |
115 | ignore_invalid_triggers=True, queued=True) | 119 | ignore_invalid_triggers=True, queued=True) |
116 | super(KeyMachine, self).__init__(**kwargs) | 120 | |
121 | self.initialized = True | ||
117 | 122 | ||
118 | # Machine states / events | 123 | # Machine states / events |
119 | def is_loaded_or_failed(self): | 124 | def is_loaded_or_failed(self): |
@@ -181,6 +186,17 @@ class KeyMachine(Widget): | |||
181 | def key_loaded_callback(self): | 186 | def key_loaded_callback(self): |
182 | self.key.parent.key_loaded_callback() | 187 | self.key.parent.key_loaded_callback() |
183 | 188 | ||
189 | def callback_action_state_changed(self): | ||
190 | if self.state not in ['failed', 'loading', 'loaded']: | ||
191 | return | ||
192 | |||
193 | if any(action.is_failed() for action in self.key.actions): | ||
194 | self.to_failed() | ||
195 | elif any(action.is_loading() for action in self.key.actions): | ||
196 | self.to_loading() | ||
197 | else: | ||
198 | self.to_loaded() | ||
199 | self.key_loaded_callback() | ||
184 | 200 | ||
185 | class Key(ButtonBehavior, Widget): | 201 | class Key(ButtonBehavior, Widget): |
186 | 202 | ||
@@ -244,14 +260,10 @@ class Key(ButtonBehavior, Widget): | |||
244 | else: | 260 | else: |
245 | raise AttributeError | 261 | raise AttributeError |
246 | 262 | ||
247 | def machine_state_changed(self, instance, machine_state): | ||
248 | self.machine_state = self.machine.state | ||
249 | |||
250 | def __init__(self, **kwargs): | 263 | def __init__(self, **kwargs): |
251 | self.actions = [] | 264 | self.actions = [] |
252 | self.current_action = None | 265 | self.current_action = None |
253 | self.machine = KeyMachine(self) | 266 | self.machine = KeyMachine(self) |
254 | self.machine.bind(state=self.machine_state_changed) | ||
255 | 267 | ||
256 | super(Key, self).__init__(**kwargs) | 268 | super(Key, self).__init__(**kwargs) |
257 | 269 | ||
@@ -272,13 +284,6 @@ class Key(ButtonBehavior, Widget): | |||
272 | def interrupt(self): | 284 | def interrupt(self): |
273 | self.current_action.interrupt() | 285 | self.current_action.interrupt() |
274 | 286 | ||
275 | # Callbacks | ||
276 | def callback_action_ready(self, action, success): | ||
277 | if not success: | ||
278 | self.fail() | ||
279 | elif all(action.is_loaded_or_failed() for action in self.actions): | ||
280 | self.success() | ||
281 | |||
282 | # Setters | 287 | # Setters |
283 | def set_description(self, description): | 288 | def set_description(self, description): |
284 | if description[0] is not None: | 289 | 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): | |||
22 | 'configuring', | 22 | 'configuring', |
23 | 'configured', | 23 | 'configured', |
24 | 'loading', | 24 | 'loading', |
25 | 'loaded', | 25 | 'loaded' |
26 | 'failed' | ||
27 | ] | 26 | ] |
28 | 27 | ||
29 | TRANSITIONS = [ | 28 | TRANSITIONS = [ |
@@ -33,11 +32,6 @@ class Mapping(RelativeLayout): | |||
33 | 'dest': 'configuring' | 32 | 'dest': 'configuring' |
34 | }, | 33 | }, |
35 | { | 34 | { |
36 | 'trigger': 'fail', | ||
37 | 'source': 'configuring', | ||
38 | 'dest': 'failed' | ||
39 | }, | ||
40 | { | ||
41 | 'trigger': 'success', | 35 | 'trigger': 'success', |
42 | 'source': 'configuring', | 36 | 'source': 'configuring', |
43 | 'dest': 'configured', | 37 | 'dest': 'configured', |
@@ -49,11 +43,6 @@ class Mapping(RelativeLayout): | |||
49 | 'dest': 'loading' | 43 | 'dest': 'loading' |
50 | }, | 44 | }, |
51 | { | 45 | { |
52 | 'trigger': 'fail', | ||
53 | 'source': 'loading', | ||
54 | 'dest': 'failed' | ||
55 | }, | ||
56 | { | ||
57 | 'trigger': 'success', | 46 | 'trigger': 'success', |
58 | 'source': 'loading', | 47 | 'source': 'loading', |
59 | 'dest': 'loaded' | 48 | 'dest': 'loaded' |
@@ -74,10 +63,11 @@ class Mapping(RelativeLayout): | |||
74 | self.running = [] | 63 | self.running = [] |
75 | self.wait_ids = {} | 64 | self.wait_ids = {} |
76 | self.open_files = {} | 65 | self.open_files = {} |
66 | self.is_leaving_application = False | ||
77 | 67 | ||
78 | Machine(model=self, states=self.STATES, | 68 | Machine(model=self, states=self.STATES, |
79 | transitions=self.TRANSITIONS, initial='initial', | 69 | transitions=self.TRANSITIONS, initial='initial', |
80 | ignore_invalid_triggers=True, queued=True) | 70 | auto_transitions=False, queued=True) |
81 | super(Mapping, self).__init__(**kwargs) | 71 | super(Mapping, self).__init__(**kwargs) |
82 | self.keyboard = Window.request_keyboard(self.on_keyboard_closed, self) | 72 | self.keyboard = Window.request_keyboard(self.on_keyboard_closed, self) |
83 | self.keyboard.bind(on_key_down=self.on_keyboard_down) | 73 | self.keyboard.bind(on_key_down=self.on_keyboard_down) |
@@ -127,13 +117,14 @@ class Mapping(RelativeLayout): | |||
127 | elif 'ctrl' in modifiers and (keycode[0] == 113 or keycode[0] == '99'): | 117 | elif 'ctrl' in modifiers and (keycode[0] == 113 or keycode[0] == '99'): |
128 | self.leave_application() | 118 | self.leave_application() |
129 | sys.exit() | 119 | sys.exit() |
130 | elif 'ctrl' in modifiers and keycode[0] == 114: | 120 | elif 'ctrl' in modifiers and keycode[0] == 114 and self.is_loaded(): |
131 | threading.Thread(name="MSReload", target=self.reload).start() | 121 | self.reload() |
132 | return True | 122 | return True |
133 | 123 | ||
134 | def leave_application(self): | 124 | def leave_application(self): |
135 | self.keyboard.unbind(on_key_down=self.on_keyboard_down) | 125 | self.keyboard.unbind(on_key_down=self.on_keyboard_down) |
136 | self.stop_all_running() | 126 | self.stop_all_running() |
127 | self.is_leaving_application = True | ||
137 | for music in self.open_files.values(): | 128 | for music in self.open_files.values(): |
138 | music.stop() | 129 | music.stop() |
139 | for thread in threading.enumerate(): | 130 | for thread in threading.enumerate(): |
@@ -167,13 +158,20 @@ class Mapping(RelativeLayout): | |||
167 | 158 | ||
168 | # Callbacks | 159 | # Callbacks |
169 | def key_loaded_callback(self): | 160 | def key_loaded_callback(self): |
161 | if hasattr(self, 'finished_loading'): | ||
162 | return | ||
163 | |||
164 | opacity = int(Config.load_all_musics) | ||
165 | |||
170 | result = self.all_keys_ready() | 166 | result = self.all_keys_ready() |
171 | if result == "success": | 167 | if result == "success": |
172 | self.ready_color = [0, 1, 0, 1] | 168 | self.ready_color = [0, 1, 0, opacity] |
169 | self.finished_loading = True | ||
173 | elif result == "partial": | 170 | elif result == "partial": |
174 | self.ready_color = [1, 0, 0, 1] | 171 | self.ready_color = [1, 0, 0, opacity] |
172 | self.finished_loading = True | ||
175 | else: | 173 | else: |
176 | self.ready_color = [1, 165/255, 0, 1] | 174 | self.ready_color = [1, 165/255, 0, opacity] |
177 | 175 | ||
178 | ## Some global actions | 176 | ## Some global actions |
179 | def stop_all_running(self, except_key=None, key_start_time=0): | 177 | 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: | |||
22 | { | 22 | { |
23 | 'name': 'loaded', | 23 | 'name': 'loaded', |
24 | 'children': [ | 24 | 'children': [ |
25 | 'stopped', | ||
25 | 'playing', | 26 | 'playing', |
26 | 'paused', | 27 | 'paused', |
27 | 'stopping' | 28 | 'stopping' |
@@ -31,9 +32,8 @@ class MusicFile: | |||
31 | TRANSITIONS = [ | 32 | TRANSITIONS = [ |
32 | { | 33 | { |
33 | 'trigger': 'load', | 34 | 'trigger': 'load', |
34 | 'source': 'initial', | 35 | 'source': ['initial', 'failed'], |
35 | 'dest': 'loading', | 36 | 'dest': 'loading' |
36 | 'after': 'poll_loaded' | ||
37 | }, | 37 | }, |
38 | { | 38 | { |
39 | 'trigger': 'fail', | 39 | 'trigger': 'fail', |
@@ -41,17 +41,19 @@ class MusicFile: | |||
41 | 'dest': 'failed' | 41 | 'dest': 'failed' |
42 | }, | 42 | }, |
43 | { | 43 | { |
44 | 'trigger': 'unload', | ||
45 | 'source': ['failed', 'loaded_stopped'], | ||
46 | 'dest': 'initial', | ||
47 | }, | ||
48 | { | ||
44 | 'trigger': 'success', | 49 | 'trigger': 'success', |
45 | 'source': 'loading', | 50 | 'source': 'loading', |
46 | 'dest': 'loaded' | 51 | 'dest': 'loaded_stopped' |
47 | }, | 52 | }, |
48 | { | 53 | { |
49 | 'trigger': 'start_playing', | 54 | 'trigger': 'start_playing', |
50 | 'source': 'loaded', | 55 | 'source': 'loaded_stopped', |
51 | 'dest': 'loaded_playing', | 56 | 'dest': 'loaded_playing' |
52 | # if a child has no transitions, then it is bubbled to the parent, | ||
53 | # and we don't want that. Not useful in that machine precisely. | ||
54 | 'conditions': ['is_loaded'] | ||
55 | }, | 57 | }, |
56 | { | 58 | { |
57 | 'trigger': 'pause', | 59 | 'trigger': 'pause', |
@@ -70,19 +72,20 @@ class MusicFile: | |||
70 | }, | 72 | }, |
71 | { | 73 | { |
72 | 'trigger': 'stopped', | 74 | 'trigger': 'stopped', |
73 | 'source': '*', | 75 | 'source': 'loaded', |
74 | 'dest': 'loaded', | 76 | 'dest': 'loaded_stopped', |
75 | 'before': 'trigger_stopped_events', | 77 | 'before': 'trigger_stopped_events', |
76 | 'conditions': ['is_in_use'] | 78 | 'unless': 'is_loaded_stopped', |
77 | } | 79 | } |
78 | ] | 80 | ] |
79 | 81 | ||
80 | def __init__(self, filename, mapping, name=None, gain=1): | 82 | def __init__(self, filename, mapping, name=None, gain=1): |
81 | Machine(model=self, states=self.STATES, | 83 | machine = Machine(model=self, states=self.STATES, |
82 | transitions=self.TRANSITIONS, initial='initial', | 84 | transitions=self.TRANSITIONS, initial='initial', |
83 | ignore_invalid_triggers=True) | 85 | auto_transitions=False, |
86 | after_state_change=self.notify_state_change) | ||
84 | 87 | ||
85 | self.loaded_callbacks = [] | 88 | self.state_change_callbacks = [] |
86 | self.mapping = mapping | 89 | self.mapping = mapping |
87 | self.filename = filename | 90 | self.filename = filename |
88 | self.name = name or filename | 91 | self.name = name or filename |
@@ -90,48 +93,41 @@ class MusicFile: | |||
90 | self.initial_volume_factor = gain | 93 | self.initial_volume_factor = gain |
91 | self.music_lock = Lock("music__" + filename) | 94 | self.music_lock = Lock("music__" + filename) |
92 | 95 | ||
93 | threading.Thread(name="MSMusicLoad", target=self.load).start() | 96 | if Config.load_all_musics: |
97 | threading.Thread(name="MSMusicLoad", target=self.load).start() | ||
94 | 98 | ||
95 | def reload_properties(self, name=None, gain=1): | 99 | def reload_properties(self, name=None, gain=1): |
96 | self.name = name or self.filename | 100 | self.name = name or self.filename |
97 | if gain != self.initial_volume_factor: | 101 | if gain != self.initial_volume_factor: |
98 | self.initial_volume_factor = gain | 102 | self.initial_volume_factor = gain |
99 | self.reload_music_file() | 103 | self.stopped() |
104 | self.unload() | ||
105 | self.load(reloading=True) | ||
100 | 106 | ||
101 | def reload_music_file(self): | 107 | # Machine related events |
102 | with file_lock: | 108 | def on_enter_initial(self): |
103 | try: | 109 | self.audio_segment = None |
104 | if self.filename.startswith("/"): | ||
105 | filename = self.filename | ||
106 | else: | ||
107 | filename = Config.music_path + self.filename | ||
108 | 110 | ||
109 | debug_print("Reloading « {} »".format(self.name)) | 111 | def on_enter_loading(self, reloading=False): |
110 | initial_db_gain = gain(self.initial_volume_factor * 100) | 112 | if reloading: |
111 | self.audio_segment = pydub.AudioSegment \ | 113 | prefix = 'Rel' |
112 | .from_file(filename) \ | 114 | prefix_s = 'rel' |
113 | .set_frame_rate(Config.frame_rate) \ | 115 | else: |
114 | .set_channels(Config.channels) \ | 116 | prefix = 'L' |
115 | .set_sample_width(Config.sample_width) \ | 117 | prefix_s = 'l' |
116 | .apply_gain(initial_db_gain) | ||
117 | except Exception as e: | ||
118 | error_print("failed to reload « {} »: {}"\ | ||
119 | .format(self.name, e)) | ||
120 | self.loading_error = e | ||
121 | self.to_failed() | ||
122 | else: | ||
123 | debug_print("Reloaded « {} »".format(self.name)) | ||
124 | 118 | ||
125 | # Machine related events | ||
126 | def on_enter_loading(self): | ||
127 | with file_lock: | 119 | with file_lock: |
120 | if self.mapping.is_leaving_application: | ||
121 | self.fail() | ||
122 | return | ||
123 | |||
128 | try: | 124 | try: |
129 | if self.filename.startswith("/"): | 125 | if self.filename.startswith("/"): |
130 | filename = self.filename | 126 | filename = self.filename |
131 | else: | 127 | else: |
132 | filename = Config.music_path + self.filename | 128 | filename = Config.music_path + self.filename |
133 | 129 | ||
134 | debug_print("Loading « {} »".format(self.name)) | 130 | debug_print("{}oading « {} »".format(prefix, self.name)) |
135 | self.mixer = self.mapping.mixer or Mixer() | 131 | self.mixer = self.mapping.mixer or Mixer() |
136 | initial_db_gain = gain(self.initial_volume_factor * 100) | 132 | initial_db_gain = gain(self.initial_volume_factor * 100) |
137 | self.audio_segment = pydub.AudioSegment \ | 133 | self.audio_segment = pydub.AudioSegment \ |
@@ -142,12 +138,13 @@ class MusicFile: | |||
142 | .apply_gain(initial_db_gain) | 138 | .apply_gain(initial_db_gain) |
143 | self.sound_duration = self.audio_segment.duration_seconds | 139 | self.sound_duration = self.audio_segment.duration_seconds |
144 | except Exception as e: | 140 | except Exception as e: |
145 | error_print("failed to load « {} »: {}".format(self.name, e)) | 141 | error_print("failed to {}oad « {} »: {}".format( |
142 | prefix_s, self.name, e)) | ||
146 | self.loading_error = e | 143 | self.loading_error = e |
147 | self.fail() | 144 | self.fail() |
148 | else: | 145 | else: |
149 | self.success() | 146 | self.success() |
150 | debug_print("Loaded « {} »".format(self.name)) | 147 | debug_print("{}oaded « {} »".format(prefix, self.name)) |
151 | 148 | ||
152 | def on_enter_loaded(self): | 149 | def on_enter_loaded(self): |
153 | self.cleanup() | 150 | self.cleanup() |
@@ -165,11 +162,15 @@ class MusicFile: | |||
165 | 162 | ||
166 | # Machine related states | 163 | # Machine related states |
167 | def is_in_use(self): | 164 | def is_in_use(self): |
168 | return self.is_loaded(allow_substates=True) and not self.is_loaded() | 165 | return self.is_loaded(allow_substates=True) and\ |
166 | not self.is_loaded_stopped() | ||
169 | 167 | ||
170 | def is_in_use_not_stopping(self): | 168 | def is_in_use_not_stopping(self): |
171 | return self.is_loaded_playing() or self.is_loaded_paused() | 169 | return self.is_loaded_playing() or self.is_loaded_paused() |
172 | 170 | ||
171 | def is_unloadable(self): | ||
172 | return self.is_loaded_stopped() or self.is_failed() | ||
173 | |||
173 | # Machine related triggers | 174 | # Machine related triggers |
174 | def trigger_stopped_events(self): | 175 | def trigger_stopped_events(self): |
175 | self.mixer.remove_file(self) | 176 | self.mixer.remove_file(self) |
@@ -243,7 +244,7 @@ class MusicFile: | |||
243 | if wait: | 244 | if wait: |
244 | self.mapping.add_wait(self.wait_event, wait_id=set_wait_id) | 245 | self.mapping.add_wait(self.wait_event, wait_id=set_wait_id) |
245 | self.wait_end() | 246 | self.wait_end() |
246 | else: | 247 | elif self.is_loaded(allow_substates=True): |
247 | self.stopped() | 248 | self.stopped() |
248 | 249 | ||
249 | def abandon_all_effects(self): | 250 | def abandon_all_effects(self): |
@@ -274,21 +275,19 @@ class MusicFile: | |||
274 | self.wait_event.clear() | 275 | self.wait_event.clear() |
275 | self.wait_event.wait() | 276 | self.wait_event.wait() |
276 | 277 | ||
277 | # Let other subscribe for an event when they are ready | 278 | # Let other subscribe for state change |
278 | def subscribe_loaded(self, callback): | 279 | def notify_state_change(self, **kwargs): |
279 | # FIXME: should lock to be sure we have no race, but it makes the | 280 | for callback in self.state_change_callbacks: |
280 | # initialization screen not showing until everything is loaded | 281 | callback(self.state) |
281 | if self.is_loaded(allow_substates=True): | 282 | |
282 | callback(True) | 283 | def subscribe_state_change(self, callback): |
283 | elif self.is_failed(): | 284 | if callback not in self.state_change_callbacks: |
284 | callback(False) | 285 | self.state_change_callbacks.append(callback) |
285 | else: | 286 | callback(self.state) |
286 | self.loaded_callbacks.append(callback) | ||
287 | 287 | ||
288 | def poll_loaded(self): | 288 | def unsubscribe_state_change(self, callback): |
289 | for callback in self.loaded_callbacks: | 289 | if callback in self.state_change_callbacks: |
290 | callback(self.is_loaded()) | 290 | self.state_change_callbacks.remove(callback) |
291 | self.loaded_callbacks = [] | ||
292 | 291 | ||
293 | # Callbacks | 292 | # Callbacks |
294 | def finished_callback(self): | 293 | def finished_callback(self): |