Created
June 22, 2019 02:53
-
-
Save bitcraft/163b4bb19af8a356f74b361f23b96899 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# os.environ["SDL_VIDEODRIVER"] = "dummy" | |
""" | |
sudo pip3 install pygame | |
sudo apt-get install python3-pyaudio | |
delay is in milliseconds | |
pasuspender python3 soundboard.py | |
Hold Escape to quit | |
""" | |
import threading | |
import time | |
import wave | |
from collections import namedtuple | |
import pyaudio | |
import pygame | |
PlayAction = namedtuple("PlayAction", ("delay", "device", "filename")) | |
QuitAction = namedtuple("PlayAction", ("delay",)) | |
allowed_actions = (PlayAction, QuitAction) | |
# Do not set the ESC key; it is reserved to quit | |
keys = { | |
# Function keys | |
pygame.K_F1: None, | |
pygame.K_F2: None, | |
pygame.K_F3: None, | |
pygame.K_F4: None, | |
pygame.K_F5: None, | |
pygame.K_F6: None, | |
pygame.K_F7: None, | |
pygame.K_F8: None, | |
pygame.K_F9: None, | |
pygame.K_F10: None, | |
pygame.K_F11: None, | |
pygame.K_F12: None, | |
# Numbers | |
pygame.K_1: PlayAction(500, "DigiHug USB Audio: - (hw:1,0)", "click.wav"), | |
pygame.K_2: None, | |
pygame.K_3: None, | |
pygame.K_4: None, | |
pygame.K_5: None, | |
pygame.K_6: None, | |
pygame.K_7: None, | |
pygame.K_8: None, | |
pygame.K_9: None, | |
pygame.K_0: None, | |
# Alphabet Row 1 | |
pygame.K_q: PlayAction(500, "front", "click2.wav"), | |
pygame.K_w: None, | |
pygame.K_e: None, | |
pygame.K_r: None, | |
pygame.K_t: None, | |
pygame.K_y: None, | |
pygame.K_u: None, | |
pygame.K_i: None, | |
pygame.K_o: None, | |
pygame.K_p: None, | |
# Alphabet Row 2 | |
pygame.K_a: PlayAction(0, "front", "click3.wav"), | |
pygame.K_s: None, | |
pygame.K_d: None, | |
pygame.K_f: None, | |
pygame.K_g: None, | |
pygame.K_h: None, | |
pygame.K_j: None, | |
pygame.K_k: None, | |
pygame.K_l: None, | |
# Alphabet Row 3 | |
pygame.K_z: None, | |
pygame.K_x: None, | |
pygame.K_c: None, | |
pygame.K_v: None, | |
pygame.K_b: None, | |
pygame.K_n: None, | |
pygame.K_m: None, | |
# Special actions | |
pygame.K_ESCAPE: QuitAction(5000), | |
} | |
def play_action(config, action): | |
lock = config["locks"][action.device] | |
if not lock.acquire(blocking=False): | |
print("no lock", action.device) | |
return | |
print("play", action.device) | |
chunk = config["chunk"] | |
snd = config["pyaudio"] | |
path = config["sounds_root"] + action.filename | |
device_index = config["devices"][action.device] | |
with wave.open(path, "rb") as wf: | |
stream = snd.open( | |
format=snd.get_format_from_width(wf.getsampwidth()), | |
channels=wf.getnchannels(), | |
output_device_index=device_index, | |
rate=wf.getframerate(), | |
output=True, | |
) | |
data = wf.readframes(chunk) | |
while len(data) > 0: | |
stream.write(data) | |
data = wf.readframes(chunk) | |
stream.stop_stream() | |
stream.close() | |
time.sleep(.5) | |
lock.release() | |
def check_actions(config, actions): | |
assert config["sounds_root"].endswith("/") | |
for action in actions: | |
if action is not None: | |
assert isinstance(action, allowed_actions) | |
if isinstance(action, PlayAction): | |
if action.device not in config["devices"]: | |
print("cannot find device: {}".format(action.device)) | |
raise ValueError | |
path = config["sounds_root"] + action.filename | |
with open(path): | |
pass | |
def get_pyaudio_device_map(snd): | |
device_map = dict() | |
devices = snd.get_device_count() | |
print("found the following audio devices") | |
for i in range(devices): | |
info = snd.get_device_info_by_index(i) | |
device_map[info["name"]] = i | |
print(" ", info["name"]) | |
return device_map | |
def main(): | |
snd = pyaudio.PyAudio() | |
config = { | |
"sounds_root": "/home/ltheden/sounds/", | |
"pyaudio": snd, | |
"devices": get_pyaudio_device_map(snd), | |
"chunk": 1024, | |
"locks": dict() | |
} | |
for name in config["devices"]: | |
config["locks"][name] = threading.Lock() | |
check_actions(config, keys.values()) | |
pygame.display.init() | |
screen = pygame.display.set_mode((640, 480)) | |
clock = pygame.time.Clock() | |
hold_timer = 0 | |
action_queue = list() | |
running = True | |
while running: | |
dt = clock.tick(30) | |
if hold_timer: | |
hold_timer += dt | |
for event in pygame.event.get(): | |
if event.type == pygame.KEYDOWN: | |
action = keys.get(event.key) | |
if action is None: | |
continue | |
hold_timer = dt | |
action_queue.append(action) | |
if event.type == pygame.KEYUP: | |
action = keys.get(event.key) | |
if action is None: | |
continue | |
elif action_queue and action_queue[-1] is action: | |
hold_timer = 0 | |
try: | |
action_queue.remove(action) | |
except ValueError: | |
pass | |
if action_queue: | |
current_action = action_queue[-1] | |
if current_action and hold_timer >= current_action.delay: | |
action_queue = list() | |
hold_timer = 0 | |
if isinstance(current_action, PlayAction): | |
t = threading.Thread(target=play_action, args=(config, current_action)) | |
t.daemon = True | |
t.start() | |
elif isinstance(current_action, QuitAction): | |
running = False | |
pygame.display.update() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment