Berry animation native frame_buffer and optimization (#23986)

This commit is contained in:
s-hadinger 2025-10-07 22:09:45 +02:00 committed by GitHub
parent 217bc7e3a5
commit 0b891392b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
81 changed files with 7987 additions and 7358 deletions

View File

@ -490,7 +490,7 @@ import animation
def test_my_animation() def test_my_animation()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(10) # Use built-in LED strip for testing var strip = global.Leds(10) # Use built-in LED strip for testing
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test basic construction # Test basic construction
var anim = animation.my_animation(engine) var anim = animation.my_animation(engine)
@ -532,7 +532,7 @@ Test with the animation engine:
```berry ```berry
var strip = global.Leds(30) # Use built-in LED strip var strip = global.Leds(30) # Use built-in LED strip
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var anim = animation.my_animation(engine) var anim = animation.my_animation(engine)
# Set parameters # Set parameters

View File

@ -19,7 +19,7 @@ var globs = "path,ctypes_bytes_dyn,tasmota,ccronexpr,gpio,light,webclient,load,M
for g:string2.split(globs, ",") for g:string2.split(globs, ",")
global.(g) = nil global.(g) = nil
end end
# special case to declane animation # special case to declare animation
global.animation = module("animation") global.animation = module("animation")
var prefix_dir = "src/" var prefix_dir = "src/"

View File

@ -17,18 +17,20 @@
# 2: `slew_size`, number of pixels to fade from back to fore color, can be `0` # 2: `slew_size`, number of pixels to fade from back to fore color, can be `0`
# 3: `beacon_size`, number of pixels of the beacon # 3: `beacon_size`, number of pixels of the beacon
import "./core/param_encoder" as encode_constraints
#@ solidify:BeaconAnimation,weak #@ solidify:BeaconAnimation,weak
class BeaconAnimation : animation.animation class BeaconAnimation : animation.animation
# NO instance variables for parameters - they are handled by the virtual parameter system # NO instance variables for parameters - they are handled by the virtual parameter system
# Parameter definitions following the new specification # Parameter definitions following the new specification
static var PARAMS = { static var PARAMS = encode_constraints({
"color": {"default": 0xFFFFFFFF}, "color": {"default": 0xFFFFFFFF},
"back_color": {"default": 0xFF000000}, "back_color": {"default": 0xFF000000},
"pos": {"default": 0}, "pos": {"default": 0},
"beacon_size": {"min": 0, "default": 1}, "beacon_size": {"min": 0, "default": 1},
"slew_size": {"min": 0, "default": 0} "slew_size": {"min": 0, "default": 0}
} })
# Render the beacon to the provided frame buffer # Render the beacon to the provided frame buffer
# #

View File

@ -8,19 +8,21 @@
# - curve_factor 1: Pure cosine wave (equivalent to pulse animation) # - curve_factor 1: Pure cosine wave (equivalent to pulse animation)
# - curve_factor 2-5: Natural breathing with pauses at peaks (5 = most pronounced pauses) # - curve_factor 2-5: Natural breathing with pauses at peaks (5 = most pronounced pauses)
import "./core/param_encoder" as encode_constraints
#@ solidify:BreatheAnimation,weak #@ solidify:BreatheAnimation,weak
class BreatheAnimation : animation.animation class BreatheAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
var breathe_provider # Internal breathe color provider var breathe_provider # Internal breathe color provider
# Parameter definitions following parameterized class specification # Parameter definitions following parameterized class specification
static var PARAMS = { static var PARAMS = encode_constraints({
"base_color": {"default": 0xFFFFFFFF}, # The base color to breathe (32-bit ARGB value) "base_color": {"default": 0xFFFFFFFF}, # The base color to breathe (32-bit ARGB value)
"min_brightness": {"min": 0, "max": 255, "default": 0}, # Minimum brightness level (0-255) "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) "max_brightness": {"min": 0, "max": 255, "default": 255}, # Maximum brightness level (0-255)
"period": {"min": 100, "default": 3000}, # Time for one complete breathe cycle in milliseconds "period": {"min": 100, "default": 3000}, # Time for one complete breathe cycle in milliseconds
"curve_factor": {"min": 1, "max": 5, "default": 2} # Factor to control breathing curve shape (1=cosine wave, 2-5=curved breathing with pauses) "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 animation # Initialize a new Breathe animation
# Following parameterized class specification - engine parameter only # Following parameterized class specification - engine parameter only

View File

@ -6,20 +6,22 @@
# The comet uses sub-pixel positioning (1/256th pixels) for smooth movement and supports # The comet uses sub-pixel positioning (1/256th pixels) for smooth movement and supports
# both wrapping around the strip and bouncing off the ends. # both wrapping around the strip and bouncing off the ends.
import "./core/param_encoder" as encode_constraints
#@ solidify:CometAnimation,weak #@ solidify:CometAnimation,weak
class CometAnimation : animation.animation class CometAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
var head_position # Current position of the comet head (in 1/256th pixels for smooth movement) var head_position # Current position of the comet head (in 1/256th pixels for smooth movement)
# Parameter definitions following parameterized class specification # Parameter definitions following parameterized class specification
static var PARAMS = { static var PARAMS = encode_constraints({
# 'color' for the comet head (32-bit ARGB value), inherited from animation class # 'color' for the comet head (32-bit ARGB value), inherited from animation class
"tail_length": {"min": 1, "max": 50, "default": 5}, # Length of the comet tail in pixels "tail_length": {"min": 1, "max": 50, "default": 5}, # Length of the comet tail in pixels
"speed": {"min": 1, "max": 25600, "default": 2560}, # Movement speed in 1/256th pixels per second "speed": {"min": 1, "max": 25600, "default": 2560}, # Movement speed in 1/256th pixels per second
"direction": {"enum": [-1, 1], "default": 1}, # Direction of movement (1 = forward, -1 = backward) "direction": {"enum": [-1, 1], "default": 1}, # Direction of movement (1 = forward, -1 = backward)
"wrap_around": {"min": 0, "max": 1, "default": 1}, # Whether comet wraps around the strip (bool) "wrap_around": {"min": 0, "max": 1, "default": 1}, # Whether comet wraps around the strip (bool)
"fade_factor": {"min": 0, "max": 255, "default": 179} # How quickly the tail fades (0-255, 255 = no fade) "fade_factor": {"min": 0, "max": 255, "default": 179} # How quickly the tail fades (0-255, 255 = no fade)
} })
# Initialize a new Comet animation # Initialize a new Comet animation
# Following parameterized class specification - engine parameter only # Following parameterized class specification - engine parameter only

View File

@ -18,19 +18,21 @@
# 3: `low_size`, number of pixel until next pos - full cycle is 2 + 3 # 3: `low_size`, number of pixel until next pos - full cycle is 2 + 3
# 4: `nb_pulse`, number of pulses, or `-1` for infinite # 4: `nb_pulse`, number of pulses, or `-1` for infinite
import "./core/param_encoder" as encode_constraints
#@ solidify:CrenelPositionAnimation,weak #@ solidify:CrenelPositionAnimation,weak
class CrenelPositionAnimation : animation.animation class CrenelPositionAnimation : animation.animation
# NO instance variables for parameters - they are handled by the virtual parameter system # NO instance variables for parameters - they are handled by the virtual parameter system
# Parameter definitions with constraints # Parameter definitions with constraints
static var PARAMS = { static var PARAMS = encode_constraints({
# 'color' for the comet head (32-bit ARGB value), inherited from animation class # 'color' for the comet head (32-bit ARGB value), inherited from animation class
"back_color": {"default": 0xFF000000}, # background color, TODO change to transparent "back_color": {"default": 0xFF000000}, # background color, TODO change to transparent
"pos": {"default": 0}, # start of the pulse (in pixel) "pos": {"default": 0}, # start of the pulse (in pixel)
"pulse_size": {"min": 0, "default": 1}, # number of pixels of the pulse "pulse_size": {"min": 0, "default": 1}, # number of pixels of the pulse
"low_size": {"min": 0, "default": 3}, # number of pixel until next pos - full cycle is 2 + 3 "low_size": {"min": 0, "default": 3}, # number of pixel until next pos - full cycle is 2 + 3
"nb_pulse": {"default": -1} # number of pulses, or `-1` for infinite "nb_pulse": {"default": -1} # number of pulses, or `-1` for infinite
} })
# Render the crenel pattern to the provided frame buffer # Render the crenel pattern to the provided frame buffer
# #

View File

@ -3,6 +3,8 @@
# This animation creates a realistic fire effect with flickering flames. # This animation creates a realistic fire effect with flickering flames.
# The fire uses random intensity variations and warm colors to simulate flames. # The fire uses random intensity variations and warm colors to simulate flames.
import "./core/param_encoder" as encode_constraints
#@ solidify:FireAnimation,weak #@ solidify:FireAnimation,weak
class FireAnimation : animation.animation class FireAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
@ -12,14 +14,14 @@ class FireAnimation : animation.animation
var random_seed # Seed for random number generation var random_seed # Seed for random number generation
# Parameter definitions following parameterized class specification # Parameter definitions following parameterized class specification
static var PARAMS = { static var PARAMS = encode_constraints({
# 'color' for the comet head (32-bit ARGB value), inherited from animation class # 'color' for the comet head (32-bit ARGB value), inherited from animation class
"intensity": {"min": 0, "max": 255, "default": 180}, "intensity": {"min": 0, "max": 255, "default": 180},
"flicker_speed": {"min": 1, "max": 20, "default": 8}, "flicker_speed": {"min": 1, "max": 20, "default": 8},
"flicker_amount": {"min": 0, "max": 255, "default": 100}, "flicker_amount": {"min": 0, "max": 255, "default": 100},
"cooling_rate": {"min": 0, "max": 255, "default": 55}, "cooling_rate": {"min": 0, "max": 255, "default": 55},
"sparking_rate": {"min": 0, "max": 255, "default": 120} "sparking_rate": {"min": 0, "max": 255, "default": 120}
} })
# Initialize a new Fire animation # Initialize a new Fire animation
# #

View File

@ -3,6 +3,8 @@
# This animation creates smooth color gradients that can be linear or radial, # This animation creates smooth color gradients that can be linear or radial,
# with optional movement and color transitions over time. # with optional movement and color transitions over time.
import "./core/param_encoder" as encode_constraints
#@ solidify:GradientAnimation,weak #@ solidify:GradientAnimation,weak
class GradientAnimation : animation.animation class GradientAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
@ -10,14 +12,14 @@ class GradientAnimation : animation.animation
var phase_offset # Current phase offset for movement var phase_offset # Current phase offset for movement
# Parameter definitions following parameterized class specification # Parameter definitions following parameterized class specification
static var PARAMS = { static var PARAMS = encode_constraints({
"color": {"default": nil, "nillable": true}, "color": {"default": nil, "nillable": true},
"gradient_type": {"min": 0, "max": 1, "default": 0}, "gradient_type": {"min": 0, "max": 1, "default": 0},
"direction": {"min": 0, "max": 255, "default": 0}, "direction": {"min": 0, "max": 255, "default": 0},
"center_pos": {"min": 0, "max": 255, "default": 128}, "center_pos": {"min": 0, "max": 255, "default": 128},
"spread": {"min": 1, "max": 255, "default": 255}, "spread": {"min": 1, "max": 255, "default": 255},
"movement_speed": {"min": 0, "max": 255, "default": 0} "movement_speed": {"min": 0, "max": 255, "default": 0}
} })
# Initialize a new Gradient animation # Initialize a new Gradient animation
def init(engine) def init(engine)

View File

@ -3,6 +3,8 @@
# This animation creates pseudo-random noise patterns with configurable # This animation creates pseudo-random noise patterns with configurable
# scale, speed, and color mapping through palettes or single colors. # scale, speed, and color mapping through palettes or single colors.
import "./core/param_encoder" as encode_constraints
#@ solidify:NoiseAnimation,weak #@ solidify:NoiseAnimation,weak
class NoiseAnimation : animation.animation class NoiseAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
@ -11,14 +13,14 @@ class NoiseAnimation : animation.animation
var noise_table # Pre-computed noise values for performance var noise_table # Pre-computed noise values for performance
# Parameter definitions following new specification # Parameter definitions following new specification
static var PARAMS = { static var PARAMS = encode_constraints({
"color": {"default": nil}, "color": {"default": nil},
"scale": {"min": 1, "max": 255, "default": 50}, "scale": {"min": 1, "max": 255, "default": 50},
"speed": {"min": 0, "max": 255, "default": 30}, "speed": {"min": 0, "max": 255, "default": 30},
"octaves": {"min": 1, "max": 4, "default": 1}, "octaves": {"min": 1, "max": 4, "default": 1},
"persistence": {"min": 0, "max": 255, "default": 128}, "persistence": {"min": 0, "max": 255, "default": 128},
"seed": {"min": 0, "max": 65535, "default": 12345} "seed": {"min": 0, "max": 65535, "default": 12345}
} })
# Initialize a new Noise animation # Initialize a new Noise animation
def init(engine) def init(engine)

View File

@ -6,16 +6,18 @@
# This version supports both RichPaletteAnimation and ColorProvider instances as color sources, # This version supports both RichPaletteAnimation and ColorProvider instances as color sources,
# allowing for more flexible usage of color providers. # allowing for more flexible usage of color providers.
import "./core/param_encoder" as encode_constraints
#@ solidify:PalettePatternAnimation,weak #@ solidify:PalettePatternAnimation,weak
class PalettePatternAnimation : animation.animation class PalettePatternAnimation : animation.animation
var value_buffer # Buffer to store values for each pixel (bytes object) var value_buffer # Buffer to store values for each pixel (bytes object)
# Static definitions of parameters with constraints # Static definitions of parameters with constraints
static var PARAMS = { static var PARAMS = encode_constraints({
# Palette pattern-specific parameters # Palette pattern-specific parameters
"color_source": {"default": nil, "type": "instance"}, "color_source": {"default": nil, "type": "instance"},
"pattern_func": {"default": nil, "type": "function"} "pattern_func": {"default": nil, "type": "function"}
} })
# Initialize a new PalettePattern animation # Initialize a new PalettePattern animation
# #
@ -158,11 +160,11 @@ end
#@ solidify:PaletteWaveAnimation,weak #@ solidify:PaletteWaveAnimation,weak
class PaletteWaveAnimation : PalettePatternAnimation class PaletteWaveAnimation : PalettePatternAnimation
# Static definitions of parameters with constraints # Static definitions of parameters with constraints
static var PARAMS = { static var PARAMS = encode_constraints({
# Wave-specific parameters only # Wave-specific parameters only
"wave_period": {"min": 1, "default": 5000}, "wave_period": {"min": 1, "default": 5000},
"wave_length": {"min": 1, "default": 10} "wave_length": {"min": 1, "default": 10}
} })
# Initialize a new wave pattern animation # Initialize a new wave pattern animation
# #
@ -211,12 +213,12 @@ end
#@ solidify:PaletteGradientAnimation,weak #@ solidify:PaletteGradientAnimation,weak
class PaletteGradientAnimation : PalettePatternAnimation class PaletteGradientAnimation : PalettePatternAnimation
# Static definitions of parameters with constraints # Static definitions of parameters with constraints
static var PARAMS = { static var PARAMS = encode_constraints({
# Gradient-specific parameters only # Gradient-specific parameters only
"shift_period": {"min": 0, "default": 0}, # Time for one complete shift cycle in ms (0 = static) "shift_period": {"min": 0, "default": 0}, # Time for one complete shift cycle in ms (0 = static)
"spatial_period": {"min": 0, "default": 0}, # Spatial period in pixels (0 = full strip) "spatial_period": {"min": 0, "default": 0}, # Spatial period in pixels (0 = full strip)
"phase_shift": {"min": 0, "max": 100, "default": 0} # Phase shift as percentage (0-100) "phase_shift": {"min": 0, "max": 100, "default": 0} # Phase shift as percentage (0-100)
} })
# Initialize a new gradient pattern animation # Initialize a new gradient pattern animation
# #
@ -273,10 +275,10 @@ end
#@ solidify:PaletteMeterAnimation,weak #@ solidify:PaletteMeterAnimation,weak
class PaletteMeterAnimation : PalettePatternAnimation class PaletteMeterAnimation : PalettePatternAnimation
# Static definitions of parameters with constraints # Static definitions of parameters with constraints
static var PARAMS = { static var PARAMS = encode_constraints({
# Meter-specific parameters only # Meter-specific parameters only
"value_func": {"default": nil, "type": "function"} "value_func": {"default": nil, "type": "function"}
} })
# Initialize a new meter pattern animation # Initialize a new meter pattern animation
# #

View File

@ -7,13 +7,15 @@
# #
# Follows the parameterized class specification with parameter forwarding pattern. # Follows the parameterized class specification with parameter forwarding pattern.
import "./core/param_encoder" as encode_constraints
#@ solidify:RichPaletteAnimation,weak #@ solidify:RichPaletteAnimation,weak
class RichPaletteAnimation : animation.animation class RichPaletteAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
var color_provider # Internal RichPaletteColorProvider instance var color_provider # Internal RichPaletteColorProvider instance
# Parameter definitions - only RichPaletteColorProvider parameters (Animation params inherited) # Parameter definitions - only RichPaletteColorProvider parameters (Animation params inherited)
static var PARAMS = { static var PARAMS = encode_constraints({
# RichPaletteColorProvider parameters (forwarded to internal provider) # RichPaletteColorProvider parameters (forwarded to internal provider)
"palette": {"type": "instance", "default": nil}, "palette": {"type": "instance", "default": nil},
"cycle_period": {"min": 0, "default": 5000}, "cycle_period": {"min": 0, "default": 5000},
@ -21,7 +23,7 @@ class RichPaletteAnimation : animation.animation
"brightness": {"min": 0, "max": 255, "default": 255}, "brightness": {"min": 0, "max": 255, "default": 255},
"range_min": {"default": 0}, "range_min": {"default": 0},
"range_max": {"default": 255} "range_max": {"default": 255}
} })
# Initialize a new RichPaletteAnimation # Initialize a new RichPaletteAnimation
# #

View File

@ -3,6 +3,8 @@
# This animation creates a twinkling stars effect with random lights # This animation creates a twinkling stars effect with random lights
# appearing and fading at different positions with customizable density and timing. # appearing and fading at different positions with customizable density and timing.
import "./core/param_encoder" as encode_constraints
#@ solidify:TwinkleAnimation,weak #@ solidify:TwinkleAnimation,weak
class TwinkleAnimation : animation.animation class TwinkleAnimation : animation.animation
# NO instance variables for parameters - they are handled by the virtual parameter system # NO instance variables for parameters - they are handled by the virtual parameter system
@ -14,14 +16,14 @@ class TwinkleAnimation : animation.animation
var random_seed # Seed for random number generation var random_seed # Seed for random number generation
# Parameter definitions with constraints # Parameter definitions with constraints
static var PARAMS = { static var PARAMS = encode_constraints({
"color": {"default": 0xFFFFFFFF}, "color": {"default": 0xFFFFFFFF},
"density": {"min": 0, "max": 255, "default": 128}, "density": {"min": 0, "max": 255, "default": 128},
"twinkle_speed": {"min": 1, "max": 5000, "default": 6}, "twinkle_speed": {"min": 1, "max": 5000, "default": 6},
"fade_speed": {"min": 0, "max": 255, "default": 180}, "fade_speed": {"min": 0, "max": 255, "default": 180},
"min_brightness": {"min": 0, "max": 255, "default": 32}, "min_brightness": {"min": 0, "max": 255, "default": 32},
"max_brightness": {"min": 0, "max": 255, "default": 255} "max_brightness": {"min": 0, "max": 255, "default": 255}
} })
# Initialize a new Twinkle animation # Initialize a new Twinkle animation
# #

View File

@ -3,6 +3,8 @@
# This animation creates various wave patterns (sine, triangle, square, sawtooth) # This animation creates various wave patterns (sine, triangle, square, sawtooth)
# with configurable amplitude, frequency, phase, and movement speed. # with configurable amplitude, frequency, phase, and movement speed.
import "./core/param_encoder" as encode_constraints
#@ solidify:WaveAnimation,weak #@ solidify:WaveAnimation,weak
class WaveAnimation : animation.animation class WaveAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
@ -11,7 +13,7 @@ class WaveAnimation : animation.animation
var wave_table # Pre-computed wave table for performance var wave_table # Pre-computed wave table for performance
# Parameter definitions for WaveAnimation # Parameter definitions for WaveAnimation
static var PARAMS = { static var PARAMS = encode_constraints({
"color": {"default": 0xFFFF0000}, "color": {"default": 0xFFFF0000},
"back_color": {"default": 0xFF000000}, "back_color": {"default": 0xFF000000},
"wave_type": {"min": 0, "max": 3, "default": 0}, "wave_type": {"min": 0, "max": 3, "default": 0},
@ -20,7 +22,7 @@ class WaveAnimation : animation.animation
"phase": {"min": 0, "max": 255, "default": 0}, "phase": {"min": 0, "max": 255, "default": 0},
"wave_speed": {"min": 0, "max": 255, "default": 50}, "wave_speed": {"min": 0, "max": 255, "default": 50},
"center_level": {"min": 0, "max": 255, "default": 128} "center_level": {"min": 0, "max": 255, "default": 128}
} })
# Initialize a new Wave animation # Initialize a new Wave animation
# #

View File

@ -3,6 +3,8 @@
# This animation creates bouncing effects where patterns bounce back and forth # This animation creates bouncing effects where patterns bounce back and forth
# across the LED strip with configurable physics and damping. # across the LED strip with configurable physics and damping.
import "./core/param_encoder" as encode_constraints
#@ solidify:BounceAnimation,weak #@ solidify:BounceAnimation,weak
class BounceAnimation : animation.animation class BounceAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
@ -14,13 +16,13 @@ class BounceAnimation : animation.animation
var last_update_time # Last update time for physics calculation var last_update_time # Last update time for physics calculation
# Parameter definitions following parameterized class specification # Parameter definitions following parameterized class specification
static var PARAMS = { static var PARAMS = encode_constraints({
"source_animation": {"type": "instance", "default": nil}, "source_animation": {"type": "instance", "default": nil},
"bounce_speed": {"min": 0, "max": 255, "default": 128}, "bounce_speed": {"min": 0, "max": 255, "default": 128},
"bounce_range": {"min": 0, "max": 1000, "default": 0}, "bounce_range": {"min": 0, "max": 1000, "default": 0},
"damping": {"min": 0, "max": 255, "default": 250}, "damping": {"min": 0, "max": 255, "default": 250},
"gravity": {"min": 0, "max": 255, "default": 0} "gravity": {"min": 0, "max": 255, "default": 0}
} })
# Initialize a new Bounce animation # Initialize a new Bounce animation
def init(engine) def init(engine)

View File

@ -3,6 +3,8 @@
# This animation adds random jitter/shake effects to patterns with configurable # This animation adds random jitter/shake effects to patterns with configurable
# intensity, frequency, and jitter types (position, color, brightness). # intensity, frequency, and jitter types (position, color, brightness).
import "./core/param_encoder" as encode_constraints
#@ solidify:JitterAnimation,weak #@ solidify:JitterAnimation,weak
class JitterAnimation : animation.animation class JitterAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
@ -13,7 +15,7 @@ class JitterAnimation : animation.animation
var current_colors # Array of current colors for each pixel var current_colors # Array of current colors for each pixel
# Parameter definitions # Parameter definitions
static var PARAMS = { static var PARAMS = encode_constraints({
"source_animation": {"type": "instance", "default": nil}, "source_animation": {"type": "instance", "default": nil},
"jitter_intensity": {"min": 0, "max": 255, "default": 100}, "jitter_intensity": {"min": 0, "max": 255, "default": 100},
"jitter_frequency": {"min": 0, "max": 255, "default": 60}, "jitter_frequency": {"min": 0, "max": 255, "default": 60},
@ -21,7 +23,7 @@ class JitterAnimation : animation.animation
"position_range": {"min": 0, "max": 255, "default": 50}, "position_range": {"min": 0, "max": 255, "default": 50},
"color_range": {"min": 0, "max": 255, "default": 30}, "color_range": {"min": 0, "max": 255, "default": 30},
"brightness_range": {"min": 0, "max": 255, "default": 40} "brightness_range": {"min": 0, "max": 255, "default": 40}
} })
# Initialize a new Jitter animation # Initialize a new Jitter animation
def init(engine) def init(engine)

View File

@ -3,6 +3,8 @@
# This animation creates classic plasma effects using sine wave interference # This animation creates classic plasma effects using sine wave interference
# patterns with configurable frequencies, phases, and time-based animation. # patterns with configurable frequencies, phases, and time-based animation.
import "./core/param_encoder" as encode_constraints
#@ solidify:PlasmaAnimation,weak #@ solidify:PlasmaAnimation,weak
class PlasmaAnimation : animation.animation class PlasmaAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
@ -10,7 +12,7 @@ class PlasmaAnimation : animation.animation
var time_phase # Current time-based phase var time_phase # Current time-based phase
# Parameter definitions following parameterized class specification # Parameter definitions following parameterized class specification
static var PARAMS = { static var PARAMS = encode_constraints({
"color": {"default": nil}, "color": {"default": nil},
"freq_x": {"min": 1, "max": 255, "default": 32}, "freq_x": {"min": 1, "max": 255, "default": 32},
"freq_y": {"min": 1, "max": 255, "default": 23}, "freq_y": {"min": 1, "max": 255, "default": 23},
@ -18,7 +20,7 @@ class PlasmaAnimation : animation.animation
"phase_y": {"min": 0, "max": 255, "default": 64}, "phase_y": {"min": 0, "max": 255, "default": 64},
"time_speed": {"min": 0, "max": 255, "default": 50}, "time_speed": {"min": 0, "max": 255, "default": 50},
"blend_mode": {"min": 0, "max": 2, "default": 0} "blend_mode": {"min": 0, "max": 2, "default": 0}
} })
# Initialize a new Plasma animation # Initialize a new Plasma animation
# #

View File

@ -3,6 +3,8 @@
# This animation scales patterns up or down with configurable scaling factors, # This animation scales patterns up or down with configurable scaling factors,
# interpolation methods, and center points. # interpolation methods, and center points.
import "./core/param_encoder" as encode_constraints
#@ solidify:ScaleAnimation,weak #@ solidify:ScaleAnimation,weak
class ScaleAnimation : animation.animation class ScaleAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
@ -12,14 +14,14 @@ class ScaleAnimation : animation.animation
var start_time # Animation start time var start_time # Animation start time
# Parameter definitions following parameterized class specification # Parameter definitions following parameterized class specification
static var PARAMS = { static var PARAMS = encode_constraints({
"source_animation": {"type": "instance", "default": nil}, "source_animation": {"type": "instance", "default": nil},
"scale_factor": {"min": 1, "max": 255, "default": 128}, "scale_factor": {"min": 1, "max": 255, "default": 128},
"scale_speed": {"min": 0, "max": 255, "default": 0}, "scale_speed": {"min": 0, "max": 255, "default": 0},
"scale_mode": {"min": 0, "max": 3, "default": 0}, "scale_mode": {"min": 0, "max": 3, "default": 0},
"scale_center": {"min": 0, "max": 255, "default": 128}, "scale_center": {"min": 0, "max": 255, "default": 128},
"interpolation": {"min": 0, "max": 1, "default": 1} "interpolation": {"min": 0, "max": 1, "default": 1}
} })
# Initialize a new Scale animation # Initialize a new Scale animation
# @param engine: AnimationEngine - Required animation engine # @param engine: AnimationEngine - Required animation engine

View File

@ -3,6 +3,8 @@
# This animation shifts/scrolls patterns horizontally across the LED strip # This animation shifts/scrolls patterns horizontally across the LED strip
# with configurable speed, direction, and wrapping behavior. # with configurable speed, direction, and wrapping behavior.
import "./core/param_encoder" as encode_constraints
#@ solidify:ShiftAnimation,weak #@ solidify:ShiftAnimation,weak
class ShiftAnimation : animation.animation class ShiftAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
@ -11,12 +13,12 @@ class ShiftAnimation : animation.animation
var current_colors # Array of current colors for each pixel var current_colors # Array of current colors for each pixel
# Parameter definitions with constraints # Parameter definitions with constraints
static var PARAMS = { static var PARAMS = encode_constraints({
"source_animation": {"type": "instance", "default": nil}, "source_animation": {"type": "instance", "default": nil},
"shift_speed": {"min": 0, "max": 255, "default": 128}, "shift_speed": {"min": 0, "max": 255, "default": 128},
"direction": {"min": -1, "max": 1, "default": 1}, "direction": {"min": -1, "max": 1, "default": 1},
"wrap_around": {"type": "bool", "default": true} "wrap_around": {"type": "bool", "default": true}
} })
# Initialize a new Shift animation # Initialize a new Shift animation
def init(engine) def init(engine)

View File

@ -3,6 +3,8 @@
# This animation creates random sparkles that appear and fade out over time, # This animation creates random sparkles that appear and fade out over time,
# with configurable density, fade speed, and colors. # with configurable density, fade speed, and colors.
import "./core/param_encoder" as encode_constraints
#@ solidify:SparkleAnimation,weak #@ solidify:SparkleAnimation,weak
class SparkleAnimation : animation.animation class SparkleAnimation : animation.animation
# Non-parameter instance variables only # Non-parameter instance variables only
@ -13,7 +15,7 @@ class SparkleAnimation : animation.animation
var last_update # Last update time for frame timing var last_update # Last update time for frame timing
# Parameter definitions following parameterized class specification # Parameter definitions following parameterized class specification
static var PARAMS = { static var PARAMS = encode_constraints({
"color": {"default": 0xFFFFFFFF}, "color": {"default": 0xFFFFFFFF},
"back_color": {"default": 0xFF000000}, "back_color": {"default": 0xFF000000},
"density": {"min": 0, "max": 255, "default": 30}, "density": {"min": 0, "max": 255, "default": 30},
@ -21,7 +23,7 @@ class SparkleAnimation : animation.animation
"sparkle_duration": {"min": 0, "max": 255, "default": 60}, "sparkle_duration": {"min": 0, "max": 255, "default": 60},
"min_brightness": {"min": 0, "max": 255, "default": 100}, "min_brightness": {"min": 0, "max": 255, "default": 100},
"max_brightness": {"min": 0, "max": 255, "default": 255} "max_brightness": {"min": 0, "max": 255, "default": 255}
} })
# Initialize a new Sparkle animation # Initialize a new Sparkle animation
# @param engine: AnimationEngine - Required animation engine reference # @param engine: AnimationEngine - Required animation engine reference

View File

@ -31,6 +31,7 @@ class be_class_FrameBufferNtv (scope: global, name: FrameBufferNtv, strings: wea
apply_opacity, static_func(be_animation_ntv_apply_opacity) apply_opacity, static_func(be_animation_ntv_apply_opacity)
apply_brightness, static_func(be_animation_ntv_apply_brightness) apply_brightness, static_func(be_animation_ntv_apply_brightness)
fill_pixels, static_func(be_animation_ntv_fill_pixels) fill_pixels, static_func(be_animation_ntv_fill_pixels)
// paste_pixels, func(be_leds_paste_pixels)
} }
@const_object_info_end */ @const_object_info_end */

View File

@ -603,6 +603,47 @@ extern "C" {
be_return_nil(vm); be_return_nil(vm);
} }
// // Leds_frame.paste_pixels(neopixel:bytes(), led_buffer:bytes(), bri:int 0..100, gamma:bool)
// //
// // Copy from ARGB buffer to RGB
// int32_t be_leds_paste_pixels(bvm *vm);
// int32_t be_leds_paste_pixels(bvm *vm) {
// int32_t top = be_top(vm); // Get the number of arguments
// if (top >= 2 && be_isbytes(vm, 2)) {
// size_t src_len = 0;
// uint32_t * src_buf = (uint32_t*) be_tobytes(vm, 1, &src_len);
// size_t dest_len = 0;
// uint8_t * dest_buf = (uint8_t*) be_tobytes(vm, 2, &dest_len);
// uint32_t bri255 = 255;
// if (top >= 3 && be_isint(vm, 3)) {
// bri255 = be_toint(vm, 3);
// }
// bool gamma = false;
// if (top >= 4 && be_isbool(vm, 4)) {
// gamma = be_tobool(vm, 4);
// }
// size_t pixels_count = src_len / 4;
// if (pixels_count > dest_len / 3) { pixels_count = dest_len / 3; }
// if (pixels_count > 0) {
// for (size_t i = 0; i < pixels_count; i++) {
// uint32_t src_argb = ApplyBriGamma(src_buf[i], bri255, gamma);
// uint32_t src_r = (src_argb >> 16) & 0xFF;
// uint32_t src_g = (src_argb >> 8) & 0xFF;
// uint32_t src_b = (src_argb ) & 0xFF;
// dest_buf[i * 3 + 0] = src_r;
// dest_buf[i * 3 + 1] = src_g;
// dest_buf[i * 3 + 2] = src_b;
// }
// }
// be_return_nil(vm);
// }
// be_raise(vm, "type_error", nullptr);
// }
} }
#endif // USE_BERRY_ANIMATION #endif // USE_BERRY_ANIMATION

View File

@ -7,19 +7,21 @@
# This is the unified base class for all visual elements in the framework. # This is the unified base class for all visual elements in the framework.
# A Pattern is simply an Animation with infinite duration (duration = 0). # A Pattern is simply an Animation with infinite duration (duration = 0).
import "./core/param_encoder" as encode_constraints
class Animation : animation.parameterized_object class Animation : animation.parameterized_object
# Non-parameter instance variables only # Non-parameter instance variables only
var opacity_frame # Frame buffer for opacity animation rendering var opacity_frame # Frame buffer for opacity animation rendering
# Parameter definitions # Parameter definitions
static var PARAMS = { static var PARAMS = encode_constraints({
"name": {"type": "string", "default": "animation"}, # Optional name for the animation "name": {"type": "string", "default": "animation"}, # Optional name for the animation
"priority": {"min": 0, "default": 10}, # Rendering priority (higher = on top, 0-255) "priority": {"min": 0, "default": 10}, # Rendering priority (higher = on top, 0-255)
"duration": {"min": 0, "default": 0}, # Animation duration in ms (0 = infinite) "duration": {"min": 0, "default": 0}, # Animation duration in ms (0 = infinite)
"loop": {"type": "bool", "default": false}, # Whether to loop when duration is reached "loop": {"type": "bool", "default": false}, # Whether to loop when duration is reached
"opacity": {"type": "any", "default": 255}, # Animation opacity (0-255 number or Animation instance) "opacity": {"type": "any", "default": 255}, # Animation opacity (0-255 number or Animation instance)
"color": {"default": 0xFFFFFFFF} # Base color in ARGB format (0xAARRGGBB) "color": {"default": 0xFFFFFFFF} # Base color in ARGB format (0xAARRGGBB)
} })
# Initialize a new animation # Initialize a new animation
# #

View File

@ -165,9 +165,7 @@ class AnimationEngine
return self._add_animation(obj) return self._add_animation(obj)
else else
# Unknown type - provide helpful error message # Unknown type - provide helpful error message
import introspect raise "type_error", "only Animation or SequenceManager"
var class_name = introspect.name(obj)
raise "type_error", f"Cannot add object of type '{class_name}' to engine. Expected Animation or SequenceManager."
end end
end end
@ -182,8 +180,7 @@ class AnimationEngine
elif isinstance(obj, animation.animation) elif isinstance(obj, animation.animation)
return self.remove_animation(obj) return self.remove_animation(obj)
else else
# Unknown type - provide helpful error message # Unknown type - ignore
raise "type_error", f"Cannot remove object of type '{classname(obj)}' from engine. Expected Animation or SequenceManager."
end end
end end
@ -502,14 +499,8 @@ class AnimationEngine
# String representation # String representation
def tostring() def tostring()
return f"AnimationEngine(running={self.is_running}, animations={size(self.animations)}, width={self.width})" return f"AnimationEngine(running={self.is_running})"
end end
end end
# Main function to create the animation engine return {'create_engine': AnimationEngine}
def create_engine(strip)
return animation.animation_engine(strip)
end
return {'animation_engine': AnimationEngine,
'create_engine': create_engine}

View File

@ -46,15 +46,15 @@ class EventHandler
end end
# Get handler info for debugging # Get handler info for debugging
def get_info() # def get_info()
return { # return {
"event_name": self.event_name, # "event_name": self.event_name,
"priority": self.priority, # "priority": self.priority,
"is_active": self.is_active, # "is_active": self.is_active,
"has_condition": self.condition != nil, # "has_condition": self.condition != nil,
"metadata": self.metadata # "metadata": self.metadata
} # }
end # end
end end
#@ solidify:EventManager,weak #@ solidify:EventManager,weak

View File

