diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2016-09-19 15:57:42 +0200 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2016-09-19 18:36:08 +0200 |
commit | 93a3e51e749afc0c3ba8488b900124fda6bb8774 (patch) | |
tree | 70cb772450af2b989c51a937e36c74275daedc02 /music_sampler/music_file.py | |
parent | 6dc040edf2f31497d4492c159397c4634037be66 (diff) | |
download | MusicSampler-93a3e51e749afc0c3ba8488b900124fda6bb8774.tar.gz MusicSampler-93a3e51e749afc0c3ba8488b900124fda6bb8774.tar.zst MusicSampler-93a3e51e749afc0c3ba8488b900124fda6bb8774.zip |
Cleanup key and action workflows
Diffstat (limited to 'music_sampler/music_file.py')
-rw-r--r-- | music_sampler/music_file.py | 119 |
1 files changed, 59 insertions, 60 deletions
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: | |||
22 | { | 22 | { |
23 | 'name': 'loaded', | 23 | 'name': 'loaded', |
24 | 'children': [ | 24 | 'children': [ |
25 | 'stopped', | ||
25 | 'playing', | 26 | 'playing', |
26 | 'paused', | 27 | 'paused', |
27 | 'stopping' | 28 | 'stopping' |
@@ -31,9 +32,8 @@ class MusicFile: | |||
31 | TRANSITIONS = [ | 32 | TRANSITIONS = [ |
32 | { | 33 | { |
33 | 'trigger': 'load', | 34 | 'trigger': 'load', |
34 | 'source': 'initial', | 35 | 'source': ['initial', 'failed'], |
35 | 'dest': 'loading', | 36 | 'dest': 'loading' |
36 | 'after': 'poll_loaded' | ||
37 | }, | 37 | }, |
38 | { | 38 | { |
39 | 'trigger': 'fail', | 39 | 'trigger': 'fail', |
@@ -41,17 +41,19 @@ class MusicFile: | |||
41 | 'dest': 'failed' | 41 | 'dest': 'failed' |
42 | }, | 42 | }, |
43 | { | 43 | { |
44 | 'trigger': 'unload', | ||
45 | 'source': ['failed', 'loaded_stopped'], | ||
46 | 'dest': 'initial', | ||
47 | }, | ||
48 | { | ||
44 | 'trigger': 'success', | 49 | 'trigger': 'success', |
45 | 'source': 'loading', | 50 | 'source': 'loading', |
46 | 'dest': 'loaded' | 51 | 'dest': 'loaded_stopped' |
47 | }, | 52 | }, |
48 | { | 53 | { |
49 | 'trigger': 'start_playing', | 54 | 'trigger': 'start_playing', |
50 | 'source': 'loaded', | 55 | 'source': 'loaded_stopped', |
51 | 'dest': 'loaded_playing', | 56 | 'dest': 'loaded_playing' |
52 | # if a child has no transitions, then it is bubbled to the parent, | ||
53 | # and we don't want that. Not useful in that machine precisely. | ||
54 | 'conditions': ['is_loaded'] | ||
55 | }, | 57 | }, |
56 | { | 58 | { |
57 | 'trigger': 'pause', | 59 | 'trigger': 'pause', |
@@ -70,19 +72,20 @@ class MusicFile: | |||
70 | }, | 72 | }, |
71 | { | 73 | { |
72 | 'trigger': 'stopped', | 74 | 'trigger': 'stopped', |
73 | 'source': '*', | 75 | 'source': 'loaded', |
74 | 'dest': 'loaded', | 76 | 'dest': 'loaded_stopped', |
75 | 'before': 'trigger_stopped_events', | 77 | 'before': 'trigger_stopped_events', |
76 | 'conditions': ['is_in_use'] | 78 | 'unless': 'is_loaded_stopped', |
77 | } | 79 | } |
78 | ] | 80 | ] |
79 | 81 | ||
80 | def __init__(self, filename, mapping, name=None, gain=1): | 82 | def __init__(self, filename, mapping, name=None, gain=1): |
81 | Machine(model=self, states=self.STATES, | 83 | machine = Machine(model=self, states=self.STATES, |
82 | transitions=self.TRANSITIONS, initial='initial', | 84 | transitions=self.TRANSITIONS, initial='initial', |
83 | ignore_invalid_triggers=True) | 85 | auto_transitions=False, |
86 | after_state_change=self.notify_state_change) | ||
84 | 87 | ||
85 | self.loaded_callbacks = [] | 88 | self.state_change_callbacks = [] |
86 | self.mapping = mapping | 89 | self.mapping = mapping |
87 | self.filename = filename | 90 | self.filename = filename |
88 | self.name = name or filename | 91 | self.name = name or filename |
@@ -90,48 +93,41 @@ class MusicFile: | |||
90 | self.initial_volume_factor = gain | 93 | self.initial_volume_factor = gain |
91 | self.music_lock = Lock("music__" + filename) | 94 | self.music_lock = Lock("music__" + filename) |
92 | 95 | ||
93 | threading.Thread(name="MSMusicLoad", target=self.load).start() | 96 | if Config.load_all_musics: |
97 | threading.Thread(name="MSMusicLoad", target=self.load).start() | ||
94 | 98 | ||
95 | def reload_properties(self, name=None, gain=1): | 99 | def reload_properties(self, name=None, gain=1): |
96 | self.name = name or self.filename | 100 | self.name = name or self.filename |
97 | if gain != self.initial_volume_factor: | 101 | if gain != self.initial_volume_factor: |
98 | self.initial_volume_factor = gain | 102 | self.initial_volume_factor = gain |
99 | self.reload_music_file() | 103 | self.stopped() |
104 | self.unload() | ||
105 | self.load(reloading=True) | ||
100 | 106 | ||
101 | def reload_music_file(self): | 107 | # Machine related events |
102 | with file_lock: | 108 | def on_enter_initial(self): |
103 | try: | 109 | self.audio_segment = None |
104 | if self.filename.startswith("/"): | ||
105 | filename = self.filename | ||
106 | else: | ||
107 | filename = Config.music_path + self.filename | ||
108 | 110 | ||
109 | debug_print("Reloading « {} »".format(self.name)) | 111 | def on_enter_loading(self, reloading=False): |
110 | initial_db_gain = gain(self.initial_volume_factor * 100) | 112 | if reloading: |
111 | self.audio_segment = pydub.AudioSegment \ | 113 | prefix = 'Rel' |
112 | .from_file(filename) \ | 114 | prefix_s = 'rel' |
113 | .set_frame_rate(Config.frame_rate) \ | 115 | else: |
114 | .set_channels(Config.channels) \ | 116 | prefix = 'L' |
115 | .set_sample_width(Config.sample_width) \ | 117 | prefix_s = 'l' |
116 | .apply_gain(initial_db_gain) | ||
117 | except Exception as e: | ||
118 | error_print("failed to reload « {} »: {}"\ | ||
119 | .format(self.name, e)) | ||
120 | self.loading_error = e | ||
121 | self.to_failed() | ||
122 | else: | ||
123 | debug_print("Reloaded « {} »".format(self.name)) | ||
124 | 118 | ||
125 | # Machine related events | ||
126 | def on_enter_loading(self): | ||
127 | with file_lock: | 119 | with file_lock: |
120 | if self.mapping.is_leaving_application: | ||
121 | self.fail() | ||
122 | return | ||
123 | |||
128 | try: | 124 | try: |
129 | if self.filename.startswith("/"): | 125 | if self.filename.startswith("/"): |
130 | filename = self.filename | 126 | filename = self.filename |
131 | else: | 127 | else: |
132 | filename = Config.music_path + self.filename | 128 | filename = Config.music_path + self.filename |
133 | 129 | ||
134 | debug_print("Loading « {} »".format(self.name)) | 130 | debug_print("{}oading « {} »".format(prefix, self.name)) |
135 | self.mixer = self.mapping.mixer or Mixer() | 131 | self.mixer = self.mapping.mixer or Mixer() |
136 | initial_db_gain = gain(self.initial_volume_factor * 100) | 132 | initial_db_gain = gain(self.initial_volume_factor * 100) |
137 | self.audio_segment = pydub.AudioSegment \ | 133 | self.audio_segment = pydub.AudioSegment \ |
@@ -142,12 +138,13 @@ class MusicFile: | |||
142 | .apply_gain(initial_db_gain) | 138 | .apply_gain(initial_db_gain) |
143 | self.sound_duration = self.audio_segment.duration_seconds | 139 | self.sound_duration = self.audio_segment.duration_seconds |
144 | except Exception as e: | 140 | except Exception as e: |
145 | error_print("failed to load « {} »: {}".format(self.name, e)) | 141 | error_print("failed to {}oad « {} »: {}".format( |
142 | prefix_s, self.name, e)) | ||
146 | self.loading_error = e | 143 | self.loading_error = e |
147 | self.fail() | 144 | self.fail() |
148 | else: | 145 | else: |
149 | self.success() | 146 | self.success() |
150 | debug_print("Loaded « {} »".format(self.name)) | 147 | debug_print("{}oaded « {} »".format(prefix, self.name)) |
151 | 148 | ||
152 | def on_enter_loaded(self): | 149 | def on_enter_loaded(self): |
153 | self.cleanup() | 150 | self.cleanup() |
@@ -165,11 +162,15 @@ class MusicFile: | |||
165 | 162 | ||
166 | # Machine related states | 163 | # Machine related states |
167 | def is_in_use(self): | 164 | def is_in_use(self): |
168 | return self.is_loaded(allow_substates=True) and not self.is_loaded() | 165 | return self.is_loaded(allow_substates=True) and\ |
166 | not self.is_loaded_stopped() | ||
169 | 167 | ||
170 | def is_in_use_not_stopping(self): | 168 | def is_in_use_not_stopping(self): |
171 | return self.is_loaded_playing() or self.is_loaded_paused() | 169 | return self.is_loaded_playing() or self.is_loaded_paused() |
172 | 170 | ||
171 | def is_unloadable(self): | ||
172 | return self.is_loaded_stopped() or self.is_failed() | ||
173 | |||
173 | # Machine related triggers | 174 | # Machine related triggers |
174 | def trigger_stopped_events(self): | 175 | def trigger_stopped_events(self): |
175 | self.mixer.remove_file(self) | 176 | self.mixer.remove_file(self) |
@@ -243,7 +244,7 @@ class MusicFile: | |||
243 | if wait: | 244 | if wait: |
244 | self.mapping.add_wait(self.wait_event, wait_id=set_wait_id) | 245 | self.mapping.add_wait(self.wait_event, wait_id=set_wait_id) |
245 | self.wait_end() | 246 | self.wait_end() |
246 | else: | 247 | elif self.is_loaded(allow_substates=True): |
247 | self.stopped() | 248 | self.stopped() |
248 | 249 | ||
249 | def abandon_all_effects(self): | 250 | def abandon_all_effects(self): |
@@ -274,21 +275,19 @@ class MusicFile: | |||
274 | self.wait_event.clear() | 275 | self.wait_event.clear() |
275 | self.wait_event.wait() | 276 | self.wait_event.wait() |
276 | 277 | ||
277 | # Let other subscribe for an event when they are ready | 278 | # Let other subscribe for state change |
278 | def subscribe_loaded(self, callback): | 279 | def notify_state_change(self, **kwargs): |
279 | # FIXME: should lock to be sure we have no race, but it makes the | 280 | for callback in self.state_change_callbacks: |
280 | # initialization screen not showing until everything is loaded | 281 | callback(self.state) |
281 | if self.is_loaded(allow_substates=True): | 282 | |
282 | callback(True) | 283 | def subscribe_state_change(self, callback): |
283 | elif self.is_failed(): | 284 | if callback not in self.state_change_callbacks: |
284 | callback(False) | 285 | self.state_change_callbacks.append(callback) |
285 | else: | 286 | callback(self.state) |
286 | self.loaded_callbacks.append(callback) | ||
287 | 287 | ||
288 | def poll_loaded(self): | 288 | def unsubscribe_state_change(self, callback): |
289 | for callback in self.loaded_callbacks: | 289 | if callback in self.state_change_callbacks: |
290 | callback(self.is_loaded()) | 290 | self.state_change_callbacks.remove(callback) |
291 | self.loaded_callbacks = [] | ||
292 | 291 | ||
293 | # Callbacks | 292 | # Callbacks |
294 | def finished_callback(self): | 293 | def finished_callback(self): |