You are on page 1of 20

Projects

GPIO music box


Make a device that plays music when you press its
buttons

Electronic components Raspberry Pi Python

Step 1 Introduction

In this project, you will build a button-controlled “music box” that plays
di erent sounds when di erent buttons are pressed. You can nd a
shortened version of this project on YouTube (https://www.youtube.co
m/watch?v=2izvSzQWYak&feature=youtu.be)

What you will make

What you will learn

Play sounds in Python with pygame


Use the Python gpiozero library to connect button presses to
function calls
Use the dictionary data structure in Python

What you will need


Hardware

A Raspberry Pi computer
A breadboard
Four (4) tactile switches (to make buttons)
Five (5) male-to-female jumper leads
Four (4) male-to-male jumper leads
Speakers or headphones

Software

The latest version of the Raspbian (https://www.raspberrypi.or


g/downloads/raspbian/) operating system

Additional information for educators

If you need to print this project, please use the printer-friendly


version (https://projects.raspberrypi.org/en/projects/gpio-musi
c-box/print).

You can nd the solution for this project here (http://rpf.io/p/en/


gpio-music-box-get).

Step 2 Set up your project

You will need some sample sounds for this project. There are lots of
sound les on Raspbian, but it can be a bit di cult to play them using
Python. However, you can convert the sound les to a di erent le
format that you can use in Python more easily.

First, in your home directory, create a directory called gpio-


music-box. You will use the new directory to store all your les
for the project.

Creating Directories on a Raspberry Pi


There are two ways to create directories on the Raspberry Pi. The rst
uses the GUI, and the second uses the Terminal.

Method 1 - Using the GUI

Open a File Manager window by clicking on the icon in the top left
corner of the screen

In the window, right-click and select Create New… and then Folder
from the context menu
In the dialogue box, type the name of your new directory and then
click OK

Method 2 - Using the Terminal

Open a new Terminal window by clicking on the icon in the top left
corner of the screen

Create a new directory using the mkdir command

mkdir my-new-directory

You can list the contents of the current directory using ls


Use the cd command to enter your new directory

cd my-new-directory

Copy the sample sounds

Use the same method as before to create a new directory called


samples in your gpio-music-box directory.

There are lots of sample sounds stored in /usr/share/sonic-


pi/samples. In the next step, you will copy these sounds into the
gpio-music-box/samples directory.

Click on the icon in the top left-hand corner of your screen to


open a terminal window.

Type the following lines to copy all the les from one directory to
the other:

cp /usr/share/sonic-pi/samples/* ~/gpio-
music-box/samples/.

When you have done that, you should be able to see all the .flac
sound les in the samples directory.
Convert the sound les

To play the sound les using Python, you need to convert the les from
.flac les to .wav les.

In a terminal, change into your samples directory.

cd ~/gpio-music-box/samples

If you want to learn more about converting media les and running
commands on multiple les, look at the two sections below.

Converting media les

You can easily convert media les on the Raspberry Pi using a piece
of software called mpeg. This is preinstalled on the latest versions
of
Raspbian (https://www.raspberrypi.org/downloads/raspbian/).

To convert sound or video les from one format to another, you


can use this basic command:

ffmpeg -i input.ext1 output.ext2

For instance, to convert a wav le (.wav) into an mp3 le (.mp3),


you would type:

ffmpeg -i my_music.wav my_music.mp3


Running batch operations on les

Running batch operations on a les using bash

What are batch operations

Renaming a le using bash is fairly easy. You could use the mv


command for instance.

mv original_file_name.txt new_file_name.txt

What if you needed to rename a thousand les though?

A batch operation is when you use a command or a set of commands


on multiple les.

Have a go at your rst basic batch operation, using any directory


that contains a few les. The rst thing to try is something trivial.
For instance, you can recreate the ls command so that;
for every le in the directory,
the name of the le is output to the terminal.
The rst part is to tell bash you want to operate on all les in the
directory.

for f in *

f will represent each le name in the directory one after another.

Next you need to tell bash what to do with each le name. In this
case you want to echo the le name to standard output.

do
echo $f

Here the $ sign is used to state that you’re talking about the
variable f. Lastly you need to tell bash that you’re done.

done
You can hit enter after each command if you like, and bash won’t
run the whole loop until you type done, so the command would
look like this in your terminal window:

for f in *.txt
> do
> echo $f
> done

Alternately, instead of hitting Enter after each line, you could use a
; to seperate the commands.

for f in *.txt; do echo $f; done

Manipulating strings.

That last command was a little pointless, but you can do much more
with batch operations. For instance, what if you wanted to change
the name of each of the les, so instead of being called 0.txt for
instance, they get called 0.md.

Try this command:

for f in *.txt; do mv "$f" "${f%.txt}.md";


done

If you ls the contents of the directory, you’ll see all the les have
been renamed. So how did this work?

The rst part for f in *.txt tells bash to run the operation on
every le ($f) with a .txt. extension.

Next, do mv $f is telling bash to move each of those les. Next


you need to provide bash with the moved le’s new name. To do
this you need to remove the .txt and replace it with .md. Luckily
bash has an inbuilt operator to remove the endings of strings. You
can use the % operator. Try this example to see how it works

words="Hello World"
echo ${words%World}
You could now end something else onto the end of the string.

echo ${words%World)Moon

So the syntax ${f%.txt}.md replaces all the .txt strings with


.md strings. Incidently, if you use the # operator instead of the % it
will remove a string from the start instead of the end.

In your terminal, type the following commands. This will convert


all the .flac les to .wav les, then delete the old les.

for f in *.flac; do ffmpeg -i "$f"


"${f%.flac}.wav"; done
rm *.flac

It will take a minute or two, depending on the Raspberry Pi


model that you are using.

You should now be able to see all the new .wav les in the samples
directory.

Step 3 Play sounds

Next, you will start to write your Python code. You can use any text
editor or IDE to do this — Mu is always a good choice.

To start to create the instruments of your music box, you need to test
whether Python can play some of the samples that you have copied.

Playing sound les with Python

To play a sound le with Python, you can use a module called


pygame. It comes pre-installed on a Raspberry Pi, but if you are on
another operating system you may need to use pip (https://pip.pyp
a.io/en/stable/installing/) to install it.
On Linux and MacOS you can open a terminal and type:

sudo pip3 install pygame

On Windows you can open PowerShell and type:

pip3 install pygame

Importing and initialising pygame

First you will need to import the pygame module and initialise it.

import pygame
pygame.init()

Playing a sound

Next you can create a Sound object and provide it with the path
to your le.

my_sound =
pygame.mixer.Sound('path/to/my/soundfile.wav')

Then you can play the sound.

my_sound.play()

First, import and initialise the pygame module for playing sound
les.

import pygame

pygame.init()
Save this le in your gpio-music-box directory.

Choose four sound les that you want to use for your project, for
example:

drum_tom_mid_hard.wav
drum_cymbal_hard.wav
drum_snare_hard.wav
drum_cowbell.wav

Then, create a Python object that links to one of these sound


les. Give the le its own unique name. For example:

drum = pygame.mixer.Sound("/home/pi/gpio-
music-box/samples/drum_tom_mid_hard.wav")

Create named objects for your remaining three sounds.

Here’s what your code should look like:

import pygame

pygame.init()

drum = pygame.mixer.Sound("/home/pi/gpio-
music-box/samples/drum_tom_mid_hard.wav")
cymbal =
pygame.mixer.Sound("/home/pi/gpio-music-
box/samples/drum_cymbal_hard.wav")
snare = pygame.mixer.Sound("/home/pi/gpio-
music-box/samples/drum_snare_hard.wav")
bell = pygame.mixer.Sound("/home/pi/gpio-
music-box/samples/drum_cowbell.wav")
Save and run your code. Then, in the shell at the bottom of the
Mu editor, use .play() commands to play the sounds.

drum.play()

If you don’t hear any sound, check that your speakers or headphones are
working and that the volume is turned up.

Step 4 Connect your buttons

You will need four buttons, each wired to separate GPIO pins on the
Raspberry Pi.

The Raspberry Pi's GPIO pins

GPIO is an acronym for General Purpose Input/Output. A Raspberry Pi


has 26 GPIO pins. These allow you to send and receive on/o signals
to and from electronic components such as LEDs, motors, and
buttons.

If you look at a Raspberry Pi with the USB ports facing towards you,
the layout of the GPIO pins is as follows.

  
3V3 5V
GPIO2 5V
GPIO3 GND
GPIO4 GPIO14
GND GPIO15
GPIO17 GPIO18
GPIO27 GND
GPIO22 GPIO23
3V3 GPIO24
GPIO10 GND
GPIO9 GPIO25
  
GPIO11 GPIO8
GND GPIO7
DNC DNC
GPIO5 GND
GPIO6 GPIO12
GPIO13 GND
GPIO19 GPIO16
GPIO26 GPIO20
GND GPIO21

Each pin has a number, and there are additional pins that provide 3.3
Volts, 5 Volts, and Ground connections.

Here’s another diagram showing the layout of the pins. It shows some
of the optional special pins as well.

Here’s a table with a brief explanation.

Full
Abbreviation Function
name
3.3 Anything connected to these pins will
3V3
volts always get 3.3V of power
Anything connected to these pins will
5V 5 volts
always get 5V of power
GND ground Zero volts, used to complete a circuit
These pins are for general-purpose use
GPIO
GP2 and can be con gured as input or output
pin 2
pins
ID_SC/ID_SD/DNC   Special purpose pins

Using a button with a Raspberry Pi

A button is one of the simplest input components you can wire to a


Raspberry Pi. It’s a non-polarised component, which means you can
place it in a circuit either way round and it will work.

There are various types of buttons - they can for example have two or
four legs. The two-leg versions are mostly used with ying wire to
connect to the control device. Buttons with four legs are generally
mounted on a PCB or a breadboard.

The diagrams below shows how to wire a two-leg or four-leg button


to a Raspberry Pi. In both cases, GPIO 17 is the input pin.

If you are using multiple buttons, then it is often best to use a


common ground to avoid connecting too many jumper leads to GND
pins. You can wire the negative rail on the breadboard to a single
ground pin, which allows all the buttons to use the same ground rail.

Place the four buttons into your breadboard.

Wire each button to a di erent numbered GPIO pin. You can


choose any pins you like, but you will need to remember the
numbers.

Here’s a video that shows how you can wire the buttons.

Step 5 Play sounds at the press of a button

To see how a function can be called using a button press, have a look at
the section below.

Triggering functions with buttons

In the diagram below, a single button has been wired to pin 17.

You can use the button to call functions that take no arguments:

First you need to set up the button using Python 3 and the
gpiozero module.
from gpiozero import Button
btn = Button(17)

Next you need to create a function that has no arguments. This


simple function will just print the word Hello to the shell.

def hello():
print('Hello')

Finally, create a trigger that calls the function.

btn.when_pressed = hello

Now each time the button is pressed, you should see Hello being
printed to the Python shell.

Your function can be as complex as you like - you can even call
functions that are parts of modules. In this example, pressing the
button switches on an LED on pin 4.

from gpiozero import Button, LED

btn = Button(17)
led = LED(4)

btn.when_pressed = led.on

When the button is pressed, the program should call a function such as
drum.play().

However, when you use an event (such as a button press) to call a


function, you don’t use brackets ().

This is because the program must only call the function when the
button is pressed, rather than straight away. So, in this case, you just
use drum.play.
First, set up one of your buttons. Remember to use the numbers
for the GPIO pins that you have used, rather than the numbers
in the example.

1 import pygame
2 from gpiozero import Button
3
4 pygame.init()
5
6 drum =
7 pygame.mixer.Sound("/home/pi/gpio-
8 music-
9 box/samples/drum_tom_mid_hard.wav")
10 cymbal =
11 pygame.mixer.Sound("/home/pi/gpio-
music-
box/samples/drum_cymbal_hard.wav")
snare =
pygame.mixer.Sound("/home/pi/gpio-
music-box/samples/drum_snare_hard.wav")
bell =
pygame.mixer.Sound("/home/pi/gpio-
music-box/samples/drum_cowbell.wav")

btn_drum = Button(4)

To play the sound when the button is pressed, just add this line
of code to the bottom of your le:

btn_drum.when_pressed = drum.play

Run the program and press the button. If you don’t hear the
sound playing, then check the wiring of your button.
Now, add code to make the remaining three buttons play their
sounds.

Here’s an example of the code that you could use for a


second button.

btn_cymbal = Button(17)

btn_cymbal.when_pressed = cymbal.play

Full code listing

import pygame
from gpiozero import Button

pygame.init()

drum = pygame.mixer.Sound("/home/pi/gpio-music-
box/samples/drum_tom_mid_hard.wav")
cymbal = pygame.mixer.Sound("/home/pi/gpio-
music-box/samples/drum_cymbal_hard.wav")
snare = pygame.mixer.Sound("/home/pi/gpio-music-
box/samples/drum_snare_hard.wav")
bell = pygame.mixer.Sound("/home/pi/gpio-music-
box/samples/drum_cowbell.wav")

btn_drum = Button(4)
btn_cymbal = Button(17)
btn_snare= Button(27)
btn_bell = Button(10)

btn_drum.when_pressed = drum.play
btn_cymbal.when_pressed = cymbal.play
btn_snare.when_pressed = snare.play
btn_bell.when_pressed = bell.play
Step 6 Improve your script

The code that you have written should work without any problems.
However, it’s generally a good idea to make your code a bit cleaner once
you have a prototype that works.

The next steps are completely optional. If you’re happy with your script,
then just leave it as it is. If you want to make your script a bit cleaner,
then follow the steps on this page.

You can store your button objects and sounds in a dictionary, instead of
having to create eight di erent objects.

Have a look at the steps below to learn about creating basic dictionaries
and then looping over them.

Dictionaries in Python

A dictionary is a type of data structure () in Python. It contains a


series of key () : value () pairs. Here is a very simple example:

band = {'john' : 'rhythm guitar', 'paul' : 'bass


guitar', 'george' : 'lead guitar', 'ringo' :
'bass guitar'}

The dictionary has a name, in this case band, and the data in it is
surrounded by curly brackets ({}). Within the dictionary are the key () :
value () pairs. In this case the keys are the names of the band
members. The values are the names of the instruments they play.
Keys and values have colons between them (:), and each pair is
separated by a comma (,). You can also write dictionaries so that
each key () : value () pair is written on a new line.

band = {
'john' : 'rhythm guitar',
'paul' : 'bass guitar',
'george' : 'lead guitar',
'ringo' : 'bass guitar'
}
Open IDLE, create a new le, and have a go at creating your own
dictionary. You can use the one above or your own if you like.
When you’re done, save and run the code. Then switch over to the
shell to have a look at the result by typing the name of your
dictionary.

You’ll probably notice that the key () : value () pairs are no longer in
the order that you typed them. This is because Python dictionaries
are unordered, so you can’t rely on any particular entry being in
any speci c position.

To look up a particular value () in a dictionary you can use its key ().
So for instance, if you wanted to nd out what instrument ringo
plays, you could type:

band['ringo']

Dictionaries can store all types of data. So you can use them to
store numbers, strings, variables, lists or even other dictionaries.

Looping over dictionaries with Python

Like any data structure in Python, you can iterate over dictionaries.

Remember, the order of keys in a dictionary can be unpredictable.

If you simply iterate over a dictionary with a for loop, you will only
be iterating over the keys.

Take this dictionary for example:

band = {
'john' : 'rhythm guitar',
'paul' : 'base guitar',
'george' : 'lead guitar',
'ringo' : 'bass guitar'
}

You can iterate over it with a for loop like this:


for member in band:
print(member)

This will produce:

>>> ringo
john
george
paul

If you want to get the keys and values, you’ll need to specify this in
your for loop

for member, instrument in band.items():


print(member, '-', instrument)

This will give the following:

>>> ringo - base guitar


john - rhythm guitar
george - lead guitar
paul - base guitar

First, create a dictionary that uses the Buttons as keys and the
Sounds as values.

button_sounds = {Button(4):
pygame.mixer.Sound("/home/pi/gpio-music-
box/samples/drum_tom_mid_hard.wav"),
Button(17):
pygame.mixer.Sound("/home/pi/gpio-music-
box/samples/drum_cymbal_hard.wav"),
Button(27):
pygame.mixer.Sound("/home/pi/gpio-music-
box/samples/drum_snare_hard.wav"),
Button(10):
pygame.mixer.Sound("/home/pi/gpio-music-
box/samples/drum_cowbell.wav")}

You can now loop over the dictionary to tell the program to play
the sound when the button is pressed:

for button, sound in button_sounds.items():


button.when_pressed = sound.play

Full code listing

import pygame
from gpiozero import Button

pygame.init()

button_sounds = {Button(4):
pygame.mixer.Sound("/home/pi/gpio-music-
box/samples/drum_tom_mid_hard.wav"),
Button(17):
pygame.mixer.Sound("/home/pi/gpio-music-
box/samples/drum_cymbal_hard.wav"),
Button(27):
pygame.mixer.Sound("/home/pi/gpio-music-
box/samples/drum_snare_hard.wav"),
Button(10):
pygame.mixer.Sound("/home/pi/gpio-music-
box/samples/drum_cowbell.wav")}

for button, sound in button_sounds.items():


button.when_pressed = sound.play

Challenge!

You might also like