Simple Sound Box - A raspberry pi as a simple music player

Project description

The project that we are going to look at today is very interesting because although it is extremely simple, it does fulfill a real-life need and will be used for I expect will be a long, long time of reliable operation.

The problems we need to solve are as follows:

  • We need a reliable source of background music to be played in a coffee shop (amplifier and speakers are already in place)
  • We need a simple method of operation
  • We need to have a long list of songs that will play randomly in a loop for many hours at a time
  • We need to ensure all songs play at a contact volume level; i.e. there are no songs that are louder than others which would make staff constantly adjust the volume
  • We need an easy way to connect to the list of files and update that list at any moment of the day without stopping the playback
  • Make it hard for staff to play their own playlists (not a big problem but a problem nonetheless that usually occurs after closing time when staff play their own playlists and then forget to put back the default playlist)
To solve these problems we proposed the following device:

The SimpleSoundBox in all its completed glory

This is a raspberry pi model 3 music player with the following features:
  • one music start/ stop button
  • one on/ off button (not visible on the picture)
  • 3 status LEDs: on/ off, ready to play and playing
  • playlist from a USB drive reachable using FTP from an internal network
Let me describe the hardware connections of each part of the project, starting with the on and off button.

On and off button

The on and off button uses the fact that the raspberry pi will wake-up when pin 5 is connected to pin 6. There is no programming required, it is just the way that it works: is you have power applied to it and short pin 5 to 6, the pi will wake up, that's it!

The shutdown is quite more complex since we do not simply want to pull out the power supply as that would mean that the pi would shutdown uncleanly which can corrupt the system and actually brick the device (it has happened to me quite a few times). We want to use the same button that we used to power on the pi.

Thankfully the internet is at our service and other smart people have already had this very need and can provide us with a solution. The one I ended up using was the one explained on this page. The author goes into more detail then I will go here on this point and I will only explain how to do the shortcut installation of his solution. To install the off script, proceed as follows:
  • ssh to your pi or use a screen to interact with it (in the latter case, open a command terminal)
  • clone the on/ off button repository: git clone https://github.com/Howchoo/pi-power-button.git
  • Run the installation script: ./pi-power-button/script/install
This will take are of it for you which basically means you will have a script running on your pi at every startup, waiting to detect the connection between pin 5 and 6 and, when that connection is detected, it will issue a clean shutdown command to the pi.

Status LEDs

Testing the status LEDs

As mentioned previously there are 3 status LEDs namely a power on LED, a ready to start LED and a "Music playing" LED.

For the power on LED, we were looking to not have to write any code, but we could not simply connect the LED to the power supply because we know that the supply will always be there whether the pi is on or off.

It turns out pin 8 is connected to the serial communications service on the pi and will by default continuously switching on and off as it waits for a serial connection to happen. We have no serial connection and we can use pin 8 to power on an LED but there is a catch. Because the pin is not actually on the on state all the time and is actually rapidly changing between on and off the average voltage it outputs is lower than the required to fully power on the LED, so it is to be expected that the brightness of the LED is lower than it would be if that was not the case. You can actually see on the picture above that the green power on LED is quite a lot less bright then the others. To minimize this issue, you can use a lower resistance value than we did. The final circuit looks like the following:
The power on LED interface to the pi
The connections for the "ready" and "music playing" LEDs is very similar to the "power on" LED, except that these ones we need to switch on and off programatically. The physical connections are as follows:
The ready and playing LEDs interface with the pi

The music start/ stop button

This button will be attached to pin 10 and the connection schematics is as simple as the following:
Note that we are feeding 3.3V (that we get from pin 1) to pin 10 whenever the switch is activated.

Hardware connections summary

We now have to link out software with the hardware we connected on the steps above. Basically we will have to find a way to define pins 11 (ready) and 12 (playing) as outputs and pin 10 (start/ stop) as input and then do something when that input (as in button press) is detected.

The Software

Assumptions

We will assume that at this point your raspberry pi is working with a fully functioning version of linux (usually raspbian). There are plenty of tutorials online on how to do this, so we will not loose that with those steps here.

Create a folder called .mpv under your root directory. This folder will hold the files you need to implement this project:

mkdir ~/.mpv

the "." tells linux the folder should be made hidden so that people do not easily temper with it. It is only a tiny precaution as anyone can see the folder and go on it (just do ls -la to see it on the list of folders).

The music player

Before anything else, we need to know what the music player will be. For several reasons we will choose mpv, the main reason being that we have some previous experience with it and have found the program to be highly customizable and able to do pretty much anything that we throw at it. To install it, just run the following command:

sudo apt-get install mpv

The python file (the brain)

This file will be the main brain of the operations. It will define each pin we mentioned previously as output or input and will wait for the press event and do something upon that detection.
We need to start by importing/ installing the python module that will assist on the interaction with the physical inputs/ outputs of the board. This is called "Raspberry Pi GPIO Python module" and you can get it using the following command:

sudo apt-get install python-rpi.gpio python3-rpi.gpio

Make sure you are on the .mpv folder (cd ~/.mpv) and start nano to create your player.py file:

nano player.py

this command will open the nano text editor but you can use any other text editor that possibly makes more sense to you (nano is a little weird as it is a command line interface text editor).

Place the following code inside that file:

EDIT 22nd Dec, 2019: please get the updated and way more stable code on github.

#Import Raspberry Pi GPIO library
import RPi.GPIO as GPIO
import time
import subprocess
import sys

