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.
# SPDX-FileCopyrightText: 2018 Anne Barela for Adafruit Industries # # SPDX-License-Identifier: MIT # 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.
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?
# 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.
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.
# 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.
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.
# 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.
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.
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.
Text editor powered by tinymce.