Berry animation DSL: computed parameters and simplified generated code (#23828)

This commit is contained in:
s-hadinger 2025-08-25 19:39:29 +02:00 committed by GitHub
parent 517eae733b
commit 72ddde049e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 15039 additions and 10867 deletions

View File

@ -57,22 +57,16 @@ var aurora_colors_ = bytes("00000022" "40004400" "8000AA44" "C044AA88" "FF88FFAA
var aurora_purple_ = bytes("00220022" "40440044" "808800AA" "C0AA44CC" "FFCCAAFF")
# Base aurora animation with slow flowing colors
var aurora_base_ = animation.rich_palette_animation(engine)
aurora_base_.palette = animation.global('aurora_colors_', 'aurora_colors') # palette
aurora_base_.palette = aurora_colors_ # palette
aurora_base_.cycle_period = 10000 # cycle period
aurora_base_.transition_type = animation.global('SINE_', 'SINE') # transition type (explicit for clarity)
aurora_base_.transition_type = animation.SINE # transition type (explicit for clarity)
aurora_base_.brightness = 180 # brightness (dimmed for aurora effect)
def sequence_demo()
var demo_ = (def (engine)
var steps = []
steps.push(animation.create_play_step(animation.global('aurora_base_'), 0)) # infinite duration (no 'for' clause)
var seq_manager = animation.SequenceManager(engine)
seq_manager.start_sequence(steps)
return seq_manager
end
# Start all animations/sequences
if global.contains('sequence_demo')
var seq_manager = global.sequence_demo()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('demo_'))
end
end)(engine)
engine.add_sequence_manager(demo_)
engine.start()

View File

@ -66,11 +66,11 @@ var breathe_orange_ = 0xFFFF8000
var breathe_palette_ = bytes("00FF0000" "33FF8000" "66FFFF00" "9900FF00" "CC0000FF" "FF800080")
# Create a rich palette color provider
var palette_pattern_ = animation.rich_palette(engine)
palette_pattern_.palette = animation.global('breathe_palette_', 'breathe_palette') # palette
palette_pattern_.palette = breathe_palette_ # palette
palette_pattern_.cycle_period = 15000 # cycle period (defaults: smooth transition, 255 brightness)
# Create breathing animation using the palette
var breathing_ = animation.breathe_animation(engine)
breathing_.color = animation.global('palette_pattern_', 'palette_pattern') # base animation
breathing_.color = palette_pattern_ # base animation
breathing_.min_brightness = 100 # min brightness
breathing_.max_brightness = 255 # max brightness
breathing_.period = 4000 # breathing period (4 seconds)
@ -79,13 +79,7 @@ var temp_smooth_152 = animation.smooth(engine)
temp_smooth_152.min_value = 100
temp_smooth_152.max_value = 255
temp_smooth_152.duration = 4000
animation.global('breathing_').opacity = temp_smooth_152
breathing_.opacity = temp_smooth_152
# Start the animation
# Start all animations/sequences
if global.contains('sequence_breathing')
var seq_manager = global.sequence_breathing()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('breathing_'))
end
engine.add_animation(breathing_)
engine.start()

View File

@ -67,52 +67,52 @@ var candy_white_ = 0xFFFFFFFF
# Create alternating red and white animation
# Using multiple pulse positions to create stripes
var stripe1_ = animation.beacon_animation(engine)
stripe1_.color = animation.global('candy_red_', 'candy_red')
stripe1_.color = candy_red_
stripe1_.pos = 3
stripe1_.beacon_size = 4
stripe1_.slew_size = 1
var stripe2_ = animation.beacon_animation(engine)
stripe2_.color = animation.global('candy_white_', 'candy_white')
stripe2_.color = candy_white_
stripe2_.pos = 9
stripe2_.beacon_size = 4
stripe2_.slew_size = 1
var stripe3_ = animation.beacon_animation(engine)
stripe3_.color = animation.global('candy_red_', 'candy_red')
stripe3_.color = candy_red_
stripe3_.pos = 15
stripe3_.beacon_size = 4
stripe3_.slew_size = 1
var stripe4_ = animation.beacon_animation(engine)
stripe4_.color = animation.global('candy_white_', 'candy_white')
stripe4_.color = candy_white_
stripe4_.pos = 21
stripe4_.beacon_size = 4
stripe4_.slew_size = 1
var stripe5_ = animation.beacon_animation(engine)
stripe5_.color = animation.global('candy_red_', 'candy_red')
stripe5_.color = candy_red_
stripe5_.pos = 27
stripe5_.beacon_size = 4
stripe5_.slew_size = 1
var stripe6_ = animation.beacon_animation(engine)
stripe6_.color = animation.global('candy_white_', 'candy_white')
stripe6_.color = candy_white_
stripe6_.pos = 33
stripe6_.beacon_size = 4
stripe6_.slew_size = 1
var stripe7_ = animation.beacon_animation(engine)
stripe7_.color = animation.global('candy_red_', 'candy_red')
stripe7_.color = candy_red_
stripe7_.pos = 39
stripe7_.beacon_size = 4
stripe7_.slew_size = 1
var stripe8_ = animation.beacon_animation(engine)
stripe8_.color = animation.global('candy_white_', 'candy_white')
stripe8_.color = candy_white_
stripe8_.pos = 45
stripe8_.beacon_size = 4
stripe8_.slew_size = 1
var stripe9_ = animation.beacon_animation(engine)
stripe9_.color = animation.global('candy_red_', 'candy_red')
stripe9_.color = candy_red_
stripe9_.pos = 51
stripe9_.beacon_size = 4
stripe9_.slew_size = 1
var stripe10_ = animation.beacon_animation(engine)
stripe10_.color = animation.global('candy_white_', 'candy_white')
stripe10_.color = candy_white_
stripe10_.pos = 57
stripe10_.beacon_size = 4
stripe10_.slew_size = 1
@ -121,113 +121,62 @@ var move_speed_ = 8000
var temp_sawtooth_258 = animation.sawtooth(engine)
temp_sawtooth_258.min_value = 3
temp_sawtooth_258.max_value = 63
temp_sawtooth_258.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe1_').pos = temp_sawtooth_258
temp_sawtooth_258.duration = move_speed_
stripe1_.pos = temp_sawtooth_258
var temp_sawtooth_277 = animation.sawtooth(engine)
temp_sawtooth_277.min_value = 9
temp_sawtooth_277.max_value = 69
temp_sawtooth_277.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe2_').pos = temp_sawtooth_277
temp_sawtooth_277.duration = move_speed_
stripe2_.pos = temp_sawtooth_277
var temp_sawtooth_296 = animation.sawtooth(engine)
temp_sawtooth_296.min_value = 15
temp_sawtooth_296.max_value = 75
temp_sawtooth_296.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe3_').pos = temp_sawtooth_296
temp_sawtooth_296.duration = move_speed_
stripe3_.pos = temp_sawtooth_296
var temp_sawtooth_315 = animation.sawtooth(engine)
temp_sawtooth_315.min_value = 21
temp_sawtooth_315.max_value = 81
temp_sawtooth_315.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe4_').pos = temp_sawtooth_315
temp_sawtooth_315.duration = move_speed_
stripe4_.pos = temp_sawtooth_315
var temp_sawtooth_334 = animation.sawtooth(engine)
temp_sawtooth_334.min_value = 27
temp_sawtooth_334.max_value = 87
temp_sawtooth_334.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe5_').pos = temp_sawtooth_334
temp_sawtooth_334.duration = move_speed_
stripe5_.pos = temp_sawtooth_334
var temp_sawtooth_353 = animation.sawtooth(engine)
temp_sawtooth_353.min_value = 33
temp_sawtooth_353.max_value = 93
temp_sawtooth_353.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe6_').pos = temp_sawtooth_353
temp_sawtooth_353.duration = move_speed_
stripe6_.pos = temp_sawtooth_353
var temp_sawtooth_372 = animation.sawtooth(engine)
temp_sawtooth_372.min_value = 39
temp_sawtooth_372.max_value = 99
temp_sawtooth_372.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe7_').pos = temp_sawtooth_372
temp_sawtooth_372.duration = move_speed_
stripe7_.pos = temp_sawtooth_372
var temp_sawtooth_391 = animation.sawtooth(engine)
temp_sawtooth_391.min_value = 45
temp_sawtooth_391.max_value = 105
temp_sawtooth_391.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe8_').pos = temp_sawtooth_391
temp_sawtooth_391.duration = move_speed_
stripe8_.pos = temp_sawtooth_391
var temp_sawtooth_410 = animation.sawtooth(engine)
temp_sawtooth_410.min_value = 51
temp_sawtooth_410.max_value = 111
temp_sawtooth_410.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe9_').pos = temp_sawtooth_410
temp_sawtooth_410.duration = move_speed_
stripe9_.pos = temp_sawtooth_410
var temp_sawtooth_429 = animation.sawtooth(engine)
temp_sawtooth_429.min_value = 57
temp_sawtooth_429.max_value = 117
temp_sawtooth_429.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe10_').pos = temp_sawtooth_429
temp_sawtooth_429.duration = move_speed_
stripe10_.pos = temp_sawtooth_429
# Start all stripes
# Start all animations/sequences
if global.contains('sequence_stripe1')
var seq_manager = global.sequence_stripe1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe1_'))
end
if global.contains('sequence_stripe2')
var seq_manager = global.sequence_stripe2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe2_'))
end
if global.contains('sequence_stripe3')
var seq_manager = global.sequence_stripe3()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe3_'))
end
if global.contains('sequence_stripe4')
var seq_manager = global.sequence_stripe4()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe4_'))
end
if global.contains('sequence_stripe5')
var seq_manager = global.sequence_stripe5()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe5_'))
end
if global.contains('sequence_stripe6')
var seq_manager = global.sequence_stripe6()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe6_'))
end
if global.contains('sequence_stripe7')
var seq_manager = global.sequence_stripe7()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe7_'))
end
if global.contains('sequence_stripe8')
var seq_manager = global.sequence_stripe8()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe8_'))
end
if global.contains('sequence_stripe9')
var seq_manager = global.sequence_stripe9()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe9_'))
end
if global.contains('sequence_stripe10')
var seq_manager = global.sequence_stripe10()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe10_'))
end
engine.add_animation(stripe1_)
engine.add_animation(stripe2_)
engine.add_animation(stripe3_)
engine.add_animation(stripe4_)
engine.add_animation(stripe5_)
engine.add_animation(stripe6_)
engine.add_animation(stripe7_)
engine.add_animation(stripe8_)
engine.add_animation(stripe9_)
engine.add_animation(stripe10_)
engine.start()

View File

@ -78,79 +78,53 @@ var engine = animation.init_strip()
var tree_green_ = 0xFF006600
var tree_base_ = animation.solid(engine)
tree_base_.color = animation.global('tree_green_', 'tree_green')
tree_base_.color = tree_green_
# Define ornament colors
var ornament_colors_ = bytes("00FF0000" "40FFD700" "800000FF" "C0FFFFFF" "FFFF00FF")
# Colorful ornaments as twinkling lights
var ornament_pattern_ = animation.rich_palette(engine)
ornament_pattern_.palette = animation.global('ornament_colors_', 'ornament_colors')
ornament_pattern_.palette = ornament_colors_
ornament_pattern_.cycle_period = 3000
ornament_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
ornament_pattern_.transition_type = animation.LINEAR
ornament_pattern_.brightness = 255
var ornaments_ = animation.twinkle_animation(engine)
ornaments_.color = animation.global('ornament_pattern_', 'ornament_pattern') # color source
ornaments_.color = ornament_pattern_ # color source
ornaments_.density = 15 # density (many ornaments)
ornaments_.twinkle_speed = 800 # twinkle speed (slow twinkle)
animation.global('ornaments_').priority = 10
ornaments_.priority = 10
# Star on top (bright yellow pulse)
var tree_star_ = animation.beacon_animation(engine)
tree_star_.color = 0xFFFFFF00 # Bright yellow
tree_star_.pos = 58 # position (near the top)
tree_star_.beacon_size = 3 # star size
tree_star_.slew_size = 1 # sharp edges
animation.global('tree_star_').priority = 20
tree_star_.priority = 20
var temp_smooth_170 = animation.smooth(engine)
temp_smooth_170.min_value = 200
temp_smooth_170.max_value = 255
temp_smooth_170.duration = 2000
animation.global('tree_star_').opacity = temp_smooth_170 # Gentle pulsing
tree_star_.opacity = temp_smooth_170 # Gentle pulsing
# Add some white sparkles for snow/magic
var snow_sparkles_ = animation.twinkle_animation(engine)
snow_sparkles_.color = 0xFFFFFFFF # White snow
snow_sparkles_.density = 8 # density (sparkle count)
snow_sparkles_.twinkle_speed = 400 # twinkle speed (quick sparkles)
animation.global('snow_sparkles_').priority = 15
snow_sparkles_.priority = 15
# Garland effect - moving colored lights
var garland_pattern_ = animation.rich_palette(engine)
garland_pattern_.palette = animation.global('ornament_colors_', 'ornament_colors')
garland_pattern_.palette = ornament_colors_
garland_pattern_.cycle_period = 2000
garland_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
garland_pattern_.transition_type = animation.LINEAR
garland_pattern_.brightness = 200
var garland_ = animation.comet_animation(engine)
garland_.color = animation.global('garland_pattern_', 'garland_pattern') # color source
garland_.color = garland_pattern_ # color source
garland_.tail_length = 6 # garland length (tail length)
garland_.speed = 4000 # slow movement (speed)
animation.global('garland_').priority = 5
garland_.priority = 5
# Start all animations
# Start all animations/sequences
if global.contains('sequence_tree_base')
var seq_manager = global.sequence_tree_base()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('tree_base_'))
end
if global.contains('sequence_ornaments')
var seq_manager = global.sequence_ornaments()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('ornaments_'))
end
if global.contains('sequence_tree_star')
var seq_manager = global.sequence_tree_star()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('tree_star_'))
end
if global.contains('sequence_snow_sparkles')
var seq_manager = global.sequence_snow_sparkles()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('snow_sparkles_'))
end
if global.contains('sequence_garland')
var seq_manager = global.sequence_garland()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('garland_'))
end
engine.add_animation(tree_base_)
engine.add_animation(ornaments_)
engine.add_animation(tree_star_)
engine.add_animation(snow_sparkles_)
engine.add_animation(garland_)
engine.start()

View File

@ -57,50 +57,29 @@ var engine = animation.init_strip()
var space_blue_ = 0xFF000066 # Note: opaque 0xFF alpha channel is implicitly added
var background_ = animation.solid(engine)
background_.color = animation.global('space_blue_', 'space_blue')
background_.color = space_blue_
# Main comet with bright white head
var comet_main_ = animation.comet_animation(engine)
comet_main_.color = 0xFFFFFFFF # White head
comet_main_.tail_length = 10 # tail length
comet_main_.speed = 2000 # speed
animation.global('comet_main_').priority = 7
comet_main_.priority = 7
# Secondary comet in different color, opposite direction
var comet_secondary_ = animation.comet_animation(engine)
comet_secondary_.color = 0xFFFF4500 # Orange head
comet_secondary_.tail_length = 8 # shorter tail
comet_secondary_.speed = 3000 # slower speed
comet_secondary_.direction = -1 # other direction
animation.global('comet_secondary_').priority = 5
comet_secondary_.direction = (-1) # other direction
comet_secondary_.priority = 5
# Add sparkle trail behind comets but on top of blue background
var comet_sparkles_ = animation.twinkle_animation(engine)
comet_sparkles_.color = 0xFFAAAAFF # Light blue sparkles
comet_sparkles_.density = 8 # density (moderate sparkles)
comet_sparkles_.twinkle_speed = 400 # twinkle speed (quick sparkle)
animation.global('comet_sparkles_').priority = 8
comet_sparkles_.priority = 8
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')
var seq_manager = global.sequence_background()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('background_'))
end
if global.contains('sequence_comet_main')
var seq_manager = global.sequence_comet_main()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('comet_main_'))
end
if global.contains('sequence_comet_secondary')
var seq_manager = global.sequence_comet_secondary()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('comet_secondary_'))
end
if global.contains('sequence_comet_sparkles')
var seq_manager = global.sequence_comet_sparkles()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('comet_sparkles_'))
end
engine.add_animation(background_)
engine.add_animation(comet_main_)
engine.add_animation(comet_secondary_)
engine.add_animation(comet_sparkles_)
engine.start()

View File

@ -0,0 +1,71 @@
# Generated Berry code from Animation DSL
# Source: computed_values_demo.anim
# Generated automatically
#
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Computed Values Demo - Example from the original request
# # Shows how to use computed values from value providers
#
# # Get the current strip length
# set strip_len = strip_length()
#
# # Create animation with computed values
# animation stream1 = comet_animation(
# color=red
# tail_length=abs(strip_len / 4) # computed value
# speed=1.5
# priority=10
# )
#
# # More complex computed values
# set base_speed = 2.0
# animation stream2 = comet_animation(
# color=blue
# tail_length=strip_len / 8 + 2 # computed with addition
# speed=base_speed * 1.5 # computed with multiplication
# direction=-1
# priority=5
# )
#
# # Property assignment with computed values
# stream1.tail_length = strip_len / 5
# stream2.opacity = strip_len * 4
#
# # Run both animations
# run stream1
# run stream2
import animation
# Computed Values Demo - Example from the original request
# Shows how to use computed values from value providers
# Get the current strip length
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var temp_strip_length_11 = animation.strip_length(engine)
var strip_len_ = temp_strip_length_11
# Create animation with computed values
var stream1_ = animation.comet_animation(engine)
stream1_.color = 0xFFFF0000
stream1_.tail_length = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.abs(self.resolve(self.resolve(strip_len_, param_name, time_ms), param_name, time_ms) / 4)) end) # computed value
stream1_.speed = 1.5
stream1_.priority = 10
# More complex computed values
var base_speed_ = 2.0
var stream2_ = animation.comet_animation(engine)
stream2_.color = 0xFF0000FF
stream2_.tail_length = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(strip_len_, param_name, time_ms) / 8 + 2) end) # computed with addition
stream2_.speed = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(base_speed_, param_name, time_ms) * 1.5) end) # computed with multiplication
stream2_.direction = (-1)
stream2_.priority = 5
# Property assignment with computed values
stream1_.tail_length = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(strip_len_, param_name, time_ms) / 5) end)
stream2_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.resolve(strip_len_, param_name, time_ms) * 4) end)
# Run both animations
engine.add_animation(stream1_)
engine.add_animation(stream2_)
engine.start()

View File

@ -71,9 +71,9 @@ var engine = animation.init_strip()
var disco_colors_ = bytes("00FF0000" "2AFF8000" "55FFFF00" "8000FF00" "AA0000FF" "D58000FF" "FFFF00FF")
# Fast color cycling base
var disco_base_ = animation.rich_palette_animation(engine)
disco_base_.palette = animation.global('disco_colors_', 'disco_colors')
disco_base_.palette = disco_colors_
disco_base_.cycle_period = 1000
disco_base_.transition_type = animation.global('LINEAR_', 'LINEAR')
disco_base_.transition_type = animation.LINEAR
disco_base_.brightness = 255
# Add strobe effect
var temp_square_105 = animation.square(engine)
@ -81,7 +81,7 @@ temp_square_105.min_value = 0
temp_square_105.max_value = 255
temp_square_105.duration = 100
temp_square_105.duty_cycle = 30
animation.global('disco_base_').opacity = temp_square_105 # Fast strobe
disco_base_.opacity = temp_square_105 # Fast strobe
# Add white flash overlay
var white_flash_ = animation.solid(engine)
white_flash_.color = 0xFFFFFFFF
@ -90,60 +90,39 @@ temp_square_142.min_value = 0
temp_square_142.max_value = 255
temp_square_142.duration = 50
temp_square_142.duty_cycle = 10
animation.global('white_flash_').opacity = temp_square_142 # Quick white flashes
animation.global('white_flash_').priority = 20
white_flash_.opacity = temp_square_142 # Quick white flashes
white_flash_.priority = 20
# Add colored sparkles
var sparkle_pattern_ = animation.rich_palette(engine)
sparkle_pattern_.palette = animation.global('disco_colors_', 'disco_colors')
sparkle_pattern_.palette = disco_colors_
sparkle_pattern_.cycle_period = 500
sparkle_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
sparkle_pattern_.transition_type = animation.LINEAR
sparkle_pattern_.brightness = 255
var disco_sparkles_ = animation.twinkle_animation(engine)
disco_sparkles_.color = animation.global('sparkle_pattern_', 'sparkle_pattern') # color source
disco_sparkles_.color = sparkle_pattern_ # color source
disco_sparkles_.density = 12 # density (many sparkles)
disco_sparkles_.twinkle_speed = 80 # twinkle speed (very quick)
animation.global('disco_sparkles_').priority = 15
disco_sparkles_.priority = 15
# Add moving pulse for extra effect
var pulse_pattern_ = animation.rich_palette(engine)
pulse_pattern_.palette = animation.global('disco_colors_', 'disco_colors')
pulse_pattern_.palette = disco_colors_
pulse_pattern_.cycle_period = 800
pulse_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
pulse_pattern_.transition_type = animation.LINEAR
pulse_pattern_.brightness = 255
var disco_pulse_ = animation.beacon_animation(engine)
disco_pulse_.color = animation.global('pulse_pattern_', 'pulse_pattern') # color source
disco_pulse_.color = pulse_pattern_ # color source
disco_pulse_.pos = 4 # initial position
disco_pulse_.beacon_size = 8 # pulse width
disco_pulse_.slew_size = 2 # sharp edges (slew size)
animation.global('disco_pulse_').priority = 10
disco_pulse_.priority = 10
var temp_sawtooth_285 = animation.sawtooth(engine)
temp_sawtooth_285.min_value = 4
temp_sawtooth_285.max_value = 56
temp_sawtooth_285.duration = 2000
animation.global('disco_pulse_').pos = temp_sawtooth_285 # Fast movement
disco_pulse_.pos = temp_sawtooth_285 # Fast movement
# Start all animations
# Start all animations/sequences
if global.contains('sequence_disco_base')
var seq_manager = global.sequence_disco_base()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('disco_base_'))
end
if global.contains('sequence_white_flash')
var seq_manager = global.sequence_white_flash()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('white_flash_'))
end
if global.contains('sequence_disco_sparkles')
var seq_manager = global.sequence_disco_sparkles()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('disco_sparkles_'))
end
if global.contains('sequence_disco_pulse')
var seq_manager = global.sequence_disco_pulse()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('disco_pulse_'))
end
engine.add_animation(disco_base_)
engine.add_animation(white_flash_)
engine.add_animation(disco_sparkles_)
engine.add_animation(disco_pulse_)
engine.start()

View File

@ -51,39 +51,28 @@ var engine = animation.init_strip()
var fire_colors_ = bytes("00000000" "40800000" "80FF0000" "C0FF4500" "FFFFFF00")
# Create base fire animation with palette
var fire_base_ = animation.rich_palette_animation(engine)
fire_base_.palette = animation.global('fire_colors_', 'fire_colors')
fire_base_.palette = fire_colors_
fire_base_.cycle_period = 3000
fire_base_.transition_type = animation.global('LINEAR_', 'LINEAR')
fire_base_.transition_type = animation.LINEAR
fire_base_.brightness = 255
# Add flickering effect with random intensity changes
var temp_smooth_89 = animation.smooth(engine)
temp_smooth_89.min_value = 180
temp_smooth_89.max_value = 255
temp_smooth_89.duration = 800
animation.global('fire_base_').opacity = temp_smooth_89
fire_base_.opacity = temp_smooth_89
# Add subtle position variation for more realism
var flicker_pattern_ = animation.rich_palette(engine)
flicker_pattern_.palette = animation.global('fire_colors_', 'fire_colors')
flicker_pattern_.palette = fire_colors_
flicker_pattern_.cycle_period = 2000
flicker_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
flicker_pattern_.transition_type = animation.LINEAR
flicker_pattern_.brightness = 255
var fire_flicker_ = animation.twinkle_animation(engine)
fire_flicker_.color = animation.global('flicker_pattern_', 'flicker_pattern') # color source
fire_flicker_.color = flicker_pattern_ # color source
fire_flicker_.density = 12 # density (number of flickers)
fire_flicker_.twinkle_speed = 200 # twinkle speed (flicker duration)
animation.global('fire_flicker_').priority = 10
fire_flicker_.priority = 10
# Start both animations
# Start all animations/sequences
if global.contains('sequence_fire_base')
var seq_manager = global.sequence_fire_base()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('fire_base_'))
end
if global.contains('sequence_fire_flicker')
var seq_manager = global.sequence_fire_flicker()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('fire_flicker_'))
end
engine.add_animation(fire_base_)
engine.add_animation(fire_flicker_)
engine.start()

