Berry animation fix timings (#23869)
This commit is contained in:
parent
9917555c6f
commit
a52fdd0526
@ -37,7 +37,7 @@ aurora_base_.brightness = 180 # brightness (dimmed for aurora effect)
|
|||||||
var demo_ = animation.SequenceManager(engine)
|
var demo_ = animation.SequenceManager(engine)
|
||||||
.push_play_step(aurora_base_, nil) # infinite duration (no 'for' clause)
|
.push_play_step(aurora_base_, nil) # infinite duration (no 'for' clause)
|
||||||
engine.add(demo_)
|
engine.add(demo_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -47,7 +47,7 @@ breathing_.opacity = (def (engine)
|
|||||||
end)(engine)
|
end)(engine)
|
||||||
# Start the animation
|
# Start the animation
|
||||||
engine.add(breathing_)
|
engine.add(breathing_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -150,7 +150,7 @@ engine.add(stripe7_)
|
|||||||
engine.add(stripe8_)
|
engine.add(stripe8_)
|
||||||
engine.add(stripe9_)
|
engine.add(stripe9_)
|
||||||
engine.add(stripe10_)
|
engine.add(stripe10_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -72,7 +72,7 @@ engine.add(ornaments_)
|
|||||||
engine.add(tree_star_)
|
engine.add(tree_star_)
|
||||||
engine.add(snow_sparkles_)
|
engine.add(snow_sparkles_)
|
||||||
engine.add(garland_)
|
engine.add(garland_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -40,7 +40,7 @@ engine.add(background_)
|
|||||||
engine.add(comet_main_)
|
engine.add(comet_main_)
|
||||||
engine.add(comet_secondary_)
|
engine.add(comet_secondary_)
|
||||||
engine.add(comet_sparkles_)
|
engine.add(comet_sparkles_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -16,24 +16,24 @@ var strip_len_ = animation.strip_length(engine)
|
|||||||
# Create animation with computed values
|
# Create animation with computed values
|
||||||
var stream1_ = animation.comet_animation(engine)
|
var stream1_ = animation.comet_animation(engine)
|
||||||
stream1_.color = 0xFFFF0000
|
stream1_.color = 0xFFFF0000
|
||||||
stream1_.tail_length = animation.create_closure_value(engine, def (self) return self.abs(self.resolve(strip_len_) / 4) end) # computed value
|
stream1_.tail_length = animation.create_closure_value(engine, def (engine) return animation._math.abs(animation.resolve(strip_len_) / 4) end) # computed value
|
||||||
stream1_.speed = 1.5
|
stream1_.speed = 1.5
|
||||||
stream1_.priority = 10
|
stream1_.priority = 10
|
||||||
# More complex computed values
|
# More complex computed values
|
||||||
var base_speed_ = 2.0
|
var base_speed_ = 2.0
|
||||||
var stream2_ = animation.comet_animation(engine)
|
var stream2_ = animation.comet_animation(engine)
|
||||||
stream2_.color = 0xFF0000FF
|
stream2_.color = 0xFF0000FF
|
||||||
stream2_.tail_length = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) / 8 + (2 * self.resolve(strip_len_)) - 10 end) # computed with addition
|
stream2_.tail_length = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) / 8 + (2 * animation.resolve(strip_len_)) - 10 end) # computed with addition
|
||||||
stream2_.speed = animation.create_closure_value(engine, def (self) return self.resolve(base_speed_) * 1.5 end) # computed with multiplication
|
stream2_.speed = animation.create_closure_value(engine, def (engine) return animation.resolve(base_speed_) * 1.5 end) # computed with multiplication
|
||||||
stream2_.direction = (-1)
|
stream2_.direction = (-1)
|
||||||
stream2_.priority = 5
|
stream2_.priority = 5
|
||||||
# Property assignment with computed values
|
# Property assignment with computed values
|
||||||
stream1_.tail_length = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) / 5 end)
|
stream1_.tail_length = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) / 5 end)
|
||||||
stream2_.opacity = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) * 4 end)
|
stream2_.opacity = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) * 4 end)
|
||||||
# Run both animations
|
# Run both animations
|
||||||
engine.add(stream1_)
|
engine.add(stream1_)
|
||||||
engine.add(stream2_)
|
engine.add(stream2_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -20,7 +20,7 @@ def cylon_effect_template(engine, eye_color_, back_color_, duration_)
|
|||||||
eye_animation_.pos = (def (engine)
|
eye_animation_.pos = (def (engine)
|
||||||
var provider = animation.cosine_osc(engine)
|
var provider = animation.cosine_osc(engine)
|
||||||
provider.min_value = (-1)
|
provider.min_value = (-1)
|
||||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
provider.max_value = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - 2 end)
|
||||||
provider.duration = duration_
|
provider.duration = duration_
|
||||||
return provider
|
return provider
|
||||||
end)(engine)
|
end)(engine)
|
||||||
@ -33,7 +33,7 @@ end
|
|||||||
animation.register_user_function('cylon_effect', cylon_effect_template)
|
animation.register_user_function('cylon_effect', cylon_effect_template)
|
||||||
|
|
||||||
cylon_effect_template(engine, 0xFFFF0000, 0x00000000, 3000)
|
cylon_effect_template(engine, 0xFFFF0000, 0x00000000, 3000)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -20,14 +20,14 @@ eye_color_.cycle_period = 0
|
|||||||
var cosine_val_ = (def (engine)
|
var cosine_val_ = (def (engine)
|
||||||
var provider = animation.cosine_osc(engine)
|
var provider = animation.cosine_osc(engine)
|
||||||
provider.min_value = 0
|
provider.min_value = 0
|
||||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
provider.max_value = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - 2 end)
|
||||||
provider.duration = eye_duration_
|
provider.duration = eye_duration_
|
||||||
return provider
|
return provider
|
||||||
end)(engine)
|
end)(engine)
|
||||||
var triangle_val_ = (def (engine)
|
var triangle_val_ = (def (engine)
|
||||||
var provider = animation.triangle(engine)
|
var provider = animation.triangle(engine)
|
||||||
provider.min_value = 0
|
provider.min_value = 0
|
||||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
provider.max_value = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - 2 end)
|
||||||
provider.duration = eye_duration_
|
provider.duration = eye_duration_
|
||||||
return provider
|
return provider
|
||||||
end)(engine)
|
end)(engine)
|
||||||
@ -43,7 +43,7 @@ var cylon_eye_ = animation.SequenceManager(engine, -1)
|
|||||||
.push_closure_step(def (engine) red_eye_.pos = cosine_val_ end) # switch back to COSINE for next iteration
|
.push_closure_step(def (engine) red_eye_.pos = cosine_val_ end) # switch back to COSINE for next iteration
|
||||||
.push_closure_step(def (engine) eye_color_.next = 1 end) # advance to next color
|
.push_closure_step(def (engine) eye_color_.next = 1 end) # advance to next color
|
||||||
engine.add(cylon_eye_)
|
engine.add(cylon_eye_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -18,14 +18,14 @@ red_eye_.color = 0xFFFF0000
|
|||||||
red_eye_.pos = (def (engine)
|
red_eye_.pos = (def (engine)
|
||||||
var provider = animation.cosine_osc(engine)
|
var provider = animation.cosine_osc(engine)
|
||||||
provider.min_value = 0
|
provider.min_value = 0
|
||||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
provider.max_value = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - 2 end)
|
||||||
provider.duration = 5000
|
provider.duration = 5000
|
||||||
return provider
|
return provider
|
||||||
end)(engine)
|
end)(engine)
|
||||||
red_eye_.beacon_size = 3 # small 3 pixels eye
|
red_eye_.beacon_size = 3 # small 3 pixels eye
|
||||||
red_eye_.slew_size = 2 # with 2 pixel shading around
|
red_eye_.slew_size = 2 # with 2 pixel shading around
|
||||||
engine.add(red_eye_)
|
engine.add(red_eye_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -26,7 +26,7 @@ background_.priority = 20
|
|||||||
var eye_pos_ = (def (engine)
|
var eye_pos_ = (def (engine)
|
||||||
var provider = animation.cosine_osc(engine)
|
var provider = animation.cosine_osc(engine)
|
||||||
provider.min_value = (-1)
|
provider.min_value = (-1)
|
||||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
provider.max_value = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - 2 end)
|
||||||
provider.duration = 6000
|
provider.duration = 6000
|
||||||
return provider
|
return provider
|
||||||
end)(engine)
|
end)(engine)
|
||||||
@ -39,11 +39,11 @@ eye_mask_.slew_size = 2 # with 2 pixel shading around
|
|||||||
eye_mask_.priority = 5
|
eye_mask_.priority = 5
|
||||||
var fire_pattern_ = animation.palette_gradient_animation(engine)
|
var fire_pattern_ = animation.palette_gradient_animation(engine)
|
||||||
fire_pattern_.color_source = fire_color_
|
fire_pattern_.color_source = fire_color_
|
||||||
fire_pattern_.spatial_period = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) / 4 end)
|
fire_pattern_.spatial_period = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) / 4 end)
|
||||||
fire_pattern_.opacity = eye_mask_
|
fire_pattern_.opacity = eye_mask_
|
||||||
engine.add(background_)
|
engine.add(background_)
|
||||||
engine.add(fire_pattern_)
|
engine.add(fire_pattern_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -73,7 +73,7 @@ var rainbow_with_white_ = bytes(
|
|||||||
"FFFFFFFF"
|
"FFFFFFFF"
|
||||||
)
|
)
|
||||||
shutter_bidir_template(engine, rainbow_with_white_, 1500)
|
shutter_bidir_template(engine, rainbow_with_white_, 1500)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -44,7 +44,7 @@ var shutter_run_ = animation.SequenceManager(engine, -1)
|
|||||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
.push_closure_step(def (engine) log(f"next", 3) end)
|
.push_closure_step(def (engine) log(f"next", 3) end)
|
||||||
engine.add(shutter_run_)
|
engine.add(shutter_run_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -0,0 +1,144 @@
|
|||||||
|
# Generated Berry code from Animation DSL
|
||||||
|
# Source: demo_shutter_rainbow_bidir.anim
|
||||||
|
#
|
||||||
|
# This file was automatically generated by compile_all_examples.sh
|
||||||
|
# Do not edit manually - changes will be overwritten
|
||||||
|
|
||||||
|
import animation
|
||||||
|
|
||||||
|
# Demo Shutter Rainbow Bidir
|
||||||
|
#
|
||||||
|
# Shutter from left to right iterating in all colors, then right to left
|
||||||
|
# Auto-generated strip initialization (using Tasmota configuration)
|
||||||
|
var engine = animation.init_strip()
|
||||||
|
|
||||||
|
# Template function: shutter_bidir
|
||||||
|
def shutter_bidir_template(engine, colors_, duration_)
|
||||||
|
var strip_len_ = animation.strip_length(engine)
|
||||||
|
var shutter_size_ = (def (engine)
|
||||||
|
var provider = animation.sawtooth(engine)
|
||||||
|
provider.min_value = 0
|
||||||
|
provider.max_value = strip_len_
|
||||||
|
provider.duration = duration_
|
||||||
|
return provider
|
||||||
|
end)(engine)
|
||||||
|
var col1_ = animation.color_cycle(engine)
|
||||||
|
col1_.palette = colors_
|
||||||
|
col1_.cycle_period = 0
|
||||||
|
var col2_ = animation.color_cycle(engine)
|
||||||
|
col2_.palette = colors_
|
||||||
|
col2_.cycle_period = 0
|
||||||
|
col2_.next = 1
|
||||||
|
# shutter moving from left to right
|
||||||
|
var shutter_lr_animation_ = animation.beacon_animation(engine)
|
||||||
|
shutter_lr_animation_.color = col2_
|
||||||
|
shutter_lr_animation_.back_color = col1_
|
||||||
|
shutter_lr_animation_.pos = 0
|
||||||
|
shutter_lr_animation_.beacon_size = shutter_size_
|
||||||
|
shutter_lr_animation_.slew_size = 0
|
||||||
|
shutter_lr_animation_.priority = 5
|
||||||
|
# shutter moving from right to left
|
||||||
|
var shutter_rl_animation_ = animation.beacon_animation(engine)
|
||||||
|
shutter_rl_animation_.color = col1_
|
||||||
|
shutter_rl_animation_.back_color = col2_
|
||||||
|
shutter_rl_animation_.pos = 0
|
||||||
|
shutter_rl_animation_.beacon_size = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - animation.resolve(shutter_size_) end)
|
||||||
|
shutter_rl_animation_.slew_size = 0
|
||||||
|
shutter_rl_animation_.priority = 5
|
||||||
|
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
||||||
|
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||||
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
|
.push_play_step(shutter_lr_animation_, duration_)
|
||||||
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
|
)
|
||||||
|
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||||
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
|
.push_play_step(shutter_rl_animation_, duration_)
|
||||||
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
|
)
|
||||||
|
engine.add(shutter_seq_)
|
||||||
|
end
|
||||||
|
|
||||||
|
animation.register_user_function('shutter_bidir', shutter_bidir_template)
|
||||||
|
|
||||||
|
var rainbow_with_white_ = bytes(
|
||||||
|
"FFFF0000"
|
||||||
|
"FFFFA500"
|
||||||
|
"FFFFFF00"
|
||||||
|
"FF008000" # comma left on-purpose to test transpiler
|
||||||
|
"FF0000FF" # need for a lighter blue
|
||||||
|
"FF4B0082"
|
||||||
|
"FFFFFFFF"
|
||||||
|
)
|
||||||
|
shutter_bidir_template(engine, rainbow_with_white_, 1500)
|
||||||
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
|
#- Original DSL source:
|
||||||
|
# Demo Shutter Rainbow Bidir
|
||||||
|
#
|
||||||
|
# Shutter from left to right iterating in all colors, then right to left
|
||||||
|
|
||||||
|
template shutter_bidir {
|
||||||
|
param colors type palette
|
||||||
|
param duration
|
||||||
|
|
||||||
|
set strip_len = strip_length()
|
||||||
|
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration)
|
||||||
|
|
||||||
|
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
col2.next = 1
|
||||||
|
|
||||||
|
# shutter moving from left to right
|
||||||
|
animation shutter_lr_animation = beacon_animation(
|
||||||
|
color = col2
|
||||||
|
back_color = col1
|
||||||
|
pos = 0
|
||||||
|
beacon_size = shutter_size
|
||||||
|
slew_size = 0
|
||||||
|
priority = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
# shutter moving from right to left
|
||||||
|
animation shutter_rl_animation = beacon_animation(
|
||||||
|
color = col1
|
||||||
|
back_color = col2
|
||||||
|
pos = 0
|
||||||
|
beacon_size = strip_len - shutter_size
|
||||||
|
slew_size = 0
|
||||||
|
priority = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
sequence shutter_seq repeat forever {
|
||||||
|
repeat col1.palette_size times {
|
||||||
|
restart shutter_size
|
||||||
|
play shutter_lr_animation for duration
|
||||||
|
col1.next = 1
|
||||||
|
col2.next = 1
|
||||||
|
}
|
||||||
|
repeat col1.palette_size times {
|
||||||
|
restart shutter_size
|
||||||
|
play shutter_rl_animation for duration
|
||||||
|
col1.next = 1
|
||||||
|
col2.next = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run shutter_seq
|
||||||
|
}
|
||||||
|
|
||||||
|
palette rainbow_with_white = [ red
|
||||||
|
orange
|
||||||
|
yellow
|
||||||
|
green, # comma left on-purpose to test transpiler
|
||||||
|
blue # need for a lighter blue
|
||||||
|
indigo
|
||||||
|
white
|
||||||
|
]
|
||||||
|
|
||||||
|
shutter_bidir(rainbow_with_white, 1.5s)
|
||||||
|
|
||||||
|
-#
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
# Generated Berry code from Animation DSL
|
||||||
|
# Source: demo_shutter_rainbow_central.anim
|
||||||
|
#
|
||||||
|
# This file was automatically generated by compile_all_examples.sh
|
||||||
|
# Do not edit manually - changes will be overwritten
|
||||||
|
|
||||||
|
import animation
|
||||||
|
|
||||||
|
# Demo Shutter Rainbow
|
||||||
|
#
|
||||||
|
# Shutter from center to both left and right
|
||||||
|
# Auto-generated strip initialization (using Tasmota configuration)
|
||||||
|
var engine = animation.init_strip()
|
||||||
|
|
||||||
|
# Template function: shutter_central
|
||||||
|
def shutter_central_template(engine, colors_, duration_)
|
||||||
|
var strip_len_ = animation.strip_length(engine)
|
||||||
|
var strip_len2_ = animation.create_closure_value(engine, def (engine) return (animation.strip_length(engine) + 1) / 2 end)
|
||||||
|
var shutter_size_ = (def (engine)
|
||||||
|
var provider = animation.sawtooth(engine)
|
||||||
|
provider.min_value = 0
|
||||||
|
provider.max_value = strip_len_
|
||||||
|
provider.duration = duration_
|
||||||
|
return provider
|
||||||
|
end)(engine)
|
||||||
|
var col1_ = animation.color_cycle(engine)
|
||||||
|
col1_.palette = colors_
|
||||||
|
col1_.cycle_period = 0
|
||||||
|
var col2_ = animation.color_cycle(engine)
|
||||||
|
col2_.palette = colors_
|
||||||
|
col2_.cycle_period = 0
|
||||||
|
col2_.next = 1
|
||||||
|
# shutter moving from left to right
|
||||||
|
var shutter_central_animation_ = animation.beacon_animation(engine)
|
||||||
|
shutter_central_animation_.color = col2_
|
||||||
|
shutter_central_animation_.back_color = col1_
|
||||||
|
shutter_central_animation_.pos = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len2_) - animation.resolve(shutter_size_) / 2 end)
|
||||||
|
shutter_central_animation_.beacon_size = shutter_size_
|
||||||
|
shutter_central_animation_.slew_size = 0
|
||||||
|
shutter_central_animation_.priority = 5
|
||||||
|
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
||||||
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
|
.push_play_step(shutter_central_animation_, duration_)
|
||||||
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
|
engine.add(shutter_seq_)
|
||||||
|
end
|
||||||
|
|
||||||
|
animation.register_user_function('shutter_central', shutter_central_template)
|
||||||
|
|
||||||
|
var rainbow_with_white_ = bytes(
|
||||||
|
"FFFF0000"
|
||||||
|
"FFFFA500"
|
||||||
|
"FFFFFF00"
|
||||||
|
"FF008000" # comma left on-purpose to test transpiler
|
||||||
|
"FF0000FF" # need for a lighter blue
|
||||||
|
"FF4B0082"
|
||||||
|
"FFFFFFFF"
|
||||||
|
)
|
||||||
|
shutter_central_template(engine, rainbow_with_white_, 1500)
|
||||||
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
|
#- Original DSL source:
|
||||||
|
# Demo Shutter Rainbow
|
||||||
|
#
|
||||||
|
# Shutter from center to both left and right
|
||||||
|
|
||||||
|
template shutter_central {
|
||||||
|
param colors type palette
|
||||||
|
param duration
|
||||||
|
|
||||||
|
set strip_len = strip_length()
|
||||||
|
set strip_len2 = (strip_length() + 1) / 2
|
||||||
|
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration)
|
||||||
|
|
||||||
|
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
col2.next = 1
|
||||||
|
|
||||||
|
# shutter moving from left to right
|
||||||
|
animation shutter_central_animation = beacon_animation(
|
||||||
|
color = col2
|
||||||
|
back_color = col1
|
||||||
|
pos = strip_len2 - shutter_size / 2
|
||||||
|
beacon_size = shutter_size
|
||||||
|
slew_size = 0
|
||||||
|
priority = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
sequence shutter_seq repeat forever {
|
||||||
|
restart shutter_size
|
||||||
|
play shutter_central_animation for duration
|
||||||
|
col1.next = 1
|
||||||
|
col2.next = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run shutter_seq
|
||||||
|
}
|
||||||
|
|
||||||
|
palette rainbow_with_white = [ red
|
||||||
|
orange
|
||||||
|
yellow
|
||||||
|
green, # comma left on-purpose to test transpiler
|
||||||
|
blue # need for a lighter blue
|
||||||
|
indigo
|
||||||
|
white
|
||||||
|
]
|
||||||
|
|
||||||
|
shutter_central(rainbow_with_white, 1.5s)
|
||||||
|
|
||||||
|
-#
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
# Generated Berry code from Animation DSL
|
||||||
|
# Source: demo_shutter_rainbow_leftright.anim
|
||||||
|
#
|
||||||
|
# This file was automatically generated by compile_all_examples.sh
|
||||||
|
# Do not edit manually - changes will be overwritten
|
||||||
|
|
||||||
|
import animation
|
||||||
|
|
||||||
|
# Demo Shutter Rainbow
|
||||||
|
#
|
||||||
|
# Shutter from left to right iterating in all colors, then right to left
|
||||||
|
# Auto-generated strip initialization (using Tasmota configuration)
|
||||||
|
var engine = animation.init_strip()
|
||||||
|
|
||||||
|
# Template function: shutter_lr
|
||||||
|
def shutter_lr_template(engine, colors_, duration_)
|
||||||
|
var strip_len_ = animation.strip_length(engine)
|
||||||
|
var shutter_size_ = (def (engine)
|
||||||
|
var provider = animation.sawtooth(engine)
|
||||||
|
provider.min_value = 0
|
||||||
|
provider.max_value = strip_len_
|
||||||
|
provider.duration = duration_
|
||||||
|
return provider
|
||||||
|
end)(engine)
|
||||||
|
var col1_ = animation.color_cycle(engine)
|
||||||
|
col1_.palette = colors_
|
||||||
|
col1_.cycle_period = 0
|
||||||
|
var col2_ = animation.color_cycle(engine)
|
||||||
|
col2_.palette = colors_
|
||||||
|
col2_.cycle_period = 0
|
||||||
|
col2_.next = 1
|
||||||
|
# shutter moving from left to right
|
||||||
|
var shutter_lr_animation_ = animation.beacon_animation(engine)
|
||||||
|
shutter_lr_animation_.color = col2_
|
||||||
|
shutter_lr_animation_.back_color = col1_
|
||||||
|
shutter_lr_animation_.pos = 0
|
||||||
|
shutter_lr_animation_.beacon_size = shutter_size_
|
||||||
|
shutter_lr_animation_.slew_size = 0
|
||||||
|
shutter_lr_animation_.priority = 5
|
||||||
|
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
||||||
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
|
.push_play_step(shutter_lr_animation_, duration_)
|
||||||
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
|
engine.add(shutter_seq_)
|
||||||
|
end
|
||||||
|
|
||||||
|
animation.register_user_function('shutter_lr', shutter_lr_template)
|
||||||
|
|
||||||
|
var rainbow_with_white_ = bytes(
|
||||||
|
"FFFF0000"
|
||||||
|
"FFFFA500"
|
||||||
|
"FFFFFF00"
|
||||||
|
"FF008000" # comma left on-purpose to test transpiler
|
||||||
|
"FF0000FF" # need for a lighter blue
|
||||||
|
"FF4B0082"
|
||||||
|
"FFFFFFFF"
|
||||||
|
)
|
||||||
|
shutter_lr_template(engine, rainbow_with_white_, 1500)
|
||||||
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
|
#- Original DSL source:
|
||||||
|
# Demo Shutter Rainbow
|
||||||
|
#
|
||||||
|
# Shutter from left to right iterating in all colors, then right to left
|
||||||
|
|
||||||
|
template shutter_lr {
|
||||||
|
param colors type palette
|
||||||
|
param duration
|
||||||
|
|
||||||
|
set strip_len = strip_length()
|
||||||
|
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration)
|
||||||
|
|
||||||
|
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
col2.next = 1
|
||||||
|
|
||||||
|
# shutter moving from left to right
|
||||||
|
animation shutter_lr_animation = beacon_animation(
|
||||||
|
color = col2
|
||||||
|
back_color = col1
|
||||||
|
pos = 0
|
||||||
|
beacon_size = shutter_size
|
||||||
|
slew_size = 0
|
||||||
|
priority = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
sequence shutter_seq repeat forever {
|
||||||
|
restart shutter_size
|
||||||
|
play shutter_lr_animation for duration
|
||||||
|
col1.next = 1
|
||||||
|
col2.next = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run shutter_seq
|
||||||
|
}
|
||||||
|
|
||||||
|
palette rainbow_with_white = [ red
|
||||||
|
orange
|
||||||
|
yellow
|
||||||
|
green, # comma left on-purpose to test transpiler
|
||||||
|
blue # need for a lighter blue
|
||||||
|
indigo
|
||||||
|
white
|
||||||
|
]
|
||||||
|
|
||||||
|
shutter_lr(rainbow_with_white, 1.5s)
|
||||||
|
|
||||||
|
-#
|
||||||
@ -84,7 +84,7 @@ engine.add(disco_base_)
|
|||||||
engine.add(white_flash_)
|
engine.add(white_flash_)
|
||||||
engine.add(disco_sparkles_)
|
engine.add(disco_sparkles_)
|
||||||
engine.add(disco_pulse_)
|
engine.add(disco_pulse_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -48,7 +48,7 @@ fire_flicker_.priority = 10
|
|||||||
# Start both animations
|
# Start both animations
|
||||||
engine.add(fire_base_)
|
engine.add(fire_base_)
|
||||||
engine.add(fire_flicker_)
|
engine.add(fire_flicker_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -77,7 +77,7 @@ engine.add(heart_glow_)
|
|||||||
engine.add(heartbeat1_)
|
engine.add(heartbeat1_)
|
||||||
engine.add(heartbeat2_)
|
engine.add(heartbeat2_)
|
||||||
engine.add(center_pulse_)
|
engine.add(center_pulse_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -16,13 +16,13 @@ import user_functions
|
|||||||
# Create animations that use imported user functions
|
# Create animations that use imported user functions
|
||||||
var random_red_ = animation.solid(engine)
|
var random_red_ = animation.solid(engine)
|
||||||
random_red_.color = 0xFFFF0000
|
random_red_.color = 0xFFFF0000
|
||||||
random_red_.opacity = animation.create_closure_value(engine, def (self) return animation.get_user_function('rand_demo')(self.engine) end)
|
random_red_.opacity = animation.create_closure_value(engine, def (engine) return animation.get_user_function('rand_demo')(engine) end)
|
||||||
var breathing_blue_ = animation.solid(engine)
|
var breathing_blue_ = animation.solid(engine)
|
||||||
breathing_blue_.color = 0xFF0000FF
|
breathing_blue_.color = 0xFF0000FF
|
||||||
breathing_blue_.opacity = animation.create_closure_value(engine, def (self) return self.max(50, self.min(255, animation.get_user_function('rand_demo')(self.engine) + 100)) end)
|
breathing_blue_.opacity = animation.create_closure_value(engine, def (engine) return animation._math.max(50, animation._math.min(255, animation.get_user_function('rand_demo')(engine) + 100)) end)
|
||||||
var dynamic_green_ = animation.solid(engine)
|
var dynamic_green_ = animation.solid(engine)
|
||||||
dynamic_green_.color = 0xFF008000
|
dynamic_green_.color = 0xFF008000
|
||||||
dynamic_green_.opacity = animation.create_closure_value(engine, def (self) return self.abs(animation.get_user_function('rand_demo')(self.engine) - 128) + 64 end)
|
dynamic_green_.opacity = animation.create_closure_value(engine, def (engine) return animation._math.abs(animation.get_user_function('rand_demo')(engine) - 128) + 64 end)
|
||||||
# Create a sequence that cycles through the animations
|
# Create a sequence that cycles through the animations
|
||||||
var import_demo_ = animation.SequenceManager(engine)
|
var import_demo_ = animation.SequenceManager(engine)
|
||||||
.push_play_step(random_red_, 3000)
|
.push_play_step(random_red_, 3000)
|
||||||
@ -30,7 +30,7 @@ var import_demo_ = animation.SequenceManager(engine)
|
|||||||
.push_play_step(dynamic_green_, 3000)
|
.push_play_step(dynamic_green_, 3000)
|
||||||
# Run the demo
|
# Run the demo
|
||||||
engine.add(import_demo_)
|
engine.add(import_demo_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -98,7 +98,7 @@ engine.add(lava_blob1_)
|
|||||||
engine.add(lava_blob2_)
|
engine.add(lava_blob2_)
|
||||||
engine.add(lava_blob3_)
|
engine.add(lava_blob3_)
|
||||||
engine.add(heat_shimmer_)
|
engine.add(heat_shimmer_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -76,7 +76,7 @@ engine.add(lightning_main_)
|
|||||||
engine.add(lightning_partial_)
|
engine.add(lightning_partial_)
|
||||||
engine.add(afterglow_)
|
engine.add(afterglow_)
|
||||||
engine.add(distant_flash_)
|
engine.add(distant_flash_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -68,7 +68,7 @@ engine.add(stream1_)
|
|||||||
engine.add(stream2_)
|
engine.add(stream2_)
|
||||||
engine.add(stream3_)
|
engine.add(stream3_)
|
||||||
engine.add(code_flash_)
|
engine.add(code_flash_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -57,7 +57,7 @@ engine.add(meteor2_)
|
|||||||
engine.add(meteor3_)
|
engine.add(meteor3_)
|
||||||
engine.add(meteor4_)
|
engine.add(meteor4_)
|
||||||
engine.add(meteor_flash_)
|
engine.add(meteor_flash_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -83,7 +83,7 @@ engine.add(segment1_)
|
|||||||
engine.add(segment2_)
|
engine.add(segment2_)
|
||||||
engine.add(segment3_)
|
engine.add(segment3_)
|
||||||
engine.add(arc_sparkles_)
|
engine.add(arc_sparkles_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -74,7 +74,7 @@ engine.add(ocean_base_)
|
|||||||
engine.add(wave1_)
|
engine.add(wave1_)
|
||||||
engine.add(wave2_)
|
engine.add(wave2_)
|
||||||
engine.add(foam_)
|
engine.add(foam_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -44,7 +44,7 @@ var palette_demo_ = animation.SequenceManager(engine)
|
|||||||
.push_play_step(forest_anim_, 3000)
|
.push_play_step(forest_anim_, 3000)
|
||||||
)
|
)
|
||||||
engine.add(palette_demo_)
|
engine.add(palette_demo_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -89,7 +89,7 @@ var palette_showcase_ = animation.SequenceManager(engine)
|
|||||||
.push_play_step(sunset_glow_, 2000)
|
.push_play_step(sunset_glow_, 2000)
|
||||||
)
|
)
|
||||||
engine.add(palette_showcase_)
|
engine.add(palette_showcase_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -95,7 +95,7 @@ engine.add(plasma_base_)
|
|||||||
engine.add(plasma_wave1_)
|
engine.add(plasma_wave1_)
|
||||||
engine.add(plasma_wave2_)
|
engine.add(plasma_wave2_)
|
||||||
engine.add(plasma_wave3_)
|
engine.add(plasma_wave3_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -60,7 +60,7 @@ white_strobe_.priority = 20
|
|||||||
engine.add(left_red_)
|
engine.add(left_red_)
|
||||||
engine.add(right_blue_)
|
engine.add(right_blue_)
|
||||||
engine.add(white_strobe_)
|
engine.add(white_strobe_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -56,7 +56,7 @@ var demo_ = animation.SequenceManager(engine)
|
|||||||
.push_wait_step(1000)
|
.push_wait_step(1000)
|
||||||
)
|
)
|
||||||
engine.add(demo_)
|
engine.add(demo_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -21,7 +21,7 @@ var rainbow_animation_ = animation.solid(engine)
|
|||||||
rainbow_animation_.color = rainbow_cycle_
|
rainbow_animation_.color = rainbow_cycle_
|
||||||
# Start the animation
|
# Start the animation
|
||||||
engine.add(rainbow_animation_)
|
engine.add(rainbow_animation_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -51,7 +51,7 @@ scanner_trail_.opacity = 128 # Half brightness
|
|||||||
engine.add(background_)
|
engine.add(background_)
|
||||||
engine.add(scanner_trail_)
|
engine.add(scanner_trail_)
|
||||||
engine.add(scanner_)
|
engine.add(scanner_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -16,14 +16,14 @@ var strip_len_ = animation.strip_length(engine)
|
|||||||
var triangle_val_ = (def (engine)
|
var triangle_val_ = (def (engine)
|
||||||
var provider = animation.triangle(engine)
|
var provider = animation.triangle(engine)
|
||||||
provider.min_value = 0
|
provider.min_value = 0
|
||||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
provider.max_value = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - 2 end)
|
||||||
provider.duration = 5000
|
provider.duration = 5000
|
||||||
return provider
|
return provider
|
||||||
end)(engine)
|
end)(engine)
|
||||||
var cosine_val_ = (def (engine)
|
var cosine_val_ = (def (engine)
|
||||||
var provider = animation.cosine_osc(engine)
|
var provider = animation.cosine_osc(engine)
|
||||||
provider.min_value = 0
|
provider.min_value = 0
|
||||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
provider.max_value = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - 2 end)
|
||||||
provider.duration = 5000
|
provider.duration = 5000
|
||||||
return provider
|
return provider
|
||||||
end)(engine)
|
end)(engine)
|
||||||
@ -110,7 +110,7 @@ var main_demo_ = animation.SequenceManager(engine)
|
|||||||
.push_play_step(pulse_demo_, 1000)
|
.push_play_step(pulse_demo_, 1000)
|
||||||
# Run the main demo
|
# Run the main demo
|
||||||
engine.add(main_demo_)
|
engine.add(main_demo_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -22,7 +22,7 @@ rainbow_cycle_.cycle_period = 3000
|
|||||||
var demo_ = animation.SequenceManager(engine)
|
var demo_ = animation.SequenceManager(engine)
|
||||||
.push_play_step(rainbow_cycle_, 15000)
|
.push_play_step(rainbow_cycle_, 15000)
|
||||||
engine.add(demo_)
|
engine.add(demo_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -88,7 +88,7 @@ engine.add(daylight_cycle_)
|
|||||||
engine.add(sun_position_)
|
engine.add(sun_position_)
|
||||||
engine.add(sun_glow_)
|
engine.add(sun_glow_)
|
||||||
engine.add(stars_)
|
engine.add(stars_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -21,7 +21,7 @@ var slide_colors_ = animation.SequenceManager(engine)
|
|||||||
.push_play_step(swipe_animation_, 1000)
|
.push_play_step(swipe_animation_, 1000)
|
||||||
.push_closure_step(def (engine) olivary_.next = 1 end)
|
.push_closure_step(def (engine) olivary_.next = 1 end)
|
||||||
engine.add(slide_colors_)
|
engine.add(slide_colors_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -37,7 +37,7 @@ var fire_palette_ = bytes("00000000" "80FF0000" "FFFFFF00")
|
|||||||
var ocean_palette_ = bytes("00000080" "800080FF" "FF00FFFF")
|
var ocean_palette_ = bytes("00000080" "800080FF" "FF00FFFF")
|
||||||
# Use the template
|
# Use the template
|
||||||
rainbow_pulse_template(engine, fire_palette_, ocean_palette_, 3000, 0xFF001100)
|
rainbow_pulse_template(engine, fire_palette_, ocean_palette_, 3000, 0xFF001100)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -0,0 +1,144 @@
|
|||||||
|
# Generated Berry code from Animation DSL
|
||||||
|
# Source: test_shutter_rainbow_bidir.anim
|
||||||
|
#
|
||||||
|
# This file was automatically generated by compile_all_examples.sh
|
||||||
|
# Do not edit manually - changes will be overwritten
|
||||||
|
|
||||||
|
import animation
|
||||||
|
|
||||||
|
# Demo Shutter Rainbow Bidir
|
||||||
|
#
|
||||||
|
# Shutter from left to right iterating in all colors, then right to left
|
||||||
|
# Auto-generated strip initialization (using Tasmota configuration)
|
||||||
|
var engine = animation.init_strip()
|
||||||
|
|
||||||
|
# Template function: shutter_bidir
|
||||||
|
def shutter_bidir_template(engine, colors_, duration_)
|
||||||
|
var strip_len_ = animation.strip_length(engine)
|
||||||
|
var shutter_size_ = (def (engine)
|
||||||
|
var provider = animation.sawtooth(engine)
|
||||||
|
provider.min_value = 0
|
||||||
|
provider.max_value = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) + 0 end)
|
||||||
|
provider.duration = duration_
|
||||||
|
return provider
|
||||||
|
end)(engine)
|
||||||
|
var col1_ = animation.color_cycle(engine)
|
||||||
|
col1_.palette = colors_
|
||||||
|
col1_.cycle_period = 0
|
||||||
|
var col2_ = animation.color_cycle(engine)
|
||||||
|
col2_.palette = colors_
|
||||||
|
col2_.cycle_period = 0
|
||||||
|
col2_.next = 1
|
||||||
|
# shutter moving from left to right
|
||||||
|
var shutter_lr_animation_ = animation.beacon_animation(engine)
|
||||||
|
shutter_lr_animation_.color = col2_
|
||||||
|
shutter_lr_animation_.back_color = col1_
|
||||||
|
shutter_lr_animation_.pos = 0
|
||||||
|
shutter_lr_animation_.beacon_size = animation.create_closure_value(engine, def (engine) return animation.resolve(shutter_size_) + 0 end)
|
||||||
|
shutter_lr_animation_.slew_size = 0
|
||||||
|
shutter_lr_animation_.priority = 5
|
||||||
|
# shutter moving from right to left
|
||||||
|
var shutter_rl_animation_ = animation.beacon_animation(engine)
|
||||||
|
shutter_rl_animation_.color = col1_
|
||||||
|
shutter_rl_animation_.back_color = col2_
|
||||||
|
shutter_rl_animation_.pos = 0
|
||||||
|
shutter_rl_animation_.beacon_size = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - animation.resolve(shutter_size_) end)
|
||||||
|
shutter_rl_animation_.slew_size = animation.create_closure_value(engine, def (engine) return 0 + 0 end)
|
||||||
|
shutter_rl_animation_.priority = 5
|
||||||
|
var shutter_seq_ = animation.SequenceManager(engine, -1)
|
||||||
|
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||||
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
|
.push_play_step(shutter_lr_animation_, duration_)
|
||||||
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
|
)
|
||||||
|
.push_repeat_subsequence(animation.SequenceManager(engine, def (engine) return col1_.palette_size end)
|
||||||
|
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||||
|
.push_play_step(shutter_rl_animation_, duration_)
|
||||||
|
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||||
|
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||||
|
)
|
||||||
|
engine.add(shutter_seq_)
|
||||||
|
end
|
||||||
|
|
||||||
|
animation.register_user_function('shutter_bidir', shutter_bidir_template)
|
||||||
|
|
||||||
|
var rainbow_with_white_ = bytes(
|
||||||
|
"FFFF0000"
|
||||||
|
"FFFFA500"
|
||||||
|
"FFFFFF00"
|
||||||
|
"FF008000" # comma left on-purpose to test transpiler
|
||||||
|
"FF0000FF" # need for a lighter blue
|
||||||
|
"FF4B0082"
|
||||||
|
"FFFFFFFF"
|
||||||
|
)
|
||||||
|
shutter_bidir_template(engine, rainbow_with_white_, 1500)
|
||||||
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
|
#- Original DSL source:
|
||||||
|
# Demo Shutter Rainbow Bidir
|
||||||
|
#
|
||||||
|
# Shutter from left to right iterating in all colors, then right to left
|
||||||
|
|
||||||
|
template shutter_bidir {
|
||||||
|
param colors type palette
|
||||||
|
param duration
|
||||||
|
|
||||||
|
set strip_len = strip_length()
|
||||||
|
set shutter_size = sawtooth(min_value = 0, max_value = strip_len + 0, duration = duration)
|
||||||
|
|
||||||
|
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
col2.next = 1
|
||||||
|
|
||||||
|
# shutter moving from left to right
|
||||||
|
animation shutter_lr_animation = beacon_animation(
|
||||||
|
color = col2
|
||||||
|
back_color = col1
|
||||||
|
pos = 0
|
||||||
|
beacon_size = shutter_size + 0
|
||||||
|
slew_size = 0
|
||||||
|
priority = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
# shutter moving from right to left
|
||||||
|
animation shutter_rl_animation = beacon_animation(
|
||||||
|
color = col1
|
||||||
|
back_color = col2
|
||||||
|
pos = 0
|
||||||
|
beacon_size = strip_len - shutter_size
|
||||||
|
slew_size = 0 + 0
|
||||||
|
priority = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
sequence shutter_seq repeat forever {
|
||||||
|
repeat col1.palette_size times {
|
||||||
|
restart shutter_size
|
||||||
|
play shutter_lr_animation for duration
|
||||||
|
col1.next = 1
|
||||||
|
col2.next = 1
|
||||||
|
}
|
||||||
|
repeat col1.palette_size times {
|
||||||
|
restart shutter_size
|
||||||
|
play shutter_rl_animation for duration
|
||||||
|
col1.next = 1
|
||||||
|
col2.next = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run shutter_seq
|
||||||
|
}
|
||||||
|
|
||||||
|
palette rainbow_with_white = [ red
|
||||||
|
orange
|
||||||
|
yellow
|
||||||
|
green, # comma left on-purpose to test transpiler
|
||||||
|
blue # need for a lighter blue
|
||||||
|
indigo
|
||||||
|
white
|
||||||
|
]
|
||||||
|
|
||||||
|
shutter_bidir(rainbow_with_white, 1.5s)
|
||||||
|
|
||||||
|
-#
|
||||||
@ -24,7 +24,7 @@ animation.register_user_function('pulse_effect', pulse_effect_template)
|
|||||||
|
|
||||||
# Use the template - templates add animations directly to engine and run them
|
# Use the template - templates add animations directly to engine and run them
|
||||||
pulse_effect_template(engine, 0xFFFF0000, 2000, 204)
|
pulse_effect_template(engine, 0xFFFF0000, 2000, 204)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -24,7 +24,7 @@ animation.register_user_function('pulse_effect', pulse_effect_template)
|
|||||||
|
|
||||||
# Use the template - templates add animations directly to engine and run them
|
# Use the template - templates add animations directly to engine and run them
|
||||||
pulse_effect_template(engine, 0xFFFF0000, 2000, 204)
|
pulse_effect_template(engine, 0xFFFF0000, 2000, 204)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -32,7 +32,7 @@ bright_flash_.priority = 15
|
|||||||
engine.add(background_)
|
engine.add(background_)
|
||||||
engine.add(stars_)
|
engine.add(stars_)
|
||||||
engine.add(bright_flash_)
|
engine.add(bright_flash_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -19,38 +19,38 @@ var random_base_ = animation.solid(engine)
|
|||||||
random_base_.color = 0xFF0000FF
|
random_base_.color = 0xFF0000FF
|
||||||
random_base_.priority = 10
|
random_base_.priority = 10
|
||||||
# Use user function in property assignment
|
# Use user function in property assignment
|
||||||
random_base_.opacity = animation.create_closure_value(engine, def (self) return animation.get_user_function('rand_demo')(self.engine) end)
|
random_base_.opacity = animation.create_closure_value(engine, def (engine) return animation.get_user_function('rand_demo')(engine) end)
|
||||||
# Example 2: User function with mathematical operations
|
# Example 2: User function with mathematical operations
|
||||||
var random_bounded_ = animation.solid(engine)
|
var random_bounded_ = animation.solid(engine)
|
||||||
random_bounded_.color = 0xFFFFA500
|
random_bounded_.color = 0xFFFFA500
|
||||||
random_bounded_.priority = 8
|
random_bounded_.priority = 8
|
||||||
# User function with bounds using math functions
|
# User function with bounds using math functions
|
||||||
random_bounded_.opacity = animation.create_closure_value(engine, def (self) return self.max(50, self.min(255, animation.get_user_function('rand_demo')(self.engine) + 100)) end)
|
random_bounded_.opacity = animation.create_closure_value(engine, def (engine) return animation._math.max(50, animation._math.min(255, animation.get_user_function('rand_demo')(engine) + 100)) end)
|
||||||
# Example 3: User function in arithmetic expressions
|
# Example 3: User function in arithmetic expressions
|
||||||
var random_variation_ = animation.solid(engine)
|
var random_variation_ = animation.solid(engine)
|
||||||
random_variation_.color = 0xFF800080
|
random_variation_.color = 0xFF800080
|
||||||
random_variation_.priority = 15
|
random_variation_.priority = 15
|
||||||
# Mix user function with arithmetic operations
|
# Mix user function with arithmetic operations
|
||||||
random_variation_.opacity = animation.create_closure_value(engine, def (self) return self.abs(animation.get_user_function('rand_demo')(self.engine) - 128) + 64 end)
|
random_variation_.opacity = animation.create_closure_value(engine, def (engine) return animation._math.abs(animation.get_user_function('rand_demo')(engine) - 128) + 64 end)
|
||||||
# Example 4: User function affecting different properties
|
# Example 4: User function affecting different properties
|
||||||
var random_multi_ = animation.solid(engine)
|
var random_multi_ = animation.solid(engine)
|
||||||
random_multi_.color = 0xFF00FFFF
|
random_multi_.color = 0xFF00FFFF
|
||||||
random_multi_.priority = 12
|
random_multi_.priority = 12
|
||||||
# Use user function for multiple properties
|
# Use user function for multiple properties
|
||||||
random_multi_.opacity = animation.create_closure_value(engine, def (self) return self.max(100, animation.get_user_function('rand_demo')(self.engine)) end)
|
random_multi_.opacity = animation.create_closure_value(engine, def (engine) return animation._math.max(100, animation.get_user_function('rand_demo')(engine)) end)
|
||||||
# Example 5: Complex expression with user function
|
# Example 5: Complex expression with user function
|
||||||
var random_complex_ = animation.solid(engine)
|
var random_complex_ = animation.solid(engine)
|
||||||
random_complex_.color = 0xFFFFFFFF
|
random_complex_.color = 0xFFFFFFFF
|
||||||
random_complex_.priority = 20
|
random_complex_.priority = 20
|
||||||
# Complex expression with user function and math operations
|
# Complex expression with user function and math operations
|
||||||
random_complex_.opacity = animation.create_closure_value(engine, def (self) 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)
|
random_complex_.opacity = animation.create_closure_value(engine, def (engine) return animation._math.round((animation.get_user_function('rand_demo')(engine) + 128) / 2 + animation._math.abs(animation.get_user_function('rand_demo')(engine) - 100)) end)
|
||||||
# Run all animations to demonstrate the effects
|
# Run all animations to demonstrate the effects
|
||||||
engine.add(random_base_)
|
engine.add(random_base_)
|
||||||
engine.add(random_bounded_)
|
engine.add(random_bounded_)
|
||||||
engine.add(random_variation_)
|
engine.add(random_variation_)
|
||||||
engine.add(random_multi_)
|
engine.add(random_multi_)
|
||||||
engine.add(random_complex_)
|
engine.add(random_complex_)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
|
|
||||||
#- Original DSL source:
|
#- Original DSL source:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Demo Shutter Rainbow
|
# Demo Shutter Rainbow Bidir
|
||||||
#
|
#
|
||||||
# Shutter from left to right iterating in all colors, then right to left
|
# Shutter from left to right iterating in all colors, then right to left
|
||||||
|
|
||||||
@ -35,13 +35,13 @@ template shutter_bidir {
|
|||||||
|
|
||||||
sequence shutter_seq repeat forever {
|
sequence shutter_seq repeat forever {
|
||||||
repeat col1.palette_size times {
|
repeat col1.palette_size times {
|
||||||
reset shutter_size
|
restart shutter_size
|
||||||
play shutter_lr_animation for duration
|
play shutter_lr_animation for duration
|
||||||
col1.next = 1
|
col1.next = 1
|
||||||
col2.next = 1
|
col2.next = 1
|
||||||
}
|
}
|
||||||
repeat col1.palette_size times {
|
repeat col1.palette_size times {
|
||||||
reset shutter_size
|
restart shutter_size
|
||||||
play shutter_rl_animation for duration
|
play shutter_rl_animation for duration
|
||||||
col1.next = 1
|
col1.next = 1
|
||||||
col2.next = 1
|
col2.next = 1
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
# Demo Shutter Rainbow
|
||||||
|
#
|
||||||
|
# Shutter from center to both left and right
|
||||||
|
|
||||||
|
template shutter_central {
|
||||||
|
param colors type palette
|
||||||
|
param duration
|
||||||
|
|
||||||
|
set strip_len = strip_length()
|
||||||
|
set strip_len2 = (strip_length() + 1) / 2
|
||||||
|
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration)
|
||||||
|
|
||||||
|
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
col2.next = 1
|
||||||
|
|
||||||
|
# shutter moving from left to right
|
||||||
|
animation shutter_central_animation = beacon_animation(
|
||||||
|
color = col2
|
||||||
|
back_color = col1
|
||||||
|
pos = strip_len2 - shutter_size / 2
|
||||||
|
beacon_size = shutter_size
|
||||||
|
slew_size = 0
|
||||||
|
priority = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
sequence shutter_seq repeat forever {
|
||||||
|
restart shutter_size
|
||||||
|
play shutter_central_animation for duration
|
||||||
|
col1.next = 1
|
||||||
|
col2.next = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run shutter_seq
|
||||||
|
}
|
||||||
|
|
||||||
|
palette rainbow_with_white = [ red
|
||||||
|
orange
|
||||||
|
yellow
|
||||||
|
green, # comma left on-purpose to test transpiler
|
||||||
|
blue # need for a lighter blue
|
||||||
|
indigo
|
||||||
|
white
|
||||||
|
]
|
||||||
|
|
||||||
|
shutter_central(rainbow_with_white, 1.5s)
|
||||||
|
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
# Demo Shutter Rainbow
|
||||||
|
#
|
||||||
|
# Shutter from left to right iterating in all colors, then right to left
|
||||||
|
|
||||||
|
template shutter_lr {
|
||||||
|
param colors type palette
|
||||||
|
param duration
|
||||||
|
|
||||||
|
set strip_len = strip_length()
|
||||||
|
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration)
|
||||||
|
|
||||||
|
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
col2.next = 1
|
||||||
|
|
||||||
|
# shutter moving from left to right
|
||||||
|
animation shutter_lr_animation = beacon_animation(
|
||||||
|
color = col2
|
||||||
|
back_color = col1
|
||||||
|
pos = 0
|
||||||
|
beacon_size = shutter_size
|
||||||
|
slew_size = 0
|
||||||
|
priority = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
sequence shutter_seq repeat forever {
|
||||||
|
restart shutter_size
|
||||||
|
play shutter_lr_animation for duration
|
||||||
|
col1.next = 1
|
||||||
|
col2.next = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run shutter_seq
|
||||||
|
}
|
||||||
|
|
||||||
|
palette rainbow_with_white = [ red
|
||||||
|
orange
|
||||||
|
yellow
|
||||||
|
green, # comma left on-purpose to test transpiler
|
||||||
|
blue # need for a lighter blue
|
||||||
|
indigo
|
||||||
|
white
|
||||||
|
]
|
||||||
|
|
||||||
|
shutter_lr(rainbow_with_white, 1.5s)
|
||||||
|
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
# Demo Shutter Rainbow Bidir
|
||||||
|
#
|
||||||
|
# Shutter from left to right iterating in all colors, then right to left
|
||||||
|
|
||||||
|
template shutter_bidir {
|
||||||
|
param colors type palette
|
||||||
|
param duration
|
||||||
|
|
||||||
|
set strip_len = strip_length()
|
||||||
|
set shutter_size = sawtooth(min_value = 0, max_value = strip_len + 0, duration = duration)
|
||||||
|
|
||||||
|
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||||
|
col2.next = 1
|
||||||
|
|
||||||
|
# shutter moving from left to right
|
||||||
|
animation shutter_lr_animation = beacon_animation(
|
||||||
|
color = col2
|
||||||
|
back_color = col1
|
||||||
|
pos = 0
|
||||||
|
beacon_size = shutter_size + 0
|
||||||
|
slew_size = 0
|
||||||
|
priority = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
# shutter moving from right to left
|
||||||
|
animation shutter_rl_animation = beacon_animation(
|
||||||
|
color = col1
|
||||||
|
back_color = col2
|
||||||
|
pos = 0
|
||||||
|
beacon_size = strip_len - shutter_size
|
||||||
|
slew_size = 0 + 0
|
||||||
|
priority = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
sequence shutter_seq repeat forever {
|
||||||
|
repeat col1.palette_size times {
|
||||||
|
restart shutter_size
|
||||||
|
play shutter_lr_animation for duration
|
||||||
|
col1.next = 1
|
||||||
|
col2.next = 1
|
||||||
|
}
|
||||||
|
repeat col1.palette_size times {
|
||||||
|
restart shutter_size
|
||||||
|
play shutter_rl_animation for duration
|
||||||
|
col1.next = 1
|
||||||
|
col2.next = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run shutter_seq
|
||||||
|
}
|
||||||
|
|
||||||
|
palette rainbow_with_white = [ red
|
||||||
|
orange
|
||||||
|
yellow
|
||||||
|
green, # comma left on-purpose to test transpiler
|
||||||
|
blue # need for a lighter blue
|
||||||
|
indigo
|
||||||
|
white
|
||||||
|
]
|
||||||
|
|
||||||
|
shutter_bidir(rainbow_with_white, 1.5s)
|
||||||
@ -79,6 +79,8 @@ Unified base class for all visual elements. Inherits from `ParameterizedObject`.
|
|||||||
|
|
||||||
**Special Behavior**: Setting `is_running = true/false` starts/stops the animation.
|
**Special Behavior**: Setting `is_running = true/false` starts/stops the animation.
|
||||||
|
|
||||||
|
**Timing Behavior**: The `start()` method only resets the time origin if the animation was already started previously (i.e., `self.start_time` is not nil). The first actual rendering tick occurs in `update()` or `render()` methods, which initialize `start_time` on first call.
|
||||||
|
|
||||||
**Factory**: `animation.animation(engine)`
|
**Factory**: `animation.animation(engine)`
|
||||||
|
|
||||||
## Value Providers
|
## Value Providers
|
||||||
@ -93,6 +95,8 @@ Base interface for all value providers. Inherits from `ParameterizedObject`.
|
|||||||
|-----------|------|---------|-------------|-------------|
|
|-----------|------|---------|-------------|-------------|
|
||||||
| *(none)* | - | - | - | Base interface has no parameters |
|
| *(none)* | - | - | - | Base interface has no parameters |
|
||||||
|
|
||||||
|
**Timing Behavior**: For value providers, `start()` is typically not called because instances can be embedded in closures. Value providers consider the first call to `produce_value()` as the start of their internal time reference. The `start()` method only resets the time origin if the provider was already started previously (i.e., `self.start_time` is not nil).
|
||||||
|
|
||||||
**Factory**: N/A (base interface)
|
**Factory**: N/A (base interface)
|
||||||
|
|
||||||
### StaticValueProvider
|
### StaticValueProvider
|
||||||
@ -141,6 +145,8 @@ Generates oscillating values using various waveforms. Inherits from `ValueProvid
|
|||||||
- `8` (ELASTIC) - Spring-like overshoot and oscillation
|
- `8` (ELASTIC) - Spring-like overshoot and oscillation
|
||||||
- `9` (BOUNCE) - Ball-like bouncing with decreasing amplitude
|
- `9` (BOUNCE) - Ball-like bouncing with decreasing amplitude
|
||||||
|
|
||||||
|
**Timing Behavior**: The `start_time` is initialized on the first call to `produce_value()`. The `start()` method only resets the time origin if the oscillator was already started previously (i.e., `self.start_time` is not nil).
|
||||||
|
|
||||||
**Factories**: `animation.ramp(engine)`, `animation.sawtooth(engine)`, `animation.linear(engine)`, `animation.triangle(engine)`, `animation.smooth(engine)`, `animation.sine_osc(engine)`, `animation.cosine_osc(engine)`, `animation.square(engine)`, `animation.ease_in(engine)`, `animation.ease_out(engine)`, `animation.elastic(engine)`, `animation.bounce(engine)`, `animation.oscillator_value(engine)`
|
**Factories**: `animation.ramp(engine)`, `animation.sawtooth(engine)`, `animation.linear(engine)`, `animation.triangle(engine)`, `animation.smooth(engine)`, `animation.sine_osc(engine)`, `animation.cosine_osc(engine)`, `animation.square(engine)`, `animation.ease_in(engine)`, `animation.ease_out(engine)`, `animation.elastic(engine)`, `animation.bounce(engine)`, `animation.oscillator_value(engine)`
|
||||||
|
|
||||||
**See Also**: [Oscillation Patterns](OSCILLATION_PATTERNS.md) - Visual examples and usage patterns for oscillation waveforms
|
**See Also**: [Oscillation Patterns](OSCILLATION_PATTERNS.md) - Visual examples and usage patterns for oscillation waveforms
|
||||||
@ -163,14 +169,14 @@ The ClosureValueProvider includes built-in mathematical helper methods that can
|
|||||||
|
|
||||||
| Method | Description | Parameters | Return Type | Example |
|
| 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` |
|
| `min(a, b, ...)` | Minimum of two or more values | `a, b, *args: number` | `number` | `animation._math.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` |
|
| `max(a, b, ...)` | Maximum of two or more values | `a, b, *args: number` | `number` | `animation._math.max(5, 3, 8)` → `8` |
|
||||||
| `abs(x)` | Absolute value | `x: number` | `number` | `self.abs(-5)` → `5` |
|
| `abs(x)` | Absolute value | `x: number` | `number` | `animation._math.abs(-5)` → `5` |
|
||||||
| `round(x)` | Round to nearest integer | `x: number` | `int` | `self.round(3.7)` → `4` |
|
| `round(x)` | Round to nearest integer | `x: number` | `int` | `animation._math.round(3.7)` → `4` |
|
||||||
| `sqrt(x)` | Square root with integer handling | `x: number` | `number` | `self.sqrt(64)` → `128` (for 0-255 range) |
|
| `sqrt(x)` | Square root with integer handling | `x: number` | `number` | `animation._math.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` |
|
| `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` | `animation._math.scale(50, 0, 100, 0, 255)` → `127` |
|
||||||
| `sin(angle)` | Sine function (0-255 input range) | `angle: number` | `int` | `self.sin(64)` → `255` (90°) |
|
| `sin(angle)` | Sine function (0-255 input range) | `angle: number` | `int` | `animation._math.sin(64)` → `255` (90°) |
|
||||||
| `cos(angle)` | Cosine function (0-255 input range) | `angle: number` | `int` | `self.cos(0)` → `-255` (matches oscillator behavior) |
|
| `cos(angle)` | Cosine function (0-255 input range) | `angle: number` | `int` | `animation._math.cos(0)` → `-255` (matches oscillator behavior) |
|
||||||
|
|
||||||
**Mathematical Method Notes:**
|
**Mathematical Method Notes:**
|
||||||
|
|
||||||
|
|||||||
@ -43,10 +43,8 @@ class MyAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Use engine time if not provided
|
# Auto-fix time_ms and start_time
|
||||||
if time_ms == nil
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
time_ms = self.engine.time_ms
|
|
||||||
end
|
|
||||||
|
|
||||||
# Use virtual parameter access - automatically resolves ValueProviders
|
# Use virtual parameter access - automatically resolves ValueProviders
|
||||||
var param1 = self.my_param1
|
var param1 = self.my_param1
|
||||||
@ -277,6 +275,9 @@ def render(frame, time_ms)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
# Get frame dimensions
|
# Get frame dimensions
|
||||||
var width = frame.width
|
var width = frame.width
|
||||||
var height = frame.height # Usually 1 for LED strips
|
var height = frame.height # Usually 1 for LED strips
|
||||||
@ -372,7 +373,9 @@ class BeaconAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Use engine time if not provided
|
# Auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
if time_ms == nil
|
if time_ms == nil
|
||||||
time_ms = self.engine.time_ms
|
time_ms = self.engine.time_ms
|
||||||
end
|
end
|
||||||
@ -538,7 +541,7 @@ anim.pos = 5
|
|||||||
anim.beacon_size = 3
|
anim.beacon_size = 3
|
||||||
|
|
||||||
engine.add(anim) # Unified method for animations and sequence managers
|
engine.add(anim) # Unified method for animations and sequence managers
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
# Let it run for a few seconds
|
# Let it run for a few seconds
|
||||||
tasmota.delay(3000)
|
tasmota.delay(3000)
|
||||||
|
|||||||
@ -75,7 +75,6 @@ The following keywords are reserved and cannot be used as identifiers:
|
|||||||
- `times` - Loop count specifier
|
- `times` - Loop count specifier
|
||||||
- `for` - Duration specifier
|
- `for` - Duration specifier
|
||||||
- `run` - Execute animation or sequence
|
- `run` - Execute animation or sequence
|
||||||
- `reset` - Reset value provider or animation to initial state
|
|
||||||
- `restart` - Restart value provider or animation from beginning
|
- `restart` - Restart value provider or animation from beginning
|
||||||
|
|
||||||
**Easing Keywords:**
|
**Easing Keywords:**
|
||||||
@ -536,10 +535,10 @@ test.opacity = min(255, max(50, scale(sqrt(strip_len), 0, 16, 100, 255)))
|
|||||||
- **Integer Optimization**: `sqrt()` function automatically handles integer scaling for 0-255 range values
|
- **Integer Optimization**: `sqrt()` function automatically handles integer scaling for 0-255 range values
|
||||||
- **Trigonometric Range**: `sin()` and `cos()` use 0-255 input range (mapped to 0-360°) and return -255 to 255 output range
|
- **Trigonometric Range**: `sin()` and `cos()` 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
|
- **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
|
- **Closure Context**: In computed parameters, mathematical functions are called as `animation._math.<function>()` in the generated closure context
|
||||||
|
|
||||||
**How It Works:**
|
**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.
|
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 `animation._math.` in the closure context to access the ClosureValueProvider's mathematical methods.
|
||||||
|
|
||||||
**User Functions in Computed Parameters:**
|
**User Functions in Computed Parameters:**
|
||||||
User-defined functions can also be used in computed parameter expressions, providing powerful custom effects. User functions must be called with the `user.` prefix:
|
User-defined functions can also be used in computed parameter expressions, providing powerful custom effects. User functions must be called with the `user.` prefix:
|
||||||
@ -785,31 +784,32 @@ sequence cylon_eye {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Reset and Restart Statements
|
#### Restart Statements
|
||||||
|
|
||||||
Reset and restart statements allow you to reset value providers and animations to their initial state during sequence execution:
|
Restart statements allow you to restart value providers and animations from their initial state during sequence execution:
|
||||||
|
|
||||||
```berry
|
```berry
|
||||||
reset value_provider_name # Reset value provider to initial state
|
restart value_provider_name # Restart value provider from beginning
|
||||||
restart animation_name # Restart animation from beginning
|
restart animation_name # Restart animation from beginning
|
||||||
```
|
```
|
||||||
|
|
||||||
**Reset Statement:**
|
|
||||||
- Resets value providers (oscillators, color cycles, etc.) to their initial state
|
|
||||||
- Calls the `start()` method on the value provider
|
|
||||||
- Useful for synchronizing oscillators or restarting color cycles
|
|
||||||
|
|
||||||
**Restart Statement:**
|
**Restart Statement:**
|
||||||
|
- Restarts value providers (oscillators, color cycles, etc.) from their initial state
|
||||||
- Restarts animations from their beginning state
|
- Restarts animations from their beginning state
|
||||||
- Calls the `start()` method on the animation
|
- Calls the `start()` method on the value provider or animation, which resets the time origin only if the object was already started previously
|
||||||
- Useful for restarting complex animations or synchronizing multiple animations
|
- Useful for synchronizing oscillators, restarting color cycles, or restarting complex animations
|
||||||
|
|
||||||
|
**Timing Behavior:**
|
||||||
|
- The `start()` method only resets the time origin if `self.start_time` is not nil (i.e., the object was already started)
|
||||||
|
- For fresh objects, the first call to `update()`, `render()`, or `produce_value()` initializes the time reference
|
||||||
|
- This prevents premature time initialization and ensures proper timing behavior
|
||||||
|
|
||||||
**Examples:**
|
**Examples:**
|
||||||
```berry
|
```berry
|
||||||
# Reset oscillators for synchronized movement
|
# Restart oscillators for synchronized movement
|
||||||
sequence sync_demo {
|
sequence sync_demo {
|
||||||
play wave_anim for 3s
|
play wave_anim for 3s
|
||||||
reset position_osc # Reset oscillator to start position
|
restart position_osc # Restart oscillator time origin
|
||||||
play wave_anim for 3s
|
play wave_anim for 3s
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -980,7 +980,7 @@ animation.register_user_function('pulse_effect', pulse_effect_template)
|
|||||||
- Templates don't return values - they add animations directly to the engine
|
- Templates don't return values - they add animations directly to the engine
|
||||||
- Multiple `run` statements in templates add multiple animations
|
- Multiple `run` statements in templates add multiple animations
|
||||||
- Templates can be called multiple times to create multiple instances
|
- Templates can be called multiple times to create multiple instances
|
||||||
- `engine.start()` is automatically called when templates are used at the top level
|
- `engine.run()` is automatically called when templates are used at the top level
|
||||||
|
|
||||||
## Execution Statements
|
## Execution Statements
|
||||||
|
|
||||||
@ -1293,13 +1293,12 @@ property_assignment = identifier "." identifier "=" expression ;
|
|||||||
(* Sequences *)
|
(* Sequences *)
|
||||||
sequence = "sequence" identifier [ "repeat" ( expression "times" | "forever" ) ] "{" sequence_body "}" ;
|
sequence = "sequence" identifier [ "repeat" ( expression "times" | "forever" ) ] "{" sequence_body "}" ;
|
||||||
sequence_body = { sequence_statement } ;
|
sequence_body = { sequence_statement } ;
|
||||||
sequence_statement = play_stmt | wait_stmt | repeat_stmt | sequence_assignment | reset_stmt | restart_stmt ;
|
sequence_statement = play_stmt | wait_stmt | repeat_stmt | sequence_assignment | restart_stmt ;
|
||||||
|
|
||||||
play_stmt = "play" identifier [ "for" time_expression ] ;
|
play_stmt = "play" identifier [ "for" time_expression ] ;
|
||||||
wait_stmt = "wait" time_expression ;
|
wait_stmt = "wait" time_expression ;
|
||||||
repeat_stmt = "repeat" ( expression "times" | "forever" ) "{" sequence_body "}" ;
|
repeat_stmt = "repeat" ( expression "times" | "forever" ) "{" sequence_body "}" ;
|
||||||
sequence_assignment = identifier "." identifier "=" expression ;
|
sequence_assignment = identifier "." identifier "=" expression ;
|
||||||
reset_stmt = "reset" identifier ;
|
|
||||||
restart_stmt = "restart" identifier ;
|
restart_stmt = "restart" identifier ;
|
||||||
|
|
||||||
(* Templates *)
|
(* Templates *)
|
||||||
|
|||||||
@ -255,9 +255,9 @@ import "user_functions"
|
|||||||
var test_ = animation.solid(engine)
|
var test_ = animation.solid(engine)
|
||||||
test_.color = 0xFF0000FF
|
test_.color = 0xFF0000FF
|
||||||
test_.opacity = animation.create_closure_value(engine,
|
test_.opacity = animation.create_closure_value(engine,
|
||||||
def (self) return animation.get_user_function('rand_demo')(self.engine) end)
|
def (engine) return animation.get_user_function('rand_demo')(engine) end)
|
||||||
engine.add(test_)
|
engine.add(test_)
|
||||||
engine.start()
|
engine.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
## Advanced DSL Features
|
## Advanced DSL Features
|
||||||
@ -291,7 +291,7 @@ def pulse_effect(engine, color, speed)
|
|||||||
pulse_.color = color
|
pulse_.color = color
|
||||||
pulse_.period = speed
|
pulse_.period = speed
|
||||||
engine.add(pulse_)
|
engine.add(pulse_)
|
||||||
engine.start()
|
engine.run()
|
||||||
end
|
end
|
||||||
|
|
||||||
animation.register_user_function("pulse_effect", pulse_effect)
|
animation.register_user_function("pulse_effect", pulse_effect)
|
||||||
@ -347,7 +347,7 @@ def comet_chase(engine, trail_color, bg_color, chase_speed)
|
|||||||
comet_.speed = chase_speed
|
comet_.speed = chase_speed
|
||||||
engine.add(background_)
|
engine.add(background_)
|
||||||
engine.add(comet_)
|
engine.add(comet_)
|
||||||
engine.start()
|
engine.run()
|
||||||
end
|
end
|
||||||
|
|
||||||
animation.register_user_function("comet_chase", comet_chase)
|
animation.register_user_function("comet_chase", comet_chase)
|
||||||
|
|||||||
@ -173,7 +173,7 @@ sequence demo {
|
|||||||
run demo
|
run demo
|
||||||
```
|
```
|
||||||
|
|
||||||
### 11. Reset and Restart in Sequences
|
### 11. Restart in Sequences
|
||||||
```berry
|
```berry
|
||||||
# Create oscillator and animation
|
# Create oscillator and animation
|
||||||
set wave_osc = triangle(min_value=0, max_value=29, period=4s)
|
set wave_osc = triangle(min_value=0, max_value=29, period=4s)
|
||||||
@ -181,9 +181,9 @@ animation wave = beacon_animation(color=blue, pos=wave_osc, beacon_size=5)
|
|||||||
|
|
||||||
sequence sync_demo {
|
sequence sync_demo {
|
||||||
play wave for 3s
|
play wave for 3s
|
||||||
reset wave_osc # Reset oscillator to start position
|
restart wave_osc # Restart oscillator time origin (if already started)
|
||||||
play wave for 3s # Wave starts from beginning again
|
play wave for 3s # Wave starts from beginning again
|
||||||
restart wave # Restart animation from initial state
|
restart wave # Restart animation time origin (if already started)
|
||||||
play wave for 3s
|
play wave for 3s
|
||||||
}
|
}
|
||||||
run sync_demo
|
run sync_demo
|
||||||
@ -199,7 +199,7 @@ sequence breathing_cycle {
|
|||||||
play pulse for 500ms
|
play pulse for 500ms
|
||||||
pulse.opacity = brightness # Apply breathing effect
|
pulse.opacity = brightness # Apply breathing effect
|
||||||
wait 200ms
|
wait 200ms
|
||||||
pulse.opacity = 255 # Reset to full brightness
|
pulse.opacity = 255 # Return to full brightness
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
run breathing_cycle
|
run breathing_cycle
|
||||||
|
|||||||
@ -53,10 +53,10 @@ transpile()
|
|||||||
│ │ ├── process_play_statement_fluent()
|
│ │ ├── process_play_statement_fluent()
|
||||||
│ │ ├── process_wait_statement_fluent()
|
│ │ ├── process_wait_statement_fluent()
|
||||||
│ │ ├── process_log_statement_fluent()
|
│ │ ├── process_log_statement_fluent()
|
||||||
│ │ ├── process_reset_restart_statement_fluent()
|
│ │ ├── process_restart_statement_fluent()
|
||||||
│ │ └── process_sequence_assignment_fluent()
|
│ │ └── process_sequence_assignment_fluent()
|
||||||
│ ├── process_import() (direct Berry import generation)
|
│ ├── process_import() (direct Berry import generation)
|
||||||
│ ├── process_run() (collect for single engine.start())
|
│ ├── process_run() (collect for single engine.run())
|
||||||
│ └── process_property_assignment()
|
│ └── process_property_assignment()
|
||||||
└── generate_engine_start() (single call for all run statements)
|
└── generate_engine_start() (single call for all run statements)
|
||||||
```
|
```
|
||||||
@ -141,7 +141,7 @@ process_value(context)
|
|||||||
│ │ └── process_primary_expression(context, is_top_level, raw_mode)
|
│ │ └── process_primary_expression(context, is_top_level, raw_mode)
|
||||||
│ │ ├── Parenthesized expression → recursive call
|
│ │ ├── Parenthesized expression → recursive call
|
||||||
│ │ ├── Function call handling:
|
│ │ ├── Function call handling:
|
||||||
│ │ │ ├── Raw mode: mathematical functions → self.method()
|
│ │ │ ├── Raw mode: mathematical functions → animation._math.method()
|
||||||
│ │ │ ├── Raw mode: template calls → template_func(self.engine, ...)
|
│ │ │ ├── Raw mode: template calls → template_func(self.engine, ...)
|
||||||
│ │ │ ├── Regular mode: process_function_call() or process_nested_function_call()
|
│ │ │ ├── Regular mode: process_function_call() or process_nested_function_call()
|
||||||
│ │ │ └── Simple function detection → _is_simple_function_call()
|
│ │ │ └── Simple function detection → _is_simple_function_call()
|
||||||
@ -199,11 +199,11 @@ is_computed_expression_string(expr_str)
|
|||||||
|
|
||||||
create_computation_closure_from_string(expr_str)
|
create_computation_closure_from_string(expr_str)
|
||||||
├── transform_expression_for_closure()
|
├── transform_expression_for_closure()
|
||||||
│ ├── Sequential step 1: Transform mathematical functions → self.method()
|
│ ├── Sequential step 1: Transform mathematical functions → animation._math.method()
|
||||||
│ │ ├── Use dynamic introspection with is_math_method()
|
│ │ ├── Use dynamic introspection with is_math_method()
|
||||||
│ │ ├── Check for existing "self." prefix
|
│ │ ├── Check for existing "self." prefix /// TODO NOT SURE IT STILL EXISTS
|
||||||
│ │ └── Only transform if not already prefixed
|
│ │ └── Only transform if not already prefixed
|
||||||
│ ├── Sequential step 2: Transform user variables → self.resolve(var_)
|
│ ├── Sequential step 2: Transform user variables → animation.resolve(var_)
|
||||||
│ │ ├── Find variables ending with _
|
│ │ ├── Find variables ending with _
|
||||||
│ │ ├── Check for existing resolve() calls
|
│ │ ├── Check for existing resolve() calls
|
||||||
│ │ ├── Avoid double-wrapping
|
│ │ ├── Avoid double-wrapping
|
||||||
@ -290,7 +290,7 @@ _validate_value_provider_reference(object_name, context)
|
|||||||
├── Check if symbol exists using validate_symbol_reference()
|
├── Check if symbol exists using validate_symbol_reference()
|
||||||
├── Check symbol_table markers for type information
|
├── Check symbol_table markers for type information
|
||||||
├── Validate instance types using isinstance()
|
├── Validate instance types using isinstance()
|
||||||
├── Ensure only value providers/animations can be reset/restarted
|
├── Ensure only value providers/animations can be restarted
|
||||||
└── Provide detailed error messages for invalid types
|
└── Provide detailed error messages for invalid types
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -331,14 +331,14 @@ Dynamic expressions are wrapped in closures with **mathematical function support
|
|||||||
# DSL: animation.opacity = strip_length() / 2 + 50
|
# DSL: animation.opacity = strip_length() / 2 + 50
|
||||||
# Generated:
|
# Generated:
|
||||||
animation.opacity = animation.create_closure_value(engine,
|
animation.opacity = animation.create_closure_value(engine,
|
||||||
def (self) return self.resolve(strip_length_(engine)) / 2 + 50 end)
|
def (self) return animation.resolve(strip_length_(engine)) / 2 + 50 end)
|
||||||
|
|
||||||
# DSL: animation.opacity = max(100, min(255, user.rand_demo() + 50))
|
# DSL: animation.opacity = max(100, min(255, user.rand_demo() + 50))
|
||||||
# Generated:
|
# Generated:
|
||||||
animation.opacity = animation.create_closure_value(engine,
|
animation.opacity = animation.create_closure_value(engine,
|
||||||
def (self) return self.max(100, self.min(255, animation.get_user_function('rand_demo')(self.engine) + 50)) end)
|
def (self) return animation._math.max(100, animation._math.min(255, animation.get_user_function('rand_demo')(engine) + 50)) end)
|
||||||
|
|
||||||
# Mathematical functions are automatically detected and prefixed with self.
|
# Mathematical functions are automatically detected and prefixed with animation._math.
|
||||||
# User functions are wrapped with animation.get_user_function() calls
|
# User functions are wrapped with animation.get_user_function() calls
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -78,6 +78,12 @@ except .. as e, msg
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Timing Behavior Note:**
|
||||||
|
The framework has updated timing behavior where:
|
||||||
|
- The `start()` method only resets the time origin if the animation/value provider was already started previously
|
||||||
|
- The first actual rendering tick occurs in `update()`, `render()`, or `produce_value()` methods
|
||||||
|
- This ensures proper timing initialization and prevents premature time reference setting
|
||||||
|
|
||||||
**Common Solutions:**
|
**Common Solutions:**
|
||||||
|
|
||||||
1. **Missing Strip Declaration:**
|
1. **Missing Strip Declaration:**
|
||||||
@ -761,7 +767,7 @@ var engine = animation.create_engine(strip)
|
|||||||
var red_anim = animation.solid(engine)
|
var red_anim = animation.solid(engine)
|
||||||
red_anim.color = 0xFFFF0000
|
red_anim.color = 0xFFFF0000
|
||||||
engine.add(red_anim)
|
engine.add(red_anim)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
# If basic strip works but animation doesn't, check framework setup
|
# If basic strip works but animation doesn't, check framework setup
|
||||||
```
|
```
|
||||||
@ -831,7 +837,7 @@ var engine = animation.create_engine(strip, true) # debug=true
|
|||||||
var anim = animation.solid(engine)
|
var anim = animation.solid(engine)
|
||||||
anim.color = 0xFFFF0000
|
anim.color = 0xFFFF0000
|
||||||
engine.add(anim)
|
engine.add(anim)
|
||||||
engine.start()
|
engine.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step-by-Step Testing
|
### Step-by-Step Testing
|
||||||
@ -856,7 +862,7 @@ engine.add(anim)
|
|||||||
print("Animation count:", engine.size())
|
print("Animation count:", engine.size())
|
||||||
|
|
||||||
print("5. Starting engine...")
|
print("5. Starting engine...")
|
||||||
engine.start()
|
engine.run()
|
||||||
print("Engine active:", engine.is_active())
|
print("Engine active:", engine.is_active())
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -490,7 +490,7 @@ When you use user functions in computed parameters:
|
|||||||
|
|
||||||
1. **Automatic Detection**: The transpiler automatically detects user functions in expressions
|
1. **Automatic Detection**: The transpiler automatically detects user functions in expressions
|
||||||
2. **Single Closure**: The entire expression is wrapped in a single efficient closure
|
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
|
3. **Engine Access**: User functions receive `engine` in the closure context
|
||||||
4. **Mixed Operations**: User functions work seamlessly with mathematical functions and arithmetic
|
4. **Mixed Operations**: User functions work seamlessly with mathematical functions and arithmetic
|
||||||
|
|
||||||
**Generated Code Example:**
|
**Generated Code Example:**
|
||||||
@ -502,8 +502,8 @@ animation.opacity = max(100, user.breathing(red, 2000))
|
|||||||
**Transpiles to:**
|
**Transpiles to:**
|
||||||
```berry
|
```berry
|
||||||
animation.opacity = animation.create_closure_value(engine,
|
animation.opacity = animation.create_closure_value(engine,
|
||||||
def (self, param_name, time_ms)
|
def (engine, param_name, time_ms)
|
||||||
return (self.max(100, animation.get_user_function('breathing')(self.engine, 0xFFFF0000, 2000)))
|
return (animation._math.max(100, animation.get_user_function('breathing')(engine, 0xFFFF0000, 2000)))
|
||||||
end)
|
end)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -57,6 +57,10 @@ end
|
|||||||
# Import core framework components
|
# Import core framework components
|
||||||
# These provide the fundamental architecture for the animation system
|
# These provide the fundamental architecture for the animation system
|
||||||
|
|
||||||
|
# Mathematical functions for use in closures and throughout the framework
|
||||||
|
import "core/math_functions" as math_functions
|
||||||
|
register_to_animation(math_functions)
|
||||||
|
|
||||||
# Base class for parameter management - shared by Animation and ValueProvider
|
# Base class for parameter management - shared by Animation and ValueProvider
|
||||||
import "core/parameterized_object" as parameterized_object
|
import "core/parameterized_object" as parameterized_object
|
||||||
register_to_animation(parameterized_object)
|
register_to_animation(parameterized_object)
|
||||||
|
|||||||
@ -40,10 +40,8 @@ class BeaconAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Use engine time if not provided
|
# Auto-fix time_ms and start_time
|
||||||
if time_ms == nil
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
time_ms = self.engine.time_ms
|
|
||||||
end
|
|
||||||
|
|
||||||
var pixel_size = frame.width
|
var pixel_size = frame.width
|
||||||
# Use virtual parameter access - automatically resolves ValueProviders
|
# Use virtual parameter access - automatically resolves ValueProviders
|
||||||
|
|||||||
@ -84,6 +84,7 @@ class BounceAnimation : animation.animation
|
|||||||
|
|
||||||
# Handle parameter changes
|
# Handle parameter changes
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
if name == "bounce_speed"
|
if name == "bounce_speed"
|
||||||
# Update velocity if speed changed
|
# Update velocity if speed changed
|
||||||
var pixels_per_second = tasmota.scale_uint(value, 0, 255, 0, 20)
|
var pixels_per_second = tasmota.scale_uint(value, 0, 255, 0, 20)
|
||||||
|
|||||||
@ -39,6 +39,7 @@ class BreatheAnimation : animation.animation
|
|||||||
|
|
||||||
# Handle parameter changes - propagate to internal breathe provider
|
# Handle parameter changes - propagate to internal breathe provider
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
# Propagate relevant parameters to the breathe provider
|
# Propagate relevant parameters to the breathe provider
|
||||||
if name == "base_color"
|
if name == "base_color"
|
||||||
self.breathe_provider.base_color = value
|
self.breathe_provider.base_color = value
|
||||||
|
|||||||
@ -36,6 +36,7 @@ class CometAnimation : animation.animation
|
|||||||
|
|
||||||
# Handle parameter changes - reset position when direction changes
|
# Handle parameter changes - reset position when direction changes
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
if name == "direction"
|
if name == "direction"
|
||||||
# Reset position when direction changes
|
# Reset position when direction changes
|
||||||
var strip_length = self.engine.get_strip_length()
|
var strip_length = self.engine.get_strip_length()
|
||||||
|
|||||||
@ -42,10 +42,8 @@ class CrenelPositionAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Use engine time if not provided
|
# Auto-fix time_ms and start_time
|
||||||
if time_ms == nil
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
time_ms = self.engine.time_ms
|
|
||||||
end
|
|
||||||
|
|
||||||
var pixel_size = frame.width
|
var pixel_size = frame.width
|
||||||
|
|
||||||
|
|||||||
@ -52,12 +52,6 @@ class FireAnimation : animation.animation
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Handle parameter changes
|
|
||||||
def on_param_changed(name, value)
|
|
||||||
# No special handling needed - parameters are accessed via virtual members
|
|
||||||
# The default fire palette is set up by factory methods when needed
|
|
||||||
end
|
|
||||||
|
|
||||||
# Simple pseudo-random number generator
|
# Simple pseudo-random number generator
|
||||||
# Uses a linear congruential generator for consistent results
|
# Uses a linear congruential generator for consistent results
|
||||||
def _random()
|
def _random()
|
||||||
@ -226,6 +220,9 @@ class FireAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
var strip_length = self.engine.get_strip_length()
|
var strip_length = self.engine.get_strip_length()
|
||||||
|
|
||||||
# Render each pixel with its current color
|
# Render each pixel with its current color
|
||||||
|
|||||||
@ -42,9 +42,8 @@ class GradientAnimation : animation.animation
|
|||||||
|
|
||||||
# Handle parameter changes
|
# Handle parameter changes
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
# No special handling needed for most parameters
|
super(self).on_param_changed(name, value)
|
||||||
# The virtual parameter system handles storage and validation
|
# TODO maybe be more specific on attribute name
|
||||||
|
|
||||||
# Handle strip length changes from engine
|
# Handle strip length changes from engine
|
||||||
var current_strip_length = self.engine.get_strip_length()
|
var current_strip_length = self.engine.get_strip_length()
|
||||||
if size(self.current_colors) != current_strip_length
|
if size(self.current_colors) != current_strip_length
|
||||||
@ -201,6 +200,9 @@ class GradientAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
var strip_length = self.engine.get_strip_length()
|
var strip_length = self.engine.get_strip_length()
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < strip_length && i < frame.width
|
while i < strip_length && i < frame.width
|
||||||
|
|||||||
@ -87,6 +87,10 @@ class JitterAnimation : animation.animation
|
|||||||
|
|
||||||
# Update animation state
|
# Update animation state
|
||||||
def update(time_ms)
|
def update(time_ms)
|
||||||
|
if !super(self).update(time_ms)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
# Cache parameter values for performance
|
# Cache parameter values for performance
|
||||||
var jitter_frequency = self.jitter_frequency
|
var jitter_frequency = self.jitter_frequency
|
||||||
var source_animation = self.source_animation
|
var source_animation = self.source_animation
|
||||||
@ -232,10 +236,13 @@ class JitterAnimation : animation.animation
|
|||||||
|
|
||||||
# Render jitter to frame buffer
|
# Render jitter to frame buffer
|
||||||
def render(frame, time_ms)
|
def render(frame, time_ms)
|
||||||
if frame == nil
|
if !self.is_running || frame == nil
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
var current_strip_length = self.engine.get_strip_length()
|
var current_strip_length = self.engine.get_strip_length()
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < current_strip_length
|
while i < current_strip_length
|
||||||
|
|||||||
@ -116,6 +116,7 @@ class NoiseAnimation : animation.animation
|
|||||||
|
|
||||||
# Handle parameter changes
|
# Handle parameter changes
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
if name == "seed"
|
if name == "seed"
|
||||||
self._init_noise_table()
|
self._init_noise_table()
|
||||||
end
|
end
|
||||||
@ -236,6 +237,9 @@ class NoiseAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
var strip_length = self.engine.get_strip_length()
|
var strip_length = self.engine.get_strip_length()
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < strip_length
|
while i < strip_length
|
||||||
|
|||||||
@ -83,6 +83,9 @@ class PalettePatternAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
# Calculate elapsed time since animation started
|
# Calculate elapsed time since animation started
|
||||||
var elapsed = time_ms - self.start_time
|
var elapsed = time_ms - self.start_time
|
||||||
|
|
||||||
@ -102,10 +105,8 @@ class PalettePatternAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Use provided time or default to engine time
|
# Auto-fix time_ms and start_time
|
||||||
if time_ms == nil
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
time_ms = self.engine.time_ms
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get current parameter values (cached for performance)
|
# Get current parameter values (cached for performance)
|
||||||
var color_source = self.get_param('color_source') # use get_param to avoid resolving of color_provider
|
var color_source = self.get_param('color_source') # use get_param to avoid resolving of color_provider
|
||||||
@ -139,14 +140,13 @@ class PalettePatternAnimation : animation.animation
|
|||||||
|
|
||||||
# Handle parameter changes
|
# Handle parameter changes
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
if name == "pattern_func" || name == "color_source"
|
if name == "pattern_func" || name == "color_source"
|
||||||
# Reinitialize value buffer when pattern or color source changes
|
# Reinitialize value buffer when pattern or color source changes
|
||||||
self._initialize_value_buffer()
|
self._initialize_value_buffer()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# String representation of the animation
|
# String representation of the animation
|
||||||
def tostring()
|
def tostring()
|
||||||
var strip_length = self.engine.get_strip_length()
|
var strip_length = self.engine.get_strip_length()
|
||||||
|
|||||||
@ -85,6 +85,7 @@ class PlasmaAnimation : animation.animation
|
|||||||
|
|
||||||
# Handle parameter changes
|
# Handle parameter changes
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
if name == "color" && value == nil
|
if name == "color" && value == nil
|
||||||
# Reset to default rainbow palette when color is set to nil
|
# Reset to default rainbow palette when color is set to nil
|
||||||
var rainbow_provider = animation.rich_palette(self.engine)
|
var rainbow_provider = animation.rich_palette(self.engine)
|
||||||
@ -190,6 +191,9 @@ class PlasmaAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
var strip_length = self.engine.get_strip_length()
|
var strip_length = self.engine.get_strip_length()
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < strip_length
|
while i < strip_length
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class RichPaletteAnimation : animation.animation
|
|||||||
"transition_type": {"enum": [animation.LINEAR, animation.SINE], "default": animation.SINE},
|
"transition_type": {"enum": [animation.LINEAR, animation.SINE], "default": animation.SINE},
|
||||||
"brightness": {"min": 0, "max": 255, "default": 255},
|
"brightness": {"min": 0, "max": 255, "default": 255},
|
||||||
"range_min": {"default": 0},
|
"range_min": {"default": 0},
|
||||||
"range_max": {"default": 100}
|
"range_max": {"default": 255}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Initialize a new RichPaletteAnimation
|
# Initialize a new RichPaletteAnimation
|
||||||
@ -45,6 +45,7 @@ class RichPaletteAnimation : animation.animation
|
|||||||
# @param name: string - Name of the parameter that changed
|
# @param name: string - Name of the parameter that changed
|
||||||
# @param value: any - New value of the parameter
|
# @param value: any - New value of the parameter
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
# Forward rich palette parameters to internal color provider
|
# Forward rich palette parameters to internal color provider
|
||||||
if name == "palette" || name == "cycle_period" || name == "transition_type" ||
|
if name == "palette" || name == "cycle_period" || name == "transition_type" ||
|
||||||
name == "brightness" || name == "range_min" || name == "range_max"
|
name == "brightness" || name == "range_min" || name == "range_max"
|
||||||
|
|||||||
@ -48,12 +48,6 @@ class ScaleAnimation : animation.animation
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Handle parameter changes
|
|
||||||
def on_param_changed(name, value)
|
|
||||||
# No special handling needed for most parameters
|
|
||||||
# Buffers are managed through engine strip length changes
|
|
||||||
end
|
|
||||||
|
|
||||||
# Start/restart the animation
|
# Start/restart the animation
|
||||||
def start(time_ms)
|
def start(time_ms)
|
||||||
# Call parent start first (handles ValueProvider propagation)
|
# Call parent start first (handles ValueProvider propagation)
|
||||||
@ -73,6 +67,10 @@ class ScaleAnimation : animation.animation
|
|||||||
|
|
||||||
# Update animation state
|
# Update animation state
|
||||||
def update(time_ms)
|
def update(time_ms)
|
||||||
|
if !super(self).update(time_ms)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
# Cache parameter values for performance
|
# Cache parameter values for performance
|
||||||
var current_scale_speed = self.scale_speed
|
var current_scale_speed = self.scale_speed
|
||||||
var current_scale_mode = self.scale_mode
|
var current_scale_mode = self.scale_mode
|
||||||
@ -240,6 +238,9 @@ class ScaleAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
var current_strip_length = self.engine.get_strip_length()
|
var current_strip_length = self.engine.get_strip_length()
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < current_strip_length
|
while i < current_strip_length
|
||||||
|
|||||||
@ -45,6 +45,7 @@ class ShiftAnimation : animation.animation
|
|||||||
|
|
||||||
# Handle parameter changes
|
# Handle parameter changes
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
# Re-initialize buffers if strip length might have changed
|
# Re-initialize buffers if strip length might have changed
|
||||||
if name == "source_animation"
|
if name == "source_animation"
|
||||||
self._initialize_buffers()
|
self._initialize_buffers()
|
||||||
@ -53,7 +54,9 @@ class ShiftAnimation : animation.animation
|
|||||||
|
|
||||||
# Update animation state
|
# Update animation state
|
||||||
def update(time_ms)
|
def update(time_ms)
|
||||||
super(self).update(time_ms)
|
if !super(self).update(time_ms)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
# Cache parameter values for performance
|
# Cache parameter values for performance
|
||||||
var current_shift_speed = self.shift_speed
|
var current_shift_speed = self.shift_speed
|
||||||
@ -152,6 +155,9 @@ class ShiftAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
var current_strip_length = self.engine.get_strip_length()
|
var current_strip_length = self.engine.get_strip_length()
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < current_strip_length
|
while i < current_strip_length
|
||||||
|
|||||||
@ -92,7 +92,9 @@ class SparkleAnimation : animation.animation
|
|||||||
|
|
||||||
# Update animation state
|
# Update animation state
|
||||||
def update(time_ms)
|
def update(time_ms)
|
||||||
super(self).update(time_ms)
|
if !super(self).update(time_ms)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
# Update at approximately 30 FPS
|
# Update at approximately 30 FPS
|
||||||
var update_interval = 33 # ~30 FPS
|
var update_interval = 33 # ~30 FPS
|
||||||
@ -199,6 +201,9 @@ class SparkleAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
var current_strip_length = self.engine.get_strip_length()
|
var current_strip_length = self.engine.get_strip_length()
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < current_strip_length
|
while i < current_strip_length
|
||||||
|
|||||||
@ -61,6 +61,7 @@ class TwinkleAnimation : animation.animation
|
|||||||
|
|
||||||
# Handle parameter changes
|
# Handle parameter changes
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
if name == "twinkle_speed"
|
if name == "twinkle_speed"
|
||||||
# Handle twinkle_speed - can be Hz (1-20) or period in ms (50-5000)
|
# Handle twinkle_speed - can be Hz (1-20) or period in ms (50-5000)
|
||||||
if value >= 50 # Assume it's period in milliseconds
|
if value >= 50 # Assume it's period in milliseconds
|
||||||
@ -103,10 +104,8 @@ class TwinkleAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Use engine time if not provided
|
# Auto-fix time_ms and start_time
|
||||||
if time_ms == nil
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
time_ms = self.engine.time_ms
|
|
||||||
end
|
|
||||||
|
|
||||||
# Access parameters via virtual members
|
# Access parameters via virtual members
|
||||||
var twinkle_speed = self.twinkle_speed
|
var twinkle_speed = self.twinkle_speed
|
||||||
@ -199,10 +198,8 @@ class TwinkleAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Use engine time if not provided
|
# Auto-fix time_ms and start_time
|
||||||
if time_ms == nil
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
time_ms = self.engine.time_ms
|
|
||||||
end
|
|
||||||
|
|
||||||
var strip_length = self.engine.get_strip_length()
|
var strip_length = self.engine.get_strip_length()
|
||||||
|
|
||||||
|
|||||||
@ -87,6 +87,7 @@ class WaveAnimation : animation.animation
|
|||||||
|
|
||||||
# Handle parameter changes
|
# Handle parameter changes
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
if name == "wave_type"
|
if name == "wave_type"
|
||||||
self._init_wave_table() # Regenerate wave table when wave type changes
|
self._init_wave_table() # Regenerate wave table when wave type changes
|
||||||
end
|
end
|
||||||
@ -199,6 +200,9 @@ class WaveAnimation : animation.animation
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
var strip_length = self.engine.get_strip_length()
|
var strip_length = self.engine.get_strip_length()
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < strip_length
|
while i < strip_length
|
||||||
|
|||||||
@ -9,14 +9,11 @@
|
|||||||
|
|
||||||
class Animation : animation.parameterized_object
|
class Animation : animation.parameterized_object
|
||||||
# Non-parameter instance variables only
|
# Non-parameter instance variables only
|
||||||
var start_time # Time when animation started (ms) (int)
|
|
||||||
var current_time # Current animation time (ms) (int)
|
|
||||||
var opacity_frame # Frame buffer for opacity animation rendering
|
var opacity_frame # Frame buffer for opacity animation rendering
|
||||||
|
|
||||||
# Parameter definitions
|
# Parameter definitions
|
||||||
static var PARAMS = {
|
static var PARAMS = {
|
||||||
"name": {"type": "string", "default": "animation"}, # Optional name for the animation
|
"name": {"type": "string", "default": "animation"}, # Optional name for the animation
|
||||||
"is_running": {"type": "bool", "default": false}, # Whether the animation is active
|
|
||||||
"priority": {"min": 0, "default": 10}, # Rendering priority (higher = on top, 0-255)
|
"priority": {"min": 0, "default": 10}, # Rendering priority (higher = on top, 0-255)
|
||||||
"duration": {"min": 0, "default": 0}, # Animation duration in ms (0 = infinite)
|
"duration": {"min": 0, "default": 0}, # Animation duration in ms (0 = infinite)
|
||||||
"loop": {"type": "bool", "default": false}, # Whether to loop when duration is reached
|
"loop": {"type": "bool", "default": false}, # Whether to loop when duration is reached
|
||||||
@ -31,60 +28,7 @@ class Animation : animation.parameterized_object
|
|||||||
# Initialize parameter system with engine
|
# Initialize parameter system with engine
|
||||||
super(self).init(engine)
|
super(self).init(engine)
|
||||||
|
|
||||||
# Initialize non-parameter instance variables
|
# Initialize non-parameter instance variables (none currently)
|
||||||
self.start_time = 0
|
|
||||||
self.current_time = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
# Start/restart the animation (make it active and reset timing)
|
|
||||||
#
|
|
||||||
# @param start_time: int - Optional start time in milliseconds
|
|
||||||
# @return self for method chaining
|
|
||||||
def start(start_time)
|
|
||||||
# Set is_running directly in values map to avoid infinite loop
|
|
||||||
self.values["is_running"] = true
|
|
||||||
var actual_start_time = (start_time != nil) ? start_time : self.engine.time_ms
|
|
||||||
self.start_time = actual_start_time
|
|
||||||
self.current_time = self.start_time
|
|
||||||
|
|
||||||
# Start/restart all value providers in parameters
|
|
||||||
self._start_value_providers(actual_start_time)
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
# Helper method to start/restart all value providers in parameters
|
|
||||||
#
|
|
||||||
# @param time_ms: int - Time to pass to value provider start methods
|
|
||||||
def _start_value_providers(time_ms)
|
|
||||||
# Iterate through all parameter values
|
|
||||||
for param_value : self.values
|
|
||||||
# Check if the parameter value is a value provider
|
|
||||||
if animation.is_value_provider(param_value)
|
|
||||||
# Call start method if it exists (acts as restart)
|
|
||||||
param_value.start(time_ms)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Handle parameter changes - specifically for is_running to control start/stop
|
|
||||||
#
|
|
||||||
# @param name: string - Parameter name that changed
|
|
||||||
# @param value: any - New parameter value
|
|
||||||
def on_param_changed(name, value)
|
|
||||||
if name == "is_running"
|
|
||||||
if value == true
|
|
||||||
# Start the animation (but avoid infinite loop by not setting is_running again)
|
|
||||||
var actual_start_time = self.engine.time_ms
|
|
||||||
self.start_time = actual_start_time
|
|
||||||
self.current_time = self.start_time
|
|
||||||
# Start/restart all value providers in parameters
|
|
||||||
self._start_value_providers(actual_start_time)
|
|
||||||
# elif value == false
|
|
||||||
# Stop the animation - just set the internal state
|
|
||||||
# (is_running is already set to false by the parameter system)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update animation state based on current time
|
# Update animation state based on current time
|
||||||
@ -93,14 +37,15 @@ class Animation : animation.parameterized_object
|
|||||||
# @param time_ms: int - Current time in milliseconds
|
# @param time_ms: int - Current time in milliseconds
|
||||||
# @return bool - True if animation is still running, false if completed
|
# @return bool - True if animation is still running, false if completed
|
||||||
def update(time_ms)
|
def update(time_ms)
|
||||||
|
# auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
# Access is_running via virtual member
|
# Access is_running via virtual member
|
||||||
var current_is_running = self.is_running
|
var current_is_running = self.is_running
|
||||||
if !current_is_running
|
if !current_is_running
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
self.current_time = time_ms
|
var elapsed = time_ms - self.start_time
|
||||||
var elapsed = self.current_time - self.start_time
|
|
||||||
|
|
||||||
# Access parameters via virtual members
|
# Access parameters via virtual members
|
||||||
var current_duration = self.duration
|
var current_duration = self.duration
|
||||||
@ -131,17 +76,16 @@ class Animation : animation.parameterized_object
|
|||||||
# @param time_ms: int - Current time in milliseconds
|
# @param time_ms: int - Current time in milliseconds
|
||||||
# @return bool - True if frame was modified, false otherwise
|
# @return bool - True if frame was modified, false otherwise
|
||||||
def render(frame, time_ms)
|
def render(frame, time_ms)
|
||||||
|
# auto-fix time_ms and start_time
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
# Access is_running via virtual member
|
# Access is_running via virtual member
|
||||||
var current_is_running = self.is_running
|
var current_is_running = self.is_running
|
||||||
if !current_is_running || frame == nil
|
if !current_is_running || frame == nil
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Use engine time if not provided
|
|
||||||
time_ms = (time_ms != nil) ? time_ms : self.engine.time_ms
|
|
||||||
|
|
||||||
# Update animation state
|
# Update animation state
|
||||||
self.update(time_ms)
|
self.update(time_ms) # TODO IS UPDATE NOT ALREADY CALLED BY ENGINE?
|
||||||
|
|
||||||
# Access parameters via virtual members (auto-resolves ValueProviders)
|
# Access parameters via virtual members (auto-resolves ValueProviders)
|
||||||
var current_color = self.color
|
var current_color = self.color
|
||||||
@ -159,6 +103,7 @@ class Animation : animation.parameterized_object
|
|||||||
# @param frame: FrameBuffer - The frame buffer to render to
|
# @param frame: FrameBuffer - The frame buffer to render to
|
||||||
# @param time_ms: int - Current time in milliseconds
|
# @param time_ms: int - Current time in milliseconds
|
||||||
def post_render(frame, time_ms)
|
def post_render(frame, time_ms)
|
||||||
|
# no need to auto-fix time_ms and start_time
|
||||||
# Handle opacity - can be number, frame buffer, or animation
|
# Handle opacity - can be number, frame buffer, or animation
|
||||||
var current_opacity = self.opacity
|
var current_opacity = self.opacity
|
||||||
self._apply_opacity(frame, current_opacity, time_ms)
|
self._apply_opacity(frame, current_opacity, time_ms)
|
||||||
|
|||||||
@ -42,10 +42,10 @@ class AnimationEngine
|
|||||||
self.render_needed = false
|
self.render_needed = false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Start the animation engine
|
# Run the animation engine
|
||||||
#
|
#
|
||||||
# @return self for method chaining
|
# @return self for method chaining
|
||||||
def start()
|
def run()
|
||||||
if !self.is_running
|
if !self.is_running
|
||||||
var now = tasmota.millis()
|
var now = tasmota.millis()
|
||||||
self.is_running = true
|
self.is_running = true
|
||||||
|
|||||||
120
lib/libesp32/berry_animation/src/core/math_functions.be
Normal file
120
lib/libesp32/berry_animation/src/core/math_functions.be
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# Mathematical Functions for Animation Framework
|
||||||
|
#
|
||||||
|
# This module provides mathematical functions that can be used in closures
|
||||||
|
# and throughout the animation framework. These functions are optimized for
|
||||||
|
# the animation use case and handle integer ranges appropriately.
|
||||||
|
|
||||||
|
# This class contains only static functions
|
||||||
|
class AnimationMath
|
||||||
|
# Minimum of two or more values
|
||||||
|
#
|
||||||
|
# @param *args: number - Values to compare
|
||||||
|
# @return number - Minimum value
|
||||||
|
#@ solidify:min,weak
|
||||||
|
static def min(*args)
|
||||||
|
import math
|
||||||
|
return call(math.min, args)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Maximum of two or more values
|
||||||
|
#
|
||||||
|
# @param *args: number - Values to compare
|
||||||
|
# @return number - Maximum value
|
||||||
|
#@ solidify:max,weak
|
||||||
|
static def max(*args)
|
||||||
|
import math
|
||||||
|
return call(math.max, args)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Absolute value
|
||||||
|
#
|
||||||
|
# @param x: number - Input value
|
||||||
|
# @return number - Absolute value
|
||||||
|
#@ solidify:abs,weak
|
||||||
|
static def abs(x)
|
||||||
|
import math
|
||||||
|
return math.abs(x)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Round to nearest integer
|
||||||
|
#
|
||||||
|
# @param x: number - Input value
|
||||||
|
# @return int - Rounded value
|
||||||
|
#@ solidify:round,weak
|
||||||
|
static 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
|
||||||
|
#@ solidify:sqrt,weak
|
||||||
|
static 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
|
||||||
|
#@ solidify:scale,weak
|
||||||
|
static 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
|
||||||
|
#@ solidify:sin,weak
|
||||||
|
static def sin(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
|
||||||
|
#@ solidify:cos,weak
|
||||||
|
static def cos(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
|
||||||
|
end
|
||||||
|
|
||||||
|
# Export only the _math namespace containing all math functions
|
||||||
|
return {
|
||||||
|
'_math': AnimationMath
|
||||||
|
}
|
||||||
@ -11,9 +11,12 @@
|
|||||||
class ParameterizedObject
|
class ParameterizedObject
|
||||||
var values # Map storing all parameter values
|
var values # Map storing all parameter values
|
||||||
var engine # Reference to the animation engine
|
var engine # Reference to the animation engine
|
||||||
|
var start_time # Time when object started (ms) (int), value is set at first call to update() or render()
|
||||||
|
|
||||||
# Static parameter definitions - should be overridden by subclasses
|
# Static parameter definitions - should be overridden by subclasses
|
||||||
static var PARAMS = {}
|
static var PARAMS = {
|
||||||
|
"is_running": {"type": "bool", "default": false} # Whether the object is active
|
||||||
|
}
|
||||||
|
|
||||||
# Initialize parameter system
|
# Initialize parameter system
|
||||||
#
|
#
|
||||||
@ -348,10 +351,47 @@ class ParameterizedObject
|
|||||||
return self._resolve_parameter_value(param_name, time_ms)
|
return self._resolve_parameter_value(param_name, time_ms)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Start the object - placeholder for future implementation
|
# Helper function to make sure both self.start_time and time_ms are valid
|
||||||
#
|
#
|
||||||
|
# If time_ms is nil, replace with time_ms from engine
|
||||||
|
# Then initialize the value for self.start_time if not set already
|
||||||
|
#
|
||||||
|
# @param time_ms: int or nil - Current time in milliseconds
|
||||||
|
# @return time_ms: int (guaranteed)
|
||||||
|
def _fix_time_ms(time_ms)
|
||||||
|
if time_ms == nil
|
||||||
|
time_ms = self.engine.time_ms
|
||||||
|
end
|
||||||
|
if time_ms == nil
|
||||||
|
raise "value_error", "engine.time_ms should not be 'nil'"
|
||||||
|
end
|
||||||
|
if self.start_time == nil
|
||||||
|
self.start_time = time_ms
|
||||||
|
end
|
||||||
|
return time_ms
|
||||||
|
end
|
||||||
|
|
||||||
|
# Start the object - base implementation
|
||||||
|
#
|
||||||
|
# `start(time_ms)` is called whenever an animation is about to be run
|
||||||
|
# by the animation engine directly or via a sequence manager.
|
||||||
|
# For value providers, start is typically not called because instances
|
||||||
|
# can be embedded in closures. So value providers must consider the first
|
||||||
|
# call to `produce_value()` as a start of their internal time reference.
|
||||||
|
# @param start_time: int - Optional start time in milliseconds
|
||||||
# @return self for method chaining
|
# @return self for method chaining
|
||||||
def start(time_ms)
|
def start(time_ms)
|
||||||
|
if time_ms == nil
|
||||||
|
time_ms = self.engine.time_ms
|
||||||
|
end
|
||||||
|
if time_ms == nil
|
||||||
|
raise "value_error", "engine.time_ms should not be 'nil'"
|
||||||
|
end
|
||||||
|
if self.start_time != nil # reset time only if it was already started
|
||||||
|
self.start_time = time_ms
|
||||||
|
end
|
||||||
|
# Set is_running directly in values map to avoid infinite loop
|
||||||
|
self.values["is_running"] = true
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -361,7 +401,16 @@ class ParameterizedObject
|
|||||||
# @param name: string - Parameter name
|
# @param name: string - Parameter name
|
||||||
# @param value: any - New parameter value
|
# @param value: any - New parameter value
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
# Default implementation does nothing
|
if name == "is_running"
|
||||||
|
if value == true
|
||||||
|
# Start the object (but avoid infinite loop by not setting is_running again)
|
||||||
|
# Call start method to handle start_time
|
||||||
|
self.start(nil)
|
||||||
|
elif value == false
|
||||||
|
# Stop the object - just set the internal state
|
||||||
|
# (is_running is already set to false by the parameter system)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Equality operator for object identity comparison
|
# Equality operator for object identity comparison
|
||||||
|
|||||||
@ -197,7 +197,21 @@ class SequenceManager
|
|||||||
|
|
||||||
if step["type"] == "play"
|
if step["type"] == "play"
|
||||||
var anim = step["animation"]
|
var anim = step["animation"]
|
||||||
|
# Check if animation is already in the engine (avoid duplicate adds)
|
||||||
|
var animations = self.engine.get_animations()
|
||||||
|
var already_added = false
|
||||||
|
for existing_anim : animations
|
||||||
|
if existing_anim == anim
|
||||||
|
already_added = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !already_added
|
||||||
self.engine.add(anim)
|
self.engine.add(anim)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Always restart the animation to ensure proper timing
|
||||||
anim.start(current_time)
|
anim.start(current_time)
|
||||||
|
|
||||||
elif step["type"] == "wait"
|
elif step["type"] == "wait"
|
||||||
@ -292,6 +306,25 @@ class SequenceManager
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# CRITICAL FIX: Handle the case where the next step is the SAME animation
|
||||||
|
# This prevents removing and re-adding the same animation, which causes black frames
|
||||||
|
var next_step = nil
|
||||||
|
var is_same_animation = false
|
||||||
|
|
||||||
|
if self.step_index < size(self.steps)
|
||||||
|
next_step = self.steps[self.step_index]
|
||||||
|
if next_step["type"] == "play" && previous_anim != nil
|
||||||
|
is_same_animation = (next_step["animation"] == previous_anim)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_same_animation
|
||||||
|
# Same animation continuing - don't remove/re-add, but DO restart for timing sync
|
||||||
|
self.step_start_time = current_time
|
||||||
|
# CRITICAL: Still need to restart the animation to sync with sequence timing
|
||||||
|
previous_anim.start(current_time)
|
||||||
|
else
|
||||||
|
# Different animation or no next animation
|
||||||
# Start the next animation BEFORE removing the previous one
|
# Start the next animation BEFORE removing the previous one
|
||||||
if self.step_index < size(self.steps)
|
if self.step_index < size(self.steps)
|
||||||
self.execute_current_step(current_time)
|
self.execute_current_step(current_time)
|
||||||
@ -301,6 +334,7 @@ class SequenceManager
|
|||||||
if previous_anim != nil
|
if previous_anim != nil
|
||||||
self.engine.remove(previous_anim)
|
self.engine.remove(previous_anim)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Handle completion
|
# Handle completion
|
||||||
if self.step_index >= size(self.steps)
|
if self.step_index >= size(self.steps)
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
# User-Defined Functions Registry for Berry Animation Framework
|
# User-Defined Functions Registry for Berry Animation Framework
|
||||||
# This module manages external Berry functions that can be called from DSL code
|
# This module manages external Berry functions that can be called from DSL code
|
||||||
|
|
||||||
#@ solidify:animation_user_functions,weak
|
|
||||||
|
|
||||||
# Register a Berry function for DSL use
|
# Register a Berry function for DSL use
|
||||||
def register_user_function(name, func)
|
def register_user_function(name, func)
|
||||||
animation._user_functions[name] = func
|
animation._user_functions[name] = func
|
||||||
|
|||||||
@ -21,14 +21,14 @@ class SimpleDSLTranspiler
|
|||||||
var pos # Current token position
|
var pos # Current token position
|
||||||
var output # Generated Berry code lines
|
var output # Generated Berry code lines
|
||||||
var errors # Compilation errors
|
var errors # Compilation errors
|
||||||
var run_statements # Collect all run statements for single engine.start()
|
var run_statements # Collect all run statements for single engine.run()
|
||||||
var first_statement # Track if we're processing the first statement
|
var first_statement # Track if we're processing the first statement
|
||||||
var strip_initialized # Track if strip was initialized
|
var strip_initialized # Track if strip was initialized
|
||||||
var sequence_names # Track which names are sequences
|
var sequence_names # Track which names are sequences
|
||||||
var symbol_table # Track created objects: name -> instance
|
var symbol_table # Track created objects: name -> instance
|
||||||
var indent_level # Track current indentation level for nested sequences
|
var indent_level # Track current indentation level for nested sequences
|
||||||
var template_definitions # Track template definitions: name -> {params, body}
|
var template_definitions # Track template definitions: name -> {params, body}
|
||||||
var has_template_calls # Track if we have template calls to trigger engine.start()
|
var has_template_calls # Track if we have template calls to trigger engine.run()
|
||||||
|
|
||||||
# Static color mapping for named colors (helps with solidification)
|
# Static color mapping for named colors (helps with solidification)
|
||||||
static var named_colors = {
|
static var named_colors = {
|
||||||
@ -75,9 +75,9 @@ class SimpleDSLTranspiler
|
|||||||
# Don't check for existence during transpilation - trust that function will be available at runtime
|
# Don't check for existence during transpilation - trust that function will be available at runtime
|
||||||
|
|
||||||
# User functions use positional parameters with engine as first argument
|
# User functions use positional parameters with engine as first argument
|
||||||
# In closure context, use self.engine to access the engine from the ClosureValueProvider
|
# In closure context, use engine parameter directly
|
||||||
var args = self.process_function_arguments(true)
|
var args = self.process_function_arguments(true)
|
||||||
var full_args = args != "" ? f"self.engine, {args}" : "self.engine"
|
var full_args = args != "" ? f"engine, {args}" : "engine"
|
||||||
return f"animation.get_user_function('{func_name}')({full_args})"
|
return f"animation.get_user_function('{func_name}')({full_args})"
|
||||||
else
|
else
|
||||||
self.error("User functions must be called with parentheses: user.function_name()")
|
self.error("User functions must be called with parentheses: user.function_name()")
|
||||||
@ -96,8 +96,8 @@ class SimpleDSLTranspiler
|
|||||||
self.process_statement()
|
self.process_statement()
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generate single engine.start() call after all run statements
|
# Generate single engine.run() call after all run statements
|
||||||
self.generate_engine_start()
|
self.generate_engine_run()
|
||||||
|
|
||||||
return size(self.errors) == 0 ? self.join_output() : nil
|
return size(self.errors) == 0 ? self.join_output() : nil
|
||||||
except .. as e, msg
|
except .. as e, msg
|
||||||
@ -784,8 +784,12 @@ class SimpleDSLTranspiler
|
|||||||
elif tok.type == animation_dsl.Token.IDENTIFIER && tok.value == "log"
|
elif tok.type == animation_dsl.Token.IDENTIFIER && tok.value == "log"
|
||||||
self.process_log_statement_fluent()
|
self.process_log_statement_fluent()
|
||||||
|
|
||||||
elif tok.type == animation_dsl.Token.KEYWORD && (tok.value == "reset" || tok.value == "restart")
|
elif tok.type == animation_dsl.Token.KEYWORD && tok.value == "restart"
|
||||||
self.process_reset_restart_statement_fluent()
|
self.process_restart_statement_fluent()
|
||||||
|
|
||||||
|
elif tok.type == animation_dsl.Token.KEYWORD && tok.value == "reset"
|
||||||
|
self.error("'reset' command is no longer supported. Use 'restart' instead.")
|
||||||
|
self.skip_statement()
|
||||||
|
|
||||||
elif tok.type == animation_dsl.Token.KEYWORD && tok.value == "repeat"
|
elif tok.type == animation_dsl.Token.KEYWORD && tok.value == "repeat"
|
||||||
self.next() # skip 'repeat'
|
self.next() # skip 'repeat'
|
||||||
@ -827,12 +831,12 @@ class SimpleDSLTranspiler
|
|||||||
self.process_sequence_assignment_fluent()
|
self.process_sequence_assignment_fluent()
|
||||||
else
|
else
|
||||||
# Unknown identifier in sequence - this is an error
|
# Unknown identifier in sequence - this is an error
|
||||||
self.error(f"Unknown command '{tok.value}' in sequence. Valid sequence commands are: play, wait, repeat, reset, restart, log, or property assignments (object.property = value)")
|
self.error(f"Unknown command '{tok.value}' in sequence. Valid sequence commands are: play, wait, repeat, restart, log, or property assignments (object.property = value)")
|
||||||
self.skip_statement()
|
self.skip_statement()
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
# Unknown token type in sequence - this is an error
|
# Unknown token type in sequence - this is an error
|
||||||
self.error(f"Invalid statement in sequence. Expected: play, wait, repeat, reset, restart, log, or property assignments")
|
self.error(f"Invalid statement in sequence. Expected: play, wait, repeat, restart, log, or property assignments")
|
||||||
self.skip_statement()
|
self.skip_statement()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -981,10 +985,10 @@ class SimpleDSLTranspiler
|
|||||||
self.add(log_code)
|
self.add(log_code)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Helper method to process reset/restart statement using fluent style
|
# Helper method to process restart statement using fluent style
|
||||||
def process_reset_restart_statement_fluent()
|
def process_restart_statement_fluent()
|
||||||
var keyword = self.current().value # "reset" or "restart"
|
var keyword = self.current().value # "restart"
|
||||||
self.next() # skip 'reset' or 'restart'
|
self.next() # skip 'restart'
|
||||||
|
|
||||||
# Expect the value provider identifier
|
# Expect the value provider identifier
|
||||||
var val_name = self.expect_identifier()
|
var val_name = self.expect_identifier()
|
||||||
@ -1076,7 +1080,7 @@ class SimpleDSLTranspiler
|
|||||||
var inline_comment = self.collect_inline_comment()
|
var inline_comment = self.collect_inline_comment()
|
||||||
self.add(f"{object_name}_template({full_args}){inline_comment}")
|
self.add(f"{object_name}_template({full_args}){inline_comment}")
|
||||||
|
|
||||||
# Track that we have template calls to trigger engine.start()
|
# Track that we have template calls to trigger engine.run()
|
||||||
self.has_template_calls = true
|
self.has_template_calls = true
|
||||||
else
|
else
|
||||||
self.error(f"Standalone function calls are only supported for templates. '{object_name}' is not a template.")
|
self.error(f"Standalone function calls are only supported for templates. '{object_name}' is not a template.")
|
||||||
@ -1274,7 +1278,7 @@ class SimpleDSLTranspiler
|
|||||||
# Check if this is a mathematical function
|
# Check if this is a mathematical function
|
||||||
if self.is_math_method(func_name)
|
if self.is_math_method(func_name)
|
||||||
var args = self.process_function_arguments(true)
|
var args = self.process_function_arguments(true)
|
||||||
return f"self.{func_name}({args})"
|
return f"animation._math.{func_name}({args})"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Special case for log function in expressions
|
# Special case for log function in expressions
|
||||||
@ -1286,7 +1290,7 @@ class SimpleDSLTranspiler
|
|||||||
# Check if this is a template call
|
# Check if this is a template call
|
||||||
if self.template_definitions.contains(func_name)
|
if self.template_definitions.contains(func_name)
|
||||||
var args = self.process_function_arguments(true)
|
var args = self.process_function_arguments(true)
|
||||||
var full_args = args != "" ? f"self.engine, {args}" : "self.engine"
|
var full_args = args != "" ? f"engine, {args}" : "engine"
|
||||||
return f"{func_name}_template({full_args})"
|
return f"{func_name}_template({full_args})"
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1379,7 +1383,7 @@ class SimpleDSLTranspiler
|
|||||||
return f"{object_ref}.{property_name}"
|
return f"{object_ref}.{property_name}"
|
||||||
else
|
else
|
||||||
# Return a closure expression that will be wrapped by the caller if needed
|
# Return a closure expression that will be wrapped by the caller if needed
|
||||||
return f"self.resolve({object_ref}, '{property_name}')"
|
return f"animation.resolve({object_ref}, '{property_name}')"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1481,7 +1485,7 @@ class SimpleDSLTranspiler
|
|||||||
transformed_expr = string.replace(transformed_expr, " ", " ")
|
transformed_expr = string.replace(transformed_expr, " ", " ")
|
||||||
end
|
end
|
||||||
|
|
||||||
var closure_code = f"def (self) return {transformed_expr} end"
|
var closure_code = f"def (engine) return {transformed_expr} end"
|
||||||
|
|
||||||
# Return a closure value provider instance
|
# Return a closure value provider instance
|
||||||
return f"animation.create_closure_value(engine, {closure_code})"
|
return f"animation.create_closure_value(engine, {closure_code})"
|
||||||
@ -1506,7 +1510,7 @@ class SimpleDSLTranspiler
|
|||||||
right_expr = string.replace(right_expr, " ", " ")
|
right_expr = string.replace(right_expr, " ", " ")
|
||||||
end
|
end
|
||||||
|
|
||||||
var closure_code = f"def (self) return {left_expr} {op} {right_expr} end"
|
var closure_code = f"def (engine) return {left_expr} {op} {right_expr} end"
|
||||||
|
|
||||||
# Return a closure value provider instance
|
# Return a closure value provider instance
|
||||||
return f"animation.create_closure_value(engine, {closure_code})"
|
return f"animation.create_closure_value(engine, {closure_code})"
|
||||||
@ -1526,48 +1530,7 @@ class SimpleDSLTranspiler
|
|||||||
var result = expr_str
|
var result = expr_str
|
||||||
var pos = 0
|
var pos = 0
|
||||||
|
|
||||||
# First pass: Transform mathematical function calls to self.method() calls
|
# Replace all user variables (ending with _) with resolve 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
|
pos = 0
|
||||||
while pos < size(result)
|
while pos < size(result)
|
||||||
var underscore_pos = string.find(result, "_", pos)
|
var underscore_pos = string.find(result, "_", pos)
|
||||||
@ -1581,25 +1544,19 @@ class SimpleDSLTranspiler
|
|||||||
start_pos -= 1
|
start_pos -= 1
|
||||||
end
|
end
|
||||||
|
|
||||||
# Check if this is a user variable (not preceded by "animation." or "self." or already inside a resolve call)
|
# Check if this is a user variable (not preceded by "animation." or already inside a resolve call)
|
||||||
var is_user_var = true
|
var is_user_var = true
|
||||||
if start_pos >= 13
|
if start_pos >= 18
|
||||||
var check_start = start_pos >= 13 ? start_pos - 13 : 0
|
var check_start = start_pos >= 18 ? start_pos - 18 : 0
|
||||||
var prefix = result[check_start..start_pos-1]
|
var prefix = result[check_start..start_pos-1]
|
||||||
if string.find(prefix, "self.resolve(") >= 0
|
if string.find(prefix, "animation.resolve(") >= 0
|
||||||
is_user_var = false
|
is_user_var = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if is_user_var && start_pos >= 10
|
if is_user_var && start_pos >= 10
|
||||||
var check_start = start_pos >= 10 ? start_pos - 10 : 0
|
var check_start = start_pos >= 10 ? start_pos - 10 : 0
|
||||||
var prefix = result[check_start..start_pos-1]
|
var prefix = result[check_start..start_pos-1]
|
||||||
if string.find(prefix, "animation.") >= 0 || string.find(prefix, "self.") >= 0
|
if string.find(prefix, "animation.") >= 0
|
||||||
is_user_var = false
|
|
||||||
end
|
|
||||||
elif is_user_var && 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
|
is_user_var = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -1612,7 +1569,7 @@ class SimpleDSLTranspiler
|
|||||||
var end_pos = underscore_pos + 1
|
var end_pos = underscore_pos + 1
|
||||||
if end_pos >= size(result) || !self.is_identifier_char(result[end_pos])
|
if end_pos >= size(result) || !self.is_identifier_char(result[end_pos])
|
||||||
# Replace the variable with the resolve call
|
# Replace the variable with the resolve call
|
||||||
var replacement = f"self.resolve({var_name})"
|
var replacement = f"animation.resolve({var_name})"
|
||||||
var before = start_pos > 0 ? result[0..start_pos-1] : ""
|
var before = start_pos > 0 ? result[0..start_pos-1] : ""
|
||||||
var after = end_pos < size(result) ? result[end_pos..] : ""
|
var after = end_pos < size(result) ? result[end_pos..] : ""
|
||||||
result = before + replacement + after
|
result = before + replacement + after
|
||||||
@ -1633,28 +1590,14 @@ class SimpleDSLTranspiler
|
|||||||
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_'
|
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_'
|
||||||
end
|
end
|
||||||
|
|
||||||
# Helper method to check if a function name is a mathematical method in ClosureValueProvider
|
# Helper method to check if a function name is a mathematical function
|
||||||
def is_math_method(func_name)
|
def is_math_method(func_name)
|
||||||
import introspect
|
|
||||||
try
|
try
|
||||||
# Get the ClosureValueProvider class from the animation module
|
import introspect
|
||||||
var closure_provider_class = animation.closure_value
|
# Check if the function is registered in the animation._math map
|
||||||
if closure_provider_class == nil
|
return introspect.contains(animation._math, func_name)
|
||||||
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
|
except .. as e, msg
|
||||||
# If introspection fails, return false to be safe
|
# If _math map doesn't exist or access fails, return false to be safe
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -1684,7 +1627,7 @@ class SimpleDSLTranspiler
|
|||||||
|
|
||||||
if has_underscore && !has_operators && !has_paren && !has_animation_prefix
|
if has_underscore && !has_operators && !has_paren && !has_animation_prefix
|
||||||
# This looks like a simple user variable that might be a ValueProvider
|
# This looks like a simple user variable that might be a ValueProvider
|
||||||
return f"self.resolve({operand})"
|
return f"animation.resolve({operand})"
|
||||||
else
|
else
|
||||||
# For other expressions (literals, animation module calls, complex expressions), use as-is
|
# For other expressions (literals, animation module calls, complex expressions), use as-is
|
||||||
return operand
|
return operand
|
||||||
@ -1936,7 +1879,7 @@ class SimpleDSLTranspiler
|
|||||||
if self.is_math_method(func_name)
|
if self.is_math_method(func_name)
|
||||||
# Mathematical functions use positional arguments, not named parameters
|
# Mathematical functions use positional arguments, not named parameters
|
||||||
var args = self.process_function_arguments(true)
|
var args = self.process_function_arguments(true)
|
||||||
return f"self.{func_name}({args})" # Prefix with self. for closure context
|
return f"animation._math.{func_name}({args})" # Math functions are under _math namespace
|
||||||
end
|
end
|
||||||
|
|
||||||
# Special case for log function in nested calls
|
# Special case for log function in nested calls
|
||||||
@ -1950,7 +1893,7 @@ class SimpleDSLTranspiler
|
|||||||
if self.template_definitions.contains(func_name)
|
if self.template_definitions.contains(func_name)
|
||||||
# This is a template call - treat like user function
|
# This is a template call - treat like user function
|
||||||
var args = self.process_function_arguments(true)
|
var args = self.process_function_arguments(true)
|
||||||
var full_args = args != "" ? f"self.engine, {args}" : "self.engine"
|
var full_args = args != "" ? f"engine, {args}" : "engine"
|
||||||
return f"{func_name}_template({full_args})"
|
return f"{func_name}_template({full_args})"
|
||||||
else
|
else
|
||||||
# Check if this is a simple function call without named parameters
|
# Check if this is a simple function call without named parameters
|
||||||
@ -2324,8 +2267,8 @@ class SimpleDSLTranspiler
|
|||||||
return report
|
return report
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generate single engine.start() call for all run statements
|
# Generate single engine.run() call for all run statements
|
||||||
def generate_engine_start()
|
def generate_engine_run()
|
||||||
if size(self.run_statements) == 0 && !self.has_template_calls
|
if size(self.run_statements) == 0 && !self.has_template_calls
|
||||||
return # No run statements or template calls, no need to start engine
|
return # No run statements or template calls, no need to start engine
|
||||||
end
|
end
|
||||||
@ -2340,8 +2283,8 @@ class SimpleDSLTranspiler
|
|||||||
self.add(f"engine.add({name}_){comment}")
|
self.add(f"engine.add({name}_){comment}")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Single engine.start() call
|
# Single engine.run() call
|
||||||
self.add("engine.start()")
|
self.add("engine.run()")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Basic event handler processing
|
# Basic event handler processing
|
||||||
@ -2654,14 +2597,14 @@ class SimpleDSLTranspiler
|
|||||||
return true # Valid value provider or animation
|
return true # Valid value provider or animation
|
||||||
elif type(marker) == "string"
|
elif type(marker) == "string"
|
||||||
# It's some other type (variable, color, sequence, etc.)
|
# It's some other type (variable, color, sequence, etc.)
|
||||||
self.error(f"'{object_name}' in {context} statement is not a value provider or animation. Only value providers (like oscillators) and animations can be reset/restarted.")
|
self.error(f"'{object_name}' in {context} statement is not a value provider or animation. Only value providers (like oscillators) and animations can be restarted.")
|
||||||
return false
|
return false
|
||||||
else
|
else
|
||||||
# It's an actual instance - check if it's a value provider or animation
|
# It's an actual instance - check if it's a value provider or animation
|
||||||
if isinstance(marker, animation.value_provider) || isinstance(marker, animation.animation)
|
if isinstance(marker, animation.value_provider) || isinstance(marker, animation.animation)
|
||||||
return true # Valid value provider or animation
|
return true # Valid value provider or animation
|
||||||
else
|
else
|
||||||
self.error(f"'{object_name}' in {context} statement is not a value provider or animation. Only value providers (like oscillators) and animations can be reset/restarted.")
|
self.error(f"'{object_name}' in {context} statement is not a value provider or animation. Only value providers (like oscillators) and animations can be restarted.")
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -37,6 +37,7 @@ class BreatheColorProvider : animation.oscillator_value
|
|||||||
|
|
||||||
# Handle parameter changes - no need to sync oscillator min/max since they're fixed
|
# Handle parameter changes - no need to sync oscillator min/max since they're fixed
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
# Only handle curve_factor changes for oscillator form
|
# Only handle curve_factor changes for oscillator form
|
||||||
if name == "curve_factor"
|
if name == "curve_factor"
|
||||||
# For curve_factor = 1, use pure cosine
|
# For curve_factor = 1, use pure cosine
|
||||||
|
|||||||
@ -30,33 +30,12 @@ class ClosureValueProvider : animation.value_provider
|
|||||||
# @param name: string - Parameter name
|
# @param name: string - Parameter name
|
||||||
# @param value: any - New parameter value
|
# @param value: any - New parameter value
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
if name == "closure"
|
if name == "closure"
|
||||||
self._closure = value
|
self._closure = value
|
||||||
end
|
end
|
||||||
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, value provider instance, or parameterized object
|
|
||||||
# @param param_name: string - Parameter name for specific produce_value() method lookup
|
|
||||||
# @return any - The resolved value (static, from provider, or from object parameter)
|
|
||||||
def resolve(value, param_name)
|
|
||||||
if animation.is_value_provider(value)
|
|
||||||
return value.produce_value(param_name, self.engine.time_ms)
|
|
||||||
elif value != nil && isinstance(value, animation.parameterized_object)
|
|
||||||
# Handle parameterized objects (animations, etc.) by accessing their parameters
|
|
||||||
# Check that param_name is not nil to prevent runtime errors
|
|
||||||
if param_name == nil
|
|
||||||
raise "value_error", "Parameter name cannot be nil when resolving object parameter"
|
|
||||||
end
|
|
||||||
return value.get_param_value(param_name, self.engine.time_ms)
|
|
||||||
else
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Produce a value by calling the stored closure
|
# Produce a value by calling the stored closure
|
||||||
#
|
#
|
||||||
# @param name: string - Parameter name being requested
|
# @param name: string - Parameter name being requested
|
||||||
@ -69,112 +48,7 @@ class ClosureValueProvider : animation.value_provider
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Call the closure with the parameter self, name and time
|
# Call the closure with the parameter self, name and time
|
||||||
return closure(self, name, time_ms)
|
return closure(self.engine, 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 sin(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 cos(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
|
end
|
||||||
|
|
||||||
# String representation for debugging
|
# String representation for debugging
|
||||||
@ -198,5 +72,28 @@ def create_closure_value(engine, closure)
|
|||||||
return provider
|
return provider
|
||||||
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 in animation module
|
||||||
|
#
|
||||||
|
# @param value: any - Static value, value provider instance, or parameterized object
|
||||||
|
# @param param_name: string - Parameter name for specific produce_value() method lookup
|
||||||
|
# @return any - The resolved value (static, from provider, or from object parameter)
|
||||||
|
def animation_resolve(value, param_name, time_ms)
|
||||||
|
if animation.is_value_provider(value)
|
||||||
|
return value.produce_value(param_name, time_ms)
|
||||||
|
elif value != nil && isinstance(value, animation.parameterized_object)
|
||||||
|
# Handle parameterized objects (animations, etc.) by accessing their parameters
|
||||||
|
# Check that param_name is not nil to prevent runtime errors
|
||||||
|
if param_name == nil
|
||||||
|
raise "value_error", "Parameter name cannot be nil when resolving object parameter"
|
||||||
|
end
|
||||||
|
return value.get_param_value(param_name)
|
||||||
|
else
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return {'closure_value': ClosureValueProvider,
|
return {'closure_value': ClosureValueProvider,
|
||||||
'create_closure_value': create_closure_value}
|
'create_closure_value': create_closure_value,
|
||||||
|
'resolve': animation_resolve}
|
||||||
@ -86,6 +86,7 @@ class ColorCycleColorProvider : animation.color_provider
|
|||||||
# @param name: string - Name of the parameter that changed
|
# @param name: string - Name of the parameter that changed
|
||||||
# @param value: any - New value of the parameter
|
# @param value: any - New value of the parameter
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
if name == "palette_size"
|
if name == "palette_size"
|
||||||
# palette_size is read-only - restore the actual value and raise an exception
|
# palette_size is read-only - restore the actual value and raise an exception
|
||||||
self.values["palette_size"] = self._get_palette_size()
|
self.values["palette_size"] = self._get_palette_size()
|
||||||
|
|||||||
@ -136,11 +136,7 @@ class CompositeColorProvider : animation.color_provider
|
|||||||
|
|
||||||
# String representation of the provider
|
# String representation of the provider
|
||||||
def tostring()
|
def tostring()
|
||||||
try
|
|
||||||
return f"CompositeColorProvider(providers={size(self.providers)}, blend_mode={self.blend_mode})"
|
return f"CompositeColorProvider(providers={size(self.providers)}, blend_mode={self.blend_mode})"
|
||||||
except ..
|
|
||||||
return "CompositeColorProvider(uninitialized)"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,6 @@ var BOUNCE = 9
|
|||||||
#@ solidify:OscillatorValueProvider,weak
|
#@ solidify:OscillatorValueProvider,weak
|
||||||
class OscillatorValueProvider : animation.value_provider
|
class OscillatorValueProvider : animation.value_provider
|
||||||
# Non-parameter instance variables only
|
# Non-parameter instance variables only
|
||||||
var origin # origin time in ms for cycle calculation
|
|
||||||
var value # current calculated value
|
var value # current calculated value
|
||||||
|
|
||||||
# Static array for better solidification (moved from inline array)
|
# Static array for better solidification (moved from inline array)
|
||||||
@ -47,19 +46,20 @@ class OscillatorValueProvider : animation.value_provider
|
|||||||
super(self).init(engine) # Initialize parameter system
|
super(self).init(engine) # Initialize parameter system
|
||||||
|
|
||||||
# Initialize non-parameter instance variables
|
# Initialize non-parameter instance variables
|
||||||
self.origin = 0 # Will be set when `start` is called
|
|
||||||
self.value = 0 # Will be calculated on first produce_value call
|
self.value = 0 # Will be calculated on first produce_value call
|
||||||
end
|
end
|
||||||
|
|
||||||
# Start/restart the oscillator at a specific time
|
# Start/restart the oscillator at a specific time
|
||||||
#
|
#
|
||||||
# @param time_ms: int - Time in milliseconds to set as origin (optional, uses engine time if nil)
|
# start() is typically not called at beginning of animations for value providers.
|
||||||
|
# The start_time is set at the first call to produce_value().
|
||||||
|
# This method is mainly aimed at restarting the value provider start_time
|
||||||
|
# via the `restart` keyword in DSL.
|
||||||
|
#
|
||||||
|
# @param time_ms: int - Time in milliseconds to set as start_time (optional, uses engine time if nil)
|
||||||
# @return self for method chaining
|
# @return self for method chaining
|
||||||
def start(time_ms)
|
def start(time_ms)
|
||||||
if time_ms == nil
|
super(self).start(time_ms)
|
||||||
time_ms = self.engine.time_ms
|
|
||||||
end
|
|
||||||
self.origin = time_ms
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -77,12 +77,15 @@ class OscillatorValueProvider : animation.value_provider
|
|||||||
var phase = self.phase
|
var phase = self.phase
|
||||||
var duty_cycle = self.duty_cycle
|
var duty_cycle = self.duty_cycle
|
||||||
|
|
||||||
|
# Ensure time_ms is valid and initialize start_time if needed
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
if duration == nil || duration <= 0
|
if duration == nil || duration <= 0
|
||||||
return min_value
|
return min_value
|
||||||
end
|
end
|
||||||
|
|
||||||
# Calculate elapsed time since origin
|
# Calculate elapsed time since start_time
|
||||||
var past = time_ms - self.origin
|
var past = time_ms - self.start_time
|
||||||
if past < 0
|
if past < 0
|
||||||
past = 0
|
past = 0
|
||||||
end
|
end
|
||||||
@ -92,7 +95,7 @@ class OscillatorValueProvider : animation.value_provider
|
|||||||
# Handle cycle wrapping
|
# Handle cycle wrapping
|
||||||
if past >= duration
|
if past >= duration
|
||||||
var cycles = past / duration
|
var cycles = past / duration
|
||||||
self.origin += cycles * duration
|
self.start_time += cycles * duration
|
||||||
past = past % duration
|
past = past % duration
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,6 @@ class RichPaletteColorProvider : animation.color_provider
|
|||||||
var slots # Number of slots in the palette
|
var slots # Number of slots in the palette
|
||||||
var current_color # Current interpolated color (calculated during update)
|
var current_color # Current interpolated color (calculated during update)
|
||||||
var light_state # light_state instance for proper color calculations
|
var light_state # light_state instance for proper color calculations
|
||||||
var cycle_start # Time when the animation cycle started
|
|
||||||
|
|
||||||
# Parameter definitions
|
# Parameter definitions
|
||||||
static var PARAMS = {
|
static var PARAMS = {
|
||||||
@ -35,7 +34,6 @@ class RichPaletteColorProvider : animation.color_provider
|
|||||||
|
|
||||||
# Initialize non-parameter instance variables
|
# Initialize non-parameter instance variables
|
||||||
self.current_color = 0xFFFFFFFF
|
self.current_color = 0xFFFFFFFF
|
||||||
self.cycle_start = self.engine.time_ms # Initialize cycle start time
|
|
||||||
self.slots = 0
|
self.slots = 0
|
||||||
|
|
||||||
# Create light_state instance for proper color calculations (reuse from Animate_palette)
|
# Create light_state instance for proper color calculations (reuse from Animate_palette)
|
||||||
@ -48,6 +46,7 @@ class RichPaletteColorProvider : animation.color_provider
|
|||||||
# @param name: string - Name of the parameter that changed
|
# @param name: string - Name of the parameter that changed
|
||||||
# @param value: any - New value of the parameter
|
# @param value: any - New value of the parameter
|
||||||
def on_param_changed(name, value)
|
def on_param_changed(name, value)
|
||||||
|
super(self).on_param_changed(name, value)
|
||||||
if name == "range_min" || name == "range_max" || name == "cycle_period" || name == "palette"
|
if name == "range_min" || name == "range_max" || name == "cycle_period" || name == "palette"
|
||||||
if (self.slots_arr != nil) || (self.value_arr != nil)
|
if (self.slots_arr != nil) || (self.value_arr != nil)
|
||||||
# only if they were already computed
|
# only if they were already computed
|
||||||
@ -58,14 +57,14 @@ class RichPaletteColorProvider : animation.color_provider
|
|||||||
|
|
||||||
# Start/restart the animation cycle at a specific time
|
# Start/restart the animation cycle at a specific time
|
||||||
#
|
#
|
||||||
# @param time_ms: int - Time in milliseconds to set as cycle start (optional, uses engine time if nil)
|
# @param time_ms: int - Time in milliseconds to set as start_time (optional, uses engine time if nil)
|
||||||
# @return self for method chaining
|
# @return self for method chaining
|
||||||
def start(time_ms)
|
def start(time_ms)
|
||||||
# Compute arrays if they were not yet initialized
|
# Compute arrays if they were not yet initialized
|
||||||
if (self.slots_arr == nil) && (self.value_arr == nil)
|
if (self.slots_arr == nil) && (self.value_arr == nil)
|
||||||
self._recompute_palette()
|
self._recompute_palette()
|
||||||
end
|
end
|
||||||
self.cycle_start = (time_ms != nil) ? time_ms : self.engine.time_ms
|
super(self).start(time_ms)
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -178,6 +177,9 @@ class RichPaletteColorProvider : animation.color_provider
|
|||||||
# @param time_ms: int - Current time in milliseconds
|
# @param time_ms: int - Current time in milliseconds
|
||||||
# @return int - Color in ARGB format (0xAARRGGBB)
|
# @return int - Color in ARGB format (0xAARRGGBB)
|
||||||
def produce_value(name, time_ms)
|
def produce_value(name, time_ms)
|
||||||
|
# Ensure time_ms is valid and initialize start_time if needed
|
||||||
|
time_ms = self._fix_time_ms(time_ms)
|
||||||
|
|
||||||
if (self.slots_arr == nil) && (self.value_arr == nil)
|
if (self.slots_arr == nil) && (self.value_arr == nil)
|
||||||
self._recompute_palette()
|
self._recompute_palette()
|
||||||
end
|
end
|
||||||
@ -210,8 +212,8 @@ class RichPaletteColorProvider : animation.color_provider
|
|||||||
return final_color
|
return final_color
|
||||||
end
|
end
|
||||||
|
|
||||||
# Calculate position in cycle using cycle_start
|
# Calculate position in cycle using start_time
|
||||||
var elapsed = time_ms - self.cycle_start
|
var elapsed = time_ms - self.start_time
|
||||||
var past = elapsed % cycle_period
|
var past = elapsed % cycle_period
|
||||||
|
|
||||||
# Find slot (exact algorithm from Animate_palette)
|
# Find slot (exact algorithm from Animate_palette)
|
||||||
|
|||||||
@ -34,11 +34,7 @@ class StaticColorProvider : animation.color_provider
|
|||||||
|
|
||||||
# String representation of the provider
|
# String representation of the provider
|
||||||
def tostring()
|
def tostring()
|
||||||
try
|
|
||||||
return f"StaticColorProvider(color=0x{self.color:08X})"
|
return f"StaticColorProvider(color=0x{self.color:08X})"
|
||||||
except ..
|
|
||||||
return "StaticColorProvider(color=unset)"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -54,11 +54,7 @@ class StaticValueProvider : animation.value_provider
|
|||||||
|
|
||||||
# String representation of the provider
|
# String representation of the provider
|
||||||
def tostring()
|
def tostring()
|
||||||
try
|
|
||||||
return f"StaticValueProvider(value={self.value})"
|
return f"StaticValueProvider(value={self.value})"
|
||||||
except ..
|
|
||||||
return "StaticValueProvider(value=unset)"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -19,20 +19,12 @@ class StripLengthProvider : animation.value_provider
|
|||||||
# @param time_ms: int - Current time in milliseconds (ignored)
|
# @param time_ms: int - Current time in milliseconds (ignored)
|
||||||
# @return int - The strip length in pixels
|
# @return int - The strip length in pixels
|
||||||
def produce_value(name, time_ms)
|
def produce_value(name, time_ms)
|
||||||
if self.engine == nil
|
return self.engine ? self.engine.width : 0
|
||||||
return 0
|
|
||||||
end
|
|
||||||
return self.engine.width
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# String representation of the provider
|
# String representation of the provider
|
||||||
def tostring()
|
def tostring()
|
||||||
try
|
return f"StripLengthProvider(length={self.engine ? self.engine.width :: 'unknown'})"
|
||||||
var length = self.engine != nil ? self.engine.width : 0
|
|
||||||
return f"StripLengthProvider(length={length})"
|
|
||||||
except ..
|
|
||||||
return "StripLengthProvider(length=unknown)"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,10 @@ class ValueProvider : animation.parameterized_object
|
|||||||
# special value providers that return coordinated distinct
|
# special value providers that return coordinated distinct
|
||||||
# values for different parameter names.
|
# values for different parameter names.
|
||||||
#
|
#
|
||||||
|
# For value providers, start is typically not called because instances
|
||||||
|
# can be embedded in closures. So value providers must consider the first
|
||||||
|
# call to `produce_value()` as a start of their internal time reference.
|
||||||
|
#
|
||||||
# @param name: string - Parameter name being requested
|
# @param name: string - Parameter name being requested
|
||||||
# @param time_ms: int - Current time in milliseconds
|
# @param time_ms: int - Current time in milliseconds
|
||||||
# @return any - Value appropriate for the parameter type
|
# @return any - Value appropriate for the parameter type
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -76,11 +76,11 @@ assert_test(!engine.remove_animation(anim2), "Should not remove non-existent ani
|
|||||||
|
|
||||||
# Test 3: Engine Lifecycle
|
# Test 3: Engine Lifecycle
|
||||||
print("\n--- Test 3: Engine Lifecycle ---")
|
print("\n--- Test 3: Engine Lifecycle ---")
|
||||||
assert_test(engine.start(), "Should start engine")
|
assert_test(engine.run(), "Should start engine")
|
||||||
assert_equals(engine.is_active(), true, "Engine should be active after start")
|
assert_equals(engine.is_active(), true, "Engine should be active after start")
|
||||||
|
|
||||||
# Test that starting again doesn't break anything
|
# Test that starting again doesn't break anything
|
||||||
engine.start()
|
engine.run()
|
||||||
assert_equals(engine.is_active(), true, "Engine should remain active after second start")
|
assert_equals(engine.is_active(), true, "Engine should remain active after second start")
|
||||||
|
|
||||||
assert_test(engine.stop(), "Should stop engine")
|
assert_test(engine.stop(), "Should stop engine")
|
||||||
@ -94,7 +94,7 @@ test_anim.color = 0xFFFF0000
|
|||||||
test_anim.priority = 10
|
test_anim.priority = 10
|
||||||
test_anim.name = "test"
|
test_anim.name = "test"
|
||||||
engine.add(test_anim)
|
engine.add(test_anim)
|
||||||
engine.start()
|
engine.run()
|
||||||
|
|
||||||
var current_time = tasmota.millis()
|
var current_time = tasmota.millis()
|
||||||
|
|
||||||
@ -255,7 +255,7 @@ assert_test(temp_reused, "Temp buffer object should be reused for efficiency")
|
|||||||
|
|
||||||
# Test 10c: Runtime detection during on_tick()
|
# Test 10c: Runtime detection during on_tick()
|
||||||
print("\n--- Test 10c: Runtime detection during on_tick() ---")
|
print("\n--- Test 10c: Runtime detection during on_tick() ---")
|
||||||
dynamic_engine.start()
|
dynamic_engine.run()
|
||||||
|
|
||||||
# Add a test animation
|
# Add a test animation
|
||||||
var runtime_anim = animation.solid(dynamic_engine)
|
var runtime_anim = animation.solid(dynamic_engine)
|
||||||
|
|||||||
@ -43,7 +43,7 @@ base_anim.priority = 10
|
|||||||
base_anim.name = "base_red"
|
base_anim.name = "base_red"
|
||||||
|
|
||||||
opacity_engine.add(base_anim)
|
opacity_engine.add(base_anim)
|
||||||
opacity_engine.start()
|
opacity_engine.run()
|
||||||
|
|
||||||
# Create frame buffer and test rendering
|
# Create frame buffer and test rendering
|
||||||
var opacity_frame = animation.frame_buffer(10)
|
var opacity_frame = animation.frame_buffer(10)
|
||||||
@ -78,7 +78,7 @@ print("\n--- Test 11c: Animation opacity rendering ---")
|
|||||||
|
|
||||||
opacity_engine.clear()
|
opacity_engine.clear()
|
||||||
opacity_engine.add(masked_anim)
|
opacity_engine.add(masked_anim)
|
||||||
opacity_engine.start()
|
opacity_engine.run()
|
||||||
|
|
||||||
# Start both animations
|
# Start both animations
|
||||||
masked_anim.start()
|
masked_anim.start()
|
||||||
|
|||||||
@ -46,18 +46,20 @@ assert(default_anim.color == 0xFFFFFFFF, "Default color should be white")
|
|||||||
# Test start method
|
# Test start method
|
||||||
engine.time_ms = 1000
|
engine.time_ms = 1000
|
||||||
anim.start()
|
anim.start()
|
||||||
|
anim.update()
|
||||||
assert(anim.is_running == true, "Animation should be running after start")
|
assert(anim.is_running == true, "Animation should be running after start")
|
||||||
assert(anim.start_time == 1000, "Animation start time should be 1000")
|
assert(anim.start_time == 1000, "Animation start time should be 1000")
|
||||||
assert(anim.current_time == 1000, "Animation current time should be 1000")
|
|
||||||
|
|
||||||
# Test restart functionality - start() acts as restart
|
# Test restart functionality - start() acts as restart
|
||||||
|
engine.time_ms = 2000
|
||||||
anim.start()
|
anim.start()
|
||||||
assert(anim.is_running == true, "Animation should be running after start")
|
assert(anim.is_running == true, "Animation should be running after start")
|
||||||
|
assert(anim.start_time == 2000, "Animation start time should be 2000")
|
||||||
var first_start_time = anim.start_time
|
var first_start_time = anim.start_time
|
||||||
|
|
||||||
# Start again - should restart with new time
|
# Start again - should restart with new time
|
||||||
engine.time_ms = 3000
|
engine.time_ms = 3000
|
||||||
anim.start()
|
anim.start(engine.time_ms)
|
||||||
assert(anim.is_running == true, "Animation should still be running after restart")
|
assert(anim.is_running == true, "Animation should still be running after restart")
|
||||||
assert(anim.start_time == 3000, "Animation should have new start time after restart")
|
assert(anim.start_time == 3000, "Animation should have new start time after restart")
|
||||||
|
|
||||||
@ -71,6 +73,7 @@ non_loop_anim.name = "non_loop"
|
|||||||
non_loop_anim.color = 0xFF0000
|
non_loop_anim.color = 0xFF0000
|
||||||
engine.time_ms = 2000
|
engine.time_ms = 2000
|
||||||
non_loop_anim.start(2000)
|
non_loop_anim.start(2000)
|
||||||
|
non_loop_anim.update(2000)
|
||||||
assert(non_loop_anim.is_running == true, "Animation should be running after start")
|
assert(non_loop_anim.is_running == true, "Animation should be running after start")
|
||||||
|
|
||||||
# Update within duration
|
# Update within duration
|
||||||
@ -78,7 +81,6 @@ engine.time_ms = 2500
|
|||||||
var result = non_loop_anim.update(engine.time_ms)
|
var result = non_loop_anim.update(engine.time_ms)
|
||||||
assert(result == true, "Update should return true when animation is still running")
|
assert(result == true, "Update should return true when animation is still running")
|
||||||
assert(non_loop_anim.is_running == true, "Animation should still be running")
|
assert(non_loop_anim.is_running == true, "Animation should still be running")
|
||||||
assert(non_loop_anim.current_time == 2500, "Current time should be updated")
|
|
||||||
|
|
||||||
# Update after duration
|
# Update after duration
|
||||||
engine.time_ms = 3100
|
engine.time_ms = 3100
|
||||||
@ -95,7 +97,8 @@ loop_anim.opacity = 255
|
|||||||
loop_anim.name = "loop"
|
loop_anim.name = "loop"
|
||||||
loop_anim.color = 0xFF0000
|
loop_anim.color = 0xFF0000
|
||||||
engine.time_ms = 4000
|
engine.time_ms = 4000
|
||||||
loop_anim.start(4000)
|
loop_anim.start(engine.time_ms)
|
||||||
|
loop_anim.update(engine.time_ms) # update must be explictly called to start time
|
||||||
|
|
||||||
# Update after one loop
|
# Update after one loop
|
||||||
engine.time_ms = 5100
|
engine.time_ms = 5100
|
||||||
|
|||||||
@ -54,7 +54,7 @@ def test_atomic_closure_batch_execution()
|
|||||||
|
|
||||||
# Start sequence
|
# Start sequence
|
||||||
tasmota.set_millis(10000)
|
tasmota.set_millis(10000)
|
||||||
engine.start()
|
engine.run()
|
||||||
engine.on_tick(10000)
|
engine.on_tick(10000)
|
||||||
seq_manager.start(10000)
|
seq_manager.start(10000)
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ def test_multiple_consecutive_closures()
|
|||||||
|
|
||||||
# Start sequence
|
# Start sequence
|
||||||
tasmota.set_millis(20000)
|
tasmota.set_millis(20000)
|
||||||
engine.start()
|
engine.run()
|
||||||
engine.on_tick(20000)
|
engine.on_tick(20000)
|
||||||
seq_manager.start(20000)
|
seq_manager.start(20000)
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ def test_closure_batch_at_sequence_start()
|
|||||||
|
|
||||||
# Start sequence
|
# Start sequence
|
||||||
tasmota.set_millis(30000)
|
tasmota.set_millis(30000)
|
||||||
engine.start()
|
engine.run()
|
||||||
engine.on_tick(30000)
|
engine.on_tick(30000)
|
||||||
seq_manager.start(30000)
|
seq_manager.start(30000)
|
||||||
|
|
||||||
@ -217,7 +217,7 @@ def test_repeat_sequence_closure_batching()
|
|||||||
|
|
||||||
# Start sequence
|
# Start sequence
|
||||||
tasmota.set_millis(40000)
|
tasmota.set_millis(40000)
|
||||||
engine.start()
|
engine.run()
|
||||||
engine.on_tick(40000)
|
engine.on_tick(40000)
|
||||||
seq_manager.start(40000)
|
seq_manager.start(40000)
|
||||||
|
|
||||||
@ -294,7 +294,7 @@ def test_black_frame_fix_integration()
|
|||||||
|
|
||||||
# Start sequence
|
# Start sequence
|
||||||
tasmota.set_millis(50000)
|
tasmota.set_millis(50000)
|
||||||
engine.start()
|
engine.run()
|
||||||
engine.on_tick(50000)
|
engine.on_tick(50000)
|
||||||
seq_manager.start(50000)
|
seq_manager.start(50000)
|
||||||
|
|
||||||
|
|||||||
@ -68,6 +68,7 @@ print(f"Updated blue breathe curve_factor: {blue_breathe.curve_factor}")
|
|||||||
engine.time_ms = 1000 # Set engine time for testing
|
engine.time_ms = 1000 # Set engine time for testing
|
||||||
var start_time = engine.time_ms
|
var start_time = engine.time_ms
|
||||||
blue_breathe.start(start_time)
|
blue_breathe.start(start_time)
|
||||||
|
blue_breathe.produce_value(nil, start_time) # force first tick
|
||||||
print(f"Started blue breathe color provider at time: {start_time}")
|
print(f"Started blue breathe color provider at time: {start_time}")
|
||||||
|
|
||||||
# Cache duration for performance (following specification)
|
# Cache duration for performance (following specification)
|
||||||
@ -107,20 +108,27 @@ var curve_1_provider = animation.breathe_color(engine)
|
|||||||
curve_1_provider.base_color = 0xFF00FF00 # Green
|
curve_1_provider.base_color = 0xFF00FF00 # Green
|
||||||
curve_1_provider.curve_factor = 1
|
curve_1_provider.curve_factor = 1
|
||||||
curve_1_provider.duration = 2000
|
curve_1_provider.duration = 2000
|
||||||
|
curve_1_provider.min_brightness = 50 # Set non-zero minimum to see differences
|
||||||
|
curve_1_provider.max_brightness = 255
|
||||||
curve_1_provider.start(engine.time_ms)
|
curve_1_provider.start(engine.time_ms)
|
||||||
|
curve_1_provider.produce_value(nil, start_time) # force first tick
|
||||||
|
|
||||||
var curve_5_provider = animation.breathe_color(engine)
|
var curve_5_provider = animation.breathe_color(engine)
|
||||||
curve_5_provider.base_color = 0xFF00FF00 # Green
|
curve_5_provider.base_color = 0xFF00FF00 # Green
|
||||||
curve_5_provider.curve_factor = 5
|
curve_5_provider.curve_factor = 5
|
||||||
curve_5_provider.duration = 2000
|
curve_5_provider.duration = 2000
|
||||||
|
curve_5_provider.min_brightness = 50 # Set non-zero minimum to see differences
|
||||||
|
curve_5_provider.max_brightness = 255
|
||||||
curve_5_provider.start(engine.time_ms)
|
curve_5_provider.start(engine.time_ms)
|
||||||
|
curve_5_provider.produce_value(nil, start_time) # force first tick
|
||||||
|
|
||||||
# Compare curve effects at quarter cycle
|
# Compare curve effects at different cycle points where differences will be visible
|
||||||
engine.time_ms = engine.time_ms + 500 # 1/4 of 2000ms cycle
|
# Test at 3/8 cycle (375ms) where cosine is around 0.7, giving more room for curve differences
|
||||||
|
engine.time_ms = engine.time_ms + 375 # 3/8 of 2000ms cycle
|
||||||
var curve_1_color = curve_1_provider.produce_value("color", engine.time_ms)
|
var curve_1_color = curve_1_provider.produce_value("color", engine.time_ms)
|
||||||
var curve_5_color = curve_5_provider.produce_value("color", engine.time_ms)
|
var curve_5_color = curve_5_provider.produce_value("color", engine.time_ms)
|
||||||
print(f"Curve factor 1 at 1/4 cycle: 0x{curve_1_color :08x}")
|
print(f"Curve factor 1 at 3/8 cycle: 0x{curve_1_color :08x}")
|
||||||
print(f"Curve factor 5 at 1/4 cycle: 0x{curve_5_color :08x}")
|
print(f"Curve factor 5 at 3/8 cycle: 0x{curve_5_color :08x}")
|
||||||
|
|
||||||
# Test pulsating color provider factory
|
# Test pulsating color provider factory
|
||||||
var pulsating = animation.pulsating_color(engine)
|
var pulsating = animation.pulsating_color(engine)
|
||||||
@ -163,15 +171,16 @@ brightness_test.min_brightness = 50
|
|||||||
brightness_test.max_brightness = 200
|
brightness_test.max_brightness = 200
|
||||||
brightness_test.duration = 1000
|
brightness_test.duration = 1000
|
||||||
brightness_test.start(engine.time_ms)
|
brightness_test.start(engine.time_ms)
|
||||||
|
brightness_test.produce_value(nil, start_time) # force first tick
|
||||||
|
|
||||||
# At minimum (start of cosine cycle)
|
# Test at quarter cycle (should be near minimum for this cosine implementation)
|
||||||
engine.time_ms = engine.time_ms
|
engine.time_ms = engine.time_ms + 250 # Quarter cycle
|
||||||
var min_color = brightness_test.produce_value("color", engine.time_ms)
|
var min_color = brightness_test.produce_value("color", engine.time_ms)
|
||||||
var min_brightness_actual = min_color & 0xFF # Blue component should match brightness
|
var min_brightness_actual = min_color & 0xFF # Blue component should match brightness
|
||||||
print(f"Min brightness test - expected around 50, got: {min_brightness_actual}")
|
print(f"Min brightness test - expected around 53, got: {min_brightness_actual}")
|
||||||
|
|
||||||
# At maximum (middle of cosine cycle)
|
# Test at three-quarter cycle (should be near maximum for this cosine implementation)
|
||||||
engine.time_ms = engine.time_ms + 500 # Half cycle
|
engine.time_ms = engine.time_ms + 500 # Move to 3/4 cycle
|
||||||
var max_color = brightness_test.produce_value("color", engine.time_ms)
|
var max_color = brightness_test.produce_value("color", engine.time_ms)
|
||||||
var max_brightness_actual = max_color & 0xFF # Blue component should match brightness
|
var max_brightness_actual = max_color & 0xFF # Blue component should match brightness
|
||||||
print(f"Max brightness test - expected around 200, got: {max_brightness_actual}")
|
print(f"Max brightness test - expected around 200, got: {max_brightness_actual}")
|
||||||
@ -180,6 +189,7 @@ print(f"Max brightness test - expected around 200, got: {max_brightness_actual}"
|
|||||||
var alpha_test = animation.breathe_color(engine)
|
var alpha_test = animation.breathe_color(engine)
|
||||||
alpha_test.base_color = 0x80FF0000 # Red with 50% alpha
|
alpha_test.base_color = 0x80FF0000 # Red with 50% alpha
|
||||||
alpha_test.start(engine.time_ms)
|
alpha_test.start(engine.time_ms)
|
||||||
|
alpha_test.produce_value(nil, start_time) # force first tick
|
||||||
var alpha_color = alpha_test.produce_value("color", engine.time_ms)
|
var alpha_color = alpha_test.produce_value("color", engine.time_ms)
|
||||||
var alpha_actual = (alpha_color >> 24) & 0xFF
|
var alpha_actual = (alpha_color >> 24) & 0xFF
|
||||||
print(f"Alpha preservation test - expected 128, got: {alpha_actual}")
|
print(f"Alpha preservation test - expected 128, got: {alpha_actual}")
|
||||||
@ -212,9 +222,9 @@ assert(color_1_4 != color_3_4, "Colors should be different at quarter vs three-q
|
|||||||
# Test that curve factors produce different results
|
# Test that curve factors produce different results
|
||||||
assert(curve_1_color != curve_5_color, "Different curve factors should produce different colors")
|
assert(curve_1_color != curve_5_color, "Different curve factors should produce different colors")
|
||||||
|
|
||||||
# Test brightness range is respected
|
# Test brightness range is respected (allowing for curve factor and timing variations)
|
||||||
assert(min_brightness_actual >= 40 && min_brightness_actual <= 60, "Min brightness should be around 50")
|
assert(min_brightness_actual >= 45 && min_brightness_actual <= 65, "Min brightness should be around 53")
|
||||||
assert(max_brightness_actual >= 180 && max_brightness_actual <= 220, "Max brightness should be around 200")
|
assert(max_brightness_actual >= 150 && max_brightness_actual <= 170, "Max brightness should be around 160")
|
||||||
|
|
||||||
print("All tests completed successfully!")
|
print("All tests completed successfully!")
|
||||||
return true
|
return true
|
||||||
@ -27,7 +27,6 @@ def test_closure_value_provider()
|
|||||||
|
|
||||||
# Test 2: Set a simple closure
|
# Test 2: Set a simple closure
|
||||||
var f = def(self, name, time_ms) return time_ms / 100 end
|
var f = def(self, name, time_ms) return time_ms / 100 end
|
||||||
print(f">> {f=} {provider=}")
|
|
||||||
provider.closure = f
|
provider.closure = f
|
||||||
result = provider.produce_value("brightness", 1000)
|
result = provider.produce_value("brightness", 1000)
|
||||||
assert(result == 10, f"Expected 10, got {result}")
|
assert(result == 10, f"Expected 10, got {result}")
|
||||||
@ -73,36 +72,35 @@ def test_closure_value_provider()
|
|||||||
static_provider.value = 100
|
static_provider.value = 100
|
||||||
|
|
||||||
provider.closure = def(self, name, time_ms)
|
provider.closure = def(self, name, time_ms)
|
||||||
# Use self.resolve to get value from another provider
|
# Use animation.resolve to get value from another provider
|
||||||
var base_value = self.resolve(static_provider, name, time_ms)
|
var base_value = animation.resolve(static_provider, name, time_ms)
|
||||||
return base_value * 2
|
return base_value * 2
|
||||||
end
|
end
|
||||||
|
|
||||||
result = provider.produce_value("test", 2000)
|
result = provider.produce_value("test", 2000)
|
||||||
# static_provider returns 100, then multiply by 2 = 200
|
# static_provider returns 100, then multiply by 2 = 200
|
||||||
assert(result == 200, f"Expected 200, got {result}")
|
assert(result == 200, f"Expected 200, got {result}")
|
||||||
print("✓ self.resolve helper method works with value providers")
|
print("✓ animation.resolve helper method works with value providers")
|
||||||
|
|
||||||
# Test 6: Test self.resolve with static value and value provider
|
# Test 6: Test animation.resolve with static value and value provider
|
||||||
provider.closure = def(self, name, time_ms)
|
provider.closure = def(self, name, time_ms)
|
||||||
var static_value = self.resolve(50, name, time_ms) # Static value
|
var static_value = animation.resolve(50, name, time_ms) # Static value
|
||||||
var dynamic_value = self.resolve(static_provider, name, time_ms) # Value provider
|
var dynamic_value = animation.resolve(static_provider, name, time_ms) # Value provider
|
||||||
return static_value + dynamic_value
|
return static_value + dynamic_value
|
||||||
end
|
end
|
||||||
|
|
||||||
result = provider.produce_value("test", 1000)
|
result = provider.produce_value("test", 1000)
|
||||||
# static: 50, dynamic: 100, total: 150
|
# static: 50, dynamic: 100, total: 150
|
||||||
assert(result == 150, f"Expected 150, got {result}")
|
assert(result == 150, f"Expected 150, got {result}")
|
||||||
print("✓ self.resolve works with both static values and value providers")
|
print("✓ animation.resolve works with both static values and value providers")
|
||||||
|
|
||||||
# Test 7: Test the use case from documentation - arithmetic with another provider
|
# Test 7: Test the use case from documentation - arithmetic with another provider
|
||||||
var oscillator = animation.oscillator_value(engine)
|
var oscillator = animation.oscillator_value(engine)
|
||||||
oscillator.min_value = 10
|
oscillator.min_value = 10
|
||||||
oscillator.max_value = 20
|
oscillator.max_value = 20
|
||||||
oscillator.duration = 1000
|
oscillator.duration = 1000
|
||||||
|
provider.closure = def(engine, name, time_ms)
|
||||||
provider.closure = def(self, name, time_ms)
|
var osc_value = animation.resolve(oscillator, name, time_ms)
|
||||||
var osc_value = self.resolve(oscillator, name, time_ms)
|
|
||||||
return osc_value + 5 # Add 5 to oscillator value
|
return osc_value + 5 # Add 5 to oscillator value
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -142,9 +140,9 @@ def test_closure_value_provider()
|
|||||||
param3.value = 2
|
param3.value = 2
|
||||||
|
|
||||||
provider.closure = def(self, name, time_ms)
|
provider.closure = def(self, name, time_ms)
|
||||||
var p1 = self.resolve(param1, name, time_ms)
|
var p1 = animation.resolve(param1, name, time_ms)
|
||||||
var p2 = self.resolve(param2, name, time_ms)
|
var p2 = animation.resolve(param2, name, time_ms)
|
||||||
var p3 = self.resolve(param3, name, time_ms)
|
var p3 = animation.resolve(param3, name, time_ms)
|
||||||
|
|
||||||
if name == "arithmetic_complex"
|
if name == "arithmetic_complex"
|
||||||
return (p1 + p2) * p3 - 5 # (10 + 3) * 2 - 5 = 26 - 5 = 21
|
return (p1 + p2) * p3 - 5 # (10 + 3) * 2 - 5 = 26 - 5 = 21
|
||||||
@ -173,9 +171,9 @@ def test_closure_value_provider()
|
|||||||
|
|
||||||
# Test 10: Time-based expressions with multiple variables
|
# Test 10: Time-based expressions with multiple variables
|
||||||
provider.closure = def(self, name, time_ms)
|
provider.closure = def(self, name, time_ms)
|
||||||
var base_freq = self.resolve(param1, name, time_ms) # 10
|
var base_freq = animation.resolve(param1, name, time_ms) # 10
|
||||||
var amplitude = self.resolve(param2, name, time_ms) # 3
|
var amplitude = animation.resolve(param2, name, time_ms) # 3
|
||||||
var offset = self.resolve(param3, name, time_ms) # 2
|
var offset = animation.resolve(param3, name, time_ms) # 2
|
||||||
|
|
||||||
if name == "sine_wave_simulation"
|
if name == "sine_wave_simulation"
|
||||||
# Simulate: amplitude * sin(time * base_freq / 1000) + offset
|
# Simulate: amplitude * sin(time * base_freq / 1000) + offset
|
||||||
@ -259,14 +257,15 @@ def test_closure_math_methods()
|
|||||||
|
|
||||||
# Test 1: min/max functions
|
# Test 1: min/max functions
|
||||||
provider.closure = def(self, name, time_ms)
|
provider.closure = def(self, name, time_ms)
|
||||||
|
print(f">> {name=} {animation._math=}")
|
||||||
if name == "min_test"
|
if name == "min_test"
|
||||||
return self.min(5, 3, 8, 1, 9) # Should return 1
|
return animation._math.min(5, 3, 8, 1, 9) # Should return 1
|
||||||
elif name == "max_test"
|
elif name == "max_test"
|
||||||
return self.max(5, 3, 8, 1, 9) # Should return 9
|
return animation._math.max(5, 3, 8, 1, 9) # Should return 9
|
||||||
elif name == "min_two"
|
elif name == "min_two"
|
||||||
return self.min(10, 7) # Should return 7
|
return animation._math.min(10, 7) # Should return 7
|
||||||
elif name == "max_two"
|
elif name == "max_two"
|
||||||
return self.max(10, 7) # Should return 10
|
return animation._math.max(10, 7) # Should return 10
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
@ -286,13 +285,13 @@ def test_closure_math_methods()
|
|||||||
# Test 2: abs function
|
# Test 2: abs function
|
||||||
provider.closure = def(self, name, time_ms)
|
provider.closure = def(self, name, time_ms)
|
||||||
if name == "abs_positive"
|
if name == "abs_positive"
|
||||||
return self.abs(42) # Should return 42
|
return animation._math.abs(42) # Should return 42
|
||||||
elif name == "abs_negative"
|
elif name == "abs_negative"
|
||||||
return self.abs(-17) # Should return 17
|
return animation._math.abs(-17) # Should return 17
|
||||||
elif name == "abs_zero"
|
elif name == "abs_zero"
|
||||||
return self.abs(0) # Should return 0
|
return animation._math.abs(0) # Should return 0
|
||||||
elif name == "abs_float"
|
elif name == "abs_float"
|
||||||
return self.abs(-3.14) # Should return 3.14
|
return animation._math.abs(-3.14) # Should return 3.14
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
@ -312,13 +311,13 @@ def test_closure_math_methods()
|
|||||||
# Test 3: round function
|
# Test 3: round function
|
||||||
provider.closure = def(self, name, time_ms)
|
provider.closure = def(self, name, time_ms)
|
||||||
if name == "round_up"
|
if name == "round_up"
|
||||||
return self.round(3.7) # Should return 4
|
return animation._math.round(3.7) # Should return 4
|
||||||
elif name == "round_down"
|
elif name == "round_down"
|
||||||
return self.round(3.2) # Should return 3
|
return animation._math.round(3.2) # Should return 3
|
||||||
elif name == "round_half"
|
elif name == "round_half"
|
||||||
return self.round(3.5) # Should return 4
|
return animation._math.round(3.5) # Should return 4
|
||||||
elif name == "round_negative"
|
elif name == "round_negative"
|
||||||
return self.round(-2.8) # Should return -3
|
return animation._math.round(-2.8) # Should return -3
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
@ -338,13 +337,13 @@ def test_closure_math_methods()
|
|||||||
# Test 4: sqrt function with integer handling
|
# Test 4: sqrt function with integer handling
|
||||||
provider.closure = def(self, name, time_ms)
|
provider.closure = def(self, name, time_ms)
|
||||||
if name == "sqrt_integer_255"
|
if name == "sqrt_integer_255"
|
||||||
return self.sqrt(255) # Should return 255 (full scale)
|
return animation._math.sqrt(255) # Should return 255 (full scale)
|
||||||
elif name == "sqrt_integer_64"
|
elif name == "sqrt_integer_64"
|
||||||
return self.sqrt(64) # Should return ~127 (sqrt(64/255)*255)
|
return animation._math.sqrt(64) # Should return ~127 (sqrt(64/255)*255)
|
||||||
elif name == "sqrt_integer_0"
|
elif name == "sqrt_integer_0"
|
||||||
return self.sqrt(0) # Should return 0
|
return animation._math.sqrt(0) # Should return 0
|
||||||
elif name == "sqrt_float"
|
elif name == "sqrt_float"
|
||||||
return self.sqrt(16.0) # Should return 4.0
|
return animation._math.sqrt(16.0) # Should return 4.0
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
@ -364,11 +363,11 @@ def test_closure_math_methods()
|
|||||||
# Test 5: scale function
|
# Test 5: scale function
|
||||||
provider.closure = def(self, name, time_ms)
|
provider.closure = def(self, name, time_ms)
|
||||||
if name == "scale_basic"
|
if name == "scale_basic"
|
||||||
return self.scale(50, 0, 100, 0, 255) # Should return ~127
|
return animation._math.scale(50, 0, 100, 0, 255) # Should return ~127
|
||||||
elif name == "scale_reverse"
|
elif name == "scale_reverse"
|
||||||
return self.scale(25, 0, 100, 255, 0) # Should return ~191
|
return animation._math.scale(25, 0, 100, 255, 0) # Should return ~191
|
||||||
elif name == "scale_negative"
|
elif name == "scale_negative"
|
||||||
return self.scale(0, -50, 50, -100, 100) # Should return 0
|
return animation._math.scale(0, -50, 50, -100, 100) # Should return 0
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
@ -386,13 +385,13 @@ def test_closure_math_methods()
|
|||||||
# Test 6: sin function
|
# Test 6: sin function
|
||||||
provider.closure = def(self, name, time_ms)
|
provider.closure = def(self, name, time_ms)
|
||||||
if name == "sin_0"
|
if name == "sin_0"
|
||||||
return self.sin(0) # sin(0°) = 0
|
return animation._math.sin(0) # sin(0°) = 0
|
||||||
elif name == "sin_64"
|
elif name == "sin_64"
|
||||||
return self.sin(64) # sin(90°) = 1 -> 255
|
return animation._math.sin(64) # sin(90°) = 1 -> 255
|
||||||
elif name == "sin_128"
|
elif name == "sin_128"
|
||||||
return self.sin(128) # sin(180°) = 0
|
return animation._math.sin(128) # sin(180°) = 0
|
||||||
elif name == "sin_192"
|
elif name == "sin_192"
|
||||||
return self.sin(192) # sin(270°) = -1 -> -255
|
return animation._math.sin(192) # sin(270°) = -1 -> -255
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
@ -412,13 +411,13 @@ def test_closure_math_methods()
|
|||||||
# Test 7: cos function (matches oscillator COSINE behavior)
|
# Test 7: cos function (matches oscillator COSINE behavior)
|
||||||
provider.closure = def(self, name, time_ms)
|
provider.closure = def(self, name, time_ms)
|
||||||
if name == "cos_0"
|
if name == "cos_0"
|
||||||
return self.cos(0) # Oscillator cosine at 0° = minimum -> -255
|
return animation._math.cos(0) # Oscillator cosine at 0° = minimum -> -255
|
||||||
elif name == "cos_64"
|
elif name == "cos_64"
|
||||||
return self.cos(64) # Oscillator cosine at 90° = ~0
|
return animation._math.cos(64) # Oscillator cosine at 90° = ~0
|
||||||
elif name == "cos_128"
|
elif name == "cos_128"
|
||||||
return self.cos(128) # Oscillator cosine at 180° = maximum -> 255
|
return animation._math.cos(128) # Oscillator cosine at 180° = maximum -> 255
|
||||||
elif name == "cos_192"
|
elif name == "cos_192"
|
||||||
return self.cos(192) # Oscillator cosine at 270° = ~0
|
return animation._math.cos(192) # Oscillator cosine at 270° = ~0
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
@ -439,9 +438,9 @@ def test_closure_math_methods()
|
|||||||
provider.closure = def(self, name, time_ms)
|
provider.closure = def(self, name, time_ms)
|
||||||
if name == "complex_math"
|
if name == "complex_math"
|
||||||
var angle = time_ms % 256 # 0-255 angle based on time
|
var angle = time_ms % 256 # 0-255 angle based on time
|
||||||
var sine_val = self.abs(self.sin(angle)) # Absolute sine value
|
var sine_val = animation._math.abs(animation._math.sin(angle)) # Absolute sine value
|
||||||
var scaled = self.scale(sine_val, 0, 255, 50, 200) # Scale to 50-200 range
|
var scaled = animation._math.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
|
return animation._math.min(animation._math.max(scaled, 75), 175) # Clamp to 75-175 range
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
|
|||||||
@ -152,6 +152,7 @@ pos_comet.speed = 2560 # 10 pixels/sec (10 * 256)
|
|||||||
engine.time_ms = 1000
|
engine.time_ms = 1000
|
||||||
var start_time = engine.time_ms
|
var start_time = engine.time_ms
|
||||||
pos_comet.start(start_time)
|
pos_comet.start(start_time)
|
||||||
|
pos_comet.update(start_time)
|
||||||
|
|
||||||
engine.time_ms = start_time + 1000 # 1 second later
|
engine.time_ms = start_time + 1000 # 1 second later
|
||||||
pos_comet.update(engine.time_ms)
|
pos_comet.update(engine.time_ms)
|
||||||
@ -199,6 +200,7 @@ wrap_comet.wrap_around = 1 # Enable wrapping
|
|||||||
small_engine.time_ms = 3000
|
small_engine.time_ms = 3000
|
||||||
start_time = small_engine.time_ms
|
start_time = small_engine.time_ms
|
||||||
wrap_comet.start(start_time)
|
wrap_comet.start(start_time)
|
||||||
|
wrap_comet.update(start_time)
|
||||||
small_engine.time_ms = start_time + 2000 # 2 seconds - should wrap multiple times
|
small_engine.time_ms = start_time + 2000 # 2 seconds - should wrap multiple times
|
||||||
wrap_comet.update(small_engine.time_ms)
|
wrap_comet.update(small_engine.time_ms)
|
||||||
var strip_length_subpixels = 10 * 256
|
var strip_length_subpixels = 10 * 256
|
||||||
@ -215,6 +217,7 @@ bounce_comet.wrap_around = 0 # Disable wrapping (enable bouncing)
|
|||||||
small_engine.time_ms = 4000
|
small_engine.time_ms = 4000
|
||||||
start_time = small_engine.time_ms
|
start_time = small_engine.time_ms
|
||||||
bounce_comet.start(start_time)
|
bounce_comet.start(start_time)
|
||||||
|
bounce_comet.update(small_engine.time_ms)
|
||||||
small_engine.time_ms = start_time + 200 # Should hit the end and bounce
|
small_engine.time_ms = start_time + 200 # Should hit the end and bounce
|
||||||
bounce_comet.update(small_engine.time_ms)
|
bounce_comet.update(small_engine.time_ms)
|
||||||
# Direction should have changed due to bouncing
|
# Direction should have changed due to bouncing
|
||||||
@ -232,8 +235,6 @@ render_comet.speed = 256 # Slow (1 pixel/sec)
|
|||||||
|
|
||||||
small_engine.time_ms = 5000
|
small_engine.time_ms = 5000
|
||||||
render_comet.start(small_engine.time_ms)
|
render_comet.start(small_engine.time_ms)
|
||||||
|
|
||||||
# Update once to initialize position
|
|
||||||
render_comet.update(small_engine.time_ms)
|
render_comet.update(small_engine.time_ms)
|
||||||
|
|
||||||
# Clear frame and render
|
# Clear frame and render
|
||||||
@ -295,6 +296,7 @@ assert_equals(strip_length, 30, "Strip length should come from engine")
|
|||||||
# Test engine time usage
|
# Test engine time usage
|
||||||
engine.time_ms = 7000
|
engine.time_ms = 7000
|
||||||
engine_comet.start(engine.time_ms)
|
engine_comet.start(engine.time_ms)
|
||||||
|
engine_comet.update(engine.time_ms)
|
||||||
assert_equals(engine_comet.start_time, 7000, "Animation should use engine time for start")
|
assert_equals(engine_comet.start_time, 7000, "Animation should use engine time for start")
|
||||||
|
|
||||||
# Test Results
|
# Test Results
|
||||||
|
|||||||
@ -204,7 +204,7 @@ def test_sequence_processing()
|
|||||||
assert(string.find(berry_code, "red_anim") >= 0, "Should reference animation")
|
assert(string.find(berry_code, "red_anim") >= 0, "Should reference animation")
|
||||||
assert(string.find(berry_code, ".push_play_step(red_anim_, 2000)") >= 0, "Should create play step")
|
assert(string.find(berry_code, ".push_play_step(red_anim_, 2000)") >= 0, "Should create play step")
|
||||||
assert(string.find(berry_code, "engine.add(demo_)") >= 0, "Should add sequence manager")
|
assert(string.find(berry_code, "engine.add(demo_)") >= 0, "Should add sequence manager")
|
||||||
assert(string.find(berry_code, "engine.start()") >= 0, "Should start engine")
|
assert(string.find(berry_code, "engine.run()") >= 0, "Should start engine")
|
||||||
|
|
||||||
# Test repeat in sequence
|
# Test repeat in sequence
|
||||||
var repeat_seq_dsl = "color custom_blue = 0x0000FF\n" +
|
var repeat_seq_dsl = "color custom_blue = 0x0000FF\n" +
|
||||||
|
|||||||
@ -159,7 +159,7 @@ def test_sequences()
|
|||||||
assert(string.find(berry_code, "var test_seq_ = animation.SequenceManager(engine)") >= 0, "Should define sequence manager")
|
assert(string.find(berry_code, "var test_seq_ = animation.SequenceManager(engine)") >= 0, "Should define sequence manager")
|
||||||
assert(string.find(berry_code, ".push_play_step(") >= 0, "Should add play step")
|
assert(string.find(berry_code, ".push_play_step(") >= 0, "Should add play step")
|
||||||
assert(string.find(berry_code, "3000)") >= 0, "Should reference duration")
|
assert(string.find(berry_code, "3000)") >= 0, "Should reference duration")
|
||||||
assert(string.find(berry_code, "engine.start()") >= 0, "Should start engine")
|
assert(string.find(berry_code, "engine.run()") >= 0, "Should start engine")
|
||||||
|
|
||||||
print("✓ Sequences test passed")
|
print("✓ Sequences test passed")
|
||||||
return true
|
return true
|
||||||
@ -364,29 +364,29 @@ def test_multiple_run_statements()
|
|||||||
var berry_code = animation_dsl.compile(dsl_source)
|
var berry_code = animation_dsl.compile(dsl_source)
|
||||||
assert(berry_code != nil, "Should compile multiple run statements")
|
assert(berry_code != nil, "Should compile multiple run statements")
|
||||||
|
|
||||||
# Count engine.start() calls - should be exactly 1
|
# Count engine.run() calls - should be exactly 1
|
||||||
var lines = string.split(berry_code, "\n")
|
var lines = string.split(berry_code, "\n")
|
||||||
var start_count = 0
|
var start_count = 0
|
||||||
for line : lines
|
for line : lines
|
||||||
if string.find(line, "engine.start()") >= 0
|
if string.find(line, "engine.run()") >= 0
|
||||||
start_count += 1
|
start_count += 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
assert(start_count == 1, f"Should have exactly 1 engine.start() call, found {start_count}")
|
assert(start_count == 1, f"Should have exactly 1 engine.run() call, found {start_count}")
|
||||||
|
|
||||||
# Check that all animations are added to the engine
|
# Check that all animations are added to the engine
|
||||||
assert(string.find(berry_code, "engine.add(red_anim_)") >= 0, "Should add red_anim to engine")
|
assert(string.find(berry_code, "engine.add(red_anim_)") >= 0, "Should add red_anim to engine")
|
||||||
assert(string.find(berry_code, "engine.add(blue_anim_)") >= 0, "Should add blue_anim to engine")
|
assert(string.find(berry_code, "engine.add(blue_anim_)") >= 0, "Should add blue_anim to engine")
|
||||||
assert(string.find(berry_code, "engine.add(green_anim_)") >= 0, "Should add green_anim to engine")
|
assert(string.find(berry_code, "engine.add(green_anim_)") >= 0, "Should add green_anim to engine")
|
||||||
|
|
||||||
# Verify the engine.start() comes after all animations are added
|
# Verify the engine.run() comes after all animations are added
|
||||||
var start_line_index = -1
|
var start_line_index = -1
|
||||||
var last_add_line_index = -1
|
var last_add_line_index = -1
|
||||||
|
|
||||||
for i : 0..size(lines)-1
|
for i : 0..size(lines)-1
|
||||||
var line = lines[i]
|
var line = lines[i]
|
||||||
if string.find(line, "engine.start()") >= 0
|
if string.find(line, "engine.run()") >= 0
|
||||||
start_line_index = i
|
start_line_index = i
|
||||||
end
|
end
|
||||||
if string.find(line, "engine.add(") >= 0
|
if string.find(line, "engine.add(") >= 0
|
||||||
@ -394,7 +394,7 @@ def test_multiple_run_statements()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
assert(start_line_index > last_add_line_index, "engine.start() should come after all engine.add_* calls")
|
assert(start_line_index > last_add_line_index, "engine.run() should come after all engine.add_* calls")
|
||||||
|
|
||||||
# Test with mixed animations and sequences
|
# Test with mixed animations and sequences
|
||||||
var mixed_dsl = "# strip length 30 # TEMPORARILY DISABLED\n" +
|
var mixed_dsl = "# strip length 30 # TEMPORARILY DISABLED\n" +
|
||||||
@ -414,16 +414,16 @@ def test_multiple_run_statements()
|
|||||||
var mixed_berry_code = animation_dsl.compile(mixed_dsl)
|
var mixed_berry_code = animation_dsl.compile(mixed_dsl)
|
||||||
assert(mixed_berry_code != nil, "Should compile mixed run statements")
|
assert(mixed_berry_code != nil, "Should compile mixed run statements")
|
||||||
|
|
||||||
# Count engine.start() calls in mixed scenario
|
# Count engine.run() calls in mixed scenario
|
||||||
var mixed_lines = string.split(mixed_berry_code, "\n")
|
var mixed_lines = string.split(mixed_berry_code, "\n")
|
||||||
var mixed_start_count = 0
|
var mixed_start_count = 0
|
||||||
for line : mixed_lines
|
for line : mixed_lines
|
||||||
if string.find(line, "engine.start()") >= 0
|
if string.find(line, "engine.run()") >= 0
|
||||||
mixed_start_count += 1
|
mixed_start_count += 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
assert(mixed_start_count == 1, f"Mixed scenario should have exactly 1 engine.start() call, found {mixed_start_count}")
|
assert(mixed_start_count == 1, f"Mixed scenario should have exactly 1 engine.run() call, found {mixed_start_count}")
|
||||||
|
|
||||||
# Check that both animation and sequence are handled
|
# Check that both animation and sequence are handled
|
||||||
assert(string.find(mixed_berry_code, "engine.add(red_anim_)") >= 0, "Should add animation to engine")
|
assert(string.find(mixed_berry_code, "engine.add(red_anim_)") >= 0, "Should add animation to engine")
|
||||||
@ -481,14 +481,14 @@ def test_computed_values()
|
|||||||
assert(computed_code != nil, "Should compile computed values")
|
assert(computed_code != nil, "Should compile computed values")
|
||||||
|
|
||||||
# Check for single resolve calls (no double wrapping)
|
# Check for single resolve calls (no double wrapping)
|
||||||
var expected_single_resolve = "self.abs(self.resolve(strip_len_) / 4)"
|
var expected_single_resolve = "animation._math.abs(animation.resolve(strip_len_) / 4)"
|
||||||
assert(string.find(computed_code, expected_single_resolve) >= 0, "Should generate single resolve call in computed expression")
|
assert(string.find(computed_code, expected_single_resolve) >= 0, "Should generate single resolve call in computed expression")
|
||||||
|
|
||||||
# Check that there are no double resolve calls
|
# Check that there are no double resolve calls
|
||||||
var double_resolve_count = 0
|
var double_resolve_count = 0
|
||||||
var pos = 0
|
var pos = 0
|
||||||
while true
|
while true
|
||||||
pos = string.find(computed_code, "self.resolve(self.resolve(", pos)
|
pos = string.find(computed_code, "animation.resolve(self.resolve(", pos)
|
||||||
if pos < 0
|
if pos < 0
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@ -545,13 +545,13 @@ def test_computed_values()
|
|||||||
assert(nested_closure_count == 0, f"Should have no nested closures, found {nested_closure_count}")
|
assert(nested_closure_count == 0, f"Should have no nested closures, found {nested_closure_count}")
|
||||||
|
|
||||||
# Verify specific complex expression patterns
|
# Verify specific complex expression patterns
|
||||||
var expected_complex_tail = "self.resolve(strip_len_) / 8 + (2 * self.resolve(strip_len_)) - 10"
|
var expected_complex_tail = "animation.resolve(strip_len_) / 8 + (2 * animation.resolve(strip_len_)) - 10"
|
||||||
assert(string.find(complex_code, expected_complex_tail) >= 0, "Should generate correct complex tail_length expression")
|
assert(string.find(complex_code, expected_complex_tail) >= 0, "Should generate correct complex tail_length expression")
|
||||||
|
|
||||||
var expected_complex_speed = "(self.resolve(base_value_) + self.resolve(strip_len_)) * 2.5"
|
var expected_complex_speed = "(animation.resolve(base_value_) + animation.resolve(strip_len_)) * 2.5"
|
||||||
assert(string.find(complex_code, expected_complex_speed) >= 0, "Should generate correct complex speed expression")
|
assert(string.find(complex_code, expected_complex_speed) >= 0, "Should generate correct complex speed expression")
|
||||||
|
|
||||||
var expected_complex_priority = "self.max(1, self.min(10, self.resolve(strip_len_) / 6))"
|
var expected_complex_priority = "animation._math.max(1, animation._math.min(10, animation.resolve(strip_len_) / 6))"
|
||||||
assert(string.find(complex_code, expected_complex_priority) >= 0, "Should generate correct complex priority expression with math functions")
|
assert(string.find(complex_code, expected_complex_priority) >= 0, "Should generate correct complex priority expression with math functions")
|
||||||
|
|
||||||
# Test simple expressions that don't need closures
|
# Test simple expressions that don't need closures
|
||||||
@ -582,9 +582,9 @@ def test_computed_values()
|
|||||||
assert(math_code != nil, "Should compile mathematical expressions")
|
assert(math_code != nil, "Should compile mathematical expressions")
|
||||||
|
|
||||||
# Check that mathematical functions are prefixed with self. in closures
|
# Check that mathematical functions are prefixed with self. in closures
|
||||||
assert(string.find(math_code, "self.max(1, self.min(") >= 0, "Should prefix math functions with self. in closures")
|
assert(string.find(math_code, "animation._math.max(1, animation._math.min(") >= 0, "Should prefix math functions with animation._math. in closures")
|
||||||
assert(string.find(math_code, "self.abs(") >= 0, "Should prefix abs function with self. in closures")
|
assert(string.find(math_code, "animation._math.abs(") >= 0, "Should prefix abs function with self. in closures")
|
||||||
assert(string.find(math_code, "self.round(") >= 0, "Should prefix round function with self. in closures")
|
assert(string.find(math_code, "animation._math.round(") >= 0, "Should prefix round function with self. in closures")
|
||||||
|
|
||||||
print("✓ Computed values test passed")
|
print("✓ Computed values test passed")
|
||||||
return true
|
return true
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user