Playing it by ear with Piano HAT

By Russell Barnes. Posted

The Piano HAT from Pimoroni is a great way to unleash your ivory-tinkling tendencies. Let’s use it to build a relative pitch tester

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

A Piano HAT

The Piano-HAT library

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]))

From The MagPi store

Subscribe

Subscribe to the newsletter

Get every issue delivered directly to your inbox and keep up to date with the latest news, offers, events, and more.