]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/commitdiff
Some new features:
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Thu, 14 Jul 2016 20:18:51 +0000 (22:18 +0200)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Thu, 14 Jul 2016 20:18:51 +0000 (22:18 +0200)
- 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

helpers/__init__.py
helpers/action.py
helpers/mapping.py
helpers/music_file.py
music_sampler.kv

index 3b97f2fb435d5837c803895e479184b9c16b694d..2339b9ba7852318d043abf45373a3a38f089089c 100644 (file)
@@ -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)]
+
index 91456299ee25c6bf072c8cdba1ac796f2c99cbcd..28afceeb6e66c3cc193428e88c28e06959cca29d 100644 (file)
@@ -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:
index ea9d0757b0244eef6f9ef2f6d949b02790424d37..d9b7ba0e242d50bab6271a0964e622638b3db803 100644 (file)
@@ -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']:
index e6a340da53e56c97dda679a14064f056e01fd670..6a28d620f69c043019a8e964e3c5a8b2a81aec74 100644 (file)
@@ -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()
index 84c40b59ca111cb686929eb7798b365cef764584..3232956a72041bd0e733ad101bbd0cb0152658df 100644 (file)
     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