From: Ismaël Bouya Date: Thu, 14 Jul 2016 20:18:51 +0000 (+0200) Subject: Some new features: X-Git-Tag: 1.0.0~60 X-Git-Url: https://git.immae.eu/?p=perso%2FImmae%2FProjets%2FPython%2FMusicSampler.git;a=commitdiff_plain;h=1b4b78f5b6df7182ac066fcc26a7b4f0e8586a47 Some new features: - gain function moved to helpers/__init__ - cleanup some unused functions - stop can now wait for fade_out to finish before returning - volume can be incremented - master volume --- diff --git a/helpers/__init__.py b/helpers/__init__.py index 3b97f2f..2339b9b 100644 --- a/helpers/__init__.py +++ b/helpers/__init__.py @@ -2,6 +2,7 @@ import argparse import sys import os +import math class Config: def __init__(self, **kwargs): @@ -60,3 +61,10 @@ def duration_to_min_sec(duration): 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(volume / 100) + else: + return [20 * math.log10(max(volume, 0.1) / max(old_volume, 0.1)), max(volume, 0)] + diff --git a/helpers/action.py b/helpers/action.py index 9145629..28afcee 100644 --- a/helpers/action.py +++ b/helpers/action.py @@ -20,6 +20,7 @@ class Action: raise Exception("Unknown action {}".format(action)) self.key = key + self.mapping = key.parent self.arguments = kwargs self.sleep_event = None @@ -49,7 +50,7 @@ class Action: if music is not None: return [music] else: - return self.key.parent.open_files.values() + return self.mapping.open_files.values() def pause(self, music = None, **kwargs): for music in self.music_list(music): @@ -72,20 +73,25 @@ class Action: if not music.is_not_stopped(): music.play(volume = volume, fade_in = fade_in, start_at = start_at) - def stop(self, music = None, fade_out = 0, **kwargs): + def stop(self, music = None, fade_out = 0, wait = False, **kwargs): + previous = None for music in self.music_list(music): if music.is_loaded_paused() or music.is_loaded_playing(): - music.stop(fade_out = fade_out) + if previous is not None: + previous.stop(fade_out = fade_out) + previous = music + + if previous is not None: + previous.stop(fade_out = fade_out, wait = wait) def stop_all_actions(self, **kwargs): - self.key.parent.stop_all_running() + self.mapping.stop_all_running() - def volume(self, music = None, value = 100, **kwargs): + def volume(self, music = None, value = 100, add = False, **kwargs): if music is not None: - music.set_volume(value) + music.set_volume(value, add = add) else: - # FIXME: todo - pass + self.mapping.set_master_volume(value, add = add) def wait(self, duration = 0, music = None, **kwargs): self.sleep_event = threading.Event() @@ -133,26 +139,34 @@ class Action: return message - def stop_print(self, music = None, fade_out = 0, **kwargs): + def stop_print(self, music = None, fade_out = 0, wait = False, **kwargs): + message = "stopping " if music is not None: - if fade_out == 0: - return "stopping music « {} »".format(music.name) - else: - return "stopping music « {} » with {}s fadeout".format(music.name, fade_out) + message += "music « {} »".format(music.name) else: - if fade_out == 0: - return "stopping all musics" - else: - return "stopping all musics with {}s fadeout".format(fade_out) + message += "all musics" + + if fade_out > 0: + message += " with {}s fadeout".format(fade_out) + if wait: + message += " (waiting the end of fadeout)" + + return message def stop_all_actions_print(self, **kwargs): return "stopping all actions" - def volume_print(self, music = None, value = 100, **kwargs): - if music is not None: - return "setting volume of « {} » to {}%".format(music.name, value) + def volume_print(self, music = None, value = 100, add = False, **kwargs): + if add: + if music is not None: + return "{:+d}% to volume of « {} »".format(value, music.name) + else: + return "{:+d}% to volume".format(value) else: - return "setting volume to {}%".format(value) + if music is not None: + return "setting volume of « {} » to {}%".format(music.name, value) + else: + return "setting volume to {}%".format(value) def wait_print(self, duration = 0, music = None, **kwargs): if music is None: diff --git a/helpers/mapping.py b/helpers/mapping.py index ea9d075..d9b7ba0 100644 --- a/helpers/mapping.py +++ b/helpers/mapping.py @@ -8,10 +8,11 @@ import yaml import sys from .music_file import * -from . import yml_file +from . import yml_file,gain class Mapping(RelativeLayout): expected_keys = NumericProperty(0) + master_volume = NumericProperty(100) ready_color = ListProperty([1, 165/255, 0, 1]) def __init__(self, **kwargs): @@ -23,6 +24,15 @@ class Mapping(RelativeLayout): Clock.schedule_interval(self.not_all_keys_ready, 1) + @property + def master_gain(self): + return gain(self.master_volume) + + def set_master_volume(self, value, add = False): + [db_gain, self.master_volume] = gain(value + int(add) * self.master_volume, self.master_volume) + for music in self.open_files.values(): + music.set_gain(db_gain) + def _keyboard_closed(self): self._keyboard.unbind(on_key_down=self._on_keyboard_down) self._keyboard = None @@ -45,16 +55,6 @@ class Mapping(RelativeLayout): return self.ids["Key_" + str(key_code[0])] return None - def find_by_unicode(self, key_sym): - for key in self.children: - if not type(key).__name__ == "Key": - continue - print(key.key_sym, key_sym) - if key.key_sym == key_sym: - print("found") - return key - return None - def not_all_keys_ready(self, dt): for key in self.children: if not type(key).__name__ == "Key": @@ -139,9 +139,11 @@ class Mapping(RelativeLayout): if filename in config['music_properties']: seen_files[filename] = MusicFile( filename, + self, **config['music_properties'][filename]) else: seen_files[filename] = MusicFile( + self, filename) if filename not in key_properties[mapped_key]['files']: diff --git a/helpers/music_file.py b/helpers/music_file.py index e6a340d..6a28d62 100644 --- a/helpers/music_file.py +++ b/helpers/music_file.py @@ -1,6 +1,5 @@ import threading import pydub -import math import time from transitions.extensions import HierarchicalMachine as Machine @@ -9,12 +8,14 @@ import sounddevice as sd import os.path from .lock import Lock +from . import gain + file_lock = Lock("file") pyaudio = pa.PyAudio() class MusicFile(Machine): - def __init__(self, filename, name = None, gain = 1): + def __init__(self, filename, mapping, name = None, gain = 1): states = [ 'initial', 'loading', @@ -34,11 +35,13 @@ class MusicFile(Machine): Machine.__init__(self, states=states, transitions=transitions, initial='initial') + self.volume = 100 + self.mapping = mapping self.filename = filename self.stream = None self.name = name or filename self.audio_segment = None - self.gain = gain + self.volume_factor = gain self.music_lock = Lock("music__" + filename) self.wait_event = threading.Event() @@ -48,8 +51,8 @@ class MusicFile(Machine): with file_lock: try: print("Loading « {} »".format(self.name)) - volume_factor = 20 * math.log10(self.gain) - self.audio_segment = pydub.AudioSegment.from_file(self.filename).set_frame_rate(44100).apply_gain(volume_factor) + db_gain = gain(self.volume_factor * 100) + self.audio_segment = pydub.AudioSegment.from_file(self.filename).set_frame_rate(44100).apply_gain(db_gain) self.sound_duration = self.audio_segment.duration_seconds except Exception as e: print("failed to load « {} »: {}".format(self.name, e)) @@ -76,11 +79,13 @@ class MusicFile(Machine): return 0 def play(self, fade_in = 0, volume = 100, start_at = 0): - self.db_gain = self.volume_to_gain(volume) + db_gain = gain(volume) + self.mapping.master_gain + self.volume = volume + ms = int(start_at * 1000) ms_fi = max(1, int(fade_in * 1000)) with self.music_lock: - self.current_audio_segment = (self.audio_segment + self.db_gain).fade(from_gain=-120, duration=ms_fi, start=ms) + self.current_audio_segment = (self.audio_segment + db_gain).fade(from_gain=-120, duration=ms_fi, start=ms) self.before_loaded_playing(initial_frame = int(start_at * self.audio_segment.frame_rate)) self.start_playing() @@ -124,7 +129,7 @@ class MusicFile(Machine): out_data[:] = audio_segment.ljust(len(out_data), b'\0') - def stop(self, fade_out = 0): + def stop(self, fade_out = 0, wait = False): if self.is_loaded_playing(): ms = int(self.sound_position * 1000) ms_fo = max(1, int(fade_out * 1000)) @@ -132,22 +137,25 @@ class MusicFile(Machine): with self.music_lock: self.current_audio_segment = self.current_audio_segment[:ms + ms_fo].fade_out(ms_fo) self.stop_playing() + if wait: + self.wait_end() else: self.stop_playing() self.stopped() - def set_volume(self, value): - if self.is_loaded_stopped(): + def set_gain(self, db_gain): + if not self.is_not_stopped(): return - db_gain = self.volume_to_gain(value) - new_audio_segment = self.current_audio_segment + (db_gain - self.db_gain) - self.db_gain = db_gain + new_audio_segment = self.current_audio_segment + db_gain + with self.music_lock: self.current_audio_segment = new_audio_segment - def volume_to_gain(self, volume): - return 20 * math.log10(max(volume, 0.0001) / 100) + def set_volume(self, value, add = False): + [db_gain, self.volume] = gain(value + int(add) * self.volume, self.volume) + + self.set_gain(db_gain) def wait_end(self): self.wait_event.clear() diff --git a/music_sampler.kv b/music_sampler.kv index 84c40b5..3232956 100644 --- a/music_sampler.kv +++ b/music_sampler.kv @@ -329,6 +329,16 @@ Ellipse: pos: self.width - self.key_size / 2, self.height - self.key_size /2 size: self.key_size / 3, self.key_size / 3 + Label: + font_name: h.path() + "fonts/Ubuntu-Regular.ttf" + font_size: math.ceil(2 * math.sqrt(self.parent.key_size or 10)) + color: 0, 0, 0, 1 + text: "volume: {}%".format(self.parent.master_volume) + valign: "top" + size_hint: None, None + size: self.texture_size[0], self.texture_size[1] + x: self.parent.width - self.width - 2 * self.parent.key_size / 3 + center_y: self.parent.height - self.height Key: id: Key_27 key_code: 27