Code with CircuitPython

Are you new to using CircuitPython? No worries, there is a full getting started guide here.

Keep Up with the Latest Version

The code will work with either CircuitPython 2.3.1 or the 3.0.0 release candidate version. Using the latest CircuitPython and library release assures that your project will function from the get-go. Visit the Circuit Playground Express learning guide for how to install the latest version.

Download the Sound Samples

Download the sound samples folder and copy the sound file contents to the Circuit Playground Express (CPX). The code will use these files to play the different percussion and chord sounds.

CircuitPython Code

Here's the CircuitPython code that will power the Blues Playground instrument. It's recommended that you use the Mu editor and it's REPL to edit and test your code. You can learn about installing and using Mu in this tutorial.

You can copy the full code into Mu and save it to the CPX as file with the name code.py to start playing the blues. A breakdown of how each section of the code works follows the full code listing.

# Blues Playground Instrument
# 2018-06-19 v03

import time
from adafruit_circuitplayground.express import cpx

# lists
drums = [  # startup and drum sound files
    "chime.wav",
    "kick.wav",
    "snare.wav",
    "hat.wav",
    "cymbal.wav",
    "tamb.wav",
    "cowbell.wav",
    "vibra_slap.wav"
]

chords = ["cp_A.wav", "cp_D.wav", "cp_E.wav"]

chord_changes = [  # chord_file, beats to play, beats played
    [0, 4, 0],
    [1, 4, 0],
    [0, 2, 0], [1, 2, 0],
    [0, 4, 0],
    [1, 4, 0],
    [1, 4, 0],
    [0, 2, 0], [1, 2, 0],
    [0, 4, 0],
    [2, 4, 0],
    [1, 4, 0],
    [0, 2, 0], [1, 2, 0],
    [0, 2, 0], [2, 2, 0]
]

cpx.pixels.fill((0, 0, 0))  # clear all pixels
cpx.play_file(drums[0])  # play startup

# select instrument mode with slide switch
if cpx.switch:
    instrument = "Chords"  # remember mode for later
    for i in range(9, -1, -1):  # fill chord range background with blue
        cpx.pixels[i] = (0, 0, 1)
        time.sleep(0.05)
else:
    instrument = "Drums"  # remember mode for later
    for i in range(9, 2, -1):  # fill voice range background with white
        cpx.pixels[i] = (1, 1, 1)
        time.sleep(0.05)

    voice = 1  # first drum voice
    cpx.pixels[10 - voice] = (0, 5, 0)  # green for first voice
    cpx.play_file(drums[voice])  # play first drum voice

    # choose drum voice with button A, lock-in selection with button B
    while not cpx.button_b:
        if cpx.button_a:  # loop through voices
            cpx.pixels[10 - voice] = (1, 1, 1)  # replace background color
            voice = voice + 1
            if voice > 7:
                voice = 1
            cpx.pixels[10 - voice] = (0, 5, 0)  # green for voice selection
            cpx.play_file(drums[voice])  # play current voice
        time.sleep(0.3)
    for i in range(3, 10):
        if i != (10 - voice):
            cpx.pixels[i] = (0, 0, 0)
            time.sleep(.1)
time.sleep(0.3)

# let's play the blues!
c_idx = 0  # start with the first chord change
while True:
    cpx.detect_taps = 1
    if cpx.tapped:  # wait for single tap
        if instrument == "Drums":
            cpx.play_file(drums[voice])  # play the selected drum sound

        if instrument == "Chords":  # play chords in sequence
            cpx.pixels[9 - (c_idx % 16)] = (3, 3, 0)  # yellow foreground
            cpx.play_file(chords[chord_changes[c_idx][0]])
            chord_changes[c_idx][2] = chord_changes[c_idx][2] + 1
            cpx.pixels[9 - (c_idx % 16)] = (0, 0, 1)  # replace background
            if chord_changes[c_idx][2] >= chord_changes[c_idx][1]:
                chord_changes[c_idx][2] = 0
                c_idx = c_idx + 1
                if c_idx > 15:
                    c_idx = 0