@ -0,0 +1,284 @@
# Parameter Constraint Encoder for Berry Animation Framework
#
# This module provides functions to encode parameter constraints into a compact
# bytes() format with type-prefixed values for maximum flexibility and correctness.
#
# Encoding Format:
# ----------------
# Byte 0: Constraint mask (bit field)
# Bit 0 (0x01): has_min
# Bit 1 (0x02): has_max
# Bit 2 (0x04): has_default
# Bit 3 (0x08): has_explicit_type
# Bit 4 (0x10): has_enum
# Bit 5 (0x20): is_nillable
# Bits 6-7: reserved
#
# Bytes 1+: Values in order (min, max, default, enum)
# Each value is prefixed with its own type byte, followed by the value data.
#
# Value Type Codes:
# 0x00 = int8 (1 byte, signed -128 to 127)
# 0x01 = int16 (2 bytes, signed -32768 to 32767)
# 0x02 = int32 (4 bytes, signed integer)
# 0x03 = string (1-byte length prefix + string bytes)
# 0x04 = bytes (2-byte length prefix + byte data)
# 0x05 = bool (1 byte, 0 or 1)
# 0x06 = nil (0 bytes)
#
# Value Encoding (each value has: type_byte + data):
# - min: [type_byte][value_data]
# - max: [type_byte][value_data]
# - default: [type_byte][value_data]
# - enum: [count_byte][type_byte][value_data][type_byte][value_data]...
# - explicit_type: [type_code] (only if has_explicit_type bit is set)
#
# Explicit Type Codes (semantic types for validation) (1 byte):
# 0x00 = int
# 0x01 = string
# 0x02 = bytes
# 0x03 = bool
# 0x04 = any
# 0x05 = instance
# 0x06 = function
# Encode a full PARAMS map into a map of encoded constraints
#
# @param params_map: map - Map of parameter names to constraint definitions
# @return map - Map of parameter names to encoded bytes() objects
#
# Example:
# encode_constraints({"color": {"default": 0xFFFFFFFF}, "size": {"min": 0, "max": 255, "default": 128}})
# => {"color": bytes("04 02 FFFFFFFF"), "size": bytes("07 00 00 FF 80")}
def encode_constraints(params_map)
# Nested function: Encode a single constraint map into bytes() format
def encode_single_constraint(constraint_map)
# Nested helper: Determine the appropriate type code for a value
def get_type_code(value)
var value_type = type(value)
if value == nil return 0x06 #-NIL-#
elif value_type == "bool" return 0x05 #-BOOL-#
elif value_type == "string" return 0x03 #-STRING-#
elif value_type == "instance" && isinstance(value, bytes) return 0x04 #-BYTES-#
elif value_type == "int"
# Use signed ranges: int8 for -128 to 127, int16 for larger values
if value >= -128 && value <= 127 return 0x00 #-INT8-#
elif value >= -32768 && value <= 32767 return 0x01 #-INT16-#
else return 0x02 #-INT32-# end
else return 0x02 #-INT32-# end
end
# Nested helper: Encode a single value with its type prefix
def encode_value_with_type(value, result)
var type_code = get_type_code(value)
result.add(type_code, 1) # Add type byte prefix
if type_code == 0x06 #-NIL-# return
elif type_code == 0x05 #-BOOL-# result.add(value ? 1 : 0, 1)
elif type_code == 0x00 #-INT8-# result.add(value & 0xFF, 1)
elif type_code == 0x01 #-INT16-# result.add(value & 0xFFFF, 2)
elif type_code == 0x02 #-INT32-# result.add(value, 4)
elif type_code == 0x03 #-STRING-#
var str_bytes = bytes().fromstring(value)
result.add(size(str_bytes), 1)
result .. str_bytes
elif type_code == 0x04 #-BYTES-#
result.add(size(value), 2)
result .. value
end
end
var mask = 0
var result = bytes()
# Reserve space for mask only (will be set at the end)
result.resize(1)
# Helper: Convert explicit type string to type code
def get_explicit_type_code(type_str)
if type_str == "int" return 0x00
elif type_str == "string" return 0x01
elif type_str == "bytes" return 0x02
elif type_str == "bool" return 0x03
elif type_str == "any" return 0x04
elif type_str == "instance" return 0x05
elif type_str == "function" return 0x06
end
return 0x04 # Default to "any"
end
# Check if explicit type is specified
var explicit_type_code = nil
if constraint_map.contains("type")
explicit_type_code = get_explicit_type_code(constraint_map["type"])
end
# Encode min value (with type prefix)
if constraint_map.contains("min")
mask |= 0x01 #-HAS_MIN-#
encode_value_with_type(constraint_map["min"], result)
end
# Encode max value (with type prefix)
if constraint_map.contains("max")
mask |= 0x02 #-HAS_MAX-#
encode_value_with_type(constraint_map["max"], result)
end
# Encode default value (with type prefix)
if constraint_map.contains("default")
mask |= 0x04 #-HAS_DEFAULT-#
encode_value_with_type(constraint_map["default"], result)
end
# Encode explicit type code if present (1 byte)
if explicit_type_code != nil
mask |= 0x08 #-HAS_EXPLICIT_TYPE-#
result.add(explicit_type_code, 1)
end
# Encode enum values (each with type prefix)
if constraint_map.contains("enum")
mask |= 0x10 #-HAS_ENUM-#
var enum_list = constraint_map["enum"]
result.add(size(enum_list), 1) # Enum count
for val : enum_list
encode_value_with_type(val, result)
end
end
# Set nillable flag
if constraint_map.contains("nillable") && constraint_map["nillable"]
mask |= 0x20 #-IS_NILLABLE-#
end
# Write mask at the beginning
result.set(0, mask, 1)
return result
end
# Encode each parameter constraint
var result = {}
for param_name : params_map.keys()
result[param_name] = encode_single_constraint(params_map[param_name])
end
return result
end
# # Decode a single value from bytes according to type code
# #
# # @param encoded_bytes: bytes - bytes() object to read from
# # @param offset: int - Offset to start reading from
# # @param type_code: int - Type code for decoding
# # @return [value, new_offset] - Decoded value and new offset
# def decode_value(encoded_bytes, offset, type_code)
# if type_code == 0x06 #-NIL-#
# return [nil, offset]
# elif type_code == 0x05 #-BOOL-#
# return [encoded_bytes[offset] != 0, offset + 1]
# elif type_code == 0x00 #-INT8-#
# var val = encoded_bytes[offset]
# # Handle signed int8
# if val > 127
# val = val - 256
# end
# return [val, offset + 1]
# elif type_code == 0x01 #-INT16-#
# var val = encoded_bytes.get(offset, 2)
# # Handle signed int16
# if val > 32767
# val = val - 65536
# end
# return [val, offset + 2]
# elif type_code == 0x02 #-INT32-#
# return [encoded_bytes.get(offset, 4), offset + 4]
# elif type_code == 0x03 #-STRING-#
# var length = encoded_bytes[offset]
# var str_bytes = encoded_bytes[offset + 1 .. offset + length]
# return [str_bytes.asstring(), offset + 1 + length]
# elif type_code == 0x04 #-BYTES-#
# var length = encoded_bytes.get(offset, 2)
# var byte_data = encoded_bytes[offset + 2 .. offset + 2 + length - 1]
# return [byte_data, offset + 2 + length]
# end
#
# return [nil, offset]
# end
# # Decode an encoded constraint bytes() back into a map
# #
# # @param encoded_bytes: bytes - Encoded constraint as bytes() object
# # @return map - Decoded constraint map
# #
# # Example:
# # decode_constraint(bytes("07 00 00 FF 80"))
# # => {"min": 0, "max": 255, "default": 128}
# def decode_constraint(encoded_bytes)
# if size(encoded_bytes) < 2
# return {}
# end
#
# var mask = encoded_bytes[0]
# var type_code = encoded_bytes[1]
# var offset = 2
# var result = {}
#
# # Decode min value
# if mask & 0x01 #-HAS_MIN-#
# var decoded = decode_value(encoded_bytes, offset, type_code)
# result["min"] = decoded[0]
# offset = decoded[1]
# end
#
# # Decode max value
# if mask & 0x02 #-HAS_MAX-#
# var decoded = decode_value(encoded_bytes, offset, type_code)
# result["max"] = decoded[0]
# offset = decoded[1]
# end
#
# # Decode default value
# if mask & 0x04 #-HAS_DEFAULT-#
# var decoded = decode_value(encoded_bytes, offset, type_code)
# result["default"] = decoded[0]
# offset = decoded[1]
# end
#
# # Decode enum values
# if mask & 0x10 #-HAS_ENUM-#
# var count = encoded_bytes[offset]
# offset += 1
# result["enum"] = []
# var i = 0
# while i < count
# var decoded = decode_value(encoded_bytes, offset, type_code)
# result["enum"].push(decoded[0])
# offset = decoded[1]
# i += 1
# end
# end
#
# # Set nillable flag
# if mask & 0x20 #-IS_NILLABLE-#
# result["nillable"] = true
# end
#
# # Add type annotation if not default int32
# if type_code == 0x03 #-STRING-#
# result["type"] = "string"
# elif type_code == 0x04 #-BYTES-#
# result["type"] = "bytes"
# elif type_code == 0x05 #-BOOL-#
# result["type"] = "bool"
# elif type_code == 0x06 #-NIL-#
# result["type"] = "nil"
# end
#
# return result
# end
# Export only the encode function (decode not needed - use constraint_mask/constraint_find instead)
# Note: constraint_mask() and constraint_find() are static methods
# in ParameterizedObject class for accessing encoded constraints
return encode_constraints

View File

@ -8,22 +8,24 @@
# through member() and setmember() methods. Subclasses should not declare instance # through member() and setmember() methods. Subclasses should not declare instance
# variables for parameters, but use the PARAMS system only. # variables for parameters, but use the PARAMS system only.
import "./core/param_encoder" as encode_constraints
class ParameterizedObject class ParameterizedObject
var values # Map storing all parameter values var values # Map storing all parameter values
var engine # Reference to the animation engine 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 start_time # Time when object started (ms) (int), value is set at first call to update() or render()
# Static parameter definitions - should be overridden by subclasses # Static parameter definitions - should be overridden by subclasses
static var PARAMS = { static var PARAMS = encode_constraints(
"is_running": {"type": "bool", "default": false} # Whether the object is active {"is_running": {"type": "bool", "default": false}
} }) # Whether the object is active
# Initialize parameter system # Initialize parameter system
# #
# @param engine: AnimationEngine - Reference to the animation engine (required) # @param engine: AnimationEngine - Reference to the animation engine (required)
def init(engine) def init(engine)
if engine == nil || type(engine) != "instance" if engine == nil || type(engine) != "instance"
raise "value_error", "ParameterizedObject requires an engine parameter" raise "value_error", "missing engine parameter"
end end
self.engine = engine self.engine = engine
@ -45,9 +47,10 @@ class ParameterizedObject
for param_name : class_params.keys() for param_name : class_params.keys()
# Only set if not already set (child class defaults take precedence) # Only set if not already set (child class defaults take precedence)
if !self.values.contains(param_name) if !self.values.contains(param_name)
var param_def = class_params[param_name] var encoded_constraints = class_params[param_name]
if param_def.contains("default") # Use static method to check for default value
self.values[param_name] = param_def["default"] if self.constraint_mask(encoded_constraints, "default")
self.values[param_name] = self.constraint_find(encoded_constraints, "default")
end end
end end
end end
@ -86,7 +89,7 @@ class ParameterizedObject
# Private method to get parameter definition from the class hierarchy # Private method to get parameter definition from the class hierarchy
# #
# @param name: string - Parameter name # @param name: string - Parameter name
# @return map - Parameter definition or nil if not found # @return bytes - Encoded parameter constraints or nil if not found
def _get_param_def(name) def _get_param_def(name)
import introspect import introspect
@ -97,7 +100,7 @@ class ParameterizedObject
if introspect.contains(current_class, "PARAMS") if introspect.contains(current_class, "PARAMS")
var class_params = current_class.PARAMS var class_params = current_class.PARAMS
if class_params.contains(name) if class_params.contains(name)
return class_params[name] return class_params[name] # Returns encoded bytes
end end
end end
@ -163,9 +166,9 @@ class ParameterizedObject
def _resolve_parameter_value(name, time_ms) def _resolve_parameter_value(name, time_ms)
if !self.values.contains(name) if !self.values.contains(name)
# Return default if available from class hierarchy # Return default if available from class hierarchy
var param_def = self._get_param_def(name) var encoded_constraints = self._get_param_def(name)
if param_def != nil && param_def.contains("default") if encoded_constraints != nil && self.constraint_mask(encoded_constraints, "default")
return param_def["default"] return self.constraint_find(encoded_constraints, "default")
end end
return nil return nil
end end
@ -188,9 +191,9 @@ class ParameterizedObject
# @param value: any - Value to validate (may be modified for real->int conversion) # @param value: any - Value to validate (may be modified for real->int conversion)
# @return any - Validated value (potentially converted from real to int) # @return any - Validated value (potentially converted from real to int)
def _validate_param(name, value) def _validate_param(name, value)
var constraints = self._get_param_def(name) var encoded_constraints = self._get_param_def(name)
if constraints == nil if encoded_constraints == nil
raise "value_error", f"Parameter '{name}' is not defined for class '{classname(self)}'" raise "attribute_error", f"'{classname(self)}' object has no attribute '{name}'"
end end
# Accept ValueProvider instances for all parameters # Accept ValueProvider instances for all parameters
@ -201,24 +204,21 @@ class ParameterizedObject
# Handle nil values # Handle nil values
if value == nil if value == nil
# Check if nil is explicitly allowed via nillable attribute # Check if nil is explicitly allowed via nillable attribute
if constraints.contains("nillable") && constraints["nillable"] == true if self.constraint_mask(encoded_constraints, "nillable")
return value # nil is allowed for this parameter return value # nil is allowed for this parameter
end end
# Check if there's a default value (nil is acceptable if there's a default) # Check if there's a default value (nil is acceptable if there's a default)
if constraints.contains("default") if self.constraint_mask(encoded_constraints, "default")
return value # nil is acceptable, will use default return value # nil is acceptable, will use default
end end
# nil is not allowed for this parameter # nil is not allowed for this parameter
raise "value_error", f"Parameter '{name}' does not accept nil values" raise "value_error", f"'{name}' does not accept nil values"
end end
# Type validation - default type is "int" if not specified # Type validation - default type is "int" if not specified
var expected_type = "int" # Default type var expected_type = self.constraint_find(encoded_constraints, "type", "int")
if constraints.contains("type")
expected_type = constraints["type"]
end
# Get actual type for validation # Get actual type for validation
var actual_type = type(value) var actual_type = type(value)
@ -235,29 +235,34 @@ class ParameterizedObject
if actual_type == "instance" && isinstance(value, bytes) if actual_type == "instance" && isinstance(value, bytes)
actual_type = "bytes" actual_type = "bytes"
elif actual_type != "instance" || !isinstance(value, bytes) elif actual_type != "instance" || !isinstance(value, bytes)
raise "value_error", f"Parameter '{name}' expects type '{expected_type}' but got '{actual_type}' (value: {value})" raise "value_error", f"'{name}' expects type '{expected_type}' but got '{actual_type}' (value: {value})"
end end
elif expected_type != actual_type elif expected_type != actual_type
raise "value_error", f"Parameter '{name}' expects type '{expected_type}' but got '{actual_type}' (value: {value})" raise "value_error", f"'{name}' expects type '{expected_type}' but got '{actual_type}' (value: {value})"
end end
end end
# Range validation for integer values only # Range validation for integer values only
if actual_type == "int" if actual_type == "int"
if constraints.contains("min") && value < constraints["min"] if self.constraint_mask(encoded_constraints, "min")
raise "value_error", f"Parameter '{name}' value {value} is below minimum {constraints['min']}" var min_val = self.constraint_find(encoded_constraints, "min")
if value < min_val
raise "value_error", f"'{name}' value {value} is below minimum {min_val}"
end
end end
if constraints.contains("max") && value > constraints["max"] if self.constraint_mask(encoded_constraints, "max")
raise "value_error", f"Parameter '{name}' value {value} is above maximum {constraints['max']}" var max_val = self.constraint_find(encoded_constraints, "max")
if value > max_val
raise "value_error", f"'{name}' value {value} is above maximum {max_val}"
end
end end
end end
# Enum validation # Enum validation
if constraints.contains("enum") if self.constraint_mask(encoded_constraints, "enum")
var valid = false var valid = false
import introspect var enum_list = self.constraint_find(encoded_constraints, "enum")
var enum_list = constraints["enum"] var list_size = size(enum_list)
var list_size = enum_list.size()
var i = 0 var i = 0
while (i < list_size) while (i < list_size)
var enum_value = enum_list[i] var enum_value = enum_list[i]
@ -268,7 +273,7 @@ class ParameterizedObject
i += 1 i += 1
end end
if !valid if !valid
raise "value_error", f"Parameter '{name}' value {value} is not in allowed values {enum_list}" raise "value_error", f"'{name}' value {value} is not in allowed values {enum_list}"
end end
end end
@ -307,9 +312,9 @@ class ParameterizedObject
end end
# Fall back to parameter default from class hierarchy # Fall back to parameter default from class hierarchy
var param_def = self._get_param_def(name) var encoded_constraints = self._get_param_def(name)
if param_def != nil && param_def.contains("default") if encoded_constraints != nil && self.constraint_mask(encoded_constraints, "default")
return param_def["default"] return self.constraint_find(encoded_constraints, "default", default_value)
end end
return default_value return default_value
@ -322,25 +327,13 @@ class ParameterizedObject
# @param time_ms: int - Current time in milliseconds # @param time_ms: int - Current time in milliseconds
# @return any - The resolved value (static or from provider) # @return any - The resolved value (static or from provider)
def resolve_value(value, param_name, time_ms) def resolve_value(value, param_name, time_ms)
if value == nil if animation.is_value_provider(value) # this also captures 'nil'
return nil
end
if animation.is_value_provider(value)
return value.produce_value(param_name, time_ms) return value.produce_value(param_name, time_ms)
else else
return value return value
end end
end end
# Get parameter metadata
#
# @param name: string - Parameter name
# @return map - Parameter metadata or nil if not found
def get_param_metadata(name)
return self._get_param_def(name)
end
# Helper method to get a resolved value from either a static value or a value provider # Helper method to get a resolved value from either a static value or a value provider
# This is the same as accessing obj.param_name but with explicit time # This is the same as accessing obj.param_name but with explicit time
# #
@ -362,9 +355,9 @@ class ParameterizedObject
if time_ms == nil if time_ms == nil
time_ms = self.engine.time_ms time_ms = self.engine.time_ms
end end
if time_ms == nil # if time_ms == nil
raise "value_error", "engine.time_ms should not be 'nil'" # raise "value_error", "engine.time_ms should not be 'nil'"
end # end
if self.start_time == nil if self.start_time == nil
self.start_time = time_ms self.start_time = time_ms
end end
@ -384,9 +377,9 @@ class ParameterizedObject
if time_ms == nil if time_ms == nil
time_ms = self.engine.time_ms time_ms = self.engine.time_ms
end end
if time_ms == nil # if time_ms == nil
raise "value_error", "engine.time_ms should not be 'nil'" # raise "value_error", "engine.time_ms should not be 'nil'"
end # end
if self.start_time != nil # reset time only if it was already started if self.start_time != nil # reset time only if it was already started
self.start_time = time_ms self.start_time = time_ms
end end
@ -431,6 +424,260 @@ class ParameterizedObject
def !=(other) def !=(other)
return !(self == other) return !(self == other)
end end
# ============================================================================
# STATIC METHODS FOR ENCODED CONSTRAINT ACCESS
# ============================================================================
# PARAMETER CONSTRAINT ENCODING
# ==============================
#
# Parameter constraints are encoded into a compact bytes() format for efficient
# storage and transmission. Each value is prefixed with its own type byte for
# maximum flexibility and correctness.
#
# Byte 0: Constraint mask (bit field)
# Bit 0 (0x01): has_min
# Bit 1 (0x02): has_max
# Bit 2 (0x04): has_default
# Bit 3 (0x08): has_explicit_type
# Bit 4 (0x10): has_enum
# Bit 5 (0x20): is_nillable
# Bits 6-7: reserved
#
# Bytes 1+: Type-prefixed values in order (min, max, default, enum)
# Each value consists of: [type_byte][value_data]
#
# Value Type Codes:
# 0x00 = int8 (1 byte, signed -128 to 127)
# 0x01 = int16 (2 bytes, signed -32768 to 32767)
# 0x02 = int32 (4 bytes, signed integer)
# 0x03 = string (1-byte length prefix + string bytes)
# 0x04 = bytes (2-byte length prefix + byte data)
# 0x05 = bool (1 byte, 0 or 1)
# 0x06 = nil (0 bytes)
#
# Explicit Type Codes (semantic types for validation) (1 byte):
# 0x00 = int
# 0x01 = string
# 0x02 = bytes
# 0x03 = bool
# 0x04 = any
# 0x05 = instance
# 0x06 = function
#
# ENCODING EXAMPLES:
#
# {"min": 0, "max": 255, "default": 128}
# => bytes("07 00 00 01 00FF 00 0080") # 8 bytes
# Breakdown:
# 07 = mask (has_min|has_max|has_default)
# 00 00 = min (type=int8, value=0)
# 01 00FF = max (type=int16, value=255)
# 00 0080 = default (type=int8, value=128)
#
# {"enum": [1, 2, 3], "default": 1}
# => bytes("0C 00 01 03 00 01 00 02 00 03") # 10 bytes
# Breakdown:
# 0C = mask (has_enum|has_default)
# 00 01 = default (type=int8, value=1)
# 03 = enum count (3 values)
# 00 01 = enum[0] (type=int8, value=1)
# 00 02 = enum[1] (type=int8, value=2)
# 00 03 = enum[2] (type=int8, value=3)
#
# {"default": nil, "nillable": true}
# => bytes("14 06") # 2 bytes
# Breakdown:
# 14 = mask (has_default|is_nillable)
# 06 = default (type=nil, no value data)
#
# USAGE:
#
# Encoding constraints (see param_encoder.be):
# import param_encoder
# var encoded = param_encoder.encode_constraints({"min": 0, "max": 255, "default": 128})
#
# Checking if constraint contains a field:
# if ParameterizedObject.constraint_mask(encoded, "min")
# print("Has min constraint")
# end
#
# Getting constraint field value:
# var min_val = ParameterizedObject.constraint_find(encoded, "min", 0)
# var max_val = ParameterizedObject.constraint_find(encoded, "max", 255)
# ============================================================================
# Check if an encoded constraint contains a specific field (monolithic, no sub-calls)
#
# This static method provides fast access to encoded constraint metadata without
# decoding the entire constraint. It directly checks the mask byte to determine
# if a field is present.
#
# @param encoded_bytes: bytes - Encoded constraint in Hybrid format
# @param name: string - Field name ("min", "max", "default", "enum", "nillable", "type")
# @return bool - True if field exists, false otherwise
#
# Example:
# var encoded = bytes("07 00 00 FF 80") # min=0, max=255, default=128
# ParameterizedObject.constraint_mask(encoded, "min") # => true
# ParameterizedObject.constraint_mask(encoded, "enum") # => false
static var _MASK = [
"min", #- 0x01 HAS_MIN-#
"max", #- 0x02, HAS_MAX-#
"default", #- 0x04, HAS_DEFAULT-#
"type", #- 0x08, HAS_EXPLICIT_TYPE-#
"enum", #- 0x10, HAS_ENUM-#
"nillable", #- 0x20, IS_NILLABLE-#
]
static var _TYPES = [
"int", # 0x00
"string", # 0x01
"bytes", # 0x02
"bool", # 0x03
"any", # 0x04
"instance", # 0x05
"function" # 0x06
]
static def constraint_mask(encoded_bytes, name)
if size(encoded_bytes) > 0
var index_mask = _class._MASK.find(name)
if (index_mask != nil)
return (encoded_bytes[0] & (1 << index_mask))
end
end
return 0
end
# Find and return an encoded constraint field value (monolithic, no sub-calls)
#
# This static method extracts a specific field value from an encoded constraint
# without decoding the entire structure. It performs direct byte reading with
# inline type handling for maximum efficiency.
#
# @param encoded_bytes: bytes - Encoded constraint in Hybrid format
# @param name: string - Field name ("min", "max", "default", "enum", "nillable", "type")
# @param default: any - Default value if field not found
# @return any - Field value or default
#
# Supported field names:
# - "min": Minimum value constraint (int)
# - "max": Maximum value constraint (int)
# - "default": Default value (any type)
# - "enum": List of allowed values (array)
# - "nillable": Whether nil is allowed (bool)
# - "type": Explicit type string ("int", "string", "bytes", "bool", "any", "instance", "function")
#
# Example:
# var encoded = bytes("07 00 00 FF 80") # min=0, max=255, default=128
# ParameterizedObject.constraint_find(encoded, "min", 0) # => 0
# ParameterizedObject.constraint_find(encoded, "max", 255) # => 255
# ParameterizedObject.constraint_find(encoded, "default", 100) # => 128
# ParameterizedObject.constraint_find(encoded, "enum", nil) # => nil (not present)
static def constraint_find(encoded_bytes, name, default)
# Helper: Skip a value with type prefix and return new offset
def _skip_typed_value(encoded_bytes, offset)
if offset >= size(encoded_bytes) return 0 end
var type_code = encoded_bytes[offset]
if type_code == 0x06 #-NIL-# return 1
elif type_code == 0x05 #-BOOL-# return 2
elif type_code == 0x00 #-INT8-# return 2
elif type_code == 0x01 #-INT16-# return 3
elif type_code == 0x02 #-INT32-# return 5
elif type_code == 0x03 #-STRING-# return 2 + encoded_bytes[offset + 1]
elif type_code == 0x04 #-BYTES-# return 3 + encoded_bytes.get(offset + 1, 2)
end
return 0
end
# Helper: Read a value with type prefix and return [value, new_offset]
def _read_typed_value(encoded_bytes, offset)
if offset >= size(encoded_bytes) return nil end
var type_code = encoded_bytes[offset]
offset += 1 # Skip type byte
if type_code == 0x06 #-NIL-# return nil
elif type_code == 0x05 #-BOOL-#
return encoded_bytes[offset] != 0
elif type_code == 0x00 #-INT8-#
var v = encoded_bytes[offset]
return v > 127 ? v - 256 : v
elif type_code == 0x01 #-INT16-#
var v = encoded_bytes.get(offset, 2)
return v > 32767 ? v - 65536 : v
elif type_code == 0x02 #-INT32-#
return encoded_bytes.get(offset, 4)
elif type_code == 0x03 #-STRING-#
var len = encoded_bytes[offset]
return encoded_bytes[offset + 1 .. offset + len].asstring()
elif type_code == 0x04 #-BYTES-#
var len = encoded_bytes.get(offset, 2)
return encoded_bytes[offset + 2 .. offset + len + 1]
end
return nil
end
if size(encoded_bytes) < 1 return default end
var mask = encoded_bytes[0]
var offset = 1
# Quick check if field exists
var target_mask = _class._MASK.find(name) # nil or 0..5
if (target_mask == nil) return default end
target_mask = (1 << target_mask)
# If no match, quick fail
if !(mask & target_mask) return default end
# Easy check if 'nillable'
if target_mask == 0x20 #-IS_NILLABLE-#
return true # since 'mask & target_mask' is true, we know we should return true
end
# Skip fields before target
if target_mask > 0x01 #-HAS_MIN-# && (mask & 0x01 #-HAS_MIN-#)
offset += _skip_typed_value(encoded_bytes, offset)
end
if target_mask > 0x02 #-HAS_MAX-# && (mask & 0x02 #-HAS_MAX-#)
offset += _skip_typed_value(encoded_bytes, offset)
end
if target_mask > 0x04 #-HAS_DEFAULT-# && (mask & 0x04 #-HAS_DEFAULT-#)
offset += _skip_typed_value(encoded_bytes, offset)
end
if target_mask > 0x08 #-HAS_EXPLICIT_TYPE-# && (mask & 0x08 #-HAS_EXPLICIT_TYPE-#)
offset += 1
end
if offset >= size(encoded_bytes) return default end # sanity check
# Special case for explicit_type
if target_mask == 0x08 #-HAS_EXPLICIT_TYPE-#
# Read explicit type code and convert to string
var type_byte = encoded_bytes[offset] # sanity check above guarantees that index is correct
if type_byte < size(_class._TYPES)
return _class._TYPES[type_byte]
end
return default
end
# Read target value
if target_mask == 0x10 #-HAS_ENUM-#
var count = encoded_bytes[offset]
offset += 1
var result = []
var i = 0
while i < count
var val_and_offset =
result.push(_read_typed_value(encoded_bytes, offset))
offset += _skip_typed_value(encoded_bytes, offset)
i += 1
end
return result
end
# All other cases
return _read_typed_value(encoded_bytes, offset)
end
end end
return {'parameterized_object': ParameterizedObject} return {'parameterized_object': ParameterizedObject}

