]>
Commit | Line | Data |
---|---|---|
6ebe6247 IB |
1 | # -*- coding: utf-8 -*- |
2 | import argparse | |
3 | import sys | |
4 | import os | |
5 | import math | |
6 | import sounddevice as sd | |
7 | import logging | |
6a327173 | 8 | import gettext |
16847231 | 9 | import yaml |
6a327173 | 10 | gettext.install('music_sampler') |
023d9381 | 11 | Logger = logging.getLogger("kivy") |
6ebe6247 IB |
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: | |
2010311b IB |
35 | error_print("Font Ubuntu regular could not be found, " |
36 | "please install it.", exit=True) | |
6ebe6247 | 37 | if symbola is None: |
2010311b IB |
38 | error_print("Font Symbola could not be found, please install it.", |
39 | exit=True) | |
6ebe6247 IB |
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 | ||
16847231 IB |
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 | }, | |
6dc040ed IB |
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 | }, | |
16847231 IB |
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', | |
6dc040ed | 152 | 'load_all_musics', |
16847231 | 153 | ] |
6ebe6247 IB |
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 | ||
023d9381 IB |
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 | ||
6ebe6247 | 169 | parser = argparse.ArgumentParser( |
16847231 IB |
170 | argument_default=argparse.SUPPRESS, |
171 | description=_("A Music Sampler application.")) | |
6ebe6247 IB |
172 | parser.add_argument("-V", "--version", |
173 | action="version", | |
6a327173 IB |
174 | help=_("Displays the current version and exits. Only use\ |
175 | in bundled package"), | |
6ebe6247 | 176 | version=show_version()) |
16847231 IB |
177 | parser.add_argument("-c", "--config", |
178 | default="config.yml", | |
6a327173 | 179 | required=False, |
16847231 IB |
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']) | |
6ebe6247 IB |
198 | parser.add_argument('--', |
199 | dest="args", | |
6a327173 IB |
200 | help=_("Kivy arguments. All arguments after this are interpreted\ |
201 | by Kivy. Pass \"-- --help\" to get Kivy's usage.")) | |
6ebe6247 | 202 | |
6ebe6247 IB |
203 | args = parser.parse_args(argv) |
204 | ||
205 | Config.yml_file = args.config | |
16847231 | 206 | build_config(args) |
6ebe6247 | 207 | |
16847231 IB |
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': | |
6a327173 IB |
219 | gettext.translation("music_sampler", |
220 | localedir=path() + '/locales', | |
16847231 IB |
221 | languages=[Config.language]).install() |
222 | if not Config.music_path.endswith("/"): | |
223 | Config.music_path = Config.music_path + "/" | |
6ebe6247 | 224 | |
5e0dc4b2 IB |
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 | ||
16847231 | 234 | def build_config(args): |
de71c01c | 235 | stream = open(Config.yml_file, "r", encoding='utf8') |
16847231 IB |
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 = {} | |
6ebe6247 | 246 | |
16847231 IB |
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)) | |
6ebe6247 | 266 | |
6ebe6247 IB |
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: | |
6a327173 | 273 | return _("option '-V' can only be used in bundled package") |
6ebe6247 IB |
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 | ||
2010311b IB |
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 | ||
6ebe6247 IB |
296 | Logger.debug('MusicSampler: ' + message, exc_info=with_trace) |
297 | ||
2010311b IB |
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) | |
6ebe6247 | 314 | |
6ebe6247 IB |
315 | Logger.warn('MusicSampler: ' + message, exc_info=with_trace) |
316 | ||
5e0dc4b2 IB |
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 |