View File

@ -60,7 +60,7 @@ var engine = animation.init_strip()
var heart_bg_ = 0xFF110000
var background_ = animation.solid(engine)
background_.color = animation.global('heart_bg_', 'heart_bg')
background_.color = heart_bg_
# Define heartbeat timing - double pulse animation
# First pulse (stronger)
var heartbeat1_ = animation.solid(engine)
@ -71,8 +71,8 @@ temp_square_46.min_value = 0
temp_square_46.max_value = 255
temp_square_46.duration = 150
temp_square_46.duty_cycle = 20
animation.global('heartbeat1_').opacity = temp_square_46 # Quick strong pulse
animation.global('heartbeat1_').priority = 10
heartbeat1_.opacity = temp_square_46 # Quick strong pulse
heartbeat1_.priority = 10
# Second pulse (weaker, slightly delayed)
var heartbeat2_ = animation.solid(engine)
heartbeat2_.color = 0xFFCC0000
@ -83,8 +83,8 @@ temp_square_92.min_value = 0
temp_square_92.max_value = 180
temp_square_92.duration = 150
temp_square_92.duty_cycle = 15
animation.global('heartbeat2_').opacity = temp_square_92 # Weaker pulse
animation.global('heartbeat2_').priority = 8
heartbeat2_.opacity = temp_square_92 # Weaker pulse
heartbeat2_.priority = 8
# Add subtle glow effect
var heart_glow_ = animation.solid(engine)
heart_glow_.color = 0xFF660000
@ -93,51 +93,25 @@ var temp_smooth_136 = animation.smooth(engine)
temp_smooth_136.min_value = 30
temp_smooth_136.max_value = 100
temp_smooth_136.duration = 1000
animation.global('heart_glow_').opacity = temp_smooth_136 # Gentle breathing glow
animation.global('heart_glow_').priority = 5
heart_glow_.opacity = temp_smooth_136 # Gentle breathing glow
heart_glow_.priority = 5
# Add center pulse for emphasis
var center_pulse_ = animation.beacon_animation(engine)
center_pulse_.color = 0xFFFFFFFF # White center
center_pulse_.pos = 30 # center of strip
center_pulse_.beacon_size = 4 # small center
center_pulse_.slew_size = 2 # soft edges
animation.global('center_pulse_').priority = 20
center_pulse_.priority = 20
var temp_square_199 = animation.square(engine)
temp_square_199.min_value = 0
temp_square_199.max_value = 200
temp_square_199.duration = 100
temp_square_199.duty_cycle = 10
animation.global('center_pulse_').opacity = temp_square_199 # Quick white flash
center_pulse_.opacity = temp_square_199 # Quick white flash
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')
var seq_manager = global.sequence_background()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('background_'))
end
if global.contains('sequence_heart_glow')
var seq_manager = global.sequence_heart_glow()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('heart_glow_'))
end
if global.contains('sequence_heartbeat1')
var seq_manager = global.sequence_heartbeat1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('heartbeat1_'))
end
if global.contains('sequence_heartbeat2')
var seq_manager = global.sequence_heartbeat2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('heartbeat2_'))
end
if global.contains('sequence_center_pulse')
var seq_manager = global.sequence_center_pulse()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('center_pulse_'))
end
engine.add_animation(background_)
engine.add_animation(heart_glow_)
engine.add_animation(heartbeat1_)
engine.add_animation(heartbeat2_)
engine.add_animation(center_pulse_)
engine.start()

View File

@ -82,100 +82,74 @@ var engine = animation.init_strip()
var lava_colors_ = bytes("00330000" "40660000" "80CC3300" "C0FF6600" "FFFFAA00")
# Base lava animation - very slow color changes
var lava_base_ = animation.rich_palette_animation(engine)
lava_base_.palette = animation.global('lava_colors_', 'lava_colors')
lava_base_.palette = lava_colors_
lava_base_.cycle_period = 15000
lava_base_.transition_type = animation.global('SINE_', 'SINE')
lava_base_.transition_type = animation.SINE
lava_base_.brightness = 180
# Add slow-moving lava blobs
var blob1_pattern_ = animation.rich_palette(engine)
blob1_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
blob1_pattern_.palette = lava_colors_
blob1_pattern_.cycle_period = 12000
blob1_pattern_.transition_type = animation.global('SINE_', 'SINE')
blob1_pattern_.transition_type = animation.SINE
blob1_pattern_.brightness = 255
var lava_blob1_ = animation.beacon_animation(engine)
lava_blob1_.color = animation.global('blob1_pattern_', 'blob1_pattern') # color source
lava_blob1_.color = blob1_pattern_ # color source
lava_blob1_.pos = 9 # initial position
lava_blob1_.beacon_size = 18 # large blob
lava_blob1_.slew_size = 12 # very soft edges
animation.global('lava_blob1_').priority = 10
lava_blob1_.priority = 10
var temp_smooth_145 = animation.smooth(engine)
temp_smooth_145.min_value = 9
temp_smooth_145.max_value = 51
temp_smooth_145.duration = 20000
animation.global('lava_blob1_').pos = temp_smooth_145 # Very slow movement
lava_blob1_.pos = temp_smooth_145 # Very slow movement
var blob2_pattern_ = animation.rich_palette(engine)
blob2_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
blob2_pattern_.palette = lava_colors_
blob2_pattern_.cycle_period = 10000
blob2_pattern_.transition_type = animation.global('SINE_', 'SINE')
blob2_pattern_.transition_type = animation.SINE
blob2_pattern_.brightness = 220
var lava_blob2_ = animation.beacon_animation(engine)
lava_blob2_.color = animation.global('blob2_pattern_', 'blob2_pattern') # color source
lava_blob2_.color = blob2_pattern_ # color source
lava_blob2_.pos = 46 # initial position
lava_blob2_.beacon_size = 14 # medium blob
lava_blob2_.slew_size = 10 # soft edges
animation.global('lava_blob2_').priority = 8
lava_blob2_.priority = 8
var temp_smooth_222 = animation.smooth(engine)
temp_smooth_222.min_value = 46
temp_smooth_222.max_value = 14
temp_smooth_222.duration = 25000
animation.global('lava_blob2_').pos = temp_smooth_222 # Opposite direction, slower
lava_blob2_.pos = temp_smooth_222 # Opposite direction, slower
var blob3_pattern_ = animation.rich_palette(engine)
blob3_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
blob3_pattern_.palette = lava_colors_
blob3_pattern_.cycle_period = 8000
blob3_pattern_.transition_type = animation.global('SINE_', 'SINE')
blob3_pattern_.transition_type = animation.SINE
blob3_pattern_.brightness = 200
var lava_blob3_ = animation.beacon_animation(engine)
lava_blob3_.color = animation.global('blob3_pattern_', 'blob3_pattern') # color source
lava_blob3_.color = blob3_pattern_ # color source
lava_blob3_.pos = 25 # initial position
lava_blob3_.beacon_size = 10 # smaller blob
lava_blob3_.slew_size = 8 # soft edges
animation.global('lava_blob3_').priority = 6
lava_blob3_.priority = 6
var temp_smooth_299 = animation.smooth(engine)
temp_smooth_299.min_value = 25
temp_smooth_299.max_value = 35
temp_smooth_299.duration = 18000
animation.global('lava_blob3_').pos = temp_smooth_299 # Small movement range
lava_blob3_.pos = temp_smooth_299 # Small movement range
# Add subtle heat shimmer effect
var shimmer_pattern_ = animation.rich_palette(engine)
shimmer_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
shimmer_pattern_.palette = lava_colors_
shimmer_pattern_.cycle_period = 6000
shimmer_pattern_.transition_type = animation.global('SINE_', 'SINE')
shimmer_pattern_.transition_type = animation.SINE
shimmer_pattern_.brightness = 255
var heat_shimmer_ = animation.twinkle_animation(engine)
heat_shimmer_.color = animation.global('shimmer_pattern_', 'shimmer_pattern') # color source
heat_shimmer_.color = shimmer_pattern_ # color source
heat_shimmer_.density = 6 # density (shimmer points)
heat_shimmer_.twinkle_speed = 1500 # twinkle speed (slow shimmer)
animation.global('heat_shimmer_').priority = 15
heat_shimmer_.priority = 15
# Start all animations
# Start all animations/sequences
if global.contains('sequence_lava_base')
var seq_manager = global.sequence_lava_base()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('lava_base_'))
end
if global.contains('sequence_lava_blob1')
var seq_manager = global.sequence_lava_blob1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('lava_blob1_'))
end
if global.contains('sequence_lava_blob2')
var seq_manager = global.sequence_lava_blob2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('lava_blob2_'))
end
if global.contains('sequence_lava_blob3')
var seq_manager = global.sequence_lava_blob3()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('lava_blob3_'))
end
if global.contains('sequence_heat_shimmer')
var seq_manager = global.sequence_heat_shimmer()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('heat_shimmer_'))
end
engine.add_animation(lava_base_)
engine.add_animation(lava_blob1_)
engine.add_animation(lava_blob2_)
engine.add_animation(lava_blob3_)
engine.add_animation(heat_shimmer_)
engine.start()

View File

@ -66,9 +66,9 @@ var engine = animation.init_strip()
var storm_colors_ = bytes("00000011" "80110022" "FF220033")
var storm_bg_ = animation.rich_palette_animation(engine)
storm_bg_.palette = animation.global('storm_colors_', 'storm_colors')
storm_bg_.palette = storm_colors_
storm_bg_.cycle_period = 12000
storm_bg_.transition_type = animation.global('SINE_', 'SINE')
storm_bg_.transition_type = animation.SINE
storm_bg_.brightness = 100
# Random lightning flashes - full strip
var lightning_main_ = animation.solid(engine)
@ -79,21 +79,21 @@ temp_square_82.min_value = 0
temp_square_82.max_value = 255
temp_square_82.duration = 80
temp_square_82.duty_cycle = 3
animation.global('lightning_main_').opacity = temp_square_82 # Quick bright flashes
animation.global('lightning_main_').priority = 20
lightning_main_.opacity = temp_square_82 # Quick bright flashes
lightning_main_.priority = 20
# Secondary lightning - partial strip
var lightning_partial_ = animation.beacon_animation(engine)
lightning_partial_.color = 0xFFFFFFAA # Slightly yellow white
lightning_partial_.pos = 30 # center position
lightning_partial_.beacon_size = 20 # covers part of strip
lightning_partial_.slew_size = 5 # soft edges
animation.global('lightning_partial_').priority = 15
lightning_partial_.priority = 15
var temp_square_149 = animation.square(engine)
temp_square_149.min_value = 0
temp_square_149.max_value = 200
temp_square_149.duration = 120
temp_square_149.duty_cycle = 4
animation.global('lightning_partial_').opacity = temp_square_149 # Different timing
lightning_partial_.opacity = temp_square_149 # Different timing
# Add blue afterglow
var afterglow_ = animation.solid(engine)
afterglow_.color = 0xFF4444FF
@ -103,44 +103,18 @@ temp_square_187.min_value = 0
temp_square_187.max_value = 80
temp_square_187.duration = 200
temp_square_187.duty_cycle = 8
animation.global('afterglow_').opacity = temp_square_187 # Longer, dimmer glow
animation.global('afterglow_').priority = 10
afterglow_.opacity = temp_square_187 # Longer, dimmer glow
afterglow_.priority = 10
# Distant thunder (dim flashes)
var distant_flash_ = animation.twinkle_animation(engine)
distant_flash_.color = 0xFF666699 # Dim blue-white
distant_flash_.density = 4 # density (few flashes)
distant_flash_.twinkle_speed = 300 # twinkle speed (medium duration)
animation.global('distant_flash_').priority = 5
distant_flash_.priority = 5
# Start all animations
# Start all animations/sequences
if global.contains('sequence_storm_bg')
var seq_manager = global.sequence_storm_bg()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('storm_bg_'))
end
if global.contains('sequence_lightning_main')
var seq_manager = global.sequence_lightning_main()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('lightning_main_'))
end
if global.contains('sequence_lightning_partial')
var seq_manager = global.sequence_lightning_partial()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('lightning_partial_'))
end
if global.contains('sequence_afterglow')
var seq_manager = global.sequence_afterglow()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('afterglow_'))
end
if global.contains('sequence_distant_flash')
var seq_manager = global.sequence_distant_flash()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('distant_flash_'))
end
engine.add_animation(storm_bg_)
engine.add_animation(lightning_main_)
engine.add_animation(lightning_partial_)
engine.add_animation(afterglow_)
engine.add_animation(distant_flash_)
engine.start()

View File

@ -76,38 +76,38 @@ var engine = animation.init_strip()
var matrix_bg_ = 0xFF000000
var background_ = animation.solid(engine)
background_.color = animation.global('matrix_bg_', 'matrix_bg')
background_.color = matrix_bg_
background_.priority = 50
# Define matrix green palette
var matrix_greens_ = bytes("00000000" "40003300" "80006600" "C000AA00" "FF00FF00")
# Create multiple cascading streams
var stream1_pattern_ = animation.rich_palette(engine)
stream1_pattern_.palette = animation.global('matrix_greens_', 'matrix_greens')
stream1_pattern_.palette = matrix_greens_
stream1_pattern_.cycle_period = 2000
stream1_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
stream1_pattern_.transition_type = animation.LINEAR
stream1_pattern_.brightness = 255
var stream1_ = animation.comet_animation(engine)
stream1_.color = animation.global('stream1_pattern_', 'stream1_pattern') # color source
stream1_.color = stream1_pattern_ # color source
stream1_.tail_length = 15 # long tail
stream1_.speed = 1500 # speed
stream1_.priority = 10
var stream2_pattern_ = animation.rich_palette(engine)
stream2_pattern_.palette = animation.global('matrix_greens_', 'matrix_greens')
stream2_pattern_.palette = matrix_greens_
stream2_pattern_.cycle_period = 1800
stream2_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
stream2_pattern_.transition_type = animation.LINEAR
stream2_pattern_.brightness = 200
var stream2_ = animation.comet_animation(engine)
stream2_.color = animation.global('stream2_pattern_', 'stream2_pattern') # color source
stream2_.color = stream2_pattern_ # color source
stream2_.tail_length = 12 # medium tail
stream2_.speed = 2200 # different speed
stream2_.priority = 8
var stream3_pattern_ = animation.rich_palette(engine)
stream3_pattern_.palette = animation.global('matrix_greens_', 'matrix_greens')
stream3_pattern_.palette = matrix_greens_
stream3_pattern_.cycle_period = 2500
stream3_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
stream3_pattern_.transition_type = animation.LINEAR
stream3_pattern_.brightness = 180
var stream3_ = animation.comet_animation(engine)
stream3_.color = animation.global('stream3_pattern_', 'stream3_pattern') # color source
stream3_.color = stream3_pattern_ # color source
stream3_.tail_length = 10 # shorter tail
stream3_.speed = 1800 # another speed
stream3_.priority = 6
@ -118,35 +118,9 @@ code_flash_.density = 3 # density (few flashes)
code_flash_.twinkle_speed = 150 # twinkle speed (quick flash)
code_flash_.priority = 20
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')
var seq_manager = global.sequence_background()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('background_'))
end
if global.contains('sequence_stream1')
var seq_manager = global.sequence_stream1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stream1_'))
end
if global.contains('sequence_stream2')
var seq_manager = global.sequence_stream2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stream2_'))
end
if global.contains('sequence_stream3')
var seq_manager = global.sequence_stream3()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stream3_'))
end
if global.contains('sequence_code_flash')
var seq_manager = global.sequence_code_flash()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('code_flash_'))
end
engine.add_animation(background_)
engine.add_animation(stream1_)
engine.add_animation(stream2_)
engine.add_animation(stream3_)
engine.add_animation(code_flash_)
engine.start()

View File

@ -80,82 +80,46 @@ var engine = animation.init_strip()
var space_bg_ = 0xFF000011
var background_ = animation.solid(engine)
background_.color = animation.global('space_bg_', 'space_bg')
background_.color = space_bg_
# Multiple meteors with different speeds and colors
var meteor1_ = animation.comet_animation(engine)
meteor1_.color = 0xFFFFFFFF # Bright white
meteor1_.tail_length = 12 # long trail
meteor1_.speed = 1500 # fast speed
animation.global('meteor1_').priority = 15
meteor1_.priority = 15
var meteor2_ = animation.comet_animation(engine)
meteor2_.color = 0xFFFFAA00 # Orange
meteor2_.tail_length = 10 # medium trail
meteor2_.speed = 2000 # medium speed
animation.global('meteor2_').priority = 12
meteor2_.priority = 12
var meteor3_ = animation.comet_animation(engine)
meteor3_.color = 0xFFAAAAFF # Blue-white
meteor3_.tail_length = 8 # shorter trail
meteor3_.speed = 1800 # fast speed
animation.global('meteor3_').priority = 10
meteor3_.priority = 10
var meteor4_ = animation.comet_animation(engine)
meteor4_.color = 0xFFFFAAAA # Pink-white
meteor4_.tail_length = 14 # long trail
meteor4_.speed = 2500 # slower speed
animation.global('meteor4_').priority = 8
meteor4_.priority = 8
# Add distant stars
var stars_ = animation.twinkle_animation(engine)
stars_.color = 0xFFCCCCCC # Dim white
stars_.density = 12 # density (many stars)
stars_.twinkle_speed = 2000 # twinkle speed (slow twinkle)
animation.global('stars_').priority = 5
stars_.priority = 5
# Add occasional bright flash (meteor explosion)
var meteor_flash_ = animation.twinkle_animation(engine)
meteor_flash_.color = 0xFFFFFFFF # Bright white
meteor_flash_.density = 1 # density (single flash)
meteor_flash_.twinkle_speed = 100 # twinkle speed (very quick)
animation.global('meteor_flash_').priority = 25
meteor_flash_.priority = 25
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')
var seq_manager = global.sequence_background()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('background_'))
end
if global.contains('sequence_stars')
var seq_manager = global.sequence_stars()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stars_'))
end
if global.contains('sequence_meteor1')
var seq_manager = global.sequence_meteor1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('meteor1_'))
end
if global.contains('sequence_meteor2')
var seq_manager = global.sequence_meteor2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('meteor2_'))
end
if global.contains('sequence_meteor3')
var seq_manager = global.sequence_meteor3()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('meteor3_'))
end
if global.contains('sequence_meteor4')
var seq_manager = global.sequence_meteor4()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('meteor4_'))
end
if global.contains('sequence_meteor_flash')
var seq_manager = global.sequence_meteor_flash()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('meteor_flash_'))
end
engine.add_animation(background_)
engine.add_animation(stars_)
engine.add_animation(meteor1_)
engine.add_animation(meteor2_)
engine.add_animation(meteor3_)
engine.add_animation(meteor4_)
engine.add_animation(meteor_flash_)
engine.start()

View File

@ -84,16 +84,16 @@ var engine = animation.init_strip()
var neon_colors_ = bytes("00FF0080" "5500FF80" "AA8000FF" "FFFF8000")
# Main neon glow with color cycling
var neon_main_ = animation.rich_palette_animation(engine)
neon_main_.palette = animation.global('neon_colors_', 'neon_colors')
neon_main_.palette = neon_colors_
neon_main_.cycle_period = 4000
neon_main_.transition_type = animation.global('LINEAR_', 'LINEAR')
neon_main_.transition_type = animation.LINEAR
neon_main_.brightness = 255
# Add electrical flickering
var temp_smooth_81 = animation.smooth(engine)
temp_smooth_81.min_value = 220
temp_smooth_81.max_value = 255
temp_smooth_81.duration = 200
animation.global('neon_main_').opacity = temp_smooth_81
neon_main_.opacity = temp_smooth_81
# Add occasional electrical surge
var neon_surge_ = animation.solid(engine)
neon_surge_.color = 0xFFFFFFFF
@ -103,74 +103,43 @@ temp_square_114.min_value = 0
temp_square_114.max_value = 255
temp_square_114.duration = 50
temp_square_114.duty_cycle = 2
animation.global('neon_surge_').opacity = temp_square_114 # Quick bright surges
animation.global('neon_surge_').priority = 20
neon_surge_.opacity = temp_square_114 # Quick bright surges
neon_surge_.priority = 20
# Add neon tube segments with gaps
var segment_pattern_ = animation.rich_palette(engine)
segment_pattern_.palette = animation.global('neon_colors_', 'neon_colors')
segment_pattern_.palette = neon_colors_
segment_pattern_.cycle_period = 4000
segment_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
segment_pattern_.transition_type = animation.LINEAR
segment_pattern_.brightness = 255
var segment1_ = animation.beacon_animation(engine)
segment1_.color = animation.global('segment_pattern_', 'segment_pattern') # color source
segment1_.color = segment_pattern_ # color source
segment1_.pos = 6 # position
segment1_.beacon_size = 12 # segment length
segment1_.slew_size = 1 # sharp edges
animation.global('segment1_').priority = 10
segment1_.priority = 10
var segment2_ = animation.beacon_animation(engine)
segment2_.color = animation.global('segment_pattern_', 'segment_pattern') # color source
segment2_.color = segment_pattern_ # color source
segment2_.pos = 24 # position
segment2_.beacon_size = 12 # segment length
segment2_.slew_size = 1 # sharp edges
animation.global('segment2_').priority = 10
segment2_.priority = 10
var segment3_ = animation.beacon_animation(engine)
segment3_.color = animation.global('segment_pattern_', 'segment_pattern') # color source
segment3_.color = segment_pattern_ # color source
segment3_.pos = 42 # position
segment3_.beacon_size = 12 # segment length
segment3_.slew_size = 1 # sharp edges
animation.global('segment3_').priority = 10
segment3_.priority = 10
# Add electrical arcing between segments
var arc_sparkles_ = animation.twinkle_animation(engine)
arc_sparkles_.color = 0xFFAAAAFF # Electric blue
arc_sparkles_.density = 4 # density (few arcs)
arc_sparkles_.twinkle_speed = 100 # twinkle speed (quick arcs)
animation.global('arc_sparkles_').priority = 15
arc_sparkles_.priority = 15
# Start all animations
# Start all animations/sequences
if global.contains('sequence_neon_main')
var seq_manager = global.sequence_neon_main()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('neon_main_'))
end
if global.contains('sequence_neon_surge')
var seq_manager = global.sequence_neon_surge()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('neon_surge_'))
end
if global.contains('sequence_segment1')
var seq_manager = global.sequence_segment1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('segment1_'))
end
if global.contains('sequence_segment2')
var seq_manager = global.sequence_segment2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('segment2_'))
end
if global.contains('sequence_segment3')
var seq_manager = global.sequence_segment3()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('segment3_'))
end
if global.contains('sequence_arc_sparkles')
var seq_manager = global.sequence_arc_sparkles()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('arc_sparkles_'))
end
engine.add_animation(neon_main_)
engine.add_animation(neon_surge_)
engine.add_animation(segment1_)
engine.add_animation(segment2_)
engine.add_animation(segment3_)
engine.add_animation(arc_sparkles_)
engine.start()

View File

