Relative pitch is the ability to identify a given musical note by comparing it to a reference note. Unlike perfect pitch, relative pitch can be improved with training. The Piano HAT is a versatile piece of hardware that we can use to create a fun game that tests people’s skill in recognising different notes. It was inspired by Zachary Igielman’s legendary PiPiano and it turns your Pi into a functional musical keyboard. Each of the 16 capacitive keys also has an LED so you can create you own ‘learn to play’ tutorials or just give your performances a visual appeal.
You'll need
Headphones or an external speaker
STEP-01 Getting started with Piano HAT
Like most HATs, this one is straightforward to use. Simply plug it carefully onto the GPIO pins of your Pi. Then install the Piano-HAT Python library. This requires the I2C bus on the Pi to be enabled, and there are plenty of instructions for this online. But to make life super-easy, those Pirates at Pimoroni provide a handy script that takes care of everything:
$ curl -sSL get.pimoroni.com/pianohat | bash
STEP-02 Wired for sound
There are two options for getting audio output from a Pi. If you are using a HDMI monitor or a TV that has built-in speakers, the audio can be played over the HDMI cable. If not, you can switch to use headphones or a speaker plugged into the headphone jack. The Pi will normally auto-detect the available outputs, but sometimes it gets this wrong. To force audio to use a specific output, you can use this command:
$ amixer cset numid=3 2
The second number determines the output:
HDMI = 2, jack = 1, auto = 0.
STEP-03 Play it again
The Piano-HAT library has a nice collection of demonstration Python scripts. A good one to start with lets you use the Piano HAT as… a piano!
$ sudo python Pimoroni/pianohat/simple-piano.py
If you press the Instrument key, you’ll notice that the sounds change from pianos to percussion. The Piano-HAT library itself does not map any of the keys to a particular sound; that is all done using Python. The sounds themselves are WAV files, which are played using the Pygame library.
Let’s map our own sound to one of the Piano HAT’s keys. Find a short WAV file online (or create your own using Sonic Pi) and save it as mysound.wav. Then type in the code from below, save it as Listing_l1.py and run it: your sound should play when the D key is pressed.
import pianohat import pygame import signal pygame.mixer.pre_init(44100, -16, 1, 512) pygame.mixer.init() pygame.mixer.set_num_channels(16) def handle_note(channel, pressed): if channel == 2: pygame.mixer.Sound('./mysound.wav').play(loops=0) pianohat.on_note(handle_note) signal.pause()
STEP-04 A little light music
You’ll notice that the relevant LED lights up when any key is pressed. This is the default behaviour, but can be disabled using:
pianohat.auto_leds(False)
Add this line immediately before the handle_note function in the code we've just written. Now we can add code so that only the D key’s LED will work. Insert
pianohat.set_led(2,True)
…before the pygame.mixer.Sound line and
pianohat.set_led(2,False)
…after it.
Now rerun the program to verify that only the D lights up when tapped.
STEP-05 Use a dictionary
Mapping keys to sounds using a bunch of if… statements is easy but rather long-winded. For our relative pitch test, we want to associate the key (an integer) with the note (a text string) and the sound to be played (also a string). A simple way is to use a Python construct called a dictionary. A real-world dictionary has an index of words, and each word has definitions. In a Python dictionary, the word is called the ‘key’, and the definitions the ‘values’.
The code below, which should be saved as Listing_l2.py, uses a simple two-item dictionary to map sounds and names (the values) onto the C and D keys (the keys). Give it a try.
import pianohat import pygame import signal pygame.mixer.pre_init(44100, -16, 1, 512) pygame.mixer.init() pygame.mixer.set_num_channels(16) NOTES = {0:['Sound1','./mysound.wav'], 2: ['Sound2', './mysound2.wav']} def handle_note(channel, pressed): if channel == 0 or channel == 2: pygame.mixer.Sound(NOTES[channel][1]).play(loops=0) pianohat.on_note(handle_note) signal.pause()
STEP-06 Putting it all together
We’ve now explored everything needed for our relative pitch tester: we’ll use the piano sounds that come with the Piano-HAT library for our notes.
Type up the code in our code listing below, which we're calling Relative_Pitch.py, and run it. Remember to use sudo if you're on wheezy. A reference note (a C) is played, then, after a pause, the note to be identified. The player then has 6 seconds to press the correct key for the note they just heard. If they get it right, all the LEDs will flash in celebration, otherwise they’re asked to try again.
As an extension, how about using different, selectable instruments?
Code listing
Alternatively, download the code from GitHub
import pianohat # import libraries we need import pygame import time, random pygame.mixer.pre_init(44100, -16, 1, 512) #Configure pygame sound pygame.mixer.init() #Initialise pygame mixer pygame.mixer.set_num_channels(16) pianohat.auto_leds(True) # LEDs light when keys pressed # A dictionary mapping sounds and notes onto keys NOTES = {'0':['C','./sounds/piano/39172__jobro__piano-ff-025.wav'], # C '1': ['C Sharp', './sounds/piano/39173__jobro__piano-ff-026.wav'], # C sharp '2':['D','./sounds/piano/39174__jobro__piano-ff-027.wav'], # D '3':['D Sharp','./sounds/piano/39175__jobro__piano-ff-028.wav'], # D sharp '4':['E','./sounds/piano/39176__jobro__piano-ff-029.wav'], # E '5':['F','./sounds/piano/39177__jobro__piano-ff-030.wav'], # F '6':['F Sharp','./sounds/piano/39178__jobro__piano-ff-031.wav'], # F sharp '7':['G','./sounds/piano/39179__jobro__piano-ff-032.wav'], # G '8':['G Sharp','./sounds/piano/39180__jobro__piano-ff-033.wav'], # G sharp '9':['A','./sounds/piano/39181__jobro__piano-ff-034.wav'], # A '10':['A Sharp','./sounds/piano/39182__jobro__piano-ff-035.wav'], # A sharp '11':['B','./sounds/piano/39183__jobro__piano-ff-036.wav'], # B '12':['C','./sounds/piano/39184__jobro__piano-ff-037.wav'] # C } def handle_note(channel, pressed): # handler for key presses global note global correct if channel < 13 and pressed: # Only for note keys if str(channel) == note: # Did the player get it right? print('correct, it was a ' + str(NOTES[note][0]) ) pianohat.auto_leds(False) for x in range(16): # Flash all the lights to celebrate pianohat.set_led(x, True) time.sleep(0.05) for x in range(16): # Them turn them off pianohat.set_led(x,False) pianohat.auto_leds(True) correct = True else: print('wrong, try again') def play(note): # Play a note from the dictionary pygame.mixer.Sound(NOTES[note][1]).play(loops=0) time.sleep(1) pianohat.on_note(handle_note) # Set keys to use our handler while True: print('Here comes a C') time.sleep(1) play('0') # Play a C time.sleep(2) correct = False print('Now identify this note') time.sleep(2) note = random.choice(list(NOTES)) # Pick a random note play(note) print('press the key for the note you heard') count = 6 # Set countdown timer while count > 0 and correct == False: time.sleep(1) print(str(count) + ' Seconds remaining') count -=1 if not correct: # If they didn't get it right, tell them the answer print("time's up, it was a " + str(NOTES[note][0]))