Merge branch 'main' into waterfall-fix

This commit is contained in:
Blaž Kristan 2024-12-25 10:51:34 +01:00 committed by GitHub
commit f001846e00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 1682 additions and 2018 deletions

7
package-lock.json generated
View File

@ -1,18 +1,21 @@
{ {
"name": "wled", "name": "wled",
"version": "0.15.0-b7", "version": "0.16.0-dev",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wled", "name": "wled",
"version": "0.15.0-b7", "version": "0.16.0-dev",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"clean-css": "^5.3.3", "clean-css": "^5.3.3",
"html-minifier-terser": "^7.2.0", "html-minifier-terser": "^7.2.0",
"inliner": "^1.13.1", "inliner": "^1.13.1",
"nodemon": "^3.1.7" "nodemon": "^3.1.7"
},
"engines": {
"node": ">=20.0.0"
} }
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {

View File

@ -1,6 +1,6 @@
{ {
"name": "wled", "name": "wled",
"version": "0.15.0-dev", "version": "0.16.0-dev",
"description": "Tools for WLED project", "description": "Tools for WLED project",
"main": "tools/cdata.js", "main": "tools/cdata.js",
"directories": { "directories": {

View File

@ -49,7 +49,7 @@ private:
void setColor(int r, int g, int b) void setColor(int r, int g, int b)
{ {
strip.setColor(0, r, g, b); strip.getMainSegment().setColor(0, RGBW32(r, g, b, 0));
stateUpdated(CALL_MODE_DIRECT_CHANGE); stateUpdated(CALL_MODE_DIRECT_CHANGE);
char msg[18] {}; char msg[18] {};
sprintf(msg, "rgb(%d,%d,%d)", r, g, b); sprintf(msg, "rgb(%d,%d,%d)", r, g, b);

View File

@ -31,14 +31,14 @@ public:
//strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); //strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true);
//select first two segments (background color + FX settable) //select first two segments (background color + FX settable)
WS2812FX::Segment &seg = strip.getSegment(0); Segment &seg = strip.getSegment(0);
seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF))); seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF)));
strip.getSegment(0).setOption(0, false); strip.getSegment(0).setOption(0, false);
strip.getSegment(0).setOption(2, false); strip.getSegment(0).setOption(2, false);
//other segments are text //other segments are text
for (int i = 1; i < 10; i++) for (int i = 1; i < 10; i++)
{ {
WS2812FX::Segment &seg = strip.getSegment(i); Segment &seg = strip.getSegment(i);
seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF))); seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF)));
strip.getSegment(i).setOption(0, true); strip.getSegment(i).setOption(0, true);
strip.setBrightness(64); strip.setBrightness(64);
@ -80,61 +80,61 @@ public:
void displayTime(byte hour, byte minute) void displayTime(byte hour, byte minute)
{ {
bool isToHour = false; //true if minute > 30 bool isToHour = false; //true if minute > 30
strip.setSegment(0, 0, 64); // background strip.getSegment(0).setGeometry(0, 64); // background
strip.setSegment(1, 0, 2); //It is strip.getSegment(1).setGeometry(0, 2); //It is
strip.setSegment(2, 0, 0); strip.getSegment(2).setGeometry(0, 0);
strip.setSegment(3, 0, 0); //disable minutes strip.getSegment(3).setGeometry(0, 0); //disable minutes
strip.setSegment(4, 0, 0); //past strip.getSegment(4).setGeometry(0, 0); //past
strip.setSegment(6, 0, 0); //to strip.getSegment(6).setGeometry(0, 0); //to
strip.setSegment(8, 0, 0); //disable o'clock strip.getSegment(8).setGeometry(0, 0); //disable o'clock
if (hour < 24) //valid time, display if (hour < 24) //valid time, display
{ {
if (minute == 30) if (minute == 30)
{ {
strip.setSegment(2, 3, 6); //half strip.getSegment(2).setGeometry(3, 6); //half
strip.setSegment(3, 0, 0); //minutes strip.getSegment(3).setGeometry(0, 0); //minutes
} }
else if (minute == 15 || minute == 45) else if (minute == 15 || minute == 45)
{ {
strip.setSegment(3, 0, 0); //minutes strip.getSegment(3).setGeometry(0, 0); //minutes
} }
else if (minute == 10) else if (minute == 10)
{ {
//strip.setSegment(5, 6, 8); //ten //strip.getSegment(5).setGeometry(6, 8); //ten
} }
else if (minute == 5) else if (minute == 5)
{ {
//strip.setSegment(5, 16, 18); //five //strip.getSegment(5).setGeometry(16, 18); //five
} }
else if (minute == 0) else if (minute == 0)
{ {
strip.setSegment(3, 0, 0); //minutes strip.getSegment(3).setGeometry(0, 0); //minutes
//hourChime(); //hourChime();
} }
else else
{ {
strip.setSegment(3, 18, 22); //minutes strip.getSegment(3).setGeometry(18, 22); //minutes
} }
//past or to? //past or to?
if (minute == 0) if (minute == 0)
{ //full hour { //full hour
strip.setSegment(3, 0, 0); //disable minutes strip.getSegment(3).setGeometry(0, 0); //disable minutes
strip.setSegment(4, 0, 0); //disable past strip.getSegment(4).setGeometry(0, 0); //disable past
strip.setSegment(6, 0, 0); //disable to strip.getSegment(6).setGeometry(0, 0); //disable to
strip.setSegment(8, 60, 64); //o'clock strip.getSegment(8).setGeometry(60, 64); //o'clock
} }
else if (minute > 34) else if (minute > 34)
{ {
//strip.setSegment(6, 22, 24); //to //strip.getSegment(6).setGeometry(22, 24); //to
//minute = 60 - minute; //minute = 60 - minute;
isToHour = true; isToHour = true;
} }
else else
{ {
//strip.setSegment(4, 24, 27); //past //strip.getSegment(4).setGeometry(24, 27); //past
//isToHour = false; //isToHour = false;
} }
} }
@ -143,68 +143,68 @@ public:
if (minute <= 4) if (minute <= 4)
{ {
strip.setSegment(3, 0, 0); //nothing strip.getSegment(3).setGeometry(0, 0); //nothing
strip.setSegment(5, 0, 0); //nothing strip.getSegment(5).setGeometry(0, 0); //nothing
strip.setSegment(6, 0, 0); //nothing strip.getSegment(6).setGeometry(0, 0); //nothing
strip.setSegment(8, 60, 64); //o'clock strip.getSegment(8).setGeometry(60, 64); //o'clock
} }
else if (minute <= 9) else if (minute <= 9)
{ {
strip.setSegment(5, 16, 18); // five past strip.getSegment(5).setGeometry(16, 18); // five past
strip.setSegment(4, 24, 27); //past strip.getSegment(4).setGeometry(24, 27); //past
} }
else if (minute <= 14) else if (minute <= 14)
{ {
strip.setSegment(5, 6, 8); // ten past strip.getSegment(5).setGeometry(6, 8); // ten past
strip.setSegment(4, 24, 27); //past strip.getSegment(4).setGeometry(24, 27); //past
} }
else if (minute <= 19) else if (minute <= 19)
{ {
strip.setSegment(5, 8, 12); // quarter past strip.getSegment(5).setGeometry(8, 12); // quarter past
strip.setSegment(3, 0, 0); //minutes strip.getSegment(3).setGeometry(0, 0); //minutes
strip.setSegment(4, 24, 27); //past strip.getSegment(4).setGeometry(24, 27); //past
} }
else if (minute <= 24) else if (minute <= 24)
{ {
strip.setSegment(5, 12, 16); // twenty past strip.getSegment(5).setGeometry(12, 16); // twenty past
strip.setSegment(4, 24, 27); //past strip.getSegment(4).setGeometry(24, 27); //past
} }
else if (minute <= 29) else if (minute <= 29)
{ {
strip.setSegment(5, 12, 18); // twenty-five past strip.getSegment(5).setGeometry(12, 18); // twenty-five past
strip.setSegment(4, 24, 27); //past strip.getSegment(4).setGeometry(24, 27); //past
} }
else if (minute <= 34) else if (minute <= 34)
{ {
strip.setSegment(5, 3, 6); // half past strip.getSegment(5).setGeometry(3, 6); // half past
strip.setSegment(3, 0, 0); //minutes strip.getSegment(3).setGeometry(0, 0); //minutes
strip.setSegment(4, 24, 27); //past strip.getSegment(4).setGeometry(24, 27); //past
} }
else if (minute <= 39) else if (minute <= 39)
{ {
strip.setSegment(5, 12, 18); // twenty-five to strip.getSegment(5).setGeometry(12, 18); // twenty-five to
strip.setSegment(6, 22, 24); //to strip.getSegment(6).setGeometry(22, 24); //to
} }
else if (minute <= 44) else if (minute <= 44)
{ {
strip.setSegment(5, 12, 16); // twenty to strip.getSegment(5).setGeometry(12, 16); // twenty to
strip.setSegment(6, 22, 24); //to strip.getSegment(6).setGeometry(22, 24); //to
} }
else if (minute <= 49) else if (minute <= 49)
{ {
strip.setSegment(5, 8, 12); // quarter to strip.getSegment(5).setGeometry(8, 12); // quarter to
strip.setSegment(3, 0, 0); //minutes strip.getSegment(3).setGeometry(0, 0); //minutes
strip.setSegment(6, 22, 24); //to strip.getSegment(6).setGeometry(22, 24); //to
} }
else if (minute <= 54) else if (minute <= 54)
{ {
strip.setSegment(5, 6, 8); // ten to strip.getSegment(5).setGeometry(6, 8); // ten to
strip.setSegment(6, 22, 24); //to strip.getSegment(6).setGeometry(22, 24); //to
} }
else if (minute <= 59) else if (minute <= 59)
{ {
strip.setSegment(5, 16, 18); // five to strip.getSegment(5).setGeometry(16, 18); // five to
strip.setSegment(6, 22, 24); //to strip.getSegment(6).setGeometry(22, 24); //to
} }
//hours //hours
@ -220,45 +220,45 @@ public:
switch (hour) switch (hour)
{ {
case 1: case 1:
strip.setSegment(7, 27, 29); strip.getSegment(7).setGeometry(27, 29);
break; //one break; //one
case 2: case 2:
strip.setSegment(7, 35, 37); strip.getSegment(7).setGeometry(35, 37);
break; //two break; //two
case 3: case 3:
strip.setSegment(7, 29, 32); strip.getSegment(7).setGeometry(29, 32);
break; //three break; //three
case 4: case 4:
strip.setSegment(7, 32, 35); strip.getSegment(7).setGeometry(32, 35);
break; //four break; //four
case 5: case 5:
strip.setSegment(7, 37, 40); strip.getSegment(7).setGeometry(37, 40);
break; //five break; //five
case 6: case 6:
strip.setSegment(7, 43, 45); strip.getSegment(7).setGeometry(43, 45);
break; //six break; //six
case 7: case 7:
strip.setSegment(7, 40, 43); strip.getSegment(7).setGeometry(40, 43);
break; //seven break; //seven
case 8: case 8:
strip.setSegment(7, 45, 48); strip.getSegment(7).setGeometry(45, 48);
break; //eight break; //eight
case 9: case 9:
strip.setSegment(7, 48, 50); strip.getSegment(7).setGeometry(48, 50);
break; //nine break; //nine
case 10: case 10:
strip.setSegment(7, 54, 56); strip.getSegment(7).setGeometry(54, 56);
break; //ten break; //ten
case 11: case 11:
strip.setSegment(7, 50, 54); strip.getSegment(7).setGeometry(50, 54);
break; //eleven break; //eleven
case 12: case 12:
strip.setSegment(7, 56, 60); strip.getSegment(7).setGeometry(56, 60);
break; //twelve break; //twelve
} }
selectWordSegments(true); selectWordSegments(true);
applyMacro(1); applyPreset(1);
} }
void timeOfDay() void timeOfDay()

View File

