import argparse
import sys
import os
+import math
class Config:
def __init__(self, **kwargs):
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)]
+
raise Exception("Unknown action {}".format(action))
self.key = key
+ self.mapping = key.parent
self.arguments = kwargs
self.sleep_event = None
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):
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()
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:
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):
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
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":
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']:
import threading
import pydub
-import math
import time
from transitions.extensions import HierarchicalMachine as Machine
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',
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()
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))
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()
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))
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()
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