Create an ever varying rainbow pattern on NeoPixel LED strips using the equation for "1D wave simulation".

It is possible to create numeric simulations of waves which resemble ocean waves or ripples on a pond.  Here is an example as a web demo. In this case, instead of the simulation values representing heights of a liquid wave, they represent colors on the color wheel, which are shown on a neon-like NeoPixel strip.  By changing a few parameters in the Python source code, you can create a relaxed experience or an almost stroboscopic effect

This demo, heavily adapted from an answer on Stack Overflow, is designed for a neon-like NeoPixel strip and I ran it using an Adafruit Feather nRF52840.  However, you can adapt it to a wide range of CircuitPython devices and NeoPixel strip types.

Wire the neopixels to the feather, as shown below.  If you need to use a different pin than D5, or the number of neopixels you have is not 96, you'll  need to change some things in the code.

You'll need to manually install the necessary libraries from the bundle:

  • neopixel.mpy

Next, copy the code below to code.py on the CIRCUITPY drive.

Many of the parameters can be tinkered with to give different effects. Some are pretty, some are boring, and a few will even cause errors because they give a result of infinity!

The elements of f that are nonzero indicate places where energy is added to the wave. The main function randomly assigns one element of f to be nonzero, every once in awhile.

dx, dt, and c control how quickly the wave reacts, but in slightly different ways. dx is how far apart the sampled points are, dt is how far apart in time the calculated instants are, and c is the maximum speed of a wave in distance per time.  These are all in arbitrary units; they don't have anything to do with the physical distance between NeoPixels or the time between updates of the strip, which always goes as fast as possible.

The number 0.99 which is used as a multiplier of u and um within the main loop is a damping factor. 0.99 damps a very small amount.  Values closer to 0 dampen the wave more.

import random

import board
import neopixel
from rainbowio import colorwheel
from ulab import numpy as np

# Customize your neopixel configuration here...
pixel_pin = board.D5
num_pixels = 96
pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.1,
                           auto_write=False, pixel_order=neopixel.RGB)

ddt = np.array([1.,-2.,1.])
def step(u, um, f, n, dx, dt, c):
    dt2 = dt*dt
    C2 = (c*dt/dx)**2
    deriv = np.convolve(u, ddt)[1:-1] * C2
    up = -um + u * 2 + deriv + f * dt2
    up[0] = 0
    up[n-1] = 0

    return up

def main():
    # This precomputes the color palette for maximum speed
    # You could change it to compute the color palette of your choice
    w = [colorwheel(i) for i in range(256)]

    # This sets up the initial wave as a smooth gradient
    u = np.zeros(num_pixels)
    um = np.zeros(num_pixels)
    f = np.zeros(num_pixels)

    slope = np.linspace(0, 256, num=num_pixels)
    th = 1

    # the first time is always random (is that a contradiction?)
    r = 0

    while True:

        # Some of the time, add a random new wave to the mix
        # increase .15 to add waves more often
        # decrease it to add waves less often
        if r < .01:
            ii = random.randrange(1, num_pixels-1)
            # increase 2 to make bigger waves
            f[ii] = (random.random() - .5) * 2

        # Here's where to change dx, dt, and c
        # try .2, .02, 2 for relaxed
        # try 1., .7, .2 for very busy / almost random
        u, um = step(u, um, f, num_pixels, .1, .02, 1), u

        v = u * 200000 + slope + th
        for i, vi in enumerate(v):
            # Scale up by an empirical value, rotate by th, and look up the color
            pixels[i] = w[round(vi) % 256]

        # Take away a portion of the energy of the waves so they don't get out
        # of control
        u = u * .99

        # incrementing th causes the colorwheel to slowly cycle even if nothing else is happening
        th = (th + .25) % 256
        pixels.show()

        # Clear out the old random value, if any
        f[ii] = 0

        # and get a new random value
        r = random.random()

main()
You love NeoPixels, and you love silicone diffusion? Peep this Flexible Silicone Neon-like Skinny NeoPixel LED Strip! OK it's a bit of a mouthful, but check...
$34.95
In Stock
The Adafruit Feather nRF52840 Express is the new Feather family member with Bluetooth Low Energy and native USB support featuring the nRF52840!  It's...
$24.95
In Stock

This guide was first published on Mar 06, 2020. It was last updated on Mar 06, 2020.

This page (Example: NeoPixel Wave Simulation) was last updated on Oct 15, 2021.

Text editor powered by tinymce.