Maloney Home Page  |  Python Music Player

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: