|
1
|
|
|
""" |
|
2
|
|
|
* PyDMXControl: A Python 3 module to control DMX using OpenDMX or uDMX. |
|
3
|
|
|
* Featuring fixture profiles, built-in effects and a web control panel. |
|
4
|
|
|
* <https://github.com/MattIPv4/PyDMXControl/> |
|
5
|
|
|
* Copyright (C) 2018 Matt Cowley (MattIPv4) ([email protected]) |
|
6
|
|
|
""" |
|
7
|
|
|
|
|
8
|
|
|
import contextlib |
|
9
|
|
|
from threading import Thread |
|
10
|
|
|
from time import sleep, time |
|
11
|
|
|
|
|
12
|
|
|
with contextlib.redirect_stdout(None): # PyGame please shut up |
|
13
|
|
|
import pygame |
|
14
|
|
|
|
|
15
|
|
|
from ..utils.exceptions import AudioException |
|
16
|
|
|
from .. import DEFAULT_INTERVAL |
|
17
|
|
|
|
|
18
|
|
|
|
|
19
|
|
|
class Player: |
|
20
|
|
|
|
|
21
|
|
|
def __init__(self): |
|
22
|
|
|
# set up states |
|
23
|
|
|
self.__paused = False |
|
24
|
|
|
self.__done = True |
|
25
|
|
|
|
|
26
|
|
|
# set up mixer |
|
27
|
|
|
# https://www.daniweb.com/programming/software-development/threads/491663/how-to-open-and-play-mp3-file-in-python |
|
28
|
|
|
self.__freq = 44100 # audio CD quality |
|
29
|
|
|
self.__bitsize = -16 # unsigned 16 bit |
|
30
|
|
|
self.__channels = 2 # 1 is mono, 2 is stereo |
|
31
|
|
|
self.__buffer = 2048 # number of samples |
|
32
|
|
|
pygame.mixer.init(self.__freq, self.__bitsize, self.__channels, self.__buffer) |
|
33
|
|
|
|
|
34
|
|
|
# set up user preferences |
|
35
|
|
|
self.__volume = 0 |
|
36
|
|
|
self.set_volume(0.8) |
|
37
|
|
|
|
|
38
|
|
|
def __set_volume(self, vol): |
|
39
|
|
|
vol = max(min(vol, 1), 0) # clamp |
|
40
|
|
|
self.__volume = vol |
|
41
|
|
|
pygame.mixer.music.set_volume(vol) |
|
42
|
|
|
|
|
43
|
|
|
def __fade_volume(self, current, target, millis): |
|
44
|
|
|
start = time() * 1000.0 |
|
45
|
|
|
gap = target - current |
|
46
|
|
|
|
|
47
|
|
|
if millis > 0: |
|
48
|
|
|
while (time() * 1000.0) - start <= millis: |
|
49
|
|
|
diff = gap * (((time() * 1000.0) - start) / millis) |
|
50
|
|
|
self.__set_volume(current + diff) |
|
51
|
|
|
sleep(0.000001) |
|
52
|
|
|
self.__set_volume(target) |
|
53
|
|
|
|
|
54
|
|
|
def set_volume(self, volume: float, milliseconds: int = 0): |
|
55
|
|
|
volume = max(min(volume, 1), 0) # clamp |
|
56
|
|
|
|
|
57
|
|
|
# Create the thread and run loop |
|
58
|
|
|
thread = Thread(target=self.__fade_volume, args=(self.__volume, volume, milliseconds), daemon=True) |
|
59
|
|
|
thread.start() |
|
60
|
|
|
|
|
61
|
|
|
def __play(self, file, start_millis): |
|
62
|
|
|
# Stop anything previous |
|
63
|
|
|
self.stop() |
|
64
|
|
|
|
|
65
|
|
|
# Set states |
|
66
|
|
|
self.__paused = False |
|
67
|
|
|
self.__done = False |
|
68
|
|
|
|
|
69
|
|
|
# Get the file |
|
70
|
|
|
try: |
|
71
|
|
|
pygame.mixer.music.load(file) |
|
72
|
|
|
except pygame.error: |
|
73
|
|
|
self.__done = True |
|
74
|
|
|
raise AudioException("Error occurred loading '{}': {}".format(file, pygame.get_error())) |
|
75
|
|
|
|
|
76
|
|
|
# Play the file and tick until play completed |
|
77
|
|
|
pygame.mixer.music.play(start=start_millis / 1000) |
|
78
|
|
|
while pygame.mixer.music.get_busy(): |
|
79
|
|
|
sleep(DEFAULT_INTERVAL) |
|
80
|
|
|
|
|
81
|
|
|
# Let everyone know play is done |
|
82
|
|
|
self.__done = True |
|
83
|
|
|
|
|
84
|
|
|
def play(self, file: str, start_millis: int = 0): |
|
85
|
|
|
# Play in the background so this isn't blocking |
|
86
|
|
|
thread = Thread(target=self.__play, args=[file, start_millis], daemon=True) |
|
87
|
|
|
thread.start() |
|
88
|
|
|
|
|
89
|
|
|
def pause(self): |
|
90
|
|
|
pygame.mixer.music.pause() |
|
91
|
|
|
|
|
92
|
|
|
def unpause(self): |
|
93
|
|
|
pygame.mixer.music.unpause() |
|
94
|
|
|
|
|
95
|
|
|
def stop(self): |
|
96
|
|
|
pygame.mixer.music.stop() |
|
97
|
|
|
|
|
98
|
|
|
def sleep_till_done(self): |
|
99
|
|
|
# Hold until the play method sets done |
|
100
|
|
|
while not self.__done: |
|
101
|
|
|
sleep(DEFAULT_INTERVAL) |
|
102
|
|
|
|
|
103
|
|
|
def sleep_till_interrupt(self): |
|
104
|
|
|
# This is very useful for playing song, stopping at a specific point and getting the timestamp |
|
105
|
|
|
# Probably not very useful in production but useful in development |
|
106
|
|
|
|
|
107
|
|
|
# Hold |
|
108
|
|
|
try: |
|
109
|
|
|
while True: |
|
110
|
|
|
sleep(DEFAULT_INTERVAL) |
|
111
|
|
|
except KeyboardInterrupt: |
|
112
|
|
|
print(pygame.mixer.music.get_pos()) |
|
113
|
|
|
self.stop() |
|
114
|
|
|
|
|
115
|
|
|
|
|
116
|
|
|
# Player can only have one instance due to how pygame works |
|
117
|
|
|
the_player = Player() |
|
118
|
|
|
|