diff options
-rw-r--r-- | music_sampler/key.py | 156 |
1 files changed, 89 insertions, 67 deletions
diff --git a/music_sampler/key.py b/music_sampler/key.py index bdae5bd..534a3db 100644 --- a/music_sampler/key.py +++ b/music_sampler/key.py | |||
@@ -9,7 +9,7 @@ import time | |||
9 | import threading | 9 | import threading |
10 | from transitions.extensions import HierarchicalMachine as Machine | 10 | from transitions.extensions import HierarchicalMachine as Machine |
11 | 11 | ||
12 | class Key(ButtonBehavior, Widget): | 12 | class KeyMachine(Widget): |
13 | STATES = [ | 13 | STATES = [ |
14 | 'initial', | 14 | 'initial', |
15 | 'configuring', | 15 | 'configuring', |
@@ -101,11 +101,84 @@ class Key(ButtonBehavior, Widget): | |||
101 | }, | 101 | }, |
102 | ] | 102 | ] |
103 | 103 | ||
104 | state = StringProperty("") | ||
105 | |||
106 | def __init__(self, key, **kwargs): | ||
107 | self.key = key | ||
108 | |||
109 | Machine(model=self, states=self.STATES, | ||
110 | transitions=self.TRANSITIONS, initial='initial', | ||
111 | ignore_invalid_triggers=True, queued=True) | ||
112 | super(KeyMachine, self).__init__(**kwargs) | ||
113 | |||
114 | # Machine states / events | ||
115 | def is_loaded_or_failed(self): | ||
116 | return self.is_loaded(allow_substates=True) or self.is_failed() | ||
117 | |||
118 | def is_loaded_inactive(self): | ||
119 | return self.is_loaded_no_config() or self.is_loaded_no_actions() | ||
120 | |||
121 | def on_enter_configuring(self): | ||
122 | if self.key.key_sym in self.key.parent.key_config: | ||
123 | self.key.config = self.key.parent.key_config[self.key.key_sym] | ||
124 | |||
125 | self.key.actions = [] | ||
126 | for key_action in self.key.config['actions']: | ||
127 | self.key.add_action(key_action[0], **key_action[1]) | ||
128 | |||
129 | if 'description' in self.key.config['properties']: | ||
130 | self.key.set_description(self.key.config['properties']['description']) | ||
131 | else: | ||
132 | self.key.unset_description() | ||
133 | if 'color' in self.key.config['properties']: | ||
134 | self.key.set_color(self.key.config['properties']['color']) | ||
135 | else: | ||
136 | self.key.unset_color() | ||
137 | self.success() | ||
138 | else: | ||
139 | self.no_config() | ||
140 | |||
141 | def on_enter_loading(self): | ||
142 | if len(self.key.actions) > 0: | ||
143 | for action in self.key.actions: | ||
144 | action.load() | ||
145 | else: | ||
146 | self.no_actions() | ||
147 | |||
148 | def run_actions(self, modifiers): | ||
149 | self.key.parent.parent.ids['KeyList'].append(self.key.key_sym) | ||
150 | debug_print("running actions for {}".format(self.key.key_sym)) | ||
151 | start_time = time.time() | ||
152 | self.key.parent.start_running(self, start_time) | ||
153 | for self.key.current_action in self.key.actions: | ||
154 | if self.key.parent.keep_running(self, start_time): | ||
155 | self.key.list_actions() | ||
156 | self.key.current_action.run(start_time) | ||
157 | self.key.list_actions(last_action_finished=True) | ||
158 | |||
159 | self.key.parent.finished_running(self, start_time) | ||
160 | |||
161 | def on_enter_loaded_protecting_repeat(self, modifiers): | ||
162 | if 'repeat_delay' in self.key.config['properties']: | ||
163 | self.key.protecting_repeat_timer = threading.Timer( | ||
164 | self.key.config['properties']['repeat_delay'], | ||
165 | self.key.repeat_protection_finished) | ||
166 | self.key.protecting_repeat_timer.start() | ||
167 | else: | ||
168 | self.key.repeat_protection_finished() | ||
169 | |||
170 | # Callbacks | ||
171 | def key_loaded_callback(self): | ||
172 | self.key.parent.key_loaded_callback() | ||
173 | |||
174 | |||
175 | class Key(ButtonBehavior, Widget): | ||
176 | |||
104 | key_sym = StringProperty(None) | 177 | key_sym = StringProperty(None) |
105 | custom_color = ListProperty([0, 1, 0]) | 178 | custom_color = ListProperty([0, 1, 0]) |
106 | description_title = StringProperty("") | 179 | description_title = StringProperty("") |
107 | description = ListProperty([]) | 180 | description = ListProperty([]) |
108 | state = StringProperty("") | 181 | machine_state = StringProperty("") |
109 | 182 | ||
110 | def get_alias_line_cross_color(self): | 183 | def get_alias_line_cross_color(self): |
111 | if not self.is_failed() and ( | 184 | if not self.is_failed() and ( |
@@ -122,7 +195,7 @@ class Key(ButtonBehavior, Widget): | |||
122 | line_cross_color = AliasProperty( | 195 | line_cross_color = AliasProperty( |
123 | get_alias_line_cross_color, | 196 | get_alias_line_cross_color, |
124 | set_alias_line_cross_color, | 197 | set_alias_line_cross_color, |
125 | bind=['state']) | 198 | bind=['machine_state']) |
126 | 199 | ||
127 | def get_alias_line_color(self): | 200 | def get_alias_line_color(self): |
128 | if self.is_loaded_running(): | 201 | if self.is_loaded_running(): |
@@ -134,7 +207,7 @@ class Key(ButtonBehavior, Widget): | |||
134 | pass | 207 | pass |
135 | 208 | ||
136 | line_color = AliasProperty(get_alias_line_color, set_alias_line_color, | 209 | line_color = AliasProperty(get_alias_line_color, set_alias_line_color, |
137 | bind=['state']) | 210 | bind=['machine_state']) |
138 | 211 | ||
139 | def get_alias_color(self): | 212 | def get_alias_color(self): |
140 | if self.is_loaded_inactive(): | 213 | if self.is_loaded_inactive(): |
@@ -153,15 +226,23 @@ class Key(ButtonBehavior, Widget): | |||
153 | pass | 226 | pass |
154 | 227 | ||
155 | color = AliasProperty(get_alias_color, set_alias_color, | 228 | color = AliasProperty(get_alias_color, set_alias_color, |
156 | bind=['state', 'custom_color']) | 229 | bind=['machine_state', 'custom_color']) |
230 | |||
231 | def __getattr__(self, name): | ||
232 | if hasattr(self.machine, name): | ||
233 | return getattr(self.machine, name) | ||
234 | else: | ||
235 | raise AttributeError | ||
236 | |||
237 | def machine_state_changed(self, instance, machine_state): | ||
238 | self.machine_state = self.machine.state | ||
157 | 239 | ||
158 | def __init__(self, **kwargs): | 240 | def __init__(self, **kwargs): |
159 | self.actions = [] | 241 | self.actions = [] |
160 | self.current_action = None | 242 | self.current_action = None |
243 | self.machine = KeyMachine(self) | ||
244 | self.machine.bind(state=self.machine_state_changed) | ||
161 | 245 | ||
162 | Machine(model=self, states=self.STATES, | ||
163 | transitions=self.TRANSITIONS, initial='initial', | ||
164 | ignore_invalid_triggers=True, queued=True) | ||
165 | super(Key, self).__init__(**kwargs) | 246 | super(Key, self).__init__(**kwargs) |
166 | 247 | ||
167 | # Kivy events | 248 | # Kivy events |
@@ -172,71 +253,12 @@ class Key(ButtonBehavior, Widget): | |||
172 | def on_press(self): | 253 | def on_press(self): |
173 | self.list_actions() | 254 | self.list_actions() |
174 | 255 | ||
175 | # Machine states / events | ||
176 | def is_loaded_or_failed(self): | ||
177 | return self.is_loaded(allow_substates=True) or self.is_failed() | ||
178 | |||
179 | def is_loaded_inactive(self): | ||
180 | return self.is_loaded_no_config() or self.is_loaded_no_actions() | ||
181 | |||
182 | def on_enter_configuring(self): | ||
183 | if self.key_sym in self.parent.key_config: | ||
184 | self.config = self.parent.key_config[self.key_sym] | ||
185 | |||
186 | self.actions = [] | ||
187 | for key_action in self.config['actions']: | ||
188 | self.add_action(key_action[0], **key_action[1]) | ||
189 | |||
190 | if 'description' in self.config['properties']: | ||
191 | self.set_description(self.config['properties']['description']) | ||
192 | else: | ||
193 | self.unset_description() | ||
194 | if 'color' in self.config['properties']: | ||
195 | self.set_color(self.config['properties']['color']) | ||
196 | else: | ||
197 | self.unset_color() | ||
198 | self.success() | ||
199 | else: | ||
200 | self.no_config() | ||
201 | |||
202 | def on_enter_loading(self): | ||
203 | if len(self.actions) > 0: | ||
204 | for action in self.actions: | ||
205 | action.load() | ||
206 | else: | ||
207 | self.no_actions() | ||
208 | |||
209 | def run_actions(self, modifiers): | ||
210 | self.parent.parent.ids['KeyList'].append(self.key_sym) | ||
211 | debug_print("running actions for {}".format(self.key_sym)) | ||
212 | start_time = time.time() | ||
213 | self.parent.start_running(self, start_time) | ||
214 | for self.current_action in self.actions: | ||
215 | if self.parent.keep_running(self, start_time): | ||
216 | self.list_actions() | ||
217 | self.current_action.run(start_time) | ||
218 | self.list_actions(last_action_finished=True) | ||
219 | |||
220 | self.parent.finished_running(self, start_time) | ||
221 | |||
222 | def on_enter_loaded_protecting_repeat(self, modifiers): | ||
223 | if 'repeat_delay' in self.config['properties']: | ||
224 | self.protecting_repeat_timer = threading.Timer( | ||
225 | self.config['properties']['repeat_delay'], | ||
226 | self.repeat_protection_finished) | ||
227 | self.protecting_repeat_timer.start() | ||
228 | else: | ||
229 | self.repeat_protection_finished() | ||
230 | |||
231 | # This one cannot be in the Machine state since it would be queued to run | 256 | # This one cannot be in the Machine state since it would be queued to run |
232 | # *after* the loop is ended... | 257 | # *after* the loop is ended... |
233 | def interrupt(self): | 258 | def interrupt(self): |
234 | self.current_action.interrupt() | 259 | self.current_action.interrupt() |
235 | 260 | ||
236 | # Callbacks | 261 | # Callbacks |
237 | def key_loaded_callback(self): | ||
238 | self.parent.key_loaded_callback() | ||
239 | |||
240 | def callback_action_ready(self, action, success): | 262 | def callback_action_ready(self, action, success): |
241 | if not success: | 263 | if not success: |
242 | self.fail() | 264 | self.fail() |