-
Notifications
You must be signed in to change notification settings - Fork 44
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
Add animations. #8
Conversation
def draw(self): | ||
""" | ||
Animation subclasses must implement draw() to render the animation sequence. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a raise NotImplementedError()
here instead of leaving it empty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.
self._next_update = monotonic_ns() | ||
self.pixel_object.auto_write = False | ||
self.color = color | ||
self.peers = peers if peers else [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be done by just having the default for the parameter be []
instead of None
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pylint hates [] as the default for a param.
W: 70, 4: Dangerous default value [] as argument (dangerous-default-value)
def change_color(self, color): | ||
""" | ||
By default changing color uses the color property. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed? Why not just use the color
property setter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's difficult to override a property setter. I'm trying to remember why I needed it. I think _recompute_color is what needs overriding and I forgot I put it there. Will try to squash this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, now I remember - it ended up there because I didn't use a property on an animation sequence, time to fix that.
adafruit_led_animation/animation.py
Outdated
def change_color(self, color): | ||
""" | ||
ColorCycle doesn't support change_color. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should an exception be raised here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code removed.
""" | ||
self._next_update = monotonic_ns() + self._time_left_at_pause | ||
self._time_left_at_pause = 0 | ||
self._paused = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about replacing freeze()
and resume()
with a paused
property that can be set or return current state?
# to pause
animation.paused = True
# to check
if animation.paused:
print("Animation currently paused.")
# to un-pause
animation.paused = False
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The freeze/resume protocol on the Animation children grew out of the AnimationSequence and AnimationGroup. It would be possible to use a paused property, but it doesn't behave cleanly:
AnimationSequence freezes/resumes the current animation in the sequence.
AnimationGroup freezes/resumes all members of the group.
Querying AnimationSequence.paused would return the sequence paused state so that wouldn't be too bad.
Querying AnimationGroup.paused however would need to return all() or any() on the paused state of all members of the group - as it doesn't itself know if they're paused.
I intentionally made the basic signatures for Animations, AnimationGroup and AnimationSequence identical so you can mix and match them in any order.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So is the only potential confusion when querying the current state of an AnimationGroup? If freeze/resume acts on all members, how do you get to a state where they are mixed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a mixed state in an AnimationGroup seems to defeat the purpose of the Group. If your end goal is a mixed state, you'd want to handle things separately.
@caternuson What is your reasoning behind suggesting this change? I'm trying to understand the purpose of the suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess the more important thing here is what do we want the interface to be for pausing and resuming. a property or two methods? @caternuson @kattni
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was thinking it would be more Pythonic. Also, to have a way to get current state. But this could be refactored later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactoring it later not the right choice as it would change the protocol people use.... So I'll get this refactored ASAP, as @kattni needs this for her latest guide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say keep as is then. The audioio API has separate functions for play/stop/pause:
https://circuitpython.readthedocs.io/en/latest/shared-bindings/audioio/AudioOut.html
so seems OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverted back to the freeze/resume methods.
@caternuson @kattni Let me know which you prefer. |
@rhooper Now that I've looked at the audioio API, what do you think about mimicking that? Is your animation control similar enough to audio control? |
@caternuson I'm not sure, I'll have to look at the AudioOut API when I have time. |
adafruit_led_animation/animation.py
Outdated
""" | ||
The animation speed in fractional seconds. | ||
""" | ||
return self._speed_ns / 1000000000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is technically a "magic" number and appears more than once, consider pulling this into a module-level constant, like https://github.com/theacodes/Winterbloom-Sol-software/blob/ce2b8b07f19b5f5c20cd32fa50e2ef7e7007c603/winterbloom_sol/adsr.py#L30
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had thought about that, but I am also concerned about performance. I vaguely remember there's a constant thing in CircuitPython that might be faster than a bare module variable, but I need to benchmark. Having worked a lot in the CircuitPython core with a JLink and gdb lately makes me think about these things :)
I'll look into the performance of both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my experience moving things to module-level variables doesn't impact performance, and might even help, considering that CP doesn't need to do an allocation for the module-level variable every time you use it.
If performance is a big concern we should do another pass at this once it's merged. There's lots of use of for x in y
, range
, and slicing that outright murder performance on CP compared to manipulating index variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another pass at it was already planned. The initial code was written to make it work. Making it better is the next step.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@theacodes there is indeed plenty of room for performance fixes. Two different things happened while writing this: 1) Making it work. 2) A lot of the code I wrote was written with _pixelbuf in mind, which perform faster with slices.. Running the code without _pixelbuf is, as you said, much slower. Once we get this merged, there's a number of issues to file regarding improvements, additions, and design documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this to a constant in the module.
self._reverse_comet_colors = None | ||
self.reverse = reverse | ||
self.bounce = bounce | ||
super(Comet, self).__init__(pixel_object, speed, color) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: it's unusual to see the superclass __init__
so far down. I've generally seen this done as soon as possible, like in the very first statement in the __init__
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I usually do it first, but in this case, when I did this, the color setup work needed doing before the rest of super is run. I'll check if that's still true. If not, a comment would be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments added and init for both the base class and this class reorganized slightly.
self._dim_color = dim_color | ||
|
||
def draw(self): | ||
pixel = random.randint(0, (len(self.pixel_object) - 2)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if the pixel object has less than 2 pixels?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then it would crash. Good call. (note: this was based on code from elsewhere, and this animation doesn't support single pixels -- init should check that).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Init now checks that the strip is big enough.
adafruit_led_animation/animation.py
Outdated
self._last_update = now | ||
self._cycle_position = (self._cycle_position + time_since_last_draw) % self._period | ||
intensity = self.min_intensity + ( | ||
sin(self._radians_per_second * self._cycle_position) * self._intensity_delta) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sin's range is -1.0 to 1.0, so this could end up setting intensity to a negative number.
For example:
min_intensity = 0.0
max_intensity = 1.0
intensity_delta = max_intensity - min_intensity # 1.0
# assume radians per second and cycle position end up passing 1.5pi to sin.
intensity = min_intensity + (sin(1.5 * pi) * intensity_delta)
intensity = 0.0 + (-1.0 * 1.0)
intensity = -1.0
The pixel colors would then be multiplied by -1
which likely isn't what we want.
You can solve this by moving sin's range to be 0 - 1.0
like this:
def normalized_sin(angle):
return 0.5 + (sin(angle) * 0.5)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm planning to remove the use of sin() very soon, as the performance is poor. The code, however, shouldn't produce a negative brightness because it's clamped to 0-180 degrees by self._radians_per_second and % self._period set during init.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sin removed in favour of a linear function for now. Code still needs significant optimization as expected.
@theacodes Thank you for the review notes. @rhooper addressed them. As getting this merged is time-sensitive, I'm going to go ahead and merge it. If you have further suggestions, please file an issue. We intend to file separate issues for each of the improvements we want to make to make it easier to discuss them in the thread - please consider doing the same if you decide to file any issues. Thanks! |
Updating https://github.com/adafruit/Adafruit_CircuitPython_BLE to 3.1.1 from 3.1.0: > Merge pull request adafruit/Adafruit_CircuitPython_BLE#43 from makermelissa/master Updating https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation to 1.0.0 from 0.8.2: > Merge pull request adafruit/Adafruit_CircuitPython_LED_Animation#8 from kattni/animations > Merge pull request adafruit/Adafruit_CircuitPython_LED_Animation#7 from makermelissa/master > Merge pull request adafruit/Adafruit_CircuitPython_LED_Animation#5 from demophoon/add-blinka-to-requirements-txt
Created
animations.py
and added animations and animation features:Added RAINBOW color list to
color.py
.