Configuration item for harmonic palettes

Comment cleanup
This commit is contained in:
Blaz Kristan 2024-02-06 11:06:23 +01:00
parent 66d9e8c038
commit 41e51bbeb5
8 changed files with 52 additions and 54 deletions

View File

@ -420,8 +420,8 @@ typedef struct Segment {
// perhaps this should be per segment, not static // perhaps this should be per segment, not static
static CRGBPalette16 _randomPalette; // actual random palette static CRGBPalette16 _randomPalette; // actual random palette
static CRGBPalette16 _newRandomPalette; // target random palette static CRGBPalette16 _newRandomPalette; // target random palette
static uint16_t _lastPaletteChange; // last random palette change time in millis()/1000 static uint16_t _lastPaletteChange; // last random palette change time in millis()/1000
static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
static bool _modeBlend; // mode/effect blending semaphore static bool _modeBlend; // mode/effect blending semaphore
#endif #endif

View File

@ -77,10 +77,10 @@ uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for t
uint16_t Segment::maxWidth = DEFAULT_LED_COUNT; uint16_t Segment::maxWidth = DEFAULT_LED_COUNT;
uint16_t Segment::maxHeight = 1; uint16_t Segment::maxHeight = 1;
CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment
uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only) uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only)
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
bool Segment::_modeBlend = false; bool Segment::_modeBlend = false;
@ -221,9 +221,9 @@ CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint
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;
case 1: {//periodically replace palette with a random one case 1: //randomly generated palette
targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette() targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette()
break;} break;
case 2: {//primary color only case 2: {//primary color only
CRGB prim = gamma32(colors[0]); CRGB prim = gamma32(colors[0]);
targetPalette = CRGBPalette16(prim); break;} targetPalette = CRGBPalette16(prim); break;}
@ -452,24 +452,21 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u
return targetPalette; return targetPalette;
} }
// relies on WS2812FX::service() to call it max every 8ms or more (MIN_SHOW_DELAY) // relies on WS2812FX::service() to call it for each frame
void Segment::handleRandomPalette() { void Segment::handleRandomPalette() {
// just do a blend; if the palettes are identical it will just compare 48 bytes (same as _randomPalette == _newRandomPalette) // is it time to generate a new palette?
// this will slowly blend _newRandomPalette into _randomPalette every 15ms or 8ms (depending on MIN_SHOW_DELAY)
// if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls)
if ((millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) { if ((millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) {
_newRandomPalette = generateHarmonicRandomPalette(_randomPalette); _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette();
_lastPaletteChange = millis()/1000U; _lastPaletteChange = millis()/1000U;
_lastPaletteBlend = (uint16_t)(millis()&0xFFFF)-512; //starts blending immediately _lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately
} }
if (strip.paletteFade) // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls)
{ if (strip.paletteFade) {
if ((millis() & 0xFFFF) - _lastPaletteBlend < strip.getTransition() >> 7) {//assumes that 128 updates are needed to blend a palette, so shift by 7 (can be more, can be less) // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less)
return; //not time to fade yet, delay the update // in reality there need to be 255 blends to fully blend two entirely different palettes
} if ((millis() & 0xFFFF) - _lastPaletteBlend < strip.getTransition() >> 7) return; // not yet time to fade, delay the update
_lastPaletteBlend = millis(); _lastPaletteBlend = millis();
} }
nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48);
} }

View File

@ -395,6 +395,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
strip.setTransition(fadeTransition ? transitionDelayDefault : 0); strip.setTransition(fadeTransition ? transitionDelayDefault : 0);
CJSON(strip.paletteFade, light_tr["pal"]); CJSON(strip.paletteFade, light_tr["pal"]);
CJSON(randomPaletteChangeTime, light_tr[F("rpc")]); CJSON(randomPaletteChangeTime, light_tr[F("rpc")]);
CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]);
JsonObject light_nl = light["nl"]; JsonObject light_nl = light["nl"];
CJSON(nightlightMode, light_nl["mode"]); CJSON(nightlightMode, light_nl["mode"]);
@ -872,6 +873,7 @@ void serializeConfig() {
light_tr["dur"] = transitionDelayDefault / 100; light_tr["dur"] = transitionDelayDefault / 100;
light_tr["pal"] = strip.paletteFade; light_tr["pal"] = strip.paletteFade;
light_tr[F("rpc")] = randomPaletteChangeTime; light_tr[F("rpc")] = randomPaletteChangeTime;
light_tr[F("hrp")] = useHarmonicRandomPalette;
JsonObject light_nl = light.createNestedObject("nl"); JsonObject light_nl = light.createNestedObject("nl");
light_nl["mode"] = nightlightMode; light_nl["mode"] = nightlightMode;

View File

@ -91,12 +91,11 @@ void setRandomColor(byte* rgb)
colorHStoRGB(lastRandomIndex*256,255,rgb); colorHStoRGB(lastRandomIndex*256,255,rgb);
} }
/* /*
*generates a random palette based on harmonic color theory * generates a random palette based on harmonic color theory
*takes a base palette as the input, it will choose one color of the base palette and keep it * takes a base palette as the input, it will choose one color of the base palette and keep it
*/ */
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 = random8(4); //color position of current random palette to keep
@ -104,7 +103,7 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette)
palettecolors[keepcolorposition].hue += random8(10)-5; // +/- 5 randomness of base color palettecolors[keepcolorposition].hue += 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 = random8(200,255);
@ -114,8 +113,7 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette)
palettecolors[3].saturation = random8(20,255); palettecolors[3].saturation = random8(20,255);
palettecolors[3].value = random8(80,255); palettecolors[3].value = 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[random8(i + 1)].saturation);
std::swap(palettecolors[i].value, palettecolors[random8(i + 1)].value); std::swap(palettecolors[i].value, palettecolors[random8(i + 1)].value);
@ -151,17 +149,15 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette)
harmonics[2] = basehue + 265 + random8(10); harmonics[2] = basehue + 265 + random8(10);
break; break;
case 4: // tetradic case 4: // tetradic
harmonics[0] = basehue + 80 + random8(20); harmonics[0] = basehue + 80 + random8(20);
harmonics[1] = basehue + 170 + random8(20); harmonics[1] = basehue + 170 + random8(20);
harmonics[2] = basehue + random8(30)-15; harmonics[2] = basehue + random8(30)-15;
break; break;
} }
if (random8() < 128) {
if (random8() < 128) //50:50 chance of shuffeling hues or keep the color order //50:50 chance of shuffling hues or keep the color order
{
//shuffle the hues:
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[random8(i + 1)]);
} }
@ -170,36 +166,35 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette)
//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 (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) {
palettecolors[i].saturation -= 160; //desaturate all four colors palettecolors[i].saturation -= 160; //desaturate all four colors
} }
RGBpalettecolors[i] = (CRGB)palettecolors[i]; //convert to RGB RGBpalettecolors[i] = (CRGB)palettecolors[i]; //convert to RGB
RGBpalettecolors[i] = gamma32((uint32_t)RGBpalettecolors[i]); RGBpalettecolors[i] = gamma32(((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU); //strip alpha from CRGB
} }
return CRGBPalette16( RGBpalettecolors[0], return CRGBPalette16(RGBpalettecolors[0],
RGBpalettecolors[1], RGBpalettecolors[1],
RGBpalettecolors[2], RGBpalettecolors[2],
RGBpalettecolors[3]); RGBpalettecolors[3]);
} }
CRGBPalette16 generateRandomPalette(void) //generate fully random palette CRGBPalette16 generateRandomPalette(void) //generate fully random palette
{ {
return CRGBPalette16( return CRGBPalette16(CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)), CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)), CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255))); CHSV(random8(), random8(160, 255), random8(128, 255)));