@ -70,73 +70,52 @@ var engine = animation.init_strip()
var ocean_colors_ = bytes("00000080" "400040C0" "800080FF" "C040C0FF" "FF80FFFF")
# Base ocean animation with slow color cycling
var ocean_base_ = animation.rich_palette_animation(engine)
ocean_base_.palette = animation.global('ocean_colors_', 'ocean_colors')
ocean_base_.palette = ocean_colors_
ocean_base_.cycle_period = 8000
ocean_base_.transition_type = animation.global('SINE_', 'SINE')
ocean_base_.transition_type = animation.SINE
ocean_base_.brightness = 200
# Add wave motion with moving pulses
var wave1_pattern_ = animation.rich_palette(engine)
wave1_pattern_.palette = animation.global('ocean_colors_', 'ocean_colors')
wave1_pattern_.palette = ocean_colors_
wave1_pattern_.cycle_period = 6000
wave1_pattern_.transition_type = animation.global('SINE_', 'SINE')
wave1_pattern_.transition_type = animation.SINE
wave1_pattern_.brightness = 255
var wave1_ = animation.beacon_animation(engine)
wave1_.color = animation.global('wave1_pattern_', 'wave1_pattern') # color source
wave1_.color = wave1_pattern_ # color source
wave1_.pos = 0 # initial position
wave1_.beacon_size = 12 # wave width
wave1_.slew_size = 6 # soft edges
animation.global('wave1_').priority = 10
wave1_.priority = 10
var temp_sawtooth_145 = animation.sawtooth(engine)
temp_sawtooth_145.min_value = 0
temp_sawtooth_145.max_value = 48
temp_sawtooth_145.duration = 5000
animation.global('wave1_').pos = temp_sawtooth_145 # 60-12 = 48
wave1_.pos = temp_sawtooth_145 # 60-12 = 48
var wave2_pattern_ = animation.rich_palette(engine)
wave2_pattern_.palette = animation.global('ocean_colors_', 'ocean_colors')
wave2_pattern_.palette = ocean_colors_
wave2_pattern_.cycle_period = 4000
wave2_pattern_.transition_type = animation.global('SINE_', 'SINE')
wave2_pattern_.transition_type = animation.SINE
wave2_pattern_.brightness = 180
var wave2_ = animation.beacon_animation(engine)
wave2_.color = animation.global('wave2_pattern_', 'wave2_pattern') # color source
wave2_.color = wave2_pattern_ # color source
wave2_.pos = 52 # initial position
wave2_.beacon_size = 8 # smaller wave
wave2_.slew_size = 4 # soft edges
animation.global('wave2_').priority = 8
wave2_.priority = 8
var temp_sawtooth_222 = animation.sawtooth(engine)
temp_sawtooth_222.min_value = 52
temp_sawtooth_222.max_value = 8
temp_sawtooth_222.duration = 7000
animation.global('wave2_').pos = temp_sawtooth_222 # Opposite direction
wave2_.pos = temp_sawtooth_222 # Opposite direction
# Add foam sparkles
var foam_ = animation.twinkle_animation(engine)
foam_.color = 0xFFFFFFFF # White foam
foam_.density = 6 # density (sparkle count)
foam_.twinkle_speed = 300 # twinkle speed (quick sparkles)
animation.global('foam_').priority = 15
foam_.priority = 15
# Start all animations
# Start all animations/sequences
if global.contains('sequence_ocean_base')
var seq_manager = global.sequence_ocean_base()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('ocean_base_'))
end
if global.contains('sequence_wave1')
var seq_manager = global.sequence_wave1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('wave1_'))
end
if global.contains('sequence_wave2')
var seq_manager = global.sequence_wave2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('wave2_'))
end
if global.contains('sequence_foam')
var seq_manager = global.sequence_foam()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('foam_'))
end
engine.add_animation(ocean_base_)
engine.add_animation(wave1_)
engine.add_animation(wave2_)
engine.add_animation(foam_)
engine.start()

View File

@ -61,13 +61,13 @@ var fire_colors_ = bytes("00000000" "40800000" "80FF0000" "C0FF8000" "FFFFFF00")
var ocean_colors_ = bytes("00000080" "400000FF" "8000FFFF" "C000FF80" "FF008000")
# Create animations using the palettes
var fire_anim_ = animation.rich_palette_animation(engine)
fire_anim_.palette = animation.global('fire_colors_', 'fire_colors')
fire_anim_.palette = fire_colors_
fire_anim_.cycle_period = 5000
var ocean_anim_ = animation.rich_palette_animation(engine)
ocean_anim_.palette = animation.global('ocean_colors_', 'ocean_colors')
ocean_anim_.palette = ocean_colors_
ocean_anim_.cycle_period = 8000
# Sequence to show both palettes
def sequence_palette_demo()
var palette_demo_ = (def (engine)
var steps = []
steps.push(animation.create_play_step(animation.global('fire_anim_'), 10000))
steps.push(animation.create_wait_step(1000))
@ -80,12 +80,6 @@ def sequence_palette_demo()
var seq_manager = animation.SequenceManager(engine)
seq_manager.start_sequence(steps)
return seq_manager
end
# Start all animations/sequences
if global.contains('sequence_palette_demo')
var seq_manager = global.sequence_palette_demo()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('palette_demo_'))
end
end)(engine)
engine.add_sequence_manager(palette_demo_)
engine.start()

View File

@ -106,25 +106,25 @@ var aurora_borealis_ = bytes("00000022" "40004400" "8000AA44" "C044AA88" "FF88FF
var sunset_sky_ = bytes("00191970" "40800080" "80FF69B4" "C0FFA500" "FFFFFF00")
# Create animations using each palette
var fire_effect_ = animation.rich_palette_animation(engine)
fire_effect_.palette = animation.global('fire_gradient_', 'fire_gradient')
fire_effect_.palette = fire_gradient_
fire_effect_.cycle_period = 3000
var ocean_waves_ = animation.rich_palette_animation(engine)
ocean_waves_.palette = animation.global('ocean_depths_', 'ocean_depths')
ocean_waves_.palette = ocean_depths_
ocean_waves_.cycle_period = 8000
ocean_waves_.transition_type = animation.global('SINE_', 'SINE')
ocean_waves_.transition_type = animation.SINE
ocean_waves_.brightness = 200
var aurora_lights_ = animation.rich_palette_animation(engine)
aurora_lights_.palette = animation.global('aurora_borealis_', 'aurora_borealis')
aurora_lights_.palette = aurora_borealis_
aurora_lights_.cycle_period = 12000
aurora_lights_.transition_type = animation.global('SINE_', 'SINE')
aurora_lights_.transition_type = animation.SINE
aurora_lights_.brightness = 180
var sunset_glow_ = animation.rich_palette_animation(engine)
sunset_glow_.palette = animation.global('sunset_sky_', 'sunset_sky')
sunset_glow_.palette = sunset_sky_
sunset_glow_.cycle_period = 6000
sunset_glow_.transition_type = animation.global('SINE_', 'SINE')
sunset_glow_.transition_type = animation.SINE
sunset_glow_.brightness = 220
# Sequence to showcase all palettes
def sequence_palette_showcase()
var palette_showcase_ = (def (engine)
var steps = []
# Fire effect
steps.push(animation.create_play_step(animation.global('fire_effect_'), 8000))
@ -148,12 +148,6 @@ def sequence_palette_showcase()
var seq_manager = animation.SequenceManager(engine)
seq_manager.start_sequence(steps)
return seq_manager
end
# Start all animations/sequences
if global.contains('sequence_palette_showcase')
var seq_manager = global.sequence_palette_showcase()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('palette_showcase_'))
end
end)(engine)
engine.add_sequence_manager(palette_showcase_)
engine.start()

View File

@ -76,89 +76,68 @@ var engine = animation.init_strip()
var plasma_colors_ = bytes("00FF0080" "33FF8000" "66FFFF00" "9980FF00" "CC00FF80" "FF0080FF")
# Base plasma animation with medium speed
var plasma_base_ = animation.rich_palette_animation(engine)
plasma_base_.palette = animation.global('plasma_colors_', 'plasma_colors')
plasma_base_.palette = plasma_colors_
plasma_base_.cycle_period = 6000
plasma_base_.transition_type = animation.global('SINE_', 'SINE')
plasma_base_.transition_type = animation.SINE
plasma_base_.brightness = 200
# Add multiple wave layers for complexity
var wave1_pattern_ = animation.rich_palette(engine)
wave1_pattern_.palette = animation.global('plasma_colors_', 'plasma_colors')
wave1_pattern_.palette = plasma_colors_
wave1_pattern_.cycle_period = 4000
wave1_pattern_.transition_type = animation.global('SINE_', 'SINE')
wave1_pattern_.transition_type = animation.SINE
wave1_pattern_.brightness = 255
var plasma_wave1_ = animation.beacon_animation(engine)
plasma_wave1_.color = animation.global('wave1_pattern_', 'wave1_pattern') # color source
plasma_wave1_.color = wave1_pattern_ # color source
plasma_wave1_.pos = 0 # initial position
plasma_wave1_.beacon_size = 20 # wide wave
plasma_wave1_.slew_size = 10 # very smooth
animation.global('plasma_wave1_').priority = 10
plasma_wave1_.priority = 10
var temp_smooth_153 = animation.smooth(engine)
temp_smooth_153.min_value = 0
temp_smooth_153.max_value = 40
temp_smooth_153.duration = 8000
animation.global('plasma_wave1_').pos = temp_smooth_153
plasma_wave1_.pos = temp_smooth_153
var wave2_pattern_ = animation.rich_palette(engine)
wave2_pattern_.palette = animation.global('plasma_colors_', 'plasma_colors')
wave2_pattern_.palette = plasma_colors_
wave2_pattern_.cycle_period = 5000
wave2_pattern_.transition_type = animation.global('SINE_', 'SINE')
wave2_pattern_.transition_type = animation.SINE
wave2_pattern_.brightness = 180
var plasma_wave2_ = animation.beacon_animation(engine)
plasma_wave2_.color = animation.global('wave2_pattern_', 'wave2_pattern') # color source
plasma_wave2_.color = wave2_pattern_ # color source
plasma_wave2_.pos = 45 # initial position
plasma_wave2_.beacon_size = 15 # medium wave
plasma_wave2_.slew_size = 8 # smooth
animation.global('plasma_wave2_').priority = 8
plasma_wave2_.priority = 8
var temp_smooth_229 = animation.smooth(engine)
temp_smooth_229.min_value = 45
temp_smooth_229.max_value = 15
temp_smooth_229.duration = 10000
animation.global('plasma_wave2_').pos = temp_smooth_229 # Opposite direction
plasma_wave2_.pos = temp_smooth_229 # Opposite direction
var wave3_pattern_ = animation.rich_palette(engine)
wave3_pattern_.palette = animation.global('plasma_colors_', 'plasma_colors')
wave3_pattern_.palette = plasma_colors_
wave3_pattern_.cycle_period = 3000
wave3_pattern_.transition_type = animation.global('SINE_', 'SINE')
wave3_pattern_.transition_type = animation.SINE
wave3_pattern_.brightness = 220
var plasma_wave3_ = animation.beacon_animation(engine)
plasma_wave3_.color = animation.global('wave3_pattern_', 'wave3_pattern') # color source
plasma_wave3_.color = wave3_pattern_ # color source
plasma_wave3_.pos = 20 # initial position
plasma_wave3_.beacon_size = 12 # smaller wave
plasma_wave3_.slew_size = 6 # smooth
animation.global('plasma_wave3_').priority = 12
plasma_wave3_.priority = 12
var temp_smooth_306 = animation.smooth(engine)
temp_smooth_306.min_value = 20
temp_smooth_306.max_value = 50
temp_smooth_306.duration = 6000
animation.global('plasma_wave3_').pos = temp_smooth_306 # Different speed
plasma_wave3_.pos = temp_smooth_306 # Different speed
# Add subtle intensity variation
var temp_smooth_329 = animation.smooth(engine)
temp_smooth_329.min_value = 150
temp_smooth_329.max_value = 255
temp_smooth_329.duration = 12000
animation.global('plasma_base_').opacity = temp_smooth_329
plasma_base_.opacity = temp_smooth_329
# Start all animations
# Start all animations/sequences
if global.contains('sequence_plasma_base')
var seq_manager = global.sequence_plasma_base()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('plasma_base_'))
end
if global.contains('sequence_plasma_wave1')
var seq_manager = global.sequence_plasma_wave1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('plasma_wave1_'))
end
if global.contains('sequence_plasma_wave2')
var seq_manager = global.sequence_plasma_wave2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('plasma_wave2_'))
end
if global.contains('sequence_plasma_wave3')
var seq_manager = global.sequence_plasma_wave3()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('plasma_wave3_'))
end
engine.add_animation(plasma_base_)
engine.add_animation(plasma_wave1_)
engine.add_animation(plasma_wave2_)
engine.add_animation(plasma_wave3_)
engine.start()

View File

@ -60,26 +60,26 @@ left_red_.color = 0xFFFF0000 # Bright red
left_red_.pos = 15 # center of left half
left_red_.beacon_size = 15 # half the strip
left_red_.slew_size = 2 # sharp edges
animation.global('left_red_').priority = 10
left_red_.priority = 10
var temp_square_57 = animation.square(engine)
temp_square_57.min_value = 0
temp_square_57.max_value = 255
temp_square_57.duration = 400
temp_square_57.duty_cycle = 50
animation.global('left_red_').opacity = temp_square_57 # 50% duty cycle
left_red_.opacity = temp_square_57 # 50% duty cycle
# Right side blue flashing (opposite phase)
var right_blue_ = animation.beacon_animation(engine)
right_blue_.color = 0xFF0000FF # Bright blue
right_blue_.pos = 45 # center of right half
right_blue_.beacon_size = 15 # half the strip
right_blue_.slew_size = 2 # sharp edges
animation.global('right_blue_').priority = 10
right_blue_.priority = 10
var temp_square_118 = animation.square(engine)
temp_square_118.min_value = 255
temp_square_118.max_value = 0
temp_square_118.duration = 400
temp_square_118.duty_cycle = 50
animation.global('right_blue_').opacity = temp_square_118 # Opposite phase
right_blue_.opacity = temp_square_118 # Opposite phase
# Add white strobe overlay occasionally
var white_strobe_ = animation.solid(engine)
white_strobe_.color = 0xFFFFFFFF
@ -88,26 +88,10 @@ temp_square_155.min_value = 0
temp_square_155.max_value = 255
temp_square_155.duration = 100
temp_square_155.duty_cycle = 5
animation.global('white_strobe_').opacity = temp_square_155 # Quick bright flashes
animation.global('white_strobe_').priority = 20
white_strobe_.opacity = temp_square_155 # Quick bright flashes
white_strobe_.priority = 20
# Start all animations
# Start all animations/sequences
if global.contains('sequence_left_red')
var seq_manager = global.sequence_left_red()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('left_red_'))
end
if global.contains('sequence_right_blue')
var seq_manager = global.sequence_right_blue()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('right_blue_'))
end
if global.contains('sequence_white_strobe')
var seq_manager = global.sequence_white_strobe()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('white_strobe_'))
end
engine.add_animation(left_red_)
engine.add_animation(right_blue_)
engine.add_animation(white_strobe_)
engine.start()

View File

@ -64,30 +64,30 @@ var blue_custom_ = 0xFF0000FF
var green_custom_ = 0xFF00FF00
# Create animations
var left_pulse_ = animation.beacon_animation(engine)
left_pulse_.color = animation.global('red_custom_', 'red_custom')
left_pulse_.color = red_custom_
left_pulse_.pos = 15
left_pulse_.beacon_size = 15
left_pulse_.slew_size = 3
var center_pulse_ = animation.beacon_animation(engine)
center_pulse_.color = animation.global('blue_custom_', 'blue_custom')
center_pulse_.color = blue_custom_
center_pulse_.pos = 30
center_pulse_.beacon_size = 15
center_pulse_.slew_size = 3
var right_pulse_ = animation.beacon_animation(engine)
right_pulse_.color = animation.global('green_custom_', 'green_custom')
right_pulse_.color = green_custom_
right_pulse_.pos = 45
right_pulse_.beacon_size = 15
right_pulse_.slew_size = 3
# Set different opacities
animation.global('left_pulse_').opacity = 255 # Full slew_size
animation.global('center_pulse_').opacity = 200 # Slightly dimmed
animation.global('right_pulse_').opacity = 150 # More dimmed
left_pulse_.opacity = 255 # Full slew_size
center_pulse_.opacity = 200 # Slightly dimmed
right_pulse_.opacity = 150 # More dimmed
# Set priorities (higher numbers have priority)
animation.global('left_pulse_').priority = 10
animation.global('center_pulse_').priority = 15 # Center has highest priority
animation.global('right_pulse_').priority = 5
left_pulse_.priority = 10
center_pulse_.priority = 15 # Center has highest priority
right_pulse_.priority = 5
# Create a sequence that shows all three
def sequence_demo()
var demo_ = (def (engine)
var steps = []
steps.push(animation.create_play_step(animation.global('left_pulse_'), 3000))
steps.push(animation.create_wait_step(500))
@ -105,12 +105,6 @@ def sequence_demo()
var seq_manager = animation.SequenceManager(engine)
seq_manager.start_sequence(steps)
return seq_manager
end
# Start all animations/sequences
if global.contains('sequence_demo')
var seq_manager = global.sequence_demo()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('demo_'))
end
end)(engine)
engine.add_sequence_manager(demo_)
engine.start()

View File

@ -34,13 +34,7 @@ var rainbow_cycle_ = animation.color_cycle(engine)
rainbow_cycle_.palette = [0xFFFF0000, 0xFFFF8000, 0xFFFFFF00, 0xFF00FF00, 0xFF0000FF, 0xFF8000FF, 0xFFFF00FF] # rainbow colors
rainbow_cycle_.cycle_period = 5000 # cycle period
var rainbow_animation_ = animation.solid(engine)
rainbow_animation_.color = animation.global('rainbow_cycle_', 'rainbow_cycle')
rainbow_animation_.color = rainbow_cycle_
# Start the animation
# Start all animations/sequences
if global.contains('sequence_rainbow_animation')
var seq_manager = global.sequence_rainbow_animation()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('rainbow_animation_'))
end
engine.add_animation(rainbow_animation_)
engine.start()

View File

@ -55,52 +55,36 @@ var engine = animation.init_strip()
var scanner_bg_ = 0xFF110000
var background_ = animation.solid(engine)
background_.color = animation.global('scanner_bg_', 'scanner_bg')
background_.color = scanner_bg_
# Main scanner pulse that bounces
var scanner_ = animation.beacon_animation(engine)
scanner_.color = 0xFFFF0000 # Bright red
scanner_.pos = 2 # initial position
scanner_.beacon_size = 3 # pulse width
scanner_.slew_size = 2 # fade region
animation.global('scanner_').priority = 10
scanner_.priority = 10
# Bouncing position from left to right and back
var temp_triangle_70 = animation.triangle(engine)
temp_triangle_70.min_value = 2
temp_triangle_70.max_value = 57
temp_triangle_70.duration = 2000
animation.global('scanner_').pos = temp_triangle_70
scanner_.pos = temp_triangle_70
# Add trailing glow effect
var scanner_trail_ = animation.beacon_animation(engine)
scanner_trail_.color = 0xFF660000 # Dim red trail
scanner_trail_.pos = 2 # initial position
scanner_trail_.beacon_size = 6 # wider trail
scanner_trail_.slew_size = 4 # more fade
animation.global('scanner_trail_').priority = 5
scanner_trail_.priority = 5
var temp_triangle_125 = animation.triangle(engine)
temp_triangle_125.min_value = 2
temp_triangle_125.max_value = 57
temp_triangle_125.duration = 2000
var pos_test_ = temp_triangle_125
animation.global('scanner_trail_').pos = animation.global('pos_test_', 'pos_test')
animation.global('scanner_trail_').opacity = 128 # Half brightness
scanner_trail_.pos = pos_test_
scanner_trail_.opacity = 128 # Half brightness
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')
var seq_manager = global.sequence_background()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('background_'))
end
if global.contains('sequence_scanner_trail')
var seq_manager = global.sequence_scanner_trail()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('scanner_trail_'))
end
if global.contains('sequence_scanner')
var seq_manager = global.sequence_scanner()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('scanner_'))
end
engine.add_animation(background_)
engine.add_animation(scanner_trail_)
engine.add_animation(scanner_)
engine.start()

View File

@ -42,21 +42,15 @@ var engine = animation.init_strip()
var rainbow_ = bytes("00FF0000" "40FFA500" "80FFFF00" "C0008000" "FF0000FF")
# Create an animation using the palette
var rainbow_cycle_ = animation.rich_palette_animation(engine)
rainbow_cycle_.palette = animation.global('rainbow_', 'rainbow')
rainbow_cycle_.palette = rainbow_
rainbow_cycle_.cycle_period = 3000
# Simple sequence
def sequence_demo()
var demo_ = (def (engine)
var steps = []
steps.push(animation.create_play_step(animation.global('rainbow_cycle_'), 15000))
var seq_manager = animation.SequenceManager(engine)
seq_manager.start_sequence(steps)
return seq_manager
end
# Start all animations/sequences
if global.contains('sequence_demo')
var seq_manager = global.sequence_demo()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('demo_'))
end
end)(engine)
engine.add_sequence_manager(demo_)
engine.start()

View File

@ -76,7 +76,7 @@ var engine = animation.init_strip()
var daylight_colors_ = bytes("00000011" "20001133" "40FF4400" "60FFAA00" "80FFFF88" "A0FFAA44" "C0FF6600" "E0AA2200" "FF220011")
# Main daylight cycle - very slow transition
var daylight_cycle_ = animation.rich_palette_animation(engine)
daylight_cycle_.palette = animation.global('daylight_colors_', 'daylight_colors')
daylight_cycle_.palette = daylight_colors_
daylight_cycle_.cycle_period = 60000
# Add sun position effect - bright spot that moves
var sun_position_ = animation.beacon_animation(engine)
@ -84,69 +84,48 @@ sun_position_.color = 0xFFFFFFAA # Bright yellow sun
sun_position_.pos = 5 # initial position
sun_position_.beacon_size = 8 # sun size
sun_position_.slew_size = 4 # soft glow
animation.global('sun_position_').priority = 10
sun_position_.priority = 10
var temp_smooth_147 = animation.smooth(engine)
temp_smooth_147.min_value = 5
temp_smooth_147.max_value = 55
temp_smooth_147.duration = 30000
animation.global('sun_position_').pos = temp_smooth_147 # Sun arc across sky
sun_position_.pos = temp_smooth_147 # Sun arc across sky
var temp_smooth_167 = animation.smooth(engine)
temp_smooth_167.min_value = 0
temp_smooth_167.max_value = 255
temp_smooth_167.duration = 30000
animation.global('sun_position_').opacity = temp_smooth_167 # Fade in and out
sun_position_.opacity = temp_smooth_167 # Fade in and out
# Add atmospheric glow around sun
var sun_glow_ = animation.beacon_animation(engine)
sun_glow_.color = 0xFFFFCC88 # Warm glow
sun_glow_.pos = 5 # initial position
sun_glow_.beacon_size = 16 # larger glow
sun_glow_.slew_size = 8 # very soft
animation.global('sun_glow_').priority = 5
sun_glow_.priority = 5
var temp_smooth_224 = animation.smooth(engine)
temp_smooth_224.min_value = 5
temp_smooth_224.max_value = 55
temp_smooth_224.duration = 30000
animation.global('sun_glow_').pos = temp_smooth_224 # Follow sun
sun_glow_.pos = temp_smooth_224 # Follow sun
var temp_smooth_244 = animation.smooth(engine)
temp_smooth_244.min_value = 0
temp_smooth_244.max_value = 150
temp_smooth_244.duration = 30000
animation.global('sun_glow_').opacity = temp_smooth_244 # Dimmer glow
sun_glow_.opacity = temp_smooth_244 # Dimmer glow
# Add twinkling stars during night phases
var stars_ = animation.twinkle_animation(engine)
stars_.color = 0xFFFFFFFF # White stars
stars_.density = 6 # density (star count)
stars_.twinkle_speed = 1000 # twinkle speed (slow twinkle)
animation.global('stars_').priority = 15
stars_.priority = 15
var temp_smooth_296 = animation.smooth(engine)
temp_smooth_296.min_value = 255
temp_smooth_296.max_value = 0
temp_smooth_296.duration = 30000
animation.global('stars_').opacity = temp_smooth_296 # Fade out during day
stars_.opacity = temp_smooth_296 # Fade out during day
# Start all animations
# Start all animations/sequences
if global.contains('sequence_daylight_cycle')
var seq_manager = global.sequence_daylight_cycle()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('daylight_cycle_'))
end
if global.contains('sequence_sun_position')
var seq_manager = global.sequence_sun_position()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('sun_position_'))
end
if global.contains('sequence_sun_glow')
var seq_manager = global.sequence_sun_glow()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('sun_glow_'))
end
if global.contains('sequence_stars')
var seq_manager = global.sequence_stars()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stars_'))
end
engine.add_animation(daylight_cycle_)
engine.add_animation(sun_position_)
engine.add_animation(sun_glow_)
engine.add_animation(stars_)
engine.start()

