X-Git-Url: https://git.immae.eu/?p=perso%2FImmae%2FProjets%2FPython%2FMusicSampler.git;a=blobdiff_plain;f=music_sampler%2Fmusic_file.py;fp=music_sampler%2Fmusic_file.py;h=ec50951661196a5b4c23bc841f086ebc6549fb9d;hp=4ba65e342b921f2cd74f93feaecea7d7b223cf7a;hb=93a3e51e749afc0c3ba8488b900124fda6bb8774;hpb=6dc040edf2f31497d4492c159397c4634037be66 diff --git a/music_sampler/music_file.py b/music_sampler/music_file.py index 4ba65e3..ec50951 100644 --- a/music_sampler/music_file.py +++ b/music_sampler/music_file.py @@ -22,6 +22,7 @@ class MusicFile: { 'name': 'loaded', 'children': [ + 'stopped', 'playing', 'paused', 'stopping' @@ -31,27 +32,28 @@ class MusicFile: TRANSITIONS = [ { 'trigger': 'load', - 'source': 'initial', - 'dest': 'loading', - 'after': 'poll_loaded' + 'source': ['initial', 'failed'], + 'dest': 'loading' }, { 'trigger': 'fail', 'source': 'loading', 'dest': 'failed' }, + { + 'trigger': 'unload', + 'source': ['failed', 'loaded_stopped'], + 'dest': 'initial', + }, { 'trigger': 'success', 'source': 'loading', - 'dest': 'loaded' + 'dest': 'loaded_stopped' }, { 'trigger': 'start_playing', - 'source': 'loaded', - 'dest': 'loaded_playing', - # if a child has no transitions, then it is bubbled to the parent, - # and we don't want that. Not useful in that machine precisely. - 'conditions': ['is_loaded'] + 'source': 'loaded_stopped', + 'dest': 'loaded_playing' }, { 'trigger': 'pause', @@ -70,19 +72,20 @@ class MusicFile: }, { 'trigger': 'stopped', - 'source': '*', - 'dest': 'loaded', + 'source': 'loaded', + 'dest': 'loaded_stopped', 'before': 'trigger_stopped_events', - 'conditions': ['is_in_use'] + 'unless': 'is_loaded_stopped', } ] def __init__(self, filename, mapping, name=None, gain=1): - Machine(model=self, states=self.STATES, + machine = Machine(model=self, states=self.STATES, transitions=self.TRANSITIONS, initial='initial', - ignore_invalid_triggers=True) + auto_transitions=False, + after_state_change=self.notify_state_change) - self.loaded_callbacks = [] + self.state_change_callbacks = [] self.mapping = mapping self.filename = filename self.name = name or filename @@ -90,48 +93,41 @@ class MusicFile: self.initial_volume_factor = gain self.music_lock = Lock("music__" + filename) - threading.Thread(name="MSMusicLoad", target=self.load).start() + if Config.load_all_musics: + threading.Thread(name="MSMusicLoad", target=self.load).start() def reload_properties(self, name=None, gain=1): self.name = name or self.filename if gain != self.initial_volume_factor: self.initial_volume_factor = gain - self.reload_music_file() + self.stopped() + self.unload() + self.load(reloading=True) - def reload_music_file(self): - with file_lock: - try: - if self.filename.startswith("/"): - filename = self.filename - else: - filename = Config.music_path + self.filename + # Machine related events + def on_enter_initial(self): + self.audio_segment = None - debug_print("Reloading « {} »".format(self.name)) - initial_db_gain = gain(self.initial_volume_factor * 100) - self.audio_segment = pydub.AudioSegment \ - .from_file(filename) \ - .set_frame_rate(Config.frame_rate) \ - .set_channels(Config.channels) \ - .set_sample_width(Config.sample_width) \ - .apply_gain(initial_db_gain) - except Exception as e: - error_print("failed to reload « {} »: {}"\ - .format(self.name, e)) - self.loading_error = e - self.to_failed() - else: - debug_print("Reloaded « {} »".format(self.name)) + def on_enter_loading(self, reloading=False): + if reloading: + prefix = 'Rel' + prefix_s = 'rel' + else: + prefix = 'L' + prefix_s = 'l' - # Machine related events - def on_enter_loading(self): with file_lock: + if self.mapping.is_leaving_application: + self.fail() + return + try: if self.filename.startswith("/"): filename = self.filename else: filename = Config.music_path + self.filename - debug_print("Loading « {} »".format(self.name)) + debug_print("{}oading « {} »".format(prefix, self.name)) self.mixer = self.mapping.mixer or Mixer() initial_db_gain = gain(self.initial_volume_factor * 100) self.audio_segment = pydub.AudioSegment \ @@ -142,12 +138,13 @@ class MusicFile: .apply_gain(initial_db_gain) self.sound_duration = self.audio_segment.duration_seconds except Exception as e: - error_print("failed to load « {} »: {}".format(self.name, e)) + error_print("failed to {}oad « {} »: {}".format( + prefix_s, self.name, e)) self.loading_error = e self.fail() else: self.success() - debug_print("Loaded « {} »".format(self.name)) + debug_print("{}oaded « {} »".format(prefix, self.name)) def on_enter_loaded(self): self.cleanup() @@ -165,11 +162,15 @@ class MusicFile: # Machine related states def is_in_use(self): - return self.is_loaded(allow_substates=True) and not self.is_loaded() + return self.is_loaded(allow_substates=True) and\ + not self.is_loaded_stopped() def is_in_use_not_stopping(self): return self.is_loaded_playing() or self.is_loaded_paused() + def is_unloadable(self): + return self.is_loaded_stopped() or self.is_failed() + # Machine related triggers def trigger_stopped_events(self): self.mixer.remove_file(self) @@ -243,7 +244,7 @@ class MusicFile: if wait: self.mapping.add_wait(self.wait_event, wait_id=set_wait_id) self.wait_end() - else: + elif self.is_loaded(allow_substates=True): self.stopped() def abandon_all_effects(self): @@ -274,21 +275,19 @@ class MusicFile: self.wait_event.clear() self.wait_event.wait() - # Let other subscribe for an event when they are ready - def subscribe_loaded(self, callback): - # FIXME: should lock to be sure we have no race, but it makes the - # initialization screen not showing until everything is loaded - if self.is_loaded(allow_substates=True): - callback(True) - elif self.is_failed(): - callback(False) - else: - self.loaded_callbacks.append(callback) + # Let other subscribe for state change + def notify_state_change(self, **kwargs): + for callback in self.state_change_callbacks: + callback(self.state) + + def subscribe_state_change(self, callback): + if callback not in self.state_change_callbacks: + self.state_change_callbacks.append(callback) + callback(self.state) - def poll_loaded(self): - for callback in self.loaded_callbacks: - callback(self.is_loaded()) - self.loaded_callbacks = [] + def unsubscribe_state_change(self, callback): + if callback in self.state_change_callbacks: + self.state_change_callbacks.remove(callback) # Callbacks def finished_callback(self):