]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/blob - helpers/mapping.py
Add interrupt_wait action
[perso/Immae/Projets/Python/MusicSampler.git] / helpers / mapping.py
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
5
6 import threading
7 import yaml
8 import sys
9
10 from .music_file import *
11 from .mixer import Mixer
12 from . import Config, gain, error_print
13 from .music_effect import GainEffect
14 from .action import Action
15
16 class Mapping(RelativeLayout):
17 expected_keys = NumericProperty(0)
18 master_volume = NumericProperty(100)
19 ready_color = ListProperty([1, 165/255, 0, 1])
20
21 def __init__(self, **kwargs):
22 if Config.builtin_mixing:
23 self.mixer = Mixer()
24 else:
25 self.mixer = None
26 self.key_config, self.open_files = self.parse_config()
27 super(Mapping, self).__init__(**kwargs)
28 self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
29 self._keyboard.bind(on_key_down=self._on_keyboard_down)
30 self.running = []
31 self.wait_ids = {}
32 Clock.schedule_interval(self.not_all_keys_ready, 1)
33
34 @property
35 def master_gain(self):
36 return gain(self.master_volume)
37
38 def set_master_volume(self, value, delta=False, fade=0):
39 [db_gain, self.master_volume] = gain(
40 value + int(delta) * self.master_volume,
41 self.master_volume)
42
43 for music in self.open_files.values():
44 if not (music.is_loaded_playing() or music.is_loaded_paused()):
45 continue
46
47 if fade > 0:
48 music.gain_effects.append(GainEffect(
49 "fade",
50 music.current_audio_segment,
51 music.current_loop,
52 music.sound_position,
53 music.sound_position + fade,
54 gain=db_gain))
55 else:
56 music.set_gain(db_gain)
57
58 def add_wait_id(self, wait_id, action_or_wait):
59 self.wait_ids[wait_id] = action_or_wait
60
61 def interrupt_wait(self, wait_id):
62 if wait_id in self.wait_ids:
63 action_or_wait = self.wait_ids[wait_id]
64 del(self.wait_ids[wait_id])
65 if isinstance(action_or_wait, Action):
66 action_or_wait.interrupt()
67 else:
68 action_or_wait.set()
69
70 def _keyboard_closed(self):
71 self._keyboard.unbind(on_key_down=self._on_keyboard_down)
72 self._keyboard = None
73
74 def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
75 key = self.find_by_key_code(keycode)
76 if len(modifiers) == 0 and key is not None:
77 threading.Thread(name="MSKeyAction", target=key.do_actions).start()
78 elif 'ctrl' in modifiers and (keycode[0] == 113 or keycode[0] == '99'):
79 for thread in threading.enumerate():
80 if thread.getName()[0:2] != "MS":
81 continue
82 thread.join()
83
84 sys.exit()
85 return True
86
87 def find_by_key_code(self, key_code):
88 if "Key_" + str(key_code[0]) in self.ids:
89 return self.ids["Key_" + str(key_code[0])]
90 return None
91
92 def not_all_keys_ready(self, dt):
93 for key in self.children:
94 if not type(key).__name__ == "Key":
95 continue
96 if not key.is_key_ready:
97 return True
98 self.ready_color = [0, 1, 0, 1]
99 return False
100
101 def stop_all_running(self):
102 running = self.running
103 self.running = []
104 for (key, start_time) in running:
105 key.interrupt_action()
106
107 def start_running(self, key, start_time):
108 self.running.append((key, start_time))
109
110 def keep_running(self, key, start_time):
111 return (key, start_time) in self.running
112
113 def finished_running(self, key, start_time):
114 if (key, start_time) in self.running:
115 self.running.remove((key, start_time))
116
117 def parse_config(self):
118 stream = open(Config.yml_file, "r")
119 try:
120 config = yaml.load(stream)
121 except Exception as e:
122 error_print("Error while loading config file: {}".format(e))
123 sys.exit()
124 stream.close()
125
126 aliases = config['aliases']
127 seen_files = {}
128
129 key_properties = {}
130
131 for key in config['key_properties']:
132 if key not in key_properties:
133 key_prop = config['key_properties'][key]
134 if 'include' in key_prop:
135 included = key_prop['include']
136 del(key_prop['include'])
137
138 if isinstance(included, str):
139 key_prop.update(aliases[included], **key_prop)
140 else:
141 for included_ in included:
142 key_prop.update(aliases[included_], **key_prop)
143
144 key_properties[key] = {
145 "actions": [],
146 "properties": key_prop,
147 "files": []
148 }
149
150 for mapped_key in config['keys']:
151 if mapped_key not in key_properties:
152 key_properties[mapped_key] = {
153 "actions": [],
154 "properties": {},
155 "files": []
156 }
157 for action in config['keys'][mapped_key]:
158 action_name = list(action)[0]
159 action_args = {}
160 if action[action_name] is None:
161 action[action_name] = []
162
163 if 'include' in action[action_name]:
164 included = action[action_name]['include']
165 del(action[action_name]['include'])
166
167 if isinstance(included, str):
168 action[action_name].update(
169 aliases[included],
170 **action[action_name])
171 else:
172 for included_ in included:
173 action[action_name].update(
174 aliases[included_],
175 **action[action_name])
176
177 for argument in action[action_name]:
178 if argument == 'file':
179 filename = action[action_name]['file']
180 if filename not in seen_files:
181 if filename in config['music_properties']:
182 seen_files[filename] = MusicFile(
183 filename,
184 self,
185 **config['music_properties'][filename])
186 else:
187 seen_files[filename] = MusicFile(
188 self,
189 filename)
190
191 if filename not in key_properties[mapped_key]['files']:
192 key_properties[mapped_key]['files'] \
193 .append(seen_files[filename])
194
195 action_args['music'] = seen_files[filename]
196
197 else:
198 action_args[argument] = action[action_name][argument]
199
200 key_properties[mapped_key]['actions'] \
201 .append([action_name, action_args])
202
203 return (key_properties, seen_files)
204
205