Let's break down the code into sections to understand what it does.

Import Libraries

The code will need some libraries to provide support for reading the CPX's switch, buttons, and accelerometer, operating the on-board NeoPixels, and playing sound over the speaker. We will also use a library for creating time delays.

time provides the delays we'll need for NeoPixel animation. The most important library, adafruit_circuitplayground.express, is the one that supports the myriad of CPX features, of which we'll be using the slide switch, push buttons, accelerometer, speaker, and NeoPixels.

You can learn more about installing the time and adafruit_circuitplayground.express libraries in the CircuitPython Essentials Guide on Libraries.

Download: file
import time
from adafruit_circuitplayground.express import cpx

Make Some Lists

The Blues Playground code frequently refers to the recorded percussion sound and chord file names. The percussion sound file names are elements of the drums list. The three musical chords used in the blues sequence are elements of the chords list.

The sequence of chords played for the 12-bar blues is contained in the chord_changes list. Each element of the list contains a tuple (like a sub-list) for each chord that refers to the chord file by number, lists the number of beats for playing that chord, and has a third element that keeps track of how many times that chord was played. For example, the first entry of the list, [0, 4, 0], indicates that first file in the chords list, cp_A.wav (the chord in the key of A), should be played for four beats and that no beats have been played so far.

A "bar" in the 12-bar blues sequence is a musical measure consisting of four beats. In this sequence, one of the three chords (A, D, E) will be played 16 times during the 12 measures, so there are a few instances in the chord_changes list where a chord will only be played for two beats. Can you spot them?

Download: file
# lists
drums = [  # startup and drum sound files
    "chime.wav",
    "kick.wav",
    "snare.wav",
    "hat.wav",
    "cymbal.wav",
    "tamb.wav",
    "cowbell.wav",
    "vibra_slap.wav"
]

chords = ["cp_A.wav", "cp_D.wav", "cp_E.wav"]

chord_changes = [  # chord_file, beats to play, beats played
    [0, 4, 0],
    [1, 4, 0],
    [0, 2, 0], [1, 2, 0],
    [0, 4, 0],
    [1, 4, 0],
    [1, 4, 0],
    [0, 2, 0], [1, 2, 0],
    [0, 4, 0],
    [2, 4, 0],
    [1, 4, 0],
    [0, 2, 0], [1, 2, 0],
    [0, 2, 0], [2, 2, 0]
]

Initialize the NeoPixels and Play the Startup Chime Sound

The next section of code turns off all the NeoPixels and plays a startup sound over the CPX speaker.

Download: file
cpx.pixels.fill((0, 0, 0))  # clear all pixels
cpx.play_file(drums[0])  # play startup

Choosing a Percussion Sound

The slide switch and push buttons are used to select which percussion instrument sound to play. If the slide switch is positioned to the right, then the Blues Playground will play percussion. To the left selects the blues chords sequence mode.

When the slide switch is in the Chords position, all the NeoPixels turn blue and the instrument goes immediately into play mode, bypassing the remaining code in this section.

Download: file
# select instrument mode with slide switch
if cpx.switch:
    instrument = "Chords"  # remember mode for later
    for i in range(9, -1, -1):  # fill chord range background with blue
        cpx.pixels[i] = (0, 0, 1)
        time.sleep(0.05)

