From 3b3dad4fd39ecc198e9f4cdd089caf23e08ab577 Mon Sep 17 00:00:00 2001 From: ewowi Date: Thu, 19 Feb 2026 13:02:41 +0100 Subject: [PATCH 1/6] RadarEffect and ArtNet in/out tuning - pio.ini: latest FastLED Backend ======= - Effects: add RadarEffect - Artnet in : bugfix channelsD - artnet out: ip ranges --- docs/moonlight/drivers.md | 2 +- platformio.ini | 4 +- src/MoonBase/Char.h | 2 + src/MoonLight/Modules/ModuleEffects.h | 16 +++--- src/MoonLight/Nodes/Drivers/D_ArtnetIn.h | 8 +-- src/MoonLight/Nodes/Drivers/D_ArtnetOut.h | 35 ++++++++---- src/MoonLight/Nodes/Effects/E_MoonLight.h | 69 ++++++++++++++++++++++- 7 files changed, 110 insertions(+), 26 deletions(-) diff --git a/docs/moonlight/drivers.md b/docs/moonlight/drivers.md index 889f1194..b45fab96 100644 --- a/docs/moonlight/drivers.md +++ b/docs/moonlight/drivers.md @@ -100,7 +100,7 @@ Sends Lights in Art-Net compatible packages to an Art-Net controller specified b **Controls** * **Light preset**: See above. -* **Controller IPs**: The last segment of the IP address within your local network, of the hardware Art-Net controller. Add more IPs if you send to more than one controller, comma separated. +* **Controller IPs**: The last segment of the IP address within your local network, of the hardware Art-Net controller. Add more IPs if you send to more than one controller, comma separated or use a hyphen for a range of IPs. * **Port**: The network port added to the IP address, 6454 is the default for Art-Net. * **FPS Limiter**: set the max frames per second Art-Net packages are send out (also all the other nodes will run at this speed). * Art-Net specs recommend about 44 FPS but higher framerates will work mostly (up to until ~130FPS tested) diff --git a/platformio.ini b/platformio.ini index a081431f..675488c7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -57,7 +57,7 @@ build_flags = -D BUILD_TARGET=\"$PIOENV\" -D APP_NAME=\"MoonLight\" ; πŸŒ™ Must only contain characters from [a-zA-Z0-9-_] as this is converted into a filename -D APP_VERSION=\"0.8.1\" ; semver compatible version string - -D APP_DATE=\"20260218\" ; πŸŒ™ + -D APP_DATE=\"20260219\" ; πŸŒ™ -D PLATFORM_VERSION=\"pioarduino-55.03.37\" ; πŸŒ™ make sure it matches with above plaftform @@ -157,7 +157,7 @@ build_flags = ; -D FASTLED_TESTING ; causes duplicate definition of initSpiHardware(); - workaround: removed implementation in spi_hw_manager_esp32.cpp.hpp -D FASTLED_BUILD=\"20260217\" lib_deps = - https://github.com/FastLED/FastLED#99e55a02ebf54ff89aa687972f0589870540cb2a ; master 20260217 + https://github.com/FastLED/FastLED#d9ffd095605ee1065aba40d8f89b757be5aad52f ; master 20260219 https://github.com/ewowi/WLED-sync#25f280b5e8e47e49a95282d0b78a5ce5301af4fe ; sourceIP + fftUdp.clear() if arduino >=3 (20251104) ; πŸ’« currently only enabled on s3 as esp32dev runs over 100% diff --git a/src/MoonBase/Char.h b/src/MoonBase/Char.h index 5b4f2e92..f87195c2 100644 --- a/src/MoonBase/Char.h +++ b/src/MoonBase/Char.h @@ -88,6 +88,7 @@ struct Char { char operator[](const uint16_t indexV) const { return (indexV < sizeof(s)) ? s[indexV] : '\0'; } + // returns a substring, starting at begin and ending at end-1 (not exclusive) Char substring(uint16_t begin, uint16_t end = sizeof(s) - 1) { Char sub; if (begin >= sizeof(s) || end >= sizeof(s) || end < begin) @@ -102,6 +103,7 @@ struct Char { int toInt() const { return atoi(s); } float toFloat() const { return atof(s); } bool contains(const char* rhs) const { return strnstr(s, rhs, sizeof(s)) != nullptr; } + // returns index of first character of token (starting with 0) size_t indexOf(const char* token) const { const char* pos = strnstr(s, token, sizeof(s)); return pos ? (pos - s) : SIZE_MAX; diff --git a/src/MoonLight/Modules/ModuleEffects.h b/src/MoonLight/Modules/ModuleEffects.h index eac5269c..b4668295 100644 --- a/src/MoonLight/Modules/ModuleEffects.h +++ b/src/MoonLight/Modules/ModuleEffects.h @@ -90,15 +90,15 @@ class ModuleEffects : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); #if USE_M5UNIFIED addControlValue(control, getNameAndTags()); #endif addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); @@ -108,6 +108,7 @@ class ModuleEffects : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); @@ -189,13 +190,13 @@ class ModuleEffects : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); #if USE_M5UNIFIED if (!node) node = checkAndAlloc(name); #endif if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); @@ -203,10 +204,11 @@ class ModuleEffects : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); // MoonModules effects, alphabetically if (!node) node = checkAndAlloc(name); diff --git a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h index ac645e89..7581afdc 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h @@ -130,9 +130,9 @@ class ArtNetInDriver : public Node { int ledIndex = startPixel + i; if (ledIndex < layerP.lights.header.nrOfLights) { if (layer == 0) { // Physical layer - memcpy(&layerP.lights.channelsE[ledIndex * layerP.lights.header.channelsPerLight], &dmxData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); + memcpy(&layerP.lights.channelsD[ledIndex * layerP.lights.header.channelsPerLight], &dmxData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); } else { // Virtual layer - layerP.layers[layer - 1]->forEachLightIndex(ledIndex, [&](nrOfLights_t indexP) { memcpy(&layerP.lights.channelsE[indexP * layerP.lights.header.channelsPerLight], &dmxData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); }); + layerP.layers[layer - 1]->forEachLightIndex(ledIndex, [&](nrOfLights_t indexP) { memcpy(&layerP.lights.channelsD[indexP * layerP.lights.header.channelsPerLight], &dmxData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); }); // setLight(ledIndex, &dmxData[i * layerP.lights.header.channelsPerLight], 0, layerP.lights.header.channelsPerLight); } } @@ -161,9 +161,9 @@ class ArtNetInDriver : public Node { int ledIndex = startPixel + i; if (ledIndex < layerP.lights.header.nrOfLights) { if (layer == 0) { // Physical layer - memcpy(&layerP.lights.channelsE[ledIndex * layerP.lights.header.channelsPerLight], &pixelData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); + memcpy(&layerP.lights.channelsD[ledIndex * layerP.lights.header.channelsPerLight], &pixelData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); } else { // Virtual layer - layerP.layers[layer - 1]->forEachLightIndex(ledIndex, [&](nrOfLights_t indexP) { memcpy(&layerP.lights.channelsE[indexP * layerP.lights.header.channelsPerLight], &pixelData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); }); + layerP.layers[layer - 1]->forEachLightIndex(ledIndex, [&](nrOfLights_t indexP) { memcpy(&layerP.lights.channelsD[indexP * layerP.lights.header.channelsPerLight], &pixelData[i * layerP.lights.header.channelsPerLight], layerP.lights.header.channelsPerLight); }); // setLight(ledIndex, &pixelData[i * layerP.lights.header.channelsPerLight], 0, layerP.lights.header.channelsPerLight); } } diff --git a/src/MoonLight/Nodes/Drivers/D_ArtnetOut.h b/src/MoonLight/Nodes/Drivers/D_ArtnetOut.h index 6b6a6cd9..ae345538 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetOut.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetOut.h @@ -66,20 +66,33 @@ class ArtNetOutDriver : public DriverNode { DriverNode::onUpdate(oldValue, control); // !! if (control["name"] == "controllerIPs") { - EXT_LOGD(MB_TAG, "IPs: %s", controllerIP3s.c_str()); nrOfIPAddresses = 0; - controllerIP3s.split(",", [this](const char* token, uint8_t nr) { - int ipSegment = atoi(token); - if (nrOfIPAddresses < std::size(ipAddresses) && ipSegment >= 0 && ipSegment <= 255) { - EXT_LOGD(MB_TAG, "Found IP: %s (%d / %d)", token, nr, nrOfIPAddresses); - ipAddresses[nrOfIPAddresses] = ipSegment; - nrOfIPAddresses++; - } else - EXT_LOGW(MB_TAG, "Too many IPs provided (%d) or invalid IP segment: %d ", nrOfIPAddresses, ipSegment); - }); + size_t index = controllerIP3s.indexOf("-"); + EXT_LOGD(MB_TAG, "IPs: %s (%d)", controllerIP3s.c_str(), index); + if (index != SIZE_MAX) { + Char<8> first = controllerIP3s.substring(0, index); + Char<8> second = controllerIP3s.substring(index + 1); + for (int ipSegment = first.toInt(); ipSegment <= second.toInt(); ipSegment++) { + if (nrOfIPAddresses < std::size(ipAddresses) && ipSegment >= 0 && ipSegment <= 255) { + EXT_LOGD(MB_TAG, "Found IP: %s-%s %d (%d)", first.c_str(), second.c_str(), ipSegment, nrOfIPAddresses); + ipAddresses[nrOfIPAddresses] = ipSegment; + nrOfIPAddresses++; + } + } + } else { + controllerIP3s.split(",", [this](const char* token, uint8_t nr) { + int ipSegment = atoi(token); + if (nrOfIPAddresses < std::size(ipAddresses) && ipSegment >= 0 && ipSegment <= 255) { + EXT_LOGD(MB_TAG, "Found IP: %s (%d / %d)", token, nr, nrOfIPAddresses); + ipAddresses[nrOfIPAddresses] = ipSegment; + nrOfIPAddresses++; + } else + EXT_LOGW(MB_TAG, "Too many IPs provided (%d) or invalid IP segment: %d ", nrOfIPAddresses, ipSegment); + }); + } } - totalChannels = layerP.lights.header.nrOfLights * layerP.lights.header.channelsPerLight; + totalChannels = layerP.lights.header.nrOfLights * layerP.lights.header.channelsPerLight / (nrOfIPAddresses?nrOfIPAddresses:1); usedChannelsPerUniverse = universeSize / layerP.lights.header.channelsPerLight * layerP.lights.header.channelsPerLight; // calculated totalUniverses = (totalChannels + usedChannelsPerUniverse - 1) / usedChannelsPerUniverse; // ceiling //calculated diff --git a/src/MoonLight/Nodes/Effects/E_MoonLight.h b/src/MoonLight/Nodes/Effects/E_MoonLight.h index 310fcf87..49462291 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonLight.h +++ b/src/MoonLight/Nodes/Effects/E_MoonLight.h @@ -1588,7 +1588,7 @@ class VUMeterEffect : public Node { uint8_t band = 0; for (int h = 0; h < nHorizontal; h++) { for (int v = 0; v < nVertical; v++) { - drawNeedle((float)sharedData.bands[2 * (band++)] / 2.0, {layer->size.x * h / nHorizontal, layer->size.y * v / nVertical, 0}, {(layer->size.x-1) / nHorizontal, (layer->size.y-1) / nVertical, 0}, ColorFromPalette(layerP.palette, 255 / (nHorizontal * nVertical) * band)); + drawNeedle((float)sharedData.bands[2 * (band++)] / 2.0, {layer->size.x * h / nHorizontal, layer->size.y * v / nVertical, 0}, {(layer->size.x - 1) / nHorizontal, (layer->size.y - 1) / nVertical, 0}, ColorFromPalette(layerP.palette, 255 / (nHorizontal * nVertical) * band)); } // sharedData.bands[band++] / 200 } // ppf(" v:%f, f:%f", sharedData.volume, (float) sharedData.bands[5]); @@ -1745,4 +1745,71 @@ class AudioRingsEffect : public RingEffect { } }; +class RadarEffect : public Node { + public: + static const char* name() { return "Radar"; } + static uint8_t dim() { return _2D; } + static const char* tags() { return "πŸ”₯"; } + + uint8_t bpm = 60; // 1 beat per second + uint8_t fade = 128; + bool fullLine = false; + uint8_t tubeSpacing = 10; + + void setup() override { + addControl(bpm, "bpm", "slider"); + addControl(fade, "fade", "slider"); + addControl(fullLine, "fullLine", "checkbox"); + addControl(tubeSpacing, "tubeSpacing", "number"); + } + + void loop() override { + layer->fadeToBlackBy(fade); + + uint16_t W = layer->size.x; + uint16_t H = layer->size.y; + + float physW = W * (float)tubeSpacing; + float physH = H * 1.0f; + float physPerimeter = 2.0f * (physW + physH); + + uint32_t cycleMs = 60000 / bpm; + float physPos = (float)(millis() % cycleMs) / cycleMs * physPerimeter; + + auto physToXY = [&](float p, int16_t& x, int16_t& y) { + if (p < physW) { + x = (int16_t)(p / 10.0f); + y = 0; + } // top + else if (p < physW + physH) { + x = W - 1; + y = (int16_t)(p - physW); + } // right + else if (p < 2 * physW + physH) { + x = (int16_t)((2 * physW + physH - p) / 10.0f); + y = H - 1; + } // bottom + else { + x = 0; + y = (int16_t)(physPerimeter - p); + } // left + }; + + int16_t x1, y1, x2, y2; + physToXY(physPos, x1, y1); + + if (fullLine) { + float physPosB = fmod(physPos + physPerimeter / 2.0f, physPerimeter); + int16_t x2, y2; + physToXY(physPosB, x2, y2); + layer->drawLine(x1, y1, x2, y2, ColorFromPalette(layerP.palette, (uint8_t)(physPos / physPerimeter * 255)), false); + } else { + // Half line: from center to perimeter point + layer->drawLine(W / 2, H / 2, x1, y1, ColorFromPalette(layerP.palette, (uint8_t)(physPos / physPerimeter * 255)), false); + } + } + + ~RadarEffect() override {}; // e,g, to free allocated memory +}; + #endif \ No newline at end of file From dbaff4d432319d2f15ae9397998010598f03672c Mon Sep 17 00:00:00 2001 From: ewowi Date: Thu, 19 Feb 2026 16:11:02 +0100 Subject: [PATCH 2/6] ArtNet in : add swapMutex Effects: RadarEffect use tubeSpacing consequently, DJLight: propagate over X and Z --- src/MoonLight/Nodes/Drivers/D_ArtnetIn.h | 4 ++++ src/MoonLight/Nodes/Effects/E_MoonLight.h | 8 ++++---- src/MoonLight/Nodes/Effects/E_WLED.h | 10 +++++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h index 7581afdc..9c1db3db 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h @@ -126,6 +126,7 @@ class ArtNetInDriver : public Node { int startPixel = (universe - universeMin) * (512 / layerP.lights.header.channelsPerLight); int numPixels = MIN(dataLength / layerP.lights.header.channelsPerLight, layerP.lights.header.nrOfLights - startPixel); + xSemaphoreTake(swapMutex, portMAX_DELAY); // because ArtNetIn is like an effect writing data into the channels array for (int i = 0; i < numPixels; i++) { int ledIndex = startPixel + i; if (ledIndex < layerP.lights.header.nrOfLights) { @@ -137,6 +138,7 @@ class ArtNetInDriver : public Node { } } } + xSemaphoreGive(swapMutex); } } } @@ -157,6 +159,7 @@ class ArtNetInDriver : public Node { int startPixel = offset / layerP.lights.header.channelsPerLight; int numPixels = MIN(dataLen / layerP.lights.header.channelsPerLight, layerP.lights.header.nrOfLights - startPixel); + xSemaphoreTake(swapMutex, portMAX_DELAY); // because ArtNetIn is like an effect writing data into the channels array for (int i = 0; i < numPixels; i++) { int ledIndex = startPixel + i; if (ledIndex < layerP.lights.header.nrOfLights) { @@ -168,6 +171,7 @@ class ArtNetInDriver : public Node { } } } + xSemaphoreGive(swapMutex); } } }; diff --git a/src/MoonLight/Nodes/Effects/E_MoonLight.h b/src/MoonLight/Nodes/Effects/E_MoonLight.h index 49462291..dcbabeaf 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonLight.h +++ b/src/MoonLight/Nodes/Effects/E_MoonLight.h @@ -1773,12 +1773,12 @@ class RadarEffect : public Node { float physH = H * 1.0f; float physPerimeter = 2.0f * (physW + physH); - uint32_t cycleMs = 60000 / bpm; + uint32_t cycleMs = bpm ? 60000 / bpm : UINT32_MAX; float physPos = (float)(millis() % cycleMs) / cycleMs * physPerimeter; auto physToXY = [&](float p, int16_t& x, int16_t& y) { if (p < physW) { - x = (int16_t)(p / 10.0f); + x = (int16_t)(p / (float)tubeSpacing); y = 0; } // top else if (p < physW + physH) { @@ -1786,7 +1786,7 @@ class RadarEffect : public Node { y = (int16_t)(p - physW); } // right else if (p < 2 * physW + physH) { - x = (int16_t)((2 * physW + physH - p) / 10.0f); + x = (int16_t)((2 * physW + physH - p) / (float)tubeSpacing); y = H - 1; } // bottom else { @@ -1795,7 +1795,7 @@ class RadarEffect : public Node { } // left }; - int16_t x1, y1, x2, y2; + int16_t x1, y1; physToXY(physPos, x1, y1); if (fullLine) { diff --git a/src/MoonLight/Nodes/Effects/E_WLED.h b/src/MoonLight/Nodes/Effects/E_WLED.h index b80e2db4..a6a8517a 100644 --- a/src/MoonLight/Nodes/Effects/E_WLED.h +++ b/src/MoonLight/Nodes/Effects/E_WLED.h @@ -1974,11 +1974,15 @@ class DJLightEffect : public Node { // layer->setRGB(mid, color.fadeToBlackBy(map(sharedData.bands[4], 0, 255, 255, 4))); // 0.13.x fade -> 180hz-260hz uint8_t fadeVal = ::map(sharedData.bands[3], 0, 255, 255, 4); // 0.14.x fade -> 216hz-301hz if (candyFactory) fadeVal = constrain(fadeVal, 0, 176); // "candy factory" mode - avoid complete fade-out - layer->setRGB(Coord3D(0, mid), color.fadeToBlackBy(fadeVal)); - for (int i = layer->size.y - 1; i > mid; i--) layer->setRGB(Coord3D(0, i), layer->getRGB(Coord3D(0, i - 1))); // move to the left - for (int i = 0; i < mid; i++) layer->setRGB(Coord3D(0, i), layer->getRGB(Coord3D(0, i + 1))); // move to the right + for (int x = 0; x < layer->size.x; x++) { + for (int z = 0; z < layer->size.z; z++) { + layer->setRGB(Coord3D(x, mid, z), color.fadeToBlackBy(fadeVal)); + for (int y = layer->size.y - 1; y > mid; y--) layer->setRGB(Coord3D(x, y, z), layer->getRGB(Coord3D(x, y - 1, z))); // move to the left + for (int y = 0; y < mid; y++) layer->setRGB(Coord3D(x, y, z), layer->getRGB(Coord3D(x, y + 1, z))); // move to the right + } + } layer->fadeToBlackBy(fade); } } From 98ac25272514e1b59a60f2a54f486158a9472434 Mon Sep 17 00:00:00 2001 From: ewowi Date: Thu, 19 Feb 2026 16:24:37 +0100 Subject: [PATCH 3/6] bugfix --- src/MoonLight/Nodes/Drivers/D_ArtnetIn.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h index 9c1db3db..7af2d32b 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h @@ -11,6 +11,8 @@ #if FT_MOONLIGHT +extern SemaphoreHandle_t swapMutex ; + class ArtNetInDriver : public Node { public: static const char* name() { return "Art-Net In"; } From a2c47a40b2465804181bb3531b0e822de3982c80 Mon Sep 17 00:00:00 2001 From: ewowi Date: Fri, 20 Feb 2026 12:21:07 +0100 Subject: [PATCH 4/6] New effects, emoji tweaks, fixed colors palettes - Emoji tweaks - New effects: BlinkRainbow, Metor, Oscillate, PhasedNoise backend ======= - palettes: add singlecolors --- docs/develop/nodes.md | 4 +- src/MoonBase/Nodes.cpp | 4 +- src/MoonLight/Modules/ModuleEffects.h | 8 + src/MoonLight/Modules/palettes.h | 138 +++++----- src/MoonLight/Nodes/Drivers/D_ArtnetIn.h | 6 +- src/MoonLight/Nodes/Drivers/D_AudioSync.h | 6 +- src/MoonLight/Nodes/Drivers/D__Sandbox.h | 2 +- src/MoonLight/Nodes/Effects/E_FastLED.h | 2 +- src/MoonLight/Nodes/Effects/E_MoonLight.h | 24 +- src/MoonLight/Nodes/Effects/E_MoonModules.h | 6 +- src/MoonLight/Nodes/Effects/E_MovingHeads.h | 8 +- src/MoonLight/Nodes/Effects/E_WLED.h | 286 ++++++++++++++++++-- src/MoonLight/Nodes/Effects/E__Sandbox.h | 2 +- src/MoonLight/Nodes/Layouts/L__Sandbox.h | 2 +- src/MoonLight/Nodes/Modifiers/M_MoonLight.h | 12 +- src/MoonLight/Nodes/Modifiers/M__Sandbox.h | 2 +- 16 files changed, 377 insertions(+), 135 deletions(-) diff --git a/docs/develop/nodes.md b/docs/develop/nodes.md index 6b8bbbd0..60f03116 100644 --- a/docs/develop/nodes.md +++ b/docs/develop/nodes.md @@ -102,8 +102,8 @@ This is the current list of supported lights ranging from 3 channels per light ( * GRBW: rgbw LED eg. SK6812 * GRB6: some LED curtains * RGBWYP: 6 channel par/dmx light with UV etc -* MHBeeEyes150W-15 🐺: 15 channels moving head, see https://moonmodules.org/MoonLight/moonbase/module/drivers/#art-net -* MHBeTopper19x15W-32 🐺: 32 channels moving head +* MHBeeEyes150W-15: 15 channels moving head, see https://moonmodules.org/MoonLight/moonbase/module/drivers/#art-net +* MHBeTopper19x15W-32: 32 channels moving head * MH19x15W-24: 24 channels moving heads Based on the chosen value, the channels per light and the offsets will be set e.g. for GRB: header->channelsPerLight = 3; header->offsetRed = 1; header->offsetGreen = 0; header->offsetBlue = 2;. Drivers should not make this mapping, the code calling drivers should do. diff --git a/src/MoonBase/Nodes.cpp b/src/MoonBase/Nodes.cpp index 9d5d3b74..7739b99a 100644 --- a/src/MoonBase/Nodes.cpp +++ b/src/MoonBase/Nodes.cpp @@ -361,8 +361,8 @@ void DriverNode::setup() { addControlValue("Curtain GRB6"); // some LED curtains addControlValue("Curtain RGB2040"); // curtain RGB2040 addControlValue("Lightbar RGBWYP"); // 6 channel par/dmx light with UV etc - addControlValue("MH BeeEyes 150W-15 🐺"); // 15 channels moving head, see https://moonmodules.org/MoonLight/moonlight/drivers/#art-net - addControlValue("MH BeTopper 19x15W-32 🐺"); // 32 channels moving head + addControlValue("MH BeeEyes 150W-15"); // 15 channels moving head, see https://moonmodules.org/MoonLight/moonlight/drivers/#art-net + addControlValue("MH BeTopper 19x15W-32"); // 32 channels moving head addControlValue("MH 19x15W-24"); // 24 channels moving heads } diff --git a/src/MoonLight/Modules/ModuleEffects.h b/src/MoonLight/Modules/ModuleEffects.h index b4668295..02abbd0a 100644 --- a/src/MoonLight/Modules/ModuleEffects.h +++ b/src/MoonLight/Modules/ModuleEffects.h @@ -145,6 +145,10 @@ class ModuleEffects : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); // FastLED effects addControlValue(control, getNameAndTags()); @@ -243,6 +247,10 @@ class ModuleEffects : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); // FastLED if (!node) node = checkAndAlloc(name); diff --git a/src/MoonLight/Modules/palettes.h b/src/MoonLight/Modules/palettes.h index 19b9bc82..72b2e646 100644 --- a/src/MoonLight/Modules/palettes.h +++ b/src/MoonLight/Modules/palettes.h @@ -439,6 +439,14 @@ const uint8_t fastled_rainbow_gp[] = {255, 1, 6, 0}; const uint8_t fastled_rainbow_stripe_gp[] = {255, 1, 7, 0}; const uint8_t moonlight_mm_gp[] = {255, 2, 0, 0}; const uint8_t moonlight_random_gp[] = {255, 2, 1, 0}; +const uint8_t moonlight_red_gp[] = {255, 2, 2, 0}; +const uint8_t moonlight_green_gp[] = {255, 2, 3, 0}; +const uint8_t moonlight_blue_gp[] = {255, 2, 4, 0}; +const uint8_t moonlight_orange_gp[] = {255, 2, 5, 0}; +const uint8_t moonlight_purple_gp[] = {255, 2, 6, 0}; +const uint8_t moonlight_cyan_gp[] = {255, 2, 7, 0}; +const uint8_t moonlight_w_white_gp[] = {255, 2, 8, 0}; +const uint8_t moonlight_c_white_gp[] = {255, 2, 9, 0}; // Single array of defined cpt-city color palettes. // This will let us programmatically choose one based on @@ -456,6 +464,14 @@ const uint8_t* const gGradientPalettes[] = { fastled_rainbow_stripe_gp, // Rainbow bands moonlight_mm_gp, // MoonLight πŸ’« moonlight_random_gp, // Random + moonlight_red_gp, // Red + moonlight_green_gp, // Green + moonlight_blue_gp, // Blue + moonlight_orange_gp, // Orange + moonlight_purple_gp, // Purple + moonlight_cyan_gp, // Cyan + moonlight_w_white_gp, // Warm White + moonlight_c_white_gp, // Cold White audio_responsive_gp, // Audio Hue ☾ audio_responsive_gp, // Audio Ramp ☾ audio_responsive_gp, // Audio Ratio ☾ @@ -519,77 +535,29 @@ const uint8_t* const gGradientPalettes[] = { retro2_16_gp, // Yellowout }; -const char* const palette_names[] = {"Cloud⚑️", - "Forest⚑️", - "Heat⚑️", - "Lava⚑️", - "Ocean⚑️", - "Party⚑️", - "Rainbow⚑️", - "Rainbow Bands⚑️", // - "MoonLightπŸ’«", - "RandomπŸ’«", - "Audio HueπŸŒ™", - "Audio RampπŸŒ™", - "Audio RatioπŸŒ™", - "Analogous", - "April Night", - "Aqua Flash", - "Atlantica", - "Aurora", - "Aurora 2", - "Autumn", +const char* const palette_names[] = {"Cloud⚑️", "Forest⚑️", "Heat⚑️", "Lava⚑️", + "Ocean⚑️", "Party⚑️", "Rainbow⚑️", "Rainbow Bands⚑️", + "MoonLightπŸ’«", "RandomπŸ’«", "Red", "Green", + "Blue", "Orange", "Purple", "Cyan", + "Warm White", "Cold White", "Audio HueπŸŒ™", "Audio RampπŸŒ™", + "Audio RatioπŸŒ™", "Analogous", "April Night", "Aqua Flash", + "Atlantica", "Aurora", "Aurora 2", "Autumn", "Beach", // - "Beech", - "Blink Red", - "Breeze", - "C9", - "C9 2", - "C9 New", - "Candy", - "Candy2", - "Cyane", - "Departure", + "Beech", "Blink Red", "Breeze", "C9", + "C9 2", "C9 New", "Candy", "Candy2", + "Cyane", "Departure", "Drywet", // - "Fairy Reaf", - "Fire", - "Grintage", - "Hult", - "Hult 64", - "Icefire", - "Jul", - "Landscape", + "Fairy Reaf", "Fire", "Grintage", "Hult", + "Hult 64", "Icefire", "Jul", "Landscape", "Light Pink", // - "Lite Light", - "Magenta", - "Magred", - "Orange & Teal", - "Orangery", - "Pastel", - "Pink Candy", - "Red & Blue", - "Red Flash", - "Red Reaf", - "Red Shift", - "Red Tide", - "Retro Clown", - "Rewhi", - "Rivendell", - "Sakura", - "Semi Blue", - "Sherbet", - "Splash", - "Sunset", - "Sunset 2", - "Temperature", - "Tertiary", - "Tiamat", - "Toxy Reaf", - "Vintage", - "Yelblu Hot", - "Yelblu", - "Yelmag", - "Yellowout"}; + "Lite Light", "Magenta", "Magred", "Orange & Teal", + "Orangery", "Pastel", "Pink Candy", "Red & Blue", + "Red Flash", "Red Reaf", "Red Shift", "Red Tide", + "Retro Clown", "Rewhi", "Rivendell", "Sakura", + "Semi Blue", "Sherbet", "Splash", "Sunset", + "Sunset 2", "Temperature", "Tertiary", "Tiamat", + "Toxy Reaf", "Vintage", "Yelblu Hot", "Yelblu", + "Yelmag", "Yellowout"}; CRGBPalette16 getGradientPalette(uint8_t index) { CRGBPalette16 palette; @@ -624,15 +592,43 @@ CRGBPalette16 getGradientPalette(uint8_t index) { break; } } else if (gpArray[1] == 2) { // MoonLight palettes - if (gpArray[2] == 0) { // MoonLight + switch (gpArray[2]) { + case 0: // MoonLight for (int i = 0; i < 16; i++) { palette[i] = CRGB(map(i, 0, 15, 255, 0), map(i, 0, 15, 31, 0), map(i, 0, 15, 0, 255)); // from orange to blue } - } - if (gpArray[2] == 1) { // random + break; + case 1: // random for (int i = 0; i < 16; i++) { palette[i] = CHSV(random8(), 255, 255); // take the max saturation, max brightness of the colorwheel } + break; + case 2: // Red + for (int i = 0; i < 16; i++) palette[i] = CRGB::Red; + break; + case 3: // Green + for (int i = 0; i < 16; i++) palette[i] = CRGB::Green; + break; + case 4: // Blue + for (int i = 0; i < 16; i++) palette[i] = CRGB::Blue; + break; + case 5: // Orange + for (int i = 0; i < 16; i++) palette[i] = CRGB(255, 31, 0); + // CRGB::Orange is too yellow + break; + case 6: // Purple + for (int i = 0; i < 16; i++) palette[i] = CRGB::Purple; + break; + case 7: // Cyan + for (int i = 0; i < 16; i++) palette[i] = CRGB::Cyan; + break; + case 8: // Warm White + for (int i = 0; i < 16; i++) palette[i] = CRGB(255, 120, 20); + // Very Warm / Candle-like (β‰ˆ2200K) or CRGB(255, 160, 60) - Softer Warm White or CRGB(255, 147, 41) - Typical Warm White (β‰ˆ2700K) + break; + case 9: // Cold White + for (int i = 0; i < 16; i++) palette[i] = CRGB::White; + break; } } } else { // gradient array palettes diff --git a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h index 7af2d32b..0896b540 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h @@ -11,7 +11,7 @@ #if FT_MOONLIGHT -extern SemaphoreHandle_t swapMutex ; +extern SemaphoreHandle_t swapMutex; class ArtNetInDriver : public Node { public: @@ -128,7 +128,7 @@ class ArtNetInDriver : public Node { int startPixel = (universe - universeMin) * (512 / layerP.lights.header.channelsPerLight); int numPixels = MIN(dataLength / layerP.lights.header.channelsPerLight, layerP.lights.header.nrOfLights - startPixel); - xSemaphoreTake(swapMutex, portMAX_DELAY); // because ArtNetIn is like an effect writing data into the channels array + xSemaphoreTake(swapMutex, portMAX_DELAY); // prevent effectTask from swapping channelsD/channelsE pointers while writing directly to channelsD (effects must be disabled when ArtNetIn is active) for (int i = 0; i < numPixels; i++) { int ledIndex = startPixel + i; if (ledIndex < layerP.lights.header.nrOfLights) { @@ -161,7 +161,7 @@ class ArtNetInDriver : public Node { int startPixel = offset / layerP.lights.header.channelsPerLight; int numPixels = MIN(dataLen / layerP.lights.header.channelsPerLight, layerP.lights.header.nrOfLights - startPixel); - xSemaphoreTake(swapMutex, portMAX_DELAY); // because ArtNetIn is like an effect writing data into the channels array + xSemaphoreTake(swapMutex, portMAX_DELAY); // prevent effectTask from swapping channelsD/channelsE pointers while writing directly to channelsD (effects must be disabled when ArtNetIn is active) for (int i = 0; i < numPixels; i++) { int ledIndex = startPixel + i; if (ledIndex < layerP.lights.header.nrOfLights) { diff --git a/src/MoonLight/Nodes/Drivers/D_AudioSync.h b/src/MoonLight/Nodes/Drivers/D_AudioSync.h index 8b74a8e7..704cec99 100644 --- a/src/MoonLight/Nodes/Drivers/D_AudioSync.h +++ b/src/MoonLight/Nodes/Drivers/D_AudioSync.h @@ -14,6 +14,8 @@ #include // https://github.com/netmindz/WLED-sync #include + #define audioPaletteIndex 18 // see palettes.h + class AudioSyncDriver : public Node { public: static const char* name() { return "Audio Sync"; } @@ -53,8 +55,8 @@ class AudioSyncDriver : public Node { moduleControl->read( [&](const ModuleState& state) { uint8_t palette = state.data["palette"]; - if (palette >= 10 && palette <= 12) { // Audio palettes - layerP.palette.loadDynamicGradientPalette(getAudioPalette(palette-10)); + if (palette >= audioPaletteIndex && palette <= audioPaletteIndex + 2) { // Audio palettes + layerP.palette.loadDynamicGradientPalette(getAudioPalette(palette - audioPaletteIndex)); } }, name()); diff --git a/src/MoonLight/Nodes/Drivers/D__Sandbox.h b/src/MoonLight/Nodes/Drivers/D__Sandbox.h index a7c5b956..db766fd2 100644 --- a/src/MoonLight/Nodes/Drivers/D__Sandbox.h +++ b/src/MoonLight/Nodes/Drivers/D__Sandbox.h @@ -18,7 +18,7 @@ class ExampleDriver : public Node { public: static const char* name() { return "Example"; } static uint8_t dim() { return _NoD; } // Dimensions not relevant for drivers? - static const char* tags() { return "☸️⏳"; } // use emojis see https://moonmodules.org/MoonLight/moonlight/overview/#emoji-coding, ☸️ for drivers + static const char* tags() { return "β˜ΈοΈπŸ†•"; } // use emojis see https://moonmodules.org/MoonLight/moonlight/overview/#emoji-coding, ☸️ for drivers uint8_t pin = 16; diff --git a/src/MoonLight/Nodes/Effects/E_FastLED.h b/src/MoonLight/Nodes/Effects/E_FastLED.h index 6149689a..a48cf96e 100644 --- a/src/MoonLight/Nodes/Effects/E_FastLED.h +++ b/src/MoonLight/Nodes/Effects/E_FastLED.h @@ -15,7 +15,7 @@ class RainbowEffect : public Node { public: static const char* name() { return "Rainbow"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸ”₯⚑️"; } + static const char* tags() { return "⚑️"; } uint8_t speed = 8; // default 8*32 = 256 / 256 = 1 = hue++ uint8_t deltaHue = 7; diff --git a/src/MoonLight/Nodes/Effects/E_MoonLight.h b/src/MoonLight/Nodes/Effects/E_MoonLight.h index dcbabeaf..9b0afa53 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonLight.h +++ b/src/MoonLight/Nodes/Effects/E_MoonLight.h @@ -671,7 +671,7 @@ class FreqSawsEffect : public Node { public: static const char* name() { return "Frequency Saws"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ”₯β™«πŸͺš"; } + static const char* tags() { return "πŸ”₯β™«"; } uint8_t fade = 4; uint8_t increaser = 211; @@ -767,7 +767,7 @@ class RubiksCubeEffect : public Node { public: static const char* name() { return "Rubik's Cube"; } static uint8_t dim() { return _3D; } - static const char* tags() { return "πŸ”₯πŸ’«"; } + static const char* tags() { return "πŸ”₯"; } struct Cube { uint8_t SIZE; @@ -1071,7 +1071,7 @@ class ParticlesEffect : public Node { public: static const char* name() { return "Particles"; } static uint8_t dim() { return _3D; } - static const char* tags() { return "πŸ”₯πŸ’«πŸ§­"; } + static const char* tags() { return "πŸ”₯🧭"; } struct Particle { float x, y, z; @@ -1300,7 +1300,7 @@ class MoonManEffect : public Node { public: static const char* name() { return "Moon Man"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ”₯🎡☾"; } + static const char* tags() { return "πŸ”₯🎡"; } // Create an M5Canvas for PNG processing M5Canvas* canvas; //(&M5.Display); @@ -1365,7 +1365,7 @@ class SpiralFireEffect : public Node { public: static const char* name() { return "Spiral Fire"; } static uint8_t dim() { return _3D; } - static const char* tags() { return "πŸ”₯⏳"; } + static const char* tags() { return "πŸ”₯"; } uint8_t speed = 60; uint8_t intensity = 180; @@ -1434,7 +1434,7 @@ class FireEffect : public Node { public: static const char* name() { return "Fire"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ’«"; } + static const char* tags() { return "πŸ”₯"; } const uint32_t colors[11] = {0x000000, 0x100000, 0x300000, 0x600000, 0x800000, 0xA00000, 0xC02000, 0xC04000, 0xC06000, 0xC08000, 0x807080}; const uint8_t NCOLORS = std::size(colors); @@ -1550,7 +1550,7 @@ class VUMeterEffect : public Node { public: static const char* name() { return "VU Meter"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "β™«πŸ’«πŸ“Ί"; } + static const char* tags() { return "πŸ”₯β™«"; } void drawNeedle(float angle, Coord3D topLeft, Coord3D size, CRGB color) { int x0 = topLeft.x + size.x / 2; // Center of the needle @@ -1599,7 +1599,7 @@ class PixelMapEffect : public Node { public: static const char* name() { return "PixelMap"; } static uint8_t dim() { return _3D; } - static const char* tags() { return "πŸ’«"; } + static const char* tags() { return "πŸ”₯"; } Coord3D pos = {0, 0, 0}; @@ -1616,7 +1616,7 @@ class MarioTestEffect : public Node { public: static const char* name() { return "MarioTest"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ’«"; } + static const char* tags() { return "πŸ”₯"; } bool background = false; Coord3D offset = {0, 0, 0}; @@ -1672,7 +1672,7 @@ class RingRandomFlowEffect : public RingEffect { public: static const char* name() { return "RingRandomFlow"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸ’«"; } + static const char* tags() { return "πŸ”₯"; } // void setup() override {} //so no palette control is created @@ -1704,7 +1704,7 @@ class AudioRingsEffect : public RingEffect { public: static const char* name() { return "AudioRings"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "β™«πŸ’«"; } + static const char* tags() { return "πŸ”₯β™«"; } bool inWards = true; @@ -1760,7 +1760,7 @@ class RadarEffect : public Node { addControl(bpm, "bpm", "slider"); addControl(fade, "fade", "slider"); addControl(fullLine, "fullLine", "checkbox"); - addControl(tubeSpacing, "tubeSpacing", "number"); + addControl(tubeSpacing, "tubeSpacing", "number", 1); } void loop() override { diff --git a/src/MoonLight/Nodes/Effects/E_MoonModules.h b/src/MoonLight/Nodes/Effects/E_MoonModules.h index e402fab2..9db5eafc 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonModules.h +++ b/src/MoonLight/Nodes/Effects/E_MoonModules.h @@ -18,7 +18,7 @@ class GameOfLifeEffect : public Node { public: static const char* name() { return "Game Of Life"; } static uint8_t dim() { return _3D; } // supports 3D but also 2D (1D as well?) - static const char* tags() { return "πŸ”₯πŸ’«"; } + static const char* tags() { return "πŸŒ™"; } void placePentomino(uint8_t* futureCells, bool colorByAge) { uint8_t pattern[5][2] = {{1, 0}, {0, 1}, {1, 1}, {2, 1}, {2, 2}}; // R-pentomino @@ -374,7 +374,7 @@ class GEQ3DEffect : public Node { public: static const char* name() { return "GEQ 3D"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ”₯β™«πŸΊ"; } + static const char* tags() { return "πŸŒ™β™«"; } uint8_t speed = 2; uint8_t frontFill = 228; @@ -520,7 +520,7 @@ class PaintBrushEffect : public Node { public: static const char* name() { return "Paintbrush"; } static uint8_t dim() { return _3D; } - static const char* tags() { return "πŸ”₯β™«πŸΊ"; } + static const char* tags() { return "πŸŒ™β™«"; } uint8_t oscillatorOffset = 6 * 160 / 255; uint8_t numLines = 255; diff --git a/src/MoonLight/Nodes/Effects/E_MovingHeads.h b/src/MoonLight/Nodes/Effects/E_MovingHeads.h index db8d0e72..2cbdeda6 100644 --- a/src/MoonLight/Nodes/Effects/E_MovingHeads.h +++ b/src/MoonLight/Nodes/Effects/E_MovingHeads.h @@ -15,7 +15,7 @@ class Troy1ColorEffect : public Node { public: static const char* name() { return "Troy1 Color"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸš¨β™«πŸΊ"; } + static const char* tags() { return "πŸš¨β™«"; } bool audioReactive = true; @@ -38,7 +38,7 @@ class Troy1MoveEffect : public Node { public: static const char* name() { return "Troy1 Move"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸ—Όβ™«πŸΊ"; } + static const char* tags() { return "πŸ—Όβ™«"; } // set default values here uint8_t bpm = 30; @@ -131,7 +131,7 @@ class Troy2ColorEffect : public Node { public: static const char* name() { return "Troy2 Color"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸš¨β™«πŸΊ"; } + static const char* tags() { return "πŸš¨β™«"; } uint8_t cutin = 200; @@ -170,7 +170,7 @@ class Troy2MoveEffect : public Node { public: static const char* name() { return "Troy2 Move"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸ—Όβ™«πŸΊ"; } + static const char* tags() { return "πŸ—Όβ™«"; } uint8_t bpm = 30; uint8_t pan = 175; diff --git a/src/MoonLight/Nodes/Effects/E_WLED.h b/src/MoonLight/Nodes/Effects/E_WLED.h index a6a8517a..f1bd0b2d 100644 --- a/src/MoonLight/Nodes/Effects/E_WLED.h +++ b/src/MoonLight/Nodes/Effects/E_WLED.h @@ -15,7 +15,7 @@ class BouncingBallsEffect : public Node { public: static const char* name() { return "Bouncing Balls"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ”₯πŸ™"; } + static const char* tags() { return "πŸ™"; } uint8_t grav = 128; uint8_t numBalls = 8; @@ -97,7 +97,7 @@ class BlurzEffect : public Node { public: static const char* name() { return "Blurz"; } static uint8_t dim() { return _3D; } // test... - static const char* tags() { return "πŸ”₯🎡☾"; } + static const char* tags() { return "πŸ™πŸŽ΅"; } // static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz Plus ☾@Fade rate,Blur,,,,FreqMap ☾,GEQ Scanner ☾,;!,Color mix;!;01f;sx=48,ix=127,m12=7,si=0"; // Pinwheel, Beatsin @@ -171,7 +171,7 @@ class DistortionWavesEffect : public Node { public: static const char* name() { return "Distortion Waves"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ”₯πŸ™"; } + static const char* tags() { return "πŸ™"; } uint8_t speed = 4; uint8_t scale = 4; @@ -228,7 +228,7 @@ class FreqMatrixEffect : public Node { public: static const char* name() { return "Freq Matrix"; } static uint8_t dim() { return _1D; } // 2D/3D-ish? - static const char* tags() { return "πŸ”₯β™ͺπŸ™"; } + static const char* tags() { return "πŸ™β™ͺ"; } uint8_t speed = 255; uint8_t fx = 128; @@ -302,7 +302,7 @@ class GEQEffect : public Node { public: static const char* name() { return "GEQ"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ”₯β™«πŸ™"; } + static const char* tags() { return "πŸ™β™«"; } uint8_t fadeOut = 255; uint8_t ripple = 128; @@ -415,7 +415,7 @@ class LissajousEffect : public Node { public: static const char* name() { return "Lissajous"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ”₯πŸ™"; } + static const char* tags() { return "πŸ™"; } uint8_t xFrequency = 64; uint8_t fadeRate = 128; @@ -446,7 +446,7 @@ class Noise2DEffect : public Node { public: static const char* name() { return "Noise2D"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ”₯πŸ™"; } + static const char* tags() { return "πŸ™"; } uint8_t speed = 8; uint8_t scale = 64; @@ -470,7 +470,7 @@ class NoiseMeterEffect : public Node { public: static const char* name() { return "Noise Meter"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "β™ͺπŸ™"; } + static const char* tags() { return "πŸ™β™ͺ"; } uint8_t fadeRate = 248; uint8_t width = 128; @@ -527,7 +527,7 @@ class PacManEffect : public Node { public: static const char* name() { return "PacMan"; } static uint8_t dim() { return _1D; } // it is a 1D effect, todo: 2D - static const char* tags() { return "πŸ”₯πŸ™"; } + static const char* tags() { return "πŸ™"; } // static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of PowerDots,Blink distance,Blur,# of Ghosts,Dots,Smear,Compact;;!;1;m12=0,sx=192,ix=64,c1=64,c2=0,c3=12,o1=1,o2=0"; uint8_t speed = 192; @@ -788,7 +788,7 @@ class AntEffect : public Node { public: static const char* name() { return "Ants"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸ”₯"; } + static const char* tags() { return "πŸ™"; } uint8_t antSpeed = 192; uint8_t nrOfAnts = MAX_ANTS / 2; @@ -955,7 +955,7 @@ class TetrixEffect : public Node { public: static const char* name() { return "Tetrix"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ”₯πŸ™"; } + static const char* tags() { return "πŸ™"; } uint8_t speedControl = 0; // 1 beat per second uint8_t width = 0; @@ -1060,7 +1060,7 @@ class PopCornEffect : public Node { public: static const char* name() { return "PopCorn"; } static uint8_t dim() { return _1D; } // 2D-ish? check latest in WLED... - static const char* tags() { return "β™ͺπŸ™"; } + static const char* tags() { return "πŸ™β™ͺ"; } uint8_t speed = 128; uint8_t numPopcorn = maxNumPopcorn / 2; @@ -1136,7 +1136,7 @@ class WaverlyEffect : public Node { public: static const char* name() { return "Waverly"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ”₯β™ͺπŸ™"; } + static const char* tags() { return "πŸ™β™ͺ"; } uint8_t fadeRate = 128; uint8_t amplification = 30; @@ -1181,7 +1181,7 @@ class BlackholeEffect : public Node { public: static const char* name() { return "Blackhole"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ”₯β³πŸ™"; } + static const char* tags() { return "πŸ™"; } uint8_t fadeRate = 128; // speed uint8_t outerYfreq = 128; // intensity @@ -1232,7 +1232,7 @@ class DNAEffect : public Node { public: static const char* name() { return "DNA"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ™πŸ’«"; } + static const char* tags() { return "πŸ™"; } uint8_t speed = 16; uint8_t blur = 128; @@ -1551,7 +1551,7 @@ class FunkyPlankEffect : public Node { public: static const char* name() { return "Funky Plank"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "β™«πŸ™πŸ’«"; } + static const char* tags() { return "πŸ™β™«"; } uint8_t speed = 255; uint8_t bands = NUM_GEQ_CHANNELS; @@ -1600,7 +1600,7 @@ class FlowEffect : public Node { public: static const char* name() { return "Flow"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸ™"; } // πŸ™ means wled origin + static const char* tags() { return "πŸ™"; } // means wled origin uint8_t speed = 128; uint8_t zonesControl = 128; @@ -1771,7 +1771,7 @@ class DripEffect : public Node { public: static const char* name() { return "Drip"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ™πŸ’«"; } + static const char* tags() { return "πŸ™"; } uint8_t gravityControl = 128; uint8_t drips = 4; @@ -1875,7 +1875,7 @@ class HeartBeatEffect : public Node { public: static const char* name() { return "HeartBeat"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸ™πŸ’«β™₯"; } + static const char* tags() { return "β™₯"; } uint8_t speed = 15; uint8_t intensity = 128; @@ -1918,7 +1918,7 @@ class DJLightEffect : public Node { public: static const char* name() { return "DJLight"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "β™«πŸ™"; } + static const char* tags() { return "πŸ™β™«"; } uint8_t speed = 255; bool candyFactory = true; @@ -1975,9 +1975,11 @@ class DJLightEffect : public Node { uint8_t fadeVal = ::map(sharedData.bands[3], 0, 255, 255, 4); // 0.14.x fade -> 216hz-301hz if (candyFactory) fadeVal = constrain(fadeVal, 0, 176); // "candy factory" mode - avoid complete fade-out + CRGB fadedColor = color; + fadedColor.fadeToBlackBy(fadeVal); for (int x = 0; x < layer->size.x; x++) { for (int z = 0; z < layer->size.z; z++) { - layer->setRGB(Coord3D(x, mid, z), color.fadeToBlackBy(fadeVal)); + layer->setRGB(Coord3D(x, mid, z), fadedColor); for (int y = layer->size.y - 1; y > mid; y--) layer->setRGB(Coord3D(x, y, z), layer->getRGB(Coord3D(x, y - 1, z))); // move to the left for (int y = 0; y < mid; y++) layer->setRGB(Coord3D(x, y, z), layer->getRGB(Coord3D(x, y + 1, z))); // move to the right @@ -1992,7 +1994,7 @@ class ColorTwinkleEffect : public Node { public: static const char* name() { return "ColorTwinkle"; } static uint8_t dim() { return _3D; } - static const char* tags() { return "πŸ”₯⏳"; } + static const char* tags() { return "πŸ™"; } uint8_t fadeSpeed = 128; uint8_t spawnSpeed = 128; @@ -2073,7 +2075,7 @@ class PlasmaEffect : public Node { public: static const char* name() { return "Plasma"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸ”₯"; } + static const char* tags() { return "πŸ™"; } uint8_t speed = 60; uint8_t intensity = 128; @@ -2105,7 +2107,7 @@ class JuliaEffect : public Node { public: static const char* name() { return "Julia"; } static uint8_t dim() { return _3D; } - static const char* tags() { return "πŸ”₯"; } + static const char* tags() { return "πŸ™"; } uint8_t speed = 60; // 1 beat per second uint8_t iterations = 64; // 24; @@ -2242,7 +2244,7 @@ class PoliceEffect : public Node { public: static const char* name() { return "Police"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸ”₯"; } + static const char* tags() { return "πŸ™"; } uint8_t speed = 60; // 1 beat per second uint8_t widthC = 128; @@ -2544,4 +2546,238 @@ class PoliceEffect : public Node { // } // static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate;!,!;!"; +class BlinkRainbowEffect : public Node { + public: + static const char* name() { return "Blink Rainbow"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™"; } + + uint8_t frequency = 128; + uint8_t blinkDuration = 128; + + void setup() override { + addControl(frequency, "frequency", "slider"); + addControl(blinkDuration, "blinkDuration", "slider"); + } + + uint8_t colorIndex = 0; + + void loop() override { + uint16_t dutyCycle = blinkDuration + 1; + uint16_t onTime = (dutyCycle * frequency) >> 8; + uint16_t offTime = dutyCycle - onTime; + + unsigned long timebase = millis(); + bool on = (timebase % dutyCycle) < onTime; + + if (on) { + CRGB color = ColorFromPalette(layerP.palette, colorIndex); + layer->fill_solid(color); + } else { + layer->fill_solid(CRGB::Black); + } + + // Change color every cycle + if ((timebase % dutyCycle) == 0) { + colorIndex += 32; + } + } +}; + +class MeteorEffect : public Node { + public: + static const char* name() { return "Meteor"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™"; } + + uint8_t speed = 128; + uint8_t trail = 128; + bool gradient = false; + bool smooth = false; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(trail, "trail", "slider"); + addControl(gradient, "gradient", "checkbox"); + addControl(smooth, "smooth", "checkbox"); + } + + uint8_t* trailData = nullptr; + size_t trailDataSize = 0; + uint16_t step = 0; + + ~MeteorEffect() override { + if (trailData) freeMB(trailData, "trailData"); + } + + void onSizeChanged(const Coord3D& prevSize) override { reallocMB2(trailData, trailDataSize, layer->nrOfLights, "trailData"); } + + void loop() override { + if (!trailData) return; + + const unsigned meteorSize = 1 + layer->nrOfLights / 20; // 5% + uint16_t meteorStart; + + if (smooth) { + meteorStart = beatsin16(speed >> 2, 0, layer->nrOfLights - 1); + } else { + unsigned counter = millis() * ((speed >> 2) + 8); + meteorStart = (counter * layer->nrOfLights) >> 16; + } + + // Fade all LEDs + for (int i = 0; i < layer->nrOfLights; i++) { + if (random8() <= 255 - trail) { + if (smooth) { + if (trailData[i] > 0) { + int change = trailData[i] + 4 - random8(24); + trailData[i] = constrain(change, 0, 240); + } + CRGB col = gradient ? ColorFromPalette(layerP.palette, i * 255 / layer->nrOfLights, trailData[i]) : ColorFromPalette(layerP.palette, trailData[i]); + layer->setRGB(i, col); + } else { + trailData[i] = scale8(trailData[i], 128 + random8(127)); + int index = gradient ? map(i, 0, layer->nrOfLights, 0, 240) : trailData[i]; + CRGB col = ColorFromPalette(layerP.palette, index, trailData[i]); + layer->setRGB(i, col); + } + } + } + + // Draw meteor head + for (int j = 0; j < meteorSize; j++) { + int index = (meteorStart + j) % layer->nrOfLights; + trailData[index] = 240; + int colorIdx = gradient ? (index * 255 / layer->nrOfLights) : 240; + CRGB col = ColorFromPalette(layerP.palette, colorIdx, 255); + layer->setRGB(index, col); + } + + step += speed + 1; + } +}; + +class OscillateEffect : public Node { + public: + static const char* name() { return "Oscillate"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + struct Oscillator { + int16_t pos; + int8_t size; + int8_t dir; + int8_t oscSpeed; // Renamed from 'speed' to avoid shadowing + }; + + Oscillator oscillators[3]; + uint32_t lastUpdate = 0; + bool initialized = false; + + void onSizeChanged(const Coord3D& prevSize) override { + // Initialize oscillators when layer size is known + if (layer->nrOfLights > 0) { + oscillators[0] = {(int16_t)(layer->nrOfLights / 4), (int8_t)(layer->nrOfLights / 8), 1, 1}; + oscillators[1] = {(int16_t)(layer->nrOfLights / 4 * 3), (int8_t)(layer->nrOfLights / 8), 1, 2}; + oscillators[2] = {(int16_t)(layer->nrOfLights / 4 * 2), (int8_t)(layer->nrOfLights / 8), -1, 1}; + initialized = true; + lastUpdate = 0xFFFFFFFF; // Force update on first frame + } + } + + void loop() override { + if (!initialized || layer->nrOfLights == 0) return; + + uint32_t cycleTime = 20 + (2 * (uint32_t)(255 - speed)); + uint32_t it = millis() / cycleTime; + + // Update oscillator positions + for (int i = 0; i < 3; i++) { + if (it != lastUpdate) { + oscillators[i].pos += oscillators[i].dir * oscillators[i].oscSpeed; + } + + oscillators[i].size = layer->nrOfLights / (3 + intensity / 8); + + if ((oscillators[i].dir == -1) && (oscillators[i].pos <= 0)) { + oscillators[i].pos = 0; + oscillators[i].dir = 1; + oscillators[i].oscSpeed = speed > 100 ? random8(2, 4) : random8(1, 3); + } + if ((oscillators[i].dir == 1) && (oscillators[i].pos >= (layer->nrOfLights - 1))) { + oscillators[i].pos = layer->nrOfLights - 1; + oscillators[i].dir = -1; + oscillators[i].oscSpeed = speed > 100 ? random8(2, 4) : random8(1, 3); + } + } + + // Clear and render + layer->fill_solid(CRGB::Black); + + for (int i = 0; i < layer->nrOfLights; i++) { + CRGB color = CRGB::Black; + for (int j = 0; j < 3; j++) { + if (i >= oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) { + CRGB newColor = ColorFromPalette(layerP.palette, j * 85); + color = (color == CRGB::Black) ? newColor : blend(color, newColor, 128); + } + } + layer->setRGB(i, color); + } + + lastUpdate = it; + } +}; + +class PhasedNoiseEffect : public Node { + public: + static const char* name() { return "Phased Noise"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + float phase = 0; + + void loop() override { + uint8_t allfreq = 16; // Base frequency + uint8_t cutOff = (255 - intensity); // Cutoff threshold + uint8_t index = millis() / 64; // Color rotation + + phase += speed / 32.0f; // Phase increment + + for (int i = 0; i < layer->nrOfLights; i++) { + // Add noise modulation + uint8_t modVal = (inoise8(i * 10 + i * 10) / 16); + if (modVal == 0) modVal = 1; + + uint16_t val = (i + 1) * allfreq; + val += phase * (i % modVal + 1) / 2; + + uint8_t b = cubicwave8(val); + b = (b > cutOff) ? (b - cutOff) : 0; + + CRGB color = blend(CRGB::Black, ColorFromPalette(layerP.palette, index), b); + layer->setRGB(i, color); + + index += 256 / layer->nrOfLights; + if (layer->nrOfLights > 256) index++; + } + } +}; + #endif \ No newline at end of file diff --git a/src/MoonLight/Nodes/Effects/E__Sandbox.h b/src/MoonLight/Nodes/Effects/E__Sandbox.h index 3c87ed26..91b5b49a 100644 --- a/src/MoonLight/Nodes/Effects/E__Sandbox.h +++ b/src/MoonLight/Nodes/Effects/E__Sandbox.h @@ -18,7 +18,7 @@ class ExampleEffect : public Node { public: static const char* name() { return "Example"; } static uint8_t dim() { return _3D; } // Dimensions supported _3D prefered, _2D or _1D can be used for first phase - static const char* tags() { return "πŸ”₯⏳"; } // use emojis see https://moonmodules.org/MoonLight/moonlight/overview/#emoji-coding, πŸ”₯ for effect + static const char* tags() { return "πŸ”₯πŸ†•"; } // use emojis see https://moonmodules.org/MoonLight/moonlight/overview/#emoji-coding, πŸ”₯ for effect uint8_t bpm = 60; // 1 beat per second uint8_t intensity = 128; diff --git a/src/MoonLight/Nodes/Layouts/L__Sandbox.h b/src/MoonLight/Nodes/Layouts/L__Sandbox.h index 1a4be0fe..0273fc0f 100644 --- a/src/MoonLight/Nodes/Layouts/L__Sandbox.h +++ b/src/MoonLight/Nodes/Layouts/L__Sandbox.h @@ -18,7 +18,7 @@ class ExampleLayout : public Node { public: static const char* name() { return "Example"; } static uint8_t dim() { return _3D; } // dimensions supported - static const char* tags() { return "πŸš₯⏳"; } // use emojis see https://moonmoduÍles.org/MoonLight/moonlight/overview/#emoji-coding, πŸš₯ for layout + static const char* tags() { return "πŸš₯πŸ†•"; } // use emojis see https://moonmoduÍles.org/MoonLight/moonlight/overview/#emoji-coding, πŸš₯ for layout uint8_t width = 12; uint8_t height = 12; diff --git a/src/MoonLight/Nodes/Modifiers/M_MoonLight.h b/src/MoonLight/Nodes/Modifiers/M_MoonLight.h index 9ccc266a..8a8cc32d 100644 --- a/src/MoonLight/Nodes/Modifiers/M_MoonLight.h +++ b/src/MoonLight/Nodes/Modifiers/M_MoonLight.h @@ -16,7 +16,7 @@ class CircleModifier : public Node { public: static const char* name() { return "Circle"; } static uint8_t dim() { return _2D; } // 1D to 2D ... - static const char* tags() { return "πŸ’ŽπŸ™"; } + static const char* tags() { return "πŸ’Ž"; } Coord3D modifierSize; @@ -52,7 +52,7 @@ class MirrorModifier : public Node { public: static const char* name() { return "Mirror"; } static uint8_t dim() { return _3D; } - static const char* tags() { return "πŸ’ŽπŸ™"; } + static const char* tags() { return "πŸ’Ž"; } bool mirrorX = true; bool mirrorY = true; @@ -199,7 +199,7 @@ class RippleXZModifier : public Node { public: static const char* name() { return "RippleXZ"; } static uint8_t dim() { return _3D; } - static const char* tags() { return "πŸ’ŽπŸ’«"; } + static const char* tags() { return "πŸ’Ž"; } bool shrink = true; bool towardsX = true; @@ -267,7 +267,7 @@ class RotateModifier : public Node { public: static const char* name() { return "Rotate"; } static uint8_t dim() { return _2D; } - static const char* tags() { return "πŸ’ŽπŸ’«"; } + static const char* tags() { return "πŸ’Ž"; } bool expand = false; bool flip, reverse, alternate; @@ -394,7 +394,7 @@ class TransposeModifier : public Node { public: static const char* name() { return "Transpose"; } static uint8_t dim() { return _3D; } - static const char* tags() { return "πŸ’ŽπŸ™"; } + static const char* tags() { return "πŸ’Ž"; } bool transposeXY = true; bool transposeXZ = false; @@ -463,7 +463,7 @@ class TransposeModifier : public Node { class CheckerboardModifier : public Node { public: static const char* name() { return "Checkerboard"; } - static const char* tags() { return "πŸ’ŽπŸ’«"; } + static const char* tags() { return "πŸ’Ž"; } Coord3D size = {3, 3, 3}; bool invert = false; diff --git a/src/MoonLight/Nodes/Modifiers/M__Sandbox.h b/src/MoonLight/Nodes/Modifiers/M__Sandbox.h index 7a2ccbb3..bbe8d116 100644 --- a/src/MoonLight/Nodes/Modifiers/M__Sandbox.h +++ b/src/MoonLight/Nodes/Modifiers/M__Sandbox.h @@ -18,7 +18,7 @@ class ExampleModifier : public Node { public: static const char* name() { return "Example"; } static uint8_t dim() { return _3D; } // for which effect dimension this modifier can be used, preferably 3D - static const char* tags() { return "πŸ’Žβ³"; } // use emojis see https://moonmodules.org/MoonLight/moonlight/overview/#emoji-coding, πŸ’Ž for modifier + static const char* tags() { return "πŸ’ŽπŸ†•"; } // use emojis see https://moonmodules.org/MoonLight/moonlight/overview/#emoji-coding, πŸ’Ž for modifier bool mirror = true; From 67835e029e1ac241fe4e4b0b8901e94444de346f Mon Sep 17 00:00:00 2001 From: ewowi Date: Fri, 20 Feb 2026 14:42:12 +0100 Subject: [PATCH 5/6] Add WLED-MM volume sound effects --- src/MoonLight/Modules/ModuleEffects.h | 56 +- src/MoonLight/Modules/palettes.h | 10 +- src/MoonLight/Nodes/Drivers/D_ArtnetIn.h | 2 +- src/MoonLight/Nodes/Drivers/D_AudioSync.h | 3 +- src/MoonLight/Nodes/Effects/E_MoonLight.h | 2 - src/MoonLight/Nodes/Effects/E_MovingHeads.h | 89 +-- src/MoonLight/Nodes/Effects/E_WLED.h | 829 +++++++++++++++++++- 7 files changed, 913 insertions(+), 78 deletions(-) diff --git a/src/MoonLight/Modules/ModuleEffects.h b/src/MoonLight/Modules/ModuleEffects.h index 02abbd0a..cda7ab59 100644 --- a/src/MoonLight/Modules/ModuleEffects.h +++ b/src/MoonLight/Modules/ModuleEffects.h @@ -119,8 +119,9 @@ class ModuleEffects : public NodeManager { // WLED effects, alphabetically addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); @@ -135,20 +136,37 @@ class ModuleEffects : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); + + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); // FastLED effects addControlValue(control, getNameAndTags()); @@ -221,8 +239,9 @@ class ModuleEffects : public NodeManager { // WLED effects, alphabetically if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); @@ -237,20 +256,37 @@ class ModuleEffects : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); + + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); // FastLED if (!node) node = checkAndAlloc(name); diff --git a/src/MoonLight/Modules/palettes.h b/src/MoonLight/Modules/palettes.h index 72b2e646..016c1705 100644 --- a/src/MoonLight/Modules/palettes.h +++ b/src/MoonLight/Modules/palettes.h @@ -559,6 +559,8 @@ const char* const palette_names[] = {"Cloud⚑️", "Forest⚑️", "He "Toxy Reaf", "Vintage", "Yelblu Hot", "Yelblu", "Yelmag", "Yellowout"}; +static_assert(sizeof(gGradientPalettes) / sizeof(gGradientPalettes[0]) == sizeof(palette_names) / sizeof(palette_names[0]), "gGradientPalettes and palette_names must have the same number of entries"); + CRGBPalette16 getGradientPalette(uint8_t index) { CRGBPalette16 palette; const byte* gpArray = gGradientPalettes[index]; @@ -590,6 +592,9 @@ CRGBPalette16 getGradientPalette(uint8_t index) { case 7: palette = RainbowStripeColors_p; break; + default: + palette = RainbowStripeColors_p; + break; } } else if (gpArray[1] == 2) { // MoonLight palettes switch (gpArray[2]) { @@ -607,7 +612,7 @@ CRGBPalette16 getGradientPalette(uint8_t index) { for (int i = 0; i < 16; i++) palette[i] = CRGB::Red; break; case 3: // Green - for (int i = 0; i < 16; i++) palette[i] = CRGB::Green; + for (int i = 0; i < 16; i++) palette[i] = CRGB(0, 255, 0); // CRGB::Green is CRGB(0, 128, 0) break; case 4: // Blue for (int i = 0; i < 16; i++) palette[i] = CRGB::Blue; @@ -629,6 +634,9 @@ CRGBPalette16 getGradientPalette(uint8_t index) { case 9: // Cold White for (int i = 0; i < 16; i++) palette[i] = CRGB::White; break; + default: // unknown MoonLight sub-palette β€” fall back to black + for (int i = 0; i < 16; i++) palette[i] = CRGB::Black; + break; } } } else { // gradient array palettes diff --git a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h index 0896b540..2f822951 100644 --- a/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h +++ b/src/MoonLight/Nodes/Drivers/D_ArtnetIn.h @@ -36,7 +36,7 @@ class ArtNetInDriver : public Node { addControl(layer, "layer", "select"); addControlValue("Physical layer"); uint8_t i = 1; // start with one - for (VirtualLayer* layer : layerP.layers) { + for (VirtualLayer* vLayer : layerP.layers) { Char<32> layerName; layerName.format("Layer %d", i); addControlValue(layerName.c_str()); diff --git a/src/MoonLight/Nodes/Drivers/D_AudioSync.h b/src/MoonLight/Nodes/Drivers/D_AudioSync.h index 704cec99..73053a7e 100644 --- a/src/MoonLight/Nodes/Drivers/D_AudioSync.h +++ b/src/MoonLight/Nodes/Drivers/D_AudioSync.h @@ -14,8 +14,6 @@ #include // https://github.com/netmindz/WLED-sync #include - #define audioPaletteIndex 18 // see palettes.h - class AudioSyncDriver : public Node { public: static const char* name() { return "Audio Sync"; } @@ -24,6 +22,7 @@ class AudioSyncDriver : public Node { WLEDSync sync; bool init = false; + static constexpr uint8_t audioPaletteIndex = 18; // see palettes.h void loop() override { if (!WiFi.isConnected() && !ETH.connected()) { diff --git a/src/MoonLight/Nodes/Effects/E_MoonLight.h b/src/MoonLight/Nodes/Effects/E_MoonLight.h index 9b0afa53..dd86410f 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonLight.h +++ b/src/MoonLight/Nodes/Effects/E_MoonLight.h @@ -1808,8 +1808,6 @@ class RadarEffect : public Node { layer->drawLine(W / 2, H / 2, x1, y1, ColorFromPalette(layerP.palette, (uint8_t)(physPos / physPerimeter * 255)), false); } } - - ~RadarEffect() override {}; // e,g, to free allocated memory }; #endif \ No newline at end of file diff --git a/src/MoonLight/Nodes/Effects/E_MovingHeads.h b/src/MoonLight/Nodes/Effects/E_MovingHeads.h index 2cbdeda6..739389c7 100644 --- a/src/MoonLight/Nodes/Effects/E_MovingHeads.h +++ b/src/MoonLight/Nodes/Effects/E_MovingHeads.h @@ -53,18 +53,18 @@ class Troy1MoveEffect : public Node { bool autoMove = true; bool audioReactive = true; bool invert = true; - int closestColorIndex = -1; - - std::vector colorwheelpalette = { - CRGB(255, 255, 255), // White - CRGB(255, 0, 0), // Red - CRGB(0, 255, 0), // Green - CRGB(0, 0, 255), // Blue - CRGB(255, 255, 0), // Yellow - CRGB(0, 255, 255), // Cyan - CRGB(255, 165, 0), // Orange - CRGB(128, 0, 128) // Purple - }; + // int closestColorIndex = -1; + + // std::vector colorwheelpalette = { + // CRGB(255, 255, 255), // White + // CRGB(255, 0, 0), // Red + // CRGB(0, 255, 0), // Green + // CRGB(0, 0, 255), // Blue + // CRGB(255, 255, 0), // Yellow + // CRGB(0, 255, 255), // Cyan + // CRGB(255, 165, 0), // Orange + // CRGB(128, 0, 128) // Purple + // }; void setup() override { addControl(bpm, "bpm", "slider"); @@ -74,29 +74,29 @@ class Troy1MoveEffect : public Node { addControl(colorwheel, "colorwheel", "slider", 0, 7); // 0-7 for 8 colors in the colorwheel addControl(colorwheelbrightness, "colorwheelbrightness", "slider"); // 0-7 for 8 colors in the colorwheel addControl(autoMove, "autoMove", "checkbox"); - addControl(range, "slider", "slider"); + addControl(range, "range", "slider"); addControl(audioReactive, "audioReactive", "checkbox"); addControl(invert, "invert", "checkbox"); } // Function to compute Euclidean distance between two colors - double colorDistance(const CRGB& c1, const CRGB& c2) { return std::sqrt(std::pow(c1.r - c2.r, 2) + std::pow(c1.g - c2.g, 2) + std::pow(c1.b - c2.b, 2)); } + // double colorDistance(const CRGB& c1, const CRGB& c2) { return std::sqrt(std::pow(c1.r - c2.r, 2) + std::pow(c1.g - c2.g, 2) + std::pow(c1.b - c2.b, 2)); } // Function to find the index of the closest color - int findClosestColorWheelIndex(const CRGB& inputColor, const std::vector& palette) { - int closestIndex = 0; - double minDistance = colorDistance(inputColor, palette[0]); - - for (size_t i = 1; i < palette.size(); ++i) { - double distance = colorDistance(inputColor, palette[i]); - if (distance < minDistance) { - minDistance = distance; - closestIndex = static_cast(i); - } - } + // int findClosestColorWheelIndex(const CRGB& inputColor, const std::vector& palette) { + // int closestIndex = 0; + // double minDistance = colorDistance(inputColor, palette[0]); - return closestIndex; - } + // for (size_t i = 1; i < palette.size(); ++i) { + // double distance = colorDistance(inputColor, palette[i]); + // if (distance < minDistance) { + // minDistance = distance; + // closestIndex = static_cast(i); + // } + // } + + // return closestIndex; + // } void loop() override { for (int x = 0; x < layer->size.x; x++) { // loop over lights defined in layout @@ -191,27 +191,28 @@ class Troy2MoveEffect : public Node { addControl(zoom, "zoom", "slider"); addControl(cutin, "cutin", "slider"); addControl(autoMove, "autoMove", "checkbox"); - addControl(range, "slider", "slider"); + addControl(range, "range", "slider"); addControl(audioReactive, "audioReactive", "checkbox"); addControl(invert, "invert", "checkbox"); } // Function to compute Euclidean distance between two colors - double colorDistance(const CRGB& c1, const CRGB& c2) { return std::sqrt(std::pow(c1.r - c2.r, 2) + std::pow(c1.g - c2.g, 2) + std::pow(c1.b - c2.b, 2)); } - - // Function to find the index of the closest color - int findClosestColorWheelIndex(const CRGB& inputColor, const std::vector& palette) { - int closestIndex = 0; - double minDistance = colorDistance(inputColor, palette[0]); - for (size_t i = 1; i < palette.size(); ++i) { - double distance = colorDistance(inputColor, palette[i]); - if (distance < minDistance) { - minDistance = distance; - closestIndex = static_cast(i); - } - } - return closestIndex; - } + // double colorDistance(const CRGB& c1, const CRGB& c2) { return std::sqrt(std::pow(c1.r - c2.r, 2) + std::pow(c1.g - c2.g, 2) + std::pow(c1.b - c2.b, 2)); } + + // // Function to find the index of the closest color + // int findClosestColorWheelIndex(const CRGB& inputColor, const std::vector& palette) { + // int closestIndex = 0; + // double minDistance = colorDistance(inputColor, palette[0]); + + // for (size_t i = 1; i < palette.size(); ++i) { + // double distance = colorDistance(inputColor, palette[i]); + // if (distance < minDistance) { + // minDistance = distance; + // closestIndex = static_cast(i); + // } + // } + // return closestIndex; + // } void loop() override { bool coolDownSet = false; @@ -307,7 +308,7 @@ class WowiMoveEffect : public Node { addControl(tilt, "tilt", "slider"); addControl(zoom, "zoom", "slider"); addControl(autoMove, "autoMove", "checkbox"); - addControl(range, "slider", "slider"); + addControl(range, "range", "slider"); addControl(invert, "invert", "checkbox"); } diff --git a/src/MoonLight/Nodes/Effects/E_WLED.h b/src/MoonLight/Nodes/Effects/E_WLED.h index f1bd0b2d..0e5b90a1 100644 --- a/src/MoonLight/Nodes/Effects/E_WLED.h +++ b/src/MoonLight/Nodes/Effects/E_WLED.h @@ -11,6 +11,14 @@ #if FT_MOONLIGHT + // #define MAX_FREQ_LOG10 4.04238f // log10(MAX_FREQUENCY) + #define MIN_FREQUENCY 80 // 80 HZ - due to lower resolution + #define MIN_FREQ_LOG10 1.90309f // log10(MIN_FREQUENCY) + #define MIN_FREQ_LOG 4.38202663467f // log(MIN_FREQUENCY) + #define MAX_FREQUENCY 11025 // sample frequency / 2 (as per Nyquist criterion) + #define MAX_FREQ_LOG10 4.04238f // log10(MAX_FREQUENCY) + #define MAX_FREQ_LOG 9.30792070f // log(MAX_FREQUENCY) + class BouncingBallsEffect : public Node { public: static const char* name() { return "Bouncing Balls"; } @@ -88,11 +96,10 @@ class BouncingBallsEffect : public Node { } }; - // original version from SR 0.13, with some enhancements by @softhack007 - // Blurz. By Andrew Tuline. - // Hint: Looks best with segment brightness set to max (use global brightness to reduce brightness) - // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment - #define MAX_FREQ_LOG10 4.04238f // log10(MAX_FREQUENCY) +// original version from SR 0.13, with some enhancements by @softhack007 +// Blurz. By Andrew Tuline. +// Hint: Looks best with segment brightness set to max (use global brightness to reduce brightness) +// even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment class BlurzEffect : public Node { public: static const char* name() { return "Blurz"; } @@ -222,8 +229,6 @@ class DistortionWavesEffect : public Node { } }; // DistortionWaves - #define MAX_FREQUENCY 11025 // sample frequency / 2 (as per Nyquist criterion) - class FreqMatrixEffect : public Node { public: static const char* name() { return "Freq Matrix"; } @@ -2561,11 +2566,12 @@ class BlinkRainbowEffect : public Node { } uint8_t colorIndex = 0; + uint32_t lastCycle = 0; void loop() override { uint16_t dutyCycle = blinkDuration + 1; uint16_t onTime = (dutyCycle * frequency) >> 8; - uint16_t offTime = dutyCycle - onTime; + // uint16_t offTime = dutyCycle - onTime; unsigned long timebase = millis(); bool on = (timebase % dutyCycle) < onTime; @@ -2577,9 +2583,11 @@ class BlinkRainbowEffect : public Node { layer->fill_solid(CRGB::Black); } - // Change color every cycle - if ((timebase % dutyCycle) == 0) { + // Advance color exactly once per complete cycle + uint32_t currentCycle = timebase / dutyCycle; + if (currentCycle != lastCycle) { colorIndex += 32; + lastCycle = currentCycle; } } }; @@ -2604,7 +2612,7 @@ class MeteorEffect : public Node { uint8_t* trailData = nullptr; size_t trailDataSize = 0; - uint16_t step = 0; + // uint16_t step = 0; ~MeteorEffect() override { if (trailData) freeMB(trailData, "trailData"); @@ -2653,7 +2661,7 @@ class MeteorEffect : public Node { layer->setRGB(index, col); } - step += speed + 1; + // step += speed + 1; } }; @@ -2672,8 +2680,8 @@ class OscillateEffect : public Node { } struct Oscillator { - int16_t pos; - int8_t size; + nrOfLights_t pos; + nrOfLights_t size; int8_t dir; int8_t oscSpeed; // Renamed from 'speed' to avoid shadowing }; @@ -2685,9 +2693,9 @@ class OscillateEffect : public Node { void onSizeChanged(const Coord3D& prevSize) override { // Initialize oscillators when layer size is known if (layer->nrOfLights > 0) { - oscillators[0] = {(int16_t)(layer->nrOfLights / 4), (int8_t)(layer->nrOfLights / 8), 1, 1}; - oscillators[1] = {(int16_t)(layer->nrOfLights / 4 * 3), (int8_t)(layer->nrOfLights / 8), 1, 2}; - oscillators[2] = {(int16_t)(layer->nrOfLights / 4 * 2), (int8_t)(layer->nrOfLights / 8), -1, 1}; + oscillators[0] = {(nrOfLights_t)(layer->nrOfLights / 4), (nrOfLights_t)(layer->nrOfLights / 8), 1, 1}; + oscillators[1] = {(nrOfLights_t)(layer->nrOfLights / 4 * 3), (nrOfLights_t)(layer->nrOfLights / 8), 1, 2}; + oscillators[2] = {(nrOfLights_t)(layer->nrOfLights / 4 * 2), (nrOfLights_t)(layer->nrOfLights / 8), -1, 1}; initialized = true; lastUpdate = 0xFFFFFFFF; // Force update on first frame } @@ -2762,7 +2770,7 @@ class PhasedNoiseEffect : public Node { for (int i = 0; i < layer->nrOfLights; i++) { // Add noise modulation - uint8_t modVal = (inoise8(i * 10 + i * 10) / 16); + uint8_t modVal = (inoise8(i * 10, i * 10) / 16); if (modVal == 0) modVal = 1; uint16_t val = (i + 1) * allfreq; @@ -2780,4 +2788,789 @@ class PhasedNoiseEffect : public Node { } }; +class FreqmapEffect : public Node { + public: + static const char* name() { return "Freqmap"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + void loop() override { + int fadeoutDelay = (256 - speed) / 96; + if ((fadeoutDelay <= 1) || ((millis() % fadeoutDelay) == 0)) layer->fadeToBlackBy(speed); + + float myMajorPeak = MAX(sharedData.majorPeak, 1.0f); + float myMagnitude = sharedData.volume / 4.0f; + + int locn = roundf((log10f(myMajorPeak) - 1.78f) * (float)layer->nrOfLights / (MAX_FREQ_LOG10 - 1.78f)); + if (locn < 1) locn = 0; + if (locn >= layer->nrOfLights) locn = layer->nrOfLights - 1; + + uint16_t pixCol = (log10f(myMajorPeak) - 1.78f) * 255.0f / (MAX_FREQ_LOG10 - 1.78f); + if (myMajorPeak < 61.0f) pixCol = 0; + + uint16_t bright = (int)(sqrtf(myMagnitude) * 16.0f); + + CRGB color = ColorFromPalette(layerP.palette, intensity + pixCol); + layer->setRGB(locn, color); // blend(layerP.color2, color, bright)); + + if (speed > 228) { + layer->blur2d(5 * (speed - 224)); + layer->setRGB(locn, color); // blend(layerP.color2, color, bright)); + } + } +}; + +class FreqpixelsEffect : public Node { + public: + static const char* name() { return "Freqpixels"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + void loop() override { + uint16_t fadeRate = 2 * speed - speed * speed / 255; + + int fadeoutDelay = (256 - speed) / 64; + if ((fadeoutDelay <= 1) || ((millis() % fadeoutDelay) == 0)) layer->fadeToBlackBy(fadeRate); + + float myMajorPeak = MAX(sharedData.majorPeak, 1.0f); + float myMagnitude = sharedData.volume / 16.0f; + + for (int i = 0; i < intensity / 32 + 1; i++) { + uint16_t locn = random16(0, layer->nrOfLights); + uint8_t pixCol = (log10f(myMajorPeak) - 1.78f) * 255.0f / (MAX_FREQ_LOG10 - 1.78f); + if (myMajorPeak < 61.0f) pixCol = 0; + + CRGB color = ColorFromPalette(layerP.palette, intensity + pixCol); + layer->setRGB(locn, color); // blend(layerP.color2, color, (int)myMagnitude)); + } + } +}; + +static float mapf(float x, float in_min, float in_max, float out_min, float out_max) { + if (in_max == in_min) return (out_min); // WLEDMM avoid div/0 + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +class FreqwaveEffect : public Node { + public: + static const char* name() { return "Freqwave"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t speed = 128; + uint8_t lowBin = 3; + uint8_t highBin = 42; + bool musicalScale = false; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(lowBin, "lowBin", "slider"); + addControl(highBin, "highBin", "slider"); + addControl(musicalScale, "musicalScale", "checkbox"); + } + + uint8_t secondHand = 0; + + void loop() override { + uint8_t newSecondHand = (speed < 255) ? (micros() / (256 - speed) / 500 % 16) : 0; + if ((speed > 254) || (secondHand != newSecondHand)) { + secondHand = newSecondHand; + + float sensitivity = 0.5f * map(31, 1, 31, 0.5, 10); + float pixVal = sharedData.volume * (float)255 / 256.0f * sensitivity; + if (pixVal > 255) pixVal = 255; + + float intensity = mapf(pixVal, 0, 255, 0, 100) / 100.0f; + CRGB color = CRGB::Black; + + if ((sharedData.majorPeak > 80.0f) && (sharedData.volume > 0.25f) && (sharedData.majorPeak < 10800.0f)) { + uint8_t i = 0; + if (!musicalScale) { + int upperLimit = 80 + 42 * highBin; + int lowerLimit = 80 + 3 * lowBin; + i = (lowerLimit != upperLimit) ? mapf(sharedData.majorPeak, lowerLimit, upperLimit, 0, 255) : sharedData.majorPeak; + } else { + float upperLimit = logf(80 + 42 * highBin); + float lowerLimit = logf(80 + 3 * lowBin); + float peakMapped = fabsf(lowerLimit - upperLimit) > 0.05f ? mapf(logf(sharedData.majorPeak), lowerLimit, upperLimit, 0, 255) : sharedData.majorPeak; + if (peakMapped > 255) intensity = constrain((320 - peakMapped), 0, intensity * 100) / 100.0f; + i = constrain(peakMapped, 0, 255); + } + uint16_t b = 255.0f * intensity; + if (b > 255) b = 255; + color = CHSV(i, 176 + (uint8_t)b / 4, (uint8_t)b); + } + + layer->setRGB(layer->nrOfLights / 2, color); + + // Shift pixels outward + for (int i = layer->nrOfLights - 1; i > layer->nrOfLights / 2; i--) layer->setRGB(i, layer->getRGB(i - 1)); + for (int i = 0; i < layer->nrOfLights / 2; i++) layer->setRGB(i, layer->getRGB(i + 1)); + } + } +}; + +// Shared Gravity struct +struct Gravity { + int16_t topLED = 0; + uint8_t gravityCounter = 0; +}; + +class GravfreqEffect : public Node { + public: + static const char* name() { return "Gravfreq"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + Gravity* gravData = nullptr; + size_t gravDataSize = 0; + uint8_t lastColorIndex = 0; + + ~GravfreqEffect() override { + if (gravData) freeMB(gravData, "gravData"); + } + + void onSizeChanged(const Coord3D& prevSize) override { + reallocMB2(gravData, gravDataSize, 1, "gravData"); + if (gravData) { + gravData->topLED = 0; + gravData->gravityCounter = 0; + } + } + + void loop() override { + if (!gravData) return; + + layer->fadeToBlackBy(96); + + float segmentSampleAvg = sharedData.volume * (float)intensity / 255.0f * 0.125f; + float mySampleAvg = mapf(segmentSampleAvg * 2.0f, 0, 32, 0, (float)layer->nrOfLights / 2.0f); + int tempsamp = constrain(mySampleAvg, 0, layer->nrOfLights / 2); + uint8_t gravity = 8 - speed / 32; + + float myMajorPeak = MAX(sharedData.majorPeak, 1.0f); + int indexNew = (logf(myMajorPeak) - MIN_FREQ_LOG) * 255.0f / (MAX_FREQ_LOG - MIN_FREQ_LOG); + indexNew = constrain(indexNew, 0, 255); + int palIndex = (indexNew + lastColorIndex) / 2; + + for (int i = 0; i < tempsamp; i++) { + CRGB color = ColorFromPalette(layerP.palette, (uint8_t)palIndex); + layer->setRGB(i + layer->nrOfLights / 2, color); + layer->setRGB(layer->nrOfLights / 2 - i - 1, color); + } + + if (tempsamp >= gravData->topLED) + gravData->topLED = tempsamp - 1; + else if (gravData->gravityCounter % gravity == 0) + gravData->topLED--; + + if ((gravData->topLED >= 0) && (speed < 255)) { + layer->setRGB(gravData->topLED + layer->nrOfLights / 2, CRGB::Gray); + layer->setRGB(layer->nrOfLights / 2 - 1 - gravData->topLED, CRGB::Gray); + } + gravData->gravityCounter = (gravData->gravityCounter + 1) % gravity; + lastColorIndex = indexNew; + } +}; + +class GravimeterEffect : public Node { + public: + static const char* name() { return "Gravimeter"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + bool reverse = false; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + addControl(reverse, "reverse", "checkbox"); + } + + Gravity* gravData = nullptr; + size_t gravDataSize = 0; + + ~GravimeterEffect() override { + if (gravData) freeMB(gravData, "gravData"); + } + + void onSizeChanged(const Coord3D& prevSize) override { + reallocMB2(gravData, gravDataSize, 1, "gravData"); + if (gravData) { + gravData->topLED = 0; + gravData->gravityCounter = 0; + } + } + + void loop() override { + if (!gravData) return; + + layer->fadeToBlackBy(253); + + float sensGain = (float)(intensity + 2) / 257.0f; + if (sensGain > 0.5f) sensGain = ((sensGain - 0.5f) * 3.0f) + 0.5f; + + float segmentSampleAvg = sharedData.volume * sensGain * 0.25f; + float mySampleAvg = mapf(segmentSampleAvg * 2.0f, 0, 64, 0, layer->nrOfLights - 1); + int tempsamp = constrain(mySampleAvg, 0, layer->nrOfLights - 1); + uint8_t gravity = 8 - speed / 32; + + int blendVal = reverse ? 255 - roundf(segmentSampleAvg * 6.5f) : roundf(segmentSampleAvg * 8.0f); + blendVal = constrain(blendVal, 32, 255); + + if (sharedData.volume > 0.85f) { + for (int i = 0; i < tempsamp; i++) { + uint8_t index = inoise8(i * segmentSampleAvg + millis(), 5000 + i * segmentSampleAvg); + CRGB color = ColorFromPalette(layerP.palette, index, (uint8_t)blendVal); + layer->setRGB(i, color); // blend(layerP.color2, color, (uint8_t)blendVal)); + } + } + + if (tempsamp >= gravData->topLED) + gravData->topLED = tempsamp; + else if (gravData->gravityCounter % gravity == 0) + gravData->topLED--; + + if ((gravData->topLED > 0) && (speed < 255)) { + CRGB color = ColorFromPalette(layerP.palette, MAX(uint16_t(millis() / 2), (uint16_t)2)); + layer->setRGB(gravData->topLED, color); + } + gravData->gravityCounter = (gravData->gravityCounter + 1) % gravity; + } +}; + +class GravcenterEffect : public Node { + public: + static const char* name() { return "Gravcenter"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + Gravity* gravData = nullptr; + size_t gravDataSize = 0; + + ~GravcenterEffect() override { + if (gravData) freeMB(gravData, "gravData"); + } + + void onSizeChanged(const Coord3D& prevSize) override { + reallocMB2(gravData, gravDataSize, 1, "gravData"); + if (gravData) { + gravData->topLED = 0; + gravData->gravityCounter = 0; + } + } + + void loop() override { + if (!gravData) return; + + layer->fadeToBlackBy(251); + + float segmentSampleAvg = sharedData.volume * (float)intensity / 255.0f * 0.125f; + float mySampleAvg = mapf(segmentSampleAvg * 2.0f, 0, 32, 0, (float)layer->nrOfLights / 2.0f); + uint16_t tempsamp = constrain(mySampleAvg, 0, layer->nrOfLights / 2); + uint8_t gravity = 8 - speed / 32; + + for (int i = 0; i < tempsamp; i++) { + uint8_t index = inoise8(i * segmentSampleAvg + millis(), 5000 + i * segmentSampleAvg); + CRGB color = ColorFromPalette(layerP.palette, index, segmentSampleAvg * 8); + // color = blend(layerP.color2, color, segmentSampleAvg * 8); + layer->setRGB(i + layer->nrOfLights / 2, color); + layer->setRGB(layer->nrOfLights / 2 - i - 1, color); + } + + if (tempsamp >= gravData->topLED) + gravData->topLED = tempsamp - 1; + else if (gravData->gravityCounter % gravity == 0) + gravData->topLED--; + + if (gravData->topLED >= 0) { + CRGB color = ColorFromPalette(layerP.palette, millis() / 4); + layer->setRGB(gravData->topLED + layer->nrOfLights / 2, color); + layer->setRGB(layer->nrOfLights / 2 - 1 - gravData->topLED, color); + } + gravData->gravityCounter = (gravData->gravityCounter + 1) % gravity; + } +}; + +class GravcentricEffect : public Node { + public: + static const char* name() { return "Gravcentric"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + Gravity* gravData = nullptr; + size_t gravDataSize = 0; + + ~GravcentricEffect() override { + if (gravData) freeMB(gravData, "gravData"); + } + + void onSizeChanged(const Coord3D& prevSize) override { + reallocMB2(gravData, gravDataSize, 1, "gravData"); + if (gravData) { + gravData->topLED = 0; + gravData->gravityCounter = 0; + } + } + + void loop() override { + if (!gravData) return; + + layer->fadeToBlackBy(253); + + float segmentSampleAvg = sharedData.volume * (float)intensity / 255.0f * 0.125f; + float mySampleAvg = mapf(segmentSampleAvg * 2.0f, 0.0f, 32.0f, 0.0f, (float)layer->nrOfLights / 2.0f); + int tempsamp = constrain(mySampleAvg, 0, layer->nrOfLights / 2); + uint8_t gravity = 8 - speed / 32; + + for (int i = 0; i < tempsamp; i++) { + uint8_t index = segmentSampleAvg * 24 + millis() / 200; + CRGB color = ColorFromPalette(layerP.palette, index); + layer->setRGB(i + layer->nrOfLights / 2, color); + layer->setRGB(layer->nrOfLights / 2 - 1 - i, color); + } + + if (tempsamp >= gravData->topLED) + gravData->topLED = tempsamp - 1; + else if (gravData->gravityCounter % gravity == 0) + gravData->topLED--; + + if (gravData->topLED >= 0) { + layer->setRGB(gravData->topLED + layer->nrOfLights / 2, CRGB::Gray); + layer->setRGB(layer->nrOfLights / 2 - 1 - gravData->topLED, CRGB::Gray); + } + gravData->gravityCounter = (gravData->gravityCounter + 1) % gravity; + } +}; + +class MidnoiseEffect : public Node { + public: + static const char* name() { return "Midnoise"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + uint16_t xdist = 0; + uint16_t ydist = 0; + + void loop() override { + layer->fadeToBlackBy(speed / 2); + + float tmpSound2 = sharedData.volume * (float)intensity / 256.0f * (float)intensity / 128.0f; + int maxLen = mapf(tmpSound2, 0, 127, 0, layer->nrOfLights / 2); + if (maxLen > layer->nrOfLights / 2) maxLen = layer->nrOfLights / 2; + + for (int i = (layer->nrOfLights / 2 - maxLen); i < (layer->nrOfLights / 2 + maxLen); i++) { + uint8_t index = inoise8(i * sharedData.volume + xdist, ydist + i * sharedData.volume); + layer->setRGB(i, ColorFromPalette(layerP.palette, index)); + } + + xdist = xdist + beatsin8(5, 0, 10); + ydist = ydist + beatsin8(4, 0, 10); + } +}; + +class NoisemoveEffect : public Node { + public: + static const char* name() { return "Noisemove"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + unsigned long call = 0; + + void loop() override { + int fadeoutDelay = (256 - speed) / 96; + if ((fadeoutDelay <= 1) || ((call % fadeoutDelay) == 0)) layer->fadeToBlackBy(4 + speed / 4); + + uint8_t numBins = ::map(intensity, 0, 255, 0, 16); + for (int i = 0; i < numBins; i++) { + uint16_t locn = inoise16(millis() * speed + i * 50000, millis() * speed); + locn = ::map(locn, 7500, 58000, 0, layer->nrOfLights - 1); + CRGB color = ColorFromPalette(layerP.palette, i * 64); + // color = blend(layerP.color2, color, sharedData.bands[i % 16] * 4); + layer->setRGB(locn, color); + } + call++; + } +}; + +class NoisefireEffect : public Node { + public: + static const char* name() { return "Noisefire"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ”₯"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + void loop() override { + CRGBPalette16 myPal = CRGBPalette16(CHSV(0, 255, 2), CHSV(0, 255, 4), CHSV(0, 255, 8), CHSV(0, 255, 8), CHSV(0, 255, 16), CRGB::Red, CRGB::Red, CRGB::Red, CRGB::DarkOrange, CRGB::DarkOrange, CRGB::Orange, CRGB::Orange, CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow); + + for (int i = 0; i < layer->nrOfLights; i++) { + uint16_t index = inoise8(i * speed / 64, millis() * speed / 64 * layer->nrOfLights / 255); + index = (255 - i * 256 / layer->nrOfLights) * index / (256 - intensity); + + CRGB color = ColorFromPalette(myPal, index, sharedData.volume * 2, LINEARBLEND); + layer->setRGB(i, color); + } + } +}; + +class PixelwaveEffect : public Node { + public: + static const char* name() { return "Pixelwave"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + uint8_t secondHand = 0; + + void loop() override { + uint8_t newSecondHand = micros() / (256 - speed) / 500 + 1 % 16; + if ((speed > 254) || (secondHand != newSecondHand)) { + secondHand = newSecondHand; + + float rawPixel = (float)sharedData.volumeRaw; + rawPixel = rawPixel * rawPixel / 256.0f; + int pixBri = rawPixel * (intensity + 1) / 96; + + CRGB color = ColorFromPalette(layerP.palette, millis() / 5, pixBri); + // color = blend(layerP.color2, color, pixBri); + layer->setRGB(layer->nrOfLights / 2, color); + + for (int i = layer->nrOfLights - 1; i > layer->nrOfLights / 2; i--) layer->setRGB(i, layer->getRGB(i - 1)); + for (int i = 0; i < layer->nrOfLights / 2; i++) layer->setRGB(i, layer->getRGB(i + 1)); + } + } +}; + +struct Plasphase { + int8_t thisphase = 0; + int8_t thatphase = 0; +}; + +class PlasmoidEffect : public Node { + public: + static const char* name() { return "Plasmoid"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + Plasphase* phaseData = nullptr; + size_t phaseDataSize = 0; + + ~PlasmoidEffect() override { + if (phaseData) freeMB(phaseData, "phaseData"); + } + + void onSizeChanged(const Coord3D& prevSize) override { + reallocMB2(phaseData, phaseDataSize, 1, "phaseData"); + if (phaseData) { + phaseData->thisphase = 0; + phaseData->thatphase = 0; + } + } + + void loop() override { + if (!phaseData) return; + + layer->fadeToBlackBy(48); + + phaseData->thisphase += beatsin8(6, -4, 4); + phaseData->thatphase += beatsin8(7, -4, 4); + + for (int i = 0; i < layer->nrOfLights; i++) { + uint8_t thisbright = cubicwave8(((i * (1 + (3 * speed / 32))) + phaseData->thisphase) & 0xFF) / 2; + thisbright += cos8(((i * (97 + (5 * speed / 32))) + phaseData->thatphase) & 0xFF) / 2; + + uint8_t colorIndex = thisbright; + if (sharedData.volume * intensity / 64 < thisbright) { + thisbright = 0; + } + + CRGB color = ColorFromPalette(layerP.palette, colorIndex, thisbright); + layer->addRGB(i, color); // blend(layerP.color2, color, thisbright)); + } + } +}; + +class PuddlepeakEffect : public Node { + public: + static const char* name() { return "Puddlepeak"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + void loop() override { + uint8_t fadeVal = ::map(speed, 0, 255, 224, 254); + layer->fadeToBlackBy(fadeVal); + + // Note: samplePeak not in SharedData - using volume threshold instead + bool peak = sharedData.volume > 128.0f; + uint16_t size = 0; + uint16_t pos = random16(layer->nrOfLights); + + if (peak) { + size = sharedData.volume * intensity / 256 / 4 + 1; + if (pos + size >= layer->nrOfLights) size = layer->nrOfLights - pos; + } + + for (int i = 0; i < size; i++) { + layer->setRGB(pos + i, ColorFromPalette(layerP.palette, millis() / 4)); + } + } +}; + +class PuddlesEffect : public Node { + public: + static const char* name() { return "Puddles"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + void loop() override { + uint8_t fadeVal = ::map(speed, 0, 255, 224, 254); + layer->fadeToBlackBy(fadeVal); + + uint16_t size = 0; + uint16_t pos = random16(layer->nrOfLights); + + if (sharedData.volumeRaw > 1) { + size = sharedData.volumeRaw * intensity / 256 / 8 + 1; + if (pos + size >= layer->nrOfLights) size = layer->nrOfLights - pos; + } + + for (int i = 0; i < size; i++) { + layer->setRGB(pos + i, ColorFromPalette(layerP.palette, millis() / 4)); + } + } +}; + +struct Ripple { + uint16_t pos = 0; + uint8_t color = 0; + uint8_t state = 254; +}; + +class RipplepeakEffect : public Node { + public: + static const char* name() { return "Ripple Peak"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t intensity = 128; + + void setup() override { addControl(intensity, "intensity", "slider"); } + + Ripple* ripples = nullptr; + size_t ripplesSize = 0; + + ~RipplepeakEffect() override { + if (ripples) freeMB(ripples, "ripples"); + } + + void onSizeChanged(const Coord3D& prevSize) override { reallocMB2(ripples, ripplesSize, 16, "ripples"); } + + void loop() override { + if (!ripples) return; + + layer->fadeToBlackBy(224); + + bool peak = sharedData.volume > 128.0f; + + for (int i = 0; i < intensity / 16; i++) { + if (peak) ripples[i].state = 255; + + switch (ripples[i].state) { + case 254: + break; + case 255: + ripples[i].pos = random16(layer->nrOfLights); + if (sharedData.majorPeak > 1.0f) + ripples[i].color = (int)(logf(sharedData.majorPeak) * 32.0f); + else + ripples[i].color = 0; + ripples[i].state = 0; + break; + case 0: + layer->setRGB(ripples[i].pos, ColorFromPalette(layerP.palette, ripples[i].color)); // blend(layerP.color2, ColorFromPalette(layerP.palette, ripples[i].color), brightness)); + ripples[i].state++; + break; + case 16: + ripples[i].state = 254; + break; + default: + layer->setRGB((ripples[i].pos + ripples[i].state + layer->nrOfLights) % layer->nrOfLights, ColorFromPalette(layerP.palette, ripples[i].color)); // blend(layerP.color2, ColorFromPalette(layerP.palette, ripples[i].color), brightness / ripples[i].state * 2)); + layer->setRGB((ripples[i].pos - ripples[i].state + layer->nrOfLights) % layer->nrOfLights, ColorFromPalette(layerP.palette, ripples[i].color)); // blend(layerP.color2, ColorFromPalette(layerP.palette, ripples[i].color), brightness / ripples[i].state * 2)); + ripples[i].state++; + break; + } + } + } +}; + +class RocktavesEffect : public Node { + public: + static const char* name() { return "Rocktaves"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + void setup() override {} + + void loop() override { + layer->fadeToBlackBy(16); + + float frTemp = sharedData.majorPeak; + uint8_t octCount = 0; + uint8_t volTemp = 0; + + float myMagnitude = sharedData.volume / 16.0f; + volTemp = 32.0f + myMagnitude * 1.5f; + if (myMagnitude < 48) volTemp = 0; + if (myMagnitude > 144) volTemp = 255; + + while (frTemp > 249) { + octCount++; + frTemp = frTemp / 2; + } + + frTemp -= 132; + frTemp = fabsf(frTemp * 2.1f); + + uint16_t i = ::map(beatsin8(8 + octCount * 4, 0, 255, 0, octCount * 8), 0, 255, 0, layer->nrOfLights - 1); + i = constrain(i, 0, layer->nrOfLights - 1); + layer->addRGB(i, ColorFromPalette(layerP.palette, (uint8_t)frTemp)); // blend(layerP.color2, ColorFromPalette(layerP.palette, (uint8_t)frTemp), volTemp)); + } +}; + +class WaterfallEffect : public Node { + public: + static const char* name() { return "Waterfall"; } + static uint8_t dim() { return _1D; } + static const char* tags() { return "πŸ™β™ͺ"; } + + uint8_t speed = 128; + uint8_t intensity = 128; + + void setup() override { + addControl(speed, "speed", "slider"); + addControl(intensity, "intensity", "slider"); + } + + uint8_t secondHand = 0; + + void loop() override { + uint8_t newSecondHand = micros() / (256 - speed) / 500 + 1 % 16; + if ((speed > 254) || (secondHand != newSecondHand)) { + secondHand = newSecondHand; + + float myMajorPeak = MAX(sharedData.majorPeak, 1.0f); + float myMagnitude = sharedData.volume / 8.0f; + + uint8_t pixCol = (log10f(myMajorPeak) - 2.26f) * 150; + if (myMajorPeak < 182.0f) pixCol = 0; + + for (int i = 0; i < layer->nrOfLights - 1; i++) layer->setRGB(i, layer->getRGB(i + 1)); + + bool peak = sharedData.volume > 128.0f; + if (peak) { + layer->setRGB(layer->nrOfLights - 1, CHSV(92, 92, 92)); + } else { + CRGB color = ColorFromPalette(layerP.palette, pixCol + intensity, 127 + myMagnitude / 2.0); + layer->setRGB(layer->nrOfLights - 1, color); // blend(layerP.color2, color, (int)myMagnitude)); + } + } + } +}; + #endif \ No newline at end of file From ac4760b0afc8d77d841f538c978b52c7a8bb5041 Mon Sep 17 00:00:00 2001 From: ewowi Date: Fri, 20 Feb 2026 15:37:57 +0100 Subject: [PATCH 6/6] Add block modifier, effect and palette tweaks backend ======= - effect moves, renames - palette purple fix - Add block modifier --- src/MoonLight/Modules/ModuleEffects.h | 18 +++++----- src/MoonLight/Modules/palettes.h | 6 ++-- src/MoonLight/Nodes/Effects/E_MoonLight.h | 8 ++--- src/MoonLight/Nodes/Effects/E_MovingHeads.h | 15 ++++---- src/MoonLight/Nodes/Effects/E_WLED.h | 36 +++++++++---------- src/MoonLight/Nodes/Modifiers/M_MoonLight.h | 39 +++++++++++++++++++++ 6 files changed, 81 insertions(+), 41 deletions(-) diff --git a/src/MoonLight/Modules/ModuleEffects.h b/src/MoonLight/Modules/ModuleEffects.h index cda7ab59..e21cbba9 100644 --- a/src/MoonLight/Modules/ModuleEffects.h +++ b/src/MoonLight/Modules/ModuleEffects.h @@ -127,7 +127,6 @@ class ModuleEffects : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); @@ -138,7 +137,8 @@ class ModuleEffects : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); @@ -151,6 +151,7 @@ class ModuleEffects : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); @@ -158,8 +159,7 @@ class ModuleEffects : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); @@ -185,6 +185,7 @@ class ModuleEffects : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); @@ -249,7 +250,6 @@ class ModuleEffects : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); @@ -258,7 +258,8 @@ class ModuleEffects : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); @@ -271,6 +272,7 @@ class ModuleEffects : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); @@ -278,8 +280,7 @@ class ModuleEffects : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); @@ -307,6 +308,7 @@ class ModuleEffects : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); diff --git a/src/MoonLight/Modules/palettes.h b/src/MoonLight/Modules/palettes.h index 016c1705..5f90e553 100644 --- a/src/MoonLight/Modules/palettes.h +++ b/src/MoonLight/Modules/palettes.h @@ -612,7 +612,8 @@ CRGBPalette16 getGradientPalette(uint8_t index) { for (int i = 0; i < 16; i++) palette[i] = CRGB::Red; break; case 3: // Green - for (int i = 0; i < 16; i++) palette[i] = CRGB(0, 255, 0); // CRGB::Green is CRGB(0, 128, 0) + for (int i = 0; i < 16; i++) palette[i] = CRGB(0, 255, 0); + // CRGB::Green is CRGB(0, 128, 0) break; case 4: // Blue for (int i = 0; i < 16; i++) palette[i] = CRGB::Blue; @@ -622,7 +623,8 @@ CRGBPalette16 getGradientPalette(uint8_t index) { // CRGB::Orange is too yellow break; case 6: // Purple - for (int i = 0; i < 16; i++) palette[i] = CRGB::Purple; + for (int i = 0; i < 16; i++) palette[i] = CRGB(128, 0, 255); + // CRGB::Purple is CRGB(128,0,128) β€” half brightness; use a full-intensity violet instead break; case 7: // Cyan for (int i = 0; i < 16; i++) palette[i] = CRGB::Cyan; diff --git a/src/MoonLight/Nodes/Effects/E_MoonLight.h b/src/MoonLight/Nodes/Effects/E_MoonLight.h index dd86410f..0f733946 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonLight.h +++ b/src/MoonLight/Nodes/Effects/E_MoonLight.h @@ -1597,7 +1597,7 @@ class VUMeterEffect : public Node { class PixelMapEffect : public Node { public: - static const char* name() { return "PixelMap"; } + static const char* name() { return "Pixel Map"; } static uint8_t dim() { return _3D; } static const char* tags() { return "πŸ”₯"; } @@ -1614,7 +1614,7 @@ class PixelMapEffect : public Node { class MarioTestEffect : public Node { public: - static const char* name() { return "MarioTest"; } + static const char* name() { return "Mario Test"; } static uint8_t dim() { return _2D; } static const char* tags() { return "πŸ”₯"; } @@ -1670,7 +1670,7 @@ class RingEffect : public Node { class RingRandomFlowEffect : public RingEffect { public: - static const char* name() { return "RingRandomFlow"; } + static const char* name() { return "Ring Random Flow"; } static uint8_t dim() { return _1D; } static const char* tags() { return "πŸ”₯"; } @@ -1702,7 +1702,7 @@ class RingRandomFlowEffect : public RingEffect { // by netmindz class AudioRingsEffect : public RingEffect { public: - static const char* name() { return "AudioRings"; } + static const char* name() { return "Audio Rings"; } static uint8_t dim() { return _1D; } static const char* tags() { return "πŸ”₯β™«"; } diff --git a/src/MoonLight/Nodes/Effects/E_MovingHeads.h b/src/MoonLight/Nodes/Effects/E_MovingHeads.h index 739389c7..e6c0204a 100644 --- a/src/MoonLight/Nodes/Effects/E_MovingHeads.h +++ b/src/MoonLight/Nodes/Effects/E_MovingHeads.h @@ -74,7 +74,7 @@ class Troy1MoveEffect : public Node { addControl(colorwheel, "colorwheel", "slider", 0, 7); // 0-7 for 8 colors in the colorwheel addControl(colorwheelbrightness, "colorwheelbrightness", "slider"); // 0-7 for 8 colors in the colorwheel addControl(autoMove, "autoMove", "checkbox"); - addControl(range, "range", "slider"); + addControl(range, "range", "slider", 0, 127); addControl(audioReactive, "audioReactive", "checkbox"); addControl(invert, "invert", "checkbox"); } @@ -147,11 +147,8 @@ class Troy2ColorEffect : public Node { if (audioReactive) { layer->setRGB(x, CRGB(sharedData.bands[NUM_GEQ_CHANNELS - 1] > cutin ? sharedData.bands[NUM_GEQ_CHANNELS - 1] : 0, sharedData.bands[7] > cutin ? sharedData.bands[7] : 0, sharedData.bands[0])); layer->setRGB1(x, CRGB(sharedData.bands[NUM_GEQ_CHANNELS - 1], sharedData.bands[7] > cutin ? sharedData.bands[7] : 0, sharedData.bands[0] > cutin ? sharedData.bands[0] : 0)); - layer->setRGB2(x, - CRGB(sharedData.bands[NUM_GEQ_CHANNELS - 1] > cutin ? sharedData.bands[NUM_GEQ_CHANNELS - 1] : 0, sharedData.bands[7], sharedData.bands[0] > cutin ? sharedData.bands[0] : 0)); - layer->setRGB3( - x, CRGB(sharedData.bands[NUM_GEQ_CHANNELS - 1] > cutin ? ::map(sharedData.bands[NUM_GEQ_CHANNELS - 1], cutin - 1, 255, 0, 255) : 0, - sharedData.bands[7] > cutin ? ::map(sharedData.bands[7], cutin - 1, 255, 0, 255) : 0, sharedData.bands[0] > cutin ? ::map(sharedData.bands[0], cutin - 1, 255, 0, 255) : 0)); + layer->setRGB2(x, CRGB(sharedData.bands[NUM_GEQ_CHANNELS - 1] > cutin ? sharedData.bands[NUM_GEQ_CHANNELS - 1] : 0, sharedData.bands[7], sharedData.bands[0] > cutin ? sharedData.bands[0] : 0)); + layer->setRGB3(x, CRGB(sharedData.bands[NUM_GEQ_CHANNELS - 1] > cutin ? ::map(sharedData.bands[NUM_GEQ_CHANNELS - 1], cutin - 1, 255, 0, 255) : 0, sharedData.bands[7] > cutin ? ::map(sharedData.bands[7], cutin - 1, 255, 0, 255) : 0, sharedData.bands[0] > cutin ? ::map(sharedData.bands[0], cutin - 1, 255, 0, 255) : 0)); // layer->setZoom(x, (sharedData.bands[0]>cutin)?255:0); if (sharedData.bands[0] + sharedData.bands[7] + sharedData.bands[NUM_GEQ_CHANNELS - 1] > 1) { layer->setBrightness(x, 255); @@ -275,7 +272,7 @@ class FreqColorsEffect : public Node { layer->setRGB1({x, 0, 0}, ColorFromPalette(layerP.palette, (x + 3) * delta, sharedData.bands[(x * 3 + 1) % 16])); layer->setRGB2({x, 0, 0}, ColorFromPalette(layerP.palette, (x + 6) * delta, sharedData.bands[(x * 3 + 2) % 16])); } else { - if (x == beatsin8(bpm, 0, layer->size.x - 1)) { // sinelon over moving heads + if (x == beatsin8(bpm, 0, layer->size.x - 1)) { // sinelon over moving heads layer->setRGB({x, 0, 0}, ColorFromPalette(layerP.palette, beatsin8(10))); // colorwheel 10 times per minute layer->setRGB1({x, 0, 0}, ColorFromPalette(layerP.palette, beatsin8(10))); // colorwheel 10 times per minute layer->setRGB2({x, 0, 0}, ColorFromPalette(layerP.palette, beatsin8(10))); // colorwheel 10 times per minute @@ -308,7 +305,7 @@ class WowiMoveEffect : public Node { addControl(tilt, "tilt", "slider"); addControl(zoom, "zoom", "slider"); addControl(autoMove, "autoMove", "checkbox"); - addControl(range, "range", "slider"); + addControl(range, "range", "slider"), 0, 127; addControl(invert, "invert", "checkbox"); } @@ -364,7 +361,7 @@ class AmbientMoveEffect : public Node { uint8_t tilt = ::map(bandSpeed[band], 0, UINT16_MAX, tiltMin, tiltMax); // the higher the band speed, the higher the tilt uint8_t pan = ::map(beatsin8((bandSpeed[band] > UINT16_MAX / 4) ? panBPM : 0, 0, 255, 0, (invert && x % 2 == 0) ? 128 : 0), 0, 255, panMin, panMax); // expect a bit of volume before panning - uint8_t tilt2 = ::map(beatsin8((bandSpeed[band] > UINT16_MAX / 4) ? panBPM : 0, 0, 255, 0, 64), 0, 255, panMin, panMax); // this is beatcos8, so pan and tilt draw a circle + uint8_t tilt2 = ::map(beatsin8((bandSpeed[band] > UINT16_MAX / 4) ? panBPM : 0, 0, 255, 0, 64), 0, 255, panMin, panMax); // this is beatcos8, so pan and tilt draw a circle layer->setTilt(x, (tilt + tilt2) / 2); layer->setPan(x, pan); diff --git a/src/MoonLight/Nodes/Effects/E_WLED.h b/src/MoonLight/Nodes/Effects/E_WLED.h index 0e5b90a1..5bd37b19 100644 --- a/src/MoonLight/Nodes/Effects/E_WLED.h +++ b/src/MoonLight/Nodes/Effects/E_WLED.h @@ -449,7 +449,7 @@ class LissajousEffect : public Node { class Noise2DEffect : public Node { public: - static const char* name() { return "Noise2D"; } + static const char* name() { return "Noise 2D"; } static uint8_t dim() { return _2D; } static const char* tags() { return "πŸ™"; } @@ -1063,7 +1063,7 @@ struct Spark { class PopCornEffect : public Node { public: - static const char* name() { return "PopCorn"; } + static const char* name() { return "Popcorn"; } static uint8_t dim() { return _1D; } // 2D-ish? check latest in WLED... static const char* tags() { return "πŸ™β™ͺ"; } @@ -1878,7 +1878,7 @@ class DripEffect : public Node { class HeartBeatEffect : public Node { public: - static const char* name() { return "HeartBeat"; } + static const char* name() { return "Heartbeat"; } static uint8_t dim() { return _1D; } static const char* tags() { return "β™₯"; } @@ -1921,7 +1921,7 @@ class HeartBeatEffect : public Node { class DJLightEffect : public Node { public: - static const char* name() { return "DJLight"; } + static const char* name() { return "DJ Light"; } static uint8_t dim() { return _1D; } static const char* tags() { return "πŸ™β™«"; } @@ -1997,7 +1997,7 @@ class DJLightEffect : public Node { class ColorTwinkleEffect : public Node { public: - static const char* name() { return "ColorTwinkle"; } + static const char* name() { return "Color Twinkle"; } static uint8_t dim() { return _3D; } static const char* tags() { return "πŸ™"; } @@ -2790,7 +2790,7 @@ class PhasedNoiseEffect : public Node { class FreqmapEffect : public Node { public: - static const char* name() { return "Freqmap"; } + static const char* name() { return "Freq Map"; } static uint8_t dim() { return _1D; } static const char* tags() { return "πŸ™β™ͺ"; } @@ -2830,7 +2830,7 @@ class FreqmapEffect : public Node { class FreqpixelsEffect : public Node { public: - static const char* name() { return "Freqpixels"; } + static const char* name() { return "Freq Pixels"; } static uint8_t dim() { return _1D; } static const char* tags() { return "πŸ™β™ͺ"; } @@ -2869,7 +2869,7 @@ static float mapf(float x, float in_min, float in_max, float out_min, float out_ class FreqwaveEffect : public Node { public: - static const char* name() { return "Freqwave"; } + static const char* name() { return "Freq Wave"; } static uint8_t dim() { return _1D; } static const char* tags() { return "πŸ™β™ͺ"; } @@ -2934,7 +2934,7 @@ struct Gravity { class GravfreqEffect : public Node { public: - static const char* name() { return "Gravfreq"; } + static const char* name() { return "Grav Freq"; } static uint8_t dim() { return _1D; } static const char* tags() { return "πŸ™β™ͺ"; } @@ -3067,7 +3067,7 @@ class GravimeterEffect : public Node { class GravcenterEffect : public Node { public: - static const char* name() { return "Gravcenter"; } + static const char* name() { return "Grav Center"; } static uint8_t dim() { return _1D; } static const char* tags() { return "πŸ™β™ͺ"; } @@ -3128,7 +3128,7 @@ class GravcenterEffect : public Node { class GravcentricEffect : public Node { public: - static const char* name() { return "Gravcentric"; } + static const char* name() { return "Grav Centric"; } static uint8_t dim() { return _1D; } static const char* tags() { return "πŸ™β™ͺ"; } @@ -3221,7 +3221,7 @@ class MidnoiseEffect : public Node { class NoisemoveEffect : public Node { public: - static const char* name() { return "Noisemove"; } + static const char* name() { return "Noise Move"; } static uint8_t dim() { return _1D; } static const char* tags() { return "πŸ™"; } @@ -3253,9 +3253,9 @@ class NoisemoveEffect : public Node { class NoisefireEffect : public Node { public: - static const char* name() { return "Noisefire"; } + static const char* name() { return "Noise Fire"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸ”₯"; } + static const char* tags() { return "πŸ™"; } uint8_t speed = 128; uint8_t intensity = 128; @@ -3295,7 +3295,7 @@ class PixelwaveEffect : public Node { uint8_t secondHand = 0; void loop() override { - uint8_t newSecondHand = micros() / (256 - speed) / 500 + 1 % 16; + uint8_t newSecondHand = (micros() / (256 - speed) / 500 + 1) % 16; if ((speed > 254) || (secondHand != newSecondHand)) { secondHand = newSecondHand; @@ -3322,7 +3322,7 @@ class PlasmoidEffect : public Node { public: static const char* name() { return "Plasmoid"; } static uint8_t dim() { return _1D; } - static const char* tags() { return "πŸ™"; } + static const char* tags() { return "πŸ™β™ͺ"; } uint8_t speed = 128; uint8_t intensity = 128; @@ -3372,7 +3372,7 @@ class PlasmoidEffect : public Node { class PuddlepeakEffect : public Node { public: - static const char* name() { return "Puddlepeak"; } + static const char* name() { return "Puddle Peak"; } static uint8_t dim() { return _1D; } static const char* tags() { return "πŸ™β™ͺ"; } @@ -3550,7 +3550,7 @@ class WaterfallEffect : public Node { uint8_t secondHand = 0; void loop() override { - uint8_t newSecondHand = micros() / (256 - speed) / 500 + 1 % 16; + uint8_t newSecondHand = (micros() / (256 - speed) / 500 + 1) % 16; if ((speed > 254) || (secondHand != newSecondHand)) { secondHand = newSecondHand; diff --git a/src/MoonLight/Nodes/Modifiers/M_MoonLight.h b/src/MoonLight/Nodes/Modifiers/M_MoonLight.h index 8a8cc32d..d47e6f53 100644 --- a/src/MoonLight/Nodes/Modifiers/M_MoonLight.h +++ b/src/MoonLight/Nodes/Modifiers/M_MoonLight.h @@ -48,6 +48,45 @@ class CircleModifier : public Node { } }; +// Takes the y dimension from the layout (1D effect) and turn it into concentric square blocks in 2D. +class BlockModifier : public Node { + public: + static const char* name() { return "Block"; } + static uint8_t dim() { return _2D; } // 1D to 2D + static const char* tags() { return "πŸ’Ž"; } + + Coord3D modifierSize; + + bool hasModifier() const override { return true; } + + void modifySize() override { + modifierSize = layer->size; + + modifyPosition(layer->size); // modify the virtual size as x, 0, 0 + + // change the size to be one bigger in each dimension + layer->size.x++; + layer->size.y++; + layer->size.z++; + } + + void modifyPosition(Coord3D& position) override { + // Calculate the distance from center using Chebyshev distance (max of abs differences) + int centerX = (modifierSize.x + 1) / 2 - 1; + int centerY = (modifierSize.y + 1) / 2 - 1; + + int dx = abs((int)position.x - centerX); + int dy = abs((int)position.y - centerY); + + // Block distance is the maximum of the two deltas (creates square rings) + int distance = MAX(dx, dy); + + position.x = 0; + position.y = distance; + position.z = 0; + } +}; + class MirrorModifier : public Node { public: static const char* name() { return "Mirror"; }