aboutsummaryrefslogtreecommitdiff
path: root/music_sampler/key.py
diff options
context:
space:
mode:
Diffstat (limited to 'music_sampler/key.py')
-rw-r--r--music_sampler/key.py59
1 files changed, 39 insertions, 20 deletions
diff --git a/music_sampler/key.py b/music_sampler/key.py
index ce2f45b..e05bb16 100644
--- a/music_sampler/key.py
+++ b/music_sampler/key.py
@@ -9,7 +9,11 @@ import time
9import threading 9import threading
10from transitions.extensions import HierarchicalMachine as Machine 10from transitions.extensions import HierarchicalMachine as Machine
11 11
12class KeyMachine(Widget): 12# All drawing operations should happen in the main thread
13# https://github.com/kivy/kivy/wiki/Working-with-Python-threads-inside-a-Kivy-application
14from kivy.clock import mainthread
15
16class KeyMachine():
13 STATES = [ 17 STATES = [
14 'initial', 18 'initial',
15 'configuring', 19 'configuring',
@@ -97,11 +101,15 @@ class KeyMachine(Widget):
97 { 101 {
98 'trigger': 'repeat_protection_finished', 102 'trigger': 'repeat_protection_finished',
99 'source': 'loaded_protecting_repeat', 103 'source': 'loaded_protecting_repeat',
100 'dest': 'loaded' 104 'dest': 'loaded',
105 'after': 'callback_action_state_changed'
101 }, 106 },
102 ] 107 ]
103 108
104 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)
105 113
106 def __init__(self, key, **kwargs): 114 def __init__(self, key, **kwargs):
107 self.key = key 115 self.key = key
@@ -109,7 +117,8 @@ class KeyMachine(Widget):
109 Machine(model=self, states=self.STATES, 117 Machine(model=self, states=self.STATES,
110 transitions=self.TRANSITIONS, initial='initial', 118 transitions=self.TRANSITIONS, initial='initial',
111 ignore_invalid_triggers=True, queued=True) 119 ignore_invalid_triggers=True, queued=True)
112 super(KeyMachine, self).__init__(**kwargs) 120
121 self.initialized = True
113 122
114 # Machine states / events 123 # Machine states / events
115 def is_loaded_or_failed(self): 124 def is_loaded_or_failed(self):
@@ -118,22 +127,22 @@ class KeyMachine(Widget):
118 def is_loaded_inactive(self): 127 def is_loaded_inactive(self):
119 return self.is_loaded_no_config() or self.is_loaded_no_actions() 128 return self.is_loaded_no_config() or self.is_loaded_no_actions()
120 129
130 @mainthread
121 def on_enter_configuring(self): 131 def on_enter_configuring(self):
132 self.destroy_actions()
133 self.key.unset_description()
134 self.key.unset_color()
135
122 if self.key.key_sym in self.key.parent.key_config: 136 if self.key.key_sym in self.key.parent.key_config:
123 self.key.config = self.key.parent.key_config[self.key.key_sym] 137 self.key.config = self.key.parent.key_config[self.key.key_sym]
124 138
125 self.key.actions = []
126 for key_action in self.key.config['actions']: 139 for key_action in self.key.config['actions']:
127 self.key.add_action(key_action[0], **key_action[1]) 140 self.key.add_action(key_action[0], **key_action[1])
128 141
129 if 'description' in self.key.config['properties']: 142 if 'description' in self.key.config['properties']:
130 self.key.set_description(self.key.config['properties']['description']) 143 self.key.set_description(self.key.config['properties']['description'])
131 else:
132 self.key.unset_description()
133 if 'color' in self.key.config['properties']: 144 if 'color' in self.key.config['properties']:
134 self.key.set_color(self.key.config['properties']['color']) 145 self.key.set_color(self.key.config['properties']['color'])
135 else:
136 self.key.unset_color()
137 self.success() 146 self.success()
138 else: 147 else:
139 self.no_config() 148 self.no_config()
@@ -145,6 +154,11 @@ class KeyMachine(Widget):
145 else: 154 else:
146 self.no_actions() 155 self.no_actions()
147 156
157 def destroy_actions(self):
158 for action in self.key.actions:
159 action.destroy()
160 self.key.actions = []
161
148 def run_actions(self, modifiers): 162 def run_actions(self, modifiers):
149 self.key.parent.parent.ids['KeyList'].append(self.key.key_sym) 163 self.key.parent.parent.ids['KeyList'].append(self.key.key_sym)
150 debug_print("running actions for {}".format(self.key.key_sym)) 164 debug_print("running actions for {}".format(self.key.key_sym))
@@ -168,9 +182,21 @@ class KeyMachine(Widget):
168 self.key.repeat_protection_finished() 182 self.key.repeat_protection_finished()
169 183
170 # Callbacks 184 # Callbacks
185 @mainthread
171 def key_loaded_callback(self): 186 def key_loaded_callback(self):
172 self.key.parent.key_loaded_callback() 187 self.key.parent.key_loaded_callback()
173 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()
174 200
175class Key(ButtonBehavior, Widget): 201class Key(ButtonBehavior, Widget):
176 202
@@ -234,18 +260,18 @@ class Key(ButtonBehavior, Widget):
234 else: 260 else:
235 raise AttributeError 261 raise AttributeError
236 262
237 def machine_state_changed(self, instance, machine_state):
238 self.machine_state = self.machine.state
239
240 def __init__(self, **kwargs): 263 def __init__(self, **kwargs):
241 self.actions = [] 264 self.actions = []
242 self.current_action = None 265 self.current_action = None
243 self.machine = KeyMachine(self) 266 self.machine = KeyMachine(self)
244 self.machine.bind(state=self.machine_state_changed)
245 267
246 super(Key, self).__init__(**kwargs) 268 super(Key, self).__init__(**kwargs)
247 269
248 # Kivy events 270 # Kivy events
271 @mainthread
272 def update_state(self, value):
273 self.machine_state = value
274
249 def on_key_sym(self, key, key_sym): 275 def on_key_sym(self, key, key_sym):
250 if key_sym != "": 276 if key_sym != "":
251 self.configure() 277 self.configure()
@@ -258,13 +284,6 @@ class Key(ButtonBehavior, Widget):
258 def interrupt(self): 284 def interrupt(self):
259 self.current_action.interrupt() 285 self.current_action.interrupt()
260 286
261 # Callbacks
262 def callback_action_ready(self, action, success):
263 if not success:
264 self.fail()
265 elif all(action.is_loaded_or_failed() for action in self.actions):
266 self.success()
267
268 # Setters 287 # Setters
269 def set_description(self, description): 288 def set_description(self, description):
270 if description[0] is not None: 289 if description[0] is not None: