aboutsummaryrefslogtreecommitdiff
path: root/helpers/mapping.py
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2016-07-27 21:33:09 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2016-07-27 21:33:09 +0200
commit63ba5a8dc2aa4ec3e6f203b0ba4db249ecf0b00e (patch)
treef043ab215425aca3434bf178029dad9f9e62dbe9 /helpers/mapping.py
parent35bde798b6cda13579337b0ec5a803fdd5eab19a (diff)
downloadMusicSampler-63ba5a8dc2aa4ec3e6f203b0ba4db249ecf0b00e.tar.gz
MusicSampler-63ba5a8dc2aa4ec3e6f203b0ba4db249ecf0b00e.tar.zst
MusicSampler-63ba5a8dc2aa4ec3e6f203b0ba4db249ecf0b00e.zip
Rename helpers to music_sampler
Diffstat (limited to 'helpers/mapping.py')
-rw-r--r--helpers/mapping.py399
1 files changed, 0 insertions, 399 deletions
diff --git a/helpers/mapping.py b/helpers/mapping.py
deleted file mode 100644
index bb20e67..0000000
--- a/helpers/mapping.py
+++ /dev/null
@@ -1,399 +0,0 @@
1from kivy.uix.relativelayout import RelativeLayout
2from kivy.properties import NumericProperty, ListProperty, StringProperty
3from kivy.core.window import Window
4from kivy.clock import Clock
5
6import threading
7import yaml
8import sys
9from collections import defaultdict
10
11from transitions.extensions import HierarchicalMachine as Machine
12
13from .music_file import MusicFile
14from .mixer import Mixer
15from . import Config, gain, error_print, warn_print
16from .action import Action
17
18class Mapping(RelativeLayout):
19 STATES = [
20 'initial',
21 'configuring',
22 'configured',
23 'loading',
24 'loaded',
25 'failed'
26 ]
27
28 TRANSITIONS = [
29 {
30 'trigger': 'configure',
31 'source': 'initial',
32 'dest': 'configuring'
33 },
34 {
35 'trigger': 'fail',
36 'source': 'configuring',
37 'dest': 'failed'
38 },
39 {
40 'trigger': 'success',
41 'source': 'configuring',
42 'dest': 'configured',
43 'after': 'load'
44 },
45 {
46 'trigger': 'load',
47 'source': 'configured',
48 'dest': 'loading'
49 },
50 {
51 'trigger': 'fail',
52 'source': 'loading',
53 'dest': 'failed'
54 },
55 {
56 'trigger': 'success',
57 'source': 'loading',
58 'dest': 'loaded'
59 },
60 {
61 'trigger': 'reload',
62 'source': 'loaded',
63 'dest': 'configuring'
64 }
65 ]
66
67 master_volume = NumericProperty(100)
68 ready_color = ListProperty([1, 165/255, 0, 1])
69 state = StringProperty("")
70
71 def __init__(self, **kwargs):
72 self.keys = []
73 self.running = []
74 self.wait_ids = {}
75 self.open_files = {}
76
77 Machine(model=self, states=self.STATES,
78 transitions=self.TRANSITIONS, initial='initial',
79 ignore_invalid_triggers=True, queued=True)
80 super(Mapping, self).__init__(**kwargs)
81 self.keyboard = Window.request_keyboard(self.on_keyboard_closed, self)
82 self.keyboard.bind(on_key_down=self.on_keyboard_down)
83
84 self.configure()
85
86 def on_enter_configuring(self):
87 if Config.builtin_mixing:
88 self.mixer = Mixer()
89 else:
90 self.mixer = None
91
92 try:
93 self.key_config, self.open_files = self.parse_config()
94 except Exception as e:
95 error_print("Error while loading configuration: {}".format(e),
96 with_trace=True)
97 sys.exit()
98 else:
99 self.success()
100
101 def on_enter_loading(self):
102 for key in self.keys:
103 key.reload()
104 self.success()
105
106 # Kivy events
107 def add_widget(self, widget, index=0):
108 if type(widget).__name__ == "Key" and widget not in self.keys:
109 self.keys.append(widget)
110 return super(Mapping, self).add_widget(widget, index)
111
112 def remove_widget(self, widget, index=0):
113 if type(widget).__name__ == "Key" and widget in self.keys:
114 self.keys.remove(widget)
115 return super(Mapping, self).remove_widget(widget, index)
116
117 def on_keyboard_closed(self):
118 self.keyboard.unbind(on_key_down=self.on_keyboard_down)
119 self.keyboard = None
120
121 def on_keyboard_down(self, keyboard, keycode, text, modifiers):
122 key = self.find_by_key_code(keycode)
123 if self.allowed_modifiers(modifiers) and key is not None:
124 modifiers.sort()
125 threading.Thread(name="MSKeyAction", target=key.run,
126 args=['-'.join(modifiers)]).start()
127 elif 'ctrl' in modifiers and (keycode[0] == 113 or keycode[0] == '99'):
128 self.stop_all_running()
129 for thread in threading.enumerate():
130 if thread.getName()[0:2] != "MS":
131 continue
132 thread.join()
133
134 sys.exit()
135 elif 'ctrl' in modifiers and keycode[0] == 114:
136 threading.Thread(name="MSReload", target=self.reload).start()
137 return True
138
139 # Helpers
140 def allowed_modifiers(self, modifiers):
141 allowed = []
142 return len([a for a in modifiers if a not in allowed]) == 0
143
144 def find_by_key_code(self, key_code):
145 if "Key_" + str(key_code[0]) in self.ids:
146 return self.ids["Key_" + str(key_code[0])]
147 return None
148
149 def all_keys_ready(self):
150 partial = False
151 for key in self.keys:
152 if not key.is_loaded_or_failed():
153 return "not_ready"
154 partial = partial or key.is_failed()
155
156 if partial:
157 return "partial"
158 else:
159 return "success"
160
161 # Callbacks
162 def key_loaded_callback(self):
163 result = self.all_keys_ready()
164 if result == "success":
165 self.ready_color = [0, 1, 0, 1]
166 elif result == "partial":
167 self.ready_color = [1, 0, 0, 1]
168 else:
169 self.ready_color = [1, 165/255, 0, 1]
170
171 ## Some global actions
172 def stop_all_running(self, except_key=None, key_start_time=0):
173 running = self.running
174 self.running = [r for r in running\
175 if r[0] == except_key and r[1] == key_start_time]
176 for (key, start_time) in running:
177 if (key, start_time) != (except_key, key_start_time):
178 key.interrupt()
179
180 # Master volume methods
181 @property
182 def master_gain(self):
183 return gain(self.master_volume)
184
185 def set_master_volume(self, value, delta=False, fade=0):
186 [db_gain, self.master_volume] = gain(
187 value + int(delta) * self.master_volume,
188 self.master_volume)
189
190 for music in self.open_files.values():
191 music.set_gain_with_effect(db_gain, fade=fade)
192
193 # Wait handler methods
194 def add_wait_id(self, wait_id, action_or_wait):
195 self.wait_ids[wait_id] = action_or_wait
196
197 def interrupt_wait(self, wait_id):
198 if wait_id in self.wait_ids:
199 action_or_wait = self.wait_ids[wait_id]
200 del(self.wait_ids[wait_id])
201 if isinstance(action_or_wait, Action):
202 action_or_wait.interrupt()
203 else:
204 action_or_wait.set()
205
206 # Methods to control running keys
207 def start_running(self, key, start_time):
208 self.running.append((key, start_time))
209
210 def keep_running(self, key, start_time):
211 return (key, start_time) in self.running
212
213 def finished_running(self, key, start_time):
214 if (key, start_time) in self.running:
215 self.running.remove((key, start_time))
216
217 # YML config parser
218 def parse_config(self):
219 def update_alias(prop_hash, aliases, key):
220 if isinstance(aliases[key], dict):
221 prop_hash.update(aliases[key], **prop_hash)
222 else:
223 warn_print("Alias {} is not a hash, ignored".format(key))
224
225 def include_aliases(prop_hash, aliases):
226 if 'include' not in prop_hash:
227 return
228
229 included = prop_hash['include']
230 del(prop_hash['include'])
231 if isinstance(included, str):
232 update_alias(prop_hash, aliases, included)
233 elif isinstance(included, list):
234 for included_ in included:
235 if isinstance(included_, str):
236 update_alias(prop_hash, aliases, included_)
237 else:
238 warn_print("Unkown alias include type, ignored: "
239 "{} in {}".format(included_, included))
240 else:
241 warn_print("Unkown alias include type, ignored: {}"
242 .format(included))
243
244 def check_key_property(key_property, key):
245 if 'description' in key_property:
246 desc = key_property['description']
247 if not isinstance(desc, list):
248 warn_print("description in key_property '{}' is not "
249 "a list, ignored".format(key))
250 del(key_property['description'])
251 if 'color' in key_property:
252 color = key_property['color']
253 if not isinstance(color, list)\
254 or len(color) != 3\
255 or not all(isinstance(item, int) for item in color)\
256 or any(item < 0 or item > 255 for item in color):
257 warn_print("color in key_property '{}' is not "
258 "a list of 3 valid integers, ignored".format(key))
259 del(key_property['color'])
260
261 def check_key_properties(config):
262 if 'key_properties' in config:
263 if isinstance(config['key_properties'], dict):
264 return config['key_properties']
265 else:
266 warn_print("key_properties config is not a hash, ignored")
267 return {}
268 else:
269 return {}
270
271 def check_mapped_keys(config):
272 if 'keys' in config:
273 if isinstance(config['keys'], dict):
274 return config['keys']
275 else:
276 warn_print("keys config is not a hash, ignored")
277 return {}
278 else:
279 return {}
280
281 def check_mapped_key(mapped_keys, key):
282 if not isinstance(mapped_keys[key], list):
283 warn_print("key config '{}' is not an array, ignored"
284 .format(key))
285 return []
286 else:
287 return mapped_keys[key]
288
289 def check_music_property(music_property, filename):
290 if not isinstance(music_property, dict):
291 warn_print("music_property config '{}' is not a hash, ignored"
292 .format(filename))
293 return {}
294 if 'name' in music_property:
295 music_property['name'] = str(music_property['name'])
296 if 'gain' in music_property:
297 try:
298 music_property['gain'] = float(music_property['gain'])
299 except ValueError as e:
300 del(music_property['gain'])
301 warn_print("gain for music_property '{}' is not "
302 "a float, ignored".format(filename))
303 return music_property
304
305 stream = open(Config.yml_file, "r")
306 try:
307 config = yaml.safe_load(stream)
308 except Exception as e:
309 error_print("Error while loading config file: {}".format(e))
310 sys.exit()
311 stream.close()
312
313 if not isinstance(config, dict):
314 raise Exception("Top level config is supposed to be a hash")
315
316 if 'aliases' in config and isinstance(config['aliases'], dict):
317 aliases = config['aliases']
318 else:
319 aliases = defaultdict(dict)
320 if 'aliases' in config:
321 warn_print("aliases config is not a hash, ignored")
322
323 music_properties = defaultdict(dict)
324 if 'music_properties' in config and\
325 isinstance(config['music_properties'], dict):
326 music_properties.update(config['music_properties'])
327 elif 'music_properties' in config:
328 warn_print("music_properties config is not a hash, ignored")
329
330 seen_files = {}
331
332 key_properties = defaultdict(lambda: {
333 "actions": [],
334 "properties": {},
335 "files": []
336 })
337
338 for key in check_key_properties(config):
339 key_prop = config['key_properties'][key]
340
341 if not isinstance(key_prop, dict):
342 warn_print("key_property '{}' is not a hash, ignored"
343 .format(key))
344 continue
345
346 include_aliases(key_prop, aliases)
347 check_key_property(key_prop, key)
348
349 key_properties[key]["properties"] = key_prop
350
351 for mapped_key in check_mapped_keys(config):
352 for index, action in enumerate(check_mapped_key(
353 config['keys'], mapped_key)):
354 if not isinstance(action, dict) or\
355 not len(action) == 1 or\
356 not isinstance(list(action.values())[0] or {}, dict):
357 warn_print("action number {} of key '{}' is invalid, "
358 "ignored".format(index + 1, mapped_key))
359 continue
360
361 action_name = list(action)[0]
362 action_args = {}
363 if action[action_name] is None:
364 action[action_name] = {}
365
366 include_aliases(action[action_name], aliases)
367
368 for argument in action[action_name]:
369 if argument == 'file':
370 filename = str(action[action_name]['file'])
371 if filename not in seen_files:
372 music_property = check_music_property(
373 music_properties[filename],
374 filename)
375
376 if filename in self.open_files:
377 self.open_files[filename]\
378 .reload_properties(**music_property)
379
380 seen_files[filename] =\
381 self.open_files[filename]
382 else:
383 seen_files[filename] = MusicFile(
384 filename, self, **music_property)
385
386 if filename not in key_properties[mapped_key]['files']:
387 key_properties[mapped_key]['files'] \
388 .append(seen_files[filename])
389
390 action_args['music'] = seen_files[filename]
391 else:
392 action_args[argument] = action[action_name][argument]
393
394 key_properties[mapped_key]['actions'] \
395 .append([action_name, action_args])
396
397 return (key_properties, seen_files)
398
399