@ -1,305 +0,0 @@
#include "wled.h"
/*
* This v1 usermod file allows you to add own functionality to WLED more easily
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
* EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h)
* If you just need 8 bytes, use 2551-2559 (you do not need to increase EEPSIZE)
*
* Consider the v2 usermod API if you need a more advanced feature set!
*/
uint8_t minuteLast = 99;
int dayBrightness = 128;
int nightBrightness = 16;
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
//gets called once at boot. Do all initialization that doesn't depend on network here
void userSetup()
{
saveMacro(14, "A=128", false);
saveMacro(15, "A=64", false);
saveMacro(16, "A=16", false);
saveMacro(1, "&FX=0&R=255&G=255&B=255", false);
//strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true);
//select first two segments (background color + FX settable)
Segment &seg = strip.getSegment(0);
seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF)));
strip.getSegment(0).setOption(0, false);
strip.getSegment(0).setOption(2, false);
//other segments are text
for (int i = 1; i < 10; i++)
{
Segment &seg = strip.getSegment(i);
seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF)));
strip.getSegment(i).setOption(0, true);
strip.setBrightness(128);
}
}
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
void userConnected()
{
}
void selectWordSegments(bool state)
{
for (int i = 1; i < 10; i++)
{
//Segment &seg = strip.getSegment(i);
strip.getSegment(i).setOption(0, state);
// strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true);
//seg.mode = 12;
//seg.palette = 1;
//strip.setBrightness(255);
}
strip.getSegment(0).setOption(0, !state);
}
void hourChime()
{
//strip.resetSegments();
selectWordSegments(true);
colorUpdated(CALL_MODE_FX_CHANGED);
//savePreset(255);
selectWordSegments(false);
//strip.getSegment(0).setOption(0, true);
strip.getSegment(0).setOption(2, true);
applyPreset(12);
colorUpdated(CALL_MODE_FX_CHANGED);
}
void displayTime(byte hour, byte minute)
{
bool isToHour = false; //true if minute > 30
strip.setSegment(0, 0, 64); // background
strip.setSegment(1, 0, 2); //It is
strip.setSegment(2, 0, 0);
strip.setSegment(3, 0, 0); //disable minutes
strip.setSegment(4, 0, 0); //past
strip.setSegment(6, 0, 0); //to
strip.setSegment(8, 0, 0); //disable o'clock
if (hour < 24) //valid time, display
{
if (minute == 30)
{
strip.setSegment(2, 3, 6); //half
strip.setSegment(3, 0, 0); //minutes
}
else if (minute == 15 || minute == 45)
{
strip.setSegment(3, 0, 0); //minutes
}
else if (minute == 10)
{
//strip.setSegment(5, 6, 8); //ten
}
else if (minute == 5)
{
//strip.setSegment(5, 16, 18); //five
}
else if (minute == 0)
{
strip.setSegment(3, 0, 0); //minutes
//hourChime();
}
else
{
strip.setSegment(3, 18, 22); //minutes
}
//past or to?
if (minute == 0)
{ //full hour
strip.setSegment(3, 0, 0); //disable minutes
strip.setSegment(4, 0, 0); //disable past
strip.setSegment(6, 0, 0); //disable to
strip.setSegment(8, 60, 64); //o'clock
}
else if (minute > 34)
{
//strip.setSegment(6, 22, 24); //to
//minute = 60 - minute;
isToHour = true;
}
else
{
//strip.setSegment(4, 24, 27); //past
//isToHour = false;
}
}
else
{ //temperature display
}
//byte minuteRem = minute %10;
if (minute <= 4)
{
strip.setSegment(3, 0, 0); //nothing
strip.setSegment(5, 0, 0); //nothing
strip.setSegment(6, 0, 0); //nothing
strip.setSegment(8, 60, 64); //o'clock
}
else if (minute <= 9)
{
strip.setSegment(5, 16, 18); // five past
strip.setSegment(4, 24, 27); //past
}
else if (minute <= 14)
{
strip.setSegment(5, 6, 8); // ten past
strip.setSegment(4, 24, 27); //past
}
else if (minute <= 19)
{
strip.setSegment(5, 8, 12); // quarter past
strip.setSegment(3, 0, 0); //minutes
strip.setSegment(4, 24, 27); //past
}
else if (minute <= 24)
{
strip.setSegment(5, 12, 16); // twenty past
strip.setSegment(4, 24, 27); //past
}
else if (minute <= 29)
{
strip.setSegment(5, 12, 18); // twenty-five past
strip.setSegment(4, 24, 27); //past
}
else if (minute <= 34)
{
strip.setSegment(5, 3, 6); // half past
strip.setSegment(3, 0, 0); //minutes
strip.setSegment(4, 24, 27); //past
}
else if (minute <= 39)
{
strip.setSegment(5, 12, 18); // twenty-five to
strip.setSegment(6, 22, 24); //to
}
else if (minute <= 44)
{
strip.setSegment(5, 12, 16); // twenty to
strip.setSegment(6, 22, 24); //to
}
else if (minute <= 49)
{
strip.setSegment(5, 8, 12); // quarter to
strip.setSegment(3, 0, 0); //minutes
strip.setSegment(6, 22, 24); //to
}
else if (minute <= 54)
{
strip.setSegment(5, 6, 8); // ten to
strip.setSegment(6, 22, 24); //to
}
else if (minute <= 59)
{
strip.setSegment(5, 16, 18); // five to
strip.setSegment(6, 22, 24); //to
}
//hours
if (hour > 23)
return;
if (isToHour)
hour++;
if (hour > 12)
hour -= 12;
if (hour == 0)
hour = 12;
switch (hour)
{
case 1:
strip.setSegment(7, 27, 29);
break; //one
case 2:
strip.setSegment(7, 35, 37);
break; //two
case 3:
strip.setSegment(7, 29, 32);
break; //three
case 4:
strip.setSegment(7, 32, 35);
break; //four
case 5:
strip.setSegment(7, 37, 40);
break; //five
case 6:
strip.setSegment(7, 43, 45);
break; //six
case 7:
strip.setSegment(7, 40, 43);
break; //seven
case 8:
strip.setSegment(7, 45, 48);
break; //eight
case 9:
strip.setSegment(7, 48, 50);
break; //nine
case 10:
strip.setSegment(7, 54, 56);
break; //ten
case 11:
strip.setSegment(7, 50, 54);
break; //eleven
case 12:
strip.setSegment(7, 56, 60);
break; //twelve
}
selectWordSegments(true);
applyMacro(1);
}
void timeOfDay() {
// NOT USED: use timed macros instead
//Used to set brightness dependant of time of day - lights dimmed at night
//monday to thursday and sunday
if ((weekday(localTime) == 6) | (weekday(localTime) == 7)) {
if (hour(localTime) > 0 | hour(localTime) < 8) {
strip.setBrightness(nightBrightness);
}
else {
strip.setBrightness(dayBrightness);
}
}
else {
if (hour(localTime) < 6 | hour(localTime) >= 22) {
strip.setBrightness(nightBrightness);
}
else {
strip.setBrightness(dayBrightness);
}
}
}
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
void userLoop()
{
if (minute(localTime) != minuteLast)
{
updateLocalTime();
//timeOfDay();
minuteLast = minute(localTime);
displayTime(hour(localTime), minute(localTime));
if (minute(localTime) == 0){
hourChime();
}
if (minute(localTime) == 1){
//turn off background segment;
strip.getSegment(0).setOption(2, false);
//applyPreset(255);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@
#include <vector> #include <vector>
#include "const.h" #include "const.h"
#include "bus_manager.h"
#define FASTLED_INTERNAL //remove annoying pragma messages #define FASTLED_INTERNAL //remove annoying pragma messages
#define USE_GET_MILLISECOND_TIMER #define USE_GET_MILLISECOND_TIMER
@ -42,6 +43,9 @@
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) #define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
#endif #endif
extern bool realtimeRespectLedMaps; // used in getMappedPixelIndex()
extern byte realtimeMode; // used in getMappedPixelIndex()
/* Not used in all effects yet */ /* Not used in all effects yet */
#define WLED_FPS 42 #define WLED_FPS 42
#define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME_FIXED (1000/WLED_FPS)
@ -88,11 +92,11 @@
#define NUM_COLORS 3 /* number of colors per segment */ #define NUM_COLORS 3 /* number of colors per segment */
#define SEGMENT strip._segments[strip.getCurrSegmentId()] #define SEGMENT strip._segments[strip.getCurrSegmentId()]
#define SEGENV strip._segments[strip.getCurrSegmentId()] #define SEGENV strip._segments[strip.getCurrSegmentId()]
//#define SEGCOLOR(x) strip._segments[strip.getCurrSegmentId()].currentColor(x, strip._segments[strip.getCurrSegmentId()].colors[x]) #define SEGCOLOR(x) Segment::getCurrentColor(x)
//#define SEGLEN strip._segments[strip.getCurrSegmentId()].virtualLength()
#define SEGCOLOR(x) strip.segColor(x) /* saves us a few kbytes of code */
#define SEGPALETTE Segment::getCurrentPalette() #define SEGPALETTE Segment::getCurrentPalette()
#define SEGLEN strip._virtualSegmentLength /* saves us a few kbytes of code */ #define SEGLEN Segment::vLength()
#define SEG_W Segment::vWidth()
#define SEG_H Segment::vHeight()
#define SPEED_FORMULA_L (5U + (50U*(255U - SEGMENT.speed))/SEGLEN) #define SPEED_FORMULA_L (5U + (50U*(255U - SEGMENT.speed))/SEGLEN)
// some common colors // some common colors
@ -204,7 +208,7 @@
#define FX_MODE_COLORTWINKLE 74 #define FX_MODE_COLORTWINKLE 74
#define FX_MODE_LAKE 75 #define FX_MODE_LAKE 75
#define FX_MODE_METEOR 76 #define FX_MODE_METEOR 76
#define FX_MODE_METEOR_SMOOTH 77 //#define FX_MODE_METEOR_SMOOTH 77 // merged with meteor
#define FX_MODE_RAILWAY 78 #define FX_MODE_RAILWAY 78
#define FX_MODE_RIPPLE 79 #define FX_MODE_RIPPLE 79
#define FX_MODE_TWINKLEFOX 80 #define FX_MODE_TWINKLEFOX 80
@ -398,7 +402,7 @@ typedef struct Segment {
uint32_t _stepT; uint32_t _stepT;
uint32_t _callT; uint32_t _callT;
uint8_t *_dataT; uint8_t *_dataT;
uint16_t _dataLenT; unsigned _dataLenT;
TemporarySegmentData() TemporarySegmentData()
: _dataT(nullptr) // just in case... : _dataT(nullptr) // just in case...
, _dataLenT(0) , _dataLenT(0)
@ -416,10 +420,14 @@ typedef struct Segment {
uint8_t _reserved : 4; uint8_t _reserved : 4;
}; };
}; };
uint16_t _dataLen; uint8_t _default_palette; // palette number that gets assigned to pal0
static uint16_t _usedSegmentData; unsigned _dataLen;
static unsigned _usedSegmentData;
// perhaps this should be per segment, not static static uint8_t _segBri; // brightness of segment for current effect
static unsigned _vLength; // 1D dimension used for current effect
static unsigned _vWidth, _vHeight; // 2D dimensions used for current effect
static uint32_t _currentColors[NUM_COLORS]; // colors used for current effect
static bool _colorScaled; // color has been scaled prior to setPixelColor() call
static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette()) static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette())
static CRGBPalette16 _randomPalette; // actual random palette static CRGBPalette16 _randomPalette; // actual random palette
static CRGBPalette16 _newRandomPalette; // target random palette static CRGBPalette16 _newRandomPalette; // target random palette
@ -452,6 +460,8 @@ typedef struct Segment {
{} {}
} *_t; } *_t;
[[gnu::hot]] void _setPixelColorXY_raw(int& x, int& y, uint32_t& col); // set pixel without mapping (internal use only)
public: public:
Segment(uint16_t sStart=0, uint16_t sStop=30) : Segment(uint16_t sStart=0, uint16_t sStop=30) :
@ -484,6 +494,7 @@ typedef struct Segment {
aux1(0), aux1(0),
data(nullptr), data(nullptr),
_capabilities(0), _capabilities(0),
_default_palette(0),
_dataLen(0), _dataLen(0),
_t(nullptr) _t(nullptr)
{ {
@ -532,23 +543,30 @@ typedef struct Segment {
inline uint16_t length() const { return width() * height(); } // segment length (count) in physical pixels inline uint16_t length() const { return width() * height(); } // segment length (count) in physical pixels
inline uint16_t groupLength() const { return grouping + spacing; } inline uint16_t groupLength() const { return grouping + spacing; }
inline uint8_t getLightCapabilities() const { return _capabilities; } inline uint8_t getLightCapabilities() const { return _capabilities; }
inline void deactivate() { setGeometry(0,0); }
inline static uint16_t getUsedSegmentData() { return _usedSegmentData; } inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; }
inline static void addUsedSegmentData(int len) { _usedSegmentData += len; } inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; }
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
inline static void modeBlend(bool blend) { _modeBlend = blend; } inline static void modeBlend(bool blend) { _modeBlend = blend; }
#endif #endif
static void handleRandomPalette(); inline static unsigned vLength() { return Segment::_vLength; }
inline static unsigned vWidth() { return Segment::_vWidth; }
inline static unsigned vHeight() { return Segment::_vHeight; }
inline static uint32_t getCurrentColor(unsigned i) { return Segment::_currentColors[i]; } // { return i < 3 ? Segment::_currentColors[i] : 0; }
inline static const CRGBPalette16 &getCurrentPalette() { return Segment::_currentPalette; } inline static const CRGBPalette16 &getCurrentPalette() { return Segment::_currentPalette; }
inline static uint8_t getCurrentBrightness() { return Segment::_segBri; }
static void handleRandomPalette();
void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1); void beginDraw(); // set up parameters for current effect
void setGeometry(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1, uint8_t m12=0);
Segment &setColor(uint8_t slot, uint32_t c); Segment &setColor(uint8_t slot, uint32_t c);
Segment &setCCT(uint16_t k); Segment &setCCT(uint16_t k);
Segment &setOpacity(uint8_t o); Segment &setOpacity(uint8_t o);
Segment &setOption(uint8_t n, bool val); Segment &setOption(uint8_t n, bool val);
Segment &setMode(uint8_t fx, bool loadDefaults = false); Segment &setMode(uint8_t fx, bool loadDefaults = false);
Segment &setPalette(uint8_t pal); Segment &setPalette(uint8_t pal);
uint8_t differs(Segment& b) const; uint8_t differs(const Segment& b) const;
void refreshLightCapabilities(); void refreshLightCapabilities();
// runtime data functions // runtime data functions
@ -578,7 +596,6 @@ typedef struct Segment {
uint8_t currentMode() const; // currently active effect/mode (while in transition) uint8_t currentMode() const; // currently active effect/mode (while in transition)
[[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition) [[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition)
CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal);
void setCurrentPalette();
// 1D strip // 1D strip
[[gnu::hot]] uint16_t virtualLength() const; [[gnu::hot]] uint16_t virtualLength() const;
@ -599,21 +616,19 @@ typedef struct Segment {
void fadeToBlackBy(uint8_t fadeBy); void fadeToBlackBy(uint8_t fadeBy);
inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); }
inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); }
inline void addPixelColor(int n, uint32_t color, bool fast = false) { setPixelColor(n, color_add(getPixelColor(n), color, fast)); } inline void addPixelColor(int n, uint32_t color, bool preserveCR = true) { setPixelColor(n, color_add(getPixelColor(n), color, preserveCR)); }
inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); } inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) { addPixelColor(n, RGBW32(r,g,b,w), preserveCR); }
inline void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } inline void addPixelColor(int n, CRGB c, bool preserveCR = true) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), preserveCR); }
inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); }
[[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const; [[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const;
[[gnu::hot]] uint32_t color_wheel(uint8_t pos) const; [[gnu::hot]] uint32_t color_wheel(uint8_t pos) const;
// 2D Blur: shortcuts for bluring columns or rows only (50% faster than full 2D blur) // 2D Blur: shortcuts for bluring columns or rows only (50% faster than full 2D blur)
inline void blurCols(fract8 blur_amount, bool smear = false) { // blur all columns inline void blurCols(fract8 blur_amount, bool smear = false) { // blur all columns
const unsigned cols = virtualWidth(); blur2D(0, blur_amount, smear);
for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear);
} }
inline void blurRows(fract8 blur_amount, bool smear = false) { // blur all rows inline void blurRows(fract8 blur_amount, bool smear = false) { // blur all rows
const unsigned rows = virtualHeight(); blur2D(blur_amount, 0, smear);
for ( unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear);
} }
// 2D matrix // 2D matrix
@ -642,31 +657,28 @@ typedef struct Segment {
// 2D support functions // 2D support functions
inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); }
inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, fast)); } inline void addPixelColorXY(int x, int y, uint32_t color, bool preserveCR = true) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, preserveCR)); }
inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), fast); } inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) { addPixelColorXY(x, y, RGBW32(r,g,b,w), preserveCR); }
inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); } inline void addPixelColorXY(int x, int y, CRGB c, bool preserveCR = true) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), preserveCR); }
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); }
void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur //void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur
void blur2D(uint8_t blur_amount, bool smear = false); void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false);
void blurRow(uint32_t row, fract8 blur_amount, bool smear = false); void moveX(int delta, bool wrap = false);
void blurCol(uint32_t col, fract8 blur_amount, bool smear = false); void moveY(int delta, bool wrap = false);
void moveX(int8_t delta, bool wrap = false); void move(unsigned dir, unsigned delta, bool wrap = false);
void moveY(int8_t delta, bool wrap = false);
void move(uint8_t dir, uint8_t delta, bool wrap = false);
void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false);
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); }
void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false);
inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); }
void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false); void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false);
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline
void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0); void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0, bool usePalGrad = false);
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0)); } // automatic inline inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0)); } // automatic inline
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate); } // automatic inline inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0, bool usePalGrad = false) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate, usePalGrad); } // automatic inline
void wu_pixel(uint32_t x, uint32_t y, CRGB c); void wu_pixel(uint32_t x, uint32_t y, CRGB c);
inline void blur2d(fract8 blur_amount) { blur(blur_amount); }
inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); }
#else #else
inline uint16_t XY(uint16_t x, uint16_t y) { return x; } inline uint16_t XY(int x, int y) { return x; }
inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); }
inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); } inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); }
@ -680,16 +692,16 @@ typedef struct Segment {
inline uint32_t getPixelColorXY(int x, int y) { return getPixelColor(x); } inline uint32_t getPixelColorXY(int x, int y) { return getPixelColor(x); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); }
inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { addPixelColor(x, color, fast); } inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) { addPixelColor(x, color, saturate); }
inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(x, RGBW32(r,g,b,w), fast); } inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColor(x, RGBW32(r,g,b,w), saturate); }
inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); } inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), saturate); }
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); }
inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {} //inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {}
inline void blur2D(uint8_t blur_amount, bool smear = false) {} inline void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) {}
inline void blurRow(uint32_t row, fract8 blur_amount, bool smear = false) {} inline void blurRow(int row, fract8 blur_amount, bool smear = false) {}
inline void blurCol(uint32_t col, fract8 blur_amount, bool smear = false) {} inline void blurCol(int col, fract8 blur_amount, bool smear = false) {}
inline void moveX(int8_t delta, bool wrap = false) {} inline void moveX(int delta, bool wrap = false) {}
inline void moveY(int8_t delta, bool wrap = false) {} inline void moveY(int delta, bool wrap = false) {}
inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {} inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {}
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) {} inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) {}
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {} inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {}
@ -697,9 +709,9 @@ typedef struct Segment {
inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {} inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {}
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) {} inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) {}
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) {} inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0, bool = false) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0, bool usePalGrad = false) {}
inline void wu_pixel(uint32_t x, uint32_t y, CRGB c) {} inline void wu_pixel(uint32_t x, uint32_t y, CRGB c) {}
#endif #endif
} segment; } segment;
@ -737,9 +749,6 @@ class WS2812FX { // 96 bytes
#endif #endif
correctWB(false), correctWB(false),
cctFromRgb(false), cctFromRgb(false),
// semi-private (just obscured) used in effect functions through macros
_colors_t{0,0,0},
_virtualSegmentLength(0),
// true private variables // true private variables
_suspend(false), _suspend(false),
_length(DEFAULT_LED_COUNT), _length(DEFAULT_LED_COUNT),
@ -787,26 +796,22 @@ class WS2812FX { // 96 bytes
#endif #endif
finalizeInit(), // initialises strip components finalizeInit(), // initialises strip components
service(), // executes effect functions when due and calls strip.show() service(), // executes effect functions when due and calls strip.show()
setMode(uint8_t segid, uint8_t m), // sets effect/mode for given segment (high level API)
setColor(uint8_t slot, uint32_t c), // sets color (in slot) for given segment (high level API)
setCCT(uint16_t k), // sets global CCT (either in relative 0-255 value or in K) setCCT(uint16_t k), // sets global CCT (either in relative 0-255 value or in K)
setBrightness(uint8_t b, bool direct = false), // sets strip brightness setBrightness(uint8_t b, bool direct = false), // sets strip brightness
setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay
purgeSegments(), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint) purgeSegments(), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint)
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1), setMainSegmentId(unsigned n = 0),
setMainSegmentId(uint8_t n),
resetSegments(), // marks all segments for reset resetSegments(), // marks all segments for reset
makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs
fixInvalidSegments(), // fixes incorrect segment configuration fixInvalidSegments(), // fixes incorrect segment configuration
setPixelColor(unsigned n, uint32_t c), // paints absolute strip pixel with index n and color c setPixelColor(unsigned n, uint32_t c), // paints absolute strip pixel with index n and color c
show(), // initiates LED output show(), // initiates LED output
setTargetFps(uint8_t fps), setTargetFps(unsigned fps),
setupEffectData(); // add default effects to the list; defined in FX.cpp setupEffectData(); // add default effects to the list; defined in FX.cpp
inline void resetTimebase() { timebase = 0UL - millis(); } inline void resetTimebase() { timebase = 0UL - millis(); }
inline void restartRuntime() { for (Segment &seg : _segments) { seg.markForReset().resetIfRequired(); } } inline void restartRuntime() { for (Segment &seg : _segments) { seg.markForReset().resetIfRequired(); } }
inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); }
inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); }
inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); }
inline void setPixelColor(unsigned n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); } inline void setPixelColor(unsigned n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); }
inline void fill(uint32_t c) { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) inline void fill(uint32_t c) { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline)
@ -822,9 +827,9 @@ class WS2812FX { // 96 bytes
checkSegmentAlignment(), checkSegmentAlignment(),
hasRGBWBus() const, hasRGBWBus() const,
hasCCTBus() const, hasCCTBus() const,
isUpdating() const, // return true if the strip is being sent pixel updates deserializeMap(unsigned n = 0);
deserializeMap(uint8_t n=0);
inline bool isUpdating() const { return !BusManager::canAllShow(); } // return true if the strip is being sent pixel updates
inline bool isServicing() const { return _isServicing; } // returns true if strip.service() is executing inline bool isServicing() const { return _isServicing; } // returns true if strip.service() is executing
inline bool hasWhiteChannel() const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel inline bool hasWhiteChannel() const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel
inline bool isOffRefreshRequired() const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset) inline bool isOffRefreshRequired() const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset)
@ -841,7 +846,7 @@ class WS2812FX { // 96 bytes
addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp; addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp;
inline uint8_t getBrightness() const { return _brightness; } // returns current strip brightness inline uint8_t getBrightness() const { return _brightness; } // returns current strip brightness
inline uint8_t getMaxSegments() const { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) inline static constexpr unsigned getMaxSegments() { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value)
inline uint8_t getSegmentsNum() const { return _segments.size(); } // returns currently present segments inline uint8_t getSegmentsNum() const { return _segments.size(); } // returns currently present segments
inline uint8_t getCurrSegmentId() const { return _segment_index; } // returns current segment index (only valid while strip.isServicing()) inline uint8_t getCurrSegmentId() const { return _segment_index; } // returns current segment index (only valid while strip.isServicing())
inline uint8_t getMainSegmentId() const { return _mainSegment; } // returns main segment index inline uint8_t getMainSegmentId() const { return _mainSegment; } // returns main segment index
@ -851,28 +856,27 @@ class WS2812FX { // 96 bytes
uint16_t uint16_t
getLengthPhysical() const, getLengthPhysical() const,
getLengthTotal() const, // will include virtual/nonexistent pixels in matrix getLengthTotal() const; // will include virtual/nonexistent pixels in matrix
getFps() const,
getMappedPixelIndex(uint16_t index) const;
inline uint16_t getFps() const { return (millis() - _lastShow > 2000) ? 0 : (FPS_MULTIPLIER * _cumulativeFps) >> FPS_CALC_SHIFT; } // Returns the refresh rate of the LED strip (_cumulativeFps is stored in fixed point)
inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms) inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms)
inline uint16_t getMinShowDelay() const { return MIN_FRAME_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) inline uint16_t getMinShowDelay() const { return MIN_FRAME_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant)
inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H)
inline uint16_t getTransition() const { return _transitionDur; } // returns currently set transition time (in ms) inline uint16_t getTransition() const { return _transitionDur; } // returns currently set transition time (in ms)
inline uint16_t getMappedPixelIndex(uint16_t index) const { // convert logical address to physical
if (index < customMappingSize && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index];
return index;
};
unsigned long now, timebase; unsigned long now, timebase;
uint32_t getPixelColor(unsigned) const; uint32_t getPixelColor(unsigned) const;
inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call
inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition
const char * const char *getModeData(unsigned id = 0) const { return (id && id < _modeCount) ? _modeData[id] : PSTR("Solid"); }
getModeData(uint8_t id = 0) const { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } inline const char **getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data
const char ** Segment& getSegment(unsigned id);
getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data
Segment& getSegment(uint8_t id);
inline Segment& getFirstSelectedSeg() { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected" inline Segment& getFirstSelectedSeg() { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected"
inline Segment& getMainSegment() { return _segments[getMainSegmentId()]; } // returns reference to main segment inline Segment& getMainSegment() { return _segments[getMainSegmentId()]; } // returns reference to main segment
inline Segment* getSegments() { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully) inline Segment* getSegments() { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully)
@ -931,11 +935,6 @@ class WS2812FX { // 96 bytes
bool cctFromRgb : 1; bool cctFromRgb : 1;
}; };
// using public variables to reduce code size increase due to inline function getSegment() (with bounds checking)
// and color transitions
uint32_t _colors_t[3]; // color used for effect (includes transition)
uint16_t _virtualSegmentLength;
std::vector<segment> _segments; std::vector<segment> _segments;
friend class Segment; friend class Segment;

View File

@ -148,57 +148,68 @@ void WS2812FX::setUpMatrix() {
// XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element)
uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) uint16_t IRAM_ATTR_YN Segment::XY(int x, int y)
{ {
unsigned width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
unsigned height = virtualHeight(); // segment height in logical pixels (is always >= 1) const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
return isActive() ? (x%width) + (y%height) * width : 0; return isActive() ? (x%vW) + (y%vH) * vW : 0;
}
// raw setColor function without checks (checks are done in setPixelColorXY())
void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(int& x, int& y, uint32_t& col)
{
const int baseX = start + x;
const int baseY = startY + y;
#ifndef WLED_DISABLE_MODE_BLEND
// if blending modes, blend with underlying pixel
if (_modeBlend) col = color_blend16(strip.getPixelColorXY(baseX, baseY), col, 0xFFFFU - progress());
#endif
strip.setPixelColorXY(baseX, baseY, col);
// Apply mirroring
if (mirror || mirror_y) {
auto setMirroredPixel = [&](int mx, int my) {
strip.setPixelColorXY(mx, my, col);
};
const int mirrorX = start + width() - x - 1;
const int mirrorY = startY + height() - y - 1;
if (mirror) setMirroredPixel(transpose ? baseX : mirrorX, transpose ? mirrorY : baseY);
if (mirror_y) setMirroredPixel(transpose ? mirrorX : baseX, transpose ? baseY : mirrorY);
if (mirror && mirror_y) setMirroredPixel(mirrorX, mirrorY);
}
} }
void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col)
{ {
if (!isActive()) return; // not active if (!isActive()) return; // not active
if ((unsigned)x >= virtualWidth() || (unsigned)y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit
uint8_t _bri_t = currentBri(); const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
if (_bri_t < 255) { const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
col = color_fade(col, _bri_t); // negative values of x & y cast into unsigend will become very large values and will therefore be greater than vW/vH
} if (unsigned(x) >= unsigned(vW) || unsigned(y) >= unsigned(vH)) return; // if pixel would fall out of virtual segment just exit
if (reverse ) x = virtualWidth() - x - 1; // if color is unscaled
if (reverse_y) y = virtualHeight() - y - 1; if (!_colorScaled) col = color_fade(col, _segBri);
if (reverse ) x = vW - x - 1;
if (reverse_y) y = vH - y - 1;
if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed
unsigned groupLen = groupLength();
x *= groupLength(); // expand to physical pixels if (groupLen > 1) {
y *= groupLength(); // expand to physical pixels
int W = width(); int W = width();
int H = height(); int H = height();
if (x >= W || y >= H) return; // if pixel would fall out of segment just exit x *= groupLen; // expand to physical pixels
y *= groupLen; // expand to physical pixels
uint32_t tmpCol = col; const int maxY = std::min(y + grouping, H);
for (int j = 0; j < grouping; j++) { // groupping vertically const int maxX = std::min(x + grouping, W);
for (int g = 0; g < grouping; g++) { // groupping horizontally for (int yY = y; yY < maxY; yY++) {
int xX = (x+g), yY = (y+j); for (int xX = x; xX < maxX; xX++) {
if (xX >= W || yY >= H) continue; // we have reached one dimension's end _setPixelColorXY_raw(xX, yY, col);
#ifndef WLED_DISABLE_MODE_BLEND
// if blending modes, blend with underlying pixel
if (_modeBlend) tmpCol = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true);
#endif
strip.setPixelColorXY(start + xX, startY + yY, tmpCol);
if (mirror) { //set the corresponding horizontally mirrored pixel
if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol);
else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol);
}
if (mirror_y) { //set the corresponding vertically mirrored pixel
if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol);
else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol);
}
if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel
strip.setPixelColorXY(start + width() - xX - 1, startY + height() - yY - 1, tmpCol);
} }
} }
} else {
_setPixelColorXY_raw(x, y, col);
} }
} }
@ -209,11 +220,8 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
if (!isActive()) return; // not active if (!isActive()) return; // not active
if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized
const unsigned cols = virtualWidth(); float fX = x * (vWidth()-1);
const unsigned rows = virtualHeight(); float fY = y * (vHeight()-1);
float fX = x * (cols-1);
float fY = y * (rows-1);
if (aa) { if (aa) {
unsigned xL = roundf(fX-0.49f); unsigned xL = roundf(fX-0.49f);
unsigned xR = roundf(fX+0.49f); unsigned xR = roundf(fX+0.49f);
@ -251,9 +259,11 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
// returns RGBW values of pixel // returns RGBW values of pixel
uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const {
if (!isActive()) return 0; // not active if (!isActive()) return 0; // not active
if ((unsigned)x >= virtualWidth() || (unsigned)y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit const int vW = vWidth();
if (reverse ) x = virtualWidth() - x - 1; const int vH = vHeight();
if (reverse_y) y = virtualHeight() - y - 1; if (unsigned(x) >= unsigned(vW) || unsigned(y) >= unsigned(vH)) return 0; // if pixel would fall out of virtual segment just exit
if (reverse ) x = vW - x - 1;
if (reverse_y) y = vH - y - 1;
if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed
x *= groupLength(); // expand to physical pixels x *= groupLength(); // expand to physical pixels
y *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels
@ -261,91 +271,26 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const {
return strip.getPixelColorXY(start + x, startY + y); return strip.getPixelColorXY(start + x, startY + y);
} }
// blurRow: perform a blur on a row of a rectangular matrix // 2D blurring, can be asymmetrical
void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) {
if (!isActive() || blur_amount == 0) return; // not active if (!isActive()) return; // not active
const unsigned cols = virtualWidth(); const unsigned cols = vWidth();
const unsigned rows = virtualHeight(); const unsigned rows = vHeight();
if (row >= rows) return;
// blur one row
uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
uint32_t carryover = BLACK;
uint32_t lastnew; uint32_t lastnew;
uint32_t last; uint32_t last;
uint32_t curnew = BLACK; if (blur_x) {
for (unsigned x = 0; x < cols; x++) { const uint8_t keepx = smear ? 255 : 255 - blur_x;
uint32_t cur = getPixelColorXY(x, row); const uint8_t seepx = blur_x >> 1;
uint32_t part = color_fade(cur, seep); for (unsigned row = 0; row < rows; row++) { // blur rows (x direction)
curnew = color_fade(cur, keep);
if (x > 0) {
if (carryover)
curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
if (last != prev) // optimization: only set pixel if color has changed
setPixelColorXY(x - 1, row, prev);
} else // first pixel
setPixelColorXY(x, row, curnew);
lastnew = curnew;
last = cur; // save original value for comparison on next iteration
carryover = part;
}
setPixelColorXY(cols-1, row, curnew); // set last pixel
}
// blurCol: perform a blur on a column of a rectangular matrix
void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) {
if (!isActive() || blur_amount == 0) return; // not active
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
if (col >= cols) return;
// blur one column
uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
uint32_t carryover = BLACK;
uint32_t lastnew;
uint32_t last;
uint32_t curnew = BLACK;
for (unsigned y = 0; y < rows; y++) {
uint32_t cur = getPixelColorXY(col, y);
uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep);
if (y > 0) {
if (carryover)
curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
if (last != prev) // optimization: only set pixel if color has changed
setPixelColorXY(col, y - 1, prev);
} else // first pixel
setPixelColorXY(col, y, curnew);
lastnew = curnew;
last = cur; //save original value for comparison on next iteration
carryover = part;
}
setPixelColorXY(col, rows - 1, curnew);
}
void Segment::blur2D(uint8_t blur_amount, bool smear) {
if (!isActive() || blur_amount == 0) return; // not active
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
const uint8_t keep = smear ? 255 : 255 - blur_amount;
const uint8_t seep = blur_amount >> (1 + smear);
uint32_t lastnew;
uint32_t last;
for (unsigned row = 0; row < rows; row++) {
uint32_t carryover = BLACK; uint32_t carryover = BLACK;
uint32_t curnew = BLACK; uint32_t curnew = BLACK;
for (unsigned x = 0; x < cols; x++) { for (unsigned x = 0; x < cols; x++) {
uint32_t cur = getPixelColorXY(x, row); uint32_t cur = getPixelColorXY(x, row);
uint32_t part = color_fade(cur, seep); uint32_t part = color_fade(cur, seepx);
curnew = color_fade(cur, keep); curnew = color_fade(cur, keepx);
if (x > 0) { if (x > 0) {
if (carryover) curnew = color_add(curnew, carryover, true); if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part, true); uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed // optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(x - 1, row, prev); if (last != prev) setPixelColorXY(x - 1, row, prev);
} else setPixelColorXY(x, row, curnew); // first pixel } else setPixelColorXY(x, row, curnew); // first pixel
@ -355,16 +300,20 @@ void Segment::blur2D(uint8_t blur_amount, bool smear) {
} }
setPixelColorXY(cols-1, row, curnew); // set last pixel setPixelColorXY(cols-1, row, curnew); // set last pixel
} }
}
if (blur_y) {
const uint8_t keepy = smear ? 255 : 255 - blur_y;
const uint8_t seepy = blur_y >> 1;
for (unsigned col = 0; col < cols; col++) { for (unsigned col = 0; col < cols; col++) {
uint32_t carryover = BLACK; uint32_t carryover = BLACK;
uint32_t curnew = BLACK; uint32_t curnew = BLACK;
for (unsigned y = 0; y < rows; y++) { for (unsigned y = 0; y < rows; y++) {
uint32_t cur = getPixelColorXY(col, y); uint32_t cur = getPixelColorXY(col, y);
uint32_t part = color_fade(cur, seep); uint32_t part = color_fade(cur, seepy);
curnew = color_fade(cur, keep); curnew = color_fade(cur, keepy);
if (y > 0) { if (y > 0) {
if (carryover) curnew = color_add(curnew, carryover, true); if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part, true); uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed // optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(col, y - 1, prev); if (last != prev) setPixelColorXY(col, y - 1, prev);
} else setPixelColorXY(col, y, curnew); // first pixel } else setPixelColorXY(col, y, curnew); // first pixel
@ -374,15 +323,17 @@ void Segment::blur2D(uint8_t blur_amount, bool smear) {
} }
setPixelColorXY(col, rows - 1, curnew); setPixelColorXY(col, rows - 1, curnew);
} }
}
} }
/*
// 2D Box blur // 2D Box blur
void Segment::box_blur(unsigned radius, bool smear) { void Segment::box_blur(unsigned radius, bool smear) {
if (!isActive() || radius == 0) return; // not active if (!isActive() || radius == 0) return; // not active
if (radius > 3) radius = 3; if (radius > 3) radius = 3;
const unsigned d = (1 + 2*radius) * (1 + 2*radius); // averaging divisor const unsigned d = (1 + 2*radius) * (1 + 2*radius); // averaging divisor
const unsigned cols = virtualWidth(); const unsigned cols = vWidth();
const unsigned rows = virtualHeight(); const unsigned rows = vHeight();
uint16_t *tmpRSum = new uint16_t[cols*rows]; uint16_t *tmpRSum = new uint16_t[cols*rows];
uint16_t *tmpGSum = new uint16_t[cols*rows]; uint16_t *tmpGSum = new uint16_t[cols*rows];
uint16_t *tmpBSum = new uint16_t[cols*rows]; uint16_t *tmpBSum = new uint16_t[cols*rows];
@ -448,40 +399,56 @@ void Segment::box_blur(unsigned radius, bool smear) {
delete[] tmpBSum; delete[] tmpBSum;
delete[] tmpWSum; delete[] tmpWSum;
} }
*/
void Segment::moveX(int8_t delta, bool wrap) { void Segment::moveX(int delta, bool wrap) {
if (!isActive()) return; // not active if (!isActive() || !delta) return; // not active
const int cols = virtualWidth(); const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int rows = virtualHeight(); const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
if (!delta || abs(delta) >= cols) return; int absDelta = abs(delta);
uint32_t newPxCol[cols]; if (absDelta >= vW) return;
for (int y = 0; y < rows; y++) { uint32_t newPxCol[vW];
if (delta > 0) { int newDelta;
for (int x = 0; x < cols-delta; x++) newPxCol[x] = getPixelColorXY((x + delta), y); int stop = vW;
for (int x = cols-delta; x < cols; x++) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) - cols : x, y); int start = 0;
} else { if (wrap) newDelta = (delta + vW) % vW; // +cols in case delta < 0
for (int x = cols-1; x >= -delta; x--) newPxCol[x] = getPixelColorXY((x + delta), y); else {
for (int x = -delta-1; x >= 0; x--) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) + cols : x, y); if (delta < 0) start = absDelta;
stop = vW - absDelta;
newDelta = delta > 0 ? delta : 0;
} }
for (int x = 0; x < cols; x++) setPixelColorXY(x, y, newPxCol[x]); for (int y = 0; y < vH; y++) {
for (int x = 0; x < stop; x++) {
int srcX = x + newDelta;
if (wrap) srcX %= vW; // Wrap using modulo when `wrap` is true
newPxCol[x] = getPixelColorXY(srcX, y);
}
for (int x = 0; x < stop; x++) setPixelColorXY(x + start, y, newPxCol[x]);
} }
} }
void Segment::moveY(int8_t delta, bool wrap) { void Segment::moveY(int delta, bool wrap) {
if (!isActive()) return; // not active if (!isActive() || !delta) return; // not active
const int cols = virtualWidth(); const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int rows = virtualHeight(); const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
if (!delta || abs(delta) >= rows) return; int absDelta = abs(delta);
uint32_t newPxCol[rows]; if (absDelta >= vH) return;
for (int x = 0; x < cols; x++) { uint32_t newPxCol[vH];
if (delta > 0) { int newDelta;
for (int y = 0; y < rows-delta; y++) newPxCol[y] = getPixelColorXY(x, (y + delta)); int stop = vH;
for (int y = rows-delta; y < rows; y++) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) - rows : y); int start = 0;
} else { if (wrap) newDelta = (delta + vH) % vH; // +rows in case delta < 0
for (int y = rows-1; y >= -delta; y--) newPxCol[y] = getPixelColorXY(x, (y + delta)); else {
for (int y = -delta-1; y >= 0; y--) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) + rows : y); if (delta < 0) start = absDelta;
stop = vH - absDelta;
newDelta = delta > 0 ? delta : 0;
} }
for (int y = 0; y < rows; y++) setPixelColorXY(x, y, newPxCol[y]); for (int x = 0; x < vW; x++) {
for (int y = 0; y < stop; y++) {
int srcY = y + newDelta;
if (wrap) srcY %= vH; // Wrap using modulo when `wrap` is true
newPxCol[y] = getPixelColorXY(x, srcY);
}
for (int y = 0; y < stop; y++) setPixelColorXY(x, y + start, newPxCol[y]);
} }
} }
@ -489,7 +456,7 @@ void Segment::moveY(int8_t delta, bool wrap) {
// @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down // @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down
// @param delta number of pixels to move // @param delta number of pixels to move
// @param wrap around // @param wrap around
void Segment::move(uint8_t dir, uint8_t delta, bool wrap) { void Segment::move(unsigned dir, unsigned delta, bool wrap) {
if (delta==0) return; if (delta==0) return;
switch (dir) { switch (dir) {
case 0: moveX( delta, wrap); break; case 0: moveX( delta, wrap); break;
@ -507,46 +474,49 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col,
if (!isActive() || radius == 0) return; // not active if (!isActive() || radius == 0) return; // not active
if (soft) { if (soft) {
// Xiaolin Wus algorithm // Xiaolin Wus algorithm
int rsq = radius*radius; const int rsq = radius*radius;
int x = 0; int x = 0;
int y = radius; int y = radius;
unsigned oldFade = 0; unsigned oldFade = 0;
while (x < y) { while (x < y) {
float yf = sqrtf(float(rsq - x*x)); // needs to be floating point float yf = sqrtf(float(rsq - x*x)); // needs to be floating point
unsigned fade = float(0xFFFF) * (ceilf(yf) - yf); // how much color to keep uint8_t fade = float(0xFF) * (ceilf(yf) - yf); // how much color to keep
if (oldFade > fade) y--; if (oldFade > fade) y--;
oldFade = fade; oldFade = fade;
setPixelColorXY(cx+x, cy+y, color_blend(col, getPixelColorXY(cx+x, cy+y), fade, true)); int px, py;
setPixelColorXY(cx-x, cy+y, color_blend(col, getPixelColorXY(cx-x, cy+y), fade, true)); for (uint8_t i = 0; i < 16; i++) {
setPixelColorXY(cx+x, cy-y, color_blend(col, getPixelColorXY(cx+x, cy-y), fade, true)); int swaps = (i & 0x4 ? 1 : 0); // 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1
setPixelColorXY(cx-x, cy-y, color_blend(col, getPixelColorXY(cx-x, cy-y), fade, true)); int adj = (i < 8) ? 0 : 1; // 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1
setPixelColorXY(cx+y, cy+x, color_blend(col, getPixelColorXY(cx+y, cy+x), fade, true)); int dx = (i & 1) ? -1 : 1; // 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1
setPixelColorXY(cx-y, cy+x, color_blend(col, getPixelColorXY(cx-y, cy+x), fade, true)); int dy = (i & 2) ? -1 : 1; // 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1
setPixelColorXY(cx+y, cy-x, color_blend(col, getPixelColorXY(cx+y, cy-x), fade, true)); if (swaps) {
setPixelColorXY(cx-y, cy-x, color_blend(col, getPixelColorXY(cx-y, cy-x), fade, true)); px = cx + (y - adj) * dx;
setPixelColorXY(cx+x, cy+y-1, color_blend(getPixelColorXY(cx+x, cy+y-1), col, fade, true)); py = cy + x * dy;
setPixelColorXY(cx-x, cy+y-1, color_blend(getPixelColorXY(cx-x, cy+y-1), col, fade, true)); } else {
setPixelColorXY(cx+x, cy-y+1, color_blend(getPixelColorXY(cx+x, cy-y+1), col, fade, true)); px = cx + x * dx;
setPixelColorXY(cx-x, cy-y+1, color_blend(getPixelColorXY(cx-x, cy-y+1), col, fade, true)); py = cy + (y - adj) * dy;
setPixelColorXY(cx+y-1, cy+x, color_blend(getPixelColorXY(cx+y-1, cy+x), col, fade, true)); }
setPixelColorXY(cx-y+1, cy+x, color_blend(getPixelColorXY(cx-y+1, cy+x), col, fade, true)); uint32_t pixCol = getPixelColorXY(px, py);
setPixelColorXY(cx+y-1, cy-x, color_blend(getPixelColorXY(cx+y-1, cy-x), col, fade, true)); setPixelColorXY(px, py, adj ?
setPixelColorXY(cx-y+1, cy-x, color_blend(getPixelColorXY(cx-y+1, cy-x), col, fade, true)); color_blend(pixCol, col, fade) :
color_blend(col, pixCol, fade));
}
x++; x++;
} }
} else { } else {
// pre-scale color for all pixels
col = color_fade(col, _segBri);
_colorScaled = true;
// Bresenhams Algorithm // Bresenhams Algorithm
int d = 3 - (2*radius); int d = 3 - (2*radius);
int y = radius, x = 0; int y = radius, x = 0;
while (y >= x) { while (y >= x) {
setPixelColorXY(cx+x, cy+y, col); for (int i = 0; i < 4; i++) {
setPixelColorXY(cx-x, cy+y, col); int dx = (i & 1) ? -x : x;
setPixelColorXY(cx+x, cy-y, col); int dy = (i & 2) ? -y : y;
setPixelColorXY(cx-x, cy-y, col); setPixelColorXY(cx + dx, cy + dy, col);
setPixelColorXY(cx+y, cy+x, col); setPixelColorXY(cx + dy, cy + dx, col);
setPixelColorXY(cx-y, cy+x, col); }
setPixelColorXY(cx+y, cy-x, col);
setPixelColorXY(cx-y, cy-x, col);
x++; x++;
if (d > 0) { if (d > 0) {
y--; y--;
@ -555,33 +525,38 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col,
d += 4 * x + 6; d += 4 * x + 6;
} }
} }
_colorScaled = false;
} }
} }
// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs // by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) {
if (!isActive() || radius == 0) return; // not active if (!isActive() || radius == 0) return; // not active
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
// draw soft bounding circle // draw soft bounding circle
if (soft) drawCircle(cx, cy, radius, col, soft); if (soft) drawCircle(cx, cy, radius, col, soft);
// pre-scale color for all pixels
col = color_fade(col, _segBri);
_colorScaled = true;
// fill it // fill it
const int cols = virtualWidth();
const int rows = virtualHeight();
for (int y = -radius; y <= radius; y++) { for (int y = -radius; y <= radius; y++) {
for (int x = -radius; x <= radius; x++) { for (int x = -radius; x <= radius; x++) {
if (x * x + y * y <= radius * radius && if (x * x + y * y <= radius * radius &&
int(cx)+x>=0 && int(cy)+y>=0 && int(cx)+x >= 0 && int(cy)+y >= 0 &&
int(cx)+x<cols && int(cy)+y<rows) int(cx)+x < vW && int(cy)+y < vH)
setPixelColorXY(cx + x, cy + y, col); setPixelColorXY(cx + x, cy + y, col);
} }
} }
_colorScaled = false;
} }
//line function //line function
void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) { void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) {
if (!isActive()) return; // not active if (!isActive()) return; // not active
const int cols = virtualWidth(); const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int rows = virtualHeight(); const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return; if (x0 >= vW || x1 >= vW || y0 >= vH || y1 >= vH) return;
const int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1; // x distance & step const int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1; // x distance & step
const int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1; // y distance & step const int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1; // y distance & step
@ -608,17 +583,20 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
float gradient = x1-x0 == 0 ? 1.0f : float(y1-y0) / float(x1-x0); float gradient = x1-x0 == 0 ? 1.0f : float(y1-y0) / float(x1-x0);
float intersectY = y0; float intersectY = y0;
for (int x = x0; x <= x1; x++) { for (int x = x0; x <= x1; x++) {
unsigned keep = float(0xFFFF) * (intersectY-int(intersectY)); // how much color to keep uint8_t keep = float(0xFF) * (intersectY-int(intersectY)); // how much color to keep
unsigned seep = 0xFFFF - keep; // how much background to keep uint8_t seep = 0xFF - keep; // how much background to keep
int y = int(intersectY); int y = int(intersectY);
if (steep) std::swap(x,y); // temporaryly swap if steep if (steep) std::swap(x,y); // temporaryly swap if steep
// pixel coverage is determined by fractional part of y co-ordinate // pixel coverage is determined by fractional part of y co-ordinate
setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep, true)); setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep));
setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep, true)); setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep));
intersectY += gradient; intersectY += gradient;
if (steep) std::swap(x,y); // restore if steep if (steep) std::swap(x,y); // restore if steep
} }
} else { } else {
// pre-scale color for all pixels
c = color_fade(c, _segBri);
_colorScaled = true;
// Bresenham's algorithm // Bresenham's algorithm
int err = (dx>dy ? dx : -dy)/2; // error direction int err = (dx>dy ? dx : -dy)/2; // error direction
for (;;) { for (;;) {
@ -628,6 +606,7 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
if (e2 >-dx) { err -= dy; x0 += sx; } if (e2 >-dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; } if (e2 < dy) { err += dx; y0 += sy; }
} }
_colorScaled = false;
} }
} }
@ -639,16 +618,15 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
// draws a raster font character on canvas // draws a raster font character on canvas
// only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM // only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM
void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate) { void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate, bool usePalGrad) {
if (!isActive()) return; // not active if (!isActive()) return; // not active
if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported
chr -= 32; // align with font table entries chr -= 32; // align with font table entries
const int cols = virtualWidth();
const int rows = virtualHeight();
const int font = w*h; const int font = w*h;
CRGB col = CRGB(color); CRGB col = CRGB(color);
CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col); CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col);
if(usePalGrad) grad = SEGPALETTE; // selected palette as gradient
//if (w<5 || w>6 || h!=8) return; //if (w<5 || w>6 || h!=8) return;
for (int i = 0; i<h; i++) { // character height for (int i = 0; i<h; i++) { // character height
@ -661,7 +639,10 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w,
case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font
default: return; default: return;
} }
col = ColorFromPalette(grad, (i+1)*255/h, 255, NOBLEND); uint32_t c = ColorFromPaletteWLED(grad, (i+1)*255/h, 255, NOBLEND);
// pre-scale color for all pixels
c = color_fade(c, _segBri);
_colorScaled = true;
for (int j = 0; j<w; j++) { // character width for (int j = 0; j<w; j++) { // character width
int x0, y0; int x0, y0;
switch (rotate) { switch (rotate) {
@ -671,11 +652,12 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w,
case 1: x0 = x + i; y0 = y + j; break; // +90 deg case 1: x0 = x + i; y0 = y + j; break; // +90 deg
default: x0 = x + (w-1) - j; y0 = y + i; break; // no rotation default: x0 = x + (w-1) - j; y0 = y + i; break; // no rotation
} }
if (x0 < 0 || x0 >= cols || y0 < 0 || y0 >= rows) continue; // drawing off-screen if (x0 < 0 || x0 >= (int)vWidth() || y0 < 0 || y0 >= (int)vHeight()) continue; // drawing off-screen
if (((bits>>(j+(8-w))) & 0x01)) { // bit set if (((bits>>(j+(8-w))) & 0x01)) { // bit set
setPixelColorXY(x0, y0, col); setPixelColorXY(x0, y0, c);
} }
} }
_colorScaled = false;
} }
} }

