Compare commits
1 Commits
55a97ac51c
...
94266d5a7c
| Author | SHA1 | Date | |
|---|---|---|---|
| 94266d5a7c |
@@ -26,13 +26,13 @@ class Aurora:
|
||||
c = self.driver.apply_brightness(colors[idx], preset.b)
|
||||
w = 255 - abs(128 - ((i * 8 + phase) & 255)) * 2
|
||||
w = max(0, min(255, w + shimmer))
|
||||
self.driver.n[i] = (
|
||||
self.driver.n[self.driver.led_i(preset, i)] = (
|
||||
(c[0] * w) // 255,
|
||||
(c[1] * w) // 255,
|
||||
(c[2] * w) // 255,
|
||||
)
|
||||
self.driver.n.write()
|
||||
phase = (phase + 1) & 255
|
||||
phase = (phase + self.driver.signed(preset, 1)) & 255
|
||||
self.driver.step = phase
|
||||
last = utime.ticks_add(last, d)
|
||||
if not preset.a:
|
||||
@@ -76,9 +76,9 @@ class Aurora:
|
||||
peak = lerp3(colors[fi], colors[fi + 1], frac)
|
||||
peak = self.driver.apply_brightness(peak, preset.b)
|
||||
mixf = min(255, int(w * contrast * 2) >> 1)
|
||||
self.driver.n[i] = lerp3(bg, peak, mixf)
|
||||
self.driver.n[self.driver.led_i(preset, i)] = lerp3(bg, peak, mixf)
|
||||
self.driver.n.write()
|
||||
phase = (phase + drift) % 256
|
||||
phase = (phase + self.driver.signed(preset, drift)) % 256
|
||||
last = utime.ticks_add(last, d)
|
||||
if not preset.a:
|
||||
yield
|
||||
|
||||
@@ -46,12 +46,14 @@ class Blizzard:
|
||||
for pos, ci, wj in flakes:
|
||||
p = pos
|
||||
lateral = wind + (wj if wj else 0)
|
||||
p -= speed
|
||||
p += lateral
|
||||
p -= self.driver.signed(preset, speed)
|
||||
p += self.driver.signed(preset, lateral)
|
||||
if p < -2 or p >= nled + 2:
|
||||
continue
|
||||
pi = max(0, min(nled - 1, int(p)))
|
||||
self.driver.n[pi] = self.driver.apply_brightness(colors[ci], preset.b)
|
||||
self.driver.n[self.driver.led_i(preset, pi)] = self.driver.apply_brightness(
|
||||
colors[ci], preset.b
|
||||
)
|
||||
nf.append([p, ci, wj])
|
||||
flakes = nf
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class Chase:
|
||||
def _run_marquee(self, preset, colors):
|
||||
on_len = max(1, int(preset.n1) if int(preset.n1) > 0 else 3)
|
||||
off_len = max(1, int(preset.n2) if int(preset.n2) > 0 else 2)
|
||||
step = max(1, int(preset.n3) if int(preset.n3) > 0 else 1)
|
||||
step = max(1, abs(self.driver.signed(preset, int(preset.n3) if int(preset.n3) > 0 else 1)))
|
||||
phase = self.driver.step % (on_len + off_len)
|
||||
last = utime.ticks_ms()
|
||||
while True:
|
||||
@@ -23,9 +23,9 @@ class Chase:
|
||||
bg_color = self.driver.apply_brightness(preset.background_or(colors), preset.b)
|
||||
for i in range(self.driver.num_leds):
|
||||
m = (i + phase) % (on_len + off_len)
|
||||
self.driver.n[i] = c if m < on_len else bg_color
|
||||
self.driver.n[self.driver.led_i(preset, i)] = c if m < on_len else bg_color
|
||||
self.driver.n.write()
|
||||
phase = (phase + step) % (on_len + off_len)
|
||||
phase = (phase + self.driver.signed(preset, step)) % (on_len + off_len)
|
||||
self.driver.step = phase
|
||||
last = utime.ticks_add(last, d)
|
||||
if not preset.a:
|
||||
@@ -66,8 +66,8 @@ class Chase:
|
||||
|
||||
n1 = max(1, int(preset.n1)) # LEDs of color 0
|
||||
n2 = max(1, int(preset.n2)) # LEDs of color 1
|
||||
n3 = int(preset.n3) # Step movement on even steps (can be negative)
|
||||
n4 = int(preset.n4) # Step movement on odd steps (can be negative)
|
||||
n3 = self.driver.signed(preset, int(preset.n3)) # Step movement on even steps
|
||||
n4 = self.driver.signed(preset, int(preset.n4)) # Step movement on odd steps
|
||||
|
||||
segment_length = n1 + n2
|
||||
|
||||
@@ -101,9 +101,9 @@ class Chase:
|
||||
|
||||
# Determine which color based on position in segment
|
||||
if relative_pos < n1:
|
||||
self.driver.n[i] = color0
|
||||
self.driver.n[self.driver.led_i(preset, i)] = color0
|
||||
else:
|
||||
self.driver.n[i] = color1
|
||||
self.driver.n[self.driver.led_i(preset, i)] = color1
|
||||
|
||||
self.driver.n.write()
|
||||
print("[chase] step", step_count)
|
||||
@@ -147,9 +147,9 @@ class Chase:
|
||||
|
||||
# Determine which color based on position in segment
|
||||
if relative_pos < n1:
|
||||
self.driver.n[i] = color0
|
||||
self.driver.n[self.driver.led_i(preset, i)] = color0
|
||||
else:
|
||||
self.driver.n[i] = color1
|
||||
self.driver.n[self.driver.led_i(preset, i)] = color1
|
||||
|
||||
self.driver.n.write()
|
||||
print("[chase] step", step_count)
|
||||
|
||||
@@ -18,7 +18,7 @@ class ColourCycle:
|
||||
pos -= 170
|
||||
return (0, pos * 3, 255 - pos * 3)
|
||||
|
||||
def _render_gradient(self, colors, phase, brightness):
|
||||
def _render_gradient(self, preset, colors, phase, brightness):
|
||||
num_leds = self.driver.num_leds
|
||||
color_count = len(colors)
|
||||
if num_leds <= 0 or color_count <= 0:
|
||||
@@ -40,14 +40,16 @@ class ColourCycle:
|
||||
c1[1] + ((c2[1] - c1[1]) * frac) // 256,
|
||||
c1[2] + ((c2[2] - c1[2]) * frac) // 256,
|
||||
)
|
||||
self.driver.n[i] = self.driver.apply_brightness(blended, brightness)
|
||||
self.driver.n[self.driver.led_i(preset, i)] = self.driver.apply_brightness(
|
||||
blended, brightness
|
||||
)
|
||||
self.driver.n.write()
|
||||
|
||||
def _render_rainbow(self, phase, brightness):
|
||||
def _render_rainbow(self, preset, phase, brightness):
|
||||
num_leds = self.driver.num_leds
|
||||
for i in range(num_leds):
|
||||
rc_index = (i * 256 // max(1, num_leds)) + phase
|
||||
self.driver.n[i] = self.driver.apply_brightness(
|
||||
self.driver.n[self.driver.led_i(preset, i)] = self.driver.apply_brightness(
|
||||
self._wheel(rc_index & 255), brightness
|
||||
)
|
||||
self.driver.n.write()
|
||||
@@ -64,8 +66,8 @@ class ColourCycle:
|
||||
|
||||
if mode == 1:
|
||||
if not preset.a:
|
||||
self._render_rainbow(phase, preset.b)
|
||||
self.driver.step = (phase + step_amount) % 256
|
||||
self._render_rainbow(preset, phase, preset.b)
|
||||
self.driver.step = (phase + self.driver.signed(preset, step_amount)) % 256
|
||||
yield
|
||||
return
|
||||
last_update = utime.ticks_ms()
|
||||
@@ -73,16 +75,16 @@ class ColourCycle:
|
||||
delay_ms = max(1, int(preset.d))
|
||||
now = utime.ticks_ms()
|
||||
if utime.ticks_diff(now, last_update) >= delay_ms:
|
||||
self._render_rainbow(phase, preset.b)
|
||||
phase = (phase + step_amount) % 256
|
||||
self._render_rainbow(preset, phase, preset.b)
|
||||
phase = (phase + self.driver.signed(preset, step_amount)) % 256
|
||||
self.driver.step = phase
|
||||
last_update = utime.ticks_add(last_update, delay_ms)
|
||||
yield
|
||||
|
||||
colors = preset.c if preset.c else [(255, 0, 0), (0, 0, 255)]
|
||||
if not preset.a:
|
||||
self._render_gradient(colors, phase, preset.b)
|
||||
self.driver.step = (phase + step_amount) % 256
|
||||
self._render_gradient(preset, colors, phase, preset.b)
|
||||
self.driver.step = (phase + self.driver.signed(preset, step_amount)) % 256
|
||||
yield
|
||||
return
|
||||
|
||||
@@ -91,8 +93,8 @@ class ColourCycle:
|
||||
delay_ms = max(1, int(preset.d))
|
||||
now = utime.ticks_ms()
|
||||
if utime.ticks_diff(now, last_update) >= delay_ms:
|
||||
self._render_gradient(colors, phase, preset.b)
|
||||
phase = (phase + step_amount) % 256
|
||||
self._render_gradient(preset, colors, phase, preset.b)
|
||||
phase = (phase + self.driver.signed(preset, step_amount)) % 256
|
||||
self.driver.step = phase
|
||||
last_update = utime.ticks_add(last_update, delay_ms)
|
||||
yield
|
||||
|
||||
@@ -44,7 +44,7 @@ class Icicles:
|
||||
if idx >= nled:
|
||||
break
|
||||
br = ((k + 1) * 255) // max(1, ic_len)
|
||||
self.driver.n[idx] = (
|
||||
self.driver.n[self.driver.led_i(preset, idx)] = (
|
||||
(tip[0] * br + bg[0] * (255 - br)) // 255,
|
||||
(tip[1] * br + bg[1] * (255 - br)) // 255,
|
||||
(tip[2] * br + bg[2] * (255 - br)) // 255,
|
||||
@@ -52,7 +52,7 @@ class Icicles:
|
||||
aidx += 1
|
||||
|
||||
self.driver.n.write()
|
||||
phase = (phase + phase_step) % span
|
||||
phase = (phase + self.driver.signed(preset, phase_step)) % span
|
||||
last = utime.ticks_add(last, d_ms)
|
||||
|
||||
if not preset.a:
|
||||
|
||||
@@ -30,9 +30,9 @@ class Meteor:
|
||||
base = colors[color_index % len(colors)]
|
||||
lit = self.driver.apply_brightness(base, preset.b)
|
||||
if 0 <= head < self.driver.num_leds:
|
||||
self.driver.n[head] = lit
|
||||
self.driver.n[self.driver.led_i(preset, head)] = lit
|
||||
self.driver.n.write()
|
||||
head += direction * speed
|
||||
head += self.driver.signed(preset, direction * speed)
|
||||
if head >= self.driver.num_leds + tail_len:
|
||||
head = self.driver.num_leds - 1
|
||||
direction = -1
|
||||
@@ -62,14 +62,22 @@ class Meteor:
|
||||
i1 = p1 - t
|
||||
if 0 <= i1 < self.driver.num_leds:
|
||||
s = (255 * (tail - t)) // max(1, tail)
|
||||
self.driver.n[i1] = ((c1[0] * s) // 255, (c1[1] * s) // 255, (c1[2] * s) // 255)
|
||||
self.driver.n[self.driver.led_i(preset, i1)] = (
|
||||
(c1[0] * s) // 255,
|
||||
(c1[1] * s) // 255,
|
||||
(c1[2] * s) // 255,
|
||||
)
|
||||
i2 = p2 + t
|
||||
if 0 <= i2 < self.driver.num_leds:
|
||||
s = (255 * (tail - t)) // max(1, tail)
|
||||
self.driver.n[i2] = ((c2[0] * s) // 255, (c2[1] * s) // 255, (c2[2] * s) // 255)
|
||||
self.driver.n[self.driver.led_i(preset, i2)] = (
|
||||
(c2[0] * s) // 255,
|
||||
(c2[1] * s) // 255,
|
||||
(c2[2] * s) // 255,
|
||||
)
|
||||
self.driver.n.write()
|
||||
p1 += speed
|
||||
p2 -= speed
|
||||
p1 += self.driver.signed(preset, speed)
|
||||
p2 -= self.driver.signed(preset, speed)
|
||||
if p1 - tail > self.driver.num_leds and p2 + tail < 0:
|
||||
p1 = 0
|
||||
p2 = self.driver.num_leds - 1 - gap
|
||||
@@ -89,10 +97,10 @@ class Meteor:
|
||||
if dist < 0:
|
||||
dist = -dist
|
||||
if dist > width:
|
||||
self.driver.n[i] = bg_color
|
||||
self.driver.n[self.driver.led_i(preset, i)] = bg_color
|
||||
else:
|
||||
scale = ((width - dist) * 255) // max(1, width)
|
||||
self.driver.n[i] = (
|
||||
self.driver.n[self.driver.led_i(preset, i)] = (
|
||||
(base[0] * scale) // 255,
|
||||
(base[1] * scale) // 255,
|
||||
(base[2] * scale) // 255,
|
||||
@@ -101,7 +109,7 @@ class Meteor:
|
||||
if pause_frames > 0:
|
||||
pause_frames -= 1
|
||||
else:
|
||||
center += direction
|
||||
center += self.driver.signed(preset, direction)
|
||||
if center >= self.driver.num_leds - 1:
|
||||
center = self.driver.num_leds - 1
|
||||
direction = -1
|
||||
@@ -121,7 +129,11 @@ class Meteor:
|
||||
|
||||
if mode == 1:
|
||||
gap = max(0, int(preset.n3))
|
||||
p1, p2 = 0, self.driver.num_leds - 1 - gap
|
||||
nled = self.driver.num_leds
|
||||
if self.driver.is_reversed(preset):
|
||||
p1, p2 = nled - 1, gap
|
||||
else:
|
||||
p1, p2 = 0, nled - 1 - gap
|
||||
last = utime.ticks_ms()
|
||||
while True:
|
||||
p1, p2, last, stepped = self._run_comet_dual(preset, colors, p1, p2, last)
|
||||
@@ -131,7 +143,11 @@ class Meteor:
|
||||
yield
|
||||
|
||||
if mode == 2:
|
||||
color_index, center, direction, pause_frames = 0, 0, 1, 0
|
||||
nled = self.driver.num_leds
|
||||
if self.driver.is_reversed(preset):
|
||||
color_index, center, direction, pause_frames = 0, max(0, nled - 1), -1, 0
|
||||
else:
|
||||
color_index, center, direction, pause_frames = 0, 0, 1, 0
|
||||
last_update = utime.ticks_ms()
|
||||
while True:
|
||||
color_index, center, direction, pause_frames, last_update, stepped = (
|
||||
@@ -144,7 +160,11 @@ class Meteor:
|
||||
return
|
||||
yield
|
||||
|
||||
color_index, head, direction = 0, 0, 1
|
||||
nled = self.driver.num_leds
|
||||
if self.driver.is_reversed(preset):
|
||||
color_index, head, direction = 0, max(0, nled - 1), -1
|
||||
else:
|
||||
color_index, head, direction = 0, 0, 1
|
||||
last_update = utime.ticks_ms()
|
||||
while True:
|
||||
color_index, head, direction, last_update, stepped = self._run_meteor(
|
||||
|
||||
@@ -12,7 +12,7 @@ class Particles:
|
||||
|
||||
def _run_snowfall(self, preset, colors, flakes, last):
|
||||
density = max(1, int(preset.n1) if int(preset.n1) > 0 else 20)
|
||||
speed = max(1, int(preset.n2) if int(preset.n2) > 0 else 1)
|
||||
speed = max(1, abs(self.driver.signed(preset, int(preset.n2) if int(preset.n2) > 0 else 1)))
|
||||
d = max(1, int(preset.d))
|
||||
now = utime.ticks_ms()
|
||||
if utime.ticks_diff(now, last) < d:
|
||||
@@ -25,8 +25,10 @@ class Particles:
|
||||
nf = []
|
||||
for pos, ci in flakes:
|
||||
if 0 <= pos < self.driver.num_leds:
|
||||
self.driver.n[pos] = self.driver.apply_brightness(colors[ci], preset.b)
|
||||
pos -= speed
|
||||
self.driver.n[self.driver.led_i(preset, pos)] = self.driver.apply_brightness(
|
||||
colors[ci], preset.b
|
||||
)
|
||||
pos -= self.driver.signed(preset, speed)
|
||||
if pos >= -1:
|
||||
nf.append([pos, ci])
|
||||
self.driver.n.write()
|
||||
@@ -34,7 +36,7 @@ class Particles:
|
||||
|
||||
def _run_starfall(self, preset, colors, stars, last):
|
||||
rate = max(1, min(255, int(preset.n1) if int(preset.n1) > 0 else 14))
|
||||
speed = max(1, int(preset.n2) if int(preset.n2) > 0 else 2)
|
||||
speed = max(1, abs(self.driver.signed(preset, int(preset.n2) if int(preset.n2) > 0 else 2)))
|
||||
tail = max(2, int(preset.n3) if int(preset.n3) > 0 else 10)
|
||||
max_stars = 4
|
||||
d = max(1, int(preset.d))
|
||||
@@ -65,13 +67,14 @@ class Particles:
|
||||
(base[2] * fade) // 255,
|
||||
)
|
||||
lit = self.driver.apply_brightness(lit, preset.b)
|
||||
o = self.driver.n[idx]
|
||||
self.driver.n[idx] = (
|
||||
pix = self.driver.led_i(preset, idx)
|
||||
o = self.driver.n[pix]
|
||||
self.driver.n[pix] = (
|
||||
max(o[0], lit[0]),
|
||||
max(o[1], lit[1]),
|
||||
max(o[2], lit[2]),
|
||||
)
|
||||
h -= speed
|
||||
h -= self.driver.signed(preset, speed)
|
||||
if h >= -tail:
|
||||
s["h"] = h
|
||||
ns.append(s)
|
||||
|
||||
19
src/patterns/pattern_direction.py
Normal file
19
src/patterns/pattern_direction.py
Normal file
@@ -0,0 +1,19 @@
|
||||
"""Strip install direction: n5 bit 0 reverses along-strip motion (upside-down wiring)."""
|
||||
|
||||
|
||||
def is_reversed(preset):
|
||||
return bool(int(getattr(preset, "n5", 0) or 0) & 1)
|
||||
|
||||
|
||||
def led_i(driver, preset, logical_index):
|
||||
"""Map a logical strip index (0 = pattern start) to a physical pixel index."""
|
||||
n = int(driver.num_leds)
|
||||
i = int(logical_index)
|
||||
if 0 <= i < n and is_reversed(preset):
|
||||
return n - 1 - i
|
||||
return i
|
||||
|
||||
|
||||
def signed(preset, value):
|
||||
v = int(value)
|
||||
return -v if is_reversed(preset) else v
|
||||
@@ -13,13 +13,8 @@ class Pulse:
|
||||
bg_base = preset.background_or(colors)
|
||||
self.driver.fill(self.driver.apply_brightness(bg_base, preset.b))
|
||||
|
||||
manual = not preset.a
|
||||
color_index = self.driver.step % max(1, len(colors))
|
||||
if not preset.a:
|
||||
# Manual / beat trigger: each select restarts this generator and resets
|
||||
# cycle_start below. Advancing step here makes each beat the next colour
|
||||
# without requiring a full wall-clock cycle between beats.
|
||||
nclr = max(1, len(colors))
|
||||
self.driver.step = (color_index + 1) % nclr
|
||||
cycle_start = utime.ticks_ms()
|
||||
|
||||
# State machine based pulse using a single generator loop
|
||||
@@ -29,7 +24,7 @@ class Pulse:
|
||||
attack_ms = max(0, int(preset.n1)) # Attack time in ms
|
||||
hold_ms = max(0, int(preset.n2)) # Hold time in ms
|
||||
decay_ms = max(0, int(preset.n3)) # Decay time in ms
|
||||
delay_ms = max(0, int(preset.d))
|
||||
delay_ms = 0 if manual else max(0, int(preset.d))
|
||||
|
||||
total_ms = attack_ms + hold_ms + decay_ms + delay_ms
|
||||
if total_ms <= 0:
|
||||
@@ -58,12 +53,13 @@ class Pulse:
|
||||
# Delay phase: LEDs off between pulses
|
||||
self.driver.fill(bg_color)
|
||||
else:
|
||||
# End of cycle: auto advances colour and loops; manual already
|
||||
# advanced step at run start for the next beat.
|
||||
if not preset.a:
|
||||
break
|
||||
color_index = (color_index + 1) % max(1, len(colors))
|
||||
# End of cycle: advance colour for the next run, then loop or stop.
|
||||
nclr = max(1, len(colors))
|
||||
color_index = (color_index + 1) % nclr
|
||||
self.driver.step = color_index
|
||||
if manual:
|
||||
self.driver.fill(bg_color)
|
||||
break
|
||||
cycle_start = now
|
||||
yield
|
||||
continue
|
||||
|
||||
@@ -32,6 +32,18 @@ class Preset:
|
||||
int_fields = {"d", "b", "n1", "n2", "n3", "n4", "n5", "n6"}
|
||||
allowed_fields = {"p", "c", "d", "b", "a", "bg", "n1", "n2", "n3", "n4", "n5", "n6"}
|
||||
for key, value in data.items():
|
||||
if key == "reverse":
|
||||
try:
|
||||
if isinstance(value, bool):
|
||||
self.n5 = 1 if value else 0
|
||||
elif isinstance(value, (int, float)):
|
||||
self.n5 = 1 if int(value) else 0
|
||||
elif isinstance(value, str):
|
||||
lowered = value.lower()
|
||||
self.n5 = 1 if lowered in ("true", "1", "yes", "on") else 0
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
continue
|
||||
key = aliases.get(key, key)
|
||||
if key not in allowed_fields:
|
||||
continue
|
||||
|
||||
@@ -198,7 +198,7 @@ class Presets:
|
||||
if (
|
||||
preset_name == self.selected
|
||||
and not preset.a
|
||||
and preset.p == "chase"
|
||||
and preset.p in ("chase", "pulse")
|
||||
and self.generator is not None
|
||||
):
|
||||
while self.generator is not None:
|
||||
@@ -222,6 +222,21 @@ class Presets:
|
||||
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
|
||||
self.num_leds = num_leds
|
||||
|
||||
def is_reversed(self, preset):
|
||||
from patterns.pattern_direction import is_reversed as _is_reversed
|
||||
|
||||
return _is_reversed(preset)
|
||||
|
||||
def led_i(self, preset, logical_index):
|
||||
from patterns.pattern_direction import led_i as _led_i
|
||||
|
||||
return _led_i(self, preset, logical_index)
|
||||
|
||||
def signed(self, preset, value):
|
||||
from patterns.pattern_direction import signed as _signed
|
||||
|
||||
return _signed(preset, value)
|
||||
|
||||
def apply_brightness(self, color, brightness_override=None):
|
||||
# Combine per-preset brightness (override) with global brightness self.b
|
||||
local = brightness_override if brightness_override is not None else 255
|
||||
|
||||
Reference in New Issue
Block a user