View File

@ -413,22 +413,22 @@ class SequenceManager
return self.is_running return self.is_running
end end
# Get current step info for debugging # # Get current step info for debugging
def get_current_step_info() # def get_current_step_info()
if !self.is_running || self.step_index >= size(self.steps) # if !self.is_running || self.step_index >= size(self.steps)
return nil # return nil
end # end
return { # return {
"step_index": self.step_index, # "step_index": self.step_index,
"total_steps": size(self.steps), # "total_steps": size(self.steps),
"current_step": self.steps[self.step_index], # "current_step": self.steps[self.step_index],
"elapsed_ms": self.engine.time_ms - self.step_start_time, # "elapsed_ms": self.engine.time_ms - self.step_start_time,
"repeat_count": self.repeat_count, # "repeat_count": self.repeat_count,
"current_iteration": self.current_iteration, # "current_iteration": self.current_iteration,
"is_repeat_sequence": self.is_repeat_sequence # "is_repeat_sequence": self.is_repeat_sequence
} # }
end # end
end end
return {'SequenceManager': SequenceManager} return {'SequenceManager': SequenceManager}

View File

@ -9,16 +9,18 @@
# - curve_factor 1: Pure cosine wave (smooth pulsing) # - curve_factor 1: Pure cosine wave (smooth pulsing)
# - curve_factor 2-5: Natural breathing with pauses at peaks (5 = most pronounced pauses) # - curve_factor 2-5: Natural breathing with pauses at peaks (5 = most pronounced pauses)
import "./core/param_encoder" as encode_constraints
#@ solidify:BreatheColorProvider,weak #@ solidify:BreatheColorProvider,weak
class BreatheColorProvider : animation.oscillator_value class BreatheColorProvider : animation.oscillator_value
# Additional parameter definitions for color-specific functionality # Additional parameter definitions for color-specific functionality
# The oscillator parameters (min_value, max_value, duration, form, etc.) are inherited # The oscillator parameters (min_value, max_value, duration, form, etc.) are inherited
static var PARAMS = { static var PARAMS = encode_constraints({
"base_color": {"default": 0xFFFFFFFF}, # The base color to modulate (32-bit ARGB value) "base_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) "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) "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) "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 # Initialize a new Breathe Color Provider
# Following parameterized class specification - engine parameter only # Following parameterized class specification - engine parameter only

View File

@ -15,14 +15,16 @@
# animation.brightness = provider # animation.brightness = provider
# #
import "./core/param_encoder" as encode_constraints
#@ solidify:ClosureValueProvider,weak #@ solidify:ClosureValueProvider,weak
class ClosureValueProvider : animation.value_provider class ClosureValueProvider : animation.value_provider
var _closure # We keep the closure as instance variable for faster dereferencing, in addition to PARAMS var _closure # We keep the closure as instance variable for faster dereferencing, in addition to PARAMS
# Static parameter definitions # Static parameter definitions
static var PARAMS = { static var PARAMS = encode_constraints({
"closure": {"type": "function", "default": nil} "closure": {"type": "function", "default": nil}
} })
# Method called when a parameter is changed # Method called when a parameter is changed
# Copy "closure" parameter to _closure instance variable # Copy "closure" parameter to _closure instance variable

View File

@ -11,6 +11,8 @@
# - Constructor takes only 'engine' parameter # - Constructor takes only 'engine' parameter
# - All other parameters set via virtual member assignment after creation # - All other parameters set via virtual member assignment after creation
import "./core/param_encoder" as encode_constraints
#@ solidify:ColorCycleColorProvider,weak #@ solidify:ColorCycleColorProvider,weak
class ColorCycleColorProvider : animation.color_provider class ColorCycleColorProvider : animation.color_provider
# Non-parameter instance variables only # Non-parameter instance variables only
@ -18,7 +20,7 @@ class ColorCycleColorProvider : animation.color_provider
var current_index # Current color index for next functionality var current_index # Current color index for next functionality
# Parameter definitions # Parameter definitions
static var PARAMS = { static var PARAMS = encode_constraints({
"palette": {"type": "bytes", "default": "palette": {"type": "bytes", "default":
bytes( # Palette bytes in AARRGGBB format bytes( # Palette bytes in AARRGGBB format
"FF0000FF" # Blue "FF0000FF" # Blue
@ -29,7 +31,7 @@ class ColorCycleColorProvider : animation.color_provider
"cycle_period": {"min": 0, "default": 5000}, # 0 = manual only, >0 = auto cycle time in ms "cycle_period": {"min": 0, "default": 5000}, # 0 = manual only, >0 = auto cycle time in ms
"next": {"default": 0}, # Write `<n>` to move to next <n> colors "next": {"default": 0}, # Write `<n>` to move to next <n> colors
"palette_size": {"type": "int", "default": 3} # Read-only: number of colors in palette "palette_size": {"type": "int", "default": 3} # Read-only: number of colors in palette
} })
# Initialize a new ColorCycleColorProvider # Initialize a new ColorCycleColorProvider
# #
@ -50,10 +52,10 @@ class ColorCycleColorProvider : animation.color_provider
def _get_palette_bytes() def _get_palette_bytes()
var palette_bytes = self.palette var palette_bytes = self.palette
if palette_bytes == nil if palette_bytes == nil
# Get default from PARAMS # Get default from PARAMS using encoded constraints
var param_def = self._get_param_def("palette") var encoded_constraints = self._get_param_def("palette")
if param_def != nil && param_def.contains("default") if encoded_constraints != nil && self.constraint_mask(encoded_constraints, "default")
palette_bytes = param_def["default"] palette_bytes = self.constraint_find(encoded_constraints, "default", nil)
end end
end end
return palette_bytes return palette_bytes

View File

@ -7,15 +7,17 @@
# - Constructor takes only 'engine' parameter # - Constructor takes only 'engine' parameter
# - All other parameters set via virtual member assignment after creation # - All other parameters set via virtual member assignment after creation
import "./core/param_encoder" as encode_constraints
#@ solidify:CompositeColorProvider,weak #@ solidify:CompositeColorProvider,weak
class CompositeColorProvider : animation.color_provider class CompositeColorProvider : animation.color_provider
# Non-parameter instance variables only # Non-parameter instance variables only
var providers # List of color providers var providers # List of color providers
# Parameter definitions # Parameter definitions
static var PARAMS = { static var PARAMS = encode_constraints({
"blend_mode": {"enum": [0, 1, 2], "default": 0} # 0=overlay, 1=add, 2=multiply "blend_mode": {"enum": [0, 1, 2], "default": 0} # 0=overlay, 1=add, 2=multiply
} })
# Initialize a new CompositeColorProvider # Initialize a new CompositeColorProvider
# #

View File

@ -19,10 +19,14 @@
# } # }
# } # }
import "./core/param_encoder" as encode_constraints
#@ solidify:IterationNumberProvider,weak #@ solidify:IterationNumberProvider,weak
class IterationNumberProvider : animation.value_provider class IterationNumberProvider : animation.value_provider
# Static parameter definitions (no parameters needed) # Static parameter definitions (no parameters needed)
static var PARAMS = {} static var PARAMS = encode_constraints({
})
# Produce the current iteration number from the animation engine # Produce the current iteration number from the animation engine
# #

View File

@ -9,6 +9,8 @@
# - SQUARE (3): Square wave alternating between a and b # - SQUARE (3): Square wave alternating between a and b
# - COSINE (4): Smooth cosine wave from a to b # - COSINE (4): Smooth cosine wave from a to b
import "./core/param_encoder" as encode_constraints
# Waveform constants # Waveform constants
var SAWTOOTH = 1 var SAWTOOTH = 1
var LINEAR = 1 var LINEAR = 1
@ -30,14 +32,14 @@ class OscillatorValueProvider : animation.value_provider
static var form_names = ["", "SAWTOOTH", "TRIANGLE", "SQUARE", "COSINE", "SINE", "EASE_IN", "EASE_OUT", "ELASTIC", "BOUNCE"] static var form_names = ["", "SAWTOOTH", "TRIANGLE", "SQUARE", "COSINE", "SINE", "EASE_IN", "EASE_OUT", "ELASTIC", "BOUNCE"]
# Parameter definitions for the oscillator # Parameter definitions for the oscillator
static var PARAMS = { static var PARAMS = encode_constraints({
"min_value": {"default": 0}, "min_value": {"default": 0},
"max_value": {"default": 100}, "max_value": {"default": 100},
"duration": {"min": 1, "default": 1000}, "duration": {"min": 1, "default": 1000},
"form": {"enum": [1, 2, 3, 4, 5, 6, 7, 8, 9], "default": 1}, "form": {"enum": [1, 2, 3, 4, 5, 6, 7, 8, 9], "default": 1},
"phase": {"min": 0, "max": 100, "default": 0}, "phase": {"min": 0, "max": 100, "default": 0},
"duty_cycle": {"min": 0, "max": 100, "default": 50} "duty_cycle": {"min": 0, "max": 100, "default": 50}
} })
# Initialize a new OscillatorValueProvider # Initialize a new OscillatorValueProvider
# #

View File

@ -7,6 +7,8 @@
# - Constructor takes only 'engine' parameter # - Constructor takes only 'engine' parameter
# - All other parameters set via virtual member assignment after creation # - All other parameters set via virtual member assignment after creation
import "./core/param_encoder" as encode_constraints
#@ solidify:RichPaletteColorProvider,weak #@ solidify:RichPaletteColorProvider,weak
class RichPaletteColorProvider : animation.color_provider class RichPaletteColorProvider : animation.color_provider
# Non-parameter instance variables only # Non-parameter instance variables only
@ -17,14 +19,14 @@ class RichPaletteColorProvider : animation.color_provider
var light_state # light_state instance for proper color calculations var light_state # light_state instance for proper color calculations
# Parameter definitions # Parameter definitions
static var PARAMS = { static var PARAMS = encode_constraints({
"palette": {"type": "bytes", "default": nil}, # Palette bytes or predefined palette constant "palette": {"type": "bytes", "default": nil}, # Palette bytes or predefined palette constant
"cycle_period": {"min": 0, "default": 5000}, # 5 seconds default, 0 = value-based only "cycle_period": {"min": 0, "default": 5000}, # 5 seconds default, 0 = value-based only
"transition_type": {"enum": [animation.LINEAR, animation.SINE], "default": animation.SINE}, "transition_type": {"enum": [animation.LINEAR, animation.SINE], "default": animation.SINE},
"brightness": {"min": 0, "max": 255, "default": 255}, "brightness": {"min": 0, "max": 255, "default": 255},
"range_min": {"default": 0}, "range_min": {"default": 0},
"range_max": {"default": 255} "range_max": {"default": 255}
} })
# Initialize a new RichPaletteColorProvider # Initialize a new RichPaletteColorProvider
# #

View File

@ -7,12 +7,14 @@
# - Constructor takes only 'engine' parameter # - Constructor takes only 'engine' parameter
# - All other parameters set via virtual member assignment after creation # - All other parameters set via virtual member assignment after creation
import "./core/param_encoder" as encode_constraints
#@ solidify:StaticColorProvider,weak #@ solidify:StaticColorProvider,weak
class StaticColorProvider : animation.color_provider class StaticColorProvider : animation.color_provider
# Parameter definitions # Parameter definitions
static var PARAMS = { static var PARAMS = encode_constraints({
"color": {"default": 0xFFFFFFFF} # Default to white "color": {"default": 0xFFFFFFFF} # Default to white
} })
# Produce the solid color for any parameter name # Produce the solid color for any parameter name
# #

View File

@ -11,12 +11,14 @@
# - Constructor takes only 'engine' parameter # - Constructor takes only 'engine' parameter
# - Value is set via virtual member assignment after creation # - Value is set via virtual member assignment after creation
import "./core/param_encoder" as encode_constraints
#@ solidify:StaticValueProvider,weak #@ solidify:StaticValueProvider,weak
class StaticValueProvider : animation.value_provider class StaticValueProvider : animation.value_provider
# Parameter definitions # Parameter definitions
static var PARAMS = { static var PARAMS = encode_constraints({
"value": {"default": nil, "type": "any"} "value": {"default": nil, "type": "any"}
} })
# Comparison operators to make StaticValueProvider work with validation code # Comparison operators to make StaticValueProvider work with validation code
def <(other) def <(other)

View File

@ -12,10 +12,14 @@
# - All other parameters set via virtual member assignment # - All other parameters set via virtual member assignment
# - No setter/getter methods for parameters # - No setter/getter methods for parameters
import "./core/param_encoder" as encode_constraints
#@ solidify:ValueProvider,weak #@ solidify:ValueProvider,weak
class ValueProvider : animation.parameterized_object class ValueProvider : animation.parameterized_object
# Static parameter definitions - can be overridden by subclasses # Static parameter definitions - can be overridden by subclasses
static var PARAMS = {} static var PARAMS = encode_constraints({
})
# Initialize the value provider # Initialize the value provider
# #

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@ end
# Test 1: Engine Creation # Test 1: Engine Creation
print("\n--- Test 1: Engine Creation ---") print("\n--- Test 1: Engine Creation ---")
var strip = global.Leds(20) var strip = global.Leds(20)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
assert_not_nil(engine, "Engine should be created") assert_not_nil(engine, "Engine should be created")
assert_equals(engine.width, 20, "Engine width should match strip length") assert_equals(engine.width, 20, "Engine width should match strip length")
@ -155,7 +155,7 @@ assert_test(render_time < 200, f"10 render cycles should be fast (took {render_t
# Test 8: Error Handling # Test 8: Error Handling
print("\n--- Test 8: Error Handling ---") print("\n--- Test 8: Error Handling ---")
try try
var bad_engine = animation.animation_engine(nil) var bad_engine = animation.create_engine(nil)
assert_test(false, "Should throw error for nil strip") assert_test(false, "Should throw error for nil strip")
except "value_error" except "value_error"
assert_test(true, "Should throw value_error for nil strip") assert_test(true, "Should throw value_error for nil strip")
@ -167,7 +167,7 @@ var engine2 = animation.create_engine(strip)
assert_not_nil(engine2, "Second engine should be created") assert_not_nil(engine2, "Second engine should be created")
assert_equals(engine2.width, strip.length(), "Second engine width should match strip") assert_equals(engine2.width, strip.length(), "Second engine width should match strip")
var engine3 = animation.animation_engine(strip) var engine3 = animation.create_engine(strip)
assert_not_nil(engine3, "Direct engine creation should work") assert_not_nil(engine3, "Direct engine creation should work")
assert_equals(engine3.width, strip.length(), "Direct engine width should match strip") assert_equals(engine3.width, strip.length(), "Direct engine width should match strip")
@ -221,7 +221,7 @@ end
# Create engine with dynamic strip # Create engine with dynamic strip
var dynamic_strip = MockDynamicStrip(15) var dynamic_strip = MockDynamicStrip(15)
var dynamic_engine = animation.animation_engine(dynamic_strip) var dynamic_engine = animation.create_engine(dynamic_strip)
# Test initial state # Test initial state
assert_equals(dynamic_engine.width, 15, "Engine should start with strip length 15") assert_equals(dynamic_engine.width, 15, "Engine should start with strip length 15")

View File

@ -32,7 +32,7 @@ print("\n--- Test 11: Animation Opacity with Animation Masks ---")
# Create a fresh engine for opacity tests # Create a fresh engine for opacity tests
var opacity_strip = global.Leds(10) var opacity_strip = global.Leds(10)
var opacity_engine = animation.animation_engine(opacity_strip) var opacity_engine = animation.create_engine(opacity_strip)
# Test 11a: Basic numeric opacity # Test 11a: Basic numeric opacity
print("\n--- Test 11a: Basic numeric opacity ---") print("\n--- Test 11a: Basic numeric opacity ---")

View File

@ -12,7 +12,7 @@ import animation
# Create animation engine for testing # Create animation engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test Animation class # Test Animation class
assert(animation.animation != nil, "Animation class should be defined") assert(animation.animation != nil, "Animation class should be defined")
@ -129,10 +129,13 @@ assert(param_anim.set_param("priority", -1) == false, "Value below min should be
assert(param_anim.get_param("unknown", "default") == "default", "Unknown parameter should return default") assert(param_anim.get_param("unknown", "default") == "default", "Unknown parameter should return default")
assert(param_anim.get_param("priority", 0) == 75, "Known parameter should return current value") assert(param_anim.get_param("priority", 0) == 75, "Known parameter should return current value")
# Test parameter metadata # Test parameter definition using _has_param and _get_param_def
var metadata = param_anim.get_param_metadata("priority") assert(param_anim._has_param("priority") == true, "Should have priority parameter")
assert(metadata != nil, "Metadata should exist for static parameter") var param_def = param_anim._get_param_def("priority")
assert(metadata["min"] == 0, "Metadata should contain correct min value") assert(param_def != nil, "Parameter definition should exist for static parameter")
# Use static methods to access encoded constraint data
assert(param_anim.constraint_mask(param_def, "min") == 0x01, "Parameter definition should have min constraint")
assert(param_anim.constraint_find(param_def, "min", nil) == 0, "Parameter definition should contain correct min value")
# Test updating multiple static parameters # Test updating multiple static parameters
# Test individual parameter updates using existing static parameters # Test individual parameter updates using existing static parameters

View File

@ -14,7 +14,7 @@ var passed_count = 0
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(10) # Use built-in LED strip for testing var strip = global.Leds(10) # Use built-in LED strip for testing
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
def test_assert(condition, message) def test_assert(condition, message)
test_count += 1 test_count += 1

View File

@ -14,7 +14,7 @@ def test_bounce_animation_basic()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create a simple source animation # Create a simple source animation
var source = animation.solid(engine) var source = animation.solid(engine)
@ -42,7 +42,7 @@ def test_bounce_animation_custom()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(20) var strip = global.Leds(20)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF00FF00 source.color = 0xFF00FF00
@ -79,7 +79,7 @@ def test_bounce_animation_parameters()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF0000FF source.color = 0xFF0000FF
@ -114,7 +114,7 @@ def test_bounce_animation_physics()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFFFF00 source.color = 0xFFFFFF00
@ -154,7 +154,7 @@ def test_bounce_animation_update_render()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFF00FF source.color = 0xFFFF00FF
@ -203,7 +203,7 @@ def test_bounce_constructors()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF00FFFF source.color = 0xFF00FFFF
@ -243,7 +243,7 @@ def test_bounce_animation_gravity()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFFFFFF source.color = 0xFFFFFFFF
@ -278,7 +278,7 @@ def test_bounce_tostring()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(12) var strip = global.Leds(12)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF888888 source.color = 0xFF888888

View File

@ -13,7 +13,7 @@ print("Imported animation module")
# Create LED strip and animation engine following specification # Create LED strip and animation engine following specification
var strip = global.Leds(10) # Use global.Leds() for testing as per specification var strip = global.Leds(10) # Use global.Leds() for testing as per specification
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
print("Created LED strip and animation engine") print("Created LED strip and animation engine")
# Create a breathe animation with engine-only parameter # Create a breathe animation with engine-only parameter

View File

@ -13,7 +13,7 @@ print("Imported animation module")
# Create LED strip and animation engine following specification # Create LED strip and animation engine following specification
var strip = global.Leds(10) # Use global.Leds() for testing as per specification var strip = global.Leds(10) # Use global.Leds() for testing as per specification
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
print("Created LED strip and animation engine") print("Created LED strip and animation engine")
# Create a breathe color provider with engine-only parameter # Create a breathe color provider with engine-only parameter

View File

@ -4,13 +4,15 @@
import animation import animation
import animation_dsl import animation_dsl
import "./core/param_encoder" as encode_constraints
# Test class that uses bytes parameter # Test class that uses bytes parameter
class BytesTestClass : animation.parameterized_object class BytesTestClass : animation.parameterized_object
static var PARAMS = { static var PARAMS = encode_constraints({
"data": {"type": "bytes", "default": nil, "nillable": true}, "data": {"type": "bytes", "default": nil, "nillable": true},
"required_data": {"type": "bytes"}, "required_data": {"type": "bytes"},
"name": {"type": "string", "default": "test"} "name": {"type": "string", "default": "test"}
} })
def init(engine) def init(engine)
super(self).init(engine) super(self).init(engine)
@ -76,14 +78,16 @@ def test_bytes_type_validation()
success = obj.set_param("data", "invalid") success = obj.set_param("data", "invalid")
assert(success == false, "Method setting with invalid type should fail") assert(success == false, "Method setting with invalid type should fail")
# Test 5: Parameter metadata # Test 5: Parameter definition
var metadata = obj.get_param_metadata("data") assert(obj._has_param("data") == true, "data parameter should exist")
assert(metadata["type"] == "bytes", "Data parameter should have bytes type") var param_def = obj._get_param_def("data")
assert(metadata["nillable"] == true, "Data parameter should be nillable") assert(obj.constraint_find(param_def, "type", nil) == "bytes", "Data parameter should have bytes type")
assert(obj.constraint_mask(param_def, "nillable") == 0x20, "Data parameter should be nillable")
var req_metadata = obj.get_param_metadata("required_data") assert(obj._has_param("required_data") == true, "required_data parameter should exist")
assert(req_metadata["type"] == "bytes", "Required data should have bytes type") var req_param_def = obj._get_param_def("required_data")
assert(req_metadata.find("nillable", false) == false, "Required data should not be nillable") assert(obj.constraint_find(req_param_def, "type", nil) == "bytes", "Required data should have bytes type")
assert(obj.constraint_mask(req_param_def, "nillable") == 0x00, "Required data should not be nillable")
print("✓ All bytes type validation tests passed!") print("✓ All bytes type validation tests passed!")
end end

View File

@ -10,7 +10,7 @@ import animation
# Create a real engine for testing using global.Leds() # Create a real engine for testing using global.Leds()
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create a test class # Create a test class
class ColorCycleAnimationTest class ColorCycleAnimationTest

View File

@ -167,16 +167,17 @@ def test_palette_size_parameter_metadata()
var engine = MockEngine() var engine = MockEngine()
var provider = animation.color_cycle(engine) var provider = animation.color_cycle(engine)
# Test 1: Parameter should exist in metadata # Test 1: Parameter should exist
var metadata = provider.get_param_metadata("palette_size") assert(provider._has_param("palette_size") == true, "palette_size parameter should exist")
assert(metadata != nil, "palette_size should have metadata") var param_def = provider._get_param_def("palette_size")
assert(param_def != nil, "palette_size should have parameter definition")
# Test 2: Check parameter definition # Test 2: Check parameter definition using static methods
assert(metadata.contains("type"), "palette_size metadata should have type") assert(provider.constraint_mask(param_def, "type") == 0x08, "palette_size definition should have type")
assert(metadata["type"] == "int", f"palette_size type should be 'int', got '{metadata['type']}'") assert(provider.constraint_find(param_def, "type", nil) == "int", f"palette_size type should be 'int', got '{provider.constraint_find(param_def, 'type', nil)}'")
assert(metadata.contains("default"), "palette_size metadata should have default") assert(provider.constraint_mask(param_def, "default") == 0x04, "palette_size definition should have default")
assert(metadata["default"] == 3, f"palette_size default should be 3, got {metadata['default']}") assert(provider.constraint_find(param_def, "default", nil) == 3, f"palette_size default should be 3, got {provider.constraint_find(param_def, 'default', nil)}")
print("✓ Parameter metadata tests passed!") print("✓ Parameter metadata tests passed!")
end end

View File

@ -39,7 +39,7 @@ end
# Create LED strip and animation engine following specification # Create LED strip and animation engine following specification
var strip = global.Leds(30) # Use global.Leds() for testing as per specification var strip = global.Leds(30) # Use global.Leds() for testing as per specification
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
print("Created LED strip and animation engine") print("Created LED strip and animation engine")
# Test 1: Basic Construction # Test 1: Basic Construction
@ -188,7 +188,7 @@ print("\n--- Test 6: Wrap Around vs Bounce ---")
# Create smaller strip for faster testing # Create smaller strip for faster testing
var small_strip = global.Leds(10) var small_strip = global.Leds(10)
var small_engine = animation.animation_engine(small_strip) var small_engine = animation.create_engine(small_strip)
# Test wrap around # Test wrap around
var wrap_comet = animation.comet_animation(small_engine) var wrap_comet = animation.comet_animation(small_engine)

View File

@ -0,0 +1,336 @@
# Constraint Encoding Test Suite
#
# Comprehensive tests for encode_constraints() and ParameterizedObject static methods:
# - constraint_mask()
# - constraint_find()
#
# Tests all PARAMS patterns found in the Berry Animation Framework codebase.
import "./core/param_encoder" as encode_constraints
# Test counter
var test_count = 0
var pass_count = 0
var fail_count = 0
# Test helper function
def assert_equal(actual, expected, test_name)
test_count += 1
if actual == expected
pass_count += 1
print(f"✓ Test {test_count}: {test_name}")
return true
else
fail_count += 1
print(f"✗ Test {test_count}: {test_name}")
print(f" Expected: {expected}")
print(f" Actual: {actual}")
return false
end
end
# Test helper for array equality
def assert_array_equal(actual, expected, test_name)
test_count += 1
if size(actual) != size(expected)
fail_count += 1
print(f"✗ Test {test_count}: {test_name}")
print(f" Expected size: {size(expected)}, Actual size: {size(actual)}")
return false
end
var i = 0
while i < size(actual)
if actual[i] != expected[i]
fail_count += 1
print(f"✗ Test {test_count}: {test_name}")
print(f" Mismatch at index {i}: expected {expected[i]}, got {actual[i]}")
return false
end
i += 1
end
pass_count += 1
print(f"✓ Test {test_count}: {test_name}")
return true
end
print("=" * 70)
print("CONSTRAINT ENCODING TEST SUITE")
print("=" * 70)
# ============================================================================
# TEST GROUP 1: Basic Integer Constraints (min/max/default)
# ============================================================================
print("\n--- Test Group 1: Basic Integer Constraints ---")
# Test 1.1: Simple min/max/default (int8 range)
var params_1_1 = {"min": 0, "max": 255, "default": 128}
var encoded_1_1 = encode_constraints({"test": params_1_1})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_1_1, "min"), 0x01, "1.1a: has min")
assert_equal(animation.parameterized_object.constraint_mask(encoded_1_1, "max"), 0x02, "1.1b: has max")
assert_equal(animation.parameterized_object.constraint_mask(encoded_1_1, "default"), 0x04, "1.1c: has default")
assert_equal(animation.parameterized_object.constraint_find(encoded_1_1, "min", nil), 0, "1.1d: min value")
assert_equal(animation.parameterized_object.constraint_find(encoded_1_1, "max", nil), 255, "1.1e: max value")
assert_equal(animation.parameterized_object.constraint_find(encoded_1_1, "default", nil), 128, "1.1f: default value")
# Test 1.2: Only default (no min/max)
var params_1_2 = {"default": 0xFFFFFFFF}
var encoded_1_2 = encode_constraints({"test": params_1_2})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_1_2, "min"), 0x00, "1.2a: no min")
assert_equal(animation.parameterized_object.constraint_mask(encoded_1_2, "max"), 0x00, "1.2b: no max")
assert_equal(animation.parameterized_object.constraint_mask(encoded_1_2, "default"), 0x04, "1.2c: has default")
assert_equal(animation.parameterized_object.constraint_find(encoded_1_2, "default", nil), 0xFFFFFFFF, "1.2d: default value")
# Test 1.3: Min only
var params_1_3 = {"min": 1, "default": 1000}
var encoded_1_3 = encode_constraints({"test": params_1_3})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_1_3, "min"), 0x01, "1.3a: has min")
assert_equal(animation.parameterized_object.constraint_mask(encoded_1_3, "max"), 0x00, "1.3b: no max")
assert_equal(animation.parameterized_object.constraint_find(encoded_1_3, "min", nil), 1, "1.3c: min value")
assert_equal(animation.parameterized_object.constraint_find(encoded_1_3, "default", nil), 1000, "1.3d: default value")
# Test 1.4: Negative values
var params_1_4 = {"min": -128, "max": 127, "default": 0}
var encoded_1_4 = encode_constraints({"test": params_1_4})["test"]
assert_equal(animation.parameterized_object.constraint_find(encoded_1_4, "min", nil), -128, "1.4a: negative min")
assert_equal(animation.parameterized_object.constraint_find(encoded_1_4, "max", nil), 127, "1.4b: positive max")
assert_equal(animation.parameterized_object.constraint_find(encoded_1_4, "default", nil), 0, "1.4c: zero default")
# Test 1.5: Large int32 values
var params_1_5 = {"min": 0, "max": 25600, "default": 2560}
var encoded_1_5 = encode_constraints({"test": params_1_5})["test"]
assert_equal(animation.parameterized_object.constraint_find(encoded_1_5, "max", nil), 25600, "1.5a: large max")
assert_equal(animation.parameterized_object.constraint_find(encoded_1_5, "default", nil), 2560, "1.5b: large default")
# ============================================================================
# TEST GROUP 2: Enum Constraints
# ============================================================================
print("\n--- Test Group 2: Enum Constraints ---")
# Test 2.1: Simple enum with positive values
var params_2_1 = {"enum": [1, 2, 3, 4, 5, 6, 7, 8, 9], "default": 1}
var encoded_2_1 = encode_constraints({"test": params_2_1})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_2_1, "enum"), 0x10, "2.1a: has enum")
assert_equal(animation.parameterized_object.constraint_find(encoded_2_1, "default", nil), 1, "2.1b: default value")
var enum_2_1 = animation.parameterized_object.constraint_find(encoded_2_1, "enum", nil)
assert_array_equal(enum_2_1, [1, 2, 3, 4, 5, 6, 7, 8, 9], "2.1c: enum values")
# Test 2.2: Enum with negative values
var params_2_2 = {"enum": [-1, 1], "default": 1}
var encoded_2_2 = encode_constraints({"test": params_2_2})["test"]
var enum_2_2 = animation.parameterized_object.constraint_find(encoded_2_2, "enum", nil)
assert_array_equal(enum_2_2, [-1, 1], "2.2a: enum with negative values")
# Test 2.3: Enum with min/max/default
var params_2_3 = {"min": 0, "max": 3, "enum": [0, 1, 2, 3], "default": 0}
var encoded_2_3 = encode_constraints({"test": params_2_3})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_2_3, "min"), 0x01, "2.3a: has min")
assert_equal(animation.parameterized_object.constraint_mask(encoded_2_3, "max"), 0x02, "2.3b: has max")
assert_equal(animation.parameterized_object.constraint_mask(encoded_2_3, "enum"), 0x10, "2.3c: has enum")
var enum_2_3 = animation.parameterized_object.constraint_find(encoded_2_3, "enum", nil)
assert_array_equal(enum_2_3, [0, 1, 2, 3], "2.3d: enum values")
# ============================================================================
# TEST GROUP 3: Type Annotations
# ============================================================================
print("\n--- Test Group 3: Type Annotations ---")
# Test 3.1: Bool type
var params_3_1 = {"type": "bool", "default": false}
var encoded_3_1 = encode_constraints({"test": params_3_1})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_3_1, "type"), 0x08, "3.1a: has type")
assert_equal(animation.parameterized_object.constraint_find(encoded_3_1, "type", nil), "bool", "3.1b: type is bool")
assert_equal(animation.parameterized_object.constraint_find(encoded_3_1, "default", nil), false, "3.1c: default is false")
# Test 3.2: String type
var params_3_2 = {"type": "string", "default": "animation"}
var encoded_3_2 = encode_constraints({"test": params_3_2})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_3_2, "type"), 0x08, "3.2a: has type")
assert_equal(animation.parameterized_object.constraint_find(encoded_3_2, "type", nil), "string", "3.2b: type is string")
assert_equal(animation.parameterized_object.constraint_find(encoded_3_2, "default", nil), "animation", "3.2c: default string")
# Test 3.3: Int type (explicit)
var params_3_3 = {"type": "int", "default": 3}
var encoded_3_3 = encode_constraints({"test": params_3_3})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_3_3, "type"), 0x08, "3.3a: has type")
assert_equal(animation.parameterized_object.constraint_find(encoded_3_3, "type", nil), "int", "3.3b: type is int")
assert_equal(animation.parameterized_object.constraint_find(encoded_3_3, "default", nil), 3, "3.3c: default int")
# Test 3.4: Any type
var params_3_4 = {"type": "any", "default": 255}
var encoded_3_4 = encode_constraints({"test": params_3_4})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_3_4, "type"), 0x08, "3.4a: has type")
assert_equal(animation.parameterized_object.constraint_find(encoded_3_4, "type", nil), "any", "3.4b: type is any")
# Test 3.5: Instance type
var params_3_5 = {"type": "instance", "default": nil}
var encoded_3_5 = encode_constraints({"test": params_3_5})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_3_5, "type"), 0x08, "3.5a: has type")
assert_equal(animation.parameterized_object.constraint_find(encoded_3_5, "type", nil), "instance", "3.5b: type is instance")
# Test 3.6: Function type
var params_3_6 = {"type": "function"}
var encoded_3_6 = encode_constraints({"test": params_3_6})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_3_6, "type"), 0x08, "3.6a: has type")
assert_equal(animation.parameterized_object.constraint_find(encoded_3_6, "type", nil), "function", "3.6b: type is function")
# Test 3.7: Bytes type
var params_3_7 = {"type": "bytes", "default": bytes("FF0000FF")}
var encoded_3_7 = encode_constraints({"test": params_3_7})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_3_7, "type"), 0x08, "3.7a: has type")
assert_equal(animation.parameterized_object.constraint_find(encoded_3_7, "type", nil), "bytes", "3.7b: type is bytes")
# Note: bytes comparison would need special handling
# ============================================================================
# TEST GROUP 4: Nillable Constraints
# ============================================================================
print("\n--- Test Group 4: Nillable Constraints ---")
# Test 4.1: Nillable with nil default
var params_4_1 = {"default": nil, "nillable": true}
var encoded_4_1 = encode_constraints({"test": params_4_1})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_4_1, "nillable"), 0x20, "4.1a: has nillable")
assert_equal(animation.parameterized_object.constraint_mask(encoded_4_1, "default"), 0x04, "4.1b: has default")
assert_equal(animation.parameterized_object.constraint_find(encoded_4_1, "nillable", false), true, "4.1c: nillable is true")
assert_equal(animation.parameterized_object.constraint_find(encoded_4_1, "default", 999), nil, "4.1d: default is nil")
# Test 4.2: Nillable without explicit default
var params_4_2 = {"nillable": true}
var encoded_4_2 = encode_constraints({"test": params_4_2})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_4_2, "nillable"), 0x20, "4.2a: has nillable")
assert_equal(animation.parameterized_object.constraint_find(encoded_4_2, "nillable", false), true, "4.2b: nillable is true")
# Test 4.3: Non-nillable (default behavior)
var params_4_3 = {"default": 0}
var encoded_4_3 = encode_constraints({"test": params_4_3})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_4_3, "nillable"), 0x00, "4.3a: no nillable flag")
# ============================================================================
# TEST GROUP 5: Real-World PARAMS from Codebase
# ============================================================================
print("\n--- Test Group 5: Real-World PARAMS ---")
# Test 5.1: BeaconAnimation PARAMS
var beacon_params = {
"color": {"default": 0xFFFFFFFF},
"back_color": {"default": 0xFF000000},
"pos": {"default": 0},
"beacon_size": {"min": 0, "default": 1},
"slew_size": {"min": 0, "default": 0}
}
var beacon_encoded = encode_constraints(beacon_params)
assert_equal(animation.parameterized_object.constraint_find(beacon_encoded["color"], "default", nil), 0xFFFFFFFF, "5.1a: beacon color")
assert_equal(animation.parameterized_object.constraint_find(beacon_encoded["beacon_size"], "min", nil), 0, "5.1b: beacon_size min")
# Test 5.2: CometAnimation PARAMS
var comet_params = {
"tail_length": {"min": 1, "max": 50, "default": 5},
"speed": {"min": 1, "max": 25600, "default": 2560},
"direction": {"enum": [-1, 1], "default": 1},
"wrap_around": {"min": 0, "max": 1, "default": 1},
"fade_factor": {"min": 0, "max": 255, "default": 179}
}
var comet_encoded = encode_constraints(comet_params)
assert_equal(animation.parameterized_object.constraint_find(comet_encoded["tail_length"], "max", nil), 50, "5.2a: tail_length max")
assert_equal(animation.parameterized_object.constraint_find(comet_encoded["speed"], "max", nil), 25600, "5.2b: speed max")
var direction_enum = animation.parameterized_object.constraint_find(comet_encoded["direction"], "enum", nil)
assert_array_equal(direction_enum, [-1, 1], "5.2c: direction enum")
# Test 5.3: Animation base class PARAMS
var animation_params = {
"name": {"type": "string", "default": "animation"},
"priority": {"min": 0, "default": 10},
"duration": {"min": 0, "default": 0},
"loop": {"type": "bool", "default": false},
"opacity": {"type": "any", "default": 255},
"color": {"default": 0xFFFFFFFF}
}
var animation_encoded = encode_constraints(animation_params)
assert_equal(animation.parameterized_object.constraint_find(animation_encoded["name"], "type", nil), "string", "5.3a: name type")
assert_equal(animation.parameterized_object.constraint_find(animation_encoded["name"], "default", nil), "animation", "5.3b: name default")
assert_equal(animation.parameterized_object.constraint_find(animation_encoded["loop"], "type", nil), "bool", "5.3c: loop type")
assert_equal(animation.parameterized_object.constraint_find(animation_encoded["opacity"], "type", nil), "any", "5.3d: opacity type")
# Test 5.4: GradientAnimation PARAMS (with nillable)
var gradient_params = {
"color": {"default": nil, "nillable": true},
"gradient_type": {"min": 0, "max": 1, "default": 0},
"direction": {"min": 0, "max": 255, "default": 0}
}
var gradient_encoded = encode_constraints(gradient_params)
assert_equal(animation.parameterized_object.constraint_mask(gradient_encoded["color"], "nillable"), 0x20, "5.4a: color nillable")
assert_equal(animation.parameterized_object.constraint_find(gradient_encoded["color"], "default", 999), nil, "5.4b: color default nil")
# Test 5.5: OscillatorValueProvider PARAMS (large enum)
var oscillator_params = {
"min_value": {"default": 0},
"max_value": {"default": 100},
"duration": {"min": 1, "default": 1000},
"form": {"enum": [1, 2, 3, 4, 5, 6, 7, 8, 9], "default": 1},
"phase": {"min": 0, "max": 100, "default": 0}
}
var oscillator_encoded = encode_constraints(oscillator_params)
var form_enum = animation.parameterized_object.constraint_find(oscillator_encoded["form"], "enum", nil)
assert_array_equal(form_enum, [1, 2, 3, 4, 5, 6, 7, 8, 9], "5.5a: form enum")
# Test 5.6: BreatheAnimation PARAMS
var breathe_params = {
"base_color": {"default": 0xFFFFFFFF},
"min_brightness": {"min": 0, "max": 255, "default": 0},
"max_brightness": {"min": 0, "max": 255, "default": 255},
"period": {"min": 100, "default": 3000},
"curve_factor": {"min": 1, "max": 5, "default": 2}
}
var breathe_encoded = encode_constraints(breathe_params)
assert_equal(animation.parameterized_object.constraint_find(breathe_encoded["period"], "min", nil), 100, "5.6a: period min")
assert_equal(animation.parameterized_object.constraint_find(breathe_encoded["curve_factor"], "max", nil), 5, "5.6b: curve_factor max")
# ============================================================================
# TEST GROUP 6: Edge Cases and Special Scenarios
# ============================================================================
print("\n--- Test Group 6: Edge Cases ---")
# Test 6.1: Empty constraints (only default)
var params_6_1 = {"default": 42}
var encoded_6_1 = encode_constraints({"test": params_6_1})["test"]
assert_equal(animation.parameterized_object.constraint_mask(encoded_6_1, "min"), 0x00, "6.1a: no min")
assert_equal(animation.parameterized_object.constraint_mask(encoded_6_1, "max"), 0x00, "6.1b: no max")
assert_equal(animation.parameterized_object.constraint_mask(encoded_6_1, "enum"), 0x00, "6.1c: no enum")
assert_equal(animation.parameterized_object.constraint_find(encoded_6_1, "default", nil), 42, "6.1d: default value")
# Test 6.2: Zero values
var params_6_2 = {"min": 0, "max": 0, "default": 0}
var encoded_6_2 = encode_constraints({"test": params_6_2})["test"]
assert_equal(animation.parameterized_object.constraint_find(encoded_6_2, "min", nil), 0, "6.2a: zero min")
assert_equal(animation.parameterized_object.constraint_find(encoded_6_2, "max", nil), 0, "6.2b: zero max")
assert_equal(animation.parameterized_object.constraint_find(encoded_6_2, "default", nil), 0, "6.2c: zero default")
# Test 6.3: Single-element enum
var params_6_3 = {"enum": [42], "default": 42}
var encoded_6_3 = encode_constraints({"test": params_6_3})["test"]
var enum_6_3 = animation.parameterized_object.constraint_find(encoded_6_3, "enum", nil)
assert_array_equal(enum_6_3, [42], "6.3a: single-element enum")
# Test 6.4: Default not found (should return provided default)
var params_6_4 = {"min": 0, "max": 100}
var encoded_6_4 = encode_constraints({"test": params_6_4})["test"]
assert_equal(animation.parameterized_object.constraint_find(encoded_6_4, "default", 999), 999, "6.4a: missing default returns fallback")
# Test 6.5: Field not found (should return provided default)
assert_equal(animation.parameterized_object.constraint_find(encoded_6_4, "nonexistent", 777), 777, "6.5a: nonexistent field returns fallback")
# ============================================================================
# SUMMARY
# ============================================================================
print("\n" + "=" * 70)
print("TEST SUMMARY")
print("=" * 70)
print(f"Total tests: {test_count}")
print(f"Passed: {pass_count}")
print(f"Failed: {fail_count}")
if fail_count == 0
print("\n✓ ALL TESTS PASSED!")
else
print(f"\n✗ {fail_count} TEST(S) FAILED")
raise "test_failed"
end
print("=" * 70)