View File

@ -66,10 +66,15 @@ static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTy
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Segment class implementation // Segment class implementation
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[] unsigned Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[]
uint16_t Segment::maxWidth = DEFAULT_LED_COUNT; uint16_t Segment::maxWidth = DEFAULT_LED_COUNT;
uint16_t Segment::maxHeight = 1; uint16_t Segment::maxHeight = 1;
unsigned Segment::_vLength = 0;
unsigned Segment::_vWidth = 0;
unsigned Segment::_vHeight = 0;
uint8_t Segment::_segBri = 0;
uint32_t Segment::_currentColors[NUM_COLORS] = {0,0,0};
bool Segment::_colorScaled = false;
CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black); CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black);
CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
@ -196,19 +201,7 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0;
if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip
//default palette. Differs depending on effect //default palette. Differs depending on effect
if (pal == 0) switch (mode) { if (pal == 0) pal = _default_palette; //load default palette set in FX _data, party colors as default
case FX_MODE_FIRE_2012 : pal = 35; break; // heat palette
case FX_MODE_COLORWAVES : pal = 26; break; // landscape 33
case FX_MODE_FILLNOISE8 : pal = 9; break; // ocean colors
case FX_MODE_NOISE16_1 : pal = 20; break; // Drywet
case FX_MODE_NOISE16_2 : pal = 43; break; // Blue cyan yellow
case FX_MODE_NOISE16_3 : pal = 35; break; // heat palette
case FX_MODE_NOISE16_4 : pal = 26; break; // landscape 33
case FX_MODE_GLITTER : pal = 11; break; // rainbow colors
case FX_MODE_SUNRISE : pal = 35; break; // heat palette
case FX_MODE_RAILWAY : pal = 3; break; // prim + sec
case FX_MODE_2DSOAP : pal = 11; break; // rainbow colors
}
switch (pal) { switch (pal) {
case 0: //default palette. Exceptions for specific effects above case 0: //default palette. Exceptions for specific effects above
targetPalette = PartyColors_p; break; targetPalette = PartyColors_p; break;
@ -385,7 +378,7 @@ void Segment::restoreSegenv(tmpsegd_t &tmpSeg) {
} }
#endif #endif
uint8_t IRAM_ATTR Segment::currentBri(bool useCct) const { uint8_t Segment::currentBri(bool useCct) const {
unsigned prog = progress(); unsigned prog = progress();
if (prog < 0xFFFFU) { if (prog < 0xFFFFU) {
unsigned curBri = (useCct ? cct : (on ? opacity : 0)) * prog; unsigned curBri = (useCct ? cct : (on ? opacity : 0)) * prog;
@ -403,16 +396,31 @@ uint8_t Segment::currentMode() const {
return mode; return mode;
} }
uint32_t IRAM_ATTR_YN Segment::currentColor(uint8_t slot) const { uint32_t Segment::currentColor(uint8_t slot) const {
if (slot >= NUM_COLORS) slot = 0; if (slot >= NUM_COLORS) slot = 0;
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot]; return isInTransition() ? color_blend16(_t->_segT._colorT[slot], colors[slot], progress()) : colors[slot];
#else #else
return isInTransition() ? color_blend(_t->_colorT[slot], colors[slot], progress(), true) : colors[slot]; return isInTransition() ? color_blend16(_t->_colorT[slot], colors[slot], progress()) : colors[slot];
#endif #endif
} }
void Segment::setCurrentPalette() { // pre-calculate drawing parameters for faster access (based on the idea from @softhack007 from MM fork)
void Segment::beginDraw() {
_vWidth = virtualWidth();
_vHeight = virtualHeight();
_vLength = virtualLength();
_segBri = currentBri();
// adjust gamma for effects
for (unsigned i = 0; i < NUM_COLORS; i++) {
#ifndef WLED_DISABLE_MODE_BLEND
uint32_t col = isInTransition() ? color_blend16(_t->_segT._colorT[i], colors[i], progress()) : colors[i];
#else
uint32_t col = isInTransition() ? color_blend16(_t->_colorT[i], colors[i], progress()) : colors[i];
#endif
_currentColors[i] = gamma32(col);
}
// load palette into _currentPalette
loadPalette(_currentPalette, palette); loadPalette(_currentPalette, palette);
unsigned prog = progress(); unsigned prog = progress();
if (strip.paletteFade && prog < 0xFFFFU) { if (strip.paletteFade && prog < 0xFFFFU) {
@ -444,8 +452,10 @@ void Segment::handleRandomPalette() {
nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48);
} }
// segId is given when called from network callback, changes are queued if that segment is currently in its effect function // sets Segment geometry (length or width/height and grouping, spacing and offset as well as 2D mapping)
void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) { // strip must be suspended (strip.suspend()) before calling this function
// this function may call fill() to clear pixels if spacing or mapping changed (which requires setting _vWidth, _vHeight, _vLength or beginDraw())
void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y, uint8_t m12) {
// return if neither bounds nor grouping have changed // return if neither bounds nor grouping have changed
bool boundsUnchanged = (start == i1 && stop == i2); bool boundsUnchanged = (start == i1 && stop == i2);
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
@ -453,11 +463,19 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t
#endif #endif
if (boundsUnchanged if (boundsUnchanged
&& (!grp || (grouping == grp && spacing == spc)) && (!grp || (grouping == grp && spacing == spc))
&& (ofs == UINT16_MAX || ofs == offset)) return; && (ofs == UINT16_MAX || ofs == offset)
&& (m12 == map1D2D)
) return;
stateChanged = true; // send UDP/WS broadcast stateChanged = true; // send UDP/WS broadcast
if (stop) fill(BLACK); // turn old segment range off (clears pixels if changing spacing) if (stop || spc != spacing || m12 != map1D2D) {
_vWidth = virtualWidth();
_vHeight = virtualHeight();
_vLength = virtualLength();
_segBri = currentBri();
fill(BLACK); // turn old segment range off or clears pixels if changing spacing (requires _vWidth/_vHeight/_vLength/_segBri)
}
if (grp) { // prevent assignment of 0 if (grp) { // prevent assignment of 0
grouping = grp; grouping = grp;
spacing = spc; spacing = spc;
@ -466,6 +484,7 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t
spacing = 0; spacing = 0;
} }
if (ofs < UINT16_MAX) offset = ofs; if (ofs < UINT16_MAX) offset = ofs;
map1D2D = constrain(m12, 0, 7);
DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1); DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1);
DEBUG_PRINT(','); DEBUG_PRINT(i2); DEBUG_PRINT(','); DEBUG_PRINT(i2);
@ -554,9 +573,9 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
if (modeBlending) startTransition(strip.getTransition()); // set effect transitions if (modeBlending) startTransition(strip.getTransition()); // set effect transitions
#endif #endif
mode = fx; mode = fx;
int sOpt;
// load default values from effect string // load default values from effect string
if (loadDefaults) { if (loadDefaults) {
int sOpt;
sOpt = extractModeDefaults(fx, "sx"); speed = (sOpt >= 0) ? sOpt : DEFAULT_SPEED; sOpt = extractModeDefaults(fx, "sx"); speed = (sOpt >= 0) ? sOpt : DEFAULT_SPEED;
sOpt = extractModeDefaults(fx, "ix"); intensity = (sOpt >= 0) ? sOpt : DEFAULT_INTENSITY; sOpt = extractModeDefaults(fx, "ix"); intensity = (sOpt >= 0) ? sOpt : DEFAULT_INTENSITY;
sOpt = extractModeDefaults(fx, "c1"); custom1 = (sOpt >= 0) ? sOpt : DEFAULT_C1; sOpt = extractModeDefaults(fx, "c1"); custom1 = (sOpt >= 0) ? sOpt : DEFAULT_C1;
@ -573,6 +592,9 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business
sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) setPalette(sOpt); //else setPalette(0); sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) setPalette(sOpt); //else setPalette(0);
} }
sOpt = extractModeDefaults(fx, "pal"); // always extract 'pal' to set _default_palette
if(sOpt <= 0) sOpt = 6; // partycolors if zero or not set
_default_palette = sOpt; // _deault_palette is loaded into pal0 in loadPalette() (if selected)
markForReset(); markForReset();
stateChanged = true; // send UDP/WS broadcast stateChanged = true; // send UDP/WS broadcast
} }
@ -591,14 +613,14 @@ Segment &Segment::setPalette(uint8_t pal) {
} }
// 2D matrix // 2D matrix
unsigned IRAM_ATTR Segment::virtualWidth() const { unsigned Segment::virtualWidth() const {
unsigned groupLen = groupLength(); unsigned groupLen = groupLength();
unsigned vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; unsigned vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen;
if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED
return vWidth; return vWidth;
} }
unsigned IRAM_ATTR Segment::virtualHeight() const { unsigned Segment::virtualHeight() const {
unsigned groupLen = groupLength(); unsigned groupLen = groupLength();
unsigned vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; unsigned vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen;
if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED
@ -642,7 +664,7 @@ static int getPinwheelLength(int vW, int vH) {
#endif #endif
// 1D strip // 1D strip
uint16_t IRAM_ATTR Segment::virtualLength() const { uint16_t Segment::virtualLength() const {
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
if (is2D()) { if (is2D()) {
unsigned vW = virtualWidth(); unsigned vW = virtualWidth();
@ -676,18 +698,31 @@ uint16_t IRAM_ATTR Segment::virtualLength() const {
void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
{ {
if (!isActive()) return; // not active if (!isActive() || i < 0) return; // not active or invalid index
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
int vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) int vStrip = 0;
#endif #endif
i &= 0xFFFF; int vL = vLength();
// if the 1D effect is using virtual strips "i" will have virtual strip id stored in upper 16 bits
if (i >= virtualLength() || i<0) return; // if pixel would fall out of segment just exit // in such case "i" will be > virtualLength()
if (i >= vL) {
// check if this is a virtual strip
#ifndef WLED_DISABLE_2D
vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows)
i &= 0xFFFF; //truncate vstrip index
if (i >= vL) return; // if pixel would still fall out of segment just exit
#else
return;
#endif
}
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
if (is2D()) { if (is2D()) {
int vH = virtualHeight(); // segment height in logical pixels const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
int vW = virtualWidth(); const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
// pre-scale color for all pixels
col = color_fade(col, _segBri);
_colorScaled = true;
switch (map1D2D) { switch (map1D2D) {
case M12_Pixels: case M12_Pixels:
// use all available pixels as a long strip // use all available pixels as a long strip
@ -695,12 +730,12 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
break; break;
case M12_pBar: case M12_pBar:
// expand 1D effect vertically or have it play on virtual strips // expand 1D effect vertically or have it play on virtual strips
if (vStrip>0) setPixelColorXY(vStrip - 1, vH - i - 1, col); if (vStrip > 0) setPixelColorXY(vStrip - 1, vH - i - 1, col);
else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col); else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col);
break; break;
case M12_pArc: case M12_pArc:
// expand in circular fashion from center // expand in circular fashion from center
if (i==0) if (i == 0)
setPixelColorXY(0, 0, col); setPixelColorXY(0, 0, col);
else { else {
float r = i; float r = i;
@ -779,13 +814,14 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
break; break;
} }
} }
_colorScaled = false;
return; return;
} else if (Segment::maxHeight!=1 && (width()==1 || height()==1)) { } else if (Segment::maxHeight != 1 && (width() == 1 || height() == 1)) {
if (start < Segment::maxWidth*Segment::maxHeight) { if (start < Segment::maxWidth*Segment::maxHeight) {
// we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed) // we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed)
int x = 0, y = 0; int x = 0, y = 0;
if (virtualHeight()>1) y = i; if (vHeight() > 1) y = i;
if (virtualWidth() >1) x = i; if (vWidth() > 1) x = i;
setPixelColorXY(x, y, col); setPixelColorXY(x, y, col);
return; return;
} }
@ -793,10 +829,8 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
#endif #endif
unsigned len = length(); unsigned len = length();
uint8_t _bri_t = currentBri(); // if color is unscaled
if (_bri_t < 255) { if (!_colorScaled) col = color_fade(col, _segBri);
col = color_fade(col, _bri_t);
}
// expand pixel (taking into account start, grouping, spacing [and offset]) // expand pixel (taking into account start, grouping, spacing [and offset])
i = i * groupLength(); i = i * groupLength();
@ -819,14 +853,14 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
indexMir += offset; // offset/phase indexMir += offset; // offset/phase
if (indexMir >= stop) indexMir -= len; // wrap if (indexMir >= stop) indexMir -= len; // wrap
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true); if (_modeBlend) tmpCol = color_blend16(strip.getPixelColor(indexMir), col, uint16_t(0xFFFFU - progress()));
#endif #endif
strip.setPixelColor(indexMir, tmpCol); strip.setPixelColor(indexMir, tmpCol);
} }
indexSet += offset; // offset/phase indexSet += offset; // offset/phase
if (indexSet >= stop) indexSet -= len; // wrap if (indexSet >= stop) indexSet -= len; // wrap
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true); if (_modeBlend) tmpCol = color_blend16(strip.getPixelColor(indexSet), col, uint16_t(0xFFFFU - progress()));
#endif #endif
strip.setPixelColor(indexSet, tmpCol); strip.setPixelColor(indexSet, tmpCol);
} }
@ -871,23 +905,20 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa)
uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
{ {
if (!isActive()) return 0; // not active if (!isActive()) return 0; // not active
#ifndef WLED_DISABLE_2D
int vStrip = i>>16;
#endif
i &= 0xFFFF;
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
if (is2D()) { if (is2D()) {
int vH = virtualHeight(); // segment height in logical pixels const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
int vW = virtualWidth(); const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
switch (map1D2D) { switch (map1D2D) {
case M12_Pixels: case M12_Pixels:
return getPixelColorXY(i % vW, i / vW); return getPixelColorXY(i % vW, i / vW);
break; break;
case M12_pBar: case M12_pBar: {
if (vStrip>0) return getPixelColorXY(vStrip - 1, vH - i -1); int vStrip = i>>16; // virtual strips are only relevant in Bar expansion mode
if (vStrip > 0) return getPixelColorXY(vStrip - 1, vH - (i & 0xFFFF) -1);
else return getPixelColorXY(0, vH - i -1); else return getPixelColorXY(0, vH - i -1);
break; break; }
case M12_pArc: case M12_pArc:
if (i >= vW && i >= vH) { if (i >= vW && i >= vH) {
unsigned vI = sqrt16(i*i/2); unsigned vI = sqrt16(i*i/2);
@ -931,7 +962,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
} }
#endif #endif
if (reverse) i = virtualLength() - i - 1; if (reverse) i = vLength() - i - 1;
i *= groupLength(); i *= groupLength();
i += start; i += start;
// offset/phase // offset/phase
@ -940,7 +971,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
return strip.getPixelColor(i); return strip.getPixelColor(i);
} }
uint8_t Segment::differs(Segment& b) const { uint8_t Segment::differs(const Segment& b) const {
uint8_t d = 0; uint8_t d = 0;
if (start != b.start) d |= SEG_DIFFERS_BOUNDS; if (start != b.start) d |= SEG_DIFFERS_BOUNDS;
if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS; if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS;
@ -1020,12 +1051,16 @@ void Segment::refreshLightCapabilities() {
*/ */
void Segment::fill(uint32_t c) { void Segment::fill(uint32_t c) {
if (!isActive()) return; // not active if (!isActive()) return; // not active
const int cols = is2D() ? virtualWidth() : virtualLength(); const int cols = is2D() ? vWidth() : vLength();
const int rows = virtualHeight(); // will be 1 for 1D const int rows = vHeight(); // will be 1 for 1D
// pre-scale color for all pixels
c = color_fade(c, _segBri);
_colorScaled = true;
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
if (is2D()) setPixelColorXY(x, y, c); if (is2D()) setPixelColorXY(x, y, c);
else setPixelColor(x, c); else setPixelColor(x, c);
} }
_colorScaled = false;
} }
/* /*
@ -1033,8 +1068,8 @@ void Segment::fill(uint32_t c) {
*/ */
void Segment::fade_out(uint8_t rate) { void Segment::fade_out(uint8_t rate) {
if (!isActive()) return; // not active if (!isActive()) return; // not active
const int cols = is2D() ? virtualWidth() : virtualLength(); const int cols = is2D() ? vWidth() : vLength();
const int rows = virtualHeight(); // will be 1 for 1D const int rows = vHeight(); // will be 1 for 1D
rate = (255-rate) >> 1; rate = (255-rate) >> 1;
float mappedRate = 1.0f / (float(rate) + 1.1f); float mappedRate = 1.0f / (float(rate) + 1.1f);
@ -1072,8 +1107,8 @@ void Segment::fade_out(uint8_t rate) {
// fades all pixels to black using nscale8() // fades all pixels to black using nscale8()
void Segment::fadeToBlackBy(uint8_t fadeBy) { void Segment::fadeToBlackBy(uint8_t fadeBy) {
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
const int cols = is2D() ? virtualWidth() : virtualLength(); const int cols = is2D() ? vWidth() : vLength();
const int rows = virtualHeight(); // will be 1 for 1D const int rows = vHeight(); // will be 1 for 1D
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
if (is2D()) setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), 255-fadeBy)); if (is2D()) setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), 255-fadeBy));
@ -1083,20 +1118,21 @@ void Segment::fadeToBlackBy(uint8_t fadeBy) {
/* /*
* blurs segment content, source: FastLED colorutils.cpp * blurs segment content, source: FastLED colorutils.cpp
* Note: for blur_amount > 215 this function does not work properly (creates alternating pattern)
*/ */
void Segment::blur(uint8_t blur_amount, bool smear) { void Segment::blur(uint8_t blur_amount, bool smear) {
if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur" if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur"
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
if (is2D()) { if (is2D()) {
// compatibility with 2D // compatibility with 2D
blur2D(blur_amount, smear); blur2D(blur_amount, blur_amount, smear); // symmetrical 2D blur
//box_blur(map(blur_amount,1,255,1,3), smear); //box_blur(map(blur_amount,1,255,1,3), smear);
return; return;
} }
#endif #endif
uint8_t keep = smear ? 255 : 255 - blur_amount; uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> (1 + smear); uint8_t seep = blur_amount >> 1;
unsigned vlength = virtualLength(); unsigned vlength = vLength();
uint32_t carryover = BLACK; uint32_t carryover = BLACK;
uint32_t lastnew; uint32_t lastnew;
uint32_t last; uint32_t last;
@ -1106,12 +1142,11 @@ void Segment::blur(uint8_t blur_amount, bool smear) {
uint32_t part = color_fade(cur, seep); uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep); curnew = color_fade(cur, keep);
if (i > 0) { if (i > 0) {
if (carryover) curnew = color_add(curnew, carryover, true); if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part, true); uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed // optimization: only set pixel if color has changed
if (last != prev) setPixelColor(i - 1, prev); if (last != prev) setPixelColor(i - 1, prev);
} else // first pixel } else setPixelColor(i, curnew); // first pixel
setPixelColor(i, curnew);
lastnew = curnew; lastnew = curnew;
last = cur; // save original value for comparison on next iteration last = cur; // save original value for comparison on next iteration
carryover = part; carryover = part;
@ -1126,11 +1161,11 @@ void Segment::blur(uint8_t blur_amount, bool smear) {
*/ */
uint32_t Segment::color_wheel(uint8_t pos) const { uint32_t Segment::color_wheel(uint8_t pos) const {
if (palette) return color_from_palette(pos, false, true, 0); // perhaps "strip.paletteBlend < 2" should be better instead of "true" if (palette) return color_from_palette(pos, false, true, 0); // perhaps "strip.paletteBlend < 2" should be better instead of "true"
uint8_t w = W(currentColor(0)); uint8_t w = W(getCurrentColor(0));
pos = 255 - pos; pos = 255 - pos;
if (pos < 85) { if (pos < 85) {
return RGBW32((255 - pos * 3), 0, (pos * 3), w); return RGBW32((255 - pos * 3), 0, (pos * 3), w);
} else if(pos < 170) { } else if (pos < 170) {
pos -= 85; pos -= 85;
return RGBW32(0, (pos * 3), (255 - pos * 3), w); return RGBW32(0, (pos * 3), (255 - pos * 3), w);
} else { } else {
@ -1149,18 +1184,21 @@ uint32_t Segment::color_wheel(uint8_t pos) const {
* @returns Single color from palette * @returns Single color from palette
*/ */
uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) const { uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) const {
uint32_t color = gamma32(currentColor(mcol)); uint32_t color = getCurrentColor(mcol < NUM_COLORS ? mcol : 0);
// default palette or no RGB support on segment // default palette or no RGB support on segment
if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) {
return color_fade(color, pbri, true);
}
const int vL = vLength();
unsigned paletteIndex = i; unsigned paletteIndex = i;
if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); if (mapping && vL > 1) paletteIndex = (i*255)/(vL -1);
// paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined)
if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end"
CRGB fastled_col = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global CRGBW palcol = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global
palcol.w = W(color);
return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, W(color)); return palcol.color32;
} }
@ -1324,11 +1362,6 @@ void WS2812FX::service() {
if (!seg.freeze) { //only run effect function if not frozen if (!seg.freeze) { //only run effect function if not frozen
int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based)
_virtualSegmentLength = seg.virtualLength(); //SEGLEN
_colors_t[0] = gamma32(seg.currentColor(0));
_colors_t[1] = gamma32(seg.currentColor(1));
_colors_t[2] = gamma32(seg.currentColor(2));
seg.setCurrentPalette(); // load actual palette
// when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio
// when cctFromRgb is true we implicitly calculate WW and CW from RGB values // when cctFromRgb is true we implicitly calculate WW and CW from RGB values
if (cctFromRgb) BusManager::setSegmentCCT(-1); if (cctFromRgb) BusManager::setSegmentCCT(-1);
@ -1340,13 +1373,14 @@ void WS2812FX::service() {
// overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer
// would need to be allocated for each effect and then blended together for each pixel. // would need to be allocated for each effect and then blended together for each pixel.
[[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition
seg.beginDraw(); // set up parameters for get/setPixelColor()
frameDelay = (*_mode[seg.mode])(); // run new/current mode frameDelay = (*_mode[seg.mode])(); // run new/current mode
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
if (modeBlending && seg.mode != tmpMode) { if (modeBlending && seg.mode != tmpMode) {
Segment::tmpsegd_t _tmpSegData; Segment::tmpsegd_t _tmpSegData;
Segment::modeBlend(true); // set semaphore Segment::modeBlend(true); // set semaphore
seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state)
_virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) seg.beginDraw(); // set up parameters for get/setPixelColor()
unsigned d2 = (*_mode[tmpMode])(); // run old mode unsigned d2 = (*_mode[tmpMode])(); // run old mode
seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state)
frameDelay = min(frameDelay,d2); // use shortest delay frameDelay = min(frameDelay,d2); // use shortest delay
@ -1362,7 +1396,6 @@ void WS2812FX::service() {
} }
_segment_index++; _segment_index++;
} }
_virtualSegmentLength = 0;
_isServicing = false; _isServicing = false;
_triggered = false; _triggered = false;
@ -1412,50 +1445,12 @@ void WS2812FX::show() {
} }
} }
/** void WS2812FX::setTargetFps(unsigned fps) {
* Returns a true value if any of the strips are still being updated.
* On some hardware (ESP32), strip updates are done asynchronously.
*/
bool WS2812FX::isUpdating() const {
return !BusManager::canAllShow();
}
/**
* Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough.
* Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accuracy varies
*/
uint16_t WS2812FX::getFps() const {
if (millis() - _lastShow > 2000) return 0;
return (FPS_MULTIPLIER * _cumulativeFps) >> FPS_CALC_SHIFT; // _cumulativeFps is stored in fixed point
}
void WS2812FX::setTargetFps(uint8_t fps) {
if (fps <= 250) _targetFps = fps; if (fps <= 250) _targetFps = fps;
if (_targetFps > 0) _frametime = 1000 / _targetFps; if (_targetFps > 0) _frametime = 1000 / _targetFps;
else _frametime = MIN_FRAME_DELAY; // unlimited mode else _frametime = MIN_FRAME_DELAY; // unlimited mode
} }
void WS2812FX::setMode(uint8_t segid, uint8_t m) {
if (segid >= _segments.size()) return;
if (m >= getModeCount()) m = getModeCount() - 1;
if (_segments[segid].mode != m) {
_segments[segid].setMode(m); // do not load defaults
}
}
//applies to all active and selected segments
void WS2812FX::setColor(uint8_t slot, uint32_t c) {
if (slot >= NUM_COLORS) return;
for (segment &seg : _segments) {
if (seg.isActive() && seg.isSelected()) {
seg.setColor(slot, c);
}
}
}
void WS2812FX::setCCT(uint16_t k) { void WS2812FX::setCCT(uint16_t k) {
for (segment &seg : _segments) { for (segment &seg : _segments) {
if (seg.isActive() && seg.isSelected()) { if (seg.isActive() && seg.isSelected()) {
@ -1502,7 +1497,7 @@ uint8_t WS2812FX::getFirstSelectedSegId() const {
return getMainSegmentId(); return getMainSegmentId();
} }
void WS2812FX::setMainSegmentId(uint8_t n) { void WS2812FX::setMainSegmentId(unsigned n) {
_mainSegment = 0; _mainSegment = 0;
if (n < _segments.size()) { if (n < _segments.size()) {
_mainSegment = n; _mainSegment = n;
@ -1578,23 +1573,10 @@ void WS2812FX::purgeSegments() {
} }
} }
Segment& WS2812FX::getSegment(uint8_t id) { Segment& WS2812FX::getSegment(unsigned id) {
return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors
} }
// sets new segment bounds, queues if that segment is currently running
void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) {
if (segId >= getSegmentsNum()) {
if (i2 <= i1) return; // do not append empty/inactive segments
appendSegment(Segment(0, strip.getLengthTotal()));
segId = getSegmentsNum()-1; // segments are added at the end of list
}
suspend();
_segments[segId].setUp(i1, i2, grouping, spacing, offset, startY, stopY);
resume();
if (segId > 0 && segId == getSegmentsNum()-1 && i2 <= i1) _segments.pop_back(); // if last segment was deleted remove it from vector
}
void WS2812FX::resetSegments() { void WS2812FX::resetSegments() {
_segments.clear(); // destructs all Segment as part of clearing _segments.clear(); // destructs all Segment as part of clearing
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
@ -1793,7 +1775,7 @@ void WS2812FX::loadCustomPalettes() {
} }
//load custom mapping table from JSON file (called from finalizeInit() or deserializeState()) //load custom mapping table from JSON file (called from finalizeInit() or deserializeState())
bool WS2812FX::deserializeMap(uint8_t n) { bool WS2812FX::deserializeMap(unsigned n) {
// 2D support creates its own ledmap (on the fly) if a ledmap.json exists it will overwrite built one. // 2D support creates its own ledmap (on the fly) if a ledmap.json exists it will overwrite built one.
char fileName[32]; char fileName[32];
@ -1845,14 +1827,6 @@ bool WS2812FX::deserializeMap(uint8_t n) {
return (customMappingSize > 0); return (customMappingSize > 0);
} }
uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) const {
// convert logical address to physical
if (index < customMappingSize
&& (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index];
return index;
}
WS2812FX* WS2812FX::instance = nullptr; WS2812FX* WS2812FX::instance = nullptr;
@ -1865,5 +1839,5 @@ const char JSON_palette_names[] PROGMEM = R"=====([
"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", "Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura",
"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf", "Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf",
"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", "Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide",
"Candy2" "Candy2","Traffic Light"
])====="; ])=====";

View File

@ -126,10 +126,10 @@ void onAlexaChange(EspalexaDevice* dev)
} else { } else {
colorKtoRGB(k, rgbw); colorKtoRGB(k, rgbw);
} }
strip.setColor(0, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3])); strip.getMainSegment().setColor(0, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]));
} else { } else {
uint32_t color = dev->getRGB(); uint32_t color = dev->getRGB();
strip.setColor(0, color); strip.getMainSegment().setColor(0, color);
} }
stateUpdated(CALL_MODE_ALEXA); stateUpdated(CALL_MODE_ALEXA);
} }

