Skip to content

Rainbowsparkle fix #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 40 additions & 11 deletions adafruit_led_animation/animation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,13 @@ class Animation:
"""
Base class for animations.
"""
cycle_complete_supported = False
on_cycle_complete_supported = False

# pylint: disable=too-many-arguments
def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=None):
self.pixel_object = pixel_object
self.pixel_object.auto_write = False
self.peers = peers if peers else []
"""A sequence of animations to trigger .draw() on when this animation draws."""
self._peers = [self] + peers if peers is not None else [self]
self._speed_ns = 0
self._color = None
self._paused = paused
Expand All @@ -71,6 +70,7 @@ def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=No
self.speed = speed # sets _speed_ns
self.color = color # Triggers _recompute_color
self.name = name
self.cycle_complete = False
self.notify_cycles = 1
"""Number of cycles to trigger additional cycle_done notifications after"""
self.draw_count = 0
Expand All @@ -95,30 +95,59 @@ def animate(self):
if now < self._next_update:
return False

self.draw()
self.draw_count += 1

# Draw related animations together
if self.peers:
for peer in self.peers:
peer.draw()
for anim in self._peers:
anim.draw()
anim.after_draw()

for anim in self._peers:
anim.show()

# Note that the main animation cycle_complete flag is used, not the peer flag.
for anim in self._peers:
if self.cycle_complete:
anim.on_cycle_complete()
anim.cycle_complete = False

self._next_update = now + self._speed_ns
return True

def draw(self):
"""
Animation subclasses must implement draw() to render the animation sequence.
Draw must call show().
Animations should not call show(), as animate() will do so, after after_draw().
Animations should set .cycle_done = True when an animation cycle is completed.
"""
raise NotImplementedError()

def after_draw(self):
"""
Animation subclasses may implement after_draw() to do operations after the main draw()
is called.
"""

def show(self):
"""
Displays the updated pixels. Called during animates with changes.
"""
self.pixel_object.show()

@property
def peers(self):
"""
Get the animation's peers. Peers are drawn, then shown together.
"""
return self._peers[1:]

@peers.setter
def peers(self, peer_list):
"""
Set the animation's peers.
:param list peer_list: List of peer animations.
"""
if peer_list is not None:
self._peers = [self] + peer_list

def freeze(self):
"""
Stops the animation until resumed.
Expand Down Expand Up @@ -173,7 +202,7 @@ def _recompute_color(self, color):
Override as needed.
"""

def cycle_complete(self):
def on_cycle_complete(self):
"""
Called by some animations when they complete an animation cycle.
Animations that support cycle complete notifications will have X property set to False.
Expand Down
5 changes: 2 additions & 3 deletions adafruit_led_animation/animation/chase.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def _resetter():

super().__init__(pixel_object, speed, color, name=name)

cycle_complete_supported = True
on_cycle_complete_supported = True

@property
def reverse(self):
Expand Down Expand Up @@ -115,10 +115,9 @@ def bar_colors():

colorgen = bar_colors()
self.pixel_object[:] = [next(colorgen) for _ in self.pixel_object]
self.show()

if self.draw_count % len(self.pixel_object) == 0:
self.cycle_complete()
self.cycle_complete = True
self._offset = (self._offset + self._direction) % self._repeat_width

def bar_color(self, n, pixel_no=0): # pylint: disable=unused-argument
Expand Down
5 changes: 2 additions & 3 deletions adafruit_led_animation/animation/colorcycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,10 @@ def __init__(self, pixel_object, speed, colors=RAINBOW, name=None):
self._generator = self._color_generator()
next(self._generator)

cycle_complete_supported = True
on_cycle_complete_supported = True

def draw(self):
self.pixel_object.fill(self.color)
self.show()
next(self._generator)

def _color_generator(self):
Expand All @@ -78,7 +77,7 @@ def _color_generator(self):
yield
index = (index + 1) % len(self.colors)
if index == 0:
self.cycle_complete()
self.cycle_complete = True

def reset(self):
"""
Expand Down
5 changes: 2 additions & 3 deletions adafruit_led_animation/animation/comet.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def __init__(
self._generator = self._comet_generator()
super().__init__(pixel_object, speed, color, name=name)

cycle_complete_supported = True
on_cycle_complete_supported = True

def _recompute_color(self, color):
pass
Expand Down Expand Up @@ -130,13 +130,12 @@ def _comet_generator(self):
]
else:
self.pixel_object[start : start + end] = colors[0:end]
self.show()
yield
cycle_passes += 1
if self.bounce:
self.reverse = not self.reverse
if not self.bounce or cycle_passes == 2:
self.cycle_complete()
self.cycle_complete = True
cycle_passes = 0

