diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2016-07-18 21:17:12 +0200 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2016-07-20 21:48:54 +0200 |
commit | aee1334ca47ff55c815eee204fe03683a572be0f (patch) | |
tree | ba9beef4beca1ab9ac86430016ff223eece31e75 /helpers | |
parent | b37c72a236806f02e5538ba7e607f6add0cc6fb6 (diff) | |
download | MusicSampler-aee1334ca47ff55c815eee204fe03683a572be0f.tar.gz MusicSampler-aee1334ca47ff55c815eee204fe03683a572be0f.tar.zst MusicSampler-aee1334ca47ff55c815eee204fe03683a572be0f.zip |
Add fading
Diffstat (limited to 'helpers')
-rw-r--r-- | helpers/__init__.py | 2 | ||||
-rw-r--r-- | helpers/action.py | 21 | ||||
-rw-r--r-- | helpers/mapping.py | 2 | ||||
-rw-r--r-- | helpers/music_effect.py | 50 | ||||
-rw-r--r-- | helpers/music_file.py | 48 |
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 | ||
132 | def gain(volume, old_volume=None): | 132 | def 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 @@ | |||
1 | class 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 | |||
10 | from .lock import Lock | 10 | from .lock import Lock |
11 | from . import Config, gain, debug_print, error_print | 11 | from . import Config, gain, debug_print, error_print |
12 | from .mixer import Mixer | 12 | from .mixer import Mixer |
13 | from .music_effect import GainEffect | ||
13 | 14 | ||
14 | file_lock = Lock("file") | 15 | file_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() |