]>
git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/blob - helpers/mapping.py
1f63459adbf76ca206788ba405cc2e448a0198a3
1 from kivy
.uix
.relativelayout
import RelativeLayout
2 from kivy
.properties
import NumericProperty
, ListProperty
3 from kivy
.core
.window
import Window
4 from kivy
.clock
import Clock
9 from collections
import defaultdict
11 from .music_file
import MusicFile
12 from .mixer
import Mixer
13 from . import Config
, gain
, error_print
, warn_print
14 from .action
import Action
16 class Mapping(RelativeLayout
):
17 expected_keys
= NumericProperty(0)
18 master_volume
= NumericProperty(100)
19 ready_color
= ListProperty([1, 165/255, 0, 1])
21 def __init__(self
, **kwargs
):
22 if Config
.builtin_mixing
:
28 self
.key_config
, self
.open_files
= self
.parse_config()
29 except Exception as e
:
30 error_print("Error while loading configuration: {}".format(e
),
34 super(Mapping
, self
).__init
__(**kwargs
)
35 self
._keyboard
= Window
.request_keyboard(self
._keyboard
_closed
, self
)
36 self
._keyboard
.bind(on_key_down
=self
._on
_keyboard
_down
)
39 Clock
.schedule_interval(self
.not_all_keys_ready
, 1)
42 def master_gain(self
):
43 return gain(self
.master_volume
)
45 def set_master_volume(self
, value
, delta
=False, fade
=0):
46 [db_gain
, self
.master_volume
] = gain(
47 value
+ int(delta
) * self
.master_volume
,
50 for music
in self
.open_files
.values():
51 music
.set_gain_with_effect(db_gain
, fade
=fade
)
53 def add_wait_id(self
, wait_id
, action_or_wait
):
54 self
.wait_ids
[wait_id
] = action_or_wait
56 def interrupt_wait(self
, wait_id
):
57 if wait_id
in self
.wait_ids
:
58 action_or_wait
= self
.wait_ids
[wait_id
]
59 del(self
.wait_ids
[wait_id
])
60 if isinstance(action_or_wait
, Action
):
61 action_or_wait
.interrupt()
65 def _keyboard_closed(self
):
66 self
._keyboard
.unbind(on_key_down
=self
._on
_keyboard
_down
)
69 def _on_keyboard_down(self
, keyboard
, keycode
, text
, modifiers
):
70 key
= self
.find_by_key_code(keycode
)
71 if len(modifiers
) == 0 and key
is not None:
72 threading
.Thread(name
="MSKeyAction", target
=key
.run
).start()
73 elif 'ctrl' in modifiers
and (keycode
[0] == 113 or keycode
[0] == '99'):
74 for thread
in threading
.enumerate():
75 if thread
.getName()[0:2] != "MS":
82 def find_by_key_code(self
, key_code
):
83 if "Key_" + str(key_code
[0]) in self
.ids
:
84 return self
.ids
["Key_" + str(key_code
[0])]
87 def not_all_keys_ready(self
, dt
):
88 for key
in self
.children
:
89 if not type(key
).__name
__ == "Key":
91 if not key
.is_loaded_or_failed():
93 self
.ready_color
= [0, 1, 0, 1]
96 def stop_all_running(self
):
97 running
= self
.running
99 for (key
, start_time
) in running
:
102 def start_running(self
, key
, start_time
):
103 self
.running
.append((key
, start_time
))
105 def keep_running(self
, key
, start_time
):
106 return (key
, start_time
) in self
.running
108 def finished_running(self
, key
, start_time
):
109 if (key
, start_time
) in self
.running
:
110 self
.running
.remove((key
, start_time
))
112 def parse_config(self
):
113 def update_alias(prop_hash
, aliases
, key
):
114 if isinstance(aliases
[key
], dict):
115 prop_hash
.update(aliases
[key
], **prop_hash
)
117 warn_print("Alias {} is not a hash, ignored".format(key
))
119 def include_aliases(prop_hash
, aliases
):
120 if 'include' not in prop_hash
:
123 included
= prop_hash
['include']
124 del(prop_hash
['include'])
125 if isinstance(included
, str):
126 update_alias(prop_hash
, aliases
, included
)
127 elif isinstance(included
, list):
128 for included_
in included
:
129 if isinstance(included_
, str):
130 update_alias(prop_hash
, aliases
, included_
)
132 warn_print("Unkown alias include type, ignored: "
133 "{} in {}".format(included_
, included
))
135 warn_print("Unkown alias include type, ignored: {}"
138 def check_key_property(key_property
, key
):
139 if 'description' in key_property
:
140 desc
= key_property
['description']
141 if not isinstance(desc
, list):
142 warn_print("description in key_property '{}' is not "
143 "a list, ignored".format(key
))
144 del(key_property
['description'])
145 if 'color' in key_property
:
146 color
= key_property
['color']
147 if not isinstance(color
, list)\
149 or not all(isinstance(item
, int) for item
in color
)\
150 or any(item
< 0 or item
> 255 for item
in color
):
151 warn_print("color in key_property '{}' is not "
152 "a list of 3 valid integers, ignored".format(key
))
153 del(key_property
['color'])
155 def check_key_properties(config
):
156 if 'key_properties' in config
:
157 if isinstance(config
['key_properties'], dict):
158 return config
['key_properties']
160 warn_print("key_properties config is not a hash, ignored")
165 def check_mapped_keys(config
):
167 if isinstance(config
['keys'], dict):
168 return config
['keys']
170 warn_print("keys config is not a hash, ignored")
175 def check_mapped_key(mapped_keys
, key
):
176 if not isinstance(mapped_keys
[key
], list):
177 warn_print("key config '{}' is not an array, ignored"
181 return mapped_keys
[key
]
183 def check_music_property(music_property
, filename
):
184 if not isinstance(music_property
, dict):
185 warn_print("music_property config '{}' is not a hash, ignored"
188 if 'name' in music_property
:
189 music_property
['name'] = str(music_property
['name'])
190 if 'gain' in music_property
:
192 music_property
['gain'] = float(music_property
['gain'])
193 except ValueError as e
:
194 del(music_property
['gain'])
195 warn_print("gain for music_property '{}' is not "
196 "a float, ignored".format(filename
))
197 return music_property
199 stream
= open(Config
.yml_file
, "r")
201 config
= yaml
.safe_load(stream
)
202 except Exception as e
:
203 error_print("Error while loading config file: {}".format(e
))
207 if not isinstance(config
, dict):
208 raise Exception("Top level config is supposed to be a hash")
210 if 'aliases' in config
and isinstance(config
['aliases'], dict):
211 aliases
= config
['aliases']
213 aliases
= defaultdict(dict)
214 if 'aliases' in config
:
215 warn_print("aliases config is not a hash, ignored")
217 music_properties
= defaultdict(dict)
218 if 'music_properties' in config
and\
219 isinstance(config
['music_properties'], dict):
220 music_properties
.update(config
['music_properties'])
221 elif 'music_properties' in config
:
222 warn_print("music_properties config is not a hash, ignored")
226 key_properties
= defaultdict(lambda: {
232 for key
in check_key_properties(config
):
233 key_prop
= config
['key_properties'][key
]
235 if not isinstance(key_prop
, dict):
236 warn_print("key_property '{}' is not a hash, ignored"
240 include_aliases(key_prop
, aliases
)
241 check_key_property(key_prop
, key
)
243 key_properties
[key
]["properties"] = key_prop
245 for mapped_key
in check_mapped_keys(config
):
246 for index
, action
in enumerate(check_mapped_key(
247 config
['keys'], mapped_key
)):
248 if not isinstance(action
, dict) or\
249 not len(action
) == 1 or\
250 not isinstance(list(action
.values())[0] or {}, dict):
251 warn_print("action number {} of key '{}' is invalid, "
252 "ignored".format(index
+ 1, mapped_key
))
255 action_name
= list(action
)[0]
257 if action
[action_name
] is None:
258 action
[action_name
] = {}
260 include_aliases(action
[action_name
], aliases
)
262 for argument
in action
[action_name
]:
263 if argument
== 'file':
264 filename
= str(action
[action_name
]['file'])
265 if filename
not in seen_files
:
266 music_property
= check_music_property(
267 music_properties
[filename
],
270 seen_files
[filename
] = MusicFile(
271 filename
, self
, **music_property
)
273 if filename
not in key_properties
[mapped_key
]['files']:
274 key_properties
[mapped_key
]['files'] \
275 .append(seen_files
[filename
])
277 action_args
['music'] = seen_files
[filename
]
279 action_args
[argument
] = action
[action_name
][argument
]
281 key_properties
[mapped_key
]['actions'] \
282 .append([action_name
, action_args
])
284 return (key_properties
, seen_files
)