This is the full version of the code with capacitive touch functionality. If you're making the lights-only version, head to the MakeCode page for software instructions.

It's always a good idea to get your software loaded onto your board before the build. That way you'll be able to test your solder joints at each step of the way, and you'll get instant gratification when you plug in the lights.

Getting the software loaded is a 3-step process:

  1. Install the operating system (CircuitPython) on the board
  2. Copy the required libraries into the /lib folder on the board
  3. Save the code.py file to the board

CircuitPython is a fairly new OS that's changing rapidly. New features are being added and bugs are being fixed all the time, so it's always best to get a fresh version of CircuitPython and the library files before coding.

Install CircuitPython

The Circuit Playground Bluefruit ships with CircuitPython, but let's go ahead and update it to the latest version. It's super easy with the circuitpython.org website. Follow the directions on the  CircuitPlayground BlueFruit Guide, or click the button below for a quick link to the download.

Download the file, plug your Circuit Playground into your computer via the USB port, and double-click the reset button. You'll see a drive appear called CPLAYBTBOOT. Drag the .uf2 file you just downloaded onto this drive to install CircuitPython.

You'll know it worked if the CPLAYBTBOOT drive name changes to CIRCUITPY.

Adafruit Circuit Python Libraries

Download the CircuitPython library bundle per the Circuit Playground Bluefruit instructions here. Unzip the files into a folder on your computer. Create a new folder on the CIRCUITPY drive and name it lib. The following libraries are required to run the code properly.

  • adafruit_bus_device (directory)
  • adafruit_circuitplayground (directory)
  • adafruit_led_animation (directory)
  • adafruit_lis3dh.mpy
  • adafruit_mpr121.mpy
  • adafruit_thermistor.mpy
  • neopixel.mpy

Find all these files in the library bundle and copy them into the lib file you just made on your CIRCUITPY drive.

Upload Files

Click the link below to download the project zip – This contains the code and audio files. Upload the code.py file to the CIRCUITPY drive root (main) folder.

Create a new folder on the CIRCUITPY drive and name it sounds. Upload the audio files to that folder. The code will run properly when all of the files have been uploaded.

Check out the image above to see what your CIRCUITPY drive should look like when all the files are in place.

"""
Bottle Piano with Capacitive Touch
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Melissa LeBlanc, Erin St Blaine & Limor Fried for Adafruit Industries
Copyright (c) 2019-2020 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.
"""

import time
import digitalio
import board
import neopixel
import busio
import adafruit_mpr121
from audiocore import WaveFile
from audiopwmio import PWMAudioOut as AudioOut
from adafruit_led_animation.animation.rainbow import Rainbow
from adafruit_led_animation.animation.rainbowchase import RainbowChase
from adafruit_led_animation.animation.chase import Chase
from adafruit_led_animation.animation.rainbowcomet import RainbowComet
from adafruit_led_animation.sequence import AnimationSequence
from adafruit_led_animation.color import (
    BLACK,
    RED,
    ORANGE,
    YELLOW,
    GREEN,
    BLUE,
    MAGENTA,
    PURPLE,
    AMBER,
    TEAL,
)
from adafruit_debouncer import Debouncer

# pylint: disable=global-statement

# NeoPixel strip setup -- set your total number of pixels here  -----------------
PIXEL_NUM = 200
pixel_pin = board.A1
pixels = neopixel.NeoPixel(pixel_pin, PIXEL_NUM, brightness=1, auto_write=False)
LAST_BUTTON = None

'''
Customize your light strip for individual notes. Each line represents a bottle.
Change the numbers to reflect the first and last pixel in each ring. You can also
change the colors assigned to each bottle here.
'''

bottle_lights = (
    (0, 10, RED),
    (15, 30, ORANGE),
    (31, 52, AMBER),
    (53, 72, YELLOW),
    (77, 96, GREEN),
    (98, 119, TEAL),
    (120, 145, BLUE),
    (150, 173, PURPLE),
    (180, 200, MAGENTA),
)

# Cap touch board setup   ------------------------------------------------------
i2c = busio.I2C(board.SCL, board.SDA)
mpr121 = adafruit_mpr121.MPR121(i2c)

# Demo MODE LED Animations  ------------------------------------------------------
rainbow = Rainbow(pixels, speed=0, period=10, name="rainbow", step=1)
rainbow_chase = RainbowChase(pixels, speed=0, size=5, spacing=10)
chase = Chase(pixels, speed=0.1, color=RED, size=1, spacing=6)
rainbow_comet = RainbowComet(pixels, speed=0.01, tail_length=60, bounce=True)

# Animation Sequence Playlist -- rearrange to change the order of animations

animations = AnimationSequence(
    rainbow_chase,
    chase,
    rainbow_comet,
    auto_clear=True,
    auto_reset=True,
)

def go_dark():
    '''set all pixels to black'''
    pixels.fill(BLACK)
    pixels.show()


