]>
Commit | Line | Data |
---|---|---|
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() |