View File

@ -28,7 +28,7 @@ def run_tests()
# Create engine and strip for testing # Create engine and strip for testing
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test 1: Basic construction with new parameterized pattern # Test 1: Basic construction with new parameterized pattern
var crenel = animation.crenel_position_animation(engine) var crenel = animation.crenel_position_animation(engine)

View File

@ -12,7 +12,7 @@ def test_crenel_with_integer_color()
# Create engine and strip for testing # Create engine and strip for testing
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var frame = animation.frame_buffer(10) var frame = animation.frame_buffer(10)
var red_color = 0xFFFF0000 # Red var red_color = 0xFFFF0000 # Red
@ -51,7 +51,7 @@ def test_crenel_with_color_provider()
# Create engine and strip for testing # Create engine and strip for testing
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var frame = animation.frame_buffer(10) var frame = animation.frame_buffer(10)
var blue_color = 0xFF0000FF # Blue var blue_color = 0xFF0000FF # Blue
@ -94,7 +94,7 @@ def test_crenel_with_dynamic_color_provider()
# Create engine and strip for testing # Create engine and strip for testing
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var frame = animation.frame_buffer(10) var frame = animation.frame_buffer(10)
@ -144,7 +144,7 @@ def test_crenel_with_generic_value_provider()
# Create engine and strip for testing # Create engine and strip for testing
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var frame = animation.frame_buffer(10) var frame = animation.frame_buffer(10)
@ -186,7 +186,7 @@ def test_crenel_set_color_methods()
# Create engine and strip for testing # Create engine and strip for testing
var strip = global.Leds(5) var strip = global.Leds(5)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var frame = animation.frame_buffer(5) var frame = animation.frame_buffer(5)
@ -233,7 +233,7 @@ def test_crenel_tostring()
# Create engine and strip for testing # Create engine and strip for testing
var strip = global.Leds(5) var strip = global.Leds(5)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test with integer color # Test with integer color
var crenel_int = animation.crenel_position_animation(engine) var crenel_int = animation.crenel_position_animation(engine)