View File

@ -155,16 +155,6 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], is2Pin(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax); DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], is2Pin(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax);
} }
//fine tune power estimation constants for your setup
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
#ifndef MA_FOR_ESP
#ifdef ESP8266
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
#else
#define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA)
#endif
#endif
//DISCLAIMER //DISCLAIMER
//The following function attemps to calculate the current LED power usage, //The following function attemps to calculate the current LED power usage,
//and will limit the brightness to stay below a set amperage threshold. //and will limit the brightness to stay below a set amperage threshold.
@ -306,22 +296,22 @@ void BusDigital::setStatusPixel(uint32_t c) {
} }
} }
void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
if (!_valid) return; if (!_valid) return;
uint8_t cctWW = 0, cctCW = 0;
if (hasWhite()) c = autoWhiteCalc(c); if (hasWhite()) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
if (_data) { if (_data) {
size_t offset = pix * getNumberOfChannels(); size_t offset = pix * getNumberOfChannels();
uint8_t* dataptr = _data + offset;
if (hasRGB()) { if (hasRGB()) {
_data[offset++] = R(c); *dataptr++ = R(c);
_data[offset++] = G(c); *dataptr++ = G(c);
_data[offset++] = B(c); *dataptr++ = B(c);
} }
if (hasWhite()) _data[offset++] = W(c); if (hasWhite()) *dataptr++ = W(c);
// unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT // unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT
// we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio) // we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio)
if (hasCCT()) _data[offset] = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it if (hasCCT()) *dataptr = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it
} else { } else {
if (_reversed) pix = _len - pix -1; if (_reversed) pix = _len - pix -1;
pix += _skip; pix += _skip;
@ -336,16 +326,22 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) {
case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break;
} }
} }
if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); uint16_t wwcw = 0;
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW); if (hasCCT()) {
uint8_t cctWW = 0, cctCW = 0;
Bus::calculateCCT(c, cctWW, cctCW);
wwcw = (cctCW<<8) | cctWW;
}
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw);
} }
} }
// returns original color if global buffering is enabled, else returns lossly restored color from bus // returns original color if global buffering is enabled, else returns lossly restored color from bus
uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const { uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const {
if (!_valid) return 0; if (!_valid) return 0;
if (_data) { if (_data) {
size_t offset = pix * getNumberOfChannels(); const size_t offset = pix * getNumberOfChannels();
uint32_t c; uint32_t c;
if (!hasRGB()) { if (!hasRGB()) {
c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]); c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]);
@ -356,7 +352,7 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const {
} else { } else {
if (_reversed) pix = _len - pix -1; if (_reversed) pix = _len - pix -1;
pix += _skip; pix += _skip;
unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); const unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri); uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri);
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
unsigned r = R(c); unsigned r = R(c);
@ -501,7 +497,7 @@ BusPwm::BusPwm(BusConfig &bc)
DEBUG_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]); DEBUG_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]);
} }
void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { void BusPwm::setPixelColor(unsigned pix, uint32_t c) {
if (pix != 0 || !_valid) return; //only react to first pixel if (pix != 0 || !_valid) return; //only react to first pixel
if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c); if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) {
@ -538,7 +534,7 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) {
} }
//does no index check //does no index check
uint32_t BusPwm::getPixelColor(uint16_t pix) const { uint32_t BusPwm::getPixelColor(unsigned pix) const {
if (!_valid) return 0; if (!_valid) return 0;
// TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented) // TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented)
switch (_type) { switch (_type) {
@ -567,19 +563,15 @@ void BusPwm::show() {
const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8) const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8)
[[maybe_unused]] const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits) [[maybe_unused]] const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits)
// use CIE brightness formula (cubic) to fit (or approximate linearity of) human eye perceived brightness // use CIE brightness formula (linear + cubic) to approximate human eye perceived brightness
// the formula is based on 12 bit resolution as there is no need for greater precision
// see: https://en.wikipedia.org/wiki/Lightness // see: https://en.wikipedia.org/wiki/Lightness
unsigned pwmBri = (unsigned)_bri * 100; // enlarge to use integer math for linear response unsigned pwmBri = _bri;
if (pwmBri < 2040) { if (pwmBri < 21) { // linear response for values [0-20]
// linear response for values [0-20] pwmBri = (pwmBri * maxBri + 2300 / 2) / 2300 ; // adding '0.5' before division for correct rounding, 2300 gives a good match to CIE curve
pwmBri = ((pwmBri << 12) + 115043) / 230087; //adding '0.5' before division for correct rounding } else { // cubic response for values [21-255]
} else { float temp = float(pwmBri + 41) / float(255 + 41); // 41 is to match offset & slope to linear part
// cubic response for values [21-255]
pwmBri += 4080;
float temp = (float)pwmBri / 29580.0f;
temp = temp * temp * temp * (float)maxBri; temp = temp * temp * temp * (float)maxBri;
pwmBri = (unsigned)temp; // pwmBri is in range [0-maxBri] pwmBri = (unsigned)temp; // pwmBri is in range [0-maxBri] C
} }
[[maybe_unused]] unsigned hPoint = 0; // phase shift (0 - maxBri) [[maybe_unused]] unsigned hPoint = 0; // phase shift (0 - maxBri)
@ -674,7 +666,7 @@ BusOnOff::BusOnOff(BusConfig &bc)
DEBUG_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin); DEBUG_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin);
} }
void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) { void BusOnOff::setPixelColor(unsigned pix, uint32_t c) {
if (pix != 0 || !_valid) return; //only react to first pixel if (pix != 0 || !_valid) return; //only react to first pixel
c = autoWhiteCalc(c); c = autoWhiteCalc(c);
uint8_t r = R(c); uint8_t r = R(c);
@ -684,7 +676,7 @@ void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) {
_data[0] = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; _data[0] = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0;
} }
uint32_t BusOnOff::getPixelColor(uint16_t pix) const { uint32_t BusOnOff::getPixelColor(unsigned pix) const {
if (!_valid) return 0; if (!_valid) return 0;
return RGBW32(_data[0], _data[0], _data[0], _data[0]); return RGBW32(_data[0], _data[0], _data[0], _data[0]);
} }
@ -734,7 +726,7 @@ BusNetwork::BusNetwork(BusConfig &bc)
DEBUG_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]); DEBUG_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]);
} }
void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { void BusNetwork::setPixelColor(unsigned pix, uint32_t c) {
if (!_valid || pix >= _len) return; if (!_valid || pix >= _len) return;
if (_hasWhite) c = autoWhiteCalc(c); if (_hasWhite) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
@ -745,7 +737,7 @@ void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
if (_hasWhite) _data[offset+3] = W(c); if (_hasWhite) _data[offset+3] = W(c);
} }
uint32_t BusNetwork::getPixelColor(uint16_t pix) const { uint32_t BusNetwork::getPixelColor(unsigned pix) const {
if (!_valid || pix >= _len) return 0; if (!_valid || pix >= _len) return 0;
unsigned offset = pix * _UDPchannels; unsigned offset = pix * _UDPchannels;
return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (hasWhite() ? _data[offset+3] : 0)); return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (hasWhite() ? _data[offset+3] : 0));
@ -943,7 +935,6 @@ void BusManager::show() {
busses[i]->show(); busses[i]->show();
_milliAmpsUsed += busses[i]->getUsedCurrent(); _milliAmpsUsed += busses[i]->getUsedCurrent();
} }
if (_milliAmpsUsed) _milliAmpsUsed += MA_FOR_ESP;
} }
void BusManager::setStatusPixel(uint32_t c) { void BusManager::setStatusPixel(uint32_t c) {
@ -952,7 +943,7 @@ void BusManager::setStatusPixel(uint32_t c) {
} }
} }
void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c) { void IRAM_ATTR BusManager::setPixelColor(unsigned pix, uint32_t c) {
for (unsigned i = 0; i < numBusses; i++) { for (unsigned i = 0; i < numBusses; i++) {
unsigned bstart = busses[i]->getStart(); unsigned bstart = busses[i]->getStart();
if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue; if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue;
@ -975,7 +966,7 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) {
Bus::setCCT(cct); Bus::setCCT(cct);
} }
uint32_t BusManager::getPixelColor(uint16_t pix) { uint32_t BusManager::getPixelColor(unsigned pix) {
for (unsigned i = 0; i < numBusses; i++) { for (unsigned i = 0; i < numBusses; i++) {
unsigned bstart = busses[i]->getStart(); unsigned bstart = busses[i]->getStart();
if (!busses[i]->containsPixel(pix)) continue; if (!busses[i]->containsPixel(pix)) continue;

View File

@ -6,6 +6,7 @@
*/ */
#include "const.h" #include "const.h"
#include "pin_manager.h"
#include <vector> #include <vector>
//colors.cpp //colors.cpp
@ -83,10 +84,10 @@ class Bus {
virtual void show() = 0; virtual void show() = 0;
virtual bool canShow() const { return true; } virtual bool canShow() const { return true; }
virtual void setStatusPixel(uint32_t c) {} virtual void setStatusPixel(uint32_t c) {}
virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; virtual void setPixelColor(unsigned pix, uint32_t c) = 0;
virtual void setBrightness(uint8_t b) { _bri = b; }; virtual void setBrightness(uint8_t b) { _bri = b; };
virtual void setColorOrder(uint8_t co) {} virtual void setColorOrder(uint8_t co) {}
virtual uint32_t getPixelColor(uint16_t pix) const { return 0; } virtual uint32_t getPixelColor(unsigned pix) const { return 0; }
virtual uint8_t getPins(uint8_t* pinArray = nullptr) const { return 0; } virtual uint8_t getPins(uint8_t* pinArray = nullptr) const { return 0; }
virtual uint16_t getLength() const { return isOk() ? _len : 0; } virtual uint16_t getLength() const { return isOk() ? _len : 0; }
virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; }
@ -110,7 +111,7 @@ class Bus {
inline void setStart(uint16_t start) { _start = start; } inline void setStart(uint16_t start) { _start = start; }
inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; }
inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; } inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; }
inline uint8_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); } inline uint32_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); }
inline uint16_t getStart() const { return _start; } inline uint16_t getStart() const { return _start; }
inline uint8_t getType() const { return _type; } inline uint8_t getType() const { return _type; }
inline bool isOk() const { return _valid; } inline bool isOk() const { return _valid; }
@ -119,8 +120,8 @@ class Bus {
inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; } inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; }
static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes
static constexpr uint8_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK static constexpr uint32_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK
static constexpr uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } static constexpr uint32_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); }
static constexpr bool hasRGB(uint8_t type) { static constexpr bool hasRGB(uint8_t type) {
return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF); return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF);
} }
@ -204,9 +205,9 @@ class BusDigital : public Bus {
bool canShow() const override; bool canShow() const override;
void setBrightness(uint8_t b) override; void setBrightness(uint8_t b) override;
void setStatusPixel(uint32_t c) override; void setStatusPixel(uint32_t c) override;
[[gnu::hot]] void setPixelColor(uint16_t pix, uint32_t c) override; [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override;
void setColorOrder(uint8_t colorOrder) override; void setColorOrder(uint8_t colorOrder) override;
[[gnu::hot]] uint32_t getPixelColor(uint16_t pix) const override; [[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override;
uint8_t getColorOrder() const override { return _colorOrder; } uint8_t getColorOrder() const override { return _colorOrder; }
uint8_t getPins(uint8_t* pinArray = nullptr) const override; uint8_t getPins(uint8_t* pinArray = nullptr) const override;
uint8_t skippedLeds() const override { return _skip; } uint8_t skippedLeds() const override { return _skip; }
@ -252,8 +253,8 @@ class BusPwm : public Bus {
BusPwm(BusConfig &bc); BusPwm(BusConfig &bc);
~BusPwm() { cleanup(); } ~BusPwm() { cleanup(); }
void setPixelColor(uint16_t pix, uint32_t c) override; void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(uint16_t pix) const override; //does no index check uint32_t getPixelColor(unsigned pix) const override; //does no index check
uint8_t getPins(uint8_t* pinArray = nullptr) const override; uint8_t getPins(uint8_t* pinArray = nullptr) const override;
uint16_t getFrequency() const override { return _frequency; } uint16_t getFrequency() const override { return _frequency; }
void show() override; void show() override;
@ -279,8 +280,8 @@ class BusOnOff : public Bus {
BusOnOff(BusConfig &bc); BusOnOff(BusConfig &bc);
~BusOnOff() { cleanup(); } ~BusOnOff() { cleanup(); }
void setPixelColor(uint16_t pix, uint32_t c) override; void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(uint16_t pix) const override; uint32_t getPixelColor(unsigned pix) const override;
uint8_t getPins(uint8_t* pinArray) const override; uint8_t getPins(uint8_t* pinArray) const override;
void show() override; void show() override;
void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); } void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); }
@ -299,8 +300,8 @@ class BusNetwork : public Bus {
~BusNetwork() { cleanup(); } ~BusNetwork() { cleanup(); }
bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out
void setPixelColor(uint16_t pix, uint32_t c) override; void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(uint16_t pix) const override; uint32_t getPixelColor(unsigned pix) const override;
uint8_t getPins(uint8_t* pinArray = nullptr) const override; uint8_t getPins(uint8_t* pinArray = nullptr) const override;
void show() override; void show() override;
void cleanup(); void cleanup();
@ -363,6 +364,16 @@ struct BusConfig {
}; };
//fine tune power estimation constants for your setup
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
#ifndef MA_FOR_ESP
#ifdef ESP8266
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
#else
#define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA)
#endif
#endif
class BusManager { class BusManager {
public: public:
BusManager() {}; BusManager() {};
@ -370,7 +381,7 @@ class BusManager {
//utility to get the approx. memory usage of a given BusConfig //utility to get the approx. memory usage of a given BusConfig
static uint32_t memUsage(BusConfig &bc); static uint32_t memUsage(BusConfig &bc);
static uint32_t memUsage(unsigned channels, unsigned count, unsigned buses = 1); static uint32_t memUsage(unsigned channels, unsigned count, unsigned buses = 1);
static uint16_t currentMilliamps() { return _milliAmpsUsed; } static uint16_t currentMilliamps() { return _milliAmpsUsed + MA_FOR_ESP; }
static uint16_t ablMilliampsMax() { return _milliAmpsMax; } static uint16_t ablMilliampsMax() { return _milliAmpsMax; }
static int add(BusConfig &bc); static int add(BusConfig &bc);
@ -385,13 +396,13 @@ class BusManager {
static void show(); static void show();
static bool canAllShow(); static bool canAllShow();
static void setStatusPixel(uint32_t c); static void setStatusPixel(uint32_t c);
[[gnu::hot]] static void setPixelColor(uint16_t pix, uint32_t c); [[gnu::hot]] static void setPixelColor(unsigned pix, uint32_t c);
static void setBrightness(uint8_t b); static void setBrightness(uint8_t b);
// for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K // for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K
// WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT() // WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT()
static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false);
static inline void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;} static inline void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;}
static uint32_t getPixelColor(uint16_t pix); [[gnu::hot]] static uint32_t getPixelColor(unsigned pix);
static inline int16_t getSegmentCCT() { return Bus::getCCT(); } static inline int16_t getSegmentCCT() { return Bus::getCCT(); }
static Bus* getBus(uint8_t busNr); static Bus* getBus(uint8_t busNr);

View File

@ -436,13 +436,12 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
else gammaCorrectBri = false; else gammaCorrectBri = false;
if (light_gc_col > 1.0f) gammaCorrectCol = true; if (light_gc_col > 1.0f) gammaCorrectCol = true;
else gammaCorrectCol = false; else gammaCorrectCol = false;
if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) { if (gammaCorrectVal <= 1.0f || gammaCorrectVal > 3) {
if (gammaCorrectVal != 2.8f) NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal);
} else {
gammaCorrectVal = 1.0f; // no gamma correction gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false; gammaCorrectBri = false;
gammaCorrectCol = false; gammaCorrectCol = false;
} }
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table
JsonObject light_tr = light["tr"]; JsonObject light_tr = light["tr"];
CJSON(fadeTransition, light_tr["mode"]); CJSON(fadeTransition, light_tr["mode"]);

View File

@ -5,61 +5,56 @@
*/ */
/* /*
* color blend function * color blend function, based on FastLED blend function
* the calculation for each color is: result = (A*(amountOfA) + A + B*(amountOfB) + B) / 256 with amountOfA = 255 - amountOfB
*/ */
uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) { uint32_t color_blend(uint32_t color1, uint32_t color2, uint8_t blend) {
if (blend == 0) return color1; // min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance
unsigned blendmax = b16 ? 0xFFFF : 0xFF; uint32_t rb1 = color1 & 0x00FF00FF;
if (blend == blendmax) return color2; uint32_t wg1 = (color1>>8) & 0x00FF00FF;
unsigned shift = b16 ? 16 : 8; uint32_t rb2 = color2 & 0x00FF00FF;
uint32_t wg2 = (color2>>8) & 0x00FF00FF;
uint32_t w1 = W(color1); uint32_t rb3 = ((((rb1 << 8) | rb2) + (rb2 * blend) - (rb1 * blend)) >> 8) & 0x00FF00FF;
uint32_t r1 = R(color1); uint32_t wg3 = ((((wg1 << 8) | wg2) + (wg2 * blend) - (wg1 * blend))) & 0xFF00FF00;
uint32_t g1 = G(color1); return rb3 | wg3;
uint32_t b1 = B(color1);
uint32_t w2 = W(color2);
uint32_t r2 = R(color2);
uint32_t g2 = G(color2);
uint32_t b2 = B(color2);
uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift;
uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift;
uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift;
uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift;
return RGBW32(r3, g3, b3, w3);
} }
/* /*
* color add function that preserves ratio * color add function that preserves ratio
* idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule * original idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule
* speed optimisations by @dedehai
*/ */
uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR)
{ {
if (c1 == BLACK) return c2; if (c1 == BLACK) return c2;
if (c2 == BLACK) return c1; if (c2 == BLACK) return c1;
if (fast) { uint32_t rb = (c1 & 0x00FF00FF) + (c2 & 0x00FF00FF); // mask and add two colors at once
uint8_t r = R(c1); uint32_t wg = ((c1>>8) & 0x00FF00FF) + ((c2>>8) & 0x00FF00FF);
uint8_t g = G(c1); uint32_t r = rb >> 16; // extract single color values
uint8_t b = B(c1); uint32_t b = rb & 0xFFFF;
uint8_t w = W(c1); uint32_t w = wg >> 16;
r = qadd8(r, R(c2)); uint32_t g = wg & 0xFFFF;
g = qadd8(g, G(c2));
b = qadd8(b, B(c2)); if (preserveCR) { // preserve color ratios
w = qadd8(w, W(c2)); uint32_t max = std::max(r,g); // check for overflow note
return RGBW32(r,g,b,w); max = std::max(max,b);
max = std::max(max,w);
//unsigned max = r; // check for overflow note
//max = g > max ? g : max;
//max = b > max ? b : max;
//max = w > max ? w : max;
if (max > 255) {
uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead
rb = ((rb * scale) >> 8) & 0x00FF00FF; //
wg = (wg * scale) & 0xFF00FF00;
} else wg = wg << 8; //shift white and green back to correct position
return rb | wg;
} else { } else {
uint32_t r = R(c1) + R(c2); r = r > 255 ? 255 : r;
uint32_t g = G(c1) + G(c2); g = g > 255 ? 255 : g;
uint32_t b = B(c1) + B(c2); b = b > 255 ? 255 : b;
uint32_t w = W(c1) + W(c2); w = w > 255 ? 255 : w;
unsigned max = r; return RGBW32(r,g,b,w);
if (g > max) max = g;
if (b > max) max = b;
if (w > max) max = w;
if (max < 256) return RGBW32(r, g, b, w);
else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max);
} }
} }
@ -70,27 +65,53 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast)
uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video)
{ {
if (c1 == BLACK || amount + video == 0) return BLACK; if (amount == 255) return c1;
if (c1 == BLACK || amount == 0) return BLACK;
uint32_t scaledcolor; // color order is: W R G B from MSB to LSB uint32_t scaledcolor; // color order is: W R G B from MSB to LSB
uint32_t r = R(c1);
uint32_t g = G(c1);
uint32_t b = B(c1);
uint32_t w = W(c1);
uint32_t scale = amount; // 32bit for faster calculation uint32_t scale = amount; // 32bit for faster calculation
if (video) { uint32_t addRemains = 0;
scaledcolor = (((r * scale) >> 8) + ((r && scale) ? 1 : 0)) << 16; if (!video) scale++; // add one for correct scaling using bitshifts
scaledcolor |= (((g * scale) >> 8) + ((g && scale) ? 1 : 0)) << 8; else { // video scaling: make sure colors do not dim to zero if they started non-zero
scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0); addRemains = R(c1) ? 0x00010000 : 0;
scaledcolor |= (((w * scale) >> 8) + ((w && scale) ? 1 : 0)) << 24; addRemains |= G(c1) ? 0x00000100 : 0;
} else { addRemains |= B(c1) ? 0x00000001 : 0;
scaledcolor = ((r * scale) >> 8) << 16; addRemains |= W(c1) ? 0x01000000 : 0;
scaledcolor |= ((g * scale) >> 8) << 8;
scaledcolor |= (b * scale) >> 8;
scaledcolor |= ((w * scale) >> 8) << 24;
} }
uint32_t rb = (((c1 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue
uint32_t wg = (((c1 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green
scaledcolor = (rb | wg) + addRemains;
return scaledcolor; return scaledcolor;
} }
// 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes)
uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType)
{
if (blendType == LINEARBLEND_NOWRAP) {
index = (index*240) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping
}
unsigned hi4 = byte(index) >> 4;
const CRGB* entry = (CRGB*)((uint8_t*)(&(pal[0])) + (hi4 * sizeof(CRGB)));
unsigned red1 = entry->r;
unsigned green1 = entry->g;
unsigned blue1 = entry->b;
if (blendType != NOBLEND) {
if (hi4 == 15) entry = &(pal[0]);
else ++entry;
unsigned f2 = ((index & 0x0F) << 4) + 1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8
unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max
red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8;
green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8;
blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8;
}
if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted
uint32_t scale = brightness + 1; // adjust for rounding (bitshift)
red1 = (red1 * scale) >> 8;
green1 = (green1 * scale) >> 8;
blue1 = (blue1 * scale) >> 8;
}
return RGBW32(red1,green1,blue1,0);
}
void setRandomColor(byte* rgb) void setRandomColor(byte* rgb)
{ {
lastRandomIndex = get_random_wheel_index(lastRandomIndex); lastRandomIndex = get_random_wheel_index(lastRandomIndex);
@ -103,86 +124,86 @@ void setRandomColor(byte* rgb)
*/ */
CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette)
{ {
CHSV palettecolors[4]; //array of colors for the new palette CHSV palettecolors[4]; // array of colors for the new palette
uint8_t keepcolorposition = random8(4); //color position of current random palette to keep uint8_t keepcolorposition = hw_random8(4); // color position of current random palette to keep
palettecolors[keepcolorposition] = rgb2hsv_approximate(basepalette.entries[keepcolorposition*5]); //read one of the base colors of the current palette palettecolors[keepcolorposition] = rgb2hsv(basepalette.entries[keepcolorposition*5]); // read one of the base colors of the current palette
palettecolors[keepcolorposition].hue += random8(10)-5; // +/- 5 randomness of base color palettecolors[keepcolorposition].hue += hw_random8(10)-5; // +/- 5 randomness of base color
//generate 4 saturation and brightness value numbers // generate 4 saturation and brightness value numbers
//only one saturation is allowed to be below 200 creating mostly vibrant colors // only one saturation is allowed to be below 200 creating mostly vibrant colors
//only one brightness value number is allowed below 200, creating mostly bright palettes // only one brightness value number is allowed below 200, creating mostly bright palettes
for (int i = 0; i < 3; i++) { //generate three high values for (int i = 0; i < 3; i++) { // generate three high values
palettecolors[i].saturation = random8(200,255); palettecolors[i].saturation = hw_random8(200,255);
palettecolors[i].value = random8(220,255); palettecolors[i].value = hw_random8(220,255);
} }
//allow one to be lower // allow one to be lower
palettecolors[3].saturation = random8(20,255); palettecolors[3].saturation = hw_random8(20,255);
palettecolors[3].value = random8(80,255); palettecolors[3].value = hw_random8(80,255);
//shuffle the arrays // shuffle the arrays
for (int i = 3; i > 0; i--) { for (int i = 3; i > 0; i--) {
std::swap(palettecolors[i].saturation, palettecolors[random8(i + 1)].saturation); std::swap(palettecolors[i].saturation, palettecolors[hw_random8(i + 1)].saturation);
std::swap(palettecolors[i].value, palettecolors[random8(i + 1)].value); std::swap(palettecolors[i].value, palettecolors[hw_random8(i + 1)].value);
} }
//now generate three new hues based off of the hue of the chosen current color // now generate three new hues based off of the hue of the chosen current color
uint8_t basehue = palettecolors[keepcolorposition].hue; uint8_t basehue = palettecolors[keepcolorposition].hue;
uint8_t harmonics[3]; //hues that are harmonic but still a little random uint8_t harmonics[3]; // hues that are harmonic but still a little random
uint8_t type = random8(5); //choose a harmony type uint8_t type = hw_random8(5); // choose a harmony type
switch (type) { switch (type) {
case 0: // analogous case 0: // analogous
harmonics[0] = basehue + random8(30, 50); harmonics[0] = basehue + hw_random8(30, 50);
harmonics[1] = basehue + random8(10, 30); harmonics[1] = basehue + hw_random8(10, 30);
harmonics[2] = basehue - random8(10, 30); harmonics[2] = basehue - hw_random8(10, 30);
break; break;
case 1: // triadic case 1: // triadic
harmonics[0] = basehue + 113 + random8(15); harmonics[0] = basehue + 113 + hw_random8(15);
harmonics[1] = basehue + 233 + random8(15); harmonics[1] = basehue + 233 + hw_random8(15);
harmonics[2] = basehue - 7 + random8(15); harmonics[2] = basehue - 7 + hw_random8(15);
break; break;
case 2: // split-complementary case 2: // split-complementary
harmonics[0] = basehue + 145 + random8(10); harmonics[0] = basehue + 145 + hw_random8(10);
harmonics[1] = basehue + 205 + random8(10); harmonics[1] = basehue + 205 + hw_random8(10);
harmonics[2] = basehue - 5 + random8(10); harmonics[2] = basehue - 5 + hw_random8(10);
break; break;
case 3: // square case 3: // square
harmonics[0] = basehue + 85 + random8(10); harmonics[0] = basehue + 85 + hw_random8(10);
harmonics[1] = basehue + 175 + random8(10); harmonics[1] = basehue + 175 + hw_random8(10);
harmonics[2] = basehue + 265 + random8(10); harmonics[2] = basehue + 265 + hw_random8(10);
break; break;
case 4: // tetradic case 4: // tetradic
harmonics[0] = basehue + 80 + random8(20); harmonics[0] = basehue + 80 + hw_random8(20);
harmonics[1] = basehue + 170 + random8(20); harmonics[1] = basehue + 170 + hw_random8(20);
harmonics[2] = basehue - 15 + random8(30); harmonics[2] = basehue - 15 + hw_random8(30);
break; break;
} }
if (random8() < 128) { if (hw_random8() < 128) {
//50:50 chance of shuffling hues or keep the color order // 50:50 chance of shuffling hues or keep the color order
for (int i = 2; i > 0; i--) { for (int i = 2; i > 0; i--) {
std::swap(harmonics[i], harmonics[random8(i + 1)]); std::swap(harmonics[i], harmonics[hw_random8(i + 1)]);
} }
} }
//now set the hues // now set the hues
int j = 0; int j = 0;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (i==keepcolorposition) continue; //skip the base color if (i==keepcolorposition) continue; // skip the base color
palettecolors[i].hue = harmonics[j]; palettecolors[i].hue = harmonics[j];
j++; j++;
} }
bool makepastelpalette = false; bool makepastelpalette = false;
if (random8() < 25) { //~10% chance of desaturated 'pastel' colors if (hw_random8() < 25) { // ~10% chance of desaturated 'pastel' colors
makepastelpalette = true; makepastelpalette = true;
} }
//apply saturation & gamma correction // apply saturation & gamma correction
CRGB RGBpalettecolors[4]; CRGB RGBpalettecolors[4];
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (makepastelpalette && palettecolors[i].saturation > 180) { if (makepastelpalette && palettecolors[i].saturation > 180) {
@ -198,34 +219,72 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette)
RGBpalettecolors[3]); RGBpalettecolors[3]);
} }
CRGBPalette16 generateRandomPalette() //generate fully random palette CRGBPalette16 generateRandomPalette() // generate fully random palette
{ {
return CRGBPalette16(CHSV(random8(), random8(160, 255), random8(128, 255)), return CRGBPalette16(CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)), CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)), CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255))); CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)));
} }
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB (32bit with white = 0)
{ {
float h = ((float)hue)/10922.5f; // hue*6/65535 unsigned int remainder, region, p, q, t;
float s = ((float)sat)/255.0f; unsigned int h = hsv.h;
int i = int(h); unsigned int s = hsv.s;
float f = h - i; unsigned int v = hsv.v;
int p = int(255.0f * (1.0f-s)); if (s == 0) {
int q = int(255.0f * (1.0f-s*f)); rgb = v << 16 | v << 8 | v;
int t = int(255.0f * (1.0f-s*(1.0f-f))); return;
p = constrain(p, 0, 255);
q = constrain(q, 0, 255);
t = constrain(t, 0, 255);
switch (i%6) {
case 0: rgb[0]=255,rgb[1]=t, rgb[2]=p; break;
case 1: rgb[0]=q, rgb[1]=255,rgb[2]=p; break;
case 2: rgb[0]=p, rgb[1]=255,rgb[2]=t; break;
case 3: rgb[0]=p, rgb[1]=q, rgb[2]=255;break;
case 4: rgb[0]=t, rgb[1]=p, rgb[2]=255;break;
case 5: rgb[0]=255,rgb[1]=p, rgb[2]=q; break;
} }
region = h / 10923; // 65536 / 6 = 10923
remainder = (h - (region * 10923)) * 6;
p = (v * (255 - s)) >> 8;
q = (v * (255 - ((s * remainder) >> 16))) >> 8;
t = (v * (255 - ((s * (65535 - remainder)) >> 16))) >> 8;
switch (region) {
case 0:
rgb = v << 16 | t << 8 | p; break;
case 1:
rgb = q << 16 | v << 8 | p; break;
case 2:
rgb = p << 16 | v << 8 | t; break;
case 3:
rgb = p << 16 | q << 8 | v; break;
case 4:
rgb = t << 16 | p << 8 | v; break;
default:
rgb = v << 16 | p << 8 | q; break;
}
}
void rgb2hsv(const uint32_t rgb, CHSV32& hsv) // convert RGB to HSV (16bit hue), much more accurate and faster than fastled version
{
hsv.raw = 0;
int32_t r = (rgb>>16)&0xFF;
int32_t g = (rgb>>8)&0xFF;
int32_t b = rgb&0xFF;
int32_t minval, maxval, delta;
minval = min(r, g);
minval = min(minval, b);
maxval = max(r, g);
maxval = max(maxval, b);
if (maxval == 0) return; // black
hsv.v = maxval;
delta = maxval - minval;
hsv.s = (255 * delta) / maxval;
if (hsv.s == 0) return; // gray value
if (maxval == r) hsv.h = (10923 * (g - b)) / delta;
else if (maxval == g) hsv.h = 21845 + (10923 * (b - r)) / delta;
else hsv.h = 43690 + (10923 * (r - g)) / delta;
}
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) { //hue, sat to rgb
uint32_t crgb;
hsv2rgb(CHSV32(hue, sat, 255), crgb);
rgb[0] = byte((crgb) >> 16);
rgb[1] = byte((crgb) >> 8);
rgb[2] = byte(crgb);
} }
//get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html)
@ -452,24 +511,8 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb) {
} }
} }
//gamma 2.8 lookup table used for color correction // gamma lookup table used for color correction (filled on 1st use (cfg.cpp & set.cpp))
uint8_t NeoGammaWLEDMethod::gammaT[256] = { uint8_t NeoGammaWLEDMethod::gammaT[256];
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
// re-calculates & fills gamma table // re-calculates & fills gamma table
void NeoGammaWLEDMethod::calcGammaTable(float gamma) void NeoGammaWLEDMethod::calcGammaTable(float gamma)

View File

@ -5,7 +5,7 @@
* Readability defines and their associated numerical values + compile-time constants * Readability defines and their associated numerical values + compile-time constants
*/ */
#define GRADIENT_PALETTE_COUNT 58 #define GRADIENT_PALETTE_COUNT 59
// You can define custom product info from build flags. // You can define custom product info from build flags.
// This is useful to allow API consumer to identify what type of WLED version // This is useful to allow API consumer to identify what type of WLED version

View File

@ -761,7 +761,7 @@ Swap: <select id="xw${s}" name="XW${s}">
Enable automatic brightness limiter: <input type="checkbox" name="ABL" onchange="enABL()"><br> Enable automatic brightness limiter: <input type="checkbox" name="ABL" onchange="enABL()"><br>
<div id="abl"> <div id="abl">
<i>Automatically limits brightness to stay close to the limit.<br> <i>Automatically limits brightness to stay close to the limit.<br>
Keep at &lt;1A if poweing LEDs directly from the ESP 5V pin!<br> Keep at &lt;1A if powering LEDs directly from the ESP 5V pin!<br>
If using multiple outputs it is recommended to use per-output limiter.<br> If using multiple outputs it is recommended to use per-output limiter.<br>
Analog (PWM) and virtual LEDs cannot use automatic brightness limiter.<br></i> Analog (PWM) and virtual LEDs cannot use automatic brightness limiter.<br></i>
<div id="psuMA">Maximum PSU Current: <input name="MA" type="number" class="xl" min="250" max="65000" oninput="UI()" required> mA<br></div> <div id="psuMA">Maximum PSU Current: <input name="MA" type="number" class="xl" min="250" max="65000" oninput="UI()" required> mA<br></div>

View File

@ -39,6 +39,7 @@ void handleDDPPacket(e131_packet_t* p) {
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP);
if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) { if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) {
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = start; i < stop; i++, c += ddpChannelsPerLed) { for (unsigned i = start; i < stop; i++, c += ddpChannelsPerLed) {
setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0); setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0);
} }
@ -147,6 +148,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0; wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0;
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = 0; i < totalLen; i++) for (unsigned i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel); setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel);
break; break;
@ -164,6 +166,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
strip.setBrightness(bri, true); strip.setBrightness(bri, true);
} }
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = 0; i < totalLen; i++) for (unsigned i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel); setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel);
break; break;
@ -308,6 +311,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
} }
} }
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
if (!is4Chan) { if (!is4Chan) {
for (unsigned i = previousLeds; i < ledsTotal; i++) { for (unsigned i = previousLeds; i < ledsTotal; i++) {
setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], 0); setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], 0);