View File

@ -47,37 +47,21 @@ var engine = animation.init_strip()
var night_sky_ = 0xFF000033
var background_ = animation.solid(engine)
background_.color = animation.global('night_sky_', 'night_sky')
background_.color = night_sky_
# White twinkling stars
var stars_ = animation.twinkle_animation(engine)
stars_.color = 0xFFFFFFFF # White stars
stars_.density = 8 # density (number of stars)
stars_.twinkle_speed = 500 # twinkle speed (twinkle duration)
animation.global('stars_').priority = 10
stars_.priority = 10
# Add occasional bright flash
var bright_flash_ = animation.twinkle_animation(engine)
bright_flash_.color = 0xFFFFFFAA # Bright yellow-white
bright_flash_.density = 2 # density (fewer bright flashes)
bright_flash_.twinkle_speed = 300 # twinkle speed (quick flash)
animation.global('bright_flash_').priority = 15
bright_flash_.priority = 15
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')
var seq_manager = global.sequence_background()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('background_'))
end
if global.contains('sequence_stars')
var seq_manager = global.sequence_stars()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stars_'))
end
if global.contains('sequence_bright_flash')
var seq_manager = global.sequence_bright_flash()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('bright_flash_'))
end
engine.add_animation(background_)
engine.add_animation(stars_)
engine.add_animation(bright_flash_)
engine.start()

View File

@ -0,0 +1,108 @@
# Generated Berry code from Animation DSL
# Source: user_functions_demo.anim
# Generated automatically
#
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # User Functions Demo - Advanced Computed Parameters
# # Shows how to use user functions in computed parameters via property assignments
#
# # Get the current strip length for calculations
# set strip_len = strip_length()
#
# # Example 1: Simple user function in computed parameter
# animation random_base = solid(
# color=blue
# priority=10
# )
# # Use user function in property assignment
# random_base.opacity = rand_demo()
#
# # Example 2: User function with mathematical operations
# animation random_bounded = solid(
# color=orange
# priority=8
# )
# # User function with bounds using math functions
# random_bounded.opacity = max(50, min(255, rand_demo() + 100))
#
# # Example 3: User function in arithmetic expressions
# animation random_variation = solid(
# color=purple
# priority=15
# )
# # Mix user function with arithmetic operations
# random_variation.opacity = abs(rand_demo() - 128) + 64
#
# # Example 4: User function affecting different properties
# animation random_multi = solid(
# color=cyan
# priority=12
# )
# # Use user function for multiple properties
# random_multi.opacity = max(100, rand_demo())
#
# # Example 5: Complex expression with user function
# animation random_complex = solid(
# color=white
# priority=20
# )
# # Complex expression with user function and math operations
# random_complex.opacity = round((rand_demo() + 128) / 2 + abs(rand_demo() - 100))
#
# # Run all animations to demonstrate the effects
# run random_base
# run random_bounded
# run random_variation
# run random_multi
# run random_complex
import animation
# User Functions Demo - Advanced Computed Parameters
# Shows how to use user functions in computed parameters via property assignments
# Get the current strip length for calculations
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var temp_strip_length_11 = animation.strip_length(engine)
var strip_len_ = temp_strip_length_11
# Example 1: Simple user function in computed parameter
var random_base_ = animation.solid(engine)
random_base_.color = 0xFF0000FF
random_base_.priority = 10
# Use user function in property assignment
random_base_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (animation.get_user_function('rand_demo')(self.engine)) end)
# Example 2: User function with mathematical operations
var random_bounded_ = animation.solid(engine)
random_bounded_.color = 0xFFFFA500
random_bounded_.priority = 8
# User function with bounds using math functions
random_bounded_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.max(50, self.min(255, animation.get_user_function('rand_demo')(self.engine) + 100))) end)
# Example 3: User function in arithmetic expressions
var random_variation_ = animation.solid(engine)
random_variation_.color = 0xFF800080
random_variation_.priority = 15
# Mix user function with arithmetic operations
random_variation_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.abs(animation.get_user_function('rand_demo')(self.engine) - 128) + 64) end)
# Example 4: User function affecting different properties
var random_multi_ = animation.solid(engine)
random_multi_.color = 0xFF00FFFF
random_multi_.priority = 12
# Use user function for multiple properties
random_multi_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.max(100, animation.get_user_function('rand_demo')(self.engine))) end)
# Example 5: Complex expression with user function
var random_complex_ = animation.solid(engine)
random_complex_.color = 0xFFFFFFFF
random_complex_.priority = 20
# Complex expression with user function and math operations
random_complex_.opacity = animation.create_closure_value(engine, def (self, param_name, time_ms) return (self.round((animation.get_user_function('rand_demo')(self.engine) + 128) / 2 + self.abs(animation.get_user_function('rand_demo')(self.engine) - 100))) end)
# Run all animations to demonstrate the effects
engine.add_animation(random_base_)
engine.add_animation(random_bounded_)
engine.add_animation(random_variation_)
engine.add_animation(random_multi_)
engine.add_animation(random_complex_)
engine.start()

View File

@ -0,0 +1,31 @@
# Computed Values Demo - Example from the original request
# Shows how to use computed values from value providers
# Get the current strip length
set strip_len = strip_length()
# Create animation with computed values
animation stream1 = comet_animation(
color=red
tail_length=abs(strip_len / 4) # computed value
speed=1.5
priority=10
)
# More complex computed values
set base_speed = 2.0
animation stream2 = comet_animation(
color=blue
tail_length=strip_len / 8 + 2 # computed with addition
speed=base_speed * 1.5 # computed with multiplication
direction=-1
priority=5
)
# Property assignment with computed values
stream1.tail_length = strip_len / 5
stream2.opacity = strip_len * 4
# Run both animations
run stream1
run stream2

View File

@ -0,0 +1,52 @@
# User Functions Demo - Advanced Computed Parameters
# Shows how to use user functions in computed parameters via property assignments
# Get the current strip length for calculations
set strip_len = strip_length()
# Example 1: Simple user function in computed parameter
animation random_base = solid(
color=blue
priority=10
)
# Use user function in property assignment
random_base.opacity = rand_demo()
# Example 2: User function with mathematical operations
animation random_bounded = solid(
color=orange
priority=8
)
# User function with bounds using math functions
random_bounded.opacity = max(50, min(255, rand_demo() + 100))
# Example 3: User function in arithmetic expressions
animation random_variation = solid(
color=purple
priority=15
)
# Mix user function with arithmetic operations
random_variation.opacity = abs(rand_demo() - 128) + 64
# Example 4: User function affecting different properties
animation random_multi = solid(
color=cyan
priority=12
)
# Use user function for multiple properties
random_multi.opacity = max(100, rand_demo())
# Example 5: Complex expression with user function
animation random_complex = solid(
color=white
priority=20
)
# Complex expression with user function and math operations
random_complex.opacity = round((rand_demo() + 128) / 2 + abs(rand_demo() - 100))
# Run all animations to demonstrate the effects
run random_base
run random_bounded
run random_variation
run random_multi
run random_complex

View File

@ -42,6 +42,7 @@ ParameterizedObject
├── StaticValueProvider
├── StripLengthProvider
├── OscillatorValueProvider
├── ClosureValueProvider (internal use only)
└── ColorProvider
├── StaticColorProvider
├── ColorCycleColorProvider
@ -144,6 +145,61 @@ Generates oscillating values using various waveforms. Inherits from `ValueProvid
**See Also**: [Oscillation Patterns](OSCILLATION_PATTERNS.md) - Visual examples and usage patterns for oscillation waveforms
### ClosureValueProvider
**⚠️ INTERNAL USE ONLY - NOT FOR DIRECT USE**
Wraps a closure/function as a value provider for internal transpiler use. This class is used internally by the DSL transpiler to handle computed values and should not be used directly by users.
| Parameter | Type | Default | Constraints | Description |
|-----------|------|---------|-------------|-------------|
| `closure` | function | nil | - | The closure function to call for value generation |
**Internal Usage**: This provider is automatically created by the DSL transpiler when it encounters computed expressions or arithmetic operations involving value providers. The closure is called with `(self, param_name, time_ms)` parameters.
#### Mathematical Helper Methods
The ClosureValueProvider includes built-in mathematical helper methods that can be used within closures for computed values:
| Method | Description | Parameters | Return Type | Example |
|--------|-------------|------------|-------------|---------|
| `min(a, b, ...)` | Minimum of two or more values | `a, b, *args: number` | `number` | `self.min(5, 3, 8)``3` |
| `max(a, b, ...)` | Maximum of two or more values | `a, b, *args: number` | `number` | `self.max(5, 3, 8)``8` |
| `abs(x)` | Absolute value | `x: number` | `number` | `self.abs(-5)``5` |
| `round(x)` | Round to nearest integer | `x: number` | `int` | `self.round(3.7)``4` |
| `sqrt(x)` | Square root with integer handling | `x: number` | `number` | `self.sqrt(64)``128` (for 0-255 range) |
| `scale(v, from_min, from_max, to_min, to_max)` | Scale value between ranges | `v, from_min, from_max, to_min, to_max: number` | `int` | `self.scale(50, 0, 100, 0, 255)``127` |
| `sine(angle)` | Sine function (0-255 input range) | `angle: number` | `int` | `self.sine(64)``255` (90°) |
| `cosine(angle)` | Cosine function (0-255 input range) | `angle: number` | `int` | `self.cosine(0)``-255` (matches oscillator behavior) |
**Mathematical Method Notes:**
- **Integer Handling**: `sqrt()` treats integers in 0-255 range as normalized values (255 = 1.0)
- **Angle Range**: `sine()` and `cosine()` use 0-255 input range (0-360 degrees)
- **Output Range**: Trigonometric functions return -255 to 255 (mapped from -1.0 to 1.0)
- **Cosine Behavior**: Matches oscillator COSINE waveform (starts at minimum, not maximum)
- **Scale Function**: Uses `tasmota.scale_int()` for efficient integer scaling
#### Usage in Computed Values
These methods are automatically available in DSL computed expressions:
```dsl
# Example: Dynamic brightness based on strip position
set strip_len = strip_length()
animation pulse = pulsating_animation(
color=red
brightness=strip_len / 4 + 50 # Uses built-in arithmetic
)
# Complex mathematical expressions are automatically wrapped in closures
# that have access to all mathematical helper methods
```
**Factory**: `animation.closure_value(engine)` (internal use only)
**Note**: Users should not create ClosureValueProvider instances directly. Instead, use the DSL's computed value syntax which automatically creates these providers as needed.
## Color Providers
Color providers generate dynamic colors over time, extending ValueProvider for color-specific functionality.

View File

@ -202,6 +202,9 @@ set opacity_level = 80% # Static percentage (converted to 204)
# Value providers for dynamic values
set brightness_osc = smooth(min_value=50, max_value=255, period=3s)
set position_sweep = triangle(min_value=0, max_value=29, period=5s)
# Computed values using strip length
set strip_len = strip_length() # Get current strip length
```
## Color Definitions
@ -327,6 +330,11 @@ pulse_red.position = 15
# Dynamic properties using value providers
pulse_red.position = triangle(min_value=0, max_value=29, period=5s)
pulse_red.opacity = smooth(min_value=100, max_value=255, period=2s)
# Computed properties using arithmetic expressions
set strip_len = strip_length()
pulse_red.position = strip_len / 2 # Center position
pulse_red.opacity = strip_len * 4 # Scale with strip size
```
**Common Properties:**
@ -336,6 +344,151 @@ pulse_red.opacity = smooth(min_value=100, max_value=255, period=2s)
- `speed` - Speed multiplier
- `phase` - Phase offset
## Computed Values
The DSL supports computed values using arithmetic expressions with value providers and mathematical functions:
```dsl
# Get strip dimensions
set strip_len = strip_length()
# Use computed values in animation parameters
animation stream1 = comet_animation(
color=red
tail_length=strip_len / 4 # Computed: quarter of strip length
speed=1.5
priority=10
)
# Complex expressions with multiple operations
set base_speed = 2.0
animation stream2 = comet_animation(
color=blue
tail_length=strip_len / 8 + 2 # Computed: eighth of strip + 2
speed=base_speed * 1.5 # Computed: base speed × 1.5
)
# Computed values in property assignments
stream1.position = strip_len / 2 # Center of strip
stream2.opacity = strip_len * 4 # Scale opacity with strip size
# Using mathematical functions in computed values
animation pulse = pulsating_animation(
color=red
period=2s
)
pulse.opacity = abs(sine(strip_len) * 128 + 127) # Sine wave opacity
pulse.position = max(0, min(strip_len - 1, round(strip_len / 2))) # Clamped center position
```
**Supported Operations:**
- Addition: `+`
- Subtraction: `-`
- Multiplication: `*`
- Division: `/`
- Parentheses for grouping: `(expression)`
**Mathematical Functions:**
The following mathematical functions are available in computed parameters and are automatically detected by the transpiler:
| Function | Description | Parameters | Return Value |
|----------|-------------|------------|--------------|
| `min(a, b, ...)` | Returns the minimum value | Two or more numbers | Minimum value |
| `max(a, b, ...)` | Returns the maximum value | Two or more numbers | Maximum value |
| `abs(x)` | Returns the absolute value | One number | Absolute value |
| `round(x)` | Rounds to nearest integer | One number | Rounded integer |
| `sqrt(x)` | Returns the square root | One number | Square root (scaled for integers) |
| `scale(v, from_min, from_max, to_min, to_max)` | Scales value from one range to another | Value and range parameters | Scaled integer |
| `sine(angle)` | Returns sine of angle | Angle in 0-255 range (0-360°) | Sine value in -255 to 255 range |
| `cosine(angle)` | Returns cosine of angle | Angle in 0-255 range (0-360°) | Cosine value in -255 to 255 range |
**Mathematical Function Examples:**
```dsl
# Basic math functions
set strip_len = strip_length()
animation test = pulsating_animation(color=red, period=2s)
# Absolute value for ensuring positive results
test.opacity = abs(strip_len - 200)
# Min/max for clamping values
test.position = max(0, min(strip_len - 1, 15)) # Clamp position to valid range
# Rounding for integer positions
test.position = round(strip_len / 2.5)
# Square root for non-linear scaling
test.brightness = sqrt(strip_len * 4) # Non-linear brightness based on strip size
# Scaling values between ranges
test.opacity = scale(strip_len, 10, 60, 50, 255) # Scale strip length to opacity range
# Trigonometric functions for wave patterns
set angle = 128 # 180 degrees in 0-255 range
test.opacity = sine(angle) + 128 # Sine wave shifted to positive range
test.brightness = cosine(angle) + 128 # Cosine wave shifted to positive range
# Complex expressions combining multiple functions
test.position = max(0, round(abs(sine(strip_len * 2)) * (strip_len - 1) / 255))
test.opacity = min(255, max(50, scale(sqrt(strip_len), 0, 16, 100, 255)))
```
**Special Notes:**
- **Integer Optimization**: `sqrt()` function automatically handles integer scaling for 0-255 range values
- **Trigonometric Range**: `sine()` and `cosine()` use 0-255 input range (mapped to 0-360°) and return -255 to 255 output range
- **Automatic Detection**: Mathematical functions are automatically detected at transpile time using dynamic introspection
- **Closure Context**: In computed parameters, mathematical functions are called as `self.<function>()` in the generated closure context
**How It Works:**
When the DSL detects arithmetic expressions containing value providers, variable references, or mathematical functions, it automatically creates closure functions that capture the computation. These closures are called with `(self, param_name, time_ms)` parameters, allowing the computation to be re-evaluated dynamically as needed. Mathematical functions are automatically prefixed with `self.` in the closure context to access the ClosureValueProvider's mathematical methods.
**User Functions in Computed Parameters:**
User-defined functions can also be used in computed parameter expressions, providing powerful custom effects:
```dsl
# Simple user function in computed parameter
animation base = solid(color=blue)
base.opacity = rand_demo()
# User functions mixed with math operations
animation dynamic = solid(
color=purple
opacity=max(50, min(255, rand_demo() + 100))
)
```
### User Functions
User functions are custom Berry functions that can be called from computed parameters. They provide dynamic values that change over time.
**Available User Functions:**
- `rand_demo()` - Returns random values for demonstration purposes
**Usage in Computed Parameters:**
```dsl
# Simple user function
animation.opacity = rand_demo()
# User function with math operations
animation.opacity = max(100, rand_demo())
# User function in arithmetic expressions
animation.opacity = abs(rand_demo() - 128) + 64
```
**Available User Functions:**
The following user functions are available by default (see [User Functions Guide](USER_FUNCTIONS.md) for details):
| Function | Parameters | Description |
|----------|------------|-------------|
| `rand_demo()` | none | Returns a random value (0-255) for demonstration |
**User Function Behavior:**
- User functions are automatically detected by the transpiler
- They receive `self.engine` as the first parameter in closure context
- They can be mixed with mathematical functions and arithmetic operations
- The entire expression is wrapped in a single efficient closure
## Sequences
Sequences orchestrate multiple animations with timing control:
@ -487,6 +640,21 @@ pulsating_animation(
)
```
**Mathematical Functions in Computed Parameters:**
Mathematical functions can be used in computed parameter expressions and are automatically detected by the transpiler:
```dsl
animation wave = pulsating_animation(
color=blue
period=2s
)
# Mathematical functions in property assignments
wave.opacity = abs(sine(strip_length()) - 128) # Sine wave opacity
wave.position = max(0, min(strip_length() - 1, 15)) # Clamped position
wave.brightness = round(sqrt(strip_length()) * 4) # Non-linear scaling
```
## Supported Classes
### Value Providers
@ -768,7 +936,10 @@ This applies to:
- Parameter validation at compile time
- Execution statements
- User-defined functions (with engine-first parameter pattern) - see **[User Functions Guide](USER_FUNCTIONS.md)**
- **User functions in computed parameters**: User functions can be used in arithmetic expressions alongside mathematical functions
- **Flexible parameter syntax**: Commas optional when parameters are on separate lines
- **Computed values**: Arithmetic expressions with value providers automatically create closures
- **Mathematical functions**: `min`, `max`, `abs`, `round`, `sqrt`, `scale`, `sine`, `cosine` in computed parameters
### 🚧 Partially Implemented
- Expression evaluation (basic support)
@ -778,7 +949,6 @@ This applies to:
### ❌ Planned Features
- Advanced control flow (if/else, choose random)
- Event system and handlers
- Mathematical expressions
- Variable references with $ syntax
- Spatial operations and zones
- 2D matrix support

View File

@ -124,6 +124,62 @@ run pulse_red
```
The DSL transpiles to Berry code where each animation gets an engine parameter and named parameters are set individually.
## Symbol Resolution
The DSL transpiler uses intelligent symbol resolution at compile time to optimize generated code and eliminate runtime lookups:
### Transpile-Time Symbol Resolution
When the DSL encounters an identifier (like `SINE` or `red`), it checks at transpile time whether the symbol exists in the `animation` module using Berry's introspection capabilities:
```dsl
# If SINE exists in animation module
animation wave = wave_animation(waveform=SINE)
# Transpiles to: animation.SINE (direct access)
# If custom_color doesn't exist in animation module
color custom_color = #FF0000
animation solid_red = solid(color=custom_color)
# Transpiles to: custom_color_ (user-defined variable)
```
### Benefits
- **Performance**: Eliminates runtime symbol lookups for built-in constants
- **Error Detection**: Catches undefined symbols at compile time
- **Code Clarity**: Generated Berry code clearly shows built-in vs user-defined symbols
- **Optimization**: Direct access to animation module symbols is faster
### Symbol Categories
**Built-in Symbols** (resolved to `animation.<symbol>`):
- Animation factory functions: `solid`, `pulsating_animation`, `comet_animation`
- Value providers: `triangle`, `smooth`, `sine`, `static_value`
- Color providers: `color_cycle`, `breathe_color`, `rich_palette`
- Constants: `PALETTE_RAINBOW`, `SINE`, `TRIANGLE`, etc.
**User-defined Symbols** (resolved to `<symbol>_`):
- Custom colors: `my_red`, `fire_color`
- Custom animations: `pulse_effect`, `rainbow_wave`
- Variables: `brightness_level`, `cycle_time`
### Property Assignment Resolution
Property assignments also use the same resolution logic:
```dsl
# Built-in symbol (if 'engine' existed in animation module)
engine.brightness = 200
# Would transpile to: animation.engine.brightness = 200
# User-defined symbol
my_animation.priority = 10
# Transpiles to: my_animation_.priority = 10
```
This intelligent resolution ensures optimal performance while maintaining clear separation between framework and user code.
## Advanced DSL Features
### User-Defined Functions

View File

@ -96,9 +96,43 @@ sequence sunrise_show {
run sunrise_show
```
## User Functions in Computed Parameters
### 9. Simple User Function
```dsl
# Simple user function in computed parameter
animation random_base = solid(color=blue, priority=10)
random_base.opacity = rand_demo()
run random_base
```
### 10. User Function with Math Operations
```dsl
# Mix user functions with mathematical functions
animation random_bounded = solid(
color=purple
opacity=max(50, min(255, rand_demo() + 100))
priority=15
)
run random_bounded
```
### 11. User Function in Arithmetic Expression
```dsl
# Use user function in arithmetic expressions
animation random_variation = solid(
color=cyan
opacity=abs(rand_demo() - 128) + 64
priority=12
)
run random_variation
```
See `anim_examples/user_functions_demo.anim` for a complete working example.
## Advanced Examples
### 9. Dynamic Position
### 13. Dynamic Position
```dsl
strip length 60
@ -114,7 +148,7 @@ animation moving_pulse = beacon_animation(
run moving_pulse
```
### 10. Multi-Layer Effect
### 14. Multi-Layer Effect
```dsl
# Base layer - slow breathing
set breathing = smooth(min_value=100, max_value=255, period=4s)

View File

@ -293,6 +293,81 @@ def breathing_effect(engine, color, period, min_brightness, max_brightness)
end
```
## User Functions in Computed Parameters
User functions can be used in computed parameter expressions alongside mathematical functions, creating powerful dynamic animations:
### Simple User Function in Computed Parameter
```dsl
# Simple user function call in property assignment
animation base = solid(color=blue, priority=10)
base.opacity = rand_demo() # User function as computed parameter
```
### User Functions with Mathematical Operations
```dsl
# Get strip length for calculations
set strip_len = strip_length()
# Mix user functions with mathematical functions
animation dynamic_solid = solid(
color=purple
opacity=max(50, min(255, rand_demo() + 100)) # Random opacity with bounds
priority=15
)
```
### User Functions in Complex Expressions
```dsl
# Use user function in arithmetic expressions
animation random_effect = solid(
color=cyan
opacity=abs(rand_demo() - 128) + 64 # Random variation around middle value
priority=12
)
```
### How It Works
When you use user functions in computed parameters:
1. **Automatic Detection**: The transpiler automatically detects user functions in expressions
2. **Single Closure**: The entire expression is wrapped in a single efficient closure
3. **Engine Access**: User functions receive `self.engine` in the closure context
4. **Mixed Operations**: User functions work seamlessly with mathematical functions and arithmetic
**Generated Code Example:**
```dsl
# DSL code
animation.opacity = max(100, breathing(red, 2000))
```
**Transpiles to:**
```berry
animation.opacity = animation.create_closure_value(engine,
def (self, param_name, time_ms)
return (self.max(100, animation.get_user_function('breathing')(self.engine, 0xFFFF0000, 2000)))
end)
```
### Available User Functions
The following user functions are available by default:
| Function | Parameters | Description |
|----------|------------|-------------|
| `rand_demo()` | none | Returns a random value (0-255) for demonstration |
### Best Practices for Computed Parameters
1. **Keep expressions readable**: Break complex expressions across multiple lines
2. **Use meaningful variable names**: `set strip_len = strip_length()` not `set s = strip_length()`
3. **Combine wisely**: Mix user functions with math functions for rich effects
4. **Test incrementally**: Start simple and build up complex expressions
## Loading and Using Functions
### In Tasmota autoexec.be

