]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/blame - helpers/action.py
Cleanup actions and subscribe to music events for loading
[perso/Immae/Projets/Python/MusicSampler.git] / helpers / action.py
CommitLineData
0deb82a5 1import threading
be27763f
IB
2import time
3
b7ca3fc2
IB
4from transitions.extensions import HierarchicalMachine as Machine
5from . import debug_print, error_print
a24c34bc 6
be27763f 7class Action:
b7ca3fc2 8 ACTION_TYPES = [
be27763f 9 'command',
3aaddc9d 10 'interrupt_wait',
be27763f
IB
11 'pause',
12 'play',
52d58baf 13 'seek',
be27763f
IB
14 'stop',
15 'stop_all_actions',
9de92b6d 16 'unpause',
be27763f
IB
17 'volume',
18 'wait',
19 ]
20
b7ca3fc2
IB
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
be27763f 66 def __init__(self, action, key, **kwargs):
b7ca3fc2
IB
67 Machine(model=self, states=self.STATES,
68 transitions=self.TRANSITIONS, initial='initial',
69 ignore_invalid_triggers=True, queued=True)
be27763f 70
b7ca3fc2 71 self.action = action
be27763f 72 self.key = key
1b4b78f5 73 self.mapping = key.parent
be27763f 74 self.arguments = kwargs
0deb82a5 75 self.sleep_event = None
b7ca3fc2
IB
76 self.waiting_music = None
77 self.load()
be27763f
IB
78
79 def ready(self):
b7ca3fc2
IB
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()
be27763f 95 else:
b7ca3fc2
IB
96 error_print("Unknown action {}".format(self.action))
97 self.fail()
98
be27763f 99
b7ca3fc2 100 def on_enter_loaded_running(self):
a24c34bc 101 debug_print(self.description())
9de92b6d 102 getattr(self, self.action)(**self.arguments)
be27763f 103
b7ca3fc2 104 def trigger_interrupt(self):
0deb82a5
IB
105 if getattr(self, self.action + "_interrupt", None):
106 return getattr(self, self.action + "_interrupt")(**self.arguments)
107
b7ca3fc2 108 # Helpers
29597680 109 def music_list(self, music):
be27763f 110 if music is not None:
29597680 111 return [music]
be27763f 112 else:
1b4b78f5 113 return self.mapping.open_files.values()
29597680 114
b7ca3fc2
IB
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
c80ff6dc 121 # Actions
2e404903 122 def command(self, command="", **kwargs):
c80ff6dc
IB
123 # FIXME: todo
124 pass
125
2e404903 126 def pause(self, music=None, **kwargs):
29597680
IB
127 for music in self.music_list(music):
128 if music.is_loaded_playing():
129 music.pause()
be27763f 130
2e404903 131 def unpause(self, music=None, **kwargs):
29597680
IB
132 for music in self.music_list(music):
133 if music.is_loaded_paused():
134 music.unpause()
9de92b6d 135
2e404903
IB
136 def play(self, music=None, fade_in=0, start_at=0,
137 restart_if_running=False, volume=100,
138 loop=0, **kwargs):
c80ff6dc 139 for music in self.music_list(music):
0e5d59f7 140 if restart_if_running:
20586193 141 if music.is_in_use():
0e5d59f7 142 music.stop()
2e404903
IB
143 music.play(
144 volume=volume,
145 fade_in=fade_in,
146 start_at=start_at,
147 loop=loop)
20586193 148 elif not music.is_in_use():
2e404903
IB
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):
52d58baf 156 for music in self.music_list(music):
2e404903 157 music.seek(value=value, delta=delta)
52d58baf 158
3aaddc9d
IB
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):
1b4b78f5 164 previous = None
29597680
IB
165 for music in self.music_list(music):
166 if music.is_loaded_paused() or music.is_loaded_playing():
1b4b78f5 167 if previous is not None:
2e404903 168 previous.stop(fade_out=fade_out)
1b4b78f5 169 previous = music
51322669
IB
170 else:
171 music.stop(fade_out=fade_out)
1b4b78f5
IB
172
173 if previous is not None:
b7ca3fc2 174 self.waiting_music = previous
3aaddc9d
IB
175 previous.stop(
176 fade_out=fade_out,
177 wait=wait,
178 set_wait_id=set_wait_id)
be27763f
IB
179
180 def stop_all_actions(self, **kwargs):
1b4b78f5 181 self.mapping.stop_all_running()
be27763f 182
aee1334c 183 def volume(self, music=None, value=100, fade=0, delta=False, **kwargs):
0e5d59f7 184 if music is not None:
aee1334c 185 music.set_volume(value, delta=delta, fade=fade)
0e5d59f7 186 else:
a8340c5d 187 self.mapping.set_master_volume(value, delta=delta, fade=fade)
be27763f 188
3aaddc9d
IB
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
0deb82a5
IB
193 self.sleep_event = threading.Event()
194
195 if music is not None:
b86db9f1 196 music.wait_end()
be27763f 197
0deb82a5
IB
198 threading.Timer(duration, self.sleep_event.set).start()
199 self.sleep_event.wait()
200
201 # Action messages
2e404903 202 def command_print(self, command="", **kwargs):
be27763f
IB
203 return "running command {}".format(command)
204
3aaddc9d
IB
205 def interrupt_wait_print(self, wait_id=None, **kwargs):
206 return "interrupt wait with id {}".format(wait_id)
207
2e404903 208 def pause_print(self, music=None, **kwargs):
be27763f 209 if music is not None:
9de92b6d 210 return "pausing « {} »".format(music.name)
be27763f
IB
211 else:
212 return "pausing all musics"
213
2e404903 214 def unpause_print(self, music=None, **kwargs):
9de92b6d
IB
215 if music is not None:
216 return "unpausing « {} »".format(music.name)
217 else:
218 return "unpausing all musics"
219
2e404903
IB
220 def play_print(self, music=None, fade_in=0, start_at=0,
221 restart_if_running=False, volume=100, loop=0, **kwargs):
be27763f
IB
222 message = "starting "
223 if music is not None:
9de92b6d 224 message += "« {} »".format(music.name)
be27763f 225 else:
c80ff6dc 226 message += "all musics"
be27763f
IB
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
6f4944c1
IB
236 if loop > 0:
237 message += " {} times".format(loop + 1)
238 elif loop < 0:
239 message += " in loop"
240
be27763f
IB
241 if restart_if_running:
242 message += " (restarting if already running)"
243
244 return message
245
3aaddc9d
IB
246 def stop_print(self, music=None, fade_out=0, wait=False,
247 set_wait_id=None, **kwargs):
248
1b4b78f5 249 message = "stopping "
be27763f 250 if music is not None:
1b4b78f5 251 message += "music « {} »".format(music.name)
be27763f 252 else:
1b4b78f5
IB
253 message += "all musics"
254
255 if fade_out > 0:
256 message += " with {}s fadeout".format(fade_out)
257 if wait:
3aaddc9d
IB
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)"
1b4b78f5
IB
263
264 return message
be27763f 265
0e5d59f7 266 def stop_all_actions_print(self, **kwargs):
be27763f
IB
267 return "stopping all actions"
268
2e404903 269 def seek_print(self, music=None, value=0, delta=False, **kwargs):
52d58baf
IB
270 if delta:
271 if music is not None:
2e404903
IB
272 return "moving music « {} » by {:+d}s" \
273 .format(music.name, value)
52d58baf 274 else:
2e404903
IB
275 return "moving all musics by {:+d}s" \
276 .format(value)
52d58baf
IB
277 else:
278 if music is not None:
2e404903
IB
279 return "moving music « {} » to position {}s" \
280 .format(music.name, value)
52d58baf 281 else:
2e404903
IB
282 return "moving all musics to position {}s" \
283 .format(value)
52d58baf 284
aee1334c
IB
285 def volume_print(self, music=None,
286 value=100, delta=False, fade=0, **kwargs):
287 message = ""
8e50011c 288 if delta:
1b4b78f5 289 if music is not None:
aee1334c 290 message += "{:+d}% to volume of « {} »" \
2e404903 291 .format(value, music.name)
1b4b78f5 292 else:
aee1334c 293 message += "{:+d}% to volume" \
2e404903 294 .format(value)
be27763f 295 else:
1b4b78f5 296 if music is not None:
aee1334c 297 message += "setting volume of « {} » to {}%" \
2e404903 298 .format(music.name, value)
1b4b78f5 299 else:
aee1334c 300 message += "setting volume to {}%" \
2e404903 301 .format(value)
be27763f 302
a8340c5d 303 if fade > 0:
aee1334c
IB
304 message += " with {}s fade".format(fade)
305
306 return message
307
3aaddc9d
IB
308 def wait_print(self, duration=0, music=None, set_wait_id=None, **kwargs):
309 message = ""
0deb82a5 310 if music is None:
3aaddc9d 311 message += "waiting {}s" \
2e404903 312 .format(duration)
0deb82a5 313 elif duration == 0:
3aaddc9d 314 message += "waiting the end of « {} »" \
2e404903 315 .format(music.name)
0deb82a5 316 else:
3aaddc9d 317 message += "waiting the end of « {} » + {}s" \
2e404903 318 .format(music.name, duration)
be27763f 319
3aaddc9d
IB
320 if set_wait_id is not None:
321 message += " (setting id = {})".format(set_wait_id)
322
323 return message
be27763f 324
b7ca3fc2 325 # Interruptions (only for non-"atomic" actions)
2e404903 326 def wait_interrupt(self, duration=0, music=None, **kwargs):
0deb82a5
IB
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
b7ca3fc2
IB
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()