Skip to content

Instantly share code, notes, and snippets.

@bitcraft
Created June 22, 2019 02:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bitcraft/163b4bb19af8a356f74b361f23b96899 to your computer and use it in GitHub Desktop.
Save bitcraft/163b4bb19af8a356f74b361f23b96899 to your computer and use it in GitHub Desktop.
# 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