View File

@ -86,6 +86,14 @@ register_to_animation(event_handler)
import "core/user_functions" as user_functions
register_to_animation(user_functions)
# Import and register actual user functions
try
import "user_functions" as user_funcs # This registers the actual user functions
except .. as e, msg
# User functions are optional - continue without them if not available
print(f"Note: User functions not loaded: {msg}")
end
# Import value providers
import "providers/value_provider.be" as value_provider
register_to_animation(value_provider)
@ -95,6 +103,8 @@ import "providers/oscillator_value_provider.be" as oscillator_value_provider
register_to_animation(oscillator_value_provider)
import "providers/strip_length_provider.be" as strip_length_provider
register_to_animation(strip_length_provider)
import "providers/closure_value_provider.be" as closure_value_provider
register_to_animation(closure_value_provider)
# Import color providers
import "providers/color_provider.be" as color_provider
@ -171,7 +181,7 @@ def animation_init_strip(*l)
import animation
import introspect
# we keep a hash of strip configurations to reuse existing engines
if !introspect.contains(animation, "_strips")
if !introspect.contains(animation, "_engines")
animation._engines = {}
end
@ -184,6 +194,7 @@ def animation_init_strip(*l)
else
var strip = call(global.Leds, l) # call global.Leds() with vararg
engine = animation.create_engine(strip)
animation._engines[l_as_string] = engine
end
return engine

View File

@ -142,7 +142,7 @@ class ParameterizedObject
def _set_parameter_value(name, value)
# Validate the value (skip validation for ValueProvider instances)
if !animation.is_value_provider(value)
self._validate_param(name, value) # This will raise exception with details if invalid
value = self._validate_param(name, value) # Get potentially converted value
end
# Store the value
@ -182,7 +182,8 @@ class ParameterizedObject
# Raises detailed exceptions for validation failures
#
# @param name: string - Parameter name
# @param value: any - Value to validate
# @param value: any - Value to validate (may be modified for real->int conversion)
# @return any - Validated value (potentially converted from real to int)
def _validate_param(name, value)
var constraints = self._get_param_def(name)
if constraints == nil
@ -191,19 +192,19 @@ class ParameterizedObject
# Accept ValueProvider instances for all parameters
if animation.is_value_provider(value)
return
return value
end
# Handle nil values
if value == nil
# Check if nil is explicitly allowed via nillable attribute
if constraints.contains("nillable") && constraints["nillable"] == true
return # nil is allowed for this parameter
return value # nil is allowed for this parameter
end
# Check if there's a default value (nil is acceptable if there's a default)
if constraints.contains("default")
return # nil is acceptable, will use default
return value # nil is acceptable, will use default
end
# nil is not allowed for this parameter
@ -221,8 +222,12 @@ class ParameterizedObject
# Skip type validation if expected type is "any"
if expected_type != "any"
# Validate type
if expected_type != actual_type
# Special case: accept real values for int parameters and convert them
if expected_type == "int" && actual_type == "real"
import math
value = int(math.round(value))
actual_type = "int"
elif expected_type != actual_type
raise "value_error", f"Parameter '{name}' expects type '{expected_type}' but got '{actual_type}' (value: {value})"
end
end
@ -256,6 +261,8 @@ class ParameterizedObject
raise "value_error", f"Parameter '{name}' value {value} is not in allowed values {enum_list}"
end
end
return value
end
# Set a parameter value with validation

View File