View File

@ -66,6 +66,89 @@ typedef struct WiFiConfig {
} wifi_config; } wifi_config;
//colors.cpp //colors.cpp
#define ColorFromPalette ColorFromPaletteWLED // override fastled version
// CRGBW can be used to manipulate 32bit colors faster. However: if it is passed to functions, it adds overhead compared to a uint32_t color
// use with caution and pay attention to flash size. Usually converting a uint32_t to CRGBW to extract r, g, b, w values is slower than using bitshifts
// it can be useful to avoid back and forth conversions between uint32_t and fastled CRGB
struct CRGBW {
union {
uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB)
struct {
uint8_t b;
uint8_t g;
uint8_t r;
uint8_t w;
};
uint8_t raw[4]; // Access as an array in the order B, G, R, W
};
// Default constructor
inline CRGBW() __attribute__((always_inline)) = default;
// Constructor from a 32-bit color (0xWWRRGGBB)
constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {}
// Constructor with r, g, b, w values
constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : b(blue), g(green), r(red), w(white) {}
// Constructor from CRGB
constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : b(rgb.b), g(rgb.g), r(rgb.r), w(0) {}
// Access as an array
inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; }
// Assignment from 32-bit color
inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; }
// Assignment from r, g, b, w
inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { b = rgb.b; g = rgb.g; r = rgb.r; w = 0; return *this; }
// Conversion operator to uint32_t
inline operator uint32_t() const __attribute__((always_inline)) {
return color32;
}
/*
// Conversion operator to CRGB
inline operator CRGB() const __attribute__((always_inline)) {
return CRGB(r, g, b);
}
CRGBW& scale32 (uint8_t scaledown) // 32bit math
{
if (color32 == 0) return *this; // 2 extra instructions, worth it if called a lot on black (which probably is true) adding check if scaledown is zero adds much more overhead as its 8bit
uint32_t scale = scaledown + 1;
uint32_t rb = (((color32 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue
uint32_t wg = (((color32 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green
color32 = rb | wg;
return *this;
}*/
};
struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions
union {
struct {
uint16_t h; // hue
uint8_t s; // saturation
uint8_t v; // value
};
uint32_t raw; // 32bit access
};
inline CHSV32() __attribute__((always_inline)) = default; // default constructor
/// Allow construction from hue, saturation, and value
/// @param ih input hue
/// @param is input saturation
/// @param iv input value
inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v
: h(ih), s(is), v(iv) {}
inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v
: h((uint16_t)ih << 8), s(is), v(iv) {}
inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV
: h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {}
inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV
};
// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod) // similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod)
class NeoGammaWLEDMethod { class NeoGammaWLEDMethod {
public: public:
@ -78,13 +161,18 @@ class NeoGammaWLEDMethod {
}; };
#define gamma32(c) NeoGammaWLEDMethod::Correct32(c) #define gamma32(c) NeoGammaWLEDMethod::Correct32(c)
#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) #define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c)
[[gnu::hot]] uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); [[gnu::hot]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend);
[[gnu::hot]] uint32_t color_add(uint32_t,uint32_t, bool fast=false); inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); };
[[gnu::hot]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false);
[[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); [[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false);
[[gnu::hot]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND);
CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette);
CRGBPalette16 generateRandomPalette(); CRGBPalette16 generateRandomPalette();
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb void hsv2rgb(const CHSV32& hsv, uint32_t& rgb);
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb);
void rgb2hsv(const uint32_t rgb, CHSV32& hsv);
inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b)))), hsv); return CHSV(hsv); } // CRGB to hsv
void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorKtoRGB(uint16_t kelvin, byte* rgb);
void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb
void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO
@ -370,6 +458,12 @@ void userConnected();
void userLoop(); void userLoop();
//util.cpp //util.cpp
#ifdef ESP8266
#define HW_RND_REGISTER RANDOM_REG32
#else // ESP32 family
#include "soc/wdev_reg.h"
#define HW_RND_REGISTER REG_READ(WDEV_RND_REG)
#endif
int getNumVal(const String* req, uint16_t pos); int getNumVal(const String* req, uint16_t pos);
void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255);
bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); // getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form) bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); // getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form)
@ -397,6 +491,23 @@ void enumerateLedmaps();
uint8_t get_random_wheel_index(uint8_t pos); uint8_t get_random_wheel_index(uint8_t pos);
float mapf(float x, float in_min, float in_max, float out_min, float out_max); float mapf(float x, float in_min, float in_max, float out_min, float out_max);
// fast (true) random numbers using hardware RNG, all functions return values in the range lowerlimit to upperlimit-1
// note: for true random numbers with high entropy, do not call faster than every 200ns (5MHz)
// tests show it is still highly random reading it quickly in a loop (better than fastled PRNG)
// for 8bit and 16bit random functions: no limit check is done for best speed
// 32bit inputs are used for speed and code size, limits don't work if inverted or out of range
// inlining does save code size except for random(a,b) and 32bit random with limits
#define random hw_random // replace arduino random()
inline uint32_t hw_random() { return HW_RND_REGISTER; };
uint32_t hw_random(uint32_t upperlimit); // not inlined for code size
int32_t hw_random(int32_t lowerlimit, int32_t upperlimit);
inline uint16_t hw_random16() { return HW_RND_REGISTER; };
inline uint16_t hw_random16(uint32_t upperlimit) { return (hw_random16() * upperlimit) >> 16; }; // input range 0-65535 (uint16_t)
inline int16_t hw_random16(int32_t lowerlimit, int32_t upperlimit) { int32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random16(range); }; // signed limits, use int16_t ranges
inline uint8_t hw_random8() { return HW_RND_REGISTER; };
inline uint8_t hw_random8(uint32_t upperlimit) { return (hw_random8() * upperlimit) >> 8; }; // input range 0-255
inline uint8_t hw_random8(uint32_t lowerlimit, uint32_t upperlimit) { uint32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random8(range); }; // input range 0-255
// RAII guard class for the JSON Buffer lock // RAII guard class for the JSON Buffer lock
// Modeled after std::lock_guard // Modeled after std::lock_guard
class JSONBufferGuard { class JSONBufferGuard {

View File

@ -129,7 +129,7 @@ static void changeEffectSpeed(int8_t amount)
} else { // if Effect == "solid Color", change the hue of the primary color } else { // if Effect == "solid Color", change the hue of the primary color
Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
CRGB fastled_col = CRGB(sseg.colors[0]); CRGB fastled_col = CRGB(sseg.colors[0]);
CHSV prim_hsv = rgb2hsv_approximate(fastled_col); CHSV prim_hsv = rgb2hsv(fastled_col);
int16_t new_val = (int16_t)prim_hsv.h + amount; int16_t new_val = (int16_t)prim_hsv.h + amount;
if (new_val > 255) new_val -= 255; // roll-over if bigger than 255 if (new_val > 255) new_val -= 255; // roll-over if bigger than 255
if (new_val < 0) new_val += 255; // roll-over if smaller than 0 if (new_val < 0) new_val += 255; // roll-over if smaller than 0
@ -173,7 +173,7 @@ static void changeEffectIntensity(int8_t amount)
} else { // if Effect == "solid Color", change the saturation of the primary color } else { // if Effect == "solid Color", change the saturation of the primary color
Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
CRGB fastled_col = CRGB(sseg.colors[0]); CRGB fastled_col = CRGB(sseg.colors[0]);
CHSV prim_hsv = rgb2hsv_approximate(fastled_col); CHSV prim_hsv = rgb2hsv(fastled_col);
int16_t new_val = (int16_t) prim_hsv.s + amount; int16_t new_val = (int16_t) prim_hsv.s + amount;
prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255 prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255
hsv2rgb_rainbow(prim_hsv, fastled_col); hsv2rgb_rainbow(prim_hsv, fastled_col);
@ -435,7 +435,7 @@ static void decodeIR44(uint32_t code)
case IR44_DIY2 : presetFallback(2, FX_MODE_BREATH, 0); break; case IR44_DIY2 : presetFallback(2, FX_MODE_BREATH, 0); break;
case IR44_DIY3 : presetFallback(3, FX_MODE_FIRE_FLICKER, 0); break; case IR44_DIY3 : presetFallback(3, FX_MODE_FIRE_FLICKER, 0); break;
case IR44_DIY4 : presetFallback(4, FX_MODE_RAINBOW, 0); break; case IR44_DIY4 : presetFallback(4, FX_MODE_RAINBOW, 0); break;
case IR44_DIY5 : presetFallback(5, FX_MODE_METEOR_SMOOTH, 0); break; case IR44_DIY5 : presetFallback(5, FX_MODE_METEOR, 0); break;
case IR44_DIY6 : presetFallback(6, FX_MODE_RAIN, 0); break; case IR44_DIY6 : presetFallback(6, FX_MODE_RAIN, 0); break;
case IR44_AUTO : changeEffect(FX_MODE_STATIC); break; case IR44_AUTO : changeEffect(FX_MODE_STATIC); break;
case IR44_FLASH : changeEffect(FX_MODE_PALETTE); break; case IR44_FLASH : changeEffect(FX_MODE_PALETTE); break;
@ -593,7 +593,7 @@ static void decodeIRJson(uint32_t code)
decBrightness(); decBrightness();
} else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback } else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback
uint8_t p1 = fdo["PL"] | 1; uint8_t p1 = fdo["PL"] | 1;
uint8_t p2 = fdo["FX"] | random8(strip.getModeCount() -1); uint8_t p2 = fdo["FX"] | hw_random8(strip.getModeCount() -1);
uint8_t p3 = fdo["FP"] | 0; uint8_t p3 = fdo["FP"] | 0;
presetFallback(p1, p2, p3); presetFallback(p1, p2, p3);
} }

