import math
import sounddevice as sd
import logging
+import gettext
+import yaml
+gettext.install('music_sampler')
+Logger = logging.getLogger("kivy")
from . import sysfont
symbola = find_font("Symbola")
if ubuntu_regular is None:
- error_print("Font Ubuntu regular could not be found, please install it.")
- sys.exit()
+ error_print("Font Ubuntu regular could not be found, "
+ "please install it.", exit=True)
if symbola is None:
- error_print("Font Symbola could not be found, please install it.")
- sys.exit()
+ error_print("Font Symbola could not be found, please install it.",
+ exit=True)
if ubuntu_bold is None:
warn_print("Font Ubuntu Bold could not be found.")
else:
return os.path.dirname(os.path.realpath(__file__))
+
+Configs = {
+ 'music_path': {
+ 'abbr': '-p',
+ 'default': '.',
+ 'help': _("Folder in which to find the music files"),
+ 'type': None
+ },
+ 'latency': {
+ 'abbr': '-l',
+ 'default': 'high',
+ 'help': _("Latency: low, high or number of seconds"),
+ 'type': None
+ },
+ 'language': {
+ 'abbr': '-L',
+ 'default': "fr",
+ 'help': _("Select another language"),
+ 'type': None
+ },
+ 'device': {
+ 'abbr': '-d',
+ 'default': None,
+ 'help': _("Select this sound device"),
+ 'type': None
+ },
+ 'blocksize': {
+ 'abbr': '-b',
+ 'default': 0,
+ 'help': _("Blocksize: If not 0, the number of frames to take\
+ at each step for the mixer"),
+ 'type': int
+ },
+ 'frame_rate': {
+ 'abbr': '-f',
+ 'default': 44100,
+ 'help': _("Frame rate to play the musics"),
+ 'type': int
+ },
+ 'channels': {
+ 'abbr': '-x',
+ 'default': 2,
+ 'help': _("Number of channels to use"),
+ 'type': int
+ },
+ 'sample_width': {
+ 'abbr': '-s',
+ 'default': 2,
+ 'help': _("Sample width (number of bytes for each frame)"),
+ 'type': int
+ },
+ 'builtin_mixing': {
+ 'default': False,
+ 'help_yes': _("Make the mixing of sounds manually\
+ (do it if the system cannot handle it correctly)"),
+ 'help_no': _("Don't make the mixing of sounds manually (default)"),
+ 'type': 'boolean'
+ },
+ 'debug': {
+ 'abbr': '-d',
+ 'default': False,
+ 'help_yes': _("Print messages in console"),
+ 'help_no': _("Don't print messages in console (default)"),
+ 'type': 'boolean'
+ },
+ 'focus_warning': {
+ 'default': True,
+ 'help_yes': _("Show a warning when focus is lost (default)"),
+ 'help_no': _("Don't show warning when focus is lost"),
+ 'type': 'boolean'
+ },
+ 'load_all_musics': {
+ 'default': True,
+ 'help_yes': _("Load all the musics at launch time (default)"),
+ 'help_no': _("Don't load all the musics at launch time (use it if you \
+ have memory problems)"),
+ 'type': 'boolean'
+ },
+ 'list_devices': {
+ 'help': _("List available sound devices"),
+ 'type': 'action'
+ },
+}
+Configs_order = [
+ 'debug',
+ 'music_path',
+ 'builtin_mixing',
+ 'latency',
+ 'blocksize',
+ 'frame_rate',
+ 'channels',
+ 'sample_width',
+ 'focus_warning',
+ 'language',
+ 'list_devices',
+ 'device',
+ 'load_all_musics',
+]
def parse_args():
argv = sys.argv[1 :]
sys.argv = sys.argv[: 1]
sys.argv.extend(kivy_args)
+ os.environ["KIVY_NO_CONFIG"] = 'true'
+ sys.argv.extend(["-c", "kivy:log_level:warning"])
+ sys.argv.extend(["-c", "kivy:log_dir:/tmp"])
+ sys.argv.extend(["-c", "kivy:log_name:/tmp/music_sampler_%_.txt"])
+
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("-p", "--music-path",
- default=".",
- required=False,
- help="Folder in which to find the music files")
- 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)")
+ argument_default=argparse.SUPPRESS,
+ description=_("A Music Sampler application."))
parser.add_argument("-V", "--version",
action="version",
- help="Displays the current version and exits. Only use\
- in bundled package",
+ 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("-c", "--config",
+ default="config.yml",
+ required=False,
+ help=_("Config file to load (default: config.yml)"))
+ for argument in Configs_order:
+ arg = Configs[argument]
+ if arg['type'] != 'boolean' and arg['type'] != 'action':
+ parser.add_argument(arg['abbr'], '--' + argument.replace('_', '-'),
+ type=arg['type'],
+ help=arg['help']+_(" (default: {})").format(arg['default']))
+ elif arg['type'] == 'boolean':
+ parser.add_argument('--' + argument.replace('_', '-'),
+ action='store_const', const=True,
+ help=arg['help_yes'])
+ parser.add_argument('--no-' + argument.replace('_', '-'),
+ action='store_const', const=True,
+ help=arg['help_no'])
+ else:
+ parser.add_argument('--' + argument.replace('_', '-'),
+ action='store_const', const=True,
+ help=arg['help'])
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)
+ help=_("Kivy arguments. All arguments after this are interpreted\
+ by Kivy. Pass \"-- --help\" to get Kivy's usage."))
args = parser.parse_args(argv)
Config.yml_file = args.config
+ build_config(args)
- 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
- if args.music_path.endswith("/"):
- Config.music_path = args.music_path
- else:
- Config.music_path = args.music_path + "/"
-
-class DebugModeAction(argparse.Action):
- def __call__(self, parser, namespace, values, option_string=None):
- from kivy.logger import Logger
- Logger.setLevel(logging.DEBUG)
+ if Config.device is not None:
+ sd.default.device = Config.device
-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):
+ if Config.list_devices:
print(sd.query_devices())
sys.exit()
+ if Config.debug:
+ sys.argv.extend(["-c", "kivy:log_level:debug"])
+
+ if Config.language != 'en':
+ gettext.translation("music_sampler",
+ localedir=path() + '/locales',
+ languages=[Config.language]).install()
+ if not Config.music_path.endswith("/"):
+ Config.music_path = Config.music_path + "/"
+
+def dump_config():
+ max_size = max(max(map(len, Configs_order)), len('config'))
+ info_print("{:<{}} : {}".format(
+ "config", max_size, Config.yml_file))
+ for item in Config.__dict__:
+ if item in Configs_order:
+ info_print("{:<{}} : {}".format(
+ item, max_size, getattr(Config, item)))
+
+def build_config(args):
+ stream = open(Config.yml_file, "r", encoding='utf8')
+ try:
+ config = yaml.safe_load(stream)
+ except Exception as e:
+ error_print("Error while loading config file: {}".format(e))
+ config = {}
+ stream.close()
+ if 'config' in config:
+ config = config['config']
+ else:
+ config = {}
+
+ for config_item in Configs_order:
+ if Configs[config_item]['type'] != 'boolean' and \
+ Configs[config_item]['type'] != 'action':
+ t = Configs[config_item]['type'] or str
+ if hasattr(args, config_item):
+ setattr(Config, config_item, getattr(args, config_item))
+ elif config_item in config:
+ setattr(Config, config_item, t(config[config_item]))
+ else:
+ setattr(Config, config_item, Configs[config_item]['default'])
+ elif Configs[config_item]['type'] == 'boolean':
+ if hasattr(args, 'no_' + config_item) or hasattr(args, config_item):
+ setattr(Config, config_item, hasattr(args, config_item))
+ elif config_item in config:
+ setattr(Config, config_item, config[config_item])
+ else:
+ setattr(Config, config_item, Configs[config_item]['default'])
+ else:
+ setattr(Config, config_item, hasattr(args, config_item))
+
+
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"
+ return _("option '-V' can only be used in bundled package")
def duration_to_min_sec(duration):
minutes = int(duration / 60)
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
+def debug_print(message, with_trace=None):
+ if with_trace is None:
+ with_trace = (Logger.getEffectiveLevel() < logging.WARN)
+ with_trace &= (sys.exc_info()[0] is not None)
+
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 error_print(message, exit=False, with_trace=None):
+ if with_trace is None:
+ with_trace = (Logger.getEffectiveLevel() < logging.WARN)
+ with_trace &= (sys.exc_info()[0] is not None)
+
+ # FIXME: handle it correctly when in a thread
+ if exit:
+ Logger.critical('MusicSampler: ' + message, exc_info=with_trace)
+ sys.exit(1)
+ else:
+ Logger.error('MusicSampler: ' + message, exc_info=with_trace)
+
+def warn_print(message, with_trace=None):
+ if with_trace is None:
+ with_trace = (Logger.getEffectiveLevel() < logging.WARN)
+ with_trace &= (sys.exc_info()[0] is not None)
-def warn_print(message, with_trace=False):
- from kivy.logger import Logger
Logger.warn('MusicSampler: ' + message, exc_info=with_trace)
+def info_print(message, with_trace=None):
+ if with_trace is None:
+ with_trace = (Logger.getEffectiveLevel() < logging.WARN)
+ with_trace &= (sys.exc_info()[0] is not None)
+
+ Logger.info('MusicSampler: ' + message, exc_info=with_trace)
+