View File

@ -210,23 +210,6 @@ def test_animation_engine_event_integration()
introspect.contains(engine, "resume") introspect.contains(engine, "resume")
end end
# Test 12: Event Metadata Handling
def test_event_metadata_handling()
var manager = animation.event_manager
var received_metadata = nil
var metadata = {"interval": 1000, "repeat": true}
var handler = manager.register_handler("metadata_test", def(data)
received_metadata = data
end, 0, nil, metadata)
# Check handler info includes metadata
var handler_info = handler.get_info()
return handler_info["metadata"]["interval"] == 1000 &&
handler_info["metadata"]["repeat"] == true
end
# Run all tests # Run all tests
def run_all_tests() def run_all_tests()
print("=== Event System Test Suite ===") print("=== Event System Test Suite ===")
@ -243,7 +226,6 @@ def run_all_tests()
run_test("Event Handler Deactivation", test_event_handler_deactivation) run_test("Event Handler Deactivation", test_event_handler_deactivation)
run_test("Event Queue Processing", test_event_queue_processing) run_test("Event Queue Processing", test_event_queue_processing)
run_test("Animation Engine Event Integration", test_animation_engine_event_integration) run_test("Animation Engine Event Integration", test_animation_engine_event_integration)
run_test("Event Metadata Handling", test_event_metadata_handling)
print("=== Test Results ===") print("=== Test Results ===")
print(f"Total tests: {test_count}") print(f"Total tests: {test_count}")

View File

@ -7,7 +7,7 @@ print("=== Fire Animation Test ===")
# Create engine and LED strip for testing # Create engine and LED strip for testing
var strip = global.Leds(30) # Use built-in LED strip for testing var strip = global.Leds(30) # Use built-in LED strip for testing
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test 1: Basic Fire Animation Creation # Test 1: Basic Fire Animation Creation
print("\n1. Testing basic fire animation creation...") print("\n1. Testing basic fire animation creation...")
@ -148,7 +148,7 @@ print("\n10. Testing edge cases...")
# Very small strip # Very small strip
var tiny_strip = global.Leds(1) var tiny_strip = global.Leds(1)
var tiny_engine = animation.animation_engine(tiny_strip) var tiny_engine = animation.create_engine(tiny_strip)
var tiny_fire = animation.fire_animation(tiny_engine) var tiny_fire = animation.fire_animation(tiny_engine)
tiny_fire.intensity = 180 tiny_fire.intensity = 180
tiny_fire.priority = 1 tiny_fire.priority = 1
@ -161,7 +161,7 @@ print("Tiny fire (1 pixel) created and rendered successfully")
# Zero intensity # Zero intensity
var dim_strip = global.Leds(10) var dim_strip = global.Leds(10)
var dim_engine = animation.animation_engine(dim_strip) var dim_engine = animation.create_engine(dim_strip)
var dim_fire = animation.fire_animation(dim_engine) var dim_fire = animation.fire_animation(dim_engine)
dim_fire.intensity = 0 dim_fire.intensity = 0
dim_fire.priority = 10 dim_fire.priority = 10

View File

@ -11,7 +11,7 @@ def test_gradient_creation()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test default gradient (rainbow linear) # Test default gradient (rainbow linear)
var gradient = animation.gradient_animation(engine) var gradient = animation.gradient_animation(engine)
@ -47,7 +47,7 @@ def test_gradient_parameters()
print("Testing GradientAnimation parameters...") print("Testing GradientAnimation parameters...")
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var gradient = animation.gradient_animation(engine) var gradient = animation.gradient_animation(engine)
gradient.color = 0xFFFFFFFF gradient.color = 0xFFFFFFFF
gradient.name = "test" gradient.name = "test"
@ -80,7 +80,7 @@ def test_gradient_updates()
print("Testing GradientAnimation updates...") print("Testing GradientAnimation updates...")
var strip = global.Leds(5) var strip = global.Leds(5)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var gradient = animation.gradient_animation(engine) var gradient = animation.gradient_animation(engine)
gradient.color = 0xFF00FF00 gradient.color = 0xFF00FF00
gradient.movement_speed = 100 gradient.movement_speed = 100
@ -109,7 +109,7 @@ def test_gradient_rendering()
print("Testing GradientAnimation rendering...") print("Testing GradientAnimation rendering...")
var strip = global.Leds(5) var strip = global.Leds(5)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var gradient = animation.gradient_animation(engine) var gradient = animation.gradient_animation(engine)
gradient.color = 0xFFFF0000 gradient.color = 0xFFFF0000
gradient.movement_speed = 0 gradient.movement_speed = 0
@ -141,7 +141,7 @@ def test_gradient_factory_methods()
print("Testing GradientAnimation factory methods...") print("Testing GradientAnimation factory methods...")
var strip = global.Leds(20) var strip = global.Leds(20)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test rainbow linear factory # Test rainbow linear factory
var rainbow_linear = animation.gradient_rainbow_linear(engine) var rainbow_linear = animation.gradient_rainbow_linear(engine)
@ -170,7 +170,7 @@ def test_gradient_position_calculations()
print("Testing GradientAnimation position calculations...") print("Testing GradientAnimation position calculations...")
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test linear gradient with different directions # Test linear gradient with different directions
var linear_gradient = animation.gradient_animation(engine) var linear_gradient = animation.gradient_animation(engine)
@ -212,7 +212,7 @@ def test_gradient_color_refactoring()
print("Testing GradientAnimation color refactoring...") print("Testing GradientAnimation color refactoring...")
var strip = global.Leds(5) var strip = global.Leds(5)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test with static color # Test with static color
var static_gradient = animation.gradient_animation(engine) var static_gradient = animation.gradient_animation(engine)
@ -250,7 +250,7 @@ def test_gradient_virtual_parameters()
print("Testing GradientAnimation virtual parameters...") print("Testing GradientAnimation virtual parameters...")
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var gradient = animation.gradient_animation(engine) var gradient = animation.gradient_animation(engine)
gradient.name = "test" gradient.name = "test"
@ -283,7 +283,7 @@ def test_gradient_tostring()
import string import string
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test with static color # Test with static color
var static_gradient = animation.gradient_animation(engine) var static_gradient = animation.gradient_animation(engine)

View File

@ -5,7 +5,7 @@ print("Testing gradient rainbow with light_state HSV conversion...")
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test rainbow gradient (nil color) # Test rainbow gradient (nil color)
var rainbow_gradient = animation.gradient_animation(engine) var rainbow_gradient = animation.gradient_animation(engine)

View File

@ -5,7 +5,7 @@ print("Testing basic GradientAnimation functionality...")
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(5) var strip = global.Leds(5)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test basic creation # Test basic creation
var gradient = animation.gradient_animation(engine) var gradient = animation.gradient_animation(engine)

View File

@ -12,7 +12,7 @@ def test_jitter_animation_basic()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create a simple source animation # Create a simple source animation
var source = animation.solid(engine) var source = animation.solid(engine)
@ -39,7 +39,7 @@ def test_jitter_animation_custom()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(20) var strip = global.Leds(20)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF00FF00 source.color = 0xFF00FF00
@ -70,7 +70,7 @@ def test_jitter_animation_parameters()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF0000FF source.color = 0xFF0000FF
@ -110,7 +110,7 @@ def test_jitter_animation_types()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFFFF00 source.color = 0xFFFFFF00
@ -144,7 +144,7 @@ def test_jitter_animation_update_render()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFF00FF source.color = 0xFFFF00FF
@ -182,7 +182,7 @@ def test_jitter_animation_random()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF00FFFF source.color = 0xFF00FFFF
@ -211,7 +211,7 @@ def test_jitter_constructors()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFAAAAAA source.color = 0xFFAAAAAA
@ -262,7 +262,7 @@ def test_jitter_animation_color_effects()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF808080 source.color = 0xFF808080
@ -290,7 +290,7 @@ def test_jitter_tostring()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(12) var strip = global.Leds(12)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF666666 source.color = 0xFF666666

View File

@ -12,7 +12,7 @@ def test_bounce_animation_basic()
# Create engine and source animation # Create engine and source animation
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFF0000 source.color = 0xFFFF0000
@ -37,7 +37,7 @@ def test_scale_animation_basic()
# Create engine and source animation # Create engine and source animation
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF00FF00 source.color = 0xFF00FF00
@ -62,7 +62,7 @@ def test_jitter_animation_basic()
# Create engine and source animation # Create engine and source animation
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF0000FF source.color = 0xFF0000FF
@ -87,7 +87,7 @@ def test_motion_effects_custom()
print("Testing motion effects with custom parameters...") print("Testing motion effects with custom parameters...")
var strip = global.Leds(20) var strip = global.Leds(20)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFFFF00 source.color = 0xFFFFFF00
@ -156,7 +156,7 @@ def test_motion_effects_update_render()
print("Testing motion effects update and render...") print("Testing motion effects update and render...")
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFF00FF source.color = 0xFFFF00FF
var frame = animation.frame_buffer(10) var frame = animation.frame_buffer(10)
@ -223,7 +223,7 @@ def test_motion_effects_constructors()
print("Testing motion effects constructor functions...") print("Testing motion effects constructor functions...")
var strip = global.Leds(30) var strip = global.Leds(30)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF00FFFF source.color = 0xFF00FFFF
@ -279,7 +279,7 @@ def test_motion_effects_tostring()
print("Testing motion effects string representations...") print("Testing motion effects string representations...")
var strip = global.Leds(12) var strip = global.Leds(12)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFFFFFF source.color = 0xFFFFFFFF

View File

@ -1,14 +1,16 @@
# Test for nillable parameter attribute # Test for nillable parameter attribute
import animation import animation
import "./core/param_encoder" as encode_constraints
print("Testing nillable parameter attribute...") print("Testing nillable parameter attribute...")
# Create a test class with nillable and non-nillable parameters # Create a test class with nillable and non-nillable parameters
class TestParameterizedClass : animation.parameterized_object class TestParameterizedClass : animation.parameterized_object
static var PARAMS = { static var PARAMS = encode_constraints({
"nillable_param": {"type": "int", "nillable": true}, "nillable_param": {"type": "int", "nillable": true},
"non_nillable_param": {"type": "int"} # No default, no nillable "non_nillable_param": {"type": "int"} # No default, no nillable
} })
def init(engine) def init(engine)
super(self).init(engine) super(self).init(engine)
@ -17,7 +19,7 @@ end
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(5) var strip = global.Leds(5)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test nillable parameter # Test nillable parameter
var test_obj = TestParameterizedClass(engine) var test_obj = TestParameterizedClass(engine)

View File

@ -12,7 +12,7 @@ def test_noise_animation_basic()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test with default parameters # Test with default parameters
var noise_anim = animation.noise_animation(engine) var noise_anim = animation.noise_animation(engine)
@ -32,7 +32,7 @@ def test_noise_animation_custom()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(20) var strip = global.Leds(20)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test with custom parameters # Test with custom parameters
var noise_anim = animation.noise_animation(engine) var noise_anim = animation.noise_animation(engine)
@ -64,7 +64,7 @@ def test_noise_animation_parameters()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var noise_anim = animation.noise_animation(engine) var noise_anim = animation.noise_animation(engine)
@ -91,7 +91,7 @@ def test_noise_animation_update_render()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var noise_anim = animation.noise_animation(engine) var noise_anim = animation.noise_animation(engine)
noise_anim.color = 0xFFFF0000 noise_anim.color = 0xFFFF0000
@ -133,7 +133,7 @@ def test_noise_constructors()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test noise_rainbow # Test noise_rainbow
var rainbow_noise = animation.noise_rainbow(engine) var rainbow_noise = animation.noise_rainbow(engine)
@ -164,7 +164,7 @@ def test_noise_tostring()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(12) var strip = global.Leds(12)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var noise_anim = animation.noise_animation(engine) var noise_anim = animation.noise_animation(engine)
noise_anim.scale = 75 noise_anim.scale = 75
@ -188,7 +188,7 @@ def test_noise_integer_color_conversion()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(5) var strip = global.Leds(5)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var noise_anim = animation.noise_animation(engine) var noise_anim = animation.noise_animation(engine)

View File

@ -8,7 +8,7 @@ import string
# Create a real engine for testing using global.Leds() # Create a real engine for testing using global.Leds()
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test the EASE_IN waveform # Test the EASE_IN waveform
def test_ease_in_waveform() def test_ease_in_waveform()

View File

@ -8,7 +8,7 @@ import string
# Create a real engine for testing using global.Leds() # Create a real engine for testing using global.Leds()
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test the ELASTIC waveform # Test the ELASTIC waveform
def test_elastic_waveform() def test_elastic_waveform()

View File

@ -6,13 +6,15 @@
import animation import animation
import global import global
import "./core/param_encoder" as encode_constraints
# Test that parameters accept ValueProviders and integers only # Test that parameters accept ValueProviders and integers only
def test_parameter_accepts_value_providers() def test_parameter_accepts_value_providers()
print("Testing parameter validation with ValueProviders...") print("Testing parameter validation with ValueProviders...")
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create a test animation using new constructor pattern # Create a test animation using new constructor pattern
var test_anim = animation.animation(engine) var test_anim = animation.animation(engine)
@ -48,7 +50,7 @@ def test_loop_boolean_validation()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create a test animation # Create a test animation
var test_anim = animation.animation(engine) var test_anim = animation.animation(engine)
@ -77,7 +79,7 @@ def test_range_validation()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create a test animation # Create a test animation
var test_anim = animation.animation(engine) var test_anim = animation.animation(engine)
@ -96,7 +98,7 @@ def test_range_validation_with_providers()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create a test animation # Create a test animation
var test_anim = animation.animation(engine) var test_anim = animation.animation(engine)
@ -123,18 +125,18 @@ def test_type_validation()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create a test class with different parameter types # Create a test class with different parameter types
class TestClass : animation.parameterized_object class TestClass : animation.parameterized_object
static var PARAMS = { static var PARAMS = encode_constraints({
"int_param": {"default": 42}, # Default type is "int" "int_param": {"default": 42}, # Default type is "int"
"explicit_int_param": {"type": "int", "default": 10}, "explicit_int_param": {"type": "int", "default": 10},
"string_param": {"type": "string", "default": "hello"}, "string_param": {"type": "string", "default": "hello"},
"bool_param": {"type": "bool", "default": true}, "bool_param": {"type": "bool", "default": true},
"instance_param": {"type": "instance", "default": nil}, "instance_param": {"type": "instance", "default": nil},
"any_param": {"type": "any", "default": nil} "any_param": {"type": "any", "default": nil}
} })
def init(engine) def init(engine)
super(self).init(engine) super(self).init(engine)

View File

@ -5,6 +5,8 @@
import animation import animation
import "./core/param_encoder" as encode_constraints
# Create a mock engine for testing # Create a mock engine for testing
class MockEngine class MockEngine
var time_ms var time_ms
@ -24,11 +26,11 @@ def test_parameterized_object_basic()
class TestObject : animation.parameterized_object class TestObject : animation.parameterized_object
# No instance variables for parameters - they're handled by the virtual system # No instance variables for parameters - they're handled by the virtual system
static var PARAMS = { static var PARAMS = encode_constraints({
"test_value": {"min": 0, "max": 100, "default": 50}, "test_value": {"min": 0, "max": 100, "default": 50},
"test_name": {"type": "string", "default": "test"}, "test_name": {"type": "string", "default": "test"},
"test_enum": {"enum": [1, 2, 3], "default": 1} "test_enum": {"enum": [1, 2, 3], "default": 1}
} })
def init(engine, value, name) def init(engine, value, name)
super(self).init(engine) # This initializes parameters with defaults super(self).init(engine) # This initializes parameters with defaults
@ -93,10 +95,10 @@ def test_parameter_hierarchy()
# Create a base class with some parameters # Create a base class with some parameters
class BaseClass : animation.parameterized_object class BaseClass : animation.parameterized_object
static var PARAMS = { static var PARAMS = encode_constraints({
"base_param": {"type": "string", "default": "base_value"}, "base_param": {"type": "string", "default": "base_value"},
"shared_param": {"type": "string", "default": "base_default"} "shared_param": {"type": "string", "default": "base_default"}
} })
def init(engine) def init(engine)
super(self).init(engine) super(self).init(engine)
@ -105,10 +107,10 @@ def test_parameter_hierarchy()
# Create a child class with additional parameters # Create a child class with additional parameters
class ChildClass : BaseClass class ChildClass : BaseClass
static var PARAMS = { static var PARAMS = encode_constraints({
"child_param": {"min": 0, "max": 10, "default": 5}, "child_param": {"min": 0, "max": 10, "default": 5},
"shared_param": {"type": "string", "default": "child_default"} # Override parent default "shared_param": {"type": "string", "default": "child_default"} # Override parent default
} })
def init(engine) def init(engine)
super(self).init(engine) super(self).init(engine)
@ -142,9 +144,9 @@ def test_value_provider_as_parameter()
# Create a simple test class # Create a simple test class
class TestClass : animation.parameterized_object class TestClass : animation.parameterized_object
static var PARAMS = { static var PARAMS = encode_constraints({
"dynamic_value": {"min": 0, "max": 100, "default": 50} "dynamic_value": {"min": 0, "max": 100, "default": 50}
} })
def init(engine) def init(engine)
super(self).init(engine) super(self).init(engine)
@ -191,11 +193,11 @@ def test_parameter_metadata()
print("Testing parameter metadata...") print("Testing parameter metadata...")
class TestClass : animation.parameterized_object class TestClass : animation.parameterized_object
static var PARAMS = { static var PARAMS = encode_constraints({
"range_param": {"min": 0, "max": 100, "default": 50}, "range_param": {"min": 0, "max": 100, "default": 50},
"enum_param": {"enum": [1, 2, 3], "default": 1}, "enum_param": {"enum": [1, 2, 3], "default": 1},
"simple_param": {"type": "string", "default": "test"} "simple_param": {"type": "string", "default": "test"}
} })
def init(engine) def init(engine)
super(self).init(engine) super(self).init(engine)
@ -204,17 +206,19 @@ def test_parameter_metadata()
var obj = TestClass(mock_engine) var obj = TestClass(mock_engine)
# Test getting single parameter metadata # Test getting single parameter definition
var range_meta = obj.get_param_metadata("range_param") assert(obj._has_param("range_param") == true, "range_param should exist")
assert(range_meta != nil, "Should get range parameter metadata") var range_def = obj._get_param_def("range_param")
assert(range_meta["min"] == 0, "Should have min constraint") assert(range_def != nil, "Should get range parameter definition")
assert(range_meta["max"] == 100, "Should have max constraint") assert(obj.constraint_find(range_def, "min", nil) == 0, "Should have min constraint")
assert(range_meta["default"] == 50, "Should have default value") assert(obj.constraint_find(range_def, "max", nil) == 100, "Should have max constraint")
assert(obj.constraint_find(range_def, "default", nil) == 50, "Should have default value")
var enum_meta = obj.get_param_metadata("enum_param") assert(obj._has_param("enum_param") == true, "enum_param should exist")
assert(enum_meta != nil, "Should get enum parameter metadata") var enum_def = obj._get_param_def("enum_param")
assert(enum_meta.contains("enum"), "Should have enum constraint") assert(enum_def != nil, "Should get enum parameter definition")
assert(enum_meta["default"] == 1, "Should have default value") assert(obj.constraint_mask(enum_def, "enum") == 0x10, "Should have enum constraint")
assert(obj.constraint_find(enum_def, "default", nil) == 1, "Should have default value")
print("✓ Parameter metadata test passed") print("✓ Parameter metadata test passed")
end end
@ -224,9 +228,9 @@ def test_virtual_member_errors()
print("Testing virtual member error handling...") print("Testing virtual member error handling...")
class TestClass : animation.parameterized_object class TestClass : animation.parameterized_object
static var PARAMS = { static var PARAMS = encode_constraints({
"valid_param": {"min": 0, "max": 100, "default": 50} "valid_param": {"min": 0, "max": 100, "default": 50}
} })
def init(engine) def init(engine)
super(self).init(engine) super(self).init(engine)
@ -275,9 +279,9 @@ def test_undefined_parameter_behavior()
import string # Import once at the top of the function import string # Import once at the top of the function
class TestClass : animation.parameterized_object class TestClass : animation.parameterized_object
static var PARAMS = { static var PARAMS = encode_constraints({
"defined_param": {"min": 0, "max": 100, "default": 50} "defined_param": {"min": 0, "max": 100, "default": 50}
} })
def init(engine) def init(engine)
super(self).init(engine) super(self).init(engine)
@ -354,10 +358,11 @@ def test_undefined_parameter_behavior()
obj.defined_param = 75 obj.defined_param = 75
assert(obj.defined_param == 75, "Defined parameter assignment should still work") assert(obj.defined_param == 75, "Defined parameter assignment should still work")
# Test get_param_metadata for undefined parameter # Test _has_param and _get_param_def for undefined parameter
print(" Testing metadata for undefined parameter...") print(" Testing parameter definition for undefined parameter...")
var undefined_meta = obj.get_param_metadata("undefined_param") assert(obj._has_param("undefined_param") == false, "_has_param for undefined parameter should return false")
assert(undefined_meta == nil, "Metadata for undefined parameter should be nil") var undefined_def = obj._get_param_def("undefined_param")
assert(undefined_def == nil, "_get_param_def for undefined parameter should be nil")
# Test get_param_value for undefined parameter # Test get_param_value for undefined parameter
print(" Testing get_param_value for undefined parameter...") print(" Testing get_param_value for undefined parameter...")
@ -372,9 +377,9 @@ def test_engine_requirement()
print("Testing engine parameter requirement...") print("Testing engine parameter requirement...")
class TestClass : animation.parameterized_object class TestClass : animation.parameterized_object
static var PARAMS = { static var PARAMS = encode_constraints({
"test_param": {"default": 42} "test_param": {"default": 42}
} })
end end
# Test that nil engine raises error # Test that nil engine raises error
@ -398,9 +403,9 @@ def test_equality_operator()
print("Testing equality operator...") print("Testing equality operator...")
class TestClass : animation.parameterized_object class TestClass : animation.parameterized_object
static var PARAMS = { static var PARAMS = encode_constraints({
"test_param": {"default": 42} "test_param": {"default": 42}
} })
def init(engine) def init(engine)
super(self).init(engine) super(self).init(engine)

