Last active
September 19, 2021 23:41
-
-
Save jepler/d1e5623931b285c7d23cb511a9a9de09 to your computer and use it in GitHub Desktop.
"1D Wave" demo for circuitpython & neopixel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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