# Debouncer ------------------------------------------------------

buttons = [Debouncer(mpr121[i]) for i in range(12)]


# Audio Setup ------------------------------------------------------

spkr_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
spkr_enable.direction = digitalio.Direction.OUTPUT
spkr_enable.value = True

audio = AudioOut(board.SPEAKER)

tracks = (
    WaveFile(open("sounds/F2.wav", "rb")),  # 0
    WaveFile(open("sounds/G2.wav", "rb")),  # 1
    WaveFile(open("sounds/A2.wav", "rb")),  # 2
    WaveFile(open("sounds/Bb2.wav", "rb")),  # 3
    WaveFile(open("sounds/C2.wav", "rb")),  # 4
    WaveFile(open("sounds/D3.wav", "rb")),  # 5
    WaveFile(open("sounds/E3.wav", "rb")),  # 6
    WaveFile(open("sounds/F3.wav", "rb")),  # 7
    WaveFile(open("sounds/F1.wav", "rb")),  # 7
    WaveFile(open("sounds/G1.wav", "rb")),  # 8
    WaveFile(open("sounds/A1.wav", "rb")),  # 9
    WaveFile(open("sounds/Bb1.wav", "rb")),  # 10
    WaveFile(open("sounds/C1.wav", "rb")),  # 11
    WaveFile(open("sounds/D2.wav", "rb")),  # 12
    WaveFile(open("sounds/E2.wav", "rb")),  # 13
    WaveFile(open("sounds/F2.wav", "rb")),  # 13
)

# Add or change song track names here. They will play in the order listed.
demo_tracks = (
    WaveFile(open("sounds/undersea.wav", "rb")),
    WaveFile(open("sounds/tequila.wav", "rb")),
    WaveFile(open("sounds/lion.wav", "rb")),
)

MODE = 0  # Initial mode = OFF
SONG = 0




def light_up(bottle):
    '''light up the bottles'''
    lights = bottle_lights[bottle]
    for pixel_id in range(lights[0], lights[1]):
        pixels[pixel_id] = lights[2]


def play_bottle(bottle_id, is_octave):
    ''' play audio tracks and light up bottles'''
    global MODE
    go_dark()
    light_up(bottle_id)
    if is_octave:
        audio.play(tracks[bottle_id + 7])  # Start playing sound
        light_up(8)
    else:
        audio.play(tracks[bottle_id])  # Start playing sound
    pixels.show()
    MODE = 2


def check_buttons(touched):
    ''' check to see if buttons have been pressed'''
    global MODE, LAST_BUTTON
    octave = touched[11]
    off = touched[9]
    if octave:
        light_up(8)
    for pad in range(1, 9):
        if LAST_BUTTON is not None and not touched[LAST_BUTTON]:
            LAST_BUTTON = None
        if pad != LAST_BUTTON and touched[pad]:
            LAST_BUTTON = pad
            play_bottle(pad - 1, octave)
    if off:
        MODE = 9
        go_dark()
    if touched[10]:
        go_dark()
        audio.play(demo_tracks[SONG])
        while audio.playing:
            animations.animate()

        MODE = 3


while True:
    # Idle mode: Play a Rainbow animation when nothing's being touched
    if MODE == 0:
        pixels.brightness = 0.3  #rainbow mode is much brighter than the other modes, so adjust here
        rainbow.animate()
        for button in buttons:
            button.update()
        check_buttons(mpr121.touched_pins)
        time.sleep(0.1)
        for i in range(12):
            if buttons[i].fell:
                MODE = 1
    # If not idle mode
    if MODE >= 1:
        pixels.brightness = 1
        check_buttons(mpr121.touched_pins)
        time.sleep(0.1)
        if MODE == 2:  # mode 2 is individual notes
            if audio.playing:
                pixels.show()
                while audio.playing:
                    check_buttons(mpr121.touched_pins)
                    time.sleep(0.07)
            else:
                MODE = 0  # Return to idle mode
        if MODE == 3:
            SONG = SONG + 1
            animations.next()
            if SONG == 3:
                MODE = 0
                SONG = 0

            else:
                MODE = 0  # Return to idle mode
        if MODE == 9:  # MODE 9 is "off" mode, listening for a new button press to wake up.
#             for button in buttons:
#                 button.update()
            for i in range(12):
                if buttons[i].fell:
                    MODE = 1

Customizing Your Code

The best way to edit and upload your code is with the Mu Editor, a simple Python editor that works with Adafruit CircuitPython hardware. It's written in Python and works on Windows, MacOS, Linux and Raspberry Pi. The serial console is built right in so you get immediate feedback from your board's serial output. Instructions for installing Mu is here.

NeoPixel Setup

Look near the top of the code to find PIXEL_NUM. Change this number to reflect your total number of LEDs in your NeoPixel strips.

Download: file
# NeoPixel strip setup -- set your total number of pixels here  -----------------
PIXEL_NUM = 200
pixel_pin = board.A1
pixels = neopixel.NeoPixel(pixel_pin, PIXEL_NUM, brightness=1, auto_write=False)
LAST_BUTTON = None

Next we'll set up the pixel mapping for the individual bottles. Starting from 0, count out the pixels in the section of LED strip below each bottle. Replace my numbers with yours -- the first number is the first pixel in the section and the second number is the last pixel in the section.

If you want to customize the colors you can do that here as well. Remember to import any additional colors you want to add in the list near the top of the code.

This code is based on the CircuitPython LED Animations code library by Kattni Rembor. Check out that guide for a lot more info on using this library.

Download: file
'''
Customize your light strip for individual notes. Each line represents a bottle.
Change the numbers to reflect the first and last pixel in each ring. You can also
change the colors assigned to each bottle here. 
'''

bottle_lights = (
    (0, 10, RED),
    (15, 30, ORANGE),
    (31, 52, AMBER),
    (53, 72, YELLOW),
    (77, 96, GREEN),
    (98, 119, TEAL),
    (120, 145, BLUE),
    (150, 173, PURPLE),
    (180, 200, MAGENTA),
)

Demo Mode Animations

Customize the animations that play in idle mode and demo mode here. Play with the settings until the animations look exactly the way you want. Or try out some other animations -- there are a lot more plug-and-play animations and ideas in the LED animations guide.

The lower section is your animation playlist. Rearrange the order so they go with your songs.

Download: file
# Demo MODE LED Animations  ------------------------------------------------------
rainbow = Rainbow(pixels, speed=0.1, period=10, name="rainbow", step=1)
rainbow_chase = RainbowChase(pixels, speed=0, size=5, spacing=10)
chase = Chase(pixels, speed=0.1, color=RED, size=1, spacing=6)
rainbow_comet = RainbowComet(pixels, speed=0.01, tail_length=60, bounce=True)

# Animation Sequence Playlist -- rearrange to change the order of animations

animations = AnimationSequence(
    rainbow_chase,
    chase,
    rainbow_comet,
    auto_clear=True,
    auto_reset=True,
)

Audio Files

We've included two octaves of bottle tones in the key of F, as well as three demo tracks. This castle can play a few bars of Under the Sea (from the Little Mermaid movie), Tequila, and The Lion Sleeps Tonight. It's not hard to add your own favorite melodies, but you'll need to keep them fairly short - there is 2MB of memory on this board and we've just about filled it up with all these files.

Adafruit CircuitPython supports 16-bit, Mono, 22.050kHz .wav audio format. See this guide to help format any audio files you might want to use in this project besides the files provided.

Download: file
tracks = (
    WaveFile(open("sounds/F1.wav", "rb")),  # 0
    WaveFile(open("sounds/G1.wav", "rb")),  # 1
    WaveFile(open("sounds/A1.wav", "rb")),  # 2
    WaveFile(open("sounds/Bb1.wav", "rb")),  # 3
    WaveFile(open("sounds/C1.wav", "rb")),  # 4
    WaveFile(open("sounds/D2.wav", "rb")),  # 5
    WaveFile(open("sounds/E2.wav", "rb")),  # 6
    WaveFile(open("sounds/F2.wav", "rb")),  # 7
    WaveFile(open("sounds/G2.wav", "rb")),  # 8
    WaveFile(open("sounds/A2.wav", "rb")),  # 9
    WaveFile(open("sounds/Bb2.wav", "rb")),  # 10
    WaveFile(open("sounds/C2.wav", "rb")),  # 11
    WaveFile(open("sounds/D3.wav", "rb")),  # 12
    WaveFile(open("sounds/E3.wav", "rb")),  # 13
    WaveFile(open("sounds/F3.wav", "rb")),  # 13
)

# Add or change song track names here. They will play in the order listed.
demo_tracks = (
    WaveFile(open("sounds/undersea.wav", "rb")),
    WaveFile(open("sounds/tequila.wav", "rb")),
    WaveFile(open("sounds/lion.wav", "rb")),
)

Adding your Own Wav Files

I found that even following the very specific guidelines for .wav file setup, some files would crash the Feather when triggered. If you're having this problem, head to this guide and re-crunch your files using Audacity. 

This is a teeny tiny speaker, so sound files with more high-end frequencies will sound a lot better than deep, booming sounds. Sound files that have a lot of (or even any) low frequencies do not sound good when played back over small speakers. Too much low end will cause a small speaker to distort.  To optimize your sound files for small speaker playback use a High Pass Filter (HPF) in your preferred audio editing software.  Rolling off low frequencies below 250 Hz is a good starting point.  

Audacity is great and simple audio editing app, and best of all, it’s free! 

This guide was first published on Jul 15, 2020. It was last updated on Jul 15, 2020.
This page (Advanced Code with CircuitPython) was last updated on Oct 24, 2020.