diff --git a/docs/moonbase/inputoutput.md b/docs/moonbase/inputoutput.md index 13e3fa97d..fa62be815 100644 --- a/docs/moonbase/inputoutput.md +++ b/docs/moonbase/inputoutput.md @@ -326,6 +326,7 @@ The **I2C frequency** can be adjusted (default 100 kHz). Higher frequencies (400 * Choose the esp32-s3-n8r8v board in the [MoonLight Installer](../../gettingstarted/installer/) * Set Switch1 the same as you set the jumper on the board: off / default: Infrared. on: Ethernet. * Only 5 boards were ever produced. If you are one of the lucky few, feel free to reach out to limpkin on [Discord](https://discord.gg/TC8NSUSCdV) +* Use the [L_SE16.sc](https://github.com/MoonModules/MoonLight/blob/main/livescripts/Layouts/L_SE16.sc) Live Script layout for this board. Controls: `mirroredPins` (wiring mode), `pinsAreColumns` (axis orientation), `ledsPerPin` (LEDs per output). #### LightCrafter16 @@ -334,3 +335,4 @@ The **I2C frequency** can be adjusted (default 100 kHz). Higher frequencies (400 * Choose the esp32-s3-n8r8v board in the [MoonLight Installer](../../gettingstarted/installer/) * Documentation to be soon published on [limpkin's website](https://www.limpkin.fr) +* Use the [L_LC16.sc](https://github.com/MoonModules/MoonLight/blob/main/livescripts/Layouts/L_LC16.sc) Live Script layout for this board. Controls: `pinsAreColumns` (axis orientation), `ledsPerPin` (LEDs per output). diff --git a/docs/moonlight/livescripts.md b/docs/moonlight/livescripts.md index 3db89c5ed..2633c43f9 100644 --- a/docs/moonlight/livescripts.md +++ b/docs/moonlight/livescripts.md @@ -36,7 +36,7 @@ The **Live Scripts module** itself does not create scripts — it shows all curr image -See [example scripts on GitHub](https://github.com/MoonModules/MoonLight/tree/main/misc/livescripts) — `E_*.sc` for effects, `L_*.sc` for layouts, `P_*.sc` for palettes. +See example scripts on GitHub: [Effects](https://github.com/MoonModules/MoonLight/tree/main/livescripts/Effects), [Layouts](https://github.com/MoonModules/MoonLight/tree/main/livescripts/Layouts), [Palettes](https://github.com/MoonModules/MoonLight/tree/main/livescripts/Palettes). **Step 3**: Select the script in the appropriate module: @@ -74,9 +74,11 @@ A script can combine these — for example, an effect with both `setup()` (to cr `uint8_t`, `uint16_t`, `uint32_t`, `int`, `float`, `bool`, `void`, `CRGB` -### Predefined variable: `NUM_LEDS` +### Predefined types and constants -`NUM_LEDS` is automatically defined as the current number of lights before compilation. +| Name | Definition | Description | +|---|---|---| +| `NUM_LEDS` | `#define` | Current number of lights, set before compilation | --- @@ -113,10 +115,18 @@ A script can combine these — for example, an effect with both `setup()` (to cr | Function | Description | |---|---| | `void fadeToBlackBy(uint8_t amount)` | Fade all LEDs toward black | +| `CRGB ColorFromPalette(uint8_t index, uint8_t brightness)` | Look up a color from the current global palette (index 0–255) | +| `CRGB getRGB(uint16_t index)` | Read the current color of LED at index | | `void setRGB(uint16_t index, CRGB color)` | Set LED at index to an RGB color | +| `void setRGBXY(int x, int y, CRGB color)` | Set LED at coordinate (x, y) — runs through modifier chain | +| `void setRGBXYZ(int x, int y, int z, CRGB color)` | Set LED at coordinate (x, y, z) — runs through modifier chain | +| `void setHSV(uint16_t index, uint8_t h, uint8_t s, uint8_t v)` | Set LED at index to an HSV color | +| `void setHSVXY(int x, int y, uint8_t h, uint8_t s, uint8_t v)` | Set LED at coordinate (x, y) to an HSV color | | `void setRGBPal(uint16_t index, uint8_t palIndex, uint8_t brightness)` | Set LED using the current palette | | `void setPan(uint16_t index, uint8_t value)` | Set pan channel (moving heads) | | `void setTilt(uint16_t index, uint8_t value)` | Set tilt channel (moving heads) | +| `void drawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, CRGB color)` | Draw a 2D line between two points | +| `void drawCircle(int cx, int cy, uint8_t radius, CRGB color)` | Draw a 2D circle outline | ### Layout functions (for `L_` scripts) @@ -153,6 +163,9 @@ A script can combine these — for example, an effect with both `setup()` (to cr | `gravityX` | `int` | IMU gravity vector X component | | `gravityY` | `int` | IMU gravity vector Y component | | `gravityZ` | `int` | IMU gravity vector Z component | +| `hour` | `uint8_t` | Current hour (0–23), requires NTP | +| `minute` | `uint8_t` | Current minute (0–59), requires NTP | +| `second` | `uint8_t` | Current second (0–59), requires NTP | --- @@ -265,4 +278,4 @@ void loop() { --- -More example scripts are available on [GitHub](https://github.com/MoonModules/MoonLight/tree/main/misc/livescripts). To request new functions, send a [ping on Discord](https://discord.com/channels/700041398778331156/1369578126450884608). +More example scripts are available on GitHub: [Effects](https://github.com/MoonModules/MoonLight/tree/main/livescripts/Effects), [Layouts](https://github.com/MoonModules/MoonLight/tree/main/livescripts/Layouts), [Palettes](https://github.com/MoonModules/MoonLight/tree/main/livescripts/Palettes). To request new functions, send a [ping on Discord](https://discord.com/channels/700041398778331156/1369578126450884608). diff --git a/livescripts/Effects/Arti-FX/E_Beatmania.sc b/livescripts/Effects/Arti-FX/E_Beatmania.sc new file mode 100644 index 000000000..dcb328ce3 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Beatmania.sc @@ -0,0 +1,31 @@ +// Beatmania - beat-synced moving lights +// Original: beatmania.wled by Andrew Tuline + +uint8_t speed = 128; +uint8_t intensity = 128; + +void setup() { + addControl(&speed, "speed", "slider", 1, 255); + addControl(&intensity, "intensity", "slider", 1, 255); +} + +void loop() { + fadeToBlackBy(intensity / 8); + + uint8_t locn1 = beatsin8(speed / 3 + 1, 0, NUM_LEDS - 1, 0, 0); + uint8_t locn2 = beatsin8(speed / 4 + 1, 0, NUM_LEDS - 1, 0, 0); + + uint8_t colr1 = beatsin8(intensity / 6 + 1, 0, 255, 0, 0); + uint8_t colr2 = beatsin8(intensity / 7 + 1, 0, 255, 0, 0); + + uint8_t bri1 = beatsin8(intensity / 6 + 1, 32, 255, 0, 0); + uint8_t bri2 = beatsin8(intensity / 7 + 1, 32, 255, 0, 0); + + int locn12 = (locn1 + locn2) % NUM_LEDS; + uint8_t colr12 = colr1 + colr2; + uint8_t bri12 = bri1 + bri2; + + setRGBPal(locn12, colr12, bri12); + setRGBPal(locn1, colr2, bri1); + setRGBPal(locn2, colr1, bri2); +} diff --git a/livescripts/Effects/Arti-FX/E_BlockReflections.sc b/livescripts/Effects/Arti-FX/E_BlockReflections.sc new file mode 100644 index 000000000..856e80bcf --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_BlockReflections.sc @@ -0,0 +1,41 @@ +// Block Reflections - shifting reflected blocks +// Original: block_reflections.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t2 = (millis() % 6553) / 6553.0 * PI2; + float t1 = (millis() % 6553) / 6553.0; + float t3 = (millis() % 32767) / 32767.0; + float t4 = (millis() % 13107) / 13107.0 * PI2; + + // triangle(t1) + float tf = t1 * 2.0 - 1.0; + if (tf < 0.0) tf = 0.0 - tf; + float trit1 = 1.0 - tf; + + // triangle(t3) + float t3f = t3 * 2.0 - 1.0; + if (t3f < 0.0) t3f = 0.0 - t3f; + float trit3 = 1.0 - t3f; + + float m = 0.3 + trit1 * 0.2; + + for (int i = 0; i < NUM_LEDS; i++) { + float h = sin(t2); + float hh = (i - NUM_LEDS / 2.0) / NUM_LEDS; + float hhh = trit3 * 10.0 + 4.0 * sin(t4); + float hmod = hh * hhh; + float mf = hmod - (int)(hmod / m) * m; + h = h + mf; + + float vv = h; + if (vv < 0.0) vv = 0.0 - vv; + float vm = m; + if (vm < 0.0) vm = 0.0 - vm; + float v = (vv + vm + t1); + float vf = v - (int)v; + v = vf * vf; + setHSV(i, h * 255, 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_BrightPulse.sc b/livescripts/Effects/Arti-FX/E_BrightPulse.sc new file mode 100644 index 000000000..d002a25c9 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_BrightPulse.sc @@ -0,0 +1,19 @@ +// Bright Pulse - fill LEDs based on audio volume +// Original: BrightPulseSR.wled (idea by @tony) +// Requires an audio driver (WLED Audio or FastLED Audio) + +uint8_t speed = 128; + +void setup() { + addControl(&speed, "sensitivity", "slider", 1, 255); +} + +void loop() { + int lum = volume * 256 / (256 - speed); + if (lum > 255) lum = 255; + if (lum < 0) lum = 0; + + for (int i = 0; i < NUM_LEDS; i++) { + setHSV(i, 0, 0, lum); + } +} diff --git a/livescripts/Effects/Arti-FX/E_Clock.sc b/livescripts/Effects/Arti-FX/E_Clock.sc new file mode 100644 index 000000000..b9285cb05 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Clock.sc @@ -0,0 +1,12 @@ +// Clock - show hour, minute, second as colored pixels +// Original: Clock.wled from MoonModules/MM-Effects +// Requires NTP to be configured for correct time + +void loop() { + fadeToBlackBy(255); // instant clear + + // hour, minute, second as dots at positions along the strip + if (hour < NUM_LEDS) setRGBPal(hour, 25, 255); + if (minute < NUM_LEDS) setRGBPal(minute, 125, 255); + if (second < NUM_LEDS) setRGBPal(second, 225, 255); +} diff --git a/livescripts/Effects/Arti-FX/E_Clock2D.sc b/livescripts/Effects/Arti-FX/E_Clock2D.sc new file mode 100644 index 000000000..8d8b199d5 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Clock2D.sc @@ -0,0 +1,42 @@ +// Clock 2D - analog clock on a 2D matrix +// Original: Clock2D.wled from MoonModules/MM-Effects +// Requires a 2D layout (panel) and NTP for correct time + +#define PI2 6.28318 + +void loop() { + fadeToBlackBy(255); // instant clear + + int cx = width / 2; + int cy = height / 2; + int radius = cx; + if (cy < radius) radius = cy; + + // draw 12 hour markers + for (int h = 0; h < 12; h++) { + float angle = h * PI2 / 12.0; + int mx = cx + sin(angle) * (radius - 1); + int my = cy - cos(angle) * (radius - 1); + if (mx >= 0 && mx < width && my >= 0 && my < height) { + setRGBXY(mx, my, ColorFromPalette(225, 128)); + } + } + + // second hand + float sa = second * PI2 / 60.0; + int sx = cx + sin(sa) * (radius - 1); + int sy = cy - cos(sa) * (radius - 1); + drawLine(cx, cy, sx, sy, ColorFromPalette(25, 255)); + + // minute hand + float ma = minute * PI2 / 60.0; + int mx = cx + sin(ma) * (radius - 2); + int my = cy - cos(ma) * (radius - 2); + drawLine(cx, cy, mx, my, ColorFromPalette(92, 255)); + + // hour hand (shorter) + float ha = (hour % 12) * PI2 / 12.0 + minute * PI2 / 720.0; + int hx = cx + sin(ha) * (radius / 2); + int hy = cy - cos(ha) * (radius / 2); + drawLine(cx, cy, hx, hy, ColorFromPalette(160, 255)); +} diff --git a/livescripts/Effects/Arti-FX/E_ColorFadePulse.sc b/livescripts/Effects/Arti-FX/E_ColorFadePulse.sc new file mode 100644 index 000000000..e9eda04ab --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_ColorFadePulse.sc @@ -0,0 +1,23 @@ +// Color Fade Pulse - fading color pulse +// Original: color_fade_pulse.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI 3.14159 +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 655) / 655.0; + float t2 = (millis() % 6553) / 6553.0 * PI2; + float t3 = (millis() % 1310) / 1310.0; + + for (int i = 0; i < NUM_LEDS; i++) { + float il = i * 1.0 / NUM_LEDS; + float h = il * 2.0 - t1; + float s = (1.0 + sin(t2 + il * PI)) / 2.0; + float vt = (t3 + il * 4.0); + float vf = vt - (int)vt; + float v = vf * 2.0 - 1.0; + if (v < 0.0) v = 0.0 - v; + v = 1.0 - v; + setHSV(i, h * 255, s * 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_ColorRandom.sc b/livescripts/Effects/Arti-FX/E_ColorRandom.sc new file mode 100644 index 000000000..a10c00d23 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_ColorRandom.sc @@ -0,0 +1,6 @@ +// ColorRandom - random palette colors +// Original: ColorRandom.wled from MoonModules/MM-Effects + +void loop() { + setRGBPal(random16(NUM_LEDS), random16(255), 255); +} diff --git a/livescripts/Effects/Arti-FX/E_ColorTwinkleBounce.sc b/livescripts/Effects/Arti-FX/E_ColorTwinkleBounce.sc new file mode 100644 index 000000000..c3f931d50 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_ColorTwinkleBounce.sc @@ -0,0 +1,19 @@ +// Color Twinkle Bounce - twinkling bouncing colors +// Original: color_twinkle_bounce.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 3276) / 3276.0 * PI2; + float t2 = (millis() % 3276) / 3276.0 * PI2; + float tb = (millis() % 6553) / 6553.0; + + for (int i = 0; i < NUM_LEDS; i++) { + float aa = i / 2.0 + 5.0 * sin(t1); + float a = (1.0 + sin(aa)) / 2.0; + float ba = i / 2.0 + 5.0 * sin(t2); + float b = tb + 1.0 + sin(ba); + float v = a * a * a; + setHSV(i, b * 255, 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_ColorTwinkles.sc b/livescripts/Effects/Arti-FX/E_ColorTwinkles.sc new file mode 100644 index 000000000..43f478ae3 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_ColorTwinkles.sc @@ -0,0 +1,20 @@ +// Color Twinkles - sparkling color twinkles +// Original: color_twinkles.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 9830) / 9830.0 * PI2; + float t2 = (millis() % 32767) / 32767.0 * PI2; + + for (int i = 0; i < NUM_LEDS; i++) { + float aa = i / 3.0 + PI2 * sin(i / 2.0 + t1); + float a = (1.0 + sin(aa)) / 2.0; + a = a * a * a * a; + if (a <= 0.1) a = 0.0; + + float bb = i / 3.0 + PI2 * sin(i / 2.0 + t2); + float b = sin(bb); + setHSV(i, b * 255, 255, a * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_Drip.sc b/livescripts/Effects/Arti-FX/E_Drip.sc new file mode 100644 index 000000000..bf627a60e --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Drip.sc @@ -0,0 +1,32 @@ +// Drip - single drip falling with gravity +// Original: drip.wled by Andrew Tuline + +uint8_t speed = 50; +uint8_t fade = 64; +float dripSpd = 0.0; +float dripLocn = 0.0; +uint8_t colr = 1; +uint8_t bri = 64; + +void setup() { + addControl(&speed, "speed", "slider", 1, 255); + addControl(&fade, "fade", "slider", 1, 255); + dripLocn = NUM_LEDS - 1; +} + +void loop() { + float grav = speed / 1000.0; + fadeToBlackBy(fade); + + dripSpd = dripSpd + grav; + dripLocn = dripLocn - dripSpd; + + if (dripLocn >= 0.0) { + setRGBPal((int)dripLocn, colr, bri); + } + + if (dripLocn < 0.0) { + dripLocn = NUM_LEDS - 1; + dripSpd = 0.0; + } +} diff --git a/livescripts/Effects/Arti-FX/E_EdgeBurst.sc b/livescripts/Effects/Arti-FX/E_EdgeBurst.sc new file mode 100644 index 000000000..fc8a4a869 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_EdgeBurst.sc @@ -0,0 +1,30 @@ +// Edge Burst - bursts from edges +// Original: edge_burst.wled (PixelBlaze, converted by Andrew Tuline) + +void loop() { + // t1 = triangle(time(0.1)) + float tval = (millis() % 6553) / 6553.0; + float tf = tval * 2.0 - 1.0; + if (tf < 0.0) tf = 0.0 - tf; + float t1 = 1.0 - tf; + + for (int i = 0; i < NUM_LEDS; i++) { + float f = i * 1.0 / NUM_LEDS; + // triangle(f) + float ft = f * 2.0 - 1.0; + if (ft < 0.0) ft = 0.0 - ft; + float trif = 1.0 - ft; + + float edge = trif + t1 * 4.0 - 2.0; + if (edge < 0.0) edge = 0.0; + if (edge > 1.0) edge = 1.0; + + // triangle(edge) + float et = edge * 2.0 - 1.0; + if (et < 0.0) et = 0.0 - et; + float v = 1.0 - et; + + float h = edge * edge - 0.2; + setHSV(i, h * 255, 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_FFTBands.sc b/livescripts/Effects/Arti-FX/E_FFTBands.sc new file mode 100644 index 000000000..bb4eb209d --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_FFTBands.sc @@ -0,0 +1,9 @@ +// FFT Bands - frequency-reactive brightness per band +// Original: fftBrightness.wled by ewowi + +void loop() { + for (int i = 0; i < NUM_LEDS; i++) { + uint8_t band = i % 16; + setRGBPal(i, i * 16, bands[band]); + } +} diff --git a/livescripts/Effects/Arti-FX/E_FastPulse.sc b/livescripts/Effects/Arti-FX/E_FastPulse.sc new file mode 100644 index 000000000..54a806a7f --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_FastPulse.sc @@ -0,0 +1,23 @@ +// Fast Pulse - pulsing light wave +// Original: fast_pulse.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 6553) / 6553.0; + float w = (1.0 + sin(t1 * PI2)) / 2.0; + + for (int i = 0; i < NUM_LEDS; i++) { + float il = i * 1.0 / NUM_LEDS; + float x = (2.0 * w + il); + float frac = x - (int)x; + // triangle of frac + float v = frac * 2.0 - 1.0; + if (v < 0.0) v = 0.0 - v; + v = 1.0 - v; + v = v * v * v * v * v; + uint8_t s = 255; + if (v >= 0.9) s = 0; + setHSV(i, t1 * 255, s, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_FireworkDust.sc b/livescripts/Effects/Arti-FX/E_FireworkDust.sc new file mode 100644 index 000000000..b4cb65265 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_FireworkDust.sc @@ -0,0 +1,12 @@ +// Firework Dust - random sparkles +// Original: firework_dust.wled (PixelBlaze) + +void loop() { + for (int i = 0; i < NUM_LEDS; i++) { + if (random16(65535) > 64500) { + setHSV(i, random16(255), 255, 255); + } else { + setRGB(i, CRGB(0, 0, 0)); + } + } +} diff --git a/livescripts/Effects/Arti-FX/E_FireworkSparks.sc b/livescripts/Effects/Arti-FX/E_FireworkSparks.sc new file mode 100644 index 000000000..c9b2ebc83 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_FireworkSparks.sc @@ -0,0 +1,32 @@ +// Firework Rocket Sparks - flickering rocket sparks +// Original: firework_rocket_sparks.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 3276) / 3276.0; + + for (int i = 0; i < NUM_LEDS; i++) { + float il = i * 1.0 / NUM_LEDS; + float v = (1.0 + sin((t1 + il) * PI2)) / 2.0; + float v2 = (1.0 + sin((t1 + (i + 10.0) / NUM_LEDS) * PI2)) / 2.0; + bool spark = (v2 < 0.95); + float vout = 0.0; + if (v > 0.95 && random16(65535) > 59000) vout = v; + + float h; + if (spark) { + h = random16(65535) / 65535.0; + } else { + float hv = i / 20.0; + float hf = hv - (int)(hv / 0.2) * 0.2; + h = hf; + } + + float sv = 1.0; + if (spark) sv = 0.0; + float val = sv + vout; + if (val > 1.0) val = 1.0; + setHSV(i, h * 255, sv * 255, val * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_GlitchBands.sc b/livescripts/Effects/Arti-FX/E_GlitchBands.sc new file mode 100644 index 000000000..8e3e93f18 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_GlitchBands.sc @@ -0,0 +1,57 @@ +// Glitch Bands - glitchy color bands +// Original: glitch_bands.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 6553) / 6553.0 * PI2; + float t2 = (millis() % 6553) / 6553.0; + float t3 = (millis() % 32767) / 32767.0; + float t4 = (millis() % 13107) / 13107.0 * PI2; + float t5 = (millis() % 3276) / 3276.0; + float t6 = (millis() % 1310) / 1310.0; + + // triangle(t2) + float tf2 = t2 * 2.0 - 1.0; + if (tf2 < 0.0) tf2 = 0.0 - tf2; + float trit2 = 1.0 - tf2; + + // triangle(t3) + float tf3 = t3 * 2.0 - 1.0; + if (tf3 < 0.0) tf3 = 0.0 - tf3; + float trit3 = 1.0 - tf3; + + float m = 0.3 + trit2 * 0.2; + + for (int i = 0; i < NUM_LEDS; i++) { + float il = i * 1.0 / NUM_LEDS; + float h = sin(t1); + float h1 = (i - NUM_LEDS / 2.0) / NUM_LEDS; + float h2 = trit3 * 10.0 + 4.0 * sin(t4); + float hmod = h1 * h2; + float mf = hmod - (int)(hmod / m) * m; + h = h + mf; + + float s1v = (t5 + il * 5.0); + float s1f = s1v - (int)s1v; + float s1t = s1f * 2.0 - 1.0; + if (s1t < 0.0) s1t = 0.0 - s1t; + float s1 = 1.0 - s1t; + s1 = s1 * s1; + + float s2v = (t6 - (i - NUM_LEDS) * 1.0 / NUM_LEDS); + float s2f = s2v - (int)s2v; + if (s2f < 0.0) s2f = s2f + 1.0; + float s2t = s2f * 2.0 - 1.0; + if (s2t < 0.0) s2t = 0.0 - s2t; + float s2 = 1.0 - s2t; + + float st = s1 * s2; + float stt = st * 2.0 - 1.0; + if (stt < 0.0) stt = 0.0 - stt; + + float v = 0.5; + if (s1 > s2) v = 1.5; + setHSV(i, h * 255, sst * 255, v * 170); + } +} diff --git a/livescripts/Effects/Arti-FX/E_GreenRipple.sc b/livescripts/Effects/Arti-FX/E_GreenRipple.sc new file mode 100644 index 000000000..fbd4ce1a2 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_GreenRipple.sc @@ -0,0 +1,28 @@ +// Green Ripple Reflections - layered green sine waves +// Original: green_ripple_reflections.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 +#define PI6 18.84954 +#define PI10 31.4159 + +void loop() { + float t1 = (millis() % 1966) / 1966.0 * PI2; + float t2 = (millis() % 3276) / 3276.0 * PI2; + float t3 = (millis() % 2621) / 2621.0 * PI2; + + for (int i = 0; i < NUM_LEDS; i++) { + float il = i * 1.0 / NUM_LEDS; + float a = sin(il * PI10 + t1); + a = a * a; + float b = sin(il * PI6 - t2); + float c0 = il * 3.0 + 1.0 + sin(t3); + float cf = c0 / 2.0; + float cfrac = cf - (int)cf; + float ct = cfrac * 2.0 - 1.0; + if (ct < 0.0) ct = 0.0 - ct; + float c = 1.0 - ct; + float v = (a + b + c) / 3.0; + v = v * v; + setHSV(i, 76, a * 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_HalloweenTwinkles.sc b/livescripts/Effects/Arti-FX/E_HalloweenTwinkles.sc new file mode 100644 index 000000000..6021f866b --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_HalloweenTwinkles.sc @@ -0,0 +1,33 @@ +// Halloween Color Twinkles - orange and purple twinkles +// Original: halloween_color_twinkles.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 98302) / 98302.0 * PI2; + float t2 = (millis() % 22937) / 22937.0 * PI2; + + for (int i = 0; i < NUM_LEDS; i++) { + float ha = sin(i / 2.0 + t1); + float h = sin(i / 3.0 + PI2 * ha); + + float va = sin(i / 2.0 + t2); + float wv = (1.0 + sin((i / 3.0 / PI2 + va) * PI2)) / 2.0; + float v = wv * wv * wv * wv; + if (v <= 0.1) v = 0.0; + + // triangle(h) for hue + float ht = h; + if (ht < 0.0) ht = 0.0 - ht; + float tri = ht * 2.0 - 1.0; + if (tri < 0.0) tri = 0.0 - tri; + tri = 1.0 - tri; + + if (h > 0.0) { + h = tri * 0.1 + 0.7; + } else { + h = tri * 0.05 + 0.02; + } + setHSV(i, h * 255, 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_Kitt.sc b/livescripts/Effects/Arti-FX/E_Kitt.sc new file mode 100644 index 000000000..3b4c9711d --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Kitt.sc @@ -0,0 +1,20 @@ +// Kitt - bouncing bar of light +// Original: Kitt.wled from MoonModules/MM-Effects + +int pixelCounter = 0; +bool goingUp = true; + +void loop() { + if (pixelCounter > NUM_LEDS - 5) goingUp = false; + if (pixelCounter == 0) goingUp = true; + + setRGBPal(pixelCounter, pixelCounter, 255); + + if (goingUp) { + if (pixelCounter >= 5) setRGB(pixelCounter - 5, CRGB(0, 0, 0)); + pixelCounter = pixelCounter + 1; + } else { + if (pixelCounter + 5 < NUM_LEDS) setRGB(pixelCounter + 5, CRGB(0, 0, 0)); + pixelCounter = pixelCounter - 1; + } +} diff --git a/livescripts/Effects/Arti-FX/E_MarchingRainbow.sc b/livescripts/Effects/Arti-FX/E_MarchingRainbow.sc new file mode 100644 index 000000000..56d96ced2 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_MarchingRainbow.sc @@ -0,0 +1,22 @@ +// Marching Rainbow - layered sine waves +// Original: marching_rainbow.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 6553) / 6553.0; + float t2 = (millis() % 3276) / 3276.0; + + for (int i = 0; i < NUM_LEDS; i++) { + float il = i * 1.0 / NUM_LEDS; + float w1 = (1.0 + sin((t1 + il) * PI2)) / 2.0; + float w2 = (1.0 + sin((t2 - il * 10.0 + 0.2) * PI2)) / 2.0; + float v = w1 - w2; + if (v < 0.0) v = 0.0; + + float ha = (1.0 + sin((t1 + il) * PI2)) / 2.0; + float hb = (1.0 + sin(ha * PI2)) / 2.0; + float h = (1.0 + sin((hb - il) * PI2)) / 2.0; + setHSV(i, h * 255, 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_Matrix2DPulse.sc b/livescripts/Effects/Arti-FX/E_Matrix2DPulse.sc new file mode 100644 index 000000000..364a4ee40 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Matrix2DPulse.sc @@ -0,0 +1,21 @@ +// Matrix 2D Pulse - pulsing 2D matrix pattern +// Original: matrix_2D_pulse.wled (PixelBlaze, converted by Ewoud Wijma) +// Requires a 2D layout (panel) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 3276) / 3276.0 * PI2; + float t2 = (millis() % 5898) / 5898.0 * PI2; + float tw = (millis() % 13107) / 13107.0; + float z = 1.0 + (1.0 + sin(tw * PI2)) / 2.0 * 5.0; + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + float h = (1.0 + sin(x * 1.0 / width * z + t1) + cos(y * 1.0 / height * z + t2)) * 0.5; + float v = h; + v = v * v * v; + setRGBXY(x, y, hsv(h * 255, 255, v / 2.0 * 255)); + } + } +} diff --git a/livescripts/Effects/Arti-FX/E_Millipede.sc b/livescripts/Effects/Arti-FX/E_Millipede.sc new file mode 100644 index 000000000..80a5ffdce --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Millipede.sc @@ -0,0 +1,22 @@ +// Millipede - crawling wave pattern +// Original: millipede.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 3276) / 3276.0; + float t2 = (millis() % 6553) / 6553.0; + float tl = (millis() % 6553) / 6553.0; + + for (int i = 0; i < NUM_LEDS; i++) { + float il = i * 1.0 / NUM_LEDS; + float ha = (i + tl * NUM_LEDS); + float hb = ((int)ha % NUM_LEDS) * 5.0 / NUM_LEDS; + float frac = hb - (int)hb; + float hc = il + (1.0 + sin(t1 * PI2)) / 2.0; + float h = frac + hc; + float v = (1.0 + sin((h + t2) * PI2)) / 2.0; + v = v * v; + setHSV(i, h * 255, 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_Mover.sc b/livescripts/Effects/Arti-FX/E_Mover.sc new file mode 100644 index 000000000..d5c1f86b2 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Mover.sc @@ -0,0 +1,16 @@ +// Mover - moving colored segments +// Original: Mover.wled (idea by @Atuline) + +void loop() { + fadeToBlackBy(255); // instant clear + + int locn = millis() / 100; + for (int i = 0; i < NUM_LEDS; i = i + 30) { + int p1 = (locn + i) % NUM_LEDS; + int p2 = (locn + i + 10) % NUM_LEDS; + int p3 = (locn + i + 20) % NUM_LEDS; + setHSV(p1, 50, 255, 255); + setHSV(p2, 125, 255, 255); + setHSV(p3, 200, 255, 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_Opposites.sc b/livescripts/Effects/Arti-FX/E_Opposites.sc new file mode 100644 index 000000000..b960140d8 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Opposites.sc @@ -0,0 +1,32 @@ +// Opposites - opposing wave patterns +// Original: opposites.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 6553) / 6553.0; + float t2 = (millis() % 13107) / 13107.0; + + for (int i = 0; i < NUM_LEDS; i++) { + float il = i * 1.0 / NUM_LEDS; + float w1 = (1.0 + sin((t1 + il) * PI2)) / 2.0; + float w2 = (1.0 + sin((t2 - il) * PI2)) / 2.0; + float wsum = (il + w1 + w2); + float wsf = wsum - (int)wsum; + float w3 = (1.0 + sin(wsf * PI2)) / 2.0; + float hf = w3; + float hm = hf - (int)(hf / 0.3) * 0.3; + + float h; + if (hm > 0.15) { + h = hm + t1; + } else { + h = hm + 0.5 + t1; + } + + float v = (w1 + 0.1) * (w2 + 0.1) * (w3 + 0.1); + v = v * 2.0; + if (v > 1.0) v = 1.0; + setHSV(i, h * 255, 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_PerlinMove.sc b/livescripts/Effects/Arti-FX/E_PerlinMove.sc new file mode 100644 index 000000000..ea62aa4ec --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_PerlinMove.sc @@ -0,0 +1,30 @@ +// Perlin Move - noise-driven moving pixels +// Original: PerlinMove.wled from MoonModules/MM-Effects + +uint8_t speed = 128; +uint8_t intensity = 128; +uint8_t fade = 200; + +void setup() { + addControl(&speed, "speed", "slider", 1, 255); + addControl(&intensity, "intensity", "slider", 1, 255); + addControl(&fade, "fade", "slider", 1, 255); +} + +void loop() { + fadeToBlackBy(fade); + + for (int i = 0; i < intensity / 16 + 1; i++) { + uint16_t y = millis() * 128 / (260 - speed); + uint16_t x = y + i * 200; + uint8_t locn = inoise8(x, y, 0); + uint16_t x2 = millis() * 2 + i * 200; + uint8_t clr = inoise8(x2, 0, 0); + + int pixloc = locn * NUM_LEDS / 256; + if (pixloc >= NUM_LEDS) pixloc = NUM_LEDS - 1; + if (pixloc < 0) pixloc = 0; + + setRGBPal(pixloc, clr, 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_PhaseShift.sc b/livescripts/Effects/Arti-FX/E_PhaseShift.sc new file mode 100644 index 000000000..f7b6a8b57 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_PhaseShift.sc @@ -0,0 +1,15 @@ +// PhaseShift - shifting palette pattern +// Original: PhaseShift.wled from MoonModules/MM-Effects + +int pixelCounter = 3; +int countAdd = 1; + +void loop() { + pixelCounter = pixelCounter + countAdd; + if (pixelCounter > 15) countAdd = 0 - 1; + if (pixelCounter < 3) countAdd = 1; + + for (int i = 0; i < NUM_LEDS; i++) { + setRGBPal(i, i * pixelCounter, 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_RainbowFonts.sc b/livescripts/Effects/Arti-FX/E_RainbowFonts.sc new file mode 100644 index 000000000..b68822708 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_RainbowFonts.sc @@ -0,0 +1,19 @@ +// Rainbow Fonts - center-outward rainbow waves +// Original: rainbow_fonts.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 6553) / 6553.0; + int hl = NUM_LEDS / 2; + if (hl < 1) hl = 1; + + for (int i = 0; i < NUM_LEDS; i++) { + float diff = i - hl; + if (diff < 0) diff = 0 - diff; + float c = 1.0 - diff / hl; + c = (1.0 + sin(c * PI2)) / 2.0; + c = (1.0 + sin((c + t1) * PI2)) / 2.0; + setHSV(i, c * 255, 255, 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_RainbowFonts2.sc b/livescripts/Effects/Arti-FX/E_RainbowFonts2.sc new file mode 100644 index 000000000..a53e0044b --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_RainbowFonts2.sc @@ -0,0 +1,20 @@ +// Rainbow Fonts 2 - center-outward with oscillating offset +// Original: rainbow_fonts_2.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 6553) / 6553.0; + int scale = NUM_LEDS / 2; + float timeOff = (millis() % 13107) / 13107.0; + float offset = sin(timeOff * PI2) * NUM_LEDS / 10.0; + + for (int i = 0; i < NUM_LEDS; i++) { + float diff = (i + offset) - scale; + if (diff < 0) diff = 0.0 - diff; + float c = 1.0 - diff / scale; + c = (1.0 + sin(c * PI2)) / 2.0; + c = (1.0 + sin((c + t1 + offsetL) * PI2)) / 2.0; + setHSV(i, c * 255, 255, 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_RainbowMelt.sc b/livescripts/Effects/Arti-FX/E_RainbowMelt.sc new file mode 100644 index 000000000..811e969fc --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_RainbowMelt.sc @@ -0,0 +1,22 @@ +// Rainbow Melt - melting rainbow from center +// Original: rainbow_melt.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 6553) / 6553.0; + float t2 = (millis() % 8519) / 8519.0; + int hl = NUM_LEDS / 2; + if (hl < 1) hl = 1; + + for (int i = 0; i < NUM_LEDS; i++) { + float diff = i - hl; + if (diff < 0) diff = 0 - diff; + float c1 = 1.0 - diff * 1.0 / hl; + float c2 = (1.0 + sin(c1 * PI2)) / 2.0; + float c3 = (1.0 + sin((c2 + t1) * PI2)) / 2.0; + float v = (1.0 + sin((c3 + t1) * PI2)) / 2.0; + v = v * v; + setHSV(i, (c1 + t2) * 255, 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_RainbowPinwheel.sc b/livescripts/Effects/Arti-FX/E_RainbowPinwheel.sc new file mode 100644 index 000000000..9224c14a5 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_RainbowPinwheel.sc @@ -0,0 +1,12 @@ +// Rainbow Pinwheel - scrolling rainbow +// Original: rainbow_pinwheel.wled (PixelBlaze) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 3276) / 3276.0; + for (int i = 0; i < NUM_LEDS; i++) { + float h = (1.0 + sin((t1 + i * 1.0 / NUM_LEDS) * PI2)) / 2.0; + setHSV(i, h * 255, 255, 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_Ripple.sc b/livescripts/Effects/Arti-FX/E_Ripple.sc new file mode 100644 index 000000000..9210345a2 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Ripple.sc @@ -0,0 +1,33 @@ +// Ripple - expanding ripple from random center +// Original: ripple.wled by Andrew Tuline + +uint8_t fade = 64; +int step = -1; +int center = 0; +uint8_t colour = 0; +int maxsteps = 16; + +void setup() { + addControl(&fade, "fade", "slider", 1, 255); +} + +void loop() { + fadeToBlackBy(fade); + + if (step < 0) { + center = random16(NUM_LEDS); + colour = random16(255); + step = 0; + } + + if (step > 0) { + int bri = 255 / step; + int ledL = (center + step + NUM_LEDS) % NUM_LEDS; + int ledR = (center - step + NUM_LEDS) % NUM_LEDS; + setRGBPal(ledL, colour, bri); + setRGBPal(ledR, colour, bri); + } + step = step + 1; + + if (step >= maxsteps) step = -1; +} diff --git a/livescripts/Effects/Arti-FX/E_Shift.sc b/livescripts/Effects/Arti-FX/E_Shift.sc new file mode 100644 index 000000000..77ab4405f --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Shift.sc @@ -0,0 +1,10 @@ +// Shift - shift all pixels and add random at start +// Original: Shift.wled (idea by @Haribro) + +void loop() { + // shift all pixels up by one + for (int i = NUM_LEDS - 1; i > 0; i--) { + setRGB(i, getRGB(i - 1)); + } + setRGBPal(0, random16(255), 255); +} diff --git a/livescripts/Effects/Arti-FX/E_Sinelon.sc b/livescripts/Effects/Arti-FX/E_Sinelon.sc new file mode 100644 index 000000000..dad085a79 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Sinelon.sc @@ -0,0 +1,21 @@ +// Sinelon - sine wave moving up and down +// Original: Sinelon.wled by Andrew Tuline + +uint8_t speed = 128; +uint8_t fade = 64; + +void setup() { + addControl(&speed, "speed", "slider", 1, 255); + addControl(&fade, "fade", "slider", 1, 255); +} + +void loop() { + fadeToBlackBy(fade); + + float locn = millis() / ((256.0 - speed) * 4.0); + float newVal = (sin(locn) + 1.0) / 2.0; + int pos = (int)(newVal * NUM_LEDS); + if (pos >= NUM_LEDS) pos = NUM_LEDS - 1; + if (pos < 0) pos = 0; + setRGBPal(pos, 0, 255); +} diff --git a/livescripts/Effects/Arti-FX/E_SlowColorShift.sc b/livescripts/Effects/Arti-FX/E_SlowColorShift.sc new file mode 100644 index 000000000..f380ebc1e --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_SlowColorShift.sc @@ -0,0 +1,19 @@ +// Slow Color Shift - gently shifting colors +// Original: slow_color_shift.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 9830) / 9830.0 * PI2; + float t2 = (millis() % 6553) / 6553.0; + int h4 = NUM_LEDS * 4; + + for (int i = 0; i < NUM_LEDS; i++) { + float il = i * 1.0 / NUM_LEDS; + float a1 = i / 2.0 + 5.0 * sin(t1); + float a = (1.0 + sin(a1)) / 2.0; + float b = (t2 + 1.0 + sin(a1) / 5.0) + i * 1.0 / h4; + float v = a * a * a * a; + setHSV(i, b * 255, 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_Smiley.sc b/livescripts/Effects/Arti-FX/E_Smiley.sc new file mode 100644 index 000000000..2025daba2 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Smiley.sc @@ -0,0 +1,33 @@ +// Smiley - draws a smiley face on a 2D matrix +// Original: Smiley.wled from MoonModules/MM-Effects +// Requires a 2D layout (panel) + +void loop() { + fadeToBlackBy(10); + CRGB eyeColor = CRGB(255, 0, 0); + CRGB faceColor = CRGB(255, 255, 0); + + // face circle + int cx = width / 2; + int cy = height / 2; + int minDim = width; + if (height < minDim) minDim = height; + int radius = minDim * 2 / 5; + drawCircle(cx, cy, radius, faceColor); + + // left eye + int leX = width * 35 / 100; + int leY = height * 32 / 100; + int reX = width * 65 / 100; + drawLine(leX, leY, leX + width / 10, leY, eyeColor); + + // right eye + drawLine(reX - width / 10, leY, reX, leY, eyeColor); + + // nose + int nX = width / 2; + drawLine(nX, height * 45 / 100, nX, height * 55 / 100, eyeColor); + + // mouth + drawLine(width * 35 / 100, height * 68 / 100, width * 65 / 100, height * 68 / 100, eyeColor); +} diff --git a/livescripts/Effects/Arti-FX/E_Snake.sc b/livescripts/Effects/Arti-FX/E_Snake.sc new file mode 100644 index 000000000..98d3b3a4e --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Snake.sc @@ -0,0 +1,22 @@ +// Snake - moving segment of light +// Original: snake.wled (PixelBlaze) + +uint8_t snakeLen = 10; + +void setup() { + addControl(&snakeLen, "length", "slider", 3, 50); +} + +void loop() { + float t1 = (millis() % 6553) / 6553.0; + float head = t1 * NUM_LEDS; + + for (int i = 0; i < NUM_LEDS; i++) { + float h = i * 1.0 / NUM_LEDS; + float offset = head - i + NUM_LEDS; + int off = (int)offset % NUM_LEDS; + float v = 1.0 - off * 1.0 / snakeLen; + if (v < 0.0) v = 0.0; + setHSV(i, h * 255, 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_SpinCycle.sc b/livescripts/Effects/Arti-FX/E_SpinCycle.sc new file mode 100644 index 000000000..0b1695fdf --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_SpinCycle.sc @@ -0,0 +1,25 @@ +// Spin Cycle - spinning color pattern +// Original: spin_cycle.wled (PixelBlaze, converted by Andrew Tuline) + +#define PI2 6.28318 + +void loop() { + float t1 = (millis() % 6553) / 6553.0; + float w1 = (1.0 + sin(t1 * PI2)) / 2.0; + + for (int i = 0; i < NUM_LEDS; i++) { + float il = i * 1.0 / NUM_LEDS; + float h = il * (5.0 + w1 * 5.0) + w1 * 2.0; + float hf = h - (int)h; + float hmod = hf - (int)(hf / 0.5) * 0.5; + h = hmod + t1; + + float tv = (il * 5.0 + t1 * 10.0); + float tvf = tv - (int)tv; + float v = tvf * 2.0 - 1.0; + if (v < 0.0) v = 0.0 - v; + v = 1.0 - v; + v = v * v * v; + setHSV(i, h * 255, 255, v * 255); + } +} diff --git a/livescripts/Effects/Arti-FX/E_Subpixel.sc b/livescripts/Effects/Arti-FX/E_Subpixel.sc new file mode 100644 index 000000000..3739e9635 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_Subpixel.sc @@ -0,0 +1,25 @@ +// Subpixel - smooth sub-pixel movement +// Original: Subpixel.wled (idea by @Atuline) + +uint8_t intensity = 128; + +void setup() { + addControl(&intensity, "intensity", "slider", 1, 255); +} + +void loop() { + float t = (sin(millis() / 1000.0) + 1.0) / 2.0; + t = t * NUM_LEDS; + int reverseSlider = 256 - intensity; + if (reverseSlider < 1) reverseSlider = 1; + + for (int i = 0; i < NUM_LEDS; i++) { + float diff = t - i; + if (diff < 0.0) diff = 0.0 - diff; + if (diff > 256 / reverseSlider) diff = 256 / reverseSlider; + int bri = 256 - (int)(diff * reverseSlider); + if (bri > 255) bri = 255; + if (bri < 0) bri = 0; + setHSV(i, 0, 255, bri); + } +} diff --git a/livescripts/Effects/Arti-FX/E_TwinkleUp.sc b/livescripts/Effects/Arti-FX/E_TwinkleUp.sc new file mode 100644 index 000000000..a18d5185f --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_TwinkleUp.sc @@ -0,0 +1,14 @@ +// Twinkle Up - fade LEDs in and out with pseudo-random timing +// Original: twinkleup.wled by @Atuline + +void loop() { + for (int i = 0; i < NUM_LEDS; i++) { + // pseudo-random per-pixel values using sin8 as hash + uint8_t startVal = sin8(i * 137 + 73); + uint8_t freq = 255 - sin8(i * 53 + 17) / 16 - 16; + if (freq < 1) freq = 1; + float pixbri = (sin(startVal + millis() * 1.0 / freq) + 1.0) * 128.0; + uint8_t palIdx = sin8(i * 89 + 31); + setRGBPal(i, palIdx + millis() / 100, (int)pixbri); + } +} diff --git a/livescripts/Effects/Arti-FX/E_WaveSins.sc b/livescripts/Effects/Arti-FX/E_WaveSins.sc new file mode 100644 index 000000000..053a82fb5 --- /dev/null +++ b/livescripts/Effects/Arti-FX/E_WaveSins.sc @@ -0,0 +1,25 @@ +// Wave Sins - phase-shifted beat sine waves +// Original: WaveSins.wled by Andrew Tuline + +uint8_t speed = 128; +uint8_t intensity = 128; +uint8_t custom1 = 0; +uint8_t custom2 = 128; +uint8_t custom3 = 16; + +void setup() { + addControl(&speed, "speed", "slider", 1, 255); + addControl(&intensity, "intensity", "slider", 1, 255); + addControl(&custom1, "offset", "slider", 0, 255); + addControl(&custom2, "range", "slider", 1, 255); + addControl(&custom3, "phase", "slider", 1, 255); +} + +void loop() { + for (int i = 0; i < NUM_LEDS; i++) { + float bri = sin(millis() / 4.0 + i * intensity) * 128.0 + 128.0; + uint8_t highest = (custom1 + custom2 > 255) ? 255 : custom1 + custom2; + uint8_t palIdx = beatsin8(speed, custom1, highest, 0, i * custom3); + setRGBPal(i, palIdx, (int)bri); + } +} diff --git a/misc/livescripts/E_GEQ2D.sc b/livescripts/Effects/E_GEQ2D.sc similarity index 87% rename from misc/livescripts/E_GEQ2D.sc rename to livescripts/Effects/E_GEQ2D.sc index dcf89bea0..c49602d8f 100644 --- a/misc/livescripts/E_GEQ2D.sc +++ b/livescripts/Effects/E_GEQ2D.sc @@ -16,7 +16,7 @@ void loop() { // scale level to column height uint8_t barHeight = level * height / 255; for (int y = 0; y < barHeight; y++) { - setRGBPal(y * width + x, x * 255 / width + y * 4, 255); + setRGBXY(x, y, ColorFromPalette(x * 255 / width + y * 4, 255)); } } } diff --git a/misc/livescripts/E_GyroBall.sc b/livescripts/Effects/E_GyroBall.sc similarity index 100% rename from misc/livescripts/E_GyroBall.sc rename to livescripts/Effects/E_GyroBall.sc diff --git a/misc/livescripts/E_PanTilt.sc b/livescripts/Effects/E_PanTilt.sc similarity index 100% rename from misc/livescripts/E_PanTilt.sc rename to livescripts/Effects/E_PanTilt.sc diff --git a/misc/livescripts/E_lines.sc b/livescripts/Effects/E_lines.sc similarity index 100% rename from misc/livescripts/E_lines.sc rename to livescripts/Effects/E_lines.sc diff --git a/misc/livescripts/E_noise.sc b/livescripts/Effects/E_noise.sc similarity index 100% rename from misc/livescripts/E_noise.sc rename to livescripts/Effects/E_noise.sc diff --git a/misc/livescripts/E_octo.sc b/livescripts/Effects/E_octo.sc similarity index 100% rename from misc/livescripts/E_octo.sc rename to livescripts/Effects/E_octo.sc diff --git a/misc/livescripts/E_random.sc b/livescripts/Effects/E_random.sc similarity index 100% rename from misc/livescripts/E_random.sc rename to livescripts/Effects/E_random.sc diff --git a/livescripts/Layouts/L_LC16.sc b/livescripts/Layouts/L_LC16.sc new file mode 100644 index 000000000..17f8f2649 --- /dev/null +++ b/livescripts/Layouts/L_LC16.sc @@ -0,0 +1,49 @@ +// LightCrafter16 board layout +// Pins 0-7 run top-to-bottom, pins 8-15 run bottom-to-top (snake wiring). +// pinsAreColumns: true = strips are columns (limpkin), false = strips are rows (ewowi). + +bool pinsAreColumns = false; +uint16_t ledsPerPin = 10; + +void setup() { + addControl(&pinsAreColumns, "pinsAreColumns", "checkbox"); + addControl(&ledsPerPin, "ledsPerPin", "slider", 1, 255); +} + +void addStrip(int xposition, int start_y, int stop_y) { + if (start_y > stop_y) { + for (int y = start_y; y >= stop_y; y--) { + if (pinsAreColumns) + addLight(xposition, y, 0); + else + addLight(y, xposition, 0); + } + } else { + for (int y = start_y; y <= stop_y; y++) { + if (pinsAreColumns) + addLight(xposition, y, 0); + else + addLight(y, xposition, 0); + } + } + nextPin(); +} + +void onLayout() { + addStrip(0, ledsPerPin-1, 0); + addStrip(1, ledsPerPin-1, 0); + addStrip(2, ledsPerPin-1, 0); + addStrip(3, ledsPerPin-1, 0); + addStrip(4, ledsPerPin-1, 0); + addStrip(5, ledsPerPin-1, 0); + addStrip(6, ledsPerPin-1, 0); + addStrip(7, ledsPerPin-1, 0); + addStrip(7, ledsPerPin, 2*ledsPerPin-1); + addStrip(6, ledsPerPin, 2*ledsPerPin-1); + addStrip(5, ledsPerPin, 2*ledsPerPin-1); + addStrip(4, ledsPerPin, 2*ledsPerPin-1); + addStrip(3, ledsPerPin, 2*ledsPerPin-1); + addStrip(2, ledsPerPin, 2*ledsPerPin-1); + addStrip(1, ledsPerPin, 2*ledsPerPin-1); + addStrip(0, ledsPerPin, 2*ledsPerPin-1); +} diff --git a/livescripts/Layouts/L_R16.sc b/livescripts/Layouts/L_R16.sc new file mode 100644 index 000000000..e425f2571 --- /dev/null +++ b/livescripts/Layouts/L_R16.sc @@ -0,0 +1,40 @@ +// Rings16Layout: 16 rings of 24 LEDs arranged in a hexagonal pattern. +// Each ring is assigned to its own pin (16 pins total). +// Ring positions match the Rings16Layout C++ class. + +uint8_t scale = 1; + +void setup() { + addControl(&scale, "scale", "slider", 1, 10); +} + +void addRing(int cx, int cy) { + int nrOfLEDs = 24; + float radius = nrOfLEDs / 6.2832; // nrOfLEDs / (2 * PI) + for (int i = 0; i < nrOfLEDs; i++) { + float angleRad = 3.14159 + 6.2832 * i / nrOfLEDs; // PI + (2*PI*i)/nrOfLEDs + int x = scale * cx - scale * sin(angleRad) * radius; + int y = scale * cy + scale * cos(angleRad) * radius; + addLight(x, y, 0); + } + nextPin(); +} + +void onLayout() { + addRing(59, 23); + addRing(70, 28); + addRing(59, 10); + addRing(59, 34); + addRing(47, 17); + addRing(59, 46); + addRing(41, 5); + addRing(47, 39); + addRing(35, 17); + addRing(41, 53); + addRing(22, 10); + addRing(35, 39); + addRing(22, 23); + addRing(22, 46); + addRing(10, 28); + addRing(22, 34); +} diff --git a/livescripts/Layouts/L_SE16.sc b/livescripts/Layouts/L_SE16.sc new file mode 100644 index 000000000..0f98a1c4c --- /dev/null +++ b/livescripts/Layouts/L_SE16.sc @@ -0,0 +1,58 @@ +bool mirroredPins = false; +bool pinsAreColumns = false; +uint16_t ledsPerPin = 10; + +void setup() { + addControl(&mirroredPins, "mirroredPins", "checkbox"); + addControl(&pinsAreColumns, "pinsAreColumns", "checkbox"); + addControl(&ledsPerPin, "ledsPerPin", "slider", 1, 255); +} + +void addStrip(int xposition, int start_y, int stop_y) { + if (start_y > stop_y) { + for (int y = start_y; y >= stop_y; y--) { + if (pinsAreColumns) + addLight(xposition, y, 0); + else + addLight(y, xposition, 0); + } + } else { + for (int y = start_y; y <= stop_y; y++) { + if (pinsAreColumns) + addLight(xposition, y, 0); + else + addLight(y, xposition, 0); + } + } + nextPin(); +} + +void onLayout() { + if (mirroredPins) { + addStrip(7, ledsPerPin, 2*ledsPerPin-1); addStrip(7, ledsPerPin-1, 0); + addStrip(6, ledsPerPin, 2*ledsPerPin-1); addStrip(6, ledsPerPin-1, 0); + addStrip(5, ledsPerPin, 2*ledsPerPin-1); addStrip(5, ledsPerPin-1, 0); + addStrip(4, ledsPerPin, 2*ledsPerPin-1); addStrip(4, ledsPerPin-1, 0); + addStrip(3, ledsPerPin, 2*ledsPerPin-1); addStrip(3, ledsPerPin-1, 0); + addStrip(2, ledsPerPin, 2*ledsPerPin-1); addStrip(2, ledsPerPin-1, 0); + addStrip(1, ledsPerPin, 2*ledsPerPin-1); addStrip(1, ledsPerPin-1, 0); + addStrip(0, ledsPerPin, 2*ledsPerPin-1); addStrip(0, ledsPerPin-1, 0); + } else { + addStrip(14, 0, ledsPerPin-1); + addStrip(15, 0, ledsPerPin-1); + addStrip(12, 0, ledsPerPin-1); + addStrip(13, 0, ledsPerPin-1); + addStrip(10, 0, ledsPerPin-1); + addStrip(11, 0, ledsPerPin-1); + addStrip(8, 0, ledsPerPin-1); + addStrip(9, 0, ledsPerPin-1); + addStrip(6, 0, ledsPerPin-1); + addStrip(7, 0, ledsPerPin-1); + addStrip(4, 0, ledsPerPin-1); + addStrip(5, 0, ledsPerPin-1); + addStrip(2, 0, ledsPerPin-1); + addStrip(3, 0, ledsPerPin-1); + addStrip(0, 0, ledsPerPin-1); + addStrip(1, 0, ledsPerPin-1); + } +} diff --git a/misc/livescripts/L_panel.sc b/livescripts/Layouts/L_panel.sc similarity index 100% rename from misc/livescripts/L_panel.sc rename to livescripts/Layouts/L_panel.sc diff --git a/misc/livescripts/L_panels.sc b/livescripts/Layouts/L_panels.sc similarity index 100% rename from misc/livescripts/L_panels.sc rename to livescripts/Layouts/L_panels.sc diff --git a/misc/livescripts/L_rings241.sc b/livescripts/Layouts/L_rings241.sc similarity index 100% rename from misc/livescripts/L_rings241.sc rename to livescripts/Layouts/L_rings241.sc diff --git a/misc/livescripts/P_Fire.sc b/livescripts/Palettes/P_Fire.sc similarity index 100% rename from misc/livescripts/P_Fire.sc rename to livescripts/Palettes/P_Fire.sc diff --git a/misc/livescripts/P_Shift.sc b/livescripts/Palettes/P_Shift.sc similarity index 100% rename from misc/livescripts/P_Shift.sc rename to livescripts/Palettes/P_Shift.sc diff --git a/misc/livescripts/L_SE16.sc b/misc/livescripts/L_SE16.sc deleted file mode 100644 index d728e17b0..000000000 --- a/misc/livescripts/L_SE16.sc +++ /dev/null @@ -1,30 +0,0 @@ -uint8_t height = 16; - -void setup() { - addControl(&height, "height", "slider", 1, 255); -} - -void addStrip( uint8_t xposition, uint8_t start_y, uint8_t stop_y) { - if (start_y > stop_y){ - for (int y = start_y; y>=stop_y; y--) { - addLight(xposition, y, 0); - } - } - else { - for (int y = start_y; y<=stop_y; y++) { - addLight(xposition, y, 0); - } - } - nextPin(); //each strip it's own pin -} - -void onLayout() { - addStrip(7, height, 2*height-1); addStrip(7, height-1, 0); - addStrip(6, height, 2*height-1); addStrip(6, height-1, 0); - addStrip(5, height, 2*height-1); addStrip(5, height-1, 0); - addStrip(4, height, 2*height-1); addStrip(4, height-1, 0); - addStrip(3, height, 2*height-1); addStrip(3, height-1, 0); - addStrip(2, height, 2*height-1); addStrip(2, height-1, 0); - addStrip(1, height, 2*height-1); addStrip(1, height-1, 0); - addStrip(0, height, 2*height-1); addStrip(0, height-1, 0); -} \ No newline at end of file diff --git a/misc/upstream-prs/ESPLiveScript-Coord3D.md b/misc/upstream-prs/ESPLiveScript-Coord3D.md new file mode 100644 index 000000000..93e58583f --- /dev/null +++ b/misc/upstream-prs/ESPLiveScript-Coord3D.md @@ -0,0 +1,243 @@ +# ESPLiveScript: Fix `__userDefined__` types as function parameters + (optional) add `Coord3D` built-in + +## Summary + +Two complementary improvements, in order of priority: + +1. **Bug fix (Fix A)** — `parseCreateArguments` incorrectly applies register optimisation to struct-typed parameters, corrupting the `target` field that `getVarType()` uses for member lookup. This causes a parser crash ("member not found") for any user-defined struct used as a function parameter type. + +2. **New built-in (Fix B, optional)** — Add `Coord3D` (three `int` fields: `x`, `y`, `z`) as a built-in type, analogous to `CRGB`. This is needed if you want to register C++ external functions that take a `Coord3D` parameter (e.g., `addExternal("void setRGBCoord(Coord3D,CRGB)", ptr)`). + +Fix A alone is sufficient to allow scripts to write helper functions with user-defined struct parameters. Fix B additionally allows the C++ host to expose functions with `Coord3D` in their signature string. + +--- + +## Root cause of the crash + +### Symptom + +```text +member not found in Coord3D +Guru Meditation Error: Core 0 panic'ed (LoadProhibited) + #0 copyPrty(NodeToken*, NodeToken*) at NodeToken.h:1272 + #1 createNodeVariable(Token*, bool) at NodeToken.h:1544 + ... +``` + +### Analysis + +`NodeToken` uses the `target` field for two distinct purposes: + +| Context | `target` value | Meaning | +|---|---|---| +| Register-optimised local/param | small integer (0, 1, 2…) | register slot index | +| User-defined struct variable | `EOF_TEXTARRAY` (9999) | "no member selected; return whole-struct type" | +| User-defined struct member access | text index of member name | "look up this member in the struct" | + +In `NodeToken::getVarType()` (`NodeToken.h:739`): + +```cpp +if (type == TokenUserDefinedVariable) +{ + if (target != EOF_TEXTARRAY) + { + if (getTargetText()[0] == '@') + return &_userDefinedTypes[_vartype]; // whole struct + int i = findMember(_vartype, getTargetText()); // look up member + if (i < 0) { printf("member not found"); return NULL; } // CRASH + } + return &_userDefinedTypes[_vartype]; // whole struct (EOF case) +} +``` + +`parseStatement` (`ESPLiveScript.h:1726`) correctly guards register mode for primitive types only: + +```cpp +if (_for_depth_reg.get() <= _MAX_FOR_DEPTH_REG_2 + and (d == __float__ or d == __int__ or d == __s_int__ + or d == __uint8_t__ or d == __uint32_t__ or d == __uint16_t__)) +{ + _is_variable_as_register.set(true); +} +``` + +But `parseCreateArguments` (`ESPLiveScript.h:2075`) has no such type guard: + +```cpp +if (_for_depth_reg.get() <= _MAX_FOR_DEPTH_REG_2) +{ + _is_variable_as_register.set(true); // ← applies to ALL types including structs +} +if (_is_variable_as_register.get()) +{ + _nd = NodeToken(_nd, defLocalVariableNodeAsRegister); + _nd.target = _for_depth_reg.get(); // ← corrupts target for struct params! + _for_depth_reg.increase(); +} +``` + +When `Coord3D p` is declared as a function parameter and `_for_depth_reg ≤ _MAX_FOR_DEPTH_REG_2`, `p`'s context entry gets `target = 0` (or 1, 2…). Later, when `p.x` is parsed, `getVarType()` sees `target != EOF_TEXTARRAY`, calls `findMember(k, all_targets.getText(0))`, finds some unrelated text at index 0, and crashes. + +Local variable `Coord3D pos;` works because `parseStatement` only enables register mode for the primitive types listed above — `__userDefined__` is not in that list. + +--- + +## Fix A — `parseCreateArguments`: guard register mode by type (recommended) + +**File:** `ESPLiveScript.h` + +In `parseCreateArguments`, replace the unconditional register-mode check (around line 2075) with the same type guard used in `parseStatement`. The check must happen after `parseType()` has resolved the type. + +The first parameter: + +```diff +- if (_for_depth_reg.get() <= _MAX_FOR_DEPTH_REG_2) +- { +- _is_variable_as_register.set(true); +- } ++ if (_for_depth_reg.get() <= _MAX_FOR_DEPTH_REG_2) ++ { ++ varTypeEnum paramType = nodeTokenList.get().getVarType()->_varType; ++ if (paramType == __float__ or paramType == __int__ or paramType == __s_int__ or ++ paramType == __uint8_t__ or paramType == __uint32_t__ or paramType == __uint16_t__) ++ { ++ _is_variable_as_register.set(true); ++ } ++ } +``` + +The same fix must be applied in the `while (Match(TokenComma))` loop (around line 2121), which processes subsequent parameters. + +### Effect + +After this fix, scripts can write: + +```c +void helper(Coord3D p, CRGB c) { + setRGBCoord(p.x, p.y, p.z, c); +} +``` + +without crashing. This works for any user-defined struct, not just `Coord3D`. + +### Why this is safe + +- Struct-typed parameters cannot fit in a single Xtensa register; register optimisation for them is incorrect to begin with. +- `parseStatement` already excludes structs from register mode — this aligns `parseCreateArguments` with the same policy. +- Existing scripts are unaffected: primitive-type parameters continue to use register optimisation as before. + +--- + +## Fix B — Add `Coord3D` as a built-in type (optional, for external registration) + +Fix A does not change how `addExternal()` parses its type strings. If the C++ host needs to register functions with `Coord3D` in the signature (e.g., `"void setRGBCoord(Coord3D,CRGB)"`), `Coord3D` must be a built-in `TokenKeywordVarType`. + +### 1. `asm_struct_enum.h` — add `__Coord3D__` to the type enum + +```diff + enum varTypeEnum + { + __none__, + __uint8_t__, + __uint16_t__, + __uint32_t__, + __int__, + __s_int__, + __float__, + __void__, + __CRGB__, + __CRGBW__, + __char__, + __Args__, + __bool__, ++ __Coord3D__, + __userDefined__, + __unknown__ + }; +``` + +### 2. `tokenizer.h` — three locations + +#### 2a. Debug name array `varTypeEnumNames[]` + +```diff + "__bool__", ++ "__Coord3D__", + "__userDefined__", +``` + +#### 2b. `_varTypes[]` — add the type descriptor after `__bool__` + +`Coord3D` has three `int` members (4 bytes each, loaded/stored with `l32i`/`s32i`): + +```diff ++ { ++ ._varType = __Coord3D__, ++ .varName = "d", ++ ._varSize = 3, ++ .load = {l32i, l32i, l32i}, ++ .store = {s32i, s32i, s32i}, ++ .membersNames = {"x", "y", "z"}, ++ .starts = {0, 4, 8}, ++ .memberSize = {1, 1, 1}, ++ .types = {__int__, __int__, __int__}, ++ .sizes = {4, 4, 4}, ++ .size = 3, ++ .total_size = 12, ++ }, ++ + }; // end of _varTypes[] +``` + +#### 2c. Keyword table — add `"Coord3D"` to the type-variable section + +```diff +-#define nb_keywords 39 +-#define nb_typeVariables 13 ++#define nb_keywords 40 ++#define nb_typeVariables 14 + string keyword_array[nb_keywords] = + {"none", "uint8_t", "uint16_t", "uint32_t", "int", "s_int", "float", "void", "CRGB", +- "CRGBW", "char", "Args", "bool", "external", "for", ...}; ++ "CRGBW", "char", "Args", "bool", "Coord3D", "external", "for", ...}; +``` + +`"Coord3D"` must be placed at index `nb_typeVariables - 1` (the last slot before the non-type keywords), so the tokenizer assigns it `TokenKeywordVarType` and maps it to `__Coord3D__` via its position in the array. + +### Effect of Fix B alone (without Fix A) + +`Coord3D` becomes a `TokenKeywordVarType`, not a `TokenUserDefinedVariable`. This means it bypasses the `__userDefined__` path entirely — including the register-mode collision — so the crash does not occur. Additionally, the host can call: + +```cpp +addExternal("void setRGBCoord(Coord3D,CRGB)", (void*)_setRGBCoord); +``` + +### Notes + +- `int` is 4 bytes on all ESP32 targets (Xtensa LX7 and RISC-V); `total_size = 12` is correct. +- `__userDefined__` and `__unknown__` shift by one enum value. They are only referenced by name in the codebase, not by numeric literal, so no other changes are needed. +- The `_varTypes[]` array must stay indexed by `varTypeEnum` value; the new entry at position 13 maintains that invariant. +- No changes to `ESPLivescriptRuntime.h` or `execute.h` are expected — the existing struct member load/store machinery already handles both built-in struct types and user-defined types with `l32i`/`s32i`. + +--- + +## Recommendation + +Apply **Fix A** first — it is a small, general bug fix that benefits all user-defined struct types. Apply **Fix B** if external function registration with `Coord3D` parameter types is needed on the host side. + +With only Fix A, MoonLight scripts can write: + +```c +void helper(Coord3D p, CRGB c) { + setRGBCoord(p.x, p.y, p.z, c); +} +``` + +With Fix B additionally, the MoonLight C++ side can expose: + +```cpp +static void _setRGBCoord(Coord3D pos, CRGB color) { + gNode->layer->setRGB(pos, color); +} +// registered as: "void setRGBCoord(Coord3D,CRGB)" +``` diff --git a/src/MoonBase/LiveScriptNode.cpp b/src/MoonBase/LiveScriptNode.cpp index 5c93b9f89..fc8655f8f 100644 --- a/src/MoonBase/LiveScriptNode.cpp +++ b/src/MoonBase/LiveScriptNode.cpp @@ -19,26 +19,70 @@ #define USE_FASTLED // as ESPLiveScript.h calls hsv ! one of the reserved functions!! #include "ESPLiveScript.h" -Node* gNode = nullptr; +Node* gNode = nullptr; // fallback for synchronous (non-task) contexts such as onLayout + +// Per-task node map: maps each script's FreeRTOS TaskHandle to its LiveScriptNode. 🌙 +// Fixes the gNode race when multiple scripts run concurrently as separate tasks. +// Max 4 matches the WaitAnimationSync semaphore count. +static const int MAX_LIVE_SCRIPTS = 4; +struct TaskNodePair { TaskHandle_t task = nullptr; Node* node = nullptr; }; +static TaskNodePair gTaskNodeMap[MAX_LIVE_SCRIPTS]; + +// Returns the Node for the calling task; falls back to gNode for synchronous contexts. 🌙 +static Node* currentNode() { + TaskHandle_t h = xTaskGetCurrentTaskHandle(); + for (const auto& m : gTaskNodeMap) + if (m.task == h) return m.node; + return gNode; +} +static void registerNodeForTask(TaskHandle_t task, Node* node) { + for (auto& m : gTaskNodeMap) + if (m.task == nullptr) { m.task = task; m.node = node; return; } + EXT_LOGE(MB_TAG, "gTaskNodeMap full"); +} +static void unregisterNodeForTask(TaskHandle_t task) { + for (auto& m : gTaskNodeMap) + if (m.task == task) { m.task = nullptr; m.node = nullptr; return; } +} static void _addControl(uint8_t* var, char* name, char* type, uint8_t min = 0, uint8_t max = UINT8_MAX) { EXT_LOGV(MB_TAG, "%s %s %p (%d-%d)", name, type, (void*)var, min, max); - gNode->addControl(*var, name, type, min, max); + currentNode()->addControl(*var, name, type, min, max); } static void _nextPin() { layerP.nextPin(); } -static void _addLight(uint8_t x, uint8_t y, uint8_t z) { layerP.addLight({x, y, z}); } - -static void _modifySize() { gNode->modifySize(); } -static void _modifyPosition(Coord3D& position) { gNode->modifyPosition(position); } // need &position parameter -// static void _modifyXYZ() {gNode->modifyXYZ();}//need &position parameter - -void _fadeToBlackBy(uint8_t fadeValue) { gNode->layer->fadeToBlackBy(fadeValue); } -static void _setRGB(uint16_t indexV, CRGB color) { gNode->layer->setRGB(indexV, color); } -static void _setRGBPal(uint16_t indexV, uint8_t index, uint8_t brightness) { gNode->layer->setRGB(indexV, ColorFromPalette(layerP.palette, index, brightness)); } -static void _setPan(uint16_t indexV, uint8_t value) { gNode->layer->setPan(indexV, value); } -static void _setTilt(uint16_t indexV, uint8_t value) { gNode->layer->setTilt(indexV, value); } +static void _addLight(uint16_t x, uint16_t y, uint16_t z) { layerP.addLight({x, y, z}); } + +static void _modifySize() { currentNode()->modifySize(); } +static void _modifyPosition(Coord3D& position) { currentNode()->modifyPosition(position); } // need &position parameter +// static void _modifyXYZ() {currentNode()->modifyXYZ();}//need &position parameter + +void _fadeToBlackBy(uint8_t fadeValue) { currentNode()->layer->fadeToBlackBy(fadeValue); } +static CRGB _getRGB(uint16_t indexV) { return currentNode()->layer->getRGB(indexV); } +static void _setRGB(uint16_t indexV, CRGB color) { currentNode()->layer->setRGB(indexV, color); } +static void _setRGBXY(int x, int y, CRGB color) { currentNode()->layer->setRGB(Coord3D{x, y}, color); } // 🌙 coordinate-based setRGB, exposed via preamble-injected setRGB(Coord3D,CRGB) wrapper +static void _setRGBXYZ(int x, int y, int z, CRGB color) { currentNode()->layer->setRGB(Coord3D{x, y, z}, color); } // 🌙 coordinate-based setRGB, exposed via preamble-injected setRGB(Coord3D,CRGB) wrapper +static CRGB _colorFromPalette(uint8_t index, uint8_t bri) { return ColorFromPalette(layerP.palette, index, bri); } // 🌙 +static void _setRGBPal(uint16_t indexV, uint8_t index, uint8_t brightness) { currentNode()->layer->setRGB(indexV, ColorFromPalette(layerP.palette, index, brightness)); } +static void _setPan(uint16_t indexV, uint8_t value) { currentNode()->layer->setPan(indexV, value); } +static void _setTilt(uint16_t indexV, uint8_t value) { currentNode()->layer->setTilt(indexV, value); } static void _setPalEntry(uint8_t index, uint8_t r, uint8_t g, uint8_t b) { if (index < 16) layerP.palette.entries[index] = CRGB(r, g, b); } static void _setPalEntryHSV(uint8_t index, uint8_t h, uint8_t s, uint8_t v) { if (index < 16) layerP.palette.entries[index] = CHSV(h, s, v); } +static void _setHSV(uint16_t indexV, uint8_t h, uint8_t s, uint8_t v) { currentNode()->layer->setRGB(indexV, CHSV(h, s, v)); } +static void _setHSVXY(int x, int y, uint8_t h, uint8_t s, uint8_t v) { currentNode()->layer->setRGB(Coord3D{x, y}, CHSV(h, s, v)); } + +// 2D drawing +static void _drawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, CRGB color) { currentNode()->layer->drawLine(x0, y0, x1, y1, color); } +static void _drawCircle(int cx, int cy, uint8_t radius, CRGB color) { currentNode()->layer->drawCircle(cx, cy, radius, color, false); } + +// time of day +static uint8_t _lsHour = 0; +static uint8_t _lsMinute = 0; +static uint8_t _lsSecond = 0; +static void _updateTime() { + time_t now = time(nullptr); + struct tm t; + if (localtime_r(&now, &t)) { _lsHour = t.tm_hour; _lsMinute = t.tm_min; _lsSecond = t.tm_sec; } +} volatile SemaphoreHandle_t WaitAnimationSync = xSemaphoreCreateCounting(4, 0); // max 4 concurrent scripts volatile uint8_t scriptsToSync = 0; // count of scripts that still need to finish their frame @@ -123,7 +167,7 @@ void LiveScriptNode::setup() { // MoonLight functions addExternal("void addControl(void*,char*,char*,uint8_t,uint8_t)", (void*)_addControl); addExternal("void nextPin()", (void*)_nextPin); - addExternal("void addLight(uint8_t,uint8_t,uint8_t)", (void*)_addLight); + addExternal("void addLight(uint16_t,uint16_t,uint16_t)", (void*)_addLight); addExternal("void modifySize()", (void*)_modifySize); // addExternal( "void modifyPosition(Coord3D &position)", (void *)_modifyPosition); // addExternal( "void modifyXYZ(uint16_t,uint16_t,uint16_t)", (void *)_modifyXYZ); @@ -138,12 +182,18 @@ void LiveScriptNode::setup() { addExternal("void fadeToBlackBy(uint8_t)", (void*)_fadeToBlackBy); addExternal("CRGB* leds", (void*)(CRGB*)layerP.lights.channelsE); + addExternal("CRGB getRGB(uint16_t)", (void*)_getRGB); addExternal("void setRGB(uint16_t,CRGB)", (void*)_setRGB); + addExternal("void setRGBXY(int,int,CRGB)", (void*)_setRGBXY); // 🌙 called by preamble-injected setRGB(Coord3D,CRGB) + addExternal("void setRGBXYZ(int,int,int,CRGB)", (void*)_setRGBXYZ); // 🌙 called by preamble-injected setRGB(Coord3D,CRGB) + addExternal("CRGB ColorFromPalette(uint8_t,uint8_t)", (void*)_colorFromPalette); // 🌙 addExternal("void setRGBPal(uint16_t,uint8_t,uint8_t)", (void*)_setRGBPal); addExternal("void setPan(uint16_t,uint8_t)", (void*)_setPan); addExternal("void setTilt(uint16_t,uint8_t)", (void*)_setTilt); addExternal("void setPalEntry(uint8_t,uint8_t,uint8_t,uint8_t)", (void*)_setPalEntry); addExternal("void setPalEntryHSV(uint8_t,uint8_t,uint8_t,uint8_t)", (void*)_setPalEntryHSV); + addExternal("void setHSV(uint16_t,uint8_t,uint8_t,uint8_t)", (void*)_setHSV); + addExternal("void setHSVXY(int,int,uint8_t,uint8_t,uint8_t)", (void*)_setHSVXY); addExternal("uint8_t width", &layer->size.x); addExternal("uint8_t height", &layer->size.y); addExternal("uint8_t depth", &layer->size.z); @@ -158,6 +208,16 @@ void LiveScriptNode::setup() { addExternal("int gravityY", &sharedData.gravity.y); addExternal("int gravityZ", &sharedData.gravity.z); + // 2D drawing + addExternal("void drawLine(uint8_t,uint8_t,uint8_t,uint8_t,CRGB)", (void*)_drawLine); + addExternal("void drawCircle(int,int,uint8_t,CRGB)", (void*)_drawCircle); + + // time of day + _updateTime(); + addExternal("uint8_t hour", &_lsHour); + addExternal("uint8_t minute", &_lsMinute); + addExternal("uint8_t second", &_lsSecond); + // for (asm_external el: external_links) { // EXT_LOGV(MB_TAG, "elink %s %s %d", el.shortname.c_str(), el.name.c_str(), el.type); // } @@ -169,6 +229,7 @@ void LiveScriptNode::setup() { } void LiveScriptNode::loop() { + _updateTime(); // keep hour/minute/second current for clock scripts if (!hasLoopTask) return; // 🌙 only sync scripts whose loop task is actually running // 🌙 Only increment scriptsToSync when the give succeeds. If the semaphore is full // (more than 4 concurrent loop scripts) the give fails and we skip this frame rather @@ -265,7 +326,7 @@ void LiveScriptNode::compileAndRun() { scriptRuntime.addExe(executable); // if already exists, delete it first EXT_LOGV(MB_TAG, "addExe success %s", executable.exeExist ? "true" : "false"); - gNode = this; // todo: this is not working well with multiple scripts running!!! + gNode = this; // fallback for the brief window before registerNodeForTask() and for synchronous scripts if (executable.exeExist) { execute(); @@ -299,6 +360,12 @@ void LiveScriptNode::execute() { scriptRuntime.executeAsTask(animation.c_str(), "main"); // background task (async - vs sync) hasLoopTask = true; // 🌙 task is now running; loop() may start signalling WaitAnimationSync // assert failed: xEventGroupSync event_groups.c:228 (uxBitsToWaitFor != 0) + // 🌙 Register the task handle → node mapping so concurrent scripts each use their own node. + Executable* exec = scriptRuntime.findExecutable(animation.c_str()); + if (exec && exec->__run_handle_index != 9999) { + TaskHandle_t h = *runningPrograms.getHandleByIndex(exec->__run_handle_index); + if (h) registerNodeForTask(h, this); + } } else { EXT_LOGV(MB_TAG, "%s execute main", animation.c_str()); scriptRuntime.execute(animation.c_str(), "main"); @@ -309,6 +376,12 @@ void LiveScriptNode::execute() { void LiveScriptNode::kill() { EXT_LOGV(MB_TAG, "%s", animation.c_str()); hasLoopTask = false; // 🌙 task is being killed; loop() must not signal WaitAnimationSync + // 🌙 Unregister task → node mapping before the task is deleted. + Executable* exec = scriptRuntime.findExecutable(animation.c_str()); + if (exec && exec->__run_handle_index != 9999) { + TaskHandle_t h = *runningPrograms.getHandleByIndex(exec->__run_handle_index); + if (h) unregisterNodeForTask(h); + } scriptRuntime.kill(animation.c_str()); } @@ -319,7 +392,7 @@ void LiveScriptNode::free() { void LiveScriptNode::killAndDelete() { EXT_LOGV(MB_TAG, "%s", animation.c_str()); - scriptRuntime.kill(animation.c_str()); + kill(); // scriptRuntime.free(animation.c_str()); scriptRuntime.deleteExe(animation.c_str()); };