]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/blob - keyboard.py
0c1115f2d49cccda6b5656e96d2c7140f3be5d3b
[perso/Immae/Projets/Python/MusicSampler.git] / keyboard.py
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()