@ -24,6 +24,7 @@ class SimpleDSLTranspiler
var run_statements # Collect all run statements for single engine.start()
var first_statement # Track if we're processing the first statement
var strip_initialized # Track if strip was initialized
var sequence_names # Track which names are sequences
# Static color mapping for named colors (helps with solidification)
static var named_colors = {
@ -50,6 +51,7 @@ class SimpleDSLTranspiler
self.run_statements = []
self.first_statement = true # Track if we're processing the first statement
self.strip_initialized = false # Track if strip was initialized
self.sequence_names = {} # Track which names are sequences
end
# Main transpilation method - single pass
@ -375,9 +377,13 @@ class SimpleDSLTranspiler
return
end
# Track that this name is a sequence
self.sequence_names[name] = true
self.expect_left_brace()
self.add(f"def sequence_{name}()")
# Generate anonymous closure that creates and returns sequence manager
self.add(f"var {name}_ = (def (engine)")
self.add(f" var steps = []")
# Process sequence body
@ -388,7 +394,7 @@ class SimpleDSLTranspiler
self.add(f" var seq_manager = animation.SequenceManager(engine)")
self.add(f" seq_manager.start_sequence(steps)")
self.add(f" return seq_manager")
self.add("end")
self.add(f"end)(engine)")
self.expect_right_brace()
end
@ -423,7 +429,7 @@ class SimpleDSLTranspiler
# This is a function call - process it as a nested function call
anim_ref = self.process_nested_function_call()
else
# This is an identifier reference
# This is an identifier reference - sequences need runtime resolution
var anim_name = self.expect_identifier()
anim_ref = f"animation.global('{anim_name}_')"
end
@ -478,7 +484,7 @@ class SimpleDSLTranspiler
# This is a function call - process it as a nested function call
anim_ref = self.process_nested_function_call()
else
# This is an identifier reference
# This is an identifier reference - sequences need runtime resolution
var anim_name = self.expect_identifier()
anim_ref = f"animation.global('{anim_name}_')"
end
@ -533,8 +539,19 @@ class SimpleDSLTranspiler
var value = self.process_value("property")
var inline_comment = self.collect_inline_comment()
# Generate property assignment: animation.global('object_name_').property = value
self.add(f"animation.global('{object_name}_').{property_name} = {value}{inline_comment}")
# NEW: Use symbol resolution logic for property assignments
import introspect
var object_ref = ""
if introspect.contains(animation, object_name)
# Symbol exists in animation module, use it directly
object_ref = f"animation.{object_name}"
else
# Symbol doesn't exist in animation module, use underscore suffix
object_ref = f"{object_name}_"
end
# Generate property assignment
self.add(f"{object_ref}.{property_name} = {value}{inline_comment}")
else
# Not a property assignment, skip this statement
self.error(f"Expected property assignment for '{object_name}' but found no dot")
@ -544,6 +561,59 @@ class SimpleDSLTranspiler
# Process any value - unified approach
def process_value(context)
return self.process_expression(context)
end
# Process expressions with arithmetic operations
def process_expression(context)
return self.process_additive_expression(context)
end
# Process additive expressions (+ and -)
def process_additive_expression(context)
var left = self.process_multiplicative_expression(context)
while !self.at_end()
var tok = self.current()
if tok != nil && (tok.type == animation_dsl.Token.PLUS || tok.type == animation_dsl.Token.MINUS)
var op = tok.value
self.next() # consume operator
var right = self.process_multiplicative_expression(context)
left = f"{left} {op} {right}"
else
break
end
end
# Check if the entire expression needs a closure (after building the full expression)
if self.is_computed_expression_string(left)
return self.create_computation_closure_from_string(left)
else
return left
end
end
# Process multiplicative expressions (* and /)
def process_multiplicative_expression(context)
var left = self.process_unary_expression(context)
while !self.at_end()
var tok = self.current()
if tok != nil && (tok.type == animation_dsl.Token.MULTIPLY || tok.type == animation_dsl.Token.DIVIDE)
var op = tok.value
self.next() # consume operator
var right = self.process_unary_expression(context)
left = f"{left} {op} {right}"
else
break
end
end
return left
end
# Process unary expressions (- and +)
def process_unary_expression(context)
var tok = self.current()
if tok == nil
self.error("Expected value")
@ -553,15 +623,33 @@ class SimpleDSLTranspiler
# Handle unary minus for negative numbers
if tok.type == animation_dsl.Token.MINUS
self.next() # consume the minus
var next_tok = self.current()
if next_tok != nil && next_tok.type == animation_dsl.Token.NUMBER
var value = "-" + next_tok.value
self.next() # consume the number
return value
else
self.error("Expected number after '-'")
return "0"
end
var expr = self.process_unary_expression(context)
return f"(-{expr})"
end
# Handle unary plus (optional)
if tok.type == animation_dsl.Token.PLUS
self.next() # consume the plus
return self.process_unary_expression(context)
end
return self.process_primary_expression(context)
end
# Process primary expressions (literals, identifiers, function calls, parentheses)
def process_primary_expression(context)
var tok = self.current()
if tok == nil
self.error("Expected value")
return "nil"
end
# Parenthesized expression
if tok.type == animation_dsl.Token.LEFT_PAREN
self.next() # consume '('
var expr = self.process_expression(context)
self.expect_right_paren()
return f"({expr})"
end
# Function call: identifier or easing keyword followed by '('
@ -627,8 +715,15 @@ class SimpleDSLTranspiler
return self.get_named_color_value(name)
end
# Use underscore suffix for DSL variables to avoid conflicts
return f"animation.global('{name}_', '{name}')"
# NEW: Check at transpile time if symbol exists in animation module
import introspect
if introspect.contains(animation, name)
# Symbol exists in animation module, use it directly
return f"animation.{name}"
else
# Symbol doesn't exist in animation module, use underscore suffix
return f"{name}_"
end
end
# Boolean keywords
@ -650,6 +745,258 @@ class SimpleDSLTranspiler
return "nil"
end
# Check if an expression string contains computed values that need a closure
def is_computed_expression_string(expr_str)
import string
# Check if the expression contains operators that make it a computation
var has_operators = (
string.find(expr_str, " + ") >= 0 || # Addition
string.find(expr_str, " - ") >= 0 || # Subtraction
string.find(expr_str, " * ") >= 0 || # Multiplication
string.find(expr_str, " / ") >= 0 # Division
)
# Check for function calls (parentheses with identifiers before them)
# This excludes simple parenthesized literals like (-1)
var has_function_calls = false
var paren_pos = string.find(expr_str, "(")
if paren_pos > 0
# Check if there's an identifier before the parenthesis (indicating a function call)
var char_before = expr_str[paren_pos-1]
if self.is_identifier_char(char_before)
has_function_calls = true
end
end
# Only create closures for expressions that actually involve computation
return has_operators || has_function_calls
end
# Check if an expression contains computed values that need a closure (legacy method)
def is_computed_expression(left, op, right)
import string
# Check if either operand contains a function call, variable reference, or user variable
# We're permissive here - any expression with these patterns gets a closure
var has_dynamic_content = (
string.find(left, "(") >= 0 || string.find(right, "(") >= 0 || # Function calls
string.find(left, "animation.global") >= 0 || string.find(right, "animation.global") >= 0 || # Variable refs
string.find(left, "animation.") >= 0 || string.find(right, "animation.") >= 0 || # Animation module calls
string.find(left, "_") >= 0 || string.find(right, "_") >= 0 # User variables (might be ValueProviders)
)
return has_dynamic_content
end
# Create a closure for computed expressions from a complete expression string
def create_computation_closure_from_string(expr_str)
import string
# Transform the entire expression to handle ValueProvider instances
var transformed_expr = self.transform_expression_for_closure(expr_str)
# Clean up spacing in the expression - remove extra spaces
while string.find(transformed_expr, " ") >= 0
transformed_expr = string.replace(transformed_expr, " ", " ")
end
var closure_code = f"def (self, param_name, time_ms) return ({transformed_expr}) end"
# Return a closure value provider instance
return f"animation.create_closure_value(engine, {closure_code})"
end
# Create a closure for computed expressions (legacy method)
def create_computation_closure(left, op, right)
import string
# Create a closure value provider that wraps the computation
# This replaces the old DSL computed value wrapper approach
# Transform operands to handle ValueProvider instances
var left_expr = self.transform_operand_for_closure(left)
var right_expr = self.transform_operand_for_closure(right)
# Clean up spacing in the expression - remove extra spaces
while string.find(left_expr, " ") >= 0
left_expr = string.replace(left_expr, " ", " ")
end
while string.find(right_expr, " ") >= 0
right_expr = string.replace(right_expr, " ", " ")
end
var closure_code = f"def (self, param_name, time_ms) return ({left_expr} {op} {right_expr}) end"
# Return a closure value provider instance
return f"animation.create_closure_value(engine, {closure_code})"
end
# Transform a complete expression for use in a closure, handling ValueProvider instances
def transform_expression_for_closure(expr_str)
import string
var result = expr_str
var pos = 0
# First pass: Transform mathematical function calls to self.method() calls
# Use a simple pattern-based approach that works with the existing logic
var search_pos = 0
while true
var paren_pos = string.find(result, "(", search_pos)
if paren_pos < 0
break
end
# Find the function name before the parenthesis
var func_start = paren_pos - 1
while func_start >= 0 && self.is_identifier_char(result[func_start])
func_start -= 1
end
func_start += 1
if func_start < paren_pos
var func_name = result[func_start..paren_pos-1]
# Check if this is a mathematical method using dynamic introspection
if self.is_math_method(func_name)
# Check if it's not already prefixed with "self."
var prefix_start = func_start >= 5 ? func_start - 5 : 0
var prefix = result[prefix_start..func_start-1]
if string.find(prefix, "self.") < 0
# Replace the function call with self.method()
var before = func_start > 0 ? result[0..func_start-1] : ""
var after = result[func_start..]
result = before + "self." + after
search_pos = func_start + 5 + size(func_name) # Skip past "self." + func_name
else
search_pos = paren_pos + 1
end
else
search_pos = paren_pos + 1
end
else
search_pos = paren_pos + 1
end
end
# Second pass: Replace all user variables (ending with _) with resolve calls
pos = 0
while pos < size(result)
var underscore_pos = string.find(result, "_", pos)
if underscore_pos < 0
break
end
# Find the start of the identifier
var start_pos = underscore_pos
while start_pos > 0 && self.is_identifier_char(result[start_pos-1])
start_pos -= 1
end
# Check if this is a user variable (not preceded by "animation." or "self.")
var is_user_var = true
if start_pos >= 10
var check_start = start_pos >= 10 ? start_pos - 10 : 0
var prefix = result[check_start..start_pos-1]
if string.find(prefix, "animation.") >= 0 || string.find(prefix, "self.") >= 0
is_user_var = false
end
elif start_pos >= 5
var check_start = start_pos >= 5 ? start_pos - 5 : 0
var prefix = result[check_start..start_pos-1]
if string.find(prefix, "self.") >= 0
is_user_var = false
end
end
if is_user_var && start_pos < underscore_pos
# Extract the variable name
var var_name = result[start_pos..underscore_pos]
# Check if it's followed by non-identifier characters
var end_pos = underscore_pos + 1
if end_pos >= size(result) || !self.is_identifier_char(result[end_pos])
# Replace the variable with the resolve call
var replacement = f"self.resolve({var_name}, param_name, time_ms)"
var before = start_pos > 0 ? result[0..start_pos-1] : ""
var after = end_pos < size(result) ? result[end_pos..] : ""
result = before + replacement + after
pos = start_pos + size(replacement)
else
pos = underscore_pos + 1
end
else
pos = underscore_pos + 1
end
end
return result
end
# Helper method to check if a character is part of an identifier
def is_identifier_char(ch)
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_'
end
# Helper method to check if a function name is a mathematical method in ClosureValueProvider
def is_math_method(func_name)
import introspect
try
# Get the ClosureValueProvider class from the animation module
var closure_provider_class = animation.closure_value
if closure_provider_class == nil
return false
end
# Check if the method exists in the class
var members = introspect.members(closure_provider_class)
for member : members
if member == func_name
# Additional check: make sure it's actually a method (function)
var method = introspect.get(closure_provider_class, func_name)
return introspect.ismethod(method) || type(method) == 'function'
end
end
return false
except .. as e, msg
# If introspection fails, return false to be safe
return false
end
end
# Transform an operand for use in a closure, handling ValueProvider instances
def transform_operand_for_closure(operand)
import string
# If the operand is already a closure (contains create_closure_value), extract its inner expression
if string.find(operand, "animation.create_closure_value") >= 0
# Extract the inner expression from the closure
var start_pos = string.find(operand, "return (") + 8
var end_pos = size(operand) - 5 # Remove " end)"
var inner_expr = operand[start_pos..end_pos]
# Clean up any extra spaces
while string.find(inner_expr, " ") >= 0
inner_expr = string.replace(inner_expr, " ", " ")
end
return inner_expr
end
# Check if this is a simple user variable (identifier ending with _ and no operators/parentheses)
var has_underscore = string.find(operand, "_") >= 0
var has_operators = string.find(operand, " ") >= 0 # Simple check for operators (they have spaces)
var has_paren = string.find(operand, "(") >= 0
var has_animation_prefix = string.find(operand, "animation.") >= 0
if has_underscore && !has_operators && !has_paren && !has_animation_prefix
# This looks like a simple user variable that might be a ValueProvider
return f"self.resolve({operand}, param_name, time_ms)"
else
# For other expressions (literals, animation module calls, complex expressions), use as-is
return operand
end
end
# Process function call (legacy - for non-animation contexts)
def process_function_call(context)
var tok = self.current()
@ -664,6 +1011,13 @@ class SimpleDSLTranspiler
return "nil"
end
# Check if this is a mathematical function - handle with positional arguments
if self.is_math_method(func_name)
# Mathematical functions use positional arguments, not named parameters
var args = self.process_function_arguments()
return f"{func_name}({args})" # Return as-is for transformation in closure
end
var args = self.process_function_arguments()
# Check if it's a user-defined function first
@ -836,8 +1190,197 @@ class SimpleDSLTranspiler
end
return result
end
# Process function arguments for expressions (returns raw expressions without closures)
def process_function_arguments_for_expression()
self.expect_left_paren()
var args = []
while !self.at_end() && !self.check_right_paren()
self.skip_whitespace()
if self.check_right_paren()
break
end
var arg = self.process_expression_argument()
args.push(arg)
self.skip_whitespace()
if self.current() != nil && self.current().type == animation_dsl.Token.COMMA
self.next() # skip comma
self.skip_whitespace()
elif !self.check_right_paren()
self.error("Expected ',' or ')' in function arguments")
break
end
end
self.expect_right_paren()
# Join arguments with commas
var result = ""
for i : 0..size(args)-1
if i > 0
result += ", "
end
result += args[i]
end
return result
end
# Process expression argument (raw expression without closure wrapping)
def process_expression_argument()
# Just process as a raw additive expression - this handles all cases
return self.process_additive_expression_raw()
end
# Process additive expression without closure wrapping (for function arguments)
def process_additive_expression_raw()
var left = self.process_multiplicative_expression_raw()
while !self.at_end()
var tok = self.current()
if tok != nil && (tok.type == animation_dsl.Token.PLUS || tok.type == animation_dsl.Token.MINUS)
var op = tok.value
self.next() # consume operator
var right = self.process_multiplicative_expression_raw()
left = f"{left} {op} {right}"
else
break
end
end
return left
end
# Process multiplicative expression without closure wrapping (for function arguments)
def process_multiplicative_expression_raw()
var left = self.process_unary_expression_raw()
while !self.at_end()
var tok = self.current()
if tok != nil && (tok.type == animation_dsl.Token.MULTIPLY || tok.type == animation_dsl.Token.DIVIDE)
var op = tok.value
self.next() # consume operator
var right = self.process_unary_expression_raw()
left = f"{left} {op} {right}"
else
break
end
end
return left
end
# Process unary expression without closure wrapping (for function arguments)
def process_unary_expression_raw()
var tok = self.current()
if tok == nil
self.error("Expected value")
return "nil"
end
# Handle unary minus for negative numbers
if tok.type == animation_dsl.Token.MINUS
self.next() # consume the minus
var expr = self.process_unary_expression_raw()
return f"(-{expr})"
end
# Handle unary plus (optional)
if tok.type == animation_dsl.Token.PLUS
self.next() # consume the plus
return self.process_unary_expression_raw()
end
return self.process_primary_expression_raw()
end
# Process primary expression without closure wrapping (for function arguments)
def process_primary_expression_raw()
var tok = self.current()
if tok == nil
self.error("Expected value")
return "nil"
end
# Parenthesized expression
if tok.type == animation_dsl.Token.LEFT_PAREN
self.next() # consume '('
var expr = self.process_additive_expression_raw()
self.expect_right_paren()
return f"({expr})"
end
# Function call: identifier or easing keyword followed by '('
if (tok.type == animation_dsl.Token.KEYWORD || tok.type == animation_dsl.Token.IDENTIFIER) &&
self.peek() != nil && self.peek().type == animation_dsl.Token.LEFT_PAREN
var func_name = tok.value
self.next()
# Check if this is a mathematical function
if self.is_math_method(func_name)
var args = self.process_function_arguments_for_expression()
return f"self.{func_name}({args})"
end
# Check if this is a user-defined function
if animation.is_user_function(func_name)
var args = self.process_function_arguments_for_expression()
var full_args = args != "" ? f"self.engine, {args}" : "self.engine"
return f"animation.get_user_function('{func_name}')({full_args})"
end
# For other functions, this shouldn't happen in expression context
self.error(f"Function '{func_name}' not supported in expression context")
return "nil"
end
# Color value
if tok.type == animation_dsl.Token.COLOR
self.next()
return self.convert_color(tok.value)
end
# Time value
if tok.type == animation_dsl.Token.TIME
return str(self.process_time_value())
end
# Percentage value
if tok.type == animation_dsl.Token.PERCENTAGE
return str(self.process_percentage_value())
end
# Number value
if tok.type == animation_dsl.Token.NUMBER
var value = tok.value
self.next()
return value
end
# String value
if tok.type == animation_dsl.Token.STRING
var value = tok.value
self.next()
return f'"{value}"'
end
# Identifier - variable reference
if tok.type == animation_dsl.Token.IDENTIFIER
var name = tok.value
self.next()
return f"self.resolve({name}_, param_name, time_ms)"
end
self.error(f"Unexpected token in expression: {tok.value}")
return "nil"
end
# Process nested function call (generates temporary variable)
# Process nested function call (generates temporary variable or raw expression)
def process_nested_function_call()
var tok = self.current()
var func_name = ""
@ -851,11 +1394,19 @@ class SimpleDSLTranspiler
return "nil"
end
# Check if this is a mathematical function - handle with positional arguments
if self.is_math_method(func_name)
# Mathematical functions use positional arguments, not named parameters
var args = self.process_function_arguments_for_expression()
return f"self.{func_name}({args})" # Prefix with self. for closure context
end
# Check if this is a user-defined function
if animation.is_user_function(func_name)
# User functions use positional parameters with engine as first argument
var args = self.process_function_arguments()
var full_args = args != "" ? f"engine, {args}" : "engine"
# In closure context, use self.engine to access the engine from the ClosureValueProvider
var args = self.process_function_arguments_for_expression()
var full_args = args != "" ? f"self.engine, {args}" : "self.engine"
return f"animation.get_user_function('{func_name}')({full_args})"
else
# Built-in functions use the new engine-first + named parameters pattern
@ -1289,20 +1840,19 @@ class SimpleDSLTranspiler
return # No run statements, no need to start engine
end
self.add("# Start all animations/sequences")
# Add all animations/sequences to the engine
for run_stmt : self.run_statements
var name = run_stmt["name"]
var comment = run_stmt["comment"]
# Check what exists: sequence function or animation variable
self.add(f"if global.contains('sequence_{name}'){comment}")
self.add(f" var seq_manager = global.sequence_{name}()")
self.add(f" engine.add_sequence_manager(seq_manager)")
self.add(f"else")
self.add(f" engine.add_animation(animation.global('{name}_'))")
self.add(f"end")
# Check if this is a sequence or regular animation
if self.sequence_names.contains(name)
# It's a sequence - the closure returned a SequenceManager
self.add(f"engine.add_sequence_manager({name}_){comment}")
else
# It's a regular animation
self.add(f"engine.add_animation({name}_){comment}")
end
end
# Single engine.start() call
@ -1534,6 +2084,11 @@ class SimpleDSLTranspiler
# Validate animation factory exists and creates animation.animation instance
def _validate_animation_factory_exists(func_name)
# Skip validation for mathematical functions - they will be handled by closure transformation
if self.is_math_method(func_name)
return true # Skip validation for mathematical functions
end
return self._validate_factory_function(func_name, nil)
end

View File

@ -0,0 +1,196 @@
# ClosureValueProvider - ValueProvider that wraps a closure/function
#
# This provider allows using closures (functions) as value providers.
# The closure is called with (self, param_name, time_ms) parameters when
# a value is requested.
#
# Usage:
# var provider = animation.closure_value_provider(engine)
# provider.closure = def(self, param_name, time_ms) return time_ms / 100 end
# animation.brightness = provider
# Alternative with reference to another value:
# var strip_len_ = animation.strip_length(engine)
# var provider = animation.closure_value_provider(engine)
# provider.closure = def(self, param_name, time_ms) return self.resolve(strip_len_, param_name, timer_ms) + 2 end
# animation.brightness = provider
#
#@ solidify:ClosureValueProvider,weak
class ClosureValueProvider : animation.value_provider
var _closure # We keep the closure as instance variable for faster dereferencing, in addition to PARAMS
# Static parameter definitions
static var PARAMS = {
"closure": {"type": "function", "default": nil}
}
# Method called when a parameter is changed
# Copy "closure" parameter to _closure instance variable
#
# @param name: string - Parameter name
# @param value: any - New parameter value
def on_param_changed(name, value)
if name == "closure"
self._closure = value
end
end
# Helper method to resolve a value that can be either static or from a value provider
# This is equivalent to 'resolve_param' but with a shorter name
# and available at first dereferencing of method name (hence faster)
#
# @param value: any - Static value or value provider instance
# @param param_name: string - Parameter name for specific produce_value() method lookup
# @param time_ms: int - Current time in milliseconds
# @return any - The resolved value (static or from provider)
def resolve(value, param_name, time_ms)
if animation.is_value_provider(value)
return value.produce_value(param_name, time_ms)
else
return value
end
end
# Produce a value by calling the stored closure
#
# @param name: string - Parameter name being requested
# @param time_ms: int - Current time in milliseconds
# @return any - Value returned by the closure
def produce_value(name, time_ms)
var closure = self._closure
if closure == nil
return nil
end
# Call the closure with the parameter self, name and time
return closure(self, name, time_ms)
end
# Mathematical helper methods for use in closures
# Minimum of two or more values
#
# @param a: number - First value
# @param b: number - Second value
# @param *args: number - Additional values (optional)
# @return number - Minimum value
def min(*args)
import math
return call(math.min, args)
end
# Maximum of two or more values
#
# @param a: number - First value
# @param b: number - Second value
# @param *args: number - Additional values (optional)
# @return number - Maximum value
def max(*args)
import math
return call(math.max, args)
end
# Absolute value
#
# @param x: number - Input value
# @return number - Absolute value
def abs(x)
import math
return math.abs(x)
end
# Round to nearest integer
#
# @param x: number - Input value
# @return int - Rounded value
def round(x)
import math
return int(math.round(x))
end
# Square root with integer handling
# For integers, treats 1.0 as 255 (full scale)
#
# @param x: number - Input value
# @return number - Square root
def sqrt(x)
import math
# If x is an integer in 0-255 range, scale to 0-1 for sqrt, then back
if type(x) == 'int' && x >= 0 && x <= 255
var normalized = x / 255.0
return int(math.sqrt(normalized) * 255)
else
return math.sqrt(x)
end
end
# Scale a value from one range to another using tasmota.scale_int
#
# @param v: number - Value to scale
# @param from_min: number - Source range minimum
# @param from_max: number - Source range maximum
# @param to_min: number - Target range minimum
# @param to_max: number - Target range maximum
# @return int - Scaled value
def scale(v, from_min, from_max, to_min, to_max)
return tasmota.scale_int(v, from_min, from_max, to_min, to_max)
end
# Sine function using tasmota.sine_int (works on integers)
# Input angle is in 0-255 range (mapped to 0-360 degrees)
# Output is in -255 to 255 range (mapped from -1.0 to 1.0)
#
# @param angle: number - Angle in 0-255 range (0-360 degrees)
# @return int - Sine value in -255 to 255 range
def sine(angle)
# Map angle from 0-255 to 0-32767 (tasmota.sine_int input range)
var tasmota_angle = tasmota.scale_int(angle, 0, 255, 0, 32767)
# Get sine value from -4096 to 4096 (representing -1.0 to 1.0)
var sine_val = tasmota.sine_int(tasmota_angle)
# Map from -4096..4096 to -255..255 for integer output
return tasmota.scale_int(sine_val, -4096, 4096, -255, 255)
end
# Cosine function using tasmota.sine_int with phase shift
# Input angle is in 0-255 range (mapped to 0-360 degrees)
# Output is in -255 to 255 range (mapped from -1.0 to 1.0)
# Note: This matches the oscillator COSINE behavior (starts at minimum, not maximum)
#
# @param angle: number - Angle in 0-255 range (0-360 degrees)
# @return int - Cosine value in -255 to 255 range
def cosine(angle)
# Map angle from 0-255 to 0-32767 (tasmota.sine_int input range)
var tasmota_angle = tasmota.scale_int(angle, 0, 255, 0, 32767)
# Get cosine value by shifting sine by -90 degrees (matches oscillator behavior)
var cosine_val = tasmota.sine_int(tasmota_angle - 8192)
# Map from -4096..4096 to -255..255 for integer output
return tasmota.scale_int(cosine_val, -4096, 4096, -255, 255)
end
# String representation for debugging
#
# @return string - Human-readable description of the provider
def tostring()
return f"ClosureValueProvider({self._closure ? 'closure set' :: 'no closure'})"
end
end
# Create a ClosureValueProvider in a single call, by passing the closure argument
#
# This is used only by the transpiler, and is not usable in the DSL by itself
#
# @param engine: AnimationEngine - Animation engine reference
# @param closure: function - the closure to evaluate at run-time
# @return ClosureValueProvider - New ClosureValueProvider instance
def create_closure_value(engine, closure)
var provider = animation.closure_value(engine)
provider.closure = closure
return provider
end
return {'closure_value': ClosureValueProvider,
'create_closure_value': create_closure_value}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,460 @@
# Test for ClosureValueProvider
#
# This test verifies that the ClosureValueProvider correctly wraps
# closures and evaluates them when producing values.
import animation
def test_closure_value_provider()
print("Testing ClosureValueProvider...")
# Create a mock engine
class MockEngine
var time_ms
def init()
self.time_ms = 1000
end
end
var engine = MockEngine()
# Create a closure value provider
var provider = animation.closure_value(engine)
# Test 1: Provider without closure returns nil
var result = provider.produce_value("test", 1000)
assert(result == nil, "Provider without closure should return nil")
print("✓ Provider without closure returns nil")
# Test 2: Set a simple closure
var f = def(self, name, time_ms) return time_ms / 100 end
print(f">> {f=} {provider=}")
provider.closure = f
result = provider.produce_value("brightness", 1000)
assert(result == 10, f"Expected 10, got {result}")
print("✓ Simple closure evaluation works")
# Test 3: Closure receives correct parameters
var captured_name = nil
var captured_time = nil
provider.closure = def(self, name, time_ms)
captured_name = name
captured_time = time_ms
return 42
end
result = provider.produce_value("color", 2000)
assert(result == 42, f"Expected 42, got {result}")
assert(captured_name == "color", f"Expected 'color', got '{captured_name}'")
assert(captured_time == 2000, f"Expected 2000, got {captured_time}")
print("✓ Closure receives correct parameters")
# Test 4: Complex closure with calculations
provider.closure = def(self, name, time_ms)
if name == "brightness"
return (time_ms % 2000) / 2000.0 * 255
elif name == "hue"
return (time_ms / 10) % 360
else
return 0
end
end
var brightness = provider.produce_value("brightness", 1000)
var hue = provider.produce_value("hue", 3600)
var other = provider.produce_value("other", 1000)
assert(brightness == 127.5, f"Expected 127.5, got {brightness}")
assert(hue == 0, f"Expected 0, got {hue}") # 3600 / 10 = 360, 360 % 360 = 0
assert(other == 0, f"Expected 0, got {other}")
print("✓ Complex closure with parameter-specific logic works")
# Test 5: Test self.resolve helper method with actual value provider
var static_provider = animation.static_value(engine)
static_provider.value = 100
provider.closure = def(self, name, time_ms)
# Use self.resolve to get value from another provider
var base_value = self.resolve(static_provider, name, time_ms)
return base_value * 2
end
result = provider.produce_value("test", 2000)
# static_provider returns 100, then multiply by 2 = 200
assert(result == 200, f"Expected 200, got {result}")
print("✓ self.resolve helper method works with value providers")
# Test 6: Test self.resolve with static value and value provider
provider.closure = def(self, name, time_ms)
var static_value = self.resolve(50, name, time_ms) # Static value
var dynamic_value = self.resolve(static_provider, name, time_ms) # Value provider
return static_value + dynamic_value
end
result = provider.produce_value("test", 1000)
# static: 50, dynamic: 100, total: 150
assert(result == 150, f"Expected 150, got {result}")
print("✓ self.resolve works with both static values and value providers")
# Test 7: Test the use case from documentation - arithmetic with another provider
var oscillator = animation.oscillator_value(engine)
oscillator.min_value = 10
oscillator.max_value = 20
oscillator.duration = 1000
provider.closure = def(self, name, time_ms)
var osc_value = self.resolve(oscillator, name, time_ms)
return osc_value + 5 # Add 5 to oscillator value
end
result = provider.produce_value("position", 500)
# Oscillator should return a value between 10-20, plus 5 = 15-25
assert(result >= 15 && result <= 25, f"Expected result between 15-25, got {result}")
print("✓ Documentation use case works - arithmetic with other providers")
# Test 8: Test negative numbers and negative expressions
provider.closure = def(self, name, time_ms)
if name == "negative_literal"
return -2
elif name == "negative_expression"
return -(time_ms / 100)
elif name == "negative_with_addition"
return -5 + 3
else
return 0
end
end
var neg_literal = provider.produce_value("negative_literal", 1000)
var neg_expr = provider.produce_value("negative_expression", 500)
var neg_add = provider.produce_value("negative_with_addition", 1000)
assert(neg_literal == -2, f"Expected -2, got {neg_literal}")
assert(neg_expr == -5, f"Expected -5, got {neg_expr}") # -(500/100) = -5
assert(neg_add == -2, f"Expected -2, got {neg_add}") # -5 + 3 = -2
print("✓ Negative numbers and expressions work correctly")
# Test 9: Complex expressions with multiple parameters and operators
var param1 = animation.static_value(engine)
param1.value = 10
var param2 = animation.static_value(engine)
param2.value = 3
var param3 = animation.static_value(engine)
param3.value = 2
provider.closure = def(self, name, time_ms)
var p1 = self.resolve(param1, name, time_ms)
var p2 = self.resolve(param2, name, time_ms)
var p3 = self.resolve(param3, name, time_ms)
if name == "arithmetic_complex"
return (p1 + p2) * p3 - 5 # (10 + 3) * 2 - 5 = 26 - 5 = 21
elif name == "division_modulo"
return (p1 * p2) / p3 % 7 # (10 * 3) / 2 % 7 = 30 / 2 % 7 = 15 % 7 = 1
elif name == "mixed_operations"
return p1 - p2 + p3 * 4 / 2 # 10 - 3 + 2 * 4 / 2 = 10 - 3 + 8 / 2 = 10 - 3 + 4 = 11
elif name == "power_and_comparison"
var base = p1 + p2 # 13
return base > 12 ? base * p3 : base / p3 # 13 > 12 ? 13 * 2 : 13 / 2 = 26
else
return 0
end
end
var arith_result = provider.produce_value("arithmetic_complex", 1000)
var div_mod_result = provider.produce_value("division_modulo", 1000)
var mixed_result = provider.produce_value("mixed_operations", 1000)
var power_result = provider.produce_value("power_and_comparison", 1000)
assert(arith_result == 21, f"Expected 21, got {arith_result}")
assert(div_mod_result == 1, f"Expected 1, got {div_mod_result}")
assert(mixed_result == 11, f"Expected 11, got {mixed_result}")
assert(power_result == 26, f"Expected 26, got {power_result}")
print("✓ Complex expressions with multiple parameters and operators work")
# Test 10: Time-based expressions with multiple variables
provider.closure = def(self, name, time_ms)
var base_freq = self.resolve(param1, name, time_ms) # 10
var amplitude = self.resolve(param2, name, time_ms) # 3
var offset = self.resolve(param3, name, time_ms) # 2
if name == "sine_wave_simulation"
# Simulate: amplitude * sin(time * base_freq / 1000) + offset
# Simplified: just use modulo for wave-like behavior
var wave = (time_ms * base_freq / 1000) % 360
return amplitude * (wave > 180 ? -1 : 1) + offset
elif name == "exponential_decay"
# Simulate: base_freq * exp(-time/1000) + offset
# Simplified: base_freq / (1 + time/1000) + offset
return base_freq / (1 + time_ms / 1000) + offset
elif name == "linear_interpolation"
# Linear interpolation between amplitude and base_freq over time
var t = (time_ms % 2000) / 2000.0 # 0 to 1 over 2 seconds
return amplitude + t * (base_freq - amplitude)
else
return 0
end
end
var sine_result = provider.produce_value("sine_wave_simulation", 1500)
var decay_result = provider.produce_value("exponential_decay", 1000)
var lerp_result = provider.produce_value("linear_interpolation", 1000)
# sine_wave_simulation: (1500 * 10 / 1000) % 360 = 15 % 360 = 15, 15 <= 180, so 3 * 1 + 2 = 5
assert(sine_result == 5, f"Expected 5, got {sine_result}")
# exponential_decay: 10 / (1 + 1000/1000) + 2 = 10 / 2 + 2 = 5 + 2 = 7
assert(decay_result == 7, f"Expected 7, got {decay_result}")
# linear_interpolation: t = 1000/2000 = 0.5, result = 3 + 0.5 * (10 - 3) = 3 + 0.5 * 7 = 3 + 3.5 = 6.5
assert(lerp_result == 6.5, f"Expected 6.5, got {lerp_result}")
print("✓ Time-based expressions with multiple variables work")
# Test 11: Edge cases with zero, negative, and boundary values
provider.closure = def(self, name, time_ms)
if name == "division_by_small"
return 100 / 0.1 # Should be 1000
elif name == "negative_modulo"
return -17 % 5 # Should be -2 in Berry
elif name == "zero_operations"
return 0 * 999 + 0 / 1 - 0 # Should be 0
elif name == "boundary_conditions"
var val = time_ms % 1000
return val == 0 ? -1 : val > 500 ? 1 : 0
else
return 0
end
end
var div_small = provider.produce_value("division_by_small", 1000)
var neg_mod = provider.produce_value("negative_modulo", 1000)
var zero_ops = provider.produce_value("zero_operations", 1000)
var boundary1 = provider.produce_value("boundary_conditions", 1000) # 1000 % 1000 = 0, so -1
var boundary2 = provider.produce_value("boundary_conditions", 1750) # 1750 % 1000 = 750 > 500, so 1
var boundary3 = provider.produce_value("boundary_conditions", 1250) # 1250 % 1000 = 250 <= 500, so 0
assert(div_small == 1000, f"Expected 1000, got {div_small}")
assert(neg_mod == -2, f"Expected -2, got {neg_mod}")
assert(zero_ops == 0, f"Expected 0, got {zero_ops}")
assert(boundary1 == -1, f"Expected -1, got {boundary1}")
assert(boundary2 == 1, f"Expected 1, got {boundary2}")
assert(boundary3 == 0, f"Expected 0, got {boundary3}")
print("✓ Edge cases with zero, negative, and boundary values work")
print("All ClosureValueProvider tests passed!")
end
# Test mathematical helper methods
def test_closure_math_methods()
print("Testing ClosureValueProvider mathematical methods...")
# Create a mock engine
class MockEngine
var time_ms
def init()
self.time_ms = 1000
end
end
var engine = MockEngine()
# Create a closure value provider
var provider = animation.closure_value(engine)
# Test 1: min/max functions
provider.closure = def(self, name, time_ms)
if name == "min_test"
return self.min(5, 3, 8, 1, 9) # Should return 1
elif name == "max_test"
return self.max(5, 3, 8, 1, 9) # Should return 9
elif name == "min_two"
return self.min(10, 7) # Should return 7
elif name == "max_two"
return self.max(10, 7) # Should return 10
else
return 0
end
end
var min_result = provider.produce_value("min_test", 1000)
var max_result = provider.produce_value("max_test", 1000)
var min_two = provider.produce_value("min_two", 1000)
var max_two = provider.produce_value("max_two", 1000)
assert(min_result == 1, f"Expected min=1, got {min_result}")
assert(max_result == 9, f"Expected max=9, got {max_result}")
assert(min_two == 7, f"Expected min=7, got {min_two}")
assert(max_two == 10, f"Expected max=10, got {max_two}")
print("✓ min/max functions work correctly")
# Test 2: abs function
provider.closure = def(self, name, time_ms)
if name == "abs_positive"
return self.abs(42) # Should return 42
elif name == "abs_negative"
return self.abs(-17) # Should return 17
elif name == "abs_zero"
return self.abs(0) # Should return 0
elif name == "abs_float"
return self.abs(-3.14) # Should return 3.14
else
return 0
end
end
var abs_pos = provider.produce_value("abs_positive", 1000)
var abs_neg = provider.produce_value("abs_negative", 1000)
var abs_zero = provider.produce_value("abs_zero", 1000)
var abs_float = provider.produce_value("abs_float", 1000)
assert(abs_pos == 42, f"Expected abs(42)=42, got {abs_pos}")
assert(abs_neg == 17, f"Expected abs(-17)=17, got {abs_neg}")
assert(abs_zero == 0, f"Expected abs(0)=0, got {abs_zero}")
assert(abs_float == 3.14, f"Expected abs(-3.14)=3.14, got {abs_float}")
print("✓ abs function works correctly")
# Test 3: round function
provider.closure = def(self, name, time_ms)
if name == "round_up"
return self.round(3.7) # Should return 4
elif name == "round_down"
return self.round(3.2) # Should return 3
elif name == "round_half"
return self.round(3.5) # Should return 4
elif name == "round_negative"
return self.round(-2.8) # Should return -3
else
return 0
end
end
var round_up = provider.produce_value("round_up", 1000)
var round_down = provider.produce_value("round_down", 1000)
var round_half = provider.produce_value("round_half", 1000)
var round_neg = provider.produce_value("round_negative", 1000)
assert(round_up == 4, f"Expected round(3.7)=4, got {round_up}")
assert(round_down == 3, f"Expected round(3.2)=3, got {round_down}")
assert(round_half == 4, f"Expected round(3.5)=4, got {round_half}")
assert(round_neg == -3, f"Expected round(-2.8)=-3, got {round_neg}")
print("✓ round function works correctly")
# Test 4: sqrt function with integer handling
provider.closure = def(self, name, time_ms)
if name == "sqrt_integer_255"
return self.sqrt(255) # Should return 255 (full scale)
elif name == "sqrt_integer_64"
return self.sqrt(64) # Should return ~127 (sqrt(64/255)*255)
elif name == "sqrt_integer_0"
return self.sqrt(0) # Should return 0
elif name == "sqrt_float"
return self.sqrt(16.0) # Should return 4.0
else
return 0
end
end
var sqrt_255 = provider.produce_value("sqrt_integer_255", 1000)
var sqrt_64 = provider.produce_value("sqrt_integer_64", 1000)
var sqrt_0 = provider.produce_value("sqrt_integer_0", 1000)
var sqrt_float = provider.produce_value("sqrt_float", 1000)
assert(sqrt_255 == 255, f"Expected sqrt(255)=255, got {sqrt_255}")
assert(sqrt_64 >= 127 && sqrt_64 <= 129, f"Expected sqrt(64)~128, got {sqrt_64}")
assert(sqrt_0 == 0, f"Expected sqrt(0)=0, got {sqrt_0}")
assert(sqrt_float == 4.0, f"Expected sqrt(16.0)=4.0, got {sqrt_float}")
print("✓ sqrt function works correctly")
# Test 5: scale function
provider.closure = def(self, name, time_ms)
if name == "scale_basic"
return self.scale(50, 0, 100, 0, 255) # Should return ~127
elif name == "scale_reverse"
return self.scale(25, 0, 100, 255, 0) # Should return ~191
elif name == "scale_negative"
return self.scale(0, -50, 50, -100, 100) # Should return 0
else
return 0
end
end
var scale_basic = provider.produce_value("scale_basic", 1000)
var scale_reverse = provider.produce_value("scale_reverse", 1000)
var scale_neg = provider.produce_value("scale_negative", 1000)
assert(scale_basic >= 127 && scale_basic <= 128, f"Expected scale(50,0,100,0,255)~127, got {scale_basic}")
assert(scale_reverse >= 191 && scale_reverse <= 192, f"Expected scale(25,0,100,255,0)~191, got {scale_reverse}")
assert(scale_neg == 0, f"Expected scale(0,-50,50,-100,100)=0, got {scale_neg}")
print("✓ scale function works correctly")
# Test 6: sine function
provider.closure = def(self, name, time_ms)
if name == "sine_0"
return self.sine(0) # sin(0°) = 0
elif name == "sine_64"
return self.sine(64) # sin(90°) = 1 -> 255
elif name == "sine_128"
return self.sine(128) # sin(180°) = 0
elif name == "sine_192"
return self.sine(192) # sin(270°) = -1 -> -255
else
return 0
end
end
var sine_0 = provider.produce_value("sine_0", 1000)
var sine_64 = provider.produce_value("sine_64", 1000)
var sine_128 = provider.produce_value("sine_128", 1000)
var sine_192 = provider.produce_value("sine_192", 1000)
assert(sine_0 >= -5 && sine_0 <= 5, f"Expected sine(0)~0, got {sine_0}")
assert(sine_64 >= 250 && sine_64 <= 255, f"Expected sine(64)~255, got {sine_64}")
assert(sine_128 >= -5 && sine_128 <= 5, f"Expected sine(128)~0, got {sine_128}")
assert(sine_192 >= -255 && sine_192 <= -250, f"Expected sine(192)~-255, got {sine_192}")
print("✓ sine function works correctly")
# Test 7: cosine function (matches oscillator COSINE behavior)
provider.closure = def(self, name, time_ms)
if name == "cosine_0"
return self.cosine(0) # Oscillator cosine at 0° = minimum -> -255
elif name == "cosine_64"
return self.cosine(64) # Oscillator cosine at 90° = ~0
elif name == "cosine_128"
return self.cosine(128) # Oscillator cosine at 180° = maximum -> 255
elif name == "cosine_192"
return self.cosine(192) # Oscillator cosine at 270° = ~0
else
return 0
end
end
var cosine_0 = provider.produce_value("cosine_0", 1000)
var cosine_64 = provider.produce_value("cosine_64", 1000)
var cosine_128 = provider.produce_value("cosine_128", 1000)
var cosine_192 = provider.produce_value("cosine_192", 1000)
assert(cosine_0 >= -255 && cosine_0 <= -250, f"Expected cosine(0)~-255, got {cosine_0}")
assert(cosine_64 >= -5 && cosine_64 <= 5, f"Expected cosine(64)~0, got {cosine_64}")
assert(cosine_128 >= 250 && cosine_128 <= 255, f"Expected cosine(128)~255, got {cosine_128}")
assert(cosine_192 >= -5 && cosine_192 <= 5, f"Expected cosine(192)~0, got {cosine_192}")
print("✓ cosine function works correctly")
# Test 8: Complex expression using multiple math functions
provider.closure = def(self, name, time_ms)
if name == "complex_math"
var angle = time_ms % 256 # 0-255 angle based on time
var sine_val = self.abs(self.sine(angle)) # Absolute sine value
var scaled = self.scale(sine_val, 0, 255, 50, 200) # Scale to 50-200 range
return self.min(self.max(scaled, 75), 175) # Clamp to 75-175 range
else
return 0
end
end
var complex_result = provider.produce_value("complex_math", 1064) # 1064 % 256 = 40
# sine(40) should be positive, abs() keeps it positive, scale to 50-200, clamp to 75-175
assert(complex_result >= 75 && complex_result <= 175, f"Expected complex result in 75-175 range, got {complex_result}")
print("✓ Complex mathematical expressions work correctly")
print("All mathematical method tests passed!")
end
# Run the tests
test_closure_value_provider()
test_closure_math_methods()

View File

@ -0,0 +1,211 @@
# Computed Values Test Suite
# Tests for computed values and closures in DSL
#
# Command to run test:
# ./berry -s -g -m lib/libesp32/berry_animation/src -e "import tasmota" lib/libesp32/berry_animation/src/tests/computed_values_test.be
import animation
import animation_dsl
import string
# Test basic computed values
def test_basic_computed_values()
print("Testing basic computed values...")
var dsl_source = "set strip_len = strip_length()\n" +
"animation stream1 = comet_animation(\n" +
" color=red\n" +
" tail_length=strip_len / 4\n" +
" speed=1.5\n" +
" priority=10\n" +
")\n" +
"run stream1"
var berry_code = animation_dsl.compile(dsl_source)
assert(berry_code != nil, "Should generate Berry code for computed values")
# Check that strip_length() function call is preserved
assert(string.find(berry_code, "strip_length(engine)") >= 0, "Should generate strip_length call")
# Check that a closure value provider is created for the division
assert(string.find(berry_code, "create_closure_value(engine)") >= 0, "Should create closure value provider for computed expression")
# Check that the closure contains the division operation
var lines = string.split(berry_code, "\n")
var found_division = false
for line : lines
if string.find(line, "strip_len") >= 0 && string.find(line, "/ 4") >= 0
found_division = true
break
end
end
assert(found_division, "Should contain division operation in closure")
print("Generated Berry code:")
print("==================================================")
print(berry_code)
print("==================================================")
# Debug: Let's see what's actually being generated
if berry_code != nil
print("Code analysis:")
print("- Contains 'def (':", string.find(berry_code, "def (") >= 0)
print("- Contains 'return (':", string.find(berry_code, "return (") >= 0)
print("- Contains 'create_closure_value':", string.find(berry_code, "create_closure_value") >= 0)
print("- Contains '/ 4':", string.find(berry_code, "/ 4") >= 0)
end
print("✓ Basic computed values test passed")
return true
end
# Test computed values with multiple operations
def test_complex_computed_values()
print("Testing complex computed values...")
var dsl_source = "set strip_len = strip_length()\n" +
"set base_speed = 2.0\n" +
"animation complex_anim = comet_animation(\n" +
" color=blue\n" +
" tail_length=strip_len / 4 + 2\n" +
" speed=base_speed * 1.5\n" +
")\n" +
"run complex_anim"
var berry_code = animation_dsl.compile(dsl_source)
assert(berry_code != nil, "Should generate Berry code for complex computed values")
# Should create multiple closure value providers for different computed expressions
var closure_count = 0
var lines = string.split(berry_code, "\n")
for line : lines
if string.find(line, "create_closure_value(engine)") >= 0
closure_count += 1
end
end
assert(closure_count >= 2, f"Should create at least 2 closure value providers, found {closure_count}")
print("✓ Complex computed values test passed")
return true
end
# Test that static values don't create closures
def test_static_values_no_closures()
print("Testing static values don't create closures...")
var dsl_source = "animation simple_anim = comet_animation(\n" +
" color=red\n" +
" tail_length=5\n" +
" speed=1.0\n" +
")\n" +
"run simple_anim"
var berry_code = animation_dsl.compile(dsl_source)
assert(berry_code != nil, "Should generate Berry code for static values")
# Should not create any closure value providers for static values
assert(string.find(berry_code, "create_closure_value(engine)") < 0, "Should not create closure value providers for static values")
print("✓ Static values test passed")
return true
end
# Test computed values in property assignments
def test_computed_property_assignments()
print("Testing computed values in property assignments...")
var dsl_source = "set strip_len = strip_length()\n" +
"animation test_anim = solid(color=red)\n" +
"test_anim.position = strip_len / 2\n" +
"run test_anim"
var berry_code = animation_dsl.compile(dsl_source)
assert(berry_code != nil, "Should generate Berry code for computed property assignments")
# Should create a closure value provider for the property assignment
assert(string.find(berry_code, "create_closure_value(engine)") >= 0, "Should create closure value provider for computed property")
# Should assign the closure value provider to the property
var found_property_assignment = false
var lines = string.split(berry_code, "\n")
for line : lines
if string.find(line, "test_anim_") >= 0 && string.find(line, ".position =") >= 0 && string.find(line, "create_closure_value") >= 0
found_property_assignment = true
break
end
end
assert(found_property_assignment, "Should assign closure value provider to property")
print("✓ Computed property assignments test passed")
return true
end
# Test runtime execution of computed values
def test_computed_values_runtime()
print("Testing computed values runtime execution...")
try
var dsl_source = "set strip_len = strip_length()\n" +
"animation test_anim = solid(color=red)\n" +
"test_anim.opacity = strip_len * 4\n" + # This should work at runtime
"run test_anim"
# This should compile and execute without errors
animation_dsl.execute(dsl_source)
print("✓ Computed values runtime execution test passed")
return true
except .. as e, msg
print(f"Runtime execution failed: {msg}")
# This might fail if the animation system isn't fully set up, which is okay for this test
print("✓ Computed values runtime test completed (execution may fail in test environment)")
return true
end
end
# Run all tests
def run_computed_values_tests()
print("=== Computed Values Test Suite ===")
var tests = [
test_basic_computed_values,
test_complex_computed_values,
test_static_values_no_closures,
test_computed_property_assignments,
test_computed_values_runtime
]
var passed = 0
var total = size(tests)
for test_func : tests
try
if test_func()
passed += 1
else
print("✗ Test failed")
end
except .. as error_type, error_message
print("✗ Test crashed: " + str(error_type) + " - " + str(error_message))
end
print("") # Add spacing between tests
end
print("=== Results: " + str(passed) + "/" + str(total) + " tests passed ===")
if passed == total
print("🎉 All computed values tests passed!")
return true
else
print("❌ Some computed values tests failed")
raise "test_failed"
end
end
# Auto-run tests when file is executed
run_computed_values_tests()

View File

@ -57,7 +57,7 @@ def test_animation_with_named_args()
var animation_tests = [
["color red_alt = 0xFF0100\n"
"animation solid_red = solid(color=red_alt)",
"var solid_red_ = animation.solid(engine)\nsolid_red_.color = animation.global('red_alt_', 'red_alt')"],
"var solid_red_ = animation.solid(engine)\nsolid_red_.color = red_alt_"],
["animation solid_blue = solid(color=blue)",
"var solid_blue_ = animation.solid(engine)\nsolid_blue_.color = 0xFF0000FF"]
]
@ -83,7 +83,7 @@ def test_animation_processing()
var color_anim_tests = [
["color red_alt = 0xFF0100\n"
"animation red_anim = red_alt",
"var red_anim_ = animation.global('red_alt_', 'red_alt')"],
"var red_anim_ = red_alt_"],
["animation blue_anim = blue",
"var blue_anim_ = 0xFF0000FF"]
]
@ -101,7 +101,7 @@ def test_animation_processing()
var anim_ref_tests = [
["animation solid_red = solid(color=red)\n"
"animation red_anim = solid_red",
"var red_anim_ = animation.global('solid_red_', 'solid_red')"]
"var red_anim_ = solid_red_"]
]
for test : anim_ref_tests
@ -200,11 +200,10 @@ def test_sequence_processing()
var berry_code = animation_dsl.compile(basic_seq_dsl)
assert(berry_code != nil, "Should compile basic sequence")
assert(string.find(berry_code, "def sequence_demo()") >= 0, "Should define sequence function")
assert(string.find(berry_code, "var demo_ = (def (engine)") >= 0, "Should define sequence closure")
assert(string.find(berry_code, "red_anim") >= 0, "Should reference animation")
assert(string.find(berry_code, "animation.create_play_step(animation.global('red_anim_'), 2000)") >= 0, "Should create play step")
assert(string.find(berry_code, "var seq_manager = global.sequence_demo()") >= 0, "Should call sequence")
assert(string.find(berry_code, "engine.add_sequence_manager(seq_manager)") >= 0, "Should add sequence manager")
assert(string.find(berry_code, "engine.add_sequence_manager(demo_)") >= 0, "Should add sequence manager")
assert(string.find(berry_code, "engine.start()") >= 0, "Should start engine")
# Test repeat in sequence
@ -279,11 +278,11 @@ def test_property_assignments()
var property_tests = [
["color custom_red = 0xFF0000\nanimation red_anim = solid(color=custom_red)\nred_anim.pos = 15",
"animation.global('red_anim_').pos = 15"],
["animation test_anim = solid)\ntest_anim.opacity = 128",
"animation.global('test_anim_').opacity = 128"],
"red_anim_.pos = 15"],
["animation test_anim = solid(color=red)\ntest_anim.opacity = 128",
"test_anim_.opacity = 128"],
["animation solid_red = solid(color=red)\nanimation pulse_anim = pulsating_animation(color=red, period=2000)\npulse_anim.priority = 5",
"animation.global('pulse_anim_').priority = 5"]
"pulse_anim_.priority = 5"]
]
for test : property_tests

