2019-02-09 15:37:20 +00:00
/*
WS2812FX_fcn . cpp contains all utility functions
Harm Aldick - 2016
www . aldick . org
LICENSE
The MIT License ( MIT )
Copyright ( c ) 2016 Harm Aldick
Permission is hereby granted , free of charge , to any person obtaining a copy
of this software and associated documentation files ( the " Software " ) , to deal
in the Software without restriction , including without limitation the rights
to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
copies of the Software , and to permit persons to whom the Software is
furnished to do so , subject to the following conditions :
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software .
THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE .
Modified heavily for WLED
*/
2021-02-24 19:23:32 +00:00
# include "wled.h"
2019-10-07 21:38:21 +00:00
# include "FX.h"
2019-02-09 15:37:20 +00:00
# include "palettes.h"
2021-02-13 00:02:14 +00:00
/*
Custom per - LED mapping has moved !
2020-03-20 23:57:54 +00:00
2021-02-13 00:02:14 +00:00
Create a file " ledmap.json " using the edit page .
2020-03-20 23:57:54 +00:00
2021-02-13 00:02:14 +00:00
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 ] }
2020-03-20 23:57:54 +00:00
2021-02-13 00:02:14 +00:00
another example . Switches direction every 5 LEDs .
{ " map " : [
0 , 1 , 2 , 3 , 4 , 9 , 8 , 7 , 6 , 5 , 10 , 11 , 12 , 13 , 14 ,
2021-02-24 19:23:32 +00:00
19 , 18 , 17 , 16 , 15 , 20 , 21 , 22 , 23 , 24 , 29 , 28 , 27 , 26 , 25 ] }
2021-02-13 00:02:14 +00:00
*/
2020-12-13 18:02:12 +00:00
2021-04-15 08:55:22 +00:00
//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
2021-12-02 12:09:53 +00:00
# define PIXEL_COUNTS DEFAULT_LED_COUNT
2021-04-15 08:55:22 +00:00
# endif
# ifndef DATA_PINS
# define DATA_PINS LEDPIN
# endif
# ifndef DEFAULT_LED_TYPE
# define DEFAULT_LED_TYPE TYPE_WS2812_RGB
# endif
2022-03-15 23:17:49 +00:00
# ifndef DEFAULT_LED_COLOR_ORDER
# define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB
# endif
2021-06-24 00:29:14 +00:00
# if MAX_NUM_SEGMENTS < WLED_MAX_BUSSES
# error "Max segments must be at least max number of busses!"
# endif
2022-07-06 11:13:54 +00:00
2022-07-10 20:23:25 +00:00
///////////////////////////////////////////////////////////////////////////////
2022-07-17 13:58:41 +00:00
// Segment class implementation
2022-07-10 20:23:25 +00:00
///////////////////////////////////////////////////////////////////////////////
2022-07-30 12:20:36 +00:00
uint16_t Segment : : _usedSegmentData = 0U ; // amount of RAM all segments use for their data[]
2022-12-16 21:31:07 +00:00
uint16_t Segment : : maxWidth = DEFAULT_LED_COUNT ;
uint16_t Segment : : maxHeight = 1 ;
2022-07-10 20:23:25 +00:00
2024-05-15 13:34:53 +00:00
CRGBPalette16 Segment : : _currentPalette = CRGBPalette16 ( CRGB : : Black ) ;
2024-02-06 10:06:23 +00:00
CRGBPalette16 Segment : : _randomPalette = generateRandomPalette ( ) ; // was CRGBPalette16(DEFAULT_COLOR);
CRGBPalette16 Segment : : _newRandomPalette = generateRandomPalette ( ) ; // was CRGBPalette16(DEFAULT_COLOR);
uint16_t Segment : : _lastPaletteChange = 0 ; // perhaps it should be per segment
uint16_t Segment : : _lastPaletteBlend = 0 ; //in millis (lowest 16 bits only)
2023-08-03 20:28:53 +00:00
2023-09-02 18:20:51 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2023-08-05 19:01:06 +00:00
bool Segment : : _modeBlend = false ;
2024-04-03 16:38:06 +00:00
uint16_t Segment : : _clipStart = 0 ;
uint16_t Segment : : _clipStop = 0 ;
uint8_t Segment : : _clipStartY = 0 ;
uint8_t Segment : : _clipStopY = 1 ;
2023-09-02 18:20:51 +00:00
# endif
2023-08-05 19:01:06 +00:00
2022-07-30 12:50:11 +00:00
// copy constructor
2022-07-17 13:58:41 +00:00
Segment : : Segment ( const Segment & orig ) {
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("-- Copy segment constructor: %p -> %p\n"), &orig, this);
2023-03-05 21:56:14 +00:00
memcpy ( ( void * ) this , ( void * ) & orig , sizeof ( Segment ) ) ;
2023-09-10 16:52:14 +00:00
_t = nullptr ; // copied segment cannot be in transition
2022-07-17 13:58:41 +00:00
name = nullptr ;
data = nullptr ;
_dataLen = 0 ;
if ( orig . name ) { name = new char [ strlen ( orig . name ) + 1 ] ; if ( name ) strcpy ( name , orig . name ) ; }
2022-07-29 18:24:29 +00:00
if ( orig . data ) { if ( allocateData ( orig . _dataLen ) ) memcpy ( data , orig . data , orig . _dataLen ) ; }
2022-07-17 13:58:41 +00:00
}
2022-07-30 12:50:11 +00:00
// move constructor
2022-07-17 13:58:41 +00:00
Segment : : Segment ( Segment & & orig ) noexcept {
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("-- Move segment constructor: %p -> %p\n"), &orig, this);
2023-03-05 21:56:14 +00:00
memcpy ( ( void * ) this , ( void * ) & orig , sizeof ( Segment ) ) ;
2023-10-29 10:27:17 +00:00
orig . _t = nullptr ; // old segment cannot be in transition any more
2022-07-17 13:58:41 +00:00
orig . name = nullptr ;
orig . data = nullptr ;
2022-07-19 14:16:43 +00:00
orig . _dataLen = 0 ;
2022-07-17 13:58:41 +00:00
}
2022-07-30 12:50:11 +00:00
// copy assignment
2022-07-17 13:58:41 +00:00
Segment & Segment : : operator = ( const Segment & orig ) {
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this);
2022-07-17 13:58:41 +00:00
if ( this ! = & orig ) {
2022-08-06 10:39:12 +00:00
// clean destination
2023-10-12 14:15:16 +00:00
if ( name ) { delete [ ] name ; name = nullptr ; }
2023-10-29 10:27:17 +00:00
stopTransition ( ) ;
2022-07-17 13:58:41 +00:00
deallocateData ( ) ;
2022-08-06 10:39:12 +00:00
// copy source
2023-03-05 21:56:14 +00:00
memcpy ( ( void * ) this , ( void * ) & orig , sizeof ( Segment ) ) ;
2022-08-06 10:39:12 +00:00
// erase pointers to allocated data
2022-07-17 13:58:41 +00:00
data = nullptr ;
_dataLen = 0 ;
2022-08-06 10:39:12 +00:00
// copy source data
2022-07-17 13:58:41 +00:00
if ( orig . name ) { name = new char [ strlen ( orig . name ) + 1 ] ; if ( name ) strcpy ( name , orig . name ) ; }
2022-07-29 18:24:29 +00:00
if ( orig . data ) { if ( allocateData ( orig . _dataLen ) ) memcpy ( data , orig . data , orig . _dataLen ) ; }
2022-07-17 13:58:41 +00:00
}
return * this ;
}
2022-07-30 12:50:11 +00:00
// move assignment
2022-07-17 13:58:41 +00:00
Segment & Segment : : operator = ( Segment & & orig ) noexcept {
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this);
2022-07-17 13:58:41 +00:00
if ( this ! = & orig ) {
2023-07-12 18:52:34 +00:00
if ( name ) { delete [ ] name ; name = nullptr ; } // free old name
2023-10-29 10:27:17 +00:00
stopTransition ( ) ;
2022-07-17 13:58:41 +00:00
deallocateData ( ) ; // free old runtime data
2023-03-05 21:56:14 +00:00
memcpy ( ( void * ) this , ( void * ) & orig , sizeof ( Segment ) ) ;
2022-07-17 13:58:41 +00:00
orig . name = nullptr ;
orig . data = nullptr ;
2022-07-19 14:16:43 +00:00
orig . _dataLen = 0 ;
2023-09-10 16:52:14 +00:00
orig . _t = nullptr ; // old segment cannot be in transition
2022-07-17 13:58:41 +00:00
}
return * this ;
2022-07-10 20:23:25 +00:00
}
2023-12-27 18:36:25 +00:00
// allocates effect data buffer on heap and initialises (erases) it
2023-10-15 11:06:40 +00:00
bool IRAM_ATTR Segment : : allocateData ( size_t len ) {
2023-12-27 18:36:25 +00:00
if ( len = = 0 ) return false ; // nothing to do
2023-12-16 11:38:15 +00:00
if ( data & & _dataLen > = len ) { // already allocated enough (reduce fragmentation)
if ( call = = 0 ) memset ( data , 0 , len ) ; // erase buffer if called during effect initialisation
return true ;
}
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n", len, this);
2023-12-27 18:36:25 +00:00
deallocateData ( ) ; // if the old buffer was smaller release it first
2023-08-12 10:45:11 +00:00
if ( Segment : : getUsedSegmentData ( ) + len > MAX_SEGMENT_DATA ) {
// not enough memory
2023-09-08 14:01:11 +00:00
DEBUG_PRINT ( F ( " !!! Effect RAM depleted: " ) ) ;
2024-02-17 10:33:42 +00:00
DEBUG_PRINTF_P ( PSTR ( " %d/%d !!! \n " ) , len , Segment : : getUsedSegmentData ( ) ) ;
2023-12-27 18:36:25 +00:00
errorFlag = ERR_NORAM ;
2023-08-12 10:45:11 +00:00
return false ;
}
2023-05-28 20:50:19 +00:00
// do not use SPI RAM on ESP32 since it is slow
2023-12-27 18:36:25 +00:00
data = ( byte * ) calloc ( len , sizeof ( byte ) ) ;
2023-12-16 11:38:15 +00:00
if ( ! data ) { DEBUG_PRINTLN ( F ( " !!! Allocation failed. !!! " ) ) ; return false ; } // allocation failed
2022-07-30 12:20:36 +00:00
Segment : : addUsedSegmentData ( len ) ;
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data);
2022-07-17 13:58:41 +00:00
_dataLen = len ;
return true ;
}
2023-10-15 11:06:40 +00:00
void IRAM_ATTR Segment : : deallocateData ( ) {
2023-09-08 10:06:23 +00:00
if ( ! data ) { _dataLen = 0 ; return ; }
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("--- Released data (%p): %d/%d -> %p\n"), this, _dataLen, Segment::getUsedSegmentData(), data);
2023-09-08 10:06:23 +00:00
if ( ( Segment : : getUsedSegmentData ( ) > 0 ) & & ( _dataLen > 0 ) ) { // check that we don't have a dangling / inconsistent data pointer
free ( data ) ;
} else {
2023-09-08 12:06:57 +00:00
DEBUG_PRINT ( F ( " ---- Released data " ) ) ;
2024-02-17 10:33:42 +00:00
DEBUG_PRINTF_P ( PSTR ( " (%p): " ) , this ) ;
2023-09-08 12:06:57 +00:00
DEBUG_PRINT ( F ( " inconsistent UsedSegmentData " ) ) ;
2024-02-17 10:33:42 +00:00
DEBUG_PRINTF_P ( PSTR ( " (%d/%d) " ) , _dataLen , Segment : : getUsedSegmentData ( ) ) ;
2023-09-08 14:01:11 +00:00
DEBUG_PRINTLN ( F ( " , cowardly refusing to free nothing. " ) ) ;
2023-09-08 10:06:23 +00:00
}
2022-07-17 13:58:41 +00:00
data = nullptr ;
2023-08-07 14:50:18 +00:00
Segment : : addUsedSegmentData ( _dataLen < = Segment : : getUsedSegmentData ( ) ? - _dataLen : - Segment : : getUsedSegmentData ( ) ) ;
2022-07-17 13:58:41 +00:00
_dataLen = 0 ;
}
2023-01-06 08:10:39 +00:00
/**
2022-07-17 13:58:41 +00:00
* If reset of this segment was requested , clears runtime
* settings of this segment .
* Must not be called while an effect mode function is running
2023-01-06 08:10:39 +00:00
* because it could access the data buffer and this method
2022-07-17 13:58:41 +00:00
* may free that data buffer .
*/
void Segment : : resetIfRequired ( ) {
2023-07-13 01:09:42 +00:00
if ( ! reset ) return ;
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this);
2023-12-27 18:36:25 +00:00
if ( data & & _dataLen > 0 ) memset ( data , 0 , _dataLen ) ; // prevent heap fragmentation (just erase buffer instead of deallocateData())
2023-07-13 01:09:42 +00:00
next_time = 0 ; step = 0 ; call = 0 ; aux0 = 0 ; aux1 = 0 ;
reset = false ;
2022-07-10 20:23:25 +00:00
}
2023-10-15 11:06:40 +00:00
CRGBPalette16 IRAM_ATTR & Segment : : loadPalette ( CRGBPalette16 & targetPalette , uint8_t pal ) {
2022-07-29 10:15:56 +00:00
if ( pal < 245 & & pal > GRADIENT_PALETTE_COUNT + 13 ) pal = 0 ;
2024-05-15 13:34:53 +00:00
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
2022-07-28 21:19:58 +00:00
//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
2023-02-21 16:07:32 +00:00
case FX_MODE_RAILWAY : pal = 3 ; break ; // prim + sec
2023-04-26 23:22:33 +00:00
case FX_MODE_2DSOAP : pal = 11 ; break ; // rainbow colors
2022-07-28 21:19:58 +00:00
}
switch ( pal ) {
case 0 : //default palette. Exceptions for specific effects above
targetPalette = PartyColors_p ; break ;
2024-02-06 10:06:23 +00:00
case 1 : //randomly generated palette
2024-01-30 21:28:40 +00:00
targetPalette = _randomPalette ; //random palette is generated at intervals in handleRandomPalette()
2024-02-06 10:06:23 +00:00
break ;
2022-07-28 21:19:58 +00:00
case 2 : { //primary color only
2022-09-04 18:17:05 +00:00
CRGB prim = gamma32 ( colors [ 0 ] ) ;
2022-07-28 21:19:58 +00:00
targetPalette = CRGBPalette16 ( prim ) ; break ; }
case 3 : { //primary + secondary
2022-09-04 18:17:05 +00:00
CRGB prim = gamma32 ( colors [ 0 ] ) ;
CRGB sec = gamma32 ( colors [ 1 ] ) ;
2022-07-28 21:19:58 +00:00
targetPalette = CRGBPalette16 ( prim , prim , sec , sec ) ; break ; }
case 4 : { //primary + secondary + tertiary
2022-09-04 18:17:05 +00:00
CRGB prim = gamma32 ( colors [ 0 ] ) ;
CRGB sec = gamma32 ( colors [ 1 ] ) ;
CRGB ter = gamma32 ( colors [ 2 ] ) ;
2022-07-28 21:19:58 +00:00
targetPalette = CRGBPalette16 ( ter , sec , prim ) ; break ; }
2024-02-06 13:47:20 +00:00
case 5 : { //primary + secondary (+tertiary if not off), more distinct
2022-09-04 18:17:05 +00:00
CRGB prim = gamma32 ( colors [ 0 ] ) ;
CRGB sec = gamma32 ( colors [ 1 ] ) ;
2022-07-28 21:19:58 +00:00
if ( colors [ 2 ] ) {
2022-09-04 18:17:05 +00:00
CRGB ter = gamma32 ( colors [ 2 ] ) ;
2022-07-28 21:19:58 +00:00
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 ;
default : //progmem palettes
if ( pal > 245 ) {
targetPalette = strip . customPalettes [ 255 - pal ] ; // we checked bounds above
} else {
2023-08-03 20:28:53 +00:00
byte tcp [ 72 ] ;
2022-07-28 21:19:58 +00:00
memcpy_P ( tcp , ( byte * ) pgm_read_dword ( & ( gGradientPalettes [ pal - 13 ] ) ) , 72 ) ;
targetPalette . loadDynamicGradientPalette ( tcp ) ;
}
break ;
}
return targetPalette ;
}
2022-08-24 21:04:51 +00:00
void Segment : : startTransition ( uint16_t dur ) {
2023-09-10 16:52:14 +00:00
if ( dur = = 0 ) {
2024-02-06 13:47:20 +00:00
if ( isInTransition ( ) ) _t - > _dur = dur ; // this will stop transition in next handleTransition()
2023-06-23 21:49:54 +00:00
return ;
}
2023-09-10 16:52:14 +00:00
if ( isInTransition ( ) ) return ; // already in transition no need to store anything
2022-08-24 21:04:51 +00:00
// starting a transition has to occur before change so we get current values 1st
2023-06-23 21:49:54 +00:00
_t = new Transition ( dur ) ; // no previous transition running
2022-08-24 21:04:51 +00:00
if ( ! _t ) return ; // failed to allocate data
2023-06-23 21:49:54 +00:00
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t);
2023-09-10 16:52:14 +00:00
loadPalette ( _t - > _palT , palette ) ;
2024-06-02 19:30:44 +00:00
_t - > _palTid = palette ;
2023-08-12 10:45:11 +00:00
_t - > _briT = on ? opacity : 0 ;
_t - > _cctT = cct ;
2023-09-02 18:20:51 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2024-04-08 14:24:27 +00:00
swapSegenv ( _t - > _segT ) ;
_t - > _modeT = mode ;
_t - > _segT . _dataLenT = 0 ;
_t - > _segT . _dataT = nullptr ;
if ( _dataLen > 0 & & data ) {
_t - > _segT . _dataT = ( byte * ) malloc ( _dataLen ) ;
if ( _t - > _segT . _dataT ) {
//DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT);
memcpy ( _t - > _segT . _dataT , data , _dataLen ) ;
_t - > _segT . _dataLenT = _dataLen ;
2023-08-05 11:50:08 +00:00
}
}
2023-09-02 18:20:51 +00:00
# else
for ( size_t i = 0 ; i < NUM_COLORS ; i + + ) _t - > _colorT [ i ] = colors [ i ] ;
# endif
2022-08-24 21:04:51 +00:00
}
2023-08-07 14:50:18 +00:00
void Segment : : stopTransition ( ) {
2023-09-10 16:52:14 +00:00
if ( isInTransition ( ) ) {
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("-- Stopping transition: %p\n"), this);
2023-09-02 18:20:51 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2023-08-12 10:45:11 +00:00
if ( _t - > _segT . _dataT & & _t - > _segT . _dataLenT > 0 ) {
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("-- Released duplicate data (%d) for %p: %p\n"), _t->_segT._dataLenT, this, _t->_segT._dataT);
2023-08-12 10:45:11 +00:00
free ( _t - > _segT . _dataT ) ;
_t - > _segT . _dataT = nullptr ;
2023-09-08 10:06:23 +00:00
_t - > _segT . _dataLenT = 0 ;
2023-08-07 14:50:18 +00:00
}
2023-09-02 18:20:51 +00:00
# endif
2023-08-07 14:50:18 +00:00
delete _t ;
_t = nullptr ;
}
}
2022-08-24 21:04:51 +00:00
// transition progression between 0-65535
2024-08-05 14:42:21 +00:00
uint16_t IRAM_ATTR Segment : : progress ( ) const {
2023-09-10 16:52:14 +00:00
if ( isInTransition ( ) ) {
2024-05-15 13:34:53 +00:00
unsigned diff = millis ( ) - _t - > _start ;
if ( _t - > _dur > 0 & & diff < _t - > _dur ) return diff * 0xFFFFU / _t - > _dur ;
2023-08-05 11:50:08 +00:00
}
return 0xFFFFU ;
2022-08-24 21:04:51 +00:00
}
2023-09-02 18:20:51 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2023-08-12 10:45:11 +00:00
void Segment : : swapSegenv ( tmpsegd_t & tmpSeg ) {
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("-- Saving temp seg: %p->(%p) [%d->%p]\n"), this, &tmpSeg, _dataLen, data);
2023-08-12 10:45:11 +00:00
tmpSeg . _optionsT = options ;
for ( size_t i = 0 ; i < NUM_COLORS ; i + + ) tmpSeg . _colorT [ i ] = colors [ i ] ;
tmpSeg . _speedT = speed ;
tmpSeg . _intensityT = intensity ;
tmpSeg . _custom1T = custom1 ;
tmpSeg . _custom2T = custom2 ;
tmpSeg . _custom3T = custom3 ;
tmpSeg . _check1T = check1 ;
tmpSeg . _check2T = check2 ;
tmpSeg . _check3T = check3 ;
tmpSeg . _aux0T = aux0 ;
tmpSeg . _aux1T = aux1 ;
tmpSeg . _stepT = step ;
tmpSeg . _callT = call ;
tmpSeg . _dataT = data ;
tmpSeg . _dataLenT = _dataLen ;
2023-09-08 14:01:11 +00:00
if ( _t & & & tmpSeg ! = & ( _t - > _segT ) ) {
2023-08-12 10:45:11 +00:00
// swap SEGENV with transitional data
options = _t - > _segT . _optionsT ;
for ( size_t i = 0 ; i < NUM_COLORS ; i + + ) colors [ i ] = _t - > _segT . _colorT [ i ] ;
speed = _t - > _segT . _speedT ;
intensity = _t - > _segT . _intensityT ;
custom1 = _t - > _segT . _custom1T ;
custom2 = _t - > _segT . _custom2T ;
custom3 = _t - > _segT . _custom3T ;
check1 = _t - > _segT . _check1T ;
check2 = _t - > _segT . _check2T ;
check3 = _t - > _segT . _check3T ;
aux0 = _t - > _segT . _aux0T ;
aux1 = _t - > _segT . _aux1T ;
step = _t - > _segT . _stepT ;
call = _t - > _segT . _callT ;
data = _t - > _segT . _dataT ;
_dataLen = _t - > _segT . _dataLenT ;
}
2023-08-05 11:50:08 +00:00
}
2023-08-12 10:45:11 +00:00
void Segment : : restoreSegenv ( tmpsegd_t & tmpSeg ) {
2024-02-17 10:33:42 +00:00
//DEBUG_PRINTF_P(PSTR("-- Restoring temp seg: %p->(%p) [%d->%p]\n"), &tmpSeg, this, _dataLen, data);
2023-08-12 10:45:11 +00:00
if ( _t & & & ( _t - > _segT ) ! = & tmpSeg ) {
// update possibly changed variables to keep old effect running correctly
_t - > _segT . _aux0T = aux0 ;
_t - > _segT . _aux1T = aux1 ;
_t - > _segT . _stepT = step ;
_t - > _segT . _callT = call ;
2024-02-17 10:33:42 +00:00
//if (_t->_segT._dataT != data) DEBUG_PRINTF_P(PSTR("--- data re-allocated: (%p) %p -> %p\n"), this, _t->_segT._dataT, data);
2023-09-12 04:17:06 +00:00
_t - > _segT . _dataT = data ;
_t - > _segT . _dataLenT = _dataLen ;
2023-08-05 11:50:08 +00:00
}
2023-08-12 10:45:11 +00:00
options = tmpSeg . _optionsT ;
for ( size_t i = 0 ; i < NUM_COLORS ; i + + ) colors [ i ] = tmpSeg . _colorT [ i ] ;
speed = tmpSeg . _speedT ;
intensity = tmpSeg . _intensityT ;
custom1 = tmpSeg . _custom1T ;
custom2 = tmpSeg . _custom2T ;
custom3 = tmpSeg . _custom3T ;
check1 = tmpSeg . _check1T ;
check2 = tmpSeg . _check2T ;
check3 = tmpSeg . _check3T ;
aux0 = tmpSeg . _aux0T ;
aux1 = tmpSeg . _aux1T ;
step = tmpSeg . _stepT ;
call = tmpSeg . _callT ;
data = tmpSeg . _dataT ;
_dataLen = tmpSeg . _dataLenT ;
2023-08-07 14:50:18 +00:00
}
2023-09-02 18:20:51 +00:00
# endif
2023-08-07 14:50:18 +00:00
2024-08-05 14:42:21 +00:00
uint8_t IRAM_ATTR Segment : : currentBri ( bool useCct ) const {
2023-08-07 14:50:18 +00:00
uint32_t prog = progress ( ) ;
2024-04-03 16:38:06 +00:00
uint32_t curBri = useCct ? cct : ( on ? opacity : 0 ) ;
2023-08-07 14:50:18 +00:00
if ( prog < 0xFFFFU ) {
2024-04-03 16:38:06 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2024-04-03 18:55:59 +00:00
uint8_t tmpBri = useCct ? _t - > _cctT : ( _t - > _segT . _optionsT & 0x0004 ? _t - > _briT : 0 ) ;
2024-04-03 16:38:06 +00:00
if ( blendingStyle > BLEND_STYLE_FADE ) return _modeBlend ? tmpBri : curBri ; // not fade/blend transition, each effect uses its brightness
2024-04-03 18:55:59 +00:00
# else
uint8_t tmpBri = useCct ? _t - > _cctT : _t - > _briT ;
2024-04-03 16:38:06 +00:00
# endif
curBri * = prog ;
curBri + = tmpBri * ( 0xFFFFU - prog ) ;
2023-09-10 16:52:14 +00:00
return curBri / 0xFFFFU ;
2023-08-07 14:50:18 +00:00
}
2024-04-03 16:38:06 +00:00
return curBri ;
2023-08-07 14:50:18 +00:00
}
2024-08-05 14:42:21 +00:00
uint8_t IRAM_ATTR Segment : : currentMode ( ) const {
2023-09-02 18:20:51 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2024-04-26 18:11:46 +00:00
unsigned prog = progress ( ) ;
2024-07-05 19:23:59 +00:00
if ( prog = = 0xFFFFU ) return mode ;
if ( blendingStyle > BLEND_STYLE_FADE ) {
// workaround for on/off transition to respect blending style
uint8_t modeT = ( bri ! = briT ) & & bri ? FX_MODE_STATIC : _t - > _modeT ; // On/Off transition active (bri!=briT) and final bri>0 : old mode is STATIC
uint8_t modeS = ( bri ! = briT ) & & ! bri ? FX_MODE_STATIC : mode ; // On/Off transition active (bri!=briT) and final bri==0 : new mode is STATIC
return _modeBlend ? modeT : modeS ;
}
return _modeBlend ? _t - > _modeT : mode ;
# else
2023-09-10 16:52:14 +00:00
return mode ;
2024-07-05 19:23:59 +00:00
# endif
2022-08-24 21:04:51 +00:00
}
2024-08-05 14:42:21 +00:00
uint32_t IRAM_ATTR Segment : : currentColor ( uint8_t slot ) const {
2024-01-22 19:48:03 +00:00
if ( slot > = NUM_COLORS ) slot = 0 ;
2024-04-03 16:38:06 +00:00
uint32_t prog = progress ( ) ;
if ( prog = = 0xFFFFU ) return colors [ slot ] ;
2023-09-02 18:20:51 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2024-07-05 19:23:59 +00:00
if ( blendingStyle > BLEND_STYLE_FADE ) {
// workaround for on/off transition to respect blending style
uint32_t colT = ( bri ! = briT ) & & bri ? BLACK : _t - > _segT . _colorT [ slot ] ; // On/Off transition active (bri!=briT) and final bri>0 : old color is BLACK
uint32_t colS = ( bri ! = briT ) & & ! bri ? BLACK : colors [ slot ] ; // On/Off transition active (bri!=briT) and final bri==0 : new color is BLACK
return _modeBlend ? colT : colS ;
}
2024-04-03 16:38:06 +00:00
return color_blend ( _t - > _segT . _colorT [ slot ] , colors [ slot ] , prog , true ) ;
2023-09-02 18:20:51 +00:00
# else
2024-04-03 16:38:06 +00:00
return color_blend ( _t - > _colorT [ slot ] , colors [ slot ] , prog , true ) ;
2023-09-02 18:20:51 +00:00
# endif
2022-08-24 21:04:51 +00:00
}
2024-08-05 14:47:25 +00:00
uint8_t IRAM_ATTR Segment : : currentPalette ( ) const {
2024-06-02 19:30:44 +00:00
unsigned prog = progress ( ) ;
if ( prog < 0xFFFFU ) {
# ifndef WLED_DISABLE_MODE_BLEND
if ( blendingStyle > BLEND_STYLE_FADE & & _modeBlend ) return _t - > _palTid ;
# else
return _t - > _palTid ;
# endif
}
return palette ;
}
2024-05-15 13:34:53 +00:00
void Segment : : setCurrentPalette ( ) {
loadPalette ( _currentPalette , palette ) ;
2024-04-26 18:11:46 +00:00
unsigned prog = progress ( ) ;
2024-06-02 19:30:44 +00:00
if ( prog < 0xFFFFU ) {
2024-04-03 16:38:06 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2024-06-02 19:30:44 +00:00
if ( blendingStyle > BLEND_STYLE_FADE ) {
//if (_modeBlend) loadPalette(_currentPalette, _t->_palTid); // not fade/blend transition, each effect uses its palette
if ( _modeBlend ) _currentPalette = _t - > _palT ; // not fade/blend transition, each effect uses its palette
} else
2024-04-03 16:38:06 +00:00
# endif
2024-06-02 19:30:44 +00:00
{
// 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 noOfBlends = ( ( 255U * prog ) / 0xFFFFU ) - _t - > _prevPaletteBlends ;
for ( unsigned i = 0 ; i < noOfBlends ; i + + , _t - > _prevPaletteBlends + + ) nblendPaletteTowardPalette ( _t - > _palT , _currentPalette , 48 ) ;
_currentPalette = _t - > _palT ; // copy transitioning/temporary palette
}
2022-07-28 21:19:58 +00:00
}
}
2024-02-06 10:06:23 +00:00
// relies on WS2812FX::service() to call it for each frame
2023-08-03 20:28:53 +00:00
void Segment : : handleRandomPalette ( ) {
2024-02-06 10:06:23 +00:00
// is it time to generate a new palette?
2024-04-08 14:24:27 +00:00
if ( ( uint16_t ) ( millis ( ) / 1000U ) - _lastPaletteChange > randomPaletteChangeTime ) {
_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette ( _randomPalette ) : generateRandomPalette ( ) ;
_lastPaletteChange = ( uint16_t ) ( millis ( ) / 1000U ) ;
_lastPaletteBlend = ( uint16_t ) ( millis ( ) ) - 512 ; // starts blending immediately
2024-01-30 21:28:40 +00:00
}
2024-04-08 14:24:27 +00:00
// assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less)
// in reality there need to be 255 blends to fully blend two entirely different palettes
if ( ( uint16_t ) millis ( ) - _lastPaletteBlend < strip . getTransition ( ) > > 7 ) return ; // not yet time to fade, delay the update
_lastPaletteBlend = ( uint16_t ) millis ( ) ;
2023-08-05 11:50:08 +00:00
nblendPaletteTowardPalette ( _randomPalette , _newRandomPalette , 48 ) ;
2023-08-03 20:28:53 +00:00
}
2023-07-13 01:09:42 +00:00
// segId is given when called from network callback, changes are queued if that segment is currently in its effect function
2023-12-22 14:39:07 +00:00
void Segment : : setUp ( uint16_t i1 , uint16_t i2 , uint8_t grp , uint8_t spc , uint16_t ofs , uint16_t i1Y , uint16_t i2Y ) {
2023-07-12 18:52:34 +00:00
// return if neither bounds nor grouping have changed
2022-12-22 17:13:32 +00:00
bool boundsUnchanged = ( start = = i1 & & stop = = i2 ) ;
2022-12-23 15:37:13 +00:00
# ifndef WLED_DISABLE_2D
if ( Segment : : maxHeight > 1 ) boundsUnchanged & = ( startY = = i1Y & & stopY = = i2Y ) ; // 2D
# endif
2022-12-22 17:13:32 +00:00
if ( boundsUnchanged
& & ( ! grp | | ( grouping = = grp & & spacing = = spc ) )
& & ( ofs = = UINT16_MAX | | ofs = = offset ) ) return ;
2023-09-10 16:52:14 +00:00
stateChanged = true ; // send UDP/WS broadcast
2023-07-12 18:52:34 +00:00
if ( stop ) fill ( BLACK ) ; // turn old segment range off (clears pixels if changing spacing)
if ( grp ) { // prevent assignment of 0
2022-12-22 17:13:32 +00:00
grouping = grp ;
spacing = spc ;
2023-07-12 18:52:34 +00:00
} else {
grouping = 1 ;
spacing = 0 ;
2022-12-22 17:13:32 +00:00
}
if ( ofs < UINT16_MAX ) offset = ofs ;
2023-07-12 18:52:34 +00:00
2023-09-10 16:52:14 +00:00
DEBUG_PRINT ( F ( " setUp segment: " ) ) ; DEBUG_PRINT ( i1 ) ;
DEBUG_PRINT ( ' , ' ) ; DEBUG_PRINT ( i2 ) ;
DEBUG_PRINT ( F ( " -> " ) ) ; DEBUG_PRINT ( i1Y ) ;
DEBUG_PRINT ( ' , ' ) ; DEBUG_PRINTLN ( i2Y ) ;
2022-12-22 17:13:32 +00:00
markForReset ( ) ;
2023-07-13 11:08:36 +00:00
if ( boundsUnchanged ) return ;
2023-07-13 01:09:42 +00:00
// apply change immediately
if ( i2 < = i1 ) { //disable segment
stop = 0 ;
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 ( 1 , i2 ) ) ;
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 ( 1 , i2Y ) ;
}
# endif
// safety check
if ( start > = stop | | startY > = stopY ) {
stop = 0 ;
return ;
2023-07-12 18:52:34 +00:00
}
2023-07-13 01:09:42 +00:00
refreshLightCapabilities ( ) ;
2022-12-22 17:13:32 +00:00
}
2022-07-10 20:23:25 +00:00
bool Segment : : setColor ( uint8_t slot , uint32_t c ) { //returns true if changed
if ( slot > = NUM_COLORS | | c = = colors [ slot ] ) return false ;
2023-03-02 17:21:55 +00:00
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
}
2024-07-05 19:23:59 +00:00
//DEBUG_PRINTF_P(PSTR("- Starting color transition: %d [0x%X]\n"), slot, c);
2024-04-08 14:24:27 +00:00
startTransition ( strip . getTransition ( ) ) ; // start transition prior to change
2022-07-10 20:23:25 +00:00
colors [ slot ] = c ;
2022-11-13 11:13:49 +00:00
stateChanged = true ; // send UDP/WS broadcast
2022-07-10 20:23:25 +00:00
return true ;
}
void Segment : : setCCT ( uint16_t k ) {
2022-07-06 11:13:54 +00:00
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 ;
2024-07-05 19:23:59 +00:00
//DEBUG_PRINTF_P(PSTR("- Starting CCT transition: %d\n"), k);
2024-04-08 14:24:27 +00:00
startTransition ( strip . getTransition ( ) ) ; // start transition prior to change
2022-07-06 11:13:54 +00:00
cct = k ;
2022-11-13 11:13:49 +00:00
stateChanged = true ; // send UDP/WS broadcast
2022-07-06 11:13:54 +00:00
}
2022-07-10 20:23:25 +00:00
void Segment : : setOpacity ( uint8_t o ) {
2022-07-06 11:13:54 +00:00
if ( opacity = = o ) return ;
2024-07-05 19:23:59 +00:00
//DEBUG_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o);
2024-04-08 14:24:27 +00:00
startTransition ( strip . getTransition ( ) ) ; // start transition prior to change
2022-07-06 11:13:54 +00:00
opacity = o ;
2022-11-13 11:13:49 +00:00
stateChanged = true ; // send UDP/WS broadcast
2022-07-06 11:13:54 +00:00
}
2022-07-10 20:23:25 +00:00
void Segment : : setOption ( uint8_t n , bool val ) {
2022-08-06 10:39:12 +00:00
bool prevOn = on ;
2024-04-08 14:24:27 +00:00
if ( n = = SEG_OPTION_ON & & val ! = prevOn ) startTransition ( strip . getTransition ( ) ) ; // start transition prior to change
2022-07-06 11:13:54 +00:00
if ( val ) options | = 0x01 < < n ;
else options & = ~ ( 0x01 < < n ) ;
2023-09-10 16:52:14 +00:00
if ( ! ( n = = SEG_OPTION_SELECTED | | n = = SEG_OPTION_RESET ) ) stateChanged = true ; // send UDP/WS broadcast
2022-07-06 11:13:54 +00:00
}
2022-09-29 10:49:12 +00:00
void Segment : : setMode ( uint8_t fx , bool loadDefaults ) {
2023-12-27 18:36:25 +00:00
// skip reserved
while ( fx < strip . getModeCount ( ) & & strncmp_P ( " RSVD " , strip . getModeData ( fx ) , 4 ) = = 0 ) fx + + ;
if ( fx > = strip . getModeCount ( ) ) fx = 0 ; // set solid mode
2022-09-29 10:49:12 +00:00
// if we have a valid mode & is not reserved
2023-12-27 18:36:25 +00:00
if ( fx ! = mode ) {
2023-09-12 04:17:06 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2024-07-05 19:23:59 +00:00
//DEBUG_PRINTF_P(PSTR("- Starting effect transition: %d\n"), fx);
2024-04-08 14:24:27 +00:00
startTransition ( strip . getTransition ( ) ) ; // set effect transitions
2023-09-12 04:17:06 +00:00
# endif
2023-12-27 18:36:25 +00:00
mode = fx ;
// load default values from effect string
if ( loadDefaults ) {
2024-04-26 18:11:46 +00:00
int sOpt ;
2023-12-27 18:36:25 +00:00
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 ;
sOpt = extractModeDefaults ( fx , " m12 " ) ; if ( sOpt > = 0 ) map1D2D = constrain ( sOpt , 0 , 7 ) ; else map1D2D = M12_Pixels ; // reset mapping if not defined (2D FX may not work)
sOpt = extractModeDefaults ( fx , " si " ) ; if ( sOpt > = 0 ) soundSim = constrain ( sOpt , 0 , 3 ) ;
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 ) setPalette ( sOpt ) ; //else setPalette(0);
2022-09-29 10:49:12 +00:00
}
2023-12-27 18:36:25 +00:00
markForReset ( ) ;
stateChanged = true ; // send UDP/WS broadcast
2022-09-29 10:49:12 +00:00
}
}
void Segment : : setPalette ( uint8_t pal ) {
2022-11-25 16:33:29 +00:00
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 ) {
2024-07-05 19:23:59 +00:00
//DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal);
2024-04-08 14:24:27 +00:00
startTransition ( strip . getTransition ( ) ) ;
2022-11-25 16:33:29 +00:00
palette = pal ;
stateChanged = true ; // send UDP/WS broadcast
2022-09-29 10:49:12 +00:00
}
}
2022-07-06 11:13:54 +00:00
// 2D matrix
2023-10-15 11:06:40 +00:00
uint16_t IRAM_ATTR Segment : : virtualWidth ( ) const {
2024-04-26 18:11:46 +00:00
unsigned groupLen = groupLength ( ) ;
unsigned vWidth = ( ( transpose ? height ( ) : width ( ) ) + groupLen - 1 ) / groupLen ;
2022-08-06 10:39:12 +00:00
if ( mirror ) vWidth = ( vWidth + 1 ) / 2 ; // divide by 2 if mirror, leave at least a single LED
2022-07-06 11:13:54 +00:00
return vWidth ;
}
2023-10-15 11:06:40 +00:00
uint16_t IRAM_ATTR Segment : : virtualHeight ( ) const {
2024-04-26 18:11:46 +00:00
unsigned groupLen = groupLength ( ) ;
unsigned vHeight = ( ( transpose ? width ( ) : height ( ) ) + groupLen - 1 ) / groupLen ;
2022-08-06 10:39:12 +00:00
if ( mirror_y ) vHeight = ( vHeight + 1 ) / 2 ; // divide by 2 if mirror, leave at least a single LED
2022-07-06 11:13:54 +00:00
return vHeight ;
}
2023-10-15 11:06:40 +00:00
uint16_t IRAM_ATTR Segment : : nrOfVStrips ( ) const {
2024-04-26 18:11:46 +00:00
unsigned vLen = 1 ;
2022-08-25 19:57:43 +00:00
# ifndef WLED_DISABLE_2D
if ( is2D ( ) ) {
2022-08-29 19:51:46 +00:00
switch ( map1D2D ) {
case M12_pBar :
vLen = virtualWidth ( ) ;
break ;
}
2022-08-25 19:57:43 +00:00
}
# endif
return vLen ;
}
2024-05-07 22:04:29 +00:00
// Constants for mapping mode "Pinwheel"
2024-05-14 09:30:25 +00:00
# ifndef WLED_DISABLE_2D
2024-05-13 21:43:31 +00:00
constexpr int Pinwheel_Steps_Small = 72 ; // no holes up to 16x16
2024-05-14 15:30:33 +00:00
constexpr int Pinwheel_Size_Small = 16 ; // larger than this -> use "Medium"
2024-05-13 21:43:31 +00:00
constexpr int Pinwheel_Steps_Medium = 192 ; // no holes up to 32x32
2024-05-14 09:30:25 +00:00
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 ;
2024-05-13 21:43:31 +00:00
constexpr float Int_to_Rad_Small = ( DEG_TO_RAD * 360 ) / Pinwheel_Steps_Small ; // conversion: from 0...72 to Radians
2024-05-14 15:30:33 +00:00
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
2024-05-14 09:30:25 +00:00
2024-05-14 08:36:50 +00:00
constexpr int Fixed_Scale = 512 ; // fixpoint scaling factor (9bit for fraction)
2024-05-14 09:30:25 +00:00
2024-05-14 08:36:50 +00:00
// Pinwheel helper function: pixel index to radians
static float getPinwheelAngle ( int i , int vW , int vH ) {
int maxXY = max ( vW , vH ) ;
2024-05-14 15:30:33 +00:00
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 ;
// else
2024-05-14 15:46:52 +00:00
return float ( i ) * Int_to_Rad_XL ;
2024-05-14 08:36:50 +00:00
}
2024-05-14 09:30:25 +00:00
// 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 ;
// else
return Pinwheel_Steps_XL ;
}
# endif
2024-05-07 22:04:29 +00:00
2022-07-06 11:13:54 +00:00
// 1D strip
2023-10-15 11:06:40 +00:00
uint16_t IRAM_ATTR Segment : : virtualLength ( ) const {
2022-07-14 11:22:34 +00:00
# ifndef WLED_DISABLE_2D
2022-07-30 12:20:36 +00:00
if ( is2D ( ) ) {
2024-04-26 18:11:46 +00:00
unsigned vW = virtualWidth ( ) ;
unsigned vH = virtualHeight ( ) ;
2024-08-15 15:22:59 +00:00
unsigned vLen ;
2022-07-20 19:22:23 +00:00
switch ( map1D2D ) {
2022-08-25 19:57:43 +00:00
case M12_pBar :
vLen = vH ;
2022-07-14 11:22:34 +00:00
break ;
2022-08-25 19:57:43 +00:00
case M12_pCorner :
2022-07-25 19:31:50 +00:00
vLen = max ( vW , vH ) ; // get the longest dimension
2022-07-14 11:22:34 +00:00
break ;
2024-08-05 14:42:21 +00:00
case M12_pArc :
vLen = sqrt16 ( vH * vH + vW * vW ) ; // use diagonal
break ;
2024-05-07 22:04:29 +00:00
case M12_sPinwheel :
2024-05-14 09:30:25 +00:00
vLen = getPinwheelLength ( vW , vH ) ;
2024-05-07 22:04:29 +00:00
break ;
2024-08-15 15:22:59 +00:00
default :
vLen = vW * vH ; // use all pixels from segment
break ;
2022-07-14 11:22:34 +00:00
}
return vLen ;
}
# endif
2024-04-26 18:11:46 +00:00
unsigned groupLen = groupLength ( ) ; // is always >= 1
unsigned vLength = ( length ( ) + groupLen - 1 ) / groupLen ;
2022-08-06 10:39:12 +00:00
if ( mirror ) vLength = ( vLength + 1 ) / 2 ; // divide by 2 if mirror, leave at least a single LED
2022-07-06 11:13:54 +00:00
return vLength ;
}
2024-04-03 16:38:06 +00:00
// pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false)
// if clipping start > stop the clipping range is inverted
// _modeBlend==true -> old effect during transition
// _modeBlend==false -> new effect during transition
2024-08-05 14:47:25 +00:00
bool IRAM_ATTR Segment : : isPixelClipped ( int i ) const {
2024-04-03 16:38:06 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
if ( _clipStart ! = _clipStop & & blendingStyle > BLEND_STYLE_FADE ) {
2024-08-01 08:24:40 +00:00
bool invert = _clipStart > _clipStop ; // ineverted start & stop
2024-04-08 14:24:27 +00:00
int start = invert ? _clipStop : _clipStart ;
int stop = invert ? _clipStart : _clipStop ;
2024-04-03 16:38:06 +00:00
if ( blendingStyle = = BLEND_STYLE_FAIRY_DUST ) {
unsigned len = stop - start ;
if ( len < 2 ) return false ;
unsigned shuffled = hashInt ( i ) % len ;
unsigned pos = ( shuffled * 0xFFFFU ) / len ;
2024-06-02 19:30:44 +00:00
return ( progress ( ) < = pos ) ^ _modeBlend ;
2024-04-03 16:38:06 +00:00
}
const bool iInside = ( i > = start & & i < stop ) ;
2024-06-02 19:30:44 +00:00
//if (!invert && iInside) return _modeBlend;
//if ( invert && !iInside) return _modeBlend;
//return !_modeBlend;
return ! iInside ^ invert ^ _modeBlend ; // thanks @willmmiles (https://github.com/Aircoookie/WLED/pull/3877#discussion_r1554633876)
2024-04-03 16:38:06 +00:00
}
# endif
return false ;
}
2022-07-10 20:23:25 +00:00
void IRAM_ATTR Segment : : setPixelColor ( int i , uint32_t col )
{
2023-07-12 18:52:34 +00:00
if ( ! isActive ( ) ) return ; // not active
2023-03-11 14:03:28 +00:00
# ifndef WLED_DISABLE_2D
2022-08-27 16:25:54 +00:00
int vStrip = i > > 16 ; // hack to allow running on virtual strips (2D segment columns/rows)
2023-03-11 14:03:28 +00:00
# endif
2022-08-25 19:57:43 +00:00
i & = 0xFFFF ;
2024-07-30 15:26:50 +00:00
int vL = virtualLength ( ) ;
if ( i > = vL | | i < 0 ) return ; // if pixel would fall out of segment just exit
2022-08-23 13:57:05 +00:00
2022-07-14 11:22:34 +00:00
# ifndef WLED_DISABLE_2D
2022-12-16 21:31:07 +00:00
if ( is2D ( ) ) {
2024-04-26 18:11:46 +00:00
int vH = virtualHeight ( ) ; // segment height in logical pixels
int vW = virtualWidth ( ) ;
2022-07-20 19:22:23 +00:00
switch ( map1D2D ) {
2022-07-11 09:20:30 +00:00
case M12_Pixels :
2022-07-14 11:22:34 +00:00
// use all available pixels as a long strip
setPixelColorXY ( i % vW , i / vW , col ) ;
2022-07-11 09:20:30 +00:00
break ;
2022-08-25 19:57:43 +00:00
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 for ( int x = 0 ; x < vW ; x + + ) setPixelColorXY ( x , vH - i - 1 , col ) ;
2022-07-11 09:20:30 +00:00
break ;
2022-08-25 19:57:43 +00:00
case M12_pArc :
2022-07-14 11:22:34 +00:00
// expand in circular fashion from center
2022-08-22 12:35:34 +00:00
if ( i = = 0 )
setPixelColorXY ( 0 , 0 , col ) ;
else {
2024-08-05 14:42:21 +00:00
float r = i ;
float step = HALF_PI / ( 2.8284f * r + 4 ) ; // we only need (PI/4)/(r/sqrt(2)+1) steps
for ( float rad = 0.0f ; rad < = ( HALF_PI / 2 ) + step / 2 ; rad + = step ) {
int x = roundf ( sin_t ( rad ) * r ) ;
int y = roundf ( cos_t ( rad ) * r ) ;
// exploit symmetry
2022-08-22 12:35:34 +00:00
setPixelColorXY ( x , y , col ) ;
2024-08-05 14:42:21 +00:00
setPixelColorXY ( y , x , col ) ;
2022-08-22 12:35:34 +00:00
}
2023-01-12 18:13:07 +00:00
// Bresenham’ s Algorithm (may not fill every pixel)
2023-01-06 08:10:39 +00:00
//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;
// }
//}
2022-07-11 09:20:30 +00:00
}
break ;
2022-08-25 19:57:43 +00:00
case M12_pCorner :
2022-07-20 19:22:23 +00:00
for ( int x = 0 ; x < = i ; x + + ) setPixelColorXY ( x , i , col ) ;
for ( int y = 0 ; y < i ; y + + ) setPixelColorXY ( i , y , col ) ;
2022-07-11 09:20:30 +00:00
break ;
2024-05-07 22:04:29 +00:00
case M12_sPinwheel : {
2024-05-12 15:52:31 +00:00
// i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small)
2024-05-07 22:04:29 +00:00
float centerX = roundf ( ( vW - 1 ) / 2.0f ) ;
float centerY = roundf ( ( vH - 1 ) / 2.0f ) ;
2024-05-14 08:36:50 +00:00
float angleRad = getPinwheelAngle ( i , vW , vH ) ; // angle in radians
2024-05-10 00:38:41 +00:00
float cosVal = cos_t ( angleRad ) ;
float sinVal = sin_t ( angleRad ) ;
2024-05-07 22:04:29 +00:00
2024-05-12 11:29:04 +00:00
// avoid re-painting the same pixel
int lastX = INT_MIN ; // impossible position
int lastY = INT_MIN ; // impossible position
2024-05-07 22:04:29 +00:00
// 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
2024-05-10 00:38:41 +00:00
// 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
2024-05-07 22:04:29 +00:00
int32_t maxX = vW * Fixed_Scale ; // X edge in fixedpoint
int32_t maxY = vH * Fixed_Scale ; // Y edge in fixedpoint
2024-05-12 15:52:31 +00:00
// Odd rays start further from center if prevRay started at center.
2024-05-13 17:27:41 +00:00
static int prevRay = INT_MIN ; // previous ray number
2024-05-12 20:35:33 +00:00
if ( ( i % 2 = = 1 ) & & ( i - 1 = = prevRay | | i + 1 = = prevRay ) ) {
2024-05-13 21:43:31 +00:00
int jump = min ( vW / 3 , vH / 3 ) ; // can add 2 if using medium pinwheel
2024-05-12 21:46:42 +00:00
posx + = inc_x * jump ;
posy + = inc_y * jump ;
2024-05-12 15:52:31 +00:00
}
prevRay = i ;
2024-05-12 11:29:04 +00:00
// draw ray until we hit any edge
2024-05-13 17:27:41 +00:00
while ( ( posx > = 0 ) & & ( posy > = 0 ) & & ( posx < maxX ) & & ( posy < maxY ) ) {
2024-05-07 22:04:29 +00:00
// scale down to integer (compiler will replace division with appropriate bitshift)
int x = posx / Fixed_Scale ;
int y = posy / Fixed_Scale ;
// set pixel
2024-05-12 11:29:04 +00:00
if ( x ! = lastX | | y ! = lastY ) setPixelColorXY ( x , y , col ) ; // only paint if pixel position is different
lastX = x ;
lastY = y ;
2024-05-07 22:04:29 +00:00
// advance to next position
posx + = inc_x ;
posy + = inc_y ;
}
break ;
}
2022-07-10 20:23:25 +00:00
}
return ;
2022-12-16 21:31:07 +00:00
} else if ( Segment : : maxHeight ! = 1 & & ( width ( ) = = 1 | | height ( ) = = 1 ) ) {
2023-02-01 18:30:56 +00:00
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 ;
if ( virtualWidth ( ) > 1 ) x = i ;
setPixelColorXY ( x , y , col ) ;
return ;
}
2022-07-10 20:23:25 +00:00
}
2022-07-14 11:22:34 +00:00
# endif
2022-07-10 20:23:25 +00:00
2024-07-30 15:26:50 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2024-08-01 08:24:40 +00:00
// if we blend using "push" style we need to "shift" new mode to left or right
if ( isInTransition ( ) & & ! _modeBlend & & ( blendingStyle = = BLEND_STYLE_PUSH_RIGHT | | blendingStyle = = BLEND_STYLE_PUSH_LEFT ) ) {
2024-07-30 15:26:50 +00:00
unsigned prog = 0xFFFF - progress ( ) ;
unsigned dI = prog * vL / 0xFFFF ;
if ( blendingStyle = = BLEND_STYLE_PUSH_RIGHT ) i - = dI ;
else i + = dI ;
}
# endif
if ( i > = vL | | i < 0 | | isPixelClipped ( i ) ) return ; // handle clipping on 1D
2024-04-03 16:38:06 +00:00
2024-04-26 18:11:46 +00:00
unsigned len = length ( ) ;
2023-09-10 16:52:14 +00:00
uint8_t _bri_t = currentBri ( ) ;
2022-07-10 20:23:25 +00:00
if ( _bri_t < 255 ) {
2024-04-16 08:43:06 +00:00
col = color_fade ( col , _bri_t ) ;
2022-07-10 20:23:25 +00:00
}
// expand pixel (taking into account start, grouping, spacing [and offset])
i = i * groupLength ( ) ;
2022-08-06 10:39:12 +00:00
if ( reverse ) { // is segment reversed?
if ( mirror ) { // is segment mirrored?
2022-07-10 20:23:25 +00:00
i = ( len - 1 ) / 2 - i ; //only need to index half the pixels
} else {
i = ( len - 1 ) - i ;
}
}
i + = start ; // starting pixel in a group
2023-08-05 15:35:14 +00:00
uint32_t tmpCol = col ;
2022-07-10 20:23:25 +00:00
// set all the pixels in the group
for ( int j = 0 ; j < grouping ; j + + ) {
2023-12-28 22:32:47 +00:00
unsigned indexSet = i + ( ( reverse ) ? - j : j ) ;
2022-07-10 20:23:25 +00:00
if ( indexSet > = start & & indexSet < stop ) {
2022-08-06 10:39:12 +00:00
if ( mirror ) { //set the corresponding mirrored pixel
2023-12-28 22:32:47 +00:00
unsigned indexMir = stop - indexSet + start - 1 ;
2022-07-10 20:23:25 +00:00
indexMir + = offset ; // offset/phase
if ( indexMir > = stop ) indexMir - = len ; // wrap
2023-09-02 18:20:51 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2024-04-03 16:38:06 +00:00
// _modeBlend==true -> old effect
if ( _modeBlend & & blendingStyle = = BLEND_STYLE_FADE ) tmpCol = color_blend ( strip . getPixelColor ( indexMir ) , col , 0xFFFFU - progress ( ) , true ) ;
2023-09-02 18:20:51 +00:00
# endif
2023-08-05 15:35:14 +00:00
strip . setPixelColor ( indexMir , tmpCol ) ;
2022-07-10 20:23:25 +00:00
}
indexSet + = offset ; // offset/phase
if ( indexSet > = stop ) indexSet - = len ; // wrap
2023-09-02 18:20:51 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2024-04-03 16:38:06 +00:00
// _modeBlend==true -> old effect
if ( _modeBlend & & blendingStyle = = BLEND_STYLE_FADE ) tmpCol = color_blend ( strip . getPixelColor ( indexSet ) , col , 0xFFFFU - progress ( ) , true ) ;
2023-09-02 18:20:51 +00:00
# endif
2023-08-05 15:35:14 +00:00
strip . setPixelColor ( indexSet , tmpCol ) ;
2022-07-10 20:23:25 +00:00
}
}
}
2024-04-13 16:25:25 +00:00
# ifdef WLED_USE_AA_PIXELS
2022-07-10 20:23:25 +00:00
// anti-aliased normalized version of setPixelColor()
void Segment : : setPixelColor ( float i , uint32_t col , bool aa )
{
2023-07-12 18:52:34 +00:00
if ( ! isActive ( ) ) return ; // not active
2022-08-27 16:25:54 +00:00
int vStrip = int ( i / 10.0f ) ; // hack to allow running on virtual strips (2D segment columns/rows)
i - = int ( i ) ;
2022-07-10 20:23:25 +00:00
if ( i < 0.0f | | i > 1.0f ) return ; // not normalized
float fC = i * ( virtualLength ( ) - 1 ) ;
if ( aa ) {
2024-04-26 18:11:46 +00:00
unsigned iL = roundf ( fC - 0.49f ) ;
unsigned iR = roundf ( fC + 0.49f ) ;
2022-08-30 15:18:56 +00:00
float dL = ( fC - iL ) * ( fC - iL ) ;
float dR = ( iR - fC ) * ( iR - fC ) ;
2022-08-27 16:25:54 +00:00
uint32_t cIL = getPixelColor ( iL | ( vStrip < < 16 ) ) ;
uint32_t cIR = getPixelColor ( iR | ( vStrip < < 16 ) ) ;
2022-07-10 20:23:25 +00:00
if ( iR ! = iL ) {
// blend L pixel
cIL = color_blend ( col , cIL , uint8_t ( dL * 255.0f ) ) ;
2022-08-27 16:25:54 +00:00
setPixelColor ( iL | ( vStrip < < 16 ) , cIL ) ;
2022-07-10 20:23:25 +00:00
// blend R pixel
cIR = color_blend ( col , cIR , uint8_t ( dR * 255.0f ) ) ;
2022-08-27 16:25:54 +00:00
setPixelColor ( iR | ( vStrip < < 16 ) , cIR ) ;
2022-07-10 20:23:25 +00:00
} else {
// exact match (x & y land on a pixel)
2022-08-27 16:25:54 +00:00
setPixelColor ( iL | ( vStrip < < 16 ) , col ) ;
2022-07-10 20:23:25 +00:00
}
} else {
2024-04-26 18:11:46 +00:00
setPixelColor ( int ( roundf ( fC ) ) | ( vStrip < < 16 ) , col ) ;
2022-07-10 20:23:25 +00:00
}
}
2024-04-13 16:25:25 +00:00
# endif
2022-07-10 20:23:25 +00:00
2024-08-05 14:42:21 +00:00
uint32_t IRAM_ATTR Segment : : getPixelColor ( int i ) const
2022-07-10 20:23:25 +00:00
{
2023-07-12 18:52:34 +00:00
if ( ! isActive ( ) ) return 0 ; // not active
2023-03-11 14:03:28 +00:00
# ifndef WLED_DISABLE_2D
2022-08-27 16:25:54 +00:00
int vStrip = i > > 16 ;
2023-03-11 14:03:28 +00:00
# endif
2022-08-25 19:57:43 +00:00
i & = 0xFFFF ;
2024-07-30 15:26:50 +00:00
int vL = virtualLength ( ) ;
if ( i > = vL | | i < 0 ) return 0 ;
2022-07-14 11:22:34 +00:00
# ifndef WLED_DISABLE_2D
2022-12-16 21:31:07 +00:00
if ( is2D ( ) ) {
2024-04-26 18:11:46 +00:00
unsigned vH = virtualHeight ( ) ; // segment height in logical pixels
unsigned vW = virtualWidth ( ) ;
2022-07-20 19:22:23 +00:00
switch ( map1D2D ) {
2022-07-13 13:40:40 +00:00
case M12_Pixels :
2022-07-14 11:22:34 +00:00
return getPixelColorXY ( i % vW , i / vW ) ;
2022-07-13 13:40:40 +00:00
break ;
2022-08-25 19:57:43 +00:00
case M12_pBar :
if ( vStrip > 0 ) return getPixelColorXY ( vStrip - 1 , vH - i - 1 ) ;
else return getPixelColorXY ( 0 , vH - i - 1 ) ;
2022-07-25 19:31:50 +00:00
break ;
2022-08-25 19:57:43 +00:00
case M12_pArc :
2024-08-15 15:22:59 +00:00
if ( i > = vW & & i > = vH ) {
unsigned vI = sqrt16 ( i * i / 2 ) ;
return getPixelColorXY ( vI , vI ) ; // use diagonal
}
2022-08-25 19:57:43 +00:00
case M12_pCorner :
2022-07-25 19:31:50 +00:00
// use longest dimension
return vW > vH ? getPixelColorXY ( i , 0 ) : getPixelColorXY ( 0 , i ) ;
2022-07-13 13:40:40 +00:00
break ;
2024-05-07 22:04:29 +00:00
case M12_sPinwheel :
2024-05-14 08:36:50 +00:00
// not 100% accurate, returns pixel at outer edge
2024-05-13 21:43:31 +00:00
// 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 ) ;
2024-05-14 08:36:50 +00:00
float angleRad = getPinwheelAngle ( i , vW , vH ) ; // angle in radians
2024-05-13 21:43:31 +00:00
float cosVal = cos_t ( angleRad ) ;
float sinVal = sin_t ( 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
2024-05-14 08:36:50 +00:00
// trace ray from center until we hit any edge - to avoid rounding problems, we use the same method as in setPixelColor
2024-05-13 21:43:31 +00:00
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 ;
}
2024-05-07 22:04:29 +00:00
return getPixelColorXY ( x , y ) ;
break ;
}
2022-07-13 13:40:40 +00:00
return 0 ;
}
2022-07-14 11:22:34 +00:00
# endif
2022-07-13 13:40:40 +00:00
2024-07-30 15:26:50 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2024-08-01 08:24:40 +00:00
if ( isInTransition ( ) & & ! _modeBlend & & ( blendingStyle = = BLEND_STYLE_PUSH_RIGHT | | blendingStyle = = BLEND_STYLE_PUSH_LEFT ) ) {
2024-07-30 15:26:50 +00:00
unsigned prog = 0xFFFF - progress ( ) ;
unsigned dI = prog * vL / 0xFFFF ;
if ( blendingStyle = = BLEND_STYLE_PUSH_RIGHT ) i - = dI ;
else i + = dI ;
}
# endif
if ( i > = vL | | i < 0 | | isPixelClipped ( i ) ) return 0 ; // handle clipping on 1D
2024-04-08 14:24:27 +00:00
2024-07-30 15:26:50 +00:00
if ( reverse ) i = vL - i - 1 ;
2022-07-10 20:23:25 +00:00
i * = groupLength ( ) ;
i + = start ;
2024-08-14 20:15:48 +00:00
// offset/phase
2022-07-10 20:23:25 +00:00
i + = offset ;
2024-08-14 20:15:48 +00:00
if ( i > = stop ) i - = length ( ) ;
2022-07-10 20:23:25 +00:00
return strip . getPixelColor ( i ) ;
}
2022-08-17 18:45:30 +00:00
uint8_t Segment : : differs ( Segment & b ) const {
2022-07-06 11:13:54 +00:00
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 ;
2023-09-10 16:52:14 +00:00
//bit pattern: (msb first)
// set:2, sound:2, mapping:3, transposed, mirrorY, reverseY, [reset,] paused, mirrored, on, reverse, [selected]
if ( ( options & 0b1111111111011110U ) ! = ( b . options & 0b1111111111011110U ) ) d | = SEG_DIFFERS_OPT ;
2022-11-13 11:13:49 +00:00
if ( ( options & 0x0001U ) ! = ( b . options & 0x0001U ) ) d | = SEG_DIFFERS_SEL ;
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < NUM_COLORS ; i + + ) if ( colors [ i ] ! = b . colors [ i ] ) d | = SEG_DIFFERS_COL ;
2022-07-06 11:13:54 +00:00
return d ;
}
void Segment : : refreshLightCapabilities ( ) {
2024-04-26 18:11:46 +00:00
unsigned capabilities = 0 ;
unsigned segStartIdx = 0xFFFFU ;
unsigned segStopIdx = 0 ;
2023-02-15 19:36:54 +00:00
2023-07-12 18:52:34 +00:00
if ( ! isActive ( ) ) {
_capabilities = 0 ;
return ;
}
2023-02-15 19:36:54 +00:00
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 + + ) {
2024-04-26 18:11:46 +00:00
unsigned index = strip . getMappedPixelIndex ( x + Segment : : maxWidth * y ) ; // convert logical address to physical
2023-02-15 19:36:54 +00:00
if ( index < 0xFFFFU ) {
if ( segStartIdx > index ) segStartIdx = index ;
if ( segStopIdx < index ) segStopIdx = index ;
}
2023-02-28 18:08:41 +00:00
if ( segStartIdx = = segStopIdx ) segStopIdx + + ; // we only have 1 pixel segment
2023-02-15 19:36:54 +00:00
}
} else {
// we are on the strip located after the matrix
segStartIdx = start ;
segStopIdx = stop ;
}
2022-07-06 11:13:54 +00:00
2023-12-29 22:07:29 +00:00
for ( unsigned b = 0 ; b < BusManager : : getNumBusses ( ) ; b + + ) {
Bus * bus = BusManager : : getBus ( b ) ;
2022-07-06 11:13:54 +00:00
if ( bus = = nullptr | | bus - > getLength ( ) = = 0 ) break ;
if ( ! bus - > isOk ( ) ) continue ;
2023-02-15 19:36:54 +00:00
if ( bus - > getStart ( ) > = segStopIdx ) continue ;
if ( bus - > getStart ( ) + bus - > getLength ( ) < = segStartIdx ) continue ;
2022-07-06 11:13:54 +00:00
2023-02-14 16:11:58 +00:00
//uint8_t type = bus->getType();
2024-08-17 13:09:41 +00:00
if ( bus - > hasRGB ( ) | | ( strip . cctFromRgb & & bus - > hasCCT ( ) ) ) capabilities | = SEG_CAPABILITY_RGB ;
if ( ! strip . cctFromRgb & & bus - > hasCCT ( ) ) capabilities | = SEG_CAPABILITY_CCT ;
if ( strip . correctWB & & ( bus - > hasRGB ( ) | | bus - > hasCCT ( ) ) ) capabilities | = SEG_CAPABILITY_CCT ; //white balance correction (CCT slider)
2023-02-14 00:33:06 +00:00
if ( bus - > hasWhite ( ) ) {
2024-04-26 18:11:46 +00:00
unsigned aWM = Bus : : getGlobalAWMode ( ) = = AW_GLOBAL_DISABLED ? bus - > getAutoWhiteMode ( ) : Bus : : getGlobalAWMode ( ) ;
2023-02-14 00:33:06 +00:00
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 ;
2022-07-06 11:13:54 +00:00
}
}
_capabilities = capabilities ;
}
2022-07-10 20:23:25 +00:00
/*
* Fills segment with color
*/
void Segment : : fill ( uint32_t c ) {
2023-07-12 18:52:34 +00:00
if ( ! isActive ( ) ) return ; // not active
2024-04-26 18:11:46 +00:00
const int cols = is2D ( ) ? virtualWidth ( ) : virtualLength ( ) ;
const int rows = virtualHeight ( ) ; // will be 1 for 1D
2023-09-10 16:52:14 +00:00
for ( int y = 0 ; y < rows ; y + + ) for ( int x = 0 ; x < cols ; x + + ) {
2022-07-30 12:20:36 +00:00
if ( is2D ( ) ) setPixelColorXY ( x , y , c ) ;
else setPixelColor ( x , c ) ;
2022-07-10 20:23:25 +00:00
}
}
/*
* fade out function , higher rate = quicker fade
*/
void Segment : : fade_out ( uint8_t rate ) {
2023-07-12 18:52:34 +00:00
if ( ! isActive ( ) ) return ; // not active
2024-04-26 18:11:46 +00:00
const int cols = is2D ( ) ? virtualWidth ( ) : virtualLength ( ) ;
const int rows = virtualHeight ( ) ; // will be 1 for 1D
2022-07-10 20:23:25 +00:00
rate = ( 255 - rate ) > > 1 ;
2024-08-05 14:42:21 +00:00
float mappedRate = 1.0f / ( float ( rate ) + 1.1f ) ;
2022-07-10 20:23:25 +00:00
uint32_t color = colors [ 1 ] ; // SEGCOLOR(1); // target color
int w2 = W ( color ) ;
int r2 = R ( color ) ;
int g2 = G ( color ) ;
int b2 = B ( color ) ;
2023-09-10 16:52:14 +00:00
for ( int y = 0 ; y < rows ; y + + ) for ( int x = 0 ; x < cols ; x + + ) {
2022-07-30 12:20:36 +00:00
color = is2D ( ) ? getPixelColorXY ( x , y ) : getPixelColor ( x ) ;
2024-08-05 14:42:21 +00:00
if ( color = = colors [ 1 ] ) continue ; // already at target color
2022-07-10 20:23:25 +00:00
int w1 = W ( color ) ;
int r1 = R ( color ) ;
int g1 = G ( color ) ;
int b1 = B ( color ) ;
2024-08-05 14:42:21 +00:00
int wdelta = ( w2 - w1 ) * mappedRate ;
int rdelta = ( r2 - r1 ) * mappedRate ;
int gdelta = ( g2 - g1 ) * mappedRate ;
int bdelta = ( b2 - b1 ) * mappedRate ;
2022-07-10 20:23:25 +00:00
// 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 ;
2022-07-30 12:20:36 +00:00
if ( is2D ( ) ) setPixelColorXY ( x , y , r1 + rdelta , g1 + gdelta , b1 + bdelta , w1 + wdelta ) ;
else setPixelColor ( x , r1 + rdelta , g1 + gdelta , b1 + bdelta , w1 + wdelta ) ;
2022-07-10 20:23:25 +00:00
}
}
// fades all pixels to black using nscale8()
void Segment : : fadeToBlackBy ( uint8_t fadeBy ) {
2023-07-12 18:52:34 +00:00
if ( ! isActive ( ) | | fadeBy = = 0 ) return ; // optimization - no scaling to apply
2024-04-26 18:11:46 +00:00
const int cols = is2D ( ) ? virtualWidth ( ) : virtualLength ( ) ;
const int rows = virtualHeight ( ) ; // will be 1 for 1D
2022-07-10 20:23:25 +00:00
2023-09-10 16:52:14 +00:00
for ( int y = 0 ; y < rows ; y + + ) for ( int x = 0 ; x < cols ; x + + ) {
if ( is2D ( ) ) setPixelColorXY ( x , y , color_fade ( getPixelColorXY ( x , y ) , 255 - fadeBy ) ) ;
else setPixelColor ( x , color_fade ( getPixelColor ( x ) , 255 - fadeBy ) ) ;
2022-07-10 20:23:25 +00:00
}
}
/*
* blurs segment content , source : FastLED colorutils . cpp
*/
2024-04-15 19:20:45 +00:00
void Segment : : blur ( uint8_t blur_amount , bool smear ) {
2023-07-12 18:52:34 +00:00
if ( ! isActive ( ) | | blur_amount = = 0 ) return ; // optimization: 0 means "don't blur"
2022-07-30 12:20:36 +00:00
# ifndef WLED_DISABLE_2D
if ( is2D ( ) ) {
2022-07-10 20:23:25 +00:00
// compatibility with 2D
2024-08-14 20:15:48 +00:00
blur2D ( blur_amount , smear ) ;
//box_blur(map(blur_amount,1,255,1,3), smear);
2022-07-10 20:23:25 +00:00
return ;
}
2022-07-30 12:20:36 +00:00
# endif
2024-04-16 08:43:06 +00:00
uint8_t keep = smear ? 255 : 255 - blur_amount ;
2024-08-14 20:15:48 +00:00
uint8_t seep = blur_amount > > ( 1 + smear ) ;
2023-09-10 16:52:14 +00:00
unsigned vlength = virtualLength ( ) ;
2024-04-15 19:20:45 +00:00
uint32_t carryover = BLACK ;
uint32_t lastnew ;
uint32_t last ;
2024-04-23 17:05:49 +00:00
uint32_t curnew = BLACK ;
2023-09-10 16:52:14 +00:00
for ( unsigned i = 0 ; i < vlength ; i + + ) {
uint32_t cur = getPixelColor ( i ) ;
uint32_t part = color_fade ( cur , seep ) ;
2024-04-16 08:43:06 +00:00
curnew = color_fade ( cur , keep ) ;
2023-09-10 16:52:14 +00:00
if ( i > 0 ) {
2024-08-14 20:15:48 +00:00
if ( carryover ) curnew = color_add ( curnew , carryover , true ) ;
2024-04-15 19:20:45 +00:00
uint32_t prev = color_add ( lastnew , part , true ) ;
2024-08-14 20:15:48 +00:00
// optimization: only set pixel if color has changed
if ( last ! = prev ) setPixelColor ( i - 1 , prev ) ;
} else // first pixel
2024-04-15 19:20:45 +00:00
setPixelColor ( i , curnew ) ;
lastnew = curnew ;
last = cur ; // save original value for comparison on next iteration
2022-07-10 20:23:25 +00:00
carryover = part ;
}
2024-04-15 19:20:45 +00:00
setPixelColor ( vlength - 1 , curnew ) ;
2022-07-10 20:23:25 +00:00
}
/*
* 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 .
*/
2024-08-05 14:42:21 +00:00
uint32_t Segment : : color_wheel ( uint8_t pos ) const {
2024-01-22 19:48:03 +00:00
if ( palette ) return color_from_palette ( pos , false , true , 0 ) ; // perhaps "strip.paletteBlend < 2" should be better instead of "true"
2023-12-09 21:39:26 +00:00
uint8_t w = W ( currentColor ( 0 ) ) ;
2022-07-10 20:23:25 +00:00
pos = 255 - pos ;
2023-09-10 16:52:14 +00:00
if ( pos < 85 ) {
2023-12-09 21:39:26 +00:00
return RGBW32 ( ( 255 - pos * 3 ) , 0 , ( pos * 3 ) , w ) ;
2022-07-10 20:23:25 +00:00
} else if ( pos < 170 ) {
pos - = 85 ;
2023-12-09 21:39:26 +00:00
return RGBW32 ( 0 , ( pos * 3 ) , ( 255 - pos * 3 ) , w ) ;
2022-07-10 20:23:25 +00:00
} else {
pos - = 170 ;
2023-12-09 21:39:26 +00:00
return RGBW32 ( ( pos * 3 ) , ( 255 - pos * 3 ) , 0 , w ) ;
2022-07-10 20:23:25 +00:00
}
}
/*
* 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
2023-01-11 10:21:45 +00:00
* @ param wrap FastLED palettes will usually wrap back to the start smoothly . Set false to get a hard edge
2022-07-10 20:23:25 +00:00
* @ 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
*/
2024-08-05 14:42:21 +00:00
uint32_t Segment : : color_from_palette ( uint16_t i , bool mapping , bool wrap , uint8_t mcol , uint8_t pbri ) const {
2023-12-09 21:39:26 +00:00
uint32_t color = gamma32 ( currentColor ( mcol ) ) ;
2022-07-28 21:19:58 +00:00
// default palette or no RGB support on segment
2024-06-02 19:30:44 +00:00
if ( ( currentPalette ( ) = = 0 & & mcol < NUM_COLORS ) | | ! _isRGB ) return ( pbri = = 255 ) ? color : color_fade ( color , pbri , true ) ;
2022-07-10 20:23:25 +00:00
2024-04-26 18:11:46 +00:00
unsigned paletteIndex = i ;
2022-07-10 20:23:25 +00:00
if ( mapping & & virtualLength ( ) > 1 ) paletteIndex = ( i * 255 ) / ( virtualLength ( ) - 1 ) ;
2024-01-22 19:48:03 +00:00
// paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined)
2023-09-10 16:52:14 +00:00
if ( ! wrap & & strip . paletteBlend ! = 3 ) paletteIndex = scale8 ( paletteIndex , 240 ) ; //cut off blend at palette "end"
2024-05-15 13:34:53 +00:00
CRGB fastled_col = ColorFromPalette ( _currentPalette , paletteIndex , pbri , ( strip . paletteBlend = = 3 ) ? NOBLEND : LINEARBLEND ) ; // NOTE: paletteBlend should be global
2022-07-10 20:23:25 +00:00
2023-12-09 21:39:26 +00:00
return RGBW32 ( fastled_col . r , fastled_col . g , fastled_col . b , W ( color ) ) ;
2022-07-10 20:23:25 +00:00
}
///////////////////////////////////////////////////////////////////////////////
// WS2812FX class implementation
///////////////////////////////////////////////////////////////////////////////
2022-07-06 11:13:54 +00:00
2021-01-18 19:51:32 +00:00
//do not call this method from system context (network callback)
2023-10-15 11:06:40 +00:00
void WS2812FX : : finalizeInit ( void ) {
2022-02-09 07:43:35 +00:00
//reset segment runtimes
2022-07-17 13:58:41 +00:00
for ( segment & seg : _segments ) {
seg . markForReset ( ) ;
seg . resetIfRequired ( ) ;
2022-02-09 07:43:35 +00:00
}
2022-08-10 18:53:11 +00:00
// 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
2024-06-23 12:08:18 +00:00
// the other option is saving UI settings which will cause enumeration
2022-08-10 18:53:11 +00:00
enumerateLedmaps ( ) ;
2022-02-04 12:28:00 +00:00
_hasWhiteChannel = _isOffRefreshRequired = false ;
2019-11-29 17:53:01 +00:00
2021-03-12 22:56:29 +00:00
//if busses failed to load, add default (fresh install, FS issue, ...)
2023-12-29 22:07:29 +00:00
if ( BusManager : : getNumBusses ( ) = = 0 ) {
2022-03-19 13:21:14 +00:00
DEBUG_PRINTLN ( F ( " No busses, init default " ) ) ;
2024-04-23 17:05:49 +00:00
const unsigned defDataPins [ ] = { DATA_PINS } ;
const unsigned defCounts [ ] = { PIXEL_COUNTS } ;
const unsigned defNumPins = ( ( sizeof defDataPins ) / ( sizeof defDataPins [ 0 ] ) ) ;
2024-07-11 19:22:58 +00:00
const unsigned defNumCounts = ( ( sizeof defCounts ) / ( sizeof defCounts [ 0 ] ) ) ;
// if number of pins is divisible by counts, use number of counts to determine number of buses, otherwise use pins
const unsigned defNumBusses = defNumPins > defNumCounts & & defNumPins % defNumCounts = = 0 ? defNumCounts : defNumPins ;
2024-04-23 17:05:49 +00:00
const unsigned pinsPerBus = defNumPins / defNumBusses ;
unsigned prevLen = 0 ;
for ( unsigned i = 0 ; i < defNumBusses & & i < WLED_MAX_BUSSES + WLED_MIN_VIRTUAL_BUSSES ; i + + ) {
uint8_t defPin [ 5 ] ; // max 5 pins
for ( unsigned j = 0 ; j < pinsPerBus ; j + + ) defPin [ j ] = defDataPins [ i * pinsPerBus + j ] ;
2024-03-24 16:37:11 +00:00
// when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware
// i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), etc
if ( pinManager . isPinAllocated ( defPin [ 0 ] ) ) {
defPin [ 0 ] = 1 ; // start with GPIO1 and work upwards
while ( pinManager . isPinAllocated ( defPin [ 0 ] ) & & defPin [ 0 ] < WLED_NUM_PINS ) defPin [ 0 ] + + ;
}
2024-04-23 17:05:49 +00:00
unsigned start = prevLen ;
2024-07-11 19:22:58 +00:00
// if we have less counts than pins and they do not align, use last known count to set current count
2024-04-23 17:05:49 +00:00
unsigned count = defCounts [ ( i < defNumCounts ) ? i : defNumCounts - 1 ] ;
2021-04-15 08:55:22 +00:00
prevLen + = count ;
2024-06-29 18:22:47 +00:00
BusConfig defCfg = BusConfig ( DEFAULT_LED_TYPE , defPin , start , count , DEFAULT_LED_COLOR_ORDER , false , 0 , RGBW_MODE_MANUAL_ONLY , 0 , useGlobalLedBuffer ) ;
2023-12-29 22:07:29 +00:00
if ( BusManager : : add ( defCfg ) = = - 1 ) break ;
2021-04-15 08:55:22 +00:00
}
2021-01-30 19:51:36 +00:00
}
2021-01-18 19:51:32 +00:00
2021-02-24 19:23:32 +00:00
_length = 0 ;
2023-12-29 22:07:29 +00:00
for ( int i = 0 ; i < BusManager : : getNumBusses ( ) ; i + + ) {
Bus * bus = BusManager : : getBus ( i ) ;
2021-02-24 19:23:32 +00:00
if ( bus = = nullptr ) continue ;
2021-10-11 00:19:33 +00:00
if ( bus - > getStart ( ) + bus - > getLength ( ) > MAX_LEDS ) break ;
2021-05-21 22:13:49 +00:00
//RGBW mode is enabled if at least one of the strips is RGBW
2023-02-14 00:33:06 +00:00
_hasWhiteChannel | = bus - > hasWhite ( ) ;
2021-05-21 22:13:49 +00:00
//refresh is required to remain off if at least one of the strips requires the refresh.
2022-02-04 12:28:00 +00:00
_isOffRefreshRequired | = bus - > isOffRefreshRequired ( ) ;
2024-04-26 18:11:46 +00:00
unsigned busEnd = bus - > getStart ( ) + bus - > getLength ( ) ;
2021-10-11 00:19:33 +00:00
if ( busEnd > _length ) _length = busEnd ;
2021-06-24 00:29:14 +00:00
# ifdef ESP8266
2024-06-23 12:08:18 +00:00
// why do we need to reinitialise GPIO3???
//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();
2021-06-24 00:29:14 +00:00
# endif
2021-02-24 19:23:32 +00:00
}
2021-09-11 23:00:54 +00:00
2023-12-28 22:32:47 +00:00
Segment : : maxWidth = _length ;
Segment : : maxHeight = 1 ;
2022-12-22 17:13:32 +00:00
2022-08-05 21:03:38 +00:00
//segments are created in makeAutoSegments();
2023-02-12 12:18:30 +00:00
DEBUG_PRINTLN ( F ( " Loading custom palettes " ) ) ;
2022-11-16 19:55:21 +00:00
loadCustomPalettes ( ) ; // (re)load all custom palettes
2023-02-12 12:18:30 +00:00
DEBUG_PRINTLN ( F ( " Loading custom ledmaps " ) ) ;
2023-12-28 22:32:47 +00:00
deserializeMap ( ) ; // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
2019-02-09 15:37:20 +00:00
}
void WS2812FX : : service ( ) {
2023-07-12 18:52:34 +00:00
unsigned long nowUp = millis ( ) ; // Be aware, millis() rolls over every 49 days
2019-11-26 19:41:15 +00:00
now = nowUp + timebase ;
2023-12-22 14:39:07 +00:00
if ( nowUp - _lastShow < MIN_SHOW_DELAY | | _suspend ) return ;
2019-02-11 22:49:04 +00:00
bool doShow = false ;
2024-06-02 19:30:44 +00:00
int pal = - 1 ; // optimise palette loading
2020-05-10 21:58:50 +00:00
2022-07-19 14:16:43 +00:00
_isServicing = true ;
2022-07-17 13:58:41 +00:00
_segment_index = 0 ;
2024-01-29 18:06:41 +00:00
2022-07-17 13:58:41 +00:00
for ( segment & seg : _segments ) {
2024-01-04 16:40:23 +00:00
if ( _suspend ) return ; // immediately stop processing segments if suspend requested during service()
2023-12-22 14:39:07 +00:00
2023-06-04 16:36:46 +00:00
// process transition (mode changes in the middle of transition)
seg . handleTransition ( ) ;
2022-07-19 14:16:43 +00:00
// reset the segment runtime data if needed
2022-07-17 13:58:41 +00:00
seg . resetIfRequired ( ) ;
2020-12-10 01:55:14 +00:00
2023-08-09 15:23:21 +00:00
if ( ! seg . isActive ( ) ) continue ;
2022-03-10 19:40:48 +00:00
// last condition ensures all solid segments are updated at the same time
2023-08-05 11:50:08 +00:00
if ( nowUp > seg . next_time | | _triggered | | ( doShow & & seg . mode = = FX_MODE_STATIC ) )
2019-02-09 15:37:20 +00:00
{
2023-07-06 19:16:29 +00:00
doShow = true ;
2024-04-26 18:11:46 +00:00
unsigned delay = FRAMETIME ;
2021-01-08 23:35:48 +00:00
2022-08-06 10:39:12 +00:00
if ( ! seg . freeze ) { //only run effect function if not frozen
2024-04-26 18:11:46 +00:00
int oldCCT = BusManager : : getSegmentCCT ( ) ; // store original CCT value (actually it is not Segment based)
2023-12-01 23:48:49 +00:00
_virtualSegmentLength = seg . virtualLength ( ) ; //SEGLEN
_colors_t [ 0 ] = gamma32 ( seg . currentColor ( 0 ) ) ;
_colors_t [ 1 ] = gamma32 ( seg . currentColor ( 1 ) ) ;
_colors_t [ 2 ] = gamma32 ( seg . currentColor ( 2 ) ) ;
2024-06-02 19:30:44 +00:00
if ( seg . currentPalette ( ) ! = pal ) seg . setCurrentPalette ( ) ; // load actual palette
pal = seg . currentPalette ( ) ;
2024-03-16 11:36:05 +00:00
// when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio
// when cctFromRgb is true we implicitly calculate WW and CW from RGB values
if ( cctFromRgb ) BusManager : : setSegmentCCT ( - 1 ) ;
else BusManager : : setSegmentCCT ( seg . currentBri ( true ) , correctWB ) ;
2023-08-12 10:45:11 +00:00
// Effect blending
// When two effects are being blended, each may have different segment data, this
2023-09-10 16:52:14 +00:00
// data needs to be saved first and then restored before running previous mode.
2023-08-05 11:50:08 +00:00
// The blending will largely depend on the effect behaviour since actual output (LEDs) may be
// overwritten by later effect. To enable seamless blending for every effect, additional LED buffer
// would need to be allocated for each effect and then blended together for each pixel.
2023-09-02 18:20:51 +00:00
# ifndef WLED_DISABLE_MODE_BLEND
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( 0 , 0 ) ; // disable clipping (just in case)
2024-06-02 19:30:44 +00:00
if ( seg . isInTransition ( ) ) {
2024-04-03 16:38:06 +00:00
// set clipping rectangle
// new mode is run inside clipping area and old mode outside clipping area
unsigned p = seg . progress ( ) ;
unsigned w = seg . is2D ( ) ? seg . virtualWidth ( ) : _virtualSegmentLength ;
unsigned h = seg . virtualHeight ( ) ;
unsigned dw = p * w / 0xFFFFU + 1 ;
unsigned dh = p * h / 0xFFFFU + 1 ;
switch ( blendingStyle ) {
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_FAIRY_DUST : // fairy dust (must set entire segment, see isPixelXYClipped())
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( 0 , w , 0 , h ) ;
2024-04-03 16:38:06 +00:00
break ;
case BLEND_STYLE_SWIPE_RIGHT : // left-to-right
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_PUSH_RIGHT : // left-to-right
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( 0 , dw , 0 , h ) ;
2024-04-03 16:38:06 +00:00
break ;
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_SWIPE_LEFT : // right-to-left
case BLEND_STYLE_PUSH_LEFT : // right-to-left
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( w - dw , w , 0 , h ) ;
2024-04-03 16:38:06 +00:00
break ;
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_PINCH_OUT : // corners
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( ( w + dw ) / 2 , ( w - dw ) / 2 , ( h + dh ) / 2 , ( h - dh ) / 2 ) ; // inverted!!
2024-04-03 16:38:06 +00:00
break ;
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_INSIDE_OUT : // outward
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( ( w - dw ) / 2 , ( w + dw ) / 2 , ( h - dh ) / 2 , ( h + dh ) / 2 ) ;
2024-04-03 16:38:06 +00:00
break ;
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_SWIPE_DOWN : // top-to-bottom (2D)
case BLEND_STYLE_PUSH_DOWN : // top-to-bottom (2D)
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( 0 , w , 0 , dh ) ;
2024-04-03 16:38:06 +00:00
break ;
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_SWIPE_UP : // bottom-to-top (2D)
case BLEND_STYLE_PUSH_UP : // bottom-to-top (2D)
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( 0 , w , h - dh , h ) ;
2024-04-03 16:38:06 +00:00
break ;
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_OPEN_H : // horizontal-outward (2D) same look as INSIDE_OUT on 1D
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( ( w - dw ) / 2 , ( w + dw ) / 2 , 0 , h ) ;
2024-04-03 16:38:06 +00:00
break ;
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_OPEN_V : // vertical-outward (2D)
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( 0 , w , ( h - dh ) / 2 , ( h + dh ) / 2 ) ;
2024-04-03 16:38:06 +00:00
break ;
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_PUSH_TL : // TL-to-BR (2D)
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( 0 , dw , 0 , dh ) ;
2024-04-03 16:38:06 +00:00
break ;
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_PUSH_TR : // TR-to-BL (2D)
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( w - dw , w , 0 , dh ) ;
2024-04-03 16:38:06 +00:00
break ;
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_PUSH_BR : // BR-to-TL (2D)
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( w - dw , w , h - dh , h ) ;
2024-04-03 16:38:06 +00:00
break ;
2024-08-01 08:24:40 +00:00
case BLEND_STYLE_PUSH_BL : // BL-to-TR (2D)
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( 0 , dw , h - dh , h ) ;
2024-04-03 16:38:06 +00:00
break ;
}
}
2024-07-05 19:23:59 +00:00
delay = ( * _mode [ seg . currentMode ( ) ] ) ( ) ; // run new/current mode
2024-06-02 19:30:44 +00:00
if ( seg . isInTransition ( ) ) {
2023-08-12 10:45:11 +00:00
Segment : : tmpsegd_t _tmpSegData ;
2023-08-05 19:01:06 +00:00
Segment : : modeBlend ( true ) ; // set semaphore
2023-08-12 10:45:11 +00:00
seg . swapSegenv ( _tmpSegData ) ; // temporarily store new mode state (and swap it with transitional state)
2023-12-01 23:48:49 +00:00
_virtualSegmentLength = seg . virtualLength ( ) ; // update SEGLEN (mapping may have changed)
2024-07-05 19:23:59 +00:00
_colors_t [ 0 ] = gamma32 ( seg . currentColor ( 0 ) ) ;
_colors_t [ 1 ] = gamma32 ( seg . currentColor ( 1 ) ) ;
_colors_t [ 2 ] = gamma32 ( seg . currentColor ( 2 ) ) ;
if ( seg . currentPalette ( ) ! = pal ) {
seg . setCurrentPalette ( ) ; // load actual palette
pal = seg . currentPalette ( ) ;
}
unsigned d2 = ( * _mode [ seg . currentMode ( ) ] ) ( ) ; // run old mode
2023-08-12 10:45:11 +00:00
seg . restoreSegenv ( _tmpSegData ) ; // restore mode state (will also update transitional state)
2023-08-05 15:35:14 +00:00
delay = MIN ( delay , d2 ) ; // use shortest delay
2023-08-12 10:45:11 +00:00
Segment : : modeBlend ( false ) ; // unset semaphore
2023-08-05 11:50:08 +00:00
}
2024-04-03 16:38:06 +00:00
# else
delay = ( * _mode [ seg . mode ] ) ( ) ; // run effect mode
2023-09-02 18:20:51 +00:00
# endif
2023-10-30 16:05:27 +00:00
seg . call + + ;
2023-09-10 16:52:14 +00:00
if ( seg . isInTransition ( ) & & delay > FRAMETIME ) delay = FRAMETIME ; // force faster updates during transition
2024-04-08 14:24:27 +00:00
BusManager : : setSegmentCCT ( oldCCT ) ; // restore old CCT for ABL adjustments
2019-06-20 12:40:12 +00:00
}
2021-01-08 23:35:48 +00:00
2022-07-17 13:58:41 +00:00
seg . next_time = nowUp + delay ;
2019-02-09 15:37:20 +00:00
}
2022-07-17 13:58:41 +00:00
_segment_index + + ;
2019-02-09 15:37:20 +00:00
}
2024-04-08 14:24:27 +00:00
Segment : : setClippingRect ( 0 , 0 ) ; // disable clipping for overlays
2022-07-06 11:13:54 +00:00
_virtualSegmentLength = 0 ;
2023-07-12 18:52:34 +00:00
_isServicing = false ;
_triggered = false ;
# ifdef WLED_DEBUG
2024-03-29 15:43:37 +00:00
if ( millis ( ) - nowUp > _frametime ) DEBUG_PRINTF_P ( PSTR ( " Slow effects %u/%d. \n " ) , ( unsigned ) ( millis ( ) - nowUp ) , ( int ) _frametime ) ;
2023-07-12 18:52:34 +00:00
# endif
if ( doShow ) {
2019-03-05 09:59:15 +00:00
yield ( ) ;
2024-05-18 09:11:40 +00:00
Segment : : handleRandomPalette ( ) ; // slowly transition random palette; move it into for loop when each segment has individual random palette
2019-02-11 22:49:04 +00:00
show ( ) ;
}
2023-07-12 18:52:34 +00:00
# ifdef WLED_DEBUG
2024-03-29 15:43:37 +00:00
if ( millis ( ) - nowUp > _frametime ) DEBUG_PRINTF_P ( PSTR ( " Slow strip %u/%d. \n " ) , ( unsigned ) ( millis ( ) - nowUp ) , ( int ) _frametime ) ;
2023-07-12 18:52:34 +00:00
# endif
2019-02-09 15:37:20 +00:00
}
2023-12-28 22:32:47 +00:00
void IRAM_ATTR WS2812FX : : setPixelColor ( unsigned i , uint32_t col ) {
2023-11-23 16:13:13 +00:00
i = getMappedPixelIndex ( i ) ;
2023-03-11 14:03:28 +00:00
if ( i > = _length ) return ;
2023-12-29 22:07:29 +00:00
BusManager : : setPixelColor ( i , col ) ;
2019-02-09 15:37:20 +00:00
}
2024-08-14 20:15:48 +00:00
uint32_t IRAM_ATTR WS2812FX : : getPixelColor ( uint16_t i ) const {
2023-11-23 16:13:13 +00:00
i = getMappedPixelIndex ( i ) ;
2023-03-11 14:03:28 +00:00
if ( i > = _length ) return 0 ;
2023-12-29 22:07:29 +00:00
return BusManager : : getPixelColor ( i ) ;
2022-07-10 20:23:25 +00:00
}
2021-10-11 00:19:53 +00:00
void WS2812FX : : show ( void ) {
2024-02-06 13:47:20 +00:00
// avoid race condition, capture _callback value
2023-07-12 18:52:34 +00:00
show_callback callback = _callback ;
if ( callback ) callback ( ) ;
2020-12-10 04:29:53 +00:00
// 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
2023-12-29 22:07:29 +00:00
BusManager : : show ( ) ;
2023-07-03 19:13:01 +00:00
2023-10-12 14:15:16 +00:00
unsigned long showNow = millis ( ) ;
size_t diff = showNow - _lastShow ;
2023-07-12 18:52:34 +00:00
size_t fpsCurr = 200 ;
if ( diff > 0 ) fpsCurr = 1000 / diff ;
2023-07-13 10:49:19 +00:00
_cumulativeFps = ( 3 * _cumulativeFps + fpsCurr + 2 ) > > 2 ; // "+2" for proper rounding (2/4 = 0.5)
2023-10-12 14:15:16 +00:00
_lastShow = showNow ;
2019-02-09 15:37:20 +00:00
}
2020-12-10 04:29:53 +00:00
/**
* Returns a true value if any of the strips are still being updated .
* On some hardware ( ESP32 ) , strip updates are done asynchronously .
*/
2024-08-14 20:15:48 +00:00
bool WS2812FX : : isUpdating ( ) const {
2023-12-29 22:07:29 +00:00
return ! BusManager : : canAllShow ( ) ;
2020-12-10 04:29:53 +00:00
}
2021-02-05 00:33:26 +00:00
/**
* Returns the refresh rate of the LED strip . Useful for finding out whether a given setup is fast enough .
2024-02-06 13:47:20 +00:00
* Only updates on show ( ) or is set to 0 fps if last show is more than 2 secs ago , so accuracy varies
2021-02-05 00:33:26 +00:00
*/
2024-08-14 20:15:48 +00:00
uint16_t WS2812FX : : getFps ( ) const {
2021-02-05 00:33:26 +00:00
if ( millis ( ) - _lastShow > 2000 ) return 0 ;
return _cumulativeFps + 1 ;
}
2021-12-25 00:30:27 +00:00
void WS2812FX : : setTargetFps ( uint8_t fps ) {
2022-08-24 21:04:51 +00:00
if ( fps > 0 & & fps < = 120 ) _targetFps = fps ;
_frametime = 1000 / _targetFps ;
2021-12-25 00:30:27 +00:00
}
2019-06-20 12:40:12 +00:00
void WS2812FX : : setMode ( uint8_t segid , uint8_t m ) {
2022-07-17 13:58:41 +00:00
if ( segid > = _segments . size ( ) ) return ;
2023-01-06 08:10:39 +00:00
2022-06-26 21:01:22 +00:00
if ( m > = getModeCount ( ) ) m = getModeCount ( ) - 1 ;
2019-06-20 12:40:12 +00:00
2022-07-17 13:58:41 +00:00
if ( _segments [ segid ] . mode ! = m ) {
2023-08-09 15:23:21 +00:00
_segments [ segid ] . setMode ( m ) ; // do not load defaults
2019-06-20 12:40:12 +00:00
}
2019-02-09 15:37:20 +00:00
}
2022-02-20 21:24:11 +00:00
//applies to all active and selected segments
2019-06-20 12:40:12 +00:00
void WS2812FX : : setColor ( uint8_t slot , uint32_t c ) {
if ( slot > = NUM_COLORS ) return ;
2020-03-14 10:28:42 +00:00
2022-07-17 13:58:41 +00:00
for ( segment & seg : _segments ) {
2022-09-16 12:07:04 +00:00
if ( seg . isActive ( ) & & seg . isSelected ( ) ) {
2022-07-17 13:58:41 +00:00
seg . setColor ( slot , c ) ;
2019-12-02 11:41:35 +00:00
}
2020-03-14 10:28:42 +00:00
}
2019-02-09 15:37:20 +00:00
}
2022-02-23 18:20:07 +00:00
void WS2812FX : : setCCT ( uint16_t k ) {
2022-07-17 13:58:41 +00:00
for ( segment & seg : _segments ) {
if ( seg . isActive ( ) & & seg . isSelected ( ) ) {
seg . setCCT ( k ) ;
2022-02-23 18:20:07 +00:00
}
}
}
2023-07-19 14:22:34 +00:00
// direct=true either expects the caller to call show() themselves (realtime modes) or be ok waiting for the next frame for the change to apply
// direct=false immediately triggers an effect redraw
2022-03-10 19:40:48 +00:00
void WS2812FX : : setBrightness ( uint8_t b , bool direct ) {
2020-12-14 22:32:57 +00:00
if ( gammaCorrectBri ) b = gamma8 ( b ) ;
2019-02-09 15:37:20 +00:00
if ( _brightness = = b ) return ;
2020-12-14 22:32:57 +00:00
_brightness = b ;
if ( _brightness = = 0 ) { //unfreeze all segments on power off
2022-07-17 13:58:41 +00:00
for ( segment & seg : _segments ) {
2022-08-19 19:14:49 +00:00
seg . freeze = false ;
2020-08-29 20:26:39 +00:00
}
}
2023-07-19 15:25:25 +00:00
// setting brightness with NeoPixelBusLg has no effect on already painted pixels,
// so we need to force an update to existing buffer
2023-12-29 22:07:29 +00:00
BusManager : : setBrightness ( b ) ;
2023-07-19 15:25:25 +00:00
if ( ! direct ) {
2022-08-24 21:04:51 +00:00
unsigned long t = millis ( ) ;
2023-07-18 21:33:28 +00:00
if ( _segments [ 0 ] . next_time > t + 22 & & t - _lastShow > MIN_SHOW_DELAY ) trigger ( ) ; //apply brightness change immediately if no refresh soon
2022-03-10 19:40:48 +00:00
}
2019-02-09 15:37:20 +00:00
}
2024-08-14 20:15:48 +00:00
uint8_t WS2812FX : : getActiveSegsLightCapabilities ( bool selectedOnly ) const {
2023-05-31 18:12:17 +00:00
uint8_t totalLC = 0 ;
2024-08-14 20:15:48 +00:00
for ( const segment & seg : _segments ) {
2023-05-31 18:12:17 +00:00
if ( seg . isActive ( ) & & ( ! selectedOnly | | seg . isSelected ( ) ) ) totalLC | = seg . getLightCapabilities ( ) ;
}
return totalLC ;
}
2024-08-14 20:15:48 +00:00
uint8_t WS2812FX : : getFirstSelectedSegId ( void ) const {
2022-07-17 13:58:41 +00:00
size_t i = 0 ;
2024-08-14 20:15:48 +00:00
for ( const segment & seg : _segments ) {
2022-09-16 12:07:04 +00:00
if ( seg . isActive ( ) & & seg . isSelected ( ) ) return i ;
2022-07-17 13:58:41 +00:00
i + + ;
2019-06-20 12:40:12 +00:00
}
2022-02-23 18:20:07 +00:00
// if none selected, use the main segment
return getMainSegmentId ( ) ;
}
void WS2812FX : : setMainSegmentId ( uint8_t n ) {
2022-02-20 21:24:11 +00:00
_mainSegment = 0 ;
2022-07-17 13:58:41 +00:00
if ( n < _segments . size ( ) ) {
_mainSegment = n ;
}
2022-02-20 21:24:11 +00:00
return ;
}
2019-06-20 12:40:12 +00:00
2024-08-14 20:15:48 +00:00
uint8_t WS2812FX : : getLastActiveSegmentId ( void ) const {
2022-08-05 21:03:38 +00:00
for ( size_t i = _segments . size ( ) - 1 ; i > 0 ; i - - ) {
if ( _segments [ i ] . isActive ( ) ) return i ;
}
return 0 ;
2019-06-20 12:40:12 +00:00
}
2024-08-14 20:15:48 +00:00
uint8_t WS2812FX : : getActiveSegmentsNum ( void ) const {
2022-07-19 14:16:43 +00:00
uint8_t c = 0 ;
2022-07-19 20:14:46 +00:00
for ( size_t i = 0 ; i < _segments . size ( ) ; i + + ) {
2022-07-19 14:16:43 +00:00
if ( _segments [ i ] . isActive ( ) ) c + + ;
}
return c ;
}
2021-09-20 19:22:50 +00:00
2024-08-14 20:15:48 +00:00
uint16_t WS2812FX : : getLengthTotal ( void ) const {
2024-04-26 18:11:46 +00:00
unsigned len = Segment : : maxWidth * Segment : : maxHeight ; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D
2023-03-11 14:03:28 +00:00
if ( isMatrix & & _length > len ) len = _length ; // for 2D with trailing strip
return len ;
}
2024-08-14 20:15:48 +00:00
uint16_t WS2812FX : : getLengthPhysical ( void ) const {
2024-04-26 18:11:46 +00:00
unsigned len = 0 ;
2023-12-29 22:07:29 +00:00
for ( size_t b = 0 ; b < BusManager : : getNumBusses ( ) ; b + + ) {
Bus * bus = BusManager : : getBus ( b ) ;
2021-10-11 00:19:53 +00:00
if ( bus - > getType ( ) > = TYPE_NET_DDP_RGB ) continue ; //exclude non-physical network busses
len + = bus - > getLength ( ) ;
}
return len ;
}
2022-02-20 21:24:11 +00:00
//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
2024-08-14 20:15:48 +00:00
bool WS2812FX : : hasRGBWBus ( void ) const {
2023-12-29 22:07:29 +00:00
for ( size_t b = 0 ; b < BusManager : : getNumBusses ( ) ; b + + ) {
Bus * bus = BusManager : : getBus ( b ) ;
2022-02-20 21:24:11 +00:00
if ( bus = = nullptr | | bus - > getLength ( ) = = 0 ) break ;
2023-02-14 00:33:06 +00:00
if ( bus - > hasRGB ( ) & & bus - > hasWhite ( ) ) return true ;
2022-02-20 21:24:11 +00:00
}
2022-08-24 21:04:51 +00:00
return false ;
2022-02-20 21:24:11 +00:00
}
2024-08-14 20:15:48 +00:00
bool WS2812FX : : hasCCTBus ( void ) const {
2022-08-24 21:04:51 +00:00
if ( cctFromRgb & & ! correctWB ) return false ;
2023-12-29 22:07:29 +00:00
for ( size_t b = 0 ; b < BusManager : : getNumBusses ( ) ; b + + ) {
Bus * bus = BusManager : : getBus ( b ) ;
2021-11-28 03:01:58 +00:00
if ( bus = = nullptr | | bus - > getLength ( ) = = 0 ) break ;
2024-03-16 11:36:05 +00:00
if ( bus - > hasCCT ( ) ) return true ;
2021-11-28 03:01:58 +00:00
}
2022-08-24 21:04:51 +00:00
return false ;
2021-11-28 03:01:58 +00:00
}
2023-12-22 14:39:07 +00:00
void WS2812FX : : purgeSegments ( ) {
2022-07-17 13:58:41 +00:00
// remove all inactive segments (from the back)
int deleted = 0 ;
2022-08-03 19:36:47 +00:00
if ( _segments . size ( ) < = 1 ) return ;
2022-08-05 21:03:38 +00:00
for ( size_t i = _segments . size ( ) - 1 ; i > 0 ; i - - )
2023-12-22 14:39:07 +00:00
if ( _segments [ i ] . stop = = 0 ) {
2022-07-19 14:16:43 +00:00
deleted + + ;
_segments . erase ( _segments . begin ( ) + i ) ;
}
2022-07-17 13:58:41 +00:00
if ( deleted ) {
_segments . shrink_to_fit ( ) ;
2023-12-22 14:39:07 +00:00
setMainSegmentId ( 0 ) ;
2022-07-17 13:58:41 +00:00
}
}
2022-07-19 14:16:43 +00:00
Segment & WS2812FX : : getSegment ( uint8_t id ) {
return _segments [ id > = _segments . size ( ) ? getMainSegmentId ( ) : id ] ; // vectors
}
2023-07-13 11:08:36 +00:00
// sets new segment bounds, queues if that segment is currently running
void WS2812FX : : setSegment ( uint8_t segId , uint16_t i1 , uint16_t i2 , uint8_t grouping , uint8_t spacing , uint16_t offset , uint16_t startY , uint16_t stopY ) {
if ( segId > = getSegmentsNum ( ) ) {
if ( i2 < = i1 ) return ; // do not append empty/inactive segments
appendSegment ( Segment ( 0 , strip . getLengthTotal ( ) ) ) ;
segId = getSegmentsNum ( ) - 1 ; // segments are added at the end of list
}
2023-12-22 14:39:07 +00:00
suspend ( ) ;
2023-07-13 11:08:36 +00:00
_segments [ segId ] . setUp ( i1 , i2 , grouping , spacing , offset , startY , stopY ) ;
2023-12-22 14:39:07 +00:00
resume ( ) ;
2023-09-10 16:52:14 +00:00
if ( segId > 0 & & segId = = getSegmentsNum ( ) - 1 & & i2 < = i1 ) _segments . pop_back ( ) ; // if last segment was deleted remove it from vector
2023-07-13 11:08:36 +00:00
}
2024-03-29 15:43:37 +00:00
2019-02-09 15:37:20 +00:00
void WS2812FX : : resetSegments ( ) {
2022-07-17 13:58:41 +00:00
_segments . clear ( ) ; // destructs all Segment as part of clearing
2022-07-30 08:49:54 +00:00
# ifndef WLED_DISABLE_2D
2022-12-16 21:31:07 +00:00
segment seg = isMatrix ? Segment ( 0 , Segment : : maxWidth , 0 , Segment : : maxHeight ) : Segment ( 0 , _length ) ;
2022-07-30 08:49:54 +00:00
# else
segment seg = Segment ( 0 , _length ) ;
# endif
2022-07-17 13:58:41 +00:00
_segments . push_back ( seg ) ;
2024-01-24 19:49:22 +00:00
_segments . shrink_to_fit ( ) ; // just in case ...
2022-07-17 13:58:41 +00:00
_mainSegment = 0 ;
2019-02-09 15:37:20 +00:00
}
2022-02-10 15:09:16 +00:00
void WS2812FX : : makeAutoSegments ( bool forceReset ) {
2023-02-25 08:41:15 +00:00
if ( autoSegments ) { //make one segment per bus
2024-04-26 18:11:46 +00:00
unsigned segStarts [ MAX_NUM_SEGMENTS ] = { 0 } ;
unsigned segStops [ MAX_NUM_SEGMENTS ] = { 0 } ;
2023-02-25 08:41:15 +00:00
size_t s = 0 ;
2022-07-10 20:23:25 +00:00
# ifndef WLED_DISABLE_2D
2023-02-25 08:41:15 +00:00
// 2D segment is the 1st one using entire matrix
if ( isMatrix ) {
segStarts [ 0 ] = 0 ;
segStops [ 0 ] = Segment : : maxWidth * Segment : : maxHeight ;
s + + ;
2023-02-01 18:30:56 +00:00
}
2022-07-10 20:23:25 +00:00
# endif
2023-02-25 08:41:15 +00:00
2023-12-29 22:07:29 +00:00
for ( size_t i = s ; i < BusManager : : getNumBusses ( ) ; i + + ) {
Bus * b = BusManager : : getBus ( i ) ;
2021-10-11 00:19:33 +00:00
segStarts [ s ] = b - > getStart ( ) ;
2022-07-17 13:58:41 +00:00
segStops [ s ] = segStarts [ s ] + b - > getLength ( ) ;
2021-10-11 00:19:33 +00:00
2023-02-25 08:41:15 +00:00
# ifndef WLED_DISABLE_2D
2024-05-18 09:11:40 +00:00
if ( isMatrix & & segStops [ s ] < = Segment : : maxWidth * Segment : : maxHeight ) continue ; // ignore buses comprising matrix
2023-02-25 08:41:15 +00:00
if ( isMatrix & & segStarts [ s ] < Segment : : maxWidth * Segment : : maxHeight ) segStarts [ s ] = Segment : : maxWidth * Segment : : maxHeight ;
# endif
2021-10-11 00:19:33 +00:00
//check for overlap with previous segments
2022-08-05 21:03:38 +00:00
for ( size_t j = 0 ; j < s ; j + + ) {
2021-10-11 00:19:33 +00:00
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 + + ;
}
2023-02-25 08:41:15 +00:00
2022-07-17 13:58:41 +00:00
_segments . clear ( ) ;
2023-02-01 18:30:56 +00:00
_segments . reserve ( s ) ; // prevent reallocations
2023-02-25 08:41:15 +00:00
// 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 ] ) ) ;
2021-09-11 23:00:54 +00:00
}
2024-05-18 09:11:40 +00:00
DEBUG_PRINTF_P ( PSTR ( " %d auto segments created. \n " ) , _segments . size ( ) ) ;
2023-02-25 08:41:15 +00:00
2021-09-11 23:00:54 +00:00
} else {
2023-02-25 08:41:15 +00:00
2022-07-19 20:14:46 +00:00
if ( forceReset | | getSegmentsNum ( ) = = 0 ) resetSegments ( ) ;
2022-02-10 15:09:16 +00:00
//expand the main seg to the entire length, but only if there are no other segments, or reset is forced
2022-07-17 13:58:41 +00:00
else if ( getActiveSegmentsNum ( ) = = 1 ) {
2022-07-19 20:14:46 +00:00
size_t i = getLastActiveSegmentId ( ) ;
2023-02-25 08:41:15 +00:00
# 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
2022-07-19 20:14:46 +00:00
_segments [ i ] . start = 0 ;
_segments [ i ] . stop = _length ;
2023-02-25 08:41:15 +00:00
# endif
2021-10-11 00:19:33 +00:00
}
}
2023-02-25 08:41:15 +00:00
_mainSegment = 0 ;
2021-10-11 00:19:33 +00:00
fixInvalidSegments ( ) ;
}
void WS2812FX : : fixInvalidSegments ( ) {
//make sure no segment is longer than total (sanity check)
2022-08-05 21:03:38 +00:00
for ( size_t i = getSegmentsNum ( ) - 1 ; i > 0 ; i - - ) {
2023-02-14 16:11:58 +00:00
if ( isMatrix ) {
# ifndef WLED_DISABLE_2D
2023-02-15 19:36:54 +00:00
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 ;
2023-02-14 16:11:58 +00:00
# endif
} else {
if ( _segments [ i ] . start > = _length ) { _segments . erase ( _segments . begin ( ) + i ) ; continue ; }
if ( _segments [ i ] . stop > _length ) _segments [ i ] . stop = _length ;
}
2021-10-11 00:19:33 +00:00
}
2024-05-18 09:11:40 +00:00
// if any segments were deleted free memory
purgeSegments ( ) ;
2023-02-15 19:36:54 +00:00
// this is always called as the last step after finalizeInit(), update covered bus types
for ( segment & seg : _segments )
seg . refreshLightCapabilities ( ) ;
2021-10-11 00:19:33 +00:00
}
//true if all segments align with a bus, or if a segment covers the total length
2023-02-11 17:41:30 +00:00
//irrelevant in 2D set-up
2021-10-11 00:19:33 +00:00
bool WS2812FX : : checkSegmentAlignment ( ) {
2022-07-17 13:58:41 +00:00
bool aligned = false ;
for ( segment & seg : _segments ) {
2023-12-29 22:07:29 +00:00
for ( unsigned b = 0 ; b < BusManager : : getNumBusses ( ) ; b + + ) {
Bus * bus = BusManager : : getBus ( b ) ;
2022-07-17 13:58:41 +00:00
if ( seg . start = = bus - > getStart ( ) & & seg . stop = = bus - > getStart ( ) + bus - > getLength ( ) ) aligned = true ;
2021-10-11 00:19:33 +00:00
}
2022-07-17 13:58:41 +00:00
if ( seg . start = = 0 & & seg . stop = = _length ) aligned = true ;
2021-10-11 00:19:33 +00:00
if ( ! aligned ) return false ;
2021-02-27 11:06:14 +00:00
}
2021-10-11 00:19:33 +00:00
return true ;
2021-02-27 11:06:14 +00:00
}
2024-01-22 19:48:03 +00:00
// used by analog clock overlay
2023-02-01 18:30:56 +00:00
void WS2812FX : : setRange ( uint16_t i , uint16_t i2 , uint32_t col ) {
2023-09-10 16:52:14 +00:00
if ( i2 < i ) std : : swap ( i , i2 ) ;
for ( unsigned x = i ; x < = i2 ; x + + ) setPixelColor ( x , col ) ;
2019-02-09 15:37:20 +00:00
}
2022-08-03 19:36:47 +00:00
# ifdef WLED_DEBUG
2023-02-01 18:30:56 +00:00
void WS2812FX : : printSize ( ) {
2022-08-03 19:36:47 +00:00
size_t size = 0 ;
2022-08-29 19:51:46 +00:00
for ( const Segment & seg : _segments ) size + = seg . getSize ( ) ;
2024-05-18 09:11:40 +00:00
DEBUG_PRINTF_P ( PSTR ( " Segments: %d -> %u/%dB \n " ) , _segments . size ( ) , size , Segment : : getUsedSegmentData ( ) ) ;
for ( const Segment & seg : _segments ) DEBUG_PRINTF_P ( PSTR ( " Seg: %d,%d [A=%d, 2D=%d, RGB=%d, W=%d, CCT=%d] \n " ) , seg . width ( ) , seg . height ( ) , seg . isActive ( ) , seg . is2D ( ) , seg . hasRGB ( ) , seg . hasWhite ( ) , seg . isCCT ( ) ) ;
2024-02-17 10:33:42 +00:00
DEBUG_PRINTF_P ( PSTR ( " Modes: %d*%d=%uB \n " ) , sizeof ( mode_ptr ) , _mode . size ( ) , ( _mode . capacity ( ) * sizeof ( mode_ptr ) ) ) ;
DEBUG_PRINTF_P ( PSTR ( " Data: %d*%d=%uB \n " ) , sizeof ( const char * ) , _modeData . size ( ) , ( _modeData . capacity ( ) * sizeof ( const char * ) ) ) ;
DEBUG_PRINTF_P ( PSTR ( " Map: %d*%d=%uB \n " ) , sizeof ( uint16_t ) , ( int ) customMappingSize , customMappingSize * sizeof ( uint16_t ) ) ;
2022-08-03 19:36:47 +00:00
}
# endif
2023-02-01 18:30:56 +00:00
void WS2812FX : : loadCustomPalettes ( ) {
2022-07-10 20:23:25 +00:00
byte tcp [ 72 ] ; //support gradient palettes with up to 18 entries
2022-07-28 21:19:58 +00:00
CRGBPalette16 targetPalette ;
2022-11-16 19:55:21 +00:00
customPalettes . clear ( ) ; // start fresh
2022-07-28 21:19:58 +00:00
for ( int index = 0 ; index < 10 ; index + + ) {
2022-07-10 20:23:25 +00:00
char fileName [ 32 ] ;
2022-10-04 20:10:20 +00:00
sprintf_P ( fileName , PSTR ( " /palette%d.json " ) , index ) ;
2022-07-10 20:23:25 +00:00
2022-07-28 21:19:58 +00:00
StaticJsonDocument < 1536 > pDoc ; // barely enough to fit 72 numbers
2022-07-10 20:23:25 +00:00
if ( WLED_FS . exists ( fileName ) ) {
DEBUG_PRINT ( F ( " Reading palette from " ) ) ;
DEBUG_PRINTLN ( fileName ) ;
if ( readObjectFromFile ( fileName , nullptr , & pDoc ) ) {
JsonArray pal = pDoc [ F ( " palette " ) ] ;
2023-10-01 19:15:14 +00:00
if ( ! pal . isNull ( ) & & pal . size ( ) > 3 ) { // not an empty palette (at least 2 entries)
2022-12-18 20:02:19 +00:00
if ( pal [ 0 ] . is < int > ( ) & & pal [ 1 ] . is < const char * > ( ) ) {
// we have an array of index & hex strings
size_t palSize = MIN ( pal . size ( ) , 36 ) ;
palSize - = palSize % 2 ; // make sure size is multiple of 2
for ( size_t 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
2023-10-01 19:15:14 +00:00
for ( size_t c = 0 ; c < 3 ; c + + ) tcp [ j + 1 + c ] = gamma8 ( rgbw [ c ] ) ; // only use RGB component
2024-02-17 10:33:42 +00:00
DEBUG_PRINTF_P ( PSTR ( " %d(%d) : %d %d %d \n " ) , i , int ( tcp [ j ] ) , int ( tcp [ j + 1 ] ) , int ( tcp [ j + 2 ] ) , int ( tcp [ j + 3 ] ) ) ;
2022-12-18 20:02:19 +00:00
}
} else {
size_t palSize = MIN ( pal . size ( ) , 72 ) ;
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
2023-10-01 19:15:14 +00:00
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
2024-02-17 10:33:42 +00:00
DEBUG_PRINTF_P ( PSTR ( " %d(%d) : %d %d %d \n " ) , i , int ( tcp [ i ] ) , int ( tcp [ i + 1 ] ) , int ( tcp [ i + 2 ] ) , int ( tcp [ i + 3 ] ) ) ;
2022-12-18 20:02:19 +00:00
}
2022-07-10 20:23:25 +00:00
}
2022-07-28 21:19:58 +00:00
customPalettes . push_back ( targetPalette . loadDynamicGradientPalette ( tcp ) ) ;
2023-10-01 19:15:14 +00:00
} else {
DEBUG_PRINTLN ( F ( " Wrong palette format. " ) ) ;
2022-07-10 20:23:25 +00:00
}
}
2022-07-28 21:19:58 +00:00
} else {
break ;
2019-02-11 22:49:04 +00:00
}
2019-02-09 15:37:20 +00:00
}
}
2021-11-03 13:52:22 +00:00
//load custom mapping table from JSON file (called from finalizeInit() or deserializeState())
2023-02-11 17:41:30 +00:00
bool WS2812FX : : deserializeMap ( uint8_t n ) {
2023-02-10 18:49:43 +00:00
// 2D support creates its own ledmap (on the fly) if a ledmap.json exists it will overwrite built one.
2022-05-08 08:50:48 +00:00
2021-09-11 23:15:51 +00:00
char fileName [ 32 ] ;
strcpy_P ( fileName , PSTR ( " /ledmap " ) ) ;
if ( n ) sprintf ( fileName + 7 , " %d " , n ) ;
2023-09-10 16:52:14 +00:00
strcat_P ( fileName , PSTR ( " .json " ) ) ;
2021-04-04 11:54:34 +00:00
bool isFile = WLED_FS . exists ( fileName ) ;
2023-12-28 22:32:47 +00:00
customMappingSize = 0 ; // prevent use of mapping if anything goes wrong
2024-06-23 12:08:18 +00:00
currentLedmap = 0 ;
if ( n = = 0 | | isFile ) interfaceUpdateCallMode = CALL_MODE_WS_SEND ; // schedule WS update (to inform UI)
2023-12-28 22:32:47 +00:00
if ( ! isFile & & n = = 0 & & isMatrix ) {
setUpMatrix ( ) ;
2023-02-11 17:41:30 +00:00
return false ;
2021-04-04 11:54:34 +00:00
}
2021-02-13 00:02:14 +00:00
2024-06-23 12:08:18 +00:00
if ( ! isFile | | ! requestJSONBufferLock ( 7 ) ) return false ;
2021-02-13 00:02:14 +00:00
2023-12-21 20:30:17 +00:00
if ( ! readObjectFromFile ( fileName , nullptr , pDoc ) ) {
2023-12-28 22:32:47 +00:00
DEBUG_PRINT ( F ( " ERROR Invalid ledmap in " ) ) ; DEBUG_PRINTLN ( fileName ) ;
2021-11-12 22:33:10 +00:00
releaseJSONBufferLock ( ) ;
2023-12-28 22:32:47 +00:00
return false ; // if file does not load properly then exit
2021-11-03 13:52:22 +00:00
}
2021-02-13 00:02:14 +00:00
2024-04-23 11:04:17 +00:00
JsonObject root = pDoc - > as < JsonObject > ( ) ;
// if we are loading default ledmap (at boot) set matrix width and height from the ledmap (compatible with WLED MM ledmaps)
if ( isMatrix & & n = = 0 & & ( ! root [ F ( " width " ) ] . isNull ( ) | | ! root [ F ( " height " ) ] . isNull ( ) ) ) {
Segment : : maxWidth = min ( max ( root [ F ( " width " ) ] . as < int > ( ) , 1 ) , 128 ) ;
Segment : : maxHeight = min ( max ( root [ F ( " height " ) ] . as < int > ( ) , 1 ) , 128 ) ;
}
2024-04-09 06:25:07 +00:00
if ( customMappingTable ) delete [ ] customMappingTable ;
customMappingTable = new uint16_t [ getLengthTotal ( ) ] ;
if ( customMappingTable ) {
DEBUG_PRINT ( F ( " Reading LED map from " ) ) ; DEBUG_PRINTLN ( fileName ) ;
JsonArray map = root [ F ( " map " ) ] ;
if ( ! map . isNull ( ) & & map . size ( ) ) { // not an empty map
customMappingSize = min ( ( unsigned ) map . size ( ) , ( unsigned ) getLengthTotal ( ) ) ;
for ( unsigned i = 0 ; i < customMappingSize ; i + + ) customMappingTable [ i ] = ( uint16_t ) ( map [ i ] < 0 ? 0xFFFFU : map [ i ] ) ;
2024-06-23 12:08:18 +00:00
currentLedmap = n ;
2024-04-09 06:25:07 +00:00
}
} else {
DEBUG_PRINTLN ( F ( " ERROR LED map allocation error. " ) ) ;
2021-02-13 00:02:14 +00:00
}
2021-11-03 13:52:22 +00:00
2021-11-12 22:33:10 +00:00
releaseJSONBufferLock ( ) ;
2024-04-09 06:25:07 +00:00
return ( customMappingSize > 0 ) ;
2021-02-13 00:02:14 +00:00
}
2024-08-14 20:15:48 +00:00
uint16_t IRAM_ATTR WS2812FX : : getMappedPixelIndex ( uint16_t index ) const {
2023-11-29 19:12:31 +00:00
// convert logical address to physical
if ( index < customMappingSize
& & ( realtimeMode = = REALTIME_MODE_INACTIVE | | realtimeRespectLedMaps ) ) index = customMappingTable [ index ] ;
2023-11-23 16:13:13 +00:00
return index ;
}
2019-12-31 10:11:05 +00:00
2021-11-24 10:02:25 +00:00
WS2812FX * WS2812FX : : instance = nullptr ;
2021-11-30 21:52:17 +00:00
2022-11-11 02:10:41 +00:00
const char JSON_mode_names [ ] PROGMEM = R " =====([ " FX names moved " ])===== " ;
2021-12-28 17:09:52 +00:00
const char JSON_palette_names [ ] PROGMEM = R " =====([
" Default " , " * Random Cycle " , " * 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 " ,
2022-08-31 19:21:53 +00:00
" Pastel " , " Sunset 2 " , " Beach " , " Vintage " , " Departure " , " Landscape " , " Beech " , " Sherbet " , " Hult " , " Hult 64 " ,
2021-12-28 17:09:52 +00:00
" 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 "
] ) = = = = = " ;