]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/blob - music_sampler/helpers.py
Add comment action
[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 'load_all_musics': {
128 'default': True,
129 'help_yes': _("Load all the musics at launch time (default)"),
130 'help_no': _("Don't load all the musics at launch time (use it if you \
131 have memory problems)"),
132 'type': 'boolean'
133 },
134 'list_devices': {
135 'help': _("List available sound devices"),
136 'type': 'action'
137 },
138 }
139 Configs_order = [
140 'debug',
141 'music_path',
142 'builtin_mixing',
143 'latency',
144 'blocksize',
145 'frame_rate',
146 'channels',
147 'sample_width',
148 'focus_warning',
149 'language',
150 'list_devices',
151 'device',
152 'load_all_musics',
153 ]
154 def parse_args():
155 argv = sys.argv[1 :]
156 sys.argv = sys.argv[: 1]
157 if "--" in argv:
158 index = argv.index("--")
159 kivy_args = argv[index+1 :]
160 argv = argv[: index]
161
162 sys.argv.extend(kivy_args)
163
164 os.environ["KIVY_NO_CONFIG"] = 'true'
165 sys.argv.extend(["-c", "kivy:log_level:warning"])
166 sys.argv.extend(["-c", "kivy:log_dir:/tmp"])
167 sys.argv.extend(["-c", "kivy:log_name:/tmp/music_sampler_%_.txt"])
168
169 parser = argparse.ArgumentParser(
170 argument_default=argparse.SUPPRESS,
171 description=_("A Music Sampler application."))
172 parser.add_argument("-V", "--version",
173 action="version",
174 help=_("Displays the current version and exits. Only use\
175 in bundled package"),
176 version=show_version())
177 parser.add_argument("-c", "--config",
178 default="config.yml",
179 required=False,
180 help=_("Config file to load (default: config.yml)"))
181 for argument in Configs_order:
182 arg = Configs[argument]
183 if arg['type'] != 'boolean' and arg['type'] != 'action':
184 parser.add_argument(arg['abbr'], '--' + argument.replace('_', '-'),
185 type=arg['type'],
186 help=arg['help']+_(" (default: {})").format(arg['default']))
187 elif arg['type'] == 'boolean':
188 parser.add_argument('--' + argument.replace('_', '-'),
189 action='store_const', const=True,
190 help=arg['help_yes'])
191 parser.add_argument('--no-' + argument.replace('_', '-'),
192 action='store_const', const=True,
193 help=arg['help_no'])
194 else:
195 parser.add_argument('--' + argument.replace('_', '-'),
196 action='store_const', const=True,
197 help=arg['help'])
198 parser.add_argument('--',
199 dest="args",
200 help=_("Kivy arguments. All arguments after this are interpreted\
201 by Kivy. Pass \"-- --help\" to get Kivy's usage."))
202
203 args = parser.parse_args(argv)
204
205 Config.yml_file = args.config
206 build_config(args)
207
208 if Config.device is not None:
209 sd.default.device = Config.device
210
211 if Config.list_devices:
212 print(sd.query_devices())
213 sys.exit()
214
215 if Config.debug:
216 sys.argv.extend(["-c", "kivy:log_level:debug"])
217
218 if Config.language != 'en':
219 gettext.translation("music_sampler",
220 localedir=path() + '/locales',
221 languages=[Config.language]).install()
222 if not Config.music_path.endswith("/"):
223 Config.music_path = Config.music_path + "/"
224
225 def dump_config():
226 max_size = max(max(map(len, Configs_order)), len('config'))
227 info_print("{:<{}} : {}".format(
228 "config", max_size, Config.yml_file))
229 for item in Config.__dict__:
230 if item in Configs_order:
231 info_print("{:<{}} : {}".format(
232 item, max_size, getattr(Config, item)))
233
234 def build_config(args):
235 stream = open(Config.yml_file, "r", encoding='utf8')
236 try:
237 config = yaml.safe_load(stream)
238 except Exception as e:
239 error_print("Error while loading config file: {}".format(e))
240 config = {}
241 stream.close()
242 if 'config' in config:
243 config = config['config']
244 else:
245 config = {}
246
247 for config_item in Configs_order:
248 if Configs[config_item]['type'] != 'boolean' and \
249 Configs[config_item]['type'] != 'action':
250 t = Configs[config_item]['type'] or str
251 if hasattr(args, config_item):
252 setattr(Config, config_item, getattr(args, config_item))
253 elif config_item in config:
254 setattr(Config, config_item, t(config[config_item]))
255 else:
256 setattr(Config, config_item, Configs[config_item]['default'])
257 elif Configs[config_item]['type'] == 'boolean':
258 if hasattr(args, 'no_' + config_item) or hasattr(args, config_item):
259 setattr(Config, config_item, hasattr(args, config_item))
260 elif config_item in config:
261 setattr(Config, config_item, config[config_item])
262 else:
263 setattr(Config, config_item, Configs[config_item]['default'])
264 else:
265 setattr(Config, config_item, hasattr(args, config_item))
266
267
268 def show_version():
269 if getattr(sys, 'frozen', False):
270 with open(path() + ".pyinstaller_commit", "r") as f:
271 return f.read()
272 else:
273 return _("option '-V' can only be used in bundled package")
274
275 def duration_to_min_sec(duration):
276 minutes = int(duration / 60)
277 seconds = int(duration) % 60
278 if minutes < 100:
279 return "{:2}:{:0>2}".format(minutes, seconds)
280 else:
281 return "{}:{:0>2}".format(minutes, seconds)
282
283 def gain(volume, old_volume=None):
284 if old_volume is None:
285 return 20 * math.log10(max(volume, 0.1) / 100)
286 else:
287 return [
288 20 * math.log10(max(volume, 0.1) / max(old_volume, 0.1)),
289 max(volume, 0)]
290
291 def debug_print(message, with_trace=None):
292 if with_trace is None:
293 with_trace = (Logger.getEffectiveLevel() < logging.WARN)
294 with_trace &= (sys.exc_info()[0] is not None)
295
296 Logger.debug('MusicSampler: ' + message, exc_info=with_trace)
297
298 def error_print(message, exit=False, with_trace=None):
299 if with_trace is None:
300 with_trace = (Logger.getEffectiveLevel() < logging.WARN)
301 with_trace &= (sys.exc_info()[0] is not None)
302
303 # FIXME: handle it correctly when in a thread
304 if exit:
305 Logger.critical('MusicSampler: ' + message, exc_info=with_trace)
306 sys.exit(1)
307 else:
308 Logger.error('MusicSampler: ' + message, exc_info=with_trace)
309
310 def warn_print(message, with_trace=None):
311 if with_trace is None:
312 with_trace = (Logger.getEffectiveLevel() < logging.WARN)
313 with_trace &= (sys.exc_info()[0] is not None)
314
315 Logger.warn('MusicSampler: ' + message, exc_info=with_trace)
316
317 def info_print(message, with_trace=None):
318 if with_trace is None:
319 with_trace = (Logger.getEffectiveLevel() < logging.WARN)
320 with_trace &= (sys.exc_info()[0] is not None)
321
322 Logger.info('MusicSampler: ' + message, exc_info=with_trace)
323