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.
# SPDX-FileCopyrightText: 2020 Jeff Epler for Adafruit Industries # # SPDX-License-Identifier: MIT 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()
Text editor powered by tinymce.