Skip to content

Instantly share code, notes, and snippets.

Last active July 20, 2023 23:46
  • Star 14 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?
Open and close an animatronic mouth on a Raspberry Pi GPIO pin
#!/usr/bin/env python
This script animates a motorized mouth on a Raspberry Pi GPIO pin so that it
appears to be speaking alongside the audio on the specified PulseAudio source
(which usually should be a sink's monitor).
Find PA_SOURCE with `pactl list` and look for a monitor device that corresponds
to your output device.
See here for a detailed discussion:
import RPi.GPIO as GPIO
import struct
import subprocess
# TODO: Move these two to a configuration file somewhere
# TODO: Make this a service
PA_SOURCE = "alsa_output.usb-C-Media_Electronics_Inc._USB_Audio_Device-00.analog-stereo.monitor"
# We're not playing this stream back anywhere, so to avoid using too much CPU
# time, use settings that are just high enough to detect when there is speech.
PA_FORMAT = "u8" # 8 bits per sample
PA_CHANNELS = 1 # Mono
PA_RATE = 2000 # Hz
PA_BUFFER = 32 # frames for a latency of 64 ms
# Capture audio using `pacat` -- PyAudio looked like a cleaner choice but
# doesn't support capturing monitor devices, so it can't be used to capture
# system output.
parec = subprocess.Popen(["/usr/bin/pacat", "--record", "--device="+PA_SOURCE,
"--rate="+str(PA_RATE), "--channels="+str(PA_CHANNELS),
"--format="+PA_FORMAT, "--latency="+str(PA_BUFFER)], stdout=subprocess.PIPE)
while not parec.stdout.closed:
# Mono audio with 1 byte per sample makes parsing trivial
sample = ord( - 128
GPIO.output(MOUTH_PIN, abs(sample) > SAMPLE_THRESHOLD)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment