Sound Reactive Lights with CircuitPython: Adafruit Circuit Playground Express

Stephen just shared a new tutorial: "Sound Reactive Lights with CircuitPython: Adafruit Circuit Playground Express"



The Adafruit Circuit Playground Express comes equipped with an onboard microphone that is capable of sensing both amplitude and frequency. This tutorial will walk through a quick sketch using CircuitPython to turn the lights on the board into a mic a…

Read more

Ive been obsessing about this code for a project ive been working on. I have several questions. Is there anybody available to help me out?

1 Like

Hello i have been obsessing about this guide for a project im working on. I barely understand code, but thanks to you all and adafruit I’m learning so much. Im using this code in a cool wizard staff build. I was wondering if there is anyone available to help me out with a few questions regarding this code so I can alter it a little to suit my needs?

2 Likes

Hi Josh,

What questions did you have about the code?

2 Likes

Hi there. Wasnt too sure if I would hear back from anybody so i made a post on adafruit forums outlining what im trying to do. https://forums.adafruit.com/viewtopic.php?t=203187 I have built a very cool wizard staff and im trying to use this code for my 90 Neopixel strips inside it as well as my circuit playground express. Im not experienced enough with code to successfully alter the code to fit my needs. Any help eould be greatly appreciated

2 Likes

And here was my first post where someone helped me alter it to send the sound waves to my strip instead of the CPX. There is a youtube link of what the staff looks like https://forums.adafruit.com/viewtopic.php?t=203147

2 Likes

Hi Josh,

From the Adafruit forum:

  1. Is there a way to change the direction the lights move.
    Currently the lights on my strip move away from my C.P.X. when sensing sound, I need them to start at the
    end of the strip and move toward the C.P.X.

The easiest way I could imagine swapping the strip would be to physically wire the strip the other way around, otherwise inverting the pixel number using the following code where the ith pixel is updated.
Here’s some pseudo code to illustrate what youd want to do:
pixels[NUM_PIXELS - i] = some colour

  1. At the bottom of this code in the while True section it seems to mention that an option is to “light up the
    peak pixel and animate it slowly dropping”. This is a function that I would very much like to activate. How
    do I activate this function?

It ought to be on by default, nothing is commented out, would it be possible to get a video with the code running from the tutorial?

  1. PEAK_COLOR changes the color of the top NeoPixel, is there a way to change the color of the bottom NeoPixel?

The volume_colour function defines the colour of all of the pixels that are being changed,

