aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2016-07-18 21:17:12 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2016-07-20 21:48:54 +0200
commitaee1334ca47ff55c815eee204fe03683a572be0f (patch)
treeba9beef4beca1ab9ac86430016ff223eece31e75
parentb37c72a236806f02e5538ba7e607f6add0cc6fb6 (diff)
downloadMusicSampler-aee1334ca47ff55c815eee204fe03683a572be0f.tar.gz
MusicSampler-aee1334ca47ff55c815eee204fe03683a572be0f.tar.zst
MusicSampler-aee1334ca47ff55c815eee204fe03683a572be0f.zip
Add fading
-rw-r--r--helpers/__init__.py2
-rw-r--r--helpers/action.py21
-rw-r--r--helpers/mapping.py2
-rw-r--r--helpers/music_effect.py50
-rw-r--r--helpers/music_file.py48
5 files changed, 108 insertions, 15 deletions
diff --git a/helpers/__init__.py b/helpers/__init__.py
index 9d66638..f5ad848 100644
--- a/helpers/__init__.py
+++ b/helpers/__init__.py
@@ -131,7 +131,7 @@ def duration_to_min_sec(duration):
131 131
132def gain(volume, old_volume=None): 132def gain(volume, old_volume=None):
133 if old_volume is None: 133 if old_volume is None:
134 return 20 * math.log10(volume / 100) 134 return 20 * math.log10(max(volume, 0.1) / 100)
135 else: 135 else:
136 return [ 136 return [
137 20 * math.log10(max(volume, 0.1) / max(old_volume, 0.1)), 137 20 * math.log10(max(volume, 0.1) / max(old_volume, 0.1)),
diff --git a/helpers/action.py b/helpers/action.py
index 218c316..b8e44e6 100644
--- a/helpers/action.py
+++ b/helpers/action.py
@@ -102,9 +102,9 @@ class Action:
102 def stop_all_actions(self, **kwargs): 102 def stop_all_actions(self, **kwargs):
103 self.mapping.stop_all_running() 103 self.mapping.stop_all_running()
104 104
105 def volume(self, music=None, value=100, delta=False, **kwargs): 105 def volume(self, music=None, value=100, fade=0, delta=False, **kwargs):
106 if music is not None: 106 if music is not None:
107 music.set_volume(value, delta=delta) 107 music.set_volume(value, delta=delta, fade=fade)
108 else: 108 else:
109 self.mapping.set_master_volume(value, delta=delta) 109 self.mapping.set_master_volume(value, delta=delta)
110 110
@@ -192,22 +192,29 @@ class Action:
192 return "moving all musics to position {}s" \ 192 return "moving all musics to position {}s" \
193 .format(value) 193 .format(value)
194 194
195 def volume_print(self, music=None, value=100, delta=False, **kwargs): 195 def volume_print(self, music=None,
196 value=100, delta=False, fade=0, **kwargs):
197 message = ""
196 if delta: 198 if delta:
197 if music is not None: 199 if music is not None:
198 return "{:+d}% to volume of « {} »" \ 200 message += "{:+d}% to volume of « {} »" \
199 .format(value, music.name) 201 .format(value, music.name)
200 else: 202 else:
201 return "{:+d}% to volume" \ 203 message += "{:+d}% to volume" \
202 .format(value) 204 .format(value)
203 else: 205 else:
204 if music is not None: 206 if music is not None:
205 return "setting volume of « {} » to {}%" \ 207 message += "setting volume of « {} » to {}%" \
206 .format(music.name, value) 208 .format(music.name, value)
207 else: 209 else:
208 return "setting volume to {}%" \ 210 message += "setting volume to {}%" \
209 .format(value) 211 .format(value)
210 212
213 if music is not None and fade > 0:
214 message += " with {}s fade".format(fade)
215
216 return message
217
211 def wait_print(self, duration=0, music=None, **kwargs): 218 def wait_print(self, duration=0, music=None, **kwargs):
212 if music is None: 219 if music is None:
213 return "waiting {}s" \ 220 return "waiting {}s" \
diff --git a/helpers/mapping.py b/helpers/mapping.py
index 19fc3c5..9420acf 100644
--- a/helpers/mapping.py
+++ b/helpers/mapping.py
@@ -91,7 +91,7 @@ class Mapping(RelativeLayout):
91 stream = open(Config.yml_file, "r") 91 stream = open(Config.yml_file, "r")
92 try: 92 try:
93 config = yaml.load(stream) 93 config = yaml.load(stream)
94 except yaml.scanner.ScannerError as e: 94 except Exception as e:
95 error_print("Error while loading config file: {}".format(e)) 95 error_print("Error while loading config file: {}".format(e))
96 sys.exit() 96 sys.exit()
97 stream.close() 97 stream.close()
diff --git a/helpers/music_effect.py b/helpers/music_effect.py
new file mode 100644
index 0000000..ef8dc90
--- /dev/null
+++ b/helpers/music_effect.py
@@ -0,0 +1,50 @@
1class GainEffect:
2 effect_types = [
3 'fade'
4 ]
5
6 def __init__(self, effect, audio_segment, start, end, **kwargs):
7 if effect in self.effect_types:
8 self.effect = effect
9 else:
10 raise Exception("Unknown effect {}".format(effect))
11
12 self.start = start
13 self.end = end
14 self.audio_segment = audio_segment
15 getattr(self, self.effect + "_init")(**kwargs)
16
17 def get_last_gain(self):
18 return getattr(self, self.effect + "_get_last_gain")()
19
20 def get_next_gain(self, current_frame, frame_count):
21 # This returns two values:
22 # - The first one is the gain to apply on that frame
23 # - The last one is True or False depending on whether it is the last
24 # call to the function and the last gain should be saved permanently
25 return getattr(self, self.effect + "_get_next_gain")(
26 current_frame,
27 frame_count)
28
29 # Fading
30 def fade_init(self, gain=0, **kwargs):
31 self.first_frame = int(self.audio_segment.frame_rate * self.start)
32 self.last_frame = int(self.audio_segment.frame_rate * self.end)
33 self.gain= gain
34
35 def fade_get_last_gain(self):
36 return self.gain
37
38 def fade_get_next_gain(self, current_frame, frame_count):
39 if current_frame >= self.last_frame:
40 return [self.gain, True]
41 elif current_frame < self.first_frame:
42 return [0, False]
43 else:
44 return [
45 (current_frame - self.first_frame) / \
46 (self.last_frame - self.first_frame) * self.gain,
47 False
48 ]
49
50
diff --git a/helpers/music_file.py b/helpers/music_file.py
index 3053008..810bc22 100644
--- a/helpers/music_file.py
+++ b/helpers/music_file.py
@@ -10,6 +10,7 @@ import audioop
10from .lock import Lock 10from .lock import Lock
11from . import Config, gain, debug_print, error_print 11from . import Config, gain, debug_print, error_print
12from .mixer import Mixer 12from .mixer import Mixer
13from .music_effect import GainEffect
13 14
14file_lock = Lock("file") 15file_lock = Lock("file")
15 16
@@ -81,7 +82,7 @@ class MusicFile(Machine):
81 self.music_lock = Lock("music__" + filename) 82 self.music_lock = Lock("music__" + filename)
82 self.wait_event = threading.Event() 83 self.wait_event = threading.Event()
83 self.db_gain = 0 84 self.db_gain = 0
84 self.volume_factor = 1 85 self.gain_effects = []
85 86
86 threading.Thread(name="MSMusicLoad", target=self.load).start() 87 threading.Thread(name="MSMusicLoad", target=self.load).start()
87 88
@@ -134,7 +135,6 @@ class MusicFile(Machine):
134 self.current_audio_segment = self.audio_segment 135 self.current_audio_segment = self.audio_segment
135 self.current_frame = int(start_at * self.audio_segment.frame_rate) 136 self.current_frame = int(start_at * self.audio_segment.frame_rate)
136 if ms_fi > 0: 137 if ms_fi > 0:
137 # FIXME: apply it to repeated when looping?
138 self.a_s_with_effect = self \ 138 self.a_s_with_effect = self \
139 .current_audio_segment[ms : ms+ms_fi] \ 139 .current_audio_segment[ms : ms+ms_fi] \
140 .fade_in(ms_fi) 140 .fade_in(ms_fi)
@@ -213,7 +213,10 @@ class MusicFile(Machine):
213 nb_frames += end_i - start_i 213 nb_frames += end_i - start_i
214 self.current_frame += end_i - start_i 214 self.current_frame += end_i - start_i
215 215
216 data = audioop.mul(data, Config.sample_width, self.volume_factor) 216 # FIXME: self.effects_next_gain should take into account the loop number
217 volume_factor = self.volume_factor(self.effects_next_gain(nb_frames))
218
219 data = audioop.mul(data, Config.sample_width, volume_factor)
217 220
218 return [data, nb_frames] 221 return [data, nb_frames]
219 222
@@ -222,6 +225,7 @@ class MusicFile(Machine):
222 if not (self.is_loaded_playing() or self.is_loaded_paused()): 225 if not (self.is_loaded_playing() or self.is_loaded_paused()):
223 return 226 return
224 with self.music_lock: 227 with self.music_lock:
228 self.abandon_all_effects()
225 self.a_s_with_effect = None 229 self.a_s_with_effect = None
226 self.current_frame = max( 230 self.current_frame = max(
227 0, 231 0,
@@ -229,6 +233,28 @@ class MusicFile(Machine):
229 + int(value * self.audio_segment.frame_rate)) 233 + int(value * self.audio_segment.frame_rate))
230 # FIXME: si on fait un seek + delta, adapter le "loop" 234 # FIXME: si on fait un seek + delta, adapter le "loop"
231 235
236 def effects_next_gain(self, frame_count):
237 db_gain = 0
238 for gain_effect in self.gain_effects:
239 [new_gain, last_gain] = gain_effect.get_next_gain(
240 self.current_frame,
241 frame_count)
242 if last_gain:
243 self.set_gain(new_gain)
244 self.gain_effects.remove(gain_effect)
245 else:
246 db_gain += new_gain
247 return db_gain
248
249
250 def abandon_all_effects(self):
251 db_gain = 0
252 for gain_effect in self.gain_effects:
253 db_gain += gain_effect.get_last_gain()
254
255 self.gain_effects = []
256 self.set_gain(db_gain)
257
232 def stop(self, fade_out=0, wait=False): 258 def stop(self, fade_out=0, wait=False):
233 if self.is_loaded_playing(): 259 if self.is_loaded_playing():
234 ms = int(self.sound_position * 1000) 260 ms = int(self.sound_position * 1000)
@@ -245,19 +271,29 @@ class MusicFile(Machine):
245 self.stop_playing() 271 self.stop_playing()
246 self.stopped() 272 self.stopped()
247 273
274 def volume_factor(self, additional_gain):
275 return 10 ** ( (self.db_gain + additional_gain) / 20)
276
248 def set_gain(self, db_gain, absolute=False): 277 def set_gain(self, db_gain, absolute=False):
249 if absolute: 278 if absolute:
250 self.db_gain = db_gain 279 self.db_gain = db_gain
251 else: 280 else:
252 self.db_gain += db_gain 281 self.db_gain += db_gain
253 self.volume_factor = 10 ** (self.db_gain / 20)
254 282
255 def set_volume(self, value, delta=False): 283 def set_volume(self, value, delta=False, fade=0):
256 [db_gain, self.volume] = gain( 284 [db_gain, self.volume] = gain(
257 value + int(delta) * self.volume, 285 value + int(delta) * self.volume,
258 self.volume) 286 self.volume)
259 287
260 self.set_gain(db_gain) 288 if fade > 0:
289 self.gain_effects.append(GainEffect(
290 "fade",
291 self.current_audio_segment,
292 self.sound_position,
293 self.sound_position + fade,
294 gain=db_gain))
295 else:
296 self.set_gain(db_gain)
261 297
262 def wait_end(self): 298 def wait_end(self):
263 self.wait_event.clear() 299 self.wait_event.clear()