def draw(self):
Expand Down
12 changes: 8 additions & 4 deletions adafruit_led_animation/animation/pulse.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,11 @@ def __init__(self, pixel_object, speed, color, period=5, name=None):
self._generator = None
self.reset()

cycle_complete_supported = True
on_cycle_complete_supported = True

def draw(self):
color = next(self._generator)
self.fill(color)
self.show()
self.pixel_object.fill(color)

def reset(self):
"""
Expand All @@ -78,8 +77,13 @@ def reset(self):
white = len(self.pixel_object[0]) > 3 and isinstance(
self.pixel_object[0][-1], int
)
dotstar = len(self.pixel_object[0]) == 4 and isinstance(
self.pixel_object[0][-1], float
)
from adafruit_led_animation.helper import ( # pylint: disable=import-outside-toplevel
pulse_generator,
)

self._generator = pulse_generator(self._period, self, white)
self._generator = pulse_generator(
self._period, self, white, dotstar_pwm=dotstar
)
5 changes: 2 additions & 3 deletions adafruit_led_animation/animation/rainbow.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def generate_rainbow(self):
self.colors.append(colorwheel(int(i)))
i += self._step

cycle_complete_supported = True
on_cycle_complete_supported = True

def _color_wheel_generator(self):
period = int(self._period * NANOS_PER_SECOND)
Expand Down Expand Up @@ -113,9 +113,8 @@ def _color_wheel_generator(self):
colorwheel((i + wheel_index) % 255) for i in range(num_pixels)
]
self._wheel_index = wheel_index
self.show()
if cycle_completed:
self.cycle_complete()
self.cycle_complete = True
yield

def _draw_precomputed(self, num_pixels, wheel_index):
Expand Down
6 changes: 3 additions & 3 deletions adafruit_led_animation/animation/rainbowchase.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ def __init__(
super().__init__(pixel_object, speed, 0, size, spacing, reverse, name)

def bar_color(self, n, pixel_no=0):
return self._colors[self._color_idx - n]
return self._colors[self._color_idx - (n % len(self._colors))]

def cycle_complete(self):
def on_cycle_complete(self):
self._color_idx = (self._color_idx + self._direction) % len(self._colors)
super().cycle_complete()
super().on_cycle_complete()
4 changes: 2 additions & 2 deletions adafruit_led_animation/animation/rainbowsparkle.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ def generate_rainbow(self):
int(self._background_brightness * color[2]),
)

def show(self):
def after_draw(self):
self.show()
pixels = [
random.randint(0, len(self.pixel_object) - 1)
for n in range(self._num_sparkles)
Expand All @@ -115,4 +116,3 @@ def show(self):
self.pixel_object[pixel] = self._bright_colors[
(self._wheel_index + pixel) % len(self._bright_colors)
]
super().show()
20 changes: 12 additions & 8 deletions adafruit_led_animation/animation/sparkle.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ class Sparkle(Animation):
def __init__(self, pixel_object, speed, color, num_sparkles=1, name=None):
if len(pixel_object) < 2:
raise ValueError("Sparkle needs at least 2 pixels")
self._half_color = None
self._dim_color = None
self._half_color = color
self._dim_color = color
self._sparkle_color = color
self._num_sparkles = num_sparkles
self._pixels = []
super().__init__(pixel_object, speed, color, name=name)

def _recompute_color(self, color):
Expand All @@ -79,16 +81,18 @@ def _recompute_color(self, color):
self.pixel_object[pixel] = dim_color
self._half_color = half_color
self._dim_color = dim_color
self._sparkle_color = color

def draw(self):
pixels = [
self._pixels = [
random.randint(0, (len(self.pixel_object) - 2))
for n in range(self._num_sparkles)
for _ in range(self._num_sparkles)
]
for pixel in pixels:
self.pixel_object[pixel] = self._color
for pixel in self._pixels:
self.pixel_object[pixel] = self._sparkle_color

def after_draw(self):
self.show()
for pixel in pixels:
for pixel in self._pixels:
self.pixel_object[pixel] = self._half_color
self.pixel_object[pixel + 1] = self._dim_color
self.show()
71 changes: 26 additions & 45 deletions adafruit_led_animation/animation/sparklepulse.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,13 @@

"""