def volume_color(volume):
    return 200, volume * (255 // NUM_PIXELS), 0

if you wanted to statically change the last pixel in the strip you could do so at the bottom of the code before the .showPixel function is run.

  1. I need to now run a basic color cycle animation on my 10 L.E.Ds onboard the C.P.X. My onboard L.E.Ds on the
    CPX are doing nothing now that I changed the output to A1 with Dan’s help. How do i run this code while
    sensing sound on the strip at the same time? I have tried many times to incorporate it into the code with
    no success. Here is my basic code for color cycle on the C.P.X.

You’ll be able to interweave the code such that both ‘pixel’ objects are defined at the start of the code and inside the loop you ‘redraw’ each of the led strips.

2 Likes

Hi thanks so much for your response! Im going to now absorb everything you have said and try it all out. And i will send a video of the code running as it is now. This is such a niche thing im trying to do that its so hard to find people that know.

1 Like

Here is a quick video showing the code running. Note that the CPX unfortunately doesnt run any lights because im outputting to A1. The changes in peak height during the video are due to my adjusting the tightness of the sphere at the top and that is by design. https://youtu.be/2hhYXPMXpQY

2 Likes

Hi Josh,

Cool staff! Have you managed to get Liam’s inversion code working?

1 Like

Hi james and liam. Yesterday I succesfully interweaved code to run the colorcycle animation on the CPX finally, i,m so proud! I have not been able to wrap my head around how to run the inversion code however. And i cant get it to “light up the peak pixel and animate it slowly dropping” like it says in the code. Any further advice and help would be very appreciated. Also for extra credit do you all know a way to make the strip show rainbow colors somehow instead of just the 2 options for color? I would love if as the strip animated sound it would show a rainbow, kinda like a rainbow comet animation. Anyways thanks again, if you all could insert that inversion code for me and re-paste the code i would be so thankful.

1 Like

Here is my updated code

import array
import math

import time
import audiobusio
import board
import neopixel

from adafruit_led_animation.animation.colorcycle import ColorCycle

# Exponential scaling factor.
# Should probably be in range -10 .. 10 to be reasonable.
CURVE = 2
SCALE_EXPONENT = math.pow(10, CURVE * -0.1)

PEAK_COLOR = (127, 0, 255)
NUM_PIXELS = 84


# Number of samples to read at once.
NUM_SAMPLES = 160

from adafruit_led_animation.color import (
    AMBER, #(255, 100, 0)
    AQUA, # (50, 255, 255)
    BLACK, #OFF (0, 0, 0)
    BLUE, # (0, 0, 255)
    CYAN, # (0, 255, 255)
    GOLD, # (255, 222, 30)
    GREEN, # (0, 255, 0)
    JADE, # (0, 255, 40)
    MAGENTA, #(255, 0, 20)
    OLD_LACE, # (253, 245, 230)
    ORANGE, # (255, 40, 0)
    PINK, # (242, 90, 255)
    PURPLE, # (180, 0, 255)
    RED, # (255, 0, 0)
    TEAL, # (0, 255, 120)
    WHITE, # (255, 255, 255)
    YELLOW, # (255, 150, 0)
    RAINBOW # a list of colors to cycle through
    # RAINBOW is RED, ORANGE, YELLOW, GREEN, BLUE, and PURPLE ((255, 0, 0), (255, 40, 0), (255, 150, 0), (0, 255, 0), (0, 0, 255), (180, 0, 255))
)

INDIGO = (63, 0, 255)
VIOLET = (127, 0, 255)

colors = [RED, MAGENTA, ORANGE, YELLOW, GREEN, JADE, BLUE, INDIGO, VIOLET, PURPLE]

pixels_pin = board.NEOPIXEL
pixels_num_of_lights = 10
pixels = neopixel.NeoPixel(pixels_pin, pixels_num_of_lights, brightness = 0.2, auto_write=True)

# Restrict value to be between floor and ceiling.


def constrain(value, floor, ceiling):
    return max(floor, min(value, ceiling))


# Scale input_value between output_min and output_max, exponentially.


def log_scale(input_value, input_min, input_max, output_min, output_max):
    normalized_input_value = (input_value - input_min) / \
                             (input_max - input_min)
    return output_min + \
        math.pow(normalized_input_value, SCALE_EXPONENT) \
        * (output_max - output_min)


# Remove DC bias before computing RMS.


def normalized_rms(values):
    minbuf = int(mean(values))
    samples_sum = sum(
        float(sample - minbuf) * (sample - minbuf)
        for sample in values
    )

    return math.sqrt(samples_sum / len(values))


def mean(values):
    return sum(values) / len(values)


def volume_color(volume):
    return 0, volume * (255 // NUM_PIXELS), 0



# Main program

colorcycle = ColorCycle(pixels, 0.5, colors=colors)

# Set up NeoPixels and turn them all off.
pixels = neopixel.NeoPixel(board.A1, NUM_PIXELS,
                           brightness=0.1, auto_write=False)




pixels.fill(0)
pixels.show()




"""
# For CircuitPython 2.x:
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA,
                       frequency=16000, bit_depth=16)
# For Circuitpython 3.0 and up, "frequency" is now called "sample_rate".
# Comment the lines above and uncomment the lines below.
"""
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA,
                       sample_rate=16000, bit_depth=16)

# Record an initial sample to calibrate. Assume it's quiet when we start.
samples = array.array('H', [0] * NUM_SAMPLES)
mic.record(samples, len(samples))
# Set lowest level to expect, plus a little.
input_floor = normalized_rms(samples) + 84
# OR: used a fixed floor
# input_floor = 50

# You might want to print the input_floor to help adjust other values.
# print(input_floor)

# Corresponds to sensitivity: lower means more pixels light up with lower sound
# Adjust this as you see fit.
input_ceiling = input_floor + 500
 
 
 
peak = 0
while True:
    colorcycle.animate()
    mic.record(samples, len(samples))
    magnitude = normalized_rms(samples)
    # You might want to print this to see the values.
    # print(magnitude)

    # Compute scaled logarithmic reading in the range 0 to NUM_PIXELS
    c = log_scale(constrain(magnitude, input_floor, input_ceiling),
                  input_floor, input_ceiling, 0, NUM_PIXELS)

    # Light up pixels that are below the scaled and interpolated magnitude.
    pixels.fill(0)
    for i in range(NUM_PIXELS):
        if i < c:
            pixels[i] = volume_color(i)
        # Light up the peak pixel and animate it slowly dropping.
        if c >= peak:
            peak = min(c, NUM_PIXELS - 1)
        elif peak > 0:
            peak = peak - 1
        if peak > 0:
            pixels[int(peak)] = PEAK_COLOR
    pixels.show()# Write your code here :-)
1 Like

Hi Josh,

Nice crack at the code!

I’ve taken @Liam120347 's suggestions from above and quickly implemented them below (untested):

I have not been able to wrap my head around how to run the inversion code however.

pixels[NUM_PIXELS - i] = some colour - note the two replacements where the colours are written to the pixels,

And i cant get it to “light up the peak pixel and animate it slowly dropping” like it says in the code.

It’s likely that it moves too quick to be noticeable, I’ve changed the colour to white so that its more noticeable, to get it to drop sloooooooowly you might want to add a non-blocking delay (Here’s the docs page walking through each of the functions)

import array
import math

import time
import audiobusio
import board
import neopixel

from adafruit_led_animation.animation.colorcycle import ColorCycle

# Exponential scaling factor.
# Should probably be in range -10 .. 10 to be reasonable.
CURVE = 2
SCALE_EXPONENT = math.pow(10, CURVE * -0.1)

PEAK_COLOR = (255, 255, 255)
NUM_PIXELS = 84


# Number of samples to read at once.
NUM_SAMPLES = 160

from adafruit_led_animation.color import (
    AMBER, #(255, 100, 0)
    AQUA, # (50, 255, 255)
    BLACK, #OFF (0, 0, 0)
    BLUE, # (0, 0, 255)
    CYAN, # (0, 255, 255)
    GOLD, # (255, 222, 30)
    GREEN, # (0, 255, 0)
    JADE, # (0, 255, 40)
    MAGENTA, #(255, 0, 20)
    OLD_LACE, # (253, 245, 230)
    ORANGE, # (255, 40, 0)
    PINK, # (242, 90, 255)
    PURPLE, # (180, 0, 255)
    RED, # (255, 0, 0)
    TEAL, # (0, 255, 120)
    WHITE, # (255, 255, 255)
    YELLOW, # (255, 150, 0)
    RAINBOW # a list of colors to cycle through
    # RAINBOW is RED, ORANGE, YELLOW, GREEN, BLUE, and PURPLE ((255, 0, 0), (255, 40, 0), (255, 150, 0), (0, 255, 0), (0, 0, 255), (180, 0, 255))
)

INDIGO = (63, 0, 255)
VIOLET = (127, 0, 255)

colors = [RED, MAGENTA, ORANGE, YELLOW, GREEN, JADE, BLUE, INDIGO, VIOLET, PURPLE]

pixels_pin = board.NEOPIXEL
pixels_num_of_lights = 10
pixels = neopixel.NeoPixel(pixels_pin, pixels_num_of_lights, brightness = 0.2, auto_write=True)

# Restrict value to be between floor and ceiling.


def constrain(value, floor, ceiling):
    return max(floor, min(value, ceiling))


# Scale input_value between output_min and output_max, exponentially.


def log_scale(input_value, input_min, input_max, output_min, output_max):
    normalized_input_value = (input_value - input_min) / \
                             (input_max - input_min)
    return output_min + \
        math.pow(normalized_input_value, SCALE_EXPONENT) \
        * (output_max - output_min)


# Remove DC bias before computing RMS.


def normalized_rms(values):
    minbuf = int(mean(values))
    samples_sum = sum(
        float(sample - minbuf) * (sample - minbuf)
        for sample in values
    )

    return math.sqrt(samples_sum / len(values))


def mean(values):
    return sum(values) / len(values)


def volume_color(volume):
    return 0, volume * (255 // NUM_PIXELS), 0



# Main program

colorcycle = ColorCycle(pixels, 0.5, colors=colors)

# Set up NeoPixels and turn them all off.
pixels = neopixel.NeoPixel(board.A1, NUM_PIXELS,
                           brightness=0.1, auto_write=False)




pixels.fill(0)
pixels.show()




"""
# For CircuitPython 2.x:
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA,
                       frequency=16000, bit_depth=16)
# For Circuitpython 3.0 and up, "frequency" is now called "sample_rate".
# Comment the lines above and uncomment the lines below.
"""
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA,
                       sample_rate=16000, bit_depth=16)

# Record an initial sample to calibrate. Assume it's quiet when we start.
samples = array.array('H', [0] * NUM_SAMPLES)
mic.record(samples, len(samples))
# Set lowest level to expect, plus a little.
input_floor = normalized_rms(samples) + 84
# OR: used a fixed floor
# input_floor = 50

# You might want to print the input_floor to help adjust other values.
# print(input_floor)

# Corresponds to sensitivity: lower means more pixels light up with lower sound
# Adjust this as you see fit.
input_ceiling = input_floor + 500
 
 
 
peak = 0
while True:
    colorcycle.animate()
    mic.record(samples, len(samples))
    magnitude = normalized_rms(samples)
    # You might want to print this to see the values.
    # print(magnitude)

    # Compute scaled logarithmic reading in the range 0 to NUM_PIXELS
    c = log_scale(constrain(magnitude, input_floor, input_ceiling),
                  input_floor, input_ceiling, 0, NUM_PIXELS)

    # Light up pixels that are below the scaled and interpolated magnitude.
    pixels.fill(0)
    for i in range(NUM_PIXELS):
        if i < c:
            pixels[NUM_PIXELS - i] = volume_color(i)
        # Light up the peak pixel and animate it slowly dropping.
        if c >= peak:
            peak = min(c, NUM_PIXELS - 1)
        elif peak > 0:
            peak = peak - 1
        if peak > 0:
            pixels[NUM_PIXELS - int(peak)] = PEAK_COLOR
    pixels.show()# Write your code here :-)

I would love if as the strip animated sound it would show a rainbow, kinda like a rainbow comet animation

This sounds siiiick! I’d check out any libraries that can convert a HSV value to RGB - then you can plug the ‘value’ into the respective pixel.
Liam

2 Likes