1
0

basic_effects.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131
  1. #!/usr/bin/env python3
  2. """
  3. WLED Basic Effects for Raspberry Pi
  4. Effects 0-30: Static, Blink, Rainbow, Scan, etc.
  5. Ported from WLED FX.cpp
  6. """
  7. import random
  8. from ..segment import Segment
  9. from ..utils.colors import *
  10. # Effect return value is delay in milliseconds
  11. FRAMETIME = 24 # ~42 FPS
  12. def mode_static(seg: Segment) -> int:
  13. """Solid color"""
  14. seg.fill(seg.get_color(0))
  15. return 350 if seg.call == 0 else FRAMETIME
  16. def mode_blink(seg: Segment) -> int:
  17. """Blink between two colors"""
  18. cycle_time = (255 - seg.speed) * 20
  19. on_time = FRAMETIME + ((cycle_time * seg.intensity) >> 8)
  20. cycle_time += FRAMETIME * 2
  21. now = seg.now()
  22. iteration = now // cycle_time
  23. rem = now % cycle_time
  24. on = (iteration != seg.step) or (rem <= on_time)
  25. seg.step = iteration
  26. seg.fill(seg.get_color(0) if on else seg.get_color(1))
  27. return FRAMETIME
  28. def mode_strobe(seg: Segment) -> int:
  29. """Strobe effect"""
  30. cycle_time = (255 - seg.speed) * 20 + FRAMETIME * 2
  31. now = seg.now()
  32. iteration = now // cycle_time
  33. on = (iteration != seg.step)
  34. seg.step = iteration
  35. seg.fill(seg.get_color(0) if on else seg.get_color(1))
  36. return FRAMETIME
  37. def mode_breath(seg: Segment) -> int:
  38. """Breathing effect"""
  39. counter = (seg.now() * ((seg.speed >> 3) + 10)) & 0xFFFF
  40. counter = (counter >> 2) + (counter >> 4)
  41. var = 0
  42. if counter < 16384:
  43. if counter > 8192:
  44. counter = 8192 - (counter - 8192)
  45. var = sin16(counter) // 103
  46. lum = 30 + var
  47. for i in range(seg.length):
  48. seg.set_pixel_color(i, color_blend(seg.get_color(1),
  49. seg.color_from_palette(i),
  50. lum & 0xFF))
  51. return FRAMETIME
  52. def mode_fade(seg: Segment) -> int:
  53. """Fade between two colors"""
  54. counter = seg.now() * ((seg.speed >> 3) + 10)
  55. lum = triwave16(counter & 0xFFFF) >> 8
  56. for i in range(seg.length):
  57. seg.set_pixel_color(i, color_blend(seg.get_color(1),
  58. seg.color_from_palette(i),
  59. lum))
  60. return FRAMETIME
  61. def mode_scan(seg: Segment) -> int:
  62. """Scanning pixel"""
  63. if seg.length <= 1:
  64. return mode_static(seg)
  65. cycle_time = 750 + (255 - seg.speed) * 150
  66. perc = seg.now() % cycle_time
  67. prog = (perc * 65535) // cycle_time
  68. size = 1 + ((seg.intensity * seg.length) >> 9)
  69. led_index = (prog * ((seg.length * 2) - size * 2)) >> 16
  70. seg.fill(seg.get_color(1))
  71. led_offset = led_index - (seg.length - size)
  72. led_offset = abs(led_offset)
  73. for j in range(led_offset, min(led_offset + size, seg.length)):
  74. seg.set_pixel_color(j, seg.color_from_palette(j))
  75. return FRAMETIME
  76. def mode_dual_scan(seg: Segment) -> int:
  77. """Dual scanning pixels"""
  78. if seg.length <= 1:
  79. return mode_static(seg)
  80. cycle_time = 750 + (255 - seg.speed) * 150
  81. perc = seg.now() % cycle_time
  82. prog = (perc * 65535) // cycle_time
  83. size = 1 + ((seg.intensity * seg.length) >> 9)
  84. led_index = (prog * ((seg.length * 2) - size * 2)) >> 16
  85. seg.fill(seg.get_color(1))
  86. led_offset = led_index - (seg.length - size)
  87. led_offset = abs(led_offset)
  88. # First scanner
  89. for j in range(led_offset, min(led_offset + size, seg.length)):
  90. seg.set_pixel_color(j, seg.color_from_palette(j))
  91. # Second scanner (opposite direction)
  92. for j in range(led_offset, min(led_offset + size, seg.length)):
  93. i2 = seg.length - 1 - j
  94. seg.set_pixel_color(i2, seg.color_from_palette(i2))
  95. return FRAMETIME
  96. def mode_rainbow(seg: Segment) -> int:
  97. """Solid rainbow (cycles through hues)"""
  98. counter = (seg.now() * ((seg.speed >> 2) + 2)) & 0xFFFF
  99. counter = counter >> 8
  100. if seg.intensity < 128:
  101. color = color_blend(color_wheel(counter), WHITE,
  102. 128 - seg.intensity)
  103. else:
  104. color = color_wheel(counter)
  105. seg.fill(color)
  106. return FRAMETIME
  107. def mode_rainbow_cycle(seg: Segment) -> int:
  108. """Rainbow distributed across strip"""
  109. counter = (seg.now() * ((seg.speed >> 2) + 2)) & 0xFFFF
  110. counter = counter >> 8
  111. for i in range(seg.length):
  112. # intensity controls density
  113. index = (i * (16 << (seg.intensity // 29)) // seg.length) + counter
  114. seg.set_pixel_color(i, color_wheel(index & 0xFF))
  115. return FRAMETIME
  116. def mode_theater_chase(seg: Segment) -> int:
  117. """Theater chase effect"""
  118. width = 3 + (seg.intensity >> 4)
  119. cycle_time = 50 + (255 - seg.speed)
  120. iteration = seg.now() // cycle_time
  121. for i in range(seg.length):
  122. if (i % width) == seg.aux0:
  123. seg.set_pixel_color(i, seg.color_from_palette(i))
  124. else:
  125. seg.set_pixel_color(i, seg.get_color(1))
  126. if iteration != seg.step:
  127. seg.aux0 = (seg.aux0 + 1) % width
  128. seg.step = iteration
  129. return FRAMETIME
  130. def mode_running_lights(seg: Segment) -> int:
  131. """Running lights with sine wave"""
  132. x_scale = seg.intensity >> 2
  133. counter = (seg.now() * seg.speed) >> 9
  134. for i in range(seg.length):
  135. a = i * x_scale - counter
  136. s = sin8(a & 0xFF)
  137. color = color_blend(seg.get_color(1),
  138. seg.color_from_palette(i), s)
  139. seg.set_pixel_color(i, color)
  140. return FRAMETIME
  141. def mode_color_wipe(seg: Segment) -> int:
  142. """Color wipe effect"""
  143. if seg.length <= 1:
  144. return mode_static(seg)
  145. cycle_time = 750 + (255 - seg.speed) * 150
  146. perc = seg.now() % cycle_time
  147. prog = (perc * 65535) // cycle_time
  148. back = prog > 32767
  149. if back:
  150. prog -= 32767
  151. if seg.step == 0:
  152. seg.step = 1
  153. else:
  154. if seg.step == 2:
  155. seg.step = 3
  156. led_index = (prog * seg.length) >> 15
  157. rem = (prog * seg.length) * 2
  158. rem //= (seg.intensity + 1)
  159. rem = min(255, rem)
  160. col0 = seg.get_color(0)
  161. col1 = seg.get_color(1)
  162. for i in range(seg.length):
  163. if i < led_index:
  164. seg.set_pixel_color(i, col1 if back else col0)
  165. else:
  166. seg.set_pixel_color(i, col0 if back else col1)
  167. if i == led_index:
  168. blended = color_blend(col1 if back else col0,
  169. col0 if back else col1,
  170. rem)
  171. seg.set_pixel_color(i, blended)
  172. return FRAMETIME
  173. def mode_random_color(seg: Segment) -> int:
  174. """Random solid colors with fade"""
  175. cycle_time = 200 + (255 - seg.speed) * 50
  176. iteration = seg.now() // cycle_time
  177. rem = seg.now() % cycle_time
  178. fade_dur = (cycle_time * seg.intensity) >> 8
  179. fade = 255
  180. if fade_dur:
  181. fade = (rem * 255) // fade_dur
  182. fade = min(255, fade)
  183. if seg.call == 0:
  184. seg.aux0 = random.randint(0, 255)
  185. seg.step = 2
  186. if iteration != seg.step:
  187. seg.aux1 = seg.aux0
  188. seg.aux0 = random.randint(0, 255)
  189. seg.step = iteration
  190. color = color_blend(color_wheel(seg.aux1),
  191. color_wheel(seg.aux0), fade)
  192. seg.fill(color)
  193. return FRAMETIME
  194. def mode_dynamic(seg: Segment) -> int:
  195. """Dynamic random colors per pixel"""
  196. if seg.call == 0:
  197. seg.data = [random.randint(0, 255) for _ in range(seg.length)]
  198. cycle_time = 50 + (255 - seg.speed) * 15
  199. iteration = seg.now() // cycle_time
  200. if iteration != seg.step and seg.speed != 0:
  201. for i in range(seg.length):
  202. if random.randint(0, 255) <= seg.intensity:
  203. seg.data[i] = random.randint(0, 255)
  204. seg.step = iteration
  205. for i in range(seg.length):
  206. seg.set_pixel_color(i, color_wheel(seg.data[i]))
  207. return FRAMETIME
  208. def mode_twinkle(seg: Segment) -> int:
  209. """Twinkle effect"""
  210. seg.fade_out(224)
  211. cycle_time = 20 + (255 - seg.speed) * 5
  212. iteration = seg.now() // cycle_time
  213. if iteration != seg.step:
  214. max_on = max(1, (seg.intensity * seg.length) // 255)
  215. if seg.aux0 >= max_on:
  216. seg.aux0 = 0
  217. seg.aux1 = random.randint(0, 0xFFFF)
  218. seg.aux0 += 1
  219. seg.step = iteration
  220. prng = seg.aux1
  221. for _ in range(seg.aux0):
  222. prng = (prng * 2053 + 13849) & 0xFFFF
  223. j = (prng * seg.length) >> 16
  224. if j < seg.length:
  225. seg.set_pixel_color(j, seg.color_from_palette(j))
  226. return FRAMETIME
  227. def mode_sparkle(seg: Segment) -> int:
  228. """Single sparkle effect"""
  229. for i in range(seg.length):
  230. seg.set_pixel_color(i, seg.color_from_palette(i))
  231. cycle_time = 10 + (255 - seg.speed) * 2
  232. iteration = seg.now() // cycle_time
  233. if iteration != seg.step:
  234. seg.aux0 = random.randint(0, seg.length - 1)
  235. seg.step = iteration
  236. seg.set_pixel_color(seg.aux0, seg.get_color(0))
  237. return FRAMETIME
  238. def mode_fire(seg: Segment) -> int:
  239. """Fire/flame effect"""
  240. if seg.call == 0:
  241. seg.data = [0] * seg.length
  242. # Cooling parameter (higher = cooler flames)
  243. cooling = ((100 - (seg.intensity >> 1)) * 10) // seg.length + 2
  244. # Heat decay for all pixels
  245. for i in range(seg.length):
  246. cool_down = random.randint(0, cooling)
  247. seg.data[i] = max(0, seg.data[i] - cool_down)
  248. # Heat drift upward
  249. for i in range(seg.length - 1, 2, -1):
  250. seg.data[i] = (seg.data[i - 1] + seg.data[i - 2] + seg.data[i - 2]) // 3
  251. # Randomly ignite new sparks near bottom
  252. if random.randint(0, 255) < seg.intensity:
  253. spark_pos = random.randint(0, min(7, seg.length - 1))
  254. seg.data[spark_pos] = min(255, seg.data[spark_pos] + random.randint(160, 255))
  255. # Convert heat to colors
  256. for i in range(seg.length):
  257. heat = seg.data[i]
  258. # Black -> Red -> Yellow -> White
  259. if heat < 85:
  260. color = (heat * 3, 0, 0)
  261. elif heat < 170:
  262. h = heat - 85
  263. color = (255, h * 3, 0)
  264. else:
  265. h = heat - 170
  266. color = (255, 255, h * 3)
  267. r, g, b = color
  268. seg.set_pixel_color(i, (r << 16) | (g << 8) | b)
  269. return FRAMETIME
  270. def mode_comet(seg: Segment) -> int:
  271. """Comet/shooting star effect"""
  272. if seg.call == 0:
  273. seg.aux0 = 0
  274. seg.aux1 = 0
  275. seg.fade_out(128)
  276. size = 1 + ((seg.intensity * seg.length) >> 9)
  277. cycle_time = 10 + (255 - seg.speed)
  278. iteration = seg.now() // cycle_time
  279. if iteration != seg.step:
  280. seg.aux0 = (seg.aux0 + 1) % seg.length
  281. seg.step = iteration
  282. # Draw comet
  283. for i in range(size):
  284. pos = (seg.aux0 - i) % seg.length
  285. brightness = 255 - (i * 255 // max(1, size))
  286. color = color_blend(0, seg.color_from_palette(pos), brightness)
  287. seg.set_pixel_color(pos, color)
  288. return FRAMETIME
  289. def mode_chase(seg: Segment) -> int:
  290. """Chase effect with colored segments"""
  291. if seg.call == 0:
  292. seg.aux0 = 0
  293. size = max(1, seg.length // 4)
  294. cycle_time = 10 + (255 - seg.speed)
  295. iteration = seg.now() // cycle_time
  296. if iteration != seg.step:
  297. seg.aux0 = (seg.aux0 + 1) % seg.length
  298. seg.step = iteration
  299. seg.fill(seg.get_color(1))
  300. for i in range(size):
  301. pos = (seg.aux0 + i) % seg.length
  302. seg.set_pixel_color(pos, seg.color_from_palette(pos))
  303. return FRAMETIME
  304. def mode_police(seg: Segment) -> int:
  305. """Police lights (red/blue alternating)"""
  306. cycle_time = 25 + (255 - seg.speed)
  307. on_time = cycle_time // 2
  308. now = seg.now()
  309. iteration = now // cycle_time
  310. rem = now % cycle_time
  311. on = rem < on_time
  312. half = seg.length // 2
  313. # Red on left, blue on right
  314. red = (255, 0, 0)
  315. blue = (0, 0, 255)
  316. off_color = (0, 0, 0)
  317. for i in range(half):
  318. if (iteration % 2 == 0 and on) or (iteration % 2 == 1 and not on):
  319. seg.set_pixel_color(i, (red[0] << 16) | (red[1] << 8) | red[2])
  320. else:
  321. seg.set_pixel_color(i, (off_color[0] << 16) | (off_color[1] << 8) | off_color[2])
  322. for i in range(half, seg.length):
  323. if (iteration % 2 == 1 and on) or (iteration % 2 == 0 and not on):
  324. seg.set_pixel_color(i, (blue[0] << 16) | (blue[1] << 8) | blue[2])
  325. else:
  326. seg.set_pixel_color(i, (off_color[0] << 16) | (off_color[1] << 8) | off_color[2])
  327. return FRAMETIME
  328. def mode_lightning(seg: Segment) -> int:
  329. """Lightning flash effect"""
  330. if seg.call == 0:
  331. seg.aux0 = 0
  332. seg.aux1 = 0
  333. cycle_time = 50 + (255 - seg.speed) * 10
  334. iteration = seg.now() // cycle_time
  335. if iteration != seg.step:
  336. # Random chance of lightning
  337. if random.randint(0, 255) < seg.intensity:
  338. seg.aux0 = random.randint(3, 8) # Number of flashes
  339. seg.aux1 = seg.now()
  340. seg.step = iteration
  341. # Flash sequence
  342. if seg.aux0 > 0:
  343. flash_duration = 50
  344. time_since = seg.now() - seg.aux1
  345. if time_since < flash_duration:
  346. # Flash on
  347. brightness = 255 - (time_since * 255 // flash_duration)
  348. for i in range(seg.length):
  349. color = color_blend(0, WHITE, brightness)
  350. seg.set_pixel_color(i, color)
  351. else:
  352. # Flash off, wait for next
  353. if time_since > flash_duration + random.randint(10, 100):
  354. seg.aux0 -= 1
  355. seg.aux1 = seg.now()
  356. else:
  357. seg.fill(seg.get_color(1))
  358. else:
  359. seg.fill(seg.get_color(1))
  360. return FRAMETIME
  361. def mode_fireworks(seg: Segment) -> int:
  362. """Fireworks effect"""
  363. seg.fade_out(64)
  364. cycle_time = 20 + (255 - seg.speed)
  365. iteration = seg.now() // cycle_time
  366. if iteration != seg.step:
  367. # Launch new firework
  368. if random.randint(0, 255) < seg.intensity:
  369. pos = random.randint(0, seg.length - 1)
  370. color = color_wheel(random.randint(0, 255))
  371. # Bright center
  372. seg.set_pixel_color(pos, color)
  373. # Dimmer neighbors
  374. if pos > 0:
  375. seg.set_pixel_color(pos - 1, color_blend(0, color, 128))
  376. if pos < seg.length - 1:
  377. seg.set_pixel_color(pos + 1, color_blend(0, color, 128))
  378. seg.step = iteration
  379. return FRAMETIME
  380. def mode_ripple(seg: Segment) -> int:
  381. """Ripple effect"""
  382. if seg.call == 0:
  383. seg.data = [0] * seg.length
  384. seg.aux0 = seg.length // 2
  385. seg.fade_out(250)
  386. cycle_time = 50 + (255 - seg.speed) * 2
  387. iteration = seg.now() // cycle_time
  388. if iteration != seg.step:
  389. # New ripple
  390. if random.randint(0, 255) < seg.intensity:
  391. seg.aux0 = random.randint(0, seg.length - 1)
  392. seg.data[seg.aux0] = 255
  393. # Propagate ripple
  394. new_data = seg.data.copy()
  395. for i in range(1, seg.length - 1):
  396. new_data[i] = (seg.data[i - 1] + seg.data[i + 1]) // 2
  397. seg.data = new_data
  398. seg.step = iteration
  399. for i in range(seg.length):
  400. if seg.data[i] > 0:
  401. color = color_blend(seg.get_color(1),
  402. seg.color_from_palette(i),
  403. seg.data[i])
  404. seg.set_pixel_color(i, color)
  405. return FRAMETIME
  406. def mode_flow(seg: Segment) -> int:
  407. """Smooth flowing color movement"""
  408. counter = seg.now() * ((seg.speed >> 3) + 1)
  409. for i in range(seg.length):
  410. pos = ((i * 256 // seg.length) + counter) & 0xFFFF
  411. color = color_wheel((pos >> 8) & 0xFF)
  412. # Apply intensity as brightness modulation
  413. brightness = 128 + ((sin8((pos >> 7) & 0xFF) - 128) * seg.intensity // 255)
  414. color = color_blend(0, color, brightness)
  415. seg.set_pixel_color(i, color)
  416. return FRAMETIME
  417. def mode_colorloop(seg: Segment) -> int:
  418. """Smooth color loop across entire strip"""
  419. counter = (seg.now() * ((seg.speed >> 3) + 1)) & 0xFFFF
  420. for i in range(seg.length):
  421. # Create gradient based on position and time
  422. hue = ((i * 256 // max(1, seg.length)) + (counter >> 7)) & 0xFF
  423. color = color_wheel(hue)
  424. # Intensity controls saturation
  425. if seg.intensity < 255:
  426. color = color_blend(color, WHITE, 255 - seg.intensity)
  427. seg.set_pixel_color(i, color)
  428. return FRAMETIME
  429. def mode_palette_flow(seg: Segment) -> int:
  430. """Flowing palette colors"""
  431. counter = seg.now() * ((seg.speed >> 3) + 1)
  432. for i in range(seg.length):
  433. # Get color from palette based on position and time
  434. palette_pos = ((i * 255 // max(1, seg.length)) + (counter >> 7)) & 0xFF
  435. color = seg.color_from_palette(palette_pos)
  436. # Intensity controls brightness modulation
  437. if seg.intensity < 255:
  438. brightness = 128 + ((sin8(palette_pos) - 128) * seg.intensity // 255)
  439. color = color_blend(0, color, brightness)
  440. seg.set_pixel_color(i, color)
  441. return FRAMETIME
  442. def mode_gradient(seg: Segment) -> int:
  443. """Smooth gradient between colors"""
  444. for i in range(seg.length):
  445. # Create gradient from color 0 to color 2
  446. blend_amount = (i * 255) // max(1, seg.length - 1)
  447. color = color_blend(seg.get_color(0), seg.get_color(2), blend_amount)
  448. # Intensity controls a pulsing brightness
  449. if seg.intensity > 0:
  450. counter = (seg.now() * ((seg.speed >> 3) + 1)) & 0xFFFF
  451. pulse = sin8((counter >> 8) & 0xFF)
  452. brightness = 128 + ((pulse - 128) * seg.intensity // 255)
  453. color = color_blend(0, color, brightness)
  454. seg.set_pixel_color(i, color)
  455. return FRAMETIME
  456. def mode_multi_strobe(seg: Segment) -> int:
  457. """Multi-color strobe effect"""
  458. cycle_time = 50 + (255 - seg.speed)
  459. flash_duration = max(5, cycle_time // 4)
  460. now = seg.now()
  461. iteration = now // cycle_time
  462. rem = now % cycle_time
  463. if rem < flash_duration:
  464. # Strobe on with color from palette
  465. color_index = (iteration * 85) & 0xFF
  466. color = color_wheel(color_index)
  467. seg.fill(color)
  468. else:
  469. # Strobe off
  470. seg.fill(seg.get_color(1))
  471. return FRAMETIME
  472. def mode_waves(seg: Segment) -> int:
  473. """Sine wave effect"""
  474. counter = seg.now() * ((seg.speed >> 3) + 1)
  475. for i in range(seg.length):
  476. # Create wave pattern
  477. wave_pos = (i * 255 // max(1, seg.length)) + (counter >> 7)
  478. brightness = sin8(wave_pos & 0xFF)
  479. # Intensity controls wave amplitude
  480. brightness = 128 + ((brightness - 128) * seg.intensity // 255)
  481. color = color_blend(seg.get_color(1),
  482. seg.color_from_palette(i),
  483. brightness)
  484. seg.set_pixel_color(i, color)
  485. return FRAMETIME
  486. def mode_bpm(seg: Segment) -> int:
  487. """BPM (beats per minute) pulse effect"""
  488. # Calculate BPM based on speed (60-180 BPM)
  489. bpm = 60 + ((seg.speed * 120) >> 8)
  490. ms_per_beat = 60000 // bpm
  491. beat_phase = (seg.now() % ms_per_beat) * 255 // ms_per_beat
  492. brightness = sin8(beat_phase)
  493. for i in range(seg.length):
  494. # Create traveling beat
  495. offset = (i * 255 // max(1, seg.length))
  496. local_brightness = sin8((beat_phase + offset) & 0xFF)
  497. # Intensity controls brightness range
  498. local_brightness = 128 + ((local_brightness - 128) * seg.intensity // 255)
  499. color = color_blend(seg.get_color(1),
  500. seg.color_from_palette(i),
  501. local_brightness)
  502. seg.set_pixel_color(i, color)
  503. return FRAMETIME
  504. def mode_juggle(seg: Segment) -> int:
  505. """Juggling colored dots"""
  506. if seg.call == 0:
  507. seg.data = [0] * 8 # Track 8 dot positions
  508. seg.fade_out(224)
  509. cycle_time = 10 + (255 - seg.speed) // 2
  510. iteration = seg.now() // cycle_time
  511. if iteration != seg.step:
  512. # Update dot positions using different sine waves
  513. for dot in range(min(8, 1 + seg.intensity // 32)):
  514. phase = (seg.now() * (dot + 1)) & 0xFFFF
  515. pos = (sin16(phase) + 32768) * seg.length // 65536
  516. pos = max(0, min(seg.length - 1, pos))
  517. hue = (dot * 32) & 0xFF
  518. color = color_wheel(hue)
  519. seg.set_pixel_color(pos, color)
  520. seg.step = iteration
  521. return FRAMETIME
  522. def mode_meteor(seg: Segment) -> int:
  523. """Meteor shower effect with trails"""
  524. if seg.call == 0:
  525. seg.aux0 = 0
  526. seg.aux1 = 0
  527. # Fade all pixels
  528. seg.fade_out(200)
  529. size = 1 + ((seg.intensity * seg.length) >> 8)
  530. cycle_time = 10 + (255 - seg.speed)
  531. iteration = seg.now() // cycle_time
  532. if iteration != seg.step:
  533. seg.aux0 = (seg.aux0 + 1) % (seg.length + size)
  534. seg.step = iteration
  535. # Draw meteor head and tail
  536. if seg.aux0 < seg.length:
  537. for i in range(size):
  538. pos = seg.aux0 - i
  539. if 0 <= pos < seg.length:
  540. brightness = 255 - (i * 200 // max(1, size))
  541. color = color_blend(0, seg.color_from_palette(pos), brightness)
  542. # Add to existing color for brighter effect
  543. existing = seg.get_pixel_color(pos)
  544. seg.set_pixel_color(pos, color_add(existing, color))
  545. return FRAMETIME
  546. def mode_pride(seg: Segment) -> int:
  547. """Pride flag colors moving effect"""
  548. counter = seg.now() * ((seg.speed >> 3) + 1)
  549. # Pride flag colors (6 stripes)
  550. pride_colors = [
  551. (0xE4, 0x00, 0x3A), # Red
  552. (0xFF, 0x8C, 0x00), # Orange
  553. (0xFF, 0xED, 0x00), # Yellow
  554. (0x00, 0x81, 0x1F), # Green
  555. (0x00, 0x4C, 0xFF), # Blue
  556. (0x76, 0x01, 0x89), # Purple
  557. ]
  558. for i in range(seg.length):
  559. # Determine which stripe this pixel belongs to
  560. stripe_size = max(1, seg.length // 6)
  561. offset = (counter >> 7) & 0xFF
  562. stripe_idx = ((i + offset) // stripe_size) % 6
  563. r, g, b = pride_colors[stripe_idx]
  564. color = (r << 16) | (g << 8) | b
  565. # Intensity controls blending with background
  566. if seg.intensity < 255:
  567. color = color_blend(seg.get_color(1), color, seg.intensity)
  568. seg.set_pixel_color(i, color)
  569. return FRAMETIME
  570. def mode_pacifica(seg: Segment) -> int:
  571. """Ocean/water simulation"""
  572. if seg.call == 0:
  573. seg.data = [0] * seg.length
  574. counter = seg.now() * ((seg.speed >> 4) + 1)
  575. for i in range(seg.length):
  576. # Create multiple layered waves
  577. wave1 = sin8(((i * 5) + (counter >> 5)) & 0xFF)
  578. wave2 = sin8(((i * 3) + (counter >> 3)) & 0xFF)
  579. wave3 = sin8(((i * 7) + (counter >> 6)) & 0xFF)
  580. # Combine waves
  581. brightness = (wave1 + wave2 + wave3) // 3
  582. # Blue-green ocean colors
  583. if brightness < 64:
  584. # Deep blue
  585. r, g, b = 0, 0, 60 + brightness
  586. elif brightness < 128:
  587. # Blue-cyan
  588. val = brightness - 64
  589. r, g, b = 0, val * 2, 100 + val
  590. else:
  591. # Cyan-white (foam)
  592. val = brightness - 128
  593. r, g, b = val, 128 + val, 180 + (val // 2)
  594. # Apply intensity
  595. brightness = 128 + ((brightness - 128) * seg.intensity // 255)
  596. color = (r << 16) | (g << 8) | b
  597. color = color_blend(0, color, brightness)
  598. seg.set_pixel_color(i, color)
  599. return FRAMETIME
  600. def mode_plasma(seg: Segment) -> int:
  601. """Plasma effect"""
  602. counter = seg.now() * ((seg.speed >> 3) + 1)
  603. for i in range(seg.length):
  604. # Create plasma using multiple sine waves
  605. phase1 = sin8(((i * 16) + (counter >> 5)) & 0xFF)
  606. phase2 = sin8(((i * 8) + (counter >> 6)) & 0xFF)
  607. phase3 = sin8((counter >> 4) & 0xFF)
  608. # Combine phases
  609. hue = (phase1 + phase2 + phase3) & 0xFF
  610. color = color_wheel(hue)
  611. # Intensity controls saturation
  612. if seg.intensity < 255:
  613. color = color_blend(color, WHITE, 255 - seg.intensity)
  614. seg.set_pixel_color(i, color)
  615. return FRAMETIME
  616. def mode_dissolve(seg: Segment) -> int:
  617. """Random pixel dissolve/fade"""
  618. if seg.call == 0:
  619. seg.data = [0] * seg.length
  620. for i in range(seg.length):
  621. seg.data[i] = random.randint(0, 255)
  622. cycle_time = 20 + (255 - seg.speed)
  623. iteration = seg.now() // cycle_time
  624. if iteration != seg.step:
  625. # Randomly update some pixels
  626. for i in range(seg.length):
  627. if random.randint(0, 255) < seg.intensity:
  628. seg.data[i] = random.randint(0, 255)
  629. seg.step = iteration
  630. for i in range(seg.length):
  631. brightness = seg.data[i]
  632. color = color_blend(seg.get_color(1),
  633. seg.color_from_palette(i),
  634. brightness)
  635. seg.set_pixel_color(i, color)
  636. return FRAMETIME
  637. def mode_glitter(seg: Segment) -> int:
  638. """Sparkle glitter overlay"""
  639. # Fill with base color
  640. for i in range(seg.length):
  641. seg.set_pixel_color(i, seg.color_from_palette(i))
  642. # Add random sparkles based on intensity
  643. sparkle_chance = seg.intensity
  644. num_sparkles = max(1, (seg.length * sparkle_chance) // 255)
  645. for _ in range(num_sparkles):
  646. if random.randint(0, 255) < seg.speed:
  647. pos = random.randint(0, seg.length - 1)
  648. seg.set_pixel_color(pos, WHITE)
  649. return FRAMETIME
  650. def mode_confetti(seg: Segment) -> int:
  651. """Random colored pixels"""
  652. seg.fade_out(224)
  653. cycle_time = 10 + (255 - seg.speed)
  654. iteration = seg.now() // cycle_time
  655. if iteration != seg.step:
  656. # Add random confetti
  657. density = 1 + (seg.intensity >> 5)
  658. for _ in range(density):
  659. pos = random.randint(0, seg.length - 1)
  660. hue = random.randint(0, 255)
  661. color = color_wheel(hue)
  662. seg.set_pixel_color(pos, color)
  663. seg.step = iteration
  664. return FRAMETIME
  665. def mode_sinelon(seg: Segment) -> int:
  666. """Sine wave colored dot"""
  667. seg.fade_out(224)
  668. counter = seg.now() * ((seg.speed >> 2) + 1)
  669. phase = (counter >> 8) & 0xFFFF
  670. # Calculate position using sine
  671. pos = (sin16(phase) + 32768) * seg.length // 65536
  672. pos = max(0, min(seg.length - 1, pos))
  673. # Color rotates
  674. hue = (counter >> 7) & 0xFF
  675. color = color_wheel(hue)
  676. # Intensity controls trail length (via fade)
  677. if seg.intensity < 255:
  678. color = color_blend(0, color, seg.intensity)
  679. seg.set_pixel_color(pos, color)
  680. return FRAMETIME
  681. def mode_candle(seg: Segment) -> int:
  682. """Flickering candle simulation"""
  683. if seg.call == 0:
  684. seg.data = [128] * seg.length
  685. # Random flicker for each pixel
  686. for i in range(seg.length):
  687. # Small random changes
  688. change = random.randint(-20, 20)
  689. seg.data[i] = max(30, min(255, seg.data[i] + change))
  690. # Speed affects flicker rate
  691. if seg.speed > 128 and random.randint(0, 255) < (seg.speed - 128):
  692. seg.data[i] = random.randint(50, 200)
  693. # Warm orange/yellow color
  694. brightness = seg.data[i]
  695. r = brightness
  696. g = (brightness * 2) // 3
  697. b = (brightness * seg.intensity) // 512 # Intensity controls blue
  698. color = (r << 16) | (g << 8) | b
  699. seg.set_pixel_color(i, color)
  700. return FRAMETIME
  701. def mode_aurora(seg: Segment) -> int:
  702. """Northern lights effect"""
  703. if seg.call == 0:
  704. seg.data = [0] * seg.length
  705. counter = seg.now() * ((seg.speed >> 4) + 1)
  706. for i in range(seg.length):
  707. # Multiple slow-moving waves
  708. wave1 = sin8(((i * 3) + (counter >> 6)) & 0xFF)
  709. wave2 = sin8(((i * 5) + (counter >> 7) + 85) & 0xFF)
  710. wave3 = sin8(((i * 2) + (counter >> 5) + 170) & 0xFF)
  711. # Aurora colors: green, blue, purple
  712. brightness = (wave1 + wave2 + wave3) // 3
  713. if brightness < 85:
  714. # Green
  715. r, g, b = 0, brightness * 2, brightness // 2
  716. elif brightness < 170:
  717. # Blue-green
  718. val = brightness - 85
  719. r, g, b = 0, 100 + val, 100 + val
  720. else:
  721. # Purple-pink
  722. val = brightness - 170
  723. r, g, b = val * 2, val, 150 + val
  724. # Apply intensity for brightness variation
  725. brightness_mod = 128 + ((brightness - 128) * seg.intensity // 255)
  726. color = (r << 16) | (g << 8) | b
  727. color = color_blend(0, color, brightness_mod)
  728. seg.set_pixel_color(i, color)
  729. return FRAMETIME
  730. def mode_rain(seg: Segment) -> int:
  731. """Rain drops falling"""
  732. if seg.call == 0:
  733. seg.data = [0] * seg.length
  734. seg.aux0 = 0
  735. seg.fade_out(235)
  736. cycle_time = 15 + (255 - seg.speed)
  737. iteration = seg.now() // cycle_time
  738. if iteration != seg.step:
  739. # New rain drops
  740. if random.randint(0, 255) < seg.intensity:
  741. pos = random.randint(0, min(5, seg.length - 1)) # Start near beginning
  742. seg.data[pos] = 255
  743. # Move drops down
  744. new_data = [0] * seg.length
  745. for i in range(seg.length - 1):
  746. if seg.data[i] > 0:
  747. # Drop moves forward
  748. if i + 1 < seg.length:
  749. new_data[i + 1] = seg.data[i] - 10
  750. seg.data = new_data
  751. seg.step = iteration
  752. # Draw rain drops (blue)
  753. for i in range(seg.length):
  754. if seg.data[i] > 0:
  755. brightness = seg.data[i]
  756. color = color_blend(0, seg.color_from_palette(i), brightness)
  757. seg.set_pixel_color(i, color)
  758. return FRAMETIME
  759. def mode_halloween(seg: Segment) -> int:
  760. """Halloween orange and purple"""
  761. counter = seg.now() * ((seg.speed >> 3) + 1)
  762. # Alternating orange and purple
  763. orange = (0xFF << 16) | (0x44 << 8) | 0x00
  764. purple = (0x88 << 16) | (0x00 << 8) | 0xFF
  765. for i in range(seg.length):
  766. # Create moving pattern
  767. phase = ((i * 255 // max(1, seg.length)) + (counter >> 7)) & 0xFF
  768. wave = sin8(phase)
  769. # Blend between orange and purple
  770. if wave < 128:
  771. color = orange
  772. else:
  773. color = purple
  774. # Intensity controls blending smoothness
  775. if seg.intensity > 0:
  776. blend_amt = (wave * seg.intensity) // 255
  777. color = color_blend(orange, purple, blend_amt)
  778. seg.set_pixel_color(i, color)
  779. return FRAMETIME
  780. def mode_noise(seg: Segment) -> int:
  781. """Perlin-like noise pattern"""
  782. if seg.call == 0:
  783. seg.data = [random.randint(0, 255) for _ in range(seg.length)]
  784. cycle_time = 20 + (255 - seg.speed) // 2
  785. iteration = seg.now() // cycle_time
  786. if iteration != seg.step:
  787. # Smooth noise by averaging neighbors
  788. new_data = seg.data.copy()
  789. for i in range(1, seg.length - 1):
  790. avg = (seg.data[i - 1] + seg.data[i] * 2 + seg.data[i + 1]) // 4
  791. variation = random.randint(-20, 20)
  792. new_data[i] = max(0, min(255, avg + variation))
  793. # Occasionally inject new random values
  794. if random.randint(0, 255) < seg.intensity:
  795. pos = random.randint(0, seg.length - 1)
  796. new_data[pos] = random.randint(0, 255)
  797. seg.data = new_data
  798. seg.step = iteration
  799. # Map noise to colors
  800. for i in range(seg.length):
  801. hue = seg.data[i]
  802. color = color_wheel(hue)
  803. seg.set_pixel_color(i, color)
  804. return FRAMETIME
  805. def mode_funky_plank(seg: Segment) -> int:
  806. """Multiple colored bars moving"""
  807. if seg.call == 0:
  808. seg.aux0 = 0
  809. cycle_time = 10 + (255 - seg.speed)
  810. iteration = seg.now() // cycle_time
  811. if iteration != seg.step:
  812. seg.aux0 = (seg.aux0 + 1) % seg.length
  813. seg.step = iteration
  814. num_planks = max(2, 1 + (seg.intensity >> 5))
  815. plank_size = max(1, seg.length // num_planks)
  816. for i in range(seg.length):
  817. plank_idx = ((i + seg.aux0) // plank_size) % num_planks
  818. hue = (plank_idx * 256 // num_planks) & 0xFF
  819. color = color_wheel(hue)
  820. seg.set_pixel_color(i, color)
  821. return FRAMETIME
  822. # Effect registry
  823. EFFECTS = {
  824. 0: ("Static", mode_static),
  825. 1: ("Blink", mode_blink),
  826. 2: ("Breathe", mode_breath),
  827. 3: ("Wipe", mode_color_wipe),
  828. 4: ("Fade", mode_fade),
  829. 5: ("Scan", mode_scan),
  830. 6: ("Dual Scan", mode_dual_scan),
  831. 7: ("Rainbow Cycle", mode_rainbow),
  832. 8: ("Rainbow", mode_rainbow_cycle),
  833. 9: ("Theater Chase", mode_theater_chase),
  834. 10: ("Running Lights", mode_running_lights),
  835. 11: ("Random Color", mode_random_color),
  836. 12: ("Dynamic", mode_dynamic),
  837. 13: ("Twinkle", mode_twinkle),
  838. 14: ("Sparkle", mode_sparkle),
  839. 15: ("Strobe", mode_strobe),
  840. 16: ("Fire", mode_fire),
  841. 17: ("Comet", mode_comet),
  842. 18: ("Chase", mode_chase),
  843. 19: ("Police", mode_police),
  844. 20: ("Lightning", mode_lightning),
  845. 21: ("Fireworks", mode_fireworks),
  846. 22: ("Ripple", mode_ripple),
  847. 23: ("Flow", mode_flow),
  848. 24: ("Colorloop", mode_colorloop),
  849. 25: ("Palette Flow", mode_palette_flow),
  850. 26: ("Gradient", mode_gradient),
  851. 27: ("Multi Strobe", mode_multi_strobe),
  852. 28: ("Waves", mode_waves),
  853. 29: ("BPM", mode_bpm),
  854. 30: ("Juggle", mode_juggle),
  855. 31: ("Meteor", mode_meteor),
  856. 32: ("Pride", mode_pride),
  857. 33: ("Pacifica", mode_pacifica),
  858. 34: ("Plasma", mode_plasma),
  859. 35: ("Dissolve", mode_dissolve),
  860. 36: ("Glitter", mode_glitter),
  861. 37: ("Confetti", mode_confetti),
  862. 38: ("Sinelon", mode_sinelon),
  863. 39: ("Candle", mode_candle),
  864. 40: ("Aurora", mode_aurora),
  865. 41: ("Rain", mode_rain),
  866. 42: ("Halloween", mode_halloween),
  867. 43: ("Noise", mode_noise),
  868. 44: ("Funky Plank", mode_funky_plank),
  869. }
  870. def get_effect(effect_id: int):
  871. """Get effect function by ID"""
  872. if effect_id in EFFECTS:
  873. return EFFECTS[effect_id][1]
  874. return mode_static
  875. def get_effect_name(effect_id: int) -> str:
  876. """Get effect name by ID"""
  877. if effect_id in EFFECTS:
  878. return EFFECTS[effect_id][0]
  879. return "Unknown"
  880. def get_all_effects():
  881. """Get list of all effects"""
  882. return [(k, v[0]) for k, v in sorted(EFFECTS.items())]