diff options
Diffstat (limited to 'keyboard.py')
-rw-r--r-- | keyboard.py | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/keyboard.py b/keyboard.py new file mode 100644 index 0000000..0c1115f --- /dev/null +++ b/keyboard.py | |||
@@ -0,0 +1,222 @@ | |||
1 | from kivy.app import App | ||
2 | from kivy.uix.widget import Widget | ||
3 | from kivy.uix.floatlayout import FloatLayout | ||
4 | from kivy.uix.relativelayout import RelativeLayout | ||
5 | from kivy.properties import AliasProperty, ReferenceListProperty, BooleanProperty, NumericProperty, ListProperty, StringProperty, ObjectProperty | ||
6 | from kivy.vector import Vector | ||
7 | from kivy.clock import Clock | ||
8 | from kivy.uix.behaviors import ButtonBehavior | ||
9 | from kivy.uix.label import Label | ||
10 | from kivy.core.window import Window | ||
11 | |||
12 | from helpers.action import * | ||
13 | import time | ||
14 | import sys | ||
15 | import math | ||
16 | import pygame | ||
17 | import helpers | ||
18 | import threading | ||
19 | |||
20 | class KeyDescription(Label): | ||
21 | pass | ||
22 | |||
23 | class Key(ButtonBehavior, Widget): | ||
24 | key_sym = StringProperty(None) | ||
25 | custom_color = ListProperty([0, 1, 0, 1]) | ||
26 | custom_unready_color = ListProperty([0, 1, 0, 100/255]) | ||
27 | description_title = StringProperty("") | ||
28 | description = ListProperty([]) | ||
29 | is_key_ready = BooleanProperty(True) | ||
30 | |||
31 | def get_color(self): | ||
32 | if not self.has_actions: | ||
33 | return [1, 1, 1, 1] | ||
34 | elif self.all_actions_ready: | ||
35 | return self.custom_color | ||
36 | else: | ||
37 | return self.custom_unready_color | ||
38 | def set_color(self): | ||
39 | pass | ||
40 | |||
41 | color = AliasProperty(get_color, set_color, bind=['is_key_ready']) | ||
42 | |||
43 | def __init__(self, **kwargs): | ||
44 | super(Key, self).__init__(**kwargs) | ||
45 | self.actions = [] | ||
46 | |||
47 | def on_key_sym(self, key, key_sym): | ||
48 | if key_sym in self.parent.key_config: | ||
49 | self.is_key_ready = False | ||
50 | |||
51 | self.config = self.parent.key_config[key_sym] | ||
52 | |||
53 | self.actions = [] | ||
54 | for key_action in self.config['actions']: | ||
55 | self.add_action(key_action[0], **key_action[1]) | ||
56 | |||
57 | if 'description' in self.config['properties']: | ||
58 | key.set_description(self.config['properties']['description']) | ||
59 | if 'color' in self.config['properties']: | ||
60 | key.set_color(self.config['properties']['color']) | ||
61 | |||
62 | Clock.schedule_interval(self.check_all_active, 1) | ||
63 | |||
64 | def check_all_active(self, dt): | ||
65 | if self.all_actions_ready: | ||
66 | self.is_key_ready = True | ||
67 | return False | ||
68 | |||
69 | def set_description(self, description): | ||
70 | if description[0] is not None: | ||
71 | self.description_title = str(description[0]) | ||
72 | for desc in description[1:]: | ||
73 | if desc is None: | ||
74 | self.description.append("") | ||
75 | else: | ||
76 | self.description.append(str(desc).replace(" ", " ")) | ||
77 | |||
78 | def set_color(self, color): | ||
79 | color = [x / 255 for x in color] | ||
80 | color.append(1) | ||
81 | self.custom_color = color | ||
82 | color[3] = 100 / 255 | ||
83 | self.custom_unready_color = tuple(color) | ||
84 | |||
85 | @property | ||
86 | def has_actions(self): | ||
87 | return len(self.actions) > 0 | ||
88 | |||
89 | @property | ||
90 | def all_actions_ready(self): | ||
91 | return all(action.ready() for action in self.actions) | ||
92 | |||
93 | def add_action(self, action_name, **arguments): | ||
94 | self.actions.append(Action(action_name, self, **arguments)) | ||
95 | |||
96 | def do_actions(self): | ||
97 | print("running actions for {}".format(self.key_sym)) | ||
98 | start_time = time.time() | ||
99 | self.parent.start_running(self, start_time) | ||
100 | action_number = 0 | ||
101 | for action in self.actions: | ||
102 | if self.parent.keep_running(self, start_time): | ||
103 | self.list_actions(action_number = action_number + 0.5) | ||
104 | action.run() | ||
105 | action_number += 1 | ||
106 | self.list_actions(action_number = action_number) | ||
107 | |||
108 | self.parent.finished_running(self, start_time) | ||
109 | |||
110 | def list_actions(self, action_number = 0): | ||
111 | self.parent.parent.ids['ActionList'].update_list(self, action_number) | ||
112 | |||
113 | def on_press(self): | ||
114 | self.list_actions() | ||
115 | pass | ||
116 | |||
117 | class PlayList(RelativeLayout): | ||
118 | playlist = ListProperty([]) | ||
119 | |||
120 | def __init__(self, **kwargs): | ||
121 | super(PlayList, self).__init__(**kwargs) | ||
122 | Clock.schedule_interval(self.update_playlist, 0.5) | ||
123 | |||
124 | def update_playlist(self, dt): | ||
125 | if self.parent is None or 'Mapping' not in self.parent.ids: | ||
126 | return True | ||
127 | |||
128 | open_files = self.parent.ids['Mapping'].open_files | ||
129 | self.playlist = [] | ||
130 | for music_file in open_files.values(): | ||
131 | if not music_file.is_playing(): | ||
132 | continue | ||
133 | if music_file.is_paused(): | ||
134 | self.playlist.append(["⏸", music_file.name, False]) | ||
135 | else: | ||
136 | self.playlist.append(["⏵", music_file.name, True]) | ||
137 | |||
138 | |||
139 | class ActionList(RelativeLayout): | ||
140 | action_title = StringProperty("") | ||
141 | action_list = ListProperty([]) | ||
142 | |||
143 | def update_list(self, key, action_number = 0): | ||
144 | self.action_title = "actions linked to key {}:".format(key.key_sym) | ||
145 | self.action_list = [] | ||
146 | |||
147 | action_descriptions = [action.description() for action in key.actions] | ||
148 | |||
149 | for index, description in enumerate(action_descriptions): | ||
150 | if index < int(action_number): | ||
151 | icon = "✓" | ||
152 | elif index + 0.5 == action_number: | ||
153 | icon = "✅" | ||
154 | else: | ||
155 | icon = " " | ||
156 | |||
157 | self.action_list.append([icon, description]) | ||
158 | |||
159 | class Mapping(RelativeLayout): | ||
160 | expected_keys = NumericProperty(0) | ||
161 | |||
162 | def __init__(self, **kwargs): | ||
163 | self.key_config, self.channel_number, self.open_files = helpers.parse_config2() | ||
164 | super(Mapping, self).__init__(**kwargs) | ||
165 | self._keyboard = Window.request_keyboard(self._keyboard_closed, self) | ||
166 | self._keyboard.bind(on_key_down=self._on_keyboard_down) | ||
167 | self.running = [] | ||
168 | |||
169 | |||
170 | pygame.mixer.init(frequency = 44100) | ||
171 | pygame.mixer.set_num_channels(self.channel_number) | ||
172 | |||
173 | def _keyboard_closed(self): | ||
174 | self._keyboard.unbind(on_key_down=self._on_keyboard_down) | ||
175 | self._keyboard = None | ||
176 | |||
177 | def _on_keyboard_down(self, keyboard, keycode, text, modifiers): | ||
178 | key = self.find_by_key_code(keycode) | ||
179 | if key is not None: | ||
180 | threading.Thread(name = "MSKeyAction", target=key.do_actions).start() | ||
181 | return True | ||
182 | |||
183 | def find_by_key_code(self, key_code): | ||
184 | if "Key_" + str(key_code[0]) in self.ids: | ||
185 | return self.ids["Key_" + str(key_code[0])] | ||
186 | return None | ||
187 | |||
188 | def find_by_unicode(self, key_sym): | ||
189 | for key in self.children: | ||
190 | if not type(key).__name__ == "Key": | ||
191 | continue | ||
192 | print(key.key_sym, key_sym) | ||
193 | if key.key_sym == key_sym: | ||
194 | print("found") | ||
195 | return key | ||
196 | return None | ||
197 | |||
198 | def stop_all_running(self): | ||
199 | self.running = [] | ||
200 | |||
201 | def start_running(self, key, start_time): | ||
202 | self.running.append((key, start_time)) | ||
203 | |||
204 | def keep_running(self, key, start_time): | ||
205 | return (key, start_time) in self.running | ||
206 | |||
207 | def finished_running(self, key, start_time): | ||
208 | if (key, start_time) in self.running: | ||
209 | self.running.remove((key, start_time)) | ||
210 | |||
211 | |||
212 | class Screen(FloatLayout): | ||
213 | pass | ||
214 | |||
215 | class MusicSamplerApp(App): | ||
216 | def build(self): | ||
217 | Window.size = (913, 563) | ||
218 | |||
219 | return Screen() | ||
220 | |||
221 | if __name__ == '__main__': | ||
222 | MusicSamplerApp().run() | ||