CircuitPython makes it fun and straightforward to program this project, but before we get going, there are a few things to do to make sure your Circuit Playground Express is all set up and ready to go.

Get Ready!

  1. First, make sure you're familiar with the basics of using CircuitPython on the Circuit Playground Express. Follow this guide to familiarize yourself.
  2. Then, install CircuitPython on your board by following these instructions.
  3. The last thing to do to prepare is to install the library bundle onto your board as shown here. The libraries give us what we need to code easily with high level commands!

Download the latest bundle from this link.

Once you've uncompressed the contents of the zip file, drag its contents to your Circuit Playground Express lib directory.

Code

On to the code! There are a few fundamental things we need to do to make the FruitBox Sequencer work:

  • Play .wav file sound samples
  • Light up NeoPixels
  • Read the capacitive touch pads, with proper sensitivity
  • Read the buttons and the switch

Drum Samples

Download the zip file linked here to get a set of drum samples to use. You can later provide your own, but these will get you off to a great start!

Unzip the file and then drag all seven .wav samples to your CIRCUITPY drive (that's the name that shows up when your Circuit Playground Express is plugged in and ready to be programmed.)

Below is our final, finished code. You can copy and paste this into your text or code editor, and then save it to you Circuit Playground Express board as main.py.

Download the FruitBox Python Code

Copy the code below, and paste it into a new text document in your text editor, or in the Mu code editor for CircuitPython.

then save it to you Circuit Playground Express board as main.py.

# FruitBox Sequencer
# for Adafruit Circuit Playground express
# with CircuitPython

import time

from adafruit_circuitplayground.express import cpx

# Change this number to adjust touch sensitivity threshold, 0 is default
cpx.adjust_touch_threshold(600)

bpm = 60  # quarter note beats per minute, change this to suit your tempo

beat = 15 / bpm  # 16th note expressed as seconds

WHITE = (30, 30, 30)
RED = (90, 0, 0)
YELLOW = (45, 45, 0)
GREEN = (0, 90, 0)
AQUA = (0, 45, 45)
BLUE = (0, 0, 90)
PURPLE = (45, 0, 45)
BLACK = (0, 0, 0)

cpx.pixels.brightness = 0.1  # set brightness value

# The seven files assigned to the touchpads
audio_files = ["fB_bd_tek.wav", "fB_elec_hi_snare.wav", "fB_elec_cymbal.wav",
               "fB_elec_blip2.wav", "fB_bd_zome.wav", "fB_bass_hit_c.wav",
               "fB_drum_cowbell.wav"]

step_advance = 0  # to count steps
step = 0

# sixteen steps in a sequence
step_note = [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]

# step pixels
step_pixel = [9, 8, 7, 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]

# step colors
step_col = [WHITE, RED, YELLOW, GREEN, AQUA, BLUE, PURPLE, BLACK]


def prog_mode(index):
    cpx.play_file(audio_files[index])
    step_note[step] = index
    cpx.pixels[step_pixel[step]] = step_col[step_note[step]]
    print("playing file " + audio_files[index])


while True:
    # playback mode
    if cpx.switch:  # switch is slid to the left, play mode

        cpx.red_led = False

        if cpx.button_a:
            cpx.pixels.fill(GREEN)
            time.sleep(.2)
            cpx.pixels.fill(BLACK)

        if cpx.button_b:
            for i in range(16):
                step = i
                # light a pixel
                cpx.pixels[step_pixel[i]] = step_col[step_note[i]]
                cpx.pixels[step_pixel[i - 1]] = BLACK

                # play a file
                cpx.play_file(audio_files[step_note[i]])

                # sleep a beat
                time.sleep(beat)
            cpx.pixels.fill(BLACK)

    # beat programming mode
    else:  # switch is slid to the right, record mode
        cpx.red_led = True
        if cpx.button_a:  # clear pixels, reset step to first step
            cpx.pixels.fill(RED)
            time.sleep(.2)
            cpx.pixels.fill(BLACK)
            cpx.pixels[9] = WHITE
            step = 0
            step_advance = 0

        # press B button to advance neo pixel steps
        if cpx.button_b:  # button has been pressed
            step_advance += 1
            step = step_advance % 16
            cpx.play_file(audio_files[step_note[step]])
            cpx.pixels[step_pixel[step]] = step_col[step_note[step]]
            cpx.pixels[step_pixel[step - 1]] = BLACK

        if cpx.touch_A1:
            prog_mode(0)
        if cpx.touch_A2:
            prog_mode(1)
        if cpx.touch_A3:
            prog_mode(2)
        if cpx.touch_A4:
            prog_mode(3)
        if cpx.touch_A5:
            prog_mode(4)
        if cpx.touch_A6:
            prog_mode(5)
        if cpx.touch_A7:
            prog_mode(6)

Try it out! In playback mode, hit B to hear it play.

Then, switch to beat programming mode, and pick a sound per step. Advance to the next step with the B button, until you're finished with all sixteen steps. Then return to playback mode, hit B again and enjoy your composition! You can hold B to make it loop indefinitely.

Check out the code!

Let's have a look at the code in pieces.

Library Import

First, we'll import a couple of libraries that allow us to use simple, high-level commands to do more complicated things.

Download: file
# FruitBox Step Sequencer
# Circuit Playground Express
from adafruit_circuitplayground.express import cpx
import time

The first line imports the adafruit_circuitplayground.express library, and allows us to refer to it as cpx throughout our code.

This library take all sorts of CircuitPython functions that run on the Circuit Playground Express and gives us a consistent, simple way to control them.

For example, there are usually a few distinct steps associated with using a button in CircuitPython:

  1. Declaring a digital pin as an input
  2. Giving it a name
  3. Reading it to see if it's pressed or not

By using the adafruit_circuitplayground.express library with one of the buttons built onto the Circuit Playground Express, all of those steps are reduced to a single line -- cpx.button_a -- when we want to see if the button is pressed. That's much easier!

Then we import the time library which allows us to keep track of time for precise intervals -- an important feature when building a drum sequencer!

Touchpad Sensitivity

The next thing we'll do is tune the sensitivity of the on-board capacitive touchpads. They are automatically calibrated at startup, but within a range that's appropriate for the built-in pads, not external ones.

Since we'll be adding wires and fruit, the values need to be adjusted to prevent them from being overly sensitive and playing phantom beats!

The cpx.adjust_touch_threshold line includes a number that you can adjust (0 for no offset, higher numbers will reduce the sensitivity. 600 worked well for the fruit I used). We then list all of the pads by name that should be offset.

Download: file
# Change this number to adjust touch sensitivity threshold, 0 is default
cpx.adjust_touch_threshold(600)

BPM Setup

Next, we'll create a couple of variables to help us with time signature. Sequencer music is usually expressed in beats per minute (BPM) so we'll make a variable with named bpm and assign a default value of 90 BPM. This is the number to change in your code if you want to slow down or speed up your sequence. A range of 40-120 works well for the samples we're playing.

Then, we'll create a variable call beat that is used to convert the chosen BPM into seconds. This is helpful because the time library uses seconds as its unit of measure.

Download: file
bpm = 90  # quarter note beats per minute, change this to suit your tempo
beat = 15 / bpm  # 16th note expressed as seconds

NeoPixel Setup

We'll use the on-board NeoPixels to indicate which sample is being played at a given time step by color coding them. The color of a NeoPixel is typically defined as an amount from 0-255 of red, green, and blue. Just to make our code simpler to read and edit later, we'll define a few colors by name, such as PURPLE = (45, 0, 45)

Additionally, we'll set an overall brightness of about 1/10th of the possible brightness (did we mention NeoPixels are BRIGHT!?) 

Download: file
WHITE = (30, 30, 30)
RED = (90, 0, 0)
YELLOW = (45, 45, 0)
GREEN = (0, 90, 0)
AQUA = (0, 45, 45)
BLUE = (0, 0, 90)
PURPLE = (45, 0, 45)
BLACK = (0, 0, 0)

cpx.pixels.brightness = 0.1  # set brightness value

Sound Sample Filename Array

When we play back sound files, the play_file command we'll be using takes an argument of the actual filenames we've saved to the board. Since these tend to be long, wild, and wooly names, we don't want to have to use them over and over again in our code. This is a great opportunity to create a list, which gives us a nice, concise shorthand for referring to the files later.

Download: file
# The seven files assigned to the touchpads
audio_files = ["fB_bd_tek.wav", "fB_elec_hi_snare.wav", "fB_elec_cymbal.wav",
               "fB_elec_blip2.wav", "fB_bd_zome.wav", "fB_bass_hit_c.wav",
               "fB_drum_cowbell.wav"]

This way, we'll be able to ask for audio_files[2] in our code instead of using the name fb_elec_cymbal.wav, for example. (Note, Python lists are "zero indexed" which means the first item in the list is item 0, and so on.)

Also, when we want to loop through a series of items with different names, it's great to have them in an list, so that another simple integer variable, such as i, can be used as an easily controlled pointer that refers to items in the list. For example:

for i in range(7):

    cpx.playFile(audio_files[i])

    time.sleep(0.1)

That would play all of the files with a short delay in between without needing us to call them each by name.

Counting Variables

The next bit of setup we'll do involves creating variables to keep track of steps in the sequence, which sounds should play at each step, and which colors to show per step.

These have some defaults built in, so that it will play back before you've entered your own sequence. Those values will be changed interactively when you're using your FruitBox Sequencer.

Download: file
step_advance = 0  # to count steps
step = 0

# sixteen steps in a sequence
step_note = [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]

# step pixels
step_pixel = [9, 8, 7, 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]

# step colors
step_col = [WHITE, RED, YELLOW, GREEN, AQUA, BLUE, PURPLE, BLACK]

Playback Function

 The next thing we'll do is create a function called prog_mode() to handle the playback of notes and color coding of pixels when you touch a fruit trigger while you're in beat programming mode. This function will be fed a number corresponding to which fruit you've pressed, numbers 0-6.

That number is then passed to each remaining line to:

  • tell the sound file associated with that fruit trigger to play
  • store that note at the current step in the list which holds the full step sequence
  • light up the current step pixel to the color associated with that fruit trigger
  • print out the name of the file for debugging purposes to the REPL console
Download: file
def prog_mode(i):
    cpx.play_file(audio_files[i])
    step_note[step] = i
    cpx.pixels[step_pixel[step]] = step_col[stepnote[step]]
    print("playing file " + audio_files[i])

Loop & Switch

Now we're finished with the setup and function definition, and on to the meat of the code, the while True: loop. This is what runs over and over and over again.

Download: file
while True:
    # playback mode
    if cpx.switch:  # switch is slid to the left, play mode
        # lots of things happen here...
        
    # beat programming mode
    else: 
        # other stuff happens here...

The first thing we do is establish if the Circuit Playground Express board's toggle switch has been slid to the left (Playback mode) or to the right (Beat programming mode). We'll then build up code within those two sections.

In CircuitPython, the line if cpx.switch: is using the adafruit_circuitplayground.express library to query the slide switch, and the answer to that query is True if the switch is on the left, and all of the code below and indented from that line will run. else: it returns False, meaning the switch is on the right, and all of the code under that section will run.

Playback Mode

Here's what happens if the switch is on the left. First, the small, on-board red LED (to the right of the USB jack) is turned off. We'll reserve that indicator for when we're in beat programming mode, kind of like the red light on a camera when you're recording a video.

We'll then query button A, the one on the left side of the board. If it is pressed, we'll flash all of the NeoPixels green and then turn them off. This is to clear any colors left over from playback. You could program this button to do other things in the future if you like!

Download: file
        cpx.red_led = False

        if cpx.button_a:
            cpx.pixels.fill(GREEN)
            time.sleep(.2)
            cpx.pixels.fill(BLACK)

Next we'll look at what happens when button B is pressed while in Playback mode.

We will run a loop sixteen times where the variable i is increased in value each time, starting at 0 and ending at 15. Then, all of the lines of code within the loop will be executed sixteen times, each time with the 'i' value increasing.

cpx.pixels[step_pixel[i]] = step_col[step_note[i]] is the first of these. This may seem a bit complicated, but what it does is set the color of the current pixel (the step_pixel[] list we created earlier allows us to run through the upper right pixel, clockwise through to the eighth one, and then wrap back again up to the upper right) to equal the color associated with the current note that is stored in the step_note[] list.

So, if we consider the CircuitPython point-of-view the first iteration of running through this, based on the default step_note, it would see this line like this:

cpx.pixels[step_pixel[0]] = step_col[step_note[0]]

So, when we look back in the Counting Variables section above at what values are in the 0 position of the arrays being called we see:

  • step_pixel[0] is NeoPixel 9
  • step_note[0] is 1, therefore:
  • step_col[1] is RED

So we can now imagine the line of code resolves to this:

cpx.pixels[9] = RED

So, not to tricky after all, but it takes this type of logic to loop through elements that can contain a few volatile variables efficiently!

Given the default values when the FruitBox Sequencer starts up, here's how that line of code resolves internally for the full sixteen step loop:

cpx.pixels[9] = RED
cpx.pixels[8] = RED
cpx.pixels[7] = RED
cpx.pixels[6] = RED
cpx.pixels[5] = RED
cpx.pixels[4] = RED
cpx.pixels[3] = RED
cpx.pixels[2] = RED

cpx.pixels[9] = WHITE
cpx.pixels[8] = WHITE
cpx.pixels[7] = WHITE
cpx.pixels[6] = WHITE
cpx.pixels[5] = RED
cpx.pixels[4] = RED
cpx.pixels[3] = RED
cpx.pixels[2] = RED

Whew! We wouldn't want to do that by hand, especially because these colors will be changing as you program in different beats!

We follow each step with a command to turn the previous pixel off, and then to play the proper sound file. For the case of the first step in our loop that would resolve to this:

cpx.play_file("fB_bd_tek.wav")

We pause for a beat as defined by the bpm variable after each note, and then, when the loop has completed, turn all of the NeoPixels off.

Download: file
if cpx.button_b:
            for i in range(16):
                step = i
                # light a pixel
                cpx.pixels[step_pixel[i]] = step_col[step_note[i]]
                cpx.pixels[step_pixel[i - 1]] = BLACK

                # play a file
                cpx.play_file(audio_files[step_note[i]])

                # sleep a beat
                time.sleep(beat)
            cpx.pixels.fill(BLACK)

Beat Programming Mode

Flip the switch to the right, and it's time to input a different sounds per step!

First, the red LED is turned on, to indicate the mode change.

Then, we again check the A button. If it's pressed, we'll flash all of the pixels red, and then turn them off, and set pixel 9 to white.

Also note how the step and step_advance counter variables are reset to 0. This way, you can press A at any time to go back to the start of the pattern.

Download: file
# beat programming mode
    else:  # switch is slid to the right, record mode
        cpx.red_led = True
        if cpx.button_a:  # clear pixels, reset step to first step
            cpx.pixels.fill(RED)
            time.sleep(.2)
            cpx.pixels.fill(BLACK)
            cpx.pixels[9] = WHITE
            step = 0
            step_advance = 0

We'll check the B button next. When it is pressed, the step is advanced, this is how you move step-by-step through the sequence. Whatever sound is currently stored in that slot of the step_note list is played, and the corresponding color is set on the proper NeoPixel.

Download: file
# press B button to advance neopixel steps
        if cpx.button_b:  # button has been pressed
            step_advance += 1
            step = step_advance % 16
            cpx.play_file(audio_files[step_note[step]])
            cpx.pixels[step_pixel[step]] = step_col[step_note[step]]
            cpx.pixels[step_pixel[step - 1]] = BLACK

Note, you can change the sample triggered at the current step at any time by pressing one of the fruit triggers. How, you ask? Like this!:

Fruit Triggers

In this final bit of code, we check each capacitive pad to see if it is touched. When it is, it calls the prog_mode function we defined at the top, with an input value that corresponds to the touchpad.

So, for example, if A2 is touched while we're on step 0, the value 1 is fed to the prog_mode function, which plays the fB_elec_hi_snare.wav file, and updates the step_note[] list with this information. Now, when the FruitBox Sequencer needs to play a note while on step 0, it will play the snare!

Download: file
        if cpx.touch_A1:
            prog_mode(0)
        if cpx.touch_A2:
            prog_mode(1)
        if cpx.touch_A3:
            prog_mode(2)
        if cpx.touch_A4:
            prog_mode(3)
        if cpx.touch_A5:
            prog_mode(4)
        if cpx.touch_A6:
            prog_mode(5)
        if cpx.touch_A7:
            prog_mode(6)

This guide was first published on Dec 11, 2017. It was last updated on Dec 11, 2017.

This page (Code with CircuitPython) was last updated on Nov 06, 2020.