2744 lines
111 KiB
C++
2744 lines
111 KiB
C++
/*
|
||
WS2812FX_fcn.cpp contains all utility functions
|
||
Harm Aldick - 2016
|
||
www.aldick.org
|
||
Modified heavily for WLED
|
||
*/
|
||
#include "wled.h"
|
||
#include "FX.h"
|
||
#include "palettes.h"
|
||
#ifdef ARDUINO_ARCH_ESP32
|
||
#include <esp_timer.h> // WLEDMM to get esp_timer_get_time()
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/portmacro.h"
|
||
static portMUX_TYPE s_wled_strip_mux = portMUX_INITIALIZER_UNLOCKED; // to protect deleting Segment::_globalLeds
|
||
#endif
|
||
|
||
/*
|
||
Custom per-LED mapping has moved!
|
||
|
||
Create a file "ledmap.json" using the edit page.
|
||
|
||
this is just an example (30 LEDs). It will first set all even, then all uneven LEDs.
|
||
{"map":[
|
||
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28,
|
||
1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29]}
|
||
|
||
another example. Switches direction every 5 LEDs.
|
||
{"map":[
|
||
0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14,
|
||
19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]}
|
||
*/
|
||
|
||
//factory defaults LED setup
|
||
//#define PIXEL_COUNTS 30, 30, 30, 30
|
||
//#define DATA_PINS 16, 1, 3, 4
|
||
//#define DEFAULT_LED_TYPE TYPE_WS2812_RGB
|
||
|
||
#ifndef PIXEL_COUNTS
|
||
#define PIXEL_COUNTS DEFAULT_LED_COUNT
|
||
#endif
|
||
|
||
#ifndef DATA_PINS
|
||
#define DATA_PINS LEDPIN
|
||
#endif
|
||
|
||
#ifndef DEFAULT_LED_TYPE
|
||
#define DEFAULT_LED_TYPE TYPE_WS2812_RGB
|
||
#endif
|
||
|
||
#ifndef DEFAULT_LED_COLOR_ORDER
|
||
#define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB
|
||
#endif
|
||
|
||
|
||
#if MAX_NUM_SEGMENTS < WLED_MAX_BUSSES
|
||
#error "Max segments must be at least max number of busses!"
|
||
#endif
|
||
|
||
// WLEDMM experimental . this is a "C style" wrapper for strip.waitUntilIdle();
|
||
// This workaround is just needed for the segment class, that does't know about "strip"
|
||
void strip_wait_until_idle(String whoCalledMe) {
|
||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_PROTECT_SERVICE) // WLEDMM experimental
|
||
if (strip.isServicing() && (strncmp(pcTaskGetTaskName(NULL), "loopTask", 8) != 0)) { // if we are in looptask (arduino loop), its safe to proceed without waiting
|
||
USER_PRINTLN(whoCalledMe + String(": strip is still drawing effects."));
|
||
strip.waitUntilIdle();
|
||
}
|
||
#endif
|
||
}
|
||
// WLEDMM another helper for segment class
|
||
bool strip_uses_global_leds(void) {
|
||
return strip.useLedsArray;
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// Segment class implementation
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
size_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[]
|
||
CRGB *Segment::_globalLeds = nullptr;
|
||
uint16_t Segment::maxWidth = DEFAULT_LED_COUNT;
|
||
uint16_t Segment::maxHeight = 1;
|
||
|
||
CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black);
|
||
|
||
// copy constructor - creates a new segment by copy from orig, but does not copy buffers. Does not modify orig!
|
||
Segment::Segment(const Segment &orig) {
|
||
DEBUG_PRINTLN(F("-- Copy segment constructor --"));
|
||
memcpy((void*)this, (void*)&orig, sizeof(Segment)); //WLEDMM copy to this
|
||
transitional = false; // copied segment cannot be in transition
|
||
// WLEDMM temporarily prevent any fast draw calls to the new segment
|
||
// _isValid2D = false;
|
||
_isSimpleSegment = false;
|
||
_isSuperSimpleSegment = false;
|
||
|
||
name = nullptr;
|
||
data = nullptr;
|
||
_dataLen = 0;
|
||
_t = nullptr;
|
||
if (ledsrgb && !Segment::_globalLeds) {ledsrgb = nullptr; ledsrgbSize = 0;} // WLEDMM
|
||
if (orig.name) { name = new(std::nothrow) char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); }
|
||
if (orig.data) { if (allocateData(orig._dataLen, true)) memcpy(data, orig.data, orig._dataLen); }
|
||
//if (orig._t) { _t = new(std::nothrow) Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); }
|
||
//else markForReset(); // WLEDMM
|
||
// if (orig.ledsrgb && !Segment::_globalLeds) { allocLeds(); if (ledsrgb) memcpy(ledsrgb, orig.ledsrgb, sizeof(CRGB)*length()); } // WLEDMM
|
||
jMap = nullptr; //WLEDMM jMap
|
||
}
|
||
|
||
//WLEDMM: recreate ledsrgb if more space needed (will not free ledsrgb!)
|
||
void Segment::allocLeds() {
|
||
size_t size = sizeof(CRGB)*max((size_t) length(), ledmapMaxSize); // TroyHacks
|
||
if ((size < sizeof(CRGB)) || (size > 164000)) { //softhack too small (<3) or too large (>160Kb)
|
||
DEBUG_PRINTF("allocLeds warning: size == %u !!\n", size);
|
||
if (ledsrgb && (ledsrgbSize == 0)) {
|
||
USER_PRINTLN("allocLeds warning: ledsrgbSize == 0 but ledsrgb!=NULL");
|
||
free(ledsrgb); ledsrgb=nullptr;
|
||
} // softhack007 clean up buffer
|
||
}
|
||
if ((size > 0) && (!ledsrgb || size > ledsrgbSize)) { //softhack dont allocate zero bytes
|
||
USER_PRINTF("allocLeds (%d,%d to %d,%d), %u from %u\n", start, startY, stop, stopY, size, ledsrgb?ledsrgbSize:0);
|
||
if (ledsrgb) free(ledsrgb); // we need a bigger buffer, so free the old one first
|
||
ledsrgb = (CRGB*)calloc(size, 1);
|
||
ledsrgbSize = ledsrgb?size:0;
|
||
if (ledsrgb == nullptr) {
|
||
USER_PRINTLN("allocLeds failed!!");
|
||
errorFlag = ERR_LOW_BUF; // WLEDMM raise errorflag
|
||
}
|
||
}
|
||
else {
|
||
//USER_PRINTF("reuse Leds %u from %u\n", size, ledsrgb?ledsrgbSize:0);
|
||
}
|
||
}
|
||
|
||
// move constructor --> moves everything (including buffer) from orig to this
|
||
Segment::Segment(Segment &&orig) noexcept {
|
||
DEBUG_PRINTLN(F("-- Move segment constructor --"));
|
||
|
||
// WLEDMM temporarily prevent any fast draw calls to old and new segment
|
||
orig._isSimpleSegment = false;
|
||
orig._isSuperSimpleSegment = false;
|
||
|
||
memcpy((void*)this, (void*)&orig, sizeof(Segment));
|
||
orig.transitional = false; // old segment cannot be in transition any more
|
||
#ifdef WLEDMM_FASTPATH
|
||
// WLEDMM prevent any draw calls to old segment
|
||
orig._isValid2D = false;
|
||
#endif
|
||
orig.name = nullptr;
|
||
orig.data = nullptr;
|
||
orig._dataLen = 0;
|
||
orig._t = nullptr;
|
||
orig.ledsrgb = nullptr; //WLEDMM
|
||
orig.ledsrgbSize = 0; // WLEDMM
|
||
orig.jMap = nullptr; //WLEDMM jMap
|
||
}
|
||
|
||
// copy assignment --> overwrite segment with orig - deletes old buffers in "this", but does not change orig!
|
||
Segment& Segment::operator= (const Segment &orig) {
|
||
DEBUG_PRINTLN(F("-- Copy-assignment segment --"));
|
||
if (this != &orig) {
|
||
// clean destination
|
||
transitional = false; // copied segment cannot be in transition
|
||
if (name) delete[] name;
|
||
if (_t) delete _t;
|
||
CRGB* oldLeds = ledsrgb;
|
||
size_t oldLedsSize = ledsrgbSize;
|
||
if (ledsrgb && !Segment::_globalLeds) free(ledsrgb);
|
||
deallocateData();
|
||
// copy source
|
||
memcpy((void*)this, (void*)&orig, sizeof(Segment));
|
||
transitional = false;
|
||
|
||
// WLEDMM prevent any fast draw calls to this segment until the next frame starts
|
||
//_isValid2D = false;
|
||
_isSimpleSegment = false;
|
||
_isSuperSimpleSegment = false;
|
||
|
||
// erase pointers to allocated data
|
||
name = nullptr;
|
||
data = nullptr;
|
||
_dataLen = 0;
|
||
_t = nullptr;
|
||
//if (!Segment::_globalLeds) {ledsrgb = oldLeds; ledsrgbSize = oldLedsSize;}; // WLEDMM reuse leds instead of ledsrgb = nullptr;
|
||
if (!Segment::_globalLeds) {ledsrgb = nullptr; ledsrgbSize = 0;}; // WLEDMM copy has no buffers (yet)
|
||
// copy source data
|
||
if (orig.name) { name = new(std::nothrow) char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); }
|
||
if (orig.data) { if (allocateData(orig._dataLen, true)) memcpy(data, orig.data, orig._dataLen); }
|
||
//if (orig._t) { _t = new(std::nothrow) Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); }
|
||
//else markForReset(); // WLEDMM
|
||
//if (orig.ledsrgb && !Segment::_globalLeds) { allocLeds(); if (ledsrgb) memcpy(ledsrgb, orig.ledsrgb, sizeof(CRGB)*length()); } // WLEDMM don't copy old buffer
|
||
jMap = nullptr; //WLEDMM jMap
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
// move assignment --> moves everything (including buffers) from "orig" to "this"
|
||
Segment& Segment::operator= (Segment &&orig) noexcept {
|
||
DEBUG_PRINTLN(F("-- Move-assignment segment --"));
|
||
if (this != &orig) {
|
||
transitional = false; // just temporary
|
||
if (name) { delete[] name; name = nullptr; } // free old name
|
||
deallocateData(); // free old runtime data
|
||
if (_t) { delete _t; _t = nullptr; }
|
||
if (ledsrgb && !Segment::_globalLeds) free(ledsrgb); //WLEDMM: not needed anymore as we will use leds from copy. no need to nullify ledsrgb as it gets new value in memcpy
|
||
|
||
// WLEDMM temporarily prevent any fast draw calls to old and new segment
|
||
orig._isSimpleSegment = false;
|
||
orig._isSuperSimpleSegment = false;
|
||
|
||
memcpy((void*)this, (void*)&orig, sizeof(Segment));
|
||
#ifdef WLEDMM_FASTPATH
|
||
// WLEDMM temporarily prevent any draw calls to old segment
|
||
orig._isValid2D = false;
|
||
#endif
|
||
orig.name = nullptr;
|
||
orig.data = nullptr;
|
||
orig._dataLen = 0;
|
||
orig._t = nullptr;
|
||
orig.ledsrgb = nullptr; //WLEDMM: do not free as moved to here
|
||
orig.ledsrgbSize = 0; //WLEDMM
|
||
orig.jMap = nullptr; //WLEDMM jMap
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
bool Segment::allocateData(size_t len, bool allowOverdraft) { // WLEDMM allowOverdraft for temporary overdraft by segment copy constructor
|
||
// WLEDMM
|
||
if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation)
|
||
if ((call == 0) && (len > 0)) memset(data, 0, len); // erase buffer if called during effect initialisation
|
||
return true;
|
||
}
|
||
//DEBUG_PRINTF("allocateData(%u) start %d, stop %d, vlen %d\n", len, start, stop, virtualLength());
|
||
deallocateData();
|
||
if (len == 0) return false; // nothing to do
|
||
if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) {
|
||
if (!allowOverdraft || (Segment::getUsedSegmentData() + len > MAX_SEGMENT_OVERDATA)) { // WLEDMM 50% overdraft allowed temporarily
|
||
//USER_PRINTF("Segment::allocateData: Segment data quota exceeded! used:%u request:%u max:%d\n", Segment::getUsedSegmentData(), len, MAX_SEGMENT_DATA);
|
||
if (len > 0) errorFlag = ERR_LOW_SEG_MEM; // WLEDMM raise errorflag
|
||
return false; //not enough memory
|
||
}
|
||
}
|
||
// do not use SPI RAM on ESP32 since it is slow
|
||
//#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM)
|
||
//if (psramFound())
|
||
// data = (byte*) ps_malloc(len);
|
||
//else
|
||
//#endif
|
||
data = (byte*) malloc(len);
|
||
if (!data) {
|
||
_dataLen = 0; // WLEDMM reset dataLen
|
||
if ((errorFlag != ERR_LOW_MEM) && (errorFlag != ERR_LOW_SEG_MEM)) { // spam filter
|
||
USER_PRINT(F("Segment::allocateData: FAILED to allocate "));
|
||
USER_PRINT(len); USER_PRINTLN(F(" bytes."));
|
||
}
|
||
errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
|
||
return false;
|
||
} //allocation failed
|
||
Segment::addUsedSegmentData(len);
|
||
DEBUG_PRINTF("Segment::allocateData: %u bytes allocated (%u used)\n", len, Segment::getUsedSegmentData());
|
||
_dataLen = len;
|
||
memset(data, 0, len);
|
||
if ((errorFlag == ERR_LOW_SEG_MEM) || (errorFlag == ERR_LOW_MEM) || (errorFlag == ERR_NORAM_PX)) errorFlag = ERR_NONE; // WLEDMM reset errorflag on success
|
||
return true;
|
||
}
|
||
|
||
void Segment::deallocateData() {
|
||
if (!data) {
|
||
if (_dataLen>0) {
|
||
Segment::addUsedSegmentData(-_dataLen); // WLEDMM fix housekeeping
|
||
DEBUG_PRINTF("Segment::deallocateData unregistering %u bytes as unused.", _dataLen);
|
||
}
|
||
_dataLen = 0;
|
||
return;
|
||
} // WLEDMM reset dataLen
|
||
free(data);
|
||
data = nullptr;
|
||
DEBUG_PRINTF("Segment::deallocateData: free'd %d bytes.\n", _dataLen);
|
||
Segment::addUsedSegmentData(-_dataLen);
|
||
_dataLen = 0;
|
||
}
|
||
|
||
/**
|
||
* If reset of this segment was requested, clears runtime
|
||
* settings of this segment.
|
||
* Must not be called while an effect mode function is running
|
||
* because it could access the data buffer and this method
|
||
* may free that data buffer.
|
||
*/
|
||
void Segment::resetIfRequired() {
|
||
if (reset) {
|
||
if (ledsrgb && !Segment::_globalLeds) { free(ledsrgb); ledsrgb = nullptr; ledsrgbSize=0;} // WLEDMM segment has changed, so we need a fresh buffer.
|
||
if (transitional && _t) { transitional = false; delete _t; _t = nullptr; }
|
||
deallocateData();
|
||
next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
|
||
reset = false; // setOption(SEG_OPTION_RESET, false);
|
||
startFrame(); // WLEDMM update cached propoerties
|
||
if (isActive() && !freeze) { fill(BLACK); needsBlank = false; } // WLEDMM start clean
|
||
#ifdef WLED_ENABLE_GIF
|
||
endImagePlayback(this);
|
||
#endif
|
||
DEBUG_PRINTLN("Segment reset");
|
||
} else if (needsBlank) {
|
||
startFrame(); // WLEDMM update cached propoerties
|
||
if (isActive() && !freeze) {
|
||
fill(BLACK); // WLEDMM start clean
|
||
DEBUG_PRINTLN("Segment blanked");
|
||
needsBlank = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
void Segment::setUpLeds() {
|
||
// deallocation happens in resetIfRequired() as it is called when segment changes or in destructor
|
||
if (Segment::_globalLeds) {
|
||
#ifndef WLED_DISABLE_2D
|
||
ledsrgb = &Segment::_globalLeds[start + startY*Segment::maxWidth];
|
||
ledsrgbSize = length() * sizeof(CRGB); // also set this when using global leds.
|
||
//USER_PRINTF("\nsetUpLeds() Global LEDs: startX=%d stopx=%d startY=%d stopy=%d maxwidth=%d; length=%d, size=%d\n\n", start, stop, startY, stopY, Segment::maxWidth, length(), ledsrgbSize/3);
|
||
#else
|
||
ledsrgb = &Segment::_globalLeds[start];
|
||
ledsrgbSize = length() * sizeof(CRGB); // also set this when using global leds.
|
||
#endif
|
||
} else if (length() > 0) { //WLEDMM we always want a new buffer //softhack007 quickfix - avoid malloc(0) which is undefined behaviour (should not happen, but i've seen it)
|
||
//#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM)
|
||
//if (psramFound())
|
||
// ledsrgb = (CRGB*)ps_malloc(sizeof(CRGB)*length()); // softhack007 disabled; putting leds into psram leads to horrible slowdown on WROVER boards
|
||
//else
|
||
//#endif
|
||
allocLeds(); //WLEDMM
|
||
//USER_PRINTF("\nsetUpLeds() local LEDs: startX=%d stopx=%d startY=%d stopy=%d maxwidth=%d; length=%d, size=%d\n\n", start, stop, startY, stopY, Segment::maxWidth, length(), ledsrgbSize/3);
|
||
}
|
||
}
|
||
|
||
CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) const {
|
||
static unsigned long _lastPaletteChange = millis() - 990000; // perhaps it should be per segment //WLEDMM changed init value to avoid pure orange after startup
|
||
static CRGBPalette16 randomPalette = CRGBPalette16(DEFAULT_COLOR);
|
||
static CRGBPalette16 prevRandomPalette = CRGBPalette16(CRGB(BLACK));
|
||
byte tcp[76] = { 255 }; //WLEDMM: prevent out-of-range access in loadDynamicGradientPalette()
|
||
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0;
|
||
if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip
|
||
//default palette. Differs depending on effect
|
||
if (pal == 0) switch (mode) {
|
||
case FX_MODE_FIRE_2012 : pal = 35; break; // heat palette
|
||
case FX_MODE_COLORWAVES : pal = 26; break; // landscape 33
|
||
case FX_MODE_FILLNOISE8 : pal = 9; break; // ocean colors
|
||
case FX_MODE_NOISE16_1 : pal = 20; break; // Drywet
|
||
case FX_MODE_NOISE16_2 : pal = 43; break; // Blue cyan yellow
|
||
case FX_MODE_NOISE16_3 : pal = 35; break; // heat palette
|
||
case FX_MODE_NOISE16_4 : pal = 26; break; // landscape 33
|
||
case FX_MODE_GLITTER : pal = 11; break; // rainbow colors
|
||
case FX_MODE_SUNRISE : pal = 35; break; // heat palette
|
||
case FX_MODE_RAILWAY : pal = 3; break; // prim + sec
|
||
case FX_MODE_2DSOAP : pal = 11; break; // rainbow colors
|
||
}
|
||
switch (pal) {
|
||
case 0: //default palette. Exceptions for specific effects above
|
||
targetPalette = PartyColors_p; break;
|
||
case 1: {//Random smooth: periodically replace palette with a random one. Transition palette change in 500ms
|
||
uint32_t timeSinceLastChange = millis() - _lastPaletteChange;
|
||
if (timeSinceLastChange > randomPaletteChangeTime * 1000U) {
|
||
prevRandomPalette = randomPalette;
|
||
randomPalette = CRGBPalette16(
|
||
CHSV(random8(), random8(160, 255), random8(128, 255)),
|
||
CHSV(random8(), random8(160, 255), random8(128, 255)),
|
||
CHSV(random8(), random8(160, 255), random8(128, 255)),
|
||
CHSV(random8(), random8(160, 255), random8(128, 255)));
|
||
_lastPaletteChange = millis();
|
||
timeSinceLastChange = 0;
|
||
}
|
||
|
||
//WLEDMM: smooth transitions of palettes instead of every 5 sec with short transition
|
||
for (int i=0; i< 16; i++) {
|
||
targetPalette[i].r = prevRandomPalette[i].r*(5000-timeSinceLastChange)/5000 + randomPalette[i].r*timeSinceLastChange/5000;
|
||
targetPalette[i].g = prevRandomPalette[i].g*(5000-timeSinceLastChange)/5000 + randomPalette[i].g*timeSinceLastChange/5000;
|
||
targetPalette[i].b = prevRandomPalette[i].b*(5000-timeSinceLastChange)/5000 + randomPalette[i].b*timeSinceLastChange/5000;
|
||
}
|
||
break;}
|
||
case 74: {//periodically replace palette with a random one. Transition palette change in 500ms
|
||
uint32_t timeSinceLastChange = millis() - _lastPaletteChange;
|
||
if (timeSinceLastChange > randomPaletteChangeTime * 1000U) {
|
||
prevRandomPalette = randomPalette;
|
||
randomPalette = CRGBPalette16(
|
||
CHSV(random8(), random8(160, 255), random8(128, 255)),
|
||
CHSV(random8(), random8(160, 255), random8(128, 255)),
|
||
CHSV(random8(), random8(160, 255), random8(128, 255)),
|
||
CHSV(random8(), random8(160, 255), random8(128, 255)));
|
||
_lastPaletteChange = millis();
|
||
timeSinceLastChange = 0;
|
||
}
|
||
if (timeSinceLastChange <= 250) {
|
||
targetPalette = prevRandomPalette;
|
||
// there needs to be 255 palette blends (48) for full blend but that is too resource intensive
|
||
// so 128 is a compromise (we need to perform full blend of the two palettes as each segment can have random
|
||
// palette selected but only 2 static palettes are used)
|
||
size_t noOfBlends = ((128U * timeSinceLastChange) / 250U);
|
||
for (size_t i=0; i<noOfBlends; i++) nblendPaletteTowardPalette(targetPalette, randomPalette, 48);
|
||
} else {
|
||
targetPalette = randomPalette;
|
||
}
|
||
break;}
|
||
case 2: {//primary color only
|
||
CRGB prim = gamma32(colors[0]);
|
||
targetPalette = CRGBPalette16(prim); break;}
|
||
case 3: {//primary + secondary
|
||
CRGB prim = gamma32(colors[0]);
|
||
CRGB sec = gamma32(colors[1]);
|
||
targetPalette = CRGBPalette16(prim,prim,sec,sec); break;}
|
||
case 4: {//primary + secondary + tertiary
|
||
CRGB prim = gamma32(colors[0]);
|
||
CRGB sec = gamma32(colors[1]);
|
||
CRGB ter = gamma32(colors[2]);
|
||
targetPalette = CRGBPalette16(ter,sec,prim); break;}
|
||
case 5: {//primary + secondary (+tertiary if not off), more distinct
|
||
CRGB prim = gamma32(colors[0]);
|
||
CRGB sec = gamma32(colors[1]);
|
||
if (colors[2]) {
|
||
CRGB ter = gamma32(colors[2]);
|
||
targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,ter,ter,ter,ter,ter,prim);
|
||
} else {
|
||
targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec);
|
||
}
|
||
break;}
|
||
case 6: //Party colors
|
||
targetPalette = PartyColors_p; break;
|
||
case 7: //Cloud colors
|
||
targetPalette = CloudColors_p; break;
|
||
case 8: //Lava colors
|
||
targetPalette = LavaColors_p; break;
|
||
case 9: //Ocean colors
|
||
targetPalette = OceanColors_p; break;
|
||
case 10: //Forest colors
|
||
targetPalette = ForestColors_p; break;
|
||
case 11: //Rainbow colors
|
||
targetPalette = RainbowColors_p; break;
|
||
case 12: //Rainbow stripe colors
|
||
targetPalette = RainbowStripeColors_p; break;
|
||
case 71: //WLEDMM netmindz ar palette +1
|
||
case 72: //WLEDMM netmindz ar palette +2
|
||
case 73: //WLEDMM netmindz ar palette +3
|
||
targetPalette.loadDynamicGradientPalette(getAudioPalette(pal)); break;
|
||
default: //progmem palettes
|
||
if (pal>245) {
|
||
targetPalette = strip.customPalettes[255-pal]; // we checked bounds above
|
||
} else {
|
||
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72);
|
||
targetPalette.loadDynamicGradientPalette(tcp);
|
||
}
|
||
break;
|
||
}
|
||
return targetPalette;
|
||
}
|
||
|
||
void Segment::startTransition(uint16_t dur) {
|
||
if (transitional || _t) return; // already in transition no need to store anything
|
||
|
||
// starting a transition has to occur before change so we get current values 1st
|
||
uint8_t _briT = currentBri(on ? opacity : 0);
|
||
uint8_t _cctT = currentBri(cct, true);
|
||
CRGBPalette16 _palT = CRGBPalette16(DEFAULT_COLOR); loadPalette(_palT, palette);
|
||
uint8_t _modeP = mode;
|
||
uint32_t _colorT[NUM_COLORS];
|
||
for (size_t i=0; i<NUM_COLORS; i++) _colorT[i] = currentColor(i, colors[i]);
|
||
|
||
if (!_t) _t = new(std::nothrow) Transition(dur); // no previous transition running
|
||
if (!_t) return; // failed to allocate data
|
||
_t->_briT = _briT;
|
||
_t->_cctT = _cctT;
|
||
_t->_palT = _palT;
|
||
_t->_modeP = _modeP;
|
||
for (size_t i=0; i<NUM_COLORS; i++) _t->_colorT[i] = _colorT[i];
|
||
transitional = true; // setOption(SEG_OPTION_TRANSITIONAL, true);
|
||
}
|
||
|
||
// WLEDMM Segment::progress() is declared inline, see FX.h
|
||
#if 0
|
||
// transition progression between 0-65535
|
||
uint16_t IRAM_ATTR_YN Segment::progress() const {
|
||
if (!transitional || !_t) return 0xFFFFU;
|
||
unsigned long timeNow = millis();
|
||
if (timeNow - _t->_start > _t->_dur || _t->_dur == 0) return 0xFFFFU;
|
||
return (timeNow - _t->_start) * 0xFFFFU / _t->_dur;
|
||
}
|
||
#endif
|
||
|
||
// WLEDMM Segment::currentBri() is declared inline, see FX.h
|
||
#if 0
|
||
uint8_t IRAM_ATTR_YN Segment::currentBri(uint8_t briNew, bool useCct) const {
|
||
uint32_t prog = (transitional && _t) ? progress() : 0xFFFFU;
|
||
if (transitional && _t && prog < 0xFFFFU) {
|
||
if (useCct) return ((briNew * prog) + _t->_cctT * (0xFFFFU - prog)) >> 16;
|
||
else return ((briNew * prog) + _t->_briT * (0xFFFFU - prog)) >> 16;
|
||
} else {
|
||
return (useCct ? briNew : (on ? briNew : 0)); // WLEDMM aligned with upstream
|
||
}
|
||
}
|
||
#endif
|
||
|
||
uint8_t Segment::currentMode(uint8_t newMode) {
|
||
return (progress()>32767U) ? newMode : (_t ? _t->_modeP : newMode); // change effect in the middle of transition
|
||
}
|
||
|
||
uint32_t Segment::currentColor(uint8_t slot, uint32_t colorNew) {
|
||
return transitional && _t ? color_blend(_t->_colorT[slot], colorNew, progress(), true) : colorNew;
|
||
}
|
||
|
||
void Segment::setCurrentPalette() {
|
||
loadPalette(_currentPalette, palette);
|
||
if (transitional && _t && progress() < 0xFFFFU) {
|
||
// blend palettes
|
||
// there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time)
|
||
// minimum blend time is 100ms maximum is 65535ms
|
||
unsigned long timeMS = millis() - _t->_start;
|
||
uint16_t noOfBlends = min(64UL, (255U * timeMS / _t->_dur) - _t->_prevPaletteBlends); // WLEDMM limit to 64 blends at once, prevent rollover
|
||
for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48);
|
||
_currentPalette = _t->_palT; // copy transitioning/temporary palette
|
||
}
|
||
}
|
||
|
||
void Segment::handleTransition() {
|
||
if (!transitional || !_t) return; // Early exit if no transition active
|
||
unsigned long maxWait = millis() + 20;
|
||
if (mode == FX_MODE_STATIC && next_time > maxWait) next_time = maxWait;
|
||
if (progress() == 0xFFFFU) {
|
||
if (_t) {
|
||
//if (_t->_modeP != mode) markForReset(); // WLEDMM effect transition disabled as it does not work (flashes due to double effect restart)
|
||
delete _t;
|
||
_t = nullptr;
|
||
}
|
||
transitional = false; // finish transitioning segment
|
||
}
|
||
}
|
||
|
||
void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) {
|
||
//return if neither bounds nor grouping have changed
|
||
bool boundsUnchanged = (start == i1 && stop == i2);
|
||
#ifndef WLED_DISABLE_2D
|
||
if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D
|
||
#endif
|
||
if (boundsUnchanged
|
||
&& (!grp || (grouping == grp && spacing == spc))
|
||
&& (ofs == UINT16_MAX || ofs == offset)) return;
|
||
|
||
stateChanged = true; // send UDP/WS broadcast
|
||
|
||
if (stop>start) markForBlank(); //turn old segment range off // WLEDMM stop > start // toDo: check if this can be skipped when boundsUnchanged
|
||
if (i2 <= i1) { //disable segment
|
||
#ifdef WLED_ENABLE_GIF
|
||
endImagePlayback(this);
|
||
#endif
|
||
stop = 0;
|
||
markForReset();
|
||
return;
|
||
}
|
||
if (i1 < Segment::maxWidth || (i1 >= Segment::maxWidth*Segment::maxHeight && i1 < strip.getLengthTotal())) start = i1; // Segment::maxWidth equals strip.getLengthTotal() for 1D
|
||
stop = i2 > Segment::maxWidth*Segment::maxHeight ? min(i2,strip.getLengthTotal()) : (i2 > Segment::maxWidth ? Segment::maxWidth : max((uint16_t)1,i2)); // WLEDMM: use native min/max
|
||
startY = 0;
|
||
stopY = 1;
|
||
#ifndef WLED_DISABLE_2D
|
||
if (Segment::maxHeight>1) { // 2D
|
||
if (i1Y < Segment::maxHeight) startY = i1Y;
|
||
stopY = i2Y > Segment::maxHeight ? Segment::maxHeight : max((uint16_t)1,i2Y); // WLEDMM: use native min/max
|
||
}
|
||
#endif
|
||
if (grp) {
|
||
grouping = grp;
|
||
spacing = spc;
|
||
}
|
||
if (ofs < UINT16_MAX) offset = ofs;
|
||
|
||
if (!boundsUnchanged) {
|
||
markForReset();
|
||
refreshLightCapabilities();
|
||
}
|
||
}
|
||
|
||
|
||
bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed
|
||
if (slot >= NUM_COLORS || c == colors[slot]) return false;
|
||
if (!_isRGB && !_hasW) {
|
||
if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black
|
||
if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black
|
||
}
|
||
if (fadeTransition && on) startTransition(strip.getTransition()); // start transition prior to change // WLEDMM only on real change
|
||
colors[slot] = c;
|
||
stateChanged = true; // send UDP/WS broadcast
|
||
return true;
|
||
}
|
||
|
||
void Segment::setCCT(uint16_t k) {
|
||
if (k > 255) { //kelvin value, convert to 0-255
|
||
if (k < 1900) k = 1900;
|
||
if (k > 10091) k = 10091;
|
||
k = (k - 1900) >> 5;
|
||
}
|
||
if (cct == k) return;
|
||
if (fadeTransition && on) startTransition(strip.getTransition()); // start transition prior to change
|
||
cct = k;
|
||
stateChanged = true; // send UDP/WS broadcast
|
||
}
|
||
|
||
void Segment::setOpacity(uint8_t o) {
|
||
if (opacity == o) return;
|
||
if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change
|
||
opacity = o;
|
||
stateChanged = true; // send UDP/WS broadcast
|
||
}
|
||
|
||
void Segment::setOption(uint8_t n, bool val) {
|
||
bool prevOn = on;
|
||
if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change
|
||
if (val) options |= 0x01 << n;
|
||
else options &= ~(0x01 << n);
|
||
if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET || n == SEG_OPTION_TRANSITIONAL)) stateChanged = true; // send UDP/WS broadcast
|
||
}
|
||
|
||
void Segment::setMode(uint8_t fx, bool loadDefaults, bool sliderDefaultsOnly) {
|
||
//WLEDMM: return to old setting if not explicitly set
|
||
static int16_t oldMap = -1;
|
||
static int16_t oldSim = -1;
|
||
static int16_t oldPalette = -1;
|
||
// if we have a valid mode & is not reserved
|
||
if (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4)) {
|
||
if (fx != mode) {
|
||
startTransition(strip.getTransition()); // set effect transitions
|
||
//markForReset(); // transition will handle this
|
||
mode = fx;
|
||
|
||
// load default values from effect string
|
||
if (loadDefaults) {
|
||
int16_t sOpt;
|
||
sOpt = extractModeDefaults(fx, "sx"); speed = (sOpt >= 0) ? sOpt : DEFAULT_SPEED;
|
||
sOpt = extractModeDefaults(fx, "ix"); intensity = (sOpt >= 0) ? sOpt : DEFAULT_INTENSITY;
|
||
sOpt = extractModeDefaults(fx, "c1"); custom1 = (sOpt >= 0) ? sOpt : DEFAULT_C1;
|
||
sOpt = extractModeDefaults(fx, "c2"); custom2 = (sOpt >= 0) ? sOpt : DEFAULT_C2;
|
||
sOpt = extractModeDefaults(fx, "c3"); custom3 = (sOpt >= 0) ? sOpt : DEFAULT_C3;
|
||
sOpt = extractModeDefaults(fx, "o1"); check1 = (sOpt >= 0) ? (bool)sOpt : false;
|
||
sOpt = extractModeDefaults(fx, "o2"); check2 = (sOpt >= 0) ? (bool)sOpt : false;
|
||
sOpt = extractModeDefaults(fx, "o3"); check3 = (sOpt >= 0) ? (bool)sOpt : false;
|
||
if (!sliderDefaultsOnly) {
|
||
//WLEDMM: return to old setting if not explicitly set
|
||
sOpt = extractModeDefaults(fx, "m12"); if (sOpt >= 0) {if (oldMap==-1) oldMap = map1D2D; map1D2D = constrain(sOpt, 0, 7);} else {if (oldMap!=-1) map1D2D = oldMap; oldMap = -1;}
|
||
sOpt = extractModeDefaults(fx, "si"); if (sOpt >= 0) {if (oldSim==-1) oldSim = soundSim; soundSim = constrain(sOpt, 0, 1);} else {if (oldSim!=-1) soundSim = oldSim; oldSim = -1;}
|
||
sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) reverse = (bool)sOpt;
|
||
sOpt = extractModeDefaults(fx, "mi"); if (sOpt >= 0) mirror = (bool)sOpt; // NOTE: setting this option is a risky business
|
||
sOpt = extractModeDefaults(fx, "rY"); if (sOpt >= 0) reverse_y = (bool)sOpt;
|
||
sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business
|
||
sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) {if (oldPalette==-1) oldPalette = palette; setPalette(sOpt);} else {if (oldPalette!=-1) setPalette(oldPalette); oldPalette = -1;}
|
||
}
|
||
}
|
||
/*if (!fadeTransition)*/ markForReset(); // WLEDMM quickfix for effect "double startup" bug.
|
||
stateChanged = true; // send UDP/WS broadcast
|
||
}
|
||
}
|
||
}
|
||
|
||
void Segment::setPalette(uint8_t pal) {
|
||
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes
|
||
if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes
|
||
if (pal != palette) {
|
||
if (strip.paletteFade && on) startTransition(strip.getTransition());
|
||
palette = pal;
|
||
stateChanged = true; // send UDP/WS broadcast
|
||
}
|
||
}
|
||
|
||
// 2D matrix
|
||
|
||
//
|
||
// WLEDMM Segment::virtualWidth() and Segment::virtualHeight() are declared inline, see FX.h
|
||
//
|
||
|
||
uint16_t Segment::nrOfVStrips() const {
|
||
uint16_t vLen = 1;
|
||
#ifndef WLED_DISABLE_2D
|
||
if (is2D()) {
|
||
switch (map1D2D) {
|
||
case M12_pBar:
|
||
vLen = calc_virtualWidth();
|
||
break;
|
||
case M12_sCircle: //WLEDMM
|
||
vLen = (calc_virtualWidth() + calc_virtualHeight()) / 6; // take third of the average width
|
||
break;
|
||
case M12_sBlock: //WLEDMM
|
||
vLen = (calc_virtualWidth() + calc_virtualHeight()) / 8; // take half of the average width
|
||
break;
|
||
}
|
||
}
|
||
#endif
|
||
return vLen;
|
||
}
|
||
|
||
//WLEDMM jMap
|
||
struct XandY {
|
||
uint8_t x;
|
||
uint8_t y;
|
||
};
|
||
struct ArrayAndSize {
|
||
uint16_t size;
|
||
XandY *array;
|
||
};
|
||
class JMapC {
|
||
public:
|
||
char previousSegmentName[50] = "";
|
||
|
||
~JMapC() {
|
||
DEBUG_PRINTLN("~JMapC");
|
||
deletejVectorMap();
|
||
}
|
||
void deletejVectorMap() {
|
||
if (jVectorMap.size() > 0) {
|
||
DEBUG_PRINTLN("delete jVectorMap");
|
||
for (size_t i=0; i<jVectorMap.size(); i++)
|
||
if (jVectorMap[i].array) { delete[] jVectorMap[i].array; jVectorMap[i].array = nullptr; } // softhack007 quickfix for memory leak
|
||
jVectorMap.clear();
|
||
}
|
||
}
|
||
uint16_t length() {
|
||
updatejMapDoc();
|
||
size_t size = jVectorMap.size();
|
||
if (size > 0)
|
||
return size;
|
||
else
|
||
return SEGMENT.calc_virtualWidth() * SEGMENT.calc_virtualHeight(); // calc pixel sizes
|
||
}
|
||
void setPixelColor(uint16_t i, uint32_t col) {
|
||
updatejMapDoc();
|
||
if (jVectorMap.size() > i) {
|
||
if (i==0) {
|
||
SEGMENT.fadeToBlackBy(10); //as not all pixels used
|
||
}
|
||
for (int j=0; j<jVectorMap[i].size; j++) {
|
||
SEGMENT.setPixelColorXY(jVectorMap[i].array[j].x * scale, jVectorMap[i].array[j].y * scale, col);
|
||
}
|
||
}
|
||
}
|
||
uint32_t getPixelColor(uint16_t i) {
|
||
updatejMapDoc();
|
||
if (jVectorMap.size() > i) // implies jVectorMap.size() > 0, because i is unsigned
|
||
return SEGMENT.getPixelColorXY(jVectorMap[i].array[0].x * scale, jVectorMap[i].array[0].y * scale);
|
||
else
|
||
return 0;
|
||
}
|
||
private:
|
||
std::vector<ArrayAndSize> jVectorMap;
|
||
StaticJsonDocument<4096> docChunk; //must fit forks with about 32 points each
|
||
uint8_t scale=1;
|
||
|
||
void updatejMapDoc() {
|
||
if (SEGMENT.name == nullptr && jVectorMap.size() > 0) {
|
||
deletejVectorMap();
|
||
}
|
||
else if (SEGMENT.name != nullptr && strcmp(SEGMENT.name, previousSegmentName) != 0) {
|
||
uint32_t dataSize = 0;
|
||
deletejVectorMap();
|
||
DEBUG_PRINT("New "); DEBUG_PRINTLN(SEGMENT.name);
|
||
char jMapFileName[50] = {'\0'}; // we need at most 32 + 7 bytes
|
||
strcpy(jMapFileName, "/");
|
||
strcat(jMapFileName, SEGMENT.name);
|
||
strcat(jMapFileName, ".json");
|
||
File jMapFile;
|
||
jMapFile = WLED_FS.open(jMapFileName, "r");
|
||
|
||
uint_fast16_t maxWidth = 0; // WLEDMM fix uint8 overflow for large width/height
|
||
uint_fast16_t maxHeight = 0; // WLEDMM
|
||
|
||
//https://arduinojson.org/v6/how-to/deserialize-a-very-large-document/
|
||
jMapFile.find("[");
|
||
do { //for each element in the array
|
||
DeserializationError err = deserializeJson(docChunk, jMapFile);
|
||
// serializeJson(docChunk, Serial); USER_PRINTLN();
|
||
// USER_PRINTF("docChunk %u / %u%% (%u %u %u) %u\n", (unsigned int)docChunk.memoryUsage(), 100 * docChunk.memoryUsage() / docChunk.capacity(), (unsigned int)docChunk.size(), docChunk.overflowed(), (unsigned int)docChunk.nesting(), jMapFile.size());
|
||
if (err)
|
||
{
|
||
USER_PRINTF("deserializeJson() of parseTree failed with code %s\n", err.c_str());
|
||
USER_FLUSH();
|
||
// softhack007: DO NOT delete SEGMENT.name - it's owned by Segment class, deleting it from outside can lead to use-after-free
|
||
// if (SEGMENT.name) delete[] SEGMENT.name; SEGMENT.name = nullptr; //need to clear the name as otherwise continuously loaded // softhack007 avoid deleting nullptr
|
||
strlcpy(previousSegmentName, SEGMENT.name, sizeof(previousSegmentName)); // Mark name as processed to avoid reload loop
|
||
if (jMapFile) jMapFile.close(); // make sure the file is closed
|
||
return;
|
||
}
|
||
|
||
if (docChunk.is<JsonArray>()) { //each item is or an array of arrays (fork) or an array of x,y (no fork)
|
||
//fill the vector with arrays and get the width and height of the jMap
|
||
|
||
JsonArray arrayChunk = docChunk.as<JsonArray>();
|
||
ArrayAndSize arrayAndSize;
|
||
arrayAndSize.size = 0;
|
||
if (arrayChunk[0].is<JsonArray>()) { //if array of arrays
|
||
arrayAndSize.array = new(std::nothrow) XandY[arrayChunk.size()];
|
||
for (JsonVariant arrayElement: arrayChunk) {
|
||
maxWidth = max((uint16_t)maxWidth, arrayElement[0].as<uint16_t>()); // WLEDMM use native min/max
|
||
maxHeight = max((uint16_t)maxHeight, arrayElement[1].as<uint16_t>()); // WLEDMM
|
||
arrayAndSize.array[arrayAndSize.size].x = arrayElement[0].as<uint8_t>();
|
||
arrayAndSize.array[arrayAndSize.size].y = arrayElement[1].as<uint8_t>();
|
||
arrayAndSize.size++;
|
||
dataSize += sizeof(XandY);
|
||
}
|
||
}
|
||
else { // if array (of x and y)
|
||
arrayAndSize.array = new(std::nothrow) XandY[1];
|
||
maxWidth = max((uint16_t)maxWidth, arrayChunk[0].as<uint16_t>()); // WLEDMM use native min/max
|
||
maxHeight = max((uint16_t)maxHeight, arrayChunk[1].as<uint16_t>()); // WLEDMM
|
||
arrayAndSize.array[arrayAndSize.size].x = arrayChunk[0].as<uint8_t>();
|
||
arrayAndSize.array[arrayAndSize.size].y = arrayChunk[1].as<uint8_t>();
|
||
arrayAndSize.size++;
|
||
dataSize += sizeof(XandY);
|
||
}
|
||
jVectorMap.push_back(arrayAndSize);
|
||
dataSize += sizeof(arrayAndSize);
|
||
}
|
||
|
||
} while (jMapFile.findUntil(",", "]"));
|
||
|
||
jMapFile.close();
|
||
|
||
maxWidth++; maxHeight++;
|
||
scale = min(SEGMENT.calc_virtualWidth() / maxWidth, SEGMENT.calc_virtualHeight() / maxHeight); // WLEDMM re-calc width/heiht from active settings
|
||
dataSize += sizeof(jVectorMap);
|
||
USER_PRINT("dataSize ");
|
||
USER_PRINT(dataSize);
|
||
USER_PRINT(" scale ");
|
||
USER_PRINTLN(scale);
|
||
strcpy(previousSegmentName, SEGMENT.name);
|
||
}
|
||
} //updatejMapDoc
|
||
}; //class JMapC
|
||
|
||
//WLEDMM jMap
|
||
void Segment::createjMap() {
|
||
if (!jMap) {
|
||
DEBUG_PRINTLN("createjMap");
|
||
jMap = new(std::nothrow) JMapC();
|
||
}
|
||
}
|
||
|
||
//WLEDMM jMap
|
||
void Segment::deletejMap() {
|
||
//Should be called from ~Segment but causes crash (and ~Segment is called quite often...)
|
||
if (jMap) {
|
||
DEBUG_PRINTLN("deletejMap");
|
||
delete (JMapC *)jMap; jMap = nullptr;
|
||
}
|
||
}
|
||
|
||
|
||
// Constants for mapping mode "Pinwheel"
|
||
#ifndef WLED_DISABLE_2D
|
||
constexpr int Pinwheel_Steps_Small = 72; // no holes up to 16x16
|
||
constexpr int Pinwheel_Size_Small = 16; // larger than this -> use "Medium"
|
||
constexpr int Pinwheel_Steps_Medium = 192; // no holes up to 32x32
|
||
constexpr int Pinwheel_Size_Medium = 32; // larger than this -> use "Big"
|
||
constexpr int Pinwheel_Steps_Big = 304; // no holes up to 50x50
|
||
constexpr int Pinwheel_Size_Big = 50; // larger than this -> use "XL"
|
||
constexpr int Pinwheel_Steps_XL = 368;
|
||
constexpr int Pinwheel_Size_XL = 58; // larger than this -> use "XXL"
|
||
constexpr int Pinwheel_Steps_XXL = 456;
|
||
constexpr int Pinwheel_Size_XXL = 68; // larger than this -> use "LL"
|
||
constexpr int Pinwheel_Steps_LL = 592; // 128x64 no holes, 28fps
|
||
constexpr float Int_to_Rad_Small = (DEG_TO_RAD * 360) / Pinwheel_Steps_Small; // conversion: from 0...72 to Radians
|
||
constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360) / Pinwheel_Steps_Medium; // conversion: from 0...192 to Radians
|
||
constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360) / Pinwheel_Steps_Big; // conversion: from 0...304 to Radians
|
||
constexpr float Int_to_Rad_XL = (DEG_TO_RAD * 360) / Pinwheel_Steps_XL; // conversion: from 0...368 to Radians
|
||
constexpr float Int_to_Rad_XXL = (DEG_TO_RAD * 360) / Pinwheel_Steps_XXL; // conversion: from 0...456 to Radians
|
||
constexpr float Int_to_Rad_LL = (DEG_TO_RAD * 360) / Pinwheel_Steps_LL; // conversion: from 0...592 to Radians
|
||
|
||
constexpr int Fixed_Scale = 32768; // fixpoint scaling factor (15bit for fraction)
|
||
|
||
// Pinwheel helper function: pixel index to radians
|
||
static float getPinwheelAngle(int i, int vW, int vH) {
|
||
int maxXY = max(vW, vH);
|
||
if (maxXY <= Pinwheel_Size_Small) return float(i) * Int_to_Rad_Small;
|
||
if (maxXY <= Pinwheel_Size_Medium) return float(i) * Int_to_Rad_Med;
|
||
if (maxXY <= Pinwheel_Size_Big) return float(i) * Int_to_Rad_Big;
|
||
if (maxXY <= Pinwheel_Size_XL) return float(i) * Int_to_Rad_XL;
|
||
if (maxXY <= Pinwheel_Size_XXL) return float(i) * Int_to_Rad_XXL;
|
||
// else
|
||
return float(i) * Int_to_Rad_LL;
|
||
}
|
||
// Pinwheel helper function: matrix dimensions to number of rays
|
||
static int getPinwheelLength(int vW, int vH) {
|
||
int maxXY = max(vW, vH);
|
||
if (maxXY <= Pinwheel_Size_Small) return Pinwheel_Steps_Small;
|
||
if (maxXY <= Pinwheel_Size_Medium) return Pinwheel_Steps_Medium;
|
||
if (maxXY <= Pinwheel_Size_Big) return Pinwheel_Steps_Big;
|
||
if (maxXY <= Pinwheel_Size_XL) return Pinwheel_Steps_XL;
|
||
if (maxXY <= Pinwheel_Size_XXL) return Pinwheel_Steps_XXL;
|
||
// else
|
||
return Pinwheel_Steps_LL;
|
||
}
|
||
#endif
|
||
|
||
// 1D strip
|
||
uint16_t Segment::calc_virtualLength() const {
|
||
#ifndef WLED_DISABLE_2D
|
||
if (is2D()) {
|
||
uint16_t vW = calc_virtualWidth();
|
||
uint16_t vH = calc_virtualHeight();
|
||
uint16_t vLen = vW * vH; // use all pixels from segment
|
||
switch (map1D2D) {
|
||
case M12_pBar:
|
||
vLen = vH;
|
||
break;
|
||
case M12_pCorner:
|
||
vLen = max(vW,vH); // get the longest dimension
|
||
break;
|
||
case M12_pArc:
|
||
{ unsigned vLen2 = vW * vW + vH * vH; // length ^2
|
||
if (vLen2 < UINT16_MAX) vLen = sqrt16(vLen2); // use faster function for 16bit values
|
||
else vLen = sqrtf(vLen2); // fall-back to float if bigger
|
||
if (vW != vH) vLen++; // round up
|
||
}
|
||
break;
|
||
case M12_jMap: //WLEDMM jMap
|
||
if (jMap)
|
||
vLen = ((JMapC *)jMap)->length();
|
||
break;
|
||
case M12_sCircle: //WLEDMM
|
||
vLen = max(vW,vH); // get the longest dimension
|
||
// vLen = (virtualWidth() + virtualHeight()) * 3;
|
||
break;
|
||
case M12_sBlock: //WLEDMM
|
||
if (nrOfVStrips()>1)
|
||
vLen = max(vW,vH) * 4;//0.5; // get the longest dimension
|
||
else
|
||
vLen = max(vW,vH) * 0.5f; // get the longest dimension
|
||
break;
|
||
case M12_sPinwheel:
|
||
vLen = getPinwheelLength(vW, vH);
|
||
break;
|
||
}
|
||
return vLen;
|
||
}
|
||
#endif
|
||
uint16_t groupLen = groupLength();
|
||
uint16_t vLength = (length() + groupLen - 1) / groupLen;
|
||
if (mirror && width() > 1) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED // WLEDMM bugfix for pseudo 2d strips
|
||
return vLength;
|
||
}
|
||
|
||
//WLEDMM used for M12_sBlock
|
||
static void xyFromBlock(uint16_t &x,uint16_t &y, uint16_t i, uint16_t vW, uint16_t vH, uint16_t vStrip) {
|
||
float i2;
|
||
if (i<=SEGLEN*0.25f) { //top, left to right
|
||
i2 = i/(SEGLEN*0.25f);
|
||
x = vW / 2 - vStrip - 1 + i2 * vStrip * 2;
|
||
y = vH / 2 - vStrip - 1;
|
||
}
|
||
else if (i <= SEGLEN * 0.5f) { //right, top to bottom
|
||
i2 = (i-SEGLEN*0.25f)/(SEGLEN*0.25f);
|
||
x = vW / 2 + vStrip;
|
||
y = vH / 2 - vStrip - 1 + i2 * vStrip * 2;
|
||
}
|
||
else if (i <= SEGLEN * 0.75f) { //bottom, right to left
|
||
i2 = (i-SEGLEN*0.5f)/(SEGLEN*0.25f);
|
||
x = vW / 2 + vStrip - i2 * vStrip * 2;
|
||
y = vH / 2 + vStrip;
|
||
}
|
||
else if (i <= SEGLEN) { //left, bottom to top
|
||
i2 = (i-SEGLEN*0.75f)/(SEGLEN*0.25f);
|
||
x = vW / 2 - vStrip - 1;
|
||
y = vH / 2 + vStrip - i2 * vStrip * 2;
|
||
}
|
||
// softhack007 not sure if clamping is necessary
|
||
//x = min(x, uint16_t(vW-1)); // clamp x at vW-1
|
||
//y = min(y, uint16_t(vH-1)); // clamp y at vH-1
|
||
}
|
||
|
||
void IRAM_ATTR_YN WLED_O2_ATTR __attribute__((hot)) Segment::setPixelColor(int i, uint32_t col) //WLEDMM: IRAM_ATTR conditionally
|
||
{
|
||
if (!isActive()) return; // not active
|
||
#ifndef WLED_DISABLE_2D
|
||
int vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows)
|
||
#endif
|
||
i &= 0xFFFF;
|
||
if (unsigned(i) >= virtualLength()) return; // if pixel would fall out of segment just exit //WLEDMM unsigned(i)>SEGLEN also catches "i<0"
|
||
|
||
#ifndef WLED_DISABLE_2D
|
||
if (is2D()) {
|
||
uint16_t vH = virtualHeight(); // segment height in logical pixels
|
||
uint16_t vW = virtualWidth();
|
||
switch (map1D2D) {
|
||
case M12_Pixels:
|
||
// use all available pixels as a long strip
|
||
setPixelColorXY(i % vW, i / vW, col);
|
||
break;
|
||
case M12_pBar:
|
||
// expand 1D effect vertically or have it play on virtual strips
|
||
if (vStrip>0) setPixelColorXY(vStrip - 1, vH - i - 1, col);
|
||
else drawLine(0,vH-i-1, vW-1,vH-i-1, col, false); // WLEDMM draw line instead of plotting each pixel
|
||
break;
|
||
case M12_pArc:
|
||
// expand in circular fashion from center
|
||
if (i==0)
|
||
setPixelColorXY(0, 0, col);
|
||
else {
|
||
if (i == virtualLength() - 1) setPixelColorXY(vW-1, vH-1, col); // Last i always fill corner
|
||
if (!_isSuperSimpleSegment) {
|
||
// WLEDMM: drawArc() is faster if it's NOT "super simple" as the regular M12_pArc
|
||
// can do "useSymmetry" to speed things along, but a more complicated segment likey
|
||
// uses mirroring which generates a symmetry speed-up, or other things which mean
|
||
// less pixels are calculated.
|
||
drawArc(0, 0, i, col);
|
||
} else {
|
||
//WLEDMM: some optimizations for the drawing loop
|
||
// pre-calculate loop limits, exploit symmetry at 45deg
|
||
float radius = float(i);
|
||
|
||
// float step = HALF_PI / (2.85f * radius); // upstream uses this
|
||
float step = HALF_PI / (M_PI * radius); // WLEDMM we use the correct circumference
|
||
bool useSymmetry = (max(vH, vW) > 20); // for segments wider than 20 pixels, we exploit symmetry
|
||
unsigned numSteps;
|
||
if (useSymmetry) numSteps = 1 + ((HALF_PI/2.0f + step/2.0f) / step); // with symmetry
|
||
else numSteps = 1 + ((HALF_PI + step/2.0f) / step); // without symmetry
|
||
|
||
float rad = 0.0f;
|
||
for (unsigned count = 0; count < numSteps; count++) {
|
||
// may want to try float version as well (with or without antialiasing)
|
||
// int x = max(0, min(vW-1, (int)roundf(sinf(rad) * radius)));
|
||
// int y = max(0, min(vH-1, (int)roundf(cosf(rad) * radius)));
|
||
int x = roundf(sinf(rad) * radius);
|
||
int y = roundf(cosf(rad) * radius);
|
||
setPixelColorXY(x, y, col);
|
||
if(useSymmetry) setPixelColorXY(y, x, col);// WLEDMM
|
||
rad += step;
|
||
}
|
||
|
||
// // Bresenham’s Algorithm (may not fill every pixel)
|
||
// int d = 3 - (2*i);
|
||
// int y = i, x = 0;
|
||
// while (y >= x) {
|
||
// setPixelColorXY(x, y, col);
|
||
// setPixelColorXY(y, x, col);
|
||
// x++;
|
||
// if (d > 0) {
|
||
// y--;
|
||
// d += 4 * (x - y) + 10;
|
||
// } else {
|
||
// d += 4 * x + 6;
|
||
// }
|
||
// }
|
||
}
|
||
}
|
||
break;
|
||
case M12_pCorner: {
|
||
int x = min(i, vW-1);
|
||
int y = min(i, vH-1);
|
||
if (i <= y) drawLine(0,y, x,y, col, false); // botton line (if visible)
|
||
if (i <= x) drawLine(x,0, x,y, col, false); // right line (if visible)
|
||
}
|
||
break;
|
||
case M12_jMap: //WLEDMM jMap
|
||
if (jMap)
|
||
((JMapC *)jMap)->setPixelColor(i, col);
|
||
break;
|
||
case M12_sCircle: //WLEDMM
|
||
if (vStrip > 0)
|
||
{
|
||
int x = roundf(sinf(360*i/SEGLEN*DEG_TO_RAD) * vW * (vStrip+1)/nrOfVStrips());
|
||
int y = roundf(cosf(360*i/SEGLEN*DEG_TO_RAD) * vW * (vStrip+1)/nrOfVStrips());
|
||
setPixelColorXY(x + vW/2, y + vH/2, col);
|
||
}
|
||
else // pArc -> circle
|
||
drawArc(vW/2, vH/2, i/2, col);
|
||
break;
|
||
case M12_sBlock: //WLEDMM
|
||
if (vStrip > 0)
|
||
{
|
||
//vStrip+1 is distance from centre, i is how much of the square is filled
|
||
uint16_t x=0,y=0;
|
||
xyFromBlock(x,y, i, vW, vH, (vStrip+1)*2);
|
||
setPixelColorXY(x, y, col);
|
||
}
|
||
else { // pCorner -> block
|
||
int centerX = (vW+1)/2 - 1;
|
||
int centerY = (vH+1)/2 - 1;
|
||
int xLeft = max(centerX-i, 0);
|
||
int yTop = max(centerY-i, 0);
|
||
int xRight = min(centerX+i+1, vW-1);
|
||
int yBottom = min(centerY+i+1, vH-1);
|
||
|
||
if (yTop == centerY-i) drawLine(xLeft,yTop, xRight, yTop, col); // top and bottom horizontal lines, if visible
|
||
if (yBottom == centerY+i+1) drawLine(xLeft,yBottom, xRight, yBottom, col);
|
||
if (xLeft == centerX-i) drawLine(xLeft,yTop, xLeft, yBottom, col); // left and right vertical lines, if visible
|
||
if (xRight == centerX+i+1) drawLine(xRight,yTop, xRight, yBottom, col);
|
||
}
|
||
break;
|
||
case M12_sPinwheel: {
|
||
// WLEDMM shortcut when no grouping/spacing used
|
||
bool simpleSegment = (grouping == 1) && (spacing == 0);
|
||
uint32_t scaled_col = col;
|
||
if (simpleSegment) {
|
||
// segment brightness must be pre-calculated for the "fast" setPixelColorXY variant!
|
||
uint8_t _bri_t = currentBri(on ? opacity : 0);
|
||
if (!_bri_t && !transitional) return;
|
||
if (_bri_t < 255) scaled_col = color_fade(col, _bri_t);
|
||
}
|
||
|
||
// i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small)
|
||
float centerX = roundf((vW-1) / 2.0f);
|
||
float centerY = roundf((vH-1) / 2.0f);
|
||
float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians
|
||
float cosVal = cosf(angleRad);
|
||
float sinVal = sinf(angleRad);
|
||
|
||
// avoid re-painting the same pixel
|
||
int lastX = INT_MIN; // impossible position
|
||
int lastY = INT_MIN; // impossible position
|
||
// draw line at angle, starting at center and ending at the segment edge
|
||
// we use fixed point math for better speed. Starting distance is 0.5 for better rounding
|
||
// int_fast16_t and int_fast32_t types changed to int, minimum bits commented
|
||
int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit
|
||
int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit
|
||
int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit
|
||
int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit
|
||
|
||
int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint
|
||
int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint
|
||
|
||
// Odd rays start further from center if prevRay started at center.
|
||
static int prevRay = INT_MIN; // previous ray number
|
||
if ((i % 2 == 1) && (i - 1 == prevRay || i + 1 == prevRay)) {
|
||
int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel
|
||
posx += inc_x * jump;
|
||
posy += inc_y * jump;
|
||
}
|
||
prevRay = i;
|
||
|
||
// draw ray until we hit any edge
|
||
while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) {
|
||
// scale down to integer (compiler will replace division with appropriate bitshift)
|
||
int x = posx / Fixed_Scale;
|
||
int y = posy / Fixed_Scale;
|
||
// set pixel
|
||
if (x != lastX || y != lastY) { // only paint if pixel position is different
|
||
if (simpleSegment) setPixelColorXY_fast(x, y, col, scaled_col, vW, vH);
|
||
else setPixelColorXY_slow(x, y, col);
|
||
}
|
||
lastX = x;
|
||
lastY = y;
|
||
// advance to next position
|
||
posx += inc_x;
|
||
posy += inc_y;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
return;
|
||
} else if (Segment::maxHeight!=1 && (width()==1 || height()==1)) {
|
||
if (start < Segment::maxWidth*Segment::maxHeight) {
|
||
// we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed)
|
||
int x = 0, y = 0;
|
||
if (virtualHeight()>1) y = i;
|
||
else if (virtualWidth() >1) x = i;
|
||
setPixelColorXY(x, y, col);
|
||
return;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
if (ledsrgb) ledsrgb[i] = col;
|
||
|
||
uint16_t len = length();
|
||
uint8_t _bri_t = currentBri(on ? opacity : 0);
|
||
if (!_bri_t && !transitional && fadeTransition) return; // if _bri_t == 0 && segment is not transitioning && transitions are enabled then save a few CPU cycles
|
||
if (_bri_t < 255) {
|
||
col = color_fade(col, _bri_t);
|
||
}
|
||
|
||
// expand pixel (taking into account start, grouping, spacing [and offset])
|
||
i = i * groupLength();
|
||
if (reverse) { // is segment reversed?
|
||
if (mirror) { // is segment mirrored?
|
||
i = (len - 1) / 2 - i; //only need to index half the pixels
|
||
} else {
|
||
i = (len - 1) - i;
|
||
}
|
||
}
|
||
i += start; // starting pixel in a group
|
||
|
||
#if 0 // needs more testing
|
||
// WLEDMM shortcut when no grouping/spacing used
|
||
bool simpleSegment = !mirror && (grouping == 1) && (spacing == 0);
|
||
if (simpleSegment) {
|
||
int indexSet = i + offset; // offset/phase
|
||
if (indexSet >= stop) indexSet -= len; // wrap
|
||
strip.setPixelColor(indexSet, col);
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
// set all the pixels in the group
|
||
for (int j = 0; j < grouping; j++) {
|
||
uint16_t indexSet = i + ((reverse) ? -j : j);
|
||
if (indexSet >= start && indexSet < stop) {
|
||
if (mirror) { //set the corresponding mirrored pixel
|
||
uint16_t indexMir = stop - indexSet + start - 1;
|
||
indexMir += offset; // offset/phase
|
||
if (indexMir >= stop) indexMir -= len; // wrap
|
||
strip.setPixelColor(indexMir, col);
|
||
}
|
||
indexSet += offset; // offset/phase
|
||
if (indexSet >= stop) indexSet -= len; // wrap
|
||
strip.setPixelColor(indexSet, col);
|
||
}
|
||
}
|
||
}
|
||
|
||
// anti-aliased normalized version of setPixelColor()
|
||
void Segment::setPixelColor(float i, uint32_t col, bool aa)
|
||
{
|
||
if (!isActive()) return; // not active
|
||
int vStrip = int(i/10.0f); // hack to allow running on virtual strips (2D segment columns/rows)
|
||
i -= int(i);
|
||
|
||
if (i<0.0f || i>1.0f) return; // not normalized
|
||
|
||
float fC = i * (virtualLength()-1);
|
||
if (aa) {
|
||
uint16_t iL = roundf(fC-0.49f);
|
||
uint16_t iR = roundf(fC+0.49f);
|
||
float dL = (fC - iL)*(fC - iL);
|
||
float dR = (iR - fC)*(iR - fC);
|
||
uint32_t cIL = getPixelColor(iL | (vStrip<<16));
|
||
uint32_t cIR = getPixelColor(iR | (vStrip<<16));
|
||
if (iR!=iL) {
|
||
// blend L pixel
|
||
cIL = color_blend(col, cIL, uint8_t(dL*255.0f));
|
||
setPixelColor(iL | (vStrip<<16), cIL);
|
||
// blend R pixel
|
||
cIR = color_blend(col, cIR, uint8_t(dR*255.0f));
|
||
setPixelColor(iR | (vStrip<<16), cIR);
|
||
} else {
|
||
// exact match (x & y land on a pixel)
|
||
setPixelColor(iL | (vStrip<<16), col);
|
||
}
|
||
} else {
|
||
setPixelColor(uint16_t(roundf(fC)) | (vStrip<<16), col);
|
||
}
|
||
}
|
||
|
||
uint32_t WLED_O2_ATTR __attribute__((hot)) Segment::getPixelColor(int i) const
|
||
{
|
||
if (!isActive()) return 0; // not active
|
||
#ifndef WLED_DISABLE_2D
|
||
int vStrip = i>>16;
|
||
#endif
|
||
i &= 0xFFFF;
|
||
|
||
#ifndef WLED_DISABLE_2D
|
||
if (is2D()) {
|
||
uint16_t vH = virtualHeight(); // segment height in logical pixels
|
||
uint16_t vW = virtualWidth();
|
||
switch (map1D2D) {
|
||
case M12_Pixels:
|
||
return getPixelColorXY(i % vW, i / vW);
|
||
break;
|
||
case M12_pBar:
|
||
if (vStrip>0) return getPixelColorXY(vStrip - 1, vH - i -1);
|
||
else return getPixelColorXY(0, vH - i -1);
|
||
break;
|
||
case M12_pCorner:
|
||
case M12_pArc: {
|
||
if (i < max(vW, vH)) {
|
||
return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); // Corner and Arc
|
||
break;
|
||
}
|
||
float minradius = float(i) - 0.1f;
|
||
const int minradius2 = roundf(minradius * minradius);
|
||
int startX, startY;
|
||
if (vW >= vH) {startX = vW - 1; startY = 1;} // Last Column
|
||
else {startX = 1; startY = vH - 1;} // Last Row
|
||
// Loop through only last row/column depending on orientation
|
||
for (int x = startX; x < vW; x++) {
|
||
int newX2 = x * x;
|
||
for (int y = startY; y < vH; y++) {
|
||
int newY2 = y * y;
|
||
if (newX2 + newY2 >= minradius2) return getPixelColorXY(x, y);
|
||
}
|
||
}
|
||
return getPixelColorXY(vW-1, vH-1); // Last pixel
|
||
break;
|
||
}
|
||
case M12_jMap: //WLEDMM jMap
|
||
if (jMap)
|
||
return ((JMapC *)jMap)->getPixelColor(i);
|
||
break;
|
||
case M12_sCircle: //WLEDMM
|
||
if (vStrip > 0)
|
||
{
|
||
int x = roundf(sinf(360*i/SEGLEN*DEG_TO_RAD) * vW * (vStrip+1)/nrOfVStrips());
|
||
int y = roundf(cosf(360*i/SEGLEN*DEG_TO_RAD) * vW * (vStrip+1)/nrOfVStrips());
|
||
return getPixelColorXY(x + vW/2, y + vH/2);
|
||
}
|
||
else
|
||
return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i);
|
||
break;
|
||
case M12_sBlock: //WLEDMM
|
||
if (vStrip > 0)
|
||
{
|
||
uint16_t x=0,y=0;
|
||
xyFromBlock(x,y, i, vW, vH, (vStrip+1)*2);
|
||
return getPixelColorXY(x, y);
|
||
}
|
||
else
|
||
return getPixelColorXY(vW / 2, vH / 2 - i - 1);
|
||
break;
|
||
case M12_sPinwheel:
|
||
// not 100% accurate, returns pixel at outer edge
|
||
// i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small)
|
||
float centerX = roundf((vW-1) / 2.0f);
|
||
float centerY = roundf((vH-1) / 2.0f);
|
||
float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians
|
||
float cosVal = cosf(angleRad);
|
||
float sinVal = sinf(angleRad);
|
||
|
||
int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit
|
||
int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit
|
||
int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit
|
||
int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit
|
||
int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint
|
||
int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint
|
||
|
||
// trace ray from center until we hit any edge - to avoid rounding problems, we use the same method as in setPixelColor
|
||
int x = INT_MIN;
|
||
int y = INT_MIN;
|
||
while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) {
|
||
// scale down to integer (compiler will replace division with appropriate bitshift)
|
||
x = posx / Fixed_Scale;
|
||
y = posy / Fixed_Scale;
|
||
// advance to next position
|
||
posx += inc_x;
|
||
posy += inc_y;
|
||
}
|
||
return getPixelColorXY(x, y);
|
||
break;
|
||
}
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
if (ledsrgb) return RGBW32(ledsrgb[i].r, ledsrgb[i].g, ledsrgb[i].b, 0);
|
||
|
||
if (reverse) i = virtualLength() - i - 1;
|
||
i *= groupLength();
|
||
i += start;
|
||
/* offset/phase */
|
||
if (offset < INT16_MAX) i += offset; // WLEDMM
|
||
if ((i >= stop) && (stop>0)) i -= length(); // WLEDMM avoid negative index (stop = 0 is a possible value)
|
||
if (i<0) i=0; // WLEDMM just to be 100% sure
|
||
return strip.getPixelColorRestored(i);
|
||
}
|
||
|
||
uint8_t Segment::differs(Segment& b) const {
|
||
uint8_t d = 0;
|
||
if (start != b.start) d |= SEG_DIFFERS_BOUNDS;
|
||
if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS;
|
||
if (offset != b.offset) d |= SEG_DIFFERS_GSO;
|
||
if (grouping != b.grouping) d |= SEG_DIFFERS_GSO;
|
||
if (spacing != b.spacing) d |= SEG_DIFFERS_GSO;
|
||
if (opacity != b.opacity) d |= SEG_DIFFERS_BRI;
|
||
if (mode != b.mode) d |= SEG_DIFFERS_FX;
|
||
if (speed != b.speed) d |= SEG_DIFFERS_FX;
|
||
if (intensity != b.intensity) d |= SEG_DIFFERS_FX;
|
||
if (palette != b.palette) d |= SEG_DIFFERS_FX;
|
||
if (custom1 != b.custom1) d |= SEG_DIFFERS_FX;
|
||
if (custom2 != b.custom2) d |= SEG_DIFFERS_FX;
|
||
if (custom3 != b.custom3) d |= SEG_DIFFERS_FX;
|
||
if (startY != b.startY) d |= SEG_DIFFERS_BOUNDS;
|
||
if (stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS;
|
||
|
||
//bit pattern: (msb first) set:2, sound:1, mapping:3, transposed, mirrorY, reverseY, [transitional, reset,] paused, mirrored, on, reverse, [selected]
|
||
if ((options & 0b1111111110011110U) != (b.options & 0b1111111110011110U)) d |= SEG_DIFFERS_OPT;
|
||
if ((options & 0x0001U) != (b.options & 0x0001U)) d |= SEG_DIFFERS_SEL;
|
||
for (uint8_t i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL;
|
||
|
||
return d;
|
||
}
|
||
|
||
void Segment::refreshLightCapabilities() {
|
||
uint8_t capabilities = 0;
|
||
uint16_t segStartIdx = 0xFFFFU;
|
||
uint16_t segStopIdx = 0;
|
||
|
||
if (!isActive()) {
|
||
_capabilities = 0;
|
||
return;
|
||
}
|
||
|
||
if (start < Segment::maxWidth * Segment::maxHeight) {
|
||
// we are withing 2D matrix (includes 1D segments)
|
||
for (int y = startY; y < stopY; y++) for (int x = start; x < stop; x++) {
|
||
uint16_t index = x + Segment::maxWidth * y;
|
||
if (index < strip.customMappingSize) index = strip.customMappingTable[index]; // convert logical address to physical
|
||
if (index < 0xFFFFU) {
|
||
if (segStartIdx > index) segStartIdx = index;
|
||
if (segStopIdx < index) segStopIdx = index;
|
||
}
|
||
if (segStartIdx == segStopIdx) segStopIdx++; // we only have 1 pixel segment
|
||
}
|
||
} else {
|
||
// we are on the strip located after the matrix
|
||
segStartIdx = start;
|
||
segStopIdx = stop;
|
||
}
|
||
|
||
for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
|
||
Bus *bus = busses.getBus(b);
|
||
if (bus == nullptr || bus->getLength()==0) break;
|
||
if (!bus->isOk()) continue;
|
||
if (bus->getStart() >= segStopIdx) continue;
|
||
if (bus->getStart() + bus->getLength() <= segStartIdx) continue;
|
||
|
||
//uint8_t type = bus->getType();
|
||
if (bus->hasRGB() || (cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB;
|
||
if (!cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT;
|
||
if (correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider)
|
||
if (bus->hasWhite()) {
|
||
uint8_t aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode();
|
||
bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed
|
||
// if auto white calculation from RGB is active (Accurate/Brighter), force RGB controls even if there are no RGB busses
|
||
if (!whiteSlider) capabilities |= SEG_CAPABILITY_RGB;
|
||
// if auto white calculation from RGB is disabled/optional (None/Dual), allow white channel adjustments
|
||
if ( whiteSlider) capabilities |= SEG_CAPABILITY_W;
|
||
}
|
||
}
|
||
_capabilities = capabilities;
|
||
}
|
||
|
||
/*
|
||
* Fills segment with color - WLEDMM using faster sPC if possible
|
||
*/
|
||
void __attribute__((hot)) Segment::fill(uint32_t c) {
|
||
if (!isActive()) return; // not active
|
||
|
||
const uint_fast16_t cols = is2D() ? virtualWidth() : virtualLength(); // WLEDMM use fast int types
|
||
const uint_fast16_t rows = virtualHeight(); // will be 1 for 1D
|
||
|
||
if (is2D()) {
|
||
// pre-calculate scaled color
|
||
uint32_t scaled_col = c;
|
||
bool simpleSegment = (grouping == 1) && (spacing == 0);
|
||
if (simpleSegment) {
|
||
uint8_t _bri_t = currentBri(on ? opacity : 0);
|
||
if (!_bri_t && !transitional) return;
|
||
if (_bri_t < 255) scaled_col = color_fade(c, _bri_t);
|
||
}
|
||
// fill 2D segment
|
||
for(unsigned y = 0; y < rows; y++) for (unsigned x = 0; x < cols; x++) {
|
||
if (simpleSegment) setPixelColorXY_fast(x, y, c, scaled_col, cols, rows);
|
||
else setPixelColorXY_slow(x, y, c);
|
||
}
|
||
} else { // fill 1D strip
|
||
for (unsigned x = 0; x < cols; x++) setPixelColor(int(x), c);
|
||
}
|
||
}
|
||
|
||
// Blends the specified color with the existing pixel color.
|
||
void Segment::blendPixelColor(int n, uint32_t color, uint8_t blend) {
|
||
if (blend == UINT8_MAX) setPixelColor(n, color);
|
||
else setPixelColor(n, color_blend(getPixelColor(n), color, blend));
|
||
}
|
||
|
||
// Adds the specified color with the existing pixel color perserving color balance.
|
||
void Segment::addPixelColor(int n, uint32_t color, bool fast) {
|
||
if (!isActive()) return; // not active
|
||
uint32_t col = getPixelColor(n);
|
||
uint8_t r = R(col);
|
||
uint8_t g = G(col);
|
||
uint8_t b = B(col);
|
||
uint8_t w = W(col);
|
||
if (fast) {
|
||
r = qadd8(r, R(color));
|
||
g = qadd8(g, G(color));
|
||
b = qadd8(b, B(color));
|
||
w = qadd8(w, W(color));
|
||
col = RGBW32(r,g,b,w);
|
||
} else {
|
||
col = color_add(col, color);
|
||
}
|
||
setPixelColor(n, col);
|
||
}
|
||
|
||
void Segment::fadePixelColor(uint16_t n, uint8_t fade) {
|
||
if (!isActive()) return; // not active
|
||
CRGB pix = CRGB(getPixelColor(n)).nscale8_video(fade);
|
||
setPixelColor(n, pix);
|
||
}
|
||
|
||
/*
|
||
* fade out function, higher rate = quicker fade
|
||
*/
|
||
void __attribute__((hot)) Segment::fade_out(uint8_t rate) {
|
||
if (!isActive()) return; // not active
|
||
const uint_fast16_t cols = is2D() ? virtualWidth() : virtualLength(); // WLEDMM use fast int types
|
||
const uint_fast16_t rows = virtualHeight(); // will be 1 for 1D
|
||
|
||
uint_fast8_t fadeRate = (255-rate) >> 1;
|
||
float mappedRate_r = 1.0f / (float(fadeRate) +1.1f); // WLEDMM use reciprocal 1/mappedRate -> faster on non-FPU chips
|
||
|
||
uint32_t color2 = colors[1]; // SEGCOLOR(1); // target color // WLEDMM minor optimization
|
||
int w2 = W(color2);
|
||
int r2 = R(color2);
|
||
int g2 = G(color2);
|
||
int b2 = B(color2);
|
||
|
||
for (unsigned y = 0; y < rows; y++) for (unsigned x = 0; x < cols; x++) {
|
||
uint32_t color = is2D() ? getPixelColorXY(int(x), int(y)) : getPixelColor(int(x));
|
||
if (color == color2) continue; // WLEDMM speedup - pixel color = target color, so nothing to do
|
||
int w1 = W(color);
|
||
int r1 = R(color);
|
||
int g1 = G(color);
|
||
int b1 = B(color);
|
||
|
||
int wdelta = mappedRate_r * (w2 - w1); // WLEDMM use reciprocal - its faster
|
||
int rdelta = mappedRate_r * (r2 - r1);
|
||
int gdelta = mappedRate_r * (g2 - g1);
|
||
int bdelta = mappedRate_r * (b2 - b1);
|
||
|
||
// if fade isn't complete, make sure delta is at least 1 (fixes rounding issues)
|
||
wdelta += (w2 == w1) ? 0 : (w2 > w1) ? 1 : -1;
|
||
rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1;
|
||
gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1;
|
||
bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1;
|
||
uint32_t colorNew = RGBW32(r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); // WLEDMM
|
||
|
||
if (colorNew != color) { // WLEDMM speedup - do not repaint the same color
|
||
if (is2D()) setPixelColorXY(int(x), int(y), colorNew);
|
||
else setPixelColor(int(x), colorNew);
|
||
}
|
||
}
|
||
}
|
||
|
||
// fades all pixels to black using nscale8()
|
||
void __attribute__((hot)) Segment::fadeToBlackBy(uint8_t fadeBy) {
|
||
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
|
||
const uint_fast16_t cols = is2D() ? virtualWidth() : virtualLength(); // WLEDMM use fast int types
|
||
const uint_fast16_t rows = virtualHeight(); // will be 1 for 1D
|
||
const uint_fast8_t scaledown = 255-fadeBy; // WLEDMM faster to pre-compute this
|
||
|
||
// WLEDMM minor optimization
|
||
if(is2D()) {
|
||
for (unsigned y = 0; y < rows; y++) for (unsigned x = 0; x < cols; x++) {
|
||
uint32_t cc = getPixelColorXY(int(x),int(y)); // WLEDMM avoid RGBW32 -> CRGB -> RGBW32 conversion
|
||
uint32_t cc2 = color_fade(cc, scaledown); // fade
|
||
#ifdef WLEDMM_FASTPATH
|
||
if (cc2 != cc) // WLEDMM only re-paint if faded color is different - normally disabled - causes problem with text overlay
|
||
#endif
|
||
setPixelColorXY(int(x), int(y), cc2);
|
||
}
|
||
} else {
|
||
for (uint_fast16_t x = 0; x < cols; x++) {
|
||
setPixelColor((uint16_t)x, CRGB(getPixelColor((uint16_t)x)).nscale8(scaledown));
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* blurs segment content, source: FastLED colorutils.cpp
|
||
*/
|
||
void __attribute__((hot)) Segment::blur(uint8_t blur_amount, bool smear) {
|
||
if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur"
|
||
#ifndef WLED_DISABLE_2D
|
||
if (is2D()) {
|
||
// compatibility with 2D
|
||
const uint_fast32_t cols = virtualWidth();
|
||
const uint_fast32_t rows = virtualHeight();
|
||
for (uint_fast32_t i = 0; i < rows; i++) blurRow(i, blur_amount, smear); // blur all rows
|
||
for (uint_fast32_t k = 0; k < cols; k++) blurCol(k, blur_amount, smear); // blur all columns
|
||
return;
|
||
}
|
||
#endif
|
||
uint8_t keep = smear ? 255 : 255 - blur_amount;
|
||
uint8_t seep = blur_amount >> 1;
|
||
unsigned vlength = virtualLength();
|
||
uint32_t carryover = BLACK;
|
||
uint32_t lastnew;
|
||
uint32_t last;
|
||
uint32_t curnew = 0;
|
||
for (unsigned i = 0; i < vlength; i++) {
|
||
uint32_t cur = getPixelColor(i);
|
||
uint32_t part = color_fade(cur, seep);
|
||
curnew = color_fade(cur, keep);
|
||
if (i > 0) {
|
||
if (carryover)
|
||
curnew = color_add(curnew, carryover, !smear); // WLEDMM
|
||
uint32_t prev = color_add(lastnew, part, !smear); // WLEDMM
|
||
if (last != prev) // optimization: only set pixel if color has changed
|
||
setPixelColor(int(i - 1), prev);
|
||
}
|
||
else // first pixel
|
||
setPixelColor(int(i), curnew);
|
||
lastnew = curnew;
|
||
last = cur; // save original value for comparison on next iteration
|
||
carryover = part;
|
||
}
|
||
setPixelColor(int(vlength - 1), curnew);
|
||
}
|
||
|
||
/*
|
||
* Put a value 0 to 255 in to get a color value.
|
||
* The colours are a transition r -> g -> b -> back to r
|
||
* Inspired by the Adafruit examples.
|
||
*/
|
||
uint32_t Segment::color_wheel(uint8_t pos) {
|
||
if (palette) return color_from_palette(pos, false, true, 0);
|
||
pos = 255 - pos;
|
||
if(pos < 85) {
|
||
return ((uint32_t)(255 - pos * 3) << 16) | ((uint32_t)(0) << 8) | (pos * 3);
|
||
} else if(pos < 170) {
|
||
pos -= 85;
|
||
return ((uint32_t)(0) << 16) | ((uint32_t)(pos * 3) << 8) | (255 - pos * 3);
|
||
} else {
|
||
pos -= 170;
|
||
return ((uint32_t)(pos * 3) << 16) | ((uint32_t)(255 - pos * 3) << 8) | (0);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Returns a new, random wheel index with a minimum distance of 42 from pos.
|
||
*/
|
||
uint8_t Segment::get_random_wheel_index(uint8_t pos) const { // WLEDMM use fast int types, use native min/max
|
||
uint_fast8_t r = 0, x = 0, y = 0, d = 0;
|
||
|
||
while(d < 42) {
|
||
r = random8();
|
||
x = abs(int(pos - r));
|
||
y = 255 - x;
|
||
d = min(x, y);
|
||
}
|
||
return r;
|
||
}
|
||
|
||
/*
|
||
* Gets a single color from the currently selected palette.
|
||
* @param i Palette Index (if mapping is true, the full palette will be _virtualSegmentLength long, if false, 255). Will wrap around automatically.
|
||
* @param mapping if true, LED position in segment is considered for color
|
||
* @param wrap FastLED palettes will usually wrap back to the start smoothly. Set false to get a hard edge
|
||
* @param mcol If the default palette 0 is selected, return the standard color 0, 1 or 2 instead. If >2, Party palette is used instead
|
||
* @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling)
|
||
* @returns Single color from palette
|
||
*/
|
||
uint32_t __attribute__((hot)) Segment::color_from_palette(uint_fast16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) // WLEDMM use fast int types
|
||
{
|
||
// default palette or no RGB support on segment
|
||
if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) {
|
||
uint32_t color = currentColor(mcol, colors[mcol]);
|
||
color = gamma32(color);
|
||
if (pbri == 255) return color;
|
||
return RGBW32(scale8_video(R(color),pbri), scale8_video(G(color),pbri), scale8_video(B(color),pbri), scale8_video(W(color),pbri));
|
||
}
|
||
|
||
uint8_t paletteIndex = i;
|
||
uint_fast16_t vLen = mapping ? virtualLength() : 1;
|
||
if (mapping && vLen > 1) paletteIndex = (i*255)/(vLen -1);
|
||
if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end"
|
||
CRGB fastled_col = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global
|
||
|
||
return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, 0);
|
||
}
|
||
|
||
//WLEDMM netmindz ar palette
|
||
uint8_t * Segment::getAudioPalette(int pal) const {
|
||
// https://forum.makerforums.info/t/hi-is-it-possible-to-define-a-gradient-palette-at-runtime-the-define-gradient-palette-uses-the/63339
|
||
|
||
um_data_t *um_data;
|
||
if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
|
||
um_data = simulateSound(SEGMENT.soundSim);
|
||
}
|
||
uint8_t *fftResult = (uint8_t*)um_data->u_data[2];
|
||
|
||
static uint8_t xyz[16]; // Needs to be 4 times however many colors are being used.
|
||
// 3 colors = 12, 4 colors = 16, etc.
|
||
|
||
xyz[0] = 0; // anchor of first color - must be zero
|
||
xyz[1] = 0;
|
||
xyz[2] = 0;
|
||
xyz[3] = 0;
|
||
|
||
CRGB rgb = getCRGBForBand(1, fftResult, pal);
|
||
xyz[4] = 1; // anchor of first color
|
||
xyz[5] = rgb.r;
|
||
xyz[6] = rgb.g;
|
||
xyz[7] = rgb.b;
|
||
|
||
rgb = getCRGBForBand(128, fftResult, pal);
|
||
xyz[8] = 128;
|
||
xyz[9] = rgb.r;
|
||
xyz[10] = rgb.g;
|
||
xyz[11] = rgb.b;
|
||
|
||
rgb = getCRGBForBand(255, fftResult, pal);
|
||
xyz[12] = 255; // anchor of last color - must be 255
|
||
xyz[13] = rgb.r;
|
||
xyz[14] = rgb.g;
|
||
xyz[15] = rgb.b;
|
||
|
||
return xyz;
|
||
}
|
||
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// WS2812FX class implementation
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
|
||
//WLEDMM from util.cpp
|
||
// enumerate all ledmapX.json files on FS and extract ledmap names if existing
|
||
void WS2812FX::enumerateLedmaps() {
|
||
ledmapMaxSize = 0;
|
||
ledMaps = 1;
|
||
for (int i=1; i<10; i++) {
|
||
char fileName[33] = {'\0'}; // WLEDMM ensure termination
|
||
snprintf_P(fileName, sizeof(fileName), PSTR("/ledmap%d.json"), i);
|
||
bool isFile = WLED_FS.exists(fileName);
|
||
|
||
#ifndef ESP8266
|
||
if (ledmapNames[i-1]) { //clear old name
|
||
delete[] ledmapNames[i-1];
|
||
ledmapNames[i-1] = nullptr;
|
||
}
|
||
#endif
|
||
|
||
if (isFile) {
|
||
ledMaps |= 1 << i;
|
||
|
||
#ifndef ESP8266
|
||
if (requestJSONBufferLock(21)) {
|
||
//WLEDMM: upstream code loops over all ledmap files, read them all, every byte (!!!!) and only get the name of the file!!!
|
||
File f;
|
||
f = WLED_FS.open(fileName, "r");
|
||
if (f) {
|
||
f.find("\"n\":");
|
||
char name[34] = { '\0' }; // ensure string termination
|
||
f.readBytesUntil('\n', name, sizeof(name)-1);
|
||
|
||
size_t len = strlen(name);
|
||
if (len > 0 && len < 33) {
|
||
(void) cleanUpName(name);
|
||
len = strlen(name);
|
||
ledmapNames[i-1] = new(std::nothrow) char[len+1]; // +1 to include terminating \0
|
||
if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], name, 33);
|
||
}
|
||
if (!ledmapNames[i-1]) {
|
||
char tmp[33];
|
||
snprintf_P(tmp, 32, PSTR("ledmap%d.json"), i);
|
||
len = strlen(tmp);
|
||
ledmapNames[i-1] = new(std::nothrow) char[len+1];
|
||
if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], tmp, 33);
|
||
}
|
||
|
||
USER_PRINTF("enumerateLedmaps %s \"%s\"", fileName, name);
|
||
if (isMatrix) {
|
||
//WLEDMM calc ledmapMaxSize (TroyHacks)
|
||
char dim[34] = { '\0' };
|
||
f.find("\"width\":");
|
||
f.readBytesUntil('\n', dim, sizeof(dim)-1); //hack: use fileName as we have this allocated already
|
||
uint16_t maxWidth = atoi(cleanUpName(dim));
|
||
f.find("\"height\":");
|
||
memset(dim, 0, sizeof(dim)); // clear buffer before reading
|
||
f.readBytesUntil('\n', dim, sizeof(dim)-1);
|
||
uint16_t maxHeight = atoi(cleanUpName(dim));
|
||
ledmapMaxSize = MAX(ledmapMaxSize, maxWidth * maxHeight);
|
||
|
||
if (maxWidth*maxHeight>0) {
|
||
USER_PRINTF(" (%dx%d -> %d)\n", maxWidth, maxHeight, ledmapMaxSize);
|
||
} else {
|
||
USER_PRINTLN();
|
||
}
|
||
}
|
||
else
|
||
USER_PRINTLN();
|
||
}
|
||
f.close();
|
||
USER_FLUSH();
|
||
releaseJSONBufferLock();
|
||
}
|
||
#endif
|
||
}
|
||
}
|
||
//WLEDMM add segment names to be used as ledmap names
|
||
uint8_t segment_index = 0;
|
||
for (segment &seg : _segments) {
|
||
if (seg.name != nullptr && strlen(seg.name) > 0) {
|
||
char fileName[33+11] = { '\0' }; // segment name is 32 chars max, so we need 43 chars in worst case
|
||
snprintf_P(fileName, sizeof(fileName)-1, PSTR("/lm%s.json"), seg.name);
|
||
bool isFile = WLED_FS.exists(fileName);
|
||
if (isFile) ledMaps |= 1 << (10+segment_index);
|
||
}
|
||
segment_index++;
|
||
}
|
||
}
|
||
|
||
|
||
//do not call this method from system context (network callback)
|
||
void WS2812FX::finalizeInit(void)
|
||
{
|
||
//reset segment runtimes
|
||
suspendStripService = true; // WLEDMM avoid running effects on an incomplete strip
|
||
for (segment &seg : _segments) {
|
||
seg.markForReset();
|
||
seg.resetIfRequired();
|
||
}
|
||
|
||
// for the lack of better place enumerate ledmaps here
|
||
// if we do it in json.cpp (serializeInfo()) we are getting flashes on LEDs
|
||
// unfortunately this means we do not get updates after uploads
|
||
enumerateLedmaps();
|
||
|
||
_hasWhiteChannel = _isOffRefreshRequired = false;
|
||
|
||
//if busses failed to load, add default (fresh install, FS issue, ...)
|
||
if (busses.getNumBusses() == 0) {
|
||
DEBUG_PRINTLN(F("No busses, init default"));
|
||
const uint8_t defDataPins[] = {DATA_PINS};
|
||
const uint16_t defCounts[] = {PIXEL_COUNTS};
|
||
const uint8_t defNumBusses = ((sizeof defDataPins) / (sizeof defDataPins[0]));
|
||
const uint8_t defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0]));
|
||
uint16_t prevLen = 0;
|
||
for (uint8_t i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) {
|
||
uint8_t defPin[] = {defDataPins[i]};
|
||
uint16_t start = prevLen;
|
||
uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
|
||
prevLen += count;
|
||
BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY);
|
||
if (busses.add(defCfg) == -1) break;
|
||
}
|
||
}
|
||
|
||
_length = 0;
|
||
for (uint8_t i=0; i<busses.getNumBusses(); i++) {
|
||
Bus *bus = busses.getBus(i);
|
||
if (bus == nullptr) continue;
|
||
if (bus->getStart() + bus->getLength() > MAX_LEDS) {
|
||
USER_PRINT("\nError: too many LEDs, max number is "); USER_PRINTLN(MAX_LEDS);
|
||
break;
|
||
}
|
||
//RGBW mode is enabled if at least one of the strips is RGBW
|
||
_hasWhiteChannel |= bus->hasWhite();
|
||
//refresh is required to remain off if at least one of the strips requires the refresh.
|
||
_isOffRefreshRequired |= bus->isOffRefreshRequired();
|
||
uint16_t busEnd = bus->getStart() + bus->getLength();
|
||
if (busEnd > _length) _length = busEnd;
|
||
#ifdef ESP8266
|
||
if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue;
|
||
uint8_t pins[5];
|
||
if (!bus->getPins(pins)) continue;
|
||
BusDigital* bd = static_cast<BusDigital*>(bus);
|
||
if (pins[0] == 3) bd->reinit();
|
||
#endif
|
||
}
|
||
|
||
if (isMatrix) setUpMatrix();
|
||
else {
|
||
Segment::maxWidth = _length;
|
||
Segment::maxHeight = 1;
|
||
}
|
||
|
||
//initialize leds array. TBD: realloc if nr of leds change
|
||
if (Segment::_globalLeds) {
|
||
// DONG - Valkyrie is about to die
|
||
// this is a critical section that will be removed with PR #278 which removes _globalLeds
|
||
// problem: suspendStripService provides interlocking, but there’s a window before service() observes it,
|
||
// and ESP32 is dual-core. A critical section closes that window so the pointer swap is atomic across cores.
|
||
#if defined(ARDUINO_ARCH_ESP32)
|
||
taskENTER_CRITICAL(&s_wled_strip_mux);
|
||
#endif
|
||
free(Segment::_globalLeds);
|
||
Segment::_globalLeds = nullptr;
|
||
purgeSegments(true); // WLEDMM moved here, because it seems to improve stability.
|
||
#if defined(ARDUINO_ARCH_ESP32)
|
||
taskEXIT_CRITICAL(&s_wled_strip_mux);
|
||
#endif
|
||
}
|
||
if (useLedsArray && getLengthTotal()>0) { // WLEDMM avoid malloc(0)
|
||
size_t arrSize = sizeof(CRGB) * getLengthTotal();
|
||
// softhack007 disabled; putting leds into psram leads to horrible slowdown on WROVER boards (see setUpLeds())
|
||
//#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM)
|
||
//if (psramFound())
|
||
// Segment::_globalLeds = (CRGB*) ps_malloc(arrSize);
|
||
//else
|
||
//#endif
|
||
if (arrSize > 0) Segment::_globalLeds = (CRGB*) malloc(arrSize); // WLEDMM avoid malloc(0)
|
||
if ((Segment::_globalLeds != nullptr) && (arrSize > 0)) memset(Segment::_globalLeds, 0, arrSize); // WLEDMM avoid dereferencing nullptr
|
||
if ((Segment::_globalLeds == nullptr) && (arrSize > 0)) errorFlag = ERR_NORAM_PX; // WLEDMM raise errorflag
|
||
}
|
||
|
||
//segments are created in makeAutoSegments();
|
||
DEBUG_PRINTLN(F("Loading custom palettes"));
|
||
loadCustomPalettes(); // (re)load all custom palettes
|
||
DEBUG_PRINTLN(F("Loading custom ledmaps"));
|
||
deserializeMap(); // (re)load default ledmap
|
||
_isServicing = false; // WLEDMM
|
||
suspendStripService = false; // WLEDMM ready, run !
|
||
}
|
||
|
||
// WLEDMM wait until strip is idle (=not servicing).
|
||
// on 8266 this function does nothing, because we can only do "busy waiting" on ESP32
|
||
//#define MAX_IDLE_WAIT_MS 50 // seems to work in most cases
|
||
#define MAX_IDLE_WAIT_MS 120 // better safe than sorry - similar to the timeout used by upstream WLED
|
||
void WS2812FX::waitUntilIdle(void) {
|
||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_PROTECT_SERVICE)
|
||
if (isServicing()) {
|
||
unsigned long waitStarted = millis();
|
||
do {
|
||
delay(2); // Suspending for 1 tick (or more) gives other tasks a chance to run.
|
||
//yield(); // seems to be a no-op on esp32
|
||
} while (isServicing() && (millis() - waitStarted < MAX_IDLE_WAIT_MS));
|
||
USER_PRINTF("strip.waitUntilIdle(): strip %sidle after %d ms. (task %s with prio=%d)\n", isServicing()?"not ":"", int(millis() - waitStarted), pcTaskGetTaskName(NULL), uxTaskPriorityGet(NULL));
|
||
}
|
||
return;
|
||
#else
|
||
return;
|
||
#endif
|
||
}
|
||
|
||
void WS2812FX::service() {
|
||
unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days // WLEDMM avoid losing precision
|
||
if (OTAisRunning) return; // WLEDMM avoid flickering during OTA
|
||
|
||
now = nowUp + timebase;
|
||
unsigned long elapsed = nowUp - _lastServiceShow;
|
||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_FASTPATH) // WLEDMM go faster on ESP32
|
||
//if (_suspend) return;
|
||
if (elapsed < 2) return; // keep wifi alive
|
||
if ( !_triggered && (_targetFps != FPS_UNLIMITED) && (_targetFps != FPS_UNLIMITED_AC)) {
|
||
#if 0
|
||
if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service - delivers higher fps
|
||
#else
|
||
if ((elapsed+1) < _frametime) return; // code from upstream - stricter on FPS
|
||
#endif
|
||
}
|
||
#else // legacy
|
||
if (elapsed < _frametime) return;
|
||
#endif
|
||
|
||
bool doShow = false;
|
||
unsigned speedLimit = (_targetFps != FPS_UNLIMITED) && (_targetFps != FPS_UNLIMITED_AC) ? (0.85f * FRAMETIME) : 1; // WLEDMM minimum for effect frametime
|
||
|
||
_isServicing = true;
|
||
_segment_index = 0;
|
||
for (segment &seg : _segments) {
|
||
#ifdef WLEDMM_FASTPATH
|
||
_currentSeg = &seg;
|
||
#endif
|
||
// reset the segment runtime data if needed
|
||
seg.resetIfRequired();
|
||
|
||
if (!seg.isActive()) continue;
|
||
if (!seg.on && !seg.transitional) continue; // WLEDMM skip disabled segments, unless a crossfade is ongoing
|
||
|
||
// last condition ensures all solid segments are updated at the same time
|
||
if(nowUp >= seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) // WLEDMM ">=" instead of ">"
|
||
{
|
||
if (seg.grouping == 0) seg.grouping = 1; //sanity check
|
||
if ((!seg.freeze) || _triggered) doShow = true; // WLEDMM "triggered" overrules "freeze"
|
||
uint16_t frameDelay = FRAMETIME; // WLEDMM avoid name clash with "delay" function
|
||
|
||
if (!seg.freeze) { //only run effect function if not frozen
|
||
_virtualSegmentLength = seg.calc_virtualLength();
|
||
_colors_t[0] = seg.currentColor(0, seg.colors[0]);
|
||
_colors_t[1] = seg.currentColor(1, seg.colors[1]);
|
||
_colors_t[2] = seg.currentColor(2, seg.colors[2]);
|
||
seg.setCurrentPalette(); // load actual palette
|
||
|
||
if (!cctFromRgb || correctWB) busses.setSegmentCCT(seg.currentBri(seg.cct, true), correctWB);
|
||
for (uint8_t c = 0; c < NUM_COLORS; c++) _colors_t[c] = gamma32(_colors_t[c]);
|
||
#if 0 // WARNING this would kill _supersync_
|
||
now = millis() + timebase;
|
||
#endif
|
||
seg.startFrame(); // WLEDMM
|
||
if (!_triggered && (seg.currentBri(seg.opacity) == 0) && (seg.lastBri == 0)) continue; // WLEDMM skip totally black segments
|
||
// effect blending (execute previous effect)
|
||
// actual code may be a bit more involved as effects have runtime data including allocated memory
|
||
//if (seg.transitional && seg._modeP) (*_mode[seg._modeP])(progress());
|
||
frameDelay = (*_mode[seg.currentMode(seg.mode)])();
|
||
|
||
if (frameDelay < speedLimit) frameDelay = FRAMETIME; // WLEDMM limit effects that want to go faster than target FPS
|
||
if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++;
|
||
|
||
if (seg.transitional && frameDelay > max(int(FRAMETIME), int(FRAMETIME_FIXED)))
|
||
frameDelay = max(int(FRAMETIME), int(FRAMETIME_FIXED)); // force faster updates during transition // WLEDMM only if effect requested very slow updates
|
||
|
||
seg.lastBri = seg.currentBri(seg.on ? seg.opacity:0); // WLEDMM remember for next time
|
||
seg.handleTransition();
|
||
}
|
||
|
||
seg.next_time = nowUp + frameDelay;
|
||
}
|
||
_segment_index++;
|
||
}
|
||
if (_triggered) doShow = true; // WLEDMM "triggered" always means "show"
|
||
|
||
_virtualSegmentLength = 0;
|
||
busses.setSegmentCCT(-1);
|
||
if(doShow) {
|
||
#if 0 && defined(ARDUINO_ARCH_ESP32) // EXPERIMENTAL - enabled this to enforce stricter frametime limits
|
||
static unsigned long lastTimeShow = 0;
|
||
long tdelta = millis() - lastTimeShow;
|
||
if ((lastTimeShow > 0) && (tdelta > 1) && (tdelta < _frametime)) // too early - release CPU to slow down
|
||
vTaskDelay((tdelta-1) / portTICK_PERIOD_MS); // "-1" because vTaskDelay() may actually delay longer than requested
|
||
lastTimeShow = millis();
|
||
#else
|
||
yield();
|
||
#endif
|
||
show();
|
||
_lastServiceShow = nowUp; // WLEDMM use correct timestamp
|
||
}
|
||
_triggered = false;
|
||
_isServicing = false;
|
||
}
|
||
|
||
|
||
// WLEDMM: WS2812FX::setPixelColor() moved FX.h for speed (inlining)
|
||
// WLEDMM: WS2812FX::getPixelColor() moved FX.h for speed (inlining)
|
||
// WLEDMM: WS2812FX::getPixelColorRestored() moved FX.h for speed (inlining)
|
||
|
||
|
||
//DISCLAIMER
|
||
//The following function attemps to calculate the current LED power usage,
|
||
//and will limit the brightness to stay below a set amperage threshold.
|
||
//It is NOT a measurement and NOT guaranteed to stay within the ablMilliampsMax margin.
|
||
//Stay safe with high amperage and have a reasonable safety margin!
|
||
//I am NOT to be held liable for burned down garages!
|
||
|
||
//fine tune power estimation constants for your setup
|
||
#define MA_FOR_ESP 100 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA)
|
||
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
|
||
|
||
void WS2812FX::estimateCurrentAndLimitBri() {
|
||
//power limit calculation
|
||
//each LED can draw up 195075 "power units" (approx. 53mA)
|
||
//one PU is the power it takes to have 1 channel 1 step brighter per brightness step
|
||
//so A=2,R=255,G=0,B=0 would use 510 PU per LED (1mA is about 3700 PU)
|
||
bool useWackyWS2815PowerModel = false;
|
||
byte actualMilliampsPerLed = milliampsPerLed;
|
||
|
||
if(milliampsPerLed == 255) {
|
||
useWackyWS2815PowerModel = true;
|
||
actualMilliampsPerLed = 12; // from testing an actual strip
|
||
}
|
||
|
||
if (ablMilliampsMax < 150 || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation
|
||
currentMilliamps = 0;
|
||
busses.setBrightness(_brightness);
|
||
return;
|
||
}
|
||
|
||
uint16_t pLen = getLengthPhysical();
|
||
uint32_t puPerMilliamp = 195075 / actualMilliampsPerLed;
|
||
uint32_t powerBudget = (ablMilliampsMax - MA_FOR_ESP) * puPerMilliamp; //100mA for ESP power
|
||
if (powerBudget > puPerMilliamp * pLen) { //each LED uses about 1mA in standby, exclude that from power budget
|
||
powerBudget -= puPerMilliamp * pLen;
|
||
} else {
|
||
powerBudget = 0;
|
||
}
|
||
|
||
uint32_t powerSum = 0;
|
||
|
||
for (uint_fast8_t bNum = 0; bNum < busses.getNumBusses(); bNum++) {
|
||
Bus *bus = busses.getBus(bNum);
|
||
if (!bus || !bus->isOk()) continue; // WLEDMM skip busses that are not initialized yet
|
||
auto btype = bus->getType();
|
||
if (EXCLUDE_FROM_ABL(btype)) continue; // WLEDMM exclude non-ABL and network busses
|
||
uint16_t len = bus->getLength();
|
||
uint32_t busPowerSum = 0;
|
||
for (uint_fast16_t i = 0; i < len; i++) { //sum up the usage of each LED
|
||
uint32_t c = bus->getPixelColor(i);
|
||
byte r = R(c), g = G(c), b = B(c), w = W(c);
|
||
|
||
if(useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
|
||
busPowerSum += (max(max(r,g),b)) * 3; // WLEDMM use native min/max
|
||
} else {
|
||
busPowerSum += (r + g + b + w);
|
||
}
|
||
}
|
||
|
||
if (bus->hasWhite()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
|
||
busPowerSum *= 3;
|
||
busPowerSum = busPowerSum >> 2; //same as /= 4
|
||
}
|
||
powerSum += busPowerSum;
|
||
}
|
||
|
||
uint32_t powerSum0 = powerSum;
|
||
//powerSum *= _brightness; // for NPBrightnessBus
|
||
powerSum *= 255; // no need to scale down powerSum - NPB-LG getPixelColor returns colors scaled down by brightness
|
||
|
||
if (powerSum > powerBudget) //scale brightness down to stay in current limit
|
||
{
|
||
float scale = (float)powerBudget / (float)powerSum;
|
||
uint16_t scaleI = scale * 255;
|
||
uint8_t scaleB = (scaleI > 255) ? 255 : scaleI;
|
||
uint8_t newBri = scale8(_brightness, scaleB);
|
||
// to keep brightness uniform, sets virtual busses too - softhack007: apply reductions immediately
|
||
if (scaleB < 255) busses.setBrightness(scaleB, true); // NPB-LG has already applied brightness, so its sufficient to post-apply scaling ==> use scaleB instead of newBri
|
||
busses.setBrightness(newBri, false); // set new brightness for next frame
|
||
//currentMilliamps = (powerSum0 * newBri) / puPerMilliamp; // for NPBrightnessBus
|
||
currentMilliamps = (powerSum0 * scaleB) / puPerMilliamp; // for NPBus-LG
|
||
} else {
|
||
currentMilliamps = powerSum / puPerMilliamp;
|
||
busses.setBrightness(_brightness, false); // set new brightness for next frame
|
||
}
|
||
currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate
|
||
currentMilliamps += pLen; //add standby power back to estimate
|
||
}
|
||
|
||
void WS2812FX::show(void) {
|
||
if (OTAisRunning) return; // WLEDMM avoid flickering during OTA
|
||
|
||
// avoid race condition, capture _callback value
|
||
show_callback callback = _callback;
|
||
if (callback) callback();
|
||
|
||
estimateCurrentAndLimitBri();
|
||
|
||
unsigned long showNow = millis(); // include time needed for busses.show()
|
||
#ifdef ARDUINO_ARCH_ESP32 // WLEDMM more accurate FPS measurement for ESP32
|
||
uint64_t now500 = esp_timer_get_time() / 2; // native timer; micros /2 -> millis * 500
|
||
#endif
|
||
|
||
// some buses send asynchronously and this method will return before
|
||
// all of the data has been sent.
|
||
// See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
|
||
busses.show();
|
||
|
||
unsigned long diff = showNow - _lastShow;
|
||
uint16_t fpsCurr = 200;
|
||
if (diff > 0) fpsCurr = 1000 / diff;
|
||
_cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5)
|
||
_lastShow = showNow;
|
||
_lastServiceShow = showNow;
|
||
|
||
#ifdef ARDUINO_ARCH_ESP32 // WLEDMM more accurate FPS measurement for ESP32
|
||
int64_t diff500 = now500 - _lastShow500;
|
||
if ((diff500 > 1) && (diff500 < 800000)) { // exclude stupid values (timer rollover, major hickups)
|
||
float fpcCurr500 = 500000.0f / float(diff500);
|
||
if (fpcCurr500 > 2)
|
||
_cumulativeFps500 = (3 * _cumulativeFps500 + (500.0 * fpcCurr500)) / 4; // average for some smoothing
|
||
}
|
||
_lastShow500 = now500;
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* Returns a true value if any of the strips are still being updated.
|
||
* On some hardware (ESP32), strip updates are done asynchronously.
|
||
*/
|
||
bool WS2812FX::isUpdating() const {
|
||
return !busses.canAllShow();
|
||
}
|
||
|
||
/**
|
||
* Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough.
|
||
* Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accuracy varies
|
||
*/
|
||
uint16_t WS2812FX::getFps() const {
|
||
if (millis() - _lastShow > 2000) return 0;
|
||
#ifdef ARDUINO_ARCH_ESP32
|
||
return ((_cumulativeFps500 + 250) / 500); // +250 for proper rounding
|
||
#else
|
||
return _cumulativeFps +1;
|
||
#endif
|
||
}
|
||
|
||
void WS2812FX::setTargetFps(uint8_t fps) {
|
||
if (fps <= 251) _targetFps = fps; // WLEDMM allow higher framerates
|
||
//if (fps > 0) _frametime = ((2000 / _targetFps) +1) /2; // with rounding
|
||
if (fps > 0) _frametime = 1000 / _targetFps;
|
||
else _frametime = 2; // AC WLED compatibility
|
||
if (fps >= FPS_UNLIMITED) _frametime = 2; // WLEDMM unlimited mode
|
||
}
|
||
|
||
void WS2812FX::setMode(uint8_t segid, uint8_t m) {
|
||
if (segid >= _segments.size()) return;
|
||
|
||
if (m >= getModeCount()) m = getModeCount() - 1;
|
||
|
||
if (_segments[segid].mode != m) {
|
||
_segments[segid].startTransition(_transitionDur); // set effect transitions
|
||
//_segments[segid].markForReset();
|
||
_segments[segid].mode = m;
|
||
}
|
||
}
|
||
|
||
//applies to all active and selected segments
|
||
void WS2812FX::setColor(uint8_t slot, uint32_t c) {
|
||
if (slot >= NUM_COLORS) return;
|
||
|
||
for (segment &seg : _segments) {
|
||
if (seg.isActive() && seg.isSelected()) {
|
||
seg.setColor(slot, c);
|
||
}
|
||
}
|
||
}
|
||
|
||
void WS2812FX::setCCT(uint16_t k) {
|
||
for (segment &seg : _segments) {
|
||
if (seg.isActive() && seg.isSelected()) {
|
||
seg.setCCT(k);
|
||
}
|
||
}
|
||
}
|
||
|
||
void WS2812FX::setBrightness(uint8_t b, bool direct) {
|
||
if (gammaCorrectBri) b = gamma8(b);
|
||
if (_brightness == b) return;
|
||
_brightness = b;
|
||
if (_brightness == 0) { //unfreeze all segments on power off
|
||
for (segment &seg : _segments) {
|
||
seg.freeze = false;
|
||
}
|
||
}
|
||
if (direct) {
|
||
// would be dangerous if applied immediately (could exceed ABL), but will not output until the next show()
|
||
busses.setBrightness(b);
|
||
} else {
|
||
unsigned long t = millis();
|
||
if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) show(); //apply brightness change immediately if no refresh soon
|
||
}
|
||
}
|
||
|
||
uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) {
|
||
uint8_t totalLC = 0;
|
||
for (segment &seg : _segments) {
|
||
if (seg.isActive() && (!selectedOnly || seg.isSelected())) totalLC |= seg.getLightCapabilities();
|
||
}
|
||
return totalLC;
|
||
}
|
||
|
||
uint8_t WS2812FX::getFirstSelectedSegId(void)
|
||
{
|
||
size_t i = 0;
|
||
for (segment &seg : _segments) {
|
||
if (seg.isActive() && seg.isSelected()) return i;
|
||
i++;
|
||
}
|
||
// if none selected, use the main segment
|
||
return getMainSegmentId();
|
||
}
|
||
|
||
void WS2812FX::setMainSegmentId(uint8_t n) {
|
||
_mainSegment = 0;
|
||
if (n < _segments.size()) {
|
||
_mainSegment = n;
|
||
}
|
||
return;
|
||
}
|
||
|
||
uint8_t WS2812FX::getLastActiveSegmentId(void) const {
|
||
for (size_t i = _segments.size() -1; i > 0; i--) {
|
||
if (_segments[i].isActive()) return i;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
uint8_t WS2812FX::getActiveSegmentsNum(void) const {
|
||
uint8_t c = 0;
|
||
for (size_t i = 0; i < _segments.size(); i++) {
|
||
if (_segments[i].isActive()) c++;
|
||
}
|
||
return c;
|
||
}
|
||
|
||
uint16_t WS2812FX::getLengthTotal(void) const { // WLEDMM fast int types
|
||
uint_fast16_t len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D
|
||
if (isMatrix && _length > len) len = _length; // for 2D with trailing strip
|
||
return len;
|
||
}
|
||
|
||
uint16_t WS2812FX::getLengthPhysical(void) const { // WLEDMM fast int types
|
||
uint_fast16_t len = 0;
|
||
for (unsigned b = 0; b < busses.getNumBusses(); b++) { // WLEDMM use native (fast) types
|
||
Bus *bus = busses.getBus(b);
|
||
auto btype = bus->getType();
|
||
if (EXCLUDE_FROM_ABL(btype)) continue; //exclude HUB75, and non-physical network busses
|
||
len += bus->getLength();
|
||
}
|
||
return len;
|
||
}
|
||
|
||
//WLEDMM - getLengthPhysical plus plysical busses not supporting ABL (i.e. HUB75)
|
||
uint16_t WS2812FX::getLengthPhysical2(void) const {
|
||
uint_fast16_t len = 0;
|
||
for (unsigned b = 0; b < busses.getNumBusses(); b++) {
|
||
Bus *bus = busses.getBus(b);
|
||
auto btype = bus->getType();
|
||
if (IS_VIRTUAL(btype)) continue;
|
||
len += bus->getLength();
|
||
}
|
||
return len;
|
||
}
|
||
|
||
//used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw.
|
||
//returns if there is an RGBW bus (supports RGB and White, not only white)
|
||
//not influenced by auto-white mode, also true if white slider does not affect output white channel
|
||
bool WS2812FX::hasRGBWBus(void) const {
|
||
for (unsigned b = 0; b < busses.getNumBusses(); b++) { // WLEDMM use native (fast) types
|
||
Bus *bus = busses.getBus(b);
|
||
if (bus == nullptr || bus->getLength()==0) break;
|
||
if (bus->hasRGB() && bus->hasWhite()) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool WS2812FX::hasCCTBus(void) const {
|
||
if (cctFromRgb && !correctWB) return false;
|
||
for (unsigned b = 0; b < busses.getNumBusses(); b++) { // WLEDMM use native (fast) types
|
||
Bus *bus = busses.getBus(b);
|
||
if (bus == nullptr || bus->getLength()==0) break;
|
||
switch (bus->getType()) {
|
||
case TYPE_ANALOG_5CH:
|
||
case TYPE_ANALOG_2CH:
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void WS2812FX::purgeSegments(bool force) {
|
||
// remove all inactive segments (from the back)
|
||
int deleted = 0;
|
||
if (_segments.size() <= 1) return;
|
||
for (size_t i = _segments.size()-1; i > 0; i--)
|
||
if (_segments[i].stop == 0 || force) {
|
||
deleted++;
|
||
_segments.erase(_segments.begin() + i);
|
||
}
|
||
if (deleted) {
|
||
_segments.shrink_to_fit();
|
||
/*if (_mainSegment >= _segments.size())*/ setMainSegmentId(0);
|
||
}
|
||
}
|
||
|
||
Segment& WS2812FX::getSegment(uint8_t id) {
|
||
uint8_t mainSegID = getMainSegmentId();
|
||
if (mainSegID >= _segments.size()) mainSegID=0; // WLEDMM fallback in case that getMainSegmentId() is invalid
|
||
return _segments[id >= _segments.size() ? mainSegID : id]; // vectors
|
||
}
|
||
|
||
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) {
|
||
if (n >= _segments.size()) return;
|
||
_segments[n].setUp(i1, i2, grouping, spacing, offset, startY, stopY);
|
||
}
|
||
|
||
void WS2812FX::restartRuntime(bool doReset) {
|
||
for (segment &seg : _segments) {
|
||
if (doReset) { // WLEDMM we prefer not to perform a complete restart of all effects
|
||
seg.markForReset(); // seg.resetIfRequired(); // WLEDMM calling this function from webserver context will cause troubles
|
||
} else {
|
||
seg.next_time = 0; seg.step = 0;
|
||
seg.markForBlank();
|
||
}
|
||
}
|
||
}
|
||
|
||
void WS2812FX::resetSegments(bool boundsOnly) { //WLEDMM add boundsonly
|
||
DEBUG_PRINTF("resetSegments %d %dx%d\n", boundsOnly, Segment::maxWidth, Segment::maxHeight);
|
||
if (!boundsOnly) {
|
||
_segments.clear(); // destructs all Segment as part of clearing
|
||
#ifndef WLED_DISABLE_2D
|
||
segment seg = isMatrix ? Segment(0, Segment::maxWidth, 0, Segment::maxHeight) : Segment(0, _length);
|
||
#else
|
||
segment seg = Segment(0, _length);
|
||
#endif
|
||
_segments.push_back(seg);
|
||
_mainSegment = 0;
|
||
} else { //WLEDMM boundsonly
|
||
for (segment &seg : _segments) {
|
||
#ifndef WLED_DISABLE_2D
|
||
seg.start = 0;
|
||
seg.stop = Segment::maxWidth;
|
||
seg.startY = 0;
|
||
seg.stopY = Segment::maxHeight;
|
||
#else
|
||
seg.start = 0;
|
||
seg.stop = _length;
|
||
#endif
|
||
seg.allocLeds();
|
||
}
|
||
}
|
||
}
|
||
|
||
void WS2812FX::makeAutoSegments(bool forceReset) {
|
||
if (autoSegments) { //make one segment per bus
|
||
uint16_t segStarts[MAX_NUM_SEGMENTS] = {0};
|
||
uint16_t segStops [MAX_NUM_SEGMENTS] = {0};
|
||
size_t s = 0;
|
||
|
||
#ifndef WLED_DISABLE_2D
|
||
// 2D segment is the 1st one using entire matrix
|
||
if (isMatrix) {
|
||
segStarts[0] = 0;
|
||
segStops[0] = Segment::maxWidth*Segment::maxHeight;
|
||
s++;
|
||
}
|
||
#endif
|
||
|
||
for (size_t i = s; i < busses.getNumBusses(); i++) {
|
||
Bus* b = busses.getBus(i);
|
||
|
||
segStarts[s] = b->getStart();
|
||
segStops[s] = segStarts[s] + b->getLength();
|
||
|
||
#ifndef WLED_DISABLE_2D
|
||
if (isMatrix && segStops[s] <= Segment::maxWidth*Segment::maxHeight) continue; // ignore buses comprising matrix
|
||
if (isMatrix && segStarts[s] < Segment::maxWidth*Segment::maxHeight) segStarts[s] = Segment::maxWidth*Segment::maxHeight;
|
||
#endif
|
||
|
||
//check for overlap with previous segments
|
||
for (size_t j = 0; j < s; j++) {
|
||
if (segStops[j] > segStarts[s] && segStarts[j] < segStops[s]) {
|
||
//segments overlap, merge
|
||
segStarts[j] = min(segStarts[s],segStarts[j]);
|
||
segStops [j] = max(segStops [s],segStops [j]); segStops[s] = 0;
|
||
s--;
|
||
}
|
||
}
|
||
s++;
|
||
}
|
||
|
||
_segments.clear();
|
||
_segments.reserve(s); // prevent reallocations
|
||
// there is always at least one segment (but we need to differentiate between 1D and 2D)
|
||
#ifndef WLED_DISABLE_2D
|
||
if (isMatrix)
|
||
_segments.push_back(Segment(0, Segment::maxWidth, 0, Segment::maxHeight));
|
||
else
|
||
#endif
|
||
_segments.push_back(Segment(segStarts[0], segStops[0]));
|
||
for (size_t i = 1; i < s; i++) {
|
||
_segments.push_back(Segment(segStarts[i], segStops[i]));
|
||
}
|
||
|
||
} else {
|
||
|
||
if (forceReset || getSegmentsNum() == 0) resetSegments();
|
||
//expand the main seg to the entire length, but only if there are no other segments, or reset is forced
|
||
else if (getActiveSegmentsNum() == 1) {
|
||
size_t i = getLastActiveSegmentId();
|
||
#ifndef WLED_DISABLE_2D
|
||
_segments[i].start = 0;
|
||
_segments[i].stop = Segment::maxWidth;
|
||
_segments[i].startY = 0;
|
||
_segments[i].stopY = Segment::maxHeight;
|
||
_segments[i].grouping = 1;
|
||
_segments[i].spacing = 0;
|
||
#else
|
||
_segments[i].start = 0;
|
||
_segments[i].stop = _length;
|
||
#endif
|
||
}
|
||
}
|
||
_mainSegment = 0;
|
||
|
||
fixInvalidSegments();
|
||
}
|
||
|
||
void WS2812FX::fixInvalidSegments() {
|
||
//make sure no segment is longer than total (sanity check)
|
||
for (size_t i = getSegmentsNum()-1; i > 0; i--) {
|
||
if (isMatrix) {
|
||
#ifndef WLED_DISABLE_2D
|
||
if (_segments[i].start >= Segment::maxWidth * Segment::maxHeight) {
|
||
// 1D segment at the end of matrix
|
||
if (_segments[i].start >= _length || _segments[i].startY > 0 || _segments[i].stopY > 1) { _segments.erase(_segments.begin()+i); continue; }
|
||
if (_segments[i].stop > _length) _segments[i].stop = _length;
|
||
continue;
|
||
}
|
||
if (_segments[i].start >= Segment::maxWidth || _segments[i].startY >= Segment::maxHeight) { _segments.erase(_segments.begin()+i); continue; }
|
||
if (_segments[i].stop > Segment::maxWidth) _segments[i].stop = Segment::maxWidth;
|
||
if (_segments[i].stopY > Segment::maxHeight) _segments[i].stopY = Segment::maxHeight;
|
||
#endif
|
||
} else {
|
||
if (_segments[i].start >= _length) { _segments.erase(_segments.begin()+i); continue; }
|
||
if (_segments[i].stop > _length) _segments[i].stop = _length;
|
||
}
|
||
}
|
||
// if any segments were deleted free memory
|
||
purgeSegments();
|
||
// this is always called as the last step after finalizeInit(), update covered bus types
|
||
for (segment &seg : _segments)
|
||
seg.refreshLightCapabilities();
|
||
}
|
||
|
||
//true if all segments align with a bus, or if a segment covers the total length
|
||
//irrelevant in 2D set-up
|
||
bool WS2812FX::checkSegmentAlignment() {
|
||
bool aligned = false;
|
||
for (segment &seg : _segments) {
|
||
for (uint8_t b = 0; b<busses.getNumBusses(); b++) {
|
||
Bus *bus = busses.getBus(b);
|
||
if (seg.start == bus->getStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true;
|
||
}
|
||
if (seg.start == 0 && seg.stop == _length) aligned = true;
|
||
if (!aligned) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply)
|
||
//Note: If called in an interrupt (e.g. JSON API), original segment must be restored,
|
||
//otherwise it can lead to a crash on ESP32 because _segment_index is modified while in use by the main thread
|
||
uint8_t WS2812FX::setPixelSegment(uint8_t n) {
|
||
uint8_t prevSegId = _segment_index;
|
||
if (n < _segments.size()) {
|
||
_segment_index = n;
|
||
_virtualSegmentLength = _segments[_segment_index].calc_virtualLength();
|
||
}
|
||
return prevSegId;
|
||
}
|
||
|
||
void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) {
|
||
if (i2 >= i)
|
||
{
|
||
for (uint_fast16_t x = i; x <= i2; x++) setPixelColor((uint16_t)x, col); // WLEDMM use fast int types
|
||
} else
|
||
{
|
||
for (uint_fast16_t x = i2; x <= i; x++) setPixelColor((uint16_t)x, col);
|
||
}
|
||
}
|
||
|
||
void WS2812FX::setTransitionMode(bool t) {
|
||
for (segment &seg : _segments) if (!seg.transitional) seg.startTransition(t ? _transitionDur : 0);
|
||
}
|
||
|
||
#ifdef WLED_DEBUG
|
||
void WS2812FX::printSize() {
|
||
size_t size = 0;
|
||
for (const Segment &seg : _segments) size += seg.getSize();
|
||
DEBUG_PRINTF("Segments: %d -> %uB\n", _segments.size(), size);
|
||
DEBUG_PRINTF("Modes: %d*%d=%uB\n", sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr)));
|
||
DEBUG_PRINTF("Data: %d*%d=%uB\n", sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *)));
|
||
DEBUG_PRINTF("Map: %d*%d=%uB\n", sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t));
|
||
size = getLengthTotal();
|
||
if (useLedsArray) DEBUG_PRINTF("Buffer: %d*%u=%uB\n", sizeof(CRGB), size, size*sizeof(CRGB));
|
||
}
|
||
#endif
|
||
|
||
void WS2812FX::loadCustomPalettes() {
|
||
byte tcp[72]; //support gradient palettes with up to 18 entries
|
||
CRGBPalette16 targetPalette;
|
||
customPalettes.clear(); // start fresh
|
||
for (int index = 0; index<10; index++) {
|
||
char fileName[32];
|
||
sprintf_P(fileName, PSTR("/palette%d.json"), index);
|
||
|
||
StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers
|
||
if (WLED_FS.exists(fileName)) {
|
||
DEBUG_PRINT(F("Reading palette from "));
|
||
DEBUG_PRINTLN(fileName);
|
||
|
||
if (readObjectFromFile(fileName, nullptr, &pDoc)) {
|
||
JsonArray pal = pDoc[F("palette")];
|
||
if (!pal.isNull() && pal.size()>3) { // not an empty palette (at least 2 entries)
|
||
if (pal[0].is<int>() && pal[1].is<const char *>()) {
|
||
// we have an array of index & hex strings
|
||
size_t palSize = min(pal.size(), (size_t)36); // WLEDMM use native min/max
|
||
palSize -= palSize % 2; // make sure size is multiple of 2
|
||
for (unsigned i=0, j=0; i<palSize && pal[i].as<int>()<256; i+=2, j+=4) {
|
||
uint8_t rgbw[] = {0,0,0,0};
|
||
tcp[ j ] = (uint8_t) pal[ i ].as<int>(); // index
|
||
colorFromHexString(rgbw, pal[i+1].as<const char *>()); // will catch non-string entires
|
||
for (unsigned c=0; c<3; c++) tcp[j+1+c] = gamma8(rgbw[c]); // only use RGB component
|
||
DEBUG_PRINTF("%d(%d) : %d %d %d\n", i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3]));
|
||
}
|
||
} else {
|
||
size_t palSize = min(pal.size(), (size_t)72); // WLEDMM use native min/max
|
||
palSize -= palSize % 4; // make sure size is multiple of 4
|
||
for (size_t i=0; i<palSize && pal[i].as<int>()<256; i+=4) {
|
||
tcp[ i ] = (uint8_t) pal[ i ].as<int>(); // index
|
||
tcp[i+1] = gamma8((uint8_t) pal[i+1].as<int>()); // R
|
||
tcp[i+2] = gamma8((uint8_t) pal[i+2].as<int>()); // G
|
||
tcp[i+3] = gamma8((uint8_t) pal[i+3].as<int>()); // B
|
||
DEBUG_PRINTF("%d(%d) : %d %d %d\n", i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3]));
|
||
}
|
||
}
|
||
customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp));
|
||
} else {
|
||
DEBUG_PRINTLN(F("Wrong palette format."));
|
||
}
|
||
}
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
//load custom mapping table from JSON file (called from finalizeInit() or deserializeState())
|
||
bool WS2812FX::deserializeMap(uint8_t n) {
|
||
// 2D support creates its own ledmap (on the fly) if a ledmap.json exists it will overwrite built one.
|
||
|
||
char fileName[42] = {'\0'}; // WLEDMM we need at least 32 + 7 bytes
|
||
//WLEDMM: als support segment name ledmaps
|
||
bool isFile = false;;
|
||
if (n<10) {
|
||
strcpy_P(fileName, PSTR("/ledmap"));
|
||
if (n) sprintf(fileName +7, "%d", n); //WLEDMM: trick to not include 0 in ledmap.json
|
||
strcat(fileName, ".json");
|
||
isFile = WLED_FS.exists(fileName);
|
||
} else { //WLEDMM add segment name as ledmap.name
|
||
uint8_t segment_index = 0;
|
||
for (segment &seg : _segments) {
|
||
if (n == 10 + segment_index && !isFile && seg.name != nullptr) {
|
||
snprintf_P(fileName, sizeof(fileName) -1, PSTR("/%s.json"), seg.name);
|
||
fileName[sizeof(fileName) -1] = '\0';
|
||
isFile = WLED_FS.exists(fileName);
|
||
}
|
||
if (isFile) break;
|
||
segment_index++;
|
||
}
|
||
}
|
||
|
||
if (!isFile) {
|
||
// erase custom mapping if selecting nonexistent ledmap.json (n==0)
|
||
//WLEDMM: doubt this is necessary as return false causes setupMatrix to deal with this !!!!
|
||
if (!isMatrix && !n) {
|
||
customMappingSize = 0;
|
||
loadedLedmap = 0; //WLEDMM
|
||
}
|
||
return false;
|
||
}
|
||
|
||
if (!requestJSONBufferLock(7)) return false;
|
||
|
||
// WLEDMM: before changing maps, make sure our strip is _not_ servicing effects in parallel
|
||
if (strip.isServicing()) {
|
||
USER_PRINTLN(F("deserializeMap(): strip is still drawing effects, waiting ..."));
|
||
strip.waitUntilIdle();
|
||
}
|
||
|
||
//WLEDMM: change upstream code: do not load complete ledmaps in json as this blows up memory, use file read instead
|
||
//read the file
|
||
File f;
|
||
f = WLED_FS.open(fileName, "r");
|
||
if (!f) {
|
||
releaseJSONBufferLock();
|
||
return false; //if file does not exist just exit
|
||
}
|
||
|
||
USER_PRINT(F("Reading LED map from ")); //WLEDMM use USER_PRINT
|
||
USER_PRINTLN(fileName);
|
||
|
||
if (isMatrix) {
|
||
//WLEDMM: read width and height
|
||
memset(fileName, 0, sizeof(fileName)); // clear old buffer - readBytesUntil() does not terminate strings !!!
|
||
f.find("\"width\":");
|
||
f.readBytesUntil('\n', fileName, sizeof(fileName)-1); //hack: use fileName as we have this allocated already
|
||
uint16_t maxWidth = atoi(cleanUpName(fileName));
|
||
//DEBUG_PRINTF(" (\"width\": %s) ", fileName)
|
||
|
||
memset(fileName, 0, sizeof(fileName)); // clear old buffer
|
||
f.find("\"height\":");
|
||
f.readBytesUntil('\n', fileName, sizeof(fileName)-1);
|
||
uint16_t maxHeight = atoi(cleanUpName(fileName));
|
||
//DEBUG_PRINTF(" (\"height\": %s) \n", fileName)
|
||
|
||
#ifndef WLEDMM_NO_MAP_RESET
|
||
//WLEDMM: support ledmap file properties width and height: if found change segment
|
||
if (maxWidth * maxHeight > 0) {
|
||
Segment::maxWidth = maxWidth;
|
||
Segment::maxHeight = maxHeight;
|
||
resetSegments(true); //WLEDMM not makeAutoSegments() as we only want to change bounds
|
||
}
|
||
else
|
||
setUpMatrix(); //reset segment sizes to panels
|
||
#endif
|
||
}
|
||
|
||
USER_PRINTF("deserializeMap %d x %d\n", Segment::maxWidth, Segment::maxHeight);
|
||
|
||
//WLEDMM recreate customMappingTable if more space needed
|
||
if (Segment::maxWidth * Segment::maxHeight > customMappingTableSize) {
|
||
size_t size = max(ledmapMaxSize, size_t(Segment::maxWidth * Segment::maxHeight)); // TroyHacks
|
||
USER_PRINTF("deserializemap customMappingTable alloc %u from %u\n", size, customMappingTableSize);
|
||
//if (customMappingTable != nullptr) delete[] customMappingTable;
|
||
//customMappingTable = new(std::nothrow) uint16_t[size];
|
||
|
||
// don't use new / delete
|
||
if ((size > 0) && (customMappingTable != nullptr)) {
|
||
customMappingTable = (uint16_t*) reallocf(customMappingTable, sizeof(uint16_t) * size); // reallocf will free memory if it cannot resize
|
||
}
|
||
if ((size > 0) && (customMappingTable == nullptr)) { // second try
|
||
DEBUG_PRINTLN("deserializeMap: trying to get fresh memory block.");
|
||
customMappingTable = (uint16_t*) calloc(size, sizeof(uint16_t));
|
||
if (customMappingTable == nullptr) {
|
||
DEBUG_PRINTLN("deserializeMap: alloc failed!");
|
||
errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
|
||
}
|
||
}
|
||
if ((errorFlag == ERR_LOW_MEM) && (size > 0) && (customMappingTable != nullptr)) errorFlag = ERR_NONE; // reset error flag
|
||
if (customMappingTable != nullptr) customMappingTableSize = size;
|
||
}
|
||
|
||
if (customMappingTable != nullptr) {
|
||
customMappingSize = Segment::maxWidth * Segment::maxHeight;
|
||
// WLEDMM reset mapping table before loading
|
||
//memset(customMappingTable, 0xFF, customMappingTableSize * sizeof(uint16_t)); // FFFF = no pixel
|
||
for (unsigned i=0; i<customMappingTableSize; i++) customMappingTable[i]=i; // "neutral" 1:1 mapping
|
||
|
||
//WLEDMM: find the map values
|
||
f.find("\"map\":[");
|
||
uint16_t i=0;
|
||
do { //for each element in the array
|
||
int mapi = f.readStringUntil(',').toInt();
|
||
// USER_PRINTF(", %d(%d)", mapi, i);
|
||
if (i < customMappingSize) customMappingTable[i++] = (uint16_t) (mapi<0 ? 0xFFFFU : mapi); // WLEDMM do not write past array bounds
|
||
} while (f.available() && (i < customMappingSize));
|
||
|
||
loadedLedmap = n;
|
||
f.close();
|
||
|
||
USER_PRINTF("Custom ledmap: %d size=%d\n", loadedLedmap, customMappingSize);
|
||
#ifdef WLED_DEBUG_MAPS
|
||
for (uint16_t j=0; j<customMappingSize; j++) { // fixing a minor warning: declaration of 'i' shadows a previous local
|
||
if (!(j%Segment::maxWidth)) DEBUG_PRINTLN();
|
||
DEBUG_PRINTF("%4d,", customMappingTable[j]);
|
||
}
|
||
DEBUG_PRINTLN();
|
||
#endif
|
||
} else { // memory allocation error
|
||
customMappingTableSize = 0;
|
||
errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
|
||
USER_PRINTLN(F("Deserializemap: Ledmap alloc error."));
|
||
USER_FLUSH();
|
||
}
|
||
|
||
releaseJSONBufferLock();
|
||
return true;
|
||
}
|
||
|
||
|
||
WS2812FX* WS2812FX::instance = nullptr;
|
||
|
||
const char JSON_mode_names[] PROGMEM = R"=====(["FX names moved"])=====";
|
||
//WLEDMM netmindz ar palette add Audio responsive
|
||
const char JSON_palette_names[] PROGMEM = R"=====([
|
||
"Default","* Random Smooth ☾","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean",
|
||
"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash",
|
||
"Pastel","Sunset 2","Beach","Vintage","Departure","Landscape","Beech","Sherbet","Hult","Hult 64",
|
||
"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn",
|
||
"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura",
|
||
"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf",
|
||
"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide",
|
||
"Candy2","Audio Responsive Ratio ☾","Audio Responsive Hue ☾","Audio Responsive Ramp ☾","* Random Cycle"
|
||
])=====";
|