| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221 |
- #!/usr/bin/env python3
- """
- WLED Basic Effects for Raspberry Pi
- Effects 0-30: Static, Blink, Rainbow, Scan, etc.
- Ported from WLED FX.cpp
- """
- import random
- import math
- from ..segment import Segment
- from ..utils.colors import *
- # Effect return value is delay in milliseconds
- FRAMETIME = 24 # ~42 FPS
- def mode_static(seg: Segment) -> int:
- """Solid color"""
- seg.fill(seg.get_color(0))
- return 350 if seg.call == 0 else FRAMETIME
- def mode_blink(seg: Segment) -> int:
- """Blink between two colors"""
- cycle_time = (255 - seg.speed) * 20
- on_time = FRAMETIME + ((cycle_time * seg.intensity) >> 8)
- cycle_time += FRAMETIME * 2
- now = seg.now()
- iteration = now // cycle_time
- rem = now % cycle_time
- on = (iteration != seg.step) or (rem <= on_time)
- seg.step = iteration
- seg.fill(seg.get_color(0) if on else seg.get_color(1))
- return FRAMETIME
- def mode_strobe(seg: Segment) -> int:
- """Strobe effect"""
- cycle_time = (255 - seg.speed) * 20 + FRAMETIME * 2
- now = seg.now()
- iteration = now // cycle_time
- on = (iteration != seg.step)
- seg.step = iteration
- seg.fill(seg.get_color(0) if on else seg.get_color(1))
- return FRAMETIME
- def mode_breath(seg: Segment) -> int:
- """Breathing effect"""
- counter = (seg.now() * ((seg.speed >> 3) + 10)) & 0xFFFF
- counter = (counter >> 2) + (counter >> 4)
- var = 0
- if counter < 16384:
- if counter > 8192:
- counter = 8192 - (counter - 8192)
- var = sin16(counter) // 103
- lum = 30 + var
- for i in range(seg.length):
- seg.set_pixel_color(i, color_blend(seg.get_color(1),
- seg.color_from_palette(i),
- lum & 0xFF))
- return FRAMETIME
- def mode_fade(seg: Segment) -> int:
- """Fade between two colors"""
- counter = seg.now() * ((seg.speed >> 3) + 10)
- lum = triwave16(counter & 0xFFFF) >> 8
- for i in range(seg.length):
- seg.set_pixel_color(i, color_blend(seg.get_color(1),
- seg.color_from_palette(i),
- lum))
- return FRAMETIME
- def mode_scan(seg: Segment) -> int:
- """Scanning pixel"""
- if seg.length <= 1:
- return mode_static(seg)
- cycle_time = 750 + (255 - seg.speed) * 150
- perc = seg.now() % cycle_time
- prog = (perc * 65535) // cycle_time
- size = 1 + ((seg.intensity * seg.length) >> 9)
- led_index = (prog * ((seg.length * 2) - size * 2)) >> 16
- seg.fill(seg.get_color(1))
- led_offset = led_index - (seg.length - size)
- led_offset = abs(led_offset)
- for j in range(led_offset, min(led_offset + size, seg.length)):
- seg.set_pixel_color(j, seg.color_from_palette(j))
- return FRAMETIME
- def mode_dual_scan(seg: Segment) -> int:
- """Dual scanning pixels"""
- if seg.length <= 1:
- return mode_static(seg)
- cycle_time = 750 + (255 - seg.speed) * 150
- perc = seg.now() % cycle_time
- prog = (perc * 65535) // cycle_time
- size = 1 + ((seg.intensity * seg.length) >> 9)
- led_index = (prog * ((seg.length * 2) - size * 2)) >> 16
- seg.fill(seg.get_color(1))
- led_offset = led_index - (seg.length - size)
- led_offset = abs(led_offset)
- # First scanner
- for j in range(led_offset, min(led_offset + size, seg.length)):
- seg.set_pixel_color(j, seg.color_from_palette(j))
- # Second scanner (opposite direction)
- for j in range(led_offset, min(led_offset + size, seg.length)):
- i2 = seg.length - 1 - j
- seg.set_pixel_color(i2, seg.color_from_palette(i2))
- return FRAMETIME
- def mode_rainbow(seg: Segment) -> int:
- """Solid rainbow (cycles through hues)"""
- counter = (seg.now() * ((seg.speed >> 2) + 2)) & 0xFFFF
- counter = counter >> 8
- if seg.intensity < 128:
- color = color_blend(color_wheel(counter), WHITE,
- 128 - seg.intensity)
- else:
- color = color_wheel(counter)
- seg.fill(color)
- return FRAMETIME
- def mode_rainbow_cycle(seg: Segment) -> int:
- """Rainbow distributed across strip"""
- counter = (seg.now() * ((seg.speed >> 2) + 2)) & 0xFFFF
- counter = counter >> 8
- for i in range(seg.length):
- # intensity controls density
- index = (i * (16 << (seg.intensity // 29)) // seg.length) + counter
- seg.set_pixel_color(i, color_wheel(index & 0xFF))
- return FRAMETIME
- def mode_theater_chase(seg: Segment) -> int:
- """Theater chase effect"""
- width = 3 + (seg.intensity >> 4)
- cycle_time = 50 + (255 - seg.speed)
- iteration = seg.now() // cycle_time
- for i in range(seg.length):
- if (i % width) == seg.aux0:
- seg.set_pixel_color(i, seg.color_from_palette(i))
- else:
- seg.set_pixel_color(i, seg.get_color(1))
- if iteration != seg.step:
- seg.aux0 = (seg.aux0 + 1) % width
- seg.step = iteration
- return FRAMETIME
- def mode_running_lights(seg: Segment) -> int:
- """Running lights with sine wave"""
- x_scale = seg.intensity >> 2
- counter = (seg.now() * seg.speed) >> 9
- for i in range(seg.length):
- a = i * x_scale - counter
- s = sin8(a & 0xFF)
- color = color_blend(seg.get_color(1),
- seg.color_from_palette(i), s)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_color_wipe(seg: Segment) -> int:
- """Color wipe effect"""
- if seg.length <= 1:
- return mode_static(seg)
- cycle_time = 750 + (255 - seg.speed) * 150
- perc = seg.now() % cycle_time
- prog = (perc * 65535) // cycle_time
- back = prog > 32767
- if back:
- prog -= 32767
- if seg.step == 0:
- seg.step = 1
- else:
- if seg.step == 2:
- seg.step = 3
- led_index = (prog * seg.length) >> 15
- rem = (prog * seg.length) * 2
- rem //= (seg.intensity + 1)
- rem = min(255, rem)
- col0 = seg.get_color(0)
- col1 = seg.get_color(1)
- for i in range(seg.length):
- if i < led_index:
- seg.set_pixel_color(i, col1 if back else col0)
- else:
- seg.set_pixel_color(i, col0 if back else col1)
- if i == led_index:
- blended = color_blend(col1 if back else col0,
- col0 if back else col1,
- rem)
- seg.set_pixel_color(i, blended)
- return FRAMETIME
- def mode_random_color(seg: Segment) -> int:
- """Random solid colors with fade"""
- cycle_time = 200 + (255 - seg.speed) * 50
- iteration = seg.now() // cycle_time
- rem = seg.now() % cycle_time
- fade_dur = (cycle_time * seg.intensity) >> 8
- fade = 255
- if fade_dur:
- fade = (rem * 255) // fade_dur
- fade = min(255, fade)
- if seg.call == 0:
- seg.aux0 = random.randint(0, 255)
- seg.step = 2
- if iteration != seg.step:
- seg.aux1 = seg.aux0
- seg.aux0 = random.randint(0, 255)
- seg.step = iteration
- color = color_blend(color_wheel(seg.aux1),
- color_wheel(seg.aux0), fade)
- seg.fill(color)
- return FRAMETIME
- def mode_dynamic(seg: Segment) -> int:
- """Dynamic random colors per pixel"""
- if seg.call == 0:
- seg.data = [random.randint(0, 255) for _ in range(seg.length)]
- cycle_time = 50 + (255 - seg.speed) * 15
- iteration = seg.now() // cycle_time
- if iteration != seg.step and seg.speed != 0:
- for i in range(seg.length):
- if random.randint(0, 255) <= seg.intensity:
- seg.data[i] = random.randint(0, 255)
- seg.step = iteration
- for i in range(seg.length):
- seg.set_pixel_color(i, color_wheel(seg.data[i]))
- return FRAMETIME
- def mode_twinkle(seg: Segment) -> int:
- """Twinkle effect"""
- seg.fade_out(224)
- cycle_time = 20 + (255 - seg.speed) * 5
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- max_on = max(1, (seg.intensity * seg.length) // 255)
- if seg.aux0 >= max_on:
- seg.aux0 = 0
- seg.aux1 = random.randint(0, 0xFFFF)
- seg.aux0 += 1
- seg.step = iteration
- prng = seg.aux1
- for _ in range(seg.aux0):
- prng = (prng * 2053 + 13849) & 0xFFFF
- j = (prng * seg.length) >> 16
- if j < seg.length:
- seg.set_pixel_color(j, seg.color_from_palette(j))
- return FRAMETIME
- def mode_sparkle(seg: Segment) -> int:
- """Single sparkle effect"""
- for i in range(seg.length):
- seg.set_pixel_color(i, seg.color_from_palette(i))
- cycle_time = 10 + (255 - seg.speed) * 2
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- seg.aux0 = random.randint(0, seg.length - 1)
- seg.step = iteration
- seg.set_pixel_color(seg.aux0, seg.get_color(0))
- return FRAMETIME
- def mode_fire(seg: Segment) -> int:
- """Fire/flame effect"""
- if seg.call == 0:
- seg.data = [0] * seg.length
- # Cooling parameter (higher = cooler flames)
- cooling = ((100 - (seg.intensity >> 1)) * 10) // seg.length + 2
- # Heat decay for all pixels
- for i in range(seg.length):
- cool_down = random.randint(0, cooling)
- seg.data[i] = max(0, seg.data[i] - cool_down)
- # Heat drift upward
- for i in range(seg.length - 1, 2, -1):
- seg.data[i] = (seg.data[i - 1] + seg.data[i - 2] + seg.data[i - 2]) // 3
- # Randomly ignite new sparks near bottom
- if random.randint(0, 255) < seg.intensity:
- spark_pos = random.randint(0, min(7, seg.length - 1))
- seg.data[spark_pos] = min(255, seg.data[spark_pos] + random.randint(160, 255))
- # Convert heat to colors
- for i in range(seg.length):
- heat = seg.data[i]
- # Black -> Red -> Yellow -> White
- if heat < 85:
- color = (heat * 3, 0, 0)
- elif heat < 170:
- h = heat - 85
- color = (255, h * 3, 0)
- else:
- h = heat - 170
- color = (255, 255, h * 3)
- r, g, b = color
- seg.set_pixel_color(i, (r << 16) | (g << 8) | b)
- return FRAMETIME
- def mode_comet(seg: Segment) -> int:
- """Comet/shooting star effect"""
- if seg.call == 0:
- seg.aux0 = 0
- seg.aux1 = 0
- seg.fade_out(128)
- size = 1 + ((seg.intensity * seg.length) >> 9)
- cycle_time = 10 + (255 - seg.speed)
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- seg.aux0 = (seg.aux0 + 1) % seg.length
- seg.step = iteration
- # Draw comet
- for i in range(size):
- pos = (seg.aux0 - i) % seg.length
- brightness = 255 - (i * 255 // max(1, size))
- color = color_blend(0, seg.color_from_palette(pos), brightness)
- seg.set_pixel_color(pos, color)
- return FRAMETIME
- def mode_chase(seg: Segment) -> int:
- """Chase effect with colored segments"""
- if seg.call == 0:
- seg.aux0 = 0
- size = max(1, seg.length // 4)
- cycle_time = 10 + (255 - seg.speed)
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- seg.aux0 = (seg.aux0 + 1) % seg.length
- seg.step = iteration
- seg.fill(seg.get_color(1))
- for i in range(size):
- pos = (seg.aux0 + i) % seg.length
- seg.set_pixel_color(pos, seg.color_from_palette(pos))
- return FRAMETIME
- def mode_police(seg: Segment) -> int:
- """Police lights (red/blue alternating)"""
- cycle_time = 25 + (255 - seg.speed)
- on_time = cycle_time // 2
- now = seg.now()
- iteration = now // cycle_time
- rem = now % cycle_time
- on = rem < on_time
- half = seg.length // 2
- # Red on left, blue on right
- red = (255, 0, 0)
- blue = (0, 0, 255)
- off_color = (0, 0, 0)
- for i in range(half):
- if (iteration % 2 == 0 and on) or (iteration % 2 == 1 and not on):
- seg.set_pixel_color(i, (red[0] << 16) | (red[1] << 8) | red[2])
- else:
- seg.set_pixel_color(i, (off_color[0] << 16) | (off_color[1] << 8) | off_color[2])
- for i in range(half, seg.length):
- if (iteration % 2 == 1 and on) or (iteration % 2 == 0 and not on):
- seg.set_pixel_color(i, (blue[0] << 16) | (blue[1] << 8) | blue[2])
- else:
- seg.set_pixel_color(i, (off_color[0] << 16) | (off_color[1] << 8) | off_color[2])
- return FRAMETIME
- def mode_lightning(seg: Segment) -> int:
- """Lightning flash effect"""
- if seg.call == 0:
- seg.aux0 = 0
- seg.aux1 = 0
- cycle_time = 50 + (255 - seg.speed) * 10
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- # Random chance of lightning
- if random.randint(0, 255) < seg.intensity:
- seg.aux0 = random.randint(3, 8) # Number of flashes
- seg.aux1 = seg.now()
- seg.step = iteration
- # Flash sequence
- if seg.aux0 > 0:
- flash_duration = 50
- time_since = seg.now() - seg.aux1
- if time_since < flash_duration:
- # Flash on
- brightness = 255 - (time_since * 255 // flash_duration)
- for i in range(seg.length):
- color = color_blend(0, WHITE, brightness)
- seg.set_pixel_color(i, color)
- else:
- # Flash off, wait for next
- if time_since > flash_duration + random.randint(10, 100):
- seg.aux0 -= 1
- seg.aux1 = seg.now()
- else:
- seg.fill(seg.get_color(1))
- else:
- seg.fill(seg.get_color(1))
- return FRAMETIME
- def mode_fireworks(seg: Segment) -> int:
- """Fireworks effect"""
- seg.fade_out(64)
- cycle_time = 20 + (255 - seg.speed)
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- # Launch new firework
- if random.randint(0, 255) < seg.intensity:
- pos = random.randint(0, seg.length - 1)
- color = color_wheel(random.randint(0, 255))
- # Bright center
- seg.set_pixel_color(pos, color)
- # Dimmer neighbors
- if pos > 0:
- seg.set_pixel_color(pos - 1, color_blend(0, color, 128))
- if pos < seg.length - 1:
- seg.set_pixel_color(pos + 1, color_blend(0, color, 128))
- seg.step = iteration
- return FRAMETIME
- def mode_ripple(seg: Segment) -> int:
- """Ripple effect"""
- if seg.call == 0:
- seg.data = [0] * seg.length
- seg.aux0 = seg.length // 2
- seg.fade_out(250)
- cycle_time = 50 + (255 - seg.speed) * 2
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- # New ripple
- if random.randint(0, 255) < seg.intensity:
- seg.aux0 = random.randint(0, seg.length - 1)
- seg.data[seg.aux0] = 255
- # Propagate ripple
- new_data = seg.data.copy()
- for i in range(1, seg.length - 1):
- new_data[i] = (seg.data[i - 1] + seg.data[i + 1]) // 2
- seg.data = new_data
- seg.step = iteration
- for i in range(seg.length):
- if seg.data[i] > 0:
- color = color_blend(seg.get_color(1),
- seg.color_from_palette(i),
- seg.data[i])
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_flow(seg: Segment) -> int:
- """Smooth flowing color movement"""
- counter = seg.now() * ((seg.speed >> 3) + 1)
- for i in range(seg.length):
- pos = ((i * 256 // seg.length) + counter) & 0xFFFF
- color = color_wheel((pos >> 8) & 0xFF)
- # Apply intensity as brightness modulation
- brightness = 128 + ((sin8((pos >> 7) & 0xFF) - 128) * seg.intensity // 255)
- color = color_blend(0, color, brightness)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_colorloop(seg: Segment) -> int:
- """Smooth color loop across entire strip"""
- counter = (seg.now() * ((seg.speed >> 3) + 1)) & 0xFFFF
- for i in range(seg.length):
- # Create gradient based on position and time
- hue = ((i * 256 // max(1, seg.length)) + (counter >> 7)) & 0xFF
- color = color_wheel(hue)
- # Intensity controls saturation
- if seg.intensity < 255:
- color = color_blend(color, WHITE, 255 - seg.intensity)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_palette_flow(seg: Segment) -> int:
- """Flowing palette colors"""
- counter = seg.now() * ((seg.speed >> 3) + 1)
- for i in range(seg.length):
- # Get color from palette based on position and time
- palette_pos = ((i * 255 // max(1, seg.length)) + (counter >> 7)) & 0xFF
- color = seg.color_from_palette(palette_pos)
- # Intensity controls brightness modulation
- if seg.intensity < 255:
- brightness = 128 + ((sin8(palette_pos) - 128) * seg.intensity // 255)
- color = color_blend(0, color, brightness)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_gradient(seg: Segment) -> int:
- """Smooth gradient between colors"""
- for i in range(seg.length):
- # Create gradient from color 0 to color 2
- blend_amount = (i * 255) // max(1, seg.length - 1)
- color = color_blend(seg.get_color(0), seg.get_color(2), blend_amount)
- # Intensity controls a pulsing brightness
- if seg.intensity > 0:
- counter = (seg.now() * ((seg.speed >> 3) + 1)) & 0xFFFF
- pulse = sin8((counter >> 8) & 0xFF)
- brightness = 128 + ((pulse - 128) * seg.intensity // 255)
- color = color_blend(0, color, brightness)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_multi_strobe(seg: Segment) -> int:
- """Multi-color strobe effect"""
- cycle_time = 50 + (255 - seg.speed)
- flash_duration = max(5, cycle_time // 4)
- now = seg.now()
- iteration = now // cycle_time
- rem = now % cycle_time
- if rem < flash_duration:
- # Strobe on with color from palette
- color_index = (iteration * 85) & 0xFF
- color = color_wheel(color_index)
- seg.fill(color)
- else:
- # Strobe off
- seg.fill(seg.get_color(1))
- return FRAMETIME
- def mode_waves(seg: Segment) -> int:
- """Sine wave effect"""
- counter = seg.now() * ((seg.speed >> 3) + 1)
- for i in range(seg.length):
- # Create wave pattern
- wave_pos = (i * 255 // max(1, seg.length)) + (counter >> 7)
- brightness = sin8(wave_pos & 0xFF)
- # Intensity controls wave amplitude
- brightness = 128 + ((brightness - 128) * seg.intensity // 255)
- color = color_blend(seg.get_color(1),
- seg.color_from_palette(i),
- brightness)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_bpm(seg: Segment) -> int:
- """BPM (beats per minute) pulse effect"""
- # Calculate BPM based on speed (60-180 BPM)
- bpm = 60 + ((seg.speed * 120) >> 8)
- ms_per_beat = 60000 // bpm
- beat_phase = (seg.now() % ms_per_beat) * 255 // ms_per_beat
- brightness = sin8(beat_phase)
- for i in range(seg.length):
- # Create traveling beat
- offset = (i * 255 // max(1, seg.length))
- local_brightness = sin8((beat_phase + offset) & 0xFF)
- # Intensity controls brightness range
- local_brightness = 128 + ((local_brightness - 128) * seg.intensity // 255)
- color = color_blend(seg.get_color(1),
- seg.color_from_palette(i),
- local_brightness)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_juggle(seg: Segment) -> int:
- """Juggling colored dots"""
- if seg.call == 0:
- seg.data = [0] * 8 # Track 8 dot positions
- seg.fade_out(224)
- cycle_time = 10 + (255 - seg.speed) // 2
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- # Update dot positions using different sine waves
- for dot in range(min(8, 1 + seg.intensity // 32)):
- phase = (seg.now() * (dot + 1)) & 0xFFFF
- pos = (sin16(phase) + 32768) * seg.length // 65536
- pos = max(0, min(seg.length - 1, pos))
- hue = (dot * 32) & 0xFF
- color = color_wheel(hue)
- seg.set_pixel_color(pos, color)
- seg.step = iteration
- return FRAMETIME
- def mode_meteor(seg: Segment) -> int:
- """Meteor shower effect with trails"""
- if seg.call == 0:
- seg.aux0 = 0
- seg.aux1 = 0
- # Fade all pixels
- seg.fade_out(200)
- size = 1 + ((seg.intensity * seg.length) >> 8)
- cycle_time = 10 + (255 - seg.speed)
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- seg.aux0 = (seg.aux0 + 1) % (seg.length + size)
- seg.step = iteration
- # Draw meteor head and tail
- if seg.aux0 < seg.length:
- for i in range(size):
- pos = seg.aux0 - i
- if 0 <= pos < seg.length:
- brightness = 255 - (i * 200 // max(1, size))
- color = color_blend(0, seg.color_from_palette(pos), brightness)
- # Add to existing color for brighter effect
- existing = seg.get_pixel_color(pos)
- seg.set_pixel_color(pos, color_add(existing, color))
- return FRAMETIME
- def mode_pride(seg: Segment) -> int:
- """Pride flag colors moving effect"""
- counter = seg.now() * ((seg.speed >> 3) + 1)
- # Pride flag colors (6 stripes)
- pride_colors = [
- (0xE4, 0x00, 0x3A), # Red
- (0xFF, 0x8C, 0x00), # Orange
- (0xFF, 0xED, 0x00), # Yellow
- (0x00, 0x81, 0x1F), # Green
- (0x00, 0x4C, 0xFF), # Blue
- (0x76, 0x01, 0x89), # Purple
- ]
- for i in range(seg.length):
- # Determine which stripe this pixel belongs to
- stripe_size = max(1, seg.length // 6)
- offset = (counter >> 7) & 0xFF
- stripe_idx = ((i + offset) // stripe_size) % 6
- r, g, b = pride_colors[stripe_idx]
- color = (r << 16) | (g << 8) | b
- # Intensity controls blending with background
- if seg.intensity < 255:
- color = color_blend(seg.get_color(1), color, seg.intensity)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_pacifica(seg: Segment) -> int:
- """Ocean/water simulation"""
- if seg.call == 0:
- seg.data = [0] * seg.length
- counter = seg.now() * ((seg.speed >> 4) + 1)
- for i in range(seg.length):
- # Create multiple layered waves
- wave1 = sin8(((i * 5) + (counter >> 5)) & 0xFF)
- wave2 = sin8(((i * 3) + (counter >> 3)) & 0xFF)
- wave3 = sin8(((i * 7) + (counter >> 6)) & 0xFF)
- # Combine waves
- brightness = (wave1 + wave2 + wave3) // 3
- # Blue-green ocean colors
- if brightness < 64:
- # Deep blue
- r, g, b = 0, 0, 60 + brightness
- elif brightness < 128:
- # Blue-cyan
- val = brightness - 64
- r, g, b = 0, val * 2, 100 + val
- else:
- # Cyan-white (foam)
- val = brightness - 128
- r, g, b = val, 128 + val, 180 + (val // 2)
- # Apply intensity
- brightness = 128 + ((brightness - 128) * seg.intensity // 255)
- color = (r << 16) | (g << 8) | b
- color = color_blend(0, color, brightness)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_plasma(seg: Segment) -> int:
- """Plasma effect"""
- counter = seg.now() * ((seg.speed >> 3) + 1)
- for i in range(seg.length):
- # Create plasma using multiple sine waves
- phase1 = sin8(((i * 16) + (counter >> 5)) & 0xFF)
- phase2 = sin8(((i * 8) + (counter >> 6)) & 0xFF)
- phase3 = sin8((counter >> 4) & 0xFF)
- # Combine phases
- hue = (phase1 + phase2 + phase3) & 0xFF
- color = color_wheel(hue)
- # Intensity controls saturation
- if seg.intensity < 255:
- color = color_blend(color, WHITE, 255 - seg.intensity)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_dissolve(seg: Segment) -> int:
- """Random pixel dissolve/fade"""
- if seg.call == 0:
- seg.data = [0] * seg.length
- for i in range(seg.length):
- seg.data[i] = random.randint(0, 255)
- cycle_time = 20 + (255 - seg.speed)
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- # Randomly update some pixels
- for i in range(seg.length):
- if random.randint(0, 255) < seg.intensity:
- seg.data[i] = random.randint(0, 255)
- seg.step = iteration
- for i in range(seg.length):
- brightness = seg.data[i]
- color = color_blend(seg.get_color(1),
- seg.color_from_palette(i),
- brightness)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_glitter(seg: Segment) -> int:
- """Sparkle glitter overlay"""
- # Fill with base color
- for i in range(seg.length):
- seg.set_pixel_color(i, seg.color_from_palette(i))
- # Add random sparkles based on intensity
- sparkle_chance = seg.intensity
- num_sparkles = max(1, (seg.length * sparkle_chance) // 255)
- for _ in range(num_sparkles):
- if random.randint(0, 255) < seg.speed:
- pos = random.randint(0, seg.length - 1)
- seg.set_pixel_color(pos, WHITE)
- return FRAMETIME
- def mode_confetti(seg: Segment) -> int:
- """Random colored pixels"""
- seg.fade_out(224)
- cycle_time = 10 + (255 - seg.speed)
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- # Add random confetti
- density = 1 + (seg.intensity >> 5)
- for _ in range(density):
- pos = random.randint(0, seg.length - 1)
- hue = random.randint(0, 255)
- color = color_wheel(hue)
- seg.set_pixel_color(pos, color)
- seg.step = iteration
- return FRAMETIME
- def mode_sinelon(seg: Segment) -> int:
- """Sine wave colored dot"""
- seg.fade_out(224)
- counter = seg.now() * ((seg.speed >> 2) + 1)
- phase = (counter >> 8) & 0xFFFF
- # Calculate position using sine
- pos = (sin16(phase) + 32768) * seg.length // 65536
- pos = max(0, min(seg.length - 1, pos))
- # Color rotates
- hue = (counter >> 7) & 0xFF
- color = color_wheel(hue)
- # Intensity controls trail length (via fade)
- if seg.intensity < 255:
- color = color_blend(0, color, seg.intensity)
- seg.set_pixel_color(pos, color)
- return FRAMETIME
- def mode_candle(seg: Segment) -> int:
- """Flickering candle simulation"""
- if seg.call == 0:
- seg.data = [128] * seg.length
- # Random flicker for each pixel
- for i in range(seg.length):
- # Small random changes
- change = random.randint(-20, 20)
- seg.data[i] = max(30, min(255, seg.data[i] + change))
- # Speed affects flicker rate
- if seg.speed > 128 and random.randint(0, 255) < (seg.speed - 128):
- seg.data[i] = random.randint(50, 200)
- # Warm orange/yellow color
- brightness = seg.data[i]
- r = brightness
- g = (brightness * 2) // 3
- b = (brightness * seg.intensity) // 512 # Intensity controls blue
- color = (r << 16) | (g << 8) | b
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_aurora(seg: Segment) -> int:
- """Northern lights effect"""
- if seg.call == 0:
- seg.data = [0] * seg.length
- counter = seg.now() * ((seg.speed >> 4) + 1)
- for i in range(seg.length):
- # Multiple slow-moving waves
- wave1 = sin8(((i * 3) + (counter >> 6)) & 0xFF)
- wave2 = sin8(((i * 5) + (counter >> 7) + 85) & 0xFF)
- wave3 = sin8(((i * 2) + (counter >> 5) + 170) & 0xFF)
- # Aurora colors: green, blue, purple
- brightness = (wave1 + wave2 + wave3) // 3
- if brightness < 85:
- # Green
- r, g, b = 0, brightness * 2, brightness // 2
- elif brightness < 170:
- # Blue-green
- val = brightness - 85
- r, g, b = 0, 100 + val, 100 + val
- else:
- # Purple-pink
- val = brightness - 170
- r, g, b = val * 2, val, 150 + val
- # Apply intensity for brightness variation
- brightness_mod = 128 + ((brightness - 128) * seg.intensity // 255)
- color = (r << 16) | (g << 8) | b
- color = color_blend(0, color, brightness_mod)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_rain(seg: Segment) -> int:
- """Rain drops falling"""
- if seg.call == 0:
- seg.data = [0] * seg.length
- seg.aux0 = 0
- seg.fade_out(235)
- cycle_time = 15 + (255 - seg.speed)
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- # New rain drops
- if random.randint(0, 255) < seg.intensity:
- pos = random.randint(0, min(5, seg.length - 1)) # Start near beginning
- seg.data[pos] = 255
- # Move drops down
- new_data = [0] * seg.length
- for i in range(seg.length - 1):
- if seg.data[i] > 0:
- # Drop moves forward
- if i + 1 < seg.length:
- new_data[i + 1] = seg.data[i] - 10
- seg.data = new_data
- seg.step = iteration
- # Draw rain drops (blue)
- for i in range(seg.length):
- if seg.data[i] > 0:
- brightness = seg.data[i]
- color = color_blend(0, seg.color_from_palette(i), brightness)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_halloween(seg: Segment) -> int:
- """Halloween orange and purple"""
- counter = seg.now() * ((seg.speed >> 3) + 1)
- # Alternating orange and purple
- orange = (0xFF << 16) | (0x44 << 8) | 0x00
- purple = (0x88 << 16) | (0x00 << 8) | 0xFF
- for i in range(seg.length):
- # Create moving pattern
- phase = ((i * 255 // max(1, seg.length)) + (counter >> 7)) & 0xFF
- wave = sin8(phase)
- # Blend between orange and purple
- if wave < 128:
- color = orange
- else:
- color = purple
- # Intensity controls blending smoothness
- if seg.intensity > 0:
- blend_amt = (wave * seg.intensity) // 255
- color = color_blend(orange, purple, blend_amt)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_noise(seg: Segment) -> int:
- """Perlin-like noise pattern"""
- if seg.call == 0:
- seg.data = [random.randint(0, 255) for _ in range(seg.length)]
- cycle_time = 20 + (255 - seg.speed) // 2
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- # Smooth noise by averaging neighbors
- new_data = seg.data.copy()
- for i in range(1, seg.length - 1):
- avg = (seg.data[i - 1] + seg.data[i] * 2 + seg.data[i + 1]) // 4
- variation = random.randint(-20, 20)
- new_data[i] = max(0, min(255, avg + variation))
- # Occasionally inject new random values
- if random.randint(0, 255) < seg.intensity:
- pos = random.randint(0, seg.length - 1)
- new_data[pos] = random.randint(0, 255)
- seg.data = new_data
- seg.step = iteration
- # Map noise to colors
- for i in range(seg.length):
- hue = seg.data[i]
- color = color_wheel(hue)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_funky_plank(seg: Segment) -> int:
- """Multiple colored bars moving"""
- if seg.call == 0:
- seg.aux0 = 0
- cycle_time = 10 + (255 - seg.speed)
- iteration = seg.now() // cycle_time
- if iteration != seg.step:
- seg.aux0 = (seg.aux0 + 1) % seg.length
- seg.step = iteration
- num_planks = max(2, 1 + (seg.intensity >> 5))
- plank_size = max(1, seg.length // num_planks)
- for i in range(seg.length):
- plank_idx = ((i + seg.aux0) // plank_size) % num_planks
- hue = (plank_idx * 256 // num_planks) & 0xFF
- color = color_wheel(hue)
- seg.set_pixel_color(i, color)
- return FRAMETIME
- def mode_ball_tracking(seg: Segment) -> int:
- """
- Ball tracking effect - follows the ball bearing's position in real-time
- Reads position data from state.ball_tracking_manager
- """
- # Import state here to avoid circular dependency
- from modules.core.state import state
- import logging
- logger = logging.getLogger(__name__)
- # Get ball tracking manager
- manager = state.ball_tracking_manager
- if not manager or not manager._active:
- # No active tracking, show static color or turn off
- if seg.call % 100 == 0: # Log occasionally
- logger.debug(f"Ball tracking effect: manager active={manager._active if manager else 'N/A'}")
- seg.fill(0x000000)
- return FRAMETIME
- # Get tracking data from manager
- tracking_data = manager.get_tracking_data()
- if not tracking_data:
- # No position data yet
- if seg.call % 100 == 0: # Log occasionally
- logger.debug("Ball tracking effect: No tracking data available yet")
- seg.fill(0x000000)
- return FRAMETIME
- # Log tracking data occasionally
- if seg.call % 100 == 0:
- logger.info(f"Ball tracking effect: LED index={tracking_data['led_index']}, spread={tracking_data['spread']}, buffer_size={len(manager.position_buffer)}")
- center_led = tracking_data['led_index']
- spread = tracking_data['spread']
- brightness = tracking_data['brightness']
- color_rgb = tracking_data['color']
- # Convert RGB tuple to 32-bit color
- r, g, b = color_rgb
- color_32bit = (r << 16) | (g << 8) | b
- # Initialize tracking: store last lit LEDs in seg.data
- if seg.call == 0:
- seg.data = []
- # Calculate which LEDs should be lit this frame
- current_lit_leds = set()
- half_spread = spread // 2
- for i in range(-half_spread, half_spread + 1):
- led_index = (center_led + i) % seg.length
- current_lit_leds.add(led_index)
- # Convert last frame's lit LEDs from list to set
- last_lit_leds = set(seg.data) if seg.data else set()
- # Turn off LEDs that were on but shouldn't be anymore
- leds_to_turn_off = last_lit_leds - current_lit_leds
- for led_idx in leds_to_turn_off:
- seg.set_pixel_color(led_idx, 0x000000)
- # Update LEDs that should be on with brightness fade
- for i in range(-half_spread, half_spread + 1):
- led_index = (center_led + i) % seg.length
- # Calculate intensity fade from center
- if spread > 1:
- distance = abs(i)
- intensity = 1.0 - (distance / (spread / 2.0)) * 0.5 # 50-100%
- else:
- intensity = 1.0
- # Apply brightness
- final_brightness = int(brightness * intensity * 255)
- # Scale color by brightness
- final_r = (r * final_brightness) // 255
- final_g = (g * final_brightness) // 255
- final_b = (b * final_brightness) // 255
- final_color = (final_r << 16) | (final_g << 8) | final_b
- seg.set_pixel_color(led_index, final_color)
- # Remember which LEDs are lit for next frame
- seg.data = list(current_lit_leds)
- return FRAMETIME
- # Effect registry
- EFFECTS = {
- 0: ("Static", mode_static),
- 1: ("Blink", mode_blink),
- 2: ("Breathe", mode_breath),
- 3: ("Wipe", mode_color_wipe),
- 4: ("Fade", mode_fade),
- 5: ("Scan", mode_scan),
- 6: ("Dual Scan", mode_dual_scan),
- 7: ("Rainbow Cycle", mode_rainbow),
- 8: ("Rainbow", mode_rainbow_cycle),
- 9: ("Theater Chase", mode_theater_chase),
- 10: ("Running Lights", mode_running_lights),
- 11: ("Random Color", mode_random_color),
- 12: ("Dynamic", mode_dynamic),
- 13: ("Twinkle", mode_twinkle),
- 14: ("Sparkle", mode_sparkle),
- 15: ("Strobe", mode_strobe),
- 16: ("Fire", mode_fire),
- 17: ("Comet", mode_comet),
- 18: ("Chase", mode_chase),
- 19: ("Police", mode_police),
- 20: ("Lightning", mode_lightning),
- 21: ("Fireworks", mode_fireworks),
- 22: ("Ripple", mode_ripple),
- 23: ("Flow", mode_flow),
- 24: ("Colorloop", mode_colorloop),
- 25: ("Palette Flow", mode_palette_flow),
- 26: ("Gradient", mode_gradient),
- 27: ("Multi Strobe", mode_multi_strobe),
- 28: ("Waves", mode_waves),
- 29: ("BPM", mode_bpm),
- 30: ("Juggle", mode_juggle),
- 31: ("Meteor", mode_meteor),
- 32: ("Pride", mode_pride),
- 33: ("Pacifica", mode_pacifica),
- 34: ("Plasma", mode_plasma),
- 35: ("Dissolve", mode_dissolve),
- 36: ("Glitter", mode_glitter),
- 37: ("Confetti", mode_confetti),
- 38: ("Sinelon", mode_sinelon),
- 39: ("Candle", mode_candle),
- 40: ("Aurora", mode_aurora),
- 41: ("Rain", mode_rain),
- 42: ("Halloween", mode_halloween),
- 43: ("Noise", mode_noise),
- 44: ("Funky Plank", mode_funky_plank),
- 45: ("Ball Tracking", mode_ball_tracking),
- }
- def get_effect(effect_id: int):
- """Get effect function by ID"""
- if effect_id in EFFECTS:
- return EFFECTS[effect_id][1]
- return mode_static
- def get_effect_name(effect_id: int) -> str:
- """Get effect name by ID"""
- if effect_id in EFFECTS:
- return EFFECTS[effect_id][0]
- return "Unknown"
- def get_all_effects():
- """Get list of all effects"""
- return [(k, v[0]) for k, v in sorted(EFFECTS.items())]
|