View File

@ -34,7 +34,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
//DEBUG_PRINTLN(F("-- JSON deserialize segment.")); //DEBUG_PRINTLN(F("-- JSON deserialize segment."));
Segment& seg = strip.getSegment(id); Segment& seg = strip.getSegment(id);
//DEBUG_PRINTF_P(PSTR("-- Original segment: %p (%p)\n"), &seg, seg.data); //DEBUG_PRINTF_P(PSTR("-- Original segment: %p (%p)\n"), &seg, seg.data);
Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor) const Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor)
//DEBUG_PRINTF_P(PSTR("-- Duplicate segment: %p (%p)\n"), &prev, prev.data); //DEBUG_PRINTF_P(PSTR("-- Duplicate segment: %p (%p)\n"), &prev, prev.data);
int start = elem["start"] | seg.start; int start = elem["start"] | seg.start;
@ -96,17 +96,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
uint16_t of = seg.offset; uint16_t of = seg.offset;
uint8_t soundSim = elem["si"] | seg.soundSim; uint8_t soundSim = elem["si"] | seg.soundSim;
uint8_t map1D2D = elem["m12"] | seg.map1D2D; uint8_t map1D2D = elem["m12"] | seg.map1D2D;
if ((spc>0 && spc!=seg.spacing) || seg.map1D2D!=map1D2D) seg.fill(BLACK); // clear spacing gaps
seg.map1D2D = constrain(map1D2D, 0, 7);
seg.soundSim = constrain(soundSim, 0, 3);
uint8_t set = elem[F("set")] | seg.set; uint8_t set = elem[F("set")] | seg.set;
seg.set = constrain(set, 0, 3); seg.set = constrain(set, 0, 3);
seg.soundSim = constrain(soundSim, 0, 3);
int len = 1; int len = (stop > start) ? stop - start : 1;
if (stop > start) len = stop - start;
int offset = elem[F("of")] | INT32_MAX; int offset = elem[F("of")] | INT32_MAX;
if (offset != INT32_MAX) { if (offset != INT32_MAX) {
int offsetAbs = abs(offset); int offsetAbs = abs(offset);
@ -117,7 +111,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (stop > start && of > len -1) of = len -1; if (stop > start && of > len -1) of = len -1;
// update segment (delete if necessary) // update segment (delete if necessary)
seg.setUp(start, stop, grp, spc, of, startY, stopY); // strip needs to be suspended for this to work without issues seg.setGeometry(start, stop, grp, spc, of, startY, stopY, map1D2D); // strip needs to be suspended for this to work without issues
if (newSeg) seg.refreshLightCapabilities(); // fix for #3403 if (newSeg) seg.refreshLightCapabilities(); // fix for #3403

View File

@ -75,6 +75,7 @@ byte scaledBri(byte in)
void applyBri() { void applyBri() {
if (!realtimeMode || !arlsForceMaxBri) if (!realtimeMode || !arlsForceMaxBri)
{ {
//DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld);
strip.setBrightness(scaledBri(briT)); strip.setBrightness(scaledBri(briT));
} }
} }
@ -139,7 +140,6 @@ void stateUpdated(byte callMode) {
if (transitionActive) { if (transitionActive) {
briOld = briT; briOld = briT;
tperLast = 0;
} else } else
strip.setTransitionMode(true); // force all segments to transition mode strip.setTransitionMode(true); // force all segments to transition mode
transitionActive = true; transitionActive = true;
@ -179,22 +179,21 @@ void handleTransitions()
updateInterfaces(interfaceUpdateCallMode); updateInterfaces(interfaceUpdateCallMode);
if (transitionActive && strip.getTransition() > 0) { if (transitionActive && strip.getTransition() > 0) {
float tper = (millis() - transitionStartTime)/(float)strip.getTransition(); int ti = millis() - transitionStartTime;
if (tper >= 1.0f) { int tr = strip.getTransition();
if (ti/tr) {
strip.setTransitionMode(false); // stop all transitions strip.setTransitionMode(false); // stop all transitions
// restore (global) transition time if not called from UDP notifier or single/temporary transition from JSON (also playlist) // restore (global) transition time if not called from UDP notifier or single/temporary transition from JSON (also playlist)
if (jsonTransitionOnce) strip.setTransition(transitionDelay); if (jsonTransitionOnce) strip.setTransition(transitionDelay);
transitionActive = false; transitionActive = false;
jsonTransitionOnce = false; jsonTransitionOnce = false;
tperLast = 0;
applyFinalBri(); applyFinalBri();
return; return;
} }
if (tper - tperLast < 0.004f) return; // less than 1 bit change (1/255) byte briTO = briT;
tperLast = tper; int deltaBri = (int)bri - (int)briOld;
briT = briOld + ((bri - briOld) * tper); briT = briOld + (deltaBri * ti / tr);
if (briTO != briT) applyBri();
applyBri();
} }
} }
@ -229,8 +228,8 @@ void handleNightlight()
colNlT[1] = effectSpeed; colNlT[1] = effectSpeed;
colNlT[2] = effectPalette; colNlT[2] = effectPalette;
strip.setMode(strip.getFirstSelectedSegId(), FX_MODE_STATIC); // make sure seg runtime is reset if it was in sunrise mode strip.getFirstSelectedSeg().setMode(FX_MODE_STATIC); // make sure seg runtime is reset if it was in sunrise mode
effectCurrent = FX_MODE_SUNRISE; effectCurrent = FX_MODE_SUNRISE; // colorUpdated() will take care of assigning that to all selected segments
effectSpeed = nightlightDelayMins; effectSpeed = nightlightDelayMins;
effectPalette = 0; effectPalette = 0;
if (effectSpeed > 60) effectSpeed = 60; //currently limited to 60 minutes if (effectSpeed > 60) effectSpeed = 60; //currently limited to 60 minutes

View File

@ -1,5 +1,6 @@
/* /*
* Color palettes for FastLED effects (65-73). * Color palettes for FastLED effects (65-73).
* 4 bytes per color: index, red, green, blue
*/ */
// From ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb // From ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb
@ -844,6 +845,12 @@ const byte candy2_gp[] PROGMEM = {
211, 39, 33, 34, 211, 39, 33, 34,
255, 1, 1, 1}; 255, 1, 1, 1};
const byte trafficlight_gp[] PROGMEM = {
0, 0, 0, 0, //black
85, 0, 255, 0, //green
170, 255, 255, 0, //yellow
255, 255, 0, 0}; //red
// array of fastled palettes (palette 6 - 12) // array of fastled palettes (palette 6 - 12)
const TProgmemRGBPalette16 *const fastledPalettes[] PROGMEM = { const TProgmemRGBPalette16 *const fastledPalettes[] PROGMEM = {
&PartyColors_p, //06-00 Party &PartyColors_p, //06-00 Party
@ -917,7 +924,8 @@ const byte* const gGradientPalettes[] PROGMEM = {
blink_red_gp, //67-54 Blink Red blink_red_gp, //67-54 Blink Red
red_shift_gp, //68-55 Red Shift red_shift_gp, //68-55 Red Shift
red_tide_gp, //69-56 Red Tide red_tide_gp, //69-56 Red Tide
candy2_gp //70-57 Candy2 candy2_gp, //70-57 Candy2
trafficlight_gp //71-58 Traffic Light
}; };
#endif #endif

View File

@ -146,7 +146,7 @@ static bool remoteJson(int button)
parsed = true; parsed = true;
} else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback } else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback
uint8_t p1 = fdo["PL"] | 1; uint8_t p1 = fdo["PL"] | 1;
uint8_t p2 = fdo["FX"] | random8(strip.getModeCount() -1); uint8_t p2 = fdo["FX"] | hw_random8(strip.getModeCount() -1);
uint8_t p3 = fdo["FP"] | 0; uint8_t p3 = fdo["FP"] | 0;
presetWithFallback(p1, p2, p3); presetWithFallback(p1, p2, p3);
parsed = true; parsed = true;

View File

