diff --git a/platformio.ini b/platformio.ini index 29949d33c0..16dc3b5585 100644 --- a/platformio.ini +++ b/platformio.ini @@ -161,7 +161,6 @@ upload_speed = 115200 # ------------------------------------------------------------------------------ lib_compat_mode = strict lib_deps = - fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 https://github.com/Makuna/NeoPixelBus.git#a0919d1c10696614625978dd6fb750a1317a14ce https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2 @@ -249,7 +248,6 @@ lib_deps_compat = ESPAsyncTCP @ 1.2.2 ESPAsyncUDP ESP8266PWM - fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.9 https://github.com/blazoncek/QuickESPNow.git#optional-debug diff --git a/usermods/Analog_Clock/Analog_Clock.cpp b/usermods/Analog_Clock/Analog_Clock.cpp index 3e0a1e99a6..d68ac8c2c8 100644 --- a/usermods/Analog_Clock/Analog_Clock.cpp +++ b/usermods/Analog_Clock/Analog_Clock.cpp @@ -115,7 +115,7 @@ class AnalogClockUsermod : public Usermod { ); } - static inline uint32_t scale32(uint32_t c, fract8 scale) { + static inline uint32_t scale32(uint32_t c, uint8_t scale) { return RGBW32( scale8(R(c), scale), scale8(G(c), scale), diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index f5200ef4d2..fee68db646 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -2008,12 +2008,12 @@ CRGB AudioReactive::getCRGBForBand(int x, int pal) { case 2: b = map(x, 0, 255, 0, NUM_GEQ_CHANNELS/2); // convert palette position to lower half of freq band hsv = CHSV(fftResult[b], 255, x); - hsv2rgb_rainbow(hsv, value); // convert to R,G,B + value = hsv; // convert to R,G,B break; case 1: b = map(x, 1, 255, 0, 10); // convert palette position to lower half of freq band hsv = CHSV(fftResult[b], 255, map(fftResult[b], 0, 255, 30, 255)); // pick hue - hsv2rgb_rainbow(hsv, value); // convert to R,G,B + value = hsv; // convert to R,G,B break; default: if (x == 1) { diff --git a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp index 3ec46f6f77..4e4742ad1d 100644 --- a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp +++ b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp @@ -34,6 +34,7 @@ class RgbRotaryEncoderUsermod : public Usermod byte currentColors[3]; byte lastKnownBri = 0; + inline uint32_t colorFromRgb(byte* rgb) { return uint32_t((byte(rgb[0]) << 16) | (byte(rgb[1]) << 8) | (byte(rgb[2]))); } void initRotaryEncoder() { @@ -79,7 +80,7 @@ class RgbRotaryEncoderUsermod : public Usermod for (int i = 0; i < currentPos / incrementPerClick - 1; i++) { ledBus->setPixelColor(i, 0); } - ledBus->setPixelColor(currentPos / incrementPerClick - 1, colorFromRgbw(currentColors)); + ledBus->setPixelColor(currentPos / incrementPerClick - 1, colorFromRgb(currentColors)); for (int i = currentPos / incrementPerClick; i < numLeds; i++) { ledBus->setPixelColor(i, 0); } @@ -95,7 +96,7 @@ class RgbRotaryEncoderUsermod : public Usermod if (ledMode == 3) { hsv2rgb((i) / float(numLeds), 1, .25); } - ledBus->setPixelColor(i, colorFromRgbw(currentColors)); + ledBus->setPixelColor(i, colorFromRgb(currentColors)); } for (int i = currentPos / incrementPerClick; i < numLeds; i++) { ledBus->setPixelColor(i, 0); diff --git a/usermods/user_fx/user_fx.cpp b/usermods/user_fx/user_fx.cpp index 2258b8ad4f..cf7a651db6 100644 --- a/usermods/user_fx/user_fx.cpp +++ b/usermods/user_fx/user_fx.cpp @@ -117,7 +117,7 @@ static const char _data_FX_MODE_DIFFUSIONFIRE[] PROGMEM = "Diffusion Fire@!,Spar static void mode_spinning_wheel(void) { if (SEGLEN < 1) FX_FALLBACK_STATIC; - + unsigned strips = SEGMENT.nrOfVStrips(); if (strips == 0) FX_FALLBACK_STATIC; @@ -148,7 +148,6 @@ static void mode_spinning_wheel(void) { // Handle random seeding globally (outside the virtual strip) if (SEGENV.call == 0) { - random16_set_seed(hw_random16()); SEGENV.aux1 = (255 << 8) / SEGLEN; // Cache the color scaling } @@ -156,7 +155,6 @@ static void mode_spinning_wheel(void) { uint32_t settingssum = SEGMENT.speed + SEGMENT.intensity + SEGMENT.custom1 + SEGMENT.custom3 + SEGMENT.check1 + SEGMENT.check3; bool settingsChanged = (SEGENV.aux0 != settingssum); if (settingsChanged) { - random16_add_entropy(hw_random16()); SEGENV.aux0 = settingssum; } @@ -166,7 +164,7 @@ static void mode_spinning_wheel(void) { uint8_t spinnerSize = map(SEGMENT.custom1, 0, 255, 1, 10); uint16_t spin_delay = map(SEGMENT.custom3, 0, 31, 2000, 15000); uint32_t now = strip.now; - + for (unsigned stripNr = 0; stripNr < strips; stripNr += spinnerSize) { uint32_t* stripState = &state[stripNr * stateVarsPerStrip]; // Check if this spinner is stopped AND has waited its delay @@ -211,23 +209,23 @@ static void mode_spinning_wheel(void) { // Initialize or restart if (needsReset && SEGMENT.check1) { // spin the spinner(s) only if the "Spin me!" checkbox is enabled state[CUR_POS_IDX] = 0; - + // Set velocity uint16_t speed = map(SEGMENT.speed, 0, 255, 300, 800); if (speed == 300) { // random speed (user selected 0 on speed slider) - state[VELOCITY_IDX] = random16(200, 900) * 655; // fixed-point velocity scaling (approx. 65536/100) + state[VELOCITY_IDX] = hw_random16(200, 900) * 655; // fixed-point velocity scaling (approx. 65536/100) } else { - state[VELOCITY_IDX] = random16(speed - 100, speed + 100) * 655; + state[VELOCITY_IDX] = hw_random16(speed - 100, speed + 100) * 655; } - + // Set slowdown start time uint16_t slowdown = map(SEGMENT.intensity, 0, 255, 3000, 5000); if (slowdown == 3000) { // random slowdown start time (user selected 0 on intensity slider) - state[SLOWDOWN_TIME_IDX] = now + random16(2000, 6000); + state[SLOWDOWN_TIME_IDX] = now + hw_random16(2000, 6000); } else { - state[SLOWDOWN_TIME_IDX] = now + random16(slowdown - 1000, slowdown + 1000); + state[SLOWDOWN_TIME_IDX] = now + hw_random16(slowdown - 1000, slowdown + 1000); } - + state[PHASE_IDX] = 0; state[STOP_TIME_IDX] = 0; state[WOBBLE_STEP_IDX] = 0; @@ -250,10 +248,10 @@ static void mode_spinning_wheel(void) { // Slowing phase - apply deceleration uint32_t decel = velocity / 80; if (decel < 100) decel = 100; - + velocity = (velocity > decel) ? velocity - decel : 0; state[VELOCITY_IDX] = velocity; - + // Check if stopped if (velocity < 2000) { velocity = 0; @@ -270,7 +268,7 @@ static void mode_spinning_wheel(void) { uint32_t wobble_step = state[WOBBLE_STEP_IDX]; uint16_t stop_pos = state[STOP_POS_IDX]; uint32_t elapsed = now - state[WOBBLE_TIME_IDX]; - + if (wobble_step == 0 && elapsed >= 200) { // Move back one LED from stop position uint16_t back_pos = (stop_pos == 0) ? SEGLEN - 1 : stop_pos - 1; @@ -291,13 +289,13 @@ static void mode_spinning_wheel(void) { state[STOP_TIME_IDX] = now; } } - + // Update position (phases 0 and 1 only) if (phase == 0 || phase == 1) { pos_fixed += velocity; state[CUR_POS_IDX] = pos_fixed; } - + // Draw LED for all phases uint16_t pos = (pos_fixed >> 16) % SEGLEN; @@ -314,14 +312,14 @@ static void mode_spinning_wheel(void) { hue = (SEGENV.aux1 * pos) >> 8; } - uint32_t color = ColorFromPaletteWLED(SEGPALETTE, hue, 255, LINEARBLEND); + uint32_t color = ColorFromPalette(SEGPALETTE, hue, 255, LINEARBLEND); // Draw the spinner with configurable size (1-10 LEDs) for (int8_t x = 0; x < spinnerSize; x++) { for (uint8_t y = 0; y < spinnerSize; y++) { uint16_t drawPos = (pos + y) % SEGLEN; int16_t drawStrip = stripNr + x; - + // Wrap horizontally if needed, or skip if out of bounds if (drawStrip >= 0 && drawStrip < strips) { SEGMENT.setPixelColor(indexToVStrip(drawPos, drawStrip), color); @@ -370,7 +368,7 @@ typedef struct LavaParticle { static void mode_2D_lavalamp(void) { if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up - + const uint16_t cols = SEG_W; const uint16_t rows = SEG_H; constexpr float MAX_BLOB_RADIUS = 20.0f; // cap to prevent frame rate drops on large matrices @@ -405,7 +403,7 @@ static void mode_2D_lavalamp(void) { uint8_t size = currentSize; uint8_t numParticles = currentNumParticles; - + // blob size based on matrix width const float minSize = cols * 0.15f; // Minimum 15% of width const float maxSize = cols * 0.4f; // Maximum 40% of width @@ -427,7 +425,7 @@ static void mode_2D_lavalamp(void) { lavaParticles[i].y = rows - 1; lavaParticles[i].vx = (hw_random16(7) - 3) / 250.0f; lavaParticles[i].vy = -(hw_random16(20) + 10) / 100.0f * 0.3f; - + lavaParticles[i].size = minSize + (float)hw_random16(rangeInt); if (lavaParticles[i].size > MAX_BLOB_RADIUS) lavaParticles[i].size = MAX_BLOB_RADIUS; @@ -444,7 +442,7 @@ static void mode_2D_lavalamp(void) { // Fade background slightly for trailing effect SEGMENT.fadeToBlackBy(40); - + // Update and draw particles int activeCount = 0; unsigned long currentMillis = strip.now; @@ -460,21 +458,21 @@ static void mode_2D_lavalamp(void) { } LavaParticle *p = &lavaParticles[i]; - + // Physics update p->x += p->vx; p->y += p->vy; - + // Optional particle/blob attraction if (SEGMENT.check2) { for (int j = 0; j < MAX_LAVA_PARTICLES; j++) { if (i == j || !lavaParticles[j].active) continue; - + LavaParticle *other = &lavaParticles[j]; - + // Skip attraction if moving in same vertical direction (both up or both down) if ((p->vy < 0 && other->vy < 0) || (p->vy > 0 && other->vy > 0)) continue; - + float dx = other->x - p->x; float dy = other->y - p->y; @@ -580,7 +578,7 @@ static void mode_2D_lavalamp(void) { // Get color uint32_t color; color = SEGMENT.color_from_palette(p->hue, true, PALETTE_SOLID_WRAP, 0); - + // Extract RGB and apply life/opacity uint8_t w = (W(color) * 255) >> 8; uint8_t r = (R(color) * 255) >> 8; @@ -600,7 +598,7 @@ static void mode_2D_lavalamp(void) { for (int dx = -(int)p->size - 1; dx <= (int)p->size + 1; dx++) { int px = centerX + dx; int py = centerY + dy; - + if (px < 0 || px >= cols || py < 0 || py >= rows) continue; // Sub-pixel distance: measure from true float center to pixel center @@ -667,23 +665,23 @@ static void drawMagma(const uint16_t width, const uint16_t height, float *ff_y, static void drawLavaBombs(const uint16_t width, const uint16_t height, float *particleData, float gravity, uint8_t particleCount) { for (uint16_t i = 0; i < particleCount; i++) { uint16_t idx = i * 4; - + particleData[idx + 3] -= gravity; particleData[idx + 0] += particleData[idx + 2]; particleData[idx + 1] += particleData[idx + 3]; - + float posX = particleData[idx + 0]; float posY = particleData[idx + 1]; - + if (posY > height + height / 4) { particleData[idx + 3] = -particleData[idx + 3] * 0.8f; } - + if (posY < (float)(height / 8) - 1.0f || posX < 0 || posX >= width) { particleData[idx + 0] = hw_random(0, width * 100) / 100.0f; particleData[idx + 1] = hw_random(0, height * 25) / 100.0f; particleData[idx + 2] = hw_random(-75, 75) / 100.0f; - + float baseVelocity = hw_random(60, 120) / 100.0f; if (hw_random8() < 50) { baseVelocity *= 1.6f; @@ -691,38 +689,38 @@ static void drawLavaBombs(const uint16_t width, const uint16_t height, float *pa particleData[idx + 3] = baseVelocity; continue; } - + int16_t xi = (int16_t)posX; int16_t yi = (int16_t)posY; - + if (xi >= 0 && xi < width && yi >= 0 && yi < height) { // Get a random color from the current palette uint8_t randomIndex = hw_random8(64, 128); - CRGB pcolor = ColorFromPaletteWLED(SEGPALETTE, randomIndex, 255, LINEARBLEND); + CRGB pcolor = ColorFromPalette(SEGPALETTE, randomIndex, 255, LINEARBLEND); // Pre-calculate anti-aliasing weights float xf = posX - xi; float yf = posY - yi; float ix = 1.0f - xf; float iy = 1.0f - yf; - + uint8_t w0 = 255 * ix * iy; uint8_t w1 = 255 * xf * iy; uint8_t w2 = 255 * ix * yf; uint8_t w3 = 255 * xf * yf; - + int16_t yFlipped = height - 1 - yi; // Flip Y coordinate - + SEGMENT.addPixelColorXY(xi, yFlipped, pcolor.scale8(w0)); if (xi + 1 < width) SEGMENT.addPixelColorXY(xi + 1, yFlipped, pcolor.scale8(w1)); if (yFlipped - 1 >= 0) SEGMENT.addPixelColorXY(xi, yFlipped - 1, pcolor.scale8(w2)); - if (xi + 1 < width && yFlipped - 1 >= 0) + if (xi + 1 < width && yFlipped - 1 >= 0) SEGMENT.addPixelColorXY(xi + 1, yFlipped - 1, pcolor.scale8(w3)); } } -} +} static void mode_2D_magma(void) { if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up @@ -746,7 +744,7 @@ static void mode_2D_magma(void) { uint32_t settingsKey = (uint32_t)SEGMENT.speed | ((uint32_t)SEGMENT.intensity << 8) | ((uint32_t)SEGMENT.custom1 << 16) | ((uint32_t)SEGMENT.custom2 << 24); bool settingsChanged = (*settingsSumPtr != settingsKey); - + if (SEGENV.call == 0 || settingsChanged) { // Intensity slider controls magma height uint16_t intensity = SEGMENT.intensity; @@ -772,7 +770,7 @@ static void mode_2D_magma(void) { particleData[idx + 0] = hw_random(0, width * 100) / 100.0f; particleData[idx + 1] = hw_random(0, height * 25) / 100.0f; particleData[idx + 2] = hw_random(-75, 75) / 100.0f; - + float baseVelocity = hw_random(60, 120) / 100.0f; if (hw_random8() < 50) { baseVelocity *= 1.6f; @@ -793,7 +791,7 @@ static void mode_2D_magma(void) { // Gravity control float gravity = map(SEGMENT.custom2, 0, 255, 5, 20) / 100.0f; - + // Number of particles (lava bombs) uint8_t particleCount = map(SEGMENT.custom1, 0, 255, 0, MAGMA_MAX_PARTICLES); particleCount = constrain(particleCount, 0, MAGMA_MAX_PARTICLES); @@ -1035,7 +1033,7 @@ static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant s // Build morse code pattern into a buffer static void build_morsecode_pattern(const char *morse_code, uint8_t *pattern, uint8_t *wordIndex, uint16_t &index, uint8_t currentWord, int maxSize) { const char *c = morse_code; - + // Build the dots and dashes into pattern array while (*c != '\0') { // it's a dot which is 1 pixel @@ -1082,7 +1080,7 @@ static void build_morsecode_pattern(const char *morse_code, uint8_t *pattern, ui static void mode_morsecode(void) { if (SEGLEN < 1) FX_FALLBACK_STATIC; - + // A-Z in Morse Code static const char * letters[] = {".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."}; @@ -1286,7 +1284,7 @@ class UserFxUsermod : public Usermod { // strip.addEffect(255, &mode_your_effect3, _data_FX_MODE_YOUR_EFFECT3); } - + /////////////////////////////////////////////////////////////////////////////////////////////// // If you want configuration options in the usermod settings page, implement these methods // /////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.cpp b/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.cpp index 0a485152f1..43ac2bf1bc 100644 --- a/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.cpp +++ b/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.cpp @@ -96,14 +96,14 @@ class RotaryEncoderBrightnessColor : public Usermod fastled_col.red = colPri[0]; fastled_col.green = colPri[1]; fastled_col.blue = colPri[2]; - prim_hsv = rgb2hsv_approximate(fastled_col); + prim_hsv = rgb2hsv(fastled_col); new_val = (int16_t)prim_hsv.h + fadeAmount; 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 prim_hsv.h = (byte)new_val; - hsv2rgb_rainbow(prim_hsv, fastled_col); + fastled_col = prim_hsv ; colPri[0] = fastled_col.red; colPri[1] = fastled_col.green; colPri[2] = fastled_col.blue; @@ -121,14 +121,14 @@ class RotaryEncoderBrightnessColor : public Usermod fastled_col.red = colPri[0]; fastled_col.green = colPri[1]; fastled_col.blue = colPri[2]; - prim_hsv = rgb2hsv_approximate(fastled_col); + prim_hsv = rgb2hsv(fastled_col); new_val = (int16_t)prim_hsv.h - fadeAmount; 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 prim_hsv.h = (byte)new_val; - hsv2rgb_rainbow(prim_hsv, fastled_col); + fastled_col = prim_hsv; colPri[0] = fastled_col.red; colPri[1] = fastled_col.green; colPri[2] = fastled_col.blue; diff --git a/usermods/usermod_v2_animartrix/library.json b/usermods/usermod_v2_animartrix/library.json index 667572bad9..737fd62a21 100644 --- a/usermods/usermod_v2_animartrix/library.json +++ b/usermods/usermod_v2_animartrix/library.json @@ -2,6 +2,6 @@ "name": "animartrix", "build": { "libArchive": false }, "dependencies": { - "Animartrix": "https://github.com/netmindz/animartrix.git#b172586" + "Animartrix": "https://github.com/netmindz/animartrix.git#81eb09b91c8c9c8c01f8ea442787f8127d56c72f" } } diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 0b5ecdda94..ccb0a5c008 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -13,6 +13,8 @@ #include "wled.h" #include "FX.h" #include "fcn_declare.h" +#include "colors.h" +#include "prng.h" #define FX_FALLBACK_STATIC { mode_static(); return; } @@ -81,17 +83,14 @@ //#define MAX_FREQUENCY 5120 //#define MAX_FREQ_LOG10 3.71f +static PRNG prng(hw_random()); // pseudo-random number generator class, seed = hardware random number + // effect utility functions static uint8_t sin_gap(uint16_t in) { if (in & 0x100) return 0; return sin8_t(in + 192); // correct phase shift of sine so that it starts and stops at 0 } -static uint16_t triwave16(uint16_t in) { - if (in < 0x8000) return in *2; - return 0xFFFF - (in - 0x8000)*2; -} - /* * Generates a tristate square wave w/ attac & decay * @param x input value 0-255 @@ -149,8 +148,7 @@ void mode_copy_segment(void) { Segment& sourcesegment = strip.getSegment(sourceid); if (sourcesegment.isActive()) { - uint32_t sourcecolor; - uint32_t destcolor; + CRGBW color; if(sourcesegment.is2D()) { // 2D source, note: 2D to 1D just copies the first row (or first column if 'Switch axis' is checked in FX) for (unsigned y = 0; y < SEGMENT.vHeight(); y++) { for (unsigned x = 0; x < SEGMENT.vWidth(); x++) { @@ -158,29 +156,29 @@ void mode_copy_segment(void) { unsigned sy = y; if(SEGMENT.check1) std::swap(sx, sy); // flip axis if(SEGMENT.check2) { - sourcecolor = strip.getPixelColorXY(sx + sourcesegment.start, sy + sourcesegment.startY); // read from global buffer (reads the last rendered frame) + color = strip.getPixelColorXY(sx + sourcesegment.start, sy + sourcesegment.startY); // read from global buffer (reads the last rendered frame) } else { sourcesegment.setDrawDimensions(); // set to source segment dimensions - sourcecolor = sourcesegment.getPixelColorXY(sx, sy); // read from segment buffer + color = sourcesegment.getPixelColorXY(sx, sy); // read from segment buffer } - destcolor = adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); + adjust_color(color, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); // hue shif, sat change, value change SEGMENT.setDrawDimensions(); // reset to current segment dimensions - SEGMENT.setPixelColorXY(x, y, destcolor); + SEGMENT.setPixelColorXY(x, y, color); } } } else { // 1D source, source can be expanded into 2D for (unsigned i = 0; i < SEGMENT.vLength(); i++) { if(SEGMENT.check2) { - sourcecolor = strip.getPixelColorNoMap(i + sourcesegment.start); // read from global buffer (reads the last rendered frame) + color = strip.getPixelColorNoMap(i + sourcesegment.start); // read from global buffer (reads the last rendered frame) } else { sourcesegment.setDrawDimensions(); // set to source segment dimensions - sourcecolor = sourcesegment.getPixelColor(i); + color = sourcesegment.getPixelColor(i); } - destcolor = adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); + adjust_color(color, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); // hue shif, sat change, value change SEGMENT.setDrawDimensions(); // reset to current segment dimensions - SEGMENT.setPixelColor(i, destcolor); + SEGMENT.setPixelColor(i, color); } } } @@ -1813,30 +1811,30 @@ static const char _data_FX_MODE_MULTI_COMET[] PROGMEM = "Multi Comet@!,Fade;!,!; */ void mode_random_chase(void) { if (SEGENV.call == 0) { - SEGENV.step = RGBW32(random8(), random8(), random8(), 0); - SEGENV.aux0 = random16(); + SEGENV.step = RGBW32(prng.random8(), prng.random8(), prng.random8(), 0); + SEGENV.aux0 = prng.random16(); } - unsigned prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function + unsigned prevSeed = prng.getSeed(); // save seed so we can restore it at the end of the function uint32_t cycleTime = 25 + (3 * (uint32_t)(255 - SEGMENT.speed)); uint32_t it = strip.now / cycleTime; uint32_t color = SEGENV.step; - random16_set_seed(SEGENV.aux0); + prng.setSeed(SEGENV.aux0); for (int i = SEGLEN -1; i >= 0; i--) { - uint8_t r = random8(6) != 0 ? (color >> 16 & 0xFF) : random8(); - uint8_t g = random8(6) != 0 ? (color >> 8 & 0xFF) : random8(); - uint8_t b = random8(6) != 0 ? (color & 0xFF) : random8(); + uint8_t r = prng.random8(6) != 0 ? (color >> 16 & 0xFF) : prng.random8(); + uint8_t g = prng.random8(6) != 0 ? (color >> 8 & 0xFF) : prng.random8(); + uint8_t b = prng.random8(6) != 0 ? (color & 0xFF) : prng.random8(); color = RGBW32(r, g, b, 0); SEGMENT.setPixelColor(i, color); if (i == SEGLEN -1U && SEGENV.aux1 != (it & 0xFFFFU)) { //new first color in next frame SEGENV.step = color; - SEGENV.aux0 = random16_get_seed(); + SEGENV.aux0 = prng.getSeed(); } } SEGENV.aux1 = it & 0xFFFF; - random16_set_seed(prevSeed); // restore original seed so other effects can use "random" PRNG + prng.setSeed(prevSeed); // restore original seed so other effects can use "random" PRNG } static const char _data_FX_MODE_RANDOM_CHASE[] PROGMEM = "Stream 2@!;;"; @@ -2308,8 +2306,8 @@ void mode_colortwinkle() { SEGENV.step = strip.now; CRGBW col, prev; - fract8 fadeUpAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>2) : 68-strip.getBrightness(); - fract8 fadeDownAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>3) : 68-strip.getBrightness(); + uint8_t fadeUpAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>2) : 68-strip.getBrightness(); + uint8_t fadeDownAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>3) : 68-strip.getBrightness(); for (unsigned i = 0; i < SEGLEN; i++) { CRGBW cur = SEGMENT.getPixelColor(i); prev = cur; @@ -2325,14 +2323,13 @@ void mode_colortwinkle() { bitWrite(SEGENV.data[index], bitNum, false); } - if (cur == prev) { //fix "stuck" pixels + if (col == cur) { // color_add did nothing, fix "stuck" pixels by adding the color to itself col = color_add(col, col); - SEGMENT.setPixelColor(i, col); } - else SEGMENT.setPixelColor(i, col); + SEGMENT.setPixelColor(i, col); } else { - col = color_fade(cur, 255 - fadeDownAmount); + col = color_fade(cur, 255 - fadeDownAmount, false); SEGMENT.setPixelColor(i, col); } } @@ -2577,7 +2574,7 @@ static const char _data_FX_MODE_RIPPLE_RAINBOW[] PROGMEM = "Ripple Rainbow@!,Wav // // TwinkleFOX: Twinkling 'holiday' lights that fade in and out. // Colors are chosen from a palette. Read more about this effect using the link above! -static CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) +static CRGBW twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) { // Overall twinkle speed (changed) unsigned ticks = ms / SEGENV.aux0; @@ -2614,7 +2611,7 @@ static CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) } unsigned hue = slowcycle8 - salt; - CRGB c; + CRGBW c; if (bright > 0) { c = ColorFromPalette(SEGPALETTE, hue, bright, NOBLEND); if (!SEGMENT.check1) { @@ -2629,7 +2626,7 @@ static CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) } } } else { - c = CRGB::Black; + c = 0; // black } return c; } @@ -2652,15 +2649,16 @@ static void twinklefox_base(bool cat) else SEGENV.aux0 = 22 + ((100 - SEGMENT.speed) >> 1); // Set up the background color, "bg". - CRGB bg = CRGB(SEGCOLOR(1)); + CRGBW bg = SEGCOLOR(1); unsigned bglight = bg.getAverageLight(); if (bglight > 64) { - bg.nscale8_video(16); // very bright, so scale to 1/16th + bg = color_fade(bg, 16, true); // very bright, so scale to 1/16th } else if (bglight > 16) { - bg.nscale8_video(64); // not that bright, so scale to 1/4th + bg = color_fade(bg, 64, true); // not that bright, so scale to 1/4th } else { - bg.nscale8_video(86); // dim, scale to 1/3rd. + bg = color_fade(bg, 86, true); // dim, scale to 1/3rd. } + bg = gamma32inv(bg); // need to invert gamma as the FX was written without any gamma correction and it will dim down too much otherwise unsigned backgroundBrightness = bg.getAverageLight(); @@ -2677,18 +2675,18 @@ static void twinklefox_base(bool cat) // We now have the adjusted 'clock' for this pixel, now we call // the function that computes what color the pixel should be based // on the "brightness = f( time )" idea. - CRGB c = twinklefox_one_twinkle(myclock30, myunique8, cat); + CRGBW c = twinklefox_one_twinkle(myclock30, myunique8, cat); unsigned cbright = c.getAverageLight(); int deltabright = cbright - backgroundBrightness; - if (deltabright >= 32 || (!bg)) { + if (deltabright >= 32 || (bg==0)) { // If the new pixel is significantly brighter than the background color, // use the new color. SEGMENT.setPixelColor(i, c); } else if (deltabright > 0) { // If the new pixel is just slightly brighter than the background color, // mix a blend of the new color and the background color - SEGMENT.setPixelColor(i, color_blend(RGBW32(bg.r,bg.g,bg.b,0), RGBW32(c.r,c.g,c.b,0), uint8_t(deltabright * 8))); + SEGMENT.setPixelColor(i, color_blend(bg, c, uint8_t(deltabright * 8))); } else { // if the new pixel is not at all brighter than the background color, // just use the background color. @@ -4338,18 +4336,18 @@ void mode_phased_noise(void) { static const char _data_FX_MODE_PHASEDNOISE[] PROGMEM = "Phased Noise@!,!;!,!;!"; -void mode_twinkleup(void) { // A very short twinkle routine with fade-in and dual controls. By Andrew Tuline. - unsigned prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function - random16_set_seed(535); // The randomizer needs to be re-set each time through the loop in order for the same 'random' numbers to be the same each time through. +void mode_twinkleup(void) { // A very short twinkle routine with fade-in and dual controls. By Andrew Tuline. + unsigned prevSeed = prng.getSeed(); // save seed so we can restore it at the end of the function + prng.setSeed(535); // The randomizer needs to be re-set each time through the loop in order for the same 'random' numbers to be the same each time through. for (unsigned i = 0; i < SEGLEN; i++) { - unsigned ranstart = random8(); // The starting value (aka brightness) for each pixel. Must be consistent each time through the loop for this to work. + unsigned ranstart = prng.random8(); // The starting value (aka brightness) for each pixel. Must be consistent each time through the loop for this to work. unsigned pixBri = sin8_t(ranstart + 16 * strip.now/(256-SEGMENT.speed)); - if (random8() > SEGMENT.intensity) pixBri = 0; - SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(random8()+strip.now/100, false, PALETTE_SOLID_WRAP, 0), pixBri)); + if (prng.random8() > SEGMENT.intensity) pixBri = 0; + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(prng.random8()+strip.now/100, false, PALETTE_SOLID_WRAP, 0), pixBri)); } - random16_set_seed(prevSeed); // restore original seed so other effects can use "random" PRNG + prng.setSeed(prevSeed); // restore original seed so other effects can use "random" PRNG } static const char _data_FX_MODE_TWINKLEUP[] PROGMEM = "Twinkleup@!,Intensity;!,!;!;;m12=0"; @@ -4370,6 +4368,7 @@ void mode_noisepal(void) { // Slow noise pale SEGENV.step = strip.now; unsigned baseI = hw_random8(); + //palettes[1] = CRGBPalette16(CHSV(baseI+hw_random8(64), 255, hw_random8(128,255)), CHSV(baseI+128, 255, hw_random8(128,255)), CHSV(baseI+hw_random8(92), 192, hw_random8(128,255)), CHSV(baseI+hw_random8(92), 255, hw_random8(128,255))); palettes[1] = CRGBPalette16(CHSV(baseI+hw_random8(64), 255, hw_random8(128,255)), CHSV(baseI+128, 255, hw_random8(128,255)), CHSV(baseI+hw_random8(92), 192, hw_random8(128,255)), CHSV(baseI+hw_random8(92), 255, hw_random8(128,255))); } @@ -5008,7 +5007,7 @@ void mode_ColorClouds() uint32_t pixel; if (SEGMENT.palette) { pixel = SEGMENT.color_from_palette(hue, false, true, 0, vol); } - else { hsv2rgb(CHSV32(hue, 255, vol), pixel); } + else { pixel = CRGBW(CHSV32(hue, 255, vol)); } // Suppress extremely dark pixels to avoid flickering of plain r/g/b. if (int(R(pixel)) + G(pixel) + B(pixel) <= 2) { @@ -5217,7 +5216,7 @@ void mode_2DColoredBursts() { // By: ldirko https://editor.soulma uint8_t rate = j * 255 / steps; byte dx = lerp8by8(x1, y1, rate); byte dy = lerp8by8(x2, y2, rate); - //SEGMENT.setPixelColorXY(dx, dy, grad ? color.nscale8_video(255-rate) : color); // use addPixelColorXY for different look + //SEGMENT.setPixelColorXY(dx, dy, grad ? color_fade(color, (255-rate), true) : color); // use addPixelColorXY for different look SEGMENT.addPixelColorXY(dx, dy, color); // use setPixelColorXY for different look if (grad) SEGMENT.fadePixelColorXY(dx, dy, rate); } @@ -5500,7 +5499,7 @@ void mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://na if (aliveParents) { // Set color based on random neighbor - unsigned parentIndex = parentIdx[random8(aliveParents)]; + unsigned parentIndex = parentIdx[hw_random8(aliveParents)]; birthColor = SEGMENT.getPixelColor(parentIndex); } newColor = birthColor; @@ -6011,8 +6010,7 @@ void mode_2DSunradiation(void) { // By: ldirko https://editor. uint8_t someVal = SEGMENT.speed/4; // Was 25. for (int j = 0; j < (rows + 2); j++) { for (int i = 0; i < (cols + 2); i++) { - //byte col = (inoise8_raw(i * someVal, j * someVal, t)) / 2; - byte col = ((int16_t)perlin8(i * someVal, j * someVal, t) - 0x7F) / 3; + byte col = ((int16_t)perlin8(i * someVal, j * someVal, t) - 127) >> 2; // about +/- 32 bump[index++] = col; } } @@ -6132,10 +6130,10 @@ void mode_2Dcrazybees(void) { uint8_t posX, posY, aimX, aimY, hue; int8_t deltaX, deltaY, signX, signY, error; void aimed(uint16_t w, uint16_t h) { - //random16_set_seed(millis()); - aimX = random8(0, w); - aimY = random8(0, h); - hue = random8(); + //prng.setSeed(millis()); + aimX = prng.random8(0, w); + aimY = prng.random8(0, h); + hue = prng.random8(); deltaX = abs(aimX - posX); deltaY = abs(aimY - posY); signX = posX < aimX ? 1 : -1; @@ -6148,10 +6146,10 @@ void mode_2Dcrazybees(void) { bee_t *bee = reinterpret_cast(SEGENV.data); if (SEGENV.call == 0) { - random16_set_seed(strip.now); + prng.setSeed(strip.now); for (size_t i = 0; i < n; i++) { - bee[i].posX = random8(0, cols); - bee[i].posY = random8(0, rows); + bee[i].posX = prng.random8(0, cols); + bee[i].posY = prng.random8(0, rows); bee[i].aimed(cols, rows); } } @@ -6319,7 +6317,7 @@ void mode_2Dfloatingblobs(void) { // Bounce balls around for (size_t i = 0; i < Amount; i++) { - if (SEGENV.step < strip.now) blob->color[i] = add8(blob->color[i], 4); // slowly change color + if (SEGENV.step < strip.now) blob->color[i] += 4; // slowly change color // change radius if needed if (blob->grow[i]) { // enlarge radius until it is >= 4 @@ -7766,7 +7764,7 @@ void mode_2Ddistortionwaves() { SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, brightness, 255, LINEARBLEND_NOWRAP)); } else { // color mapping: calculate hue from pixel color, map it to palette index - CHSV hsvclr = rgb2hsv_approximate(CRGB(valueR>>2, valueG>>2, valueB>>2)); // scale colors down to not saturate for better hue extraction + CHSV hsvclr = rgb2hsv(CRGB(valueR>>2, valueG>>2, valueB>>2)); // scale colors down to not saturate for better hue extraction SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hsvclr.h, brightness)); } } @@ -7825,7 +7823,7 @@ static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) { else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[indxA]*3); if ((zF >= 0) && (zF < tCR)) PixelB = pixels[indxB]; else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[indxB]*3); - ledsbuff[j] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction))); + ledsbuff[j] = (PixelA.nscale8(ease8InOutCubic(255 - fraction))) + (PixelB.nscale8(ease8InOutCubic(fraction))); } for (int j = 0; j < tCR; j++) { CRGB c = ledsbuff[j]; @@ -8173,7 +8171,7 @@ void mode_particlefireworks(void) { emitparticles = hw_random16(SEGMENT.intensity >> 2) + (SEGMENT.intensity >> 2) + 5; // defines the size of the explosion #endif - if (random16() & 1) { // 50% chance for circular explosion + if (hw_random() & 1) { // 50% chance for circular explosion circularexplosion = true; speed = 2 + hw_random16(3) + ((SEGMENT.intensity >> 6)); currentspeed = speed; @@ -10460,6 +10458,7 @@ void mode_particle1DsonicStream(void) { uint32_t baseBin = SEGMENT.custom3 >> 1; // 0 - 15 map(SEGMENT.custom3, 0, 31, 0, 14); loudness = fftResult[baseBin];// + fftResult[baseBin + 1]; + int mids = sqrt32_bw((int)fftResult[5] + (int)fftResult[6] + (int)fftResult[7] + (int)fftResult[8] + (int)fftResult[9] + (int)fftResult[10]); // average the mids, bin 5 is ~500Hz, bin 10 is ~2kHz (see audio_reactive.h) if (baseBin > 12) loudness = loudness << 2; // double loudness for high frequencies (better detecion) diff --git a/wled00/FX.h b/wled00/FX.h index ce7a222237..ae5ae3b2be 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -18,7 +18,7 @@ #include #include "wled.h" - +#include "colors.h" #ifdef WLED_DEBUG // enable additional debug output #if defined(WLED_DEBUG_HOST) @@ -38,10 +38,6 @@ #define DEBUGFX_PRINTF_P(x...) #endif -#define FASTLED_INTERNAL //remove annoying pragma messages -#define USE_GET_MILLISECOND_TIMER -#include "FastLED.h" - #define DEFAULT_BRIGHTNESS (uint8_t)127 #define DEFAULT_MODE (uint8_t)0 #define DEFAULT_SPEED (uint8_t)128 @@ -58,11 +54,6 @@ #define MAX(a,b) ((a)>(b)?(a):(b)) #endif -//color mangling macros -#ifndef RGBW32 -#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) -#endif - extern bool realtimeRespectLedMaps; // used in getMappedPixelIndex() extern byte realtimeMode; // used in getMappedPixelIndex() @@ -521,7 +512,7 @@ class Segment { , _start(millis()) , _colors{0,0,0} #ifndef WLED_SAVE_RAM - , _palT(CRGBPalette16(CRGB::Black)) + , _palT(CRGBPalette16()) #endif , _dur(dur) , _progress(0) @@ -761,8 +752,8 @@ class Segment { { addPixelColorXY(x, y, RGBW32(r,g,b,w), preserveCR); } inline void addPixelColorXY(int x, int y, CRGB c, bool preserveCR = true) const { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), preserveCR); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) const { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } - inline void blurCols(fract8 blur_amount, bool smear = false) const { blur2D(0, blur_amount, smear); } // blur all columns (50% faster than full 2D blur) - inline void blurRows(fract8 blur_amount, bool smear = false) const { blur2D(blur_amount, 0, smear); } // blur all rows (50% faster than full 2D blur) + inline void blurCols(uint8_t blur_amount, bool smear = false) const { blur2D(0, blur_amount, smear); } // blur all columns (50% faster than full 2D blur) + inline void blurRows(uint8_t blur_amount, bool smear = false) const { blur2D(blur_amount, 0, smear); } // blur all rows (50% faster than full 2D blur) //void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) const; void moveX(int delta, bool wrap = false) const; @@ -790,18 +781,18 @@ class Segment { inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) const { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); } #endif - inline bool isPixelXYClipped(int x, int y) const { return isPixelClipped(x); } - inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(x); } - inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) const { blendPixelColor(x, c, blend); } - inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) const { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) const { addPixelColor(x, color, saturate); } - inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) const { addPixelColor(x, RGBW32(r,g,b,w), saturate); } - inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) const { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), saturate); } - inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) const { fadePixelColor(x, fade); } - //inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {} + inline bool isPixelXYClipped(int x, int y) { return isPixelClipped(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, 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 saturate = false) { addPixelColor(x, color, saturate); } + 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 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 box_blur(unsigned i, bool vertical, uint8_t blur_amount) {} inline void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) {} - inline void blurCols(fract8 blur_amount, bool smear = false) { blur(blur_amount, smear); } // blur all columns (50% faster than full 2D blur) - inline void blurRows(fract8 blur_amount, bool smear = false) {} + inline void blurRow(int row, uint8_t blur_amount, bool smear = false) {} + inline void blurCol(int col, uint8_t blur_amount, bool smear = false) {} inline void moveX(int delta, bool wrap = false) {} inline void moveY(int delta, bool wrap = false) {} inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {} diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index e8f40fdf19..231e834679 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -11,6 +11,7 @@ */ #include "wled.h" #include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h? +#include "colors.h" /* Custom per-LED mapping has moved! @@ -43,7 +44,7 @@ unsigned Segment::_vLength = 0; unsigned Segment::_vWidth = 0; unsigned Segment::_vHeight = 0; uint32_t Segment::_currentColors[NUM_COLORS] = {0,0,0}; -CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black); +CRGBPalette16 Segment::_currentPalette = CRGBPalette16(); CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); uint16_t Segment::_lastPaletteChange = 0; // in seconds; perhaps it should be per segment @@ -235,7 +236,7 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { if (pal == 0) pal = _default_palette; // _default_palette is set in setMode() switch (pal) { case 0: //default palette. Exceptions for specific effects above - targetPalette = PartyColors_p; + targetPalette = PartyColors_gc22; break; case 1: //randomly generated palette targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette() @@ -1104,9 +1105,9 @@ void Segment::blur(uint8_t blur_amount, bool smear) const { uint32_t Segment::color_wheel(uint8_t pos) const { if (palette) return color_from_palette(pos, false, false, 0); // only wrap if "always wrap" is set uint8_t w = W(getCurrentColor(0)); - uint32_t rgb; - hsv2rgb(CHSV32(static_cast(pos << 8), 255, 255), rgb); - return rgb | (w << 24); // add white channel + CRGBW rgb; + rgb = CHSV32(static_cast(pos << 8), 255, 255); + return rgb.color32 | (w << 24); // add white channel } /* diff --git a/wled00/FXparticleSystem.cpp b/wled00/FXparticleSystem.cpp index e2782bdd45..78fa62a778 100644 --- a/wled00/FXparticleSystem.cpp +++ b/wled00/FXparticleSystem.cpp @@ -595,19 +595,17 @@ void ParticleSystem2D::render() { if (fireIntesity) { // fire mode brightness = (uint32_t)particles[i].ttl * (3 + (fireIntesity >> 5)) + 5; brightness = min(brightness, (uint32_t)255); - baseRGB = ColorFromPaletteWLED(SEGPALETTE, brightness, 255, LINEARBLEND_NOWRAP); // map hue to brightness for fire effect + baseRGB = ColorFromPalette(SEGPALETTE, brightness, 255, LINEARBLEND_NOWRAP); } else { brightness = min((particles[i].ttl << 1), (int)255); - baseRGB = ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255, blend); + baseRGB = ColorFromPalette(SEGPALETTE, particles[i].hue, 255, blend); if (particles[i].sat < 255) { - CHSV32 baseHSV; - rgb2hsv(baseRGB.color32, baseHSV); // convert to HSV + CHSV32 baseHSV = baseRGB; baseHSV.s = min(baseHSV.s, particles[i].sat); // set the saturation but don't increase it - hsv2rgb(baseHSV, baseRGB.color32); // convert back to RGB + hsv2rgb_spectrum(baseHSV, baseRGB); // convert back to RGB } } - if (gammaCorrectCol) brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution renderParticle(i, brightness, baseRGB, particlesettings.wrapX, particlesettings.wrapY); } @@ -677,10 +675,8 @@ void WLED_O2_ATTR ParticleSystem2D::renderParticle(const uint32_t particleindex, // - scale brigthness with gamma correction (done in render()) // - apply inverse gamma correction to brightness values // - gamma is applied again in show() -> the resulting brightness distribution is linear but gamma corrected in total - if (gammaCorrectCol) { - for (uint32_t i = 0; i < 4; i++) { - pxlbrightness[i] = gamma8inv(pxlbrightness[i]); // use look-up-table for invers gamma - } + for (uint32_t i = 0; i < 4; i++) { + pxlbrightness[i] = gamma8inv(pxlbrightness[i]); // use look-up-table for invers gamma } // standard rendering (2x2 pixels) @@ -802,9 +798,7 @@ void WLED_O2_ATTR ParticleSystem2D::renderLargeParticle(const uint32_t size, con if (pixel_brightness == 0) continue; // skip black pixels // apply inverse gamma correction if needed, if this is skipped, particles flicker due to changing total brightness - if (gammaCorrectCol) { - pixel_brightness = gamma8inv(pixel_brightness); // invert brigthess so brightness distribution is linear after gamma correction - } + pixel_brightness = gamma8inv(pixel_brightness); // invert brigthess so brightness distribution is linear after gamma correction // Render pixel uint32_t idx = render_x + (maxYpixel - render_y) * matrixX; // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer) @@ -1457,14 +1451,12 @@ void ParticleSystem1D::render() { // generate RGB values for particle brightness = min(particles[i].ttl << 1, (int)255); - baseRGB = ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255, blend); - + baseRGB = ColorFromPalette(SEGPALETTE, particles[i].hue, 255, blend); if (advPartProps != nullptr) { //saturation is advanced property in 1D system if (advPartProps[i].sat < 255) { - CHSV32 baseHSV; - rgb2hsv(baseRGB.color32, baseHSV); // convert to HSV - baseHSV.s = min(baseHSV.s, advPartProps[i].sat); // set the saturation but don't increase it - hsv2rgb(baseHSV, baseRGB.color32); // convert back to RGB + CHSV32 baseHSV = baseRGB; + baseHSV.s = advPartProps[i].sat; // set the saturation + hsv2rgb_spectrum(baseHSV, baseRGB); // convert back to RGB } } if (gammaCorrectCol) brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution @@ -1534,10 +1526,8 @@ void WLED_O2_ATTR ParticleSystem1D::renderParticle(const uint32_t particleindex, // - scale brigthness with gamma correction (done in render()) // - apply inverse gamma correction to brightness values // - gamma is applied again in show() -> the resulting brightness distribution is linear but gamma corrected in total -> fixes brightness fluctuations - if (gammaCorrectCol) { - pxlbrightness[0] = gamma8inv(pxlbrightness[0]); // use look-up-table for invers gamma - pxlbrightness[1] = gamma8inv(pxlbrightness[1]); - } + pxlbrightness[0] = gamma8inv(pxlbrightness[0]); // use look-up-table for invers gamma + pxlbrightness[1] = gamma8inv(pxlbrightness[1]); // check if any pixels are out of frame if (pixco[0] < 0) { // left pixels out of frame diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 38ddb51bbb..5846998351 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -1,12 +1,21 @@ #include "wled.h" +#include "fcn_declare.h" +#include "colors.h" /* * Color conversion & utility methods */ /* - * 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 + * FastLED Reference + * ----------------- + * functions in this file derived from FastLED @ 3.6.0 (https://github.com/FastLED/FastLED) are marked with a comment containing "derived from FastLED" + * those functions are therefore licensed under the MIT license See /src/dependencies/fastled_slim/LICENSE.txt for details. + */ + +/* + * color blend function + * the calculation for each color is: result = (C1*(256-blend)+C2+C2*blend) / 256 */ uint32_t WLED_O2_ATTR IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { // min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance @@ -25,7 +34,7 @@ uint32_t WLED_O2_ATTR IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, ui * original idea: https://github.com/wled-dev/WLED/pull/2465 by https://github.com/Proto-molecule * speed optimisations by @dedehai */ -uint32_t WLED_O2_ATTR color_add(uint32_t c1, uint32_t c2, bool preserveCR) //1212558 | 1212598 | 1212576 | 1212530 +uint32_t WLED_O2_ATTR color_add(uint32_t c1, uint32_t c2, bool preserveCR) { if (c1 == BLACK) return c2; if (c2 == BLACK) return c1; @@ -40,10 +49,9 @@ uint32_t WLED_O2_ATTR color_add(uint32_t c1, uint32_t c2, bool preserveCR) //121 uint32_t b = rb & 0xFFFF; uint32_t w = wg >> 16; uint32_t g = wg & 0xFFFF; - uint32_t max = std::max(r,g); - max = std::max(max,b); - max = std::max(max,w); - const uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead + uint32_t maxval = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); // find max value. note: faster than using max() function or on par + maxval = (w > maxval) ? w : maxval; // check white channel as well to avoid division by zero in pure white input + const uint32_t scale = (uint32_t(255)<<8) / maxval; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead rb = ((rb * scale) >> 8) & TWO_CHANNEL_MASK; wg = (wg * scale) & ~TWO_CHANNEL_MASK; } else wg <<= 8; //shift white and green back to correct position @@ -89,22 +97,21 @@ uint32_t IRAM_ATTR color_fade(uint32_t c1, uint8_t amount, bool video) { /* * color adjustment in HSV color space (converts RGB to HSV and back), color conversions are not 100% accurate! * shifts hue, increase brightness, decreases saturation (if not black) - * note: inputs are 32bit to speed up the function, useful input value ranges are 0-255 + * note: inputs are 32bit to speed up the function, useful input value ranges are -255 to +255 + * note2: if only one hue change is needed, use CRGBW.adjust_hue() instead (much faster) */ -uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) { - if (rgb == 0 || hueShift + lighten + brighten == 0) return rgb; // black or no change +WLED_O3_ATTR void adjust_color(CRGBW& rgb, int32_t hueShift, int32_t satChange, int32_t valueChange) { + if(rgb.color32 == 0 && valueChange <= 0) return; // black and no value change -> return black CHSV32 hsv; rgb2hsv(rgb, hsv); //convert to HSV hsv.h += (hueShift << 8); // shift hue (hue is 16 bits) - hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate - hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness - uint32_t rgb_adjusted; - hsv2rgb(hsv, rgb_adjusted); // convert back to RGB TODO: make this into 16 bit conversion - return rgb_adjusted; + hsv.s = (int)hsv.s + satChange < 0 ? 0 : ((int)hsv.s + satChange > 255 ? 255 : (int)hsv.s + satChange); + hsv.v = (int)hsv.v + valueChange < 0 ? 0 : ((int)hsv.v + valueChange > 255 ? 255 : (int)hsv.v + valueChange); + hsv2rgb_spectrum(hsv, rgb); // convert back to RGB } -// 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) { +// derived from FastLED: replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes) +uint32_t ColorFromPalette(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) { if (blendType == LINEARBLEND_NOWRAP) { index = (index * 0xF0) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping } @@ -298,64 +305,103 @@ void loadCustomPalettes() { } } -void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB (32bit with white = 0) -{ - unsigned int remainder, region, p, q, t; - unsigned int h = hsv.h; - unsigned int s = hsv.s; - unsigned int v = hsv.v; - if (s == 0) { - rgb = v << 16 | v << 8 | v; - return; +// convert HSV (16bit hue) to RGB (32bit with white = 0), optimized for speed +WLED_O2_ATTR void hsv2rgb_spectrum(const CHSV32& hsv, CRGBW& rgb) { + unsigned p, q, t; + unsigned region = ((unsigned)hsv.h * 6) >> 16; // h / (65536 / 6) + unsigned remainder = (hsv.h - (region * 10923)) * 6; // 10923 = (65536 / 6) + + // check for zero saturation + if (hsv.s == 0) { + rgb.r = rgb.g = rgb.b = hsv.v; + return; } - 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; + + p = (hsv.v * (255 - hsv.s)) >> 8; + q = (hsv.v * (255 - ((hsv.s * remainder) >> 16))) >> 8; + t = (hsv.v * (255 - ((hsv.s * (65535 - remainder)) >> 16))) >> 8; switch (region) { case 0: - rgb = v << 16 | t << 8 | p; break; + rgb.r = hsv.v; + rgb.g = t; + rgb.b = p; + break; case 1: - rgb = q << 16 | v << 8 | p; break; + rgb.r = q; + rgb.g = hsv.v; + rgb.b = p; + break; case 2: - rgb = p << 16 | v << 8 | t; break; + rgb.r = p; + rgb.g = hsv.v; + rgb.b = t; + break; case 3: - rgb = p << 16 | q << 8 | v; break; + rgb.r = p; + rgb.g = q; + rgb.b = hsv.v; + break; case 4: - rgb = t << 16 | p << 8 | v; break; + rgb.r = t; + rgb.g = p; + rgb.b = hsv.v; + break; default: - rgb = v << 16 | p << 8 | q; break; + rgb.r = hsv.v; + rgb.g = p; + rgb.b = 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; +// CHSV to CRGB wrapper conversion: slower so this should not be used in time critical code, use rainbow version instead +void hsv2rgb_spectrum(const CHSV& hsv, CRGB& rgb) { + CHSV32 hsv32(hsv); + CRGBW rgb32; + hsv2rgb_spectrum(hsv32, rgb32); + rgb = CRGB(rgb32); +} + +// convert RGB to HSV (16bit hue), not 100% color accurate. note: using "O3" makes it ~5% faster at minimal flash cost (~20 bytes) +WLED_O3_ATTR void rgb2hsv(const CRGBW& rgb, CHSV32& hsv) { + int32_t r = rgb.r; // note: using 32bit variables tested faster than 8bit + int32_t g = rgb.g; + int32_t b = rgb.b; + uint32_t minval, maxval; + int32_t delta; + // find min/max value. note: faster than using min/max functions (lets compiler optimize more when using "O3"), other variants (nested ifs, xor) tested slower + maxval = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); + if (maxval == 0) { + hsv.hsv32 = 0; + return; // black, avoids division by zero + } + minval = (r < g) ? ((r < b) ? r : b) : ((g < b) ? g : b); + hsv.v = maxval; + delta = maxval - minval; + if (delta != 0) { 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; + // note: early return if s==0 is omitted here to increase speed as gray values are rarely used + if (maxval == r) hsv.h = (uint16_t)((10923 * (g - b)) / delta); + else if (maxval == g) hsv.h = (uint16_t)(21845 + (10923 * (b - r)) / delta); + else hsv.h = (uint16_t)(43690 + (10923 * (r - g)) / delta); + } else { + hsv.s = 0; + hsv.h = 0; // gray, hue is undefined but set to 0 for consistency + } +} + +CHSV rgb2hsv(const CRGB c) { // CRGB to CHSV + CHSV32 hsv; + rgb2hsv(CRGBW(c), hsv); + return CHSV(hsv); } 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); + CRGBW crgb; + hsv2rgb_spectrum(CHSV32(hue, sat, 255), crgb); + rgb[0] = crgb.r; + rgb[1] = crgb.g; + rgb[2] = crgb.b; } //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) diff --git a/wled00/colors.h b/wled00/colors.h index eb5767de7e..a989073381 100644 --- a/wled00/colors.h +++ b/wled00/colors.h @@ -1,96 +1,33 @@ #pragma once #ifndef WLED_COLORS_H #define WLED_COLORS_H - +#include "src/dependencies/fastled_slim/fastled_slim.h" /* * Color structs and color utility functions */ -#include -#include "FastLED.h" - -#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 +/* + Note on color types and conversions: + - WLED uses 32bit colors (RGBW), if possible, use CRGBW instead of CRGB for better performance (no conversion in setPixelColor) + - use CRGB if RAM usage is of concern (i.e. for larger color arrays) + - direct conversion (assignment or construction) from CHSV/CHSV32 to CRGB/CRGBW use the "rainbow" method (nicer colors, see fastled documentation) + - converting CRGB(W) to HSV32 color is quite accurate but still not 100% (but much more accurate than fastled's "hsv2rgb_approximate" function) + - when converting CRGB(W) to HSV32 and back, "hsv2rgb_spectrum" preserves the colors better than the _rainbow version + - to manipulate an RGB color in HSV space, use the adjust_color function or the CRGBW.adjust_hue method + + Some functions in this file are derived from FastLED (https://github.com/FastLED/FastLED) licensed under the MIT license. + See /src/dependencies/fastled_slim/LICENSE.txt for details. +*/ + +// 32bit color mangling macros +#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) +#define R(c) (byte((c) >> 16)) +#define G(c) (byte((c) >> 8)) +#define B(c) (byte(c)) +#define W(c) (byte((c) >> 24)) + +struct CRGBW; // forward declations +struct CHSV32; - /// 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 -}; extern bool gammaCorrectCol; // similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod) class NeoGammaWLEDMethod { @@ -118,18 +55,20 @@ class NeoGammaWLEDMethod { inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); }; [[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false); [[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video = false); -[[gnu::hot, gnu::pure]] uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten); -[[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); +void adjust_color(CRGBW& rgb, int32_t hueShift, int32_t satChange,int32_t valueChange); + +[[gnu::hot, gnu::pure]] uint32_t ColorFromPalette(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); void loadCustomPalettes(); extern std::vector customPalettes; inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + customPalettes.size(); } -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 hsv2rgb(const CHSV32& hsv, uint32_t& rgb); + +void hsv2rgb_spectrum(const CHSV32& hsv, CRGBW& rgb); +void hsv2rgb_spectrum(const CHSV& hsv, CRGB& rgb); +void rgb2hsv(const CRGBW& rgb, CHSV32& hsv); +CHSV rgb2hsv(const CRGB c); 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 colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO @@ -149,7 +88,118 @@ static inline uint32_t fast_color_scale(const uint32_t c, const uint8_t scale) { } // palettes +extern const TProgmemRGBPalette16 PartyColors_gc22 PROGMEM; extern const TProgmemRGBPalette16* const fastledPalettes[]; extern const uint8_t* const gGradientPalettes[]; -#endif + +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 hsv32; // 32bit access + }; + inline CHSV32() __attribute__((always_inline)) = default; // default constructor + + // allow construction from hue (ih), saturation (is), and value (iv) + 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 + + // construction from a 32bit rgb color (white channel is ignored) + inline CHSV32(const CRGBW& rgb) __attribute__((always_inline)); + + inline CHSV32& operator= (const CRGBW& rgb) __attribute__((always_inline)); // assignment from 32bit rgb color (white channel is ignored) +}; + +// 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 (matches 32 bit colors) + }; + + // 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) {} + + // Constructor from CHSV32 + inline CRGBW(CHSV32 hsv) __attribute__((always_inline)) { hsv2rgb_rainbow(hsv.h, hsv.s, hsv.v, raw, true); } + + // Constructor from CHSV + inline CRGBW(CHSV hsv) __attribute__((always_inline)) { hsv2rgb_rainbow(hsv.h<<8, hsv.s, hsv.v, raw, true); } + + // 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 CHSV32 + inline CRGBW& operator=(CHSV32 hsv) __attribute__((always_inline)) { hsv2rgb_rainbow(hsv.h, hsv.s, hsv.v, raw, true); return *this; } + + // Assignment from CHSV + inline CRGBW& operator=(CHSV hsv) __attribute__((always_inline)) { hsv2rgb_rainbow(hsv.h<<8, hsv.s, hsv.v, raw, true); 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; + } + + // adjust hue: input range is 256 for full color cycle, input can be negative + inline void adjust_hue(int hueshift) __attribute__((always_inline)) { + CHSV32 hsv = *this; + hsv.h += hueshift << 8; + hsv2rgb_spectrum(hsv, *this); + } + + // get the average of the R, G, B and W values + uint8_t getAverageLight() const { + return (r + g + b + w) >> 2; + } +}; + +inline CHSV32::CHSV32(const CRGBW& rgb) { + rgb2hsv(rgb, *this); +} + +inline CHSV32& CHSV32::operator= (const CRGBW& rgb) { // assignment from 32bit rgb color (white channel is ignored) + rgb2hsv(rgb, *this); + return *this; +} + +// explicit hsv2rgb conversions for compatibility +inline CRGBW hsv2rgb(const CHSV32& hsv) { return CRGBW(hsv); } +inline void hsv2rgb(const CHSV32& hsv, CRGBW& rgb) { rgb = CRGBW(hsv); } +inline void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) { rgb = CRGBW(hsv).color32; } + +#endif \ No newline at end of file diff --git a/wled00/const.h b/wled00/const.h index 95e69d855b..5a959c91b4 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -736,5 +736,6 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #endif #define WLED_O2_ATTR __attribute__((optimize("O2"))) +#define WLED_O3_ATTR __attribute__((optimize("O3"))) #endif diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 67958314b4..4abbc5401b 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -3,6 +3,8 @@ #define WLED_FCN_DECLARE_H #include +#include "colors.h" + /* * All globally accessible functions are declared here */ @@ -420,9 +422,13 @@ void checkSettingsPIN(const char *pin); uint16_t crc16(const unsigned char* data_p, size_t length); String computeSHA1(const String& input); String getDeviceId(); -uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0); -uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0); -uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0); +uint16_t beat88(uint16_t beats_per_minute_88, uint32_t timebase = 0); +uint16_t beat16(uint16_t beats_per_minute, uint32_t timebase = 0); +uint8_t beat8(uint16_t beats_per_minute, uint32_t timebase = 0); +uint16_t beatsin88_t(uint16_t beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0); +uint16_t beatsin16_t(uint16_t beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0); +uint8_t beatsin8_t(uint16_t beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0); + um_data_t* simulateSound(uint8_t simulationId); void enumerateLedmaps(); [[gnu::hot]] uint8_t get_random_wheel_index(uint8_t pos); @@ -515,6 +521,9 @@ class JSONBufferGuard { }; //wled_math.cpp +#define sin_t sin_approx +#define cos_t cos_approx +#define tan_t tan_approx //float cos_t(float phi); // use float math //float sin_t(float phi); //float tan_t(float x); @@ -532,9 +541,6 @@ template T atan_t(T x); float floor_t(float x); float fmod_t(float num, float denom); uint32_t sqrt32_bw(uint32_t x); -#define sin_t sin_approx -#define cos_t cos_approx -#define tan_t tan_approx /* #include // standard math functions. use a lot of flash @@ -547,6 +553,7 @@ uint32_t sqrt32_bw(uint32_t x); #define fmod_t fmodf #define floor_t floorf */ + //wled_serial.cpp void handleSerial(); void updateBaudRate(uint32_t rate); diff --git a/wled00/ir.cpp b/wled00/ir.cpp index 6c9ea3ad5b..106dc6a973 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -2,6 +2,7 @@ #ifndef WLED_DISABLE_INFRARED #include "ir_codes.h" +#include "colors.h" /* * Infrared sensor support for several generic RGB remotes and custom JSON remote @@ -128,22 +129,18 @@ static void changeEffectSpeed(int8_t amount) } } else { // if Effect == "solid Color", change the hue of the primary color Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); - CRGB fastled_col = CRGB(sseg.colors[0]); - CHSV prim_hsv = rgb2hsv(fastled_col); - 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 < 0) new_val += 255; // roll-over if smaller than 0 - prim_hsv.h = (byte)new_val; - hsv2rgb_rainbow(prim_hsv, fastled_col); + CRGBW newcolor = CRGBW(sseg.colors[0]); + newcolor.adjust_hue(amount); + newcolor.w = W(sseg.colors[0]); if (irApplyToAllSelected) { for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive() || !seg.isSelected()) continue; - seg.colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0])); + seg.colors[0] = newcolor.color32; } setValuesFromFirstSelectedSeg(); } else { - strip.getMainSegment().colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0])); + strip.getMainSegment().colors[0] = newcolor.color32; setValuesFromMainSeg(); } } @@ -172,20 +169,21 @@ static void changeEffectIntensity(int8_t amount) } } else { // if Effect == "solid Color", change the saturation of the primary color Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); - CRGB fastled_col = CRGB(sseg.colors[0]); - CHSV prim_hsv = rgb2hsv(fastled_col); - int16_t new_val = (int16_t) prim_hsv.s + amount; + + CHSV32 prim_hsv = CRGBW(sseg.colors[0]); + int32_t new_val = (int32_t)prim_hsv.s + amount; prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255 - hsv2rgb_rainbow(prim_hsv, fastled_col); + CRGBW newcolor = prim_hsv; + newcolor.w = W(sseg.colors[0]); if (irApplyToAllSelected) { for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive() || !seg.isSelected()) continue; - seg.colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0])); + seg.colors[0] = newcolor; } setValuesFromFirstSelectedSeg(); } else { - strip.getMainSegment().colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0])); + strip.getMainSegment().colors[0] = newcolor; setValuesFromMainSeg(); } } diff --git a/wled00/json.cpp b/wled00/json.cpp index c6412de2e7..63443c754f 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1,6 +1,5 @@ #include "wled.h" - #define JSON_PATH_STATE 1 #define JSON_PATH_INFO 2 #define JSON_PATH_STATE_INFO 3 @@ -963,7 +962,7 @@ void serializePalettes(JsonObject root, int page) JsonArray curPalette = palettes.createNestedArray(String(i >= palettesCount ? 255 - i + palettesCount : i)); switch (i) { case 0: //default palette - setPaletteColors(curPalette, PartyColors_p); + setPaletteColors(curPalette, PartyColors_gc22); break; case 1: //random for (int j = 0; j < 4; j++) curPalette.add("r"); diff --git a/wled00/palettes.cpp b/wled00/palettes.cpp index 4781ffc1cc..bcbdcf22d7 100644 --- a/wled00/palettes.cpp +++ b/wled00/palettes.cpp @@ -1,4 +1,5 @@ #include "wled.h" +#include "src/dependencies/fastled_slim/fastled_slim.h" /* * WLED Color palettes @@ -9,6 +10,103 @@ * Palettes from FastLED are intended to be used without gamma correction, an inverse gamma of 2.2 is applied to original colors */ +// FastLED Palettes +// ---------------- +// Palettes imported from FastLED @ 3.6.0 (https://github.com/FastLED/FastLED) are licensed under the MIT license +// See /src/dependencies/fastled_slim/LICENSE.txt for details + +// Cloudy color palette +const TProgmemRGBPalette16 CloudColors_p PROGMEM = { + CRGB::Blue, + CRGB::DarkBlue, + CRGB::DarkBlue, + CRGB::DarkBlue, + + CRGB::DarkBlue, + CRGB::DarkBlue, + CRGB::DarkBlue, + CRGB::DarkBlue, + + CRGB::Blue, + CRGB::DarkBlue, + CRGB::SkyBlue, + CRGB::SkyBlue, + + CRGB::LightBlue, + CRGB::White, + CRGB::LightBlue, + CRGB::SkyBlue +}; + +// Lava color palette +const TProgmemRGBPalette16 LavaColors_p PROGMEM = { + CRGB::Black, + CRGB::Maroon, + CRGB::Black, + CRGB::Maroon, + + CRGB::DarkRed, + CRGB::DarkRed, + CRGB::Maroon, + CRGB::DarkRed, + + CRGB::DarkRed, + CRGB::DarkRed, + CRGB::Red, + CRGB::Orange, + + CRGB::White, + CRGB::Orange, + CRGB::Red, + CRGB::DarkRed +}; + +// Ocean colors, blues and whites +const TProgmemRGBPalette16 OceanColors_p PROGMEM = { + CRGB::MidnightBlue, + CRGB::DarkBlue, + CRGB::MidnightBlue, + CRGB::Navy, + + CRGB::DarkBlue, + CRGB::MediumBlue, + CRGB::SeaGreen, + CRGB::Teal, + + CRGB::CadetBlue, + CRGB::Blue, + CRGB::DarkCyan, + CRGB::CornflowerBlue, + + CRGB::Aquamarine, + CRGB::SeaGreen, + CRGB::Aqua, + CRGB::LightSkyBlue +}; + +// Forest colors, greens +const TProgmemRGBPalette16 ForestColors_p PROGMEM = { + CRGB::DarkGreen, + CRGB::DarkGreen, + CRGB::DarkOliveGreen, + CRGB::DarkGreen, + + CRGB::Green, + CRGB::ForestGreen, + CRGB::OliveDrab, + CRGB::Green, + + CRGB::SeaGreen, + CRGB::MediumAquamarine, + CRGB::LimeGreen, + CRGB::YellowGreen, + + CRGB::LightGreen, + CRGB::LawnGreen, + CRGB::MediumAquamarine, + CRGB::ForestGreen +}; + // Gradient palette "ib_jul01_gp", originally from // http://seaviewsensing.com/pub/cpt-city/ing/xmas/ib_jul01.c3g const uint8_t ib_jul01_gp[] PROGMEM = { @@ -671,21 +769,21 @@ const byte Aurora2_gp[] PROGMEM = { // FastLed palettes, corrected with inverse gamma of 2.2 to match original looks // Party colors -const TProgmemRGBPalette16 PartyColors_gc22 FL_PROGMEM = { +const TProgmemRGBPalette16 PartyColors_gc22 PROGMEM = { 0x9B00D5, 0xBD00B8, 0xDA0092, 0xF3005C, 0xF45500, 0xDC8F00, 0xD5B400, 0xD5D500, 0xD59B00, 0xEF6600, 0xF90044, 0xE10086, 0xC400B0, 0xA300CF, 0x7600E8, 0x0032FC}; // Rainbow colors -const TProgmemRGBPalette16 RainbowColors_gc22 FL_PROGMEM = { +const TProgmemRGBPalette16 RainbowColors_gc22 PROGMEM = { 0xFF0000, 0xEB7000, 0xD59B00, 0xD5BA00, 0xD5D500, 0x9CEB00, 0x00FF00, 0x00EB70, 0x00D59B, 0x009CD4, 0x0000FF, 0x7000EB, 0x9B00D5, 0xBA00BB, 0xD5009B, 0xEB0072}; // Rainbow colors with alternatating stripes of black -const TProgmemRGBPalette16 RainbowStripeColors_gc22 FL_PROGMEM = { +const TProgmemRGBPalette16 RainbowStripeColors_gc22 PROGMEM = { 0xFF0000, 0x000000, 0xD59B00, 0x000000, 0xD5D500, 0x000000, 0x00FF00, 0x000000, 0x00D59B, 0x000000, 0x0000FF, 0x000000, @@ -766,4 +864,4 @@ const uint8_t* const gGradientPalettes[] PROGMEM = { red_tide_gp, //69-56 Red Tide candy2_gp, //70-57 Candy2 trafficlight_gp //71-58 Traffic Light -}; +}; \ No newline at end of file diff --git a/wled00/prng.h b/wled00/prng.h new file mode 100644 index 0000000000..da0a84f124 --- /dev/null +++ b/wled00/prng.h @@ -0,0 +1,23 @@ +#include "wled.h" + +// Simple and fast Pseudo-Random-Number-Generator for 16bit and 8bit random numbers +// allows the same sequence of random numbers to be generated by setting the 16bit-seed +// values found by brute-force test algorithm: sequence has no repetitions and good randomness for its simplicity +class PRNG { +private: + uint16_t seed; +public: + PRNG(uint16_t initialSeed = 0x1234) : seed(initialSeed) {} + void setSeed(uint16_t s) { seed = s; } + uint16_t getSeed() const { return seed; } + uint16_t random16() { + seed = seed * 3001 + 31683; + seed ^= seed >> 7; + return seed; + } + uint16_t random16(uint16_t lim) { return ((uint32_t)random16() * lim) >> 16; } + uint16_t random16(uint16_t min, uint16_t lim) { uint16_t delta = lim - min; return random16(delta) + min; } + uint8_t random8() { return random16(); } + uint8_t random8(uint8_t lim) { return (uint8_t)(((uint16_t)random8() * lim) >> 8); } + uint8_t random8(uint8_t min, uint8_t lim) { uint8_t delta = lim - min; return random8(delta) + min; } +}; diff --git a/wled00/src/dependencies/fastled_slim/LICENSE.txt b/wled00/src/dependencies/fastled_slim/LICENSE.txt new file mode 100644 index 0000000000..3aa44d73b6 --- /dev/null +++ b/wled00/src/dependencies/fastled_slim/LICENSE.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 FastLED modified by @dedehai + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/wled00/src/dependencies/fastled_slim/fastled_slim.cpp b/wled00/src/dependencies/fastled_slim/fastled_slim.cpp new file mode 100644 index 0000000000..55708bbdf5 --- /dev/null +++ b/wled00/src/dependencies/fastled_slim/fastled_slim.cpp @@ -0,0 +1,259 @@ +#include "fastled_slim.h" + +// Code originally from FastLED version 3.6.0. Optimized for WLED use by @dedehai +// Licensed unter MIT license, see LICENSE.txt for details + +// convert HSV (16bit hue) to RGB (24bit), optimized for speed (integer types and function arguments were very carefully selected for best performance) +// this does the same as the FastLED hsv2rgb_rainbow function but with 16bit hue and optimizations for use with CRGB as well as CRGBW +// note: this function is used when converting CHSV->CRGB or CHSV32->CRGBW by assignment or constructor, there is no need to call it explicitly +__attribute__((optimize("O2"))) void hsv2rgb_rainbow(uint16_t h, uint8_t s, uint8_t v, uint8_t* rgbdata, bool isRGBW) { + uint8_t hue = h>>8; + uint8_t sat = s; + uint32_t val = v; + uint32_t offset = h & 0x1FFF; // 0..31, shifted + uint32_t third16 = (offset * 21846); // offset16 = offset * 1/3<<16 + uint8_t third = third16 >> 21; // max = 85 + uint8_t r, g, b; // note: making these 32bit is significantly slower + + if (!(hue & 0x80)) { + if (!(hue & 0x40)) { // section 0-1 + if (!(hue & 0x20)) { + r = 255 - third; + g = third; + b = 0; + } else { + r = 171; + g = 85 + third; + b = 0; + } + } else { // section 2-3 + if (!(hue & 0x20)) { + uint8_t twothirds = third16 >> 20; // max=170 + r = 171 - twothirds; + g = 170 + third; + b = 0; + } else { + r = 0; + g = 255 - third; + b = third; + } + } + } else { // section 4-7 + if (!(hue & 0x40)) { + if (!(hue & 0x20)) { + r = 0; + uint8_t twothirds = third16 >> 20; // max=170 + g = 171 - twothirds; + b = 85 + twothirds; + } else { + r = third; + g = 0; + b = 255 - third; + } + } else { + if (!(hue & 0x20)) { + r = 85 + third; + g = 0; + b = 171 - third; + } else { + r = 170 + third; + g = 0; + b = 85 - third; + } + } + } + + // scale down colors if desaturated and add the brightness_floor to r, g, and b. + if (sat != 255) { + if (sat == 0) { + r = 255; + g = 255; + b = 255; + } else { + //we know sat is < 255 and > 1, lets use that: scale8video is always +1, so drop the conditional + uint32_t desat = 255 - sat; + desat = (desat * desat); // scale8_video(desat, desat) but more accurate, dropped the "+1" for speed: visual difference is negligible + uint8_t brightness_floor = desat >> 8; + uint32_t satscale = 0xFFFF - desat; + if (r) r = ((r * satscale) >> 16); + if (g) g = ((g * satscale) >> 16); + if (b) b = ((b * satscale) >> 16); + + r += brightness_floor; + g += brightness_floor; + b += brightness_floor; + } + } + + // scale everything down if value < 255. + if (val != 255) { + if (val == 0) { + r = 0; + g = 0; + b = 0; + } else { + val = val*val + 512; // = scale8_video(val,val)+2; + if (r) r = ((r * val) >> 16) + 1; + if (g) g = ((g * val) >> 16) + 1; + if (b) b = ((b * val) >> 16) + 1; + } + } + if(isRGBW) { + rgbdata[0] = b; + rgbdata[1] = g; + rgbdata[2] = r; + //rgbdata[3] = 0; // white + } else { + rgbdata[0] = r; + rgbdata[1] = g; + rgbdata[2] = b; + } +} + + +// black body radiation to RGB +CRGB HeatColor(uint8_t temperature) { + CRGB heatcolor; + uint8_t t192 = (((int)temperature * 191) >> 8) + (temperature ? 1 : 0); // scale down, but keep 1 as minimum + // calculate a value that ramps up from zero to 255 in each 'third' of the scale. + uint8_t heatramp = t192 & 0x3F; // 0..63 + heatramp <<= 2; // scale up to 0..252 + heatcolor.r = 255; + heatcolor.b = 0; + if(t192 & 0x80) { // we're in the hottest third + heatcolor.g = 255; // full green + heatcolor.b = heatramp; // ramp up blue + } else if(t192 & 0x40) { // we're in the middle third + heatcolor.g = heatramp; // ramp up green + } else { // we're in the coolest third + heatcolor.r = heatramp; // ramp up red + heatcolor.g = 0; // no green + } + return heatcolor; +} + +// CRGB color fill functions (from fastled, used for color palettes) +void fill_solid_RGB(CRGB* colors, uint32_t num, const CRGB& c1) { + for(uint32_t i = 0; i < num; i++) { + colors[i] = c1; + } +} + +// fill CRGB array with a color gradient +void fill_gradient_RGB(CRGB* colors, uint32_t startpos, CRGB startcolor, uint32_t endpos, CRGB endcolor) { + if(endpos < startpos) { // if the points are in the wrong order, flip them + unsigned t = endpos; + CRGB tc = endcolor; + endcolor = startcolor; + endpos = startpos; + startpos = t; + startcolor = tc; + } + int rdistance = endcolor.r - startcolor.r; + int gdistance = endcolor.g - startcolor.g; + int bdistance = endcolor.b - startcolor.b; + + int divisor = endpos - startpos; + divisor = divisor == 0 ? 1 : divisor; // prevent division by zero + + int rdelta = (rdistance << 16) / divisor; + int gdelta = (gdistance << 16) / divisor; + int bdelta = (bdistance << 16) / divisor; + + int rshifted = startcolor.r << 16; + int gshifted = startcolor.g << 16; + int bshifted = startcolor.b << 16; + + for (unsigned i = startpos; i <= endpos; i++) { + colors[i] = CRGB(rshifted >> 16, gshifted >> 16, bshifted >> 16); + rshifted += rdelta; + gshifted += gdelta; + bshifted += bdelta; + } +} + +void fill_gradient_RGB(CRGB* colors, uint32_t num, const CRGB& c1, const CRGB& c2) { + uint32_t last = num - 1; + fill_gradient_RGB(colors, 0, c1, last, c2); +} + +void fill_gradient_RGB(CRGB* colors, uint32_t num, const CRGB& c1, const CRGB& c2, const CRGB& c3) { + uint32_t half = (num / 2); + uint32_t last = num - 1; + fill_gradient_RGB(colors, 0, c1, half, c2); + fill_gradient_RGB(colors, half, c2, last, c3); +} + +void fill_gradient_RGB(CRGB* colors, uint32_t num, const CRGB& c1, const CRGB& c2, const CRGB& c3, const CRGB& c4) { + uint32_t onethird = (num / 3); + uint32_t twothirds = ((num * 2) / 3); + uint32_t last = num - 1; + fill_gradient_RGB(colors, 0, c1, onethird, c2); + fill_gradient_RGB(colors, onethird, c2, twothirds, c3); + fill_gradient_RGB(colors, twothirds, c3, last, c4); +} + +// palette blending +void nblendPaletteTowardPalette(CRGBPalette16& current, CRGBPalette16& target, uint8_t maxChanges) { + uint8_t* p1; + uint8_t* p2; + uint32_t changes = 0; + p1 = (uint8_t*)current.entries; + p2 = (uint8_t*)target.entries; + const uint32_t totalChannels = sizeof(CRGBPalette16); + for (uint32_t i = 0; i < totalChannels; ++i) { + if (p1[i] == p2[i]) continue; // if the values are equal, no changes are needed + if (p1[i] < p2[i]) { ++p1[i]; ++changes; } // if the current value is less than the target, increase it by one + if (p1[i] > p2[i]) { // if the current value is greater than the target, increase it by one (or two if it's still greater). + --p1[i]; ++changes; + if (p1[i] > p2[i]) + --p1[i]; + } + if(changes >= maxChanges) + break; + } +} + +// cubic ease function (S-curve: 3x^2 - 2x^3 = x^2*(3-2x)) +// 8-bit +uint8_t ease8InOutCubic(uint8_t i) { + uint32_t ii = ((uint32_t)i * i); + uint32_t factor = (3 << 8) - (((uint32_t)i << 1)); // 3 - 2i + return (ii * factor) >> 16; +} +// 16-bit +uint16_t ease16InOutCubic(uint16_t i) { + uint32_t ii = ((uint32_t)i * i) >> 16; + uint32_t factor = (3 << 16) - (((uint32_t)i << 1)); // 3 - 2i + return (ii * factor) >> 16; +} + +// quadratic ease function (S-curve: x^2) +uint8_t ease8InOutQuad(uint8_t i) +{ + uint32_t j = i; + if (j & 0x80) j = 255 - j; // mirror if > 127 + uint32_t jj = (j * j) >> 7; + return (i & 0x80) ? (255 - jj) : jj; +} + +// triangular wave generator +uint8_t triwave8(uint8_t in) { + if (in & 0x80) in = 255 - in; + return in << 1; +} + +uint16_t triwave16(uint16_t in) { + if (in < 0x8000) return in *2; + return 0xFFFF - (in - 0x8000)*2; +} + +// quadratic waveform generator. Spends just a little more time at the limits than "sine" does. +uint8_t quadwave8(uint8_t in) { + return ease8InOutQuad(triwave8(in)); +} + +// cubic waveform generator. Spends visibly more time at the limits than "sine" does. +uint8_t cubicwave8(uint8_t in) { + return ease8InOutCubic(triwave8(in)); +} diff --git a/wled00/src/dependencies/fastled_slim/fastled_slim.h b/wled00/src/dependencies/fastled_slim/fastled_slim.h new file mode 100644 index 0000000000..a52da90d29 --- /dev/null +++ b/wled00/src/dependencies/fastled_slim/fastled_slim.h @@ -0,0 +1,782 @@ +#pragma once + +#include +#include // for mem operations +#include + +// Code originally from FastLED version 3.6.0. Optimized for WLED use by @dedehai +// Licensed unter MIT license, see LICENSE.txt for details + +// inline math functions +__attribute__ ((always_inline)) inline uint8_t scale8(uint8_t i, uint8_t scale ) { return ((int)i * (1 + (int)scale)) >> 8; } +__attribute__ ((always_inline)) inline uint8_t scale8_video(uint8_t i, uint8_t scale ) { return (((int)i * (int)scale) >> 8) + ((i&&scale)?1:0); } +__attribute__ ((always_inline)) inline uint16_t scale16(uint16_t i, uint16_t scale ) { return ((uint32_t)i * (1 + (uint32_t)scale)) >> 16; } +__attribute__ ((always_inline)) inline uint8_t qadd8(uint8_t i, uint8_t j) { unsigned t = i + j; return t > 255 ? 255 : t; } +__attribute__ ((always_inline)) inline uint8_t qsub8(uint8_t i, uint8_t j) { int t = i - j; return t < 0 ? 0 : t; } +__attribute__ ((always_inline)) inline uint8_t qmul8(uint8_t i, uint8_t j) { unsigned p = (unsigned)i * (unsigned)j; return p > 255 ? 255 : p; } +__attribute__ ((always_inline)) inline int8_t abs8(int8_t i) { return i < 0 ? -i : i; } +__attribute__ ((always_inline)) inline int8_t lerp8by8(uint8_t a, uint8_t b, uint8_t frac) { return a + ((((int32_t)b - (int32_t)a) * ((int32_t)frac+1)) >> 8); } + +//forward declarations +struct CRGB; +struct CHSV; +class CRGBPalette16; + +typedef uint32_t TProgmemRGBPalette16[16]; +typedef uint8_t TDynamicRGBGradientPalette_byte; // Byte of an RGB gradient entry, stored in dynamic (heap) memory +typedef const TDynamicRGBGradientPalette_byte *TDynamicRGBGradientPalette_bytes; // Pointer to bytes of an RGB gradient, stored in dynamic (heap) memory +typedef TDynamicRGBGradientPalette_bytes TDynamicRGBGradientPalettePtr; // Alias of ::TDynamicRGBGradientPalette_bytes +typedef const uint8_t TProgmemRGBGradientPalette_byte; +typedef const TProgmemRGBGradientPalette_byte *TProgmemRGBGradientPalette_bytes; +typedef TProgmemRGBGradientPalette_bytes TProgmemRGBGradientPalettePtr; + +// color interpolation options for palette +typedef enum { + NOBLEND=0, // No interpolation between palette entries + LINEARBLEND=1, // Linear interpolation between palette entries, with wrap-around from end to the beginning again + LINEARBLEND_NOWRAP=2 // Linear interpolation between palette entries, but no wrap-around +} TBlendType; + +typedef union { + struct { + uint8_t index; // index of the color entry in the gradient + uint8_t r; + uint8_t g; + uint8_t b; + }; + uint32_t dword; // values packed as 32-bit + uint8_t bytes[4]; // values as an array +} TRGBGradientPaletteEntryUnion; + +// function prototypes +void hsv2rgb_rainbow(uint16_t h, uint8_t s, uint8_t v, uint8_t* rgbdata, bool isRGBW); +CRGB HeatColor(uint8_t temperature); // black body radiation +void fill_solid_RGB(CRGB* colors, uint32_t num, const CRGB& c1) ; +void fill_gradient_RGB(CRGB* colors, uint32_t startpos, CRGB startcolor, uint32_t endpos, CRGB endcolor); +void fill_gradient_RGB(CRGB* colors, uint32_t num, const CRGB& c1, const CRGB& c2); +void fill_gradient_RGB(CRGB* colors, uint32_t num, const CRGB& c1, const CRGB& c2, const CRGB& c3); +void fill_gradient_RGB(CRGB* colors, uint32_t num, const CRGB& c1, const CRGB& c2, const CRGB& c3, const CRGB& c4); +void nblendPaletteTowardPalette(CRGBPalette16& current, CRGBPalette16& target, uint8_t maxChanges); + +uint8_t ease8InOutCubic(uint8_t i); +uint16_t ease16InOutCubic(uint16_t i); +uint8_t ease8InOutQuad(uint8_t i); +uint8_t triwave8(uint8_t in); +uint16_t triwave16(uint16_t in); +uint8_t quadwave8(uint8_t in); +uint8_t cubicwave8(uint8_t in); + +// Representation of an HSV pixel (hue, saturation, value (aka brightness)). +struct CHSV { + union { + struct { + union { + uint8_t hue; + uint8_t h; + }; + union { + uint8_t saturation; + uint8_t sat; + uint8_t s; + }; + union { + uint8_t value; + uint8_t val; + uint8_t v; + }; + }; + uint8_t raw[3]; // order is: hue [0], saturation [1], value [2] + }; + + inline uint8_t& operator[] (uint8_t x) __attribute__((always_inline)) { + return raw[x]; + } + + inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { + return raw[x]; + } + + // default constructor + // @warning default values are UNITIALIZED! + inline CHSV() __attribute__((always_inline)) = default; + + // allow construction from hue, saturation, and value + inline CHSV(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) + : h(ih), s(is), v(iv) { } + + // allow copy construction + inline CHSV(const CHSV& rhs) __attribute__((always_inline)) = default; + + // allow copy construction + inline CHSV& operator= (const CHSV& rhs) __attribute__((always_inline)) = default; + + // assign new HSV values + inline CHSV& setHSV(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) { + h = ih; + s = is; + v = iv; + return *this; + } +}; + +// representation of an RGB pixel (Red, Green, Blue) +struct CRGB { + union { + struct { + union { + uint8_t r; + uint8_t red; + }; + union { + uint8_t g; + uint8_t green; + }; + union { + uint8_t b; + uint8_t blue; + }; + }; + uint8_t raw[3]; // order is: 0 = red, 1 = green, 2 = blue + }; + + inline uint8_t& operator[] (uint8_t x) __attribute__((always_inline)) { return raw[x]; } + + // array access operator to index into the CRGB object + inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; } + + // default constructor (uninitialized) + inline CRGB() __attribute__((always_inline)) = default; + + // allow construction from red, green, and blue + inline CRGB(uint8_t ir, uint8_t ig, uint8_t ib) __attribute__((always_inline)) + : r(ir), g(ig), b(ib) { } + + // allow construction from 32-bit (really 24-bit) bit 0xRRGGBB color code + inline CRGB(uint32_t colorcode) __attribute__((always_inline)) + : r(uint8_t(colorcode>>16)), g(uint8_t(colorcode>>8)), b(uint8_t(colorcode)) { } + + // allow copy construction + inline CRGB(const CRGB& rhs) __attribute__((always_inline)) = default; + + // allow construction from a CHSV color + inline CRGB(const CHSV& rhs) __attribute__((always_inline)) { + hsv2rgb_rainbow(rhs.h<<8, rhs.s, rhs.v, raw, false); + } + + // allow assignment from hue, saturation, and value + inline CRGB& setHSV (uint8_t hue, uint8_t sat, uint8_t val) __attribute__((always_inline)) { + hsv2rgb_rainbow(hue<<8, sat, val, raw, false); return *this; + } + + // allow assignment from just a hue, sat and val are set to max + inline CRGB& setHue (uint8_t hue) __attribute__((always_inline)) { + hsv2rgb_rainbow(hue<<8, 255, 255, raw, false); return *this; + } + + // allow assignment from HSV color + inline CRGB& operator= (const CHSV& rhs) __attribute__((always_inline)) { + hsv2rgb_rainbow(rhs.h<<8, rhs.s, rhs.v, raw, false); return *this; + } + // allow assignment from one RGB struct to another + inline CRGB& operator= (const CRGB& rhs) __attribute__((always_inline)) = default; + + // allow assignment from 32-bit (really 24-bit) 0xRRGGBB color code + inline CRGB& operator= (const uint32_t colorcode) __attribute__((always_inline)) { + r = uint8_t(colorcode>>16); + g = uint8_t(colorcode>>8); + b = uint8_t(colorcode); + return *this; + } + + // allow assignment from red, green, and blue + inline CRGB& setRGB (uint8_t nr, uint8_t ng, uint8_t nb) __attribute__((always_inline)) { + r = nr; + g = ng; + b = nb; + return *this; + } + + // allow assignment from 32-bit (really 24-bit) 0xRRGGBB color code + inline CRGB& setColorCode (uint32_t colorcode) __attribute__((always_inline)) { + r = uint8_t(colorcode>>16); + g = uint8_t(colorcode>>8); + b = uint8_t(colorcode); + return *this; + } + + // add one CRGB to another, saturating at 0xFF for each channel + inline CRGB& operator+= (const CRGB& rhs) { + r = qadd8(r, rhs.r); + g = qadd8(g, rhs.g); + b = qadd8(b, rhs.b); + return *this; + } + + // add a constant to each channel, saturating at 0xFF + inline CRGB& addToRGB (uint8_t d) { + r = qadd8(r, d); + g = qadd8(g, d); + b = qadd8(b, d); + return *this; + } + + // subtract one CRGB from another, saturating at 0x00 for each channel + inline CRGB& operator-= (const CRGB& rhs) { + r = qsub8(r, rhs.r); + g = qsub8(g, rhs.g); + b = qsub8(b, rhs.b); + return *this; + } + + // subtract a constant from each channel, saturating at 0x00 + inline CRGB& subtractFromRGB(uint8_t d) { + r = qsub8(r, d); + g = qsub8(g, d); + b = qsub8(b, d); + return *this; + } + + // subtract a constant of '1' from each channel, saturating at 0x00 + inline CRGB& operator-- () __attribute__((always_inline)) { + subtractFromRGB(1); + return *this; + } + + // operator-- + inline CRGB operator-- (int) __attribute__((always_inline)) { + CRGB retval(*this); + --(*this); + return retval; + } + + // add a constant of '1' to each channel, saturating at 0xFF + inline CRGB& operator++ () __attribute__((always_inline)) { + addToRGB(1); + return *this; + } + + // operator++ + inline CRGB operator++ (int) __attribute__((always_inline)) { + CRGB retval(*this); + ++(*this); + return retval; + } + + // divide each of the channels by a constant + inline CRGB& operator/= (uint8_t d) { + r /= d; + g /= d; + b /= d; + return *this; + } + + // right shift each of the channels by a constant + inline CRGB& operator>>= (uint8_t d) { + r >>= d; + g >>= d; + b >>= d; + return *this; + } + + // multiply each of the channels by a constant, saturating each channel at 0xFF. + inline CRGB& operator*= (uint8_t d) { + r = qmul8(r, d); + g = qmul8(g, d); + b = qmul8(b, d); + return *this; + } + + // scale down a RGB to N/256ths of its current brightness (will not scale all the way to black) + inline CRGB& nscale8_video(uint8_t scaledown) { + uint8_t nonzeroscale = (scaledown != 0) ? 1 : 0; + r = (r == 0) ? 0 : (((int)r * (int)(scaledown)) >> 8) + nonzeroscale; + g = (g == 0) ? 0 : (((int)g * (int)(scaledown)) >> 8) + nonzeroscale; + b = (b == 0) ? 0 : (((int)b * (int)(scaledown)) >> 8) + nonzeroscale; + return *this; + } + + // scale down a RGB to N/256ths of its current brightness (can scale to black) + inline CRGB& nscale8(uint8_t scaledown) { + uint32_t scale_fixed = scaledown + 1; + r = (((uint32_t)r) * scale_fixed) >> 8; + g = (((uint32_t)g) * scale_fixed) >> 8; + b = (((uint32_t)b) * scale_fixed) >> 8; + return *this; + } + + inline CRGB& nscale8(const CRGB& scaledown) { + r = ::scale8(r, scaledown.r); + g = ::scale8(g, scaledown.g); + b = ::scale8(b, scaledown.b); + return *this; + } + + // return a CRGB object that is a scaled down version of this object + inline CRGB scale8(uint8_t scaledown) const { + CRGB out = *this; + uint32_t scale_fixed = scaledown + 1; + out.r = (((uint32_t)out.r) * scale_fixed) >> 8; + out.g = (((uint32_t)out.g) * scale_fixed) >> 8; + out.b = (((uint32_t)out.b) * scale_fixed) >> 8; + return out; + } + + // return a CRGB object that is a scaled down version of this object + inline CRGB scale8(const CRGB& scaledown) const { + CRGB out; + out.r = ::scale8(r, scaledown.r); + out.g = ::scale8(g, scaledown.g); + out.b = ::scale8(b, scaledown.b); + return out; + } + + // fadeToBlackBy is a synonym for nscale8(), as a fade instead of a scale + inline CRGB& fadeToBlackBy(uint8_t fadefactor) { + uint32_t scale_fixed = 256 - fadefactor; + r = (((uint32_t)r) * scale_fixed) >> 8; + g = (((uint32_t)g) * scale_fixed) >> 8; + b = (((uint32_t)b) * scale_fixed) >> 8; + return *this; + } + + // "or" operator brings each channel up to the higher of the two values + inline CRGB& operator|=(const CRGB& rhs) { + if (rhs.r > r) r = rhs.r; + if (rhs.g > g) g = rhs.g; + if (rhs.b > b) b = rhs.b; + return *this; + } + + inline CRGB& operator|=(uint8_t d) { + if (d > r) r = d; + if (d > g) g = d; + if (d > b) b = d; + return *this; + } + + // "and" operator brings each channel down to the lower of the two values + inline CRGB& operator&=(const CRGB& rhs) { + if (rhs.r < r) r = rhs.r; + if (rhs.g < g) g = rhs.g; + if (rhs.b < b) b = rhs.b; + return *this; + } + + inline CRGB& operator&=(uint8_t d) { + if (d < r) r = d; + if (d < g) g = d; + if (d < b) b = d; + return *this; + } + + // this allows testing a CRGB for zero-ness + inline explicit operator bool() const __attribute__((always_inline)) { + return r || g || b; + } + + // converts a CRGB to a 32-bit color with white = 0 + inline explicit operator uint32_t() const { + return (uint32_t{r} << 16) | + (uint32_t{g} << 8) | + uint32_t{b}; + } + + // invert each channel + inline CRGB operator-() const { + CRGB retval; + retval.r = 255 - r; + retval.g = 255 - g; + retval.b = 255 - b; + return retval; + } + + // get the average of the R, G, and B values + inline uint8_t getAverageLight() const { + return ((r + g + b) * 21846) >> 16; // x*21846>>16 is equal to "divide by 3" + } + + typedef enum { + AliceBlue=0xF0F8FF, + Amethyst=0x9966CC, + AntiqueWhite=0xFAEBD7, + Aqua=0x00FFFF, + Aquamarine=0x7FFFD4, + Azure=0xF0FFFF, + Beige=0xF5F5DC, + Bisque=0xFFE4C4, + Black=0x000000, + BlanchedAlmond=0xFFEBCD, + Blue=0x0000FF, + BlueViolet=0x8A2BE2, + Brown=0xA52A2A, + BurlyWood=0xDEB887, + CadetBlue=0x5F9EA0, + Chartreuse=0x7FFF00, + Chocolate=0xD2691E, + Coral=0xFF7F50, + CornflowerBlue=0x6495ED, + Cornsilk=0xFFF8DC, + Crimson=0xDC143C, + Cyan=0x00FFFF, + DarkBlue=0x00008B, + DarkCyan=0x008B8B, + DarkGoldenrod=0xB8860B, + DarkGray=0xA9A9A9, + DarkGrey=0xA9A9A9, + DarkGreen=0x006400, + DarkKhaki=0xBDB76B, + DarkMagenta=0x8B008B, + DarkOliveGreen=0x556B2F, + DarkOrange=0xFF8C00, + DarkOrchid=0x9932CC, + DarkRed=0x8B0000, + DarkSalmon=0xE9967A, + DarkSeaGreen=0x8FBC8F, + DarkSlateBlue=0x483D8B, + DarkSlateGray=0x2F4F4F, + DarkSlateGrey=0x2F4F4F, + DarkTurquoise=0x00CED1, + DarkViolet=0x9400D3, + DeepPink=0xFF1493, + DeepSkyBlue=0x00BFFF, + DimGray=0x696969, + DimGrey=0x696969, + DodgerBlue=0x1E90FF, + FireBrick=0xB22222, + FloralWhite=0xFFFAF0, + ForestGreen=0x228B22, + Fuchsia=0xFF00FF, + Gainsboro=0xDCDCDC, + GhostWhite=0xF8F8FF, + Gold=0xFFD700, + Goldenrod=0xDAA520, + Gray=0x808080, + Grey=0x808080, + Green=0x008000, + GreenYellow=0xADFF2F, + Honeydew=0xF0FFF0, + HotPink=0xFF69B4, + IndianRed=0xCD5C5C, + Indigo=0x4B0082, + Ivory=0xFFFFF0, + Khaki=0xF0E68C, + Lavender=0xE6E6FA, + LavenderBlush=0xFFF0F5, + LawnGreen=0x7CFC00, + LemonChiffon=0xFFFACD, + LightBlue=0xADD8E6, + LightCoral=0xF08080, + LightCyan=0xE0FFFF, + LightGoldenrodYellow=0xFAFAD2, + LightGreen=0x90EE90, + LightGrey=0xD3D3D3, + LightPink=0xFFB6C1, + LightSalmon=0xFFA07A, + LightSeaGreen=0x20B2AA, + LightSkyBlue=0x87CEFA, + LightSlateGray=0x778899, + LightSlateGrey=0x778899, + LightSteelBlue=0xB0C4DE, + LightYellow=0xFFFFE0, + Lime=0x00FF00, + LimeGreen=0x32CD32, + Linen=0xFAF0E6, + Magenta=0xFF00FF, + Maroon=0x800000, + MediumAquamarine=0x66CDAA, + MediumBlue=0x0000CD, + MediumOrchid=0xBA55D3, + MediumPurple=0x9370DB, + MediumSeaGreen=0x3CB371, + MediumSlateBlue=0x7B68EE, + MediumSpringGreen=0x00FA9A, + MediumTurquoise=0x48D1CC, + MediumVioletRed=0xC71585, + MidnightBlue=0x191970, + MintCream=0xF5FFFA, + MistyRose=0xFFE4E1, + Moccasin=0xFFE4B5, + NavajoWhite=0xFFDEAD, + Navy=0x000080, + OldLace=0xFDF5E6, + Olive=0x808000, + OliveDrab=0x6B8E23, + Orange=0xFFA500, + OrangeRed=0xFF4500, + Orchid=0xDA70D6, + PaleGoldenrod=0xEEE8AA, + PaleGreen=0x98FB98, + PaleTurquoise=0xAFEEEE, + PaleVioletRed=0xDB7093, + PapayaWhip=0xFFEFD5, + PeachPuff=0xFFDAB9, + Peru=0xCD853F, + Pink=0xFFC0CB, + Plaid=0xCC5533, + Plum=0xDDA0DD, + PowderBlue=0xB0E0E6, + Purple=0x800080, + Red=0xFF0000, + RosyBrown=0xBC8F8F, + RoyalBlue=0x4169E1, + SaddleBrown=0x8B4513, + Salmon=0xFA8072, + SandyBrown=0xF4A460, + SeaGreen=0x2E8B57, + Seashell=0xFFF5EE, + Sienna=0xA0522D, + Silver=0xC0C0C0, + SkyBlue=0x87CEEB, + SlateBlue=0x6A5ACD, + SlateGray=0x708090, + SlateGrey=0x708090, + Snow=0xFFFAFA, + SpringGreen=0x00FF7F, + SteelBlue=0x4682B4, + Tan=0xD2B48C, + Teal=0x008080, + Thistle=0xD8BFD8, + Tomato=0xFF6347, + Turquoise=0x40E0D0, + Violet=0xEE82EE, + Wheat=0xF5DEB3, + White=0xFFFFFF, + WhiteSmoke=0xF5F5F5, + Yellow=0xFFFF00, + YellowGreen=0x9ACD32, + FairyLight=0xFFE42D, // LED RGB color that roughly approximates the color of incandescent fairy lights + FairyLightNCC=0xFF9D2A // if using no color correction, use this + } HTMLColorCode; +}; + +__attribute__((always_inline)) inline CRGB operator+(const CRGB& p1, const CRGB& p2) { + return CRGB(qadd8(p1.r, p2.r), qadd8(p1.g, p2.g), qadd8(p1.b, p2.b)); +} + +__attribute__((always_inline)) inline CRGB operator-(const CRGB& p1, const CRGB& p2) { + return CRGB(qsub8(p1.r, p2.r), qsub8(p1.g, p2.g), qsub8(p1.b, p2.b)); +} + +__attribute__((always_inline)) inline bool operator== (const CRGB& lhs, const CRGB& rhs) { + return (lhs.r == rhs.r) && (lhs.g == rhs.g) && (lhs.b == rhs.b); +} + +__attribute__((always_inline)) inline bool operator!= (const CRGB& lhs, const CRGB& rhs) { + return !(lhs == rhs); +} + +// RGB color palette with 16 discrete values +class CRGBPalette16 { +public: + CRGB entries[16]; + CRGBPalette16() { + memset(entries, 0, sizeof(entries)); // default constructor: set all to black + } + + // Create palette from 16 CRGB values + CRGBPalette16(const CRGB& c00, const CRGB& c01, const CRGB& c02, const CRGB& c03, + const CRGB& c04, const CRGB& c05, const CRGB& c06, const CRGB& c07, + const CRGB& c08, const CRGB& c09, const CRGB& c10, const CRGB& c11, + const CRGB& c12, const CRGB& c13, const CRGB& c14, const CRGB& c15) { + entries[0] = c00; entries[1] = c01; entries[2] = c02; entries[3] = c03; + entries[4] = c04; entries[5] = c05; entries[6] = c06; entries[7] = c07; + entries[8] = c08; entries[9] = c09; entries[10] = c10; entries[11] = c11; + entries[12] = c12; entries[13] = c13; entries[14] = c14; entries[15] = c15; + }; + + // Copy constructor + CRGBPalette16(const CRGBPalette16& rhs) { + memmove((void*)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + } + + // Create palette from array of CRGB colors + CRGBPalette16(const CRGB rhs[16]) { + memmove((void*)&(entries[0]), &(rhs[0]), sizeof(entries)); + } + + // Copy assignment operator + CRGBPalette16& operator=(const CRGBPalette16& rhs) { + memmove((void*)&(entries[0]), &(rhs.entries[0]), sizeof(entries)); + return *this; + } + + // Create palette from array of CRGB colors + CRGBPalette16& operator=(const CRGB rhs[16]) { + memmove((void*)&(entries[0]), &(rhs[0]), sizeof(entries)); + return *this; + } + + // Create palette from palette stored in PROGMEM + CRGBPalette16(const TProgmemRGBPalette16& rhs) { + for (int i = 0; i < 16; ++i) { + entries[i] = pgm_read_dword(rhs + i); + } + } + + // Copy assignment operator for PROGMEM palette + CRGBPalette16& operator=(const TProgmemRGBPalette16& rhs) { + for (int i = 0; i < 16; ++i) { + entries[i] = pgm_read_dword(rhs + i); + } + return *this; + } + + // Equality operator + bool operator==(const CRGBPalette16& rhs) const { + const uint8_t* p = (const uint8_t*)(&(this->entries[0])); + const uint8_t* q = (const uint8_t*)(&(rhs.entries[0])); + if (p == q) return true; + for (unsigned i = 0; i < (sizeof(entries)); ++i) { + if (*p != *q) return false; + ++p; + ++q; + } + return true; + } + + // Inequality operator + bool operator!=(const CRGBPalette16& rhs) const { + return !(*this == rhs); + } + + // Array subscript operator + inline CRGB& operator[](uint8_t x) __attribute__((always_inline)) { + return entries[x]; + } + + // Array subscript operator (const) + inline const CRGB& operator[](uint8_t x) const __attribute__((always_inline)) { + return entries[x]; + } + + // Array subscript operator + inline CRGB& operator[](int x) __attribute__((always_inline)) { + return entries[(uint8_t)x]; + } + + // Array subscript operator (const) + inline const CRGB& operator[](int x) const __attribute__((always_inline)) { + return entries[(uint8_t)x]; + } + + // Get the underlying pointer to the CRGB entries making up the palette + operator CRGB*() { + return &(entries[0]); + } + + // Create palette from a single CRGB color + CRGBPalette16(const CRGB& c1) { + fill_solid_RGB(&(entries[0]), 16, c1); + } + + // Create palette from two CRGB colors + CRGBPalette16(const CRGB& c1, const CRGB& c2) { + fill_gradient_RGB(&(entries[0]), 16, c1, c2); + } + + // Create palette from three CRGB colors + CRGBPalette16(const CRGB& c1, const CRGB& c2, const CRGB& c3) { + fill_gradient_RGB(&(entries[0]), 16, c1, c2, c3); + } + + // Create palette from four CRGB colors + CRGBPalette16(const CRGB& c1, const CRGB& c2, const CRGB& c3, const CRGB& c4) { + fill_gradient_RGB(&(entries[0]), 16, c1, c2, c3, c4); + } + + // Creates a palette from a gradient palette in PROGMEM. + // + // Gradient palettes are loaded into CRGBPalettes in such a way + // that, if possible, every color represented in the gradient palette + // is also represented in the CRGBPalette, this may not preserve original + // color spacing, but will try to not omit small color bands. + + CRGBPalette16(TProgmemRGBGradientPalette_bytes progpal) { + *this = progpal; + } + + CRGBPalette16& operator=(TProgmemRGBGradientPalette_bytes progpal) { + TRGBGradientPaletteEntryUnion* progent = (TRGBGradientPaletteEntryUnion*)(progpal); + TRGBGradientPaletteEntryUnion u; + + // Count entries + int count = 0; + do { + u.dword = pgm_read_dword(progent + count); + ++count; + } while (u.index != 255); + + int lastSlotUsed = -1; + + u.dword = pgm_read_dword(progent); + CRGB rgbstart(u.r, u.g, u.b); + + int indexstart = 0; + int istart8 = 0; + int iend8 = 0; + while (indexstart < 255) { + ++progent; + u.dword = pgm_read_dword(progent); + int indexend = u.index; + CRGB rgbend(u.r, u.g, u.b); + istart8 = indexstart / 16; + iend8 = indexend / 16; + if (count < 16) { + if ((istart8 <= lastSlotUsed) && (lastSlotUsed < 15)) { + istart8 = lastSlotUsed + 1; + if (iend8 < istart8) { + iend8 = istart8; + } + } + lastSlotUsed = iend8; + } + fill_gradient_RGB(&(entries[0]), istart8, rgbstart, iend8, rgbend); + indexstart = indexend; + rgbstart = rgbend; + } + return *this; + } + + // Creates a palette from a gradient palette in dynamic (heap) memory. + CRGBPalette16& loadDynamicGradientPalette(TDynamicRGBGradientPalette_bytes gpal) { + TRGBGradientPaletteEntryUnion* ent = (TRGBGradientPaletteEntryUnion*)(gpal); + TRGBGradientPaletteEntryUnion u; + + // Count entries + unsigned count = 0; + do { + u = *(ent + count); + ++count; + } while (u.index != 255); + + int lastSlotUsed = -1; + + u = *ent; + CRGB rgbstart(u.r, u.g, u.b); + + int indexstart = 0; + int istart8 = 0; + int iend8 = 0; + while (indexstart < 255) { + ++ent; + u = *ent; + int indexend = u.index; + CRGB rgbend(u.r, u.g, u.b); + istart8 = indexstart / 16; + iend8 = indexend / 16; + if (count < 16) { + if ((istart8 <= lastSlotUsed) && (lastSlotUsed < 15)) { + istart8 = lastSlotUsed + 1; + if (iend8 < istart8) { + iend8 = istart8; + } + } + lastSlotUsed = iend8; + } + fill_gradient_RGB(&(entries[0]), istart8, rgbstart, iend8, rgbend); + indexstart = indexend; + rgbstart = rgbend; + } + return *this; + } +}; \ No newline at end of file diff --git a/wled00/util.cpp b/wled00/util.cpp index c6fbb280b4..1c21bec62f 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -1,6 +1,7 @@ #include "wled.h" #include "fcn_declare.h" #include "const.h" +#include "src/dependencies/fastled_slim/fastled_slim.h" #ifdef ESP8266 #include "user_interface.h" // for bootloop detection #include // for SHA1 on ESP8266 @@ -388,39 +389,59 @@ uint16_t crc16(const unsigned char* data_p, size_t length) { return crc; } -// fastled beatsin: 1:1 replacements to remove the use of fastled sin16() +// FastLED Reference +// ----------------- +// The following beat functions derived from FastLED @ 3.6.0 (https://github.com/FastLED/FastLED) are licensed under the MIT license +// See /src/dependencies/fastled_slim/LICENSE.txt for details + +// Generates a 16-bit "sawtooth" wave at a given BPM, with BPM specified in Q8.8 fixed-point format: +// for 120 BPM it would be 120*256 = 30720. If you just want to specify "120", use beat16() or beat8(). +// timebase is the time offset of the wave from the millis() timer +uint16_t beat88(uint16_t beats_per_minute_88, uint32_t timebase) { + return ((millis() - timebase) * beats_per_minute_88 * 280) >> 16; +} + +// Generates a 16-bit "sawtooth" wave at a given BPM +uint16_t beat16(uint16_t beats_per_minute, uint32_t timebase) { + if (beats_per_minute < 256) beats_per_minute <<= 8; + return beat88(beats_per_minute, timebase); +} + +/// Generates an 8-bit "sawtooth" wave at a given BPM +uint8_t beat8(uint16_t beats_per_minute, uint32_t timebase) { + return beat16(beats_per_minute, timebase) >> 8; +} + // Generates a 16-bit sine wave at a given BPM that oscillates within a given range. see fastled for details. -uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset) -{ - uint16_t beat = beat88( beats_per_minute_88, timebase); - uint16_t beatsin (sin16_t( beat + phase_offset) + 32768); - uint16_t rangewidth = highest - lowest; - uint16_t scaledbeat = scale16( beatsin, rangewidth); - uint16_t result = lowest + scaledbeat; - return result; +uint16_t beatsin88_t(uint16_t beats_per_minute_88, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset) { + uint16_t beat = beat88(beats_per_minute_88, timebase); + uint16_t beatsin = (sin16_t( beat + phase_offset) + 32768); + uint16_t rangewidth = highest - lowest; + uint16_t scaledbeat = scale16(beatsin, rangewidth); + uint16_t result = lowest + scaledbeat; + return result; } // Generates a 16-bit sine wave at a given BPM that oscillates within a given range. see fastled for details. -uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset) -{ - uint16_t beat = beat16( beats_per_minute, timebase); - uint16_t beatsin = (sin16_t( beat + phase_offset) + 32768); - uint16_t rangewidth = highest - lowest; - uint16_t scaledbeat = scale16( beatsin, rangewidth); - uint16_t result = lowest + scaledbeat; - return result; +uint16_t beatsin16_t(uint16_t beats_per_minute, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset) { + uint16_t beat = beat16(beats_per_minute, timebase); + uint16_t beatsin = sin16_t(beat + phase_offset) + 32768; + uint16_t rangewidth = highest - lowest; + uint16_t scaledbeat = scale16(beatsin, rangewidth); + uint16_t result = lowest + scaledbeat; + return result; } // Generates an 8-bit sine wave at a given BPM that oscillates within a given range. see fastled for details. -uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest, uint8_t highest, uint32_t timebase, uint8_t phase_offset) -{ - uint8_t beat = beat8( beats_per_minute, timebase); - uint8_t beatsin = sin8_t( beat + phase_offset); - uint8_t rangewidth = highest - lowest; - uint8_t scaledbeat = scale8( beatsin, rangewidth); - uint8_t result = lowest + scaledbeat; - return result; +uint8_t beatsin8_t(uint16_t beats_per_minute, uint8_t lowest, uint8_t highest, uint32_t timebase, uint8_t phase_offset) { + uint8_t beat = beat8(beats_per_minute, timebase); + uint8_t beatsin = sin8_t(beat + phase_offset); + uint8_t rangewidth = highest - lowest; + uint8_t scaledbeat = scale8(beatsin, rangewidth); + uint8_t result = lowest + scaledbeat; + return result; } +// end of FastLED functions /////////////////////////////////////////////////////////////////////////////// // Begin simulateSound (to enable audio enhanced effects to display something) @@ -979,7 +1000,7 @@ static inline __attribute__((always_inline)) int32_t hashToGradient(uint32_t h) } // Gradient functions for 1D, 2D and 3D Perlin noise note: forcing inline produces smaller code and makes it 3x faster! -static inline __attribute__((always_inline)) int32_t gradient1D(uint32_t x0, int32_t dx) { +static int32_t gradient1D(uint32_t x0, int32_t dx) { uint32_t h = x0 * 0x27D4EB2D; h ^= h >> 15; h *= 0x92C3412B; diff --git a/wled00/wled.cpp b/wled00/wled.cpp index d67f784078..0442780a79 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -577,10 +577,6 @@ void WLED::setup() DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize()); #endif - // Seed FastLED random functions with an esp random value, which already works properly at this point. - const uint32_t seed32 = hw_random(); - random16_set_seed((uint16_t)seed32); - #if WLED_WATCHDOG_TIMEOUT > 0 enableWatchdog(); #endif diff --git a/wled00/wled.h b/wled00/wled.h index 21b340d94c..87e2c6270c 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -186,15 +186,15 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; #define FASTLED_INTERNAL //remove annoying pragma messages #define USE_GET_MILLISECOND_TIMER -#include "FastLED.h" + #include "const.h" +#include "colors.h" #include "fcn_declare.h" #ifndef WLED_DISABLE_OTA #include "ota_update.h" #endif #include "NodeStruct.h" #include "pin_manager.h" -#include "colors.h" #include "bus_manager.h" #include "FX.h" #include "wled_metadata.h" @@ -1052,13 +1052,6 @@ WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0); //macro to convert F to const #define SET_F(x) (const char*)F(x) -//color mangling macros -#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) -#define R(c) (byte((c) >> 16)) -#define G(c) (byte((c) >> 8)) -#define B(c) (byte(c)) -#define W(c) (byte((c) >> 24)) - class WLED { public: WLED(); diff --git a/wled00/wled_math.cpp b/wled00/wled_math.cpp index 43c593080e..8b2d927b22 100644 --- a/wled00/wled_math.cpp +++ b/wled00/wled_math.cpp @@ -244,3 +244,4 @@ uint32_t sqrt32_bw(uint32_t x) { } return res; } +