aboutsummaryrefslogtreecommitdiff
path: root/music_sampler/music_file.py
diff options
context:
space:
mode:
Diffstat (limited to 'music_sampler/music_file.py')
-rw-r--r--music_sampler/music_file.py119
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):