From 22514f3ae6d4e19537ae5ab6bdb5bc9f99a64f47 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Isma=C3=ABl=20Bouya?= Date: Sun, 17 Jul 2016 00:31:00 +0200 Subject: [PATCH] Add a common mixer --- helpers/mapping.py | 2 ++ helpers/mixer.py | 50 +++++++++++++++++++++++++++++++++++++++++++ helpers/music_file.py | 41 ++++++++++++----------------------- music_sampler.spec | 4 ++-- 4 files changed, 68 insertions(+), 29 deletions(-) create mode 100644 helpers/mixer.py diff --git a/helpers/mapping.py b/helpers/mapping.py index 84845fc..60c7691 100644 --- a/helpers/mapping.py +++ b/helpers/mapping.py @@ -8,6 +8,7 @@ import yaml import sys from .music_file import * +from .mixer import Mixer from . import yml_file,gain class Mapping(RelativeLayout): @@ -23,6 +24,7 @@ class Mapping(RelativeLayout): self.running = [] Clock.schedule_interval(self.not_all_keys_ready, 1) + self.mixer = Mixer() @property def master_gain(self): diff --git a/helpers/mixer.py b/helpers/mixer.py new file mode 100644 index 0000000..9e8179a --- /dev/null +++ b/helpers/mixer.py @@ -0,0 +1,50 @@ +import sounddevice as sd +import audioop +import time + +frame_rate = 44100 +channels = 2 +sample_width = 2 + +class Mixer: + def __init__(self): + self.stream = sd.RawOutputStream(samplerate=frame_rate, + channels=channels, + dtype='int' + str(8*sample_width), # FIXME: ? + latency="high", + blocksize=5000, + callback=self.play_callback, + ) + self.open_files = [] + + def add_file(self, music_file): + if music_file not in self.open_files: + self.open_files.append(music_file) + self.start() + + def remove_file(self, music_file): + self.open_files.remove(music_file) + if len(self.open_files) == 0: + self.stop() + + def stop(self): + self.stream.stop() + + def start(self): + self.stream.start() + + def play_callback(self, out_data, frame_count, time_info, status_flags): + out_data_length = len(out_data) + empty_data = b"\0" * out_data_length + data = b"\0" * out_data_length + + for open_file in self.open_files: + file_data = open_file.play_callback(out_data_length, frame_count) + + if data == empty_data: + data = file_data + elif file_data != empty_data: + data = audioop.add(data, file_data, sample_width) + + out_data[:] = data + diff --git a/helpers/music_file.py b/helpers/music_file.py index ebe458b..6da547b 100644 --- a/helpers/music_file.py +++ b/helpers/music_file.py @@ -3,7 +3,6 @@ import pydub import time from transitions.extensions import HierarchicalMachine as Machine -import sounddevice as sd import os.path from .lock import Lock @@ -27,7 +26,7 @@ class MusicFile(Machine): { 'trigger': 'pause', 'source': 'loaded_playing', 'dest': 'loaded_paused'}, { 'trigger': 'unpause', 'source': 'loaded_paused', 'dest': 'loaded_playing'}, { 'trigger': 'stop_playing', 'source': ['loaded_playing','loaded_paused'], 'dest': 'loaded_stopping'}, - { 'trigger': 'stopped', 'source': 'loaded_stopping', 'dest': 'loaded_stopped'} + { 'trigger': 'stopped', 'source': 'loaded_stopping', 'dest': 'loaded_stopped', 'after': 'trigger_stopped_events'} ] Machine.__init__(self, states=states, transitions=transitions, initial='initial') @@ -35,7 +34,6 @@ class MusicFile(Machine): self.volume = 100 self.mapping = mapping self.filename = filename - self.stream = None self.name = name or filename self.audio_segment = None self.audio_segment_frame_width = 0 @@ -50,7 +48,7 @@ class MusicFile(Machine): try: print("Loading « {} »".format(self.name)) 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.audio_segment = pydub.AudioSegment.from_file(self.filename).set_frame_rate(44100).set_channels(2).set_sample_width(2).apply_gain(db_gain) self.audio_segment_frame_width = self.audio_segment.frame_width self.sound_duration = self.audio_segment.duration_seconds except Exception as e: @@ -94,26 +92,10 @@ class MusicFile(Machine): else: self.a_s_with_effect = None - self.before_loaded_playing() self.start_playing() - def before_loaded_playing(self): - with self.music_lock: - segment = self.current_audio_segment - - self.stream = sd.RawOutputStream(samplerate=segment.frame_rate, - channels=segment.channels, - dtype='int' + str(8*segment.sample_width), # FIXME: ? - latency=1., - callback=self.play_callback, - finished_callback=self.finished_callback - ) - def on_enter_loaded_playing(self): - self.stream.start() - - def on_enter_loaded_paused(self): - self.stream.stop() + self.mapping.mixer.add_file(self) def finished_callback(self): if self.is_loaded_playing(): @@ -121,10 +103,14 @@ class MusicFile(Machine): if self.is_loaded_stopping(): self.stopped() - def on_enter_loaded_stopped(self): + def trigger_stopped_events(self): + self.mapping.mixer.remove_file(self) self.wait_event.set() - def play_callback(self, out_data, frame_count, time_info, status_flags): + def play_callback(self, out_data_length, frame_count): + if self.is_loaded_paused(): + return b'\0' * out_data_length + with self.music_lock: [data, nb_frames] = self.get_next_sample(frame_count) if nb_frames < frame_count: @@ -135,9 +121,9 @@ class MusicFile(Machine): data += new_data nb_frames += new_nb_frames elif nb_frames == 0: - raise sd.CallbackStop + threading.Thread(name = "MSFinishedCallback", target=self.finished_callback).start() - out_data[:] = data.ljust(len(out_data), b'\0') + return data.ljust(out_data_length, b'\0') def get_next_sample(self, frame_count): fw = self.audio_segment_frame_width @@ -189,9 +175,10 @@ class MusicFile(Machine): # FIXME: stop fade_out puis seek -5 -> on abandonne le fade ? (cf # commentaire dans fonction seek + new_audio_segment = self.current_audio_segment[:ms + ms_fo].fade_out(ms_fo) with self.music_lock: - self.current_audio_segment = self.current_audio_segment[:ms + ms_fo].fade_out(ms_fo) - self.stop_playing() + self.current_audio_segment = new_audio_segment + self.stop_playing() if wait: self.wait_end() else: diff --git a/music_sampler.spec b/music_sampler.spec index 8ce1db3..5bae714 100644 --- a/music_sampler.spec +++ b/music_sampler.spec @@ -26,5 +26,5 @@ pyz = PYZ(a.pure, a.zipped_data) exe = EXE(pyz, a.scripts, a.binaries, a.zipfiles, a.datas, name='music_sampler') # Directory -exe = EXE(pyz, a.scripts, exclude_binaries=True, name='music_sampler_dir', debug=False, strip=False, upx=True, console=True ) -coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, name='music_sampler_dir') +# exe = EXE(pyz, a.scripts, exclude_binaries=True, name='music_sampler_dir', debug=False, strip=False, upx=True, console=True ) +# coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, name='music_sampler_dir') -- 2.41.0