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