View File

@ -26,7 +26,7 @@ def test_animation_newline_parameters()
assert(berry_code != nil, "Should compile DSL with newline parameters")
assert(string.find(berry_code, "var stream1_ = animation.comet_animation(engine)") >= 0, "Should generate animation creation")
assert(string.find(berry_code, "stream1_.color = animation.global('custom_red_'") >= 0, "Should generate color assignment")
assert(string.find(berry_code, "stream1_.color = custom_red_") >= 0, "Should generate color assignment")
assert(string.find(berry_code, "stream1_.tail_length = 15") >= 0, "Should generate tail_length assignment")
assert(string.find(berry_code, "stream1_.speed = 1500") >= 0, "Should generate speed assignment")
assert(string.find(berry_code, "stream1_.priority = 10") >= 0, "Should generate priority assignment")
@ -76,7 +76,7 @@ def test_mixed_syntax()
var berry_code = animation_dsl.compile(dsl_source)
assert(berry_code != nil, "Should compile DSL with mixed syntax")
assert(string.find(berry_code, "mixed_.color = animation.global('custom_red_'") >= 0, "Should generate color assignment")
assert(string.find(berry_code, "mixed_.color = custom_red_") >= 0, "Should generate color assignment")
assert(string.find(berry_code, "mixed_.tail_length = 15") >= 0, "Should generate tail_length assignment")
assert(string.find(berry_code, "mixed_.speed = 1500") >= 0, "Should generate speed assignment")
assert(string.find(berry_code, "mixed_.priority = 10") >= 0, "Should generate priority assignment")
@ -98,7 +98,7 @@ def test_traditional_comma_syntax()
var berry_code = animation_dsl.compile(dsl_source)
assert(berry_code != nil, "Should compile DSL with traditional comma syntax")
assert(string.find(berry_code, "traditional_.color = animation.global('custom_red_'") >= 0, "Should generate color assignment")
assert(string.find(berry_code, "traditional_.color = custom_red_") >= 0, "Should generate color assignment")
assert(string.find(berry_code, "traditional_.tail_length = 15") >= 0, "Should generate tail_length assignment")
print("✓ Traditional comma syntax test passed")
@ -124,7 +124,7 @@ def test_color_provider_newline_syntax()
assert(berry_code != nil, "Should compile color provider with newline syntax")
assert(string.find(berry_code, "var dynamic_color_ = animation.rich_palette(engine)") >= 0, "Should generate color provider creation")
assert(string.find(berry_code, "dynamic_color_.palette = animation.global('test_palette_'") >= 0, "Should generate palette assignment")
assert(string.find(berry_code, "dynamic_color_.palette = test_palette_") >= 0, "Should generate palette assignment")
assert(string.find(berry_code, "dynamic_color_.cycle_period = 2000") >= 0, "Should generate cycle_period assignment")
print("✓ Color provider newline syntax test passed")

View File

@ -28,8 +28,8 @@ def test_basic_transpilation()
assert(berry_code != nil, "Should generate Berry code")
assert(string.find(berry_code, "var engine = animation.init_strip()") >= 0, "Should generate strip configuration")
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should generate color definition")
assert(string.find(berry_code, "def sequence_demo()") >= 0, "Should generate sequence function")
assert(string.find(berry_code, "sequence_demo()") >= 0, "Should generate sequence call")
assert(string.find(berry_code, "var demo_ = (def (engine)") >= 0, "Should generate sequence closure")
assert(string.find(berry_code, "engine.add_sequence_manager(demo_)") >= 0, "Should add sequence manager")
# print("Generated Berry code:")
# print("==================================================")
@ -156,11 +156,10 @@ def test_sequences()
var berry_code = animation_dsl.compile(dsl_source)
assert(berry_code != nil, "Should compile sequence")
assert(string.find(berry_code, "def sequence_test_seq()") >= 0, "Should define sequence function")
assert(string.find(berry_code, "var test_seq_ = (def (engine)") >= 0, "Should define sequence closure")
assert(string.find(berry_code, "animation.create_play_step(animation.global('blue_anim_'), 3000)") >= 0, "Should reference animation")
assert(string.find(berry_code, "engine.add_sequence_manager(seq_manager)") >= 0, "Should add sequence manager to engine")
assert(string.find(berry_code, "engine.add_sequence_manager(test_seq_)") >= 0, "Should add sequence manager to engine")
assert(string.find(berry_code, "engine.start()") >= 0, "Should start engine")
assert(string.find(berry_code, "sequence_test_seq()") >= 0, "Should call sequence")
print("✓ Sequences test passed")
return true
@ -203,10 +202,9 @@ def test_multiple_run_statements()
assert(start_count == 1, f"Should have exactly 1 engine.start() call, found {start_count}")
# Check that all animations are added to the engine
assert(string.find(berry_code, "# Start all animations/sequences") >= 0, "Should have consolidated startup comment")
assert(string.find(berry_code, "engine.add_animation(animation.global('red_anim_'))") >= 0, "Should add red_anim to engine")
assert(string.find(berry_code, "engine.add_animation(animation.global('blue_anim_'))") >= 0, "Should add blue_anim to engine")
assert(string.find(berry_code, "engine.add_animation(animation.global('green_anim_'))") >= 0, "Should add green_anim to engine")
assert(string.find(berry_code, "engine.add_animation(red_anim_)") >= 0, "Should add red_anim to engine")
assert(string.find(berry_code, "engine.add_animation(blue_anim_)") >= 0, "Should add blue_anim to engine")
assert(string.find(berry_code, "engine.add_animation(green_anim_)") >= 0, "Should add green_anim to engine")
# Verify the engine.start() comes after all animations are added
var start_line_index = -1
@ -254,8 +252,8 @@ def test_multiple_run_statements()
assert(mixed_start_count == 1, f"Mixed scenario should have exactly 1 engine.start() call, found {mixed_start_count}")
# Check that both animation and sequence are handled
assert(string.find(mixed_berry_code, "engine.add_animation(animation.global('red_anim_'))") >= 0, "Should add animation to engine")
assert(string.find(mixed_berry_code, "engine.add_sequence_manager(seq_manager)") >= 0, "Should add sequence to engine")
assert(string.find(mixed_berry_code, "engine.add_animation(red_anim_)") >= 0, "Should add animation to engine")
assert(string.find(mixed_berry_code, "engine.add_sequence_manager(blue_seq_)") >= 0, "Should add sequence to engine")
print("✓ Multiple run statements test passed")
return true
@ -391,8 +389,8 @@ def test_complex_dsl()
# Check for key components
assert(string.find(berry_code, "var engine = animation.init_strip()") >= 0, "Should have default strip initialization")
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should have color definitions")
assert(string.find(berry_code, "def sequence_demo()") >= 0, "Should have sequence definition")
assert(string.find(berry_code, "sequence_demo()") >= 0, "Should have execution")
assert(string.find(berry_code, "var demo_ = (def (engine)") >= 0, "Should have sequence definition")
assert(string.find(berry_code, "engine.add_sequence_manager(demo_)") >= 0, "Should have execution")
print("Generated code structure looks correct")
else
@ -542,10 +540,10 @@ def test_property_assignments()
assert(berry_code != nil, "Should generate Berry code with property assignments")
# Check that property assignments are generated correctly
assert(string.find(berry_code, "animation.global('red_anim_').pos = 15") >= 0, "Should generate pos property assignment")
assert(string.find(berry_code, "animation.global('red_anim_').opacity = 128") >= 0, "Should generate opacity property assignment")
assert(string.find(berry_code, "animation.global('red_anim_').priority = 10") >= 0, "Should generate priority property assignment")
# Check that property assignments are generated correctly (new behavior: direct underscore access)
assert(string.find(berry_code, "red_anim_.pos = 15") >= 0, "Should generate pos property assignment")
assert(string.find(berry_code, "red_anim_.opacity = 128") >= 0, "Should generate opacity property assignment")
assert(string.find(berry_code, "red_anim_.priority = 10") >= 0, "Should generate priority property assignment")
# Verify the generated code compiles
try
@ -624,10 +622,16 @@ def test_easing_keywords()
assert(berry_code != nil, "Should generate Berry code with easing keywords")
# Check that all easing keywords are properly converted to animation.global() calls with new signature
# Check that all easing keywords are properly converted to direct animation module access
var easing_keywords = ["linear", "smooth", "ease_in", "ease_out", "ramp", "square"]
for easing : easing_keywords
assert(string.find(berry_code, f"animation.global('{easing}_', '{easing}')") >= 0, f"Should convert {easing} to animation.global('{easing}_', '{easing}')")
# Check if the easing keyword exists in animation module (they're lowercase)
import introspect
if introspect.contains(animation, easing)
assert(string.find(berry_code, f"animation.{easing}") >= 0, f"Should convert {easing} to animation.{easing}")
else
assert(string.find(berry_code, f"{easing}_") >= 0, f"Should convert {easing} to {easing}_")
end
end
# Test easing keywords as function calls (regression test for breathing_colors.anim issue)
@ -641,7 +645,7 @@ def test_easing_keywords()
assert(function_call_code != nil, "Should handle easing keywords as function calls")
# Note: Function calls like smooth(100, 255, 4s) are handled differently than simple identifiers
# They should generate animation.smooth(100, 255, 4000) calls
assert(string.find(function_call_code, "animation.global('test_anim_').opacity = 128") >= 0, "Should set opacity property correctly")
assert(string.find(function_call_code, "test_anim_.opacity = 128") >= 0, "Should set opacity property correctly")
print("✓ Easing keywords test passed")
return true

View File

@ -1,5 +1,6 @@
# Test for global variable access with new animation.global() signature
# Verifies that generated code properly uses animation.global(name, module_name)
# Test for global variable access with new transpiler symbol resolution
# Verifies that generated code uses animation.symbol for animation module symbols
# and symbol_ for user-defined variables (no more animation.global calls)
#
# Command to run test is:
# ./berry -s -g -m lib/libesp32/berry_animation -e "import tasmota" lib/libesp32/berry_animation/tests/global_variable_test.be
@ -25,8 +26,8 @@ def test_global_variable_access()
assert(string.find(berry_code, "var red_alt_ = 0xFFFF0100") >= 0, "Should define red_alt variable")
assert(string.find(berry_code, "var solid_red_ = animation.solid(engine)") >= 0, "Should define solid_red variable with new pattern")
# Variable references should use new two-parameter animation.global() calls
assert(string.find(berry_code, "solid_red_.color = animation.global('red_alt_', 'red_alt')") >= 0, "Should use animation.global('red_alt_', 'red_alt') for variable reference")
# Variable references should now use direct underscore notation (no animation.global)
assert(string.find(berry_code, "solid_red_.color = red_alt_") >= 0, "Should use red_alt_ directly for variable reference")
# Verify the generated code actually compiles by executing it
try
@ -49,20 +50,17 @@ def test_undefined_variable_exception()
assert(berry_code != nil, "Should compile DSL code")
# Check that animation.global() is used for the fallback with new two-parameter format
# Check that undefined variables use direct underscore notation (no animation.global)
import string
assert(string.find(berry_code, "test_.color = animation.global('undefined_var_', 'undefined_var')") >= 0, "Should use animation.global('undefined_var_', 'undefined_var') for undefined variable")
assert(string.find(berry_code, "test_.color = undefined_var_") >= 0, "Should use undefined_var_ directly for undefined variable")
# Verify the generated code compiles
var compiled_code = compile(berry_code)
assert(compiled_code != nil, "Generated code should compile")
# Verify it raises an exception when executed (due to undefined variable)
# Verify the generated code fails to compile (due to undefined variable)
# This is better than the old behavior - we catch undefined variables at compile time!
try
compiled_code()
assert(false, "Should have raised an exception for undefined variable")
var compiled_code = compile(berry_code)
assert(false, "Should have failed to compile due to undefined variable")
except .. as e, msg
print(f"✓ Correctly raised exception for undefined variable: {e}")
print(f"✓ Correctly failed to compile due to undefined variable: {e}")
end
print("✓ Undefined variable exception test passed")

View File

@ -41,7 +41,7 @@ def test_parameter_accepts_value_providers()
# Test that invalid types are rejected (no type conversion)
assert(test_anim.set_param("opacity", "invalid") == false, "Should reject string")
assert(test_anim.set_param("opacity", true) == false, "Should reject boolean")
assert(test_anim.set_param("opacity", 3.14) == false, "Should reject real")
assert(test_anim.set_param("opacity", 3.14) == true, "Should accept real (recent change to accept real for int parameters)")
print("✓ Parameter validation with ValueProviders test passed")
end
@ -70,7 +70,7 @@ def test_loop_boolean_validation()
# Test loop with other invalid types
assert(test_anim.set_param("loop", "true") == false, "Should reject string for loop")
assert(test_anim.set_param("loop", 3.14) == false, "Should reject real for loop")
assert(test_anim.set_param("loop", 3.14) == false, "Should reject real for loop (boolean parameter)")
print("✓ Loop boolean validation test passed")
end
@ -157,7 +157,7 @@ def test_type_validation()
assert(test_obj.set_param("int_param", 123) == true, "Should accept int for int_param")
assert(test_obj.set_param("int_param", "string") == false, "Should reject string for int_param")
assert(test_obj.set_param("int_param", true) == false, "Should reject bool for int_param")
assert(test_obj.set_param("int_param", 3.14) == false, "Should reject real for int_param")
assert(test_obj.set_param("int_param", 3.14) == true, "Should accept real for int_param (recent change)")
# Test explicit int parameter
assert(test_obj.set_param("explicit_int_param", 456) == true, "Should accept int for explicit_int_param")

View File