import random
from adafruit_led_animation import NANOS_PER_SECOND, monotonic_ns
from adafruit_led_animation.animation import Animation
from adafruit_led_animation.animation.sparkle import Sparkle
from adafruit_led_animation.helper import pulse_generator


class SparklePulse(Animation):
class SparklePulse(Sparkle):
"""
Combination of the Spark and Pulse animations.
Combination of the Sparkle and Pulse animations.

:param pixel_object: The initialised LED object.
:param int speed: Animation refresh rate in seconds, e.g. ``0.1``.
Expand All @@ -63,48 +62,30 @@ class SparklePulse(Animation):

# pylint: disable=too-many-arguments
def __init__(
self, pixel_object, speed, color, period=5, max_intensity=1, min_intensity=0
self,
pixel_object,
speed,
color,
period=5,
max_intensity=1,
min_intensity=0,
name=None,
):
if len(pixel_object) < 2:
raise ValueError("Sparkle needs at least 2 pixels")
self.max_intensity = max_intensity
self.min_intensity = min_intensity
self._max_intensity = max_intensity
self._min_intensity = min_intensity
self._period = period
self._intensity_delta = max_intensity - min_intensity
self._half_period = period / 2
self._position_factor = 1 / self._half_period
self._bpp = len(pixel_object[0])
self._last_update = monotonic_ns()
self._cycle_position = 0
self._half_color = None
self._dim_color = None
super().__init__(pixel_object, speed, color)

def _recompute_color(self, color):
half_color = tuple(color[rgb] // 4 for rgb in range(len(color)))
dim_color = tuple(color[rgb] // 10 for rgb in range(len(color)))
for pixel in range(len(self.pixel_object)):
if self.pixel_object[pixel] == self._half_color:
self.pixel_object[pixel] = half_color
elif self.pixel_object[pixel] == self._dim_color:
self.pixel_object[pixel] = dim_color
self._half_color = half_color
self._dim_color = dim_color
white = len(pixel_object) == 4 and isinstance(pixel_object[0][-1], int)
dotstar = len(pixel_object) == 4 and isinstance(pixel_object[0][-1], float)
super().__init__(
pixel_object, speed=speed, color=color, num_sparkles=1, name=name
)
self._generator = pulse_generator(
self._period, self, white, dotstar_pwm=dotstar
)

def draw(self):
pixel = random.randint(0, (len(self.pixel_object) - 2))

now = monotonic_ns()
time_since_last_draw = (now - self._last_update) / NANOS_PER_SECOND
self._last_update = now
pos = self._cycle_position = (
self._cycle_position + time_since_last_draw
) % self._period
if pos > self._half_period:
pos = self._period - pos
intensity = self.min_intensity + (
pos * self._intensity_delta * self._position_factor
)
color = [int(self.color[n] * intensity) for n in range(self._bpp)]
self.pixel_object[pixel] = color
self._sparkle_color = next(self._generator)
super().draw()

def after_draw(self):
self.show()
6 changes: 3 additions & 3 deletions adafruit_led_animation/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,15 @@ def __init__(self, *members, sync=False, name=None):

# Catch cycle_complete on the last animation.
self._members[-1].add_cycle_complete_receiver(self._group_done)
self.cycle_complete_supported = self._members[-1].cycle_complete_supported
self.on_cycle_complete_supported = self._members[-1].on_cycle_complete_supported

def __str__(self):
return "<AnimationGroup %s: %s>" % (self.__class__.__name__, self.name)

def _group_done(self, animation): # pylint: disable=unused-argument
self.cycle_complete()
self.on_cycle_complete()

def cycle_complete(self):
def on_cycle_complete(self):
"""
Called by some animations when they complete an animation cycle.
Animations that support cycle complete notifications will have X property set to False.
Expand Down
Loading