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
|
|
|
|