When in the percussion (Drums) mode, the first seven NeoPixels (#9 through #3) turn white. The code starts with the first drum sound as indicated by the voice variable, turns NeoPixel #9 to green, plays the first drum sound, and waits for button A or B to be pushed. Pushing button A will turn the current NeoPixel white, step to the next sound file, light the next NeoPixel in sequence and play a sample of the next drum sound. This will repeat until button B is pressed to lock in the selection. Once a voice is selected, the Blues Playground moves on from this section of code.

Download: file
else:
    instrument = "Drums"  # remember mode for later
    for i in range(9, 2, -1):  # fill voice range background with white
        cpx.pixels[i] = (1, 1, 1)
        time.sleep(0.05)
        
    voice = 1  # first drum voice
    cpx.pixels[10 - voice] = (0, 5, 0)  # green for first voice
    cpx.play_file(drums[voice])  # play first drum voice

    # choose drum voice with button A, lock-in selection with button B
    while not cpx.button_b:
        if cpx.button_a:  # loop through voices
            cpx.pixels[10 - voice] = (1, 1, 1)  # replace background color
            voice = voice + 1
            if voice > 7:  voice = 1
            cpx.pixels[10 - voice] = (0, 5, 0)  # green for voice selection
            cpx.play_file(drums[voice])  # play current voice
        time.sleep(0.3)
    for i in range(3, 10):
        if i != (10 - voice):
            cpx.pixels[i] = (0, 0, 0)
            time.sleep(.1)
time.sleep(0.3)

Time to Play the Blues

In this section, we start by initializing the variable c_idx (chord index) to zero. This variable is used by the Chords mode to keep track of which chord is playing -- 0 through 15 for all the chords needed to play the 12-bar blues sequence. c_idx points to the chord attributes contained in the chord_changes list.

Download: file
# let's play the blues!
c_idx = 0  # start with the first chord change

The CPX accelerometer can detect when the paddle is tapped or struck by using the cpx.tapped status. When a tap is detected, the code uses the slide switch selection made earlier (as indicated by the instrument variable) and steers the code to either play the selected drum instrument or play through the sequence of chords. Playing the selected drum sound is pretty simple: the sound file is played by the cpx.play_file(filename) function.

Download: file
while True:
    cpx.detect_taps = 1
    if cpx.tapped:  # wait for single tap
        if instrument == "Drums":
            cpx.play_file(drums[voice])  # play the selected drum sound

Playing through the chord sequence is a bit more complicated. The code will turn a NeoPixel yellow depending on which chord is being played. NeoPixel #9 is the first chord, #8 is the second, etc. After the chord is played ( cpx.play_file(chords[chord_changes[c_idx][0]]) ), the beats played portion of the tuple in the chord_changes list is incremented, the NeoPixel is reset to blue, then beats played is compared to the number of beats to play. When the last beat of that chord is played, the beats played portion of the tuple is reset to zero and the chord index c_idx moves to the next chord in sequence. After the last chord of the 16 chords is played, c_idx is reset to zero so that the sequence starts over from the top. Whew.

Download: file
        if instrument == "Chords":  # play chords in sequence
            cpx.pixels[9 - (c_idx % 16)] = (3, 3, 0)  # yellow foreground
            cpx.play_file(chords[chord_changes[c_idx][0]])
            chord_changes[c_idx][2] = chord_changes[c_idx][2] + 1
            cpx.pixels[9 - (c_idx % 16)] = (0, 0, 1)  # replace background
            if chord_changes[c_idx][2] >= chord_changes[c_idx][1]:
                chord_changes[c_idx][2] = 0
                c_idx = c_idx + 1
                if c_idx > 15:  c_idx = 0

The cpx.play_file(chords[chord_changes[c_idx][0]]) function may be a bit difficult to understand, so let's break it down a bit more. We're using the two lists, chords and chord_changes, to tell CircuitPython which chord file to play. First, we look up the first item (item 0) of the tuple in the chord_changes list that is pointed to by c_idx. That gives us a number 0 through 2 that specifies which stored chord file to play. To get the name of the chord file, we use that number to point to the corresponding item in the chords list so that we can get the string of characters that the cpx.play_file function needs to locate and play the file stored in the CPX directory.

Using multiple lists is a way of "normalizing" the information used to keep track of the 16 chord changes and the three sound file names. In this case, normalizing the information eliminates the need to duplicate the sound file names in the chord_changes list, reducing the overall size of the code so that it'll fit in the limited memory of the CPX.

This guide was first published on Jun 28, 2018. It was last updated on Jun 28, 2018.
This page (Code with CircuitPython) was last updated on Mar 14, 2020.