@ -319,13 +319,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
gammaCorrectBri = request->hasArg(F("GB")); gammaCorrectBri = request->hasArg(F("GB"));
gammaCorrectCol = request->hasArg(F("GC")); gammaCorrectCol = request->hasArg(F("GC"));
gammaCorrectVal = request->arg(F("GV")).toFloat(); gammaCorrectVal = request->arg(F("GV")).toFloat();
if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) if (gammaCorrectVal <= 1.0f || gammaCorrectVal > 3) {
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal);
else {
gammaCorrectVal = 1.0f; // no gamma correction gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false; gammaCorrectBri = false;
gammaCorrectCol = false; gammaCorrectCol = false;
} }
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table
fadeTransition = request->hasArg(F("TF")); fadeTransition = request->hasArg(F("TF"));
modeBlending = request->hasArg(F("EB")); modeBlending = request->hasArg(F("EB"));
@ -841,6 +840,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
// temporary values, write directly to segments, globals are updated by setValuesFromFirstSelectedSeg() // temporary values, write directly to segments, globals are updated by setValuesFromFirstSelectedSeg()
uint32_t col0 = selseg.colors[0]; uint32_t col0 = selseg.colors[0];
uint32_t col1 = selseg.colors[1]; uint32_t col1 = selseg.colors[1];
uint32_t col2 = selseg.colors[2];
byte colIn[4] = {R(col0), G(col0), B(col0), W(col0)}; byte colIn[4] = {R(col0), G(col0), B(col0), W(col0)};
byte colInSec[4] = {R(col1), G(col1), B(col1), W(col1)}; byte colInSec[4] = {R(col1), G(col1), B(col1), W(col1)};
byte effectIn = selseg.mode; byte effectIn = selseg.mode;
@ -875,7 +875,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (pos > 0) { if (pos > 0) {
spcI = std::max(0,getNumVal(&req, pos)); spcI = std::max(0,getNumVal(&req, pos));
} }
strip.setSegment(selectedSeg, startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY); strip.suspend(); // must suspend strip operations before changing geometry
selseg.setGeometry(startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY, selseg.map1D2D);
strip.resume();
pos = req.indexOf(F("RV=")); //Segment reverse pos = req.indexOf(F("RV=")); //Segment reverse
if (pos > 0) selseg.reverse = req.charAt(pos+3) != '0'; if (pos > 0) selseg.reverse = req.charAt(pos+3) != '0';
@ -921,7 +923,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set brightness //set brightness
updateVal(req.c_str(), "&A=", &bri); updateVal(req.c_str(), "&A=", &bri);
bool col0Changed = false, col1Changed = false; bool col0Changed = false, col1Changed = false, col2Changed = false;
//set colors //set colors
col0Changed |= updateVal(req.c_str(), "&R=", &colIn[0]); col0Changed |= updateVal(req.c_str(), "&R=", &colIn[0]);
col0Changed |= updateVal(req.c_str(), "&G=", &colIn[1]); col0Changed |= updateVal(req.c_str(), "&G=", &colIn[1]);
@ -978,7 +980,6 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
} }
//set color from HEX or 32bit DEC //set color from HEX or 32bit DEC
byte tmpCol[4];
pos = req.indexOf(F("CL=")); pos = req.indexOf(F("CL="));
if (pos > 0) { if (pos > 0) {
colorFromDecOrHexString(colIn, (char*)req.substring(pos + 3).c_str()); colorFromDecOrHexString(colIn, (char*)req.substring(pos + 3).c_str());
@ -991,10 +992,11 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
} }
pos = req.indexOf(F("C3=")); pos = req.indexOf(F("C3="));
if (pos > 0) { if (pos > 0) {
byte tmpCol[4];
colorFromDecOrHexString(tmpCol, (char*)req.substring(pos + 3).c_str()); colorFromDecOrHexString(tmpCol, (char*)req.substring(pos + 3).c_str());
uint32_t col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]); col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]);
selseg.setColor(2, col2); // defined above (SS= or main) selseg.setColor(2, col2); // defined above (SS= or main)
if (!singleSegment) strip.setColor(2, col2); // will set color to all active & selected segments col2Changed = true;
} }
//set to random hue SR=0->1st SR=1->2nd //set to random hue SR=0->1st SR=1->2nd
@ -1005,29 +1007,22 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
col0Changed |= (!sec); col1Changed |= sec; col0Changed |= (!sec); col1Changed |= sec;
} }
//swap 2nd & 1st
pos = req.indexOf(F("SC"));
if (pos > 0) {
byte temp;
for (unsigned i=0; i<4; i++) {
temp = colIn[i];
colIn[i] = colInSec[i];
colInSec[i] = temp;
}
col0Changed = col1Changed = true;
}
// apply colors to selected segment, and all selected segments if applicable // apply colors to selected segment, and all selected segments if applicable
if (col0Changed) { if (col0Changed) {
uint32_t colIn0 = RGBW32(colIn[0], colIn[1], colIn[2], colIn[3]); col0 = RGBW32(colIn[0], colIn[1], colIn[2], colIn[3]);
selseg.setColor(0, colIn0); selseg.setColor(0, col0);
if (!singleSegment) strip.setColor(0, colIn0); // will set color to all active & selected segments
} }
if (col1Changed) { if (col1Changed) {
uint32_t colIn1 = RGBW32(colInSec[0], colInSec[1], colInSec[2], colInSec[3]); col1 = RGBW32(colInSec[0], colInSec[1], colInSec[2], colInSec[3]);
selseg.setColor(1, colIn1); selseg.setColor(1, col1);
if (!singleSegment) strip.setColor(1, colIn1); // will set color to all active & selected segments }
//swap 2nd & 1st
pos = req.indexOf(F("SC"));
if (pos > 0) {
std::swap(col0,col1);
col0Changed = col1Changed = true;
} }
bool fxModeChanged = false, speedChanged = false, intensityChanged = false, paletteChanged = false; bool fxModeChanged = false, speedChanged = false, intensityChanged = false, paletteChanged = false;
@ -1057,6 +1052,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (speedChanged) seg.speed = speedIn; if (speedChanged) seg.speed = speedIn;
if (intensityChanged) seg.intensity = intensityIn; if (intensityChanged) seg.intensity = intensityIn;
if (paletteChanged) seg.setPalette(paletteIn); if (paletteChanged) seg.setPalette(paletteIn);
if (col0Changed) seg.setColor(0, col0);
if (col1Changed) seg.setColor(1, col1);
if (col2Changed) seg.setColor(2, col2);
if (custom1Changed) seg.custom1 = custom1In; if (custom1Changed) seg.custom1 = custom1In;
if (custom2Changed) seg.custom2 = custom2In; if (custom2Changed) seg.custom2 = custom2In;
if (custom3Changed) seg.custom3 = custom3In; if (custom3Changed) seg.custom3 = custom3In;

View File

@ -234,12 +234,12 @@ void parseNotifyPacket(uint8_t *udpIn) {
//apply colors from notification to main segment, only if not syncing full segments //apply colors from notification to main segment, only if not syncing full segments
if ((receiveNotificationColor || !someSel) && (version < 11 || !receiveSegmentOptions)) { if ((receiveNotificationColor || !someSel) && (version < 11 || !receiveSegmentOptions)) {
// primary color, only apply white if intented (version > 0) // primary color, only apply white if intented (version > 0)
strip.setColor(0, RGBW32(udpIn[3], udpIn[4], udpIn[5], (version > 0) ? udpIn[10] : 0)); strip.getMainSegment().setColor(0, RGBW32(udpIn[3], udpIn[4], udpIn[5], (version > 0) ? udpIn[10] : 0));
if (version > 1) { if (version > 1) {
strip.setColor(1, RGBW32(udpIn[12], udpIn[13], udpIn[14], udpIn[15])); // secondary color strip.getMainSegment().setColor(1, RGBW32(udpIn[12], udpIn[13], udpIn[14], udpIn[15])); // secondary color
} }
if (version > 6) { if (version > 6) {
strip.setColor(2, RGBW32(udpIn[20], udpIn[21], udpIn[22], udpIn[23])); // tertiary color strip.getMainSegment().setColor(2, RGBW32(udpIn[20], udpIn[21], udpIn[22], udpIn[23])); // tertiary color
if (version > 9 && udpIn[37] < 255) { // valid CCT/Kelvin value if (version > 9 && udpIn[37] < 255) { // valid CCT/Kelvin value
unsigned cct = udpIn[38]; unsigned cct = udpIn[38];
if (udpIn[37] > 0) { //Kelvin if (udpIn[37] > 0) { //Kelvin
@ -260,11 +260,12 @@ void parseNotifyPacket(uint8_t *udpIn) {
// are we syncing bounds and slave has more active segments than master? // are we syncing bounds and slave has more active segments than master?
if (receiveSegmentBounds && numSrcSegs < strip.getActiveSegmentsNum()) { if (receiveSegmentBounds && numSrcSegs < strip.getActiveSegmentsNum()) {
DEBUG_PRINTLN(F("Removing excessive segments.")); DEBUG_PRINTLN(F("Removing excessive segments."));
for (size_t i=strip.getSegmentsNum(); i>numSrcSegs; i--) { strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
if (strip.getSegment(i).isActive()) { for (size_t i=strip.getSegmentsNum(); i>numSrcSegs && i>0; i--) {
strip.setSegment(i-1,0,0); // delete segment Segment &seg = strip.getSegment(i-1);
} if (seg.isActive()) seg.deactivate(); // delete segment
} }
strip.resume();
} }
size_t inactiveSegs = 0; size_t inactiveSegs = 0;
for (size_t i = 0; i < numSrcSegs && i < strip.getMaxSegments(); i++) { for (size_t i = 0; i < numSrcSegs && i < strip.getMaxSegments(); i++) {
@ -300,7 +301,7 @@ void parseNotifyPacket(uint8_t *udpIn) {
if (!receiveSegmentOptions) { if (!receiveSegmentOptions) {
DEBUG_PRINTF_P(PSTR("Set segment w/o options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY); DEBUG_PRINTF_P(PSTR("Set segment w/o options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY); selseg.setGeometry(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY, selseg.map1D2D);
strip.resume(); strip.resume();
continue; // we do receive bounds, but not options continue; // we do receive bounds, but not options
} }
@ -342,12 +343,12 @@ void parseNotifyPacket(uint8_t *udpIn) {
if (receiveSegmentBounds) { if (receiveSegmentBounds) {
DEBUG_PRINTF_P(PSTR("Set segment w/ options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY); DEBUG_PRINTF_P(PSTR("Set segment w/ options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY); selseg.setGeometry(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY, selseg.map1D2D);
strip.resume(); strip.resume();
} else { } else {
DEBUG_PRINTF_P(PSTR("Set segment grouping: %d [%d,%d]\n"), id, (int)udpIn[5+ofs], (int)udpIn[6+ofs]); DEBUG_PRINTF_P(PSTR("Set segment grouping: %d [%d,%d]\n"), id, (int)udpIn[5+ofs], (int)udpIn[6+ofs]);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY); selseg.setGeometry(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY, selseg.map1D2D);
strip.resume(); strip.resume();
} }
} }
@ -416,18 +417,18 @@ void realtimeLock(uint32_t timeoutMs, byte md)
start = mainseg.start; start = mainseg.start;
stop = mainseg.stop; stop = mainseg.stop;
mainseg.freeze = true; mainseg.freeze = true;
// if WLED was off and using main segment only, freeze non-main segments so they stay off
if (bri == 0) {
for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
strip.getSegment(s).freeze = true;
}
}
} else { } else {
start = 0; start = 0;
stop = strip.getLengthTotal(); stop = strip.getLengthTotal();
} }
// clear strip/segment // clear strip/segment
for (size_t i = start; i < stop; i++) strip.setPixelColor(i,BLACK); for (size_t i = start; i < stop; i++) strip.setPixelColor(i,BLACK);
// if WLED was off and using main segment only, freeze non-main segments so they stay off
if (useMainSegmentOnly && bri == 0) {
for (size_t s=0; s < strip.getSegmentsNum(); s++) {
strip.getSegment(s).freeze = true;
}
}
} }
// if strip is off (bri==0) and not already in RTM // if strip is off (bri==0) and not already in RTM
if (briT == 0 && !realtimeMode && !realtimeOverride) { if (briT == 0 && !realtimeMode && !realtimeOverride) {
@ -510,12 +511,10 @@ void handleNotifications()
rgbUdp.read(lbuf, packetSize); rgbUdp.read(lbuf, packetSize);
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
unsigned id = 0;
unsigned totalLen = strip.getLengthTotal(); unsigned totalLen = strip.getLengthTotal();
for (size_t i = 0; i < packetSize -2; i += 3) if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
{ for (size_t i = 0, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) {
setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0); setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0);
id++; if (id >= totalLen) break;
} }
if (!(realtimeMode && useMainSegmentOnly)) strip.show(); if (!(realtimeMode && useMainSegmentOnly)) strip.show();
return; return;
@ -595,17 +594,11 @@ void handleNotifications()
unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
unsigned totalLen = strip.getLengthTotal(); unsigned totalLen = strip.getLengthTotal();
for (size_t i = 6; i < tpmPayloadFrameSize + 4U; i += 3) if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
{ for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) {
if (id < totalLen)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++;
} }
else break; if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received
}
if (tpmPacketCount == numPackets) //reset packet count and show if all packets were received
{
tpmPacketCount = 0; tpmPacketCount = 0;
strip.show(); strip.show();
} }
@ -629,6 +622,7 @@ void handleNotifications()
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
unsigned totalLen = strip.getLengthTotal(); unsigned totalLen = strip.getLengthTotal();
if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
if (udpIn[0] == 1 && packetSize > 5) //warls if (udpIn[0] == 1 && packetSize > 5) //warls
{ {
for (size_t i = 2; i < packetSize -3; i += 4) for (size_t i = 2; i < packetSize -3; i += 4)
@ -637,39 +631,29 @@ void handleNotifications()
} }
} else if (udpIn[0] == 2 && packetSize > 4) //drgb } else if (udpIn[0] == 2 && packetSize > 4) //drgb
{ {
unsigned id = 0; for (size_t i = 2, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++)
for (size_t i = 2; i < packetSize -2; i += 3)
{ {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++; if (id >= totalLen) break;
} }
} else if (udpIn[0] == 3 && packetSize > 6) //drgbw } else if (udpIn[0] == 3 && packetSize > 6) //drgbw
{ {
unsigned id = 0; for (size_t i = 2, id = 0; i < packetSize -3 && id < totalLen; i += 4, id++)
for (size_t i = 2; i < packetSize -3; i += 4)
{ {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
id++; if (id >= totalLen) break;
} }
} else if (udpIn[0] == 4 && packetSize > 7) //dnrgb } else if (udpIn[0] == 4 && packetSize > 7) //dnrgb
{ {
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (size_t i = 4; i < packetSize -2; i += 3) for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 3, id++)
{ {
if (id >= totalLen) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++;
} }
} else if (udpIn[0] == 5 && packetSize > 8) //dnrgbw } else if (udpIn[0] == 5 && packetSize > 8) //dnrgbw
{ {
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (size_t i = 4; i < packetSize -2; i += 4) for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 4, id++)
{ {
if (id >= totalLen) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
id++;
} }
} }
strip.show(); strip.show();
@ -704,11 +688,11 @@ void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w)
b = gamma8(b); b = gamma8(b);
w = gamma8(w); w = gamma8(w);
} }
uint32_t col = RGBW32(r,g,b,w);
if (useMainSegmentOnly) { if (useMainSegmentOnly) {
Segment &seg = strip.getMainSegment(); strip.getMainSegment().setPixelColor(pix, col); // this expects that strip.getMainSegment().beginDraw() has been called in handleNotification()
if (pix<seg.length()) seg.setPixelColor(pix, r, g, b, w);
} else { } else {
strip.setPixelColor(pix, r, g, b, w); strip.setPixelColor(pix, col);
} }
} }
} }

View File

@ -45,7 +45,7 @@
#endif #endif
#ifdef USERMOD_BH1750 #ifdef USERMOD_BH1750
#include "../usermods/BH1750_v2/usermod_BH1750.h" #include "../usermods/BH1750_v2/usermod_bh1750.h"
#endif #endif
// BME280 v2 usermod. Define "USERMOD_BME280" in my_config.h // BME280 v2 usermod. Define "USERMOD_BME280" in my_config.h

View File

@ -14,7 +14,7 @@ int getNumVal(const String* req, uint16_t pos)
void parseNumber(const char* str, byte* val, byte minv, byte maxv) void parseNumber(const char* str, byte* val, byte minv, byte maxv)
{ {
if (str == nullptr || str[0] == '\0') return; if (str == nullptr || str[0] == '\0') return;
if (str[0] == 'r') {*val = random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0 if (str[0] == 'r') {*val = hw_random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0
bool wrap = false; bool wrap = false;
if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;} if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;}
if (str[0] == '~') { if (str[0] == '~') {
@ -474,9 +474,9 @@ um_data_t* simulateSound(uint8_t simulationId)
break; break;
case UMS_WeWillRockYou: case UMS_WeWillRockYou:
if (ms%2000 < 200) { if (ms%2000 < 200) {
volumeSmth = random8(255); volumeSmth = hw_random8();
for (int i = 0; i<5; i++) for (int i = 0; i<5; i++)
fftResult[i] = random8(255); fftResult[i] = hw_random8();
} }
else if (ms%2000 < 400) { else if (ms%2000 < 400) {
volumeSmth = 0; volumeSmth = 0;
@ -484,9 +484,9 @@ um_data_t* simulateSound(uint8_t simulationId)
fftResult[i] = 0; fftResult[i] = 0;
} }
else if (ms%2000 < 600) { else if (ms%2000 < 600) {
volumeSmth = random8(255); volumeSmth = hw_random8();
for (int i = 5; i<11; i++) for (int i = 5; i<11; i++)
fftResult[i] = random8(255); fftResult[i] = hw_random8();
} }
else if (ms%2000 < 800) { else if (ms%2000 < 800) {
volumeSmth = 0; volumeSmth = 0;
@ -494,9 +494,9 @@ um_data_t* simulateSound(uint8_t simulationId)
fftResult[i] = 0; fftResult[i] = 0;
} }
else if (ms%2000 < 1000) { else if (ms%2000 < 1000) {
volumeSmth = random8(255); volumeSmth = hw_random8();
for (int i = 11; i<16; i++) for (int i = 11; i<16; i++)
fftResult[i] = random8(255); fftResult[i] = hw_random8();
} }
else { else {
volumeSmth = 0; volumeSmth = 0;
@ -516,7 +516,7 @@ um_data_t* simulateSound(uint8_t simulationId)
break; break;
} }
samplePeak = random8() > 250; samplePeak = hw_random8() > 250;
FFT_MajorPeak = 21 + (volumeSmth*volumeSmth) / 8.0f; // walk thru full range of 21hz...8200hz FFT_MajorPeak = 21 + (volumeSmth*volumeSmth) / 8.0f; // walk thru full range of 21hz...8200hz
maxVol = 31; // this gets feedback fro UI maxVol = 31; // this gets feedback fro UI
binNum = 8; // this gets feedback fro UI binNum = 8; // this gets feedback fro UI
@ -582,7 +582,7 @@ void enumerateLedmaps() {
uint8_t get_random_wheel_index(uint8_t pos) { uint8_t get_random_wheel_index(uint8_t pos) {
uint8_t r = 0, x = 0, y = 0, d = 0; uint8_t r = 0, x = 0, y = 0, d = 0;
while (d < 42) { while (d < 42) {
r = random8(); r = hw_random8();
x = abs(pos - r); x = abs(pos - r);
y = 255 - x; y = 255 - x;
d = MIN(x, y); d = MIN(x, y);
@ -594,3 +594,18 @@ uint8_t get_random_wheel_index(uint8_t pos) {
float mapf(float x, float in_min, float in_max, float out_min, float out_max) { float mapf(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
} }
// 32 bit random number generator, inlining uses more code, use hw_random16() if speed is critical (see fcn_declare.h)
uint32_t hw_random(uint32_t upperlimit) {
uint32_t rnd = hw_random();
uint64_t scaled = uint64_t(rnd) * uint64_t(upperlimit);
return scaled >> 32;
}
int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
if(lowerlimit >= upperlimit) {
return lowerlimit;
}
uint32_t diff = upperlimit - lowerlimit;
return hw_random(diff) + lowerlimit;
}

View File

@ -222,6 +222,7 @@ void WLED::loop()
BusManager::setBrightness(bri); // fix re-initialised bus' brightness #4005 BusManager::setBrightness(bri); // fix re-initialised bus' brightness #4005
if (aligned) strip.makeAutoSegments(); if (aligned) strip.makeAutoSegments();
else strip.fixInvalidSegments(); else strip.fixInvalidSegments();
BusManager::setBrightness(bri); // fix re-initialised bus' brightness
doSerializeConfig = true; doSerializeConfig = true;
} }
if (loadLedmap >= 0) { if (loadLedmap >= 0) {
@ -543,14 +544,8 @@ void WLED::setup()
#endif #endif
// Seed FastLED random functions with an esp random value, which already works properly at this point. // Seed FastLED random functions with an esp random value, which already works properly at this point.
#if defined(ARDUINO_ARCH_ESP32) const uint32_t seed32 = hw_random();
const uint32_t seed32 = esp_random(); random16_set_seed((uint16_t)seed32);
#elif defined(ARDUINO_ARCH_ESP8266)
const uint32_t seed32 = RANDOM_REG32;
#else
const uint32_t seed32 = random(std::numeric_limits<long>::max());
#endif
random16_set_seed((uint16_t)((seed32 & 0xFFFF) ^ (seed32 >> 16)));
#if WLED_WATCHDOG_TIMEOUT > 0 #if WLED_WATCHDOG_TIMEOUT > 0
enableWatchdog(); enableWatchdog();
@ -575,10 +570,11 @@ void WLED::beginStrip()
} else { } else {
// fix for #3196 // fix for #3196
if (bootPreset > 0) { if (bootPreset > 0) {
bool oldTransition = fadeTransition; // workaround if transitions are enabled // set all segments black (no transition)
fadeTransition = false; // ignore transitions temporarily for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
strip.setColor(0, BLACK); // set all segments black Segment &seg = strip.getSegment(i);
fadeTransition = oldTransition; // restore transitions if (seg.isActive()) seg.colors[0] = BLACK;
}
col[0] = col[1] = col[2] = col[3] = 0; // needed for colorUpdated() col[0] = col[1] = col[2] = col[3] = 0; // needed for colorUpdated()
} }
briLast = briS; bri = 0; briLast = briS; bri = 0;

View File

@ -3,7 +3,6 @@
/* /*
Main sketch, global variable declarations Main sketch, global variable declarations
@title WLED project sketch @title WLED project sketch
@version 0.15.0-dev
@author Christian Schwinne @author Christian Schwinne
*/ */
@ -583,7 +582,6 @@ WLED_GLOBAL bool transitionActive _INIT(false);
WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration
WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json) WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json)
WLED_GLOBAL unsigned long transitionStartTime; WLED_GLOBAL unsigned long transitionStartTime;
WLED_GLOBAL float tperLast _INIT(0.0f); // crossfade transition progress, 0.0f - 1.0f
WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt") WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt")
WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s) WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s)
WLED_GLOBAL bool useHarmonicRandomPalette _INIT(true); // use *harmonic* random palette generation (nicer looking) or truly random WLED_GLOBAL bool useHarmonicRandomPalette _INIT(true); // use *harmonic* random palette generation (nicer looking) or truly random