]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/blame - keyboard.py
Use kivy instead of pygame
[perso/Immae/Projets/Python/MusicSampler.git] / keyboard.py
CommitLineData
8612c5f8
IB
1from kivy.app import App
2from kivy.uix.widget import Widget
3from kivy.uix.floatlayout import FloatLayout
4from kivy.uix.relativelayout import RelativeLayout
5from kivy.properties import AliasProperty, ReferenceListProperty, BooleanProperty, NumericProperty, ListProperty, StringProperty, ObjectProperty
6from kivy.vector import Vector
7from kivy.clock import Clock
8from kivy.uix.behaviors import ButtonBehavior
9from kivy.uix.label import Label
10from kivy.core.window import Window
11
12from helpers.action import *
13import time
14import sys
15import math
16import pygame
17import helpers
18import threading
19
20class KeyDescription(Label):
21 pass
22
23class 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
117class 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
139class 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
159class 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
212class Screen(FloatLayout):
213 pass
214
215class MusicSamplerApp(App):
216 def build(self):
217 Window.size = (913, 563)
218
219 return Screen()
220
221if __name__ == '__main__':
222 MusicSamplerApp().run()