Upon moving into our new apartment in Oakland, I desired constant Steely Dan, at least while getting ready in the morning. Now, marked by the sad
occasion of Walter Becker's passing, I present a Python script to be used in conjunction with a Raspberry Pi and a touch sensor to launch and change Steely Dan
songs (or any songs) in the bathroom (or any room).
I used the following components:
- Vilros Raspberry Pi 2 Complete Starter Kit with Edimax WiFi from Amazon ($70)
- Capacitive touch sensor (AT42QT1010) from AdaFruit ($6)
- Powered computer speakers from Amazon ($20)
My original plan was to have a touch sensor attached to a ITO-coated photo of Donald Fagan and Walter Becker, so one
could touch the photo to start the tunes. I bought a few samples of conductive transparent film for prototyping.
However, the touch sensor was far too sensitive and would trigger immediately when connected to
just about any conductor. In fact, just steam from the shower or a breeze from outside was enough to trigger the sensor, making it necessary to
wrap it in tape, as shown above. A better version would use a touch sensor with adjustable sensitivity or another actuator type entirely.
Here were the requirements:
- Upon sensor activation, start playing songs randomly. At the end of each song, randomly select and play the next song.
- When music is playing, brief sensor activation should fade out the current song and switch to a randomly selected song.
- When music is playing, longer sensor activation should fade out the current song and stop playback.
Thus, it's important to check whether songs are being played (or have a corresponding flag set) to process the sensor signal in the appropriate way.
We use the try structure to handle errors. Pin 18 in BOARD format, corresponding to GPIO 24, is used as the trigger, so
we set it as normally low. The touch sensor is also attached to the +3.3 V and ground pins. The flag playingCheck means that songs are currently playing, so this flag is initialized to FALSE . We use glob to obtain the list of song filenames.
try:
import RPi.GPIO as GPIO
import pygame, glob, random, time
GPIO.setmode(GPIO.BOARD)
pin = 18
GPIO.setup(pin,GPIO.IN,pull_up_down=GPIO.PUD_DOWN)
pygame.init()
# Initialize states
playingCheck = False; songNext=0;
songList = glob.glob('/home/pi/Music/Tunes/*.mp3')
print "Ready to play",len(songList),"song(s)."
Now we define a function to play the next song and increment the song number:
def playNext(songList, songNext, playingCheck):
if songNext == len(songList): # Keep things from going forever
print "All songs played."
playingCheck = False
else:
pygame.mixer.music.load(songList[songNext])
pygame.mixer.music.play()
songNext += 1
The next loop is the engine of the program. We start by checking whether the playingCheck flag is TRUE but no music is playing; this condition implies that we should advance to the next song. If both of these conditions aren't met, we look for a sensor signal. If present and the flag
playingCheck is FALSE , then we randomize the file list, flip the flag, start play, and pause the loop for a second to avoid double-triggering from a quick tap. In contrast, if the flag is TRUE , we fade out and pause a second; if the input continues, we flip the flag back to FALSE and pause for 5 seconds to avoid double-triggering from a long hold.
while True:
if playingCheck == True and pygame.mixer.music.get_busy() == False:
print "Shuffling to next song."
playNext(songList, songNext, playingCheck)
if GPIO.input(pin) == 1: # Touching the sensor can either start play, change to a new song, or stop play
if playingCheck != True: # If no songs are playing
print "Randomizing songs and starting play."
random.shuffle(songList)
songNext = 0
playingCheck = True
playNext(songList, songNext, playingCheck)
time.sleep(1)
else: # If songs are currently playing
print "Continue input to stop."
pygame.mixer.music.fadeout(2000)
time.sleep(1)
if GPIO.input(pin) == 1: # The user is still triggering the sensor
print "Stopping play."
playingCheck = False
time.sleep(5)
Then we add a short pause to let the while loop relax and add the housekeeping code to end the try .
time.sleep(0.1)
except KeyboardInterrupt:
print "Program terminated."
finally:
print songNext,"song(s) played."
GPIO.cleanup()
The Raspberry Pi I bought either came with Linux installed, or I installed it myself using a smart card with no problems (I can't recall). You can hook up the Pi to a
keyboard, mouse, and monitor, or you can SSH to its IP address from another computer. I used Filezilla to SFTP the MP3 files over. I used sudo crontab –e to set the script to run automatically when the Pi is powered up.
Then Katie wanted something similar (who wouldn't?!), but with NPR streamed from online. The following script employs a (different) touch sensor to launch and switch
between NPR and jazz. The audio command is a little different, pulling in and iterating between two online streaming stations using the
modulo command % . These stations are louder than the Steely Dan tracks, so I temporarily take the volume down a notch. Both programs
can be run simultaneously.
try:
import RPi.GPIO as GPIO
import os, time
GPIO.setmode(GPIO.BOARD)
pin = 8
GPIO.setup(pin,GPIO.IN,pull_up_down=GPIO.PUD_DOWN)
playingCheck = False; channel=0; changeChannelCheck = False
os.system('mpc -q clear')
os.system('mpc -q clearerror')
os.system('mpc add http://streams.kqed.org:80/kqedradio')
os.system('mpc add http://ice7.securenetsystems.net/KCSM2')
def playNext():
global channel
os.system('mpc -q play ' + str(channel+1))
os.system('mpc current')
channel += 1
channel = channel % 2
print "Ready to play 2 channels."
while True:
if GPIO.input(pin) == 1:
if playingCheck != True:
print "Starting with first channel."
playingCheck = True
os.system('mpc -q volume 88')
playNext()
else:
print "Continue input to shut down."
os.system('mpc -q stop')
changeChannelCheck = True
time.sleep(1)
if GPIO.input(pin) == 1
print "Stopping play."
playingCheck = False
os.system('mpc -q volume 100')
time.sleep(5)
elif changeChannelCheck == True:
print "Moving to channel " + str(channel % 2 + 1)
playNext()
changeChannelCheck = False
time.sleep(0.1)
except KeyboardInterrupt:
os.system('mpc -q stop')
os.system('mpc -q volume 100')
print "Program terminated."
finally:
GPIO.cleanup()
After adding more controls and more bling:
|