from kivy.app import App from kivy.uix.widget import Widget from kivy.uix.floatlayout import FloatLayout from kivy.uix.relativelayout import RelativeLayout from kivy.properties import AliasProperty, ReferenceListProperty, BooleanProperty, NumericProperty, ListProperty, StringProperty, ObjectProperty from kivy.vector import Vector from kivy.clock import Clock from kivy.uix.behaviors import ButtonBehavior from kivy.uix.label import Label from kivy.core.window import Window from helpers.action import * import time import sys import math import pygame import helpers import threading class KeyDescription(Label): pass class Key(ButtonBehavior, Widget): key_sym = StringProperty(None) custom_color = ListProperty([0, 1, 0, 1]) custom_unready_color = ListProperty([0, 1, 0, 100/255]) description_title = StringProperty("") description = ListProperty([]) is_key_ready = BooleanProperty(True) def get_color(self): if not self.has_actions: return [1, 1, 1, 1] elif self.all_actions_ready: return self.custom_color else: return self.custom_unready_color def set_color(self): pass color = AliasProperty(get_color, set_color, bind=['is_key_ready']) def __init__(self, **kwargs): super(Key, self).__init__(**kwargs) self.actions = [] def on_key_sym(self, key, key_sym): if key_sym in self.parent.key_config: self.is_key_ready = False self.config = self.parent.key_config[key_sym] self.actions = [] for key_action in self.config['actions']: self.add_action(key_action[0], **key_action[1]) if 'description' in self.config['properties']: key.set_description(self.config['properties']['description']) if 'color' in self.config['properties']: key.set_color(self.config['properties']['color']) Clock.schedule_interval(self.check_all_active, 1) def check_all_active(self, dt): if self.all_actions_ready: self.is_key_ready = True return False def set_description(self, description): if description[0] is not None: self.description_title = str(description[0]) for desc in description[1:]: if desc is None: self.description.append("") else: self.description.append(str(desc).replace(" ", " ")) def set_color(self, color): color = [x / 255 for x in color] color.append(1) self.custom_color = color color[3] = 100 / 255 self.custom_unready_color = tuple(color) @property def has_actions(self): return len(self.actions) > 0 @property def all_actions_ready(self): return all(action.ready() for action in self.actions) def add_action(self, action_name, **arguments): self.actions.append(Action(action_name, self, **arguments)) def do_actions(self): print("running actions for {}".format(self.key_sym)) start_time = time.time() self.parent.start_running(self, start_time) action_number = 0 for action in self.actions: if self.parent.keep_running(self, start_time): self.list_actions(action_number = action_number + 0.5) action.run() action_number += 1 self.list_actions(action_number = action_number) self.parent.finished_running(self, start_time) def list_actions(self, action_number = 0): self.parent.parent.ids['ActionList'].update_list(self, action_number) def on_press(self): self.list_actions() pass class PlayList(RelativeLayout): playlist = ListProperty([]) def __init__(self, **kwargs): super(PlayList, self).__init__(**kwargs) Clock.schedule_interval(self.update_playlist, 0.5) def update_playlist(self, dt): if self.parent is None or 'Mapping' not in self.parent.ids: return True open_files = self.parent.ids['Mapping'].open_files self.playlist = [] for music_file in open_files.values(): if not music_file.is_playing(): continue if music_file.is_paused(): self.playlist.append(["⏸", music_file.name, False]) else: self.playlist.append(["⏵", music_file.name, True]) class ActionList(RelativeLayout): action_title = StringProperty("") action_list = ListProperty([]) def update_list(self, key, action_number = 0): self.action_title = "actions linked to key {}:".format(key.key_sym) self.action_list = [] action_descriptions = [action.description() for action in key.actions] for index, description in enumerate(action_descriptions): if index < int(action_number): icon = "✓" elif index + 0.5 == action_number: icon = "✅" else: icon = " " self.action_list.append([icon, description]) class Mapping(RelativeLayout): expected_keys = NumericProperty(0) def __init__(self, **kwargs): self.key_config, self.channel_number, self.open_files = helpers.parse_config2() super(Mapping, self).__init__(**kwargs) self._keyboard = Window.request_keyboard(self._keyboard_closed, self) self._keyboard.bind(on_key_down=self._on_keyboard_down) self.running = [] pygame.mixer.init(frequency = 44100) pygame.mixer.set_num_channels(self.channel_number) def _keyboard_closed(self): self._keyboard.unbind(on_key_down=self._on_keyboard_down) self._keyboard = None def _on_keyboard_down(self, keyboard, keycode, text, modifiers): key = self.find_by_key_code(keycode) if key is not None: threading.Thread(name = "MSKeyAction", target=key.do_actions).start() return True def find_by_key_code(self, key_code): if "Key_" + str(key_code[0]) in self.ids: return self.ids["Key_" + str(key_code[0])] return None def find_by_unicode(self, key_sym): for key in self.children: if not type(key).__name__ == "Key": continue print(key.key_sym, key_sym) if key.key_sym == key_sym: print("found") return key return None def stop_all_running(self): self.running = [] def start_running(self, key, start_time): self.running.append((key, start_time)) def keep_running(self, key, start_time): return (key, start_time) in self.running def finished_running(self, key, start_time): if (key, start_time) in self.running: self.running.remove((key, start_time)) class Screen(FloatLayout): pass class MusicSamplerApp(App): def build(self): Window.size = (913, 563) return Screen() if __name__ == '__main__': MusicSamplerApp().run()