View File

@ -848,6 +848,7 @@ Swap: <select id="xw${i}" name="XW${i}">
Palette transitions: <input type="checkbox" name="PF"><br> Palette transitions: <input type="checkbox" name="PF"><br>
</span> </span>
<i>Random Cycle</i> Palette Time: <input name="TP" type="number" class="m" min="1" max="255"> s<br> <i>Random Cycle</i> Palette Time: <input name="TP" type="number" class="m" min="1" max="255"> s<br>
Use harmonic <i>Random Cycle</i> Palette: <input type="checkbox" name="TH"><br>
<h3>Timed light</h3> <h3>Timed light</h3>
Default Duration: <input name="TL" type="number" class="m" min="1" max="255" required> min<br> Default Duration: <input name="TL" type="number" class="m" min="1" max="255" required> min<br>
Default Target brightness: <input name="TB" type="number" class="m" min="0" max="255" required><br> Default Target brightness: <input name="TB" type="number" class="m" min="0" max="255" required><br>

View File

@ -302,6 +302,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strip.paletteFade = request->hasArg(F("PF")); strip.paletteFade = request->hasArg(F("PF"));
t = request->arg(F("TP")).toInt(); t = request->arg(F("TP")).toInt();
randomPaletteChangeTime = MIN(255,MAX(1,t)); randomPaletteChangeTime = MIN(255,MAX(1,t));
useHarmonicRandomPalette = request->hasArg(F("TH"));
nightlightTargetBri = request->arg(F("TB")).toInt(); nightlightTargetBri = request->arg(F("TB")).toInt();
t = request->arg(F("TL")).toInt(); t = request->arg(F("TL")).toInt();

View File

@ -542,15 +542,16 @@ WLED_GLOBAL bool wasConnected _INIT(false);
WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same
// transitions // transitions
WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color
WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending
WLED_GLOBAL bool transitionActive _INIT(false); 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 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
// nightlight // nightlight
WLED_GLOBAL bool nightlightActive _INIT(false); WLED_GLOBAL bool nightlightActive _INIT(false);

View File

@ -453,6 +453,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('v',SET_F("TD"),transitionDelayDefault); sappend('v',SET_F("TD"),transitionDelayDefault);
sappend('c',SET_F("PF"),strip.paletteFade); sappend('c',SET_F("PF"),strip.paletteFade);
sappend('v',SET_F("TP"),randomPaletteChangeTime); sappend('v',SET_F("TP"),randomPaletteChangeTime);
sappend('c',SET_F("TH"),useHarmonicRandomPalette);
sappend('v',SET_F("BF"),briMultiplier); sappend('v',SET_F("BF"),briMultiplier);
sappend('v',SET_F("TB"),nightlightTargetBri); sappend('v',SET_F("TB"),nightlightTargetBri);
sappend('v',SET_F("TL"),nightlightDelayMinsDefault); sappend('v',SET_F("TL"),nightlightDelayMinsDefault);