Skip to content

Instantly share code, notes, and snippets.

@jepler
Last active September 19, 2021 23:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jepler/d1e5623931b285c7d23cb511a9a9de09 to your computer and use it in GitHub Desktop.
Save jepler/d1e5623931b285c7d23cb511a9a9de09 to your computer and use it in GitHub Desktop.
"1D Wave" demo for circuitpython & neopixel
# CircuitPython demo - NeoPixel
import time
import board
import neopixel
import random
# Customize your neopixel configuration here...
pixel_pin = board.A1
num_pixels = const(96)
pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.1, auto_write=False)
# The function "step" computes how the color data changes from one instant ot
# the next. It is an implementation of the 1D wave motion differential
# equation, based on
# https://stackoverflow.com/questions/10081942/python-writing-a-program-to-simulate-1d-wave-motion-along-a-string
#
# The mathematics behind it are pretty complicated, and I can't say I
# understand them myself, I just like how it makes pretty colors.
#
# 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!
#
# u and um hold the state of the wave "now" and "previously"; when done up
# holds the new state of the wave. While tinkering, you wouldn't usually
# change these. On the other hand, try tinkering with all of the following:
#
# n is the number of elements, which is redundant (it's just len(u)), also
# don't change this
#
# 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 LEDs are, "dt" is how far apart in time the
# calculated instants are, and "c" is the maximum speed of a wave.
#
# The assignments to up[0] and up[n-1] set the "endpoint conditions" of the
# wave. By making them unequal, the endpoints would have different colors
# even if the wave disappeared.
def step(up, u, um, f, n, dx, dt, c):
dt2 = dt*dt
C2 = (c*dt/dx)**2
for i in range(1, n-1):
up[i] = -um[i] + 2*u[i] + C2 * (u[i-1] - 2*u[i] + u[i+1]) + dt2 * f[i]
up[0] = 0
up[n-1] = .04
return up
# You could also change wheel() so that it gives a 256 element palette of your choice.
def wheel(pos):
# Input a value 0 to 255 to get a color value.
# The colours are a transition r - g - b - back to r.
if pos < 0 or pos > 255:
return (0, 0, 0)
if pos < 85:
return (255 - pos * 3, pos * 3, 0)
if pos < 170:
pos -= 85
return (0, 255 - pos * 3, pos * 3)
pos -= 170
return (pos * 3, 0, 255 - pos * 3)
def main():
# This precomputes the color palette for maximum speed
w = [wheel(i) for i in range(256)]
w = [(wi[0] << 16) | (wi[1] << 8) | wi[2] for wi in w]
# This sets up the initial wave as a smooth gradient
u = [i * .03 / num_pixels for i in range(num_pixels)]
um = list(u)
up = [0.] * num_pixels
f = [0.] * num_pixels
f[num_pixels-2] = 0
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 < .15:
ii = random.randrange(1, num_pixels-1)
# increase 2 to make bigger waves
f[ii] = (random.random() - .5) * 2
# Calculate 4 times between each update of the pixels
for i in range(1):
# Here's where to change dx, dt, and c
# try .2, .02, 2 for relaxed
# try 1., .7, .2 for very busy / almost random
step(up, u, um, f, num_pixels, .2, .07, 2)
u, um, up = up, u, um
for i in range(num_pixels):
# Calculate an average pixel value over the last 3 time instants
# you could just use v = u[i] instead
v = u[i] + um[i] + up[i]
#v = u[i]
# Scale up by an empirical value, rotate by th, and look up the color
pixels[i] = w[(round(v * 3000) + th) % 256]
# Take away 1% of the energy of the waves so they don't get out of control
u[i] *= .99
um[i] *= .99
# incrementing th causes the wheel to slowly cycle even if nothing else is happening
th = (th + 1) % 256
pixels.show()
# Clear out the old random value, if any
f[ii] = 0
# and get a new random value
r = random.random()
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment