]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/blob - helpers/action.py
Cleanup actions and subscribe to music events for loading
[perso/Immae/Projets/Python/MusicSampler.git] / helpers / action.py
1 import threading
2 import time
3
4 from transitions.extensions import HierarchicalMachine as Machine
5 from . import debug_print, error_print
6
7 class Action:
8 ACTION_TYPES = [
9 'command',
10 'interrupt_wait',
11 'pause',
12 'play',
13 'seek',
14 'stop',
15 'stop_all_actions',
16 'unpause',
17 'volume',
18 'wait',
19 ]
20
21 STATES = [
22 'initial',
23 'loading',
24 'failed',
25 {
26 'name': 'loaded',
27 'children': ['running']
28 }
29 ]
30
31 TRANSITIONS = [
32 {
33 'trigger': 'load',
34 'source': 'initial',
35 'dest': 'loading'
36 },
37 {
38 'trigger': 'fail',
39 'source': 'loading',
40 'dest': 'failed'
41 },
42 {
43 'trigger': 'success',
44 'source': 'loading',
45 'dest': 'loaded'
46 },
47 {
48 'trigger': 'run',
49 'source': 'loaded',
50 'dest': 'loaded_running',
51 'after': 'finish_action'
52 },
53 {
54 'trigger': 'interrupt',
55 'source': 'loaded_running',
56 'dest': 'loaded',
57 'before': 'trigger_interrupt'
58 },
59 {
60 'trigger': 'finish_action',
61 'source': 'loaded_running',
62 'dest': 'loaded'
63 }
64 ]
65
66 def __init__(self, action, key, **kwargs):
67 Machine(model=self, states=self.STATES,
68 transitions=self.TRANSITIONS, initial='initial',
69 ignore_invalid_triggers=True, queued=True)
70
71 self.action = action
72 self.key = key
73 self.mapping = key.parent
74 self.arguments = kwargs
75 self.sleep_event = None
76 self.waiting_music = None
77 self.load()
78
79 def ready(self):
80 return self.is_loaded(allow_substates=True)
81
82 def callback_loaded(self, success):
83 if success:
84 self.success()
85 else:
86 self.fail()
87
88 # Machine states / events
89 def on_enter_loading(self):
90 if self.action in self.ACTION_TYPES:
91 if 'music' in self.arguments:
92 self.arguments['music'].subscribe_loaded(self.callback_loaded)
93 else:
94 self.success()
95 else:
96 error_print("Unknown action {}".format(self.action))
97 self.fail()
98
99
100 def on_enter_loaded_running(self):
101 debug_print(self.description())
102 getattr(self, self.action)(**self.arguments)
103
104 def trigger_interrupt(self):
105 if getattr(self, self.action + "_interrupt", None):
106 return getattr(self, self.action + "_interrupt")(**self.arguments)
107
108 # Helpers
109 def music_list(self, music):
110 if music is not None:
111 return [music]
112 else:
113 return self.mapping.open_files.values()
114
115 def description(self):
116 if getattr(self, self.action + "_print", None):
117 return getattr(self, self.action + "_print")(**self.arguments)
118 else:
119 return "unknown action {}".format(self.action)
120
121 # Actions
122 def command(self, command="", **kwargs):
123 # FIXME: todo
124 pass
125
126 def pause(self, music=None, **kwargs):
127 for music in self.music_list(music):
128 if music.is_loaded_playing():
129 music.pause()
130
131 def unpause(self, music=None, **kwargs):
132 for music in self.music_list(music):
133 if music.is_loaded_paused():
134 music.unpause()
135
136 def play(self, music=None, fade_in=0, start_at=0,
137 restart_if_running=False, volume=100,
138 loop=0, **kwargs):
139 for music in self.music_list(music):
140 if restart_if_running:
141 if music.is_in_use():
142 music.stop()
143 music.play(
144 volume=volume,
145 fade_in=fade_in,
146 start_at=start_at,
147 loop=loop)
148 elif not music.is_in_use():
149 music.play(
150 volume=volume,
151 fade_in=fade_in,
152 start_at=start_at,
153 loop=loop)
154
155 def seek(self, music=None, value=0, delta=False, **kwargs):
156 for music in self.music_list(music):
157 music.seek(value=value, delta=delta)
158
159 def interrupt_wait(self, wait_id=None):
160 self.mapping.interrupt_wait(wait_id)
161
162 def stop(self, music=None, fade_out=0, wait=False,
163 set_wait_id=None, **kwargs):
164 previous = None
165 for music in self.music_list(music):
166 if music.is_loaded_paused() or music.is_loaded_playing():
167 if previous is not None:
168 previous.stop(fade_out=fade_out)
169 previous = music
170 else:
171 music.stop(fade_out=fade_out)
172
173 if previous is not None:
174 self.waiting_music = previous
175 previous.stop(
176 fade_out=fade_out,
177 wait=wait,
178 set_wait_id=set_wait_id)
179
180 def stop_all_actions(self, **kwargs):
181 self.mapping.stop_all_running()
182
183 def volume(self, music=None, value=100, fade=0, delta=False, **kwargs):
184 if music is not None:
185 music.set_volume(value, delta=delta, fade=fade)
186 else:
187 self.mapping.set_master_volume(value, delta=delta, fade=fade)
188
189 def wait(self, duration=0, music=None, set_wait_id=None, **kwargs):
190 if set_wait_id is not None:
191 self.mapping.add_wait_id(set_wait_id, self)
192
193 self.sleep_event = threading.Event()
194
195 if music is not None:
196 music.wait_end()
197
198 threading.Timer(duration, self.sleep_event.set).start()
199 self.sleep_event.wait()
200
201 # Action messages
202 def command_print(self, command="", **kwargs):
203 return "running command {}".format(command)
204
205 def interrupt_wait_print(self, wait_id=None, **kwargs):
206 return "interrupt wait with id {}".format(wait_id)
207
208 def pause_print(self, music=None, **kwargs):
209 if music is not None:
210 return "pausing « {} »".format(music.name)
211 else:
212 return "pausing all musics"
213
214 def unpause_print(self, music=None, **kwargs):
215 if music is not None:
216 return "unpausing « {} »".format(music.name)
217 else:
218 return "unpausing all musics"
219
220 def play_print(self, music=None, fade_in=0, start_at=0,
221 restart_if_running=False, volume=100, loop=0, **kwargs):
222 message = "starting "
223 if music is not None:
224 message += "« {} »".format(music.name)
225 else:
226 message += "all musics"
227
228 if start_at != 0:
229 message += " at {}s".format(start_at)
230
231 if fade_in != 0:
232 message += " with {}s fade_in".format(fade_in)
233
234 message += " at volume {}%".format(volume)
235
236 if loop > 0:
237 message += " {} times".format(loop + 1)
238 elif loop < 0:
239 message += " in loop"
240
241 if restart_if_running:
242 message += " (restarting if already running)"
243
244 return message
245
246 def stop_print(self, music=None, fade_out=0, wait=False,
247 set_wait_id=None, **kwargs):
248
249 message = "stopping "
250 if music is not None:
251 message += "music « {} »".format(music.name)
252 else:
253 message += "all musics"
254
255 if fade_out > 0:
256 message += " with {}s fadeout".format(fade_out)
257 if wait:
258 if set_wait_id is not None:
259 message += " (waiting the end of fadeout, with id {})"\
260 .format(set_wait_id)
261 else:
262 message += " (waiting the end of fadeout)"
263
264 return message
265
266 def stop_all_actions_print(self, **kwargs):
267 return "stopping all actions"
268
269 def seek_print(self, music=None, value=0, delta=False, **kwargs):
270 if delta:
271 if music is not None:
272 return "moving music « {} » by {:+d}s" \
273 .format(music.name, value)
274 else:
275 return "moving all musics by {:+d}s" \
276 .format(value)
277 else:
278 if music is not None:
279 return "moving music « {} » to position {}s" \
280 .format(music.name, value)
281 else:
282 return "moving all musics to position {}s" \
283 .format(value)
284
285 def volume_print(self, music=None,
286 value=100, delta=False, fade=0, **kwargs):
287 message = ""
288 if delta:
289 if music is not None:
290 message += "{:+d}% to volume of « {} »" \
291 .format(value, music.name)
292 else:
293 message += "{:+d}% to volume" \
294 .format(value)
295 else:
296 if music is not None:
297 message += "setting volume of « {} » to {}%" \
298 .format(music.name, value)
299 else:
300 message += "setting volume to {}%" \
301 .format(value)
302
303 if fade > 0:
304 message += " with {}s fade".format(fade)
305
306 return message
307
308 def wait_print(self, duration=0, music=None, set_wait_id=None, **kwargs):
309 message = ""
310 if music is None:
311 message += "waiting {}s" \
312 .format(duration)
313 elif duration == 0:
314 message += "waiting the end of « {} »" \
315 .format(music.name)
316 else:
317 message += "waiting the end of « {} » + {}s" \
318 .format(music.name, duration)
319
320 if set_wait_id is not None:
321 message += " (setting id = {})".format(set_wait_id)
322
323 return message
324
325 # Interruptions (only for non-"atomic" actions)
326 def wait_interrupt(self, duration=0, music=None, **kwargs):
327 if self.sleep_event is not None:
328 self.sleep_event.set()
329 if music is not None:
330 music.wait_event.set()
331
332 def stop_interrupt(self, music=None, fade_out=0, wait=False,
333 set_wait_id=None, **kwargs):
334 if self.waiting_music is not None:
335 self.waiting_music.wait_event.set()