]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/blob - music_sampler/helpers.py
9403875cbfe49a82b91ab5772b42f1452eb78b81
[perso/Immae/Projets/Python/MusicSampler.git] / music_sampler / helpers.py
1 # -*- coding: utf-8 -*-
2 import argparse
3 import sys
4 import os
5 import math
6 import sounddevice as sd
7 import logging
8 import gettext
9 import yaml
10 gettext.install('music_sampler')
11 Logger = logging.getLogger("kivy")
12
13 from . import sysfont
14
15 class Config:
16 pass
17
18 def find_font(name, style=sysfont.STYLE_NONE):
19 if getattr(sys, 'frozen', False):
20 font = sys._MEIPASS + "/fonts/{}_{}.ttf".format(name, style)
21 else:
22 font = sysfont.get_font(name, style=style)
23 if font is not None:
24 font = font[4]
25 return font
26
27 def register_fonts():
28 from kivy.core.text import LabelBase
29
30 ubuntu_regular = find_font("Ubuntu", style=sysfont.STYLE_NORMAL)
31 ubuntu_bold = find_font("Ubuntu", style=sysfont.STYLE_BOLD)
32 symbola = find_font("Symbola")
33
34 if ubuntu_regular is None:
35 error_print("Font Ubuntu regular could not be found, "
36 "please install it.", exit=True)
37 if symbola is None:
38 error_print("Font Symbola could not be found, please install it.",
39 exit=True)
40 if ubuntu_bold is None:
41 warn_print("Font Ubuntu Bold could not be found.")
42
43 LabelBase.register(name="Ubuntu",
44 fn_regular=ubuntu_regular,
45 fn_bold=ubuntu_bold)
46 LabelBase.register(name="Symbola",
47 fn_regular=symbola)
48
49
50 def path():
51 if getattr(sys, 'frozen', False):
52 return sys._MEIPASS + "/"
53 else:
54 return os.path.dirname(os.path.realpath(__file__))
55
56
57 Configs = {
58 'music_path': {
59 'abbr': '-p',
60 'default': '.',
61 'help': _("Folder in which to find the music files"),
62 'type': None
63 },
64 'latency': {
65 'abbr': '-l',
66 'default': 'high',
67 'help': _("Latency: low, high or number of seconds"),
68 'type': None
69 },
70 'language': {
71 'abbr': '-L',
72 'default': "fr",
73 'help': _("Select another language"),
74 'type': None
75 },
76 'device': {
77 'abbr': '-d',
78 'default': None,
79 'help': _("Select this sound device"),
80 'type': None
81 },
82 'blocksize': {
83 'abbr': '-b',
84 'default': 0,
85 'help': _("Blocksize: If not 0, the number of frames to take\
86 at each step for the mixer"),
87 'type': int
88 },
89 'frame_rate': {
90 'abbr': '-f',
91 'default': 44100,
92 'help': _("Frame rate to play the musics"),
93 'type': int
94 },
95 'channels': {
96 'abbr': '-x',
97 'default': 2,
98 'help': _("Number of channels to use"),
99 'type': int
100 },
101 'sample_width': {
102 'abbr': '-s',
103 'default': 2,
104 'help': _("Sample width (number of bytes for each frame)"),
105 'type': int
106 },
107 'builtin_mixing': {
108 'default': False,
109 'help_yes': _("Make the mixing of sounds manually\
110 (do it if the system cannot handle it correctly)"),
111 'help_no': _("Don't make the mixing of sounds manually (default)"),
112 'type': 'boolean'
113 },
114 'debug': {
115 'abbr': '-d',
116 'default': False,
117 'help_yes': _("Print messages in console"),
118 'help_no': _("Don't print messages in console (default)"),
119 'type': 'boolean'
120 },
121 'focus_warning': {
122 'default': True,
123 'help_yes': _("Show a warning when focus is lost (default)"),
124 'help_no': _("Don't show warning when focus is lost"),
125 'type': 'boolean'
126 },
127 'list_devices': {
128 'help': _("List available sound devices"),
129 'type': 'action'
130 },
131 }
132 Configs_order = [
133 'debug',
134 'music_path',
135 'builtin_mixing',
136 'latency',
137 'blocksize',
138 'frame_rate',
139 'channels',
140 'sample_width',
141 'focus_warning',
142 'language',
143 'list_devices',
144 'device',
145 ]
146 def parse_args():
147 argv = sys.argv[1 :]
148 sys.argv = sys.argv[: 1]
149 if "--" in argv:
150 index = argv.index("--")
151 kivy_args = argv[index+1 :]
152 argv = argv[: index]
153
154 sys.argv.extend(kivy_args)
155
156 os.environ["KIVY_NO_CONFIG"] = 'true'
157 sys.argv.extend(["-c", "kivy:log_level:warning"])
158 sys.argv.extend(["-c", "kivy:log_dir:/tmp"])
159 sys.argv.extend(["-c", "kivy:log_name:/tmp/music_sampler_%_.txt"])
160
161 parser = argparse.ArgumentParser(
162 argument_default=argparse.SUPPRESS,
163 description=_("A Music Sampler application."))
164 parser.add_argument("-V", "--version",
165 action="version",
166 help=_("Displays the current version and exits. Only use\
167 in bundled package"),
168 version=show_version())
169 parser.add_argument("-c", "--config",
170 default="config.yml",
171 required=False,
172 help=_("Config file to load (default: config.yml)"))
173 for argument in Configs_order:
174 arg = Configs[argument]
175 if arg['type'] != 'boolean' and arg['type'] != 'action':
176 parser.add_argument(arg['abbr'], '--' + argument.replace('_', '-'),
177 type=arg['type'],
178 help=arg['help']+_(" (default: {})").format(arg['default']))
179 elif arg['type'] == 'boolean':
180 parser.add_argument('--' + argument.replace('_', '-'),
181 action='store_const', const=True,
182 help=arg['help_yes'])
183 parser.add_argument('--no-' + argument.replace('_', '-'),
184 action='store_const', const=True,
185 help=arg['help_no'])
186 else:
187 parser.add_argument('--' + argument.replace('_', '-'),
188 action='store_const', const=True,
189 help=arg['help'])
190 parser.add_argument('--',
191 dest="args",
192 help=_("Kivy arguments. All arguments after this are interpreted\
193 by Kivy. Pass \"-- --help\" to get Kivy's usage."))
194
195 args = parser.parse_args(argv)
196
197 Config.yml_file = args.config
198 build_config(args)
199
200 if Config.device is not None:
201 sd.default.device = Config.device
202
203 if Config.list_devices:
204 print(sd.query_devices())
205 sys.exit()
206
207 if Config.debug:
208 sys.argv.extend(["-c", "kivy:log_level:debug"])
209
210 if Config.language != 'en':
211 gettext.translation("music_sampler",
212 localedir=path() + '/locales',
213 languages=[Config.language]).install()
214 if not Config.music_path.endswith("/"):
215 Config.music_path = Config.music_path + "/"
216
217 def dump_config():
218 max_size = max(max(map(len, Configs_order)), len('config'))
219 info_print("{:<{}} : {}".format(
220 "config", max_size, Config.yml_file))
221 for item in Config.__dict__:
222 if item in Configs_order:
223 info_print("{:<{}} : {}".format(
224 item, max_size, getattr(Config, item)))
225
226 def build_config(args):
227 stream = open(Config.yml_file, "r")
228 try:
229 config = yaml.safe_load(stream)
230 except Exception as e:
231 error_print("Error while loading config file: {}".format(e))
232 config = {}
233 stream.close()
234 if 'config' in config:
235 config = config['config']
236 else:
237 config = {}
238
239 for config_item in Configs_order:
240 if Configs[config_item]['type'] != 'boolean' and \
241 Configs[config_item]['type'] != 'action':
242 t = Configs[config_item]['type'] or str
243 if hasattr(args, config_item):
244 setattr(Config, config_item, getattr(args, config_item))
245 elif config_item in config:
246 setattr(Config, config_item, t(config[config_item]))
247 else:
248 setattr(Config, config_item, Configs[config_item]['default'])
249 elif Configs[config_item]['type'] == 'boolean':
250 if hasattr(args, 'no_' + config_item) or hasattr(args, config_item):
251 setattr(Config, config_item, hasattr(args, config_item))
252 elif config_item in config:
253 setattr(Config, config_item, config[config_item])
254 else:
255 setattr(Config, config_item, Configs[config_item]['default'])
256 else:
257 setattr(Config, config_item, hasattr(args, config_item))
258
259
260 def show_version():
261 if getattr(sys, 'frozen', False):
262 with open(path() + ".pyinstaller_commit", "r") as f:
263 return f.read()
264 else:
265 return _("option '-V' can only be used in bundled package")
266
267 def duration_to_min_sec(duration):
268 minutes = int(duration / 60)
269 seconds = int(duration) % 60
270 if minutes < 100:
271 return "{:2}:{:0>2}".format(minutes, seconds)
272 else:
273 return "{}:{:0>2}".format(minutes, seconds)
274
275 def gain(volume, old_volume=None):
276 if old_volume is None:
277 return 20 * math.log10(max(volume, 0.1) / 100)
278 else:
279 return [
280 20 * math.log10(max(volume, 0.1) / max(old_volume, 0.1)),
281 max(volume, 0)]
282
283 def debug_print(message, with_trace=None):
284 if with_trace is None:
285 with_trace = (Logger.getEffectiveLevel() < logging.WARN)
286 with_trace &= (sys.exc_info()[0] is not None)
287
288 Logger.debug('MusicSampler: ' + message, exc_info=with_trace)
289
290 def error_print(message, exit=False, with_trace=None):
291 if with_trace is None:
292 with_trace = (Logger.getEffectiveLevel() < logging.WARN)
293 with_trace &= (sys.exc_info()[0] is not None)
294
295 # FIXME: handle it correctly when in a thread
296 if exit:
297 Logger.critical('MusicSampler: ' + message, exc_info=with_trace)
298 sys.exit(1)
299 else:
300 Logger.error('MusicSampler: ' + message, exc_info=with_trace)
301
302 def warn_print(message, with_trace=None):
303 if with_trace is None:
304 with_trace = (Logger.getEffectiveLevel() < logging.WARN)
305 with_trace &= (sys.exc_info()[0] is not None)
306
307 Logger.warn('MusicSampler: ' + message, exc_info=with_trace)
308
309 def info_print(message, with_trace=None):
310 if with_trace is None:
311 with_trace = (Logger.getEffectiveLevel() < logging.WARN)
312 with_trace &= (sys.exc_info()[0] is not None)
313
314 Logger.info('MusicSampler: ' + message, exc_info=with_trace)
315