Play Musical Notes and Tunes with CircuitPython ?

Simple Method for Playing Tunes from CircuitPython, including Jingle Bells and Hanukkah

Play Musical Notes and Tunes with CircuitPython ?

We held a CircuitPython Workshop on Hanukkah eve, and Keren, the co-organizer of the event, had a fun idea: why won't we put an Easter egg in the slides: code that plays the Hanukkah tune?

Google quickly found for us the list of notes for the tune, but we couldn't find a way to tell CircuitPython to play a specific note, only a given sound frequency. The documentation refers you to a chart in Wikipedia,  which is definitely helpful, but not the most straightforward way to create music in Python.

To make our life easier, I wrote a small function that converts a note name into frequency. The workshop participants loved it and some even made their own tunes using this function. Here it is:

def note(name):
    octave = int(name[-1])
    PITCHES = "c,c#,d,d#,e,f,f#,g,g#,a,a#,b".split(",")
    pitch = PITCHES.index(name[:-1].lower())
    return 440 * 2 ** ((octave - 4) + (pitch - 9) / 12.)

To use it, simply call it with a name of a note, followed by an octave number. For example, if you want to get the frequency of "C" in the forth octave, just call note("C4").

You can combine it with the  cp.playTone() function to actually play the tone. So if you want to play "C4" for one second, you can write: cp.playTone(note("C4"), 1). That's it! You are ready to start composing melodies ?

How Does It Work?

The first line of the note function extracts the number of the octave by looking at the last character of the name parameter, and converting it to integer.

The next line simple defines an array of all the note names ("C", "C#", etc.), and then we take all the value of the name parameter without the last character (name[:-1] does this for us), and find its index in that array to calculate the pitch.

At this point, we have the octave number, and the pitch (number of semitones counting up from the note C). We then take 440, the frequency of the A4 note, and multiply it by 2 raised to the power of the distance between the given note and A4 (pitch 9, octave 4) divided by 12, following the formula from Wikipedia:

Let's see some examples:

Jingle Bells

import time
from adafruit_circuitplayground import cp

def note(name):
    octave = int(name[-1])
    PITCHES = "c,c#,d,d#,e,f,f#,g,g#,a,a#,b".split(",")
    pitch = PITCHES.index(name[:-1].lower())
    return 440 * 2 ** ((octave - 4) + (pitch - 9) / 12.)

sequence = [
  ("e5", 2), ("e5", 2), ("e5", 4), ("e5", 2), ("e5", 2), ("e5", 4),
  ("e5", 2), ("g5", 2), ("c5", 4), ("d5", 1), ("e5", 6), (None, 2),
  ("f5", 2), ("f5", 2), ("f5", 3), ("f5", 1), ("f5", 2), ("e5", 2),
  ("e5", 2), ("e5", 1), ("e5", 1), ("e5", 2), ("d5", 2), ("d5", 2),
  ("e5", 2), ("d5", 4), ("g5", 2), (None, 2),
  ("e5", 2), ("e5", 2), ("e5", 4), ("e5", 2), ("e5", 2), ("e5", 4),
  ("e5", 2), ("g5", 2), ("c5", 4), ("d5", 1), ("e5", 6), (None, 2),
  ("f5", 2), ("f5", 2), ("f5", 3), ("f5", 1), ("f5", 2), ("e5", 2),
  ("e5", 2), ("e5", 1), ("e5", 1), ("g5", 2), ("g5", 2), ("f5", 2),
  ("d5", 2), ("c5", 6), (None, 2)
]

for (notename, eigths) in sequence:
   length = eigths * 0.1
   if notename:
     cp.play_tone(note(notename), length)
   else:
     time.sleep(length)

Hanukkah Tune

from adafruit_circuitplayground import cp

def note(name):
    octave = int(name[-1])
    PITCHES = "c,c#,d,d#,e,f,f#,g,g#,a,a#,b".split(",")
    pitch = PITCHES.index(name[:-1].lower())
    return 440 * 2 ** ((octave - 4) + (pitch - 9) / 12.)

sequence = [
  ("g5", 2), ("e5", 2), ("g5", 4), ("g5", 2), ("e5", 2), ("g5", 4),
  ("e5", 2), ("g5", 2), ("c6", 2), ("b5", 2), ("a5", 8),
  ("f5", 2), ("d5", 2), ("f5", 4), ("f5", 2), ("d5", 2), ("f5", 4),
  ("d5", 2), ("f5", 2), ("b5", 2), ("a5", 2), ("g5", 8),
  ("g5", 2), ("e5", 2), ("g5", 4), ("g5", 2), ("e5", 2), ("g5", 4),
  ("e5", 2), ("g5", 2), ("c6", 2), ("b5", 2), ("a5", 8),
  ("b5", 2), ("b5", 2), ("b5", 4), ("b5", 2), ("b5", 2), ("b5", 4),
  ("b5", 2), ("g5", 2), ("a5", 2), ("b5", 2), ("c6", 8),
]

for (notename, eights) in sequence:
   cp.play_tone(note(notename), eights * 0.1)

And here it is in action (with some bonus lights added by one of the workshop participants):

I hope that you find this small snippet useful, and would love to hear what tunes you come up with!