-
-class Action:
- action_types = [
- 'command',
- 'pause',
- 'play',
- 'stop',
- 'volume',
- 'wait',
- ]
-
- def __init__(self, action, **kwargs):
- if action in self.action_types:
- self.action = action
- else:
- raise Exception("Unknown action {}".format(action))
-
- self.arguments = kwargs
-
- def run(self, callback):
- getattr(self, self.action)(callback, **self.arguments)
-
- def command(self, callback, command = "", **kwargs):
- pass
-
- def pause(self, callback, music = None, **kwargs):
- pass
-
- def play(self, callback,
- music = None,
- fade_in = 0,
- restart_if_running = False,
- volume = 100,
- **kwargs):
- pass
-
- def stop(self, callback, music = None, fade_out = 0, **kwargs):
- print('stopping')
- return callback()
-
- def volume(self, callback, music = None, value = 100, **kwargs):
- pass
-
- def wait(self, callback, time = 0, **kwargs):
- pass
-
-class Key:
- row_positions = {
- 'first': 5,
- 'second': 55,
- 'third': 105,
- 'fourth': 155,
- 'fifth': 205,
- 'sixth': 255,
- }
-
- default_outer_color = (120, 120, 120)
- lighter_outer_color = (200, 200, 200)
- default_inner_color = (255, 255, 255)
- mapped_inner_color = ( 0, 255, 0)
-
- def __init__(self, key_name, key_sym, top, left, width = 48, height = 48, disabled = False):
- self.key_name = key_name
- self.key_sym = key_sym
-
- if isinstance(top, str):
- self.top = self.row_positions[top]
- else:
- self.top = top
-
- self.left = left
- self.width = width
- self.height = height
-
- self.bottom = self.top + self.height
- self.right = self.left + self.width
-
- self.rect = (self.left, self.top, self.right, self.bottom)
- self.position = (self.left, self.top)
-
- if disabled:
- self.outer_color = self.lighter_outer_color
- self.linewidth = 1
- else:
- self.outer_color = self.default_outer_color
- self.linewidth = 3
-
- self.inner_color = self.default_inner_color
- self.actions = []
-
- def square(self):
- if self.has_action():
- self.inner_color = self.mapped_inner_color
-
- return RoundedRect((0, 0, self.width, self.height),
- self.outer_color, self.inner_color, self.linewidth)
-
- def collidepoint(self, position):
- return self.surface.get_rect().collidepoint(
- position[0] - self.position[0],
- position[1] - self.position[1]
- )
-
- def draw(self, background_surface):
- self.surface = self.square().surface()
-
- if getattr(sys, 'frozen', False):
- police = font.Font(sys._MEIPASS + "/Ubuntu-Regular.ttf", 14)
- else:
- police = font.Font("Ubuntu-Regular.ttf", 14)
-
- text = police.render(self.key_sym, True, (0,0,0))
- self.surface.blit(text, (5,5))
- background_surface.blit(self.surface, self.position)
-
- def has_action(self):
- return len(self.actions) > 0
-
- def add_action(self, action_name, **arguments):
- self.actions.append(Action(action_name, **arguments))
-
- def next_action(self):
- print("running next action")
-
- def do_actions(self):
- self.current_action = 0
- print("running actions for {}".format(self.key_sym))
- if len(self.actions) > 0:
- self.actions[0].run(self.next_action)
-
- def list_actions(self, surface):
- print("bouh", self.key_sym)
- surface.fill((255, 0, 0))
-
- def find_by_key_num(key_num):
- if key_num in Mapping.KEYS:
- return Mapping.KEYS[key_num]
- return None
-
- def find_by_collidepoint(position):
- for key in Mapping.KEYS:
- if Mapping.KEYS[key].collidepoint(position):
- return Mapping.KEYS[key]
- return None
-
- def find_by_unicode(key_sym):
- for key in Mapping.KEYS:
- if Mapping.KEYS[key].key_sym == key_sym:
- return Mapping.KEYS[key]
- return None
-
-class Mapping:
- KEYS = {
- K_ESCAPE: Key(K_ESCAPE, 'ESC', 'first', 0),
-
- K_F1: Key(K_F1, 'F1', 'first', 100),
- K_F2: Key(K_F2, 'F2', 'first', 150),
- K_F3: Key(K_F3, 'F3', 'first', 200),
- K_F4: Key(K_F4, 'F4', 'first', 250),
-
- K_F5: Key(K_F5, 'F5', 'first', 325),
- K_F6: Key(K_F6, 'F6', 'first', 375),
- K_F7: Key(K_F7, 'F7', 'first', 425),
- K_F8: Key(K_F8, 'F8', 'first', 475),
-
- K_F9: Key(K_F9, 'F9', 'first', 550),
- K_F10: Key(K_F10, 'F10', 'first', 600),
- K_F11: Key(K_F11, 'F11', 'first', 650),
- K_F12: Key(K_F12, 'F12', 'first', 700),
-
-
- 178: Key(178, '²', 'second', 0),
- K_AMPERSAND: Key(K_AMPERSAND, '&', 'second', 50),
- 233: Key(233, 'é', 'second', 100),
- K_QUOTEDBL: Key(K_QUOTEDBL, '"', 'second', 150),
- K_QUOTE: Key(K_QUOTE, "'", 'second', 200),
- K_LEFTPAREN: Key(K_LEFTPAREN, '(', 'second', 250),
- K_MINUS: Key(K_MINUS, '-', 'second', 300),
- 232: Key(232, 'è', 'second', 350),
- K_UNDERSCORE: Key(K_UNDERSCORE, '_', 'second', 400),
- 231: Key(231, 'ç', 'second', 450),
- 224: Key(224, 'à', 'second', 500),
- K_RIGHTPAREN: Key(K_RIGHTPAREN, ')', 'second', 550),
- K_EQUALS: Key(K_EQUALS, '=', 'second', 600),
-
- K_BACKSPACE: Key(K_BACKSPACE, '<-', 'second', 650, width = 98),
-
-
- K_TAB: Key(K_TAB, 'tab', 'third', 0, width = 73),
- K_a: Key(K_a, 'a', 'third', 75),
- K_z: Key(K_z, 'z', 'third', 125),
- K_e: Key(K_e, 'e', 'third', 175),
- K_r: Key(K_r, 'r', 'third', 225),
- K_t: Key(K_t, 't', 'third', 275),
- K_y: Key(K_y, 'y', 'third', 325),
- K_u: Key(K_u, 'u', 'third', 375),
- K_i: Key(K_i, 'i', 'third', 425),
- K_o: Key(K_o, 'o', 'third', 475),
- K_p: Key(K_p, 'p', 'third', 525),
- K_CARET: Key(K_CARET, '^', 'third', 575),
- K_DOLLAR: Key(K_DOLLAR, '$', 'third', 625),
-
- K_RETURN: Key(K_RETURN, 'Enter', 'third', 692, width = 56, height = 98),
-
- K_CAPSLOCK: Key(K_CAPSLOCK, 'CAPS', 'fourth', 0, width = 88, disabled = True),
-
- K_q: Key(K_q, 'q', 'fourth', 90),
- K_s: Key(K_s, 's', 'fourth', 140),
- K_d: Key(K_d, 'd', 'fourth', 190),
- K_f: Key(K_f, 'f', 'fourth', 240),
- K_g: Key(K_g, 'g', 'fourth', 290),
- K_h: Key(K_h, 'h', 'fourth', 340),
- K_j: Key(K_j, 'j', 'fourth', 390),
- K_k: Key(K_k, 'k', 'fourth', 440),
- K_l: Key(K_l, 'l', 'fourth', 490),
- K_m: Key(K_m, 'm', 'fourth', 540),
- 249: Key(249, 'ù', 'fourth', 590),
- K_ASTERISK: Key(K_ASTERISK, '*', 'fourth', 640),
-
-
- K_LSHIFT: Key(K_LSHIFT, 'LShift', 'fifth', 0, width = 63, disabled = True),
-
- K_LESS: Key(K_LESS, '<', 'fifth', 65),
- K_w: Key(K_w, 'w', 'fifth', 115),
- K_x: Key(K_x, 'x', 'fifth', 165),
- K_c: Key(K_c, 'c', 'fifth', 215),
- K_v: Key(K_v, 'v', 'fifth', 265),
- K_b: Key(K_b, 'b', 'fifth', 315),
- K_n: Key(K_n, 'n', 'fifth', 365),
- K_COMMA: Key(K_COMMA, ',', 'fifth', 415),
- K_SEMICOLON: Key(K_SEMICOLON, ';', 'fifth', 465),
- K_COLON: Key(K_COLON, ':', 'fifth', 515),
- K_EXCLAIM: Key(K_EXCLAIM, '!', 'fifth', 565),
-
- K_RSHIFT: Key(K_RSHIFT, 'RShift', 'fifth', 615, width = 133, disabled = True),
-
- K_LCTRL: Key(K_LCTRL, 'LCtrl', 'sixth', 0, width = 63, disabled = True),
- K_LSUPER: Key(K_LSUPER, 'LSuper', 'sixth', 115, disabled = True),
- K_LALT: Key(K_LALT, 'LAlt', 'sixth', 165, disabled = True),
- K_SPACE: Key(K_SPACE, 'Espace', 'sixth', 215, width = 248),
- K_MODE: Key(K_MODE, 'AltGr', 'sixth', 465, disabled = True),
- 314: Key(314, 'Compose', 'sixth', 515, disabled = True),
- K_RCTRL: Key(K_RCTRL, 'RCtrl', 'sixth', 565, width = 63, disabled = True),
-
-
- K_INSERT: Key(K_INSERT, 'ins', 'second', 755),
- K_HOME: Key(K_HOME, 'home', 'second', 805),
- K_PAGEUP: Key(K_PAGEUP, 'pg_u', 'second', 855),
- K_DELETE: Key(K_DELETE, 'del', 'third', 755),
- K_END: Key(K_END, 'end', 'third', 805),
- K_PAGEDOWN: Key(K_PAGEDOWN, 'pg_d', 'third', 855),
-
-
- K_UP: Key(K_UP, 'up', 'fifth', 805),
- K_DOWN: Key(K_DOWN, 'down', 'sixth', 805),
- K_LEFT: Key(K_LEFT, 'left', 'sixth', 755),
- K_RIGHT: Key(K_RIGHT, 'right', 'sixth', 855),
- }
-
-class MusicFile:
- def __init__(self, filename):
- self.filename = filename
-
-class RoundedRect:
- def __init__(self, rect, outer_color, inner_color, linewidth = 2, radius = 0.4):
- self.rect = Rect(rect)
- self.outer_color = Color(*outer_color)
- self.inner_color = Color(*inner_color)
- self.linewidth = linewidth
- self.radius = radius
-
- def surface(self):
- rectangle = self.filledRoundedRect(self.rect, self.outer_color, self.radius)
-
- inner_rect = Rect((
- self.rect.left + 2 * self.linewidth,
- self.rect.top + 2 * self.linewidth,
- self.rect.right - 2 * self.linewidth,
- self.rect.bottom - 2 * self.linewidth
- ))
-
- inner_rectangle = self.filledRoundedRect(inner_rect, self.inner_color, self.radius)
-
- rectangle.blit(inner_rectangle, (self.linewidth, self.linewidth))
-
- return rectangle
-
- def filledRoundedRect(self, rect, color, radius=0.4):
- """
- filledRoundedRect(rect,color,radius=0.4)
-
- rect : rectangle
- color : rgb or rgba
- radius : 0 <= radius <= 1
- """
-
- alpha = color.a
- color.a = 0
- pos = rect.topleft
- rect.topleft = 0,0
- rectangle = Surface(rect.size,SRCALPHA)
-
- circle = Surface([min(rect.size)*3]*2,SRCALPHA)
- draw.ellipse(circle,(0,0,0),circle.get_rect(),0)
- circle = transform.smoothscale(circle,[int(min(rect.size)*radius)]*2)
-
- radius = rectangle.blit(circle,(0,0))
- radius.bottomright = rect.bottomright
- rectangle.blit(circle,radius)
- radius.topright = rect.topright
- rectangle.blit(circle,radius)
- radius.bottomleft = rect.bottomleft
- rectangle.blit(circle,radius)
-
- rectangle.fill((0,0,0),rect.inflate(-radius.w,0))
- rectangle.fill((0,0,0),rect.inflate(0,-radius.h))
-
- rectangle.fill(color,special_flags=BLEND_RGBA_MAX)
- rectangle.fill((255,255,255,alpha),special_flags=BLEND_RGBA_MIN)
-
- return rectangle
-
-
-def parse_config():
- import yaml
- stream = open("config.yml", "r")
- config = yaml.load(stream)
- stream.close()
-
- aliases = config['aliases']
- seen_files = {}
-
- for mapped_key in config['keys']:
- key = Key.find_by_unicode(mapped_key)
- if key is None:
- continue
-
- for action in config['keys'][mapped_key]:
- action_name = list(action)[0]
- action_args = {}
- if action[action_name] is None:
- action[action_name] = []
-
- for argument in action[action_name]:
- if argument == 'include':
- included = action[action_name]['include']
- if isinstance(included, str):
- action_args.update(aliases[included])
- else:
- for included_ in included:
- action_args.update(aliases[included_])
- elif argument == 'file':
- filename = action[action_name]['file']
- if filename not in seen_files:
- seen_files[filename] = MusicFile.new(filename)
-
- action_args['music'] = seen_files[filename]
-
- else:
- action_args[argument] = action[action_name][argument]
-
- key.add_action(action_name, **action_args)
+import os
+import math
+import sounddevice as sd
+import logging
+
+class Config:
+ pass
+
+def path():
+ if getattr(sys, 'frozen', False):
+ return sys._MEIPASS + "/"
+ else:
+ path = os.path.dirname(os.path.realpath(__file__))
+ return path + "/../"
+
+def parse_args():
+ argv = sys.argv[1 :]
+ sys.argv = sys.argv[: 1]
+ if "--" in argv:
+ index = argv.index("--")
+ kivy_args = argv[index+1 :]
+ argv = argv[: index]
+
+ sys.argv.extend(kivy_args)
+
+ parser = argparse.ArgumentParser(
+ description="A Music Sampler application.",
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument("-c", "--config",
+ default="config.yml",
+ required=False,
+ help="Config file to load")
+ parser.add_argument("-d", "--debug",
+ nargs=0,
+ action=DebugModeAction,
+ help="Print messages in console")
+ parser.add_argument("-m", "--builtin-mixing",
+ action="store_true",
+ help="Make the mixing of sounds manually\
+ (do it if the system cannot handle it correctly)")
+ parser.add_argument("-l", "--latency",
+ default="high",
+ required=False,
+ help="Latency: low, high or number of seconds")
+ parser.add_argument("-b", "--blocksize",
+ default=0,
+ type=int,
+ required=False,
+ help="Blocksize: If not 0, the number of frames to take\
+ at each step for the mixer")
+ parser.add_argument("-f", "--frame-rate",
+ default=44100,
+ type=int,
+ required=False,
+ help="Frame rate to play the musics")
+ parser.add_argument("-x", "--channels",
+ default=2,
+ type=int,
+ required=False,
+ help="Number of channels to use")
+ parser.add_argument("-s", "--sample-width",
+ default=2,
+ type=int,
+ required=False,
+ help="Sample width (number of bytes for each frame)")
+ parser.add_argument("-V", "--version",
+ action="version",
+ help="Displays the current version and exits. Only use\
+ in bundled package",
+ version=show_version())
+ parser.add_argument("--device",
+ action=SelectDeviceAction,
+ help="Select this sound device"
+ )
+ parser.add_argument("--list-devices",
+ nargs=0,
+ action=ListDevicesAction,
+ help="List available sound devices"
+ )
+ parser.add_argument('--',
+ dest="args",
+ help="Kivy arguments. All arguments after this are interpreted\
+ by Kivy. Pass \"-- --help\" to get Kivy's usage.")
+
+ from kivy.logger import Logger
+ Logger.setLevel(logging.WARN)
+
+ args = parser.parse_args(argv)
+
+ Config.yml_file = args.config
+
+ Config.latency = args.latency
+ Config.blocksize = args.blocksize
+ Config.frame_rate = args.frame_rate
+ Config.channels = args.channels
+ Config.sample_width = args.sample_width
+ Config.builtin_mixing = args.builtin_mixing
+
+class DebugModeAction(argparse.Action):
+ def __call__(self, parser, namespace, values, option_string=None):
+ from kivy.logger import Logger
+ Logger.setLevel(logging.DEBUG)
+
+class SelectDeviceAction(argparse.Action):
+ def __call__(self, parser, namespace, values, option_string=None):
+ sd.default.device = values
+
+class ListDevicesAction(argparse.Action):
+ nargs = 0
+ def __call__(self, parser, namespace, values, option_string=None):
+ print(sd.query_devices())
+ sys.exit()
+
+def show_version():
+ if getattr(sys, 'frozen', False):
+ with open(path() + ".pyinstaller_commit", "r") as f:
+ return f.read()
+ else:
+ return "option '-v' can only be used in bundled package"
+
+def duration_to_min_sec(duration):
+ minutes = int(duration / 60)
+ seconds = int(duration) % 60
+ if minutes < 100:
+ return "{:2}:{:0>2}".format(minutes, seconds)
+ else:
+ return "{}:{:0>2}".format(minutes, seconds)
+
+def gain(volume, old_volume=None):
+ if old_volume is None:
+ return 20 * math.log10(max(volume, 0.1) / 100)
+ else:
+ return [
+ 20 * math.log10(max(volume, 0.1) / max(old_volume, 0.1)),
+ max(volume, 0)]
+
+def debug_print(message, with_trace=False):
+ from kivy.logger import Logger
+ Logger.debug('MusicSampler: ' + message, exc_info=with_trace)
+
+def error_print(message, with_trace=False):
+ from kivy.logger import Logger
+ Logger.error('MusicSampler: ' + message, exc_info=with_trace)
+
+def warn_print(message, with_trace=False):
+ from kivy.logger import Logger
+ Logger.warn('MusicSampler: ' + message, exc_info=with_trace)