Berry animation remove value_provider class (#24320)

This commit is contained in:
s-hadinger
2026-01-10 16:25:42 +01:00
committed by GitHub
parent 3c737580c4
commit 420a9360a9
27 changed files with 7614 additions and 7613 deletions
@@ -15,6 +15,7 @@ This document provides a comprehensive reference for all classes in the Berry An
```
parameterized_object (base class with parameter management and playable interface)
├── Animation (unified base class for all visual elements)
│ ├── engine_proxy (combines rendering and orchestration)
│ │ └── (user-defined template animations)
@@ -30,19 +31,26 @@ parameterized_object (base class with parameter management and playable interfac
│ ├── twinkle (twinkling stars effect)
│ ├── wave (wave motion effects)
│ └── rich_palette (smooth palette transitions)
├── sequence_manager (orchestrates animation sequences)
└── value_provider (dynamic value generation)
└── Value Providers (VALUE_PROVIDER = true)
├── static_value (wraps static values)
├── strip_length (provides LED strip length)
├── iteration_number (provides sequence iteration number)
├── oscillator_value (oscillating values with waveforms)
│ └── breathe_color (breathing color effect)
├── closure_value (computed values, internal use only)
└── color_provider (solid color, base for color providers)
└── Color Providers
├── color_provider (solid color, base for color providers)
├── breathe_color (breathing color effect with internal oscillator)
├── color_cycle (cycles through palette)
└── rich_palette_color (smooth palette transitions)
```
**Note on Value Providers**: Value providers inherit directly from `parameterized_object` and are identified by the static class variable `VALUE_PROVIDER = true`. Use `animation.is_value_provider(obj)` to check if an object is a value provider. This flat hierarchy reduces class overhead while maintaining clear semantic distinction.
## Base Classes
### parameterized_object
@@ -198,25 +206,41 @@ run my_shutter
## Value Providers
Value providers generate dynamic values over time for use as animation parameters.
Value providers generate dynamic values over time for use as animation parameters. They inherit directly from `parameterized_object` and are identified by the static class variable `VALUE_PROVIDER = true`.
### value_provider
**Identifying Value Providers**: Use `animation.is_value_provider(obj)` to check if an object is a value provider. This function checks for the `VALUE_PROVIDER = true` static variable.
Base interface for all value providers. Inherits from `parameterized_object`.
**Creating Custom Value Providers**: To create a custom value provider, inherit from `parameterized_object` and set `static var VALUE_PROVIDER = true`:
```berry
class my_custom_provider : animation.parameterized_object
static var VALUE_PROVIDER = true
def produce_value(name, time_ms)
# Return computed value based on time
return computed_value
end
end
```
### Common Value Provider Interface
All value providers share these characteristics from `parameterized_object`:
| Parameter | Type | Default | Constraints | Description |
|-----------|------|---------|-------------|-------------|
| *(none)* | - | - | - | Base interface has no parameters |
| *(none specific)* | - | - | - | Value providers typically have no base parameters |
**Key Method**:
- `produce_value(name, time_ms)` - Returns a value for the given parameter name at the specified time
**Timing Behavior**: For value providers, `start()` is typically not called because instances can be embedded in closures. Value providers consider the first call to `produce_value()` as the start of their internal time reference. The `start()` method only resets the time origin if the provider was already started previously (i.e., `self.start_time` is not nil).
**Update Method**: The `update(time_ms)` method does not return any value. Subclasses should check `self.is_running` to determine if the object is still active.
**Factory**: N/A (base interface)
### static_value
Wraps static values to provide value_provider interface. Inherits from `value_provider`.
Wraps static values to provide value_provider interface. Inherits from `parameterized_object` with `VALUE_PROVIDER = true`.
| Parameter | Type | Default | Constraints | Description |
|-----------|------|---------|-------------|-------------|
@@ -226,7 +250,7 @@ Wraps static values to provide value_provider interface. Inherits from `value_pr
### strip_length
Provides access to the LED strip length as a dynamic value. Inherits from `value_provider`.
Provides access to the LED strip length as a dynamic value. Inherits from `parameterized_object` with `VALUE_PROVIDER = true`.
| Parameter | Type | Default | Constraints | Description |
|-----------|------|---------|-------------|-------------|
@@ -238,7 +262,7 @@ Provides access to the LED strip length as a dynamic value. Inherits from `value
### oscillator_value
Generates oscillating values using various waveforms. Inherits from `value_provider`.
Generates oscillating values using various waveforms. Inherits from `parameterized_object` with `VALUE_PROVIDER = true`.
| Parameter | Type | Default | Constraints | Description |
|-----------|------|---------|-------------|-------------|
@@ -270,7 +294,7 @@ Generates oscillating values using various waveforms. Inherits from `value_provi
**⚠️ INTERNAL USE ONLY - NOT FOR DIRECT USE**
Wraps a closure/function as a value provider for internal transpiler use. This class is used internally by the DSL transpiler to handle computed values and should not be used directly by users.
Wraps a closure/function as a value provider for internal transpiler use. This class is used internally by the DSL transpiler to handle computed values and should not be used directly by users. Inherits from `parameterized_object` with `VALUE_PROVIDER = true`.
| Parameter | Type | Default | Constraints | Description |
|-----------|------|---------|-------------|-------------|
@@ -335,11 +359,11 @@ animation pulse = breathe(
## Color Providers
Color providers generate dynamic colors over time, extending value_provider for color-specific functionality.
Color providers generate dynamic colors over time. They inherit from `parameterized_object` with `VALUE_PROVIDER = true`, providing color-specific functionality while maintaining the value provider interface.
### color_provider
Base class for color providers that returns a solid color. Inherits from `value_provider`. Can be used directly for static colors or subclassed for dynamic color generation.
Base class for color providers that returns a solid color. Inherits from `parameterized_object` with `VALUE_PROVIDER = true`. Can be used directly for static colors or subclassed for dynamic color generation.
| Parameter | Type | Default | Constraints | Description |
|-----------|------|---------|-------------|-------------|
@@ -445,16 +469,16 @@ color fire_colors = rich_palette_color(
### breathe_color
Creates breathing/pulsing color effects by modulating the brightness of a base color over time. Inherits from `oscillator_value`.
Creates breathing/pulsing color effects by modulating the brightness of a base color over time. Inherits from `color_provider` and uses an internal `oscillator_value` for time-based brightness modulation.
| Parameter | Type | Default | Constraints | Description |
|-----------|------|---------|-------------|-------------|
| `color` | int | 0xFFFFFFFF | - | The base color to modulate (32-bit ARGB value) |
| `min_brightness` | int | 0 | 0-255 | Minimum brightness level (breathing effect) |
| `max_brightness` | int | 255 | 0-255 | Maximum brightness level (breathing effect) |
| `duration` | int | 3000 | min: 1 | Time for one complete breathing cycle in ms |
| `period` | int | 3000 | min: 1 | Time for one complete breathing cycle in ms |
| `curve_factor` | int | 2 | 1-5 | Breathing curve shape (1=cosine wave, 2-5=curved breathing with pauses) |
| *(inherits all oscillator_value parameters)* | | | | |
| *(inherits color, brightness from color_provider)* | | | | |
**Curve Factor Effects:**
- `1`: Pure cosine wave (smooth pulsing)
@@ -471,7 +495,7 @@ color breathing_red = breathe_color(
color=red,
min_brightness=20,
max_brightness=255,
duration=4s,
period=4s,
curve_factor=3
)
@@ -480,7 +504,7 @@ color pulse_blue = breathe_color(
color=blue,
min_brightness=50,
max_brightness=200,
duration=1s,
period=1s,
curve_factor=1
)
@@ -489,7 +513,7 @@ color deep_breath = breathe_color(
color=purple,
min_brightness=5,
max_brightness=255,
duration=6s,
period=6s,
curve_factor=4
)
@@ -499,7 +523,7 @@ color breathing_rainbow = breathe_color(
color=rainbow_cycle,
min_brightness=30,
max_brightness=255,
duration=3s,
period=3s,
curve_factor=2
)
```
@@ -380,7 +380,7 @@ color breathing_red = breathe_color(
color=red
min_brightness=5%
max_brightness=100%
duration=3s
period=3s
curve_factor=2
)
color pulsing_blue = breathe_color(
@@ -388,7 +388,7 @@ color pulsing_blue = breathe_color(
color=blue
min_brightness=20%
max_brightness=80%
duration=1s
period=1s
)
```
@@ -336,7 +336,7 @@ _detect_and_cache_symbol(name)
│ ├── Detect user functions via animation.is_user_function() → create_user_function()
│ ├── Test constructors with MockEngine:
│ │ ├── Create instance with mock_engine
│ │ ├── Check isinstance(instance, animation.value_provider) → create_value_provider()
│ │ ├── Check animation.is_value_provider(instance) → create_value_provider()
│ │ └── Check isinstance(instance, animation.animation) → create_animation()
│ └── Cache result for future lookups
└── Return nil if not found (handled as user-defined)
@@ -371,7 +371,7 @@ _detect_and_cache_symbol(name)
**Value Provider Detection:**
```berry
# DSL: set oscillator = triangle(min_value=0, max_value=100, period=2s)
# Detection: triangle(mock_engine) creates instance, isinstance(instance, animation.value_provider)
# Detection: triangle(mock_engine) creates instance, animation.is_value_provider(instance)
# Result: SymbolEntry("triangle", "value_provider", instance, true)
# Reference: "animation.triangle"
```
@@ -442,8 +442,8 @@ MockEngine
# Test if function creates value provider
try
var instance = factory_func(self.mock_engine)
if isinstance(instance, animation.value_provider)
return SymbolEntry.create_value_provider(name, instance, animation.value_provider)
if animation.is_value_provider(instance)
return SymbolEntry.create_value_provider(name, instance)
end
except .. as e, msg
# Constructor failed - not a valid provider
@@ -50,12 +50,13 @@ class beacon : animation.animation
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms, strip_length)
# Use virtual parameter access - automatically resolves value_providers
var back_color = self.back_color
var pos = self.pos
var slew_size = self.slew_size
var beacon_size = self.beacon_size
var color = self.color
var right_edge = self.right_edge
var member = self.member
var back_color = member(self, "back_color")
var pos = member(self, "pos")
var slew_size = member(self, "slew_size")
var beacon_size = member(self, "beacon_size")
var color = member(self, "color")
var right_edge = member(self, "right_edge")
# Fill background if not transparent
if (back_color != 0xFF000000) && ((back_color & 0xFF000000) != 0x00)
@@ -12,7 +12,7 @@ import "./core/param_encoder" as encode_constraints
class breathe : animation.animation
# Non-parameter instance variables only
var breathe_provider # Internal breathe color provider
var _breathe # Internal breathe color provider
# Parameter definitions following parameterized class specification
# Note: 'color' is inherited from Animation base class
@@ -32,10 +32,10 @@ class breathe : animation.animation
super(self).init(engine)
# Create internal breathe color provider
self.breathe_provider = animation.breathe_color(engine)
self._breathe = animation.breathe_color(engine)
# Set the animation's color parameter to use the breathe provider
self.values["color"] = self.breathe_provider
self.values["color"] = self._breathe
end
# Handle parameter changes - propagate to internal breathe provider
@@ -43,21 +43,21 @@ class breathe : animation.animation
super(self).on_param_changed(name, value)
# Propagate relevant parameters to the breathe provider
if name == "color"
# When color is set, update the breathe_provider's color
# but keep the breathe_provider as the actual color source for rendering
# When color is set, update the _breathe's color
# but keep the _breathe as the actual color source for rendering
if type(value) == 'int'
self.breathe_provider.color = value
# Restore the breathe_provider as the color source (bypass on_param_changed)
self.values["color"] = self.breathe_provider
self._breathe.color = value
# Restore the _breathe as the color source (bypass on_param_changed)
self.values["color"] = self._breathe
end
elif name == "min_brightness"
self.breathe_provider.min_brightness = value
self._breathe.min_brightness = value
elif name == "max_brightness"
self.breathe_provider.max_brightness = value
self._breathe.max_brightness = value
elif name == "period"
self.breathe_provider.duration = value
self._breathe.period = value
elif name == "curve_factor"
self.breathe_provider.curve_factor = value
self._breathe.curve_factor = value
end
end
@@ -68,17 +68,13 @@ class breathe : animation.animation
def start(start_time)
# Call parent start method first
super(self).start(start_time)
# Start the breathe provider with the same time
var actual_start_time = start_time != nil ? start_time : self.engine.time_ms
self.breathe_provider.start(actual_start_time)
self._breathe.start(start_time)
return self
end
# The render method is inherited from Animation base class
# It automatically uses self.color (which is set to self.breathe_provider)
# The breathe_provider produces the breathing color effect
# It automatically uses self.color (which is set to self._breathe)
# The _breathe produces the breathing color effect
end
return {'breathe': breathe }
@@ -41,10 +41,12 @@ class comet : animation.animation
if name == "direction"
# Reset position when direction changes
var strip_length = self.engine.strip_length
if value > 0
self.head_position = 0 # Start at beginning for forward movement
else
self.head_position = (strip_length - 1) * 256 # Start at end for backward movement
if type(value) == 'int'
if value > 0
self.head_position = 0 # Start at beginning for forward movement
else
self.head_position = (strip_length - 1) * 256 # Start at end for backward movement
end
end
end
end
@@ -98,14 +98,6 @@ class crenel : animation.animation
return true
end
# NO setter/getter methods - use direct assignment instead:
# obj.color = value
# obj.back_color = value
# obj.pos = value
# obj.pulse_size = value
# obj.low_size = value
# obj.nb_pulse = value
end
return {'crenel': crenel}
@@ -86,7 +86,7 @@ class engine_proxy : animation.animation
if isinstance(obj, animation.sequence_manager)
return self._add_sequence_manager(obj)
# Check if it's a value_provider (before Animation check, as some animations might also be providers)
elif isinstance(obj, animation.value_provider)
elif animation.is_value_provider(obj)
return self._add_value_provider(obj)
# Check if it's an Animation (or subclass)
elif isinstance(obj, animation.animation)
@@ -226,7 +226,7 @@ class engine_proxy : animation.animation
if isinstance(obj, animation.sequence_manager)
return self._remove_sequence_manager(obj)
# Check if it's a value_provider (before Animation check)
elif isinstance(obj, animation.value_provider)
elif animation.is_value_provider(obj)
return self._remove_value_provider(obj)
# Check if it's an Animation (or subclass)
elif isinstance(obj, animation.animation)
@@ -21,7 +21,27 @@ class parameterized_object
var engine # Reference to the animation engine
var start_time # Time when object started (ms) (int), value is set at first call to update() or render()
var is_running # Whether the object is active
static var VALUE_PROVIDER = false # Set to true in value_provider subclasses
# Produce a value for a specific parameter name and time
# This is the main method that value_provider subclasses should override
#
# `name` argument is generally ignored and the same value
# is returned for any name, however this allows to have
# special value providers that return coordinated distinct
# values for different parameter names.
#
# For value providers, start is typically not called because instances
# can be embedded in closures. So value providers must consider the first
# call to `produce_value()` as a start of their internal time reference.
#
# @param name: string - Parameter name being requested
# @param time_ms: int - Current time in milliseconds
# @return any - Value appropriate for the parameter type
def produce_value(name, time_ms)
return module("undefined") # Default behavior - return undefined
end
# Initialize parameter system
#
# @param engine: AnimationEngine - Reference to the animation engine (required)
@@ -394,7 +394,7 @@ class SymbolTable
var entry = animation_dsl._symbol_entry.create_color_constructor(name, instance, true)
self.entries[name] = entry
return entry
elif isinstance(instance, animation.value_provider)
elif animation.is_value_provider(instance)
var entry = animation_dsl._symbol_entry.create_value_provider_constructor(name, instance, true)
self.entries[name] = entry
return entry
@@ -1,9 +1,8 @@
# Breathe Color Provider for Berry Animation Framework
#
# This color provider creates breathing/pulsing color effects by modulating the brightness
# of a base color over time. It inherits from oscillator_value to leverage its
# robust time management and waveform generation, then applies the oscillator value
# as brightness modulation to a base color.
# of a base color over time. It inherits from color_provider to leverage its color handling
# and maintains an internal oscillator_value for time-based brightness modulation.
#
# The effect uses the oscillator's COSINE waveform with optional curve factor:
# - curve_factor 1: Pure cosine wave (smooth pulsing)
@@ -11,14 +10,17 @@
import "./core/param_encoder" as encode_constraints
class breathe_color : animation.oscillator_value
# Additional parameter definitions for color-specific functionality
# The oscillator parameters (min_value, max_value, duration, form, etc.) are inherited
class breathe_color : animation.color_provider
# Internal oscillator for brightness modulation
var _oscillator
# Parameter definitions for breathing-specific functionality
# The 'color' and 'brightness' parameters are inherited from color_provider
static var PARAMS = animation.enc_params({
"color": {"default": 0xFFFFFFFF}, # The base color to modulate (32-bit ARGB value)
"min_brightness": {"min": 0, "max": 255, "default": 0}, # Minimum brightness level (0-255)
"max_brightness": {"min": 0, "max": 255, "default": 255}, # Maximum brightness level (0-255)
"curve_factor": {"min": 1, "max": 5, "default": 2} # Factor to control breathing curve shape (1=cosine wave, 2-5=curved breathing with pauses)
"period": {"min": 1, "default": 3000}, # Breathing cycle time in ms (renamed from duration for consistency)
"curve_factor": {"min": 1, "max": 5, "default": 2} # Factor to control breathing curve shape (1=cosine wave, 2-5=curved breathing with pauses)
})
# Initialize a new Breathe Color Provider
@@ -26,23 +28,23 @@ class breathe_color : animation.oscillator_value
#
# @param engine: AnimationEngine - The animation engine (required)
def init(engine)
# Call parent constructor (oscillator_value)
# Call parent constructor (color_provider)
super(self).init(engine)
# Configure the inherited oscillator for breathing behavior
self.form = 4 #-animation.COSINE-# # Use cosine wave for smooth breathing
self.min_value = 0 # Fixed range 0-255 for normalized oscillation
self.max_value = 255 # Fixed range 0-255 for normalized oscillation
self.duration = 3000 # Default duration
# Create internal oscillator for brightness modulation
self._oscillator = animation.oscillator_value(engine)
self._oscillator.form = 4 #-animation.COSINE-# # Use cosine wave for smooth breathing
self._oscillator.min_value = 0 # Fixed range 0-255 for normalized oscillation
self._oscillator.max_value = 255 # Fixed range 0-255 for normalized oscillation
self._oscillator.duration = 3000 # Default period (synced with our 'period' param)
engine.add(self._oscillator) # register so it receives start()
end
# Handle parameter changes - no need to sync oscillator min/max since they're fixed
# Handle parameter changes - sync period to internal oscillator
def on_param_changed(name, value)
# Only handle curve_factor changes for oscillator form
if name == "curve_factor"
# For curve_factor = 1, use pure cosine
# For curve_factor > 1, we'll apply the curve in produce_value
self.form = 4 #-animation.COSINE-#
# Sync period changes to the internal oscillator's duration
if name == "period"
self._oscillator.duration = value
end
# Call parent's parameter change handler
@@ -50,14 +52,14 @@ class breathe_color : animation.oscillator_value
end
# Produce color value based on current time
# This overrides the parent's produce_value to return colors instead of raw values
# This overrides the parent's produce_value to return colors with modulated brightness
#
# @param name: string - Parameter name (ignored for color providers)
# @param time_ms: int - Current time in milliseconds
# @return int - 32-bit ARGB color value with modulated brightness
def produce_value(name, time_ms)
# Get the normalized oscillator value (0-255) from parent class
var normalized_value = super(self).produce_value(name, time_ms)
# Get the normalized oscillator value (0-255) from internal oscillator
var normalized_value = self._oscillator.produce_value(name, time_ms)
# Apply curve factor if > 1 for natural breathing effect
var current_curve_factor = self.curve_factor
@@ -82,22 +84,11 @@ class breathe_color : animation.oscillator_value
# Now map the curved value to the brightness range
var brightness = tasmota.scale_uint(curved_value, 0, 255, self.min_brightness, self.max_brightness)
# Apply brightness to the base color (using inherited 'color' parameter)
# Get the base color (inherited from color_provider)
var current_color = self.color
# Extract RGB components
var alpha = (current_color >> 24) & 0xFF
var red = (current_color >> 16) & 0xFF
var green = (current_color >> 8) & 0xFF
var blue = current_color & 0xFF
# Apply brightness scaling using tasmota.scale_uint
red = tasmota.scale_uint(red, 0, 255, 0, brightness)
green = tasmota.scale_uint(green, 0, 255, 0, brightness)
blue = tasmota.scale_uint(blue, 0, 255, 0, brightness)
# Reconstruct color
return (alpha << 24) | (red << 16) | (green << 8) | blue
# Apply brightness
return self.apply_brightness(current_color, brightness)
end
end
@@ -17,7 +17,8 @@
import "./core/param_encoder" as encode_constraints
class closure_value : animation.value_provider
class closure_value : animation.parameterized_object
static var VALUE_PROVIDER = true
var _closure # We keep the closure as instance variable for faster dereferencing, in addition to PARAMS
# Static parameter definitions
@@ -12,7 +12,8 @@
# - Constructor takes only 'engine' parameter
# - All other parameters set via virtual member assignment after creation
class color_provider : animation.value_provider
class color_provider : animation.parameterized_object
static var VALUE_PROVIDER = true
# LUT (Lookup Table) management for color providers
# Subclasses can use this to cache pre-computed colors for performance
# If a subclass doesn't use a LUT, this remains nil
@@ -80,6 +81,7 @@ class color_provider : animation.value_provider
end
# Extract RGB components (preserve alpha channel)
var alpha = (color >> 24) & 0xFF
var r = (color >> 16) & 0xFF
var g = (color >> 8) & 0xFF
var b = color & 0xFF
@@ -90,7 +92,7 @@ class color_provider : animation.value_provider
b = tasmota.scale_uint(b, 0, 255, 0, brightness)
# Reconstruct color with scaled brightness (preserve alpha)
return (color & 0xFF000000) | (r << 16) | (g << 8) | b
return (alpha << 24) | (r << 16) | (g << 8) | b
end
end
@@ -21,7 +21,8 @@
import "./core/param_encoder" as encode_constraints
class iteration_number : animation.value_provider
class iteration_number : animation.parameterized_object
static var VALUE_PROVIDER = true
# Produce the current iteration number from the animation engine
#
# @param name: string - Parameter name being requested (ignored)
@@ -1,13 +1,17 @@
# oscillator_value for Berry Animation Framework
#
# This value provider generates oscillating values based on time using various waveforms.
# It's based on the original Animate_oscillator class but adapted to work as a value_provider.
# Generates oscillating values based on time using various waveforms.
#
# Supported waveforms:
# - SAWTOOTH (1): Linear ramp from a to b
# - TRIANGLE (2): Linear ramp from a to b, then back to a
# - SQUARE (3): Square wave alternating between a and b
# - COSINE (4): Smooth cosine wave from a to b
# - SAWTOOTH (1): Linear ramp from min to max, then reset
# - TRIANGLE (2): Linear ramp from min to max, then back to min
# - SQUARE (3): Alternates between min and max (duty_cycle controls ratio)
# - COSINE (4): Smooth cosine wave (starts at min, peaks at mid-cycle)
# - SINE (5): Pure sine wave (starts at mid-value)
# - EASE_IN (6): Quadratic acceleration (slow start, fast end)
# - EASE_OUT (7): Quadratic deceleration (fast start, slow end)
# - ELASTIC (8): Spring-like overshoot and oscillation
# - BOUNCE (9): Ball-like bouncing with decreasing amplitude
import "./core/param_encoder" as encode_constraints
@@ -23,7 +27,8 @@ var EASE_OUT = 7
var ELASTIC = 8
var BOUNCE = 9
class oscillator_value : animation.value_provider
class oscillator_value : animation.parameterized_object
static var VALUE_PROVIDER = true
# Non-parameter instance variables only
var value # current calculated value
@@ -68,12 +73,13 @@ class oscillator_value : animation.value_provider
# @return number - Calculated oscillator value
def produce_value(name, time_ms)
# Get parameter values using virtual member access
var duration = self.duration
var min_value = self.min_value
var max_value = self.max_value
var form = self.form
var phase = self.phase
var duty_cycle = self.duty_cycle
var member = self.member
var duration = member(self, "duration")
var min_value = member(self, "min_value")
var max_value = member(self, "max_value")
var form = member(self, "form")
var phase = member(self, "phase")
var duty_cycle = member(self, "duty_cycle")
var scale_uint = tasmota.scale_uint
var scale_int = tasmota.scale_int
@@ -102,63 +108,61 @@ class oscillator_value : animation.value_provider
end
end
# Compute normalized value (0-255) based on waveform, then scale to min/max
var v # normalized value 0-255
# Compute value directly in min_value..max_value range
var v
var duty_mid = scale_uint(duty_cycle, 0, 255, 0, duration)
if form == 3 #-SQUARE-#
self.value = past < duty_mid ? min_value : max_value
return self.value
v = past < duty_mid ? min_value : max_value
elif form == 2 #-TRIANGLE-#
if past < duty_mid
v = scale_uint(past, 0, duty_mid - 1, 0, 255)
v = scale_uint(past, 0, duty_mid - 1, min_value, max_value)
else
v = scale_uint(past, duty_mid, duration - 1, 255, 0)
v = scale_uint(past, duty_mid, duration - 1, max_value, min_value)
end
elif form == 4 || form == 5 #-COSINE/SINE-#
var angle = scale_uint(past, 0, duration - 1, 0, 32767)
if form == 4 angle -= 8192 end # cosine phase shift
v = scale_int(tasmota.sine_int(angle), -4096, 4096, 0, 255)
v = scale_int(tasmota.sine_int(angle), -4096, 4096, min_value, max_value)
elif form == 6 || form == 7 #-EASE_IN/EASE_OUT-#
var t = scale_uint(past, 0, duration - 1, 0, 255)
if form == 6 # ease_in: t^2
v = scale_int(t * t, 0, 65025, 0, 255)
v = scale_int(t * t, 0, 65025, min_value, max_value)
else # ease_out: 1-(1-t)^2
var inv = 255 - t
v = 255 - scale_int(inv * inv, 0, 65025, 0, 255)
v = scale_int(65025 - inv * inv, 0, 65025, min_value, max_value)
end
elif form == 8 #-ELASTIC-#
var t = scale_uint(past, 0, duration - 1, 0, 255)
if t == 0 self.value = min_value return self.value end
if t == 255 self.value = max_value return self.value end
if t == 0 return (self.value := min_value) end
if t == 255 return (self.value := max_value) end
var decay = scale_uint(255 - t, 0, 255, 255, 32)
var osc = tasmota.sine_int(scale_uint(t, 0, 255, 0, 196602) % 32767)
var offset = scale_int(osc * decay, -1044480, 1044480, -255, 255)
self.value = min_value + scale_int(t, 0, 255, 0, max_value - min_value) + offset
var val_range = max_value - min_value
var offset = scale_int(osc * decay, -1044480, 1044480, -val_range, val_range)
v = min_value + scale_int(t, 0, 255, 0, val_range) + offset
# Clamp with 25% overshoot allowance
var overshoot = (max_value - min_value) / 4
if self.value > max_value + overshoot self.value = max_value + overshoot end
if self.value < min_value - overshoot self.value = min_value - overshoot end
return self.value
var overshoot = val_range / 4
if v > max_value + overshoot v = max_value + overshoot end
if v < min_value - overshoot v = min_value - overshoot end
elif form == 9 #-BOUNCE-#
var t = scale_uint(past, 0, duration - 1, 0, 255)
var val_range = max_value - min_value
if t < 128
var s = scale_uint(t, 0, 127, 0, 255)
v = 255 - scale_int((255-s)*(255-s), 0, 65025, 0, 255)
v = max_value - scale_int((255-s)*(255-s), 0, 65025, 0, val_range)
elif t < 192
var s = scale_uint(t - 128, 0, 63, 0, 255)
v = scale_int(255 - scale_int((255-s)*(255-s), 0, 65025, 0, 255), 0, 255, 0, 128)
v = max_value - scale_int((255-s)*(255-s), 0, 65025, 0, val_range / 2)
else
var s = scale_uint(t - 192, 0, 63, 0, 255)
var bv = 255 - scale_int((255-s)*(255-s), 0, 65025, 0, 255)
v = 255 - scale_int(255 - bv, 0, 255, 0, 64)
v = max_value - scale_int((255-s)*(255-s), 0, 65025, 0, val_range / 4)
end
else #-SAWTOOTH (default)-#
v = scale_uint(past, 0, duration - 1, 0, 255)
v = scale_uint(past, 0, duration - 1, min_value, max_value)
end
self.value = scale_int(v, 0, 255, min_value, max_value)
return self.value
return (self.value := v)
end
end
@@ -13,7 +13,8 @@
import "./core/param_encoder" as encode_constraints
class static_value : animation.value_provider
class static_value : animation.parameterized_object
static var VALUE_PROVIDER = true
# Parameter definitions
static var PARAMS = animation.enc_params({
"value": {"default": nil, "type": "any"}
@@ -11,7 +11,8 @@
# - Constructor takes only 'engine' parameter
# - No additional parameters needed since strip length is obtained from engine
class strip_length : animation.value_provider
class strip_length : animation.parameterized_object
static var VALUE_PROVIDER = true
# Produce the strip length value
#
# @param name: string - Parameter name being requested (ignored)
@@ -1,45 +1,17 @@
# value_provider interface for Berry Animation Framework
# value_provider utilities for Berry Animation Framework
#
# This defines the core interface for value providers in the animation framework.
# Value providers generate values based on time, which can be used by animations
# Value providers are parameterized_object subclasses with VALUE_PROVIDER = true.
# They generate values based on time, which can be used by animations
# for any parameter that needs to be dynamic over time.
#
# This is the super-class for all value provider variants and provides the interface
# that animations can use to get dynamic values for their parameters.
#
# value_providers follow the parameterized class specification:
# - Constructor takes only 'engine' parameter
# - All other parameters set via virtual member assignment
# - No setter/getter methods for parameters
# The value_provider class has been removed from the hierarchy.
# All value providers now inherit directly from parameterized_object
# and set `static var VALUE_PROVIDER = true`.
import "./core/param_encoder" as encode_constraints
class value_provider : animation.parameterized_object
# Produce a value for a specific parameter name and time
# This is the main method that subclasses should override
#
# `name` argument is generally ignored and the same value
# is returned for any name, however this allows to have
# special value providers that return coordinated distinct
# values for different parameter names.
#
# For value providers, start is typically not called because instances
# can be embedded in closures. So value providers must consider the first
# call to `produce_value()` as a start of their internal time reference.
#
# @param name: string - Parameter name being requested
# @param time_ms: int - Current time in milliseconds
# @return any - Value appropriate for the parameter type
def produce_value(name, time_ms)
return module("undefined") # Default behavior - return undefined
end
end
# Add a method to check if an object is a value provider
# Check if an object is a value provider
# Returns true if obj is a parameterized_object with VALUE_PROVIDER = true
def is_value_provider(obj)
return isinstance(obj, animation.value_provider)
return obj != nil && type(obj) == "instance" && isinstance(obj, animation.parameterized_object) && obj.VALUE_PROVIDER
end
return {'value_provider': value_provider,
'is_value_provider': is_value_provider}
return {'is_value_provider': is_value_provider}
File diff suppressed because it is too large Load Diff
@@ -2271,7 +2271,7 @@ static const bvalue be_ktab_class_SymbolTable[55] = {
/* K23 */ be_nested_str_weak(mock_engine),
/* K24 */ be_nested_str_weak(color_provider),
/* K25 */ be_nested_str_weak(create_color_constructor),
/* K26 */ be_nested_str_weak(value_provider),
/* K26 */ be_nested_str_weak(is_value_provider),
/* K27 */ be_nested_str_weak(create_value_provider_constructor),
/* K28 */ be_nested_str_weak(create_animation_constructor),
/* K29 */ be_nested_str_weak(is_dangerous_call),
@@ -2424,7 +2424,7 @@ be_local_closure(class_SymbolTable__detect_and_cache_symbol, /* name */
&be_ktab_class_SymbolTable, /* shared constants */
be_str_weak(_detect_and_cache_symbol),
&be_const_str_solidified,
( &(const binstruction[179]) { /* code */
( &(const binstruction[178]) { /* code */
0xA40A0400, // 0000 IMPORT R2 K2
0x880C0107, // 0001 GETMBR R3 R0 K7
0x8C0C0708, // 0002 GETMET R3 R3 K8
@@ -2434,7 +2434,7 @@ be_local_closure(class_SymbolTable__detect_and_cache_symbol, /* name */
0x880C0107, // 0006 GETMBR R3 R0 K7
0x940C0601, // 0007 GETIDX R3 R3 R1
0x80040600, // 0008 RET 1 R3
0xA80200A1, // 0009 EXBLK 0 #00AC
0xA80200A0, // 0009 EXBLK 0 #00AB
0xA40E1200, // 000A IMPORT R3 K9
0x8810050A, // 000B GETMBR R4 R2 K10
0x8C100908, // 000C GETMET R4 R4 K8
@@ -2495,7 +2495,7 @@ be_local_closure(class_SymbolTable__detect_and_cache_symbol, /* name */
0xB81A1C00, // 0043 GETNGBL R6 K14
0x5C1C0200, // 0044 MOVE R7 R1
0x7C100600, // 0045 CALL R4 3
0x7812005F, // 0046 JMPF R4 #00A7
0x7812005E, // 0046 JMPF R4 #00A6
0xB8121C00, // 0047 GETNGBL R4 K14
0x88100801, // 0048 GETMBR R4 R4 R1
0x60140004, // 0049 GETGBL R5 G4
@@ -2531,8 +2531,8 @@ be_local_closure(class_SymbolTable__detect_and_cache_symbol, /* name */
0x1C180B15, // 0067 EQ R6 R5 K21
0x741A0001, // 0068 JMPT R6 #006B
0x1C180B16, // 0069 EQ R6 R5 K22
0x781A003B, // 006A JMPF R6 #00A7
0xA8020036, // 006B EXBLK 0 #00A3
0x781A003A, // 006A JMPF R6 #00A6
0xA8020035, // 006B EXBLK 0 #00A2
0x5C180800, // 006C MOVE R6 R4
0x881C0117, // 006D GETMBR R7 R0 K23
0x7C180200, // 006E CALL R6 1
@@ -2552,58 +2552,57 @@ be_local_closure(class_SymbolTable__detect_and_cache_symbol, /* name */
0x98200207, // 007C SETIDX R8 R1 R7
0xA8040002, // 007D EXBLK 1 2
0x80040E00, // 007E RET 1 R7
0x70020020, // 007F JMP #00A1
0x601C000F, // 0080 GETGBL R7 G15
0x5C200C00, // 0081 MOVE R8 R6
0xB8261C00, // 0082 GETNGBL R9 K14
0x8824131A, // 0083 GETMBR R9 R9 K26
0x7C1C0400, // 0084 CALL R7 2
0x781E000A, // 0085 JMPF R7 #0091
0x881C0503, // 0086 GETMBR R7 R2 K3
0x8C1C0F1B, // 0087 GETMET R7 R7 K27
0x5C240200, // 0088 MOVE R9 R1
0x5C280C00, // 0089 MOVE R10 R6
0x502C0200, // 008A LDBOOL R11 1 0
0x7C1C0800, // 008B CALL R7 4
0x88200107, // 008C GETMBR R8 R0 K7
0x98200207, // 008D SETIDX R8 R1 R7
0xA8040002, // 008E EXBLK 1 2
0x80040E00, // 008F RET 1 R7
0x7002000F, // 0090 JMP #00A1
0x601C000F, // 0091 GETGBL R7 G15
0x5C200C00, // 0092 MOVE R8 R6
0xB8261C00, // 0093 GETNGBL R9 K14
0x8824130E, // 0094 GETMBR R9 R9 K14
0x7C1C0400, // 0095 CALL R7 2
0x781E0009, // 0096 JMPF R7 #00A1
0x881C0503, // 0097 GETMBR R7 R2 K3
0x8C1C0F1C, // 0098 GETMET R7 R7 K28
0x5C240200, // 0099 MOVE R9 R1
0x5C280C00, // 009A MOVE R10 R6
0x502C0200, // 009B LDBOOL R11 1 0
0x7C1C0800, // 009C CALL R7 4
0x88200107, // 009D GETMBR R8 R0 K7
0x98200207, // 009E SETIDX R8 R1 R7
0xA8040002, // 009F EXBLK 1 2
0x80040E00, // 00A0 RET 1 R7
0xA8040001, // 00A1 EXBLK 1 1
0x70020003, // 00A2 JMP #00A7
0xAC180002, // 00A3 CATCH R6 0 2
0x7002001F, // 007F JMP #00A0
0xB81E1C00, // 0080 GETNGBL R7 K14
0x8C1C0F1A, // 0081 GETMET R7 R7 K26
0x5C240C00, // 0082 MOVE R9 R6
0x7C1C0400, // 0083 CALL R7 2
0x781E000A, // 0084 JMPF R7 #0090
0x881C0503, // 0085 GETMBR R7 R2 K3
0x8C1C0F1B, // 0086 GETMET R7 R7 K27
0x5C240200, // 0087 MOVE R9 R1
0x5C280C00, // 0088 MOVE R10 R6
0x502C0200, // 0089 LDBOOL R11 1 0
0x7C1C0800, // 008A CALL R7 4
0x88200107, // 008B GETMBR R8 R0 K7
0x98200207, // 008C SETIDX R8 R1 R7
0xA8040002, // 008D EXBLK 1 2
0x80040E00, // 008E RET 1 R7
0x7002000F, // 008F JMP #00A0
0x601C000F, // 0090 GETGBL R7 G15
0x5C200C00, // 0091 MOVE R8 R6
0xB8261C00, // 0092 GETNGBL R9 K14
0x8824130E, // 0093 GETMBR R9 R9 K14
0x7C1C0400, // 0094 CALL R7 2
0x781E0009, // 0095 JMPF R7 #00A0
0x881C0503, // 0096 GETMBR R7 R2 K3
0x8C1C0F1C, // 0097 GETMET R7 R7 K28
0x5C240200, // 0098 MOVE R9 R1
0x5C280C00, // 0099 MOVE R10 R6
0x502C0200, // 009A LDBOOL R11 1 0
0x7C1C0800, // 009B CALL R7 4
0x88200107, // 009C GETMBR R8 R0 K7
0x98200207, // 009D SETIDX R8 R1 R7
0xA8040002, // 009E EXBLK 1 2
0x80040E00, // 009F RET 1 R7
0xA8040001, // 00A0 EXBLK 1 1
0x70020003, // 00A1 JMP #00A6
0xAC180002, // 00A2 CATCH R6 0 2
0x70020000, // 00A3 JMP #00A5
0x70020000, // 00A4 JMP #00A6
0x70020000, // 00A5 JMP #00A7
0xB0080000, // 00A6 RAISE 2 R0 R0
0x4C100000, // 00A7 LDNIL R4
0xA8040001, // 00A8 EXBLK 1 1
0x80040800, // 00A9 RET 1 R4
0xA8040001, // 00AA EXBLK 1 1
0x70020005, // 00AB JMP #00B2
0xAC0C0002, // 00AC CATCH R3 0 2
0x70020002, // 00AD JMP #00B1
0x4C140000, // 00AE LDNIL R5
0x80040A00, // 00AF RET 1 R5
0x70020000, // 00B0 JMP #00B2
0xB0080000, // 00B1 RAISE 2 R0 R0
0x80000000, // 00B2 RET 0
0xB0080000, // 00A5 RAISE 2 R0 R0
0x4C100000, // 00A6 LDNIL R4
0xA8040001, // 00A7 EXBLK 1 1
0x80040800, // 00A8 RET 1 R4
0xA8040001, // 00A9 EXBLK 1 1
0x70020005, // 00AA JMP #00B1
0xAC0C0002, // 00AB CATCH R3 0 2
0x70020002, // 00AC JMP #00B0
0x4C140000, // 00AD LDNIL R5
0x80040A00, // 00AE RET 1 R5
0x70020000, // 00AF JMP #00B1
0xB0080000, // 00B0 RAISE 2 R0 R0
0x80000000, // 00B1 RET 0
})
)
);
@@ -1,9 +1,10 @@
# Test file for Breathe Color Provider
#
# This file contains tests for the breathe_color class following parameterized class specification
# breathe_color now inherits from color_provider and uses an internal oscillator
#
# Command to run test is:
# ./berry -s -g -m lib/libesp32/berry_animation -e "import tasmota" lib/libesp32/berry_animation/tests/breathe_color_provider_test.be
# ./berry -s -g -m lib/libesp32/berry_animation/src -e "import tasmota def log(x) print(x) end import animation import animation_dsl " lib/libesp32/berry_animation/src/tests/breathe_color_provider_test.be
print("Testing breathe_color...")
@@ -24,25 +25,24 @@ print("Created breathe color provider with defaults")
print(f"Default color: 0x{provider.color :08x}")
print(f"Default min_brightness: {provider.min_brightness}")
print(f"Default max_brightness: {provider.max_brightness}")
print(f"Default duration: {provider.duration}")
print(f"Default period: {provider.period}")
print(f"Default curve_factor: {provider.curve_factor}")
print(f"Default form: {provider.form}")
# Verify inherited oscillator defaults
print(f"Inherited min_value: {provider.min_value}")
print(f"Inherited max_value: {provider.max_value}")
# Verify it inherits from color_provider
assert(animation.is_color_provider(provider), "breathe_color should be a color_provider")
print("✓ breathe_color is a color_provider")
# Create another breathe color provider and set custom parameters using virtual member assignment
var blue_breathe = animation.breathe_color(engine)
blue_breathe.color = 0xFF0000FF
blue_breathe.min_brightness = 20
blue_breathe.max_brightness = 200
blue_breathe.duration = 4000
blue_breathe.period = 4000
blue_breathe.curve_factor = 3
print(f"Blue breathe color provider color: 0x{blue_breathe.color :08x}")
print(f"Blue breathe color provider min_brightness: {blue_breathe.min_brightness}")
print(f"Blue breathe color provider max_brightness: {blue_breathe.max_brightness}")
print(f"Blue breathe color provider duration: {blue_breathe.duration}")
print(f"Blue breathe color provider period: {blue_breathe.period}")
print(f"Blue breathe color provider curve_factor: {blue_breathe.curve_factor}")
# Create red breathe color provider with different parameters
@@ -50,18 +50,18 @@ var red_breathe = animation.breathe_color(engine)
red_breathe.color = 0xFFFF0000
red_breathe.min_brightness = 10
red_breathe.max_brightness = 180
red_breathe.duration = 3000
red_breathe.period = 3000
red_breathe.curve_factor = 2
print(f"Red breathe color provider color: 0x{red_breathe.color :08x}")
# Test parameter updates using virtual member assignment
blue_breathe.min_brightness = 30
blue_breathe.max_brightness = 220
blue_breathe.duration = 3500
blue_breathe.period = 3500
blue_breathe.curve_factor = 4
print(f"Updated blue breathe min_brightness: {blue_breathe.min_brightness}")
print(f"Updated blue breathe max_brightness: {blue_breathe.max_brightness}")
print(f"Updated blue breathe duration: {blue_breathe.duration}")
print(f"Updated blue breathe period: {blue_breathe.period}")
print(f"Updated blue breathe curve_factor: {blue_breathe.curve_factor}")
# Test start method using engine time
@@ -71,35 +71,35 @@ blue_breathe.start(start_time)
blue_breathe.produce_value(nil, start_time) # force first tick
print(f"Started blue breathe color provider at time: {start_time}")
# Cache duration for performance (following specification)
var current_duration = blue_breathe.duration
# Cache period for performance (following specification)
var current_period = blue_breathe.period
# Test produce_value method at different points in the cycle
engine.time_ms = start_time + (current_duration / 10)
engine.time_ms = start_time + (current_period / 10)
var color_1_10 = blue_breathe.produce_value("color", engine.time_ms)
print(f"Color at 1/10 cycle: 0x{color_1_10 :08x}")
engine.time_ms = start_time + (current_duration / 8)
engine.time_ms = start_time + (current_period / 8)
var color_1_8 = blue_breathe.produce_value("color", engine.time_ms)
print(f"Color at 1/8 cycle: 0x{color_1_8 :08x}")
engine.time_ms = start_time + (3 * current_duration / 10)
engine.time_ms = start_time + (3 * current_period / 10)
var color_3_10 = blue_breathe.produce_value("color", engine.time_ms)
print(f"Color at 3/10 cycle: 0x{color_3_10 :08x}")
engine.time_ms = start_time + (current_duration / 4)
engine.time_ms = start_time + (current_period / 4)
var color_1_4 = blue_breathe.produce_value("color", engine.time_ms)
print(f"Color at 1/4 cycle: 0x{color_1_4 :08x}")
engine.time_ms = start_time + (current_duration / 2)
engine.time_ms = start_time + (current_period / 2)
var color_1_2 = blue_breathe.produce_value("color", engine.time_ms)
print(f"Color at 1/2 cycle: 0x{color_1_2 :08x}")
engine.time_ms = start_time + (3 * current_duration / 4)
engine.time_ms = start_time + (3 * current_period / 4)
var color_3_4 = blue_breathe.produce_value("color", engine.time_ms)
print(f"Color at 3/4 cycle: 0x{color_3_4 :08x}")
engine.time_ms = start_time + current_duration
engine.time_ms = start_time + current_period
var color_full = blue_breathe.produce_value("color", engine.time_ms)
print(f"Color at full cycle: 0x{color_full :08x}")
@@ -107,7 +107,7 @@ print(f"Color at full cycle: 0x{color_full :08x}")
var curve_1_provider = animation.breathe_color(engine)
curve_1_provider.color = 0xFF00FF00 # Green
curve_1_provider.curve_factor = 1
curve_1_provider.duration = 2000
curve_1_provider.period = 2000
curve_1_provider.min_brightness = 50 # Set non-zero minimum to see differences
curve_1_provider.max_brightness = 255
curve_1_provider.start(engine.time_ms)
@@ -116,7 +116,7 @@ curve_1_provider.produce_value(nil, start_time) # force first tick
var curve_5_provider = animation.breathe_color(engine)
curve_5_provider.color = 0xFF00FF00 # Green
curve_5_provider.curve_factor = 5
curve_5_provider.duration = 2000
curve_5_provider.period = 2000
curve_5_provider.min_brightness = 50 # Set non-zero minimum to see differences
curve_5_provider.max_brightness = 255
curve_5_provider.start(engine.time_ms)
@@ -164,7 +164,7 @@ var brightness_test = animation.breathe_color(engine)
brightness_test.color = 0xFFFFFFFF # White
brightness_test.min_brightness = 50
brightness_test.max_brightness = 200
brightness_test.duration = 1000
brightness_test.period = 1000
brightness_test.start(engine.time_ms)
brightness_test.produce_value(nil, start_time) # force first tick
@@ -199,11 +199,8 @@ assert(red_breathe != nil, "Red breathe color provider should be created")
assert(blue_breathe.color == 0xFF0000FF, "Blue breathe should have correct color")
assert(blue_breathe.min_brightness == 30, "Min brightness should be updated to 30")
assert(blue_breathe.max_brightness == 220, "Max brightness should be updated to 220")
assert(blue_breathe.duration == 3500, "Duration should be updated to 3500")
assert(blue_breathe.period == 3500, "Period should be updated to 3500")
assert(blue_breathe.curve_factor == 4, "Curve factor should be updated to 4")
assert(blue_breathe.form == 4 #-COSINE-#, "Form should be COSINE")
assert(blue_breathe.min_value == 0, "Inherited min_value should be 0")
assert(blue_breathe.max_value == 255, "Inherited max_value should be 255")
assert(blue_breathe.engine == engine, "Provider should have correct engine reference")
assert(alpha_actual == 128, "Alpha channel should be preserved")
@@ -219,4 +216,4 @@ assert(min_brightness_actual >= 45 && min_brightness_actual <= 65, "Min brightne
assert(max_brightness_actual >= 150 && max_brightness_actual <= 170, "Max brightness should be around 160")
print("All tests completed successfully!")
return true
return true
@@ -79,7 +79,8 @@ def test_get_param_value_with_generic_provider()
test_anim.opacity = 255
# Create a generic value_provider that we can track calls on
class Trackingvalue_provider : animation.value_provider
class Trackingvalue_provider : animation.parameterized_object
static var VALUE_PROVIDER = true
var value
var produce_value_called
@@ -125,7 +126,8 @@ def test_get_param_value_with_context_aware_provider()
test_anim.opacity = 255
# Create a value_provider that returns different values based on parameter name
class ContextAwareProvider : animation.value_provider
class ContextAwareProvider : animation.parameterized_object
static var VALUE_PROVIDER = true
var base_value
var produce_value_called
var last_param_name
@@ -162,7 +162,8 @@ def test_value_provider_as_parameter()
var obj = TestClass(mock_engine)
# Create a mock value_provider
class Mockvalue_provider : animation.value_provider
class Mockvalue_provider : animation.parameterized_object
static var VALUE_PROVIDER = true
var test_value
def init(engine, value)
super(self).init(engine)
@@ -1,4 +1,4 @@
# Demonstration: RichPaletteColorProvider with dynamic brightness
# Demonstration: rich_palette_color with dynamic brightness
#
# This demo shows how the LUT optimization now works correctly with
# animations that have time-varying brightness, such as breathing effects.
@@ -35,7 +35,7 @@ var rainbow_palette = bytes(
)
# Create the color provider
var provider = animation.rich_palette(engine)
var provider = animation.rich_palette_color(engine)
provider.colors = rainbow_palette
provider.period = 0 # Value-based mode for gradient
provider.brightness = 255
@@ -1,4 +1,4 @@
# Test for RichPaletteColorProvider dynamic brightness
# Test for rich_palette_color dynamic brightness
#
# This test verifies that brightness can change over time without
# invalidating the LUT cache, which is critical for animations
@@ -14,7 +14,7 @@ end
# Create a test engine
var engine = animation.init_strip()
log("=== RichPaletteColorProvider Dynamic Brightness Test ===")
log("=== rich_palette_color Dynamic Brightness Test ===")
log("")
# Test 1: Verify brightness changes don't invalidate LUT
@@ -28,7 +28,7 @@ var rgb_palette = bytes(
"FFFFFF00" # Value 255: Yellow
)
var provider = animation.rich_palette(engine)
var provider = animation.rich_palette_color(engine)
provider.colors = rgb_palette
provider.period = 0 # Static mode
@@ -142,7 +142,7 @@ log("Test 4: LUT rebuild verification")
log("---------------------------------")
# Create a fresh provider
var rebuild_provider = animation.rich_palette(engine)
var rebuild_provider = animation.rich_palette_color(engine)
rebuild_provider.colors = rgb_palette
rebuild_provider.period = 0
@@ -1,4 +1,4 @@
# Test for RichPaletteColorProvider LUT optimization
# Test for rich_palette_color LUT optimization
#
# This test verifies that the LUT cache produces correct colors
# and measures the performance improvement
@@ -13,7 +13,7 @@ end
# Create a test engine
var engine = animation.init_strip()
log("=== RichPaletteColorProvider LUT Cache Test ===")
log("=== rich_palette_color LUT Cache Test ===")
log("")
# Test 1: Verify LUT produces correct colors
@@ -41,7 +41,7 @@ while i < size(rainbow_palette)
end
log("")
var provider = animation.rich_palette(engine)
var provider = animation.rich_palette_color(engine)
provider.colors = rainbow_palette
provider.period = 0 # Static mode for testing
@@ -96,7 +96,7 @@ log("Test 3: Performance measurement")
log("-------------------------------")
# Create a fresh provider for performance testing
var perf_provider = animation.rich_palette(engine)
var perf_provider = animation.rich_palette_color(engine)
perf_provider.colors = rainbow_palette
perf_provider.period = 0
@@ -1,12 +1,23 @@
# Test suite for value_provider base class
# Test suite for value_provider functionality
#
# This test verifies that the base value_provider class works correctly
# and follows the parameterized class specification with produce_value() API.
# This test verifies that value providers work correctly
# and follow the parameterized class specification with produce_value() API.
# Note: value_provider class has been removed; providers now inherit from parameterized_object
# with VALUE_PROVIDER = true.
import animation
import "./core/param_encoder" as encode_constraints
# Simple test provider class (since value_provider class was removed)
class TestValueProvider : animation.parameterized_object
static var VALUE_PROVIDER = true
def produce_value(name, time_ms)
return module("undefined") # Default behavior
end
end
# Test the basic value_provider interface
def test_value_provider_interface()
print("Testing value_provider interface...")
@@ -15,7 +26,7 @@ def test_value_provider_interface()
var strip = global.Leds()
var engine = animation.create_engine(strip)
var provider = animation.value_provider(engine)
var provider = TestValueProvider(engine)
# Test default produce_value method
var result = provider.produce_value("test_param", 1000)
@@ -37,7 +48,8 @@ def test_custom_value_provider()
var engine = animation.create_engine(strip)
# Create a simple time-based provider using new API
class TimeBasedProvider : animation.value_provider
class TimeBasedProvider : animation.parameterized_object
static var VALUE_PROVIDER = true
# Parameter definitions
static var PARAMS = animation.enc_params({
"multiplier": {"default": 1}
@@ -75,7 +87,7 @@ def test_is_value_provider()
var strip = global.Leds()
var engine = animation.create_engine(strip)
var base_provider = animation.value_provider(engine)
var base_provider = TestValueProvider(engine)
assert(animation.is_value_provider(base_provider) == true, "value_provider should be detected")
assert(animation.is_value_provider(42) == false, "Integer should not be detected")
@@ -93,7 +105,7 @@ def test_parameterized_object_integration()
var strip = global.Leds()
var engine = animation.create_engine(strip)
var provider = animation.value_provider(engine)
var provider = TestValueProvider(engine)
# Test that it has the engine reference
assert(provider.engine != nil, "Provider should have engine reference")
@@ -118,7 +130,8 @@ def test_lifecycle_methods()
var engine = animation.create_engine(strip)
# Create a provider that tracks start calls
class LifecycleProvider : animation.value_provider
class LifecycleProvider : animation.parameterized_object
static var VALUE_PROVIDER = true
var start_called
var start_time