#Start by clearing everything that may be left over from a wrong shutdown


#Use physical pin numbering
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)

#Inputs
start_stop_pin = 10

#Outputs
music_playing_pin = 12
ready_to_start_led = 11
GPIO.setup(music_playing_pin,GPIO.OUT)

GPIO.setup(ready_to_start_led,GPIO.OUT)
GPIO.output(ready_to_start_led,GPIO.HIGH)

def start_stop_button_callback(channel):
time.sleep(0.005)
# only deal with valid edges
if GPIO.input(start_stop_pin) == 1:
#check if there are mpv instances running
command = subprocess.Popen(["pidof", "mpv"], stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
stdout, stderr = command.communicate()
print(stdout.decode('UTF-8'))
#print(stderr)
if stdout.decode('UTF-8') == '':
#if no mpv instance, start one
subprocess.Popen(["mpv", "/mnt/mydisk/Playlist", "--shuffle", "--gapless-audio", "--loop-playlist"])
#light up the LED
GPIO.output(music_playing_pin,GPIO.HIGH)
else:
#if there is an mpv instance, stop it:
subprocess.call(["pkill", "mpv"])
GPIO.output(music_playing_pin,GPIO.LOW) 
return

# Set pin 10 to be an input pin and set initial value to be pulled low (off)
GPIO.setup(start_stop_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
# Attach event to the pin; setup event on pin 10, rising edge
GPIO.add_event_detect(start_stop_pin, GPIO.RISING, callback=start_stop_button_callback)

#keep the script running
while 1:
time.sleep(1)

GPIO.cleanup()


See all the comment lines (the lines that start with '#') for a description of what each section does. In a nutshell, we start by defining the pins we are going to use as inputs or outputs. Then we define the initial state of the outputs (we set the "ready_to_start" LED to high which means, light up the LED).

The thing to note about the code above is the part that starts with def start_stop_button_callback(channel):. This is the function that gets called every time we detect that the start/ stop button has been pressed. We basically tell the CPU: feel free to do whatever you want but when you detect the button was pressed (that detection is caught on an event and is defined on the following line: GPIO.add_event_detect(start_stop_pin, GPIO.RISING, callback=start_stop_button_callback) - you see that on this line we define an event detection that happens when the state of the input changes from 'low' to 'high' and when that happens we run the code on the start_stop_button_callback function).

What does that function do? Simple it will see if mpv is running or not with the line

command = subprocess.Popen(["pidof", "mpv"], stdout = subprocess.PIPE, stderr = subprocess.STDOUT)

if mpv is not running, we make it run with the line

subprocess.Popen(["mpv", "/mnt/mydisk/Playlist", "--shuffle", "--gapless-audio", "--loop-playlist"])

If it is running, we stop it with the line

subprocess.call(["pkill", "mpv"])

This is how we make only one button do two things: start music if it's not started and stop music playback if it is running.

Note the location of the playlist on this location /mnt/mydisk/Playlist. This is where we place the files we want to play. This is a thumb drive that needs to be mounted at every start up. We also need to start the player.py script at every start up. For that scroll down to the section after the next.

Mounting the playlist USB drive at every start up

With the raspberry pi on, find out what the dive is called with this command:

sudo lsblk -o UUID,NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL,MODEL

if the drive is formatted as FAT (you can see that under the FSTYPE column on the output of the command above), install exFAT driver with the following command:

sudo apt-get install exfat-fuse

On the list you can see what your drive is called, something like /dev/sda1. Once you get that, make a directory to mount it to. In our case we did the following:

sudo mkdir /mnt/mydisk

and then mount the drive to that location

sudo mount /dev/sda1 /mnt/mydisk

The contents of your drive should not be available on /mnt/mydisk. But this will not make it so that this happens at every start up. For that you need to edit the fstab file:

sudo nano /etc/fstab

And add the following lines to the very end of that file:

UUID=B24A-E0E0 /mnt/mydisk vfat

defaults,auto,umask=000,users,rw,nofail,x-systemd.device-timeout=30 0 0

Make the brain script run at every start up

Create a file called launcher.sh and prepare it for edit:

nano ~/.mpv/launcher.sh

put the following line on it

sudo python3 /home/pi/.mpv/player.py

Now we will use a feature of the crontab to ensure this .sh file (and hence the command to start the player.py script) runs everytime the pi boots up. Issue to the following command to edit the crontab:

crontab -e

Place the following line on the crontab file:

@reboot sh /home/pi/.mpv/launcher.sh > /dev/null 2>&1

This tells the system to run the .sh file once at startup (@reboot sh /home/pi/.mpv/launcher.sh) and discard all the output (> /dev/null 2>&1).

Conclusion

This is all that is required for you to build your own simple music player. After you complete all the steps described above (there will for sure be some details that are not described here and that you have to find on your own, but that's half the fun) you need only find a suitable enclosure to keep all the components in and you will have a decent sounding, highly reliable and very simple to use music player.

NOTE1: Just one final note to say that you have activated ssh on your pi and can therefore connect to it using an FTP client (connection to sftp://) then navigate to the playlist folder and you can add new files at anytime.

NOTE2: if you cannot upload and/ or manipulate files that is most likely a permissions issue. You need to change the ownership of the Playlist folder. From inside the /mnt/mydisk directory, write

sudo chown pi:pi *

This is assuming your FTP user is pi. You should now be able to manipulate the contents of the /playlist folder from an FTP client.

Comments