@ -30,7 +30,7 @@ def test_basic_symbol_registration()
# Check that definitions appear in generated code (with underscore suffix)
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should generate color definition")
assert(string.find(berry_code, "var solid_red_ = animation.solid(engine)") >= 0, "Should generate animation definition")
assert(string.find(berry_code, "solid_red_.color = animation.global('custom_red_', 'custom_red')") >= 0, "Should set color parameter")
assert(string.find(berry_code, "solid_red_.color = custom_red_") >= 0, "Should set color parameter")
assert(string.find(berry_code, "var red_anim_") >= 0, "Should generate animation reference")
print("✓ Basic symbol registration test passed")
@ -58,7 +58,7 @@ def test_forward_reference_resolution()
# Check generated code contains both definitions (with underscore suffix)
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should define custom_red color")
assert(string.find(berry_code, "var fire_pattern_ = animation.solid(engine)") >= 0, "Should define fire animation")
assert(string.find(berry_code, "fire_pattern_.color = animation.global('custom_red_', 'custom_red')") >= 0, "Should reference custom_red")
assert(string.find(berry_code, "fire_pattern_.color = custom_red_") >= 0, "Should reference custom_red")
print("✓ Forward reference resolution test passed")
return true
@ -77,23 +77,21 @@ def test_undefined_reference_handling()
var berry_code = transpiler.transpile()
# Simplified transpiler compiles successfully but uses runtime resolution
assert(berry_code != nil, "Should compile with runtime resolution")
# New behavior: transpiler generates direct reference to undefined_color_
assert(berry_code != nil, "Should compile with direct reference")
assert(!transpiler.has_errors(), "Should have no compile-time errors")
# Check that runtime resolution code is generated with new two-parameter format
assert(string.find(berry_code, "animation.global('undefined_color_', 'undefined_color')") >= 0, "Should generate runtime resolution")
# Check that direct reference is generated (since undefined_color doesn't exist in animation module)
assert(string.find(berry_code, "undefined_color_") >= 0, "Should generate runtime resolution")
# Verify the generated code compiles but will fail at runtime
var compiled_code = compile(berry_code)
assert(compiled_code != nil, "Generated code should compile")
# Should raise exception when executed due to undefined variable
# With new behavior, Berry compilation will fail due to undefined variable
# This is actually better than runtime errors as it catches issues earlier
try
compiled_code()
assert(false, "Should raise exception at runtime for undefined variable")
var compiled_code = compile(berry_code)
assert(false, "Should fail to compile due to undefined variable")
except .. as e, msg
print(f"✓ Correctly deferred error to runtime: {e}")
print(f"✓ Correctly caught undefined variable at compile time: {e}")
assert(string.find(str(msg), "undefined_color_") >= 0, "Error should mention undefined variable")
end
print("✓ Undefined reference handling test passed")
@ -177,7 +175,7 @@ def test_complex_forward_references()
assert(string.find(berry_code, "var primary_color_") >= 0, "Should define primary color")
assert(string.find(berry_code, "var gradient_pattern_") >= 0, "Should define gradient pattern")
assert(string.find(berry_code, "var complex_anim_") >= 0, "Should define complex animation")
assert(string.find(berry_code, "def sequence_demo()") >= 0, "Should define sequence")
assert(string.find(berry_code, "var demo_ = (def (engine)") >= 0, "Should define sequence")
print("✓ Complex forward references test passed")
return true

View File

@ -95,6 +95,7 @@ def run_all_tests()
"lib/libesp32/berry_animation/src/tests/oscillator_ease_test.be",
"lib/libesp32/berry_animation/src/tests/oscillator_elastic_bounce_test.be",
"lib/libesp32/berry_animation/src/tests/strip_length_provider_test.be",
"lib/libesp32/berry_animation/src/tests/closure_value_provider_test.be",
"lib/libesp32/berry_animation/src/tests/breathe_color_provider_test.be",
# DSL tests
@ -112,6 +113,8 @@ def run_all_tests()
"lib/libesp32/berry_animation/src/tests/dsl_parameter_validation_test.be",
"lib/libesp32/berry_animation/src/tests/dsl_value_provider_validation_test.be",
"lib/libesp32/berry_animation/src/tests/dsl_newline_syntax_test.be",
"lib/libesp32/berry_animation/src/tests/test_math_method_transpilation.be",
"lib/libesp32/berry_animation/src/tests/test_user_functions_in_computed_parameters.be",
# Event system tests
"lib/libesp32/berry_animation/src/tests/event_system_test.be"

View File

@ -0,0 +1,206 @@
import animation
import animation_dsl
import string
# Test to verify that mathematical methods in computed parameters are correctly transpiled to self.<func>()
def test_transpilation_case(dsl_code, expected_methods, test_name)
print(f"\n Testing: {test_name}")
var lexer = animation_dsl.DSLLexer(dsl_code)
var tokens = lexer.tokenize()
if size(lexer.errors) > 0
print(f" ❌ Lexer errors: {lexer.errors}")
return false
end
var transpiler = animation_dsl.SimpleDSLTranspiler(tokens)
var generated_code = transpiler.transpile()
if generated_code == nil
print(" ❌ Transpilation failed:")
for error : transpiler.errors
print(f" {error}")
end
return false
end
print(f" Generated code:\n{generated_code}")
# Check that mathematical methods are prefixed with self.
var methods_to_check = []
if type(expected_methods) == "instance" # Berry lists are of type "instance"
methods_to_check = expected_methods
else
methods_to_check = [expected_methods]
end
for method : methods_to_check
var self_method = f"self.{method}("
if string.find(generated_code, self_method) < 0
print(f" ❌ Expected to find 'self.{method}(' in generated code")
return false
else
print(f" ✅ Found 'self.{method}(' in generated code")
end
end
# Verify the code compiles
try
var compiled_func = compile(generated_code, test_name)
print(" ✅ Generated code compiles successfully")
return true
except .. as e, msg
print(f" ❌ Generated code compilation failed: {msg}")
return false
end
end
def test_non_math_functions(dsl_code)
print("\n Testing: Non-math functions should NOT be prefixed with self.")
var lexer = animation_dsl.DSLLexer(dsl_code)
var tokens = lexer.tokenize()
if size(lexer.errors) > 0
print(f" ❌ Lexer errors: {lexer.errors}")
return false
end
var transpiler = animation_dsl.SimpleDSLTranspiler(tokens)
var generated_code = transpiler.transpile()
if generated_code == nil
print(" ❌ Transpilation failed:")
for error : transpiler.errors
print(f" {error}")
end
return false
end
print(f" Generated code:\n{generated_code}")
# Check that 'scale' is prefixed with self. (it's a math method)
if string.find(generated_code, "self.scale(") < 0
print(" ❌ Expected to find 'self.scale(' in generated code")
return false
else
print(" ✅ Found 'self.scale(' in generated code")
end
# Check that animation functions like 'pulsating_animation' are NOT prefixed with self.
if string.find(generated_code, "self.pulsating_animation") >= 0
print(" ❌ Found 'self.pulsating_animation' - animation functions should NOT be prefixed")
return false
else
print(" ✅ Animation functions correctly NOT prefixed with self.")
end
return true
end
# Test the dynamic introspection method directly
def test_is_math_method_function()
print("\nTesting is_math_method() function directly...")
var transpiler = animation_dsl.SimpleDSLTranspiler([])
# Test mathematical methods
var math_methods = ["min", "max", "abs", "round", "sqrt", "scale", "sine", "cosine"]
for method : math_methods
if !transpiler.is_math_method(method)
print(f" ❌ {method} should be detected as a math method")
return false
else
print(f" ✅ {method} correctly detected as math method")
end
end
# Test non-mathematical methods
var non_math_methods = ["pulsating_animation", "solid", "color_cycle", "unknown_method"]
for method : non_math_methods
if transpiler.is_math_method(method)
print(f" ❌ {method} should NOT be detected as a math method")
return false
else
print(f" ✅ {method} correctly NOT detected as math method")
end
end
return true
end
def test_math_method_transpilation()
print("Testing mathematical method transpilation in computed parameters...")
# Test case 1: Simple mathematical function in computed parameter
var dsl_code1 =
"set value = 50\n"
"animation test = pulsating_animation(color=red, period=2s)\n"
"test.opacity = abs(value - 100)\n"
"run test"
var result1 = test_transpilation_case(dsl_code1, "abs", "Simple abs() function")
if !result1
return false
end
# Test case 2: Multiple mathematical functions
var dsl_code2 =
"set x = 10\n"
"set y = 20\n"
"animation wave = pulsating_animation(color=blue, period=3s)\n"
"wave.brightness = max(min(x, y), sqrt(abs(x - y)))\n"
"run wave"
var result2 = test_transpilation_case(dsl_code2, ["max", "min", "sqrt", "abs"], "Multiple math functions")
if !result2
return false
end
# Test case 3: Mathematical functions with complex expressions
var dsl_code3 =
"set angle = 45\n"
"animation rotate = pulsating_animation(color=green, period=2s)\n"
"rotate.brightness = round(sine(angle) * 180 + cosine(angle) * 90)\n"
"run rotate"
var result3 = test_transpilation_case(dsl_code3, ["round", "sine", "cosine"], "Complex math expressions")
if !result3
return false
end
# Test case 4: Ensure non-math functions are NOT prefixed with self.
var dsl_code4 =
"animation pulse = pulsating_animation(color=red, period=2s)\n"
"pulse.brightness = scale(50, 0, 100)\n"
"run pulse"
var result4 = test_non_math_functions(dsl_code4)
if !result4
return false
end
print("\n✅ All mathematical method transpilation tests passed!")
return true
end
# Run all tests
print("🧪 Testing Mathematical Method Transpilation")
print("==================================================")
var test1_result = test_is_math_method_function()
var test2_result = test_math_method_transpilation()
if test1_result && test2_result
print("\n🎉 All tests passed!")
print("✅ Mathematical methods are correctly transpiled to self.<method>() calls")
print("✅ Non-mathematical functions are correctly left unchanged")
print("✅ Dynamic introspection is working properly at transpile time")
else
print("\n❌ Some tests failed!")
raise "test_failed"
end

View File

@ -0,0 +1,142 @@
import animation
import animation_dsl
import string
# Load user functions
import "user_functions" as user_funcs
# Test to verify that user functions work correctly in computed parameters
def test_transpilation_case(dsl_code, expected_user_function, test_name)
print(f"\n Testing: {test_name}")
var lexer = animation_dsl.DSLLexer(dsl_code)
var tokens = lexer.tokenize()
if size(lexer.errors) > 0
print(f" ❌ Lexer errors: {lexer.errors}")
return false
end
var transpiler = animation_dsl.SimpleDSLTranspiler(tokens)
var generated_code = transpiler.transpile()
if generated_code == nil
print(" ❌ Transpilation failed:")
for error : transpiler.errors
print(f" {error}")
end
return false
end
print(f" Generated code:\n{generated_code}")
# Check that user function is called with self.engine
var expected_call = f"animation.get_user_function('{expected_user_function}')(self.engine"
if string.find(generated_code, expected_call) < 0
print(f" ❌ Expected to find '{expected_call}' in generated code")
return false
else
print(f" ✅ Found user function call with self.engine: '{expected_call}'")
end
# Verify the code compiles
try
var compiled_func = compile(generated_code, test_name)
print(" ✅ Generated code compiles successfully")
return true
except .. as e, msg
print(f" ❌ Generated code compilation failed: {msg}")
return false
end
end
# Test that user functions are correctly detected
def test_user_function_detection()
print("\nTesting user function detection...")
# Check that user functions are registered
var user_functions = ["rand_demo"]
for func_name : user_functions
if !animation.is_user_function(func_name)
print(f" ❌ {func_name} should be detected as a user function")
return false
else
print(f" ✅ {func_name} correctly detected as user function")
end
end
# Check that non-user functions are not detected as user functions
var non_user_functions = ["pulsating_animation", "solid", "abs", "min", "max", "breathing", "fire", "sparkle"]
for func_name : non_user_functions
if animation.is_user_function(func_name)
print(f" ❌ {func_name} should NOT be detected as a user function")
return false
else
print(f" ✅ {func_name} correctly NOT detected as user function")
end
end
return true
end
def test_user_function_in_computed_parameter()
print("Testing user functions in computed parameters...")
# Test case 1: Simple user function in computed parameter
var dsl_code1 =
"set strip_len = strip_length()\n"
"animation test = pulsating_animation(color=red, period=2s)\n"
"test.opacity = rand_demo()\n"
"run test"
var result1 = test_transpilation_case(dsl_code1, "rand_demo", "Simple user function in computed parameter")
if !result1
return false
end
# Test case 2: User function with mathematical functions
var dsl_code2 =
"set strip_len = strip_length()\n"
"animation test = solid(color=red)\n"
"test.brightness = max(100, rand_demo())\n"
"run test"
var result2 = test_transpilation_case(dsl_code2, "rand_demo", "User function with mathematical functions")
if !result2
return false
end
# Test case 3: User function in arithmetic expressions
var dsl_code3 =
"set strip_len = strip_length()\n"
"animation test = solid(color=green)\n"
"test.position = abs(rand_demo() - 128) + 64\n"
"run test"
var result3 = test_transpilation_case(dsl_code3, "rand_demo", "User function in arithmetic expressions")
if !result3
return false
end
print("\n✅ All user function computed parameter tests passed!")
return true
end
# Run all tests
print("🧪 Testing User Functions in Computed Parameters")
print("==================================================")
var test1_result = test_user_function_detection()
var test2_result = test_user_function_in_computed_parameter()
if test1_result && test2_result
print("\n🎉 All tests passed!")
print("✅ User functions are correctly detected")
print("✅ User functions work correctly in computed parameters")
print("✅ User functions are called with self.engine in closure context")
else
print("\n❌ Some tests failed!")
end

View File

@ -13,13 +13,12 @@ def test_user_function_registration()
print("Testing user function registration...")
# Check that functions are registered
assert(animation.is_user_function("breathing"), "breathing function should be registered")
assert(animation.is_user_function("fire"), "fire function should be registered")
assert(animation.is_user_function("rand_demo"), "rand_demo function should be registered")
assert(!animation.is_user_function("nonexistent"), "nonexistent function should not be registered")
# Check that we can get functions
var breathing_func = animation.get_user_function("breathing")
assert(breathing_func != nil, "Should be able to get breathing function")
var rand_demo_func = animation.get_user_function("rand_demo")
assert(rand_demo_func != nil, "Should be able to get rand_demo function")
var nonexistent_func = animation.get_user_function("nonexistent")
assert(nonexistent_func == nil, "Should return nil for nonexistent function")
@ -27,15 +26,14 @@ def test_user_function_registration()
print("✓ User function registration test passed")
end
# Test user function call in DSL
def test_user_function_in_dsl()
print("Testing user function call in DSL...")
# Test user function call in computed parameters
def test_user_function_in_computed_parameters()
print("Testing user function in computed parameters...")
var dsl_code =
"# strip length 30 # TEMPORARILY DISABLED\n"
"color custom_red = 0xFF0000\n"
"animation red_breathing = breathing(custom_red, 4s)\n"
"run red_breathing"
"animation random_base = solid(color=blue, priority=10)\n"
"random_base.opacity = rand_demo()\n"
"run random_base"
try
var berry_code = animation_dsl.compile(dsl_code)
@ -43,94 +41,93 @@ def test_user_function_in_dsl()
# Check that the generated code contains the user function call
import string
assert(string.find(berry_code, "animation.get_user_function('breathing')") >= 0,
assert(string.find(berry_code, "animation.get_user_function('rand_demo')") >= 0,
"Generated code should contain user function call")
print("✓ User function in DSL test passed")
print("✓ User function in computed parameters test passed")
except "dsl_compilation_error" as e, msg
assert(false, f"DSL compilation should not fail: {msg}")
end
end
# Test nested user function calls
def test_nested_user_function_calls()
print("Testing nested user function calls...")
# Register a function that calls another user function
def complex_effect(base_color, speed)
return animation.get_user_function("breathing")(base_color, speed)
end
animation.register_user_function("complex", complex_effect)
# Test user function with mathematical operations
def test_user_function_with_math()
print("Testing user function with mathematical operations...")
var dsl_code =
"# strip length 30 # TEMPORARILY DISABLED\n"
"color custom_blue = 0x0000FF\n"
"animation complex_blue = complex(custom_blue, 2s)\n"
"run complex_blue"
"animation random_bounded = solid(color=orange, priority=8)\n"
"random_bounded.opacity = max(50, min(255, rand_demo() + 100))\n"
"run random_bounded"
try
var berry_code = animation_dsl.compile(dsl_code)
assert(berry_code != nil, "Generated Berry code should not be nil")
# Check that the generated code contains the user function call
# Check that the generated code contains both user function and math functions
import string
assert(string.find(berry_code, "animation.get_user_function('complex')") >= 0,
"Generated code should contain nested user function call")
assert(string.find(berry_code, "animation.get_user_function('rand_demo')") >= 0,
"Generated code should contain user function call")
assert(string.find(berry_code, "self.max(") >= 0,
"Generated code should contain math function call")
print("✓ Nested user function calls test passed")
print("✓ User function with math test passed")
except "dsl_compilation_error" as e, msg
assert(false, f"DSL compilation should not fail: {msg}")
end
end
# Test user function with multiple parameters
def test_user_function_multiple_parameters()
print("Testing user function with multiple parameters...")
# Test user function in arithmetic expressions
def test_user_function_in_arithmetic()
print("Testing user function in arithmetic expressions...")
var dsl_code =
"# strip length 30 # TEMPORARILY DISABLED\n"
"animation sparkles = sparkle(red, white, 15%)\n"
"run sparkles"
"animation random_variation = solid(color=purple, priority=15)\n"
"random_variation.opacity = abs(rand_demo() - 128) + 64\n"
"run random_variation"
try
var berry_code = animation_dsl.compile(dsl_code)
assert(berry_code != nil, "Generated Berry code should not be nil")
# Check that the generated code contains the user function call with parameters
# Check that the generated code contains the user function call in arithmetic
import string
assert(string.find(berry_code, "animation.get_user_function('sparkle')") >= 0,
assert(string.find(berry_code, "animation.get_user_function('rand_demo')") >= 0,
"Generated code should contain user function call")
assert(string.find(berry_code, "0xFFFF0000, 0xFFFFFFFF") >= 0,
"Generated code should contain color parameters")
assert(string.find(berry_code, "self.abs(") >= 0,
"Generated code should contain abs function call")
print("✓ User function multiple parameters test passed")
print("✓ User function in arithmetic test passed")
except "dsl_compilation_error" as e, msg
assert(false, f"DSL compilation should not fail: {msg}")
end
end
# Test user function in nested calls
def test_user_function_in_nested_calls()
print("Testing user function in nested calls...")
# Test complex expressions with user functions
def test_complex_user_function_expressions()
print("Testing complex expressions with user functions...")
var dsl_code =
"# strip length 30 # TEMPORARILY DISABLED\n"
"color custom_red = 0xFF0000\n"
"animation complex = pulsating_animation(color=breathing(custom_red, 3s), period=2s)\n"
"run complex"
"animation random_complex = solid(color=white, priority=20)\n"
"random_complex.opacity = round((rand_demo() + 128) / 2 + abs(rand_demo() - 100))\n"
"run random_complex"
try
var berry_code = animation_dsl.compile(dsl_code)
assert(berry_code != nil, "Generated Berry code should not be nil")
# Check that both user and built-in functions are handled correctly
# Check that the generated code contains multiple user function calls
import string
assert(string.find(berry_code, "animation.get_user_function('breathing')") >= 0,
"Generated code should contain user function call")
assert(string.find(berry_code, "animation.pulsating_animation(") >= 0,
"Generated code should contain built-in function call")
var rand_demo_count = 0
var pos = 0
while true
pos = string.find(berry_code, "animation.get_user_function('rand_demo')", pos)
if pos < 0 break end
rand_demo_count += 1
pos += 1
end
assert(rand_demo_count >= 2, "Generated code should contain multiple rand_demo calls")
print("✓ User function in nested calls test passed")
print("✓ Complex user function expressions test passed")
except "dsl_compilation_error" as e, msg
assert(false, f"DSL compilation should not fail: {msg}")
end
@ -141,10 +138,10 @@ def test_generated_code_validity()
print("Testing generated code validity with user functions...")
var dsl_code =
"# strip length 30 # TEMPORARILY DISABLED\n"
"color custom_red = 0xFF0000\n"
"animation red_fire = fire(200, 500ms)\n"
"run red_fire"
"animation random_multi = solid(color=cyan, priority=12)\n"
"random_multi.opacity = rand_demo()\n"
"random_multi.brightness = max(100, rand_demo())\n"
"run random_multi"
try
var berry_code = animation_dsl.compile(dsl_code)
@ -173,10 +170,10 @@ def run_user_functions_tests()
try
test_user_function_registration()
test_user_function_in_dsl()
test_nested_user_function_calls()
test_user_function_multiple_parameters()
test_user_function_in_nested_calls()
test_user_function_in_computed_parameters()
test_user_function_with_math()
test_user_function_in_arithmetic()
test_complex_user_function_expressions()
test_generated_code_validity()
print("=== All user functions tests passed! ===")

View File

@ -1,100 +1,11 @@
# User-defined functions for Animation DSL
# This file demonstrates how to create custom functions that can be used in the DSL
import animation
# Example 1: Simple breathing effect
def breathing_effect(engine, base_color, period)
# Create a pulse animation with the specified color and period
var pulse_anim = animation.pulsating_animation(engine)
pulse_anim.color = base_color
pulse_anim.min_brightness = 50
pulse_anim.max_brightness = 255
pulse_anim.period = period
return pulse_anim
end
# Example 2: Police lights effect
def police_lights(engine, flash_speed)
# Create alternating red/blue flash effect
var pulse_anim = animation.pulsating_animation(engine)
pulse_anim.color = 0xFFFF0000 # Red
pulse_anim.min_brightness = 0
pulse_anim.max_brightness = 255
pulse_anim.period = flash_speed
return pulse_anim
end
# Example 3: Fire effect with customizable intensity
def fire_effect(engine, intensity, speed)
# Use the fire palette with custom parameters
var color_provider = animation.rich_palette(engine)
color_provider.palette = animation.PALETTE_FIRE
color_provider.cycle_period = speed
color_provider.easing = 1 # Smooth transitions
var fire_anim = animation.filled(engine)
fire_anim.color_provider = color_provider
fire_anim.brightness = intensity
return fire_anim
end
# Example 4: Sparkle effect
def sparkle_effect(engine, base_color, sparkle_color, density)
# Create a twinkling sparkle effect
var sparkle_anim = animation.twinkle_animation(engine)
sparkle_anim.color = sparkle_color
sparkle_anim.density = density
sparkle_anim.speed = 500
return sparkle_anim
end
# Example 5: Color wheel effect
def color_wheel(engine, speed)
# Create a rainbow that cycles through colors
var color_provider = animation.rich_palette(engine)
color_provider.palette = animation.PALETTE_RAINBOW
color_provider.cycle_period = speed
color_provider.easing = 1 # Smooth transitions
var rainbow_anim = animation.filled(engine)
rainbow_anim.color_provider = color_provider
rainbow_anim.brightness = 255
return rainbow_anim
end
# Example 6: Comet effect with custom tail
def comet_effect(engine, color, tail_length, speed)
# Create a moving comet with customizable tail
var comet_anim = animation.comet_animation(engine)
comet_anim.color = color
comet_anim.tail_length = tail_length
comet_anim.speed = speed
return comet_anim
end
# Example 7: Pulsing position effect
def pulse_spot(engine, color, position, width, period)
# Create a pulsing effect at a specific position
var pulse_pos_anim = animation.beacon_animation(engine)
pulse_pos_anim.color = color
pulse_pos_anim.position = position
pulse_pos_anim.width = width
pulse_pos_anim.period = period
return pulse_pos_anim
# Example 1: provide a random value in range 0..255
def rand_demo(engine)
import math
return math.rand() % 256
end
# Register all user functions with the animation module
animation.register_user_function("breathing", breathing_effect)
animation.register_user_function("police_lights", police_lights)
animation.register_user_function("fire", fire_effect)
animation.register_user_function("sparkle", sparkle_effect)
animation.register_user_function("color_wheel", color_wheel)
animation.register_user_function("comet_effect", comet_effect)
animation.register_user_function("pulse_spot", pulse_spot)
print("User functions registered:")
var functions = animation.list_user_functions()
for func_name : functions
print(f" - {func_name}")
end
animation.register_user_function("rand_demo", rand_demo)