View File

@ -12,7 +12,7 @@ def test_plasma_animation_basic()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test with default parameters # Test with default parameters
var plasma_anim = animation.plasma_animation(engine) var plasma_anim = animation.plasma_animation(engine)
@ -36,7 +36,7 @@ def test_plasma_animation_custom()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(20) var strip = global.Leds(20)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test with custom parameters using virtual member assignment # Test with custom parameters using virtual member assignment
var plasma_anim = animation.plasma_animation(engine) var plasma_anim = animation.plasma_animation(engine)
@ -71,7 +71,7 @@ def test_plasma_animation_parameters()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var plasma_anim = animation.plasma_animation(engine) var plasma_anim = animation.plasma_animation(engine)
plasma_anim.name = "param_test" plasma_anim.name = "param_test"
@ -102,7 +102,7 @@ def test_plasma_animation_update_render()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var plasma_anim = animation.plasma_animation(engine) var plasma_anim = animation.plasma_animation(engine)
plasma_anim.color = 0xFFFF0000 plasma_anim.color = 0xFFFF0000
@ -146,7 +146,7 @@ def test_plasma_constructors()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test plasma_rainbow # Test plasma_rainbow
var rainbow_plasma = animation.plasma_rainbow(engine) var rainbow_plasma = animation.plasma_rainbow(engine)
@ -170,7 +170,7 @@ def test_plasma_tostring()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(12) var strip = global.Leds(12)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var plasma_anim = animation.plasma_animation(engine) var plasma_anim = animation.plasma_animation(engine)
plasma_anim.freq_x = 55 plasma_anim.freq_x = 55

View File

@ -13,7 +13,7 @@ print("Imported animation module")
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(10) # Use global.Leds() for testing as per specification var strip = global.Leds(10) # Use global.Leds() for testing as per specification
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
print("Created engine and LED strip") print("Created engine and LED strip")
# Create a pulse animation with new constructor (engine only) # Create a pulse animation with new constructor (engine only)

View File

@ -11,7 +11,7 @@ print("Imported animation module")
# Create LED strip and engine for testing (following specification) # Create LED strip and engine for testing (following specification)
var strip = global.Leds(10) # Use global.Leds() for testing as per specification var strip = global.Leds(10) # Use global.Leds() for testing as per specification
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
print("Created test engine with 10 LEDs") print("Created test engine with 10 LEDs")

View File

@ -12,7 +12,7 @@ def test_scale_animation_basic()
# Create LED strip and engine using global.Leds # Create LED strip and engine using global.Leds
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create a simple source animation # Create a simple source animation
var source = animation.solid(engine) var source = animation.solid(engine)
@ -38,7 +38,7 @@ def test_scale_animation_custom()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(20) var strip = global.Leds(20)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF00FF00 source.color = 0xFF00FF00
@ -67,7 +67,7 @@ def test_scale_animation_parameters()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF0000FF source.color = 0xFF0000FF
@ -104,7 +104,7 @@ def test_scale_animation_modes()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFFFF00 source.color = 0xFFFFFF00
@ -155,7 +155,7 @@ def test_scale_animation_interpolation()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF808080 source.color = 0xFF808080
@ -184,7 +184,7 @@ def test_scale_animation_sine()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF000000 source.color = 0xFF000000
@ -217,7 +217,7 @@ def test_scale_animation_update_render()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFF00FF source.color = 0xFFFF00FF
@ -257,7 +257,7 @@ def test_scale_constructors()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF00FFFF source.color = 0xFF00FFFF
@ -296,7 +296,7 @@ def test_scale_tostring()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(12) var strip = global.Leds(12)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF444444 source.color = 0xFF444444

View File

@ -34,7 +34,7 @@ def test_sequence_manager_step_creation()
# Create test animation using new parameterized API # Create test animation using new parameterized API
var strip = global.Leds(30) var strip = global.Leds(30)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var color_provider = animation.static_color(engine) var color_provider = animation.static_color(engine)
color_provider.color = 0xFFFF0000 color_provider.color = 0xFFFF0000
var test_anim = animation.solid(engine) var test_anim = animation.solid(engine)
@ -183,10 +183,6 @@ def test_sequence_manager_step_info()
var engine = animation.create_engine(strip) var engine = animation.create_engine(strip)
var seq_manager = animation.SequenceManager(engine) var seq_manager = animation.SequenceManager(engine)
# Test step info when not running
var step_info = seq_manager.get_current_step_info()
assert(step_info == nil, "Step info should be nil when not running")
# Create test sequence using new parameterized API # Create test sequence using new parameterized API
var color_provider = animation.static_color(engine) var color_provider = animation.static_color(engine)
color_provider.color = 0xFFFF0000 color_provider.color = 0xFFFF0000
@ -206,14 +202,6 @@ def test_sequence_manager_step_info()
engine.run() # Start the engine engine.run() # Start the engine
engine.on_tick(30000) # Update engine time engine.on_tick(30000) # Update engine time
# Get step info
step_info = seq_manager.get_current_step_info()
assert(step_info != nil, "Step info should not be nil when running")
assert(step_info["step_index"] == 0, "Step info should show correct step index")
assert(step_info["total_steps"] == 2, "Step info should show correct total steps")
assert(step_info["current_step"]["type"] == "play", "Step info should show correct step type")
assert(step_info["elapsed_ms"] >= 0, "Step info should show elapsed time")
print("✓ Step info tests passed") print("✓ Step info tests passed")
end end

View File

@ -12,7 +12,7 @@ def test_shift_animation_basic()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create a simple source animation # Create a simple source animation
var source = animation.solid(engine) var source = animation.solid(engine)
@ -37,7 +37,7 @@ def test_shift_animation_custom()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(20) var strip = global.Leds(20)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF00FF00 source.color = 0xFF00FF00
@ -68,7 +68,7 @@ def test_shift_animation_parameters()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF0000FF source.color = 0xFF0000FF
@ -100,7 +100,7 @@ def test_shift_animation_update_render()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFFFF00 source.color = 0xFFFFFF00
@ -144,7 +144,7 @@ def test_shift_constructors()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFFFF00FF source.color = 0xFFFF00FF
@ -183,7 +183,7 @@ def test_shift_tostring()
# Create LED strip and engine # Create LED strip and engine
var strip = global.Leds(12) var strip = global.Leds(12)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var source = animation.solid(engine) var source = animation.solid(engine)
source.color = 0xFF00FFFF source.color = 0xFF00FFFF

View File

@ -11,7 +11,7 @@ print("Imported animation module")
# Create LED strip and engine for testing (following specification) # Create LED strip and engine for testing (following specification)
var strip = global.Leds(10) # Use global.Leds() for testing as per specification var strip = global.Leds(10) # Use global.Leds() for testing as per specification
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
print("Created test engine with 10 LEDs") print("Created test engine with 10 LEDs")

View File

@ -6,7 +6,7 @@ import animation
# Create test engine (following specification) # Create test engine (following specification)
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
def test_unified_solid_function() def test_unified_solid_function()
print("Testing unified solid() function...") print("Testing unified solid() function...")

View File

@ -12,7 +12,7 @@ def test_sparkle_animation_basic()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test with default parameters # Test with default parameters
var sparkle_anim = animation.sparkle_animation(engine) var sparkle_anim = animation.sparkle_animation(engine)
@ -35,7 +35,7 @@ def test_sparkle_animation_custom()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(20) var strip = global.Leds(20)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test with custom parameters using new parameterized pattern # Test with custom parameters using new parameterized pattern
var sparkle_anim = animation.sparkle_animation(engine) var sparkle_anim = animation.sparkle_animation(engine)
@ -70,7 +70,7 @@ def test_sparkle_animation_parameters()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var sparkle_anim = animation.sparkle_animation(engine) var sparkle_anim = animation.sparkle_animation(engine)
sparkle_anim.name = "param_test" sparkle_anim.name = "param_test"
@ -102,7 +102,7 @@ def test_sparkle_animation_update_render()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var sparkle_anim = animation.sparkle_animation(engine) var sparkle_anim = animation.sparkle_animation(engine)
sparkle_anim.color = 0xFFFF0000 sparkle_anim.color = 0xFFFF0000
@ -153,7 +153,7 @@ def test_sparkle_constructors()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test sparkle_white # Test sparkle_white
var white_sparkle = animation.sparkle_white(engine) var white_sparkle = animation.sparkle_white(engine)
@ -185,7 +185,7 @@ def test_sparkle_tostring()
# Create LED strip and engine for testing # Create LED strip and engine for testing
var strip = global.Leds(12) var strip = global.Leds(12)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var sparkle_anim = animation.sparkle_animation(engine) var sparkle_anim = animation.sparkle_animation(engine)
sparkle_anim.density = 75 sparkle_anim.density = 75

View File

@ -12,7 +12,7 @@ def test_static_value_provider_interface()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var provider = animation.static_value(engine) var provider = animation.static_value(engine)
@ -34,7 +34,7 @@ def test_static_value_provider_types()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test with integer # Test with integer
var int_provider = animation.static_value(engine) var int_provider = animation.static_value(engine)
@ -60,7 +60,7 @@ def test_universal_get_methods()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var provider = animation.static_value(engine) var provider = animation.static_value(engine)
provider.value = 99 provider.value = 99
@ -96,7 +96,7 @@ def test_comparison_operators()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var provider = animation.static_value(engine) var provider = animation.static_value(engine)
provider.value = 50 provider.value = 50
@ -118,7 +118,7 @@ def test_parameterized_object_integration()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var provider = animation.static_value(engine) var provider = animation.static_value(engine)
@ -144,7 +144,7 @@ def test_value_changes()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var provider = animation.static_value(engine) var provider = animation.static_value(engine)
@ -170,7 +170,7 @@ def test_string_representation()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var provider = animation.static_value(engine) var provider = animation.static_value(engine)
provider.value = 42 provider.value = 42

View File

@ -49,7 +49,7 @@ def test_basic_functionality()
for length : test_lengths for length : test_lengths
# Create mock strip and engine # Create mock strip and engine
var strip = MockStrip(length) var strip = MockStrip(length)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create StripLengthProvider # Create StripLengthProvider
var provider = animation.strip_length(engine) var provider = animation.strip_length(engine)
@ -75,7 +75,7 @@ def test_string_representation()
print(" Testing string representation...") print(" Testing string representation...")
var strip = MockStrip(42) var strip = MockStrip(42)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var provider = animation.strip_length(engine) var provider = animation.strip_length(engine)
var str_repr = str(provider) var str_repr = str(provider)
@ -106,7 +106,7 @@ def test_integration()
print(" Testing integration with animation system...") print(" Testing integration with animation system...")
var strip = MockStrip(20) var strip = MockStrip(20)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var provider = animation.strip_length(engine) var provider = animation.strip_length(engine)
# Test that it's recognized as a value provider # Test that it's recognized as a value provider
@ -128,7 +128,7 @@ def test_engine_consistency()
print(" Testing consistency with engine properties...") print(" Testing consistency with engine properties...")
var strip = MockStrip(100) var strip = MockStrip(100)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var provider = animation.strip_length(engine) var provider = animation.strip_length(engine)
# Test that provider returns same value as engine properties # Test that provider returns same value as engine properties

View File

@ -47,6 +47,7 @@ def run_all_tests()
# Core framework tests # Core framework tests
"lib/libesp32/berry_animation/src/tests/frame_buffer_test.be", "lib/libesp32/berry_animation/src/tests/frame_buffer_test.be",
"lib/libesp32/berry_animation/src/tests/constraint_encoding_test.be", # Tests parameter constraint encoding/decoding
"lib/libesp32/berry_animation/src/tests/nillable_parameter_test.be", "lib/libesp32/berry_animation/src/tests/nillable_parameter_test.be",
"lib/libesp32/berry_animation/src/tests/parameterized_object_test.be", # Tests parameter management base class "lib/libesp32/berry_animation/src/tests/parameterized_object_test.be", # Tests parameter management base class
"lib/libesp32/berry_animation/src/tests/bytes_type_test.be", # Tests bytes type validation in parameterized objects "lib/libesp32/berry_animation/src/tests/bytes_type_test.be", # Tests bytes type validation in parameterized objects

View File

@ -13,7 +13,7 @@ print("=== Comprehensive Twinkle Animation Test ===")
# Test 1: Basic Twinkle Animation Creation # Test 1: Basic Twinkle Animation Creation
print("\n1. Testing basic twinkle animation creation...") print("\n1. Testing basic twinkle animation creation...")
var strip = global.Leds(30) var strip = global.Leds(30)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var twinkle = animation.twinkle_animation(engine) var twinkle = animation.twinkle_animation(engine)
twinkle.color = 0xFFFFFFFF twinkle.color = 0xFFFFFFFF
twinkle.density = 128 twinkle.density = 128
@ -384,7 +384,7 @@ print("\n16. Testing edge cases...")
# Very small strip # Very small strip
var tiny_strip = global.Leds(1) var tiny_strip = global.Leds(1)
var tiny_engine = animation.animation_engine(tiny_strip) var tiny_engine = animation.create_engine(tiny_strip)
var tiny_twinkle = animation.twinkle_classic(tiny_engine) var tiny_twinkle = animation.twinkle_classic(tiny_engine)
tiny_twinkle.density = 200 tiny_twinkle.density = 200
tiny_twinkle.start() tiny_twinkle.start()

View File

@ -5,13 +5,15 @@
import animation import animation
import "./core/param_encoder" as encode_constraints
# Test the basic ValueProvider interface # Test the basic ValueProvider interface
def test_value_provider_interface() def test_value_provider_interface()
print("Testing ValueProvider interface...") print("Testing ValueProvider interface...")
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var provider = animation.value_provider(engine) var provider = animation.value_provider(engine)
@ -32,14 +34,14 @@ def test_custom_value_provider()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create a simple time-based provider using new API # Create a simple time-based provider using new API
class TimeBasedProvider : animation.value_provider class TimeBasedProvider : animation.value_provider
# Parameter definitions # Parameter definitions
static var PARAMS = { static var PARAMS = encode_constraints({
"multiplier": {"default": 1} "multiplier": {"default": 1}
} })
def init(engine) def init(engine)
super(self).init(engine) super(self).init(engine)
@ -71,7 +73,7 @@ def test_is_value_provider()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var base_provider = animation.value_provider(engine) var base_provider = animation.value_provider(engine)
@ -89,7 +91,7 @@ def test_parameterized_object_integration()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var provider = animation.value_provider(engine) var provider = animation.value_provider(engine)
@ -113,7 +115,7 @@ def test_lifecycle_methods()
# Create engine for testing # Create engine for testing
var strip = global.Leds() var strip = global.Leds()
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Create a provider that tracks start calls # Create a provider that tracks start calls
class LifecycleProvider : animation.value_provider class LifecycleProvider : animation.value_provider

View File

@ -12,7 +12,7 @@ def test_wave_animation_basic()
# Create engine and animation # Create engine and animation
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var wave_anim = animation.wave_animation(engine) var wave_anim = animation.wave_animation(engine)
assert(wave_anim != nil, "WaveAnimation should be created") assert(wave_anim != nil, "WaveAnimation should be created")
@ -34,7 +34,7 @@ def test_wave_animation_custom()
# Create engine and animation with custom parameters # Create engine and animation with custom parameters
var strip = global.Leds(20) var strip = global.Leds(20)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var wave_anim = animation.wave_animation(engine) var wave_anim = animation.wave_animation(engine)
# Set custom parameters using virtual member access # Set custom parameters using virtual member access
@ -69,7 +69,7 @@ def test_wave_animation_parameters()
print("Testing WaveAnimation parameter changes...") print("Testing WaveAnimation parameter changes...")
var strip = global.Leds(15) var strip = global.Leds(15)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var wave_anim = animation.wave_animation(engine) var wave_anim = animation.wave_animation(engine)
# Test parameter changes using virtual member access # Test parameter changes using virtual member access
@ -96,7 +96,7 @@ def test_wave_animation_update_render()
print("Testing WaveAnimation update and render...") print("Testing WaveAnimation update and render...")
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var wave_anim = animation.wave_animation(engine) var wave_anim = animation.wave_animation(engine)
# Set parameters # Set parameters
@ -141,7 +141,7 @@ def test_wave_types()
print("Testing different wave types...") print("Testing different wave types...")
var strip = global.Leds(10) var strip = global.Leds(10)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var frame = animation.frame_buffer(10) var frame = animation.frame_buffer(10)
# Test each wave type # Test each wave type
@ -172,7 +172,7 @@ def test_wave_constructors()
print("Testing wave constructor functions...") print("Testing wave constructor functions...")
var strip = global.Leds(30) var strip = global.Leds(30)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
# Test wave_rainbow_sine # Test wave_rainbow_sine
var rainbow_wave = animation.wave_rainbow_sine(engine) var rainbow_wave = animation.wave_rainbow_sine(engine)
@ -203,7 +203,7 @@ def test_wave_tostring()
print("Testing WaveAnimation string representation...") print("Testing WaveAnimation string representation...")
var strip = global.Leds(12) var strip = global.Leds(12)
var engine = animation.animation_engine(strip) var engine = animation.create_engine(strip)
var wave_anim = animation.wave_animation(engine) var wave_anim = animation.wave_animation(engine)
# Set parameters # Set parameters

View File

@ -621,8 +621,8 @@
#define USE_LIGHT_ARTNET_MCAST 239,255,25,54 // Multicast address used to listen: 239.255.25.54 #define USE_LIGHT_ARTNET_MCAST 239,255,25,54 // Multicast address used to listen: 239.255.25.54
#define USE_BERRY_ANIMATE // Legacy tentative for LED animation framework, DEPRECATED #define USE_BERRY_ANIMATE // Legacy tentative for LED animation framework, DEPRECATED
// #define USE_BERRY_ANIMATION // New animation framework with dedicated language (ESP32x only, experimental, 117k not yet optimized) // #define USE_BERRY_ANIMATION // New animation framework with dedicated language (ESP32x only, experimental, 94k not yet optimized)
// #define USE_BERRY_ANIMATION_DSL // DSL transpiler for new animation framework (not mandatory if DSL is transpiled externally, +59k not optimized yet) // #define USE_BERRY_ANIMATION_DSL // DSL transpiler for new animation framework (not mandatory if DSL is transpiled externally, +98k not optimized yet)
// -- Counter input ------------------------------- // -- Counter input -------------------------------
#define USE_COUNTER // Enable inputs as counter (+0k8 code) #define USE_COUNTER // Enable inputs as counter (+0k8 code)